#207 JavaDoc improvements; Joining singularization methods

This commit is contained in:
Gunnar Morling 2014-06-22 12:22:18 +02:00
parent acfca6235d
commit 160bdb2e86
11 changed files with 86 additions and 100 deletions

View File

@ -19,34 +19,44 @@
package org.mapstruct; package org.mapstruct;
/** /**
* Strategy for mapping of collections. * Strategy for propagating the value of collection-typed properties from source to target.
*
* @author Sjaak Derksen * @author Sjaak Derksen
*/ */
public enum CollectionMappingStrategy { public enum CollectionMappingStrategy {
/** /**
* MapStruct will consider setter methods as target as way to access the target. * The setter of the target property will be used to propagate the value:
* * {@code orderDto.setOrderLines( order.getOrderLines )}.
* Note: If no setter is available a getter will be used under the assumption it has been initialized. * <p>
* If no setter is available but a getter method, this will be used, under the assumption it has been initialized:
* {@code orderDto.getOrderLines().addAll( order.getOrderLines )}.
*/ */
SETTER_ONLY, ACCESSOR_ONLY,
/** /**
* MapStruct will consider setter methods as preferred way to access the target. * If present, the setter of the target property will be used to propagate the value:
* * {@code orderDto.setOrderLines( order.getOrderLines )}.
* If no setter is available, MapStruct will first look for an adder method before resorting to a getter. * <p>
* If no setter but and adder method is present, that adder will be invoked for each element of the source
* collection: {@code order.addOrderLine( orderLine() )}.
* <p>
* If neither a setter nor an adder method but a getter for the target property is present, that getter will be
* used, assuming it returns an initialized collection: If no setter is available, MapStruct will first look for an
* adder method before resorting to a getter.
*/ */
SETTER_PREFERRED, SETTER_PREFERRED,
/** /**
* MapStruct will consider adder methods as preferred way to access the target. * Identical to {@link #SETTER_PREFERRED}, only that adder methods will be preferred over setter methods, if both
* * are present for a given collection-typed property.
* If no adder is available, MapStruct will first look for a setter method before resorting to a getter.
*/ */
ADDER_PREFERRED, ADDER_PREFERRED,
/** /**
* The default option is: {@link CollectionMappingStrategy#SETTER_ONLY}. * If given via {@link Mapper#collectionMappingStrategy()}, causes the setting specified via
* * {@link MapperConfig#collectionMappingStrategy()} to be applied, if present. Otherwise causes
* The default options forces deliberate setting in {@link Mapper#collectionMappingStrategy() }, in order * {@link #ACCESSOR_ONLY} to be applied.
* to override a setting in {@link MapperConfig#collectionMappingStrategy() }
*/ */
DEFAULT; DEFAULT;
} }

View File

@ -84,10 +84,14 @@ public @interface Mapper {
Class<?> config() default void.class; Class<?> config() default void.class;
/** /**
* When a the target is a collection, look for a suitable adder. If the property is defined as plural, (so * The strategy to be applied when propagating the value of collection-typed properties. By default, only JavaBeans
* getItems(), the adder will assumed to be the singular form: addItem() * accessor methods (setters or getters) will be used, but it is also possible to invoke a corresponding adder
* method for each element of the source collection (e.g. {@code orderDto.addOrderLine()}).
* <p>
* Any setting given for this attribute will take precedence over {@link MapperConfig#collectionMappingStrategy()},
* if present.
* *
* @return true if the adder should be used. * @return The strategy applied when propagating the value of collection-typed properties.
*/ */
CollectionMappingStrategy collectionMappingStrategy() default CollectionMappingStrategy.DEFAULT; CollectionMappingStrategy collectionMappingStrategy() default CollectionMappingStrategy.DEFAULT;

View File

@ -74,10 +74,11 @@ public @interface MapperConfig {
String componentModel() default "default"; String componentModel() default "default";
/** /**
* When a the target is a collection, look for a suitable adder. If the property is defined as plural, (so * The strategy to be applied when propagating the value of collection-typed properties. By default, only JavaBeans
* getItems(), the adder will assumed to be the singular form: addItem() * accessor methods (setters or getters) will be used, but it is also possible to invoke a corresponding adder
* method for each element of the source collection (e.g. {@code orderDto.addOrderLine()}).
* *
* @return true if the adder should be used. * @return The strategy applied when propagating the value of collection-typed properties.
*/ */
CollectionMappingStrategy collectionMappingStrategy() default CollectionMappingStrategy.DEFAULT; CollectionMappingStrategy collectionMappingStrategy() default CollectionMappingStrategy.DEFAULT;
} }

View File

