Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fix for #2693] Adding a new accessors flag - javaBeansSpecCapitalization #2996

Merged
merged 2 commits into from Oct 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS
Expand Up @@ -49,6 +49,7 @@ Takuya Murakami <tmurakam@tmurakam.org>
Thomas Darimont <thomas.darimont@gmail.com>
Till Brychcy <till.brychcy@mercateo.com>
Victor Williams Stafusa da Silva <victorwssilva@gmail.com>
Yonatan Sherwin <yonatansherwin@gmail.com>
Yun Zhi Lin <yun@yunspace.com>

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.
8 changes: 8 additions & 0 deletions src/core/lombok/ConfigurationKeys.java
Expand Up @@ -558,6 +558,14 @@ private ConfigurationKeys() {}
*/
public static final ConfigurationKey<Boolean> ACCESSORS_FLUENT = new ConfigurationKey<Boolean>("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<Boolean> ACCESSORS_JAVA_BEANS_SPEC_CAPITALIZATION = new ConfigurationKey<Boolean>("lombok.accessors.javaBeansSpecCapitalization", "Generating accessors name according to the JavaBeans Spec (default: false).") {};


// ----- ExtensionMethod -----

/**
Expand Down
45 changes: 33 additions & 12 deletions src/core/lombok/core/handlers/HandlerUtil.java
Expand Up @@ -585,11 +585,13 @@ private static String toAccessorName(AST<?, ?, ?> ast, AnnotationValues<Accessor
if (Boolean.TRUE.equals(ast.readConfiguration(ConfigurationKeys.GETTER_CONSEQUENT_BOOLEAN))) isBoolean = false;
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<String> 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;
Expand All @@ -602,7 +604,7 @@ private static String toAccessorName(AST<?, ?, ?> ast, AnnotationValues<Accessor
return booleanPrefix + fName.substring(2);
}

return buildAccessorName(isBoolean ? booleanPrefix : normalPrefix, fName);
return buildAccessorName(isBoolean ? booleanPrefix : normalPrefix, fName, javaBeansSpecCapitalization);
}

/**
Expand Down Expand Up @@ -675,12 +677,14 @@ private static List<String> 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<String> 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();

Expand All @@ -691,8 +695,8 @@ private static List<String> 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));
}
}

Expand Down Expand Up @@ -723,17 +727,34 @@ private static List<String> 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);
}

Expand Down
9 changes: 9 additions & 0 deletions src/core/lombok/experimental/Accessors.java
Expand Up @@ -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}).
* <strong>default: false</strong>
* @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
Expand Down
@@ -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;
}
}


@@ -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;
}
}


@@ -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() + ")";
}
}
@@ -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);
}
}
@@ -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;
}
}
@@ -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;
}
}