diff --git a/AUTHORS b/AUTHORS index 547246d989..9281c9a9b1 100755 --- a/AUTHORS +++ b/AUTHORS @@ -49,6 +49,7 @@ Takuya Murakami Thomas Darimont Till Brychcy Victor Williams Stafusa da Silva +Yonatan Sherwin Yun Zhi Lin By adding your name to this list, you grant full and irrevocable copyright and patent indemnity to Project Lombok and all use of Project Lombok in relation to all commits you add to Project Lombok, and you certify that you have the right to do so. diff --git a/src/core/lombok/ConfigurationKeys.java b/src/core/lombok/ConfigurationKeys.java index b8cd442ace..12307471d6 100644 --- a/src/core/lombok/ConfigurationKeys.java +++ b/src/core/lombok/ConfigurationKeys.java @@ -558,6 +558,14 @@ private ConfigurationKeys() {} */ public static final ConfigurationKey ACCESSORS_FLUENT = new ConfigurationKey("lombok.accessors.fluent", "Generate getters and setters using only the field name (no get/set prefix) (default: false).") {}; + /** + * lombok configuration: {@code lombok.accessors.javaBeansSpecCapitalization} = {@code true} | {@code false}. + * + * For any class without an {@code @Accessors} that explicitly defines the {@code javaBeansSpecCapitalization} option, this value is used (default = false). + */ + public static final ConfigurationKey ACCESSORS_JAVA_BEANS_SPEC_CAPITALIZATION = new ConfigurationKey("lombok.accessors.javaBeansSpecCapitalization", "Generating accessors name according to the JavaBeans Spec (default: false).") {}; + + // ----- ExtensionMethod ----- /** diff --git a/src/core/lombok/core/handlers/HandlerUtil.java b/src/core/lombok/core/handlers/HandlerUtil.java index f88d1679ba..f45481d497 100644 --- a/src/core/lombok/core/handlers/HandlerUtil.java +++ b/src/core/lombok/core/handlers/HandlerUtil.java @@ -585,11 +585,13 @@ private static String toAccessorName(AST ast, AnnotationValues prefix = explicitPrefix ? Arrays.asList(ac.prefix()) : ast.readConfiguration(ConfigurationKeys.ACCESSORS_PREFIX); boolean fluent = explicitFluent ? ac.fluent() : Boolean.TRUE.equals(ast.readConfiguration(ConfigurationKeys.ACCESSORS_FLUENT)); + boolean javaBeansSpecCapitalization = explicitJavaBeansSpecCapitalization ? ac.javaBeansSpecCapitalization() : Boolean.TRUE.equals(ast.readConfiguration(ConfigurationKeys.ACCESSORS_JAVA_BEANS_SPEC_CAPITALIZATION)); fieldName = removePrefix(fieldName, prefix); if (fieldName == null) return null; @@ -602,7 +604,7 @@ private static String toAccessorName(AST ast, AnnotationValues toAllAccessorNames(AST ast, AnnotationValue boolean explicitPrefix = accessors != null && accessors.isExplicit("prefix"); boolean explicitFluent = accessors != null && accessors.isExplicit("fluent"); + boolean explicitJavaBeansSpecCapitalization = accessors != null && accessors.isExplicit("javaBeansSpecCapitalization"); - Accessors ac = (explicitPrefix || explicitFluent) ? accessors.getInstance() : null; + Accessors ac = (explicitPrefix || explicitFluent || explicitJavaBeansSpecCapitalization) ? accessors.getInstance() : null; List prefix = explicitPrefix ? Arrays.asList(ac.prefix()) : ast.readConfiguration(ConfigurationKeys.ACCESSORS_PREFIX); boolean fluent = explicitFluent ? ac.fluent() : Boolean.TRUE.equals(ast.readConfiguration(ConfigurationKeys.ACCESSORS_FLUENT)); - + boolean javaBeansSpecCapitalization = explicitJavaBeansSpecCapitalization ? ac.javaBeansSpecCapitalization() : Boolean.TRUE.equals(ast.readConfiguration(ConfigurationKeys.ACCESSORS_JAVA_BEANS_SPEC_CAPITALIZATION)); + fieldName = removePrefix(fieldName, prefix); if (fieldName == null) return Collections.emptyList(); @@ -691,8 +695,8 @@ private static List toAllAccessorNames(AST ast, AnnotationValue if (adhereToFluent && fluent) { names.add(baseName); } else { - names.add(buildAccessorName(normalPrefix, baseName)); - if (!normalPrefix.equals(booleanPrefix)) names.add(buildAccessorName(booleanPrefix, baseName)); + names.add(buildAccessorName(normalPrefix, baseName, javaBeansSpecCapitalization)); + if (!normalPrefix.equals(booleanPrefix)) names.add(buildAccessorName(booleanPrefix, baseName, javaBeansSpecCapitalization)); } } @@ -723,17 +727,34 @@ private static List toBaseNames(CharSequence fieldName, boolean isBoolea * @return prefix + smartly title-cased suffix. For example, {@code setRunning}. */ public static String buildAccessorName(String prefix, String suffix) { + return buildAccessorName(prefix, suffix, false); + } + + /** + * @param prefix Something like {@code get} or {@code set} or {@code is}. + * @param suffix Something like {@code running}. + * @param shouldFollowJavaBeansSpecCapitalization {@code boolean} that indicates whether the capitalization rules should follow JavaBeanSpec + * @return if shouldFollowJavaBeansSpecCapitalization is {@code true} and name start with only single lowercase letter, returns simple suffix+prefix. For example, {@code setaFieldName} + * otherwise, returns prefix + smartly title-cased suffix. For example, {@code setRunning}. + */ + private static String buildAccessorName(String prefix, String suffix, boolean shouldFollowJavaBeansSpecCapitalization) { if (suffix.length() == 0) return prefix; if (prefix.length() == 0) return suffix; char first = suffix.charAt(0); - if (Character.isLowerCase(first)) { - boolean useUpperCase = suffix.length() > 2 && - (Character.isTitleCase(suffix.charAt(1)) || Character.isUpperCase(suffix.charAt(1))); - suffix = String.format("%s%s", - useUpperCase ? Character.toUpperCase(first) : Character.toTitleCase(first), - suffix.subSequence(1, suffix.length())); + if (!Character.isLowerCase(first)) { + return String.format("%s%s", prefix, suffix); } + + boolean useUpperCase = suffix.length() > 2 && + (Character.isTitleCase(suffix.charAt(1)) || Character.isUpperCase(suffix.charAt(1))); + if (shouldFollowJavaBeansSpecCapitalization && useUpperCase) { + return String.format("%s%s", prefix, suffix); + } + + suffix = String.format("%s%s", + useUpperCase ? Character.toUpperCase(first) : Character.toTitleCase(first), + suffix.subSequence(1, suffix.length())); return String.format("%s%s", prefix, suffix); } diff --git a/src/core/lombok/experimental/Accessors.java b/src/core/lombok/experimental/Accessors.java index dc9ae4b09d..b3da9a5b91 100644 --- a/src/core/lombok/experimental/Accessors.java +++ b/src/core/lombok/experimental/Accessors.java @@ -54,6 +54,15 @@ */ boolean chain() default false; + /** + * If true, accessors names will be capitalized according to JavaBeans capitalization rules. + * If {@code true}, an accessor for a field that starts with a single lowercase letter followed by a capital letter, + * wont capitalize the first letter (named {@code getaFieldName()}, not for example {@code getAFieldName}). + * default: false + * @return + */ + boolean javaBeansSpecCapitalization() default false; + /** * If present, only fields with any of the stated prefixes are given the getter/setter treatment. * Note that a prefix only counts if the next character is NOT a lowercase character or the last diff --git a/test/transform/resource/after-delombok/GetterWithJavaBeansSpecCapitalization.java b/test/transform/resource/after-delombok/GetterWithJavaBeansSpecCapitalization.java new file mode 100644 index 0000000000..06026c1bba --- /dev/null +++ b/test/transform/resource/after-delombok/GetterWithJavaBeansSpecCapitalization.java @@ -0,0 +1,28 @@ +class GetterWithoutJavaBeansSpecCapitalization { + int a; + int aField; + @java.lang.SuppressWarnings("all") + public int getA() { + return this.a; + } + @java.lang.SuppressWarnings("all") + public int getAField() { + return this.aField; + } +} + + +class GetterWithJavaBeansSpecCapitalization { + int a; + int aField; + @java.lang.SuppressWarnings("all") + public int getA() { + return this.a; + } + @java.lang.SuppressWarnings("all") + public int getaField() { + return this.aField; + } +} + + diff --git a/test/transform/resource/after-delombok/SetterWithJavaBeansSpecCapitalization.java b/test/transform/resource/after-delombok/SetterWithJavaBeansSpecCapitalization.java new file mode 100644 index 0000000000..24c8e883ef --- /dev/null +++ b/test/transform/resource/after-delombok/SetterWithJavaBeansSpecCapitalization.java @@ -0,0 +1,27 @@ +class SetterWithoutJavaBeansSpecCapitalization { + int a; + int aField; + @java.lang.SuppressWarnings("all") + public void setA(final int a) { + this.a = a; + } + @java.lang.SuppressWarnings("all") + public void setAField(final int aField) { + this.aField = aField; + } +} + +class SetterWithJavaBeansSpecCapitalization { + int a; + int aField; + @java.lang.SuppressWarnings("all") + public void setA(final int a) { + this.a = a; + } + @java.lang.SuppressWarnings("all") + public void setaField(final int aField) { + this.aField = aField; + } +} + + diff --git a/test/transform/resource/after-delombok/ValueWithJavaBeansSpecCapitalization.java b/test/transform/resource/after-delombok/ValueWithJavaBeansSpecCapitalization.java new file mode 100644 index 0000000000..20139baf1d --- /dev/null +++ b/test/transform/resource/after-delombok/ValueWithJavaBeansSpecCapitalization.java @@ -0,0 +1,77 @@ +final class ValueWithJavaBeansSpecCapitalization { + private final int aField; + + @java.lang.SuppressWarnings("all") + public ValueWithJavaBeansSpecCapitalization(final int aField) { + this.aField = aField; + } + + @java.lang.SuppressWarnings("all") + public int getaField() { + return this.aField; + } + + @java.lang.Override + @java.lang.SuppressWarnings("all") + public boolean equals(final java.lang.Object o) { + if (o == this) return true; + if (!(o instanceof ValueWithJavaBeansSpecCapitalization)) return false; + final ValueWithJavaBeansSpecCapitalization other = (ValueWithJavaBeansSpecCapitalization) o; + if (this.getaField() != other.getaField()) return false; + return true; + } + + @java.lang.Override + @java.lang.SuppressWarnings("all") + public int hashCode() { + final int PRIME = 59; + int result = 1; + result = result * PRIME + this.getaField(); + return result; + } + + @java.lang.Override + @java.lang.SuppressWarnings("all") + public java.lang.String toString() { + return "ValueWithJavaBeansSpecCapitalization(aField=" + this.getaField() + ")"; + } +} + +final class ValueWithoutJavaBeansSpecCapitalization { + private final int aField; + + @java.lang.SuppressWarnings("all") + public ValueWithoutJavaBeansSpecCapitalization(final int aField) { + this.aField = aField; + } + + @java.lang.SuppressWarnings("all") + public int getAField() { + return this.aField; + } + + @java.lang.Override + @java.lang.SuppressWarnings("all") + public boolean equals(final java.lang.Object o) { + if (o == this) return true; + if (!(o instanceof ValueWithoutJavaBeansSpecCapitalization)) return false; + final ValueWithoutJavaBeansSpecCapitalization other = (ValueWithoutJavaBeansSpecCapitalization) o; + if (this.getAField() != other.getAField()) return false; + return true; + } + + @java.lang.Override + @java.lang.SuppressWarnings("all") + public int hashCode() { + final int PRIME = 59; + int result = 1; + result = result * PRIME + this.getAField(); + return result; + } + + @java.lang.Override + @java.lang.SuppressWarnings("all") + public java.lang.String toString() { + return "ValueWithoutJavaBeansSpecCapitalization(aField=" + this.getAField() + ")"; + } +} diff --git a/test/transform/resource/after-delombok/WithOnJavaBeansSpecCapitalization.java b/test/transform/resource/after-delombok/WithOnJavaBeansSpecCapitalization.java new file mode 100644 index 0000000000..afed2f0e0d --- /dev/null +++ b/test/transform/resource/after-delombok/WithOnJavaBeansSpecCapitalization.java @@ -0,0 +1,29 @@ +class WithOnJavaBeansSpecCapitalization { + int aField; + + WithOnJavaBeansSpecCapitalization(int aField) { + } + + /** + * @return a clone of this object, except with this updated property (returns {@code this} if an identical value is passed). + */ + @java.lang.SuppressWarnings("all") + public WithOnJavaBeansSpecCapitalization withaField(final int aField) { + return this.aField == aField ? this : new WithOnJavaBeansSpecCapitalization(aField); + } +} + +class WithOffJavaBeansSpecCapitalization { + int aField; + + WithOffJavaBeansSpecCapitalization(int aField) { + } + + /** + * @return a clone of this object, except with this updated property (returns {@code this} if an identical value is passed). + */ + @java.lang.SuppressWarnings("all") + public WithOffJavaBeansSpecCapitalization withAField(final int aField) { + return this.aField == aField ? this : new WithOffJavaBeansSpecCapitalization(aField); + } +} diff --git a/test/transform/resource/after-ecj/GetterWithJavaBeansSpecCapitalization.java b/test/transform/resource/after-ecj/GetterWithJavaBeansSpecCapitalization.java new file mode 100644 index 0000000000..d605b4acfc --- /dev/null +++ b/test/transform/resource/after-ecj/GetterWithJavaBeansSpecCapitalization.java @@ -0,0 +1,33 @@ +class GetterWithoutJavaBeansSpecCapitalization { + @lombok.Getter int a; + @lombok.Getter int aField; + + GetterWithoutJavaBeansSpecCapitalization() { + super(); + } + + public @java.lang.SuppressWarnings("all") int getA() { + return this.a; + } + + public @java.lang.SuppressWarnings("all") int getAField() { + return this.aField; + } +} + +@lombok.experimental.Accessors(javaBeansSpecCapitalization = true) class GetterWithJavaBeansSpecCapitalization { + @lombok.Getter int a; + @lombok.Getter int aField; + + GetterWithJavaBeansSpecCapitalization() { + super(); + } + + public @java.lang.SuppressWarnings("all") int getA() { + return this.a; + } + + public @java.lang.SuppressWarnings("all") int getaField() { + return this.aField; + } +} diff --git a/test/transform/resource/after-ecj/SetterWithJavaBeansSpecCapitalization.java b/test/transform/resource/after-ecj/SetterWithJavaBeansSpecCapitalization.java new file mode 100644 index 0000000000..9907621fd5 --- /dev/null +++ b/test/transform/resource/after-ecj/SetterWithJavaBeansSpecCapitalization.java @@ -0,0 +1,33 @@ +class SetterWithoutJavaBeansSpecCapitalization { + @lombok.Setter int a; + @lombok.Setter int aField; + + SetterWithoutJavaBeansSpecCapitalization() { + super(); + } + + public @java.lang.SuppressWarnings("all") void setA(final int a) { + this.a = a; + } + + public @java.lang.SuppressWarnings("all") void setAField(final int aField) { + this.aField = aField; + } +} + +@lombok.experimental.Accessors(javaBeansSpecCapitalization = true) class SetterWithJavaBeansSpecCapitalization { + @lombok.Setter int a; + @lombok.Setter int aField; + + SetterWithJavaBeansSpecCapitalization() { + super(); + } + + public @java.lang.SuppressWarnings("all") void setA(final int a) { + this.a = a; + } + + public @java.lang.SuppressWarnings("all") void setaField(final int aField) { + this.aField = aField; + } +} diff --git a/test/transform/resource/after-ecj/ValueWithJavaBeansSpecCapitalization.java b/test/transform/resource/after-ecj/ValueWithJavaBeansSpecCapitalization.java new file mode 100644 index 0000000000..f41fcf5835 --- /dev/null +++ b/test/transform/resource/after-ecj/ValueWithJavaBeansSpecCapitalization.java @@ -0,0 +1,58 @@ +final @lombok.Value @lombok.experimental.Accessors(javaBeansSpecCapitalization = true) class ValueWithJavaBeansSpecCapitalization { + private final int aField; + public @java.lang.SuppressWarnings("all") int getaField() { + return this.aField; + } + public @java.lang.Override @java.lang.SuppressWarnings("all") boolean equals(final java.lang.Object o) { + if ((o == this)) + return true; + if ((! (o instanceof ValueWithJavaBeansSpecCapitalization))) + return false; + final ValueWithJavaBeansSpecCapitalization other = (ValueWithJavaBeansSpecCapitalization) o; + if ((this.getaField() != other.getaField())) + return false; + return true; + } + public @java.lang.Override @java.lang.SuppressWarnings("all") int hashCode() { + final int PRIME = 59; + int result = 1; + result = ((result * PRIME) + this.getaField()); + return result; + } + public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() { + return (("ValueWithJavaBeansSpecCapitalization(aField=" + this.getaField()) + ")"); + } + public @java.lang.SuppressWarnings("all") ValueWithJavaBeansSpecCapitalization(final int aField) { + super(); + this.aField = aField; + } +} +final @lombok.Value class ValueWithoutJavaBeansSpecCapitalization { + private final int aField; + public @java.lang.SuppressWarnings("all") int getAField() { + return this.aField; + } + public @java.lang.Override @java.lang.SuppressWarnings("all") boolean equals(final java.lang.Object o) { + if ((o == this)) + return true; + if ((! (o instanceof ValueWithoutJavaBeansSpecCapitalization))) + return false; + final ValueWithoutJavaBeansSpecCapitalization other = (ValueWithoutJavaBeansSpecCapitalization) o; + if ((this.getAField() != other.getAField())) + return false; + return true; + } + public @java.lang.Override @java.lang.SuppressWarnings("all") int hashCode() { + final int PRIME = 59; + int result = 1; + result = ((result * PRIME) + this.getAField()); + return result; + } + public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() { + return (("ValueWithoutJavaBeansSpecCapitalization(aField=" + this.getAField()) + ")"); + } + public @java.lang.SuppressWarnings("all") ValueWithoutJavaBeansSpecCapitalization(final int aField) { + super(); + this.aField = aField; + } +} diff --git a/test/transform/resource/after-ecj/WithOnJavaBeansSpecCapitalization.java b/test/transform/resource/after-ecj/WithOnJavaBeansSpecCapitalization.java new file mode 100644 index 0000000000..06164fe900 --- /dev/null +++ b/test/transform/resource/after-ecj/WithOnJavaBeansSpecCapitalization.java @@ -0,0 +1,25 @@ +@lombok.With @lombok.experimental.Accessors(javaBeansSpecCapitalization = true) class WithOnJavaBeansSpecCapitalization { + int aField; + WithOnJavaBeansSpecCapitalization(int aField) { + super(); + } + /** + * @return a clone of this object, except with this updated property (returns {@code this} if an identical value is passed). + */ + public @java.lang.SuppressWarnings("all") WithOnJavaBeansSpecCapitalization withaField(final int aField) { + return ((this.aField == aField) ? this : new WithOnJavaBeansSpecCapitalization(aField)); + } +} + +@lombok.With class WithOffJavaBeansSpecCapitalization { + int aField; + WithOffJavaBeansSpecCapitalization(int aField) { + super(); + } + /** + * @return a clone of this object, except with this updated property (returns {@code this} if an identical value is passed). + */ + public @java.lang.SuppressWarnings("all") WithOffJavaBeansSpecCapitalization withAField(final int aField) { + return ((this.aField == aField) ? this : new WithOffJavaBeansSpecCapitalization(aField)); + } +} diff --git a/test/transform/resource/before/GetterWithJavaBeansSpecCapitalization.java b/test/transform/resource/before/GetterWithJavaBeansSpecCapitalization.java new file mode 100644 index 0000000000..3c27317c2c --- /dev/null +++ b/test/transform/resource/before/GetterWithJavaBeansSpecCapitalization.java @@ -0,0 +1,11 @@ +class GetterWithoutJavaBeansSpecCapitalization { + @lombok.Getter int a; + @lombok.Getter int aField; +} + +@lombok.experimental.Accessors(javaBeansSpecCapitalization=true) +class GetterWithJavaBeansSpecCapitalization { + @lombok.Getter int a; + @lombok.Getter int aField; +} + diff --git a/test/transform/resource/before/SetterWithJavaBeansSpecCapitalization.java b/test/transform/resource/before/SetterWithJavaBeansSpecCapitalization.java new file mode 100644 index 0000000000..80a1b4ee2b --- /dev/null +++ b/test/transform/resource/before/SetterWithJavaBeansSpecCapitalization.java @@ -0,0 +1,11 @@ +class SetterWithoutJavaBeansSpecCapitalization { + @lombok.Setter int a; + @lombok.Setter int aField; +} + +@lombok.experimental.Accessors(javaBeansSpecCapitalization=true) +class SetterWithJavaBeansSpecCapitalization { + @lombok.Setter int a; + @lombok.Setter int aField; +} + diff --git a/test/transform/resource/before/ValueWithJavaBeansSpecCapitalization.java b/test/transform/resource/before/ValueWithJavaBeansSpecCapitalization.java new file mode 100644 index 0000000000..f0044ed90b --- /dev/null +++ b/test/transform/resource/before/ValueWithJavaBeansSpecCapitalization.java @@ -0,0 +1,10 @@ +@lombok.Value +@lombok.experimental.Accessors(javaBeansSpecCapitalization = true) +class ValueWithJavaBeansSpecCapitalization { + final int aField; +} + +@lombok.Value +class ValueWithoutJavaBeansSpecCapitalization { + final int aField; +} diff --git a/test/transform/resource/before/WithOnJavaBeansSpecCapitalization.java b/test/transform/resource/before/WithOnJavaBeansSpecCapitalization.java new file mode 100644 index 0000000000..476fb5a413 --- /dev/null +++ b/test/transform/resource/before/WithOnJavaBeansSpecCapitalization.java @@ -0,0 +1,16 @@ +@lombok.With +@lombok.experimental.Accessors(javaBeansSpecCapitalization = true) +class WithOnJavaBeansSpecCapitalization { + int aField; + + WithOnJavaBeansSpecCapitalization(int aField) { + } +} + +@lombok.With +class WithOffJavaBeansSpecCapitalization { + int aField; + + WithOffJavaBeansSpecCapitalization(int aField) { + } +}