New feature: FieldDefaults can now be configured to apply to _every_ file, regardless of annotations.

This commit is contained in:
Reinier Zwitserloot committed 2015-10-06 00:25:32 +02:00
1 parent b4d69f5cb8
commit 9d8c4e4099
14 files changed
+219 -61

No files matched your search

+1
View File
@@ -2,6 +2,7 @@ Lombok Changelog
----------------
### v1.16.7 "Edgy Guinea Pig"
* FEATURE: A `lombok.config` key can now be used to make your fields `final` and/or `private`... __everywhere__. We'll be monitoring the performance impact of this for a while. We'll touch every source file if you turn these on, and even if you don't, we have to call into the lombok config system for every file.
* BUGFIX: `@Value` and `@FieldDefaults` no longer make uninitialized static fields final. [Issue #928](https://github.com/rzwitserloot/lombok/issues/928).
* ENHANCEMENT: Putting `@NonNull` on a parameter of an abstract method no longer generates a warning, to allow you to use this annotation to document intended behaviour [Issue #807](https://github.com/rzwitserloot/lombok/issues/807).
+14
View File
@@ -395,6 +395,20 @@ public class ConfigurationKeys {
// ----- FieldDefaults -----
/**
* lombok configuration: {@code lombok.fieldDefaults.defaultPrivate} = {@code true} | {@code false}.
*
* If set to <code>true</code> <em>any</em> field without an access modifier or {@code @PackagePrivate} is marked as {@code private} by lombok, in all source files compiled.
*/
public static final ConfigurationKey<Boolean> FIELD_DEFAULTS_PRIVATE_EVERYWHERE = new ConfigurationKey<Boolean>("lombok.fieldDefaults.defaultPrivate", "If true, fields without any access modifier, in any file (lombok annotated or not) are marked as private. Use @PackagePrivate or an explicit modifier to override this.") {};
/**
* lombok configuration: {@code lombok.fieldDefaults.defaultFinal} = {@code true} | {@code false}.
*
* If set to <code>true</code> <em>any</em> field without {@code @NonFinal} is marked as {@code final} by lombok, in all source files compiled.
*/
public static final ConfigurationKey<Boolean> FIELD_DEFAULTS_FINAL_EVERYWHERE = new ConfigurationKey<Boolean>("lombok.fieldDefaults.defaultFinal", "If true, fields, in any file (lombok annotated or not) are marked as final. Use @NonFinal to override this.") {};
/**
* lombok configuration: {@code lombok.fieldDefaults.flagUsage} = {@code WARNING} | {@code ERROR}.
*
@@ -23,12 +23,17 @@ package lombok.eclipse.handlers;
import static lombok.core.handlers.HandlerUtil.*;
import static lombok.eclipse.handlers.EclipseHandlerUtil.*;
import java.util.Arrays;
import lombok.AccessLevel;
import lombok.ConfigurationKeys;
import lombok.core.AST.Kind;
import lombok.core.AnnotationValues;
import lombok.core.HandlerPriority;
import lombok.eclipse.EclipseAnnotationHandler;
import lombok.eclipse.Eclipse;
import lombok.eclipse.EclipseASTAdapter;
import lombok.eclipse.EclipseASTVisitor;
import lombok.eclipse.EclipseNode;
import lombok.experimental.FieldDefaults;
import lombok.experimental.NonFinal;
@@ -37,16 +42,19 @@ import lombok.experimental.PackagePrivate;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.Annotation;
import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference;
import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference;
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
import org.eclipse.jdt.internal.compiler.ast.TypeReference;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.mangosdk.spi.ProviderFor;
/**
* Handles the {@code lombok.FieldDefaults} annotation for eclipse.
*/
@ProviderFor(EclipseAnnotationHandler.class)
@ProviderFor(EclipseASTVisitor.class)
@HandlerPriority(-2048) //-2^11; to ensure @Value picks up on messing with the fields' 'final' state, run earlier.
public class HandleFieldDefaults extends EclipseAnnotationHandler<FieldDefaults> {
public class HandleFieldDefaults extends EclipseASTAdapter {
public boolean generateFieldDefaultsForType(EclipseNode typeNode, EclipseNode pos, AccessLevel level, boolean makeFinal, boolean checkForTypeLevelFieldDefaults) {
if (checkForTypeLevelFieldDefaults) {
if (hasAnnotation(FieldDefaults.class, typeNode)) {
@@ -105,29 +113,60 @@ public class HandleFieldDefaults extends EclipseAnnotationHandler<FieldDefaults>
fieldNode.rebuild();
}
public void handle(AnnotationValues<FieldDefaults> annotation, Annotation ast, EclipseNode annotationNode) {
handleExperimentalFlagUsage(annotationNode, ConfigurationKeys.FIELD_DEFAULTS_FLAG_USAGE, "@FieldDefaults");
private static final char[] FIELD_DEFAULTS = "FieldDefaults".toCharArray();
@Override public void visitType(EclipseNode typeNode, TypeDeclaration type) {
AnnotationValues<FieldDefaults> fieldDefaults = null;
EclipseNode source = typeNode;
EclipseNode node = annotationNode.up();
FieldDefaults instance = annotation.getInstance();
AccessLevel level = instance.level();
boolean makeFinal = instance.makeFinal();
if (level == AccessLevel.NONE && !makeFinal) {
annotationNode.addError("This does nothing; provide either level or makeFinal or both.");
return;
boolean levelIsExplicit = false;
boolean makeFinalIsExplicit = false;
FieldDefaults fd = null;
for (EclipseNode jn : typeNode.down()) {
if (jn.getKind() != Kind.ANNOTATION) continue;
Annotation ann = (Annotation) jn.get();
TypeReference typeTree = ann.type;
if (typeTree == null) continue;
if (typeTree instanceof SingleTypeReference) {
char[] t = ((SingleTypeReference) typeTree).token;
if (!Arrays.equals(t, FIELD_DEFAULTS)) continue;
} else if (typeTree instanceof QualifiedTypeReference) {
char[][] t = ((QualifiedTypeReference) typeTree).tokens;
if (!Eclipse.nameEquals(t, "lombok.experimental.FieldDefaults")) continue;
} else {
continue;
}
if (!typeMatches(FieldDefaults.class, jn, typeTree)) continue;
source = jn;
fieldDefaults = createAnnotation(FieldDefaults.class, jn);
levelIsExplicit = fieldDefaults.isExplicit("level");
makeFinalIsExplicit = fieldDefaults.isExplicit("makeFinal");
handleExperimentalFlagUsage(jn, ConfigurationKeys.FIELD_DEFAULTS_FLAG_USAGE, "@FieldDefaults");
fd = fieldDefaults.getInstance();
if (!levelIsExplicit && !makeFinalIsExplicit) {
jn.addError("This does nothing; provide either level or makeFinal or both.");
}
if (levelIsExplicit && fd.level() == AccessLevel.NONE) {
jn.addError("AccessLevel.NONE doesn't mean anything here. Pick another value.");
levelIsExplicit = false;
}
break;
}
if (level == AccessLevel.PACKAGE) {
annotationNode.addError("Setting 'level' to PACKAGE does nothing. To force fields as package private, use the @PackagePrivate annotation on the field.");
}
if (fd == null && (type.modifiers & (ClassFileConstants.AccInterface | ClassFileConstants.AccAnnotation)) != 0) return;
if (!makeFinal && annotation.isExplicit("makeFinal")) {
annotationNode.addError("Setting 'makeFinal' to false does nothing. To force fields to be non-final, use the @NonFinal annotation on the field.");
}
boolean defaultToPrivate = Boolean.TRUE.equals(typeNode.getAst().readConfiguration(ConfigurationKeys.FIELD_DEFAULTS_PRIVATE_EVERYWHERE));
boolean defaultToFinal = Boolean.TRUE.equals(typeNode.getAst().readConfiguration(ConfigurationKeys.FIELD_DEFAULTS_FINAL_EVERYWHERE));
if (node == null) return;
if (!defaultToPrivate && !defaultToFinal && fieldDefaults == null) return;
AccessLevel fdAccessLevel = (fieldDefaults != null && levelIsExplicit) ? fd.level() : defaultToPrivate ? AccessLevel.PRIVATE : null;
boolean fdToFinal = (fieldDefaults != null && makeFinalIsExplicit) ? fd.makeFinal() : defaultToFinal;
generateFieldDefaultsForType(node, annotationNode, level, makeFinal, false);
generateFieldDefaultsForType(typeNode, source, fdAccessLevel, fdToFinal, false);
}
}
@@ -31,23 +31,24 @@ import lombok.core.HandlerPriority;
import lombok.experimental.FieldDefaults;
import lombok.experimental.NonFinal;
import lombok.experimental.PackagePrivate;
import lombok.javac.JavacAnnotationHandler;
import lombok.javac.JavacASTAdapter;
import lombok.javac.JavacASTVisitor;
import lombok.javac.JavacNode;
import org.mangosdk.spi.ProviderFor;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.JCAnnotation;
import com.sun.tools.javac.tree.JCTree.JCClassDecl;
import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
/**
* Handles the {@code lombok.FieldDefaults} annotation for eclipse.
*/
@ProviderFor(JavacAnnotationHandler.class)
@ProviderFor(JavacASTVisitor.class)
@HandlerPriority(-2048) //-2^11; to ensure @Value picks up on messing with the fields' 'final' state, run earlier.
public class HandleFieldDefaults extends JavacAnnotationHandler<FieldDefaults> {
public class HandleFieldDefaults extends JavacASTAdapter {
public boolean generateFieldDefaultsForType(JavacNode typeNode, JavacNode errorNode, AccessLevel level, boolean makeFinal, boolean checkForTypeLevelFieldDefaults) {
if (checkForTypeLevelFieldDefaults) {
if (hasAnnotation(FieldDefaults.class, typeNode)) {
@@ -72,13 +73,13 @@ public class HandleFieldDefaults extends JavacAnnotationHandler<FieldDefaults> {
//Skip fields that start with $
if (fieldDecl.name.toString().startsWith("$")) continue;
setFieldDefaultsForField(field, errorNode.get(), level, makeFinal);
setFieldDefaultsForField(field, level, makeFinal);
}
return true;
}
public void setFieldDefaultsForField(JavacNode fieldNode, DiagnosticPosition pos, AccessLevel level, boolean makeFinal) {
public void setFieldDefaultsForField(JavacNode fieldNode, AccessLevel level, boolean makeFinal) {
JCVariableDecl field = (JCVariableDecl) fieldNode.get();
if (level != null && level != AccessLevel.NONE) {
if ((field.mods.flags & (Flags.PUBLIC | Flags.PRIVATE | Flags.PROTECTED)) == 0) {
@@ -99,31 +100,53 @@ public class HandleFieldDefaults extends JavacAnnotationHandler<FieldDefaults> {
fieldNode.rebuild();
}
@Override public void handle(AnnotationValues<FieldDefaults> annotation, JCAnnotation ast, JavacNode annotationNode) {
handleExperimentalFlagUsage(annotationNode, ConfigurationKeys.FIELD_DEFAULTS_FLAG_USAGE, "@FieldDefaults");
@Override public void visitType(JavacNode typeNode, JCClassDecl type) {
AnnotationValues<FieldDefaults> fieldDefaults = null;
JavacNode source = typeNode;
deleteAnnotationIfNeccessary(annotationNode, FieldDefaults.class);
deleteImportFromCompilationUnit(annotationNode, "lombok.AccessLevel");
JavacNode node = annotationNode.up();
FieldDefaults instance = annotation.getInstance();
AccessLevel level = instance.level();
boolean makeFinal = instance.makeFinal();
if (level == AccessLevel.NONE && !makeFinal) {
annotationNode.addError("This does nothing; provide either level or makeFinal or both.");
return;
boolean levelIsExplicit = false;
boolean makeFinalIsExplicit = false;
FieldDefaults fd = null;
for (JavacNode jn : typeNode.down()) {
if (jn.getKind() != Kind.ANNOTATION) continue;
JCAnnotation ann = (JCAnnotation) jn.get();
JCTree typeTree = ann.annotationType;
if (typeTree == null) continue;
String typeTreeToString = typeTree.toString();
if (!typeTreeToString.equals("FieldDefaults") && !typeTreeToString.equals("lombok.experimental.FieldDefaults")) continue;
if (!typeMatches(FieldDefaults.class, jn, typeTree)) continue;
source = jn;
fieldDefaults = createAnnotation(FieldDefaults.class, jn);
levelIsExplicit = fieldDefaults.isExplicit("level");
makeFinalIsExplicit = fieldDefaults.isExplicit("makeFinal");
handleExperimentalFlagUsage(jn, ConfigurationKeys.FIELD_DEFAULTS_FLAG_USAGE, "@FieldDefaults");
fd = fieldDefaults.getInstance();
if (!levelIsExplicit && !makeFinalIsExplicit) {
jn.addError("This does nothing; provide either level or makeFinal or both.");
}
if (levelIsExplicit && fd.level() == AccessLevel.NONE) {
jn.addError("AccessLevel.NONE doesn't mean anything here. Pick another value.");
levelIsExplicit = false;
}
deleteAnnotationIfNeccessary(jn, FieldDefaults.class);
deleteImportFromCompilationUnit(jn, "lombok.AccessLevel");
break;
}
if (level == AccessLevel.PACKAGE) {
annotationNode.addError("Setting 'level' to PACKAGE does nothing. To force fields as package private, use the @PackagePrivate annotation on the field.");
}
if (fd == null && (type.mods.flags & (Flags.INTERFACE | Flags.ANNOTATION)) != 0) return;
if (!makeFinal && annotation.isExplicit("makeFinal")) {
annotationNode.addError("Setting 'makeFinal' to false does nothing. To force fields to be non-final, use the @NonFinal annotation on the field.");
}
boolean defaultToPrivate = Boolean.TRUE.equals(typeNode.getAst().readConfiguration(ConfigurationKeys.FIELD_DEFAULTS_PRIVATE_EVERYWHERE));
boolean defaultToFinal = Boolean.TRUE.equals(typeNode.getAst().readConfiguration(ConfigurationKeys.FIELD_DEFAULTS_FINAL_EVERYWHERE));
if (node == null) return;
if (!defaultToPrivate && !defaultToFinal && fieldDefaults == null) return;
AccessLevel fdAccessLevel = (fieldDefaults != null && levelIsExplicit) ? fd.level() : defaultToPrivate ? AccessLevel.PRIVATE : null;
boolean fdToFinal = (fieldDefaults != null && makeFinalIsExplicit) ? fd.makeFinal() : defaultToFinal;
generateFieldDefaultsForType(node, annotationNode, level, makeFinal, false);
generateFieldDefaultsForType(typeNode, source, fdAccessLevel, fdToFinal, false);
}
}
@@ -51,11 +51,11 @@ import com.sun.tools.javac.util.List;
@ResolutionResetNeeded
public class HandleVal extends JavacASTAdapter {
@Override public void visitLocal(JavacNode localNode, JCVariableDecl local) {
if (local.vartype == null || (!local.vartype.toString().equals("val") && !local.vartype.toString().equals("lombok.val"))) return;
JCTree source = local.vartype;
if (!typeMatches(val.class, localNode, local.vartype)) return;
JCTree typeTree = local.vartype;
if (typeTree == null) return;
String typeTreeToString = typeTree.toString();
if (!typeTreeToString.equals("val") && !typeTreeToString.equals("lombok.val")) return;
if (!typeMatches(val.class, localNode, typeTree)) return;
handleFlagUsage(localNode, ConfigurationKeys.VAL_FLAG_USAGE, "val");
@@ -88,7 +88,7 @@ public class HandleVal extends JavacASTAdapter {
local.mods.flags |= Flags.FINAL;
if (!localNode.shouldDeleteLombokAnnotations()) {
JCAnnotation valAnnotation = recursiveSetGeneratedBy(localNode.getTreeMaker().Annotation(local.vartype, List.<JCExpression>nil()), source, localNode.getContext());
JCAnnotation valAnnotation = recursiveSetGeneratedBy(localNode.getTreeMaker().Annotation(local.vartype, List.<JCExpression>nil()), typeTree, localNode.getContext());
local.mods.annotations = local.mods.annotations == null ? List.of(valAnnotation) : local.mods.annotations.append(valAnnotation);
}
@@ -156,7 +156,7 @@ public class HandleVal extends JavacASTAdapter {
local.vartype = JavacResolution.createJavaLangObject(localNode.getAst());
throw e;
} finally {
recursiveSetGeneratedBy(local.vartype, source, localNode.getContext());
recursiveSetGeneratedBy(local.vartype, typeTree, localNode.getContext());
}
}
}
+12 -7
View File
@@ -98,15 +98,20 @@ public class Eclipse {
* string containing the same fully qualified name with dots in the string.
*/
public static boolean nameEquals(char[][] typeName, String string) {
StringBuilder sb = new StringBuilder();
boolean first = true;
for (char[] elem : typeName) {
if (first) first = false;
else sb.append('.');
sb.append(elem);
int pos = 0, len = string.length();
for (int i = 0; i < typeName.length; i++) {
char[] t = typeName[i];
if (i > 0) {
if (pos == len) return false;
if (string.charAt(pos++) != '.') return false;
}
for (int j = 0; j < t.length; j++) {
if (pos == len) return false;
if (string.charAt(pos++) != t[j]) return false;
}
}
return string.contentEquals(sb);
return true;
}
public static boolean hasClinit(TypeDeclaration parent) {
@@ -0,0 +1,11 @@
class FieldDefaultsViaConfig1 {
private final int x;
private int y;
FieldDefaultsViaConfig1(int x) {
this.x = x;
}
}
class FieldDefaultsViaConfig2 {
final int x = 2;
protected final int y = 0;
}
@@ -0,0 +1,9 @@
class FieldDefaultsViaConfigAndRequiredArgsConstructor {
final int x;
@java.beans.ConstructorProperties({"x"})
@java.lang.SuppressWarnings("all")
@javax.annotation.Generated("lombok")
public FieldDefaultsViaConfigAndRequiredArgsConstructor(final int x) {
this.x = x;
}
}
@@ -0,0 +1,15 @@
class FieldDefaultsViaConfig1 {
private final int x;
private @lombok.experimental.NonFinal int y;
FieldDefaultsViaConfig1(int x) {
super();
this.x = x;
}
}
@lombok.experimental.FieldDefaults(level = lombok.AccessLevel.PROTECTED) class FieldDefaultsViaConfig2 {
final @lombok.experimental.PackagePrivate int x = 2;
protected final int y = 0;
FieldDefaultsViaConfig2() {
super();
}
}
@@ -0,0 +1,7 @@
@lombok.RequiredArgsConstructor class FieldDefaultsViaConfigAndRequiredArgsConstructor {
final int x;
public @java.beans.ConstructorProperties({"x"}) @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") FieldDefaultsViaConfigAndRequiredArgsConstructor(final int x) {
super();
this.x = x;
}
}
@@ -0,0 +1,16 @@
//CONF: lombok.fieldDefaults.defaultFinal = true
//CONF: lombok.fieldDefaults.defaultPrivate = true
class FieldDefaultsViaConfig1 {
int x;
@lombok.experimental.NonFinal int y;
FieldDefaultsViaConfig1(int x) {
this.x = x;
}
}
@lombok.experimental.FieldDefaults(level = lombok.AccessLevel.PROTECTED)
class FieldDefaultsViaConfig2 {
@lombok.experimental.PackagePrivate int x = 2;
int y = 0;
}
@@ -0,0 +1,5 @@
//CONF: lombok.fieldDefaults.defaultFinal = true
@lombok.RequiredArgsConstructor
class FieldDefaultsViaConfigAndRequiredArgsConstructor {
int x;
}
+9
View File
@@ -85,6 +85,15 @@
<code>lombok.extern.findbugs.addSuppressFBWarnings = true</code>
</div>
</div>
<div class="overview" style="clear: left;">
<h3>Config keys that can even affect source files with 0 lombok annotations</h3>
<p>
<div class="snippet example">
<code>lombok.fieldDefaults.defaultPrivate = true</code><br />
<code>lombok.fieldDefaults.defaultFinal = true</code>
</div>
Turning either of these options on means lombok will make <em>every</em> field in <em>every</em> source file final and/or private unless it has an explicit access modifier or annotation to suppress this. <a href="experimental/FieldDefaults.html">See the <code>@FieldDefaults</code> documentation for more</a>.
</div>
<div style="clear: left;"></div>
<div class="footer">
<a href="index.html">Back to features</a> | <a href="Log.html">Previous feature (@Log)</a> | <span class="disabled">Next feature</span><br />
@@ -61,6 +61,10 @@
<dl>
<dt><code>lombok.fieldDefaults.flagUsage</code> = [<code>warning</code> | <code>error</code>] (default: not set)</dt>
<dd>Lombok will flag any usage of <code>@FieldDefaults</code> as a warning or error if configured.</dd>
<dt><code>lombok.fieldDefaults.defaultPrivate</code> = [<code>true</code> | <code>false</code>] (default: false)</dt>
<dd>(Since 1.16.8) If set to <code>true</code>, <em>every</em> field in <em>every</em> class or enum anywhere in the sources being compiled will be marked as <code>private</code> unless it has an explicit access modifier or the <code>@PackagePrivate</code> annotation, or an explicit <code>@FieldDefaults</code> annotation is present to override this config key.</dd>
<dt><code>lombok.fieldDefaults.defaultFinal</code> = [<code>true</code> | <code>false</code>] (default: false)</dt>
<dd>(Since 1.16.8) If set to <code>true</code>, <em>every</em> field in <em>every</em> class or enum anywhere in the sources being compiled will be marked as <code>final</code> unless it has the <code>@NonFinal</code> annotation, or an explicit <code>@FieldDefaults</code> annotation is present to override this config key.</dd>
</dl>
</div>
<div class="overview">