Reference
Introduction
- Builder Pattern is suggested in Effective Java and used to generate immutable object.
- Builder Pattern introduces many codes, lombok project makes it easier
- We often use Orika to map data when transfer object from UI entity to domain entity or data persistence object to domain entity.
- It's hard to map Java Bean to Builder Pattern object because Builder Pattern doesn't follows Java Bean convention. It causes we can't leverage lombok and Orika at the same time
How to fix
- Copy and paste the following code: BuilderPropertyResolver
- Leverage this object
public class Mapper { public static void main(String[] params) { MapperFactory factory = new DefaultMapperFactory.Builder() .propertyResolverStrategy(new BuilderPropertyResolver()) .build(); factory.registerObjectFactory((Object o, MappingContext mappingContext) -> Person.builder(), TypeFactory.valueOf(Person.PersonBuilder.class)); PersonBO bo = new PersonBO(); bo.setName("TEST"); factory.classMap(PersonBO.class, Person.class); System.out.println(factory.getMapperFacade().map(bo, Person.PersonBuilder.class).build()); Person.PersonBuilder builder = Person.builder().list(Arrays.asList("A")); factory.getMapperFacade().map(bo, builder); System.out.println(builder); } @Data public static class PersonBO { private String name; private List<String> list; } @ToString @Builder public static class Person { private String name; private List<String> list; } }
/** * This class is used to map from an object to a builder who follows Builder Pattern. * * <pre> * MapperFactory factory = new DefaultMapperFactory.Builder() * .propertyResolverStrategy(new BuilderPropertyResolver()) * .build(); * factory.registerObjectFactory((Object o, MappingContext mappingContext) * -> new MyBuilder(), TypeFactory.valueOf(MyBuilder.class)); * </pre> */ public class BuilderPropertyResolver extends IntrospectorPropertyResolver { private static final Logger logger = LoggerFactory.getLogger(BuilderPropertyResolver.class); @Override protected void collectProperties(Class<?> type, Type<?> referenceType, Map<String, Property> properties) { super.collectProperties(type, referenceType, properties); if (StringUtils.endsWith(type.getName(), "Builder")) { Set<String> fieldNames = Arrays.stream(type.getDeclaredFields()).map(Field::getName).collect(Collectors.toSet()); Arrays.stream(type.getDeclaredMethods()) .filter(method -> fieldNames.contains(method.getName())) .filter(method -> isWriteMethodInBuilder(type, method)) .forEach(method -> { Property.Builder builder = new Property.Builder(); builder.expression(method.getName()); builder.name(method.getName()); builder.setter(method.getName() + "(%s)"); Class<?> fieldType = method.getParameterTypes()[0]; builder.type(this.resolvePropertyType(null, fieldType, type, referenceType)); Property property = builder.build(this); properties.put(method.getName(), property); }); } } private boolean isWriteMethodInBuilder(Class<?> type, Method method) { try { Class<?> returnType = method.getReturnType(); Class<?> fieldType = type.getDeclaredField(method.getName()).getType(); boolean onlyOneParameter = method.getParameterTypes().length == 1; boolean methodReturnBuilderType = returnType.equals(type); Class<?> methodParamType = method.getParameterTypes()[0]; boolean isFieldSetter = fieldType.equals(methodParamType); return methodReturnBuilderType && onlyOneParameter && isFieldSetter; } catch (NoSuchFieldException e) { e.printStackTrace(); return false; } } }