Mahatma_Fatal_Error
Mahatma_Fatal_Error

Reputation: 779

How to assert HashMap keys to be Comparable with ArchUnit?

Background: https://dev.to/carey/java-map-keys-should-always-be-comparable-2c1b

What I want to achieve:

  1. Find code that uses HashMap.
  2. Figure out the type of the HashMaps key.
  3. Check if the key type implements Comparable interface.
  4. (optional) Check if that type resides in a certain package.

I a stuck at step 2

noClasses().that()
        .containAnyFieldsThat(have(rawType(HashMap.class)))
        .should()...

Any ideas how I can get the type of a generic class? Thanks for support.

Upvotes: 0

Views: 357

Answers (1)

Manfred
Manfred

Reputation: 3142

The crucial part of your ArchRule (which can be directly based on fields() or codeUnits()) can be expressed with custom ArchConditions:

@ArchTest
static final ArchRule fields_of_type_HashMap_should_have_Comparable_key = fields()
    .that().haveRawType(HashMap.class)
    .should(haveComparableFirstTypeParameter());

@ArchTest
static final ArchRule code_units_should_have_parameters_of_type_HashMap_with_Comparable_key = codeUnits()
    .should(new ArchCondition<JavaCodeUnit>("have parameters of type HashMap with Comparable key") {
        @Override
        public void check(JavaCodeUnit javaCodeUnit, ConditionEvents events) {
            javaCodeUnit.getParameters().forEach(parameter -> {
                if (parameter.getRawType().isEquivalentTo(HashMap.class)) {
                    haveComparableFirstTypeParameter().check(parameter, events);
                }
            });
        }
    });

@ArchTest
static final ArchRule methods_with_return_type_HashMap_should_have_return_types_with_Comparable_key = methods()
    .that().haveRawReturnType(HashMap.class)
    .should(new ArchCondition<JavaMethod>("have return type with Comparable key") {
        @Override
        public void check(JavaMethod method, ConditionEvents events) {
            class ReturnType implements HasType, HasDescription {
                @Override
                public JavaType getType() { return method.getReturnType(); }
                @Override
                public JavaClass getRawType() { return method.getRawReturnType(); }
                @Override
                public String getDescription() { return "Return type <" + getType().getName() + "> of " + method.getDescription(); }
            }
            haveComparableFirstTypeParameter().check(new ReturnType(), events);
        }
    });

private static <T extends HasType & HasDescription> ArchCondition<T> haveComparableFirstTypeParameter() {
    return new ArchCondition<T>("have Comparable first type parameter") {
        @Override
        public void check(T typed, ConditionEvents events) {
            JavaType fieldType = typed.getType();
            if (fieldType instanceof JavaParameterizedType) {
                JavaType keyType = ((JavaParameterizedType) fieldType).getActualTypeArguments().get(0);
                boolean satisfied = keyType.toErasure().getAllRawInterfaces().stream()
                        .anyMatch(rawInterface -> rawInterface.isEquivalentTo(Comparable.class));
                String message = String.format("%s has a first type parameter %s that %s Comparable",
                        typed.getDescription(), keyType.getName(), satisfied ? "is" : "is not");
                events.add(new SimpleConditionEvent(typed, satisfied, message));
            } else {
                events.add(SimpleConditionEvent.violated(typed, typed.getDescription() + " is not parameterized"));
            }
        }
    };
}

Upvotes: 2

Related Questions