In a Java/JPA project I usually define a base class (on which I put the id). The goal is to implement an generic toString() on this base class (named BaseEntity) from which all other entities inherit. We played around with apache commons ReflectionToStringBuilder
, but we ran into problems because of cyclic references and the lack of different handling depending on the type of the fields.
My wish is that, if an Entity is printed/logged I get all field names and their values except for fields of type collection, in this case we just want the IDs of these objects to avoid recursion (PS: We can assume that collections on entities always contain other entities and they have an ID)
The desired output pattern is the following
classname[fieldname1=value1, fieldname2=value2,..., collectionname1=[id1,id2,..], collectionname2=[id1,id2,...],...]
a sample output for an entity "Project" which subclasses BaseEntity may look like this:
Project[nameLong=Testproject, nameShort=tpr, support=false, active=true, priority=0, blockers=[20], responsibilities=[13,14,15]]
We came up with the following implementation using reflection:
@Override
public String toString() {
// init container for field names
List<String> fieldToPrintout = new ArrayList<String>(0);
// get all fields from this object (use declared fields to get private
// and protected fields)
Field[] fieldsOnTheObject = this.getClass().getDeclaredFields();
// set the accessibility for field which are private
AccessibleObject.setAccessible(fieldsOnTheObject, true);
for (Field field : fieldsOnTheObject) {
try {
// ignore static fields
if (!Modifier.isStatic(field.getModifiers())) {
// get the object from the field
Object fieldValue = field.get(this);
// check if the field is a collection
if (Collection.class.isAssignableFrom(field.getType())) {
// check if the collection generic type is BaseEntity
if (field.getGenericType() instanceof ParameterizedType) {
Class<?> genericType = (Class<?>) ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0];
if (BaseEntity.class.isAssignableFrom(genericType)) {
// iterate over all BaseEntities and get all id's
List<String> ids = ((Collection<BaseEntity>) fieldValue).stream().map(p -> p.getId().toString()).collect(Collectors.toList());
fieldToPrintout.add(field.getName() + "=[" + String.join(",", ids) + "]");
}
}
} else { // --> no collection
// note: Field.get(Object) creates wrapper objects for primitive
fieldToPrintout.add(field.getName() + "=" + fieldValue);
}
}
} catch (IllegalAccessException ex) {
logger.catching(ex);
}
}
return MessageFormat.format("{0}[{1}]", this.getClass().getSimpleName(), String.join(", ", fieldToPrintout));
}
I'm currently not really happy with the code, I'm sure it can be improved/simplified. Any suggestions?