Skip to content

Commit

Permalink
[implements #1456] accessors can now be made final via @Accessors.
Browse files Browse the repository at this point in the history
  • Loading branch information
rzwitserloot committed Feb 8, 2022
1 parent 8a914b1 commit 261758b
Show file tree
Hide file tree
Showing 34 changed files with 527 additions and 71 deletions.
1 change: 1 addition & 0 deletions doc/changelog.markdown
Expand Up @@ -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)
Expand Down
11 changes: 9 additions & 2 deletions 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
Expand Down Expand Up @@ -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<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}.
Expand All @@ -566,6 +566,13 @@ private ConfigurationKeys() {}
*/
public static final ConfigurationKey<Boolean> ACCESSORS_FLUENT = new ConfigurationKey<Boolean>("lombok.accessors.fluent", "Generate getters and setters using only the field name (no get/set prefix) (default: false).") {};

/**
* lombok configuration: {@code lombok.accessors.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}.
*
Expand Down
23 changes: 22 additions & 1 deletion 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
Expand Down Expand Up @@ -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<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;
}
}
10 changes: 9 additions & 1 deletion 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
Expand Down Expand Up @@ -555,6 +555,14 @@ public static boolean shouldReturnThis0(AnnotationValues<Accessors> accessors, A
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(
Expand Down
94 changes: 86 additions & 8 deletions 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
Expand Down Expand Up @@ -1631,6 +1631,14 @@ public static List<String> 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<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).
*
Expand All @@ -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> 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)}.
Expand All @@ -1648,6 +1665,14 @@ public static java.util.List<String> 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<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).
*
Expand All @@ -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> 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)}.
Expand All @@ -1665,6 +1699,14 @@ public static java.util.List<String> 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<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)}.
Expand All @@ -1673,6 +1715,14 @@ public static java.util.List<String> 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<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).
*
Expand All @@ -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> 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).
*
Expand All @@ -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> 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());
}

Expand Down Expand Up @@ -1760,25 +1834,29 @@ public static char[] removePrefixFromField(EclipseNode field) {
}

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

EclipseNode current = field.up();
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();
Expand Down Expand 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;
}
}

Expand Down
3 changes: 2 additions & 1 deletion 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
Expand Down Expand Up @@ -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");
}
}
10 changes: 7 additions & 3 deletions 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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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> 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.");
Expand All @@ -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;
Expand Down Expand Up @@ -247,7 +249,9 @@ public MethodDeclaration createGetter(TypeDeclaration parent, EclipseNode fieldN
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;
Expand Down

0 comments on commit 261758b

Please sign in to comment.