#365 make accessor naming configurable

This commit is contained in:
Christian Schuster 2015-02-24 02:13:37 +01:00 committed by Andreas Gudian
parent 3be68b233e
commit 59c791034c
10 changed files with 360 additions and 33 deletions

View File

@ -77,6 +77,12 @@
<version>${mapstruct.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>mapstruct-spi</artifactId>
<version>${mapstruct.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
</profile>
<profile>

View File

@ -206,6 +206,11 @@
<artifactId>mapstruct-processor</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>mapstruct-spi</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.maven.shared</groupId>
<artifactId>maven-verifier</artifactId>

View File

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

View File

@ -43,6 +43,10 @@
<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
@ -150,6 +154,11 @@
</goals>
<configuration>
<minimizeJar>true</minimizeJar>
<artifactSet>
<excludes>
<exclude>${project.groupId}:mapstruct-spi</exclude>
</excludes>
</artifactSet>
<filters>
<filter>
<artifact>org.freemarker:freemarker</artifact>

View File

@ -22,6 +22,7 @@ import java.util.HashSet;
import java.util.Set;
import org.mapstruct.ap.model.common.Type;
import org.mapstruct.ap.services.Services;
/**
* This wrapper handles the situation were an assignment is done via the setter.
@ -44,7 +45,8 @@ public class SetterWrapperForCollectionsAndMaps extends AssignmentWrapper {
String targetSetterName,
Assignment newCollectionOrMapAssignment) {
super( decoratedAssignment );
this.targetGetterName = "get" + targetSetterName.substring( 3 );
this.targetGetterName =
Services.getAccessorNamingStrategy().getNonBooleanGetterNameForSetterName( targetSetterName );
this.newCollectionOrMapAssignment = newCollectionOrMapAssignment;
}

View File

@ -0,0 +1,76 @@
/**
* 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.services;
import java.beans.Introspector;
import org.mapstruct.spi.AccessorNamingStrategy;
/**
* The default JavaBeans-compliant implementation of the {@link AccessorNamingStrategy} service provider interface.
*
* @author Christian Schuster
*/
class DefaultAccessorNamingStrategy implements AccessorNamingStrategy {
@Override
public boolean isNonBooleanGetterName(String methodName) {
return methodName.startsWith( "get" ) && methodName.length() > 3;
}
@Override
public boolean isBooleanGetterName(String methodName) {
return methodName.startsWith( "is" ) && methodName.length() > 2;
}
@Override
public boolean isSetterName(String methodName) {
return methodName.startsWith( "set" ) && methodName.length() > 3;
}
@Override
public boolean isAdderName(String methodName) {
return methodName.startsWith( "add" ) && methodName.length() > 3;
}
@Override
public String getPropertyNameForNonBooleanGetterName(String methodName) {
return Introspector.decapitalize( methodName.substring( 3 ) );
}
@Override
public String getPropertyNameForBooleanGetterName(String methodName) {
return Introspector.decapitalize( methodName.substring( 2 ) );
}
@Override
public String getPropertyNameForSetterName(String methodName) {
return Introspector.decapitalize( methodName.substring( 3 ) );
}
@Override
public String getElementNameForAdderName(String methodName) {
return Introspector.decapitalize( methodName.substring( 3 ) );
}
@Override
public String getNonBooleanGetterNameForSetterName(String methodName) {
return "get" + methodName.substring( 3 );
}
}

View File

@ -0,0 +1,68 @@
/**
* 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.services;
import java.util.ServiceLoader;
import org.mapstruct.spi.AccessorNamingStrategy;
/**
* A simple locator for SPI implementations.
*
* @author Christian Schuster
*/
public class Services {
private Services() {
}
/**
* Obtain an implementation of {@link AccessorNamingStrategy}. If no specialized implementation is found using
* {@link ServiceLoader}, a JavaBeans-compliant default implementation is returned.
*
* @return The implementation of {@link AccessorNamingStrategy}.
* @throws IllegalStateException If more than one implementation is found by
* {@link ServiceLoader#load(Class, ClassLoader)}.
*/
public static AccessorNamingStrategy getAccessorNamingStrategy() {
AccessorNamingStrategy impl = get( AccessorNamingStrategy.class );
if ( impl == null ) {
impl = new DefaultAccessorNamingStrategy();
}
return impl;
}
private static <T> T get(Class<T> spi) {
T matchingImplementation = null;
for ( T implementation : ServiceLoader.load( spi, spi.getClassLoader() ) ) {
if ( matchingImplementation == null ) {
matchingImplementation = implementation;
}
else {
throw new IllegalStateException(
"Multiple implementations have been found for the service provider interface "
+ spi.getCanonicalName() + ": " + matchingImplementation.getClass().getCanonicalName() + ", "
+ implementation.getClass().getCanonicalName() + "." );
}
}
return matchingImplementation;
}
}

View File

@ -18,7 +18,6 @@
*/
package org.mapstruct.ap.util;
import java.beans.Introspector;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@ -34,6 +33,8 @@ 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 static javax.lang.model.util.ElementFilter.methodsIn;
import static org.mapstruct.ap.util.SpecificCompilerWorkarounds.replaceTypeElementIfNecessary;
@ -52,41 +53,30 @@ public class Executables {
}
private static boolean isNonBooleanGetterMethod(ExecutableElement method) {
String name = method.getSimpleName().toString();
return method.getParameters().isEmpty() &&
name.startsWith( "get" ) &&
name.length() > 3 &&
method.getReturnType().getKind() != TypeKind.VOID;
return method.getParameters().isEmpty()
&& Services.getAccessorNamingStrategy().isNonBooleanGetterName( method.getSimpleName().toString() )
&& method.getReturnType().getKind() != TypeKind.VOID;
}
private static boolean isBooleanGetterMethod(ExecutableElement method) {
String name = method.getSimpleName().toString();
boolean returnTypeIsBoolean = method.getReturnType().getKind() == TypeKind.BOOLEAN ||
"java.lang.Boolean".equals( getQualifiedName( method.getReturnType() ) );
return method.getParameters().isEmpty() &&
name.startsWith( "is" ) &&
name.length() > 2 &&
returnTypeIsBoolean;
return method.getParameters().isEmpty()
&& Services.getAccessorNamingStrategy().isBooleanGetterName( method.getSimpleName().toString() )
&& returnTypeIsBoolean;
}
public static boolean isSetterMethod(ExecutableElement method) {
String name = method.getSimpleName().toString();
return isPublic( method ) &&
name.startsWith( "set" ) &&
name.length() > 3 &&
method.getParameters().size() == 1;
return isPublic( method )
&& Services.getAccessorNamingStrategy().isSetterName( method.getSimpleName().toString() )
&& method.getParameters().size() == 1;
}
public static boolean isAdderMethod(ExecutableElement method) {
String name = method.getSimpleName().toString();
return isPublic( method ) &&
name.startsWith( "add" ) && name.length() > 3 &&
method.getParameters().size() == 1;
return isPublic( method )
&& Services.getAccessorNamingStrategy().isAdderName( method.getSimpleName().toString() )
&& method.getParameters().size() == 1;
}
private static boolean isPublic(ExecutableElement method) {
@ -95,18 +85,18 @@ public class Executables {
public static String getPropertyName(ExecutableElement getterOrSetterMethod) {
if ( isNonBooleanGetterMethod( getterOrSetterMethod ) ) {
return Introspector.decapitalize(
getterOrSetterMethod.getSimpleName().toString().substring( 3 )
return Services.getAccessorNamingStrategy().getPropertyNameForNonBooleanGetterName(
getterOrSetterMethod.getSimpleName().toString()
);
}
else if ( isBooleanGetterMethod( getterOrSetterMethod ) ) {
return Introspector.decapitalize(
getterOrSetterMethod.getSimpleName().toString().substring( 2 )
return Services.getAccessorNamingStrategy().getPropertyNameForBooleanGetterName(
getterOrSetterMethod.getSimpleName().toString()
);
}
else if ( isSetterMethod( getterOrSetterMethod ) ) {
return Introspector.decapitalize(
getterOrSetterMethod.getSimpleName().toString().substring( 3 )
return Services.getAccessorNamingStrategy().getPropertyNameForSetterName(
getterOrSetterMethod.getSimpleName().toString()
);
}
@ -120,8 +110,8 @@ public class Executables {
*/
public static String getElementNameForAdder(ExecutableElement adderMethod) {
if ( isAdderMethod( adderMethod ) ) {
return Introspector.decapitalize(
adderMethod.getSimpleName().toString().substring( 3 )
return Services.getAccessorNamingStrategy().getElementNameForAdderName(
adderMethod.getSimpleName().toString()
);
}

68
spi/pom.xml Normal file
View File

@ -0,0 +1,68 @@
<?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

@ -0,0 +1,102 @@
/**
* 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 {
/**
* Determine if a method name defines a getter with a non-boolean return type.
*
* @param methodName The method name.
* @return <code>true</code> if the method name can be a non-boolean getter, <code>false</code> otherwise.
*/
boolean isNonBooleanGetterName(String methodName);
/**
* Determine if a method name defines a getter with a boolean return type.
*
* @param methodName The method name.
* @return <code>true</code> if the method name can be a boolean getter, <code>false</code> otherwise.
*/
boolean isBooleanGetterName(String methodName);
/**
* Determine if a method name defines a setter.
*
* @param methodName The method name.
* @return <code>true</code> if the method name can be a setter, <code>false</code> otherwise.
*/
boolean isSetterName(String methodName);
/**
* Determine if a method name defines an adder.
*
* @param methodName The method name.
* @return <code>true</code> if the method name can be an adder, <code>false</code> otherwise.
*/
boolean isAdderName(String methodName);
/**
* Extract the property name from a method name for which {@link #isNonBooleanGetterName(String)} returned
* <code>true</code>.
*
* @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</code>.
*
* @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</code>.
*
* @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</code>.
*
* @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.
*
* @param methodName The method name, guaranteed to be a setter name.
* @return The corresponding non-boolean getter method name.
*/
String getNonBooleanGetterNameForSetterName(String methodName);
}