#365 Moving "spi" package into processor module; Simplifying AccessorNamingStrategy contract

This commit is contained in:
Gunnar Morling 2015-05-16 19:07:19 +02:00 committed by Andreas Gudian
parent e62b612851
commit 3ba4ded58c
11 changed files with 213 additions and 331 deletions

View File

@ -41,7 +41,6 @@
<module>core</module>
<module>core-jdk8</module>
<module>processor</module>
<module>spi</module>
<module>integrationtest</module>
</modules>

View File

@ -43,10 +43,6 @@
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>mapstruct-spi</artifactId>
</dependency>
<!-- Compile-only; Using "provided" scope as there is no such scope in Maven;
these dependencies are not required at runtime, only for prism generation

View File

@ -319,7 +319,7 @@ public class PropertyMapping extends ModelElement {
// target accessor is setter, so wrap the setter in setter map/ collection handling
result = new SetterWrapperForCollectionsAndMaps(
result,
targetWriteAccessor.getSimpleName().toString(),
targetWriteAccessor,
newCollectionOrMap
);
}

View File

@ -21,8 +21,11 @@ package org.mapstruct.ap.model.assignment;
import java.util.HashSet;
import java.util.Set;
import javax.lang.model.element.ExecutableElement;
import org.mapstruct.ap.model.common.Type;
import org.mapstruct.ap.services.Services;
import org.mapstruct.ap.spi.AccessorNamingStrategy;
/**
* This wrapper handles the situation were an assignment is done via the setter.
@ -42,11 +45,15 @@ public class SetterWrapperForCollectionsAndMaps extends AssignmentWrapper {
private final Assignment newCollectionOrMapAssignment;
public SetterWrapperForCollectionsAndMaps(Assignment decoratedAssignment,
String targetSetterName,
ExecutableElement targetSetter,
Assignment newCollectionOrMapAssignment) {
super( decoratedAssignment );
this.targetGetterName =
Services.getAccessorNamingStrategy().getNonBooleanGetterNameForSetterName( targetSetterName );
AccessorNamingStrategy accessorNamingStrategy = Services.getAccessorNamingStrategy();
this.targetGetterName = accessorNamingStrategy.getCollectionGetterName(
accessorNamingStrategy.getPropertyName( targetSetter )
);
this.newCollectionOrMapAssignment = newCollectionOrMapAssignment;
}

View File

@ -20,7 +20,16 @@ package org.mapstruct.ap.services;
import java.beans.Introspector;
import org.mapstruct.spi.AccessorNamingStrategy;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.SimpleElementVisitor6;
import javax.lang.model.util.SimpleTypeVisitor6;
import org.mapstruct.ap.spi.AccessorNamingStrategy;
import org.mapstruct.ap.spi.MethodType;
/**
* The default JavaBeans-compliant implementation of the {@link AccessorNamingStrategy} service provider interface.
@ -30,51 +39,90 @@ import org.mapstruct.spi.AccessorNamingStrategy;
class DefaultAccessorNamingStrategy implements AccessorNamingStrategy {
@Override
public void setDefaultAccessorNamingStrategy(AccessorNamingStrategy defaultAccessorNamingStrategy) {
public MethodType getMethodType(ExecutableElement method) {
if ( isGetterMethod( method ) ) {
return MethodType.GETTER;
}
else if ( isSetterMethod( method ) ) {
return MethodType.SETTER;
}
else if ( isAdderMethod( method ) ) {
return MethodType.ADDER;
}
else {
return MethodType.OTHER;
}
}
@Override
public boolean isNonBooleanGetterName(String methodName) {
return methodName.startsWith( "get" ) && methodName.length() > 3;
private boolean isGetterMethod(ExecutableElement method) {
String methodName = method.getSimpleName().toString();
boolean isNonBooleanGetterName = methodName.startsWith( "get" ) && methodName.length() > 3 &&
method.getReturnType().getKind() != TypeKind.VOID;
boolean isBooleanGetterName = methodName.startsWith( "is" ) && methodName.length() > 2;
boolean returnTypeIsBoolean = method.getReturnType().getKind() == TypeKind.BOOLEAN ||
"java.lang.Boolean".equals( getQualifiedName( method.getReturnType() ) );
return isNonBooleanGetterName || ( isBooleanGetterName && returnTypeIsBoolean );
}
@Override
public boolean isBooleanGetterName(String methodName) {
return methodName.startsWith( "is" ) && methodName.length() > 2;
}
public boolean isSetterMethod(ExecutableElement method) {
String methodName = method.getSimpleName().toString();
@Override
public boolean isSetterName(String methodName) {
return methodName.startsWith( "set" ) && methodName.length() > 3;
}
@Override
public boolean isAdderName(String methodName) {
public boolean isAdderMethod(ExecutableElement method) {
String methodName = method.getSimpleName().toString();
return methodName.startsWith( "add" ) && methodName.length() > 3;
}
@Override
public String getPropertyNameForNonBooleanGetterName(String methodName) {
public String getPropertyName(ExecutableElement getterOrSetterMethod) {
String methodName = getterOrSetterMethod.getSimpleName().toString();
return Introspector.decapitalize( methodName.substring( methodName.startsWith( "is" ) ? 2 : 3 ) );
}
@Override
public String getElementName(ExecutableElement adderMethod) {
String methodName = adderMethod.getSimpleName().toString();
return Introspector.decapitalize( methodName.substring( 3 ) );
}
@Override
public String getPropertyNameForBooleanGetterName(String methodName) {
return Introspector.decapitalize( methodName.substring( 2 ) );
public String getCollectionGetterName(String property) {
return "get" + property.substring( 0, 1 ).toUpperCase() + property.substring( 1 );
}
private static String getQualifiedName(TypeMirror type) {
DeclaredType declaredType = type.accept(
new SimpleTypeVisitor6<DeclaredType, Void>() {
@Override
public String getPropertyNameForSetterName(String methodName) {
return Introspector.decapitalize( methodName.substring( 3 ) );
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 String getElementNameForAdderName(String methodName) {
return Introspector.decapitalize( methodName.substring( 3 ) );
public TypeElement visitType(TypeElement e, Void p) {
return e;
}
},
null
);
return typeElement != null ? typeElement.getQualifiedName().toString() : null;
}
@Override
public String getNonBooleanGetterNameForSetterName(String methodName) {
return "get" + methodName.substring( 3 );
}
}

View File

@ -20,7 +20,7 @@ package org.mapstruct.ap.services;
import java.util.ServiceLoader;
import org.mapstruct.spi.AccessorNamingStrategy;
import org.mapstruct.ap.spi.AccessorNamingStrategy;
/**
* A simple locator for SPI implementations.
@ -53,7 +53,7 @@ public class Services {
if ( impl == null ) {
impl = defaultImpl;
}
impl.setDefaultAccessorNamingStrategy( defaultImpl );
return impl;
}

View File

@ -0,0 +1,57 @@
/**
* Copyright 2012-2015 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mapstruct.ap.spi;
import javax.lang.model.element.ExecutableElement;
/**
* A service provider interface for the mapping between method names and properties.
*
* @author Christian Schuster
* @author Gunnar Morling
*/
public interface AccessorNamingStrategy {
/**
* Returns the type of the given method.
*/
MethodType getMethodType(ExecutableElement method);
/**
* Returns the name of the property represented by the given getter or setter method.
* <p>
* The default implementation will e.g. return "name" for {@code public String getName()} or {@code public void
* setName(String name)}.
*/
String getPropertyName(ExecutableElement getterOrSetterMethod);
/**
* Returns the element name of the given adder method.
* <p>
* The default implementation will e.g. return "item" for {@code public void addItem(String item)}.
*/
String getElementName(ExecutableElement adderMethod);
/**
* Returns the getter name of the given collection property.
* <p>
* The default implementation will e.g. return "getItems" for "items".
*/
String getCollectionGetterName(String property);
}

