mirror of
https://github.com/mapstruct/mapstruct.git
synced 2025-07-12 00:00:08 +08:00
#832, #866 Documentation update on the SPI, adding DefaultAccessorNamingStrategy javadoc, fixing responsibility issue
This commit is contained in:
parent
d99590306e
commit
daddb255dd
@ -1974,3 +1974,132 @@ The order in which the selected methods are applied is roughly determined by the
|
||||
====
|
||||
`@BeforeMapping` and `@AfterMapping` are considered experimental as of the 1.0.0.CR1 release. Details in the selection of before/after mapping methods that are applicable for a mapping method or the order in which they are called might still be changed.
|
||||
====
|
||||
[[using-spi]]
|
||||
== Using the MapStruct SPI
|
||||
=== Custom Accessor Naming Strategy
|
||||
|
||||
MapStruct offers the possibility to override the `AccessorNamingStrategy` via the Service Provide Interface (SPI). A nice example is the use of the fluent API on the source object `GolfPlayer` and `GolfPlayerDto` below.
|
||||
|
||||
.Source object GolfPlayer with fluent API.
|
||||
====
|
||||
[source, java, linenums]
|
||||
[subs="verbatim,attributes"]
|
||||
----
|
||||
public class GolfPlayer {
|
||||
|
||||
private double handicap;
|
||||
private String name;
|
||||
|
||||
public double handicap() {
|
||||
return handicap;
|
||||
}
|
||||
|
||||
public GolfPlayer withHandicap(double handicap) {
|
||||
this.handicap = handicap;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public GolfPlayer withName(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
.Source object GolfPlayerDto with fluent API.
|
||||
====
|
||||
[source, java, linenums]
|
||||
[subs="verbatim,attributes"]
|
||||
----
|
||||
public class GolfPlayerDto {
|
||||
|
||||
private double handicap;
|
||||
private String name;
|
||||
|
||||
public double handicap() {
|
||||
return handicap;
|
||||
}
|
||||
|
||||
public GolfPlayerDto withHandicap(double handicap) {
|
||||
this.handicap = handicap;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public GolfPlayerDto withName(String name) {
|
||||
this.name = name;
|
||||
return this
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
We want `GolfPlayer` to be mapped to a target object `GolfPlayerDto` similar like we 'always' do this:
|
||||
|
||||
.Source object with fluent API.
|
||||
====
|
||||
[source, java, linenums]
|
||||
[subs="verbatim,attributes"]
|
||||
----
|
||||
@Mapper
|
||||
public interface GolfPlayerMapper {
|
||||
|
||||
GolfPlayerMapper INSTANCE = Mappers.getMapper( GolfPlayerMapper.class );
|
||||
|
||||
GolfPlayerDto toDto(GolfPlayer player);
|
||||
|
||||
GolfPlayer toPlayer(GolfPlayerDto player);
|
||||
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
This can be achieved with implementing the SPI `org.mapstruct.ap.spi.AccessorNamingStrategy` as in the following example. Here's an implemented `org.mapstruct.ap.spi.AccessorNamingStrategy`:
|
||||
|
||||
.CustomAccessorNamingStrategy
|
||||
====
|
||||
[source, java, linenums]
|
||||
[subs="verbatim,attributes"]
|
||||
----
|
||||
/**
|
||||
* A custom {@link AccessorNamingStrategy} recognizing getters in the form of {@code property()} and setters in the
|
||||
* form of {@code withProperty(value)}.
|
||||
*/
|
||||
public class CustomAccessorNamingStrategy extends DefaultAccessorNamingStrategy {
|
||||
|
||||
@Override
|
||||
public boolean isGetterMethod(ExecutableElement method) {
|
||||
String methodName = method.getSimpleName().toString();
|
||||
return !methodName.startsWith( "with" ) && method.getReturnType().getKind() != TypeKind.VOID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSetterMethod(ExecutableElement method) {
|
||||
String methodName = method.getSimpleName().toString();
|
||||
return methodName.startsWith( "with" ) && methodName.length() > 4;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPropertyName(ExecutableElement getterOrSetterMethod) {
|
||||
String methodName = getterOrSetterMethod.getSimpleName().toString();
|
||||
return Introspector.decapitalize( methodName.startsWith( "with" ) ? methodName.substring( 4 ) : methodName );
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
The `CustomAccessorNamingStrategy` makes use of the `DefaultAccessorNamingStrategy` (also available in mapstruct-processor) and relies on that class to leave most of the default behaviour unchanged.
|
||||
|
||||
To use a custom SPI implementation, it must be located in a seperate .jar file together with the file `META-INF/services/org.mapstruct.ap.spi.AccessorNamingStrategy` with the fully qualified name of your custom implementation as content (e.g. `org.mapstruct.example.CustomAccessorNamingStrategy`). This .jar file needs to be added to the annotation processor classpath (i.e. add it next to the place where you added the mapstruct-processor jar).
|
||||
|
||||
[TIP]
|
||||
Fore more details: There's the above example is present in our our examples repository (https://github.com/mapstruct/mapstruct-examples).
|
||||
|
||||
|
||||
|
@ -34,6 +34,8 @@ import javax.lang.model.type.DeclaredType;
|
||||
import javax.lang.model.type.TypeKind;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
import javax.lang.model.util.Elements;
|
||||
import javax.lang.model.util.SimpleElementVisitor6;
|
||||
import javax.lang.model.util.SimpleTypeVisitor6;
|
||||
|
||||
import org.mapstruct.ap.internal.prism.AfterMappingPrism;
|
||||
import org.mapstruct.ap.internal.prism.BeforeMappingPrism;
|
||||
@ -75,9 +77,11 @@ public class Executables {
|
||||
}
|
||||
|
||||
public static boolean isPresenceCheckMethod(ExecutableElement method) {
|
||||
return isPublic( method ) &&
|
||||
method.getParameters().isEmpty() &&
|
||||
ACCESSOR_NAMING_STRATEGY.getMethodType( method ) == MethodType.PRESENCE_CHECKER;
|
||||
return isPublic( method )
|
||||
&& method.getParameters().isEmpty()
|
||||
&& ( method.getReturnType().getKind() == TypeKind.BOOLEAN ||
|
||||
"java.lang.Boolean".equals( getQualifiedName( method.getReturnType() ) ) )
|
||||
&& ACCESSOR_NAMING_STRATEGY.getMethodType( method ) == MethodType.PRESENCE_CHECKER;
|
||||
}
|
||||
|
||||
public static boolean isSetterMethod(ExecutableElement method) {
|
||||
@ -284,4 +288,33 @@ public class Executables {
|
||||
public static boolean isBeforeMappingMethod(ExecutableElement executableElement) {
|
||||
return BeforeMappingPrism.getInstanceOn( executableElement ) != null;
|
||||
}
|
||||
|
||||
private static String getQualifiedName(TypeMirror type) {
|
||||
DeclaredType declaredType = type.accept(
|
||||
new SimpleTypeVisitor6<DeclaredType, Void>() {
|
||||
@Override
|
||||
public DeclaredType visitDeclared(DeclaredType t, Void p) {
|
||||
return t;
|
||||
}
|
||||
},
|
||||
null
|
||||
);
|
||||
|
||||
if ( declaredType == null ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
TypeElement typeElement = declaredType.asElement().accept(
|
||||
new SimpleElementVisitor6<TypeElement, Void>() {
|
||||
@Override
|
||||
public TypeElement visitType(TypeElement e, Void p) {
|
||||
return e;
|
||||
}
|
||||
},
|
||||
null
|
||||
);
|
||||
|
||||
return typeElement != null ? typeElement.getQualifiedName().toString() : null;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ import javax.lang.model.util.SimpleTypeVisitor6;
|
||||
/**
|
||||
* The default JavaBeans-compliant implementation of the {@link AccessorNamingStrategy} service provider interface.
|
||||
*
|
||||
* @author Christian Schuster
|
||||
* @author Christian Schuster, Sjaak Derken
|
||||
*/
|
||||
public class DefaultAccessorNamingStrategy implements AccessorNamingStrategy {
|
||||
|
||||
@ -55,7 +55,19 @@ public class DefaultAccessorNamingStrategy implements AccessorNamingStrategy {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isGetterMethod(ExecutableElement method) {
|
||||
/**
|
||||
* Returns {@code true} when the {@link ExecutableElement} is a getter method. A method is a getter when it starts
|
||||
* with 'get' and the return type is any type other than {@code void}, OR the getter starts with 'is' and the type
|
||||
* returned is a primitive or the wrapper for {@code boolean}. NOTE: the latter does strictly not comply to the bean
|
||||
* convention. The remainder of the name is supposed to reflect the property name.
|
||||
* <p>
|
||||
* The calling MapStruct code guarantees that the given method has no arguments.
|
||||
*
|
||||
* @param method to be analyzed
|
||||
*
|
||||
* @return {@code true} when the method is a getter.
|
||||
*/
|
||||
public boolean isGetterMethod(ExecutableElement method) {
|
||||
String methodName = method.getSimpleName().toString();
|
||||
|
||||
boolean isNonBooleanGetterName = methodName.startsWith( "get" ) && methodName.length() > 3 &&
|
||||
@ -68,36 +80,109 @@ public class DefaultAccessorNamingStrategy implements AccessorNamingStrategy {
|
||||
return isNonBooleanGetterName || ( isBooleanGetterName && returnTypeIsBoolean );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns {@code true} when the {@link ExecutableElement} is a setter method. A setter starts with 'set'. The
|
||||
* remainder of the name is supposed to reflect the property name.
|
||||
* <p>
|
||||
* The calling MapStruct code guarantees that there's only one argument.
|
||||
*
|
||||
* @param method to be analyzed
|
||||
* @return {@code true} when the method is a setter.
|
||||
*/
|
||||
public boolean isSetterMethod(ExecutableElement method) {
|
||||
String methodName = method.getSimpleName().toString();
|
||||
|
||||
return methodName.startsWith( "set" ) && methodName.length() > 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} when the {@link ExecutableElement} is an adder method. An adder method starts with 'add'.
|
||||
* The remainder of the name is supposed to reflect the <em>singular</em> property name (as opposed to plural) of
|
||||
* its corresponding property. For example: property "children", but "addChild". See also
|
||||
* {@link #getElementName(ExecutableElement) }.
|
||||
* <p>
|
||||
* The calling MapStruct code guarantees there's only one argument.
|
||||
* <p>
|
||||
*
|
||||
* @param method to be analyzed
|
||||
*
|
||||
* @return {@code true} when the method is an adder method.
|
||||
*/
|
||||
public boolean isAdderMethod(ExecutableElement method) {
|
||||
String methodName = method.getSimpleName().toString();
|
||||
|
||||
return methodName.startsWith( "add" ) && methodName.length() > 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} when the {@link ExecutableElement} is a <em>presence check</em> method that checks if the
|
||||
* corresponding property is present (e.g. not null, not nil, ..). A presence check method method starts with
|
||||
* 'has'. The remainder of the name is supposed to reflect the property name.
|
||||
* <p>
|
||||
* The calling MapStruct code guarantees there's no argument and that the return type is boolean or a
|
||||
* {@link Boolean}
|
||||
*
|
||||
* @param method to be analyzed
|
||||
* @return {@code true} when the method is a presence check method.
|
||||
*/
|
||||
public boolean isPresenceCheckMethod(ExecutableElement method) {
|
||||
String methodName = method.getSimpleName().toString();
|
||||
return methodName.startsWith( "has" ) && methodName.length() > 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyzes the method (getter or setter) and derives the property name.
|
||||
* See {@link #isGetterMethod(ExecutableElement)} {@link #isSetterMethod(ExecutableElement)}. The first three
|
||||
* ('get' / 'set' scenario) characters are removed from the simple name, or the first 2 characters ('is' scenario).
|
||||
* From the remainder the first character is made into small case (to counter camel casing) and the result forms
|
||||
* the property name.
|
||||
*
|
||||
* @param getterOrSetterMethod getter or setter method.
|
||||
*
|
||||
* @return the property name.
|
||||
*/
|
||||
@Override
|
||||
public String getPropertyName(ExecutableElement getterOrSetterMethod) {
|
||||
String methodName = getterOrSetterMethod.getSimpleName().toString();
|
||||
return Introspector.decapitalize( methodName.substring( methodName.startsWith( "is" ) ? 2 : 3 ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adder methods are used to add elements to collections on a target bean. A typical use case is JPA. The
|
||||
* convention is that the element name will be equal to the remainder of the add method. Example: 'addElement'
|
||||
* element name will be 'element'.
|
||||
*
|
||||
* @param adderMethod getter or setter method.
|
||||
*
|
||||
* @return the property name.
|
||||
*/
|
||||
@Override
|
||||
public String getElementName(ExecutableElement adderMethod) {
|
||||
String methodName = adderMethod.getSimpleName().toString();
|
||||
return Introspector.decapitalize( methodName.substring( 3 ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the getter name of a collection given the property name. This will start with 'get' and the
|
||||
* first character of the remainder will be placed in upper case.
|
||||
*
|
||||
* @param property the property
|
||||
*
|
||||
* @return getter name for collections.
|
||||
*/
|
||||
@Override
|
||||
public String getCollectionGetterName(String property) {
|
||||
return "get" + property.substring( 0, 1 ).toUpperCase() + property.substring( 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method, to obtain the fully qualified name of a type.
|
||||
*
|
||||
* @param type input type
|
||||
*
|
||||
* @return fully qualified name of type when the type is a {@link DeclaredType}, null when otherwise.
|
||||
*/
|
||||
protected static String getQualifiedName(TypeMirror type) {
|
||||
DeclaredType declaredType = type.accept(
|
||||
new SimpleTypeVisitor6<DeclaredType, Void>() {
|
||||
@ -126,12 +211,4 @@ public class DefaultAccessorNamingStrategy implements AccessorNamingStrategy {
|
||||
return typeElement != null ? typeElement.getQualifiedName().toString() : null;
|
||||
}
|
||||
|
||||
private boolean isPresenceCheckMethod(ExecutableElement method) {
|
||||
String methodName = method.getSimpleName().toString();
|
||||
|
||||
return methodName.startsWith( "has" ) && methodName.length() > 3 &&
|
||||
( method.getReturnType().getKind() == TypeKind.BOOLEAN ||
|
||||
"java.lang.Boolean".equals( getQualifiedName( method.getReturnType() ) ) );
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user