@ -24,6 +24,7 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element; import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind; import javax.lang.model.element.ElementKind;
@ -35,6 +36,7 @@ import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements; import javax.lang.model.util.Elements;
import javax.lang.model.util.Types; import javax.lang.model.util.Types;
import org.mapstruct.ap.util.Executables; import org.mapstruct.ap.util.Executables;
import org.mapstruct.ap.util.Filters; import org.mapstruct.ap.util.Filters;
import org.mapstruct.ap.util.Nouns; import org.mapstruct.ap.util.Nouns;
@ -280,13 +282,13 @@ public class Type extends ModelElement implements Comparable<Type> {
* Tries to find an addMethod in this type for given collection property in this type. * Tries to find an addMethod in this type for given collection property in this type.
* *
* Matching occurs on: * Matching occurs on:
* <ul> * <ol>
* <li>1. The generic type parameter type of the collection should match the adder method argument</li> * <li>The generic type parameter type of the collection should match the adder method argument</li>
* <li>2. When there are more candidates, property name is made singular (as good as is possible). This routine * <li>When there are more candidates, property name is made singular (as good as is possible). This routine
* looks for a matching add method name.</li> * looks for a matching add method name.</li>
* <li>3. The singularization rules of Dali are used to make a property name singular. This routine * <li>The singularization rules of Dali are used to make a property name singular. This routine
* looks for a matching add method name.</li> * looks for a matching add method name.</li>
* </ul> * </ol>
* *
* @param collectionProperty property type (assumed collection) to find the adder method for * @param collectionProperty property type (assumed collection) to find the adder method for
* @param pluralPropertyName the property name (assumed plural) * @param pluralPropertyName the property name (assumed plural)
@ -321,17 +323,9 @@ public class Type extends ModelElement implements Comparable<Type> {
return candidates.get( 0 ); return candidates.get( 0 );
} }
else { else {
// try to match according human rules
for (ExecutableElement candidate : candidates) { for (ExecutableElement candidate : candidates) {
String adderName = Executables.getElementNameForAdder( candidate ); String elementName = Executables.getElementNameForAdder( candidate );
if (adderName.equals( Nouns.singularizeHuman( pluralPropertyName ) ) ) { if (elementName.equals( Nouns.singularize( pluralPropertyName ) ) ) {
return candidate;
}
}
// try to match according dali rules
for (ExecutableElement candidate : candidates) {
String adderName = Executables.getElementNameForAdder( candidate );
if (adderName.equals( Nouns.singularizeDali( pluralPropertyName ) ) ) {
return candidate; return candidate;
} }
} }

View File

@ -498,23 +498,23 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
cmStrategy.equals( CollectionMappingStrategy.ADDER_PREFERRED ) ) { cmStrategy.equals( CollectionMappingStrategy.ADDER_PREFERRED ) ) {
// first check if there's a setter method. // first check if there's a setter method.
ExecutableElement adderAccessor = null; ExecutableElement adderMethod = null;
if ( Executables.isSetterMethod( targetAccessor ) ) { if ( Executables.isSetterMethod( targetAccessor ) ) {
Type targetType = typeFactory.getSingleParameter( targetAccessor ).getType(); Type targetType = typeFactory.getSingleParameter( targetAccessor ).getType();
// ok, the current accessor is a setter. So now the strategy determines what to use // ok, the current accessor is a setter. So now the strategy determines what to use
if ( cmStrategy.equals( CollectionMappingStrategy.ADDER_PREFERRED ) ) { if ( cmStrategy.equals( CollectionMappingStrategy.ADDER_PREFERRED ) ) {
adderAccessor = method.getResultType().getAdderForType( targetType, targetPropertyName ); adderMethod = method.getResultType().getAdderForType( targetType, targetPropertyName );
} }
} }
else if ( Executables.isGetterMethod( targetAccessor ) ) { else if ( Executables.isGetterMethod( targetAccessor ) ) {
// the current accessor is a getter (no setter available). But still, an add method is according // the current accessor is a getter (no setter available). But still, an add method is according
// to the above strategy (SETTER_PREFERRED || ADDER_PREFERRED) preferred over the getter. // to the above strategy (SETTER_PREFERRED || ADDER_PREFERRED) preferred over the getter.
Type targetType = typeFactory.getReturnType( targetAccessor ); Type targetType = typeFactory.getReturnType( targetAccessor );
adderAccessor = method.getResultType().getAdderForType( targetType, targetPropertyName ); adderMethod = method.getResultType().getAdderForType( targetType, targetPropertyName );
} }
if ( adderAccessor != null ) { if ( adderMethod != null ) {
// an adder has been found (according strategy) so overrule current choice. // an adder has been found (according strategy) so overrule current choice.
targetAccessor = adderAccessor; targetAccessor = adderMethod;
} }
} }

