diff options
25 files changed, 640 insertions, 193 deletions
diff --git a/src/core/lombok/ToString.java b/src/core/lombok/ToString.java index 0c43c40b..218e4c00 100644 --- a/src/core/lombok/ToString.java +++ b/src/core/lombok/ToString.java @@ -45,6 +45,8 @@ public @interface ToString { /** * Any fields listed here will not be printed in the generated {@code toString} implementation. * Mutually exclusive with {@link #of()}. + * <p> + * Will soon be marked {@code @Deprecated}; use the {@code @ToString.Exclude} annotation instead. * * @return A list of fields to exclude. */ @@ -55,6 +57,8 @@ public @interface ToString { * Normally, all non-static fields are printed. * <p> * Mutually exclusive with {@link #exclude()}. + * <p> + * Will soon be marked {@code @Deprecated}; use the {@code @ToString.Only} annotation instead. * * @return A list of fields to use (<em>default</em>: all of them). */ @@ -75,4 +79,33 @@ public @interface ToString { * @return If {@code true}, always use direct field access instead of calling the getter method. */ boolean doNotUseGetters() default false; + + /** + * Only include fields and methods explicitly marked with {@code @ToString.Include}. + * Normally, all (non-static) fields are included by default. + */ + boolean onlyExplicitlyIncluded() default false; + + /** + * If present, do not include this field in the generated {@code toString}. + */ + @Target(ElementType.FIELD) + @Retention(RetentionPolicy.SOURCE) + public @interface Exclude {} + + /** + * Configure the behaviour of how this member is rendered in the {@code toString}; if on a method, include the method's return value in the output. + */ + @Target({ElementType.FIELD, ElementType.METHOD}) + @Retention(RetentionPolicy.SOURCE) + public @interface Include { +// /** If true and the return value is {@code null}, omit this member entirely from the {@code toString} output. */ +// boolean skipNull() default false; // -- We'll add it later, it requires a complete rework on the toString code we generate. + + /** Higher ranks are printed first. Members of the same rank are printed in the order they appear in the source file. */ + int rank() default 0; + + /** Defaults to the field / method name of the annotated member. If the name equals the name of a default-included field, this member takes its place. */ + String name() default ""; + } } diff --git a/src/core/lombok/core/LombokNode.java b/src/core/lombok/core/LombokNode.java index 07c62151..d6708956 100644 --- a/src/core/lombok/core/LombokNode.java +++ b/src/core/lombok/core/LombokNode.java @@ -21,6 +21,7 @@ */ package lombok.core; +import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -275,4 +276,15 @@ public abstract class LombokNode<A extends AST<A, L, N>, L extends LombokNode<A, public boolean isStructurallySignificant() { return isStructurallySignificant; } + + public abstract boolean hasAnnotation(Class<? extends Annotation> type); + public abstract <Z extends Annotation> AnnotationValues<Z> findAnnotation(Class<Z> type); + + public abstract boolean isStatic(); + public abstract boolean isTransient(); + public abstract boolean isEnumMember(); + + public abstract int countMethodParameters(); + + public abstract int getStartPos(); } diff --git a/src/core/lombok/core/handlers/HandlerUtil.java b/src/core/lombok/core/handlers/HandlerUtil.java index 211b4924..0d64c550 100644 --- a/src/core/lombok/core/handlers/HandlerUtil.java +++ b/src/core/lombok/core/handlers/HandlerUtil.java @@ -57,6 +57,10 @@ import lombok.experimental.Wither; public class HandlerUtil { private HandlerUtil() {} + public enum FieldAccess { + GETTER, PREFER_FIELD, ALWAYS_FIELD; + } + public static int primeForHashcode() { return 59; } diff --git a/src/core/lombok/core/handlers/InclusionExclusionUtils.java b/src/core/lombok/core/handlers/InclusionExclusionUtils.java new file mode 100644 index 00000000..e2f686cd --- /dev/null +++ b/src/core/lombok/core/handlers/InclusionExclusionUtils.java @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2009-2018 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.core.handlers; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; + +import lombok.ToString; +import lombok.ToString.Include; +import lombok.core.AST; +import lombok.core.AST.Kind; +import lombok.core.AnnotationValues; +import lombok.core.LombokNode; + +public class InclusionExclusionUtils { + private static List<Integer> createListOfNonExistentFields(List<String> list, LombokNode<?, ?, ?> type, boolean excludeStandard, boolean excludeTransient) { + boolean[] matched = new boolean[list.size()]; + + for (LombokNode<?, ?, ?> child : type.down()) { + if (list.isEmpty()) break; + if (child.getKind() != Kind.FIELD) continue; + if (excludeStandard) { + if (child.isStatic()) continue; + if (child.getName().startsWith("$")) continue; + } + if (excludeTransient && child.isTransient()) continue; + + int idx = list.indexOf(child.getName()); + if (idx > -1) matched[idx] = true; + } + + List<Integer> problematic = new ArrayList<Integer>(); + for (int i = 0 ; i < list.size() ; i++) if (!matched[i]) problematic.add(i); + + return problematic; + } + + public static void checkForBogusFieldNames(LombokNode<?, ?, ?> type, AnnotationValues<?> annotation, List<String> excludes, List<String> includes) { + if (excludes != null && !excludes.isEmpty()) { + for (int i : createListOfNonExistentFields(excludes, type, true, false)) { + annotation.setWarning("exclude", "This field does not exist, or would have been excluded anyway.", i); + } + } + + if (includes != null && !includes.isEmpty()) { + for (int i : createListOfNonExistentFields(includes, type, false, false)) { + annotation.setWarning("of", "This field does not exist.", i); + } + } + } + + public static class ToStringMember<L> { + private final L node; + private final ToString.Include inc; + private final boolean defaultInclude; + + public ToStringMember(L node, ToString.Include inc, boolean defaultInclude) { + this.node = node; + this.inc = inc; + this.defaultInclude = defaultInclude; + } + + public L getNode() { + return node; + } + + public ToString.Include getInc() { + return inc; + } + + public boolean isDefaultInclude() { + return defaultInclude; + } + } + + public static <A extends AST<A, L, N>, L extends LombokNode<A, L, N>, N> List<ToStringMember<L>> handleToStringMarking(LombokNode<A, L, N> typeNode, AnnotationValues<ToString> annotation, LombokNode<A, L, N> annotationNode) { + ToString ann = annotation == null ? null : annotation.getInstance(); + List<String> oldExcludes = (ann != null && annotation.isExplicit("exclude")) ? Arrays.asList(ann.exclude()) : null; + List<String> oldIncludes = (ann != null && annotation.isExplicit("of")) ? Arrays.asList(ann.of()) : null; + + boolean onlyExplicitlyIncluded = ann != null && ann.onlyExplicitlyIncluded(); + boolean memberAnnotationMode = onlyExplicitlyIncluded; + List<ToStringMember<L>> members = new ArrayList<ToStringMember<L>>(); + List<String> namesToAutoExclude = new ArrayList<String>(); + + if (typeNode == null || typeNode.getKind() != Kind.TYPE) return null; + + checkForBogusFieldNames(typeNode, annotation, oldExcludes, oldIncludes); + + if (oldExcludes != null && oldIncludes != null) { + oldExcludes = null; + annotation.setWarning("exclude", "exclude and of are mutually exclusive; the 'exclude' parameter will be ignored."); + } + + for (L child : typeNode.down()) { + boolean markExclude = child.getKind() == Kind.FIELD && child.hasAnnotation(ToString.Exclude.class); + AnnotationValues<ToString.Include> markInclude = null; + if (child.getKind() == Kind.FIELD || child.getKind() == Kind.METHOD) markInclude = child.findAnnotation(ToString.Include.class); + + if (markExclude || markInclude != null) memberAnnotationMode = true; + + if (markInclude != null && markExclude) { + child.addError("@ToString.Exclude and @ToString.Include are mutually exclusive; the @Include annotation will be ignored"); + markInclude = null; + } + + String name = child.getName(); + + if (markExclude) { + if (onlyExplicitlyIncluded) { + child.addWarning("The @Exclude annotation is not needed; 'onlyExplicitlyIncluded' is set, so this member would be excluded anyway"); + } else if (child.isStatic()) { + child.addWarning("The @Exclude annotation is not needed; static fields aren't included anyway"); + } else if (name.startsWith("$")) { + child.addWarning("The @Exclude annotation is not needed; fields that start with $ aren't included anyway"); + } + continue; + } + + if (oldExcludes != null && oldExcludes.contains(name)) continue; + + if (markInclude != null) { + Include inc = markInclude.getInstance(); + if (child.getKind() == Kind.METHOD) { + if (child.countMethodParameters() > 0) { + child.addError("Methods included for @ToString must have no arguments; it will not be included"); + continue; + } + String n = inc.name(); + if (n.isEmpty()) n = name; + namesToAutoExclude.add(n); + } + members.add(new ToStringMember<L>(child, inc, false)); + continue; + } + + if (onlyExplicitlyIncluded) continue; + if (oldIncludes != null) { + if (child.getKind() == Kind.FIELD && oldIncludes.contains(name)) members.add(new ToStringMember<L>(child, null, false)); + continue; + } + if (child.getKind() != Kind.FIELD) continue; + if (child.isStatic()) continue; + if (name.startsWith("$")) continue; + if (child.isEnumMember()) continue; + members.add(new ToStringMember<L>(child, null, true)); + } + + /* delete default-included fields with the same name as an explicit inclusion */ { + Iterator<ToStringMember<L>> it = members.iterator(); + while (it.hasNext()) { + ToStringMember<L> m = it.next(); + if (m.isDefaultInclude() && namesToAutoExclude.contains(m.getNode().getName())) it.remove(); + } + } + + if (annotation == null || !annotation.isExplicit("exclude")) oldExcludes = null; + if (annotation == null || !annotation.isExplicit("of")) oldIncludes = null; + + if (memberAnnotationMode && (oldExcludes != null || oldIncludes != null)) { + annotationNode.addError("The old-style 'exclude/of' parameter cannot be used together with the new-style @Include / @Exclude annotations."); + return null; + } + + Collections.sort(members, new Comparator<ToStringMember<L>>() { + @Override public int compare(ToStringMember<L> a, ToStringMember<L> b) { + int ra = a.getInc() == null ? 0 : a.getInc().rank(); + int rb = b.getInc() == null ? 0 : b.getInc().rank(); + if (ra < rb) return +1; + if (ra > rb) return -1; + + int pa = a.getNode().getStartPos(); + int pb = b.getNode().getStartPos(); + + if (pa < pb) return -1; + if (pa > pb) return +1; + + return 0; + } + }); + return members; + } +} diff --git a/src/core/lombok/eclipse/EclipseNode.java b/src/core/lombok/eclipse/EclipseNode.java index 49867e62..4db1d38d 100644 --- a/src/core/lombok/eclipse/EclipseNode.java +++ b/src/core/lombok/eclipse/EclipseNode.java @@ -23,7 +23,9 @@ package lombok.eclipse; import java.util.List; +import lombok.core.AnnotationValues; import lombok.core.AST.Kind; +import lombok.eclipse.handlers.EclipseHandlerUtil; import org.eclipse.jdt.internal.compiler.ast.ASTNode; import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; @@ -36,6 +38,7 @@ import org.eclipse.jdt.internal.compiler.ast.Initializer; import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration; import org.eclipse.jdt.internal.compiler.ast.Statement; import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; +import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; /** * Eclipse specific version of the LombokNode class. @@ -184,4 +187,72 @@ public class EclipseNode extends lombok.core.LombokNode<EclipseAST, EclipseNode, public boolean isCompleteParse() { return ast.isCompleteParse(); } + + @Override public boolean hasAnnotation(Class<? extends java.lang.annotation.Annotation> type) { + return EclipseHandlerUtil.hasAnnotation(type, this); + } + + @Override public <Z extends java.lang.annotation.Annotation> AnnotationValues<Z> findAnnotation(Class<Z> type) { + EclipseNode annotation = EclipseHandlerUtil.findAnnotation(type, this); + if (annotation == null) return null; + return EclipseHandlerUtil.createAnnotation(type, annotation); + } + + private Integer getModifiers() { + if (node instanceof TypeDeclaration) return ((TypeDeclaration) node).modifiers; + if (node instanceof FieldDeclaration) return ((FieldDeclaration) node).modifiers; + if (node instanceof LocalDeclaration) return ((LocalDeclaration) node).modifiers; + if (node instanceof AbstractMethodDeclaration) return ((AbstractMethodDeclaration) node).modifiers; + + return null; + } + + @Override public boolean isStatic() { + if (node instanceof TypeDeclaration) { + EclipseNode directUp = directUp(); + if (directUp == null || directUp.getKind() == Kind.COMPILATION_UNIT) return true; + if (!(directUp.get() instanceof TypeDeclaration)) return false; + TypeDeclaration p = (TypeDeclaration) directUp.get(); + int f = p.modifiers; + if ((ClassFileConstants.AccInterface & f) != 0) return true; + if ((ClassFileConstants.AccEnum & f) != 0) return true; + } + + if (node instanceof FieldDeclaration) { + EclipseNode directUp = directUp(); + if (directUp != null && directUp.get() instanceof TypeDeclaration) { + TypeDeclaration p = (TypeDeclaration) directUp.get(); + int f = p.modifiers; + if ((ClassFileConstants.AccInterface & f) != 0) return true; + } + } + + Integer i = getModifiers(); + if (i == null) return false; + int f = i.intValue(); + return (ClassFileConstants.AccStatic & f) != 0; + } + + @Override public boolean isTransient() { + if (getKind() != Kind.FIELD) return false; + Integer i = getModifiers(); + return i != null && (i.intValue() & ClassFileConstants.AccTransient) != 0; + } + + @Override public boolean isEnumMember() { + if (getKind() != Kind.FIELD) return false; + return ((FieldDeclaration) node).getKind() == 3; + } + + @Override public int countMethodParameters() { + if (getKind() != Kind.METHOD) return 0; + + Argument[] a = ((AbstractMethodDeclaration) node).arguments; + if (a == null) return 0; + return a.length; + } + + @Override public int getStartPos() { + return node.sourceStart; + } } diff --git a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java index 2e402c7e..9f9b3975 100644 --- a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java +++ b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java @@ -920,10 +920,6 @@ public class EclipseHandlerUtil { return null; } - public enum FieldAccess { - GETTER, PREFER_FIELD, ALWAYS_FIELD; - } - static boolean lookForGetter(EclipseNode field, FieldAccess fieldAccess) { if (fieldAccess == FieldAccess.GETTER) return true; if (fieldAccess == FieldAccess.ALWAYS_FIELD) return false; @@ -940,11 +936,13 @@ public class EclipseHandlerUtil { } static TypeReference getFieldType(EclipseNode field, FieldAccess fieldAccess) { + if (field.get() instanceof MethodDeclaration) return ((MethodDeclaration) field.get()).returnType; + boolean lookForGetter = lookForGetter(field, fieldAccess); GetterMethod getter = lookForGetter ? findGetter(field) : null; if (getter == null) { - return ((FieldDeclaration)field.get()).type; + return ((FieldDeclaration) field.get()).type; } return getter.type; @@ -952,7 +950,7 @@ public class EclipseHandlerUtil { static Expression createFieldAccessor(EclipseNode field, FieldAccess fieldAccess, ASTNode source) { int pS = source == null ? 0 : source.sourceStart, pE = source == null ? 0 : source.sourceEnd; - long p = (long)pS << 32 | pE; + long p = (long) pS << 32 | pE; boolean lookForGetter = lookForGetter(field, fieldAccess); @@ -1020,6 +1018,29 @@ public class EclipseHandlerUtil { return call; } + static Expression createMethodAccessor(EclipseNode method, ASTNode source) { + int pS = source == null ? 0 : source.sourceStart, pE = source == null ? 0 : source.sourceEnd; + long p = (long) pS << 32 | pE; + + MethodDeclaration methodDecl = (MethodDeclaration) method.get(); + MessageSend call = new MessageSend(); + setGeneratedBy(call, source); + call.sourceStart = pS; call.statementEnd = call.sourceEnd = pE; + if ((methodDecl.modifiers & ClassFileConstants.AccStatic) == 0) { + call.receiver = new ThisReference(pS, pE); + setGeneratedBy(call.receiver, source); + } else { + EclipseNode containerNode = method.up(); + if (containerNode != null && containerNode.get() instanceof TypeDeclaration) { + call.receiver = new SingleNameReference(((TypeDeclaration) containerNode.get()).name, p); + setGeneratedBy(call.receiver, source); + } + } + + call.selector = methodDecl.selector; + return call; + } + /** Serves as return value for the methods that check for the existence of fields and methods. */ public enum MemberExistsResult { NOT_EXISTS, EXISTS_BY_LOMBOK, EXISTS_BY_USER; diff --git a/src/core/lombok/eclipse/handlers/HandleBuilder.java b/src/core/lombok/eclipse/handlers/HandleBuilder.java index d4cdc654..aa9fad84 100644 --- a/src/core/lombok/eclipse/handlers/HandleBuilder.java +++ b/src/core/lombok/eclipse/handlers/HandleBuilder.java @@ -76,6 +76,7 @@ import lombok.ConfigurationKeys; import lombok.Singular; import lombok.core.AST.Kind; import lombok.core.handlers.HandlerUtil; +import lombok.core.handlers.InclusionExclusionUtils.ToStringMember; import lombok.core.AnnotationValues; import lombok.core.HandlerPriority; import lombok.eclipse.Eclipse; @@ -449,9 +450,11 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { } if (methodExists("toString", builderType, 0) == MemberExistsResult.NOT_EXISTS) { - List<EclipseNode> fieldNodes = new ArrayList<EclipseNode>(); + List<ToStringMember<EclipseNode>> fieldNodes = new ArrayList<ToStringMember<EclipseNode>>(); for (BuilderFieldData bfd : builderFields) { - fieldNodes.addAll(bfd.createdFields); + for (EclipseNode f : bfd.createdFields) { + fieldNodes.add(new ToStringMember<EclipseNode>(f, null, true)); + } } MethodDeclaration md = HandleToString.createToString(builderType, fieldNodes, true, false, ast, FieldAccess.ALWAYS_FIELD); if (md != null) injectMethod(builderType, md); diff --git a/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java b/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java index 3e226269..2a497420 100644 --- a/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java +++ b/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java @@ -43,7 +43,6 @@ import lombok.core.configuration.CallSuperType; import lombok.eclipse.Eclipse; import lombok.eclipse.EclipseAnnotationHandler; import lombok.eclipse.EclipseNode; -import lombok.eclipse.handlers.EclipseHandlerUtil.FieldAccess; import lombok.eclipse.handlers.EclipseHandlerUtil.MemberExistsResult; import org.eclipse.jdt.internal.compiler.ast.ASTNode; diff --git a/src/core/lombok/eclipse/handlers/HandleGetter.java b/src/core/lombok/eclipse/handlers/HandleGetter.java index 533b97c4..d0c2cc23 100644 --- a/src/core/lombok/eclipse/handlers/HandleGetter.java +++ b/src/core/lombok/eclipse/handlers/HandleGetter.java @@ -41,7 +41,6 @@ import lombok.core.AnnotationValues; import lombok.eclipse.EclipseAnnotationHandler; import lombok.eclipse.EclipseNode; import lombok.eclipse.agent.PatchDelegate; -import lombok.eclipse.handlers.EclipseHandlerUtil.FieldAccess; import org.eclipse.jdt.internal.compiler.ast.ASTNode; import org.eclipse.jdt.internal.compiler.ast.AllocationExpression; diff --git a/src/core/lombok/eclipse/handlers/HandleSetter.java b/src/core/lombok/eclipse/handlers/HandleSetter.java index ab40b1e5..64f7c3cf 100644 --- a/src/core/lombok/eclipse/handlers/HandleSetter.java +++ b/src/core/lombok/eclipse/handlers/HandleSetter.java @@ -32,6 +32,14 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import lombok.AccessLevel; +import lombok.ConfigurationKeys; +import lombok.Setter; +import lombok.core.AST.Kind; +import lombok.core.AnnotationValues; +import lombok.eclipse.EclipseAnnotationHandler; +import lombok.eclipse.EclipseNode; + import org.eclipse.jdt.internal.compiler.ast.ASTNode; import org.eclipse.jdt.internal.compiler.ast.Annotation; import org.eclipse.jdt.internal.compiler.ast.Argument; @@ -51,15 +59,6 @@ import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; import org.eclipse.jdt.internal.compiler.lookup.TypeIds; import org.mangosdk.spi.ProviderFor; -import lombok.AccessLevel; -import lombok.ConfigurationKeys; -import lombok.Setter; -import lombok.core.AST.Kind; -import lombok.core.AnnotationValues; -import lombok.eclipse.EclipseAnnotationHandler; -import lombok.eclipse.EclipseNode; -import lombok.eclipse.handlers.EclipseHandlerUtil.FieldAccess; - /** * Handles the {@code lombok.Setter} annotation for eclipse. */ diff --git a/src/core/lombok/eclipse/handlers/HandleToString.java b/src/core/lombok/eclipse/handlers/HandleToString.java index d8f4c569..14a2374b 100644 --- a/src/core/lombok/eclipse/handlers/HandleToString.java +++ b/src/core/lombok/eclipse/handlers/HandleToString.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2014 The Project Lombok Authors. + * Copyright (C) 2009-2018 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 @@ -24,7 +24,6 @@ package lombok.eclipse.handlers; import static lombok.core.handlers.HandlerUtil.*; import static lombok.eclipse.handlers.EclipseHandlerUtil.*; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -37,17 +36,17 @@ import lombok.ConfigurationKeys; import lombok.ToString; import lombok.core.AST.Kind; import lombok.core.AnnotationValues; +import lombok.core.handlers.InclusionExclusionUtils; +import lombok.core.handlers.InclusionExclusionUtils.ToStringMember; import lombok.eclipse.Eclipse; import lombok.eclipse.EclipseAnnotationHandler; import lombok.eclipse.EclipseNode; -import lombok.eclipse.handlers.EclipseHandlerUtil.FieldAccess; import org.eclipse.jdt.internal.compiler.ast.ASTNode; import org.eclipse.jdt.internal.compiler.ast.Annotation; import org.eclipse.jdt.internal.compiler.ast.BinaryExpression; import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; import org.eclipse.jdt.internal.compiler.ast.Expression; -import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; import org.eclipse.jdt.internal.compiler.ast.MessageSend; import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; import org.eclipse.jdt.internal.compiler.ast.NameReference; @@ -70,17 +69,25 @@ import org.mangosdk.spi.ProviderFor; */ @ProviderFor(EclipseAnnotationHandler.class) public class HandleToString extends EclipseAnnotationHandler<ToString> { - public void checkForBogusFieldNames(EclipseNode type, AnnotationValues<ToString> annotation) { - if (annotation.isExplicit("exclude")) { - for (int i : createListOfNonExistentFields(Arrays.asList(annotation.getInstance().exclude()), type, true, false)) { - annotation.setWarning("exclude", "This field does not exist, or would have been excluded anyway.", i); - } - } - if (annotation.isExplicit("of")) { - for (int i : createListOfNonExistentFields(Arrays.asList(annotation.getInstance().of()), type, false, false)) { - annotation.setWarning("of", "This field does not exist.", i); - } - } + public void handle(AnnotationValues<ToString> annotation, Annotation ast, EclipseNode annotationNode) { + handleFlagUsage(annotationNode, ConfigurationKeys.TO_STRING_FLAG_USAGE, "@ToString"); + + ToString ann = annotation.getInstance(); + List<ToStringMember<EclipseNode>> members = InclusionExclusionUtils.handleToStringMarking(annotationNode.up(), annotation, annotationNode); + if (members == null) return; + + Boolean callSuper = ann.callSuper(); + + if (!annotation.isExplicit("callSuper")) callSuper = null; + + Boolean doNotUseGettersConfiguration = annotationNode.getAst().readConfiguration(ConfigurationKeys.TO_STRING_DO_NOT_USE_GETTERS); + boolean doNotUseGetters = annotation.isExplicit("doNotUseGetters") || doNotUseGettersConfiguration == null ? ann.doNotUseGetters() : doNotUseGettersConfiguration; + FieldAccess fieldAccess = doNotUseGetters ? FieldAccess.PREFER_FIELD : FieldAccess.GETTER; + + Boolean fieldNamesConfiguration = annotationNode.getAst().readConfiguration(ConfigurationKeys.TO_STRING_INCLUDE_FIELD_NAMES); + boolean includeFieldNames = annotation.isExplicit("includeFieldNames") || fieldNamesConfiguration == null ? ann.includeFieldNames() : fieldNamesConfiguration; + + generateToString(annotationNode.up(), annotationNode, members, includeFieldNames, callSuper, true, fieldAccess); } public void generateToStringForType(EclipseNode typeNode, EclipseNode errorNode) { @@ -98,41 +105,13 @@ public class HandleToString extends EclipseAnnotationHandler<ToString> { Boolean doNotUseGettersConfiguration = typeNode.getAst().readConfiguration(ConfigurationKeys.TO_STRING_DO_NOT_USE_GETTERS); FieldAccess access = doNotUseGettersConfiguration == null || !doNotUseGettersConfiguration ? FieldAccess.GETTER : FieldAccess.PREFER_FIELD; - generateToString(typeNode, errorNode, null, null, includeFieldNames, null, false, access); + List<ToStringMember<EclipseNode>> members = InclusionExclusionUtils.handleToStringMarking(typeNode, null, null); + generateToString(typeNode, errorNode, members, includeFieldNames, null, false, access); } - public void handle(AnnotationValues<ToString> annotation, Annotation ast, EclipseNode annotationNode) { - handleFlagUsage(annotationNode, ConfigurationKeys.TO_STRING_FLAG_USAGE, "@ToString"); - - ToString ann = annotation.getInstance(); - List<String> excludes = Arrays.asList(ann.exclude()); - List<String> includes = Arrays.asList(ann.of()); - EclipseNode typeNode = annotationNode.up(); - Boolean callSuper = ann.callSuper(); + public void generateToString(EclipseNode typeNode, EclipseNode errorNode, List<ToStringMember<EclipseNode>> members, + boolean includeFieldNames, Boolean callSuper, boolean whineIfExists, FieldAccess fieldAccess) { - if (!annotation.isExplicit("callSuper")) callSuper = null; - if (!annotation.isExplicit("exclude")) excludes = null; - if (!annotation.isExplicit("of")) includes = null; - - if (excludes != null && includes != null) { - excludes = null; - annotation.setWarning("exclude", "exclude and of are mutually exclusive; the 'exclude' parameter will be ignored."); - } - - checkForBogusFieldNames(typeNode, annotation); - - Boolean doNotUseGettersConfiguration = annotationNode.getAst().readConfiguration(ConfigurationKeys.TO_STRING_DO_NOT_USE_GETTERS); - boolean doNotUseGetters = annotation.isExplicit("doNotUseGetters") || doNotUseGettersConfiguration == null ? ann.doNotUseGetters() : doNotUseGettersConfiguration; - FieldAccess fieldAccess = doNotUseGetters ? FieldAccess.PREFER_FIELD : FieldAccess.GETTER; - - Boolean fieldNamesConfiguration = annotationNode.getAst().readConfiguration(ConfigurationKeys.TO_STRING_INCLUDE_FIELD_NAMES); - boolean includeFieldNames = annotation.isExplicit("includeFieldNames") || fieldNamesConfiguration == null ? ann.includeFieldNames() : fieldNamesConfiguration; - - generateToString(typeNode, annotationNode, excludes, includes, includeFieldNames, callSuper, true, fieldAccess); - } - - public void generateToString(EclipseNode typeNode, EclipseNode errorNode, List<String> excludes, List<String> includes, - boolean includeFieldNames, Boolean callSuper, boolean whineIfExists, FieldAccess fieldAccess) { TypeDeclaration typeDecl = null; if (typeNode.get() instanceof TypeDeclaration) typeDecl = (TypeDeclaration) typeNode.get(); @@ -140,39 +119,20 @@ public class HandleToString extends EclipseAnnotationHandler<ToString> { boolean notAClass = (modifiers & (ClassFileConstants.AccInterface | ClassFileConstants.AccAnnotation)) != 0; - if (typeDecl == null || notAClass) { - errorNode.addError("@ToString is only supported on a class or enum."); - } - if (callSuper == null) { try { callSuper = ((Boolean)ToString.class.getMethod("callSuper").getDefaultValue()).booleanValue(); } catch (Exception ignore) {} } - List<EclipseNode> nodesForToString = new ArrayList<EclipseNode>(); - if (includes != null) { - for (EclipseNode child : typeNode.down()) { - if (child.getKind() != Kind.FIELD) continue; - FieldDeclaration fieldDecl = (FieldDeclaration) child.get(); - if (includes.contains(new String(fieldDecl.name))) nodesForToString.add(child); - } - } else { - for (EclipseNode child : typeNode.down()) { - if (child.getKind() != Kind.FIELD) continue; - FieldDeclaration fieldDecl = (FieldDeclaration) child.get(); - if (!filterField(fieldDecl)) continue; - - //Skip excluded fields. - if (excludes != null && excludes.contains(new String(fieldDecl.name))) continue; - - nodesForToString.add(child); - } + if (typeDecl == null || notAClass) { + errorNode.addError("@ToString is only supported on a class or enum."); + return; } switch (methodExists("toString", typeNode, 0)) { case NOT_EXISTS: - MethodDeclaration toString = createToString(typeNode, nodesForToString, includeFieldNames, callSuper, errorNode.get(), fieldAccess); + MethodDeclaration toString = createToString(typeNode, members, includeFieldNames, callSuper, errorNode.get(), fieldAccess); injectMethod(typeNode, toString); break; case EXISTS_BY_LOMBOK: @@ -185,8 +145,9 @@ public class HandleToString extends EclipseAnnotationHandler<ToString> { } } - public static MethodDeclaration createToString(EclipseNode type, Collection<EclipseNode> fields, - boolean includeFieldNames, boolean callSuper, ASTNode source, FieldAccess fieldAccess) { + public static MethodDeclaration createToString(EclipseNode type, Collection<ToStringMember<EclipseNode>> members, + boolean includeNames, boolean callSuper, ASTNode source, FieldAccess fieldAccess) { + String typeName = getTypeName(type); char[] suffix = ")".toCharArray(); String infixS = ", "; @@ -199,10 +160,13 @@ public class HandleToString extends EclipseAnnotationHandler<ToString> { if (callSuper) { prefix = (typeName + "(super=").toCharArray(); - } else if (fields.isEmpty()) { + } else if (members.isEmpty()) { prefix = (typeName + "()").toCharArray(); - } else if (includeFieldNames) { - prefix = (typeName + "(" + new String(((FieldDeclaration)fields.iterator().next().get()).name) + "=").toCharArray(); + } else if (includeNames) { + ToStringMember<EclipseNode> firstMember = members.iterator().next(); + String name = firstMember.getInc() == null ? "" : firstMember.getInc().name(); + if (name.isEmpty()) name = firstMember.getNode().getName(); + prefix = (typeName + "(" + name + "=").toCharArray(); } else { prefix = (typeName + "(").toCharArray(); } @@ -223,29 +187,35 @@ public class HandleToString extends EclipseAnnotationHandler<ToString> { first = false; } - for (EclipseNode field : fields) { - TypeReference fieldType = getFieldType(field, fieldAccess); - Expression fieldAccessor = createFieldAccessor(field, fieldAccess, source); + for (ToStringMember<EclipseNode> member : members) { + EclipseNode memberNode = member.getNode(); + + TypeReference fieldType = getFieldType(memberNode, fieldAccess); + Expression memberAccessor; + if (memberNode.getKind() == Kind.METHOD) { + memberAccessor = createMethodAccessor(memberNode, source); + } else { + memberAccessor = createFieldAccessor(memberNode, fieldAccess, source); + } // The distinction between primitive and object will be useful if we ever add a 'hideNulls' option. boolean fieldBaseTypeIsPrimitive = BUILT_IN_TYPES.contains(new String(fieldType.getLastToken())); + @SuppressWarnings("unused") boolean fieldIsPrimitive = fieldType.dimensions() == 0 && fieldBaseTypeIsPrimitive; boolean fieldIsPrimitiveArray = fieldType.dimensions() == 1 && fieldBaseTypeIsPrimitive; boolean fieldIsObjectArray = fieldType.dimensions() > 0 && !fieldIsPrimitiveArray; - @SuppressWarnings("unused") - boolean fieldIsObject = !fieldIsPrimitive && !fieldIsPrimitiveArray && !fieldIsObjectArray; Expression ex; if (fieldIsPrimitiveArray || fieldIsObjectArray) { MessageSend arrayToString = new MessageSend(); arrayToString.sourceStart = pS; arrayToString.sourceEnd = pE; arrayToString.receiver = generateQualifiedNameRef(source, TypeConstants.JAVA, TypeConstants.UTIL, "Arrays".toCharArray()); - arrayToString.arguments = new Expression[] { fieldAccessor }; + arrayToString.arguments = new Expression[] { memberAccessor }; setGeneratedBy(arrayToString.arguments[0], source); arrayToString.selector = (fieldIsObjectArray ? "deepToString" : "toString").toCharArray(); ex = arrayToString; } else { - ex = fieldAccessor; + ex = memberAccessor; } setGeneratedBy(ex, source); @@ -258,8 +228,10 @@ public class HandleToString extends EclipseAnnotationHandler<ToString> { } StringLiteral fieldNameLiteral; - if (includeFieldNames) { - char[] namePlusEqualsSign = (infixS + field.getName() + "=").toCharArray(); + if (includeNames) { + String n = member.getInc() == null ? "" : member.getInc().name(); + if (n.isEmpty()) n = memberNode.getName(); + char[] namePlusEqualsSign = (infixS + n + "=").toCharArray(); fieldNameLiteral = new StringLiteral(namePlusEqualsSign, pS, pE, 0); } else { fieldNameLiteral = new StringLiteral(infix, pS, pE, 0); diff --git a/src/core/lombok/eclipse/handlers/HandleWither.java b/src/core/lombok/eclipse/handlers/HandleWither.java index 200ebde7..c035fc26 100644 --- a/src/core/lombok/eclipse/handlers/HandleWither.java +++ b/src/core/lombok/eclipse/handlers/HandleWither.java @@ -36,7 +36,6 @@ import lombok.core.AST.Kind; import lombok.core.AnnotationValues; import lombok.eclipse.EclipseAnnotationHandler; import lombok.eclipse.EclipseNode; -import lombok.eclipse.handlers.EclipseHandlerUtil.FieldAccess; import lombok.experimental.Wither; import org.eclipse.jdt.internal.compiler.ast.ASTNode; diff --git a/src/core/lombok/javac/JavacNode.java b/src/core/lombok/javac/JavacNode.java index fa24c2f9..2bce6e3a 100644 --- a/src/core/lombok/javac/JavacNode.java +++ b/src/core/lombok/javac/JavacNode.java @@ -21,13 +21,17 @@ */ package lombok.javac; +import java.lang.annotation.Annotation; import java.util.List; import javax.lang.model.element.Element; import javax.tools.Diagnostic; +import lombok.core.AnnotationValues; import lombok.core.AST.Kind; +import lombok.javac.handlers.JavacHandlerUtil; +import com.sun.tools.javac.code.Flags; import com.sun.tools.javac.code.Symtab; import com.sun.tools.javac.model.JavacTypes; import com.sun.tools.javac.tree.JCTree; @@ -36,6 +40,7 @@ import com.sun.tools.javac.tree.JCTree.JCBlock; import com.sun.tools.javac.tree.JCTree.JCClassDecl; import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; import com.sun.tools.javac.tree.JCTree.JCMethodDecl; +import com.sun.tools.javac.tree.JCTree.JCModifiers; import com.sun.tools.javac.tree.JCTree.JCVariableDecl; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.Name; @@ -256,4 +261,70 @@ public class JavacNode extends lombok.core.LombokNode<JavacAST, JavacNode, JCTre public void addWarning(String message, DiagnosticPosition pos) { ast.printMessage(Diagnostic.Kind.WARNING, message, null, pos, false); } + + @Override public boolean hasAnnotation(Class<? extends Annotation> type) { + return JavacHandlerUtil.hasAnnotationAndDeleteIfNeccessary(type, this); + } + + @Override public <Z extends Annotation> AnnotationValues<Z> findAnnotation(Class<Z> type) { + JavacNode annotation = JavacHandlerUtil.findAnnotation(type, this, true); + if (annotation == null) return null; + return JavacHandlerUtil.createAnnotation(type, annotation); + } + + private JCModifiers getModifiers() { + if (node instanceof JCClassDecl) return ((JCClassDecl) node).getModifiers(); + if (node instanceof JCMethodDecl) return ((JCMethodDecl) node).getModifiers(); + if (node instanceof JCVariableDecl) return ((JCVariableDecl) node).getModifiers(); + return null; + } + + @Override public boolean isStatic() { + if (node instanceof JCClassDecl) { + JavacNode directUp = directUp(); + if (directUp == null || directUp.getKind() == Kind.COMPILATION_UNIT) return true; + if (!(directUp.get() instanceof JCClassDecl)) return false; + JCClassDecl p = (JCClassDecl) directUp.get(); + long f = p.mods.flags; + if ((Flags.INTERFACE & f) != 0) return true; + if ((Flags.ENUM & f) != 0) return true; + } + + if (node instanceof JCVariableDecl) { + JavacNode directUp = directUp(); + if (directUp != null && directUp.get() instanceof JCClassDecl) { + JCClassDecl p = (JCClassDecl) directUp.get(); + long f = p.mods.flags; + if ((Flags.INTERFACE & f) != 0) return true; + } + } + + JCModifiers mods = getModifiers(); + if (mods == null) return false; + return (mods.flags & Flags.STATIC) != 0; + } + + @Override public boolean isEnumMember() { + if (getKind() != Kind.FIELD) return false; + JCModifiers mods = getModifiers(); + return mods != null && (Flags.ENUM & mods.flags) != 0; + } + + @Override public boolean isTransient() { + if (getKind() != Kind.FIELD) return false; + JCModifiers mods = getModifiers(); + return mods != null && (Flags.TRANSIENT & mods.flags) != 0; + } + + @Override public int countMethodParameters() { + if (getKind() != Kind.METHOD) return 0; + + com.sun.tools.javac.util.List<JCVariableDecl> params = ((JCMethodDecl) node).params; + if (params == null) return 0; + return params.size(); + } + + @Override public int getStartPos() { + return node.getPreferredPosition(); + } } diff --git a/src/core/lombok/javac/handlers/HandleBuilder.java b/src/core/lombok/javac/handlers/HandleBuilder.java index 86ac00e6..7cf78392 100644 --- a/src/core/lombok/javac/handlers/HandleBuilder.java +++ b/src/core/lombok/javac/handlers/HandleBuilder.java @@ -58,6 +58,7 @@ import lombok.core.AST.Kind; import lombok.core.AnnotationValues; import lombok.core.HandlerPriority; import lombok.core.handlers.HandlerUtil; +import lombok.core.handlers.InclusionExclusionUtils.ToStringMember; import lombok.experimental.NonFinal; import lombok.javac.Javac; import lombok.javac.JavacAnnotationHandler; @@ -398,10 +399,13 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { } if (methodExists("toString", builderType, 0) == MemberExistsResult.NOT_EXISTS) { - java.util.List<JavacNode> fieldNodes = new ArrayList<JavacNode>(); + java.util.List<ToStringMember<JavacNode>> fieldNodes = new ArrayList<ToStringMember<JavacNode>>(); for (BuilderFieldData bfd : builderFields) { - fieldNodes.addAll(bfd.createdFields); + for (JavacNode f : bfd.createdFields) { + fieldNodes.add(new ToStringMember<JavacNode>(f, null, true)); + } } + JCMethodDecl md = HandleToString.createToString(builderType, fieldNodes, true, false, FieldAccess.ALWAYS_FIELD, ast); if (md != null) injectMethod(builderType, md); } diff --git a/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java b/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java index d8bfd154..b3650ca6 100644 --- a/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java +++ b/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java @@ -39,7 +39,6 @@ import lombok.javac.Javac; import lombok.javac.JavacAnnotationHandler; import lombok.javac.JavacNode; import lombok.javac.JavacTreeMaker; -import lombok.javac.handlers.JavacHandlerUtil.FieldAccess; import lombok.javac.handlers.JavacHandlerUtil.MemberExistsResult; import org.mangosdk.spi.ProviderFor; diff --git a/src/core/lombok/javac/handlers/HandleGetter.java b/src/core/lombok/javac/handlers/HandleGetter.java index 7e6598a7..4fc6155c 100644 --- a/src/core/lombok/javac/handlers/HandleGetter.java +++ b/src/core/lombok/javac/handlers/HandleGetter.java @@ -41,7 +41,6 @@ import lombok.javac.JavacAnnotationHandler; import lombok.javac.JavacNode; import lombok.javac.JavacTreeMaker; import lombok.javac.JavacTreeMaker.TypeTag; -import lombok.javac.handlers.JavacHandlerUtil.FieldAccess; import org.mangosdk.spi.ProviderFor; diff --git a/src/core/lombok/javac/handlers/HandleSetter.java b/src/core/lombok/javac/handlers/HandleSetter.java index 331b2fac..4e3c9576 100644 --- a/src/core/lombok/javac/handlers/HandleSetter.java +++ b/src/core/lombok/javac/handlers/HandleSetter.java @@ -36,7 +36,6 @@ import lombok.javac.Javac; import lombok.javac.JavacAnnotationHandler; import lombok.javac.JavacNode; import lombok.javac.JavacTreeMaker; -import lombok.javac.handlers.JavacHandlerUtil.FieldAccess; import org.mangosdk.spi.ProviderFor; diff --git a/src/core/lombok/javac/handlers/HandleToString.java b/src/core/lombok/javac/handlers/HandleToString.java index 897d5f2c..8c580207 100644 --- a/src/core/lombok/javac/handlers/HandleToString.java +++ b/src/core/lombok/javac/handlers/HandleToString.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2014 The Project Lombok Authors. + * Copyright (C) 2009-2018 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,8 @@ import lombok.ConfigurationKeys; import lombok.ToString; import lombok.core.AnnotationValues; import lombok.core.AST.Kind; +import lombok.core.handlers.InclusionExclusionUtils; +import lombok.core.handlers.InclusionExclusionUtils.ToStringMember; import lombok.javac.JavacAnnotationHandler; import lombok.javac.JavacNode; import lombok.javac.JavacTreeMaker; @@ -52,57 +54,33 @@ import com.sun.tools.javac.tree.JCTree.JCStatement; import com.sun.tools.javac.tree.JCTree.JCTypeParameter; import com.sun.tools.javac.tree.JCTree.JCVariableDecl; import com.sun.tools.javac.util.List; -import com.sun.tools.javac.util.ListBuffer; /** * Handles the {@code ToString} annotation for javac. */ @ProviderFor(JavacAnnotationHandler.class) public class HandleToString extends JavacAnnotationHandler<ToString> { - public void checkForBogusFieldNames(JavacNode type, AnnotationValues<ToString> annotation) { - if (annotation.isExplicit("exclude")) { - for (int i : createListOfNonExistentFields(List.from(annotation.getInstance().exclude()), type, true, false)) { - annotation.setWarning("exclude", "This field does not exist, or would have been excluded anyway.", i); - } - } - if (annotation.isExplicit("of")) { - for (int i : createListOfNonExistentFields(List.from(annotation.getInstance().of()), type, false, false)) { - annotation.setWarning("of", "This field does not exist.", i); - } - } - } - @Override public void handle(AnnotationValues<ToString> annotation, JCAnnotation ast, JavacNode annotationNode) { handleFlagUsage(annotationNode, ConfigurationKeys.TO_STRING_FLAG_USAGE, "@ToString"); deleteAnnotationIfNeccessary(annotationNode, ToString.class); ToString ann = annotation.getInstance(); - List<String> excludes = List.from(ann.exclude()); - List<String> includes = List.from(ann.of()); - JavacNode typeNode = annotationNode.up(); - - checkForBogusFieldNames(typeNode, annotation); + java.util.List<ToStringMember<JavacNode>> members = InclusionExclusionUtils.handleToStringMarking(annotationNode.up(), annotation, annotationNode); + if (members == null) return; Boolean callSuper = ann.callSuper(); if (!annotation.isExplicit("callSuper")) callSuper = null; - if (!annotation.isExplicit("exclude")) excludes = null; - if (!annotation.isExplicit("of")) includes = null; - - if (excludes != null && includes != null) { - excludes = null; - annotation.setWarning("exclude", "exclude and of are mutually exclusive; the 'exclude' parameter will be ignored."); - } Boolean doNotUseGettersConfiguration = annotationNode.getAst().readConfiguration(ConfigurationKeys.TO_STRING_DO_NOT_USE_GETTERS); boolean doNotUseGetters = annotation.isExplicit("doNotUseGetters") || doNotUseGettersConfiguration == null ? ann.doNotUseGetters() : doNotUseGettersConfiguration; FieldAccess fieldAccess = doNotUseGetters ? FieldAccess.PREFER_FIELD : FieldAccess.GETTER; Boolean fieldNamesConfiguration = annotationNode.getAst().readConfiguration(ConfigurationKeys.TO_STRING_INCLUDE_FIELD_NAMES); - boolean includeFieldNames = annotation.isExplicit("includeFieldNames") || fieldNamesConfiguration == null ? ann.includeFieldNames() : fieldNamesConfiguration; + boolean includeNames = annotation.isExplicit("includeFieldNames") || fieldNamesConfiguration == null ? ann.includeFieldNames() : fieldNamesConfiguration; - generateToString(typeNode, annotationNode, excludes, includes, includeFieldNames, callSuper, true, fieldAccess); + generateToString(annotationNode.up(), annotationNode, members, includeNames, callSuper, true, fieldAccess); } public void generateToStringForType(JavacNode typeNode, JavacNode errorNode) { @@ -111,7 +89,6 @@ public class HandleToString extends JavacAnnotationHandler<ToString> { return; } - boolean includeFieldNames = true; try { Boolean configuration = typeNode.getAst().readConfiguration(ConfigurationKeys.TO_STRING_INCLUDE_FIELD_NAMES); @@ -121,20 +98,22 @@ public class HandleToString extends JavacAnnotationHandler<ToString> { Boolean doNotUseGettersConfiguration = typeNode.getAst().readConfiguration(ConfigurationKeys.TO_STRING_DO_NOT_USE_GETTERS); FieldAccess access = doNotUseGettersConfiguration == null || !doNotUseGettersConfiguration ? FieldAccess.GETTER : FieldAccess.PREFER_FIELD; - generateToString(typeNode, errorNode, null, null, includeFieldNames, null, false, access); + java.util.List<ToStringMember<JavacNode>> members = InclusionExclusionUtils.handleToStringMarking(typeNode, null, null); + generateToString(typeNode, errorNode, members, includeFieldNames, null, false, access); } - public void generateToString(JavacNode typeNode, JavacNode source, List<String> excludes, List<String> includes, - boolean includeFieldNames, Boolean callSuper, boolean whineIfExists, FieldAccess fieldAccess) { + public void generateToString(JavacNode typeNode, JavacNode source, java.util.List<ToStringMember<JavacNode>> members, + boolean includeFieldNames, Boolean callSuper, boolean whineIfExists, FieldAccess fieldAccess) { + boolean notAClass = true; if (typeNode.get() instanceof JCClassDecl) { - long flags = ((JCClassDecl)typeNode.get()).mods.flags; + long flags = ((JCClassDecl) typeNode.get()).mods.flags; notAClass = (flags & (Flags.INTERFACE | Flags.ANNOTATION)) != 0; } if (callSuper == null) { try { - callSuper = ((Boolean)ToString.class.getMethod("callSuper").getDefaultValue()).booleanValue(); + callSuper = ((Boolean) ToString.class.getMethod("callSuper").getDefaultValue()).booleanValue(); } catch (Exception ignore) {} } @@ -143,30 +122,9 @@ public class HandleToString extends JavacAnnotationHandler<ToString> { return; } - ListBuffer<JavacNode> nodesForToString = new ListBuffer<JavacNode>(); - if (includes != null) { - for (JavacNode child : typeNode.down()) { - if (child.getKind() != Kind.FIELD) continue; - JCVariableDecl fieldDecl = (JCVariableDecl) child.get(); - if (includes.contains(fieldDecl.name.toString())) nodesForToString.append(child); - } - } else { - for (JavacNode child : typeNode.down()) { - if (child.getKind() != Kind.FIELD) continue; - JCVariableDecl fieldDecl = (JCVariableDecl) child.get(); - //Skip static fields. - if ((fieldDecl.mods.flags & Flags.STATIC) != 0) continue; - //Skip excluded fields. - if (excludes != null && excludes.contains(fieldDecl.name.toString())) continue; - //Skip fields that start with $. - if (fieldDecl.name.toString().startsWith("$")) continue; - nodesForToString.append(child); - } - } - switch (methodExists("toString", typeNode, 0)) { case NOT_EXISTS: - JCMethodDecl method = createToString(typeNode, nodesForToString.toList(), includeFieldNames, callSuper, fieldAccess, source.get()); + JCMethodDecl method = createToString(typeNode, members, includeFieldNames, callSuper, fieldAccess, source.get()); injectMethod(typeNode, method); break; case EXISTS_BY_LOMBOK: @@ -180,7 +138,9 @@ public class HandleToString extends JavacAnnotationHandler<ToString> { } } - static JCMethodDecl createToString(JavacNode typeNode, Collection<JavacNode> fields, boolean includeFieldNames, boolean callSuper, FieldAccess fieldAccess, JCTree source) { + static JCMethodDecl createToString(JavacNode typeNode, Collection<ToStringMember<JavacNode>> members, + boolean includeNames, boolean callSuper, FieldAccess fieldAccess, JCTree source) { + JavacTreeMaker maker = typeNode.getTreeMaker(); JCAnnotation overrideAnnotation = maker.Annotation(genJavaLangTypeRef(typeNode, "Override"), List.<JCExpression>nil()); @@ -195,10 +155,13 @@ public class HandleToString extends JavacAnnotationHandler<ToString> { String prefix; if (callSuper) { prefix = typeName + "(super="; - } else if (fields.isEmpty()) { + } else if (members.isEmpty()) { prefix = typeName + "()"; - } else if (includeFieldNames) { - prefix = typeName + "(" + ((JCVariableDecl)fields.iterator().next().get()).name.toString() + "="; + } else if (includeNames) { + ToStringMember<JavacNode> firstMember = members.iterator().next(); + String name = firstMember.getInc() == null ? "" : firstMember.getInc().name(); + if (name.isEmpty()) name = firstMember.getNode().getName(); + prefix = typeName + "(" + name + "="; } else { prefix = typeName + "("; } @@ -207,30 +170,35 @@ public class HandleToString extends JavacAnnotationHandler<ToString> { if (callSuper) { JCMethodInvocation callToSuper = maker.Apply(List.<JCExpression>nil(), - maker.Select(maker.Ident(typeNode.toName("super")), typeNode.toName("toString")), - List.<JCExpression>nil()); + maker.Select(maker.Ident(typeNode.toName("super")), typeNode.toName("toString")), + List.<JCExpression>nil()); current = maker.Binary(CTC_PLUS, current, callToSuper); first = false; } - for (JavacNode fieldNode : fields) { + for (ToStringMember<JavacNode> member : members) { JCExpression expr; - JCExpression fieldAccessor = createFieldAccessor(maker, fieldNode, fieldAccess); + JCExpression memberAccessor; + JavacNode memberNode = member.getNode(); + if (memberNode.getKind() == Kind.METHOD) { + memberAccessor = createMethodAccessor(maker, memberNode); + } else { + memberAccessor = createFieldAccessor(maker, memberNode, fieldAccess); + } - JCExpression fieldType = getFieldType(fieldNode, fieldAccess); + JCExpression memberType = getFieldType(memberNode, fieldAccess); // The distinction between primitive and object will be useful if we ever add a 'hideNulls' option. - boolean fieldIsPrimitive = fieldType instanceof JCPrimitiveTypeTree; - boolean fieldIsPrimitiveArray = fieldType instanceof JCArrayTypeTree && ((JCArrayTypeTree) fieldType).elemtype instanceof JCPrimitiveTypeTree; - boolean fieldIsObjectArray = !fieldIsPrimitiveArray && fieldType instanceof JCArrayTypeTree; @SuppressWarnings("unused") - boolean fieldIsObject = !fieldIsPrimitive && !fieldIsPrimitiveArray && !fieldIsObjectArray; + boolean fieldIsPrimitive = memberType instanceof JCPrimitiveTypeTree; + boolean fieldIsPrimitiveArray = memberType instanceof JCArrayTypeTree && ((JCArrayTypeTree) memberType).elemtype instanceof JCPrimitiveTypeTree; + boolean fieldIsObjectArray = !fieldIsPrimitiveArray && memberType instanceof JCArrayTypeTree; if (fieldIsPrimitiveArray || fieldIsObjectArray) { JCExpression tsMethod = chainDots(typeNode, "java", "util", "Arrays", fieldIsObjectArray ? "deepToString" : "toString"); - expr = maker.Apply(List.<JCExpression>nil(), tsMethod, List.<JCExpression>of(fieldAccessor)); - } else expr = fieldAccessor; + expr = maker.Apply(List.<JCExpression>nil(), tsMethod, List.<JCExpression>of(memberAccessor)); + } else expr = memberAccessor; if (first) { current = maker.Binary(CTC_PLUS, current, expr); @@ -238,8 +206,10 @@ public class HandleToString extends JavacAnnotationHandler<ToString> { continue; } - if (includeFieldNames) { - current = maker.Binary(CTC_PLUS, current, maker.Literal(infix + fieldNode.getName() + "=")); + if (includeNames) { + String n = member.getInc() == null ? "" : member.getInc().name(); + if (n.isEmpty()) n = memberNode.getName(); + current = maker.Binary(CTC_PLUS, current, maker.Literal(infix + n + "=")); } else { current = maker.Binary(CTC_PLUS, current, maker.Literal(infix)); } @@ -254,7 +224,7 @@ public class HandleToString extends JavacAnnotationHandler<ToString> { JCBlock body = maker.Block(0, List.of(returnStatement)); return recursiveSetGeneratedBy(maker.MethodDef(mods, typeNode.toName("toString"), returnType, - List.<JCTypeParameter>nil(), List.<JCVariableDecl>nil(), List.<JCExpression>nil(), body, null), source, typeNode.getContext()); + List.<JCTypeParameter>nil(), List.<JCVariableDecl>nil(), List.<JCExpression>nil(), body, null), source, typeNode.getContext()); } public static String getTypeName(JavacNode typeNode) { diff --git a/src/core/lombok/javac/handlers/HandleWither.java b/src/core/lombok/javac/handlers/HandleWither.java index 987a3d34..87f3c16a 100644 --- a/src/core/lombok/javac/handlers/HandleWither.java +++ b/src/core/lombok/javac/handlers/HandleWither.java @@ -36,7 +36,6 @@ import lombok.javac.JavacAnnotationHandler; import lombok.javac.JavacNode; import lombok.javac.JavacTreeMaker; import lombok.javac.handlers.JavacHandlerUtil.CopyJavadoc; -import lombok.javac.handlers.JavacHandlerUtil.FieldAccess; import org.mangosdk.spi.ProviderFor; diff --git a/src/core/lombok/javac/handlers/JavacHandlerUtil.java b/src/core/lombok/javac/handlers/JavacHandlerUtil.java index 4b5c48dd..917e2e9c 100644 --- a/src/core/lombok/javac/handlers/JavacHandlerUtil.java +++ b/src/core/lombok/javac/handlers/JavacHandlerUtil.java @@ -216,7 +216,11 @@ public class JavacHandlerUtil { } } - static JavacNode findAnnotation(Class<? extends Annotation> type, JavacNode node, boolean delete) { + public static JavacNode findAnnotation(Class<? extends Annotation> type, JavacNode node) { + return findAnnotation(type, node, false); + } + + public static JavacNode findAnnotation(Class<? extends Annotation> type, JavacNode node, boolean delete) { if (node == null) return null; if (type == null) return null; switch (node.getKind()) { @@ -874,10 +878,6 @@ public class JavacHandlerUtil { return null; } - public enum FieldAccess { - GETTER, PREFER_FIELD, ALWAYS_FIELD; - } - static boolean lookForGetter(JavacNode field, FieldAccess fieldAccess) { if (fieldAccess == FieldAccess.GETTER) return true; if (fieldAccess == FieldAccess.ALWAYS_FIELD) return false; @@ -899,12 +899,14 @@ public class JavacHandlerUtil { * @see #createFieldAccessor(TreeMaker, JavacNode, FieldAccess) */ static JCExpression getFieldType(JavacNode field, FieldAccess fieldAccess) { + if (field.getKind() == Kind.METHOD) return ((JCMethodDecl) field.get()).restype; + boolean lookForGetter = lookForGetter(field, fieldAccess); GetterMethod getter = lookForGetter ? findGetter(field) : null; if (getter == null) { - return ((JCVariableDecl)field.get()).vartype; + return ((JCVariableDecl) field.get()).vartype; } return getter.type; @@ -941,7 +943,28 @@ public class JavacHandlerUtil { if (receiver == null) receiver = maker.Ident(field.toName("this")); JCMethodInvocation call = maker.Apply(List.<JCExpression>nil(), - maker.Select(receiver, getter.name), List.<JCExpression>nil()); + maker.Select(receiver, getter.name), List.<JCExpression>nil()); + return call; + } + + static JCExpression createMethodAccessor(JavacTreeMaker maker, JavacNode method) { + JCExpression receiver; + JCMethodDecl methodDecl = (JCMethodDecl) method.get(); + + if ((methodDecl.mods.flags & Flags.STATIC) == 0) { + receiver = maker.Ident(method.toName("this")); + } else { + JavacNode containerNode = method.up(); + if (containerNode != null && containerNode.get() instanceof JCClassDecl) { + JCClassDecl container = (JCClassDecl) method.up().get(); + receiver = maker.Ident(container.name); + } else { + receiver = null; + } + } + + JCMethodInvocation call = maker.Apply(List.<JCExpression>nil(), + receiver == null ? maker.Ident(methodDecl.name) : maker.Select(receiver, methodDecl.name), List.<JCExpression>nil()); return call; } diff --git a/src/utils/lombok/eclipse/Eclipse.java b/src/utils/lombok/eclipse/Eclipse.java index f2b5486c..5ef33086 100644 --- a/src/utils/lombok/eclipse/Eclipse.java +++ b/src/utils/lombok/eclipse/Eclipse.java @@ -42,6 +42,7 @@ import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; import org.eclipse.jdt.internal.compiler.ast.TryStatement; import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; import org.eclipse.jdt.internal.compiler.ast.TypeReference; +import org.eclipse.jdt.internal.compiler.ast.UnaryExpression; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; import org.eclipse.jdt.internal.compiler.lookup.TypeIds; @@ -184,6 +185,17 @@ public class Eclipse { String qName = Eclipse.toQualifiedName(((QualifiedNameReference)e).tokens); int idx = qName.lastIndexOf('.'); return new FieldSelect(idx == -1 ? qName : qName.substring(idx+1)); + } else if (e instanceof UnaryExpression) { + if ("-".equals(((UnaryExpression) e).operatorToString())) { + Object inner = calculateValue(((UnaryExpression) e).expression); + if (inner instanceof Integer) return - ((Integer) inner).intValue(); + if (inner instanceof Byte) return - ((Byte) inner).byteValue(); + if (inner instanceof Short) return - ((Short) inner).shortValue(); + if (inner instanceof Long) return - ((Long) inner).longValue(); + if (inner instanceof Float) return - ((Float) inner).floatValue(); + if (inner instanceof Double) return - ((Double) inner).doubleValue(); + return null; + } } return null; diff --git a/test/transform/resource/after-delombok/ToStringNewStyle.java b/test/transform/resource/after-delombok/ToStringNewStyle.java new file mode 100644 index 00000000..0a54bd24 --- /dev/null +++ b/test/transform/resource/after-delombok/ToStringNewStyle.java @@ -0,0 +1,18 @@ +public class ToStringNewStyle { + int b; + double c; + int f; + int d; + int f() { + return 0; + } + int g; + int h; + int i; + int j; + @java.lang.Override + @java.lang.SuppressWarnings("all") + public java.lang.String toString() { + return "ToStringNewStyle(a=" + this.b + ", c=" + this.c + ", e=" + this.d + ", f=" + this.f() + ", g=" + this.g + ", i=" + this.i + ", h=" + this.h + ")"; + } +} diff --git a/test/transform/resource/after-ecj/ToStringNewStyle.java b/test/transform/resource/after-ecj/ToStringNewStyle.java new file mode 100644 index 00000000..cdc6f5bc --- /dev/null +++ b/test/transform/resource/after-ecj/ToStringNewStyle.java @@ -0,0 +1,20 @@ +import lombok.ToString; +public @ToString class ToStringNewStyle { + @ToString.Include(name = "a") int b; + double c; + int f; + @ToString.Include(name = "e") int d; + int g; + @ToString.Include(rank = (- 1)) int h; + int i; + @ToString.Exclude int j; + public ToStringNewStyle() { + super(); + } + @ToString.Include int f() { + return 0; + } + public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() { + return (((((((((((((("ToStringNewStyle(a=" + this.b) + ", c=") + this.c) + ", e=") + this.d) + ", f=") + this.f()) + ", g=") + this.g) + ", i=") + this.i) + ", h=") + this.h) + ")"); + } +} diff --git a/test/transform/resource/before/ToStringNewStyle.java b/test/transform/resource/before/ToStringNewStyle.java new file mode 100644 index 00000000..7e436e51 --- /dev/null +++ b/test/transform/resource/before/ToStringNewStyle.java @@ -0,0 +1,15 @@ +import lombok.ToString; +@ToString +public class ToStringNewStyle { + @ToString.Include(name = "a") int b; + double c; + int f; + @ToString.Include(name = "e") int d; + @ToString.Include int f() { + return 0; + } + int g; + @ToString.Include(rank = -1) int h; + int i; + @ToString.Exclude int j; +} diff --git a/website/resources/js/supporters.js b/website/resources/js/supporters.js index 07319226..58db28cc 100644 --- a/website/resources/js/supporters.js +++ b/website/resources/js/supporters.js @@ -26,7 +26,7 @@ function toDate(s) { var x = /^(\d{4})-(\d{2})-(\d{2})$/.exec(s); - if (x) return new Date(parseInt(x[1]), parseInt(x[2]), parseInt(x[3])); + if (x) return new Date(parseInt(x[1]), parseInt(x[2]) - 1, parseInt(x[3])); return null; } |