Reputation: 420
I have classes which are automatically created from a JSON schema. In my Example one of these classes is called AvroPropertie
.
Declaring and initializing objects from this class looks like this:
AvroPropertie aPropertie = AvroPropertie.newBuilder()
.setName(properties.get("name").asText())
.setDate(properties.get("date").asText())
.build();
If the JSON schema differs the variables like name
and date
differ as well. There could be more, less or totally different ones. It's always only these setters and the build()
method at the end.
Is there a way to generate this code automatically? Maybe reflection?
More context: I use the ApacheAvro Serializing and deserializing with code generation.
Ty!
Upvotes: 1
Views: 2401
Reputation: 38635
Using Reflection
you can:
static
newBuilder
method to create builder.fields
/setters
for given POJO
class. If in all cases setters
on builder and setters
on POJO
have the same name like in your example it should be easy to convert.build
on builder instance to create new POJO
.Using basic Java
Reflection
and Stream
API
-s it could look like below:
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Properties;
import java.util.StringJoiner;
import java.util.function.Function;
import java.util.stream.Stream;
public class ReflectionApp {
public static void main(String[] args) throws Exception {
Properties properties = new Properties();
properties.put("name", "John");
properties.put("date", "today");
AvroPropertie manualInstance = AvroPropertie.newBuilder()
.setName(properties.getProperty("name"))
.setDate(properties.getProperty("date"))
.build();
Object dynamicInstance = AvroAutoCoder.createAndSet(AvroPropertie.class, properties::getProperty);
System.out.println(manualInstance);
System.out.println(dynamicInstance);
System.out.println("manualInstance == dynamicInstance => " + manualInstance.equals(dynamicInstance));
}
}
class AvroAutoCoder {
public static Object createAndSet(Class clazz, Function<String, String> dataSupplier) throws Exception {
Object builderInstance = findMethod(clazz, "newBuilder")
.invoke(null);
Class<?> builderClass = builderInstance.getClass();
getSetters(clazz).forEach(setter -> {
try {
String fieldName = setter.getName().substring(3).toLowerCase();
findMethod(builderClass, setter.getName())
.invoke(builderInstance, dataSupplier.apply(fieldName));
} catch (Exception e) {
throw new IllegalStateException(e);
}
});
return findMethod(builderClass, "build")
.invoke(builderInstance);
}
private static Method findMethod(Class clazz, String methodName) {
return Arrays.stream(clazz.getDeclaredMethods())
.filter(method -> method.getName().equals(methodName))
.findFirst()
.orElseThrow(IllegalArgumentException::new);
}
private static Stream<Method> getSetters(Class clazz) {
return Arrays.stream(clazz.getDeclaredMethods())
.filter(method -> method.getParameterCount() == 1 && method.getReturnType() == Void.TYPE)
.filter(method -> method.getName().startsWith("set"));
}
}
Above code prints:
AvroPropertie[name='John', date='today']
AvroPropertie[name='John', date='today']
manualInstance == dynamicInstance => true
In this specific example class APropertie
has a static inner class named Builder
which holds the wanted setters. In this case the getSetters()
methods needs to be changed a little bit:
// Find all setter of class
private static Stream<Method> getSetters2(Class clazz) {
Optional<Class> first = Arrays.stream(clazz.getDeclaredClasses())
.findFirst();
return Arrays.stream(first.get().getDeclaredMethods())
.filter(method -> method.getName().startsWith("set"));
}
Upvotes: 1