Skip to content

Commit

Permalink
[projectlombok#2693] Review and updates for javabeans-style capitaliz…
Browse files Browse the repository at this point in the history
…ation lombok.config
  • Loading branch information
rzwitserloot authored and sadv1r committed Nov 23, 2021
1 parent 95c303d commit cfade59
Show file tree
Hide file tree
Showing 32 changed files with 382 additions and 279 deletions.
10 changes: 6 additions & 4 deletions src/core/lombok/ConfigurationKeys.java
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2013-2020 The Project Lombok Authors.
* Copyright (C) 2013-2021 The Project Lombok Authors.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
Expand All @@ -24,6 +24,7 @@
import java.util.List;

import lombok.core.configuration.CallSuperType;
import lombok.core.configuration.CapitalizationStrategy;
import lombok.core.configuration.CheckerFrameworkVersion;
import lombok.core.configuration.ConfigurationKey;
import lombok.core.configuration.FlagUsageType;
Expand Down Expand Up @@ -566,11 +567,12 @@ 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}.
* lombok configuration: {@code lombok.accessors.capitalization} = {@code basic} | {@code beanspec}.
*
* For any class without an {@code @Accessors} that explicitly defines the {@code javaBeansSpecCapitalization} option, this value is used (default = false).
* Which capitalization rule is used to turn field names into getter/setter/with names and vice versa for field names that start with 1 lowercase letter, then 1 uppercase letter.
* basic = {@code uShape} becomes {@code getUShape}, beanspec = {@code uShape} becomes {@code getuShape} (default = basic).
*/
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).") {};
public static final ConfigurationKey<CapitalizationStrategy> ACCESSORS_JAVA_BEANS_SPEC_CAPITALIZATION = new ConfigurationKey<CapitalizationStrategy>("lombok.accessors.capitalization", "Which capitalization strategy to use when converting field names to accessor names and vice versa (default: basic).") {};


// ----- ExtensionMethod -----
Expand Down
18 changes: 17 additions & 1 deletion src/core/lombok/core/AST.java
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2009-2019 The Project Lombok Authors.
* Copyright (C) 2009-2021 The Project Lombok Authors.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
Expand Down Expand Up @@ -430,6 +430,9 @@ private void buildWithCollection(Class<L> nodeType, Object collection, Collectio
}
}

/**
* @return The {@code lombok.config} configuration value for the provided {@code key}, or {@code null} if that key is not in the config / there is no config.
*/
public final <T> T readConfiguration(ConfigurationKey<T> key) {
long start = configTracker == null ? 0L : configTracker.start();
try {
Expand All @@ -438,4 +441,17 @@ public final <T> T readConfiguration(ConfigurationKey<T> key) {
if (configTracker != null) configTracker.end(start);
}
}

/**
* @return The {@code lombok.config} configuration value for the provided {@code key}, or {@code defaultValue} if that key is not in the config / there is no config.
*/
public final <T> T readConfigurationOr(ConfigurationKey<T> key, T defaultValue) {
long start = configTracker == null ? 0L : configTracker.start();
try {
T value = LombokConfiguration.read(key, this);
return value != null ? value : defaultValue;
} finally {
if (configTracker != null) configTracker.end(start);
}
}
}
52 changes: 52 additions & 0 deletions src/core/lombok/core/configuration/CapitalizationStrategy.java
@@ -0,0 +1,52 @@
/*
* Copyright (C) 2021 The Project Lombok Authors.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package lombok.core.configuration;

/** Used for lombok configuration to determine how to transform field names when turning them into accessor method names and vice versa. */
public enum CapitalizationStrategy {
BASIC {
@Override public String capitalize(String in) {
if (in.length() == 0) return in;
char first = in.charAt(0);
if (!Character.isLowerCase(first)) return in;
boolean useUpperCase = in.length() > 2 &&
(Character.isTitleCase(in.charAt(1)) || Character.isUpperCase(in.charAt(1)));
return (useUpperCase ? Character.toUpperCase(first) : Character.toTitleCase(first)) + in.substring(1);
}
},
BEANSPEC {
@Override public String capitalize(String in) {
if (in.length() == 0) return in;
char first = in.charAt(0);
if (!Character.isLowerCase(first) || (in.length() > 1 && Character.isUpperCase(in.charAt(1)))) return in;
boolean useUpperCase = in.length() > 2 && Character.isTitleCase(in.charAt(1));
return (useUpperCase ? Character.toUpperCase(first) : Character.toTitleCase(first)) + in.substring(1);
}
},
;

public static CapitalizationStrategy defaultValue() {
return BASIC;
}

public abstract String capitalize(String in);
}
4 changes: 2 additions & 2 deletions src/core/lombok/core/configuration/FlagUsageType.java
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2014 The Project Lombok Authors.
* Copyright (C) 2014-2021 The Project Lombok Authors.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
Expand All @@ -21,7 +21,7 @@
*/
package lombok.core.configuration;

