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