Search code examples
javareflectionarchunit

How to check if a generic parameter is serializable in an ArchUnit test?


I want to write an ArchUnit test to check that all fields in serializable classes are either static, transient, or are Serializable. This means that for all non-primitive types, I have to check if they implement serializable (recursively if these fields are of a non-primitive type).

For collections, I want to check their generics if they are serializable because the serializability of collections depends on this.

So far my rule is laid out like this:

fields().that().areDeclaredInClassesThat().implement(Serializable.class)
        .should().beStatic().orShould().haveModifier(JavaModifier.TRANSIENT)
        .orShould(new SerializableCondition()).check(classes);

where SerializableCondition implements ArchCondition with the following condition:

private boolean isSerializable(JavaClass javaClass) {
      if (javaClass.isPrimitive()) {
        return true;
      }

      // Recursively check if array components are serializable
      if (javaClass.isArray()) {
        return isSerializable(javaClass.getComponentType());
      }

      // This check does not work as expected
      if (javaClass.isAssignableTo(Collection.class)) {
        var x = ((ParameterizedType)
            javaClass.reflect().getGenericInterfaces()[0])
            .getActualTypeArguments()[0];

        return x instanceof Serializable;
      }

      return javaClass.isAssignableTo(Serializable.class);
    }

Obviously, I would like the check for collections to be recursive just like the array one, but right now I want to get it working for simple cases.


Solution

  • To me, it's neither obvious from your question how you use your isSerializable from your SerializableCondition (probably somehow by evaluating it on the tested JavaField's rawType?), nor what // This check does not work as expected means exactly, but I would try something like this:

    ArchRule rule = fields()
        .that().areDeclaredInClassesThat().implement(Serializable.class)
        .should().beStatic()
        .orShould().haveModifier(JavaModifier.TRANSIENT)
        .orShould(ArchConditions.be(DescribedPredicate.describe("serializable",
            field -> isSerializable(field.getType())
        )));
    
    static boolean isSerializable(JavaType type) {
        JavaClass rawType = type.toErasure();
        return rawType.isPrimitive()
            || rawType.isArray()
                && isSerializable(rawType.getComponentType())
            || rawType.isAssignableTo(Collection.class)
                && type instanceof JavaParameterizedType
                && isSerializable(((JavaParameterizedType) type).getActualTypeArguments().get(0))
            || rawType.isAssignableTo(Serializable.class);
    }