/** Used for lombok configuration to flag usages of certain lombok feature. */
/** Used for lombok configuration to flag usages of certain lombok features. */
public enum FlagUsageType {
WARNING, ERROR, ALLOW;
}
58 changes: 27 additions & 31 deletions src/core/lombok/core/handlers/HandlerUtil.java
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2013-2020 The Project Lombok Authors.
* Copyright (C) 2013-2021 The Project Lombok Authors.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
Expand Down Expand Up @@ -46,6 +46,7 @@
import lombok.core.JavaIdentifiers;
import lombok.core.LombokNode;
import lombok.core.configuration.AllowHelper;
import lombok.core.configuration.CapitalizationStrategy;
import lombok.core.configuration.ConfigurationKey;
import lombok.core.configuration.FlagUsageType;
import lombok.experimental.Accessors;
Expand Down Expand Up @@ -591,7 +592,7 @@ private static String toAccessorName(AST<?, ?, ?> ast, AnnotationValues<Accessor

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));
CapitalizationStrategy capitalizationStrategy = ast.readConfigurationOr(ConfigurationKeys.ACCESSORS_JAVA_BEANS_SPEC_CAPITALIZATION, CapitalizationStrategy.defaultValue());

fieldName = removePrefix(fieldName, prefix);
if (fieldName == null) return null;
Expand All @@ -604,7 +605,7 @@ private static String toAccessorName(AST<?, ?, ?> ast, AnnotationValues<Accessor
return booleanPrefix + fName.substring(2);
}

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

/**
Expand Down Expand Up @@ -677,14 +678,13 @@ 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 || explicitJavaBeansSpecCapitalization) ? accessors.getInstance() : null;
Accessors ac = (explicitPrefix || explicitFluent) ? 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));

CapitalizationStrategy capitalizationStrategy = ast.readConfigurationOr(ConfigurationKeys.ACCESSORS_JAVA_BEANS_SPEC_CAPITALIZATION, CapitalizationStrategy.defaultValue());
fieldName = removePrefix(fieldName, prefix);
if (fieldName == null) return Collections.emptyList();

Expand All @@ -695,8 +695,8 @@ private static List<String> toAllAccessorNames(AST<?, ?, ?> ast, AnnotationValue
if (adhereToFluent && fluent) {
names.add(baseName);
} else {
names.add(buildAccessorName(normalPrefix, baseName, javaBeansSpecCapitalization));
if (!normalPrefix.equals(booleanPrefix)) names.add(buildAccessorName(booleanPrefix, baseName, javaBeansSpecCapitalization));
names.add(buildAccessorName(normalPrefix, baseName, capitalizationStrategy));
if (!normalPrefix.equals(booleanPrefix)) names.add(buildAccessorName(booleanPrefix, baseName, capitalizationStrategy));
}
}

Expand All @@ -722,40 +722,36 @@ private static List<String> toBaseNames(CharSequence fieldName, boolean isBoolea
}

/**
* @param node Any node (used to fetch config of capitalization strategy).
* @param prefix Something like {@code get} or {@code set} or {@code is}.
* @param suffix Something like {@code running}.
* @return prefix + smartly title-cased suffix. For example, {@code setRunning}.
*/
public static String buildAccessorName(AST<?, ?, ?> ast, String prefix, String suffix) {
CapitalizationStrategy capitalizationStrategy = ast.readConfigurationOr(ConfigurationKeys.ACCESSORS_JAVA_BEANS_SPEC_CAPITALIZATION, CapitalizationStrategy.defaultValue());
return buildAccessorName(prefix, suffix, capitalizationStrategy);
}

