Skip to content

Commit

Permalink
Proof of concept allowing chained setters.
Browse files Browse the repository at this point in the history
  • Loading branch information
djarnis73 committed Apr 30, 2020
1 parent e14ae33 commit bcd99c8
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 3 deletions.
16 changes: 14 additions & 2 deletions src/main/java/com/remondis/remap/MappingConfiguration.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ public class MappingConfiguration<S, D> {
static final String OMIT_FIELD_DEST = "omit in destination";
static final String OMIT_FIELD_SOURCE = "omit in source";

private boolean allowFluentSetters;

private Class<S> source;
private Class<D> destination;

Expand Down Expand Up @@ -103,6 +105,16 @@ public class MappingConfiguration<S, D> {
this.mappers = new Hashtable<>();
}

/**
* @param allow if set to <code>true</code> the requirement for setters to return void is relaxed
* and mapped classes can be <em>fluent</em>.
* @return Returns this object for method chaining.
*/
public MappingConfiguration<S, D> allowFluentSetters(boolean allow) {
this.allowFluentSetters = allow;
return this;
}

/**
* Marks a destination field as omitted. The mapping will not touch this field in the destination
* object.
Expand Down Expand Up @@ -480,7 +492,7 @@ private Set<PropertyDescriptor> getUnmappedSourceProperties() {
*/
private <T> Set<PropertyDescriptor> getUnmappedProperties(Class<T> type,
Set<PropertyDescriptor> mappedSourceProperties, Target targetType) {
Set<PropertyDescriptor> allSourceProperties = Properties.getProperties(type, targetType);
Set<PropertyDescriptor> allSourceProperties = Properties.getProperties(type, targetType, allowFluentSetters);
allSourceProperties.removeAll(mappedSourceProperties);
return allSourceProperties;
}
Expand Down Expand Up @@ -563,7 +575,7 @@ static <T> PropertyDescriptor getPropertyFromFieldSelector(Target target, String
*/
static PropertyDescriptor getPropertyDescriptorOrFail(Target target, Class<?> type, String propertyName) {
Optional<PropertyDescriptor> property;
property = Properties.getProperties(type, target)
property = Properties.getProperties(type, target, false)
.stream()
.filter(pd -> pd.getName()
.equals(propertyName))
Expand Down
31 changes: 30 additions & 1 deletion src/main/java/com/remondis/remap/Properties.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
Expand Down Expand Up @@ -106,13 +107,21 @@ static String createUnmappedMessage(Set<PropertyDescriptor> unmapped) {
*
* @param inspectType The type to inspect.
* @param targetType The type of mapping target.
* @param allowFluentSetters if true, setters that return a value are allowed in the mapping.
* @return Returns the list of {@link PropertyDescriptor}s that grant read and write access.
* @throws MappingException Thrown on any introspection error.
*/
static Set<PropertyDescriptor> getProperties(Class<?> inspectType, Target targetType) {
static Set<PropertyDescriptor> getProperties(Class<?> inspectType, Target targetType, boolean allowFluentSetters) {
try {
BeanInfo beanInfo = Introspector.getBeanInfo(inspectType);
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
if (propertyDescriptors != null && allowFluentSetters && targetType == Target.DESTINATION) {
for (PropertyDescriptor pd : propertyDescriptors) {
if (pd.getWriteMethod() == null) {
checkForAndSetFluentWriteMethod(inspectType, pd);
}
}
}
return new HashSet<>(Arrays.asList(propertyDescriptors)
.stream()
.filter(pd -> !pd.getName()
Expand All @@ -131,6 +140,26 @@ static Set<PropertyDescriptor> getProperties(Class<?> inspectType, Target target
}
}

/**
* Tries to see if a setXXX method exists even though it was not found by the initial retrospection.
* If a setter exists
*/
private static void checkForAndSetFluentWriteMethod(Class<?> inspectType, PropertyDescriptor pd) {
String writeMethodName = pd.getName();
writeMethodName = "set" + writeMethodName.substring(0, 1)
.toUpperCase() + writeMethodName.substring(1);
try {
Method setMethod = inspectType.getDeclaredMethod(writeMethodName, pd.getPropertyType());
if (Modifier.isPublic(setMethod.getModifiers())) {
pd.setWriteMethod(setMethod);
}
} catch (NoSuchMethodException e) {
// just ignore the method does not have to exists
} catch (IntrospectionException e) {
throw new RuntimeException(e);
}
}

private static boolean hasGetter(PropertyDescriptor pd) {
return pd.getReadMethod() != null;
}
Expand Down
56 changes: 56 additions & 0 deletions src/test/java/com/remondis/remap/fluent/A.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.remondis.remap.fluent;

public class A {

private Integer integer;
private int i;
private String s;
private boolean b1;
private boolean b2;

public Integer getInteger() {
return integer;
}

public A setInteger(Integer integer) {
this.integer = integer;
return this;
}

public int getI() {
return integer;
}

public A setI(int i) {
this.i = i;
return this;
}

public String getS() {
return s;
}

public A setS(String s) {
this.s = s;
return this;
}

public boolean getB1() {
return b1;
}

public A setB1(boolean b) {
this.b1 = b;
return this;
}

public boolean isB2() {
return b2;
}

public A setB2(boolean b) {
this.b2 = b;
return this;
}

}
34 changes: 34 additions & 0 deletions src/test/java/com/remondis/remap/fluent/ChainedSetterTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.remondis.remap.fluent;

import com.remondis.remap.Mapper;
import com.remondis.remap.Mapping;
import com.remondis.remap.MappingException;
import org.junit.Test;

import static org.assertj.core.api.Assertions.assertThat;

public class ChainedSetterTest {

@Test
public void testChainedSetter() {
Mapper<A, A> m = Mapping.from(A.class)
.to(A.class)
.allowFluentSetters(true)
.mapper();
A expected = new A().setInteger(5)
.setI(22)
.setS("str")
.setB1(true)
.setB2(true);
A actual = m.map(expected);
assertThat(actual).isEqualToComparingFieldByField(expected);
}

@Test(expected = MappingException.class)
public void checkConfigurationOfChainedSetters() {
Mapping.from(A.class)
.to(A.class)
.mapper();
}

}

0 comments on commit bcd99c8

Please sign in to comment.