diff options
34 files changed, 527 insertions, 71 deletions
diff --git a/doc/changelog.markdown b/doc/changelog.markdown index 9c22025d..0ba93306 100644 --- a/doc/changelog.markdown +++ b/doc/changelog.markdown @@ -5,6 +5,7 @@ Lombok Changelog * FEATURE: `@ToString` has an annotation parameter called `onlyExplicitlyIncluded`. There's now a config key `lombok.toString.onlyExplicitlyIncluded` to set this property as well. [Issue #2849](https://github.com/projectlombok/lombok/pull/2849). * FEATURE: Turning a field named `uShape` into a getter is tricky: `getUShape` or `getuShape`? The community is split on which style to use. Lombok does `getUShape`, but if you prefer the `getuShape` style, add to `lombok.config`: `lombok.accessors.capitalization = beanspec`. [Issue #2693](https://github.com/projectlombok/lombok/issues/2693) [Pull Request #2996](https://github.com/projectlombok/lombok/pull/2996). Thanks __@YonathanSherwin__! +* FEATURE: You can now use `@Accessors(makeFinal = true)` to make `final` getters, setters, and with-ers. [Issue #1456](https://github.com/projectlombok/lombok/issues/1456) * BUGFIX: Various save actions and refactor scripts in eclipse work better. [Issue #2995](https://github.com/projectlombok/lombok/issues/2995) [Issue #1309](https://github.com/projectlombok/lombok/issues/1309) [Issue #2985](https://github.com/projectlombok/lombok/issues/2985) [Issue #2509](https://github.com/projectlombok/lombok/issues/2509) * BUGFIX: Eclipse projects using the jasperreports-plugin will now compile [Issue #1036](https://github.com/projectlombok/lombok/issues/1036) * SECURITY: A widely reported security issue with log4j2 ([CVE-2021-44228](https://www.randori.com/blog/cve-2021-44228/)) has absolutely no effect on either lombok itself nor does usage of lombok on its own, or even the usage of lombok's `@Log4j2`, cause any issues whatsoever: You have to ship your own log4j2 dependency in your app - update that to 2.17 or otherwise mitigate this issue (see the CVE page). To avoid unneccessary warnings from dependency checkers, our dep on log4j2, which is used solely for testing, isn't shipped by us, and cannot be exploited in any way, has been updated to 2.17.1. [Issue #3063](https://github.com/projectlombok/lombok/issues/3063) diff --git a/src/core/lombok/ConfigurationKeys.java b/src/core/lombok/ConfigurationKeys.java index 457246e7..22d5a4c5 100644 --- a/src/core/lombok/ConfigurationKeys.java +++ b/src/core/lombok/ConfigurationKeys.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2021 The Project Lombok Authors. + * Copyright (C) 2013-2022 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 @@ -550,7 +550,7 @@ public class ConfigurationKeys { * * For any class without an {@code @Accessors} that explicitly defines the {@code prefix} option, this list of prefixes is used. */ - public static final ConfigurationKey<List<String>> ACCESSORS_PREFIX = new ConfigurationKey<List<String>>("lombok.accessors.prefix", "Strip this field prefix, like 'f' or 'm_', from the names of generated getters and setters.") {}; + public static final ConfigurationKey<List<String>> ACCESSORS_PREFIX = new ConfigurationKey<List<String>>("lombok.accessors.prefix", "Strip this field prefix, like 'f' or 'm_', from the names of generated getters, setters, and with-ers.") {}; /** * lombok configuration: {@code lombok.accessors.chain} = {@code true} | {@code false}. @@ -567,6 +567,13 @@ public class 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.makeFinal} = {@code true} | {@code false}. + * + * Unless an explicit {@code @Accessors} that explicitly defines the {@code makeFinal} option, this value is used (default = false). + */ + public static final ConfigurationKey<Boolean> ACCESSORS_MAKE_FINAL = new ConfigurationKey<Boolean>("lombok.accessors.makeFinal", "Generate getters, setters and with-ers with the 'final' modifier (default: false).") {}; + + /** * lombok configuration: {@code lombok.accessors.capitalization} = {@code basic} | {@code beanspec}. * * 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. diff --git a/src/core/lombok/core/AnnotationValues.java b/src/core/lombok/core/AnnotationValues.java index f5db553c..390e9b71 100644 --- a/src/core/lombok/core/AnnotationValues.java +++ b/src/core/lombok/core/AnnotationValues.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2013 The Project Lombok Authors. + * Copyright (C) 2009-2022 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 @@ -550,4 +550,25 @@ public class AnnotationValues<A extends Annotation> { result.append(typeName); return result.toString(); } + + /** + * Creates an amalgamation where any values in this AnnotationValues that aren't explicit are 'enriched' by explicitly set stuff from {@code defaults}. + * Note that this code may modify self and then returns self, or it returns defaults - do not rely on immutability nor on getting self. + */ + public AnnotationValues<A> integrate(AnnotationValues<A> defaults) { + if (values.isEmpty()) return defaults; + for (Map.Entry<String, AnnotationValue> entry : defaults.values.entrySet()) { + if (!entry.getValue().isExplicit) continue; + AnnotationValue existingValue = values.get(entry.getKey()); + if (existingValue != null && existingValue.isExplicit) continue; + values.put(entry.getKey(), entry.getValue()); + } + return this; + } + + /** Returns {@code true} if the annotation has zero parameters. */ + public boolean isMarking() { + for (AnnotationValue v : values.values()) if (v.isExplicit) return false; + return true; + } } diff --git a/src/core/lombok/core/handlers/HandlerUtil.java b/src/core/lombok/core/handlers/HandlerUtil.java index 2415b750..039ce870 100644 --- a/src/core/lombok/core/handlers/HandlerUtil.java +++ b/src/core/lombok/core/handlers/HandlerUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2021 The Project Lombok Authors. + * Copyright (C) 2013-2022 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 @@ -555,6 +555,14 @@ public class HandlerUtil { return chain || fluent; } + public static boolean shouldMakeFinal0(AnnotationValues<Accessors> accessors, AST<?, ?, ?> ast) { + boolean isExplicit = accessors.isExplicit("makeFinal"); + if (isExplicit) return accessors.getAsBoolean("makeFinal"); + Boolean config = ast.readConfiguration(ConfigurationKeys.ACCESSORS_MAKE_FINAL); + if (config != null) return config.booleanValue(); + return false; + } + @SuppressWarnings({"all", "unchecked", "deprecation"}) public static final List<String> INVALID_ON_BUILDERS = Collections.unmodifiableList( Arrays.<String>asList( diff --git a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java index 6483a749..65e2c5c8 100644 --- a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java +++ b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2021 The Project Lombok Authors. + * Copyright (C) 2009-2022 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 @@ -1632,6 +1632,14 @@ public class EclipseHandlerUtil { } /** + * Translates the given field into all possible getter names. + * Convenient wrapper around {@link HandlerUtil#toAllGetterNames(lombok.core.AnnotationValues, CharSequence, boolean)}. + */ + public static List<String> toAllGetterNames(EclipseNode field, boolean isBoolean, AnnotationValues<Accessors> accessors) { + return HandlerUtil.toAllGetterNames(field.getAst(), accessors, field.getName(), isBoolean); + } + + /** * @return the likely getter name for the stated field. (e.g. private boolean foo; to isFoo). * * Convenient wrapper around {@link HandlerUtil#toGetterName(lombok.core.AnnotationValues, CharSequence, boolean)}. @@ -1641,6 +1649,15 @@ public class EclipseHandlerUtil { } /** + * @return the likely getter name for the stated field. (e.g. private boolean foo; to isFoo). + * + * Convenient wrapper around {@link HandlerUtil#toGetterName(lombok.core.AnnotationValues, CharSequence, boolean)}. + */ + public static String toGetterName(EclipseNode field, boolean isBoolean, AnnotationValues<Accessors> accessors) { + return HandlerUtil.toGetterName(field.getAst(), accessors, field.getName(), isBoolean); + } + + /** * Translates the given field into all possible setter names. * Convenient wrapper around {@link HandlerUtil#toAllSetterNames(lombok.core.AnnotationValues, CharSequence, boolean)}. */ @@ -1649,6 +1666,14 @@ public class EclipseHandlerUtil { } /** + * Translates the given field into all possible setter names. + * Convenient wrapper around {@link HandlerUtil#toAllSetterNames(lombok.core.AnnotationValues, CharSequence, boolean)}. + */ + public static java.util.List<String> toAllSetterNames(EclipseNode field, boolean isBoolean, AnnotationValues<Accessors> accessors) { + return HandlerUtil.toAllSetterNames(field.getAst(), accessors, field.getName(), isBoolean); + } + + /** * @return the likely setter name for the stated field. (e.g. private boolean foo; to setFoo). * * Convenient wrapper around {@link HandlerUtil#toSetterName(lombok.core.AnnotationValues, CharSequence, boolean)}. @@ -1658,6 +1683,15 @@ public class EclipseHandlerUtil { } /** + * @return the likely setter name for the stated field. (e.g. private boolean foo; to setFoo). + * + * Convenient wrapper around {@link HandlerUtil#toSetterName(lombok.core.AnnotationValues, CharSequence, boolean)}. + */ + public static String toSetterName(EclipseNode field, boolean isBoolean, AnnotationValues<Accessors> accessors) { + return HandlerUtil.toSetterName(field.getAst(), accessors, field.getName(), isBoolean); + } + + /** * Translates the given field into all possible with names. * Convenient wrapper around {@link HandlerUtil#toAllWithNames(lombok.core.AnnotationValues, CharSequence, boolean)}. */ @@ -1666,6 +1700,14 @@ public class EclipseHandlerUtil { } /** + * Translates the given field into all possible with names. + * Convenient wrapper around {@link HandlerUtil#toAllWithNames(lombok.core.AnnotationValues, CharSequence, boolean)}. + */ + public static java.util.List<String> toAllWithNames(EclipseNode field, boolean isBoolean, AnnotationValues<Accessors> accessors) { + return HandlerUtil.toAllWithNames(field.getAst(), accessors, field.getName(), isBoolean); + } + + /** * Translates the given field into all possible withBy names. * Convenient wrapper around {@link HandlerUtil#toAllWithByNames(lombok.core.AnnotationValues, CharSequence, boolean)}. */ @@ -1674,6 +1716,14 @@ public class EclipseHandlerUtil { } /** + * Translates the given field into all possible withBy names. + * Convenient wrapper around {@link HandlerUtil#toAllWithByNames(lombok.core.AnnotationValues, CharSequence, boolean)}. + */ + public static java.util.List<String> toAllWithByNames(EclipseNode field, boolean isBoolean, AnnotationValues<Accessors> accessors) { + return HandlerUtil.toAllWithByNames(field.getAst(), accessors, field.getName(), isBoolean); + } + + /** * @return the likely with name for the stated field. (e.g. private boolean foo; to withFoo). * * Convenient wrapper around {@link HandlerUtil#toWithName(lombok.core.AnnotationValues, CharSequence, boolean)}. @@ -1683,6 +1733,15 @@ public class EclipseHandlerUtil { } /** + * @return the likely with name for the stated field. (e.g. private boolean foo; to withFoo). + * + * Convenient wrapper around {@link HandlerUtil#toWithName(lombok.core.AnnotationValues, CharSequence, boolean)}. + */ + public static String toWithName(EclipseNode field, boolean isBoolean, AnnotationValues<Accessors> accessors) { + return HandlerUtil.toWithName(field.getAst(), accessors, field.getName(), isBoolean); + } + + /** * @return the likely withBy name for the stated field. (e.g. private boolean foo; to withFooBy). * * Convenient wrapper around {@link HandlerUtil#toWithByName(lombok.core.AnnotationValues, CharSequence, boolean)}. @@ -1692,12 +1751,27 @@ public class EclipseHandlerUtil { } /** + * @return the likely withBy name for the stated field. (e.g. private boolean foo; to withFooBy). + * + * Convenient wrapper around {@link HandlerUtil#toWithByName(lombok.core.AnnotationValues, CharSequence, boolean)}. + */ + public static String toWithByName(EclipseNode field, boolean isBoolean, AnnotationValues<Accessors> accessors) { + return HandlerUtil.toWithByName(field.getAst(), accessors, field.getName(), isBoolean); + } + + /** + * When generating a setter/getter/wither, should it be made final? + */ + public static boolean shouldMakeFinal(EclipseNode field, AnnotationValues<Accessors> accessors) { + if ((((FieldDeclaration) field.get()).modifiers & ClassFileConstants.AccStatic) != 0) return false; + return shouldMakeFinal0(accessors, field.getAst()); + } + /** * When generating a setter, the setter either returns void (beanspec) or Self (fluent). * This method scans for the {@code Accessors} annotation and associated config properties to figure that out. */ - public static boolean shouldReturnThis(EclipseNode field) { + public static boolean shouldReturnThis(EclipseNode field, AnnotationValues<Accessors> accessors) { if ((((FieldDeclaration) field.get()).modifiers & ClassFileConstants.AccStatic) != 0) return false; - AnnotationValues<Accessors> accessors = EclipseHandlerUtil.getAccessorsForField(field); return shouldReturnThis0(accessors, field.getAst()); } @@ -1760,9 +1834,12 @@ public class EclipseHandlerUtil { } public static AnnotationValues<Accessors> getAccessorsForField(EclipseNode field) { + AnnotationValues<Accessors> values = null; + for (EclipseNode node : field.down()) { if (annotationTypeMatches(Accessors.class, node)) { - return createAnnotation(Accessors.class, node); + values = createAnnotation(Accessors.class, node); + break; } } @@ -1770,15 +1847,16 @@ public class EclipseHandlerUtil { while (current != null) { for (EclipseNode node : current.down()) { if (annotationTypeMatches(Accessors.class, node)) { - return createAnnotation(Accessors.class, node); + AnnotationValues<Accessors> onType = createAnnotation(Accessors.class, node); + values = values == null ? onType : values.integrate(onType); } } current = current.up(); } - return AnnotationValues.of(Accessors.class, field); + return values == null ? AnnotationValues.of(Accessors.class, field) : values; } - + public static EclipseNode upToTypeNode(EclipseNode node) { if (node == null) throw new NullPointerException("node"); while (node != null && !(node.get() instanceof TypeDeclaration)) node = node.up(); @@ -2823,7 +2901,7 @@ public class EclipseHandlerUtil { if (!sectionBased) { out = stripLinesWithTagFromJavadoc(stripSectionsFromJavadoc(javadoc), JavadocTag.RETURN); } - return shouldReturnThis(node) ? addReturnsThisIfNeeded(out) : out; + return shouldReturnThis(node, EclipseHandlerUtil.getAccessorsForField(node)) ? addReturnsThisIfNeeded(out) : out; } } diff --git a/src/core/lombok/eclipse/handlers/HandleAccessors.java b/src/core/lombok/eclipse/handlers/HandleAccessors.java index 6a92dee2..3bb63aa6 100644 --- a/src/core/lombok/eclipse/handlers/HandleAccessors.java +++ b/src/core/lombok/eclipse/handlers/HandleAccessors.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2021 The Project Lombok Authors. + * Copyright (C) 2014-2022 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 @@ -39,5 +39,6 @@ public class HandleAccessors extends EclipseAnnotationHandler<Accessors> { // Accessors itself is handled by HandleGetter/Setter; this is just to ensure that usages are flagged if requested. handleExperimentalFlagUsage(annotationNode, ConfigurationKeys.ACCESSORS_FLAG_USAGE, "@Accessors"); + if (annotation.isMarking()) annotationNode.addWarning("Accessors on its own does nothing. Set at least one parameter"); } } diff --git a/src/core/lombok/eclipse/handlers/HandleGetter.java b/src/core/lombok/eclipse/handlers/HandleGetter.java index 7f8fdef2..31236d21 100644 --- a/src/core/lombok/eclipse/handlers/HandleGetter.java +++ b/src/core/lombok/eclipse/handlers/HandleGetter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2021 The Project Lombok Authors. + * Copyright (C) 2009-2022 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 @@ -34,6 +34,7 @@ import java.util.Map; import lombok.AccessLevel; import lombok.ConfigurationKeys; +import lombok.experimental.Accessors; import lombok.experimental.Delegate; import lombok.spi.Provides; import lombok.Getter; @@ -190,7 +191,8 @@ public class HandleGetter extends EclipseAnnotationHandler<Getter> { TypeReference fieldType = copyType(field.type, source); boolean isBoolean = isBoolean(fieldType); - String getterName = toGetterName(fieldNode, isBoolean); + AnnotationValues<Accessors> accessors = getAccessorsForField(fieldNode); + String getterName = toGetterName(fieldNode, isBoolean, accessors); if (getterName == null) { errorNode.addWarning("Not generating getter for this field: It does not fit your @Accessors prefix list."); @@ -199,7 +201,7 @@ public class HandleGetter extends EclipseAnnotationHandler<Getter> { int modifier = toEclipseModifier(level) | (field.modifiers & ClassFileConstants.AccStatic); - for (String altName : toAllGetterNames(fieldNode, isBoolean)) { + for (String altName : toAllGetterNames(fieldNode, isBoolean, accessors)) { switch (methodExists(altName, fieldNode, false, 0)) { case EXISTS_BY_LOMBOK: return; @@ -247,7 +249,9 @@ public class HandleGetter extends EclipseAnnotationHandler<Getter> { statements = createSimpleGetterBody(source, fieldNode); } + AnnotationValues<Accessors> accessors = getAccessorsForField(fieldNode); MethodDeclaration method = new MethodDeclaration(parent.compilationResult); + if (shouldMakeFinal(fieldNode, accessors)) modifier |= ClassFileConstants.AccFinal; method.modifiers = modifier; method.returnType = returnType; method.annotations = null; diff --git a/src/core/lombok/eclipse/handlers/HandleSetter.java b/src/core/lombok/eclipse/handlers/HandleSetter.java index 0fdd058f..fda1651d 100644 --- a/src/core/lombok/eclipse/handlers/HandleSetter.java +++ b/src/core/lombok/eclipse/handlers/HandleSetter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2021 The Project Lombok Authors. + * Copyright (C) 2009-2022 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 @@ -37,6 +37,7 @@ import lombok.core.AST.Kind; import lombok.core.AnnotationValues; import lombok.eclipse.EclipseAnnotationHandler; import lombok.eclipse.EclipseNode; +import lombok.experimental.Accessors; import lombok.spi.Provides; import org.eclipse.jdt.internal.compiler.ast.ASTNode; @@ -150,8 +151,9 @@ public class HandleSetter extends EclipseAnnotationHandler<Setter> { FieldDeclaration field = (FieldDeclaration) fieldNode.get(); TypeReference fieldType = copyType(field.type, source); boolean isBoolean = isBoolean(fieldType); - String setterName = toSetterName(fieldNode, isBoolean); - boolean shouldReturnThis = shouldReturnThis(fieldNode); + AnnotationValues<Accessors> accessors = getAccessorsForField(fieldNode); + String setterName = toSetterName(fieldNode, isBoolean, accessors); + boolean shouldReturnThis = shouldReturnThis(fieldNode, accessors); if (setterName == null) { fieldNode.addWarning("Not generating setter for this field: It does not fit your @Accessors prefix list."); @@ -160,7 +162,7 @@ public class HandleSetter extends EclipseAnnotationHandler<Setter> { int modifier = toEclipseModifier(level) | (field.modifiers & ClassFileConstants.AccStatic); - for (String altName : toAllSetterNames(fieldNode, isBoolean)) { + for (String altName : toAllSetterNames(fieldNode, isBoolean, accessors)) { switch (methodExists(altName, fieldNode, false, 1)) { case EXISTS_BY_LOMBOK: return; @@ -206,6 +208,8 @@ public class HandleSetter extends EclipseAnnotationHandler<Setter> { int pS = source.sourceStart, pE = source.sourceEnd; long p = (long) pS << 32 | pE; MethodDeclaration method = new MethodDeclaration(parent.compilationResult); + AnnotationValues<Accessors> accessors = getAccessorsForField(fieldNode); + if (shouldMakeFinal(fieldNode, accessors)) modifier |= ClassFileConstants.AccFinal; method.modifiers = modifier; if (returnType != null) { method.returnType = returnType; diff --git a/src/core/lombok/eclipse/handlers/HandleWith.java b/src/core/lombok/eclipse/handlers/HandleWith.java index bfad682b..153f0c4a 100644 --- a/src/core/lombok/eclipse/handlers/HandleWith.java +++ b/src/core/lombok/eclipse/handlers/HandleWith.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2021 The Project Lombok Authors. + * Copyright (C) 2012-2022 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 @@ -38,6 +38,7 @@ import lombok.core.configuration.CheckerFrameworkVersion; import lombok.core.AnnotationValues; import lombok.eclipse.EclipseAnnotationHandler; import lombok.eclipse.EclipseNode; +import lombok.experimental.Accessors; import lombok.spi.Provides; import org.eclipse.jdt.internal.compiler.ast.ASTNode; @@ -169,7 +170,8 @@ public class HandleWith extends EclipseAnnotationHandler<With> { FieldDeclaration field = (FieldDeclaration) fieldNode.get(); TypeReference fieldType = copyType(field.type, source); boolean isBoolean = isBoolean(fieldType); - String withName = toWithName(fieldNode, isBoolean); + AnnotationValues<Accessors> accessors = getAccessorsForField(fieldNode); + String withName = toWithName(fieldNode, isBoolean, accessors); if (withName == null) { fieldNode.addWarning("Not generating a with method for this field: It does not fit your @Accessors prefix list."); @@ -191,7 +193,7 @@ public class HandleWith extends EclipseAnnotationHandler<With> { return; } - for (String altName : toAllWithNames(fieldNode, isBoolean)) { + for (String altName : toAllWithNames(fieldNode, isBoolean, accessors)) { switch (methodExists(altName, fieldNode, false, 1)) { case EXISTS_BY_LOMBOK: return; @@ -222,7 +224,9 @@ public class HandleWith extends EclipseAnnotationHandler<With> { int pS = source.sourceStart, pE = source.sourceEnd; long p = (long) pS << 32 | pE; MethodDeclaration method = new MethodDeclaration(parent.compilationResult); - if (makeAbstract) modifier = modifier | ClassFileConstants.AccAbstract | ExtraCompilerModifiers.AccSemicolonBody; + AnnotationValues<Accessors> accessors = getAccessorsForField(fieldNode); + if (makeAbstract) modifier |= ClassFileConstants.AccAbstract | ExtraCompilerModifiers.AccSemicolonBody; + if (shouldMakeFinal(fieldNode, accessors)) modifier |= ClassFileConstants.AccFinal; method.modifiers = modifier; method.returnType = cloneSelfType(fieldNode, source); if (method.returnType == null) return null; diff --git a/src/core/lombok/eclipse/handlers/HandleWithBy.java b/src/core/lombok/eclipse/handlers/HandleWithBy.java index a8d13a84..5ab3cf81 100644 --- a/src/core/lombok/eclipse/handlers/HandleWithBy.java +++ b/src/core/lombok/eclipse/handlers/HandleWithBy.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2021 The Project Lombok Authors. + * Copyright (C) 2020-2022 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 @@ -61,6 +61,7 @@ import lombok.core.handlers.HandlerUtil.FieldAccess; import lombok.eclipse.Eclipse; import lombok.eclipse.EclipseAnnotationHandler; import lombok.eclipse.EclipseNode; +import lombok.experimental.Accessors; import lombok.experimental.WithBy; import lombok.spi.Provides; @@ -169,7 +170,8 @@ public class HandleWithBy extends EclipseAnnotationHandler<WithBy> { FieldDeclaration field = (FieldDeclaration) fieldNode.get(); TypeReference fieldType = copyType(field.type, source); boolean isBoolean = isBoolean(fieldType); - String withName = toWithByName(fieldNode, isBoolean); + AnnotationValues<Accessors> accessors = getAccessorsForField(fieldNode); + String withName = toWithByName(fieldNode, isBoolean, accessors); if (withName == null) { fieldNode.addWarning("Not generating a withXBy method for this field: It does not fit your @Accessors prefix list."); @@ -191,7 +193,7 @@ public class HandleWithBy extends EclipseAnnotationHandler<WithBy> { return; } - for (String altName : toAllWithByNames(fieldNode, isBoolean)) { + for (String altName : toAllWithByNames(fieldNode, isBoolean, accessors)) { switch (methodExists(altName, fieldNode, false, 1)) { case EXISTS_BY_LOMBOK: return; @@ -242,7 +244,9 @@ public class HandleWithBy extends EclipseAnnotationHandler<WithBy> { int pS = source.sourceStart, pE = source.sourceEnd; long p = (long) pS << 32 | pE; MethodDeclaration method = new MethodDeclaration(parent.compilationResult); - if (makeAbstract) modifier = modifier | ClassFileConstants.AccAbstract | ExtraCompilerModifiers.AccSemicolonBody; + AnnotationValues<Accessors> accessors = getAccessorsForField(fieldNode); + if (makeAbstract) modifier |= ClassFileConstants.AccAbstract | ExtraCompilerModifiers.AccSemicolonBody; + if (shouldMakeFinal(fieldNode, accessors)) modifier |= ClassFileConstants.AccFinal; method.modifiers = modifier; method.returnType = cloneSelfType(fieldNode, source); if (method.returnType == null) return null; diff --git a/src/core/lombok/experimental/Accessors.java b/src/core/lombok/experimental/Accessors.java index dc9ae4b0..394fe5c4 100644 --- a/src/core/lombok/experimental/Accessors.java +++ b/src/core/lombok/experimental/Accessors.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2017 The Project Lombok Authors. + * Copyright (C) 2012-2022 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 @@ -27,11 +27,11 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * A container for settings for the generation of getters and setters. + * A container for settings for the generation of getters, setters and "with"-ers. * <p> * Complete documentation is found at <a href="https://projectlombok.org/features/experimental/Accessors">the project lombok features page for @Accessors</a>. * <p> - * Using this annotation does nothing by itself; an annotation that makes lombok generate getters and setters, + * Using this annotation does nothing by itself; an annotation that makes lombok generate getters, setters, or "with"-ers * such as {@link lombok.Setter} or {@link lombok.Data} is also required. */ @Target({ElementType.TYPE, ElementType.FIELD}) @@ -39,7 +39,8 @@ import java.lang.annotation.Target; public @interface Accessors { /** * If true, accessors will be named after the field and not include a {@code get} or {@code set} - * prefix. If true and {@code chain} is omitted, {@code chain} defaults to {@code true}. + * prefix. If true and {@code chain} is omitted, {@code chain} defaults to {@code true}.<br> + * NB: This setting has no effect on {@code @With}; they always get a "with" prefix.<br> * <strong>default: false</strong> * * @return Whether or not to make fluent methods (named {@code fieldName()}, not for example {@code setFieldName}). @@ -55,6 +56,14 @@ public @interface Accessors { boolean chain() default false; /** + * If true, generated accessors will be marked {@code final}. + * <strong>default: false</strong> + * + * @return Whether or not accessors should be marked {@code final}. + */ + boolean makeFinal() 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 * letter of the prefix is not a letter (for instance an underscore). If multiple fields diff --git a/src/core/lombok/javac/handlers/HandleAccessors.java b/src/core/lombok/javac/handlers/HandleAccessors.java index 60466d78..ac0ace4f 100644 --- a/src/core/lombok/javac/handlers/HandleAccessors.java +++ b/src/core/lombok/javac/handlers/HandleAccessors.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2021 The Project Lombok Authors. + * Copyright (C) 2012-2022 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 @@ -44,5 +44,6 @@ public class HandleAccessors extends JavacAnnotationHandler<Accessors> { handleExperimentalFlagUsage(annotationNode, ConfigurationKeys.ACCESSORS_FLAG_USAGE, "@Accessors"); deleteAnnotationIfNeccessary(annotationNode, Accessors.class); + if (annotation.isMarking()) annotationNode.addWarning("Accessors on its own does nothing. Set at least one parameter"); } } diff --git a/src/core/lombok/javac/handlers/HandleGetter.java b/src/core/lombok/javac/handlers/HandleGetter.java index 7a7e41f9..86eb9fda 100644 --- a/src/core/lombok/javac/handlers/HandleGetter.java +++ b/src/core/lombok/javac/handlers/HandleGetter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2021 The Project Lombok Authors. + * Copyright (C) 2009-2022 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 @@ -33,6 +33,7 @@ import java.util.Map; import lombok.AccessLevel; import lombok.ConfigurationKeys; +import lombok.experimental.Accessors; import lombok.experimental.Delegate; import lombok.Getter; import lombok.core.AST.Kind; @@ -169,7 +170,7 @@ public class HandleGetter extends JavacAnnotationHandler<Getter> { return; } - JCVariableDecl fieldDecl = (JCVariableDecl)fieldNode.get(); + JCVariableDecl fieldDecl = (JCVariableDecl) fieldNode.get(); if (lazy) { if ((fieldDecl.mods.flags & Flags.PRIVATE) == 0 || (fieldDecl.mods.flags & Flags.FINAL) == 0) { @@ -186,14 +187,15 @@ public class HandleGetter extends JavacAnnotationHandler<Getter> { } } - String methodName = toGetterName(fieldNode); + AnnotationValues<Accessors> accessors = getAccessorsForField(fieldNode); + String methodName = toGetterName(fieldNode, accessors); if (methodName == null) { source.addWarning("Not generating getter for this field: It does not fit your @Accessors prefix list."); return; } - for (String altName : toAllGetterNames(fieldNode)) { + for (String altName : toAllGetterNames(fieldNode, accessors)) { switch (methodExists(altName, fieldNode, false, 0)) { case EXISTS_BY_LOMBOK: return; @@ -221,8 +223,10 @@ public class HandleGetter extends JavacAnnotationHandler<Getter> { // Remember the type; lazy will change it JCExpression methodType = cloneType(treeMaker, copyType(treeMaker, fieldNode), source); + AnnotationValues<Accessors> accessors = JavacHandlerUtil.getAccessorsForField(field); // Generate the methodName; lazy will change the field type - Name methodName = field.toName(toGetterName(field)); + Name methodName = field.toName(toGetterName(field, accessors)); + boolean makeFinal = shouldMakeFinal(field, accessors); List<JCStatement> statements; JCTree toClearOfMarkers = null; @@ -260,6 +264,7 @@ public class HandleGetter extends JavacAnnotationHandler<Getter> { } if (isFieldDeprecated(field)) annsOnMethod = annsOnMethod.prepend(treeMaker.Annotation(genJavaLangTypeRef(field, "Deprecated"), List.<JCExpression>nil())); + if (makeFinal) access |= Flags.FINAL; JCMethodDecl decl = recursiveSetGeneratedBy(treeMaker.MethodDef(treeMaker.Modifiers(access, annsOnMethod), methodName, methodType, methodGenericParams, parameters, throwsClauses, methodBody, annotationMethodDefaultValue), source); @@ -270,7 +275,6 @@ public class HandleGetter extends JavacAnnotationHandler<Getter> { } } decl.mods.annotations = decl.mods.annotations.appendList(delegates); - if (addSuppressWarningsUnchecked) { ListBuffer<JCExpression> suppressions = new ListBuffer<JCExpression>(); if (!Boolean.FALSE.equals(field.getAst().readConfiguration(ConfigurationKeys.ADD_SUPPRESSWARNINGS_ANNOTATIONS))) { diff --git a/src/core/lombok/javac/handlers/HandleSetter.java b/src/core/lombok/javac/handlers/HandleSetter.java index 1b675e8c..04fa8b77 100644 --- a/src/core/lombok/javac/handlers/HandleSetter.java +++ b/src/core/lombok/javac/handlers/HandleSetter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2021 The Project Lombok Authors. + * Copyright (C) 2009-2022 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 @@ -31,6 +31,7 @@ import lombok.AccessLevel; import lombok.ConfigurationKeys; import lombok.Setter; import lombok.core.AST.Kind; +import lombok.experimental.Accessors; import lombok.core.AnnotationValues; import lombok.javac.Javac; import lombok.javac.JavacAnnotationHandler; @@ -146,8 +147,9 @@ public class HandleSetter extends JavacAnnotationHandler<Setter> { return; } + AnnotationValues<Accessors> accessors = JavacHandlerUtil.getAccessorsForField(fieldNode); JCVariableDecl fieldDecl = (JCVariableDecl) fieldNode.get(); - String methodName = toSetterName(fieldNode); + String methodName = toSetterName(fieldNode, accessors); if (methodName == null) { fieldNode.addWarning("Not generating setter for this field: It does not fit your @Accessors prefix list."); @@ -159,7 +161,7 @@ public class HandleSetter extends JavacAnnotationHandler<Setter> { return; } - for (String altName : toAllSetterNames(fieldNode)) { + for (String altName : toAllSetterNames(fieldNode, accessors)) { switch (methodExists(altName, fieldNode, false, 1)) { case EXISTS_BY_LOMBOK: return; @@ -184,9 +186,11 @@ public class HandleSetter extends JavacAnnotationHandler<Setter> { } public static JCMethodDecl createSetter(long access, JavacNode field, JavacTreeMaker treeMaker, JavacNode source, List<JCAnnotation> onMethod, List<JCAnnotation> onParam) { - String setterName = toSetterName(field); - boolean returnThis = shouldReturnThis(field); - return createSetter(access, false, field, treeMaker, setterName, null, null, returnThis, source, onMethod, onParam); + AnnotationValues<Accessors> accessors = JavacHandlerUtil.getAccessorsForField(field); + String setterName = toSetterName(field, accessors); + boolean returnThis = shouldReturnThis(field, accessors); + JCMethodDecl setter = createSetter(access, false, field, treeMaker, setterName, null, null, returnThis, source, onMethod, onParam); + return setter; } public static JCMethodDecl createSetter(long access, boolean deprecate, JavacNode field, JavacTreeMaker treeMaker, String setterName, Name paramName, Name booleanFieldToSet, boolean shouldReturnThis, JavacNode source, List<JCAnnotation> onMethod, List<JCAnnotation> onParam) { @@ -198,8 +202,7 @@ public class HandleSetter extends JavacAnnotationHandler<Setter> { returnStatement = treeMaker.Return(treeMaker.Ident(field.toName("this"))); } - JCMethodDecl d = createSetter(access, deprecate, field, treeMaker, setterName, paramName, booleanFieldToSet, returnType, returnStatement, source, onMethod, onParam); - return d; + return createSetter(access, deprecate, field, treeMaker, setterName, paramName, booleanFieldToSet, returnType, returnStatement, source, onMethod, onParam); } public static JCMethodDecl createSetterWithRecv(long access, boolean deprecate, JavacNode field, JavacTreeMaker treeMaker, String setterName, Name paramName, Name booleanFieldToSet, boolean shouldReturnThis, JavacNode source, List<JCAnnotation> onMethod, List<JCAnnotation> onParam, JCVariableDecl recv) { @@ -270,6 +273,8 @@ public class HandleSetter extends JavacAnnotationHandler<Setter> { annsOnMethod = annsOnMethod.prepend(treeMaker.Annotation(genJavaLangTypeRef(field, "Deprecated"), List.<JCExpression>nil())); } + AnnotationValues<Accessors> accessors = JavacHandlerUtil.getAccessorsForField(field); + if (shouldMakeFinal(field, accessors)) access |= Flags.FINAL; JCMethodDecl methodDef; if (recv != null && treeMaker.hasMethodDefWithRecvParam()) { methodDef = treeMaker.MethodDefWithRecvParam(treeMaker.Modifiers(access, annsOnMethod), methodName, methodType, diff --git a/src/core/lombok/javac/handlers/HandleWith.java b/src/core/lombok/javac/handlers/HandleWith.java index 47f78b1e..c7fa0531 100644 --- a/src/core/lombok/javac/handlers/HandleWith.java +++ b/src/core/lombok/javac/handlers/HandleWith.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2021 The Project Lombok Authors. + * Copyright (C) 2012-2022 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 @@ -33,6 +33,7 @@ import lombok.With; import lombok.core.AST.Kind; import lombok.core.AnnotationValues; import lombok.core.configuration.CheckerFrameworkVersion; +import lombok.experimental.Accessors; import lombok.javac.JavacAnnotationHandler; import lombok.javac.JavacNode; import lombok.javac.JavacTreeMaker; @@ -158,8 +159,9 @@ public class HandleWith extends JavacAnnotationHandler<With> { return; } + AnnotationValues<Accessors> accessors = getAccessorsForField(fieldNode); JCVariableDecl fieldDecl = (JCVariableDecl) fieldNode.get(); - String methodName = toWithName(fieldNode); + String methodName = toWithName(fieldNode, accessors); if (methodName == null) { fieldNode.addWarning("Not generating a withX method for this field: It does not fit your @Accessors prefix list."); @@ -187,7 +189,7 @@ public class HandleWith extends JavacAnnotationHandler<With> { return; } - for (String altName : toAllWithNames(fieldNode)) { + for (String altName : toAllWithNames(fieldNode, accessors)) { switch (methodExists(altName, fieldNode, false, 1)) { case EXISTS_BY_LOMBOK: return; @@ -282,7 +284,10 @@ public class HandleWith extends JavacAnnotationHandler<With> { if (isFieldDeprecated(field)) annsOnMethod = annsOnMethod.prepend(maker.Annotation(genJavaLangTypeRef(field, "Deprecated"), List.<JCExpression>nil())); - if (makeAbstract) access = access | Flags.ABSTRACT; + if (makeAbstract) access |= Flags.ABSTRACT; + AnnotationValues<Accessors> accessors = JavacHandlerUtil.getAccessorsForField(field); + boolean makeFinal = shouldMakeFinal(field, accessors); + if (makeFinal) access |= Flags.FINAL; JCMethodDecl decl = recursiveSetGeneratedBy(maker.MethodDef(maker.Modifiers(access, annsOnMethod), methodName, returnType, methodGenericParams, parameters, throwsClauses, methodBody, annotationMethodDefaultValue), source); copyJavadoc(field, decl, CopyJavadoc.WITH); diff --git a/src/core/lombok/javac/handlers/HandleWithBy.java b/src/core/lombok/javac/handlers/HandleWithBy.java index 4ba4337e..ff67fd9f 100644 --- a/src/core/lombok/javac/handlers/HandleWithBy.java +++ b/src/core/lombok/javac/handlers/HandleWithBy.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2021 The Project Lombok Authors. + * Copyright (C) 2020-2022 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 @@ -34,6 +34,7 @@ import lombok.core.AST.Kind; import lombok.core.AnnotationValues; import lombok.core.LombokImmutableList; import lombok.core.configuration.CheckerFrameworkVersion; +import lombok.experimental.Accessors; import lombok.experimental.WithBy; import lombok.javac.Javac; import lombok.javac.JavacAnnotationHandler; @@ -158,8 +159,9 @@ public class HandleWithBy extends JavacAnnotationHandler<WithBy> { return; } + AnnotationValues<Accessors> accessors = JavacHandlerUtil.getAccessorsForField(fieldNode); JCVariableDecl fieldDecl = (JCVariableDecl) fieldNode.get(); - String methodName = toWithByName(fieldNode); + String methodName = toWithByName(fieldNode, accessors); if (methodName == null) { fieldNode.addWarning("Not generating a withXBy method for this field: It does not fit your @Accessors prefix list."); @@ -181,7 +183,7 @@ public class HandleWithBy extends JavacAnnotationHandler<WithBy> { return; } - for (String altName : toAllWithByNames(fieldNode)) { + for (String altName : toAllWithByNames(fieldNode, accessors)) { switch (methodExists(altName, fieldNode, false, 1)) { case EXISTS_BY_LOMBOK: return; @@ -326,6 +328,9 @@ public class HandleWithBy extends JavacAnnotationHandler<WithBy> { if (isFieldDeprecated(field)) annsOnMethod = annsOnMethod.prepend(maker.Annotation(genJavaLangTypeRef(field, "Deprecated"), List.<JCExpression>nil())); if (makeAbstract) access = access | Flags.ABSTRACT; + AnnotationValues<Accessors> accessors = JavacHandlerUtil.getAccessorsForField(field); + boolean makeFinal = shouldMakeFinal(field, accessors); + if (makeFinal) access |= Flags.FINAL; createRelevantNonNullAnnotation(source, param); JCMethodDecl decl = recursiveSetGeneratedBy(maker.MethodDef(maker.Modifiers(access, annsOnMethod), methodName, returnType, methodGenericParams, parameters, throwsClauses, methodBody, annotationMethodDefaultValue), source); diff --git a/src/core/lombok/javac/handlers/JavacHandlerUtil.java b/src/core/lombok/javac/handlers/JavacHandlerUtil.java index d3532f79..53a518b4 100644 --- a/src/core/lombok/javac/handlers/JavacHandlerUtil.java +++ b/src/core/lombok/javac/handlers/JavacHandlerUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2021 The Project Lombok Authors. + * Copyright (C) 2009-2022 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 @@ -573,6 +573,14 @@ public class JavacHandlerUtil { } /** + * Translates the given field into all possible getter names. + * Convenient wrapper around {@link HandlerUtil#toAllGetterNames(lombok.core.AnnotationValues, CharSequence, boolean)}. + */ + public static java.util.List<String> toAllGetterNames(JavacNode field, AnnotationValues<Accessors> accessors) { + return HandlerUtil.toAllGetterNames(field.getAst(), accessors, field.getName(), isBoolean(field)); + } + + /** * @return the likely getter name for the stated field. (e.g. private boolean foo; to isFoo). * * Convenient wrapper around {@link HandlerUtil#toGetterName(lombok.core.AnnotationValues, CharSequence, boolean)}. @@ -582,6 +590,15 @@ public class JavacHandlerUtil { } /** + * @return the likely getter name for the stated field. (e.g. private boolean foo; to isFoo). + * + * Convenient wrapper around {@link HandlerUtil#toGetterName(lombok.core.AnnotationValues, CharSequence, boolean)}. + */ + public static String toGetterName(JavacNode field, AnnotationValues<Accessors> accessors) { + return HandlerUtil.toGetterName(field.getAst(), accessors, field.getName(), isBoolean(field)); + } + + /** * Translates the given field into all possible setter names. * Convenient wrapper around {@link HandlerUtil#toAllSetterNames(lombok.core.AnnotationValues, CharSequence, boolean)}. */ @@ -590,6 +607,14 @@ public class JavacHandlerUtil { } /** + * Translates the given field into all possible setter names. + * Convenient wrapper around {@link HandlerUtil#toAllSetterNames(lombok.core.AnnotationValues, CharSequence, boolean)}. + */ + public static java.util.List<String> toAllSetterNames(JavacNode field, AnnotationValues<Accessors> accessors) { + return HandlerUtil.toAllSetterNames(field.getAst(), accessors, field.getName(), isBoolean(field)); + } + + /** * @return the likely setter name for the stated field. (e.g. private boolean foo; to setFoo). * * Convenient wrapper around {@link HandlerUtil#toSetterName(lombok.core.AnnotationValues, CharSequence, boolean)}. @@ -599,6 +624,15 @@ public class JavacHandlerUtil { } /** + * @return the likely setter name for the stated field. (e.g. private boolean foo; to setFoo). + * + * Convenient wrapper around {@link HandlerUtil#toSetterName(lombok.core.AnnotationValues, CharSequence, boolean)}. + */ + public static String toSetterName(JavacNode field, AnnotationValues<Accessors> accessors) { + return HandlerUtil.toSetterName(field.getAst(), accessors, field.getName(), isBoolean(field)); + } + + /** * Translates the given field into all possible with names. * Convenient wrapper around {@link HandlerUtil#toAllWithNames(lombok.core.AnnotationValues, CharSequence, boolean)}. */ @@ -607,6 +641,14 @@ public class JavacHandlerUtil { } /** + * Translates the given field into all possible with names. + * Convenient wrapper around {@link HandlerUtil#toAllWithNames(lombok.core.AnnotationValues, CharSequence, boolean)}. + */ + public static java.util.List<String> toAllWithNames(JavacNode field, AnnotationValues<Accessors> accessors) { + return HandlerUtil.toAllWithNames(field.getAst(), accessors, field.getName(), isBoolean(field)); + } + + /** * Translates the given field into all possible withBy names. * Convenient wrapper around {@link HandlerUtil#toAllWithByNames(lombok.core.AnnotationValues, CharSequence, boolean)}. */ @@ -615,6 +657,14 @@ public class JavacHandlerUtil { } /** + * Translates the given field into all possible withBy names. + * Convenient wrapper around {@link HandlerUtil#toAllWithByNames(lombok.core.AnnotationValues, CharSequence, boolean)}. + */ + public static java.util.List<String> toAllWithByNames(JavacNode field, AnnotationValues<Accessors> accessors) { + return HandlerUtil.toAllWithByNames(field.getAst(), accessors, field.getName(), isBoolean(field)); + } + + /** * @return the likely with name for the stated field. (e.g. private boolean foo; to withFoo). * * Convenient wrapper around {@link HandlerUtil#toWithName(lombok.core.AnnotationValues, CharSequence, boolean)}. @@ -624,6 +674,15 @@ public class JavacHandlerUtil { } /** + * @return the likely with name for the stated field. (e.g. private boolean foo; to withFoo). + * + * Convenient wrapper around {@link HandlerUtil#toWithName(lombok.core.AnnotationValues, CharSequence, boolean)}. + */ + public static String toWithName(JavacNode field, AnnotationValues<Accessors> accessors) { + return HandlerUtil.toWithName(field.getAst(), accessors, field.getName(), isBoolean(field)); + } + + /** * @return the likely withBy name for the stated field. (e.g. private boolean foo; to withFooBy). * * Convenient wrapper around {@link HandlerUtil#toWithByName(lombok.core.AnnotationValues, CharSequence, boolean)}. @@ -633,17 +692,33 @@ public class JavacHandlerUtil { } /** + * @return the likely withBy name for the stated field. (e.g. private boolean foo; to withFooBy). + * + * Convenient wrapper around {@link HandlerUtil#toWithByName(lombok.core.AnnotationValues, CharSequence, boolean)}. + */ + public static String toWithByName(JavacNode field, AnnotationValues<Accessors> accessors) { + return HandlerUtil.toWithByName(field.getAst(), accessors, field.getName(), isBoolean(field)); + } + + /** * When generating a setter, the setter either returns void (beanspec) or Self (fluent). * This method scans for the {@code Accessors} annotation to figure that out. */ - public static boolean shouldReturnThis(JavacNode field) { + public static boolean shouldReturnThis(JavacNode field, AnnotationValues<Accessors> accessors) { if ((((JCVariableDecl) field.get()).mods.flags & Flags.STATIC) != 0) return false; - AnnotationValues<Accessors> accessors = JavacHandlerUtil.getAccessorsForField(field); - return HandlerUtil.shouldReturnThis0(accessors, field.getAst()); } + /** + * When generating a setter/getter/wither, should it be made final? + */ + public static boolean shouldMakeFinal(JavacNode field, AnnotationValues<Accessors> accessors) { + if ((((JCVariableDecl) field.get()).mods.flags & Flags.STATIC) != 0) return false; + + return HandlerUtil.shouldMakeFinal0(accessors, field.getAst()); + } + public static JCExpression cloneSelfType(JavacNode childOfType) { JavacNode typeNode = childOfType; JavacTreeMaker maker = childOfType.getTreeMaker(); @@ -696,9 +771,12 @@ public class JavacHandlerUtil { } public static AnnotationValues<Accessors> getAccessorsForField(JavacNode field) { + AnnotationValues<Accessors> values = null; + for (JavacNode node : field.down()) { if (annotationTypeMatches(Accessors.class, node)) { - return createAnnotation(Accessors.class, node); + values = createAnnotation(Accessors.class, node); + break; } } @@ -706,13 +784,15 @@ public class JavacHandlerUtil { while (current != null) { for (JavacNode node : current.down()) { if (annotationTypeMatches(Accessors.class, node)) { - return createAnnotation(Accessors.class, node); + AnnotationValues<Accessors> onType = createAnnotation(Accessors.class, node); + values = values == null ? onType : values.integrate(onType); + break; } } current = current.up(); } - return AnnotationValues.of(Accessors.class, field); + return values == null ? AnnotationValues.of(Accessors.class, field) : values; } /** @@ -2185,7 +2265,7 @@ public class JavacHandlerUtil { Javac.setDocComment(cu, n, javadoc); } }); - return shouldReturnThis(node) ? addReturnsThisIfNeeded(out) : out; + return shouldReturnThis(node, JavacHandlerUtil.getAccessorsForField(node)) ? addReturnsThisIfNeeded(out) : out; } } diff --git a/test/transform/resource/after-delombok/AccessorsCascade.java b/test/transform/resource/after-delombok/AccessorsCascade.java new file mode 100644 index 00000000..ba4d13d4 --- /dev/null +++ b/test/transform/resource/after-delombok/AccessorsCascade.java @@ -0,0 +1,39 @@ +//CONF: lombok.Accessors.prefix += f +class AccessorsOuter { + private String fTest; + private String zTest2; + class AccessorsInner1 { + private String zTest3; + /** + * @return {@code this}. + */ + @java.lang.SuppressWarnings("all") + public AccessorsOuter.AccessorsInner1 setTest3(final String zTest3) { + this.zTest3 = zTest3; + return this; + } + } + class AccessorsInner2 { + private String fTest4; + @java.lang.SuppressWarnings("all") + public void setTest4(final String fTest4) { + this.fTest4 = fTest4; + } + } + /** + * @return {@code this}. + */ + @java.lang.SuppressWarnings("all") + public AccessorsOuter setTest(final String fTest) { + this.fTest = fTest; + return this; + } + /** + * @return {@code this}. + */ + @java.lang.SuppressWarnings("all") + public AccessorsOuter setTest2(final String zTest2) { + this.zTest2 = zTest2; + return this; + } +}
\ No newline at end of file diff --git a/test/transform/resource/after-delombok/AccessorsMakeFinal.java b/test/transform/resource/after-delombok/AccessorsMakeFinal.java new file mode 100644 index 00000000..d88e8616 --- /dev/null +++ b/test/transform/resource/after-delombok/AccessorsMakeFinal.java @@ -0,0 +1,11 @@ +class AccessorsMakeFinal1 { + private String test; + /** + * @return {@code this}. + */ + @java.lang.SuppressWarnings("all") + public final AccessorsMakeFinal1 test(final String test) { + this.test = test; + return this; + } +}
\ No newline at end of file diff --git a/test/transform/resource/after-delombok/AccessorsMakeFinalLombokConfig.java b/test/transform/resource/after-delombok/AccessorsMakeFinalLombokConfig.java new file mode 100644 index 00000000..61deedee --- /dev/null +++ b/test/transform/resource/after-delombok/AccessorsMakeFinalLombokConfig.java @@ -0,0 +1,7 @@ +class AccessorsMakeFinalLombokConfig { + private String test; + @java.lang.SuppressWarnings("all") + public final void setTest(final String test) { + this.test = test; + } +}
\ No newline at end of file diff --git a/test/transform/resource/after-delombok/AccessorsNoParamWarning.java b/test/transform/resource/after-delombok/AccessorsNoParamWarning.java new file mode 100644 index 00000000..3e9c4c5f --- /dev/null +++ b/test/transform/resource/after-delombok/AccessorsNoParamWarning.java @@ -0,0 +1,14 @@ +class AccessorsNoParams { + private String otherFieldWithOverride = ""; + @java.lang.SuppressWarnings("all") + public String otherFieldWithOverride() { + return this.otherFieldWithOverride; + } +} +class AccessorsNoParams2 { + private boolean foo; + @java.lang.SuppressWarnings("all") + public void setFoo(final boolean foo) { + this.foo = foo; + } +}
\ No newline at end of file diff --git a/test/transform/resource/after-ecj/Accessors.java b/test/transform/resource/after-ecj/Accessors.java index cdc7b146..149d16f9 100644 --- a/test/transform/resource/after-ecj/Accessors.java +++ b/test/transform/resource/after-ecj/Accessors.java @@ -16,7 +16,7 @@ class AccessorsFluent { } @lombok.experimental.Accessors(fluent = true) @lombok.Getter class AccessorsFluentOnClass { private @lombok.Setter String fieldName = ""; - private @lombok.experimental.Accessors String otherFieldWithOverride = ""; + private @lombok.experimental.Accessors(fluent = false) String otherFieldWithOverride = ""; AccessorsFluentOnClass() { super(); } diff --git a/test/transform/resource/after-ecj/AccessorsCascade.java b/test/transform/resource/after-ecj/AccessorsCascade.java new file mode 100644 index 00000000..cacd338f --- /dev/null +++ b/test/transform/resource/after-ecj/AccessorsCascade.java @@ -0,0 +1,43 @@ +@lombok.experimental.Accessors(chain = true) class AccessorsOuter { + class AccessorsInner1 { + private @lombok.experimental.Accessors(prefix = "z") @lombok.Setter String zTest3; + AccessorsInner1() { + super(); + } + /** + * @return {@code this}. + */ + public @java.lang.SuppressWarnings("all") AccessorsOuter.AccessorsInner1 setTest3(final String zTest3) { + this.zTest3 = zTest3; + return this; + } + } + @lombok.experimental.Accessors(chain = false) class AccessorsInner2 { + private @lombok.Setter String fTest4; + AccessorsInner2() { + super(); + } + public @java.lang.SuppressWarnings("all") void setTest4(final String fTest4) { + this.fTest4 = fTest4; + } + } + private @lombok.Setter String fTest; + private @lombok.experimental.Accessors(prefix = "z") @lombok.Setter String zTest2; + AccessorsOuter() { + super(); + } + /** + * @return {@code this}. + */ + public @java.lang.SuppressWarnings("all") AccessorsOuter setTest(final String fTest) { + this.fTest = fTest; + return this; + } + /** + * @return {@code this}. + */ + public @java.lang.SuppressWarnings("all") AccessorsOuter setTest2(final String zTest2) { + this.zTest2 = zTest2; + return this; + } +}
\ No newline at end of file diff --git a/test/transform/resource/after-ecj/AccessorsMakeFinal.java b/test/transform/resource/after-ecj/AccessorsMakeFinal.java new file mode 100644 index 00000000..c8ac4bbd --- /dev/null +++ b/test/transform/resource/after-ecj/AccessorsMakeFinal.java @@ -0,0 +1,13 @@ +@lombok.experimental.Accessors(makeFinal = true) class AccessorsMakeFinal1 { + private @lombok.Setter @lombok.experimental.Accessors(fluent = true) String test; + AccessorsMakeFinal1() { + super(); + } + /** + * @return {@code this}. + */ + public final @java.lang.SuppressWarnings("all") AccessorsMakeFinal1 test(final String test) { + this.test = test; + return this; + } +}
\ No newline at end of file diff --git a/test/transform/resource/after-ecj/AccessorsMakeFinalLombokConfig.java b/test/transform/resource/after-ecj/AccessorsMakeFinalLombokConfig.java new file mode 100644 index 00000000..f7c411e3 --- /dev/null +++ b/test/transform/resource/after-ecj/AccessorsMakeFinalLombokConfig.java @@ -0,0 +1,9 @@ +class AccessorsMakeFinalLombokConfig { + private @lombok.Setter String test; + AccessorsMakeFinalLombokConfig() { + super(); + } + public final @java.lang.SuppressWarnings("all") void setTest(final String test) { + this.test = test; + } +}
\ No newline at end of file diff --git a/test/transform/resource/after-ecj/AccessorsNoParamWarning.java b/test/transform/resource/after-ecj/AccessorsNoParamWarning.java new file mode 100644 index 00000000..e5d2d905 --- /dev/null +++ b/test/transform/resource/after-ecj/AccessorsNoParamWarning.java @@ -0,0 +1,18 @@ +@lombok.experimental.Accessors(fluent = true) class AccessorsNoParams { + private @lombok.Getter @lombok.experimental.Accessors String otherFieldWithOverride = ""; + AccessorsNoParams() { + super(); + } + public @java.lang.SuppressWarnings("all") String otherFieldWithOverride() { + return this.otherFieldWithOverride; + } +} +@lombok.experimental.Accessors class AccessorsNoParams2 { + private @lombok.Setter boolean foo; + AccessorsNoParams2() { + super(); + } + public @java.lang.SuppressWarnings("all") void setFoo(final boolean foo) { + this.foo = foo; + } +}
\ No newline at end of file diff --git a/test/transform/resource/before/Accessors.java b/test/transform/resource/before/Accessors.java index 3ef8a02f..54430cd6 100644 --- a/test/transform/resource/before/Accessors.java +++ b/test/transform/resource/before/Accessors.java @@ -7,7 +7,7 @@ class AccessorsFluent { @lombok.Getter class AccessorsFluentOnClass { @lombok.Setter private String fieldName = ""; - @lombok.experimental.Accessors private String otherFieldWithOverride = ""; + @lombok.experimental.Accessors(fluent=false) private String otherFieldWithOverride = ""; } class AccessorsChain { diff --git a/test/transform/resource/before/AccessorsCascade.java b/test/transform/resource/before/AccessorsCascade.java new file mode 100644 index 00000000..8ad141f8 --- /dev/null +++ b/test/transform/resource/before/AccessorsCascade.java @@ -0,0 +1,23 @@ +//CONF: lombok.Accessors.prefix += f + +@lombok.experimental.Accessors(chain=true) +class AccessorsOuter { + @lombok.Setter + private String fTest; + + @lombok.experimental.Accessors(prefix="z") + @lombok.Setter + private String zTest2; + + class AccessorsInner1 { + @lombok.experimental.Accessors(prefix="z") + @lombok.Setter + private String zTest3; + } + + @lombok.experimental.Accessors(chain=false) + class AccessorsInner2 { + @lombok.Setter + private String fTest4; + } +} diff --git a/test/transform/resource/before/AccessorsMakeFinal.java b/test/transform/resource/before/AccessorsMakeFinal.java new file mode 100644 index 00000000..5c45873a --- /dev/null +++ b/test/transform/resource/before/AccessorsMakeFinal.java @@ -0,0 +1,5 @@ +@lombok.experimental.Accessors(makeFinal = true) +class AccessorsMakeFinal1 { + @lombok.Setter @lombok.experimental.Accessors(fluent = true) + private String test; +} diff --git a/test/transform/resource/before/AccessorsMakeFinalLombokConfig.java b/test/transform/resource/before/AccessorsMakeFinalLombokConfig.java new file mode 100644 index 00000000..8e948520 --- /dev/null +++ b/test/transform/resource/before/AccessorsMakeFinalLombokConfig.java @@ -0,0 +1,6 @@ +//CONF: lombok.Accessors.makeFinal = true + +class AccessorsMakeFinalLombokConfig { + @lombok.Setter + private String test; +} diff --git a/test/transform/resource/before/AccessorsNoParamWarning.java b/test/transform/resource/before/AccessorsNoParamWarning.java new file mode 100644 index 00000000..572b2178 --- /dev/null +++ b/test/transform/resource/before/AccessorsNoParamWarning.java @@ -0,0 +1,9 @@ +@lombok.experimental.Accessors(fluent=true) +class AccessorsNoParams { + @lombok.Getter @lombok.experimental.Accessors private String otherFieldWithOverride = ""; +} + +@lombok.experimental.Accessors +class AccessorsNoParams2 { + @lombok.Setter private boolean foo; +} diff --git a/test/transform/resource/messages-delombok/AccessorsNoParamWarning.java.messages b/test/transform/resource/messages-delombok/AccessorsNoParamWarning.java.messages new file mode 100644 index 00000000..29215afe --- /dev/null +++ b/test/transform/resource/messages-delombok/AccessorsNoParamWarning.java.messages @@ -0,0 +1,2 @@ +3 Accessors on its own does nothing. Set at least one parameter +6 Accessors on its own does nothing. Set at least one parameter
\ No newline at end of file diff --git a/test/transform/resource/messages-ecj/AccessorsNoParamWarning.java.messages b/test/transform/resource/messages-ecj/AccessorsNoParamWarning.java.messages new file mode 100644 index 00000000..29215afe --- /dev/null +++ b/test/transform/resource/messages-ecj/AccessorsNoParamWarning.java.messages @@ -0,0 +1,2 @@ +3 Accessors on its own does nothing. Set at least one parameter +6 Accessors on its own does nothing. Set at least one parameter
\ No newline at end of file diff --git a/website/templates/features/experimental/Accessors.html b/website/templates/features/experimental/Accessors.html index 9a9385cb..d30151a5 100644 --- a/website/templates/features/experimental/Accessors.html +++ b/website/templates/features/experimental/Accessors.html @@ -6,7 +6,13 @@ <code>@Accessors</code> was introduced as experimental feature in lombok v0.11.0. </p><p> The <em>lombok.config</em> option <code>lombok.accessors.capitalization</code> = [<code>basic</code> | <code>beanspec</code>] was added in lombok v1.18.24. - </p> + </p><p> + FUNCTIONAL CHANGE: <code>@Accessors</code> now 'cascades'; any options not set on a field-level <code>@Accessors</code> annotation will get inherited from an + <code>@Accessors</code> annotation on the class (and any options not set on those, from the enclosing class). Finally, anything set in <code>lombok.config</code> + will be used as default. (lombok v1.18.24) + </p><p> + NEW FEATURE: <code>@Accessors(makeFinal = true)</code> will create <code>final</code> getters, setters, and with-ers. There's also + <code>lombok.config</code> key <code>lombok.accessors.makeFinal</code> for the same effect. (lombok v1.18.24) </@f.history> <@f.experimental> @@ -14,7 +20,7 @@ <li> We may want to roll these features into a more complete property support concept. </li><li> - New feature – community feedback requested. + The <code>makeFinal</code> feature is recently released; awaiting community feedback. </li> </ul> Current status: <em>neutral</em> - Some changes are expected. These changes are intended to be backwards compatible, but should start in an experimental feature: @@ -26,13 +32,13 @@ <@f.overview> <p> - The <code>@Accessors</code> annotation is used to configure how lombok generates and looks for getters and setters. + The <code>@Accessors</code> annotation is used to configure how lombok generates and looks for getters, setters, and with-ers. </p><p> By default, lombok follows the <em>bean specification</em> for getters and setters: The getter for a field named <code>pepper</code> is <code>getPepper</code> for example. However, some might like to break with the <em>bean specification</em> in order to end up with nicer looking APIs. <code>@Accessors</code> lets you do this. </p><p> Some programmers like to use a prefix for their fields, i.e. they write <code>fPepper</code> instead of <code>pepper</code>. We <em>strongly</em> discourage doing this, as you can't unit test the validity of your prefixes, and refactor scripts may turn fields into local variables or method names. Furthermore, your tools (such as your editor) can take care of rendering the identifier in a certain way if you want this information to be instantly visible. Nevertheless, you can list the prefixes that your project uses via <code>@Accessors</code> as well. </p><p> - <code>@Accessors</code> therefore has 3 options: + <code>@Accessors</code> has 4 options: <ul> <li> <code>fluent</code> – A boolean. If <em>true</em>, the getter for <code>pepper</code> is just <code>pepper()</code>, and the setter is <code>pepper(T newValue)</code>. Furthermore, unless specified, <code>chain</code> defaults to <em>true</em>. <br /> @@ -41,11 +47,15 @@ <code>chain</code> – A boolean. If <em>true</em>, generated setters return <code>this</code> instead of <code>void</code>.<br /> Default: <em>false</em>, unless <code>fluent=true</code>, then Default: <em>true</em>. </li><li> + <code>makeFinal</code> – A boolean. If <em>true</em>, generated getters, setters, and with-ers are marked as <code>final</code>.<br /> + Default: <em>false</em>. + </li><li> <code>prefix</code> – A list of strings. If present, fields must be prefixed with any of these prefixes. Each field name is compared to each prefix in the list in turn, and if a match is found, the prefix is stripped out to create the base name for the field. It is legal to include an empty string in the list, which will always match. For characters which are letters, the character following the prefix must not be a lowercase letter, i.e. <code>pepper</code> is not a match even to prefix <code>p</code>, but <code>pEpper</code> would be (and would mean the base name of this field is <code>epper</code>). </li> </ul> <p><p> - The <code>@Accessors</code> annotation is legal on types and fields; the annotation that applies is the one on the field if present, otherwise the one on the class. When an <code>@Accessors</code> annotation on a field is present, any <code>@Accessors</code> annotation also present on the class the field is in, is entirely ignored, <em>even for properties not configured on the field <code>@Accessors</code></em>. This in contrast to any <code>lombok.config</code> configuration keys which serve as fall-back default if any explicit <code>@Accessors</code> annotation doesn't specify. + The <code>@Accessors</code> annotation is legal on types and fields; getters/setters/with-ers will look at the annotation on the field first, on the type the field is in second (and you have types in types, + each outer type is also checked), and finally for any properties not explicitly set, the appropriate <code>lombok.config</code> setting is used. </p> </@f.overview> @@ -55,11 +65,15 @@ <dt> <code>lombok.accessors.chain</code> = [<code>true</code> | <code>false</code>] (default: false) </dt><dd> - If set to <code>true</code>, any class that either doesn't have an <code>@Accessors</code> annotation, or it does, but that annotation does not have an explicit value for the <code>chain</code> parameter, will act as if <code>@Accessors(chain = true)</code> is present. + If set to <code>true</code>, any field/class that either doesn't have an <code>@Accessors</code> annotation, or it does, but that annotation does not have an explicit value for the <code>chain</code> parameter, will act as if <code>@Accessors(chain = true)</code> is present. </dd><dt> <code>lombok.accessors.fluent</code> = [<code>true</code> | <code>false</code>] (default: false) </dt><dd> - If set to <code>true</code>, any class that either doesn't have an <code>@Accessors</code> annotation, or it does, but that annotation does not have an explicit value for the <code>fluent</code> parameter, will act as if <code>@Accessors(fluent = true)</code> is present. + If set to <code>true</code>, any field/class that either doesn't have an <code>@Accessors</code> annotation, or it does, but that annotation does not have an explicit value for the <code>fluent</code> parameter, will act as if <code>@Accessors(fluent = true)</code> is present. + </dd><dt> + <code>lombok.accessors.makeFinal</code> = [<code>true</code> | <code>false</code>] (default: false) + </dt><dd> + If set to <code>true</code>, any field/class that either doesn't have an <code>@Accessors</code> annotation, or it does, but that annotation does not have an explicit value for the <code>makeFinal</code> parameter, will act as if <code>@Accessors(makeFinal = true)</code> is present. </dd><dt> <code>lombok.accessors.prefix</code> += <em>a field prefix</em> (default: empty list) </dt><dd> |