diff --git a/doc/changelog.markdown b/doc/changelog.markdown index 9c22025daf..0ba9330682 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 457246e772..22d5a4c5a8 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 @@ private 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> ACCESSORS_PREFIX = new ConfigurationKey>("lombok.accessors.prefix", "Strip this field prefix, like 'f' or 'm_', from the names of generated getters and setters.") {}; + public static final ConfigurationKey> ACCESSORS_PREFIX = new ConfigurationKey>("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}. @@ -566,6 +566,13 @@ private ConfigurationKeys() {} */ public static final ConfigurationKey ACCESSORS_FLUENT = new ConfigurationKey("lombok.accessors.fluent", "Generate getters and setters using only the field name (no get/set prefix) (default: false).") {}; + /** + * lombok configuration: {@code lombok.accessors.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 ACCESSORS_MAKE_FINAL = new ConfigurationKey("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}. * diff --git a/src/core/lombok/core/AnnotationValues.java b/src/core/lombok/core/AnnotationValues.java index f5db553cae..390e9b71b3 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 @@ private static String inLocalPackage(LombokNode node, String typeName) 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 integrate(AnnotationValues defaults) { + if (values.isEmpty()) return defaults; + for (Map.Entry 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 2415b75059..039ce87058 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 static boolean shouldReturnThis0(AnnotationValues accessors, A return chain || fluent; } + public static boolean shouldMakeFinal0(AnnotationValues 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 INVALID_ON_BUILDERS = Collections.unmodifiableList( Arrays.asList( diff --git a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java index 6483a7498f..65e2c5c873 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 @@ -1631,6 +1631,14 @@ public static List toAllGetterNames(EclipseNode field, boolean isBoolean return HandlerUtil.toAllGetterNames(field.getAst(), getAccessorsForField(field), field.getName(), isBoolean); } + /** + * Translates the given field into all possible getter names. + * Convenient wrapper around {@link HandlerUtil#toAllGetterNames(lombok.core.AnnotationValues, CharSequence, boolean)}. + */ + public static List toAllGetterNames(EclipseNode field, boolean isBoolean, AnnotationValues 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). * @@ -1640,6 +1648,15 @@ public static String toGetterName(EclipseNode field, boolean isBoolean) { return HandlerUtil.toGetterName(field.getAst(), getAccessorsForField(field), 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)}. + */ + public static String toGetterName(EclipseNode field, boolean isBoolean, AnnotationValues 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)}. @@ -1648,6 +1665,14 @@ public static java.util.List toAllSetterNames(EclipseNode field, boolean return HandlerUtil.toAllSetterNames(field.getAst(), getAccessorsForField(field), field.getName(), isBoolean); } + /** + * 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 toAllSetterNames(EclipseNode field, boolean isBoolean, AnnotationValues 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). * @@ -1657,6 +1682,15 @@ public static String toSetterName(EclipseNode field, boolean isBoolean) { return HandlerUtil.toSetterName(field.getAst(), getAccessorsForField(field), 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)}. + */ + public static String toSetterName(EclipseNode field, boolean isBoolean, AnnotationValues 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)}. @@ -1665,6 +1699,14 @@ public static java.util.List toAllWithNames(EclipseNode field, boolean i return HandlerUtil.toAllWithNames(field.getAst(), getAccessorsForField(field), field.getName(), isBoolean); } + /** + * 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 toAllWithNames(EclipseNode field, boolean isBoolean, AnnotationValues 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)}. @@ -1673,6 +1715,14 @@ public static java.util.List toAllWithByNames(EclipseNode field, boolean return HandlerUtil.toAllWithByNames(field.getAst(), getAccessorsForField(field), field.getName(), isBoolean); } + /** + * 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 toAllWithByNames(EclipseNode field, boolean isBoolean, AnnotationValues 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). * @@ -1682,6 +1732,15 @@ public static String toWithName(EclipseNode field, boolean isBoolean) { return HandlerUtil.toWithName(field.getAst(), getAccessorsForField(field), 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)}. + */ + public static String toWithName(EclipseNode field, boolean isBoolean, AnnotationValues 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). * @@ -1691,13 +1750,28 @@ public static String toWithByName(EclipseNode field, boolean isBoolean) { return HandlerUtil.toWithByName(field.getAst(), getAccessorsForField(field), 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)}. + */ + public static String toWithByName(EclipseNode field, boolean isBoolean, AnnotationValues 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) { + 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) { if ((((FieldDeclaration) field.get()).modifiers & ClassFileConstants.AccStatic) != 0) return false; - AnnotationValues accessors = EclipseHandlerUtil.getAccessorsForField(field); return shouldReturnThis0(accessors, field.getAst()); } @@ -1760,9 +1834,12 @@ public static char[] removePrefixFromField(EclipseNode field) { } public static AnnotationValues getAccessorsForField(EclipseNode field) { + AnnotationValues 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 static AnnotationValues getAccessorsForField(EclipseNode field while (current != null) { for (EclipseNode node : current.down()) { if (annotationTypeMatches(Accessors.class, node)) { - return createAnnotation(Accessors.class, node); + AnnotationValues 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 @@ private static String applySetter(EclipseNode node, String sectionName) { 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 6a92dee2fd..3bb63aa608 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 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 7f8fdef2df..31236d21aa 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 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 void createGetterForField(AccessLevel level, TypeReference fieldType = copyType(field.type, source); boolean isBoolean = isBoolean(fieldType); - String getterName = toGetterName(fieldNode, isBoolean); + AnnotationValues 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 void createGetterForField(AccessLevel level, 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 MethodDeclaration createGetter(TypeDeclaration parent, EclipseNode fieldN statements = createSimpleGetterBody(source, fieldNode); } + AnnotationValues 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 0fdd058f06..fda1651d3e 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.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 void createSetterForField( 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 = 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 void createSetterForField( 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 @@ static MethodDeclaration createSetter(TypeDeclaration parent, boolean deprecate, int pS = source.sourceStart, pE = source.sourceEnd; long p = (long) pS << 32 | pE; MethodDeclaration method = new MethodDeclaration(parent.compilationResult); + AnnotationValues 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 bfad682b33..153f0c4a58 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.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 void createWithForField( FieldDeclaration field = (FieldDeclaration) fieldNode.get(); TypeReference fieldType = copyType(field.type, source); boolean isBoolean = isBoolean(fieldType); - String withName = toWithName(fieldNode, isBoolean); + AnnotationValues 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 void createWithForField( 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 MethodDeclaration createWith(TypeDeclaration parent, EclipseNode fieldNod 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 = 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 a8d13a843c..5ab3cf81cf 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.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 void createWithByForField( FieldDeclaration field = (FieldDeclaration) fieldNode.get(); TypeReference fieldType = copyType(field.type, source); boolean isBoolean = isBoolean(fieldType); - String withName = toWithByName(fieldNode, isBoolean); + AnnotationValues 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 void createWithByForField( 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 MethodDeclaration createWithBy(TypeDeclaration parent, EclipseNode fieldN 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 = 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 dc9ae4b09d..394fe5c4b7 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.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. *

* Complete documentation is found at the project lombok features page for @Accessors. *

- * 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 @@ 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}.
+ * NB: This setting has no effect on {@code @With}; they always get a "with" prefix.
* default: false * * @return Whether or not to make fluent methods (named {@code fieldName()}, not for example {@code setFieldName}). @@ -54,6 +55,14 @@ */ boolean chain() default false; + /** + * If true, generated accessors will be marked {@code final}. + * default: false + * + * @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 diff --git a/src/core/lombok/javac/handlers/HandleAccessors.java b/src/core/lombok/javac/handlers/HandleAccessors.java index 60466d78a0..ac0ace4f2d 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 { 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 7a7e41f96b..86eb9fda3d 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 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 void createGetterForField(AccessLevel level, 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 void createGetterForField(AccessLevel level, } } - String methodName = toGetterName(fieldNode); + AnnotationValues 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 JCMethodDecl createGetter(long access, JavacNode field, JavacTreeMaker tr // Remember the type; lazy will change it JCExpression methodType = cloneType(treeMaker, copyType(treeMaker, fieldNode), source); + AnnotationValues 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 statements; JCTree toClearOfMarkers = null; @@ -260,6 +264,7 @@ public JCMethodDecl createGetter(long access, JavacNode field, JavacTreeMaker tr } if (isFieldDeprecated(field)) annsOnMethod = annsOnMethod.prepend(treeMaker.Annotation(genJavaLangTypeRef(field, "Deprecated"), List.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 JCMethodDecl createGetter(long access, JavacNode field, JavacTreeMaker tr } } decl.mods.annotations = decl.mods.annotations.appendList(delegates); - if (addSuppressWarningsUnchecked) { ListBuffer suppressions = new ListBuffer(); 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 1b675e8cb2..04fa8b771a 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.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 void createSetterForField(AccessLevel level, JavacNode fieldNode, JavacNo return; } + AnnotationValues 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 void createSetterForField(AccessLevel level, JavacNode fieldNode, JavacNo 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 void createSetterForField(AccessLevel level, JavacNode fieldNode, JavacNo } public static JCMethodDecl createSetter(long access, JavacNode field, JavacTreeMaker treeMaker, JavacNode source, List onMethod, List onParam) { - String setterName = toSetterName(field); - boolean returnThis = shouldReturnThis(field); - return createSetter(access, false, field, treeMaker, setterName, null, null, returnThis, source, onMethod, onParam); + AnnotationValues 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 onMethod, List onParam) { @@ -198,8 +202,7 @@ public static JCMethodDecl createSetter(long access, boolean deprecate, JavacNod 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 onMethod, List onParam, JCVariableDecl recv) { @@ -270,6 +273,8 @@ public static JCMethodDecl createSetterWithRecv(long access, boolean deprecate, annsOnMethod = annsOnMethod.prepend(treeMaker.Annotation(genJavaLangTypeRef(field, "Deprecated"), List.nil())); } + AnnotationValues 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 47f78b1e5b..c7fa053127 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.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 void createWithForField(AccessLevel level, JavacNode fieldNode, JavacNode return; } + AnnotationValues 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 void createWithForField(AccessLevel level, JavacNode fieldNode, JavacNode 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 JCMethodDecl createWith(long access, JavacNode field, JavacTreeMaker make if (isFieldDeprecated(field)) annsOnMethod = annsOnMethod.prepend(maker.Annotation(genJavaLangTypeRef(field, "Deprecated"), List.nil())); - if (makeAbstract) access = access | Flags.ABSTRACT; + if (makeAbstract) access |= Flags.ABSTRACT; + AnnotationValues 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 4ba4337ea7..ff67fd9fd9 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.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 void createWithByForField(AccessLevel level, JavacNode fieldNode, JavacNo return; } + AnnotationValues 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 void createWithByForField(AccessLevel level, JavacNode fieldNode, JavacNo 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 JCMethodDecl createWithBy(long access, JavacNode field, JavacTreeMaker ma if (isFieldDeprecated(field)) annsOnMethod = annsOnMethod.prepend(maker.Annotation(genJavaLangTypeRef(field, "Deprecated"), List.nil())); if (makeAbstract) access = access | Flags.ABSTRACT; + AnnotationValues 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 d3532f7975..53a518b4e6 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 @@ -572,6 +572,14 @@ public static java.util.List toAllGetterNames(JavacNode field) { return HandlerUtil.toAllGetterNames(field.getAst(), getAccessorsForField(field), field.getName(), isBoolean(field)); } + /** + * 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 toAllGetterNames(JavacNode field, AnnotationValues 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). * @@ -581,6 +589,15 @@ public static String toGetterName(JavacNode field) { return HandlerUtil.toGetterName(field.getAst(), getAccessorsForField(field), 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)}. + */ + public static String toGetterName(JavacNode field, AnnotationValues 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)}. @@ -589,6 +606,14 @@ public static java.util.List toAllSetterNames(JavacNode field) { return HandlerUtil.toAllSetterNames(field.getAst(), getAccessorsForField(field), field.getName(), isBoolean(field)); } + /** + * 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 toAllSetterNames(JavacNode field, AnnotationValues 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). * @@ -598,6 +623,15 @@ public static String toSetterName(JavacNode field) { return HandlerUtil.toSetterName(field.getAst(), getAccessorsForField(field), 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)}. + */ + public static String toSetterName(JavacNode field, AnnotationValues 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)}. @@ -606,6 +640,14 @@ public static java.util.List toAllWithNames(JavacNode field) { return HandlerUtil.toAllWithNames(field.getAst(), getAccessorsForField(field), field.getName(), isBoolean(field)); } + /** + * 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 toAllWithNames(JavacNode field, AnnotationValues 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)}. @@ -614,6 +656,14 @@ public static java.util.List toAllWithByNames(JavacNode field) { return HandlerUtil.toAllWithByNames(field.getAst(), getAccessorsForField(field), field.getName(), isBoolean(field)); } + /** + * 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 toAllWithByNames(JavacNode field, AnnotationValues 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). * @@ -623,6 +673,15 @@ public static String toWithName(JavacNode field) { return HandlerUtil.toWithName(field.getAst(), getAccessorsForField(field), 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)}. + */ + public static String toWithName(JavacNode field, AnnotationValues 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). * @@ -632,18 +691,34 @@ public static String toWithByName(JavacNode field) { return HandlerUtil.toWithByName(field.getAst(), getAccessorsForField(field), 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)}. + */ + public static String toWithByName(JavacNode field, AnnotationValues 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) { if ((((JCVariableDecl) field.get()).mods.flags & Flags.STATIC) != 0) return false; - AnnotationValues 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) { + 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 static Name removePrefixFromField(JavacNode field) { } public static AnnotationValues getAccessorsForField(JavacNode field) { + AnnotationValues 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 static AnnotationValues getAccessorsForField(JavacNode field) while (current != null) { for (JavacNode node : current.down()) { if (annotationTypeMatches(Accessors.class, node)) { - return createAnnotation(Accessors.class, node); + AnnotationValues 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 @@ private static String applySetter(final JCCompilationUnit cu, JavacNode node, St 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 0000000000..ba4d13d4a9 --- /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 0000000000..d88e861609 --- /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 0000000000..61deedeea9 --- /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 0000000000..3e9c4c5f87 --- /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 cdc7b146bf..149d16f998 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 0000000000..cacd338f68 --- /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 0000000000..c8ac4bbd56 --- /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 0000000000..f7c411e36b --- /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 0000000000..e5d2d9051a --- /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 3ef8a02f30..54430cd663 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 0000000000..8ad141f8f4 --- /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 0000000000..5c45873a7b --- /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 0000000000..8e948520a5 --- /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 0000000000..572b21783a --- /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 0000000000..29215afe46 --- /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 0000000000..29215afe46 --- /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 9a9385cb91..d30151a5ca 100644 --- a/website/templates/features/experimental/Accessors.html +++ b/website/templates/features/experimental/Accessors.html @@ -6,7 +6,13 @@ @Accessors was introduced as experimental feature in lombok v0.11.0.

The lombok.config option lombok.accessors.capitalization = [basic | beanspec] was added in lombok v1.18.24. -

+

+ FUNCTIONAL CHANGE: @Accessors now 'cascades'; any options not set on a field-level @Accessors annotation will get inherited from an + @Accessors annotation on the class (and any options not set on those, from the enclosing class). Finally, anything set in lombok.config + will be used as default. (lombok v1.18.24) +

+ NEW FEATURE: @Accessors(makeFinal = true) will create final getters, setters, and with-ers. There's also + lombok.config key lombok.accessors.makeFinal for the same effect. (lombok v1.18.24) <@f.experimental> @@ -14,7 +20,7 @@

  • We may want to roll these features into a more complete property support concept.
  • - New feature – community feedback requested. + The makeFinal feature is recently released; awaiting community feedback.
  • Current status: neutral - 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>

    - The @Accessors annotation is used to configure how lombok generates and looks for getters and setters. + The @Accessors annotation is used to configure how lombok generates and looks for getters, setters, and with-ers.

    By default, lombok follows the bean specification for getters and setters: The getter for a field named pepper is getPepper for example. However, some might like to break with the bean specification in order to end up with nicer looking APIs. @Accessors lets you do this.

    Some programmers like to use a prefix for their fields, i.e. they write fPepper instead of pepper. We strongly 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 @Accessors as well.

    - @Accessors therefore has 3 options: + @Accessors has 4 options:

    • fluent – A boolean. If true, the getter for pepper is just pepper(), and the setter is pepper(T newValue). Furthermore, unless specified, chain defaults to true.
      @@ -40,12 +46,16 @@
    • chain – A boolean. If true, generated setters return this instead of void.
      Default: false, unless fluent=true, then Default: true. +
    • + makeFinal – A boolean. If true, generated getters, setters, and with-ers are marked as final.
      + Default: false.
    • prefix – 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. pepper is not a match even to prefix p, but pEpper would be (and would mean the base name of this field is epper).

    - The @Accessors 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 @Accessors annotation on a field is present, any @Accessors annotation also present on the class the field is in, is entirely ignored, even for properties not configured on the field @Accessors. This in contrast to any lombok.config configuration keys which serve as fall-back default if any explicit @Accessors annotation doesn't specify. + The @Accessors 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 lombok.config setting is used.

    @@ -55,11 +65,15 @@
    lombok.accessors.chain = [true | false] (default: false)
    - If set to true, any class that either doesn't have an @Accessors annotation, or it does, but that annotation does not have an explicit value for the chain parameter, will act as if @Accessors(chain = true) is present. + If set to true, any field/class that either doesn't have an @Accessors annotation, or it does, but that annotation does not have an explicit value for the chain parameter, will act as if @Accessors(chain = true) is present.
    lombok.accessors.fluent = [true | false] (default: false)
    - If set to true, any class that either doesn't have an @Accessors annotation, or it does, but that annotation does not have an explicit value for the fluent parameter, will act as if @Accessors(fluent = true) is present. + If set to true, any field/class that either doesn't have an @Accessors annotation, or it does, but that annotation does not have an explicit value for the fluent parameter, will act as if @Accessors(fluent = true) is present. +
    + lombok.accessors.makeFinal = [true | false] (default: false) +
    + If set to true, any field/class that either doesn't have an @Accessors annotation, or it does, but that annotation does not have an explicit value for the makeFinal parameter, will act as if @Accessors(makeFinal = true) is present.
    lombok.accessors.prefix += a field prefix (default: empty list)