View File

@ -0,0 +1,47 @@
/**
* Copyright 2012-2015 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mapstruct.ap.spi;
/**
* Different types of a method.
*
* @author Gunnar Morling
*/
public enum MethodType {
/**
* A JavaBeans getter method, e.g. {@code public String getName()}.
*/
GETTER,
/**
* A JavaBeans setter method, e.g. {@code public void setName(String name)}.
*/
SETTER,
/**
* An adder method, e.g. {@code public void addItem(String item)}.
*/
ADDER,
/**
* Any method which is neither a JavaBeans getter, setter nor an adder method.
*/
OTHER;
}

View File

@ -19,9 +19,7 @@
package org.mapstruct.ap.util;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
@ -30,10 +28,10 @@ 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.services.Services;
import org.mapstruct.ap.spi.AccessorNamingStrategy;
import org.mapstruct.ap.spi.MethodType;
import static javax.lang.model.util.ElementFilter.methodsIn;
import static org.mapstruct.ap.util.SpecificCompilerWorkarounds.replaceTypeElementIfNecessary;
@ -45,38 +43,27 @@ import static org.mapstruct.ap.util.SpecificCompilerWorkarounds.replaceTypeEleme
*/
public class Executables {
private static AccessorNamingStrategy accessorNamingStrategy = Services.getAccessorNamingStrategy();
private Executables() {
}
public static boolean isGetterMethod(ExecutableElement method) {
return isPublic( method ) && ( isNonBooleanGetterMethod( method ) || isBooleanGetterMethod( method ) );
}
private static boolean isNonBooleanGetterMethod(ExecutableElement method) {
return method.getParameters().isEmpty()
&& Services.getAccessorNamingStrategy().isNonBooleanGetterName( method.getSimpleName().toString() )
&& method.getReturnType().getKind() != TypeKind.VOID;
}
private static boolean isBooleanGetterMethod(ExecutableElement method) {
boolean returnTypeIsBoolean = method.getReturnType().getKind() == TypeKind.BOOLEAN ||
"java.lang.Boolean".equals( getQualifiedName( method.getReturnType() ) );
return method.getParameters().isEmpty()
&& Services.getAccessorNamingStrategy().isBooleanGetterName( method.getSimpleName().toString() )
&& returnTypeIsBoolean;
return isPublic( method ) &&
method.getParameters().isEmpty() &&
Services.getAccessorNamingStrategy().getMethodType( method ) == MethodType.GETTER;
}
public static boolean isSetterMethod(ExecutableElement method) {
return isPublic( method )
&& Services.getAccessorNamingStrategy().isSetterName( method.getSimpleName().toString() )
&& method.getParameters().size() == 1;
&& method.getParameters().size() == 1
&& accessorNamingStrategy.getMethodType( method ) == MethodType.SETTER;
}
public static boolean isAdderMethod(ExecutableElement method) {
return isPublic( method )
&& Services.getAccessorNamingStrategy().isAdderName( method.getSimpleName().toString() )
&& method.getParameters().size() == 1;
&& method.getParameters().size() == 1
&& accessorNamingStrategy.getMethodType( method ) == MethodType.ADDER;
}
private static boolean isPublic(ExecutableElement method) {
@ -84,23 +71,7 @@ public class Executables {
}
public static String getPropertyName(ExecutableElement getterOrSetterMethod) {
if ( isNonBooleanGetterMethod( getterOrSetterMethod ) ) {
return Services.getAccessorNamingStrategy().getPropertyNameForNonBooleanGetterName(
getterOrSetterMethod.getSimpleName().toString()
);
}
else if ( isBooleanGetterMethod( getterOrSetterMethod ) ) {
return Services.getAccessorNamingStrategy().getPropertyNameForBooleanGetterName(
getterOrSetterMethod.getSimpleName().toString()
);
}
else if ( isSetterMethod( getterOrSetterMethod ) ) {
return Services.getAccessorNamingStrategy().getPropertyNameForSetterName(
getterOrSetterMethod.getSimpleName().toString()
);
}
throw new IllegalArgumentException( "Executable " + getterOrSetterMethod + " is not getter or setter method." );
return accessorNamingStrategy.getPropertyName( getterOrSetterMethod );
}
/**
@ -109,51 +80,7 @@ public class Executables {
* {@code addChild(Child v)}, the element name would be 'Child'.
*/
public static String getElementNameForAdder(ExecutableElement adderMethod) {
if ( isAdderMethod( adderMethod ) ) {
return Services.getAccessorNamingStrategy().getElementNameForAdderName(
adderMethod.getSimpleName().toString()
);
}
throw new IllegalArgumentException( "Executable " + adderMethod + " is not an adder method." );
}
public static Set<String> getPropertyNames(List<ExecutableElement> propertyAccessors) {
Set<String> propertyNames = new HashSet<String>();
for ( ExecutableElement executableElement : propertyAccessors ) {
propertyNames.add( getPropertyName( executableElement ) );
}
return propertyNames;
}
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;
return accessorNamingStrategy.getElementName( adderMethod );
}
/**
@ -196,7 +123,8 @@ public class Executables {
elementUtils,
alreadyAdded,
asTypeElement( element.getSuperclass() ),
parentType );
parentType
);
}
for ( TypeMirror interfaceType : element.getInterfaces() ) {
@ -204,7 +132,8 @@ public class Executables {
elementUtils,
alreadyAdded,
asTypeElement( interfaceType ),
parentType );
parentType
);
}
}
@ -246,10 +175,11 @@ public class Executables {
/**
* @param elementUtils the elementUtils
* @param alreadyAdded the list of already collected methods of one type hierarchy (order is from sub-types to
* @param methods the list of already collected methods of one type hierarchy (order is from sub-types to
* super-types)
* @param executable the method to check
* @param parentType the type for which elements are collected
*
* @return {@code true}, iff the given executable was not yet overridden by a method in the given list.
*/
private static boolean wasNotYetOverridden(Elements elementUtils, List<ExecutableElement> alreadyAdded,

View File

@ -1,68 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2012-2014 Gunnar Morling (http://www.gunnarmorling.de/)
and/or other contributors as indicated by the @authors tag. See the
copyright.txt file in the distribution for a full listing of all
contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>mapstruct-spi</artifactId>
<packaging>jar</packaging>
<name>MapStruct Service Provider Interfaces</name>
<dependencies>
<!-- Test -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.easytesting</groupId>
<artifactId>fest-assert</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<executions>
<execution>
<id>check-style</id>
<phase>verify</phase>
<goals>
<goal>checkstyle</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -1,134 +0,0 @@
/**
* Copyright 2012-2015 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mapstruct.spi;
/**
* A service provider interface for the mapping between method names and properties.
*
* @author Christian Schuster
*/
public interface AccessorNamingStrategy {
/**
* Set the default {@link AccessorNamingStrategy} implementation. Custom implementations may use it to
* keep parts of the default behavior. This method is invoked before any other method of the interface.
* <p>
* Default implementation: Does nothing.
*
* @param defaultAccessorNamingStrategy The default implementation.
*/
void setDefaultAccessorNamingStrategy(AccessorNamingStrategy defaultAccessorNamingStrategy);
/**
* Determine if a method name defines a getter with a non-boolean return type.
* <p>
* Default implementation: Method name starts with "get" and is longer than 3 characters.
*
* @param methodName The method name.
* @return {@code true} if the method name can be a non-boolean getter, {@code false} otherwise.
*/
boolean isNonBooleanGetterName(String methodName);
/**
* Determine if a method name defines a getter with a boolean return type.
* <p>
* Default implementation: Method name starts with "is" and is longer than 2 characters.
*
* @param methodName The method name.
* @return {@code true} if the method name can be a boolean getter, {@code false} otherwise.
*/
boolean isBooleanGetterName(String methodName);
/**
* Determine if a method name defines a setter.
* <p>
* Default implementation: Method name starts with "set" and is longer than 3 characters.
*
* @param methodName The method name.
* @return {@code true} if the method name can be a setter, {@code false} otherwise.
*/
boolean isSetterName(String methodName);
/**
* Determine if a method name defines an adder.
* <p>
* Default implementation: Method name starts with "add" and is longer than 3 characters.
*
* @param methodName The method name.
* @return {@code true} if the method name can be an adder, {@code false} otherwise.
*/
boolean isAdderName(String methodName);
/**
* Extract the property name from a method name for which {@link #isNonBooleanGetterName(String)} returned
* {@code true}.
* <p>
* Default implementation: Remove first 3 characters ("get") of the method name, and
* {@link java.beans.Introspector#decapitalize(String) decapitalize} the result.
*
* @param methodName The method name, guaranteed to be a non-boolean getter name.
* @return The property name corresponding to the method name.
*/
String getPropertyNameForNonBooleanGetterName(String methodName);
/**
* Extract the property name from a method name for which {@link #isBooleanGetterName(String)} returned
* {@code true}.
* <p>
* Default implementation: Remove the first 2 characters ("is") of the method name, and
* {@link java.beans.Introspector#decapitalize(String) decapitalize} the result.
*
* @param methodName The method name, guaranteed to be a boolean getter name.
* @return The property name corresponding to the method name.
*/
String getPropertyNameForBooleanGetterName(String methodName);
/**
* Extract the property name from a method name for which {@link #isSetterName(String)} returned {@code true}.
* <p>
* Default implementation: Remove the first 3 characters ("set") of the method name, and
* {@link java.beans.Introspector#decapitalize(String) decapitalize} the result.
*
* @param methodName The method name, guaranteed to be a setter name.
* @return The property name corresponding to the method name.
*/
String getPropertyNameForSetterName(String methodName);
/**
* Extract the element name (singular form of the collection's property name) from a method name for which
* {@link #isAdderName(String)} returned {@code true}.
* <p>
* Default implementation: Remove the first 3 characters ("add") of the method name, and
* {@link java.beans.Introspector#decapitalize(String) decapitalize} the result.
*
* @param methodName The method name, guaranteed to be an adder name.
* @return The element name corresponding to the method name.
*/
String getElementNameForAdderName(String methodName);
/**
* Extract the non-boolean getter method name from the setter method name of the same property.
* <p>
* Default implementation: Replace the first 3 characters ("get") of the method name with "set".
*
* @param methodName The method name, guaranteed to be a setter name.
* @return The corresponding non-boolean getter method name.
*/
String getNonBooleanGetterNameForSetterName(String methodName);
}