/**
* @param node Any node (used to fetch config of capitalization strategy).
* @param prefix Something like {@code get} or {@code set} or {@code is}.
* @param suffix Something like {@code running}.
* @return prefix + smartly title-cased suffix. For example, {@code setRunning}.
*/
public static String buildAccessorName(String prefix, String suffix) {
return buildAccessorName(prefix, suffix, false);
public static String buildAccessorName(LombokNode<?, ?, ?> node, String prefix, String suffix) {
CapitalizationStrategy capitalizationStrategy = node.getAst().readConfigurationOr(ConfigurationKeys.ACCESSORS_JAVA_BEANS_SPEC_CAPITALIZATION, CapitalizationStrategy.defaultValue());
return buildAccessorName(prefix, suffix, capitalizationStrategy);
}

/**
* @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}.
* @param capitalizationStrategy Which strategy to use to capitalize the name part.
*/
private static String buildAccessorName(String prefix, String suffix, boolean shouldFollowJavaBeansSpecCapitalization) {
private static String buildAccessorName(String prefix, String suffix, CapitalizationStrategy capitalizationStrategy) {
if (suffix.length() == 0) return prefix;
if (prefix.length() == 0) return suffix;

char first = suffix.charAt(0);
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);
return prefix + capitalizationStrategy.capitalize(suffix);
}

public static String camelCaseToConstant(String fieldName) {
Expand Down
6 changes: 3 additions & 3 deletions src/core/lombok/eclipse/handlers/HandleBuilder.java
Expand Up @@ -665,7 +665,7 @@ private MethodDeclaration generateToBuilderMethod(BuilderJob job, TypeParameter[
for (BuilderFieldData bfd : job.builderFields) {
String setterName = new String(bfd.name);
String setterPrefix = !prefix.isEmpty() ? prefix : job.oldFluent ? "" : "set";
if (!setterPrefix.isEmpty()) setterName = HandlerUtil.buildAccessorName(setterPrefix, setterName);
if (!setterPrefix.isEmpty()) setterName = HandlerUtil.buildAccessorName(job.sourceNode, setterPrefix, setterName);

MessageSend ms = new MessageSend();
Expression[] tgt = new Expression[bfd.singularData == null ? 1 : 2];
Expand Down Expand Up @@ -1033,9 +1033,9 @@ private void makePrefixedSetterMethodForBuilder(BuilderJob job, BuilderFieldData
String setterPrefix = prefix.isEmpty() ? "set" : prefix;
String setterName;
if (job.oldFluent) {
setterName = prefix.isEmpty() ? new String(bfd.name) : HandlerUtil.buildAccessorName(setterPrefix, new String(bfd.name));
setterName = prefix.isEmpty() ? new String(bfd.name) : HandlerUtil.buildAccessorName(job.sourceNode, setterPrefix, new String(bfd.name));
} else {
setterName = HandlerUtil.buildAccessorName(setterPrefix, new String(bfd.name));
setterName = HandlerUtil.buildAccessorName(job.sourceNode, setterPrefix, new String(bfd.name));
}

for (int i = 0; i < len; i++) {
Expand Down
4 changes: 2 additions & 2 deletions src/core/lombok/eclipse/handlers/HandleSuperBuilder.java
Expand Up @@ -798,7 +798,7 @@ private MethodDeclaration generateStaticFillValuesMethod(BuilderJob job, String
}

private MessageSend createSetterCallWithInstanceValue(BuilderFieldData bfd, EclipseNode type, ASTNode source, String setterPrefix) {
char[] setterName = HandlerUtil.buildAccessorName(setterPrefix, String.valueOf(bfd.name)).toCharArray();
char[] setterName = HandlerUtil.buildAccessorName(type, setterPrefix, String.valueOf(bfd.name)).toCharArray();
MessageSend ms = new MessageSend();
Expression[] tgt = new Expression[bfd.singularData == null ? 1 : 2];

Expand Down Expand Up @@ -1004,7 +1004,7 @@ private void generateSimpleSetterMethodForBuilder(BuilderJob job, boolean deprec
if (existing == null) existing = EMPTY_METHODS;
int len = existing.length;

String setterName = HandlerUtil.buildAccessorName(setterPrefix, new String(paramName));
String setterName = HandlerUtil.buildAccessorName(job.sourceNode, setterPrefix, new String(paramName));

for (int i = 0; i < len; i++) {
if (!(existing[i] instanceof MethodDeclaration)) continue;
Expand Down
Expand Up @@ -126,7 +126,7 @@ void generateClearMethod(CheckerFrameworkVersion cfv, boolean deprecate, TypeRef
FieldReference thisDotField = new FieldReference(data.getPluralName(), 0L);
thisDotField.receiver = new ThisReference(0, 0);
Assignment a = new Assignment(thisDotField, new NullLiteral(0, 0), 0);
md.selector = HandlerUtil.buildAccessorName("clear", new String(data.getPluralName())).toCharArray();
md.selector = HandlerUtil.buildAccessorName(builderType, "clear", new String(data.getPluralName())).toCharArray();
md.statements = returnStatement != null ? new Statement[] {a, returnStatement} : new Statement[] {a};
md.returnType = returnType;
md.annotations = generateSelfReturnAnnotations(deprecate, cfv, data.getSource());
Expand Down Expand Up @@ -173,8 +173,8 @@ void generateSingularMethod(CheckerFrameworkVersion cfv, boolean deprecate, Type
md.arguments[i].annotations = typeUseAnns;
}
md.returnType = returnType;
char[] prefixedSingularName = data.getSetterPrefix().length == 0 ? data.getSingularName() : HandlerUtil.buildAccessorName(new String(data.getSetterPrefix()), new String(data.getSingularName())).toCharArray();
md.selector = fluent ? prefixedSingularName : HandlerUtil.buildAccessorName("add", new String(data.getSingularName())).toCharArray();
char[] prefixedSingularName = data.getSetterPrefix().length == 0 ? data.getSingularName() : HandlerUtil.buildAccessorName(builderType, new String(data.getSetterPrefix()), new String(data.getSingularName())).toCharArray();
md.selector = fluent ? prefixedSingularName : HandlerUtil.buildAccessorName(builderType, "add", new String(data.getSingularName())).toCharArray();
Annotation[] selfReturnAnnotations = generateSelfReturnAnnotations(deprecate, cfv, data.getSource());
Annotation[] copyToSetterAnnotations = copyAnnotations(md, findCopyableToBuilderSingularSetterAnnotations(data.getAnnotation().up()));
md.annotations = concat(selfReturnAnnotations, copyToSetterAnnotations, Annotation.class);
Expand Down Expand Up @@ -213,8 +213,8 @@ void generatePluralMethod(CheckerFrameworkVersion cfv, boolean deprecate, TypeRe

md.arguments = new Argument[] {param};
md.returnType = returnType;
char[] prefixedSelector = data.getSetterPrefix().length == 0 ? data.getPluralName() : HandlerUtil.buildAccessorName(new String(data.getSetterPrefix()), new String(data.getPluralName())).toCharArray();
md.selector = fluent ? prefixedSelector : HandlerUtil.buildAccessorName("addAll", new String(data.getPluralName())).toCharArray();
char[] prefixedSelector = data.getSetterPrefix().length == 0 ? data.getPluralName() : HandlerUtil.buildAccessorName(builderType, new String(data.getSetterPrefix()), new String(data.getPluralName())).toCharArray();
md.selector = fluent ? prefixedSelector : HandlerUtil.buildAccessorName(builderType, "addAll", new String(data.getPluralName())).toCharArray();
Annotation[] selfReturnAnnotations = generateSelfReturnAnnotations(deprecate, cfv, data.getSource());
Annotation[] copyToSetterAnnotations = copyAnnotations(md, findCopyableToSetterAnnotations(data.getAnnotation().up()));
md.annotations = concat(selfReturnAnnotations, copyToSetterAnnotations, Annotation.class);
Expand Down

0 comments on commit cfade59

Please sign in to comment.