View File

@ -106,23 +106,8 @@ public class Executables {
} }
/** /**
* Returns the 'element name' to which an adder method applies. * Returns the 'element name' to which an adder method applies. If. e.g. an adder method is named
* * {@code addChild(Child v)}, the element name would be 'Child'.
* If an collection getter / setter are defined by a plural name of the element they apply to, then this
* method gives the supposedly (singular) element name of the collection.
* for example:
* getter = {@code List<Child> getChildren()} , then the adder name is supposedly named: {@code addChild(Child v)},
* element name = 'Child'
*
* getter = {@code List<Bike> getBikes()} , then the adder name is supposedly named: {@code addBike(Bike v)},
* element name = 'Bike'
*
* getter = {@code List<Goose> getGeese()} , then the adder name is supposedly named: {@code addGoose(Goose v)},
* element name = 'Goose'
*
* @param adderMethod
*
* @return the element name
*/ */
public static String getElementNameForAdder(ExecutableElement adderMethod) { public static String getElementNameForAdder(ExecutableElement adderMethod) {
if ( isAdderMethod( adderMethod ) ) { if ( isAdderMethod( adderMethod ) ) {

View File

@ -18,22 +18,23 @@
*/ */
package org.mapstruct.ap.util; package org.mapstruct.ap.util;
import static org.mapstruct.CollectionMappingStrategy.valueOf;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element; import javax.lang.model.element.Element;
import javax.lang.model.type.DeclaredType; import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeMirror;
import org.mapstruct.CollectionMappingStrategy; import org.mapstruct.CollectionMappingStrategy;
import org.mapstruct.ap.option.ReportingPolicy; import org.mapstruct.ap.option.ReportingPolicy;
import org.mapstruct.ap.prism.MapperConfigPrism; import org.mapstruct.ap.prism.MapperConfigPrism;
import org.mapstruct.ap.prism.MapperPrism; import org.mapstruct.ap.prism.MapperPrism;
import static org.mapstruct.CollectionMappingStrategy.DEFAULT;
import static org.mapstruct.CollectionMappingStrategy.SETTER_ONLY;
import static org.mapstruct.CollectionMappingStrategy.valueOf;
/** /**
* Class decorating the {@link MapperPrism} with the 'default' configuration. * Class decorating the {@link MapperPrism} with the 'default' configuration.
* *
@ -95,20 +96,20 @@ public class MapperConfig {
public CollectionMappingStrategy getCollectionMappingStrategy() { public CollectionMappingStrategy getCollectionMappingStrategy() {
CollectionMappingStrategy mapperPolicy = valueOf( mapperPrism.collectionMappingStrategy() ); CollectionMappingStrategy mapperPolicy = valueOf( mapperPrism.collectionMappingStrategy() );
if ( !mapperPolicy.equals( DEFAULT ) ) { if ( !mapperPolicy.equals( CollectionMappingStrategy.DEFAULT ) ) {
// it is not the default mapper configuration, so return the mapper configured value // it is not the default mapper configuration, so return the mapper configured value
return mapperPolicy; return mapperPolicy;
} }
else if ( mapperConfigPrism != null ) { else if ( mapperConfigPrism != null ) {
// try the config mapper configuration // try the config mapper configuration
CollectionMappingStrategy configPolicy = valueOf( mapperConfigPrism.collectionMappingStrategy() ); CollectionMappingStrategy configPolicy = valueOf( mapperConfigPrism.collectionMappingStrategy() );
if ( !configPolicy.equals( DEFAULT ) ) { if ( !configPolicy.equals( CollectionMappingStrategy.DEFAULT ) ) {
// its not the default configuration, so return the mapper config configured value // its not the default configuration, so return the mapper config configured value
return configPolicy; return configPolicy;
} }
} }
// when nothing specified, return SETTER_ONLY (default option) // when nothing specified, return ACCESSOR_ONLY (default option)
return SETTER_ONLY; return CollectionMappingStrategy.ACCESSOR_ONLY;
} }
public String componentModel() { public String componentModel() {

View File

@ -32,7 +32,7 @@ public class Nouns {
private Nouns() { } private Nouns() { }
private static final List<ReplaceRule> SINGULAR_HUMAN_RULES = Arrays.asList( private static final List<ReplaceRule> SINGULAR_RULES = Arrays.asList(
new ReplaceRule( "(equipment|information|rice|money|species|series|fish|sheep)$", "$1" ), new ReplaceRule( "(equipment|information|rice|money|species|series|fish|sheep)$", "$1" ),
new ReplaceRule( "(f)eet$", "$1oot" ), new ReplaceRule( "(f)eet$", "$1oot" ),
new ReplaceRule( "(t)eeth$", "$1ooth" ), new ReplaceRule( "(t)eeth$", "$1ooth" ),
@ -74,48 +74,36 @@ public class Nouns {
new ReplaceRule( "s$", "" ) new ReplaceRule( "s$", "" )
); );
/**
* Replacement rules based on the routine applied by the <a href="http://www.eclipse.org/webtools/dali/">Dali</a>
* project. Applied as a fallback if the other rules didn't yield a match.
*/
private static final List<ReplaceRule> SINGULAR_DALI_RULES = Arrays.asList( private static final List<ReplaceRule> SINGULAR_DALI_RULES = Arrays.asList(
new ReplaceRule( "(us|ss)$", "$1" ), new ReplaceRule( "(us|ss)$", "$1" ),
new ReplaceRule( "(ch|s)es$", "$1" ), new ReplaceRule( "(ch|s)es$", "$1" ),
new ReplaceRule( "([^aeiouy])ies$", "$1y" ), new ReplaceRule( "([^aeiouy])ies$", "$1y" )
new ReplaceRule( "s$", "" )
); );
/** /**
* Converts given in into a singular form as much as possible according human form. This will always be a best * Converts given pluralized noun into the singular form. If no singular form could be determined, the given word
* attempt. The rules are language context dependent and * itself is returned.
*
* @param in String to singularize
* @return singularize form of in
*/ */
public static String singularizeHuman( String in ) { public static String singularize( String plural ) {
for ( ReplaceRule replaceRule : SINGULAR_HUMAN_RULES ) { for ( ReplaceRule replaceRule : SINGULAR_RULES ) {
String match = replaceRule.apply( in ); String match = replaceRule.apply( plural );
if ( match != null ) { if ( match != null ) {
return match; return match;
} }
} }
return in;
}
/**
* Converts given in into a singular form according dali
* @see <a href="http://www.eclipse.org/webtools/dali/"></a> rules
*
* These rules are assumed to be incomplete and give wrong conversions from plural to singular that should
* be taken into account as well.
*
* @param in String to singularize
* @return singularize form of in
*/
public static String singularizeDali( String in ) {
for ( ReplaceRule replaceRule : SINGULAR_DALI_RULES ) { for ( ReplaceRule replaceRule : SINGULAR_DALI_RULES ) {
String match = replaceRule.apply( in ); String match = replaceRule.apply( plural );
if ( match != null ) { if ( match != null ) {
return match; return match;
} }
} }
return in;
return plural;
} }
private static final class ReplaceRule { private static final class ReplaceRule {
@ -124,7 +112,7 @@ public class Nouns {
private final String replacement; private final String replacement;
private final Pattern pattern; private final Pattern pattern;
private ReplaceRule( String regexp, String replacement ) { private ReplaceRule( String regexp, String replacement ) {
this.regexp = regexp; this.regexp = regexp;
this.replacement = replacement; this.replacement = replacement;
this.pattern = Pattern.compile( this.regexp, Pattern.CASE_INSENSITIVE ); this.pattern = Pattern.compile( this.regexp, Pattern.CASE_INSENSITIVE );
@ -139,5 +127,9 @@ public class Nouns {
return result; return result;
} }
@Override
public String toString() {
return "'" + regexp + "' -> '" + replacement;
}
} }
} }

View File

@ -19,15 +19,16 @@
package org.mapstruct.ap.test.collection.adder; package org.mapstruct.ap.test.collection.adder;
/** /**
*
* @author Sjaak Derksen * @author Sjaak Derksen
*/ */
public class CatException extends Exception { public class CatException extends Exception {
private static final long serialVersionUID = 1L;
public CatException() { public CatException() {
} }
public CatException( String msg ) { public CatException(String msg) {
super( msg ); super( msg );
} }
} }

View File

@ -19,15 +19,16 @@
package org.mapstruct.ap.test.collection.adder; package org.mapstruct.ap.test.collection.adder;
/** /**
*
* @author Sjaak Derksen * @author Sjaak Derksen
*/ */
public class DogException extends Exception { public class DogException extends Exception {
private static final long serialVersionUID = 1L;
public DogException() { public DogException() {
} }
public DogException( String msg ) { public DogException(String msg) {
super( msg ); super( msg );
} }
} }

View File

@ -37,7 +37,4 @@ public class IndoorPet extends Pet {
public void setValue( Long value ) { public void setValue( Long value ) {
this.value = value; this.value = value;
} }
} }