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

79 Views Asked by At

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.

1

There are 1 best solutions below

1
Manfred On BEST ANSWER

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);
}