diff --git a/documentation/src/main/asciidoc/mapstruct-reference-guide.asciidoc b/documentation/src/main/asciidoc/mapstruct-reference-guide.asciidoc index df25b4038..2d2327e57 100644 --- a/documentation/src/main/asciidoc/mapstruct-reference-guide.asciidoc +++ b/documentation/src/main/asciidoc/mapstruct-reference-guide.asciidoc @@ -2757,7 +2757,7 @@ public class CustomAccessorNamingStrategy extends DefaultAccessorNamingStrategy @Override public String getPropertyName(ExecutableElement getterOrSetterMethod) { String methodName = getterOrSetterMethod.getSimpleName().toString(); - return Introspector.decapitalize( methodName.startsWith( "with" ) ? methodName.substring( 4 ) : methodName ); + return IntrospectorUtils.decapitalize( methodName.startsWith( "with" ) ? methodName.substring( 4 ) : methodName ); } } ---- diff --git a/integrationtest/src/test/resources/namingStrategyTest/strategy/src/main/java/org/mapstruct/itest/naming/CustomAccessorNamingStrategy.java b/integrationtest/src/test/resources/namingStrategyTest/strategy/src/main/java/org/mapstruct/itest/naming/CustomAccessorNamingStrategy.java index 3126a08a9..eb9d07a24 100644 --- a/integrationtest/src/test/resources/namingStrategyTest/strategy/src/main/java/org/mapstruct/itest/naming/CustomAccessorNamingStrategy.java +++ b/integrationtest/src/test/resources/namingStrategyTest/strategy/src/main/java/org/mapstruct/itest/naming/CustomAccessorNamingStrategy.java @@ -5,13 +5,12 @@ */ package org.mapstruct.itest.naming; -import java.beans.Introspector; - import javax.lang.model.element.ExecutableElement; import javax.lang.model.type.TypeKind; import org.mapstruct.ap.spi.AccessorNamingStrategy; import org.mapstruct.ap.spi.DefaultAccessorNamingStrategy; +import org.mapstruct.ap.spi.util.IntrospectorUtils; /** * A custom {@link AccessorNamingStrategy} recognizing getters in the form of {@code property()} and setters in the @@ -42,13 +41,14 @@ public class CustomAccessorNamingStrategy extends DefaultAccessorNamingStrategy @Override public String getPropertyName(ExecutableElement getterOrSetterMethod) { String methodName = getterOrSetterMethod.getSimpleName().toString(); - return Introspector.decapitalize( methodName.startsWith( "with" ) ? methodName.substring( 4 ) : methodName ); + return IntrospectorUtils.decapitalize( + methodName.startsWith( "with" ) ? methodName.substring( 4 ) : methodName ); } @Override public String getElementName(ExecutableElement adderMethod) { String methodName = adderMethod.getSimpleName().toString(); - return Introspector.decapitalize( methodName.substring( 3 ) ); + return IntrospectorUtils.decapitalize( methodName.substring( 3 ) ); } } diff --git a/processor/src/main/java/org/mapstruct/ap/spi/DefaultAccessorNamingStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/DefaultAccessorNamingStrategy.java index 118ad2ba9..8f82c8f89 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/DefaultAccessorNamingStrategy.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/DefaultAccessorNamingStrategy.java @@ -5,7 +5,6 @@ */ package org.mapstruct.ap.spi; -import java.beans.Introspector; import java.util.regex.Pattern; import javax.lang.model.element.ExecutableElement; @@ -16,6 +15,7 @@ import javax.lang.model.type.TypeMirror; import javax.lang.model.util.SimpleElementVisitor6; import javax.lang.model.util.SimpleTypeVisitor6; +import org.mapstruct.ap.spi.util.IntrospectorUtils; /** * The default JavaBeans-compliant implementation of the {@link AccessorNamingStrategy} service provider interface. @@ -157,12 +157,12 @@ public class DefaultAccessorNamingStrategy implements AccessorNamingStrategy { public String getPropertyName(ExecutableElement getterOrSetterMethod) { String methodName = getterOrSetterMethod.getSimpleName().toString(); if ( methodName.startsWith( "is" ) || methodName.startsWith( "get" ) || methodName.startsWith( "set" ) ) { - return Introspector.decapitalize( methodName.substring( methodName.startsWith( "is" ) ? 2 : 3 ) ); + return IntrospectorUtils.decapitalize( methodName.substring( methodName.startsWith( "is" ) ? 2 : 3 ) ); } else if ( isBuilderSetter( getterOrSetterMethod ) ) { return methodName; } - return Introspector.decapitalize( methodName.substring( methodName.startsWith( "is" ) ? 2 : 3 ) ); + return IntrospectorUtils.decapitalize( methodName.substring( methodName.startsWith( "is" ) ? 2 : 3 ) ); } /** @@ -177,10 +177,10 @@ public class DefaultAccessorNamingStrategy implements AccessorNamingStrategy { @Override public String getElementName(ExecutableElement adderMethod) { String methodName = adderMethod.getSimpleName().toString(); - return Introspector.decapitalize( methodName.substring( 3 ) ); + return IntrospectorUtils.decapitalize( methodName.substring( 3 ) ); } - /** + /** * Helper method, to obtain the fully qualified name of a type. * * @param type input type diff --git a/processor/src/main/java/org/mapstruct/ap/spi/util/IntrospectorUtils.java b/processor/src/main/java/org/mapstruct/ap/spi/util/IntrospectorUtils.java new file mode 100644 index 000000000..fccc22b38 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/spi/util/IntrospectorUtils.java @@ -0,0 +1,47 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.spi.util; + +/** + * Utilities for tools to learn about the properties, events, and methods supported by a target Java Bean. + * It is mainly needed to avoid using {@link java.beans.Introspector} class which is part of java.desktop module + * from java 9, thus avoiding pulling a module ( of around 10 MB ) for using a single class. + * + * @author Saheb Preet Singh + */ +public class IntrospectorUtils { + + private IntrospectorUtils() { + } + + /** + * Utility method to take a string and convert it to normal Java variable + * name capitalization. This normally means converting the first + * character from upper case to lower case, but in the (unusual) special + * case when there is more than one character and both the first and + * second characters are upper case, we leave it alone. + *
+ * Thus "FooBah" becomes "fooBah" and "X" becomes "x", but "URL" stays + * as "URL". + * + * @param name The string to be decapitalized. + * + * @return The decapitalized version of the string. + */ + public static String decapitalize(String name) { + if ( name == null || name.length() == 0 ) { + return name; + } + if ( name.length() > 1 && Character.isUpperCase( name.charAt( 1 ) ) && + Character.isUpperCase( name.charAt( 0 ) ) ) { + return name; + } + char[] chars = name.toCharArray(); + chars[0] = Character.toLowerCase( chars[0] ); + return new String( chars ); + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/spi/util/package-info.java b/processor/src/main/java/org/mapstruct/ap/spi/util/package-info.java new file mode 100644 index 000000000..7b8f374cf --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/spi/util/package-info.java @@ -0,0 +1,7 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ + +package org.mapstruct.ap.spi.util; diff --git a/processor/src/test/java/org/mapstruct/ap/spi/util/IntrospectorUtilsTest.java b/processor/src/test/java/org/mapstruct/ap/spi/util/IntrospectorUtilsTest.java new file mode 100644 index 000000000..02c171e63 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/spi/util/IntrospectorUtilsTest.java @@ -0,0 +1,28 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.spi.util; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Test; + +/** + * @author Saheb Preet Singh + */ +public class IntrospectorUtilsTest { + + @Test + public void testDecapitalize() throws Exception { + assertThat( IntrospectorUtils.decapitalize( null ) ).isNull(); + assertThat( IntrospectorUtils.decapitalize( "" ) ).isEqualTo( "" ); + assertThat( IntrospectorUtils.decapitalize( "URL" ) ).isEqualTo( "URL" ); + assertThat( IntrospectorUtils.decapitalize( "FooBar" ) ).isEqualTo( "fooBar" ); + assertThat( IntrospectorUtils.decapitalize( "PArtialCapitalized" ) ).isEqualTo( "PArtialCapitalized" ); + assertThat( IntrospectorUtils.decapitalize( "notCapitalized" ) ).isEqualTo( "notCapitalized" ); + assertThat( IntrospectorUtils.decapitalize( "a" ) ).isEqualTo( "a" ); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/naming/spi/CustomAccessorNamingStrategy.java b/processor/src/test/java/org/mapstruct/ap/test/naming/spi/CustomAccessorNamingStrategy.java index eb99c4370..d8c4ddd8c 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/naming/spi/CustomAccessorNamingStrategy.java +++ b/processor/src/test/java/org/mapstruct/ap/test/naming/spi/CustomAccessorNamingStrategy.java @@ -5,11 +5,10 @@ */ package org.mapstruct.ap.test.naming.spi; -import java.beans.Introspector; - import javax.lang.model.element.ExecutableElement; import javax.lang.model.type.TypeKind; +import org.mapstruct.ap.spi.util.IntrospectorUtils; import org.mapstruct.ap.spi.AccessorNamingStrategy; import org.mapstruct.ap.spi.DefaultAccessorNamingStrategy; @@ -42,13 +41,14 @@ public class CustomAccessorNamingStrategy extends DefaultAccessorNamingStrategy @Override public String getPropertyName(ExecutableElement getterOrSetterMethod) { String methodName = getterOrSetterMethod.getSimpleName().toString(); - return Introspector.decapitalize( methodName.startsWith( "with" ) ? methodName.substring( 4 ) : methodName ); + return IntrospectorUtils.decapitalize( + methodName.startsWith( "with" ) ? methodName.substring( 4 ) : methodName ); } @Override public String getElementName(ExecutableElement adderMethod) { String methodName = adderMethod.getSimpleName().toString(); - return Introspector.decapitalize( methodName.substring( 3 ) ); + return IntrospectorUtils.decapitalize( methodName.substring( 3 ) ); } }