diff options
Diffstat (limited to 'src/core/lombok')
128 files changed, 6130 insertions, 1756 deletions
diff --git a/src/core/lombok/AllArgsConstructor.java b/src/core/lombok/AllArgsConstructor.java index 3037c9db..c059c65d 100644 --- a/src/core/lombok/AllArgsConstructor.java +++ b/src/core/lombok/AllArgsConstructor.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2013 The Project Lombok Authors. + * Copyright (C) 2010-2017 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 @@ -30,7 +30,7 @@ import java.lang.annotation.Target; * Generates an all-args constructor. * An all-args constructor requires one argument for every field in the class. * <p> - * Complete documentation is found at <a href="https://projectlombok.org/features/Constructor.html">the project lombok features page for @Constructor</a>. + * Complete documentation is found at <a href="https://projectlombok.org/features/Constructor">the project lombok features page for @Constructor</a>. * <p> * Even though it is not listed, this annotation also has the {@code onConstructor} parameter. See the full documentation for more details. * @@ -45,31 +45,33 @@ public @interface AllArgsConstructor { * is generated with the same argument list that wraps the real constructor. * * Such a static 'constructor' is primarily useful as it infers type arguments. + * + * @return Name of static 'constructor' method to generate (blank = generate a normal constructor). */ String staticName() default ""; /** - * Any annotations listed here are put on the generated constructor. The syntax for this feature is: {@code @AllArgsConstructor(onConstructor=@__({@AnnotationsGoHere}))} + * Any annotations listed here are put on the generated constructor. + * The syntax for this feature depends on JDK version (nothing we can do about that; it's to work around javac bugs).<br> + * up to JDK7:<br> + * {@code @AllArgsConstructor(onConstructor=@__({@AnnotationsGoHere}))}<br> + * from JDK8:<br> + * {@code @AllArgsConstructor(onConstructor_={@AnnotationsGohere})} // note the underscore after {@code onConstructor}. + * + * @return List of annotations to apply to the generated constructor. */ AnyAnnotation[] onConstructor() default {}; /** * Sets the access level of the constructor. By default, generated constructors are {@code public}. - */ - AccessLevel access() default lombok.AccessLevel.PUBLIC; - - /** - * Constructors are generated with the {@link java.beans.ConstructorProperties} annotation. - * However, this annotation is new in 1.6 which means those compiling for 1.5 will need - * to set this value to true. * - * @deprecated THIS FEATURE WILL BE REMOVED after March 31st 2015. Use configuration key {@link ConfigurationKeys#ANY_CONSTRUCTOR_SUPPRESS_CONSTRUCTOR_PROPERTIES} instead. + * @return The constructor will be generated with this access modifier. */ - @Deprecated - boolean suppressConstructorProperties() default false; + AccessLevel access() default lombok.AccessLevel.PUBLIC; /** * Placeholder annotation to enable the placement of annotations on the generated code. + * * @deprecated Don't use this annotation, ever - Read the documentation. */ @Deprecated diff --git a/src/core/lombok/Builder.java b/src/core/lombok/Builder.java index 7a965486..d7a2a109 100644 --- a/src/core/lombok/Builder.java +++ b/src/core/lombok/Builder.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2014 The Project Lombok Authors. + * Copyright (C) 2013-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 @@ -48,15 +48,15 @@ import java.lang.annotation.Target; * as the relevant class, unless a method has been annotated, in which case it'll be equal to the * return type of that method. * <p> - * Complete documentation is found at <a href="https://projectlombok.org/features/experimental/Builder.html">the project lombok features page for @Builder</a>. - * <p> + * Complete documentation is found at <a href="https://projectlombok.org/features/Builder">the project lombok features page for @Builder</a>. + * <br> * <p> * Before: * * <pre> * @Builder - * class Example { - * private int foo; + * class Example<T> { + * private T foo; * private final String bar; * } * </pre> @@ -103,14 +103,23 @@ import java.lang.annotation.Target; * } * } * </pre> + * + * @see Singular */ @Target({TYPE, METHOD, CONSTRUCTOR}) @Retention(SOURCE) public @interface Builder { - /** Name of the method that creates a new builder instance. Default: {@code builder}. */ + /** + * The field annotated with {@code @Default} must have an initializing expression; that expression is taken as the default to be used if not explicitly set during building. + */ + @Target(FIELD) + @Retention(SOURCE) + public @interface Default {} + + /** @return Name of the method that creates a new builder instance. Default: {@code builder}. */ String builderMethodName() default "builder"; - /** Name of the method in the builder class that creates an instance of your {@code @Builder}-annotated class. */ + /** @return Name of the method in the builder class that creates an instance of your {@code @Builder}-annotated class. */ String buildMethodName() default "build"; /** @@ -119,6 +128,8 @@ public @interface Builder { * Default for {@code @Builder} on types and constructors: {@code (TypeName)Builder}. * <p> * Default for {@code @Builder} on methods: {@code (ReturnTypeName)Builder}. + * + * @return Name of the builder class that will be generated (or if it already exists, will be filled with builder elements). */ String builderClassName() default ""; @@ -126,6 +137,8 @@ public @interface Builder { * If true, generate an instance method to obtain a builder that is initialized with the values of this instance. * Legal only if {@code @Builder} is used on a constructor, on the type itself, or on a static method that returns * an instance of the declaring type. + * + * @return Whether to generate a {@code toBuilder()} method. */ boolean toBuilder() default false; @@ -142,13 +155,19 @@ public @interface Builder { @Target({FIELD, PARAMETER}) @Retention(SOURCE) public @interface ObtainVia { - /** Tells lombok to obtain a value with the expression {@code this.value}. */ + /** + * @return Tells lombok to obtain a value with the expression {@code this.value}. + */ String field() default ""; - /** Tells lombok to obtain a value with the expression {@code this.method()}. */ + /** + * @return Tells lombok to obtain a value with the expression {@code this.method()}. + */ String method() default ""; - /** Tells lombok to obtain a value with the expression {@code SelfType.method(this)}; requires {@code method} to be set. */ + /** + * @return Tells lombok to obtain a value with the expression {@code SelfType.method(this)}; requires {@code method} to be set. + */ boolean isStatic() default false; } } diff --git a/src/core/lombok/Cleanup.java b/src/core/lombok/Cleanup.java index c95c03c5..a3a1c198 100644 --- a/src/core/lombok/Cleanup.java +++ b/src/core/lombok/Cleanup.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2013 The Project Lombok Authors. + * Copyright (C) 2009-2017 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,7 +31,7 @@ import java.lang.annotation.Target; * of what happens. Implemented by wrapping all statements following the local variable declaration to the * end of your scope into a try block that, as a finally action, closes the resource. * <p> - * Complete documentation is found at <a href="https://projectlombok.org/features/Cleanup.html">the project lombok features page for @Cleanup</a>. + * Complete documentation is found at <a href="https://projectlombok.org/features/Cleanup">the project lombok features page for @Cleanup</a>. * <p> * Example: * <pre> @@ -61,10 +61,10 @@ import java.lang.annotation.Target; * outStream.write(b, 0, r); * } * } finally { - * if (out != null) out.close(); + * if (outStream != null) outStream.close(); * } * } finally { - * if (in != null) in.close(); + * if (inStream != null) inStream.close(); * } * } * </pre> @@ -72,6 +72,6 @@ import java.lang.annotation.Target; @Target(ElementType.LOCAL_VARIABLE) @Retention(RetentionPolicy.SOURCE) public @interface Cleanup { - /** The name of the method that cleans up the resource. By default, 'close'. The method must not have any parameters. */ + /** @return The name of the method that cleans up the resource. By default, 'close'. The method must not have any parameters. */ String value() default "close"; } diff --git a/src/core/lombok/ConfigurationKeys.java b/src/core/lombok/ConfigurationKeys.java index 04decf69..f5134bbd 100644 --- a/src/core/lombok/ConfigurationKeys.java +++ b/src/core/lombok/ConfigurationKeys.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2014 The Project Lombok Authors. + * Copyright (C) 2013-2017 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 @@ -41,9 +41,32 @@ public class ConfigurationKeys { /** * lombok configuration: {@code lombok.addGeneratedAnnotation} = {@code true} | {@code false}. * - * If unset or {@code true}, lombok generates {@code @javax.annotation.Generated("lombok")} on all fields, methods, and types that are generated. + * If {@code true}, lombok generates {@code @javax.annotation.Generated("lombok")} on all fields, methods, and types that are generated, unless {@code lombok.addJavaxGeneratedAnnotation} is set. + * <br> + * <em>BREAKING CHANGE</em>: Starting with lombok v2.0.0, defaults to {@code false} instead of {@code true}, as this annotation is broken in JDK9. + * + * @see ConfigurationKeys#ADD_JAVAX_GENERATED_ANNOTATIONS + * @see ConfigurationKeys#ADD_LOMBOK_GENERATED_ANNOTATIONS + * @deprecated Since version 1.16.14, use {@link #ADD_JAVAX_GENERATED_ANNOTATIONS} instead. + */ + @Deprecated + public static final ConfigurationKey<Boolean> ADD_GENERATED_ANNOTATIONS = new ConfigurationKey<Boolean>("lombok.addGeneratedAnnotation", "Generate @javax.annotation.Generated on all generated code (default: false). Deprecated, use 'lombok.addJavaxGeneratedAnnotation' instead.") {}; + + /** + * lombok configuration: {@code lombok.addJavaxGeneratedAnnotation} = {@code true} | {@code false}. + * + * If {@code true}, lombok generates {@code @javax.annotation.Generated("lombok")} on all fields, methods, and types that are generated. + * <br> + * <em>BREAKING CHANGE</em>: Starting with lombok v2.0.0, defaults to {@code false} instead of {@code true}, as this annotation is broken in JDK9. + */ + public static final ConfigurationKey<Boolean> ADD_JAVAX_GENERATED_ANNOTATIONS = new ConfigurationKey<Boolean>("lombok.addJavaxGeneratedAnnotation", "Generate @javax.annotation.Generated on all generated code (default: follow lombok.addGeneratedAnnotation).") {}; + + /** + * lombok configuration: {@code lombok.addLombokGeneratedAnnotation} = {@code true} | {@code false}. + * + * If {@code true}, lombok generates {@code @lombok.Generated} on all fields, methods, and types that are generated. */ - public static final ConfigurationKey<Boolean> ADD_GENERATED_ANNOTATIONS = new ConfigurationKey<Boolean>("lombok.addGeneratedAnnotation", "Generate @javax.annotation.Generated on all generated code (default: true).") {}; + public static final ConfigurationKey<Boolean> ADD_LOMBOK_GENERATED_ANNOTATIONS = new ConfigurationKey<Boolean>("lombok.addLombokGeneratedAnnotation", "Generate @lombok.Generated on all generated code (default: false).") {}; /** * lombok configuration: {@code lombok.extern.findbugs.addSuppressFBWarnings} = {@code true} | {@code false}. @@ -70,10 +93,25 @@ public class ConfigurationKeys { * To suppress the generation of it, set this configuration to {@code true}. * * NB: GWT projects, and probably android projects, should explicitly set this key to {@code true} for the entire project. + * + * <br> + * <em>BREAKING CHANGE</em>: Starting with lombok v2.0.0, defaults to {@code false} instead of {@code true}, as {@code @ConstructorProperties} requires extra modules in JDK9. + * + * @see ConfigurationKeys#ANY_CONSTRUCTOR_ADD_CONSTRUCTOR_PROPERTIES + * @deprecated Since version 2.0, use {@link #ANY_CONSTRUCTOR_ADD_CONSTRUCTOR_PROPERTIES} instead. */ + @Deprecated public static final ConfigurationKey<Boolean> ANY_CONSTRUCTOR_SUPPRESS_CONSTRUCTOR_PROPERTIES = new ConfigurationKey<Boolean>("lombok.anyConstructor.suppressConstructorProperties", "Suppress the generation of @ConstructorProperties for generated constructors (default: false).") {}; /** + * lombok configuration: {@code lombok.anyConstructor.addConstructorProperties} = {@code true} | {@code false}. + * + * If {@code true}, all generated constructors with at least 1 argument get a {@code @ConstructorProperties}. + * + */ + public static final ConfigurationKey<Boolean> ANY_CONSTRUCTOR_ADD_CONSTRUCTOR_PROPERTIES = new ConfigurationKey<Boolean>("lombok.anyConstructor.addConstructorProperties", "Generate @ConstructorProperties for generated constructors (default: false).") {}; + + /** * lombok configuration: {@code lombok.allArgsConstructor.flagUsage} = {@code WARNING} | {@code ERROR}. * * If set, <em>any</em> usage of {@code @AllArgsConstructor} results in a warning / error. @@ -88,6 +126,13 @@ public class ConfigurationKeys { public static final ConfigurationKey<FlagUsageType> NO_ARGS_CONSTRUCTOR_FLAG_USAGE = new ConfigurationKey<FlagUsageType>("lombok.noArgsConstructor.flagUsage", "Emit a warning or error if @NoArgsConstructor is used.") {}; /** + * lombok configuration: {@code lombok.noArgsConstructor.extraPrivate} = {@code true} | {@code false}. + * + * If {@code true} (default), @Data and @Value will also generate a private no-args constructor, if there isn't already one, setting all fields to their default values. + */ + public static final ConfigurationKey<Boolean> NO_ARGS_CONSTRUCTOR_EXTRA_PRIVATE = new ConfigurationKey<Boolean>("lombok.noArgsConstructor.extraPrivate", "Generate a private no-ars constructor for @Data and @Value (default: true).") {}; + + /** * lombok configuration: {@code lombok.requiredArgsConstructor.flagUsage} = {@code WARNING} | {@code ERROR}. * * If set, <em>any</em> usage of {@code @RequiredArgsConstructor} results in a warning / error. @@ -242,7 +287,7 @@ public class ConfigurationKeys { // ----- NonNull ----- /** - * lombok configuration: {@code lombok.nonNull.exceptionType} = <String: <em>a java exception type; either [{@code IllegalArgumentException} or: {@code NullPointerException}]. + * lombok configuration: {@code lombok.nonNull.exceptionType} = <String: <em>a java exception type</em>; either [{@code IllegalArgumentException} or: {@code NullPointerException}]. * * Sets the exception to throw if {@code @NonNull} is applied to a method parameter, and a caller passes in {@code null}. */ @@ -283,7 +328,8 @@ public class ConfigurationKeys { * If set, <em>any</em> usage of {@code val} results in a warning / error. */ public static final ConfigurationKey<FlagUsageType> VAL_FLAG_USAGE = new ConfigurationKey<FlagUsageType>("lombok.val.flagUsage", "Emit a warning or error if 'val' is used.") {}; - + public static final ConfigurationKey<FlagUsageType> VAR_FLAG_USAGE = new ConfigurationKey<FlagUsageType>("lombok.var.flagUsage", "Emit a warning or error if 'var' is used.") {}; + // ##### Extern ##### // ----- Logging ----- @@ -344,6 +390,13 @@ public class ConfigurationKeys { public static final ConfigurationKey<FlagUsageType> LOG_JBOSSLOG_FLAG_USAGE = new ConfigurationKey<FlagUsageType>("lombok.log.jbosslog.flagUsage", "Emit a warning or error if @JBossLog is used.") {}; /** + * lombok configuration: {@code lombok.log.flogger.flagUsage} = {@code WARNING} | {@code ERROR}. + * + * If set, <em>any</em> usage of {@code @Flogger} results in a warning / error. + */ + public static final ConfigurationKey<FlagUsageType> LOG_FLOGGER_FLAG_USAGE = new ConfigurationKey<FlagUsageType>("lombok.log.flogger.flagUsage", "Emit a warning or error if @Flogger is used.") {}; + + /** * lombok configuration: {@code lombok.log.fieldName} = <String: aJavaIdentifier> (Default: {@code log}). * * If set the various log annotations (which make a log field) will use the stated identifier instead of {@code log} as a name. @@ -440,6 +493,17 @@ public class ConfigurationKeys { */ public static final ConfigurationKey<FlagUsageType> HELPER_FLAG_USAGE = new ConfigurationKey<FlagUsageType>("lombok.helper.flagUsage", "Emit a warning or error if @Helper is used.") {}; + // ----- onX ----- + + /** + * lombok configuration: {@code lombok.onX.flagUsage} = {@code WARNING} | {@code ERROR}. + * + * If set, <em>any</em> usage of {@code onX} results in a warning / error. + * <br> + * Specifically, this flags usage of {@code @Getter(onMethod=...)}, {@code @Setter(onParam=...)}, {@code @Setter(onMethod=...)}, {@code @XArgsConstructor(onConstructor=...)}. + */ + public static final ConfigurationKey<FlagUsageType> ON_X_FLAG_USAGE = new ConfigurationKey<FlagUsageType>("lombok.onX.flagUsage", "Emit a warning or error if onX is used.") {}; + // ----- UtilityClass ----- /** @@ -449,6 +513,28 @@ public class ConfigurationKeys { */ public static final ConfigurationKey<FlagUsageType> UTILITY_CLASS_FLAG_USAGE = new ConfigurationKey<FlagUsageType>("lombok.utilityClass.flagUsage", "Emit a warning or error if @UtilityClass is used.") {}; + // ----- FieldNameConstants ----- + /** + * lombok configuration: {@code lombok.fieldNameConstants.flagUsage} = {@code WARNING} | {@code ERROR}. + * + * If set, <em>any</em> usage of {@code @FieldNameConstants} results in a warning / error. + */ + public static final ConfigurationKey<FlagUsageType> FIELD_NAME_CONSTANTS_FLAG_USAGE = new ConfigurationKey<FlagUsageType>("lombok.fieldNameConstants.flagUsage", "Emit a warning or error if @FieldNameConstants is used.") {}; + + /** + * lombok configuration: {@code lombok.fieldNameConstants.prefix} = <String: aJavaIdentifierPrefix> (Default: {@code PREFIX_}). + * + * The names of the constants generated by {@code @FieldNameConstants} will be prefixed with this value. + */ + public static final ConfigurationKey<String> FIELD_NAME_CONSTANTS_PREFIX = new ConfigurationKey<String>("lombok.fieldNameConstants.prefix", "names of constants generated by @FieldNameConstants will be prefixed with this value. (default: 'PREFIX_').") {}; + + /** + * lombok configuration: {@code lombok.fieldNameConstants.suffix} = <String: aJavaIdentifierPrefix> (Default: nothing). + * + * The names of the constants generated by {@code @FieldNameConstants} will be suffixed with this value. + */ + public static final ConfigurationKey<String> FIELD_NAME_CONSTANTS_SUFFIX = new ConfigurationKey<String>("lombok.fieldNameConstants.suffix", "names of constants generated by @FieldNameConstants will be suffixed with this value. (default: nothing).") {}; + // ----- Wither ----- /** @@ -458,6 +544,15 @@ public class ConfigurationKeys { */ public static final ConfigurationKey<FlagUsageType> WITHER_FLAG_USAGE = new ConfigurationKey<FlagUsageType>("lombok.wither.flagUsage", "Emit a warning or error if @Wither is used.") {}; + // ----- SuperBuilder ----- + + /** + * lombok configuration: {@code lombok.superBuilder.flagUsage} = {@code WARNING} | {@code ERROR}. + * + * If set, <em>any</em> usage of {@code @SuperBuilder} results in a warning / error. + */ + public static final ConfigurationKey<FlagUsageType> SUPERBUILDER_FLAG_USAGE = new ConfigurationKey<FlagUsageType>("lombok.superBuilder.flagUsage", "Emit a warning or error if @SuperBuilder is used.") {}; + // ----- Configuration System ----- /** diff --git a/src/core/lombok/Data.java b/src/core/lombok/Data.java index 4c6c2e5d..ffa968c1 100644 --- a/src/core/lombok/Data.java +++ b/src/core/lombok/Data.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2013 The Project Lombok Authors. + * Copyright (C) 2009-2017 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 @@ -32,7 +32,7 @@ import java.lang.annotation.Target; * <p> * Equivalent to {@code @Getter @Setter @RequiredArgsConstructor @ToString @EqualsAndHashCode}. * <p> - * Complete documentation is found at <a href="https://projectlombok.org/features/Data.html">the project lombok features page for @Data</a>. + * Complete documentation is found at <a href="https://projectlombok.org/features/Data">the project lombok features page for @Data</a>. * * @see Getter * @see Setter @@ -54,6 +54,8 @@ public @interface Data { * </pre> * * Default: No static constructor, instead the normal constructor is public. + * + * @return Name of static 'constructor' method to generate (blank = generate a normal constructor). */ String staticConstructor() default ""; } diff --git a/src/core/lombok/Delegate.java b/src/core/lombok/Delegate.java index d5b4b48c..0c5a4c2c 100644 --- a/src/core/lombok/Delegate.java +++ b/src/core/lombok/Delegate.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2014 The Project Lombok Authors. + * Copyright (C) 2010-2017 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 @@ -40,6 +40,8 @@ public @interface Delegate { * type listed here is used only to determine which delegate methods to generate. * * NB: All methods in {@code Object}, as well as {@code canEqual(Object other)} will never be delegated. + * + * @return For each method (not already in {@code java.lang.Object}) in these types, generate a delegate method. */ Class<?>[] types() default {}; @@ -47,6 +49,8 @@ public @interface Delegate { * Each method in any of the types listed here (include supertypes) will <em>not</em> be delegated. * * NB: All methods in {@code Object}, as well as {@code canEqual(Object other)} will never be delegated. + * + * @return For each method (not already in {@code java.lang.Object}) in these types, skip generating a delegate method (overrides {@code types()}). */ Class<?>[] excludes() default {}; } diff --git a/src/core/lombok/EqualsAndHashCode.java b/src/core/lombok/EqualsAndHashCode.java index 605815ed..b2fc672d 100644 --- a/src/core/lombok/EqualsAndHashCode.java +++ b/src/core/lombok/EqualsAndHashCode.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 @@ -29,15 +29,18 @@ import java.lang.annotation.Target; /** * Generates implementations for the {@code equals} and {@code hashCode} methods inherited by all objects, based on relevant fields. * <p> - * Complete documentation is found at <a href="https://projectlombok.org/features/EqualsAndHashCode.html">the project lombok features page for @EqualsAndHashCode</a>. + * Complete documentation is found at <a href="https://projectlombok.org/features/EqualsAndHashCode">the project lombok features page for @EqualsAndHashCode</a>. */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.SOURCE) public @interface EqualsAndHashCode { /** - * Any fields listed here will not be taken into account in the generated - * {@code equals} and {@code hashCode} implementations. + * Any fields listed here will not be taken into account in the generated {@code equals} and {@code hashCode} implementations. * Mutually exclusive with {@link #of()}. + * <p> + * Will soon be marked {@code @Deprecated}; use the {@code @EqualsAndHashCode.Exclude} annotation instead. + * + * @return A list of fields to exclude. */ String[] exclude() default {}; @@ -46,25 +49,39 @@ public @interface EqualsAndHashCode { * Normally, all non-static, non-transient fields are used for identity. * <p> * Mutually exclusive with {@link #exclude()}. + * <p> + * Will soon be marked {@code @Deprecated}; use the {@code @EqualsAndHashCode.Include} annotation together with {@code @EqualsAndHashCode(onlyExplicitlyIncluded = true)}. + * + * @return A list of fields to use (<em>default</em>: all of them). */ String[] of() default {}; /** - * Call on the superclass's implementations of {@code equals} and {@code hashCode} before calculating - * for the fields in this class. + * Call on the superclass's implementations of {@code equals} and {@code hashCode} before calculating for the fields in this class. * <strong>default: false</strong> + * + * @return Whether to call the superclass's {@code equals} implementation as part of the generated equals algorithm. */ boolean callSuper() default false; /** * Normally, if getters are available, those are called. To suppress this and let the generated code use the fields directly, set this to {@code true}. * <strong>default: false</strong> + * + * @return If {@code true}, always use direct field access instead of calling the getter method. */ boolean doNotUseGetters() default false; /** - * Any annotations listed here are put on the generated parameter of {@code equals} and {@code canEqual}. The syntax for this feature is: {@code @EqualsAndHashCode(onParam=@__({@AnnotationsGoHere}))} - * This is useful to add for example a {@code Nullable} annotation. + * Any annotations listed here are put on the generated parameter of {@code equals} and {@code canEqual}. + * This is useful to add for example a {@code Nullable} annotation.<br> + * The syntax for this feature depends on JDK version (nothing we can do about that; it's to work around javac bugs).<br> + * up to JDK7:<br> + * {@code @EqualsAndHashCode(onParam=@__({@AnnotationsGoHere}))}<br> + * from JDK8:<br> + * {@code @EqualsAndHashCode(onParam_={@AnnotationsGohere})} // note the underscore after {@code onParam}. + * + * @return List of annotations to apply to the generated parameter in the {@code equals()} method. */ AnyAnnotation[] onParam() default {}; @@ -76,4 +93,27 @@ public @interface EqualsAndHashCode { @Retention(RetentionPolicy.SOURCE) @Target({}) @interface AnyAnnotation {} + + /** + * Only include fields and methods explicitly marked with {@code @EqualsAndHashCode.Include}. + * Normally, all (non-static, non-transient) fields are included by default. + */ + boolean onlyExplicitlyIncluded() default false; + + /** + * If present, do not include this field in the generated {@code equals} and {@code hashCode} methods. + */ + @Target(ElementType.FIELD) + @Retention(RetentionPolicy.SOURCE) + public @interface Exclude {} + + /** + * Configure the behaviour of how this member is treated in the {@code equals} and {@code hashCode} implementation; if on a method, include the method's return value as part of calculating hashCode/equality. + */ + @Target({ElementType.FIELD, ElementType.METHOD}) + @Retention(RetentionPolicy.SOURCE) + public @interface Include { + /** Defaults to the method name of the annotated member. If on a method and the name equals the name of a default-included field, this member takes its place. */ + String replaces() default ""; + } } diff --git a/src/core/lombok/Getter.java b/src/core/lombok/Getter.java index 906ae60f..5a23fe30 100644 --- a/src/core/lombok/Getter.java +++ b/src/core/lombok/Getter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2013 The Project Lombok Authors. + * Copyright (C) 2009-2017 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 @@ -29,7 +29,7 @@ import java.lang.annotation.Target; /** * Put on any field to make lombok build a standard getter. * <p> - * Complete documentation is found at <a href="https://projectlombok.org/features/GetterSetter.html">the project lombok features page for @Getter and @Setter</a>. + * Complete documentation is found at <a href="https://projectlombok.org/features/GetterSetter">the project lombok features page for @Getter and @Setter</a>. * <p> * Even though it is not listed, this annotation also has the {@code onMethod} parameter. See the full documentation for more details. * <p> @@ -54,20 +54,29 @@ import java.lang.annotation.Target; public @interface Getter { /** * If you want your getter to be non-public, you can specify an alternate access level here. + * + * @return The getter method will be generated with this access modifier. */ lombok.AccessLevel value() default lombok.AccessLevel.PUBLIC; /** - * Any annotations listed here are put on the generated method. The syntax for this feature is: {@code @Getter(onMethod=@__({@AnnotationsGoHere}))} + * Any annotations listed here are put on the generated method. + * The syntax for this feature depends on JDK version (nothing we can do about that; it's to work around javac bugs).<br> + * up to JDK7:<br> + * {@code @Getter(onMethod=@__({@AnnotationsGoHere}))}<br> + * from JDK8:<br> + * {@code @Getter(onMethod_={@AnnotationsGohere})} // note the underscore after {@code onMethod}. + * + * @return List of annotations to apply to the generated getter method. */ - AnyAnnotation[] onMethod() default @AnyAnnotation; + AnyAnnotation[] onMethod() default {}; boolean lazy() default false; /** - * Placeholder annotation to enable the placement of annotations on the generated code. - * @deprecated Don't use this annotation, ever - Read the documentation. - */ + * Placeholder annotation to enable the placement of annotations on the generated code. + * @deprecated Don't use this annotation, ever - Read the documentation. + */ @Deprecated @Retention(RetentionPolicy.SOURCE) @Target({}) diff --git a/src/core/lombok/Lombok.java b/src/core/lombok/Lombok.java index 07fd083d..d86b6d1c 100644 --- a/src/core/lombok/Lombok.java +++ b/src/core/lombok/Lombok.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009 The Project Lombok Authors. + * Copyright (C) 2009-2017 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 @@ -30,7 +30,6 @@ public class Lombok { * The exception is still thrown - javac will just stop whining about it. * <p> * Example usage: - * <p> * <pre>public void run() { * throw sneakyThrow(new IOException("You don't need to catch me!")); * }</pre> @@ -49,26 +48,38 @@ public class Lombok { */ public static RuntimeException sneakyThrow(Throwable t) { if (t == null) throw new NullPointerException("t"); - Lombok.<RuntimeException>sneakyThrow0(t); - return null; + return Lombok.<RuntimeException>sneakyThrow0(t); } @SuppressWarnings("unchecked") - private static <T extends Throwable> void sneakyThrow0(Throwable t) throws T { + private static <T extends Throwable> T sneakyThrow0(Throwable t) throws T { throw (T)t; } /** - * Returns the parameter directly. <br /> + * Returns the parameter directly. * - * This method can be used to prevent a static analyzer to determine - * the nullness of the passed parameter. + * This method can be used to prevent a static analyzer to determine the nullness of the passed parameter. * - * @param <T> the type of the parameter - * @param value the value to return - * @return value + * @param <T> the type of the parameter. + * @param value the value to return. + * @return value (this method just returns the parameter). */ public static <T> T preventNullAnalysis(T value) { return value; } + + /** + * Ensures that the {@code value} is not {@code null}. + * + * @param <T> Type of the parameter. + * @param value the value to test for null. + * @param message the message of the {@link NullPointerException}. + * @return the value if it is not null. + * @throws NullPointerException with the {@code message} if the value is null. + */ + public static <T> T checkNotNull(T value, String message) { + if (value == null) throw new NullPointerException(message); + return value; + } } diff --git a/src/core/lombok/NoArgsConstructor.java b/src/core/lombok/NoArgsConstructor.java index ff437bba..672cd1c2 100644 --- a/src/core/lombok/NoArgsConstructor.java +++ b/src/core/lombok/NoArgsConstructor.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2015 The Project Lombok Authors. + * Copyright (C) 2010-2017 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 @@ -30,7 +30,7 @@ import java.lang.annotation.Target; * Generates a no-args constructor. * Will generate an error message if such a constructor cannot be written due to the existence of final fields. * <p> - * Complete documentation is found at <a href="https://projectlombok.org/features/Constructor.html">the project lombok features page for @Constructor</a>. + * Complete documentation is found at <a href="https://projectlombok.org/features/Constructor">the project lombok features page for @Constructor</a>. * <p> * Even though it is not listed, this annotation also has the {@code onConstructor} parameter. See the full documentation for more details. * <p> @@ -47,22 +47,35 @@ public @interface NoArgsConstructor { * is generated with the same argument list that wraps the real constructor. * * Such a static 'constructor' is primarily useful as it infers type arguments. + * + * @return Name of static 'constructor' method to generate (blank = generate a normal constructor). */ String staticName() default ""; /** - * Any annotations listed here are put on the generated constructor. The syntax for this feature is: {@code @NoArgsConstructor(onConstructor=@__({@AnnotationsGoHere}))} + * Any annotations listed here are put on the generated constructor. + * The syntax for this feature depends on JDK version (nothing we can do about that; it's to work around javac bugs).<br> + * up to JDK7:<br> + * {@code @NoArgsConstructor(onConstructor=@__({@AnnotationsGoHere}))}<br> + * from JDK8:<br> + * {@code @NoArgsConstructor(onConstructor_={@AnnotationsGohere})} // note the underscore after {@code onConstructor}. + * + * @return List of annotations to apply to the generated constructor. */ AnyAnnotation[] onConstructor() default {}; /** * Sets the access level of the constructor. By default, generated constructors are {@code public}. + * + * @return The constructor will be generated with this access modifier. */ AccessLevel access() default lombok.AccessLevel.PUBLIC; /** * If {@code true}, initializes all final fields to 0 / null / false. * Otherwise, a compile time error occurs. + * + * @return Return {@code} true to force generation of a no-args constructor, picking defaults if necessary to assign required fields. */ boolean force() default false; diff --git a/src/core/lombok/RequiredArgsConstructor.java b/src/core/lombok/RequiredArgsConstructor.java index 7a9da3f9..f21bd647 100644 --- a/src/core/lombok/RequiredArgsConstructor.java +++ b/src/core/lombok/RequiredArgsConstructor.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2013 The Project Lombok Authors. + * Copyright (C) 2010-2017 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 @@ -30,7 +30,7 @@ import java.lang.annotation.Target; * Generates a constructor with required arguments. * Required arguments are final fields and fields with constraints such as {@code @NonNull}. * <p> - * Complete documentation is found at <a href="https://projectlombok.org/features/Constructor.html">the project lombok features page for @Constructor</a>. + * Complete documentation is found at <a href="https://projectlombok.org/features/Constructor">the project lombok features page for @Constructor</a>. * <p> * Even though it is not listed, this annotation also has the {@code onConstructor} parameter. See the full documentation for more details. * @@ -45,28 +45,29 @@ public @interface RequiredArgsConstructor { * is generated with the same argument list that wraps the real constructor. * * Such a static 'constructor' is primarily useful as it infers type arguments. + * + * @return Name of static 'constructor' method to generate (blank = generate a normal constructor). */ String staticName() default ""; /** - * Any annotations listed here are put on the generated constructor. The syntax for this feature is: {@code @RequiredArgsConstructor(onConstructor=@__({@AnnotationsGoHere}))} + * Any annotations listed here are put on the generated constructor. + * The syntax for this feature depends on JDK version (nothing we can do about that; it's to work around javac bugs).<br> + * up to JDK7:<br> + * {@code @RequiredArgsConstructor(onConstructor=@__({@AnnotationsGoHere}))}<br> + * from JDK8:<br> + * {@code @RequiredArgsConstructor(onConstructor_={@AnnotationsGohere})} // note the underscore after {@code onConstructor}. + * + * @return List of annotations to apply to the generated constructor. */ AnyAnnotation[] onConstructor() default {}; /** * Sets the access level of the constructor. By default, generated constructors are {@code public}. - */ - AccessLevel access() default lombok.AccessLevel.PUBLIC; - - /** - * Constructors are generated with the {@link java.beans.ConstructorProperties} annotation. - * However, this annotation is new in 1.6 which means those compiling for 1.5 will need - * to set this value to true. * - * @deprecated THIS FEATURE WILL BE REMOVED after March 31st 2015. Use configuration key {@link ConfigurationKeys#ANY_CONSTRUCTOR_SUPPRESS_CONSTRUCTOR_PROPERTIES} instead. + * @return The constructor will be generated with this access modifier. */ - @Deprecated - boolean suppressConstructorProperties() default false; + AccessLevel access() default lombok.AccessLevel.PUBLIC; /** * Placeholder annotation to enable the placement of annotations on the generated code. diff --git a/src/core/lombok/Setter.java b/src/core/lombok/Setter.java index ba9e9759..1b70bac9 100644 --- a/src/core/lombok/Setter.java +++ b/src/core/lombok/Setter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2013 The Project Lombok Authors. + * Copyright (C) 2009-2017 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 @@ -29,7 +29,7 @@ import java.lang.annotation.Target; /** * Put on any field to make lombok build a standard setter. * <p> - * Complete documentation is found at <a href="https://projectlombok.org/features/GetterSetter.html">the project lombok features page for @Getter and @Setter</a>. + * Complete documentation is found at <a href="https://projectlombok.org/features/GetterSetter">the project lombok features page for @Getter and @Setter</a>. * <p> * Even though it is not listed, this annotation also has the {@code onParam} and {@code onMethod} parameter. See the full documentation for more details. * <p> @@ -55,16 +55,32 @@ import java.lang.annotation.Target; public @interface Setter { /** * If you want your setter to be non-public, you can specify an alternate access level here. + * + * @return The setter method will be generated with this access modifier. */ lombok.AccessLevel value() default lombok.AccessLevel.PUBLIC; /** - * Any annotations listed here are put on the generated method. The syntax for this feature is: {@code @Setter(onMethod=@__({@AnnotationsGoHere}))} + * Any annotations listed here are put on the generated method. + * The syntax for this feature depends on JDK version (nothing we can do about that; it's to work around javac bugs).<br> + * up to JDK7:<br> + * {@code @Setter(onMethod=@__({@AnnotationsGoHere}))}<br> + * from JDK8:<br> + * {@code @Setter(onMethod_={@AnnotationsGohere})} // note the underscore after {@code onMethod}. + * + * @return List of annotations to apply to the generated setter method. */ AnyAnnotation[] onMethod() default {}; /** - * Any annotations listed here are put on the generated method's parameter. The syntax for this feature is: {@code @Setter(onParam=@__({@AnnotationsGoHere}))} + * Any annotations listed here are put on the generated method's parameter. + * The syntax for this feature depends on JDK version (nothing we can do about that; it's to work around javac bugs).<br> + * up to JDK7:<br> + * {@code @Setter(onParam=@__({@AnnotationsGoHere}))}<br> + * from JDK8:<br> + * {@code @Setter(onParam_={@AnnotationsGohere})} // note the underscore after {@code onParam}. + * + * @return List of annotations to apply to the generated parameter in the setter method. */ AnyAnnotation[] onParam() default {}; diff --git a/src/core/lombok/Singular.java b/src/core/lombok/Singular.java index 15dec4a5..67edab96 100644 --- a/src/core/lombok/Singular.java +++ b/src/core/lombok/Singular.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 The Project Lombok Authors. + * Copyright (C) 2015-2017 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 @@ -29,10 +29,10 @@ import java.lang.annotation.Target; /** * The singular annotation is used together with {@code @Builder} to create single element 'add' methods in the builder for collections. - * <p> */ @Target({FIELD, PARAMETER}) @Retention(SOURCE) public @interface Singular { + /** @return The singular name of this field. If it's a normal english plural, lombok will figure it out automatically. Otherwise, this parameter is mandatory. */ String value() default ""; } diff --git a/src/core/lombok/SneakyThrows.java b/src/core/lombok/SneakyThrows.java index 489e13e4..0506d615 100644 --- a/src/core/lombok/SneakyThrows.java +++ b/src/core/lombok/SneakyThrows.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2013 The Project Lombok Authors. + * Copyright (C) 2009-2017 The Project Lombok Authors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -34,7 +34,7 @@ import java.lang.annotation.Target; * checked exception types. The JVM does not check for the consistency of the checked exception system; javac does, * and this annotation lets you opt out of its mechanism. * <p> - * Complete documentation is found at <a href="https://projectlombok.org/features/SneakyThrows.html">the project lombok features page for @SneakyThrows</a>. + * Complete documentation is found at <a href="https://projectlombok.org/features/SneakyThrows">the project lombok features page for @SneakyThrows</a>. * <p> * Example: * <pre> @@ -59,8 +59,9 @@ import java.lang.annotation.Target; @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) @Retention(RetentionPolicy.SOURCE) public @interface SneakyThrows { - /** The exception type(s) you want to sneakily throw onward. */ + /** @return The exception type(s) you want to sneakily throw onward. */ Class<? extends Throwable>[] value() default java.lang.Throwable.class; - //The package is mentioned in java.lang due to a bug in javac (presence of an annotation processor throws off the type resolver for some reason). + //The fully qualified name is used for java.lang.Throwable in the parameter only. This works around a bug in javac: + // presence of an annotation processor throws off the type resolver for some reason. } diff --git a/src/core/lombok/Synchronized.java b/src/core/lombok/Synchronized.java index 9a39c212..5dff0fb2 100644 --- a/src/core/lombok/Synchronized.java +++ b/src/core/lombok/Synchronized.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2013 The Project Lombok Authors. + * Copyright (C) 2009-2017 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 @@ -35,7 +35,7 @@ import java.lang.annotation.Target; * {@code $LOCK} is used. These will be generated if needed and if they aren't already present. The contents * of the fields will be serializable. * <p> - * Complete documentation is found at <a href="https://projectlombok.org/features/Synchronized.html">the project lombok features page for @Synchronized</a>. + * Complete documentation is found at <a href="https://projectlombok.org/features/Synchronized">the project lombok features page for @Synchronized</a>. */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) @@ -43,6 +43,8 @@ public @interface Synchronized { /** * Optional: specify the name of a different field to lock on. It is a compile time error if this field * doesn't already exist (the fields are automatically generated only if you don't specify a specific name. + * + * @return Name of the field to lock on (blank = generate one). */ String value() default ""; } diff --git a/src/core/lombok/ToString.java b/src/core/lombok/ToString.java index b9926148..ae3f071f 100644 --- a/src/core/lombok/ToString.java +++ b/src/core/lombok/ToString.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2013 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 @@ -29,7 +29,7 @@ import java.lang.annotation.Target; /** * Generates an implementation for the {@code toString} method inherited by all objects, consisting of printing the values of relevant fields. * <p> - * Complete documentation is found at <a href="https://projectlombok.org/features/ToString.html">the project lombok features page for @ToString</a>. + * Complete documentation is found at <a href="https://projectlombok.org/features/ToString">the project lombok features page for @ToString</a>. */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.SOURCE) @@ -37,12 +37,18 @@ public @interface ToString { /** * Include the name of each field when printing it. * <strong>default: true</strong> + * + * @return Whether or not to include the names of fields in the string produced by the generated {@code toString()}. */ boolean includeFieldNames() default true; /** * 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. */ String[] exclude() default {}; @@ -51,18 +57,55 @@ 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.Include} annotation together with {@code @ToString(onlyExplicitlyIncluded = true)}. + * + * @return A list of fields to use (<em>default</em>: all of them). */ String[] of() default {}; /** * Include the result of the superclass's implementation of {@code toString} in the output. * <strong>default: false</strong> + * + * @return Whether to call the superclass's {@code toString} implementation as part of the generated toString algorithm. */ boolean callSuper() default false; /** * Normally, if getters are available, those are called. To suppress this and let the generated code use the fields directly, set this to {@code true}. * <strong>default: false</strong> + * + * @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/Value.java b/src/core/lombok/Value.java index 39154226..562ba0cc 100644 --- a/src/core/lombok/Value.java +++ b/src/core/lombok/Value.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2013 The Project Lombok Authors. + * Copyright (C) 2012-2017 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,7 +31,7 @@ import java.lang.annotation.Target; * <p> * Equivalent to {@code @Getter @FieldDefaults(makeFinal=true, level=AccessLevel.PRIVATE) @AllArgsConstructor @ToString @EqualsAndHashCode}. * <p> - * Complete documentation is found at <a href="https://projectlombok.org/features/Value.html">the project lombok features page for @Value</a>. + * Complete documentation is found at <a href="https://projectlombok.org/features/Value">the project lombok features page for @Value</a>. * * @see lombok.Getter * @see lombok.experimental.FieldDefaults @@ -53,6 +53,8 @@ public @interface Value { * </pre> * * Default: No static constructor, instead the normal constructor is public. + * + * @return Name of static 'constructor' method to generate (blank = generate a normal constructor). */ String staticConstructor() default ""; } diff --git a/src/core/lombok/bytecode/AsmUtil.java b/src/core/lombok/bytecode/AsmUtil.java index 26e5af1f..e3d33efa 100644 --- a/src/core/lombok/bytecode/AsmUtil.java +++ b/src/core/lombok/bytecode/AsmUtil.java @@ -37,7 +37,7 @@ class AsmUtil { ClassReader reader = new ClassReader(byteCode); ClassWriter writer = new FixedClassWriter(reader, 0); - ClassVisitor visitor = new ClassVisitor(Opcodes.ASM5, writer) { + ClassVisitor visitor = new ClassVisitor(Opcodes.ASM6, writer) { @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { return new JSRInlinerAdapter(super.visitMethod(access, name, desc, signature, exceptions), access, name, desc, signature, exceptions); } diff --git a/src/core/lombok/bytecode/ClassFileMetaData.java b/src/core/lombok/bytecode/ClassFileMetaData.java index 68b8bb7d..0dc6a6c8 100644 --- a/src/core/lombok/bytecode/ClassFileMetaData.java +++ b/src/core/lombok/bytecode/ClassFileMetaData.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 The Project Lombok Authors. + * Copyright (C) 2010-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 @@ -44,6 +44,9 @@ public class ClassFileMetaData { private static final byte METHOD_HANDLE = 15; private static final byte METHOD_TYPE = 16; private static final byte INVOKE_DYNAMIC = 18; + // New in java9: support for modules + private static final byte MODULE = 19; + private static final byte PACKAGE = 20; private static final int NOT_FOUND = -1; private static final int START_OF_CONSTANT_POOL = 8; @@ -79,6 +82,8 @@ public class ClassFileMetaData { case CLASS: case STRING: case METHOD_TYPE: + case MODULE: + case PACKAGE: position += 2; break; case METHOD_HANDLE: diff --git a/src/core/lombok/bytecode/PreventNullAnalysisRemover.java b/src/core/lombok/bytecode/PreventNullAnalysisRemover.java index 5f2f5f18..c06f2d7c 100644 --- a/src/core/lombok/bytecode/PreventNullAnalysisRemover.java +++ b/src/core/lombok/bytecode/PreventNullAnalysisRemover.java @@ -50,7 +50,7 @@ public class PreventNullAnalysisRemover implements PostCompilerTransformation { class PreventNullAnalysisVisitor extends MethodVisitor { PreventNullAnalysisVisitor(MethodVisitor mv) { - super(Opcodes.ASM5, mv); + super(Opcodes.ASM6, mv); } @Override public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { @@ -68,7 +68,7 @@ public class PreventNullAnalysisRemover implements PostCompilerTransformation { } } - reader.accept(new ClassVisitor(Opcodes.ASM5, writer) { + reader.accept(new ClassVisitor(Opcodes.ASM6, writer) { @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { return new PreventNullAnalysisVisitor(super.visitMethod(access, name, desc, signature, exceptions)); } diff --git a/src/core/lombok/bytecode/SneakyThrowsRemover.java b/src/core/lombok/bytecode/SneakyThrowsRemover.java index 8ef64e59..a2ee59ea 100644 --- a/src/core/lombok/bytecode/SneakyThrowsRemover.java +++ b/src/core/lombok/bytecode/SneakyThrowsRemover.java @@ -47,12 +47,12 @@ public class SneakyThrowsRemover implements PostCompilerTransformation { ClassReader reader = new ClassReader(fixedByteCode); ClassWriter writer = new ClassWriter(reader, 0); - + final AtomicBoolean changesMade = new AtomicBoolean(); class SneakyThrowsRemoverVisitor extends MethodVisitor { SneakyThrowsRemoverVisitor(MethodVisitor mv) { - super(Opcodes.ASM5, mv); + super(Opcodes.ASM6, mv); } private boolean methodInsnQueued = false; diff --git a/src/core/lombok/core/AST.java b/src/core/lombok/core/AST.java index e6efe058..1142018f 100644 --- a/src/core/lombok/core/AST.java +++ b/src/core/lombok/core/AST.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2013 The Project Lombok Authors. + * Copyright (C) 2009-2016 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 @@ -21,7 +21,7 @@ */ package lombok.core; -import static lombok.Lombok.*; +import static lombok.Lombok.sneakyThrow; import java.lang.reflect.Array; import java.lang.reflect.Field; @@ -31,10 +31,11 @@ import java.lang.reflect.Type; import java.net.URI; import java.util.ArrayList; import java.util.Collection; -import java.util.HashMap; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import lombok.core.configuration.ConfigurationKey; import lombok.core.debug.HistogramTracker; @@ -61,12 +62,18 @@ public abstract class AST<A extends AST<A, L, N>, L extends LombokNode<A, L, N>, Map<N, N> identityDetector = new IdentityHashMap<N, N>(); private Map<N, L> nodeMap = new IdentityHashMap<N, L>(); private boolean changed = false; + + // The supertypes which are considered AST Node children. Usually, the Statement, and the Expression, + // though some platforms (such as Eclipse) group these under one common supertype. + private final Collection<Class<? extends N>> statementTypes; + private static final HistogramTracker configTracker = System.getProperty("lombok.timeConfig") == null ? null : new HistogramTracker("lombok.config"); - protected AST(String fileName, String packageDeclaration, ImportList imports) { + protected AST(String fileName, String packageDeclaration, ImportList imports, Collection<Class<? extends N>> statementTypes) { this.fileName = fileName == null ? "(unknown).java" : fileName; this.packageDeclaration = packageDeclaration; this.imports = imports; + this.statementTypes = statementTypes; } /** @@ -209,11 +216,11 @@ public abstract class AST<A extends AST<A, L, N>, L extends LombokNode<A, L, N>, } } - private static Map<Class<?>, Collection<FieldAccess>> fieldsOfASTClasses = new HashMap<Class<?>, Collection<FieldAccess>>(); + private static final ConcurrentMap<Class<?>, Collection<FieldAccess>> fieldsOfASTClasses = new ConcurrentHashMap<Class<?>, Collection<FieldAccess>>(); /** Returns FieldAccess objects for the stated class. Each field that contains objects of the kind returned by * {@link #getStatementTypes()}, either directly or inside of an array or java.util.collection (or array-of-arrays, - * or collection-of-collections, etcetera), is returned. + * or collection-of-collections, et cetera), is returned. */ protected Collection<FieldAccess> fieldsOf(Class<?> c) { Collection<FieldAccess> fields = fieldsOfASTClasses.get(c); @@ -221,8 +228,8 @@ public abstract class AST<A extends AST<A, L, N>, L extends LombokNode<A, L, N>, fields = new ArrayList<FieldAccess>(); getFields(c, fields); - fieldsOfASTClasses.put(c, fields); - return fields; + fieldsOfASTClasses.putIfAbsent(c, fields); + return fieldsOfASTClasses.get(c); } private void getFields(Class<?> c, Collection<FieldAccess> fields) { @@ -261,13 +268,8 @@ public abstract class AST<A extends AST<A, L, N>, L extends LombokNode<A, L, N>, return Object.class; } - /** - * The supertypes which are considered AST Node children. Usually, the Statement, and the Expression, - * though some platforms (such as Eclipse) group these under one common supertype. */ - protected abstract Collection<Class<? extends N>> getStatementTypes(); - - protected boolean shouldDrill(Class<?> parentType, Class<?> childType, String fieldName) { - for (Class<?> statementType : getStatementTypes()) { + private boolean shouldDrill(Class<?> parentType, Class<?> childType, String fieldName) { + for (Class<?> statementType : statementTypes) { if (statementType.isAssignableFrom(childType)) return true; } diff --git a/src/core/lombok/core/AnnotationProcessor.java b/src/core/lombok/core/AnnotationProcessor.java index ba5fd4f7..04448ecb 100644 --- a/src/core/lombok/core/AnnotationProcessor.java +++ b/src/core/lombok/core/AnnotationProcessor.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 @@ -26,6 +26,7 @@ import static lombok.core.Augments.ClassLoader_lombokAlreadyAddedTo; import java.io.File; import java.io.PrintWriter; import java.io.StringWriter; +import java.lang.reflect.Field; import java.lang.reflect.Method; import java.net.URL; import java.util.ArrayList; @@ -40,6 +41,7 @@ import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; +import javax.lang.model.element.Name; import javax.lang.model.element.TypeElement; import javax.tools.Diagnostic.Kind; @@ -47,6 +49,7 @@ import lombok.patcher.ClassRootFinder; @SupportedAnnotationTypes("*") public class AnnotationProcessor extends AbstractProcessor { + private static String trace(Throwable t) { StringWriter w = new StringWriter(); t.printStackTrace(new PrintWriter(w, true)); @@ -62,6 +65,42 @@ public class AnnotationProcessor extends AbstractProcessor { private final List<ProcessorDescriptor> registered = Arrays.asList(new JavacDescriptor(), new EcjDescriptor()); private final List<ProcessorDescriptor> active = new ArrayList<ProcessorDescriptor>(); private final List<String> delayedWarnings = new ArrayList<String>(); + + /** + * This method is a simplified version of {@link lombok.javac.apt.LombokProcessor.getJavacProcessingEnvironment} + * It simply returns the processing environment, but in case of gradle incremental compilation, + * the delegate ProcessingEnvironment of the gradle wrapper is returned. + */ + public static ProcessingEnvironment getJavacProcessingEnvironment(ProcessingEnvironment procEnv, List<String> delayedWarnings) { + ProcessingEnvironment javacProcEnv = tryRecursivelyObtainJavacProcessingEnvironment(procEnv); + + if (javacProcEnv == null) { + delayedWarnings.add("Can't get the delegate of the gradle IncrementalProcessingEnvironment."); + } + + return javacProcEnv; + } + + private static ProcessingEnvironment tryRecursivelyObtainJavacProcessingEnvironment(ProcessingEnvironment procEnv) { + if (procEnv.getClass().getName().equals("com.sun.tools.javac.processing.JavacProcessingEnvironment")) { + return procEnv; + } + + for (Class<?> procEnvClass = procEnv.getClass(); procEnvClass != null; procEnvClass = procEnvClass.getSuperclass()) { + try { + Field field = procEnvClass.getDeclaredField("delegate"); + field.setAccessible(true); + Object delegate = field.get(procEnv); + + return tryRecursivelyObtainJavacProcessingEnvironment((ProcessingEnvironment) delegate); + } catch (final Exception e) { + // no valid delegate, try superclass + } + } + + return null; + } + static class JavacDescriptor extends ProcessorDescriptor { private Processor processor; @@ -71,10 +110,12 @@ public class AnnotationProcessor extends AbstractProcessor { } @Override boolean want(ProcessingEnvironment procEnv, List<String> delayedWarnings) { - if (!procEnv.getClass().getName().equals("com.sun.tools.javac.processing.JavacProcessingEnvironment")) return false; - + ProcessingEnvironment javacProcEnv = getJavacProcessingEnvironment(procEnv, delayedWarnings); + + if (javacProcEnv == null) return false; + try { - ClassLoader classLoader = findAndPatchClassLoader(procEnv); + ClassLoader classLoader = findAndPatchClassLoader(javacProcEnv); processor = (Processor) Class.forName("lombok.javac.apt.LombokProcessor", false, classLoader).newInstance(); } catch (Exception e) { delayedWarnings.add("You found a bug in lombok; lombok.javac.apt.LombokProcessor is not available. Lombok will not run during this compilation: " + trace(e)); @@ -103,7 +144,6 @@ public class AnnotationProcessor extends AbstractProcessor { URL selfUrl = new File(ClassRootFinder.findClassRootOfClass(AnnotationProcessor.class)).toURI().toURL(); m.invoke(environmentClassLoader, selfUrl); } - return environmentClassLoader; } ClassLoader ourClassLoader = JavacDescriptor.class.getClassLoader(); @@ -164,7 +204,21 @@ public class AnnotationProcessor extends AbstractProcessor { for (ProcessorDescriptor proc : active) proc.process(annotations, roundEnv); - return false; + boolean onlyLombok = true; + boolean zeroElems = true; + for (TypeElement elem : annotations) { + zeroElems = false; + Name n = elem.getQualifiedName(); + if (n.length() > 7 && n.subSequence(0, 7).toString().equals("lombok.")) continue; + onlyLombok = false; + } + + // Normally we rely on the claiming processor to claim away all lombok annotations. + // One of the many Java9 oversights is that this 'process' API has not been fixed to address the point that 'files I want to look at' and 'annotations I want to claim' must be one and the same, + // and yet in java9 you can no longer have 2 providers for the same service, thus, if you go by module path, lombok no longer loads the ClaimingProcessor. + // This doesn't do as good a job, but it'll have to do. The only way to go from here, I think, is either 2 modules, or use reflection hackery to add ClaimingProcessor during our init. + + return onlyLombok && !zeroElems; } /** diff --git a/src/core/lombok/core/AnnotationValues.java b/src/core/lombok/core/AnnotationValues.java index d04797cb..a24330fa 100644 --- a/src/core/lombok/core/AnnotationValues.java +++ b/src/core/lombok/core/AnnotationValues.java @@ -27,6 +27,7 @@ import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; @@ -42,7 +43,7 @@ public class AnnotationValues<A extends Annotation> { private final Class<A> type; private final Map<String, AnnotationValue> values; private final LombokNode<?, ?, ?> ast; - + /** * Represents a single method on the annotation class. For example, the value() method on the Getter annotation. */ @@ -50,8 +51,7 @@ public class AnnotationValues<A extends Annotation> { /** A list of the raw expressions. List is size 1 unless an array is provided. */ public final List<String> raws; - /** Guesses for each raw expression. If the raw expression is a literal expression, the guess will - * likely be right. If not, it'll be wrong. */ + /** Guesses for each raw expression. It's 'primitive' (String or primitive), an AV.ClassLiteral, an AV.FieldSelect, or an array of one of those. */ public final List<Object> valueGuesses; /** A list of the actual expressions. List is size 1 unless an array is provided. */ @@ -160,6 +160,62 @@ public class AnnotationValues<A extends Annotation> { private A cachedInstance = null; + public List<String> getAsStringList(String methodName) { + AnnotationValue v = values.get(methodName); + + if (v == null) { + String[] s = getDefaultIf(methodName, String[].class, new String[0]); + return Collections.unmodifiableList(Arrays.asList(s)); + } + + List<String> out = new ArrayList<String>(v.valueGuesses.size()); + int idx = 0; + for (Object guess : v.valueGuesses) { + Object result = guess == null ? null : guessToType(guess, String.class, v, idx); + if (result == null) { + if (v.valueGuesses.size() == 1) { + String[] s = getDefaultIf(methodName, String[].class, new String[0]); + return Collections.unmodifiableList(Arrays.asList(s)); + } + throw new AnnotationValueDecodeFail(v, + "I can't make sense of this annotation value. Try using a fully qualified literal.", idx); + } + out.add((String) result); + } + + return Collections.unmodifiableList(out); + } + + public String getAsString(String methodName) { + AnnotationValue v = values.get(methodName); + if (v == null || v.valueGuesses.size() != 1) { + return getDefaultIf(methodName, String.class, ""); + } + + Object guess = guessToType(v.valueGuesses.get(0), String.class, v, 0); + if (guess instanceof String) return (String) guess; + return getDefaultIf(methodName, String.class, ""); + } + + public boolean getAsBoolean(String methodName) { + AnnotationValue v = values.get(methodName); + if (v == null || v.valueGuesses.size() != 1) { + return getDefaultIf(methodName, boolean.class, false); + } + + Object guess = guessToType(v.valueGuesses.get(0), boolean.class, v, 0); + if (guess instanceof Boolean) return ((Boolean) guess).booleanValue(); + return getDefaultIf(methodName, boolean.class, false); + } + + public <T> T getDefaultIf(String methodName, Class<T> type, T defaultValue) { + try { + return type.cast(type.getMethod(methodName).getDefaultValue()); + } catch (Exception e) { + return defaultValue; + } + } + /** * Creates an actual annotation instance. You can use this to query any annotation methods, except for * those annotation methods with class literals, as those can most likely not be turned into Class objects. @@ -190,7 +246,7 @@ public class AnnotationValues<A extends Annotation> { if (!isArray && v.valueGuesses.size() > 1) { throw new AnnotationValueDecodeFail(v, - "Expected a single value, but " + method.getName() + " has an array of values", -1); + "Expected a single value, but " + method.getName() + " has an array of values", -1); } if (v.valueGuesses.size() == 0 && !isArray) { @@ -217,7 +273,7 @@ public class AnnotationValues<A extends Annotation> { return defaultValue; } throw new AnnotationValueDecodeFail(v, - "I can't make sense of this annotation value. Try using a fully qualified literal.", idx); + "I can't make sense of this annotation value. Try using a fully qualified literal.", idx); } Array.set(array, idx++, result); } @@ -230,48 +286,48 @@ public class AnnotationValues<A extends Annotation> { } private Object guessToType(Object guess, Class<?> expected, AnnotationValue v, int pos) { - if (expected == int.class) { + if (expected == int.class || expected == Integer.class) { if (guess instanceof Integer || guess instanceof Short || guess instanceof Byte) { - return ((Number)guess).intValue(); + return ((Number) guess).intValue(); } } - if (expected == long.class) { + if (expected == long.class || expected == Long.class) { if (guess instanceof Long || guess instanceof Integer || guess instanceof Short || guess instanceof Byte) { - return ((Number)guess).longValue(); + return ((Number) guess).longValue(); } } - if (expected == short.class) { + if (expected == short.class || expected == Short.class) { if (guess instanceof Integer || guess instanceof Short || guess instanceof Byte) { - int intVal = ((Number)guess).intValue(); - int shortVal = ((Number)guess).shortValue(); + int intVal = ((Number) guess).intValue(); + int shortVal = ((Number) guess).shortValue(); if (shortVal == intVal) return shortVal; } } - if (expected == byte.class) { + if (expected == byte.class || expected == Byte.class) { if (guess instanceof Integer || guess instanceof Short || guess instanceof Byte) { - int intVal = ((Number)guess).intValue(); - int byteVal = ((Number)guess).byteValue(); + int intVal = ((Number) guess).intValue(); + int byteVal = ((Number) guess).byteValue(); if (byteVal == intVal) return byteVal; } } - if (expected == double.class) { - if (guess instanceof Number) return ((Number)guess).doubleValue(); + if (expected == double.class || expected == Double.class) { + if (guess instanceof Number) return ((Number) guess).doubleValue(); } - if (expected == float.class) { - if (guess instanceof Number) return ((Number)guess).floatValue(); + if (expected == float.class || expected == Float.class) { + if (guess instanceof Number) return ((Number) guess).floatValue(); } - if (expected == boolean.class) { - if (guess instanceof Boolean) return ((Boolean)guess).booleanValue(); + if (expected == boolean.class || expected == Boolean.class) { + if (guess instanceof Boolean) return ((Boolean) guess).booleanValue(); } - if (expected == char.class) { - if (guess instanceof Character) return ((Character)guess).charValue(); + if (expected == char.class || expected == Character.class) { + if (guess instanceof Character) return ((Character) guess).charValue(); } if (expected == String.class) { @@ -279,27 +335,36 @@ public class AnnotationValues<A extends Annotation> { } if (Enum.class.isAssignableFrom(expected) ) { - if (guess instanceof String) { + if (guess instanceof FieldSelect) { + String fieldSel = ((FieldSelect) guess).getFinalPart(); for (Object enumConstant : expected.getEnumConstants()) { - String target = ((Enum<?>)enumConstant).name(); - if (target.equals(guess)) return enumConstant; + String target = ((Enum<?>) enumConstant).name(); + if (target.equals(fieldSel)) return enumConstant; } throw new AnnotationValueDecodeFail(v, - "Can't translate " + guess + " to an enum of type " + expected, pos); + "Can't translate " + fieldSel + " to an enum of type " + expected, pos); } } - if (Class.class == expected) { - if (guess instanceof String) try { - return Class.forName(toFQ((String)guess)); + if (expected == Class.class) { + if (guess instanceof ClassLiteral) try { + String classLit = ((ClassLiteral) guess).getClassName(); + return Class.forName(toFQ(classLit)); } catch (ClassNotFoundException e) { throw new AnnotationValueDecodeFail(v, - "Can't translate " + guess + " to a class object.", pos); + "Can't translate " + guess + " to a class object.", pos); } } + if (guess instanceof AnnotationValues) { + return ((AnnotationValues<?>) guess).getInstance(); + } + + if (guess instanceof FieldSelect) throw new AnnotationValueDecodeFail(v, + "You must use constant literals in lombok annotations; they cannot be references to (static) fields.", pos); + throw new AnnotationValueDecodeFail(v, - "Can't translate a " + guess.getClass() + " to the expected " + expected, pos); + "Can't translate a " + guess.getClass() + " to the expected " + expected, pos); } /** diff --git a/src/core/lombok/core/LombokInternalAliasing.java b/src/core/lombok/core/LombokInternalAliasing.java index 08764a5c..3dc1bfa2 100644 --- a/src/core/lombok/core/LombokInternalAliasing.java +++ b/src/core/lombok/core/LombokInternalAliasing.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2014 The Project Lombok Authors. + * Copyright (C) 2013-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 @@ -51,6 +51,7 @@ public class LombokInternalAliasing { Map<String, String> m2 = new HashMap<String, String>(); m2.put("lombok.experimental.Value", "lombok.Value"); m2.put("lombok.experimental.Builder", "lombok.Builder"); + m2.put("lombok.experimental.var", "lombok.var"); m2.put("lombok.Delegate", "lombok.experimental.Delegate"); ALIASES = Collections.unmodifiableMap(m2); } 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/PublicApiCreatorApp.java b/src/core/lombok/core/PublicApiCreatorApp.java index 178a45e8..c1430c24 100644 --- a/src/core/lombok/core/PublicApiCreatorApp.java +++ b/src/core/lombok/core/PublicApiCreatorApp.java @@ -105,7 +105,7 @@ public class PublicApiCreatorApp extends LombokApp { int firstSlash = subName.indexOf('/'); if (firstSlash == -1) { // direct member of the lombok package. - toCopy.add(name); + if (!subName.startsWith("ConfigurationKeys")) toCopy.add(name); continue; } String topPkg = subName.substring(0, firstSlash); diff --git a/src/core/lombok/core/Version.java b/src/core/lombok/core/Version.java index ed245c08..70af48e0 100644 --- a/src/core/lombok/core/Version.java +++ b/src/core/lombok/core/Version.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2016 The Project Lombok Authors. + * Copyright (C) 2009-2017 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 @@ -30,9 +30,16 @@ public class Version { // ** CAREFUL ** - this class must always compile with 0 dependencies (it must not refer to any other sources or libraries). // Note: In 'X.Y.Z', if Z is odd, its a snapshot build built from the repository, so many different 0.10.3 versions can exist, for example. // Official builds always end in an even number. (Since 0.10.2). - private static final String VERSION = "1.16.9"; + private static final String VERSION = "1.18.1"; private static final String RELEASE_NAME = "Edgy Guinea Pig"; -// private static final String RELEASE_NAME = "Candid Duck"; +// private static final String RELEASE_NAME = "Envious Ferret"; + + // Named version history: + // Angry Butterfly + // Branching Cobra + // Candid Duck + // Dancing Elephant + // Envious Ferret private Version() { //Prevent instantiation @@ -43,7 +50,7 @@ public class Version { */ public static void main(String[] args) { if (args.length > 0) { - System.out.printf("Lombok %s\n", getFullVersion()); + System.out.printf("%s\n", getFullVersion()); } else { System.out.println(VERSION); } diff --git a/src/core/lombok/core/configuration/AllowHelper.java b/src/core/lombok/core/configuration/AllowHelper.java new file mode 100644 index 00000000..1146ccde --- /dev/null +++ b/src/core/lombok/core/configuration/AllowHelper.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 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.configuration; + +import java.util.Collection; +import java.util.Collections; + +public final class AllowHelper { + private final static Collection<? extends ConfigurationKey<?>> ALLOWABLE = Collections.emptySet(); + + private AllowHelper() { + // Prevent instantiation + } + + public static boolean isAllowable(ConfigurationKey<?> key) { + return ALLOWABLE.contains(key); + } +} diff --git a/src/core/lombok/core/configuration/ConfigurationApp.java b/src/core/lombok/core/configuration/ConfigurationApp.java index e441b4de..efe57e38 100644 --- a/src/core/lombok/core/configuration/ConfigurationApp.java +++ b/src/core/lombok/core/configuration/ConfigurationApp.java @@ -198,7 +198,7 @@ public class ConfigurationApp extends LombokApp { if (paths.size() == 1) { if (!(argsPaths.size() == 1)) out.printf("Configuration for '%s'.%n%n", paths.iterator().next()); } else { - out.printf("Configuration for:%n", paths.iterator().next()); + out.printf("Configuration for:%n"); for (String path : paths) out.printf("- %s%n", path); out.println(); } diff --git a/src/core/lombok/core/configuration/FlagUsageType.java b/src/core/lombok/core/configuration/FlagUsageType.java index b7053b7c..8717c22b 100644 --- a/src/core/lombok/core/configuration/FlagUsageType.java +++ b/src/core/lombok/core/configuration/FlagUsageType.java @@ -23,5 +23,5 @@ package lombok.core.configuration; /** Used for lombok configuration to flag usages of certain lombok feature. */ public enum FlagUsageType { - WARNING, ERROR; + WARNING, ERROR, ALLOW; } diff --git a/src/core/lombok/core/configuration/NullCheckExceptionType.java b/src/core/lombok/core/configuration/NullCheckExceptionType.java index 18a332fd..c4bb71f2 100644 --- a/src/core/lombok/core/configuration/NullCheckExceptionType.java +++ b/src/core/lombok/core/configuration/NullCheckExceptionType.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Project Lombok Authors. + * Copyright (C) 2014-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 @@ -26,7 +26,7 @@ package lombok.core.configuration; public enum NullCheckExceptionType { ILLEGAL_ARGUMENT_EXCEPTION { public String toExceptionMessage(String fieldName) { - return fieldName + " is null"; + return fieldName + " is marked @NonNull but is null"; } @Override public String getExceptionType() { @@ -35,7 +35,7 @@ public enum NullCheckExceptionType { }, NULL_POINTER_EXCEPTION { @Override public String toExceptionMessage(String fieldName) { - return fieldName; + return fieldName + " is marked @NonNull but is null"; } public String getExceptionType() { diff --git a/src/core/lombok/core/handlers/HandlerUtil.java b/src/core/lombok/core/handlers/HandlerUtil.java index a9a4be78..0d64c550 100644 --- a/src/core/lombok/core/handlers/HandlerUtil.java +++ b/src/core/lombok/core/handlers/HandlerUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2015 The Project Lombok Authors. + * Copyright (C) 2013-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 @@ -43,6 +43,7 @@ import lombok.core.AST; import lombok.core.AnnotationValues; import lombok.core.JavaIdentifiers; import lombok.core.LombokNode; +import lombok.core.configuration.AllowHelper; import lombok.core.configuration.ConfigurationKey; import lombok.core.configuration.FlagUsageType; import lombok.experimental.Accessors; @@ -56,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; } @@ -97,13 +102,24 @@ public class HandlerUtil { public static void handleFlagUsage(LombokNode<?, ?, ?> node, ConfigurationKey<FlagUsageType> key, String featureName) { FlagUsageType fut = node.getAst().readConfiguration(key); + if (fut == null && AllowHelper.isAllowable(key)) { + node.addError("Use of " + featureName + " is disabled by default. Please add '" + key.getKeyName() + " = " + FlagUsageType.ALLOW + "' to 'lombok.config' if you want to enable is."); + } + if (fut != null) { String msg = "Use of " + featureName + " is flagged according to lombok configuration."; if (fut == FlagUsageType.WARNING) node.addWarning(msg); - else node.addError(msg); + else if (fut == FlagUsageType.ERROR) node.addError(msg); } } + @SuppressWarnings("deprecation") + public static boolean shouldAddGenerated(LombokNode<?, ?, ?> node) { + Boolean add = node.getAst().readConfiguration(ConfigurationKeys.ADD_JAVAX_GENERATED_ANNOTATIONS); + if (add != null) return add; + return Boolean.TRUE.equals(node.getAst().readConfiguration(ConfigurationKeys.ADD_GENERATED_ANNOTATIONS)); + } + public static void handleExperimentalFlagUsage(LombokNode<?, ?, ?> node, ConfigurationKey<FlagUsageType> key, String featureName) { handleFlagUsage(node, key, featureName, ConfigurationKeys.EXPERIMENTAL_FLAG_USAGE, "any lombok.experimental feature"); } @@ -159,11 +175,12 @@ public class HandlerUtil { } @SuppressWarnings({"all", "unchecked", "deprecation"}) - public static final List<Class<? extends java.lang.annotation.Annotation>> INVALID_ON_BUILDERS = Collections.unmodifiableList( - Arrays.<Class<? extends java.lang.annotation.Annotation>>asList( - Getter.class, Setter.class, Wither.class, ToString.class, EqualsAndHashCode.class, - RequiredArgsConstructor.class, AllArgsConstructor.class, NoArgsConstructor.class, - Data.class, Value.class, lombok.experimental.Value.class, FieldDefaults.class)); + public static final List<String> INVALID_ON_BUILDERS = Collections.unmodifiableList( + Arrays.<String>asList( + Getter.class.getName(), Setter.class.getName(), Wither.class.getName(), + ToString.class.getName(), EqualsAndHashCode.class.getName(), + RequiredArgsConstructor.class.getName(), AllArgsConstructor.class.getName(), NoArgsConstructor.class.getName(), + Data.class.getName(), Value.class.getName(), "lombok.experimental.Value", FieldDefaults.class.getName())); /** * Given the name of a field, return the 'base name' of that field. For example, {@code fFoobar} becomes {@code foobar} if {@code f} is in the prefix list. @@ -428,4 +445,16 @@ public class HandlerUtil { } return String.format("%s%s", prefix, suffix); } + + public static String camelCaseToConstant(String fieldName) { + if (fieldName == null || fieldName.isEmpty()) return ""; + StringBuilder b = new StringBuilder(); + b.append(Character.toUpperCase(fieldName.charAt(0))); + for (int i = 1; i < fieldName.length(); i++) { + char c = fieldName.charAt(i); + if (Character.isUpperCase(c)) b.append('_'); + b.append(Character.toUpperCase(c)); + } + return b.toString(); + } } diff --git a/src/core/lombok/core/handlers/InclusionExclusionUtils.java b/src/core/lombok/core/handlers/InclusionExclusionUtils.java new file mode 100644 index 00000000..368b51fc --- /dev/null +++ b/src/core/lombok/core/handlers/InclusionExclusionUtils.java @@ -0,0 +1,228 @@ +/* + * 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.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; + +import lombok.EqualsAndHashCode; +import lombok.ToString; +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)) { + if (annotation != null) 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)) { + if (annotation != null) annotation.setWarning("of", "This field does not exist.", i); + } + } + } + + public static class Included<L, I extends Annotation> { + private final L node; + private final I inc; + private final boolean defaultInclude; + + public Included(L node, I inc, boolean defaultInclude) { + this.node = node; + this.inc = inc; + this.defaultInclude = defaultInclude; + } + + public L getNode() { + return node; + } + + public I getInc() { + return inc; + } + + public boolean isDefaultInclude() { + return defaultInclude; + } + } + + private static String innerAnnName(Class<? extends Annotation> type) { + String name = type.getSimpleName(); + Class<?> c = type.getEnclosingClass(); + while (c != null) { + name = c.getSimpleName() + "." + name; + c = c.getEnclosingClass(); + } + return name; + } + + public static <A extends AST<A, L, N>, L extends LombokNode<A, L, N>, N, I extends Annotation> List<Included<L, I>> handleIncludeExcludeMarking(Class<I> inclType, String replaceName, Class<? extends Annotation> exclType, LombokNode<A, L, N> typeNode, AnnotationValues<?> annotation, LombokNode<A, L, N> annotationNode, boolean includeTransient) { + List<String> oldExcludes = (annotation != null && annotation.isExplicit("exclude")) ? annotation.getAsStringList("exclude") : null; + List<String> oldIncludes = (annotation != null && annotation.isExplicit("of")) ? annotation.getAsStringList("of") : null; + + boolean onlyExplicitlyIncluded = annotation != null ? annotation.getAsBoolean("onlyExplicitlyIncluded") : false; + boolean memberAnnotationMode = onlyExplicitlyIncluded; + List<Included<L, I>> members = new ArrayList<Included<L, I>>(); + List<String> namesToAutoExclude = new ArrayList<String>(); + + if (typeNode == null || typeNode.getKind() != Kind.TYPE) return null; + + checkForBogusFieldNames(typeNode, annotation, oldExcludes, oldIncludes); + String inclTypeName = innerAnnName(inclType); + String exclTypeName = innerAnnName(exclType); + + if (oldExcludes != null && oldIncludes != null) { + oldExcludes = null; + if (annotation != 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(exclType); + AnnotationValues<I> markInclude = null; + if (child.getKind() == Kind.FIELD || child.getKind() == Kind.METHOD) markInclude = child.findAnnotation(inclType); + + if (markExclude || markInclude != null) memberAnnotationMode = true; + + if (markInclude != null && markExclude) { + child.addError("@" + exclTypeName + " and @" + inclTypeName + " 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) { + I inc = markInclude.getInstance(); + if (child.getKind() == Kind.METHOD) { + if (child.countMethodParameters() > 0) { + child.addError("Methods included with @" + inclTypeName + " must have no arguments; it will not be included"); + continue; + } + String n = replaceName != null ? markInclude.getAsString(replaceName) : ""; + if (n.isEmpty()) n = name; + namesToAutoExclude.add(n); + } + members.add(new Included<L, I>(child, inc, false)); + continue; + } + + if (onlyExplicitlyIncluded) continue; + if (oldIncludes != null) { + if (child.getKind() == Kind.FIELD && oldIncludes.contains(name)) members.add(new Included<L, I>(child, null, false)); + continue; + } + if (child.getKind() != Kind.FIELD) continue; + if (child.isStatic()) continue; + if (child.isTransient() && !includeTransient) continue; + if (name.startsWith("$")) continue; + if (child.isEnumMember()) continue; + members.add(new Included<L, I>(child, null, true)); + } + + /* delete default-included fields with the same name as an explicit inclusion */ { + Iterator<Included<L, I>> it = members.iterator(); + while (it.hasNext()) { + Included<L, I> 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; + } + + return members; + } + + public static <A extends AST<A, L, N>, L extends LombokNode<A, L, N>, N> List<Included<L, ToString.Include>> handleToStringMarking(LombokNode<A, L, N> typeNode, AnnotationValues<ToString> annotation, LombokNode<A, L, N> annotationNode) { + List<Included<L, ToString.Include>> members = handleIncludeExcludeMarking(ToString.Include.class, "name", ToString.Exclude.class, typeNode, annotation, annotationNode, true); + + Collections.sort(members, new Comparator<Included<L, ToString.Include>>() { + @Override public int compare(Included<L, ToString.Include> a, Included<L, ToString.Include> 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; + } + + public static <A extends AST<A, L, N>, L extends LombokNode<A, L, N>, N> List<Included<L, EqualsAndHashCode.Include>> handleEqualsAndHashCodeMarking(LombokNode<A, L, N> typeNode, AnnotationValues<EqualsAndHashCode> annotation, LombokNode<A, L, N> annotationNode) { + return handleIncludeExcludeMarking(EqualsAndHashCode.Include.class, "replaces", EqualsAndHashCode.Exclude.class, typeNode, annotation, annotationNode, false); + } +} diff --git a/src/core/lombok/core/handlers/singulars.txt b/src/core/lombok/core/handlers/singulars.txt index 9976c76c..d77308cb 100644 --- a/src/core/lombok/core/handlers/singulars.txt +++ b/src/core/lombok/core/handlers/singulars.txt @@ -19,7 +19,7 @@ Mice = mouse Lice = louse News = ! # We could add more detail (axemen, boatsmen, boogymen, cavemen, gentlemen, etc, but (A) there's stuff like 'cerumen', and (B) the 'men' ending is common in singulars and other languages.) -# Therefore, the odds of a mistake are too high, so other than these 2 well known cases, so force the explicit singular. +# Therefore, the odds of a mistake are too high, so other than these 2 well known cases, force the explicit singular. Men = man Women = woman minutiae = minutia @@ -48,7 +48,9 @@ hives = hive -shes = sh -lves = lf -rves = rf --ves = fe +saves = save +Leaves = leaf +-ves = ! -ss = ! -us = ! -s = diff --git a/src/core/lombok/eclipse/EclipseAST.java b/src/core/lombok/eclipse/EclipseAST.java index 6741b33a..dc2c9843 100644 --- a/src/core/lombok/eclipse/EclipseAST.java +++ b/src/core/lombok/eclipse/EclipseAST.java @@ -62,7 +62,7 @@ public class EclipseAST extends AST<EclipseAST, EclipseNode, ASTNode> { * @param ast The compilation unit, which serves as the top level node in the tree to be built. */ public EclipseAST(CompilationUnitDeclaration ast) { - super(toFileName(ast), packageDeclaration(ast), new EclipseImportList(ast)); + super(toFileName(ast), packageDeclaration(ast), new EclipseImportList(ast), statementTypes()); this.compilationUnitDeclaration = ast; setTop(buildCompilationUnit(ast)); this.completeParse = isComplete(ast); @@ -477,9 +477,9 @@ public class EclipseAST extends AST<EclipseAST, EclipseNode, ASTNode> { return putInMap(new EclipseNode(this, statement, childNodes, Kind.STATEMENT)); } - /** For Eclipse, only Statement counts, as Expression is a subclass of it, even though this isn't + /* For Eclipse, only Statement counts, as Expression is a subclass of it, even though this isn't * entirely correct according to the JLS spec (only some expressions can be used as statements, not all of them). */ - @Override protected Collection<Class<? extends ASTNode>> getStatementTypes() { + private static Collection<Class<? extends ASTNode>> statementTypes() { return Collections.<Class<? extends ASTNode>>singleton(Statement.class); } 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 0ff5a7f6..2dce285c 100644 --- a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java +++ b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2015 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 @@ -167,6 +167,7 @@ public class EclipseHandlerUtil { } public static boolean isFieldDeprecated(EclipseNode fieldNode) { + if (!(fieldNode.get() instanceof FieldDeclaration)) return false; FieldDeclaration field = (FieldDeclaration) fieldNode.get(); if ((field.modifiers & ClassFileConstants.AccDeprecated) != 0) { return true; @@ -196,17 +197,36 @@ public class EclipseHandlerUtil { TypeResolver resolver = new TypeResolver(node.getImportList()); return resolver.typeMatches(node, type.getName(), typeName); + } + + /** + * Checks if the given TypeReference node is likely to be a reference to the provided class. + * + * @param type An actual type. This method checks if {@code typeNode} is likely to be a reference to this type. + * @param node A Lombok AST node. Any node in the appropriate compilation unit will do (used to get access to import statements). + * @param typeRef A type reference to check. + */ + public static boolean typeMatches(String type, EclipseNode node, TypeReference typeRef) { + if (typeRef == null || typeRef.getTypeName() == null || typeRef.getTypeName().length == 0) return false; + String lastPartA = new String(typeRef.getTypeName()[typeRef.getTypeName().length -1]); + int lastIndex = type.lastIndexOf('.'); + String lastPartB = lastIndex == -1 ? type : type.substring(lastIndex + 1); + if (!lastPartA.equals(lastPartB)) return false; + String typeName = toQualifiedName(typeRef.getTypeName()); + TypeResolver resolver = new TypeResolver(node.getImportList()); + return resolver.typeMatches(node, type, typeName); } public static void sanityCheckForMethodGeneratingAnnotationsOnBuilderClass(EclipseNode typeNode, EclipseNode errorNode) { List<String> disallowed = null; for (EclipseNode child : typeNode.down()) { if (child.getKind() != Kind.ANNOTATION) continue; - for (Class<? extends java.lang.annotation.Annotation> annType : INVALID_ON_BUILDERS) { + for (String annType : INVALID_ON_BUILDERS) { if (annotationTypeMatches(annType, child)) { if (disallowed == null) disallowed = new ArrayList<String>(); - disallowed.add(annType.getSimpleName()); + int lastIndex = annType.lastIndexOf('.'); + disallowed.add(lastIndex == -1 ? annType : annType.substring(lastIndex + 1)); } } } @@ -443,6 +463,42 @@ public class EclipseHandlerUtil { } } + public static boolean hasAnnotation(String type, EclipseNode node) { + if (node == null) return false; + if (type == null) return false; + switch (node.getKind()) { + case ARGUMENT: + case FIELD: + case LOCAL: + case TYPE: + case METHOD: + for (EclipseNode child : node.down()) { + if (annotationTypeMatches(type, child)) return true; + } + // intentional fallthrough + default: + return false; + } + } + + public static EclipseNode findAnnotation(Class<? extends java.lang.annotation.Annotation> type, EclipseNode node) { + if (node == null) return null; + if (type == null) return null; + switch (node.getKind()) { + case ARGUMENT: + case FIELD: + case LOCAL: + case TYPE: + case METHOD: + for (EclipseNode child : node.down()) { + if (annotationTypeMatches(type, child)) return child; + } + // intentional fallthrough + default: + return null; + } + } + /** * Checks if the provided annotation type is likely to be the intended type for the given annotation node. * @@ -453,6 +509,16 @@ public class EclipseHandlerUtil { return typeMatches(type, node, ((Annotation) node.get()).type); } + /** + * Checks if the provided annotation type is likely to be the intended type for the given annotation node. + * + * This is a guess, but a decent one. + */ + public static boolean annotationTypeMatches(String type, EclipseNode node) { + if (node.getKind() != Kind.ANNOTATION) return false; + return typeMatches(type, node, ((Annotation) node.get()).type); + } + public static TypeReference cloneSelfType(EclipseNode context) { return cloneSelfType(context, null); } @@ -680,7 +746,7 @@ public class EclipseHandlerUtil { * Provides AnnotationValues with the data it needs to do its thing. */ public static <A extends java.lang.annotation.Annotation> AnnotationValues<A> - createAnnotation(Class<A> type, final EclipseNode annotationNode) { + createAnnotation(Class<A> type, final EclipseNode annotationNode) { final Annotation annotation = (Annotation) annotationNode.get(); Map<String, AnnotationValue> values = new HashMap<String, AnnotationValue>(); @@ -697,7 +763,7 @@ public class EclipseHandlerUtil { String mName = (n == null || n.length == 0) ? "value" : new String(pair.name); final Expression rhs = pair.value; if (rhs instanceof ArrayInitializer) { - expressions = ((ArrayInitializer)rhs).expressions; + expressions = ((ArrayInitializer) rhs).expressions; } else if (rhs != null) { expressions = new Expression[] { rhs }; } @@ -831,7 +897,7 @@ public class EclipseHandlerUtil { // Check if the class has a @Getter annotation. - if (!hasGetterAnnotation && new HandleGetter().fieldQualifiesForGetterGeneration(field)) { + if (!hasGetterAnnotation && HandleGetter.fieldQualifiesForGetterGeneration(field)) { //Check if the class has @Getter or @Data annotation. EclipseNode containingType = field.up(); @@ -854,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; @@ -874,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; @@ -886,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); @@ -954,6 +1018,43 @@ 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; + } + + static Expression createMethodAccessor(EclipseNode method, ASTNode source, char[] receiver) { + 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; + call.receiver = new SingleNameReference(receiver, 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; @@ -1031,7 +1132,7 @@ public class EclipseHandlerUtil { public static boolean filterField(FieldDeclaration declaration, boolean skipStatic) { // Skip the fake fields that represent enum constants. if (declaration.initialization instanceof AllocationExpression && - ((AllocationExpression)declaration.initialization).enumConstant != null) return false; + ((AllocationExpression) declaration.initialization).enumConstant != null) return false; if (declaration.type == null) return false; @@ -1097,6 +1198,12 @@ public class EclipseHandlerUtil { return AnnotationValues.of(Accessors.class, field); } + + public static EclipseNode upToTypeNode(EclipseNode node) { + if (node == null) throw new NullPointerException("node"); + while (node != null && !(node.get() instanceof TypeDeclaration)) node = node.up(); + return node; + } /** * Checks if there is a field with the provided name. @@ -1105,10 +1212,7 @@ public class EclipseHandlerUtil { * @param node Any node that represents the Type (TypeDeclaration) to look in, or any child node thereof. */ public static MemberExistsResult fieldExists(String fieldName, EclipseNode node) { - while (node != null && !(node.get() instanceof TypeDeclaration)) { - node = node.up(); - } - + node = upToTypeNode(node); if (node != null && node.get() instanceof TypeDeclaration) { TypeDeclaration typeDecl = (TypeDeclaration)node.get(); if (typeDecl.fields != null) for (FieldDeclaration def : typeDecl.fields) { @@ -1168,9 +1272,8 @@ public class EclipseHandlerUtil { if (params < minArgs || params > maxArgs) continue; } - if (def.annotations != null) for (Annotation anno : def.annotations) { - if (typeMatches(Tolerate.class, node, anno.type)) continue top; - } + + if (isTolerate(node, def)) continue top; return getGeneratedBy(def) == null ? MemberExistsResult.EXISTS_BY_USER : MemberExistsResult.EXISTS_BY_LOMBOK; } @@ -1181,6 +1284,13 @@ public class EclipseHandlerUtil { return MemberExistsResult.NOT_EXISTS; } + public static boolean isTolerate(EclipseNode node, AbstractMethodDeclaration def) { + if (def.annotations != null) for (Annotation anno : def.annotations) { + if (typeMatches(Tolerate.class, node, anno.type)) return true; + } + return false; + } + /** * Checks if there is a (non-default) constructor. In case of multiple constructors (overloading), only * the first constructor decides if EXISTS_BY_USER or EXISTS_BY_LOMBOK is returned. @@ -1188,19 +1298,14 @@ public class EclipseHandlerUtil { * @param node Any node that represents the Type (TypeDeclaration) to look in, or any child node thereof. */ public static MemberExistsResult constructorExists(EclipseNode node) { - while (node != null && !(node.get() instanceof TypeDeclaration)) { - node = node.up(); - } - + node = upToTypeNode(node); if (node != null && node.get() instanceof TypeDeclaration) { TypeDeclaration typeDecl = (TypeDeclaration)node.get(); - if (typeDecl.methods != null) top: for (AbstractMethodDeclaration def : typeDecl.methods) { + if (typeDecl.methods != null) for (AbstractMethodDeclaration def : typeDecl.methods) { if (def instanceof ConstructorDeclaration) { if ((def.bits & ASTNode.IsDefaultConstructor) != 0) continue; - if (def.annotations != null) for (Annotation anno : def.annotations) { - if (typeMatches(Tolerate.class, node, anno.type)) continue top; - } + if (isTolerate(node, def)) continue; return getGeneratedBy(def) == null ? MemberExistsResult.EXISTS_BY_USER : MemberExistsResult.EXISTS_BY_LOMBOK; } @@ -1325,6 +1430,7 @@ public class EclipseHandlerUtil { private static final char[] GENERATED_CODE = "generated code".toCharArray(); private static final char[] LOMBOK = "lombok".toCharArray(); private static final char[][] JAVAX_ANNOTATION_GENERATED = Eclipse.fromQualifiedName("javax.annotation.Generated"); + private static final char[][] LOMBOK_GENERATED = Eclipse.fromQualifiedName("lombok.Generated"); private static final char[][] EDU_UMD_CS_FINDBUGS_ANNOTATIONS_SUPPRESSFBWARNINGS = Eclipse.fromQualifiedName("edu.umd.cs.findbugs.annotations.SuppressFBWarnings"); public static Annotation[] addSuppressWarningsAll(EclipseNode node, ASTNode source, Annotation[] originalAnnotationArray) { @@ -1339,24 +1445,29 @@ public class EclipseHandlerUtil { } public static Annotation[] addGenerated(EclipseNode node, ASTNode source, Annotation[] originalAnnotationArray) { - if (Boolean.FALSE.equals(node.getAst().readConfiguration(ConfigurationKeys.ADD_GENERATED_ANNOTATIONS))) return originalAnnotationArray; - return addAnnotation(source, originalAnnotationArray, JAVAX_ANNOTATION_GENERATED, new StringLiteral(LOMBOK, 0, 0, 0)); + Annotation[] result = originalAnnotationArray; + if (HandlerUtil.shouldAddGenerated(node)) { + result = addAnnotation(source, result, JAVAX_ANNOTATION_GENERATED, new StringLiteral(LOMBOK, 0, 0, 0)); + } + if (Boolean.TRUE.equals(node.getAst().readConfiguration(ConfigurationKeys.ADD_LOMBOK_GENERATED_ANNOTATIONS))) { + result = addAnnotation(source, result, LOMBOK_GENERATED, null); + } + return result; } private static Annotation[] addAnnotation(ASTNode source, Annotation[] originalAnnotationArray, char[][] annotationTypeFqn, ASTNode arg) { char[] simpleName = annotationTypeFqn[annotationTypeFqn.length - 1]; if (originalAnnotationArray != null) for (Annotation ann : originalAnnotationArray) { - char[] lastToken = null; - if (ann.type instanceof QualifiedTypeReference) { char[][] t = ((QualifiedTypeReference) ann.type).tokens; - lastToken = t[t.length - 1]; - } else if (ann.type instanceof SingleTypeReference) { - lastToken = ((SingleTypeReference) ann.type).token; + if (Arrays.deepEquals(t, annotationTypeFqn)) return originalAnnotationArray; } - if (lastToken != null && Arrays.equals(simpleName, lastToken)) return originalAnnotationArray; + if (ann.type instanceof SingleTypeReference) { + char[] lastToken = ((SingleTypeReference) ann.type).token; + if (Arrays.equals(lastToken, simpleName)) return originalAnnotationArray; + } } int pS = source.sourceStart, pE = source.sourceEnd; @@ -1606,6 +1717,14 @@ public class EclipseHandlerUtil { return true; } + public static void addError(String errorName, EclipseNode node) { + if (node.getLatestJavaSpecSupported() < 8) { + node.addError("The correct format is " + errorName + "_={@SomeAnnotation, @SomeOtherAnnotation})"); + } else { + node.addError("The correct format is " + errorName + "=@__({@SomeAnnotation, @SomeOtherAnnotation}))"); + } + } + public static List<Annotation> unboxAndRemoveAnnotationParameter(Annotation annotation, String annotationName, String errorName, EclipseNode errorNode) { if ("value".equals(annotationName)) { // We can't unbox this, because SingleMemberAnnotation REQUIRES a value, and this method @@ -1628,51 +1747,70 @@ public class EclipseHandlerUtil { char[] nameAsCharArray = annotationName.toCharArray(); + top: for (int i = 0; i < pairs.length; i++) { - if (pairs[i].name == null || !Arrays.equals(nameAsCharArray, pairs[i].name)) continue; + boolean allowRaw; + char[] name = pairs[i].name; + if (name == null) continue; + if (name.length < nameAsCharArray.length) continue; + for (int j = 0; j < nameAsCharArray.length; j++) { + if (name[j] != nameAsCharArray[j]) continue top; + } + allowRaw = name.length > nameAsCharArray.length; + for (int j = nameAsCharArray.length; j < name.length; j++) { + if (name[j] != '_') continue top; + } + // If we're still here it's the targeted annotation param. Expression value = pairs[i].value; MemberValuePair[] newPairs = new MemberValuePair[pairs.length - 1]; if (i > 0) System.arraycopy(pairs, 0, newPairs, 0, i); if (i < pairs.length - 1) System.arraycopy(pairs, i + 1, newPairs, i, pairs.length - i - 1); normalAnnotation.memberValuePairs = newPairs; - // We have now removed the annotation parameter and stored '@__({... annotations ...})', - // which we must now unbox. - if (!(value instanceof Annotation)) { - errorNode.addError("The correct format is " + errorName + "@__({@SomeAnnotation, @SomeOtherAnnotation}))"); - return Collections.emptyList(); - } - - Annotation atDummyIdentifier = (Annotation) value; - if (!(atDummyIdentifier.type instanceof SingleTypeReference) || - !isAllValidOnXCharacters(((SingleTypeReference) atDummyIdentifier.type).token)) { - errorNode.addError("The correct format is " + errorName + "@__({@SomeAnnotation, @SomeOtherAnnotation}))"); - return Collections.emptyList(); - } - - if (atDummyIdentifier instanceof MarkerAnnotation) { - // It's @Getter(onMethod=@__). This is weird, but fine. - return Collections.emptyList(); - } + // We have now removed the annotation parameter and stored the value, + // which we must now unbox. It's either annotations, or @__(annotations). Expression content = null; - if (atDummyIdentifier instanceof NormalAnnotation) { - MemberValuePair[] mvps = ((NormalAnnotation) atDummyIdentifier).memberValuePairs; - if (mvps == null || mvps.length == 0) { - // It's @Getter(onMethod=@__()). This is weird, but fine. + if (value instanceof ArrayInitializer) { + if (!allowRaw) { + addError(errorName, errorNode); return Collections.emptyList(); } - if (mvps.length == 1 && Arrays.equals("value".toCharArray(), mvps[0].name)) { - content = mvps[0].value; + content = value; + } else if (!(value instanceof Annotation)) { + addError(errorName, errorNode); + return Collections.emptyList(); + } else { + Annotation atDummyIdentifier = (Annotation) value; + if (atDummyIdentifier.type instanceof SingleTypeReference && isAllValidOnXCharacters(((SingleTypeReference) atDummyIdentifier.type).token)) { + if (atDummyIdentifier instanceof MarkerAnnotation) { + return Collections.emptyList(); + } else if (atDummyIdentifier instanceof NormalAnnotation) { + MemberValuePair[] mvps = ((NormalAnnotation) atDummyIdentifier).memberValuePairs; + if (mvps == null || mvps.length == 0) { + return Collections.emptyList(); + } + if (mvps.length == 1 && Arrays.equals("value".toCharArray(), mvps[0].name)) { + content = mvps[0].value; + } + } else if (atDummyIdentifier instanceof SingleMemberAnnotation) { + content = ((SingleMemberAnnotation) atDummyIdentifier).memberValue; + } else { + addError(errorName, errorNode); + return Collections.emptyList(); + } + } else { + if (allowRaw) { + content = atDummyIdentifier; + } else { + addError(errorName, errorNode); + return Collections.emptyList(); + } } } - if (atDummyIdentifier instanceof SingleMemberAnnotation) { - content = ((SingleMemberAnnotation) atDummyIdentifier).memberValue; - } - if (content == null) { - errorNode.addError("The correct format is " + errorName + "@__({@SomeAnnotation, @SomeOtherAnnotation}))"); + addError(errorName, errorNode); return Collections.emptyList(); } @@ -1684,13 +1822,13 @@ public class EclipseHandlerUtil { if (expressions != null) for (Expression ex : expressions) { if (ex instanceof Annotation) result.add((Annotation) ex); else { - errorNode.addError("The correct format is " + errorName + "@__({@SomeAnnotation, @SomeOtherAnnotation}))"); + addError(errorName, errorNode); return Collections.emptyList(); } } return result; } else { - errorNode.addError("The correct format is " + errorName + "@__({@SomeAnnotation, @SomeOtherAnnotation}))"); + addError(errorName, errorNode); return Collections.emptyList(); } } @@ -1716,4 +1854,12 @@ public class EclipseHandlerUtil { private static long[] copy(long[] array) { return array == null ? null : array.clone(); } + + public static boolean isDirectDescendantOfObject(EclipseNode typeNode) { + if (!(typeNode.get() instanceof TypeDeclaration)) throw new IllegalArgumentException("not a type node"); + TypeDeclaration typeDecl = (TypeDeclaration) typeNode.get(); + if (typeDecl.superclass == null) return true; + String p = typeDecl.superclass.toString(); + return p.equals("Object") || p.equals("java.lang.Object"); + } } diff --git a/src/core/lombok/eclipse/handlers/EclipseSingularsRecipes.java b/src/core/lombok/eclipse/handlers/EclipseSingularsRecipes.java index 4cb41d4f..b9b9e07d 100644 --- a/src/core/lombok/eclipse/handlers/EclipseSingularsRecipes.java +++ b/src/core/lombok/eclipse/handlers/EclipseSingularsRecipes.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 The Project Lombok Authors. + * Copyright (C) 2015-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 @@ -45,6 +45,9 @@ import org.eclipse.jdt.internal.compiler.ast.OperatorIds; import org.eclipse.jdt.internal.compiler.ast.ParameterizedQualifiedTypeReference; import org.eclipse.jdt.internal.compiler.ast.ParameterizedSingleTypeReference; import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference; +import org.eclipse.jdt.internal.compiler.ast.Reference; +import org.eclipse.jdt.internal.compiler.ast.ReturnStatement; +import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference; import org.eclipse.jdt.internal.compiler.ast.Statement; import org.eclipse.jdt.internal.compiler.ast.ThisReference; @@ -53,6 +56,7 @@ import org.eclipse.jdt.internal.compiler.ast.Wildcard; import org.eclipse.jdt.internal.compiler.lookup.ClassScope; import org.eclipse.jdt.internal.compiler.lookup.MethodScope; import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; +import org.eclipse.jdt.internal.compiler.lookup.TypeIds; import lombok.core.LombokImmutableList; import lombok.core.SpiLoadUtil; @@ -60,6 +64,14 @@ import lombok.core.TypeLibrary; import lombok.eclipse.EclipseNode; public class EclipseSingularsRecipes { + public interface TypeReferenceMaker { + TypeReference make(); + } + + public interface StatementMaker { + Statement make(); + } + private static final EclipseSingularsRecipes INSTANCE = new EclipseSingularsRecipes(); private final Map<String, EclipseSingularizer> singularizers = new HashMap<String, EclipseSingularizer>(); private final TypeLibrary singularizableTypes = new TypeLibrary(); @@ -132,6 +144,10 @@ public class EclipseSingularsRecipes { } } + public ASTNode getSource() { + return source; + } + public EclipseNode getAnnotation() { return annotation; } @@ -211,8 +227,36 @@ public class EclipseSingularsRecipes { } public abstract List<EclipseNode> generateFields(SingularData data, EclipseNode builderType); - public abstract void generateMethods(SingularData data, EclipseNode builderType, boolean fluent, boolean chain); - public abstract void appendBuildCode(SingularData data, EclipseNode builderType, List<Statement> statements, char[] targetVariableName); + + /** + * Generates the singular, plural, and clear methods for the given {@link SingularData}. + * Uses the given {@code builderType} as return type if {@code chain == true}, {@code void} otherwise. + * If you need more control over the return type and value, use + * {@link #generateMethods(SingularData, boolean, EclipseNode, boolean, TypeReferenceMaker, StatementMaker)}. + */ + public void generateMethods(SingularData data, boolean deprecate, final EclipseNode builderType, boolean fluent, final boolean chain) { + TypeReferenceMaker returnTypeMaker = new TypeReferenceMaker() { + @Override public TypeReference make() { + return chain ? cloneSelfType(builderType) : TypeReference.baseTypeReference(TypeIds.T_void, 0); + } + }; + + StatementMaker returnStatementMaker = new StatementMaker() { + @Override public ReturnStatement make() { + return chain ? new ReturnStatement(new ThisReference(0, 0), 0, 0) : null; + } + }; + + generateMethods(data, deprecate, builderType, fluent, returnTypeMaker, returnStatementMaker); + } + + /** + * Generates the singular, plural, and clear methods for the given {@link SingularData}. + * Uses the given {@code returnTypeMaker} and {@code returnStatementMaker} for the generated methods. + */ + public abstract void generateMethods(SingularData data, boolean deprecate, EclipseNode builderType, boolean fluent, TypeReferenceMaker returnTypeMaker, StatementMaker returnStatementMaker); + + public abstract void appendBuildCode(SingularData data, EclipseNode builderType, List<Statement> statements, char[] targetVariableName, String builderVariable); public boolean requiresCleaning() { try { @@ -303,16 +347,16 @@ public class EclipseSingularsRecipes { private static final char[] SIZE_TEXT = new char[] {'s', 'i', 'z', 'e'}; /** Generates 'this.<em>name</em>.size()' as an expression; if nullGuard is true, it's this.name == null ? 0 : this.name.size(). */ - protected Expression getSize(EclipseNode builderType, char[] name, boolean nullGuard) { + protected Expression getSize(EclipseNode builderType, char[] name, boolean nullGuard, String builderVariable) { MessageSend invoke = new MessageSend(); - ThisReference thisRef = new ThisReference(0, 0); + Reference thisRef = getBuilderReference(builderVariable); FieldReference thisDotName = new FieldReference(name, 0L); thisDotName.receiver = thisRef; invoke.receiver = thisDotName; invoke.selector = SIZE_TEXT; if (!nullGuard) return invoke; - ThisReference cdnThisRef = new ThisReference(0, 0); + Reference cdnThisRef = getBuilderReference(builderVariable); FieldReference cdnThisDotName = new FieldReference(name, 0L); cdnThisDotName.receiver = cdnThisRef; NullLiteral nullLiteral = new NullLiteral(0, 0); @@ -341,5 +385,14 @@ public class EclipseSingularsRecipes { return new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, NULL_POSS); } + + /** @return a {@code SingleNameReference} to the builder in the variable <code>builderVariable</code>. If {@ code builderVariable == "this"}, a {@code ThisReference} is returned. */ + protected static Reference getBuilderReference(String builderVariable) { + if ("this".equals(builderVariable)) { + return new ThisReference(0, 0); + } else { + return new SingleNameReference(builderVariable.toCharArray(), 0); + } + } } } diff --git a/src/core/lombok/eclipse/handlers/HandleBuilder.java b/src/core/lombok/eclipse/handlers/HandleBuilder.java index 4e0c4218..e1b1af26 100644 --- a/src/core/lombok/eclipse/handlers/HandleBuilder.java +++ b/src/core/lombok/eclipse/handlers/HandleBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2015 The Project Lombok Authors. + * Copyright (C) 2013-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 @@ -38,6 +38,7 @@ import org.eclipse.jdt.internal.compiler.ast.Annotation; import org.eclipse.jdt.internal.compiler.ast.Argument; import org.eclipse.jdt.internal.compiler.ast.Assignment; import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; +import org.eclipse.jdt.internal.compiler.ast.ConditionalExpression; import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration; import org.eclipse.jdt.internal.compiler.ast.Expression; import org.eclipse.jdt.internal.compiler.ast.FalseLiteral; @@ -73,8 +74,10 @@ import lombok.Builder; import lombok.Builder.ObtainVia; import lombok.ConfigurationKeys; import lombok.Singular; +import lombok.ToString; import lombok.core.AST.Kind; import lombok.core.handlers.HandlerUtil; +import lombok.core.handlers.InclusionExclusionUtils.Included; import lombok.core.AnnotationValues; import lombok.core.HandlerPriority; import lombok.eclipse.Eclipse; @@ -88,6 +91,8 @@ import lombok.experimental.NonFinal; @ProviderFor(EclipseAnnotationHandler.class) @HandlerPriority(-1024) //-2^10; to ensure we've picked up @FieldDefault's changes (-2048) but @Value hasn't removed itself yet (-512), so that we can error on presence of it on the builder classes. public class HandleBuilder extends EclipseAnnotationHandler<Builder> { + private HandleConstructor handleConstructor = new HandleConstructor(); + private static final char[] CLEAN_FIELD_NAME = "$lombokUnclean".toCharArray(); private static final char[] CLEAN_METHOD_NAME = "$lombokClean".toCharArray(); @@ -102,9 +107,12 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { TypeReference type; char[] rawName; char[] name; + char[] nameOfDefaultProvider; + char[] nameOfSetFlag; SingularData singularData; ObtainVia obtainVia; EclipseNode obtainViaNode; + EclipseNode originalFieldNode; List<EclipseNode> createdFields = new ArrayList<EclipseNode>(); } @@ -127,6 +135,16 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { return true; } + private static final char[] DEFAULT_PREFIX = {'$', 'd', 'e', 'f', 'a', 'u', 'l', 't', '$'}; + private static final char[] SET_PREFIX = {'$', 's', 'e', 't'}; + + private static final char[] prefixWith(char[] prefix, char[] name) { + char[] out = new char[prefix.length + name.length]; + System.arraycopy(prefix, 0, out, 0, prefix.length); + System.arraycopy(name, 0, out, prefix.length, name.length); + return out; + } + @Override public void handle(AnnotationValues<Builder> annotation, Annotation ast, EclipseNode annotationNode) { long p = (long) ast.sourceStart << 32 | ast.sourceEnd; @@ -171,25 +189,47 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { TypeDeclaration td = (TypeDeclaration) tdParent.get(); List<EclipseNode> allFields = new ArrayList<EclipseNode>(); - @SuppressWarnings("deprecation") - boolean valuePresent = (hasAnnotation(lombok.Value.class, parent) || hasAnnotation(lombok.experimental.Value.class, parent)); - for (EclipseNode fieldNode : HandleConstructor.findAllFields(tdParent)) { + boolean valuePresent = (hasAnnotation(lombok.Value.class, parent) || hasAnnotation("lombok.experimental.Value", parent)); + for (EclipseNode fieldNode : HandleConstructor.findAllFields(tdParent, true)) { FieldDeclaration fd = (FieldDeclaration) fieldNode.get(); - // final fields with an initializer cannot be written to, so they can't be 'builderized'. Unfortunately presence of @Value makes - // non-final fields final, but @Value's handler hasn't done this yet, so we have to do this math ourselves. - // Value will only skip making a field final if it has an explicit @NonFinal annotation, so we check for that. - if (fd.initialization != null && valuePresent && !hasAnnotation(NonFinal.class, fieldNode)) continue; + EclipseNode isDefault = findAnnotation(Builder.Default.class, fieldNode); + boolean isFinal = ((fd.modifiers & ClassFileConstants.AccFinal) != 0) || (valuePresent && !hasAnnotation(NonFinal.class, fieldNode)); + BuilderFieldData bfd = new BuilderFieldData(); bfd.rawName = fieldNode.getName().toCharArray(); bfd.name = removePrefixFromField(fieldNode); bfd.type = fd.type; bfd.singularData = getSingularData(fieldNode, ast); + bfd.originalFieldNode = fieldNode; + + if (bfd.singularData != null && isDefault != null) { + isDefault.addError("@Builder.Default and @Singular cannot be mixed."); + isDefault = null; + } + + if (fd.initialization == null && isDefault != null) { + isDefault.addWarning("@Builder.Default requires an initializing expression (' = something;')."); + isDefault = null; + } + + if (fd.initialization != null && isDefault == null) { + if (isFinal) continue; + fieldNode.addWarning("@Builder will ignore the initializing expression entirely. If you want the initializing expression to serve as default, add @Builder.Default. If it is not supposed to be settable during building, make the field final."); + } + + if (isDefault != null) { + bfd.nameOfDefaultProvider = prefixWith(DEFAULT_PREFIX, bfd.name); + bfd.nameOfSetFlag = prefixWith(bfd.name, SET_PREFIX); + + MethodDeclaration md = generateDefaultProvider(bfd.nameOfDefaultProvider, td.typeParameters, fieldNode, ast); + if (md != null) injectMethod(tdParent, md); + } addObtainVia(bfd, fieldNode); builderFields.add(bfd); allFields.add(fieldNode); } - new HandleConstructor().generateConstructor(tdParent, AccessLevel.PACKAGE, allFields, false, null, SkipIfConstructorExists.I_AM_BUILDER, null, + handleConstructor.generateConstructor(tdParent, AccessLevel.PACKAGE, allFields, false, null, SkipIfConstructorExists.I_AM_BUILDER, Collections.<Annotation>emptyList(), annotationNode); returnType = namePlusTypeParamsToTypeReference(td.name, td.typeParameters, p); @@ -334,6 +374,7 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { bfd.name = arg.name; bfd.type = arg.type; bfd.singularData = getSingularData(param, ast); + bfd.originalFieldNode = param; addObtainVia(bfd, param); builderFields.add(bfd); } @@ -395,7 +436,7 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { if (constructorExists(builderType) == MemberExistsResult.NOT_EXISTS) { ConstructorDeclaration cd = HandleConstructor.createConstructor( - AccessLevel.PACKAGE, builderType, Collections.<EclipseNode>emptyList(), false, null, + AccessLevel.PACKAGE, builderType, Collections.<EclipseNode>emptyList(), false, annotationNode, Collections.<Annotation>emptyList()); if (cd != null) injectMethod(builderType, cd); } @@ -405,14 +446,16 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { } if (methodExists(buildMethodName, builderType, -1) == MemberExistsResult.NOT_EXISTS) { - MethodDeclaration md = generateBuildMethod(isStatic, buildMethodName, nameOfStaticBuilderMethod, returnType, builderFields, builderType, thrownExceptions, addCleaning, ast); + MethodDeclaration md = generateBuildMethod(tdParent, isStatic, buildMethodName, nameOfStaticBuilderMethod, returnType, builderFields, builderType, thrownExceptions, addCleaning, ast); if (md != null) injectMethod(builderType, md); } if (methodExists("toString", builderType, 0) == MemberExistsResult.NOT_EXISTS) { - List<EclipseNode> fieldNodes = new ArrayList<EclipseNode>(); + List<Included<EclipseNode, ToString.Include>> fieldNodes = new ArrayList<Included<EclipseNode, ToString.Include>>(); for (BuilderFieldData bfd : builderFields) { - fieldNodes.addAll(bfd.createdFields); + for (EclipseNode f : bfd.createdFields) { + fieldNodes.add(new Included<EclipseNode, ToString.Include>(f, null, true)); + } } MethodDeclaration md = HandleToString.createToString(builderType, fieldNodes, true, false, ast, FieldAccess.ALWAYS_FIELD); if (md != null) injectMethod(builderType, md); @@ -514,7 +557,7 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { return decl; } - public MethodDeclaration generateBuildMethod(boolean isStatic, String name, char[] staticName, TypeReference returnType, List<BuilderFieldData> builderFields, EclipseNode type, TypeReference[] thrownExceptions, boolean addCleaning, ASTNode source) { + public MethodDeclaration generateBuildMethod(EclipseNode tdParent, boolean isStatic, String name, char[] staticName, TypeReference returnType, List<BuilderFieldData> builderFields, EclipseNode type, TypeReference[] thrownExceptions, boolean addCleaning, ASTNode source) { MethodDeclaration out = new MethodDeclaration(((CompilationUnitDeclaration) type.top().get()).compilationResult); out.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; List<Statement> statements = new ArrayList<Statement>(); @@ -530,13 +573,27 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { for (BuilderFieldData bfd : builderFields) { if (bfd.singularData != null && bfd.singularData.getSingularizer() != null) { - bfd.singularData.getSingularizer().appendBuildCode(bfd.singularData, type, statements, bfd.name); + bfd.singularData.getSingularizer().appendBuildCode(bfd.singularData, type, statements, bfd.name, "this"); } } List<Expression> args = new ArrayList<Expression>(); for (BuilderFieldData bfd : builderFields) { - args.add(new SingleNameReference(bfd.name, 0L)); + if (bfd.nameOfSetFlag != null) { + MessageSend inv = new MessageSend(); + inv.sourceStart = source.sourceStart; + inv.sourceEnd = source.sourceEnd; + inv.receiver = new SingleNameReference(((TypeDeclaration) tdParent.get()).name, 0L); + inv.selector = bfd.nameOfDefaultProvider; + inv.typeArguments = typeParameterNames(((TypeDeclaration) type.get()).typeParameters); + + args.add(new ConditionalExpression( + new SingleNameReference(bfd.nameOfSetFlag, 0L), + new SingleNameReference(bfd.name, 0L), + inv)); + } else { + args.add(new SingleNameReference(bfd.name, 0L)); + } } if (addCleaning) { @@ -563,14 +620,8 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { invoke.receiver = new SingleNameReference(type.up().getName().toCharArray(), 0); else invoke.receiver = new QualifiedThisReference(new SingleTypeReference(type.up().getName().toCharArray(), 0) , 0, 0); - TypeParameter[] tps = ((TypeDeclaration) type.get()).typeParameters; - if (tps != null) { - TypeReference[] trs = new TypeReference[tps.length]; - for (int i = 0; i < trs.length; i++) { - trs[i] = new SingleTypeReference(tps[i].name, 0); - } - invoke.typeArguments = trs; - } + + invoke.typeArguments = typeParameterNames(((TypeDeclaration) type.get()).typeParameters); invoke.arguments = args.isEmpty() ? null : args.toArray(new Expression[args.size()]); if (returnType instanceof SingleTypeReference && Arrays.equals(TypeConstants.VOID, ((SingleTypeReference) returnType).token)) { statements.add(invoke); @@ -583,6 +634,33 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { return out; } + private TypeReference[] typeParameterNames(TypeParameter[] typeParameters) { + if (typeParameters == null) return null; + + TypeReference[] trs = new TypeReference[typeParameters.length]; + for (int i = 0; i < trs.length; i++) { + trs[i] = new SingleTypeReference(typeParameters[i].name, 0); + } + return trs; + } + + public static MethodDeclaration generateDefaultProvider(char[] methodName, TypeParameter[] typeParameters, EclipseNode fieldNode, ASTNode source) { + int pS = source.sourceStart, pE = source.sourceEnd; + + MethodDeclaration out = new MethodDeclaration(((CompilationUnitDeclaration) fieldNode.top().get()).compilationResult); + out.typeParameters = copyTypeParams(typeParameters, source); + out.selector = methodName; + out.modifiers = ClassFileConstants.AccPrivate | ClassFileConstants.AccStatic; + out.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + FieldDeclaration fd = (FieldDeclaration) fieldNode.get(); + out.returnType = copyType(fd.type, source); + out.statements = new Statement[] {new ReturnStatement(fd.initialization, pS, pE)}; + fd.initialization = null; + + out.traverse(new SetGeneratedByVisitor(source), ((TypeDeclaration) fieldNode.up().get()).scope); + return out; + } + public MethodDeclaration generateBuilderMethod(boolean isStatic, String builderMethodName, String builderClassName, EclipseNode type, TypeParameter[] typeParams, ASTNode source) { int pS = source.sourceStart, pE = source.sourceEnd; long p = (long) pS << 32 | pE; @@ -608,25 +686,34 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { if (child.getKind() == Kind.FIELD) existing.add(child); } - top: for (BuilderFieldData bfd : builderFields) { if (bfd.singularData != null && bfd.singularData.getSingularizer() != null) { bfd.createdFields.addAll(bfd.singularData.getSingularizer().generateFields(bfd.singularData, builderType)); } else { + EclipseNode field = null, setFlag = null; for (EclipseNode exists : existing) { char[] n = ((FieldDeclaration) exists.get()).name; - if (Arrays.equals(n, bfd.name)) { - bfd.createdFields.add(exists); - continue top; - } + if (Arrays.equals(n, bfd.name)) field = exists; + if (bfd.nameOfSetFlag != null && Arrays.equals(n, bfd.nameOfSetFlag)) setFlag = exists; } - FieldDeclaration fd = new FieldDeclaration(bfd.name, 0, 0); - fd.bits |= Eclipse.ECLIPSE_DO_NOT_TOUCH_FLAG; - fd.modifiers = ClassFileConstants.AccPrivate; - fd.type = copyType(bfd.type); - fd.traverse(new SetGeneratedByVisitor(source), (MethodScope) null); - bfd.createdFields.add(injectFieldAndMarkGenerated(builderType, fd)); + if (field == null) { + FieldDeclaration fd = new FieldDeclaration(bfd.name, 0, 0); + fd.bits |= Eclipse.ECLIPSE_DO_NOT_TOUCH_FLAG; + fd.modifiers = ClassFileConstants.AccPrivate; + fd.type = copyType(bfd.type); + fd.traverse(new SetGeneratedByVisitor(source), (MethodScope) null); + field = injectFieldAndMarkGenerated(builderType, fd); + } + if (setFlag == null && bfd.nameOfSetFlag != null) { + FieldDeclaration fd = new FieldDeclaration(bfd.nameOfSetFlag, 0, 0); + fd.bits |= Eclipse.ECLIPSE_DO_NOT_TOUCH_FLAG; + fd.modifiers = ClassFileConstants.AccPrivate; + fd.type = TypeReference.baseTypeReference(TypeIds.T_boolean, 0); + fd.traverse(new SetGeneratedByVisitor(source), (MethodScope) null); + injectFieldAndMarkGenerated(builderType, fd); + } + bfd.createdFields.add(field); } } } @@ -634,14 +721,15 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { private static final AbstractMethodDeclaration[] EMPTY = {}; public void makeSetterMethodsForBuilder(EclipseNode builderType, BuilderFieldData bfd, EclipseNode sourceNode, boolean fluent, boolean chain) { + boolean deprecate = isFieldDeprecated(bfd.originalFieldNode); if (bfd.singularData == null || bfd.singularData.getSingularizer() == null) { - makeSimpleSetterMethodForBuilder(builderType, bfd.createdFields.get(0), sourceNode, fluent, chain); + makeSimpleSetterMethodForBuilder(builderType, deprecate, bfd.createdFields.get(0), bfd.nameOfSetFlag, sourceNode, fluent, chain); } else { - bfd.singularData.getSingularizer().generateMethods(bfd.singularData, builderType, fluent, chain); + bfd.singularData.getSingularizer().generateMethods(bfd.singularData, deprecate, builderType, fluent, chain); } } - private void makeSimpleSetterMethodForBuilder(EclipseNode builderType, EclipseNode fieldNode, EclipseNode sourceNode, boolean fluent, boolean chain) { + private void makeSimpleSetterMethodForBuilder(EclipseNode builderType, boolean deprecate, EclipseNode fieldNode, char[] nameOfSetFlag, EclipseNode sourceNode, boolean fluent, boolean chain) { TypeDeclaration td = (TypeDeclaration) builderType.get(); AbstractMethodDeclaration[] existing = td.methods; if (existing == null) existing = EMPTY; @@ -652,12 +740,12 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { for (int i = 0; i < len; i++) { if (!(existing[i] instanceof MethodDeclaration)) continue; char[] existingName = existing[i].selector; - if (Arrays.equals(name, existingName)) return; + if (Arrays.equals(name, existingName) && !isTolerate(fieldNode, existing[i])) return; } String setterName = fluent ? fieldNode.getName() : HandlerUtil.buildAccessorName("set", fieldNode.getName()); - MethodDeclaration setter = HandleSetter.createSetter(td, fieldNode, setterName, chain, ClassFileConstants.AccPublic, + MethodDeclaration setter = HandleSetter.createSetter(td, deprecate, fieldNode, setterName, nameOfSetFlag, chain, ClassFileConstants.AccPublic, sourceNode, Collections.<Annotation>emptyList(), Collections.<Annotation>emptyList()); injectMethod(builderType, setter); } diff --git a/src/core/lombok/eclipse/handlers/HandleBuilderDefault.java b/src/core/lombok/eclipse/handlers/HandleBuilderDefault.java new file mode 100644 index 00000000..d0c597fd --- /dev/null +++ b/src/core/lombok/eclipse/handlers/HandleBuilderDefault.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2017-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.eclipse.handlers; + +import static lombok.eclipse.handlers.EclipseHandlerUtil.*; +import org.eclipse.jdt.internal.compiler.ast.Annotation; +import org.mangosdk.spi.ProviderFor; + +import lombok.Builder; +import lombok.core.AST.Kind; +import lombok.core.AnnotationValues; +import lombok.core.HandlerPriority; +import lombok.eclipse.EclipseAnnotationHandler; +import lombok.eclipse.EclipseNode; +import lombok.experimental.SuperBuilder; + +@ProviderFor(EclipseAnnotationHandler.class) +@HandlerPriority(-1025) //HandleBuilder's level, minus one. +public class HandleBuilderDefault extends EclipseAnnotationHandler<Builder.Default> { + @Override public void handle(AnnotationValues<Builder.Default> annotation, Annotation ast, EclipseNode annotationNode) { + EclipseNode annotatedField = annotationNode.up(); + if (annotatedField.getKind() != Kind.FIELD) return; + EclipseNode classWithAnnotatedField = annotatedField.up(); + if (!hasAnnotation(Builder.class, classWithAnnotatedField) && !hasAnnotation("lombok.experimental.Builder", classWithAnnotatedField) + && !hasAnnotation(SuperBuilder.class, classWithAnnotatedField)) { + annotationNode.addWarning("@Builder.Default requires @Builder or @SuperBuilder on the class for it to mean anything."); + } + } +} diff --git a/src/core/lombok/eclipse/handlers/HandleConstructor.java b/src/core/lombok/eclipse/handlers/HandleConstructor.java index 85d1d4ed..cab847e6 100644 --- a/src/core/lombok/eclipse/handlers/HandleConstructor.java +++ b/src/core/lombok/eclipse/handlers/HandleConstructor.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2015 The Project Lombok Authors. + * Copyright (C) 2010-2017 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 @@ -42,12 +42,15 @@ import lombok.core.AST.Kind; import lombok.core.AnnotationValues; import lombok.eclipse.EclipseAnnotationHandler; import lombok.eclipse.EclipseNode; +import lombok.eclipse.handlers.EclipseHandlerUtil.MemberExistsResult; import org.eclipse.jdt.internal.compiler.ast.ASTNode; +import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; import org.eclipse.jdt.internal.compiler.ast.AllocationExpression; import org.eclipse.jdt.internal.compiler.ast.Annotation; import org.eclipse.jdt.internal.compiler.ast.Argument; import org.eclipse.jdt.internal.compiler.ast.ArrayInitializer; +import org.eclipse.jdt.internal.compiler.ast.ArrayTypeReference; import org.eclipse.jdt.internal.compiler.ast.Assignment; import org.eclipse.jdt.internal.compiler.ast.CharLiteral; import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; @@ -79,6 +82,8 @@ import org.mangosdk.spi.ProviderFor; public class HandleConstructor { @ProviderFor(EclipseAnnotationHandler.class) public static class HandleNoArgsConstructor extends EclipseAnnotationHandler<NoArgsConstructor> { + private HandleConstructor handleConstructor = new HandleConstructor(); + @Override public void handle(AnnotationValues<NoArgsConstructor> annotation, Annotation ast, EclipseNode annotationNode) { handleFlagUsage(annotationNode, ConfigurationKeys.NO_ARGS_CONSTRUCTOR_FLAG_USAGE, "@NoArgsConstructor", ConfigurationKeys.ANY_CONSTRUCTOR_FLAG_USAGE, "any @xArgsConstructor"); @@ -92,14 +97,16 @@ public class HandleConstructor { boolean force = ann.force(); List<EclipseNode> fields = force ? findFinalFields(typeNode) : Collections.<EclipseNode>emptyList(); - List<Annotation> onConstructor = unboxAndRemoveAnnotationParameter(ast, "onConstructor", "@NoArgsConstructor(onConstructor=", annotationNode); + List<Annotation> onConstructor = unboxAndRemoveAnnotationParameter(ast, "onConstructor", "@NoArgsConstructor(onConstructor", annotationNode); - new HandleConstructor().generateConstructor(typeNode, level, fields, force, staticName, SkipIfConstructorExists.NO, null, onConstructor, annotationNode); + handleConstructor.generateConstructor(typeNode, level, fields, force, staticName, SkipIfConstructorExists.NO, onConstructor, annotationNode); } } @ProviderFor(EclipseAnnotationHandler.class) public static class HandleRequiredArgsConstructor extends EclipseAnnotationHandler<RequiredArgsConstructor> { + private HandleConstructor handleConstructor = new HandleConstructor(); + @Override public void handle(AnnotationValues<RequiredArgsConstructor> annotation, Annotation ast, EclipseNode annotationNode) { handleFlagUsage(annotationNode, ConfigurationKeys.REQUIRED_ARGS_CONSTRUCTOR_FLAG_USAGE, "@RequiredArgsConstructor", ConfigurationKeys.ANY_CONSTRUCTOR_FLAG_USAGE, "any @xArgsConstructor"); @@ -109,18 +116,15 @@ public class HandleConstructor { AccessLevel level = ann.access(); if (level == AccessLevel.NONE) return; String staticName = ann.staticName(); - Boolean suppressConstructorProperties = null; if (annotation.isExplicit("suppressConstructorProperties")) { - @SuppressWarnings("deprecation") - boolean suppress = ann.suppressConstructorProperties(); - suppressConstructorProperties = suppress; + annotationNode.addError("This deprecated feature is no longer supported. Remove it; you can create a lombok.config file with 'lombok.anyConstructor.suppressConstructorProperties = true'."); } - List<Annotation> onConstructor = unboxAndRemoveAnnotationParameter(ast, "onConstructor", "@RequiredArgsConstructor(onConstructor=", annotationNode); + List<Annotation> onConstructor = unboxAndRemoveAnnotationParameter(ast, "onConstructor", "@RequiredArgsConstructor(onConstructor", annotationNode); - new HandleConstructor().generateConstructor( + handleConstructor.generateConstructor( typeNode, level, findRequiredFields(typeNode), false, staticName, SkipIfConstructorExists.NO, - suppressConstructorProperties, onConstructor, annotationNode); + onConstructor, annotationNode); } } @@ -146,14 +150,17 @@ public class HandleConstructor { } static List<EclipseNode> findAllFields(EclipseNode typeNode) { + return findAllFields(typeNode, false); + } + + static List<EclipseNode> findAllFields(EclipseNode typeNode, boolean evenFinalInitialized) { List<EclipseNode> fields = new ArrayList<EclipseNode>(); for (EclipseNode child : typeNode.down()) { if (child.getKind() != Kind.FIELD) continue; FieldDeclaration fieldDecl = (FieldDeclaration) child.get(); if (!filterField(fieldDecl)) continue; - // Skip initialized final fields. - if (((fieldDecl.modifiers & ClassFileConstants.AccFinal) != 0) && fieldDecl.initialization != null) continue; + if (!evenFinalInitialized && ((fieldDecl.modifiers & ClassFileConstants.AccFinal) != 0) && fieldDecl.initialization != null) continue; fields.add(child); } @@ -162,6 +169,8 @@ public class HandleConstructor { @ProviderFor(EclipseAnnotationHandler.class) public static class HandleAllArgsConstructor extends EclipseAnnotationHandler<AllArgsConstructor> { + private HandleConstructor handleConstructor = new HandleConstructor(); + @Override public void handle(AnnotationValues<AllArgsConstructor> annotation, Annotation ast, EclipseNode annotationNode) { handleFlagUsage(annotationNode, ConfigurationKeys.ALL_ARGS_CONSTRUCTOR_FLAG_USAGE, "@AllArgsConstructor", ConfigurationKeys.ANY_CONSTRUCTOR_FLAG_USAGE, "any @xArgsConstructor"); @@ -171,18 +180,15 @@ public class HandleConstructor { AccessLevel level = ann.access(); if (level == AccessLevel.NONE) return; String staticName = ann.staticName(); - Boolean suppressConstructorProperties = null; if (annotation.isExplicit("suppressConstructorProperties")) { - @SuppressWarnings("deprecation") - boolean suppress = ann.suppressConstructorProperties(); - suppressConstructorProperties = suppress; + annotationNode.addError("This deprecated feature is no longer supported. Remove it; you can create a lombok.config file with 'lombok.anyConstructor.suppressConstructorProperties = true'."); } - List<Annotation> onConstructor = unboxAndRemoveAnnotationParameter(ast, "onConstructor", "@AllArgsConstructor(onConstructor=", annotationNode); + List<Annotation> onConstructor = unboxAndRemoveAnnotationParameter(ast, "onConstructor", "@AllArgsConstructor(onConstructor", annotationNode); - new HandleConstructor().generateConstructor( + handleConstructor.generateConstructor( typeNode, level, findAllFields(typeNode), false, staticName, SkipIfConstructorExists.NO, - suppressConstructorProperties, onConstructor, annotationNode); + onConstructor, annotationNode); } } @@ -200,28 +206,45 @@ public class HandleConstructor { return true; } + public enum SkipIfConstructorExists { + YES, NO, I_AM_BUILDER; + } + + public void generateExtraNoArgsConstructor(EclipseNode typeNode, EclipseNode sourceNode) { + if (!isDirectDescendantOfObject(typeNode)) return; + + Boolean v = typeNode.getAst().readConfiguration(ConfigurationKeys.NO_ARGS_CONSTRUCTOR_EXTRA_PRIVATE); + if (v == null || !v) return; + + List<EclipseNode> fields = findFinalFields(typeNode); + generate(typeNode, AccessLevel.PRIVATE, fields, true, null, SkipIfConstructorExists.NO, Collections.<Annotation>emptyList(), sourceNode, true); + } + public void generateRequiredArgsConstructor( EclipseNode typeNode, AccessLevel level, String staticName, SkipIfConstructorExists skipIfConstructorExists, List<Annotation> onConstructor, EclipseNode sourceNode) { - generateConstructor(typeNode, level, findRequiredFields(typeNode), false, staticName, skipIfConstructorExists, null, onConstructor, sourceNode); + generateConstructor(typeNode, level, findRequiredFields(typeNode), false, staticName, skipIfConstructorExists, onConstructor, sourceNode); } public void generateAllArgsConstructor( EclipseNode typeNode, AccessLevel level, String staticName, SkipIfConstructorExists skipIfConstructorExists, List<Annotation> onConstructor, EclipseNode sourceNode) { - generateConstructor(typeNode, level, findAllFields(typeNode), false, staticName, skipIfConstructorExists, null, onConstructor, sourceNode); - } - - public enum SkipIfConstructorExists { - YES, NO, I_AM_BUILDER; + generateConstructor(typeNode, level, findAllFields(typeNode), false, staticName, skipIfConstructorExists, onConstructor, sourceNode); } public void generateConstructor( EclipseNode typeNode, AccessLevel level, List<EclipseNode> fields, boolean allToDefault, String staticName, SkipIfConstructorExists skipIfConstructorExists, - Boolean suppressConstructorProperties, List<Annotation> onConstructor, EclipseNode sourceNode) { + List<Annotation> onConstructor, EclipseNode sourceNode) { + generate(typeNode, level, fields, allToDefault, staticName, skipIfConstructorExists, onConstructor, sourceNode, false); + } + + public void generate( + EclipseNode typeNode, AccessLevel level, List<EclipseNode> fields, boolean allToDefault, String staticName, SkipIfConstructorExists skipIfConstructorExists, + List<Annotation> onConstructor, EclipseNode sourceNode, boolean noArgs) { + ASTNode source = sourceNode.get(); boolean staticConstrRequired = staticName != null && !staticName.equals(""); @@ -253,9 +276,11 @@ public class HandleConstructor { } } + if (noArgs && noArgsConstructorExists(typeNode)) return; + ConstructorDeclaration constr = createConstructor( staticConstrRequired ? AccessLevel.PRIVATE : level, typeNode, fields, allToDefault, - suppressConstructorProperties, sourceNode, onConstructor); + sourceNode, onConstructor); injectMethod(typeNode, constr); if (staticConstrRequired) { MethodDeclaration staticConstr = createStaticConstructor(level, staticName, typeNode, allToDefault ? Collections.<EclipseNode>emptyList() : fields, source); @@ -263,6 +288,28 @@ public class HandleConstructor { } } + private static boolean noArgsConstructorExists(EclipseNode node) { + node = EclipseHandlerUtil.upToTypeNode(node); + + if (node != null && node.get() instanceof TypeDeclaration) { + TypeDeclaration typeDecl = (TypeDeclaration)node.get(); + if (typeDecl.methods != null) for (AbstractMethodDeclaration def : typeDecl.methods) { + if (def instanceof ConstructorDeclaration) { + Argument[] arguments = ((ConstructorDeclaration) def).arguments; + if (arguments == null || arguments.length == 0) return true; + } + } + } + + for (EclipseNode child : node.down()) { + if (annotationTypeMatches(NoArgsConstructor.class, child)) return true; + if (annotationTypeMatches(RequiredArgsConstructor.class, child) && findRequiredFields(node).isEmpty()) return true; + if (annotationTypeMatches(AllArgsConstructor.class, child) && findAllFields(node).isEmpty()) return true; + } + + return false; + } + private static final char[][] JAVA_BEANS_CONSTRUCTORPROPERTIES = new char[][] { "java".toCharArray(), "beans".toCharArray(), "ConstructorProperties".toCharArray() }; public static Annotation[] createConstructorProperties(ASTNode source, Collection<EclipseNode> fields) { if (fields.isEmpty()) return null; @@ -295,9 +342,9 @@ public class HandleConstructor { return new Annotation[] { ann }; } - public static ConstructorDeclaration createConstructor( + @SuppressWarnings("deprecation") public static ConstructorDeclaration createConstructor( AccessLevel level, EclipseNode type, Collection<EclipseNode> fields, boolean allToDefault, - Boolean suppressConstructorProperties, EclipseNode sourceNode, List<Annotation> onConstructor) { + EclipseNode sourceNode, List<Annotation> onConstructor) { ASTNode source = sourceNode.get(); TypeDeclaration typeDeclaration = ((TypeDeclaration) type.get()); @@ -307,12 +354,13 @@ public class HandleConstructor { if (isEnum) level = AccessLevel.PRIVATE; - if (suppressConstructorProperties == null) { - if (fields.isEmpty()) { - suppressConstructorProperties = false; - } else { - suppressConstructorProperties = Boolean.TRUE.equals(type.getAst().readConfiguration(ConfigurationKeys.ANY_CONSTRUCTOR_SUPPRESS_CONSTRUCTOR_PROPERTIES)); - } + boolean addConstructorProperties; + if (fields.isEmpty()) { + addConstructorProperties = false; + } else { + Boolean v = type.getAst().readConfiguration(ConfigurationKeys.ANY_CONSTRUCTOR_ADD_CONSTRUCTOR_PROPERTIES); + addConstructorProperties = v != null ? v.booleanValue() : + Boolean.FALSE.equals(type.getAst().readConfiguration(ConfigurationKeys.ANY_CONSTRUCTOR_SUPPRESS_CONSTRUCTOR_PROPERTIES)); } ConstructorDeclaration constructor = new ConstructorDeclaration(((CompilationUnitDeclaration) type.top().get()).compilationResult); @@ -353,7 +401,7 @@ public class HandleConstructor { Annotation[] nonNulls = findAnnotations(field, NON_NULL_PATTERN); Annotation[] nullables = findAnnotations(field, NULLABLE_PATTERN); if (nonNulls.length != 0) { - Statement nullCheck = generateNullCheck(field, sourceNode); + Statement nullCheck = generateNullCheck(parameter, sourceNode); if (nullCheck != null) nullChecks.add(nullCheck); } parameter.annotations = copyAnnotations(source, nonNulls, nullables); @@ -367,7 +415,7 @@ public class HandleConstructor { /* Generate annotations that must be put on the generated method, and attach them. */ { Annotation[] constructorProperties = null; - if (!allToDefault && !suppressConstructorProperties && level != AccessLevel.PRIVATE && level != AccessLevel.PACKAGE && !isLocalType(type)) { + if (!allToDefault && addConstructorProperties && !isLocalType(type)) { constructorProperties = createConstructorProperties(source, fields); } @@ -381,6 +429,8 @@ public class HandleConstructor { } private static Expression getDefaultExpr(TypeReference type, int s, int e) { + boolean array = type instanceof ArrayTypeReference; + if (array) return new NullLiteral(s, e); char[] lastToken = type.getLastToken(); if (Arrays.equals(TypeConstants.BOOLEAN, lastToken)) return new FalseLiteral(s, e); if (Arrays.equals(TypeConstants.CHAR, lastToken)) return new CharLiteral(new char[] {'\'', '\\', '0', '\''}, s, e); diff --git a/src/core/lombok/eclipse/handlers/HandleData.java b/src/core/lombok/eclipse/handlers/HandleData.java index 0ff65a47..4011890d 100644 --- a/src/core/lombok/eclipse/handlers/HandleData.java +++ b/src/core/lombok/eclipse/handlers/HandleData.java @@ -43,6 +43,12 @@ import org.mangosdk.spi.ProviderFor; */ @ProviderFor(EclipseAnnotationHandler.class) public class HandleData extends EclipseAnnotationHandler<Data> { + private HandleGetter handleGetter = new HandleGetter(); + private HandleSetter handleSetter = new HandleSetter(); + private HandleEqualsAndHashCode handleEqualsAndHashCode = new HandleEqualsAndHashCode(); + private HandleToString handleToString = new HandleToString(); + private HandleConstructor handleConstructor = new HandleConstructor(); + @Override public void handle(AnnotationValues<Data> annotation, Annotation ast, EclipseNode annotationNode) { handleFlagUsage(annotationNode, ConfigurationKeys.DATA_FLAG_USAGE, "@Data"); @@ -66,12 +72,13 @@ public class HandleData extends EclipseAnnotationHandler<Data> { //for whatever reason, though you can find callers of that one by focusing on the class name itself //and hitting 'find callers'. - new HandleGetter().generateGetterForType(typeNode, annotationNode, AccessLevel.PUBLIC, true); - new HandleSetter().generateSetterForType(typeNode, annotationNode, AccessLevel.PUBLIC, true); - new HandleEqualsAndHashCode().generateEqualsAndHashCodeForType(typeNode, annotationNode); - new HandleToString().generateToStringForType(typeNode, annotationNode); - new HandleConstructor().generateRequiredArgsConstructor( + handleGetter.generateGetterForType(typeNode, annotationNode, AccessLevel.PUBLIC, true, Collections.<Annotation>emptyList()); + handleSetter.generateSetterForType(typeNode, annotationNode, AccessLevel.PUBLIC, true, Collections.<Annotation>emptyList(), Collections.<Annotation>emptyList()); + handleEqualsAndHashCode.generateEqualsAndHashCodeForType(typeNode, annotationNode); + handleToString.generateToStringForType(typeNode, annotationNode); + handleConstructor.generateRequiredArgsConstructor( typeNode, AccessLevel.PUBLIC, ann.staticConstructor(), SkipIfConstructorExists.YES, Collections.<Annotation>emptyList(), annotationNode); + handleConstructor.generateExtraNoArgsConstructor(typeNode, annotationNode); } } diff --git a/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java b/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java index a56eee89..c99b9b5f 100644 --- a/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java +++ b/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2015 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 @@ -38,12 +38,13 @@ import lombok.ConfigurationKeys; import lombok.EqualsAndHashCode; import lombok.core.AST.Kind; import lombok.core.handlers.HandlerUtil; +import lombok.core.handlers.InclusionExclusionUtils; +import lombok.core.handlers.InclusionExclusionUtils.Included; import lombok.core.AnnotationValues; 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; @@ -57,7 +58,6 @@ import org.eclipse.jdt.internal.compiler.ast.ConditionalExpression; import org.eclipse.jdt.internal.compiler.ast.EqualExpression; import org.eclipse.jdt.internal.compiler.ast.Expression; import org.eclipse.jdt.internal.compiler.ast.FalseLiteral; -import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; import org.eclipse.jdt.internal.compiler.ast.IfStatement; import org.eclipse.jdt.internal.compiler.ast.InstanceOfExpression; import org.eclipse.jdt.internal.compiler.ast.IntLiteral; @@ -67,6 +67,7 @@ import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; import org.eclipse.jdt.internal.compiler.ast.NameReference; import org.eclipse.jdt.internal.compiler.ast.NullLiteral; import org.eclipse.jdt.internal.compiler.ast.OperatorIds; +import org.eclipse.jdt.internal.compiler.ast.ParameterizedQualifiedTypeReference; import org.eclipse.jdt.internal.compiler.ast.ParameterizedSingleTypeReference; import org.eclipse.jdt.internal.compiler.ast.QualifiedNameReference; import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference; @@ -98,59 +99,41 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler<EqualsAndH public static final Set<String> BUILT_IN_TYPES = Collections.unmodifiableSet(new HashSet<String>(Arrays.asList( "byte", "short", "int", "long", "char", "boolean", "double", "float"))); - public void checkForBogusFieldNames(EclipseNode type, AnnotationValues<EqualsAndHashCode> annotation) { - if (annotation.isExplicit("exclude")) { - for (int i : createListOfNonExistentFields(Arrays.asList(annotation.getInstance().exclude()), type, true, true)) { - 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 generateEqualsAndHashCodeForType(EclipseNode typeNode, EclipseNode errorNode) { - if (hasAnnotation(EqualsAndHashCode.class, typeNode)) { - //The annotation will make it happen, so we can skip it. - return; - } - - generateMethods(typeNode, errorNode, null, null, null, false, FieldAccess.GETTER, new ArrayList<Annotation>()); - } - @Override public void handle(AnnotationValues<EqualsAndHashCode> annotation, Annotation ast, EclipseNode annotationNode) { handleFlagUsage(annotationNode, ConfigurationKeys.EQUALS_AND_HASH_CODE_FLAG_USAGE, "@EqualsAndHashCode"); EqualsAndHashCode ann = annotation.getInstance(); - List<String> excludes = Arrays.asList(ann.exclude()); - List<String> includes = Arrays.asList(ann.of()); - EclipseNode typeNode = annotationNode.up(); + List<Included<EclipseNode, EqualsAndHashCode.Include>> members = InclusionExclusionUtils.handleEqualsAndHashCodeMarking(annotationNode.up(), annotation, annotationNode); + if (members == null) return; - List<Annotation> onParam = unboxAndRemoveAnnotationParameter(ast, "onParam", "@EqualsAndHashCode(onParam=", annotationNode); - checkForBogusFieldNames(typeNode, annotation); + List<Annotation> onParam = unboxAndRemoveAnnotationParameter(ast, "onParam", "@EqualsAndHashCode(onParam", annotationNode); 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.EQUALS_AND_HASH_CODE_DO_NOT_USE_GETTERS); boolean doNotUseGetters = annotation.isExplicit("doNotUseGetters") || doNotUseGettersConfiguration == null ? ann.doNotUseGetters() : doNotUseGettersConfiguration; FieldAccess fieldAccess = doNotUseGetters ? FieldAccess.PREFER_FIELD : FieldAccess.GETTER; - generateMethods(typeNode, annotationNode, excludes, includes, callSuper, true, fieldAccess, onParam); + generateMethods(annotationNode.up(), annotationNode, members, callSuper, true, fieldAccess, onParam); } - public void generateMethods(EclipseNode typeNode, EclipseNode errorNode, List<String> excludes, List<String> includes, - Boolean callSuper, boolean whineIfExists, FieldAccess fieldAccess, List<Annotation> onParam) { - assert excludes == null || includes == null; + public void generateEqualsAndHashCodeForType(EclipseNode typeNode, EclipseNode errorNode) { + if (hasAnnotation(EqualsAndHashCode.class, typeNode)) { + //The annotation will make it happen, so we can skip it. + return; + } + + List<Included<EclipseNode, EqualsAndHashCode.Include>> members = InclusionExclusionUtils.handleEqualsAndHashCodeMarking(typeNode, null, null); + + Boolean doNotUseGettersConfiguration = typeNode.getAst().readConfiguration(ConfigurationKeys.EQUALS_AND_HASH_CODE_DO_NOT_USE_GETTERS); + FieldAccess access = doNotUseGettersConfiguration == null || !doNotUseGettersConfiguration ? FieldAccess.GETTER : FieldAccess.PREFER_FIELD; + + generateMethods(typeNode, errorNode, members, null, false, access, new ArrayList<Annotation>()); + } + + public void generateMethods(EclipseNode typeNode, EclipseNode errorNode, List<Included<EclipseNode, EqualsAndHashCode.Include>> members, + Boolean callSuper, boolean whineIfExists, FieldAccess fieldAccess, List<Annotation> onParam) { TypeDeclaration typeDecl = null; @@ -168,18 +151,13 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler<EqualsAndH if (callSuper == null) { try { - callSuper = ((Boolean)EqualsAndHashCode.class.getMethod("callSuper").getDefaultValue()).booleanValue(); + callSuper = ((Boolean) EqualsAndHashCode.class.getMethod("callSuper").getDefaultValue()).booleanValue(); } catch (Exception ignore) { throw new InternalError("Lombok bug - this cannot happen - can't find callSuper field in EqualsAndHashCode annotation."); } } - boolean isDirectDescendantOfObject = true; - - if (typeDecl.superclass != null) { - String p = typeDecl.superclass.toString(); - isDirectDescendantOfObject = p.equals("Object") || p.equals("java.lang.Object"); - } + boolean isDirectDescendantOfObject = isDirectDescendantOfObject(typeNode); if (isDirectDescendantOfObject && callSuper) { errorNode.addError("Generating equals/hashCode with a supercall to java.lang.Object is pointless."); @@ -205,27 +183,6 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler<EqualsAndH } } - List<EclipseNode> nodesForEquality = 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))) nodesForEquality.add(child); - } - } else { - for (EclipseNode child : typeNode.down()) { - if (child.getKind() != Kind.FIELD) continue; - FieldDeclaration fieldDecl = (FieldDeclaration) child.get(); - if (!filterField(fieldDecl)) continue; - - //Skip transient fields. - if ((fieldDecl.modifiers & ClassFileConstants.AccTransient) != 0) continue; - //Skip excluded fields. - if (excludes != null && excludes.contains(new String(fieldDecl.name))) continue; - nodesForEquality.add(child); - } - } - boolean isFinal = (typeDecl.modifiers & ClassFileConstants.AccFinal) != 0; boolean needsCanEqual = !isFinal || !isDirectDescendantOfObject; MemberExistsResult equalsExists = methodExists("equals", typeNode, 1); @@ -254,7 +211,7 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler<EqualsAndH //fallthrough } - MethodDeclaration equalsMethod = createEquals(typeNode, nodesForEquality, callSuper, errorNode.get(), fieldAccess, needsCanEqual, onParam); + MethodDeclaration equalsMethod = createEquals(typeNode, members, callSuper, errorNode.get(), fieldAccess, needsCanEqual, onParam); equalsMethod.traverse(new SetGeneratedByVisitor(errorNode.get()), ((TypeDeclaration)typeNode.get()).scope); injectMethod(typeNode, equalsMethod); @@ -264,17 +221,16 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler<EqualsAndH injectMethod(typeNode, canEqualMethod); } - MethodDeclaration hashCodeMethod = createHashCode(typeNode, nodesForEquality, callSuper, errorNode.get(), fieldAccess); + MethodDeclaration hashCodeMethod = createHashCode(typeNode, members, callSuper, errorNode.get(), fieldAccess); hashCodeMethod.traverse(new SetGeneratedByVisitor(errorNode.get()), ((TypeDeclaration)typeNode.get()).scope); injectMethod(typeNode, hashCodeMethod); } - public MethodDeclaration createHashCode(EclipseNode type, Collection<EclipseNode> fields, boolean callSuper, ASTNode source, FieldAccess fieldAccess) { + public MethodDeclaration createHashCode(EclipseNode type, Collection<Included<EclipseNode, EqualsAndHashCode.Include>> members, boolean callSuper, ASTNode source, FieldAccess fieldAccess) { int pS = source.sourceStart, pE = source.sourceEnd; long p = (long)pS << 32 | pE; - MethodDeclaration method = new MethodDeclaration( - ((CompilationUnitDeclaration) type.top().get()).compilationResult); + MethodDeclaration method = new MethodDeclaration(((CompilationUnitDeclaration) type.top().get()).compilationResult); setGeneratedBy(method, source); method.modifiers = toEclipseModifier(AccessLevel.PUBLIC); @@ -291,11 +247,11 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler<EqualsAndH List<Statement> statements = new ArrayList<Statement>(); - final boolean isEmpty = fields.isEmpty(); + final boolean isEmpty = members.isEmpty(); /* final int PRIME = X; */ { - /* Without fields, PRIME isn't used, and that would trigger a 'local variable not used' warning. */ - if (!isEmpty || callSuper) { + /* Without members, PRIME isn't used, as that would trigger a 'local variable not used' warning. */ + if (!isEmpty) { LocalDeclaration primeDecl = new LocalDeclaration(PRIME, pS, pE); setGeneratedBy(primeDecl, source); primeDecl.modifiers |= Modifier.FINAL; @@ -307,31 +263,38 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler<EqualsAndH } } - /* int result = 1; */ { - LocalDeclaration resultDecl = new LocalDeclaration(RESULT, pS, pE); + /* int result = ... */ { + LocalDeclaration resultDecl = new LocalDeclaration(RESULT, pS, pE); setGeneratedBy(resultDecl, source); - resultDecl.initialization = makeIntLiteral("1".toCharArray(), source); + final Expression init; + if (callSuper) { + /* ... super.hashCode(); */ + MessageSend callToSuper = new MessageSend(); + setGeneratedBy(callToSuper, source); + callToSuper.sourceStart = pS; callToSuper.sourceEnd = pE; + callToSuper.receiver = new SuperReference(pS, pE); + setGeneratedBy(callToSuper.receiver, source); + callToSuper.selector = "hashCode".toCharArray(); + init = callToSuper; + } else { + /* ... 1; */ + init = makeIntLiteral("1".toCharArray(), source); + } + resultDecl.initialization = init; resultDecl.type = TypeReference.baseTypeReference(TypeIds.T_int, 0); resultDecl.type.sourceStart = pS; resultDecl.type.sourceEnd = pE; setGeneratedBy(resultDecl.type, source); statements.add(resultDecl); } - if (callSuper) { - MessageSend callToSuper = new MessageSend(); - setGeneratedBy(callToSuper, source); - callToSuper.sourceStart = pS; callToSuper.sourceEnd = pE; - callToSuper.receiver = new SuperReference(pS, pE); - setGeneratedBy(callToSuper.receiver, source); - callToSuper.selector = "hashCode".toCharArray(); - statements.add(createResultCalculation(source, callToSuper)); - } - - for (EclipseNode field : fields) { - TypeReference fType = getFieldType(field, fieldAccess); - char[] dollarFieldName = ("$" + field.getName()).toCharArray(); + for (Included<EclipseNode, EqualsAndHashCode.Include> member : members) { + EclipseNode memberNode = member.getNode(); + boolean isMethod = memberNode.getKind() == Kind.METHOD; + + TypeReference fType = getFieldType(memberNode, fieldAccess); + char[] dollarFieldName = ((isMethod ? "$$" : "$") + memberNode.getName()).toCharArray(); char[] token = fType.getLastToken(); - Expression fieldAccessor = createFieldAccessor(field, fieldAccess, source); + Expression fieldAccessor = isMethod ? createMethodAccessor(memberNode, source) : createFieldAccessor(memberNode, fieldAccess, source); if (fType.dimensions() == 0 && token != null) { if (Arrays.equals(TypeConstants.BOOLEAN, token)) { /* booleanField ? X : Y */ @@ -458,17 +421,46 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler<EqualsAndH return assignment; } - public TypeReference createTypeReference(EclipseNode type, long p) { + /** + * @param type Type to 'copy' into a typeref + * @param p position + * @param addWildcards If false, all generics are cut off. If true, replaces all genericparams with a ?. + * @return + */ + public TypeReference createTypeReference(EclipseNode type, long p, ASTNode source, boolean addWildcards) { + int pS = source.sourceStart; int pE = source.sourceEnd; List<String> list = new ArrayList<String>(); + List<Integer> genericsCount = addWildcards ? new ArrayList<Integer>() : null; + list.add(type.getName()); + if (addWildcards) genericsCount.add(arraySizeOf(((TypeDeclaration) type.get()).typeParameters)); + boolean staticContext = (((TypeDeclaration) type.get()).modifiers & ClassFileConstants.AccStatic) != 0; EclipseNode tNode = type.up(); + if (!staticContext && tNode.getKind() == Kind.TYPE && (((TypeDeclaration) tNode.get()).modifiers & ClassFileConstants.AccInterface) != 0) staticContext = true; + while (tNode != null && tNode.getKind() == Kind.TYPE) { list.add(tNode.getName()); + if (addWildcards) genericsCount.add(staticContext ? 0 : arraySizeOf(((TypeDeclaration) tNode.get()).typeParameters)); + if (!staticContext) staticContext = (((TypeDeclaration) tNode.get()).modifiers & Modifier.STATIC) != 0; tNode = tNode.up(); + if (!staticContext && tNode.getKind() == Kind.TYPE && (((TypeDeclaration) tNode.get()).modifiers & ClassFileConstants.AccInterface) != 0) staticContext = true; } Collections.reverse(list); + if (addWildcards) Collections.reverse(genericsCount); + + if (list.size() == 1) { + if (!addWildcards || genericsCount.get(0) == 0) { + return new SingleTypeReference(list.get(0).toCharArray(), p); + } else { + return new ParameterizedSingleTypeReference(list.get(0).toCharArray(), wildcardify(pS, pE, source, genericsCount.get(0)), 0, p); + } + } + + if (addWildcards) { + addWildcards = false; + for (int i : genericsCount) if (i > 0) addWildcards = true; + } - if (list.size() == 1) return new SingleTypeReference(list.get(0).toCharArray(), p); long[] ps = new long[list.size()]; char[][] tokens = new char[list.size()][]; for (int i = 0; i < list.size(); i++) { @@ -476,16 +468,33 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler<EqualsAndH tokens[i] = list.get(i).toCharArray(); } - return new QualifiedTypeReference(tokens, ps); + if (!addWildcards) return new QualifiedTypeReference(tokens, ps); + TypeReference[][] typeArgs2 = new TypeReference[tokens.length][]; + for (int i = 0; i < tokens.length; i++) typeArgs2[i] = wildcardify(pS, pE, source, genericsCount.get(i)); + return new ParameterizedQualifiedTypeReference(tokens, typeArgs2, 0, ps); + } + + private TypeReference[] wildcardify(int pS, int pE, ASTNode source, int count) { + if (count == 0) return null; + TypeReference[] typeArgs = new TypeReference[count]; + for (int i = 0; i < count; i++) { + typeArgs[i] = new Wildcard(Wildcard.UNBOUND); + typeArgs[i].sourceStart = pS; typeArgs[i].sourceEnd = pE; + setGeneratedBy(typeArgs[i], source); + } + + return typeArgs; } - public MethodDeclaration createEquals(EclipseNode type, Collection<EclipseNode> fields, boolean callSuper, ASTNode source, FieldAccess fieldAccess, boolean needsCanEqual, List<Annotation> onParam) { + private int arraySizeOf(Object[] arr) { + return arr == null ? 0 : arr.length; + } + + public MethodDeclaration createEquals(EclipseNode type, Collection<Included<EclipseNode, EqualsAndHashCode.Include>> members, boolean callSuper, ASTNode source, FieldAccess fieldAccess, boolean needsCanEqual, List<Annotation> onParam) { int pS = source.sourceStart; int pE = source.sourceEnd; long p = (long)pS << 32 | pE; - TypeDeclaration typeDecl = (TypeDeclaration)type.get(); - MethodDeclaration method = new MethodDeclaration( - ((CompilationUnitDeclaration) type.top().get()).compilationResult); + MethodDeclaration method = new MethodDeclaration(((CompilationUnitDeclaration) type.top().get()).compilationResult); setGeneratedBy(method, source); method.modifiers = toEclipseModifier(AccessLevel.PUBLIC); method.returnType = TypeReference.baseTypeReference(TypeIds.T_boolean, 0); @@ -528,7 +537,7 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler<EqualsAndH SingleNameReference oRef = new SingleNameReference(new char[] { 'o' }, p); setGeneratedBy(oRef, source); - TypeReference typeReference = createTypeReference(type, p); + TypeReference typeReference = createTypeReference(type, p, source, false); setGeneratedBy(typeReference, source); InstanceOfExpression instanceOf = new InstanceOfExpression(oRef, typeReference); @@ -551,30 +560,15 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler<EqualsAndH char[] otherName = "other".toCharArray(); - /* MyType<?> other = (MyType<?>) o; */ { - if (!fields.isEmpty() || needsCanEqual) { + /* Outer.Inner.MyType<?> other = (Outer.Inner.MyType<?>) o; */ { + if (!members.isEmpty() || needsCanEqual) { LocalDeclaration other = new LocalDeclaration(otherName, pS, pE); other.modifiers |= ClassFileConstants.AccFinal; setGeneratedBy(other, source); - char[] typeName = typeDecl.name; - TypeReference targetType; - if (typeDecl.typeParameters == null || typeDecl.typeParameters.length == 0) { - targetType = new SingleTypeReference(typeName, p); - setGeneratedBy(targetType, source); - other.type = new SingleTypeReference(typeName, p); - setGeneratedBy(other.type, source); - } else { - TypeReference[] typeArgs = new TypeReference[typeDecl.typeParameters.length]; - for (int i = 0; i < typeArgs.length; i++) { - typeArgs[i] = new Wildcard(Wildcard.UNBOUND); - typeArgs[i].sourceStart = pS; typeArgs[i].sourceEnd = pE; - setGeneratedBy(typeArgs[i], source); - } - targetType = new ParameterizedSingleTypeReference(typeName, typeArgs, 0, p); - setGeneratedBy(targetType, source); - other.type = new ParameterizedSingleTypeReference(typeName, copyTypes(typeArgs, source), 0, p); - setGeneratedBy(other.type, source); - } + TypeReference targetType = createTypeReference(type, p, source, true); + setGeneratedBy(targetType, source); + other.type = createTypeReference(type, p, source, true); + setGeneratedBy(other.type, source); NameReference oRef = new SingleNameReference(new char[] { 'o' }, p); setGeneratedBy(oRef, source); other.initialization = makeCastExpression(oRef, targetType, source); @@ -636,11 +630,14 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler<EqualsAndH statements.add(ifSuperEquals); } - for (EclipseNode field : fields) { - TypeReference fType = getFieldType(field, fieldAccess); + for (Included<EclipseNode, EqualsAndHashCode.Include> member : members) { + EclipseNode memberNode = member.getNode(); + boolean isMethod = memberNode.getKind() == Kind.METHOD; + + TypeReference fType = getFieldType(memberNode, fieldAccess); char[] token = fType.getLastToken(); - Expression thisFieldAccessor = createFieldAccessor(field, fieldAccess, source); - Expression otherFieldAccessor = createFieldAccessor(field, fieldAccess, source, otherName); + Expression thisFieldAccessor = isMethod ? createMethodAccessor(memberNode, source) : createFieldAccessor(memberNode, fieldAccess, source); + Expression otherFieldAccessor = isMethod ? createMethodAccessor(memberNode, source, otherName) : createFieldAccessor(memberNode, fieldAccess, source, otherName); if (fType.dimensions() == 0 && token != null) { if (Arrays.equals(TypeConstants.FLOAT, token)) { @@ -660,9 +657,9 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler<EqualsAndH } else /* objects */ { /* final java.lang.Object this$fieldName = this.fieldName; */ /* final java.lang.Object other$fieldName = other.fieldName; */ - /* if (this$fieldName == null ? other$fieldName != null : !this$fieldName.equals(other$fieldName)) return false;; */ - char[] thisDollarFieldName = ("this$" + field.getName()).toCharArray(); - char[] otherDollarFieldName = ("other$" + field.getName()).toCharArray(); + /* if (this$fieldName == null ? other$fieldName != null : !this$fieldName.equals(other$fieldName)) return false; */ + char[] thisDollarFieldName = ("this" + (isMethod ? "$$" : "$") + memberNode.getName()).toCharArray(); + char[] otherDollarFieldName = ("other" + (isMethod ? "$$" : "$") + memberNode.getName()).toCharArray(); statements.add(createLocalDeclaration(source, thisDollarFieldName, generateQualifiedTypeRef(source, TypeConstants.JAVA_LANG_OBJECT), thisFieldAccessor)); statements.add(createLocalDeclaration(source, otherDollarFieldName, generateQualifiedTypeRef(source, TypeConstants.JAVA_LANG_OBJECT), otherFieldAccessor)); @@ -675,7 +672,6 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler<EqualsAndH setGeneratedBy(other1, source); SingleNameReference other2 = new SingleNameReference(otherDollarFieldName, p); setGeneratedBy(other2, source); - NullLiteral nullLiteral = new NullLiteral(pS, pE); setGeneratedBy(nullLiteral, source); @@ -738,7 +734,6 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler<EqualsAndH return method; } - public MethodDeclaration createCanEqual(EclipseNode type, ASTNode source, List<Annotation> onParam) { /* protected boolean canEqual(final java.lang.Object other) { * return other instanceof Outer.Inner.MyType; @@ -749,8 +744,7 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler<EqualsAndH char[] otherName = "other".toCharArray(); - MethodDeclaration method = new MethodDeclaration( - ((CompilationUnitDeclaration) type.top().get()).compilationResult); + MethodDeclaration method = new MethodDeclaration(((CompilationUnitDeclaration) type.top().get()).compilationResult); setGeneratedBy(method, source); method.modifiers = toEclipseModifier(AccessLevel.PROTECTED); method.returnType = TypeReference.baseTypeReference(TypeIds.T_boolean, 0); @@ -772,7 +766,7 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler<EqualsAndH SingleNameReference otherRef = new SingleNameReference(otherName, p); setGeneratedBy(otherRef, source); - TypeReference typeReference = createTypeReference(type, p); + TypeReference typeReference = createTypeReference(type, p, source, false); setGeneratedBy(typeReference, source); InstanceOfExpression instanceOf = new InstanceOfExpression(otherRef, typeReference); diff --git a/src/core/lombok/eclipse/handlers/HandleFieldDefaults.java b/src/core/lombok/eclipse/handlers/HandleFieldDefaults.java index 5ea5a210..702713fe 100644 --- a/src/core/lombok/eclipse/handlers/HandleFieldDefaults.java +++ b/src/core/lombok/eclipse/handlers/HandleFieldDefaults.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2014 The Project Lombok Authors. + * Copyright (C) 2012-2016 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 @@ -97,14 +97,16 @@ public class HandleFieldDefaults extends EclipseASTAdapter { if (level != null && level != AccessLevel.NONE) { if ((field.modifiers & (ClassFileConstants.AccPublic | ClassFileConstants.AccPrivate | ClassFileConstants.AccProtected)) == 0) { if (!hasAnnotation(PackagePrivate.class, fieldNode)) { - field.modifiers |= EclipseHandlerUtil.toEclipseModifier(level); + if ((field.modifiers & ClassFileConstants.AccStatic) == 0) { + field.modifiers |= EclipseHandlerUtil.toEclipseModifier(level); + } } } } if (makeFinal && (field.modifiers & ClassFileConstants.AccFinal) == 0) { if (!hasAnnotation(NonFinal.class, fieldNode)) { - if ((field.modifiers & ClassFileConstants.AccStatic) == 0 || field.initialization != null) { + if ((field.modifiers & ClassFileConstants.AccStatic) == 0) { field.modifiers |= ClassFileConstants.AccFinal; } } diff --git a/src/core/lombok/eclipse/handlers/HandleFieldNameConstants.java b/src/core/lombok/eclipse/handlers/HandleFieldNameConstants.java new file mode 100644 index 00000000..c3a28f7f --- /dev/null +++ b/src/core/lombok/eclipse/handlers/HandleFieldNameConstants.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2014-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.eclipse.handlers; + +import static lombok.core.handlers.HandlerUtil.handleExperimentalFlagUsage; +import static lombok.eclipse.handlers.EclipseHandlerUtil.*; + +import java.lang.reflect.Modifier; +import java.util.Collection; + +import lombok.AccessLevel; +import lombok.ConfigurationKeys; +import lombok.core.AST.Kind; +import lombok.core.AnnotationValues; +import lombok.core.handlers.HandlerUtil; +import lombok.eclipse.Eclipse; +import lombok.eclipse.EclipseAnnotationHandler; +import lombok.eclipse.EclipseNode; +import lombok.experimental.FieldNameConstants; + +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.StringLiteral; +import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; +import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; +import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; +import org.mangosdk.spi.ProviderFor; + +@ProviderFor(EclipseAnnotationHandler.class) +public class HandleFieldNameConstants extends EclipseAnnotationHandler<FieldNameConstants> { + public void generateFieldNameConstantsForType(EclipseNode typeNode, EclipseNode errorNode, AccessLevel level, String prefix, String suffix) { + TypeDeclaration typeDecl = null; + if (typeNode.get() instanceof TypeDeclaration) typeDecl = (TypeDeclaration) typeNode.get(); + + int modifiers = typeDecl == null ? 0 : typeDecl.modifiers; + boolean notAClass = (modifiers & (ClassFileConstants.AccInterface | ClassFileConstants.AccAnnotation)) != 0; + + if (typeDecl == null || notAClass) { + errorNode.addError("@FieldNameConstants is only supported on a class, an enum, or a field."); + return; + } + + for (EclipseNode field : typeNode.down()) { + if (fieldQualifiesForFieldNameConstantsGeneration(field)) generateFieldNameConstantsForField(field, errorNode.get(), level, prefix, suffix); + } + } + + private void generateFieldNameConstantsForField(EclipseNode fieldNode, ASTNode pos, AccessLevel level, String prefix, String suffix) { + if (hasAnnotation(FieldNameConstants.class, fieldNode)) return; + createFieldNameConstantsForField(level, prefix, suffix, fieldNode, fieldNode, pos, false); + } + + private boolean fieldQualifiesForFieldNameConstantsGeneration(EclipseNode field) { + if (field.getKind() != Kind.FIELD) return false; + FieldDeclaration fieldDecl = (FieldDeclaration) field.get(); + return filterField(fieldDecl); + } + + public void handle(AnnotationValues<FieldNameConstants> annotation, Annotation ast, EclipseNode annotationNode) { + handleExperimentalFlagUsage(annotationNode, ConfigurationKeys.FIELD_NAME_CONSTANTS_FLAG_USAGE, "@FieldNameConstants"); + + EclipseNode node = annotationNode.up(); + FieldNameConstants annotatationInstance = annotation.getInstance(); + AccessLevel level = annotatationInstance.level(); + String prefix = annotatationInstance.prefix(); + String suffix = annotatationInstance.suffix(); + if (prefix.equals(" CONFIG DEFAULT ")) prefix = annotationNode.getAst().readConfiguration(ConfigurationKeys.FIELD_NAME_CONSTANTS_PREFIX); + if (suffix.equals(" CONFIG DEFAULT ")) suffix = annotationNode.getAst().readConfiguration(ConfigurationKeys.FIELD_NAME_CONSTANTS_SUFFIX); + if (prefix == null) prefix = "FIELD_"; + if (suffix == null) suffix = ""; + if (node == null) return; + + switch (node.getKind()) { + case FIELD: + if (level != AccessLevel.NONE) createFieldNameConstantsForFields(level, prefix, suffix, annotationNode.upFromAnnotationToFields(), annotationNode, annotationNode.get(), true); + break; + case TYPE: + if (level == AccessLevel.NONE) { + annotationNode.addWarning("type-level '@FieldNameConstants' does not work with AccessLevel.NONE."); + return; + } + generateFieldNameConstantsForType(node, annotationNode, level, prefix, suffix); + break; + } + } + + private void createFieldNameConstantsForFields(AccessLevel level, String prefix, String suffix, Collection<EclipseNode> fieldNodes, EclipseNode errorNode, ASTNode source, boolean whineIfExists) { + for (EclipseNode fieldNode : fieldNodes) createFieldNameConstantsForField(level, prefix, suffix, fieldNode, errorNode, source, whineIfExists); + } + + private void createFieldNameConstantsForField(AccessLevel level, String prefix, String suffix, EclipseNode fieldNode, EclipseNode errorNode, ASTNode source, boolean whineIfExists) { + if (fieldNode.getKind() != Kind.FIELD) { + errorNode.addError("@FieldNameConstants is only supported on a class, an enum, or a field"); + return; + } + + FieldDeclaration field = (FieldDeclaration) fieldNode.get(); + String fieldName = new String(field.name); + String constantName = prefix + HandlerUtil.camelCaseToConstant(fieldName) + suffix; + if (constantName.equals(fieldName)) { + fieldNode.addWarning("Not generating constant for this field: The name of the constant would be equal to the name of this field."); + return; + } + + int pS = source.sourceStart, pE = source.sourceEnd; + long p = (long) pS << 32 | pE; + FieldDeclaration fieldConstant = new FieldDeclaration(constantName.toCharArray(), pS,pE); + fieldConstant.bits |= Eclipse.ECLIPSE_DO_NOT_TOUCH_FLAG; + fieldConstant.modifiers = toEclipseModifier(level) | Modifier.STATIC | Modifier.FINAL; + fieldConstant.type = new QualifiedTypeReference(TypeConstants.JAVA_LANG_STRING, new long[] {p,p,p}); + fieldConstant.initialization = new StringLiteral(field.name, pS, pE, 0); + injectField(fieldNode.up(), fieldConstant); + } +} diff --git a/src/core/lombok/eclipse/handlers/HandleGetter.java b/src/core/lombok/eclipse/handlers/HandleGetter.java index 14f2fb72..d0c2cc23 100644 --- a/src/core/lombok/eclipse/handlers/HandleGetter.java +++ b/src/core/lombok/eclipse/handlers/HandleGetter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2014 The Project Lombok Authors. + * Copyright (C) 2009-2016 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 @@ -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; @@ -80,7 +79,7 @@ import org.mangosdk.spi.ProviderFor; public class HandleGetter extends EclipseAnnotationHandler<Getter> { private static final Annotation[] EMPTY_ANNOTATIONS_ARRAY = new Annotation[0]; - public boolean generateGetterForType(EclipseNode typeNode, EclipseNode pos, AccessLevel level, boolean checkForTypeLevelGetter) { + public boolean generateGetterForType(EclipseNode typeNode, EclipseNode pos, AccessLevel level, boolean checkForTypeLevelGetter, List<Annotation> onMethod) { if (checkForTypeLevelGetter) { if (hasAnnotation(Getter.class, typeNode)) { //The annotation will make it happen, so we can skip it. @@ -100,12 +99,12 @@ public class HandleGetter extends EclipseAnnotationHandler<Getter> { } for (EclipseNode field : typeNode.down()) { - if (fieldQualifiesForGetterGeneration(field)) generateGetterForField(field, pos.get(), level, false); + if (fieldQualifiesForGetterGeneration(field)) generateGetterForField(field, pos.get(), level, false, onMethod); } return true; } - public boolean fieldQualifiesForGetterGeneration(EclipseNode field) { + public static boolean fieldQualifiesForGetterGeneration(EclipseNode field) { if (field.getKind() != Kind.FIELD) return false; FieldDeclaration fieldDecl = (FieldDeclaration) field.get(); return filterField(fieldDecl); @@ -123,13 +122,13 @@ public class HandleGetter extends EclipseAnnotationHandler<Getter> { * If not, the getter is still generated if it isn't already there, though there will not * be a warning if its already there. The default access level is used. */ - public void generateGetterForField(EclipseNode fieldNode, ASTNode pos, AccessLevel level, boolean lazy) { + public void generateGetterForField(EclipseNode fieldNode, ASTNode pos, AccessLevel level, boolean lazy, List<Annotation> onMethod) { if (hasAnnotation(Getter.class, fieldNode)) { //The annotation will make it happen, so we can skip it. return; } - createGetterForField(level, fieldNode, fieldNode, pos, false, lazy, Collections.<Annotation>emptyList()); + createGetterForField(level, fieldNode, fieldNode, pos, false, lazy, onMethod); } public void handle(AnnotationValues<Getter> annotation, Annotation ast, EclipseNode annotationNode) { @@ -148,18 +147,15 @@ public class HandleGetter extends EclipseAnnotationHandler<Getter> { if (node == null) return; - List<Annotation> onMethod = unboxAndRemoveAnnotationParameter(ast, "onMethod", "@Getter(onMethod=", annotationNode); + List<Annotation> onMethod = unboxAndRemoveAnnotationParameter(ast, "onMethod", "@Getter(onMethod", annotationNode); switch (node.getKind()) { case FIELD: createGetterForFields(level, annotationNode.upFromAnnotationToFields(), annotationNode, annotationNode.get(), true, lazy, onMethod); break; case TYPE: - if (!onMethod.isEmpty()) { - annotationNode.addError("'onMethod' is not supported for @Getter on a type."); - } if (lazy) annotationNode.addError("'lazy' is not supported for @Getter on a type."); - generateGetterForType(node, annotationNode, level, false); + generateGetterForType(node, annotationNode, level, false, onMethod); break; } } @@ -183,6 +179,10 @@ public class HandleGetter extends EclipseAnnotationHandler<Getter> { errorNode.addError("'lazy' requires the field to be private and final."); return; } + if ((field.modifiers & ClassFileConstants.AccTransient) != 0) { + errorNode.addError("'lazy' is not supported on transient fields."); + return; + } if (field.initialization == null) { errorNode.addError("'lazy' requires field initialization."); return; diff --git a/src/core/lombok/eclipse/handlers/HandleLog.java b/src/core/lombok/eclipse/handlers/HandleLog.java index c49030d8..8c7f7971 100644 --- a/src/core/lombok/eclipse/handlers/HandleLog.java +++ b/src/core/lombok/eclipse/handlers/HandleLog.java @@ -109,7 +109,7 @@ public class HandleLog { private static FieldDeclaration createField(LoggingFramework framework, Annotation source, ClassLiteralAccess loggingType, String logFieldName, boolean useStatic, String loggerTopic) { int pS = source.sourceStart, pE = source.sourceEnd; - long p = (long)pS << 32 | pE; + long p = (long) pS << 32 | pE; // private static final <loggerType> log = <factoryMethod>(<parameter>); FieldDeclaration fieldDecl = new FieldDeclaration(logFieldName.toCharArray(), 0, -1); @@ -126,13 +126,15 @@ public class HandleLog { factoryMethodCall.selector = framework.getLoggerFactoryMethodName().toCharArray(); Expression parameter; - if (loggerTopic == null || loggerTopic.trim().length() == 0) { + if (!framework.passTypeName) { + parameter = null; + } else if (loggerTopic == null || loggerTopic.trim().length() == 0) { parameter = framework.createFactoryParameter(loggingType, source); } else { parameter = new StringLiteral(loggerTopic.toCharArray(), pS, pE, 0); } - factoryMethodCall.arguments = new Expression[] { parameter }; + factoryMethodCall.arguments = parameter != null ? new Expression[] { parameter } : null; factoryMethodCall.nameSourcePosition = p; factoryMethodCall.sourceStart = pS; factoryMethodCall.sourceEnd = factoryMethodCall.statementEnd = pE; @@ -240,6 +242,17 @@ public class HandleLog { } } + /** + * Handles the {@link lombok.extern.flogger.Flogger} annotation for Eclipse. + */ + @ProviderFor(EclipseAnnotationHandler.class) + public static class HandleFloggerLog extends EclipseAnnotationHandler<lombok.extern.flogger.Flogger> { + @Override public void handle(AnnotationValues<lombok.extern.flogger.Flogger> annotation, Annotation source, EclipseNode annotationNode) { + handleFlagUsage(annotationNode, ConfigurationKeys.LOG_FLOGGER_FLAG_USAGE, "@Flogger", ConfigurationKeys.LOG_ANY_FLAG_USAGE, "any @Log"); + processAnnotation(LoggingFramework.FLOGGER, annotation, source, annotationNode, ""); + } + } + enum LoggingFramework { // private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(TargetType.class); COMMONS("org.apache.commons.logging.Log", "org.apache.commons.logging.LogFactory", "getLog", "@CommonsLog"), @@ -278,18 +291,31 @@ public class HandleLog { // private static final org.jboss.logging.Logger log = org.jboss.logging.Logger.getLogger(TargetType.class); JBOSSLOG("org.jboss.logging.Logger", "org.jboss.logging.Logger", "getLogger", "@JBossLog"), + + // private static final com.google.common.flogger.FluentLogger log = com.google.common.flogger.FluentLogger.forEnclosingClass(); + FLOGGER("com.google.common.flogger.FluentLogger", "com.google.common.flogger.FluentLogger", "forEnclosingClass", "@Flogger", false), ; private final String loggerTypeName; private final String loggerFactoryTypeName; private final String loggerFactoryMethodName; private final String annotationAsString; - + private final boolean passTypeName; + + LoggingFramework(String loggerTypeName, String loggerFactoryTypeName, String loggerFactoryMethodName, String annotationAsString, boolean passTypeName) { + this.loggerTypeName = loggerTypeName; + this.loggerFactoryTypeName = loggerFactoryTypeName; + this.loggerFactoryMethodName = loggerFactoryMethodName; + this.annotationAsString = annotationAsString; + this.passTypeName = passTypeName; + } + LoggingFramework(String loggerTypeName, String loggerFactoryTypeName, String loggerFactoryMethodName, String annotationAsString) { this.loggerTypeName = loggerTypeName; this.loggerFactoryTypeName = loggerFactoryTypeName; this.loggerFactoryMethodName = loggerFactoryMethodName; this.annotationAsString = annotationAsString; + this.passTypeName = true; } final String getAnnotationAsString() { @@ -308,7 +334,7 @@ public class HandleLog { return loggerFactoryMethodName; } - Expression createFactoryParameter(ClassLiteralAccess loggingType, Annotation source){ + Expression createFactoryParameter(ClassLiteralAccess loggingType, Annotation source) { TypeReference copy = copyType(loggingType.type, source); ClassLiteralAccess result = new ClassLiteralAccess(source.sourceEnd, copy); setGeneratedBy(result, source); diff --git a/src/core/lombok/eclipse/handlers/HandleSetter.java b/src/core/lombok/eclipse/handlers/HandleSetter.java index 1fcf751d..d4df0deb 100644 --- a/src/core/lombok/eclipse/handlers/HandleSetter.java +++ b/src/core/lombok/eclipse/handlers/HandleSetter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2014 The Project Lombok Authors. + * Copyright (C) 2009-2017 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,11 +24,12 @@ package lombok.eclipse.handlers; import static lombok.core.handlers.HandlerUtil.*; import static lombok.eclipse.Eclipse.*; import static lombok.eclipse.handlers.EclipseHandlerUtil.*; +import static lombok.eclipse.handlers.EclipseHandlerUtil.toAllSetterNames; +import static lombok.eclipse.handlers.EclipseHandlerUtil.toSetterName; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.List; import lombok.AccessLevel; @@ -38,7 +39,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 org.eclipse.jdt.internal.compiler.ast.ASTNode; import org.eclipse.jdt.internal.compiler.ast.Annotation; @@ -52,6 +52,7 @@ import org.eclipse.jdt.internal.compiler.ast.ReturnStatement; import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; import org.eclipse.jdt.internal.compiler.ast.Statement; import org.eclipse.jdt.internal.compiler.ast.ThisReference; +import org.eclipse.jdt.internal.compiler.ast.TrueLiteral; import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; import org.eclipse.jdt.internal.compiler.ast.TypeReference; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; @@ -63,7 +64,7 @@ import org.mangosdk.spi.ProviderFor; */ @ProviderFor(EclipseAnnotationHandler.class) public class HandleSetter extends EclipseAnnotationHandler<Setter> { - public boolean generateSetterForType(EclipseNode typeNode, EclipseNode pos, AccessLevel level, boolean checkForTypeLevelSetter) { + public boolean generateSetterForType(EclipseNode typeNode, EclipseNode pos, AccessLevel level, boolean checkForTypeLevelSetter, List<Annotation> onMethod, List<Annotation> onParam) { if (checkForTypeLevelSetter) { if (hasAnnotation(Setter.class, typeNode)) { //The annotation will make it happen, so we can skip it. @@ -90,7 +91,7 @@ public class HandleSetter extends EclipseAnnotationHandler<Setter> { //Skip final fields. if ((fieldDecl.modifiers & ClassFileConstants.AccFinal) != 0) continue; - generateSetterForField(field, pos, level); + generateSetterForField(field, pos, level, onMethod, onParam); } return true; } @@ -107,39 +108,30 @@ public class HandleSetter extends EclipseAnnotationHandler<Setter> { * If not, the setter is still generated if it isn't already there, though there will not * be a warning if its already there. The default access level is used. */ - public void generateSetterForField(EclipseNode fieldNode, EclipseNode sourceNode, AccessLevel level) { + public void generateSetterForField(EclipseNode fieldNode, EclipseNode sourceNode, AccessLevel level, List<Annotation> onMethod, List<Annotation> onParam) { if (hasAnnotation(Setter.class, fieldNode)) { //The annotation will make it happen, so we can skip it. return; } - - List<Annotation> empty = Collections.emptyList(); - - createSetterForField(level, fieldNode, sourceNode, false, empty, empty); + createSetterForField(level, fieldNode, sourceNode, false, onMethod, onParam); } - public void handle(AnnotationValues<Setter> annotation, Annotation ast, EclipseNode annotationNode) { + @Override public void handle(AnnotationValues<Setter> annotation, Annotation ast, EclipseNode annotationNode) { handleFlagUsage(annotationNode, ConfigurationKeys.SETTER_FLAG_USAGE, "@Setter"); EclipseNode node = annotationNode.up(); AccessLevel level = annotation.getInstance().value(); if (level == AccessLevel.NONE || node == null) return; - List<Annotation> onMethod = unboxAndRemoveAnnotationParameter(ast, "onMethod", "@Setter(onMethod=", annotationNode); - List<Annotation> onParam = unboxAndRemoveAnnotationParameter(ast, "onParam", "@Setter(onParam=", annotationNode); + List<Annotation> onMethod = unboxAndRemoveAnnotationParameter(ast, "onMethod", "@Setter(onMethod", annotationNode); + List<Annotation> onParam = unboxAndRemoveAnnotationParameter(ast, "onParam", "@Setter(onParam", annotationNode); switch (node.getKind()) { case FIELD: createSetterForFields(level, annotationNode.upFromAnnotationToFields(), annotationNode, true, onMethod, onParam); break; case TYPE: - if (!onMethod.isEmpty()) { - annotationNode.addError("'onMethod' is not supported for @Setter on a type."); - } - if (!onParam.isEmpty()) { - annotationNode.addError("'onParam' is not supported for @Setter on a type."); - } - generateSetterForType(node, annotationNode, level, false); + generateSetterForType(node, annotationNode, level, false, onMethod, onParam); break; } } @@ -192,28 +184,40 @@ public class HandleSetter extends EclipseAnnotationHandler<Setter> { } } - MethodDeclaration method = createSetter((TypeDeclaration) fieldNode.up().get(), fieldNode, setterName, shouldReturnThis, modifier, sourceNode, onMethod, onParam); + MethodDeclaration method = createSetter((TypeDeclaration) fieldNode.up().get(), false, fieldNode, setterName, null, shouldReturnThis, modifier, sourceNode, onMethod, onParam); injectMethod(fieldNode.up(), method); } + + static MethodDeclaration createSetter(TypeDeclaration parent, boolean deprecate, EclipseNode fieldNode, String name, char[] booleanFieldToSet, boolean shouldReturnThis, int modifier, EclipseNode sourceNode, List<Annotation> onMethod, List<Annotation> onParam) { + ASTNode source = sourceNode.get(); + int pS = source.sourceStart, pE = source.sourceEnd; + + TypeReference returnType = null; + ReturnStatement returnThis = null; + if (shouldReturnThis) { + returnType = cloneSelfType(fieldNode, source); + ThisReference thisRef = new ThisReference(pS, pE); + returnThis = new ReturnStatement(thisRef, pS, pE); + } + + return createSetter(parent, deprecate, fieldNode, name, booleanFieldToSet, returnType, returnThis, modifier, sourceNode, onMethod, onParam); + } - static MethodDeclaration createSetter(TypeDeclaration parent, EclipseNode fieldNode, String name, boolean shouldReturnThis, int modifier, EclipseNode sourceNode, List<Annotation> onMethod, List<Annotation> onParam) { + static MethodDeclaration createSetter(TypeDeclaration parent, boolean deprecate, EclipseNode fieldNode, String name, char[] booleanFieldToSet, TypeReference returnType, Statement returnStatement, int modifier, EclipseNode sourceNode, List<Annotation> onMethod, List<Annotation> onParam) { FieldDeclaration field = (FieldDeclaration) fieldNode.get(); ASTNode source = sourceNode.get(); int pS = source.sourceStart, pE = source.sourceEnd; long p = (long)pS << 32 | pE; MethodDeclaration method = new MethodDeclaration(parent.compilationResult); method.modifiers = modifier; - if (shouldReturnThis) { - method.returnType = cloneSelfType(fieldNode, source); - } - - if (method.returnType == null) { + if (returnType != null) { + method.returnType = returnType; + } else { method.returnType = TypeReference.baseTypeReference(TypeIds.T_void, 0); method.returnType.sourceStart = pS; method.returnType.sourceEnd = pE; - shouldReturnThis = false; } Annotation[] deprecated = null; - if (isFieldDeprecated(fieldNode)) { + if (isFieldDeprecated(fieldNode) || deprecate) { deprecated = new Annotation[] { generateDeprecatedAnnotation(source) }; } method.annotations = copyAnnotations(source, onMethod.toArray(new Annotation[0]), deprecated); @@ -243,10 +247,12 @@ public class HandleSetter extends EclipseAnnotationHandler<Setter> { statements.add(assignment); } - if (shouldReturnThis) { - ThisReference thisRef = new ThisReference(pS, pE); - ReturnStatement returnThis = new ReturnStatement(thisRef, pS, pE); - statements.add(returnThis); + if (booleanFieldToSet != null) { + statements.add(new Assignment(new SingleNameReference(booleanFieldToSet, p), new TrueLiteral(pS, pE), pE)); + } + + if (returnType != null && returnStatement != null) { + statements.add(returnStatement); } method.statements = statements.toArray(new Statement[0]); param.annotations = copyAnnotations(source, nonNulls, nullables, onParam.toArray(new Annotation[0])); diff --git a/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java b/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java new file mode 100644 index 00000000..6b0275e4 --- /dev/null +++ b/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java @@ -0,0 +1,836 @@ +/* + * Copyright (C) 2013-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.eclipse.handlers; + +import static lombok.core.handlers.HandlerUtil.*; +import static lombok.eclipse.Eclipse.*; +import static lombok.eclipse.handlers.EclipseHandlerUtil.*; + +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.eclipse.jdt.internal.compiler.ast.ASTNode; +import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.AbstractVariableDeclaration; +import org.eclipse.jdt.internal.compiler.ast.AllocationExpression; +import org.eclipse.jdt.internal.compiler.ast.Annotation; +import org.eclipse.jdt.internal.compiler.ast.Argument; +import org.eclipse.jdt.internal.compiler.ast.Assignment; +import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; +import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration; +import org.eclipse.jdt.internal.compiler.ast.ExplicitConstructorCall; +import org.eclipse.jdt.internal.compiler.ast.Expression; +import org.eclipse.jdt.internal.compiler.ast.FalseLiteral; +import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; +import org.eclipse.jdt.internal.compiler.ast.FieldReference; +import org.eclipse.jdt.internal.compiler.ast.IfStatement; +import org.eclipse.jdt.internal.compiler.ast.MessageSend; +import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.ParameterizedQualifiedTypeReference; +import org.eclipse.jdt.internal.compiler.ast.ParameterizedSingleTypeReference; +import org.eclipse.jdt.internal.compiler.ast.QualifiedNameReference; +import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference; +import org.eclipse.jdt.internal.compiler.ast.ReturnStatement; +import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; +import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference; +import org.eclipse.jdt.internal.compiler.ast.Statement; +import org.eclipse.jdt.internal.compiler.ast.ThisReference; +import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; +import org.eclipse.jdt.internal.compiler.ast.TypeParameter; +import org.eclipse.jdt.internal.compiler.ast.TypeReference; +import org.eclipse.jdt.internal.compiler.ast.Wildcard; +import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; +import org.eclipse.jdt.internal.compiler.lookup.ClassScope; +import org.eclipse.jdt.internal.compiler.lookup.ExtraCompilerModifiers; +import org.eclipse.jdt.internal.compiler.lookup.MethodScope; +import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; +import org.eclipse.jdt.internal.compiler.lookup.TypeIds; +import org.mangosdk.spi.ProviderFor; + +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Builder.ObtainVia; +import lombok.ConfigurationKeys; +import lombok.Singular; +import lombok.ToString; +import lombok.core.AST.Kind; +import lombok.core.handlers.InclusionExclusionUtils.Included; +import lombok.core.AnnotationValues; +import lombok.core.HandlerPriority; +import lombok.eclipse.Eclipse; +import lombok.eclipse.EclipseAnnotationHandler; +import lombok.eclipse.EclipseNode; +import lombok.eclipse.handlers.EclipseHandlerUtil.MemberExistsResult; +import lombok.eclipse.handlers.EclipseSingularsRecipes.EclipseSingularizer; +import lombok.eclipse.handlers.EclipseSingularsRecipes.SingularData; +import lombok.eclipse.handlers.EclipseSingularsRecipes.StatementMaker; +import lombok.eclipse.handlers.EclipseSingularsRecipes.TypeReferenceMaker; +import lombok.experimental.NonFinal; +import lombok.experimental.SuperBuilder; + +@ProviderFor(EclipseAnnotationHandler.class) +@HandlerPriority(-1024) //-2^10; to ensure we've picked up @FieldDefault's changes (-2048) but @Value hasn't removed itself yet (-512), so that we can error on presence of it on the builder classes. +public class HandleSuperBuilder extends EclipseAnnotationHandler<SuperBuilder> { + private static final char[] CLEAN_FIELD_NAME = "$lombokUnclean".toCharArray(); + private static final char[] CLEAN_METHOD_NAME = "$lombokClean".toCharArray(); + private static final char[] SET_PREFIX = "$set".toCharArray(); + private static final char[] SELF_METHOD_NAME = "self".toCharArray(); + + private static final AbstractMethodDeclaration[] EMPTY_METHODS = {}; + + private static class BuilderFieldData { + TypeReference type; + char[] rawName; + char[] name; + char[] nameOfSetFlag; + SingularData singularData; + ObtainVia obtainVia; + EclipseNode obtainViaNode; + EclipseNode originalFieldNode; + + List<EclipseNode> createdFields = new ArrayList<EclipseNode>(); + } + + @Override + public void handle(AnnotationValues<SuperBuilder> annotation, Annotation ast, EclipseNode annotationNode) { + handleExperimentalFlagUsage(annotationNode, ConfigurationKeys.SUPERBUILDER_FLAG_USAGE, "@SuperBuilder"); + + long p = (long) ast.sourceStart << 32 | ast.sourceEnd; + + SuperBuilder builderInstance = annotation.getInstance(); + + String builderMethodName = builderInstance.builderMethodName(); + String buildMethodName = builderInstance.buildMethodName(); + + if (builderMethodName == null) builderMethodName = "builder"; + if (buildMethodName == null) buildMethodName = "build"; + + if (!checkName("builderMethodName", builderMethodName, annotationNode)) return; + if (!checkName("buildMethodName", buildMethodName, annotationNode)) return; + + EclipseNode tdParent = annotationNode.up(); + + java.util.List<BuilderFieldData> builderFields = new ArrayList<BuilderFieldData>(); + TypeReference returnType; + TypeParameter[] typeParams; + + boolean addCleaning = false; + + if (!(tdParent.get() instanceof TypeDeclaration)) { + annotationNode.addError("@SuperBuilder is only supported on types."); + return; + } + TypeDeclaration td = (TypeDeclaration) tdParent.get(); + + // Gather all fields of the class that should be set by the builder. + List<EclipseNode> allFields = new ArrayList<EclipseNode>(); + boolean valuePresent = (hasAnnotation(lombok.Value.class, tdParent) || hasAnnotation("lombok.experimental.Value", tdParent)); + for (EclipseNode fieldNode : HandleConstructor.findAllFields(tdParent, true)) { + FieldDeclaration fd = (FieldDeclaration) fieldNode.get(); + EclipseNode isDefault = findAnnotation(Builder.Default.class, fieldNode); + boolean isFinal = ((fd.modifiers & ClassFileConstants.AccFinal) != 0) || (valuePresent && !hasAnnotation(NonFinal.class, fieldNode)); + + BuilderFieldData bfd = new BuilderFieldData(); + bfd.rawName = fieldNode.getName().toCharArray(); + bfd.name = removePrefixFromField(fieldNode); + bfd.type = fd.type; + bfd.singularData = getSingularData(fieldNode, ast); + bfd.originalFieldNode = fieldNode; + + if (bfd.singularData != null && isDefault != null) { + isDefault.addError("@Builder.Default and @Singular cannot be mixed."); + isDefault = null; + } + + if (fd.initialization == null && isDefault != null) { + isDefault.addWarning("@Builder.Default requires an initializing expression (' = something;')."); + isDefault = null; + } + + if (fd.initialization != null && isDefault == null) { + if (isFinal) { + continue; + } + fieldNode.addWarning("@Builder will ignore the initializing expression entirely. If you want the initializing expression to serve as default, add @Builder.Default. If it is not supposed to be settable during building, make the field final."); + } + + if (isDefault != null) { + bfd.nameOfSetFlag = prefixWith(bfd.name, SET_PREFIX); + // The @Builder annotation removes the initializing expression on the field and moves + // it to a method called "$default$FIELDNAME". This method is then called upon building. + // We do NOT do this, because this is unexpected and may lead to bugs when using other + // constructors (see, e.g., issue #1347). + // Instead, we keep the init expression and only set a new value in the builder-based + // constructor if it was set in the builder. Drawback is that the init expression is + // always executed, even if it was unnecessary because its value is overwritten by the + // builder. + // TODO: Once the issue is resolved in @Builder, we can adapt the solution here. + } + addObtainVia(bfd, fieldNode); + builderFields.add(bfd); + allFields.add(fieldNode); + } + + // Set the names of the builder classes. + String builderClassName = String.valueOf(td.name) + "Builder"; + String builderImplClassName = builderClassName + "Impl"; + + typeParams = td.typeParameters != null ? td.typeParameters : new TypeParameter[0]; + returnType = namePlusTypeParamsToTypeReference(td.name, typeParams, p); + + // <C, B> are the generics for our builder. + String classGenericName = "C"; + String builderGenericName = "B"; + // If these generics' names collide with any generics on the annotated class, modify them. + // For instance, if there are generics <B, B2, C> on the annotated class, use "C2" and "B3" for our builder. + java.util.List<String> typeParamStrings = new ArrayList<String>(); + for (TypeParameter typeParam : typeParams) typeParamStrings.add(typeParam.toString()); + classGenericName = generateNonclashingNameFor(classGenericName, typeParamStrings); + builderGenericName = generateNonclashingNameFor(builderGenericName, typeParamStrings); + + TypeReference extendsClause = td.superclass; + TypeReference superclassBuilderClass = null; + TypeReference[] typeArguments = new TypeReference[] { + new SingleTypeReference(classGenericName.toCharArray(), 0), + new SingleTypeReference(builderGenericName.toCharArray(), 0) + }; + if (extendsClause instanceof QualifiedTypeReference) { + QualifiedTypeReference qualifiedTypeReference = (QualifiedTypeReference)extendsClause; + String superclassClassName = String.valueOf(qualifiedTypeReference.getLastToken()); + String superclassBuilderClassName = superclassClassName + "Builder"; + + char[][] tokens = Arrays.copyOf(qualifiedTypeReference.tokens, qualifiedTypeReference.tokens.length + 1); + tokens[tokens.length] = superclassBuilderClassName.toCharArray(); + long[] poss = new long[tokens.length]; + Arrays.fill(poss, p); + + TypeReference[] superclassTypeArgs = getTypeParametersFrom(extendsClause); + + // Every token may potentially have type args. Here, we only have + // type args for the last token, the superclass' builder. + TypeReference[][] typeArgsForTokens = new TypeReference[tokens.length][]; + typeArgsForTokens[typeArgsForTokens.length-1] = mergeTypeReferences(superclassTypeArgs, typeArguments); + + superclassBuilderClass = new ParameterizedQualifiedTypeReference(tokens, typeArgsForTokens, 0, poss); + } else if (extendsClause != null) { + String superClass = String.valueOf(extendsClause.getTypeName()[0]); + String superclassBuilderClassName = superClass + "Builder"; + + char[][] tokens = new char[][] {superClass.toCharArray(), superclassBuilderClassName.toCharArray()}; + long[] poss = new long[tokens.length]; + Arrays.fill(poss, p); + + TypeReference[] superclassTypeArgs = getTypeParametersFrom(extendsClause); + + // Every token may potentially have type args. Here, we only have + // type args for the last token, the superclass' builder. + TypeReference[][] typeArgsForTokens = new TypeReference[tokens.length][]; + typeArgsForTokens[typeArgsForTokens.length-1] = mergeTypeReferences(superclassTypeArgs, typeArguments); + + superclassBuilderClass = new ParameterizedQualifiedTypeReference(tokens, typeArgsForTokens, 0, poss); + } + // If there is no superclass, superclassBuilderClassExpression is still == null at this point. + // You can use it to check whether to inherit or not. + + generateBuilderBasedConstructor(tdParent, typeParams, builderFields, annotationNode, builderClassName, + superclassBuilderClass != null); + + // Create the abstract builder class. + EclipseNode builderType = findInnerClass(tdParent, builderClassName); + if (builderType == null) { + builderType = generateBuilderAbstractClass(tdParent, builderClassName, superclassBuilderClass, + typeParams, ast, classGenericName, builderGenericName); + } else { + annotationNode.addError("@SuperBuilder does not support customized builders. Use @Builder instead."); + return; + } + + // Check validity of @ObtainVia fields, and add check if adding cleaning for @Singular is necessary. + for (BuilderFieldData bfd : builderFields) { + if (bfd.singularData != null && bfd.singularData.getSingularizer() != null) { + if (bfd.singularData.getSingularizer().requiresCleaning()) { + addCleaning = true; + break; + } + } + if (bfd.obtainVia != null) { + if (bfd.obtainVia.field().isEmpty() == bfd.obtainVia.method().isEmpty()) { + bfd.obtainViaNode.addError("The syntax is either @ObtainVia(field = \"fieldName\") or @ObtainVia(method = \"methodName\")."); + return; + } + if (bfd.obtainVia.method().isEmpty() && bfd.obtainVia.isStatic()) { + bfd.obtainViaNode.addError("@ObtainVia(isStatic = true) is not valid unless 'method' has been set."); + return; + } + } + } + + // Generate the fields in the abstract builder class that hold the values for the instance. + generateBuilderFields(builderType, builderFields, ast); + if (addCleaning) { + FieldDeclaration cleanDecl = new FieldDeclaration(CLEAN_FIELD_NAME, 0, -1); + cleanDecl.declarationSourceEnd = -1; + cleanDecl.modifiers = ClassFileConstants.AccPrivate; + cleanDecl.type = TypeReference.baseTypeReference(TypeIds.T_boolean, 0); + injectFieldAndMarkGenerated(builderType, cleanDecl); + } + + // Generate abstract self() and build() methods in the abstract builder. + injectMethod(builderType, generateAbstractSelfMethod(tdParent, superclassBuilderClass != null, builderGenericName)); + injectMethod(builderType, generateAbstractBuildMethod(tdParent, buildMethodName, superclassBuilderClass != null, classGenericName, ast)); + + // Create the setter methods in the abstract builder. + for (BuilderFieldData bfd : builderFields) { + generateSetterMethodsForBuilder(builderType, bfd, annotationNode, builderGenericName); + } + + // Create the toString() method for the abstract builder. + if (methodExists("toString", builderType, 0) == MemberExistsResult.NOT_EXISTS) { + List<Included<EclipseNode, ToString.Include>> fieldNodes = new ArrayList<Included<EclipseNode, ToString.Include>>(); + for (BuilderFieldData bfd : builderFields) { + for (EclipseNode f : bfd.createdFields) { + fieldNodes.add(new Included<EclipseNode, ToString.Include>(f, null, true)); + } + } + // Let toString() call super.toString() if there is a superclass, so that it also shows fields from the superclass' builder. + MethodDeclaration md = HandleToString.createToString(builderType, fieldNodes, true, superclassBuilderClass != null, ast, FieldAccess.ALWAYS_FIELD); + if (md != null) { + injectMethod(builderType, md); + } + } + + if (addCleaning) injectMethod(builderType, generateCleanMethod(builderFields, builderType, ast)); + + if ((td.modifiers & ClassFileConstants.AccAbstract) != 0) { + // Only non-abstract classes get the Builder implementation. + return; + } + + // Create the builder implementation class. + EclipseNode builderImplType = findInnerClass(tdParent, builderImplClassName); + if (builderImplType == null) { + builderImplType = generateBuilderImplClass(tdParent, builderImplClassName, builderClassName, typeParams, ast); + } else { + annotationNode.addError("@SuperBuilder does not support customized builders. Use @Builder instead."); + return; + } + + // Create the self() and build() methods in the BuilderImpl. + injectMethod(builderImplType, generateSelfMethod(builderImplType)); + injectMethod(builderImplType, generateBuildMethod(tdParent, buildMethodName, returnType, ast)); + + // Add the builder() method to the annotated class. + if (methodExists(builderMethodName, tdParent, -1) == MemberExistsResult.NOT_EXISTS) { + MethodDeclaration md = generateBuilderMethod(builderMethodName, builderClassName, builderImplClassName, tdParent, typeParams, ast); + if (md != null) injectMethod(tdParent, md); + } + } + + private EclipseNode generateBuilderAbstractClass(EclipseNode tdParent, String builderClass, + TypeReference superclassBuilderClass, TypeParameter[] typeParams, + ASTNode source, String classGenericName, String builderGenericName) { + + TypeDeclaration parent = (TypeDeclaration) tdParent.get(); + TypeDeclaration builder = new TypeDeclaration(parent.compilationResult); + builder.bits |= Eclipse.ECLIPSE_DO_NOT_TOUCH_FLAG; + builder.modifiers |= ClassFileConstants.AccPublic | ClassFileConstants.AccStatic | ClassFileConstants.AccAbstract; + builder.name = builderClass.toCharArray(); + + // Keep any type params of the annotated class. + builder.typeParameters = Arrays.copyOf(copyTypeParams(typeParams, source), typeParams.length + 2); + // Add builder-specific type params required for inheritable builders. + // 1. The return type for the build() method, named "C", which extends the annotated class. + TypeParameter o = new TypeParameter(); + o.name = classGenericName.toCharArray(); + o.type = cloneSelfType(tdParent, source); + builder.typeParameters[builder.typeParameters.length - 2] = o; + // 2. The return type for all setter methods, named "B", which extends this builder class. + o = new TypeParameter(); + o.name = builderGenericName.toCharArray(); + TypeReference[] typerefs = appendBuilderTypeReferences(typeParams, classGenericName, builderGenericName); + o.type = new ParameterizedSingleTypeReference(builderClass.toCharArray(), typerefs, 0, 0); + builder.typeParameters[builder.typeParameters.length - 1] = o; + + builder.superclass = copyType(superclassBuilderClass, source); + + builder.createDefaultConstructor(false, true); + + builder.traverse(new SetGeneratedByVisitor(source), (ClassScope) null); + return injectType(tdParent, builder); + } + + private EclipseNode generateBuilderImplClass(EclipseNode tdParent, String builderImplClass, String builderAbstractClass, TypeParameter[] typeParams, ASTNode source) { + TypeDeclaration parent = (TypeDeclaration) tdParent.get(); + TypeDeclaration builder = new TypeDeclaration(parent.compilationResult); + builder.bits |= Eclipse.ECLIPSE_DO_NOT_TOUCH_FLAG; + builder.modifiers |= ClassFileConstants.AccPrivate | ClassFileConstants.AccStatic | ClassFileConstants.AccFinal; + builder.name = builderImplClass.toCharArray(); + + // Add type params if there are any. + if (typeParams != null && typeParams.length > 0) builder.typeParameters = copyTypeParams(typeParams, source); + + if (builderAbstractClass != null) { + // Extend the abstract builder. + // 1. Add any type params of the annotated class. + TypeReference[] typeArgs = new TypeReference[typeParams.length + 2]; + for (int i = 0; i < typeParams.length; i++) { + typeArgs[i] = new SingleTypeReference(typeParams[i].name, 0); + } + // 2. The return type for the build() method (named "C" in the abstract builder), which is the annotated class. + // 3. The return type for all setter methods (named "B" in the abstract builder), which is this builder class. + typeArgs[typeArgs.length - 2] = cloneSelfType(tdParent, source); + typeArgs[typeArgs.length - 1] = createTypeReferenceWithTypeParameters(builderImplClass, typeParams); + builder.superclass = new ParameterizedSingleTypeReference(builderAbstractClass.toCharArray(), typeArgs, 0, 0); + } + + builder.createDefaultConstructor(false, true); + + builder.traverse(new SetGeneratedByVisitor(source), (ClassScope) null); + return injectType(tdParent, builder); + } + + /** + * Generates a constructor that has a builder as the only parameter. + * The values from the builder are used to initialize the fields of new instances. + * + * @param typeNode + * the type (with the {@code @Builder} annotation) for which a + * constructor should be generated. + * @param typeParams + * @param builderFields a list of fields in the builder which should be assigned to new instances. + * @param source the annotation (used for setting source code locations for the generated code). + * @param callBuilderBasedSuperConstructor + * If {@code true}, the constructor will explicitly call a super + * constructor with the builder as argument. Requires + * {@code builderClassAsParameter != null}. + */ + private void generateBuilderBasedConstructor(EclipseNode typeNode, TypeParameter[] typeParams, List<BuilderFieldData> builderFields, + EclipseNode sourceNode, String builderClassName, boolean callBuilderBasedSuperConstructor) { + + ASTNode source = sourceNode.get(); + + TypeDeclaration typeDeclaration = ((TypeDeclaration) typeNode.get()); + long p = (long) source.sourceStart << 32 | source.sourceEnd; + + ConstructorDeclaration constructor = new ConstructorDeclaration(((CompilationUnitDeclaration) typeNode.top().get()).compilationResult); + + constructor.modifiers = toEclipseModifier(AccessLevel.PROTECTED); + constructor.selector = typeDeclaration.name; + if (callBuilderBasedSuperConstructor) { + constructor.constructorCall = new ExplicitConstructorCall(ExplicitConstructorCall.Super); + constructor.constructorCall.arguments = new Expression[] {new SingleNameReference("b".toCharArray(), p)}; + } else { + constructor.constructorCall = new ExplicitConstructorCall(ExplicitConstructorCall.ImplicitSuper); + } + constructor.constructorCall.sourceStart = source.sourceStart; + constructor.constructorCall.sourceEnd = source.sourceEnd; + constructor.thrownExceptions = null; + constructor.typeParameters = null; + constructor.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + constructor.bodyStart = constructor.declarationSourceStart = constructor.sourceStart = source.sourceStart; + constructor.bodyEnd = constructor.declarationSourceEnd = constructor.sourceEnd = source.sourceEnd; + + TypeReference[] wildcards = new TypeReference[] {new Wildcard(Wildcard.UNBOUND), new Wildcard(Wildcard.UNBOUND)}; + TypeReference builderType = new ParameterizedSingleTypeReference(builderClassName.toCharArray(), mergeToTypeReferences(typeParams, wildcards), 0, p); + constructor.arguments = new Argument[] {new Argument("b".toCharArray(), p, builderType, Modifier.FINAL)}; + + List<Statement> statements = new ArrayList<Statement>(); + List<Statement> nullChecks = new ArrayList<Statement>(); + + for (BuilderFieldData fieldNode : builderFields) { + char[] fieldName = removePrefixFromField(fieldNode.originalFieldNode); + FieldReference thisX = new FieldReference(fieldNode.rawName, p); + int s = (int) (p >> 32); + int e = (int) p; + thisX.receiver = new ThisReference(s, e); + + Expression assignmentExpr; + if (fieldNode.singularData != null && fieldNode.singularData.getSingularizer() != null) { + fieldNode.singularData.getSingularizer().appendBuildCode(fieldNode.singularData, typeNode, statements, fieldNode.name, "b"); + assignmentExpr = new SingleNameReference(fieldNode.name, p); + } else { + char[][] variableInBuilder = new char[][] {"b".toCharArray(), fieldName}; + long[] positions = new long[] {p, p}; + assignmentExpr = new QualifiedNameReference(variableInBuilder, positions, s, e); + } + Statement assignment = new Assignment(thisX, assignmentExpr, (int) p); + + // In case of @Builder.Default, only set the value if it really was set in the builder. + if (fieldNode.nameOfSetFlag != null) { + char[][] variableInBuilder = new char[][] {"b".toCharArray(), fieldNode.nameOfSetFlag}; + long[] positions = new long[] {p, p}; + QualifiedNameReference builderRef = new QualifiedNameReference(variableInBuilder, positions, s, e); + assignment = new IfStatement(builderRef, assignment, s, e); + } + statements.add(assignment); + + Annotation[] nonNulls = findAnnotations((FieldDeclaration)fieldNode.originalFieldNode.get(), NON_NULL_PATTERN); + if (nonNulls.length != 0) { + Statement nullCheck = generateNullCheck((FieldDeclaration)fieldNode.originalFieldNode.get(), sourceNode); + if (nullCheck != null) { + nullChecks.add(nullCheck); + } + } + } + + nullChecks.addAll(statements); + constructor.statements = nullChecks.isEmpty() ? null : nullChecks.toArray(new Statement[nullChecks.size()]); + + constructor.traverse(new SetGeneratedByVisitor(source), typeDeclaration.scope); + + injectMethod(typeNode, constructor); + } + + private MethodDeclaration generateBuilderMethod(String builderMethodName, String builderClassName, String builderImplClassName, EclipseNode type, TypeParameter[] typeParams, ASTNode source) { + int pS = source.sourceStart, pE = source.sourceEnd; + long p = (long) pS << 32 | pE; + + MethodDeclaration out = new MethodDeclaration(((CompilationUnitDeclaration) type.top().get()).compilationResult); + out.selector = builderMethodName.toCharArray(); + out.modifiers = ClassFileConstants.AccPublic | ClassFileConstants.AccStatic; + out.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + + // Add type params if there are any. + if (typeParams != null && typeParams.length > 0) out.typeParameters = copyTypeParams(typeParams, source); + + TypeReference[] wildcards = new TypeReference[] {new Wildcard(Wildcard.UNBOUND), new Wildcard(Wildcard.UNBOUND) }; + out.returnType = new ParameterizedSingleTypeReference(builderClassName.toCharArray(), mergeToTypeReferences(typeParams, wildcards), 0, p); + + AllocationExpression invoke = new AllocationExpression(); + invoke.type = namePlusTypeParamsToTypeReference(builderImplClassName.toCharArray(), typeParams, p); + out.statements = new Statement[] {new ReturnStatement(invoke, pS, pE)}; + + out.traverse(new SetGeneratedByVisitor(source), ((TypeDeclaration) type.get()).scope); + return out; + } + + private MethodDeclaration generateAbstractSelfMethod(EclipseNode tdParent, boolean override, String builderGenericName) { + MethodDeclaration out = new MethodDeclaration(((CompilationUnitDeclaration) tdParent.top().get()).compilationResult); + out.selector = SELF_METHOD_NAME; + out.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + out.modifiers = ClassFileConstants.AccAbstract | ClassFileConstants.AccProtected | ExtraCompilerModifiers.AccSemicolonBody; + if (override) out.annotations = new Annotation[] {makeMarkerAnnotation(TypeConstants.JAVA_LANG_OVERRIDE, tdParent.get())}; + out.returnType = new SingleTypeReference(builderGenericName.toCharArray(), 0); + return out; + } + + private MethodDeclaration generateSelfMethod(EclipseNode builderImplType) { + MethodDeclaration out = new MethodDeclaration(((CompilationUnitDeclaration) builderImplType.top().get()).compilationResult); + out.selector = SELF_METHOD_NAME; + out.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + out.modifiers = ClassFileConstants.AccProtected; + out.annotations = new Annotation[] {makeMarkerAnnotation(TypeConstants.JAVA_LANG_OVERRIDE, builderImplType.get())}; + out.returnType = new SingleTypeReference(builderImplType.getName().toCharArray(), 0); + out.statements = new Statement[] {new ReturnStatement(new ThisReference(0, 0), 0, 0)}; + return out; + } + + private MethodDeclaration generateAbstractBuildMethod(EclipseNode tdParent, String methodName, boolean override, + String classGenericName, ASTNode source) { + + MethodDeclaration out = new MethodDeclaration(((CompilationUnitDeclaration) tdParent.top().get()).compilationResult); + out.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + + out.modifiers = ClassFileConstants.AccPublic | ClassFileConstants.AccAbstract | ExtraCompilerModifiers.AccSemicolonBody; + out.selector = methodName.toCharArray(); + out.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + out.returnType = new SingleTypeReference(classGenericName.toCharArray(), 0); + if (override) out.annotations = new Annotation[] {makeMarkerAnnotation(TypeConstants.JAVA_LANG_OVERRIDE, source)}; + out.traverse(new SetGeneratedByVisitor(source), (ClassScope) null); + return out; + } + + private MethodDeclaration generateBuildMethod(EclipseNode tdParent, String name, TypeReference returnType, ASTNode source) { + MethodDeclaration out = new MethodDeclaration(((CompilationUnitDeclaration) tdParent.top().get()).compilationResult); + out.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + List<Statement> statements = new ArrayList<Statement>(); + + out.modifiers = ClassFileConstants.AccPublic; + out.selector = name.toCharArray(); + out.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + out.returnType = returnType; + out.annotations = new Annotation[] {makeMarkerAnnotation(TypeConstants.JAVA_LANG_OVERRIDE, source)}; + + AllocationExpression allocationStatement = new AllocationExpression(); + allocationStatement.type = copyType(out.returnType); + // Use a constructor that only has this builder as parameter. + allocationStatement.arguments = new Expression[] {new ThisReference(0, 0)}; + statements.add(new ReturnStatement(allocationStatement, 0, 0)); + out.statements = statements.isEmpty() ? null : statements.toArray(new Statement[statements.size()]); + out.traverse(new SetGeneratedByVisitor(source), (ClassScope) null); + return out; + } + + private MethodDeclaration generateCleanMethod(List<BuilderFieldData> builderFields, EclipseNode builderType, ASTNode source) { + List<Statement> statements = new ArrayList<Statement>(); + + for (BuilderFieldData bfd : builderFields) { + if (bfd.singularData != null && bfd.singularData.getSingularizer() != null) { + bfd.singularData.getSingularizer().appendCleaningCode(bfd.singularData, builderType, statements); + } + } + + FieldReference thisUnclean = new FieldReference(CLEAN_FIELD_NAME, 0); + thisUnclean.receiver = new ThisReference(0, 0); + statements.add(new Assignment(thisUnclean, new FalseLiteral(0, 0), 0)); + MethodDeclaration decl = new MethodDeclaration(((CompilationUnitDeclaration) builderType.top().get()).compilationResult); + decl.selector = CLEAN_METHOD_NAME; + decl.modifiers = ClassFileConstants.AccPrivate; + decl.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + decl.returnType = TypeReference.baseTypeReference(TypeIds.T_void, 0); + decl.statements = statements.toArray(new Statement[0]); + decl.traverse(new SetGeneratedByVisitor(source), (ClassScope) null); + return decl; + } + + private void generateBuilderFields(EclipseNode builderType, List<BuilderFieldData> builderFields, ASTNode source) { + List<EclipseNode> existing = new ArrayList<EclipseNode>(); + for (EclipseNode child : builderType.down()) { + if (child.getKind() == Kind.FIELD) existing.add(child); + } + + for (BuilderFieldData bfd : builderFields) { + if (bfd.singularData != null && bfd.singularData.getSingularizer() != null) { + bfd.createdFields.addAll(bfd.singularData.getSingularizer().generateFields(bfd.singularData, builderType)); + } else { + EclipseNode field = null, setFlag = null; + for (EclipseNode exists : existing) { + char[] n = ((FieldDeclaration) exists.get()).name; + if (Arrays.equals(n, bfd.name)) field = exists; + if (bfd.nameOfSetFlag != null && Arrays.equals(n, bfd.nameOfSetFlag)) setFlag = exists; + } + + if (field == null) { + FieldDeclaration fd = new FieldDeclaration(bfd.name, 0, 0); + fd.bits |= Eclipse.ECLIPSE_DO_NOT_TOUCH_FLAG; + fd.modifiers = ClassFileConstants.AccPrivate; + fd.type = copyType(bfd.type); + fd.traverse(new SetGeneratedByVisitor(source), (MethodScope) null); + field = injectFieldAndMarkGenerated(builderType, fd); + } + if (setFlag == null && bfd.nameOfSetFlag != null) { + FieldDeclaration fd = new FieldDeclaration(bfd.nameOfSetFlag, 0, 0); + fd.bits |= Eclipse.ECLIPSE_DO_NOT_TOUCH_FLAG; + fd.modifiers = ClassFileConstants.AccPrivate; + fd.type = TypeReference.baseTypeReference(TypeIds.T_boolean, 0); + fd.traverse(new SetGeneratedByVisitor(source), (MethodScope) null); + injectFieldAndMarkGenerated(builderType, fd); + } + bfd.createdFields.add(field); + } + } + } + + private void generateSetterMethodsForBuilder(EclipseNode builderType, BuilderFieldData bfd, EclipseNode sourceNode, final String builderGenericName) { + boolean deprecate = isFieldDeprecated(bfd.originalFieldNode); + + TypeReferenceMaker returnTypeMaker = new TypeReferenceMaker() { + @Override public TypeReference make() { + return new SingleTypeReference(builderGenericName.toCharArray(), 0); + } + }; + + StatementMaker returnStatementMaker = new StatementMaker() { + @Override public ReturnStatement make() { + MessageSend returnCall = new MessageSend(); + returnCall.receiver = ThisReference.implicitThis(); + returnCall.selector = SELF_METHOD_NAME; + return new ReturnStatement(returnCall, 0, 0); + } + }; + + if (bfd.singularData == null || bfd.singularData.getSingularizer() == null) { + generateSimpleSetterMethodForBuilder(builderType, deprecate, bfd.createdFields.get(0), bfd.nameOfSetFlag, returnTypeMaker.make(), returnStatementMaker.make(), sourceNode); + } else { + bfd.singularData.getSingularizer().generateMethods(bfd.singularData, deprecate, builderType, true, returnTypeMaker, returnStatementMaker); + } + } + + private void generateSimpleSetterMethodForBuilder(EclipseNode builderType, boolean deprecate, EclipseNode fieldNode, char[] nameOfSetFlag, TypeReference returnType, Statement returnStatement, EclipseNode sourceNode) { + TypeDeclaration td = (TypeDeclaration) builderType.get(); + AbstractMethodDeclaration[] existing = td.methods; + if (existing == null) existing = EMPTY_METHODS; + int len = existing.length; + FieldDeclaration fd = (FieldDeclaration) fieldNode.get(); + char[] name = fd.name; + + for (int i = 0; i < len; i++) { + if (!(existing[i] instanceof MethodDeclaration)) continue; + char[] existingName = existing[i].selector; + if (Arrays.equals(name, existingName) && !isTolerate(fieldNode, existing[i])) return; + } + + String setterName = fieldNode.getName(); + + MethodDeclaration setter = HandleSetter.createSetter(td, deprecate, fieldNode, setterName, nameOfSetFlag, returnType, returnStatement, ClassFileConstants.AccPublic, + sourceNode, Collections.<Annotation>emptyList(), Collections.<Annotation>emptyList()); + injectMethod(builderType, setter); + } + + private void addObtainVia(BuilderFieldData bfd, EclipseNode node) { + for (EclipseNode child : node.down()) { + if (!annotationTypeMatches(ObtainVia.class, child)) continue; + AnnotationValues<ObtainVia> ann = createAnnotation(ObtainVia.class, child); + bfd.obtainVia = ann.getInstance(); + bfd.obtainViaNode = child; + return; + } + } + + /** + * Returns the explicitly requested singular annotation on this node (field + * or parameter), or null if there's no {@code @Singular} annotation on it. + * + * @param node The node (field or method param) to inspect for its name and potential {@code @Singular} annotation. + */ + private SingularData getSingularData(EclipseNode node, ASTNode source) { + for (EclipseNode child : node.down()) { + if (!annotationTypeMatches(Singular.class, child)) continue; + + char[] pluralName = node.getKind() == Kind.FIELD ? removePrefixFromField(node) : ((AbstractVariableDeclaration) node.get()).name; + AnnotationValues<Singular> ann = createAnnotation(Singular.class, child); + String explicitSingular = ann.getInstance().value(); + if (explicitSingular.isEmpty()) { + if (Boolean.FALSE.equals(node.getAst().readConfiguration(ConfigurationKeys.SINGULAR_AUTO))) { + node.addError("The singular must be specified explicitly (e.g. @Singular(\"task\")) because auto singularization is disabled."); + explicitSingular = new String(pluralName); + } else { + explicitSingular = autoSingularize(new String(pluralName)); + if (explicitSingular == null) { + node.addError("Can't singularize this name; please specify the singular explicitly (i.e. @Singular(\"sheep\"))"); + explicitSingular = new String(pluralName); + } + } + } + char[] singularName = explicitSingular.toCharArray(); + + TypeReference type = ((AbstractVariableDeclaration) node.get()).type; + TypeReference[] typeArgs = null; + String typeName; + if (type instanceof ParameterizedSingleTypeReference) { + typeArgs = ((ParameterizedSingleTypeReference) type).typeArguments; + typeName = new String(((ParameterizedSingleTypeReference) type).token); + } else if (type instanceof ParameterizedQualifiedTypeReference) { + TypeReference[][] tr = ((ParameterizedQualifiedTypeReference) type).typeArguments; + if (tr != null) typeArgs = tr[tr.length - 1]; + char[][] tokens = ((ParameterizedQualifiedTypeReference) type).tokens; + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < tokens.length; i++) { + if (i > 0) sb.append("."); + sb.append(tokens[i]); + } + typeName = sb.toString(); + } else { + typeName = type.toString(); + } + + String targetFqn = EclipseSingularsRecipes.get().toQualified(typeName); + EclipseSingularizer singularizer = EclipseSingularsRecipes.get().getSingularizer(targetFqn); + if (singularizer == null) { + node.addError("Lombok does not know how to create the singular-form builder methods for type '" + typeName + "'; they won't be generated."); + return null; + } + + return new SingularData(child, singularName, pluralName, typeArgs == null ? Collections.<TypeReference>emptyList() : Arrays.asList(typeArgs), targetFqn, singularizer, source); + } + + return null; + } + + private String generateNonclashingNameFor(String classGenericName, java.util.List<String> typeParamStrings) { + if (!typeParamStrings.contains(classGenericName)) return classGenericName; + int counter = 2; + while (typeParamStrings.contains(classGenericName + counter)) counter++; + return classGenericName + counter; + } + + private TypeReference[] appendBuilderTypeReferences(TypeParameter[] typeParams, String classGenericName, String builderGenericName) { + TypeReference[] typeReferencesToAppend = new TypeReference[2]; + typeReferencesToAppend[typeReferencesToAppend.length - 2] = new SingleTypeReference(classGenericName.toCharArray(), 0); + typeReferencesToAppend[typeReferencesToAppend.length - 1] = new SingleTypeReference(builderGenericName.toCharArray(), 0); + return mergeToTypeReferences(typeParams, typeReferencesToAppend); + } + + private TypeReference[] getTypeParametersFrom(TypeReference typeRef) { + TypeReference[][] typeArgss = null; + if (typeRef instanceof ParameterizedQualifiedTypeReference) { + typeArgss = ((ParameterizedQualifiedTypeReference) typeRef).typeArguments; + } else if (typeRef instanceof ParameterizedSingleTypeReference) { + typeArgss = new TypeReference[][] {((ParameterizedSingleTypeReference) typeRef).typeArguments}; + } + TypeReference[] typeArgs = new TypeReference[0]; + if (typeArgss != null && typeArgss.length > 0) { + typeArgs = typeArgss[typeArgss.length - 1]; + } + return typeArgs; + } + + private static SingleTypeReference createTypeReferenceWithTypeParameters(String referenceName, TypeParameter[] typeParams) { + if (typeParams.length > 0) { + TypeReference[] typerefs = new TypeReference[typeParams.length]; + for (int i = 0; i < typeParams.length; i++) { + typerefs[i] = new SingleTypeReference(typeParams[i].name, 0); + } + return new ParameterizedSingleTypeReference(referenceName.toCharArray(), typerefs, 0, 0); + } else { + return new SingleTypeReference(referenceName.toCharArray(), 0); + } + } + + private TypeReference[] mergeToTypeReferences(TypeParameter[] typeParams, TypeReference[] typeReferencesToAppend) { + TypeReference[] typerefs = new TypeReference[typeParams.length + typeReferencesToAppend.length]; + for (int i = 0; i < typeParams.length; i++) { + typerefs[i] = new SingleTypeReference(typeParams[i].name, 0); + } + for (int i = 0; i < typeReferencesToAppend.length; i++) { + typerefs[typeParams.length + i] = typeReferencesToAppend[i]; + } + return typerefs; + } + + private TypeReference[] mergeTypeReferences(TypeReference[] refs1, TypeReference[] refs2) { + TypeReference[] result = new TypeReference[refs1.length + refs2.length]; + for (int i = 0; i < refs1.length; i++) result[i] = refs1[i]; + for (int i = 0; i < refs2.length; i++) result[refs1.length + i] = refs2[i]; + return result; + } + + private EclipseNode findInnerClass(EclipseNode parent, String name) { + char[] c = name.toCharArray(); + for (EclipseNode child : parent.down()) { + if (child.getKind() != Kind.TYPE) continue; + TypeDeclaration td = (TypeDeclaration) child.get(); + if (Arrays.equals(td.name, c)) return child; + } + return null; + } + + private static final char[] prefixWith(char[] prefix, char[] name) { + char[] out = new char[prefix.length + name.length]; + System.arraycopy(prefix, 0, out, 0, prefix.length); + System.arraycopy(name, 0, out, prefix.length, name.length); + return out; + } +} diff --git a/src/core/lombok/eclipse/handlers/HandleToString.java b/src/core/lombok/eclipse/handlers/HandleToString.java index a4ed254a..02a19f8d 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.Included; 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<Included<EclipseNode, ToString.Include>> 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) { @@ -94,41 +101,17 @@ public class HandleToString extends EclipseAnnotationHandler<ToString> { Boolean configuration = typeNode.getAst().readConfiguration(ConfigurationKeys.TO_STRING_INCLUDE_FIELD_NAMES); includeFieldNames = configuration != null ? configuration : ((Boolean)ToString.class.getMethod("includeFieldNames").getDefaultValue()).booleanValue(); } catch (Exception ignore) {} - generateToString(typeNode, errorNode, null, null, includeFieldNames, null, false, FieldAccess.GETTER); - } - - 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(); - - if (!annotation.isExplicit("callSuper")) callSuper = null; - if (!annotation.isExplicit("exclude")) excludes = null; - if (!annotation.isExplicit("of")) includes = null; + Boolean doNotUseGettersConfiguration = typeNode.getAst().readConfiguration(ConfigurationKeys.TO_STRING_DO_NOT_USE_GETTERS); + FieldAccess access = doNotUseGettersConfiguration == null || !doNotUseGettersConfiguration ? FieldAccess.GETTER : FieldAccess.PREFER_FIELD; - 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); + List<Included<EclipseNode, ToString.Include>> members = InclusionExclusionUtils.handleToStringMarking(typeNode, null, null); + generateToString(typeNode, errorNode, members, includeFieldNames, null, false, access); } - public void generateToString(EclipseNode typeNode, EclipseNode errorNode, List<String> excludes, List<String> includes, - boolean includeFieldNames, Boolean callSuper, boolean whineIfExists, FieldAccess fieldAccess) { + public void generateToString(EclipseNode typeNode, EclipseNode errorNode, List<Included<EclipseNode, ToString.Include>> members, + boolean includeFieldNames, Boolean callSuper, boolean whineIfExists, FieldAccess fieldAccess) { + TypeDeclaration typeDecl = null; if (typeNode.get() instanceof TypeDeclaration) typeDecl = (TypeDeclaration) typeNode.get(); @@ -136,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: @@ -181,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<Included<EclipseNode, ToString.Include>> members, + boolean includeNames, boolean callSuper, ASTNode source, FieldAccess fieldAccess) { + String typeName = getTypeName(type); char[] suffix = ")".toCharArray(); String infixS = ", "; @@ -195,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) { + Included<EclipseNode, ToString.Include> 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(); } @@ -219,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 (Included<EclipseNode, ToString.Include> 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); @@ -254,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/HandleUtilityClass.java b/src/core/lombok/eclipse/handlers/HandleUtilityClass.java index 199ce102..959c1d20 100644 --- a/src/core/lombok/eclipse/handlers/HandleUtilityClass.java +++ b/src/core/lombok/eclipse/handlers/HandleUtilityClass.java @@ -49,6 +49,7 @@ import org.mangosdk.spi.ProviderFor; import lombok.ConfigurationKeys; import lombok.core.AnnotationValues; +import lombok.core.HandlerPriority; import lombok.core.AST.Kind; import lombok.eclipse.EclipseAnnotationHandler; import lombok.eclipse.EclipseNode; @@ -57,6 +58,7 @@ import lombok.experimental.UtilityClass; /** * Handles the {@code lombok.experimental.UtilityClass} annotation for eclipse. */ +@HandlerPriority(-4096) //-2^12; to ensure @FieldDefaults picks up on the 'static' we set here. @ProviderFor(EclipseAnnotationHandler.class) public class HandleUtilityClass extends EclipseAnnotationHandler<UtilityClass> { @Override public void handle(AnnotationValues<UtilityClass> annotation, Annotation ast, EclipseNode annotationNode) { diff --git a/src/core/lombok/eclipse/handlers/HandleVal.java b/src/core/lombok/eclipse/handlers/HandleVal.java index d4ae417c..3742ac00 100644 --- a/src/core/lombok/eclipse/handlers/HandleVal.java +++ b/src/core/lombok/eclipse/handlers/HandleVal.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2014 The Project Lombok Authors. + * Copyright (C) 2010-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 @@ -21,19 +21,24 @@ */ package lombok.eclipse.handlers; -import static lombok.core.handlers.HandlerUtil.*; +import static lombok.core.handlers.HandlerUtil.handleFlagUsage; +import static lombok.eclipse.handlers.EclipseHandlerUtil.typeMatches; import lombok.ConfigurationKeys; import lombok.val; +import lombok.var; import lombok.core.HandlerPriority; import lombok.eclipse.DeferUntilPostDiet; import lombok.eclipse.EclipseASTAdapter; import lombok.eclipse.EclipseASTVisitor; import lombok.eclipse.EclipseNode; +import org.eclipse.jdt.internal.compiler.ast.ASTNode; import org.eclipse.jdt.internal.compiler.ast.ArrayInitializer; import org.eclipse.jdt.internal.compiler.ast.ForStatement; import org.eclipse.jdt.internal.compiler.ast.ForeachStatement; import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration; +import org.eclipse.jdt.internal.compiler.ast.NullLiteral; +import org.eclipse.jdt.internal.compiler.ast.TypeReference; import org.mangosdk.spi.ProviderFor; /* @@ -44,8 +49,13 @@ import org.mangosdk.spi.ProviderFor; @HandlerPriority(65536) // 2^16; resolution needs to work, so if the RHS expression is i.e. a call to a generated getter, we have to run after that getter has been generated. public class HandleVal extends EclipseASTAdapter { @Override public void visitLocal(EclipseNode localNode, LocalDeclaration local) { - if (!EclipseHandlerUtil.typeMatches(val.class, localNode, local.type)) return; - handleFlagUsage(localNode, ConfigurationKeys.VAL_FLAG_USAGE, "val"); + TypeReference type = local.type; + boolean isVal = typeMatches(val.class, localNode, type); + boolean isVar = typeMatches(var.class, localNode, type); + if (!(isVal || isVar)) return; + + if (isVal) handleFlagUsage(localNode, ConfigurationKeys.VAL_FLAG_USAGE, "val"); + if (isVar) handleFlagUsage(localNode, ConfigurationKeys.VAR_FLAG_USAGE, "var"); boolean variableOfForEach = false; @@ -54,23 +64,37 @@ public class HandleVal extends EclipseASTAdapter { variableOfForEach = fs.elementVariable == local; } + String annotation = isVal ? "val" : "var"; if (local.initialization == null && !variableOfForEach) { - localNode.addError("'val' on a local variable requires an initializer expression"); + localNode.addError("'" + annotation + "' on a local variable requires an initializer expression"); return; } if (local.initialization instanceof ArrayInitializer) { - localNode.addError("'val' is not compatible with array initializer expressions. Use the full form (new int[] { ... } instead of just { ... })"); + localNode.addError("'" + annotation + "' is not compatible with array initializer expressions. Use the full form (new int[] { ... } instead of just { ... })"); return; } - if (localNode.directUp().get() instanceof ForStatement) { + ASTNode parentRaw = localNode.directUp().get(); + + if (isVal && parentRaw instanceof ForStatement) { localNode.addError("'val' is not allowed in old-style for loops"); return; } + if (parentRaw instanceof ForStatement && ((ForStatement) parentRaw).initializations != null && ((ForStatement) parentRaw).initializations.length > 1) { + localNode.addError("'var' is not allowed in old-style for loops if there is more than 1 initializer"); + return; + } + if (local.initialization != null && local.initialization.getClass().getName().equals("org.eclipse.jdt.internal.compiler.ast.LambdaExpression")) { - localNode.addError("'val' is not allowed with lambda expressions."); + localNode.addError("'" + annotation + "' is not allowed with lambda expressions."); + return; + } + + if(isVar && local.initialization instanceof NullLiteral) { + localNode.addError("variable initializer is 'null'"); + return; } } } diff --git a/src/core/lombok/eclipse/handlers/HandleValue.java b/src/core/lombok/eclipse/handlers/HandleValue.java index 79c11771..2e0338a8 100644 --- a/src/core/lombok/eclipse/handlers/HandleValue.java +++ b/src/core/lombok/eclipse/handlers/HandleValue.java @@ -47,6 +47,12 @@ import org.mangosdk.spi.ProviderFor; @ProviderFor(EclipseAnnotationHandler.class) @HandlerPriority(-512) //-2^9; to ensure @EqualsAndHashCode and such pick up on this handler making the class final and messing with the fields' access levels, run earlier. public class HandleValue extends EclipseAnnotationHandler<Value> { + private HandleFieldDefaults handleFieldDefaults = new HandleFieldDefaults(); + private HandleGetter handleGetter = new HandleGetter(); + private HandleEqualsAndHashCode handleEqualsAndHashCode = new HandleEqualsAndHashCode(); + private HandleToString handleToString = new HandleToString(); + private HandleConstructor handleConstructor = new HandleConstructor(); + public void handle(AnnotationValues<Value> annotation, Annotation ast, EclipseNode annotationNode) { handleFlagUsage(annotationNode, ConfigurationKeys.VALUE_FLAG_USAGE, "@Value"); @@ -72,7 +78,7 @@ public class HandleValue extends EclipseAnnotationHandler<Value> { } } - new HandleFieldDefaults().generateFieldDefaultsForType(typeNode, annotationNode, AccessLevel.PRIVATE, true, true); + handleFieldDefaults.generateFieldDefaultsForType(typeNode, annotationNode, AccessLevel.PRIVATE, true, true); //Careful: Generate the public static constructor (if there is one) LAST, so that any attempt to //'find callers' on the annotation node will find callers of the constructor, which is by far the @@ -80,10 +86,11 @@ public class HandleValue extends EclipseAnnotationHandler<Value> { //for whatever reason, though you can find callers of that one by focusing on the class name itself //and hitting 'find callers'. - new HandleGetter().generateGetterForType(typeNode, annotationNode, AccessLevel.PUBLIC, true); - new HandleEqualsAndHashCode().generateEqualsAndHashCodeForType(typeNode, annotationNode); - new HandleToString().generateToStringForType(typeNode, annotationNode); - new HandleConstructor().generateAllArgsConstructor(typeNode, AccessLevel.PUBLIC, ann.staticConstructor(), SkipIfConstructorExists.YES, + handleGetter.generateGetterForType(typeNode, annotationNode, AccessLevel.PUBLIC, true, Collections.<Annotation>emptyList()); + handleEqualsAndHashCode.generateEqualsAndHashCodeForType(typeNode, annotationNode); + handleToString.generateToStringForType(typeNode, annotationNode); + handleConstructor.generateAllArgsConstructor(typeNode, AccessLevel.PUBLIC, ann.staticConstructor(), SkipIfConstructorExists.YES, Collections.<Annotation>emptyList(), annotationNode); + handleConstructor.generateExtraNoArgsConstructor(typeNode, annotationNode); } } diff --git a/src/core/lombok/eclipse/handlers/HandleWither.java b/src/core/lombok/eclipse/handlers/HandleWither.java index d8ac8df6..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; @@ -127,8 +126,8 @@ public class HandleWither extends EclipseAnnotationHandler<Wither> { AccessLevel level = annotation.getInstance().value(); if (level == AccessLevel.NONE || node == null) return; - List<Annotation> onMethod = unboxAndRemoveAnnotationParameter(ast, "onMethod", "@Wither(onMethod=", annotationNode); - List<Annotation> onParam = unboxAndRemoveAnnotationParameter(ast, "onParam", "@Wither(onParam=", annotationNode); + List<Annotation> onMethod = unboxAndRemoveAnnotationParameter(ast, "onMethod", "@Wither(onMethod", annotationNode); + List<Annotation> onParam = unboxAndRemoveAnnotationParameter(ast, "onParam", "@Wither(onParam", annotationNode); switch (node.getKind()) { case FIELD: diff --git a/src/core/lombok/eclipse/handlers/SetGeneratedByVisitor.java b/src/core/lombok/eclipse/handlers/SetGeneratedByVisitor.java index b42761f4..89964914 100644 --- a/src/core/lombok/eclipse/handlers/SetGeneratedByVisitor.java +++ b/src/core/lombok/eclipse/handlers/SetGeneratedByVisitor.java @@ -369,11 +369,6 @@ public final class SetGeneratedByVisitor extends ASTVisitor { return super.visit(node, scope); } - @Override public boolean visit(ArrayInitializer node, ClassScope scope) { - fixPositions(setGeneratedBy(node, source)); - return super.visit(node, scope); - } - @Override public boolean visit(ArrayQualifiedTypeReference node, BlockScope scope) { fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); diff --git a/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaSingularizer.java b/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaSingularizer.java index 242bde1f..f1687c9c 100644 --- a/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaSingularizer.java +++ b/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaSingularizer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 The Project Lombok Authors. + * Copyright (C) 2015-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 @@ -34,7 +34,10 @@ import lombok.core.handlers.HandlerUtil; import lombok.eclipse.EclipseNode; import lombok.eclipse.handlers.EclipseSingularsRecipes.EclipseSingularizer; import lombok.eclipse.handlers.EclipseSingularsRecipes.SingularData; +import lombok.eclipse.handlers.EclipseSingularsRecipes.StatementMaker; +import lombok.eclipse.handlers.EclipseSingularsRecipes.TypeReferenceMaker; +import org.eclipse.jdt.internal.compiler.ast.Annotation; import org.eclipse.jdt.internal.compiler.ast.Argument; import org.eclipse.jdt.internal.compiler.ast.Assignment; import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; @@ -51,13 +54,11 @@ import org.eclipse.jdt.internal.compiler.ast.NullLiteral; import org.eclipse.jdt.internal.compiler.ast.OperatorIds; import org.eclipse.jdt.internal.compiler.ast.QualifiedNameReference; import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference; -import org.eclipse.jdt.internal.compiler.ast.ReturnStatement; import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; import org.eclipse.jdt.internal.compiler.ast.Statement; import org.eclipse.jdt.internal.compiler.ast.ThisReference; import org.eclipse.jdt.internal.compiler.ast.TypeReference; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; -import org.eclipse.jdt.internal.compiler.lookup.TypeIds; abstract class EclipseGuavaSingularizer extends EclipseSingularizer { protected String getSimpleTargetTypeName(SingularData data) { @@ -96,21 +97,13 @@ abstract class EclipseGuavaSingularizer extends EclipseSingularizer { return Collections.singletonList(injectFieldAndMarkGenerated(builderType, buildField)); } - @Override public void generateMethods(SingularData data, EclipseNode builderType, boolean fluent, boolean chain) { - TypeReference returnType = chain ? cloneSelfType(builderType) : TypeReference.baseTypeReference(TypeIds.T_void, 0); - Statement returnStatement = chain ? new ReturnStatement(new ThisReference(0, 0), 0, 0) : null; - generateSingularMethod(returnType, returnStatement, data, builderType, fluent); - - returnType = chain ? cloneSelfType(builderType) : TypeReference.baseTypeReference(TypeIds.T_void, 0); - returnStatement = chain ? new ReturnStatement(new ThisReference(0, 0), 0, 0) : null; - generatePluralMethod(returnType, returnStatement, data, builderType, fluent); - - returnType = chain ? cloneSelfType(builderType) : TypeReference.baseTypeReference(TypeIds.T_void, 0); - returnStatement = chain ? new ReturnStatement(new ThisReference(0, 0), 0, 0) : null; - generateClearMethod(returnType, returnStatement, data, builderType); + @Override public void generateMethods(SingularData data, boolean deprecate, EclipseNode builderType, boolean fluent, TypeReferenceMaker returnTypeMaker, StatementMaker returnStatementMaker) { + generateSingularMethod(deprecate, returnTypeMaker.make(), returnStatementMaker.make(), data, builderType, fluent); + generatePluralMethod(deprecate, returnTypeMaker.make(), returnStatementMaker.make(), data, builderType, fluent); + generateClearMethod(deprecate, returnTypeMaker.make(), returnStatementMaker.make(), data, builderType); } - void generateClearMethod(TypeReference returnType, Statement returnStatement, SingularData data, EclipseNode builderType) { + void generateClearMethod(boolean deprecate, TypeReference returnType, Statement returnStatement, SingularData data, EclipseNode builderType) { MethodDeclaration md = new MethodDeclaration(((CompilationUnitDeclaration) builderType.top().get()).compilationResult); md.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; md.modifiers = ClassFileConstants.AccPublic; @@ -121,10 +114,12 @@ abstract class EclipseGuavaSingularizer extends EclipseSingularizer { md.selector = HandlerUtil.buildAccessorName("clear", new String(data.getPluralName())).toCharArray(); md.statements = returnStatement != null ? new Statement[] {a, returnStatement} : new Statement[] {a}; md.returnType = returnType; + md.annotations = deprecate ? new Annotation[] { generateDeprecatedAnnotation(data.getSource()) } : null; + injectMethod(builderType, md); } - void generateSingularMethod(TypeReference returnType, Statement returnStatement, SingularData data, EclipseNode builderType, boolean fluent) { + void generateSingularMethod(boolean deprecate, TypeReference returnType, Statement returnStatement, SingularData data, EclipseNode builderType, boolean fluent) { LombokImmutableList<String> suffixes = getArgumentSuffixes(); char[][] names = new char[suffixes.size()][]; for (int i = 0; i < suffixes.size(); i++) { @@ -159,12 +154,13 @@ abstract class EclipseGuavaSingularizer extends EclipseSingularizer { } md.returnType = returnType; md.selector = fluent ? data.getSingularName() : HandlerUtil.buildAccessorName(getAddMethodName(), new String(data.getSingularName())).toCharArray(); + md.annotations = deprecate ? new Annotation[] { generateDeprecatedAnnotation(data.getSource()) } : null; data.setGeneratedByRecursive(md); injectMethod(builderType, md); } - void generatePluralMethod(TypeReference returnType, Statement returnStatement, SingularData data, EclipseNode builderType, boolean fluent) { + void generatePluralMethod(boolean deprecate, TypeReference returnType, Statement returnStatement, SingularData data, EclipseNode builderType, boolean fluent) { MethodDeclaration md = new MethodDeclaration(((CompilationUnitDeclaration) builderType.top().get()).compilationResult); md.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; md.modifiers = ClassFileConstants.AccPublic; @@ -190,12 +186,13 @@ abstract class EclipseGuavaSingularizer extends EclipseSingularizer { md.arguments = new Argument[] {param}; md.returnType = returnType; md.selector = fluent ? data.getPluralName() : HandlerUtil.buildAccessorName(getAddMethodName() + "All", new String(data.getPluralName())).toCharArray(); + md.annotations = deprecate ? new Annotation[] { generateDeprecatedAnnotation(data.getSource()) } : null; data.setGeneratedByRecursive(md); injectMethod(builderType, md); } - @Override public void appendBuildCode(SingularData data, EclipseNode builderType, List<Statement> statements, char[] targetVariableName) { + @Override public void appendBuildCode(SingularData data, EclipseNode builderType, List<Statement> statements, char[] targetVariableName, String builderVariable) { TypeReference varType = new QualifiedTypeReference(fromQualifiedName(data.getTargetFqn()), NULL_POSS); String simpleTypeName = getSimpleTargetTypeName(data); int agrumentsCount = getTypeArgumentsCount(); @@ -214,14 +211,14 @@ abstract class EclipseGuavaSingularizer extends EclipseSingularizer { invokeBuild = new MessageSend(); invokeBuild.selector = new char[] {'b', 'u', 'i', 'l', 'd'}; FieldReference thisDotField = new FieldReference(data.getPluralName(), 0L); - thisDotField.receiver = new ThisReference(0, 0); + thisDotField.receiver = getBuilderReference(builderVariable); invokeBuild.receiver = thisDotField; } Expression isNull; { //this.pluralName == null FieldReference thisDotField = new FieldReference(data.getPluralName(), 0L); - thisDotField.receiver = new ThisReference(0, 0); + thisDotField.receiver = getBuilderReference(builderVariable); isNull = new EqualExpression(thisDotField, new NullLiteral(0, 0), OperatorIds.EQUAL_EQUAL); } diff --git a/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilListSetSingularizer.java b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilListSetSingularizer.java index 2d8083d3..5f86a4dc 100644 --- a/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilListSetSingularizer.java +++ b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilListSetSingularizer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 The Project Lombok Authors. + * Copyright (C) 2015-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,7 +31,10 @@ import java.util.List; import lombok.core.handlers.HandlerUtil; import lombok.eclipse.EclipseNode; import lombok.eclipse.handlers.EclipseSingularsRecipes.SingularData; +import lombok.eclipse.handlers.EclipseSingularsRecipes.StatementMaker; +import lombok.eclipse.handlers.EclipseSingularsRecipes.TypeReferenceMaker; +import org.eclipse.jdt.internal.compiler.ast.Annotation; import org.eclipse.jdt.internal.compiler.ast.Argument; import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; import org.eclipse.jdt.internal.compiler.ast.EqualExpression; @@ -44,14 +47,12 @@ import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; import org.eclipse.jdt.internal.compiler.ast.NullLiteral; import org.eclipse.jdt.internal.compiler.ast.OperatorIds; import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference; -import org.eclipse.jdt.internal.compiler.ast.ReturnStatement; import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; import org.eclipse.jdt.internal.compiler.ast.Statement; import org.eclipse.jdt.internal.compiler.ast.ThisReference; import org.eclipse.jdt.internal.compiler.ast.TypeReference; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; -import org.eclipse.jdt.internal.compiler.lookup.TypeIds; abstract class EclipseJavaUtilListSetSingularizer extends EclipseJavaUtilSingularizer { @Override public List<char[]> listFieldsToBeGenerated(SingularData data, EclipseNode builderType) { @@ -87,26 +88,18 @@ abstract class EclipseJavaUtilListSetSingularizer extends EclipseJavaUtilSingula return Collections.singletonList(injectFieldAndMarkGenerated(builderType, buildField)); } - @Override public void generateMethods(SingularData data, EclipseNode builderType, boolean fluent, boolean chain) { + @Override public void generateMethods(SingularData data, boolean deprecate, EclipseNode builderType, boolean fluent, TypeReferenceMaker returnTypeMaker, StatementMaker returnStatementMaker) { if (useGuavaInstead(builderType)) { - guavaListSetSingularizer.generateMethods(data, builderType, fluent, chain); + guavaListSetSingularizer.generateMethods(data, deprecate, builderType, fluent, returnTypeMaker, returnStatementMaker); return; } - TypeReference returnType = chain ? cloneSelfType(builderType) : TypeReference.baseTypeReference(TypeIds.T_void, 0); - Statement returnStatement = chain ? new ReturnStatement(new ThisReference(0, 0), 0, 0) : null; - generateSingularMethod(returnType, returnStatement, data, builderType, fluent); - - returnType = chain ? cloneSelfType(builderType) : TypeReference.baseTypeReference(TypeIds.T_void, 0); - returnStatement = chain ? new ReturnStatement(new ThisReference(0, 0), 0, 0) : null; - generatePluralMethod(returnType, returnStatement, data, builderType, fluent); - - returnType = chain ? cloneSelfType(builderType) : TypeReference.baseTypeReference(TypeIds.T_void, 0); - returnStatement = chain ? new ReturnStatement(new ThisReference(0, 0), 0, 0) : null; - generateClearMethod(returnType, returnStatement, data, builderType); + generateSingularMethod(deprecate, returnTypeMaker.make(), returnStatementMaker.make(), data, builderType, fluent); + generatePluralMethod(deprecate, returnTypeMaker.make(), returnStatementMaker.make(), data, builderType, fluent); + generateClearMethod(deprecate, returnTypeMaker.make(), returnStatementMaker.make(), data, builderType); } - private void generateClearMethod(TypeReference returnType, Statement returnStatement, SingularData data, EclipseNode builderType) { + private void generateClearMethod(boolean deprecate, TypeReference returnType, Statement returnStatement, SingularData data, EclipseNode builderType) { MethodDeclaration md = new MethodDeclaration(((CompilationUnitDeclaration) builderType.top().get()).compilationResult); md.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; md.modifiers = ClassFileConstants.AccPublic; @@ -122,10 +115,11 @@ abstract class EclipseJavaUtilListSetSingularizer extends EclipseJavaUtilSingula Statement clearStatement = new IfStatement(new EqualExpression(thisDotField, new NullLiteral(0, 0), OperatorIds.NOT_EQUAL), clearMsg, 0, 0); md.statements = returnStatement != null ? new Statement[] {clearStatement, returnStatement} : new Statement[] {clearStatement}; md.returnType = returnType; + md.annotations = deprecate ? new Annotation[] { generateDeprecatedAnnotation(data.getSource()) } : null; injectMethod(builderType, md); } - void generateSingularMethod(TypeReference returnType, Statement returnStatement, SingularData data, EclipseNode builderType, boolean fluent) { + void generateSingularMethod(boolean deprecate, TypeReference returnType, Statement returnStatement, SingularData data, EclipseNode builderType, boolean fluent) { MethodDeclaration md = new MethodDeclaration(((CompilationUnitDeclaration) builderType.top().get()).compilationResult); md.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; md.modifiers = ClassFileConstants.AccPublic; @@ -148,12 +142,13 @@ abstract class EclipseJavaUtilListSetSingularizer extends EclipseJavaUtilSingula md.arguments = new Argument[] {param}; md.returnType = returnType; md.selector = fluent ? data.getSingularName() : HandlerUtil.buildAccessorName("add", new String(data.getSingularName())).toCharArray(); + md.annotations = deprecate ? new Annotation[] { generateDeprecatedAnnotation(data.getSource()) } : null; data.setGeneratedByRecursive(md); injectMethod(builderType, md); } - void generatePluralMethod(TypeReference returnType, Statement returnStatement, SingularData data, EclipseNode builderType, boolean fluent) { + void generatePluralMethod(boolean deprecate, TypeReference returnType, Statement returnStatement, SingularData data, EclipseNode builderType, boolean fluent) { MethodDeclaration md = new MethodDeclaration(((CompilationUnitDeclaration) builderType.top().get()).compilationResult); md.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; md.modifiers = ClassFileConstants.AccPublic; @@ -178,6 +173,7 @@ abstract class EclipseJavaUtilListSetSingularizer extends EclipseJavaUtilSingula md.arguments = new Argument[] {param}; md.returnType = returnType; md.selector = fluent ? data.getPluralName() : HandlerUtil.buildAccessorName("addAll", new String(data.getPluralName())).toCharArray(); + md.annotations = deprecate ? new Annotation[] { generateDeprecatedAnnotation(data.getSource()) } : null; data.setGeneratedByRecursive(md); injectMethod(builderType, md); diff --git a/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilListSingularizer.java b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilListSingularizer.java index 576115b0..f512bacf 100644 --- a/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilListSingularizer.java +++ b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilListSingularizer.java @@ -45,7 +45,6 @@ import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference; import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; import org.eclipse.jdt.internal.compiler.ast.Statement; import org.eclipse.jdt.internal.compiler.ast.SwitchStatement; -import org.eclipse.jdt.internal.compiler.ast.ThisReference; import org.eclipse.jdt.internal.compiler.ast.TypeReference; import org.mangosdk.spi.ProviderFor; @@ -56,9 +55,9 @@ public class EclipseJavaUtilListSingularizer extends EclipseJavaUtilListSetSingu return LombokImmutableList.of("java.util.List", "java.util.Collection", "java.lang.Iterable"); } - @Override public void appendBuildCode(SingularData data, EclipseNode builderType, List<Statement> statements, char[] targetVariableName) { + @Override public void appendBuildCode(SingularData data, EclipseNode builderType, List<Statement> statements, char[] targetVariableName, String builderVariable) { if (useGuavaInstead(builderType)) { - guavaListSetSingularizer.appendBuildCode(data, builderType, statements, targetVariableName); + guavaListSetSingularizer.appendBuildCode(data, builderType, statements, targetVariableName, builderVariable); return; } @@ -76,7 +75,7 @@ public class EclipseJavaUtilListSingularizer extends EclipseJavaUtilListSetSingu /* case 1: (singleton) break; */ { switchContents.add(new CaseStatement(makeIntLiteral(new char[] {'1'}, null), 0, 0)); FieldReference thisDotField = new FieldReference(data.getPluralName(), 0L); - thisDotField.receiver = new ThisReference(0, 0); + thisDotField.receiver = getBuilderReference(builderVariable); MessageSend thisDotFieldGet0 = new MessageSend(); thisDotFieldGet0.receiver = thisDotField; thisDotFieldGet0.selector = new char[] {'g', 'e', 't'}; @@ -97,7 +96,7 @@ public class EclipseJavaUtilListSingularizer extends EclipseJavaUtilListSetSingu Expression argToUnmodifiable; /* new j.u.ArrayList<Generics>(this.pluralName); */ { FieldReference thisDotPluralName = new FieldReference(data.getPluralName(), 0L); - thisDotPluralName.receiver = new ThisReference(0, 0); + thisDotPluralName.receiver = getBuilderReference(builderVariable); TypeReference targetTypeExpr = new QualifiedTypeReference(JAVA_UTIL_ARRAYLIST, NULL_POSS); targetTypeExpr = addTypeArgs(1, false, builderType, targetTypeExpr, data.getTypeArgs()); AllocationExpression constructorCall = new AllocationExpression(); @@ -117,7 +116,7 @@ public class EclipseJavaUtilListSingularizer extends EclipseJavaUtilListSetSingu SwitchStatement switchStat = new SwitchStatement(); switchStat.statements = switchContents.toArray(new Statement[switchContents.size()]); - switchStat.expression = getSize(builderType, data.getPluralName(), true); + switchStat.expression = getSize(builderType, data.getPluralName(), true, builderVariable); TypeReference localShadowerType = new QualifiedTypeReference(Eclipse.fromQualifiedName(data.getTargetFqn()), NULL_POSS); localShadowerType = addTypeArgs(1, false, builderType, localShadowerType, data.getTypeArgs()); diff --git a/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilMapSingularizer.java b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilMapSingularizer.java index ef9e2a76..69c2186a 100644 --- a/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilMapSingularizer.java +++ b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilMapSingularizer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 The Project Lombok Authors. + * Copyright (C) 2015-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 @@ -29,6 +29,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import org.eclipse.jdt.internal.compiler.ast.Annotation; import org.eclipse.jdt.internal.compiler.ast.Argument; import org.eclipse.jdt.internal.compiler.ast.Block; import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; @@ -44,13 +45,11 @@ import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; import org.eclipse.jdt.internal.compiler.ast.NullLiteral; import org.eclipse.jdt.internal.compiler.ast.OperatorIds; import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference; -import org.eclipse.jdt.internal.compiler.ast.ReturnStatement; import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; import org.eclipse.jdt.internal.compiler.ast.Statement; import org.eclipse.jdt.internal.compiler.ast.ThisReference; import org.eclipse.jdt.internal.compiler.ast.TypeReference; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; -import org.eclipse.jdt.internal.compiler.lookup.TypeIds; import org.mangosdk.spi.ProviderFor; import lombok.core.LombokImmutableList; @@ -58,6 +57,8 @@ import lombok.core.handlers.HandlerUtil; import lombok.eclipse.EclipseNode; import lombok.eclipse.handlers.EclipseSingularsRecipes.EclipseSingularizer; import lombok.eclipse.handlers.EclipseSingularsRecipes.SingularData; +import lombok.eclipse.handlers.EclipseSingularsRecipes.StatementMaker; +import lombok.eclipse.handlers.EclipseSingularsRecipes.TypeReferenceMaker; @ProviderFor(EclipseSingularizer.class) public class EclipseJavaUtilMapSingularizer extends EclipseJavaUtilSingularizer { @@ -132,26 +133,18 @@ public class EclipseJavaUtilMapSingularizer extends EclipseJavaUtilSingularizer return Arrays.asList(keyFieldNode, valueFieldNode); } - @Override public void generateMethods(SingularData data, EclipseNode builderType, boolean fluent, boolean chain) { + @Override public void generateMethods(SingularData data, boolean deprecate, EclipseNode builderType, boolean fluent, TypeReferenceMaker returnTypeMaker, StatementMaker returnStatementMaker) { if (useGuavaInstead(builderType)) { - guavaMapSingularizer.generateMethods(data, builderType, fluent, chain); + guavaMapSingularizer.generateMethods(data, deprecate, builderType, fluent, returnTypeMaker, returnStatementMaker); return; } - TypeReference returnType = chain ? cloneSelfType(builderType) : TypeReference.baseTypeReference(TypeIds.T_void, 0); - Statement returnStatement = chain ? new ReturnStatement(new ThisReference(0, 0), 0, 0) : null; - generateSingularMethod(returnType, returnStatement, data, builderType, fluent); - - returnType = chain ? cloneSelfType(builderType) : TypeReference.baseTypeReference(TypeIds.T_void, 0); - returnStatement = chain ? new ReturnStatement(new ThisReference(0, 0), 0, 0) : null; - generatePluralMethod(returnType, returnStatement, data, builderType, fluent); - - returnType = chain ? cloneSelfType(builderType) : TypeReference.baseTypeReference(TypeIds.T_void, 0); - returnStatement = chain ? new ReturnStatement(new ThisReference(0, 0), 0, 0) : null; - generateClearMethod(returnType, returnStatement, data, builderType); + generateSingularMethod(deprecate, returnTypeMaker.make(), returnStatementMaker.make(), data, builderType, fluent); + generatePluralMethod(deprecate, returnTypeMaker.make(), returnStatementMaker.make(), data, builderType, fluent); + generateClearMethod(deprecate, returnTypeMaker.make(), returnStatementMaker.make(), data, builderType); } - private void generateClearMethod(TypeReference returnType, Statement returnStatement, SingularData data, EclipseNode builderType) { + private void generateClearMethod(boolean deprecate, TypeReference returnType, Statement returnStatement, SingularData data, EclipseNode builderType) { MethodDeclaration md = new MethodDeclaration(((CompilationUnitDeclaration) builderType.top().get()).compilationResult); md.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; md.modifiers = ClassFileConstants.AccPublic; @@ -178,10 +171,12 @@ public class EclipseJavaUtilMapSingularizer extends EclipseJavaUtilSingularizer Statement clearStatement = new IfStatement(new EqualExpression(thisDotField, new NullLiteral(0, 0), OperatorIds.NOT_EQUAL), clearMsgs, 0, 0); md.statements = returnStatement != null ? new Statement[] {clearStatement, returnStatement} : new Statement[] {clearStatement}; md.returnType = returnType; + md.annotations = deprecate ? new Annotation[] { generateDeprecatedAnnotation(data.getSource()) } : null; + injectMethod(builderType, md); } - private void generateSingularMethod(TypeReference returnType, Statement returnStatement, SingularData data, EclipseNode builderType, boolean fluent) { + private void generateSingularMethod(boolean deprecate, TypeReference returnType, Statement returnStatement, SingularData data, EclipseNode builderType, boolean fluent) { MethodDeclaration md = new MethodDeclaration(((CompilationUnitDeclaration) builderType.top().get()).compilationResult); md.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; md.modifiers = ClassFileConstants.AccPublic; @@ -225,12 +220,13 @@ public class EclipseJavaUtilMapSingularizer extends EclipseJavaUtilSingularizer md.arguments = new Argument[] {keyParam, valueParam}; md.returnType = returnType; md.selector = fluent ? data.getSingularName() : HandlerUtil.buildAccessorName("put", new String(data.getSingularName())).toCharArray(); + md.annotations = deprecate ? new Annotation[] { generateDeprecatedAnnotation(data.getSource()) } : null; data.setGeneratedByRecursive(md); injectMethod(builderType, md); } - private void generatePluralMethod(TypeReference returnType, Statement returnStatement, SingularData data, EclipseNode builderType, boolean fluent) { + private void generatePluralMethod(boolean deprecate, TypeReference returnType, Statement returnStatement, SingularData data, EclipseNode builderType, boolean fluent) { MethodDeclaration md = new MethodDeclaration(((CompilationUnitDeclaration) builderType.top().get()).compilationResult); md.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; md.modifiers = ClassFileConstants.AccPublic; @@ -288,21 +284,22 @@ public class EclipseJavaUtilMapSingularizer extends EclipseJavaUtilSingularizer md.arguments = new Argument[] {param}; md.returnType = returnType; md.selector = fluent ? data.getPluralName() : HandlerUtil.buildAccessorName("putAll", new String(data.getPluralName())).toCharArray(); + md.annotations = deprecate ? new Annotation[] { generateDeprecatedAnnotation(data.getSource()) } : null; data.setGeneratedByRecursive(md); injectMethod(builderType, md); } - @Override public void appendBuildCode(SingularData data, EclipseNode builderType, List<Statement> statements, char[] targetVariableName) { + @Override public void appendBuildCode(SingularData data, EclipseNode builderType, List<Statement> statements, char[] targetVariableName, String builderVariable) { if (useGuavaInstead(builderType)) { - guavaMapSingularizer.appendBuildCode(data, builderType, statements, targetVariableName); + guavaMapSingularizer.appendBuildCode(data, builderType, statements, targetVariableName, builderVariable); return; } if (data.getTargetFqn().equals("java.util.Map")) { - statements.addAll(createJavaUtilSetMapInitialCapacitySwitchStatements(data, builderType, true, "emptyMap", "singletonMap", "LinkedHashMap")); + statements.addAll(createJavaUtilSetMapInitialCapacitySwitchStatements(data, builderType, true, "emptyMap", "singletonMap", "LinkedHashMap", builderVariable)); } else { - statements.addAll(createJavaUtilSimpleCreationAndFillStatements(data, builderType, true, true, false, true, "TreeMap")); + statements.addAll(createJavaUtilSimpleCreationAndFillStatements(data, builderType, true, true, false, true, "TreeMap", builderVariable)); } } } diff --git a/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilSetSingularizer.java b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilSetSingularizer.java index 2d16eae0..200e615e 100644 --- a/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilSetSingularizer.java +++ b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilSetSingularizer.java @@ -37,16 +37,16 @@ public class EclipseJavaUtilSetSingularizer extends EclipseJavaUtilListSetSingul return LombokImmutableList.of("java.util.Set", "java.util.SortedSet", "java.util.NavigableSet"); } - @Override public void appendBuildCode(SingularData data, EclipseNode builderType, List<Statement> statements, char[] targetVariableName) { + @Override public void appendBuildCode(SingularData data, EclipseNode builderType, List<Statement> statements, char[] targetVariableName, String builderVariable) { if (useGuavaInstead(builderType)) { - guavaListSetSingularizer.appendBuildCode(data, builderType, statements, targetVariableName); + guavaListSetSingularizer.appendBuildCode(data, builderType, statements, targetVariableName, builderVariable); return; } if (data.getTargetFqn().equals("java.util.Set")) { - statements.addAll(createJavaUtilSetMapInitialCapacitySwitchStatements(data, builderType, false, "emptySet", "singleton", "LinkedHashSet")); + statements.addAll(createJavaUtilSetMapInitialCapacitySwitchStatements(data, builderType, false, "emptySet", "singleton", "LinkedHashSet", builderVariable)); } else { - statements.addAll(createJavaUtilSimpleCreationAndFillStatements(data, builderType, false, true, false, true, "TreeSet")); + statements.addAll(createJavaUtilSimpleCreationAndFillStatements(data, builderType, false, true, false, true, "TreeSet", builderVariable)); } } } diff --git a/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilSingularizer.java b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilSingularizer.java index 6661f4af..8bcfa65d 100644 --- a/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilSingularizer.java +++ b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilSingularizer.java @@ -90,7 +90,7 @@ abstract class EclipseJavaUtilSingularizer extends EclipseSingularizer { return Boolean.TRUE.equals(node.getAst().readConfiguration(ConfigurationKeys.SINGULAR_USE_GUAVA)); } - protected List<Statement> createJavaUtilSetMapInitialCapacitySwitchStatements(SingularData data, EclipseNode builderType, boolean mapMode, String emptyCollectionMethod, String singletonCollectionMethod, String targetType) { + protected List<Statement> createJavaUtilSetMapInitialCapacitySwitchStatements(SingularData data, EclipseNode builderType, boolean mapMode, String emptyCollectionMethod, String singletonCollectionMethod, String targetType, String builderVariable) { List<Statement> switchContents = new ArrayList<Statement>(); char[] keyName = mapMode ? (new String(data.getPluralName()) + "$key").toCharArray() : data.getPluralName(); @@ -112,7 +112,7 @@ abstract class EclipseJavaUtilSingularizer extends EclipseSingularizer { /* !mapMode: pluralName = java.util.Collections.singletonCollectionMethod(this.pluralName.get(0)); mapMode: pluralName = java.util.Collections.singletonCollectionMethod(this.pluralName$key.get(0), this.pluralName$value.get(0)); */ { FieldReference thisDotKey = new FieldReference(keyName, 0L); - thisDotKey.receiver = new ThisReference(0, 0); + thisDotKey.receiver = getBuilderReference(builderVariable); MessageSend thisDotKeyGet0 = new MessageSend(); thisDotKeyGet0.receiver = thisDotKey; thisDotKeyGet0.selector = new char[] {'g', 'e', 't'}; @@ -122,7 +122,7 @@ abstract class EclipseJavaUtilSingularizer extends EclipseSingularizer { if (mapMode) { char[] valueName = (new String(data.getPluralName()) + "$value").toCharArray(); FieldReference thisDotValue = new FieldReference(valueName, 0L); - thisDotValue.receiver = new ThisReference(0, 0); + thisDotValue.receiver = getBuilderReference(builderVariable); MessageSend thisDotValueGet0 = new MessageSend(); thisDotValueGet0.receiver = thisDotValue; thisDotValueGet0.selector = new char[] {'g', 'e', 't'}; @@ -143,12 +143,12 @@ abstract class EclipseJavaUtilSingularizer extends EclipseSingularizer { { // default: switchContents.add(new CaseStatement(null, 0, 0)); - switchContents.addAll(createJavaUtilSimpleCreationAndFillStatements(data, builderType, mapMode, false, true, emptyCollectionMethod == null, targetType)); + switchContents.addAll(createJavaUtilSimpleCreationAndFillStatements(data, builderType, mapMode, false, true, emptyCollectionMethod == null, targetType, builderVariable)); } SwitchStatement switchStat = new SwitchStatement(); switchStat.statements = switchContents.toArray(new Statement[switchContents.size()]); - switchStat.expression = getSize(builderType, keyName, true); + switchStat.expression = getSize(builderType, keyName, true, builderVariable); TypeReference localShadowerType = new QualifiedTypeReference(fromQualifiedName(data.getTargetFqn()), NULL_POSS); localShadowerType = addTypeArgs(mapMode ? 2 : 1, false, builderType, localShadowerType, data.getTypeArgs()); @@ -157,7 +157,7 @@ abstract class EclipseJavaUtilSingularizer extends EclipseSingularizer { return Arrays.asList(varDefStat, switchStat); } - protected List<Statement> createJavaUtilSimpleCreationAndFillStatements(SingularData data, EclipseNode builderType, boolean mapMode, boolean defineVar, boolean addInitialCapacityArg, boolean nullGuard, String targetType) { + protected List<Statement> createJavaUtilSimpleCreationAndFillStatements(SingularData data, EclipseNode builderType, boolean mapMode, boolean defineVar, boolean addInitialCapacityArg, boolean nullGuard, String targetType, String builderVariable) { char[] varName = mapMode ? (new String(data.getPluralName()) + "$key").toCharArray() : data.getPluralName(); Statement createStat; { @@ -166,11 +166,11 @@ abstract class EclipseJavaUtilSingularizer extends EclipseSingularizer { if (addInitialCapacityArg) { // this.varName.size() < MAX_POWER_OF_2 ? 1 + this.varName.size() + (this.varName.size() - 3) / 3 : Integer.MAX_VALUE; // lessThanCutOff = this.varName.size() < MAX_POWER_OF_2 - Expression lessThanCutoff = new BinaryExpression(getSize(builderType, varName, nullGuard), makeIntLiteral("0x40000000".toCharArray(), null), OperatorIds.LESS); + Expression lessThanCutoff = new BinaryExpression(getSize(builderType, varName, nullGuard, builderVariable), makeIntLiteral("0x40000000".toCharArray(), null), OperatorIds.LESS); FieldReference integerMaxValue = new FieldReference("MAX_VALUE".toCharArray(), 0L); integerMaxValue.receiver = new QualifiedNameReference(TypeConstants.JAVA_LANG_INTEGER, NULL_POSS, 0, 0); - Expression sizeFormulaLeft = new BinaryExpression(makeIntLiteral(new char[] {'1'}, null), getSize(builderType, varName, nullGuard), OperatorIds.PLUS); - Expression sizeFormulaRightLeft = new BinaryExpression(getSize(builderType, varName, nullGuard), makeIntLiteral(new char[] {'3'}, null), OperatorIds.MINUS); + Expression sizeFormulaLeft = new BinaryExpression(makeIntLiteral(new char[] {'1'}, null), getSize(builderType, varName, nullGuard, builderVariable), OperatorIds.PLUS); + Expression sizeFormulaRightLeft = new BinaryExpression(getSize(builderType, varName, nullGuard, builderVariable), makeIntLiteral(new char[] {'3'}, null), OperatorIds.MINUS); Expression sizeFormulaRight = new BinaryExpression(sizeFormulaRightLeft, makeIntLiteral(new char[] {'3'}, null), OperatorIds.DIVIDE); Expression sizeFormula = new BinaryExpression(sizeFormulaLeft, sizeFormulaRight, OperatorIds.PLUS); Expression cond = new ConditionalExpression(lessThanCutoff, sizeFormula, integerMaxValue); @@ -203,9 +203,9 @@ abstract class EclipseJavaUtilSingularizer extends EclipseSingularizer { pluralnameDotPut.selector = new char[] {'p', 'u', 't'}; pluralnameDotPut.receiver = new SingleNameReference(data.getPluralName(), 0L); FieldReference thisDotKey = new FieldReference(varName, 0L); - thisDotKey.receiver = new ThisReference(0, 0); + thisDotKey.receiver = getBuilderReference(builderVariable); FieldReference thisDotValue = new FieldReference((new String(data.getPluralName()) + "$value").toCharArray(), 0L); - thisDotValue.receiver = new ThisReference(0, 0); + thisDotValue.receiver = getBuilderReference(builderVariable); MessageSend keyArg = new MessageSend(); keyArg.receiver = thisDotKey; keyArg.arguments = new Expression[] {new SingleNameReference(iVar, 0L)}; @@ -219,7 +219,7 @@ abstract class EclipseJavaUtilSingularizer extends EclipseSingularizer { LocalDeclaration forInit = new LocalDeclaration(iVar, 0, 0); forInit.type = TypeReference.baseTypeReference(TypeIds.T_int, 0); forInit.initialization = makeIntLiteral(new char[] {'0'}, null); - Expression checkExpr = new BinaryExpression(new SingleNameReference(iVar, 0L), getSize(builderType, varName, nullGuard), OperatorIds.LESS); + Expression checkExpr = new BinaryExpression(new SingleNameReference(iVar, 0L), getSize(builderType, varName, nullGuard, builderVariable), OperatorIds.LESS); Expression incrementExpr = new PostfixExpression(new SingleNameReference(iVar, 0L), IntLiteral.One, OperatorIds.PLUS, 0); fillStat = new ForStatement(new Statement[] {forInit}, checkExpr, new Statement[] {incrementExpr}, pluralnameDotPut, true, 0, 0); } else { @@ -228,14 +228,14 @@ abstract class EclipseJavaUtilSingularizer extends EclipseSingularizer { pluralnameDotAddAll.selector = new char[] {'a', 'd', 'd', 'A', 'l', 'l'}; pluralnameDotAddAll.receiver = new SingleNameReference(data.getPluralName(), 0L); FieldReference thisDotPluralname = new FieldReference(varName, 0L); - thisDotPluralname.receiver = new ThisReference(0, 0); + thisDotPluralname.receiver = getBuilderReference(builderVariable); pluralnameDotAddAll.arguments = new Expression[] {thisDotPluralname}; fillStat = pluralnameDotAddAll; } if (nullGuard) { FieldReference thisDotField = new FieldReference(varName, 0L); - thisDotField.receiver = new ThisReference(0, 0); + thisDotField.receiver = getBuilderReference(builderVariable); Expression cond = new EqualExpression(thisDotField, new NullLiteral(0, 0), OperatorIds.NOT_EQUAL); fillStat = new IfStatement(cond, fillStat, 0, 0); } diff --git a/src/core/lombok/experimental/Accessors.java b/src/core/lombok/experimental/Accessors.java index 7d9fdc5c..dc9ae4b0 100644 --- a/src/core/lombok/experimental/Accessors.java +++ b/src/core/lombok/experimental/Accessors.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2013 The Project Lombok Authors. + * Copyright (C) 2012-2017 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 @@ -29,7 +29,7 @@ import java.lang.annotation.Target; /** * A container for settings for the generation of getters and setters. * <p> - * Complete documentation is found at <a href="https://projectlombok.org/features/experimental/Accessors.html">the project lombok features page for @Accessors</a>. + * Complete documentation is found at <a href="https://projectlombok.org/features/experimental/Accessors">the project lombok features page for @Accessors</a>. * <p> * Using this annotation does nothing by itself; an annotation that makes lombok generate getters and setters, * such as {@link lombok.Setter} or {@link lombok.Data} is also required. @@ -38,15 +38,19 @@ import java.lang.annotation.Target; @Retention(RetentionPolicy.SOURCE) public @interface Accessors { /** - * If true, accessors will be named after the field and not include a <code>get</code> or <code>set</code> - * prefix. If true and <code>chain</code> is omitted, <code>chain</code> defaults to <code>true</code>. + * If true, accessors will be named after the field and not include a {@code get} or {@code set} + * prefix. If true and {@code chain} is omitted, {@code chain} defaults to {@code true}. * <strong>default: false</strong> + * + * @return Whether or not to make fluent methods (named {@code fieldName()}, not for example {@code setFieldName}). */ boolean fluent() default false; /** - * If true, setters return <code>this</code> instead of <code>void</code>. - * <strong>default: false</strong>, unless <code>fluent=true</code>, then <strong>default: true</code> + * If true, setters return {@code this} instead of {@code void}. + * <strong>default: false</strong>, unless {@code fluent=true}, then <strong>default: true</strong> + * + * @return Whether or not setters should return themselves (chaining) or {@code void} (no chaining). */ boolean chain() default false; @@ -55,6 +59,8 @@ public @interface Accessors { * Note that a prefix only counts if the next character is NOT a lowercase character or the last * letter of the prefix is not a letter (for instance an underscore). If multiple fields * all turn into the same name when the prefix is stripped, an error will be generated. + * + * @return If you are in the habit of prefixing your fields (for example, you name them {@code fFieldName}, specify such prefixes here). */ String[] prefix() default {}; } diff --git a/src/core/lombok/experimental/Builder.java b/src/core/lombok/experimental/Builder.java deleted file mode 100644 index 4d5e0f67..00000000 --- a/src/core/lombok/experimental/Builder.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (C) 2013-2014 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.experimental; - -import static java.lang.annotation.ElementType.*; -import static java.lang.annotation.RetentionPolicy.*; - -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - -/** - * The builder annotation creates a so-called 'builder' aspect to the class that is annotated or the class - * that contains a member which is annotated with {@code @Builder}. - * <p> - * If a member is annotated, it must be either a constructor or a method. If a class is annotated, - * then a private constructor is generated with all fields as arguments - * (as if {@code @AllArgsConstructor(AccessLevel.PRIVATE)} is present - * on the class), and it is as if this constructor has been annotated with {@code @Builder} instead. - * <p> - * The effect of {@code @Builder} is that an inner class is generated named <code><strong>T</strong>Builder</code>, - * with a private constructor. Instances of <code><strong>T</strong>Builder</code> are made with the - * method named {@code builder()} which is also generated for you in the class itself (not in the builder class). - * <p> - * The <code><strong>T</strong>Builder</code> class contains 1 method for each parameter of the annotated - * constructor / method (each field, when annotating a class), which returns the builder itself. - * The builder also has a <code>build()</code> method which returns a completed instance of the original type, - * created by passing all parameters as set via the various other methods in the builder to the constructor - * or method that was annotated with {@code @Builder}. The return type of this method will be the same - * as the relevant class, unless a method has been annotated, in which case it'll be equal to the - * return type of that method. - * <p> - * Complete documentation is found at <a href="https://projectlombok.org/features/experimental/Builder.html">the project lombok features page for @Builder</a>. - * <p> - * <p> - * Before: - * - * <pre> - * @Builder - * class Example { - * private int foo; - * private final String bar; - * } - * </pre> - * - * After: - * - * <pre> - * class Example<T> { - * private T foo; - * private final String bar; - * - * private Example(T foo, String bar) { - * this.foo = foo; - * this.bar = bar; - * } - * - * public static <T> ExampleBuilder<T> builder() { - * return new ExampleBuilder<T>(); - * } - * - * public static class ExampleBuilder<T> { - * private T foo; - * private String bar; - * - * private ExampleBuilder() {} - * - * public ExampleBuilder foo(T foo) { - * this.foo = foo; - * return this; - * } - * - * public ExampleBuilder bar(String bar) { - * this.bar = bar; - * return this; - * } - * - * @java.lang.Override public String toString() { - * return "ExampleBuilder(foo = " + foo + ", bar = " + bar + ")"; - * } - * - * public Example build() { - * return new Example(foo, bar); - * } - * } - * } - * </pre> - * - * @deprecated {@link lombok.Builder} has been promoted to the main package, so use that one instead. - */ -@Target({TYPE, METHOD, CONSTRUCTOR}) -@Retention(SOURCE) -@Deprecated -public @interface Builder { - /** Name of the method that creates a new builder instance. Default: {@code builder}. */ - String builderMethodName() default "builder"; - - /** Name of the method in the builder class that creates an instance of your {@code @Builder}-annotated class. */ - String buildMethodName() default "build"; - - /** Name of the builder class. - * Default for {@code @Builder} on types and constructors: {@code (TypeName)Builder}. - * Default for {@code @Builder} on methods: {@code (ReturnTypeName)Builder}. - */ - String builderClassName() default ""; - - /** - * Normally the builder's 'set' methods are fluent, meaning, they have the same name as the field. Set this - * to {@code false} to name the setter method for field {@code someField}: {@code setSomeField}. - * <p> - * <strong>Default: true</strong> - */ - boolean fluent() default true; - - /** - * Normally the builder's 'set' methods are chaining, meaning, they return the builder so that you can chain - * calls to set methods. Set this to {@code false} to have these 'set' methods return {@code void} instead. - * <p> - * <strong>Default: true</strong> - */ - boolean chain() default true; -} diff --git a/src/core/lombok/experimental/Delegate.java b/src/core/lombok/experimental/Delegate.java index d94389b0..cc844526 100644 --- a/src/core/lombok/experimental/Delegate.java +++ b/src/core/lombok/experimental/Delegate.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2014 The Project Lombok Authors. + * Copyright (C) 2010-2017 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 @@ -40,7 +40,7 @@ import java.lang.annotation.Target; * that exist in {@link Object}, the {@code canEqual(Object)} method, and any methods that appear in types * that are listed in the {@code excludes} property. * <p> - * Complete documentation is found at <a href="https://projectlombok.org/features/experimental/Delegate.html">the project lombok features page for @Delegate</a>. + * Complete documentation is found at <a href="https://projectlombok.org/features/experimental/Delegate">the project lombok features page for @Delegate</a>. */ @Target({ElementType.FIELD, ElementType.METHOD}) @Retention(RetentionPolicy.SOURCE) @@ -52,6 +52,8 @@ public @interface Delegate { * type listed here is used only to determine which delegate methods to generate. * * NB: All methods in {@code Object}, as well as {@code canEqual(Object other)} will never be delegated. + * + * @return For each method (not already in {@code java.lang.Object}) in these types, generate a delegate method. */ Class<?>[] types() default {}; @@ -59,6 +61,8 @@ public @interface Delegate { * Each method in any of the types listed here (include supertypes) will <em>not</em> be delegated. * * NB: All methods in {@code Object}, as well as {@code canEqual(Object other)} will never be delegated. + * + * @return For each method (not already in {@code java.lang.Object}) in these types, skip generating a delegate method (overrides {@code types()}). */ Class<?>[] excludes() default {}; } diff --git a/src/core/lombok/experimental/ExtensionMethod.java b/src/core/lombok/experimental/ExtensionMethod.java index 3e64cf78..af38f6ed 100644 --- a/src/core/lombok/experimental/ExtensionMethod.java +++ b/src/core/lombok/experimental/ExtensionMethod.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2013 The Project Lombok Authors. + * Copyright (C) 2012-2017 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,8 +31,8 @@ import java.lang.annotation.*; * otherwise modifying the original type. Extension methods are a special kind of static method, but they are called as * if they were instance methods on the extended type. * <p> - * Complete documentation is found at <a href="https://projectlombok.org/features/experimental/ExtensionMethod.html">the project lombok features page for @ExtensionMethod</a>. - * <p> + * Complete documentation is found at <a href="https://projectlombok.org/features/experimental/ExtensionMethod">the project lombok features page for @ExtensionMethod</a>. + * <br> * <p> * Before: * @@ -60,12 +60,14 @@ import java.lang.annotation.*; @Target(TYPE) @Retention(SOURCE) public @interface ExtensionMethod { - /** All types whose static methods will be exposed as extension methods. */ + /** @return All types whose static methods will be exposed as extension methods. */ Class<?>[] value(); /** * If {@code true}, an applicable extension method is used (if found) even if the method call already was compilable (this is the default). * If {@code false}, an extension method is only used if the method call is not also defined by the type itself. + * + * @return Whether or not to override already existing methods with the extension. */ boolean suppressBaseMethods() default true; } diff --git a/src/core/lombok/experimental/FieldDefaults.java b/src/core/lombok/experimental/FieldDefaults.java index dbc4993b..5c387bb6 100644 --- a/src/core/lombok/experimental/FieldDefaults.java +++ b/src/core/lombok/experimental/FieldDefaults.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2013 The Project Lombok Authors. + * Copyright (C) 2012-2017 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,11 +31,11 @@ import lombok.AccessLevel; /** * Adds modifiers to each field in the type with this annotation. * <p> - * Complete documentation is found at <a href="https://projectlombok.org/features/experimental/FieldDefaults.html">the project lombok features page for @FieldDefaults</a>. + * Complete documentation is found at <a href="https://projectlombok.org/features/experimental/FieldDefaults">the project lombok features page for @FieldDefaults</a>. * <p> - * If {@code makeFinal} is {@code true}, then each field that is not annotated with {@code @NonFinal} will have the {@code final} modifier added. + * If {@code makeFinal} is {@code true}, then each (instance) field that is not annotated with {@code @NonFinal} will have the {@code final} modifier added. * <p> - * If {@code level} is set, then each field that is package private (i.e. no access modifier) and does not have the {@code @PackagePrivate} annotation will + * If {@code level} is set, then each (instance) field that is package private (i.e. no access modifier) and does not have the {@code @PackagePrivate} annotation will * have the appropriate access level modifier added. */ @Target(ElementType.TYPE) diff --git a/src/core/lombok/experimental/FieldNameConstants.java b/src/core/lombok/experimental/FieldNameConstants.java new file mode 100644 index 00000000..31c2970c --- /dev/null +++ b/src/core/lombok/experimental/FieldNameConstants.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2014-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.experimental; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import lombok.AccessLevel; + +/** + * Generates String constants containing the field name for each field. + */ +@Target({ElementType.TYPE, ElementType.FIELD}) +@Retention(RetentionPolicy.SOURCE) +public @interface FieldNameConstants { + lombok.AccessLevel level() default AccessLevel.PUBLIC; + String prefix() default " CONFIG DEFAULT "; + String suffix() default " CONFIG DEFAULT "; +} diff --git a/src/core/lombok/experimental/NonFinal.java b/src/core/lombok/experimental/NonFinal.java index 0c31dd2a..12a45d22 100644 --- a/src/core/lombok/experimental/NonFinal.java +++ b/src/core/lombok/experimental/NonFinal.java @@ -28,7 +28,7 @@ import java.lang.annotation.Target; /** * Used to indicate the explicit intention for the annotated entity to <em>not</em> be {@code final}. - * Currently used by {@code FieldDefaults} to avoid having it make a field final. + * Currently used by {@code FieldDefaults} and {@code Value} to avoid having it make a field final. */ @Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE, ElementType.ANNOTATION_TYPE, ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.SOURCE) diff --git a/src/core/lombok/experimental/PackagePrivate.java b/src/core/lombok/experimental/PackagePrivate.java index bfe5638b..42002818 100644 --- a/src/core/lombok/experimental/PackagePrivate.java +++ b/src/core/lombok/experimental/PackagePrivate.java @@ -28,7 +28,7 @@ import java.lang.annotation.Target; /** * Used to indicate the explicit intention for the annotated entity to have the <em>package private</em> access level. - * Currently used by {@code FieldDefaults} to avoid having it make a field one of {@code public}, {@code protected}, or {@code private}. + * Currently used by {@code FieldDefaults} and {@code Value} to avoid having it make a field one of {@code public}, {@code protected}, or {@code private}. */ @Target({ElementType.TYPE, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.SOURCE) diff --git a/src/core/lombok/experimental/SuperBuilder.java b/src/core/lombok/experimental/SuperBuilder.java new file mode 100644 index 00000000..26127a5f --- /dev/null +++ b/src/core/lombok/experimental/SuperBuilder.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 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.experimental; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.*; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import lombok.Singular; + +/** + * The SuperBuilder annotation creates a so-called 'builder' aspect to the class that is annotated with {@code @SuperBuilder}, but which works well when extending. + * It is similar to {@code @Builder}, except it is only legal on types, is less configurable, but allows you to {@code extends} other builder-able classes. + * <p> + * All classes in the hierarchy must be annotated with {@code @SuperBuilder}. + * <p> + * Lombok generates 2 inner 'builder' classes, which extend the parent class' builder class (unless your class doesn't have an extends clause). + * Lombok also generates a static method named {@code builder()}, and a protected constructor that takes 1 argument of the builderclass type. + * <p> + * The <code><strong>T</strong>Builder</code> class contains 1 method for each parameter of the annotated + * constructor / method (each field, when annotating a class), which returns the builder itself. + * The builder also has a <code>build()</code> method which returns a completed instance of the original type. + * <p> + * Complete documentation is found at <a href="https://projectlombok.org/features/experimental/SuperBuilder">the project lombok features page for @SuperBuilder</a>. + * + * @see Singular + */ +@Target(TYPE) +@Retention(SOURCE) +public @interface SuperBuilder { + /** @return Name of the method that creates a new builder instance. Default: {@code builder}. */ + String builderMethodName() default "builder"; + + /** @return Name of the method in the builder class that creates an instance of your {@code @Builder}-annotated class. */ + String buildMethodName() default "build"; + + // toBuilder also requires a two-stage system where each class gets its own toBuilder but calls on a second method (and also calls parentclass's method) + // to fill the builder, as this class does not know what fields to pass on to the builder. Let's consider this, but only for milestone 2. + /* + * If true, generate an instance method to obtain a builder that is initialized with the values of this instance. + * + * @return Whether to generate a {@code toBuilder()} method. + */ +// boolean toBuilder() default false; +} diff --git a/src/core/lombok/experimental/Wither.java b/src/core/lombok/experimental/Wither.java index b4131187..3df546d0 100644 --- a/src/core/lombok/experimental/Wither.java +++ b/src/core/lombok/experimental/Wither.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2013 The Project Lombok Authors. + * Copyright (C) 2012-2017 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,7 +31,7 @@ import lombok.AccessLevel; /** * Put on any field to make lombok build a 'wither' - a withX method which produces a clone of this object (except for 1 field which gets a new value). * <p> - * Complete documentation is found at <a href="https://projectlombok.org/features/experimental/Wither.html">the project lombok features page for @Wither</a>. + * Complete documentation is found at <a href="https://projectlombok.org/features/experimental/Wither">the project lombok features page for @Wither</a>. * <p> * Even though it is not listed, this annotation also has the {@code onParam} and {@code onMethod} parameter. See the full documentation for more details. * <p> @@ -56,16 +56,32 @@ import lombok.AccessLevel; public @interface Wither { /** * If you want your wither to be non-public, you can specify an alternate access level here. + * + * @return The method will be generated with this access modifier. */ AccessLevel value() default AccessLevel.PUBLIC; /** - * Any annotations listed here are put on the generated method. The syntax for this feature is: {@code @Setter(onMethod=@__({@AnnotationsGoHere}))} + * Any annotations listed here are put on the generated method. + * The syntax for this feature depends on JDK version (nothing we can do about that; it's to work around javac bugs).<br> + * up to JDK7:<br> + * {@code @Wither(onMethod=@__({@AnnotationsGoHere}))}<br> + * from JDK8:<br> + * {@code @Wither(onMethod_={@AnnotationsGohere})} // note the underscore after {@code onMethod}. + * + * @return List of annotations to apply to the generated method. */ AnyAnnotation[] onMethod() default {}; /** - * Any annotations listed here are put on the generated method's parameter. The syntax for this feature is: {@code @Setter(onParam=@__({@AnnotationsGoHere}))} + * Any annotations listed here are put on the generated method's parameter. + * The syntax for this feature depends on JDK version (nothing we can do about that; it's to work around javac bugs).<br> + * up to JDK7:<br> + * {@code @Wither(onParam=@__({@AnnotationsGoHere}))}<br> + * from JDK8:<br> + * {@code @Wither(onParam_={@AnnotationsGohere})} // note the underscore after {@code onParam}. + * + * @return List of annotations to apply to the generated parameter in the method. */ AnyAnnotation[] onParam() default {}; diff --git a/src/core/lombok/experimental/package-info.java b/src/core/lombok/experimental/package-info.java index d85e2969..22b23a52 100644 --- a/src/core/lombok/experimental/package-info.java +++ b/src/core/lombok/experimental/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2014 The Project Lombok Authors. + * Copyright (C) 2009-2017 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 @@ -28,6 +28,6 @@ * to the official feature documentation. * * @see lombok - * @see <a href="https://projectlombok.org/features/experimental/index.html">Lombok features (experimental)</a> + * @see <a href="https://projectlombok.org/features/experimental/all">Lombok features (experimental)</a> */ package lombok.experimental; diff --git a/src/core/lombok/experimental/var.java b/src/core/lombok/experimental/var.java new file mode 100644 index 00000000..71cc141a --- /dev/null +++ b/src/core/lombok/experimental/var.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2010-2017 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.experimental; + +/** + * like val but not final + * + * @deprecated {@code var} has been promoted to the main package; use {@link lombok.var} instead. + */ +@Deprecated +public @interface var { +} diff --git a/src/core/lombok/extern/apachecommons/CommonsLog.java b/src/core/lombok/extern/apachecommons/CommonsLog.java index 777f4b35..fa3d6f09 100644 --- a/src/core/lombok/extern/apachecommons/CommonsLog.java +++ b/src/core/lombok/extern/apachecommons/CommonsLog.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2013 The Project Lombok Authors. + * Copyright (C) 2010-2017 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 @@ -29,7 +29,7 @@ import java.lang.annotation.Target; /** * Causes lombok to generate a logger field. * <p> - * Complete documentation is found at <a href="https://projectlombok.org/features/Log.html">the project lombok features page for lombok log annotations</a>. + * Complete documentation is found at <a href="https://projectlombok.org/features/Log">the project lombok features page for lombok log annotations</a>. * <p> * Example: * <pre> @@ -46,22 +46,21 @@ import java.lang.annotation.Target; * } * </pre> * - * This annotation is valid for classes and enumerations.<br /> + * This annotation is valid for classes and enumerations.<br> * - * @see org.apache.commons.logging.Log org.apache.commons.logging.Log - * @see org.apache.commons.logging.LogFactory#getLog(java.lang.Class) org.apache.commons.logging.LogFactory.getLog(Class target) + * @see <a href="https://commons.apache.org/proper/commons-logging/apidocs/org/apache/commons/logging/Log.html">org.apache.commons.logging.Log</a> + * @see <a href="https://commons.apache.org/proper/commons-logging/apidocs/org/apache/commons/logging/LogFactory.html#getLog(java.lang.Class)">org.apache.commons.logging.LogFactory#getLog(java.lang.Class)</a> * @see lombok.extern.java.Log @Log * @see lombok.extern.log4j.Log4j @Log4j * @see lombok.extern.log4j.Log4j2 @Log4j2 * @see lombok.extern.slf4j.Slf4j @Slf4j * @see lombok.extern.slf4j.XSlf4j @XSlf4j * @see lombok.extern.jbosslog.JBossLog @JBossLog + * @see lombok.extern.flogger.Flogger @Flogger */ @Retention(RetentionPolicy.SOURCE) @Target(ElementType.TYPE) public @interface CommonsLog { - /** - * Sets the category of the constructed Logger. By default, it will use the type where the annotation is placed. - */ + /** @return The category of the constructed Logger. By default, it will use the type where the annotation is placed. */ String topic() default ""; } diff --git a/src/core/lombok/experimental/Value.java b/src/core/lombok/extern/flogger/Flogger.java index 46ec7a05..ecbfd28c 100644 --- a/src/core/lombok/experimental/Value.java +++ b/src/core/lombok/extern/flogger/Flogger.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2013 The Project Lombok Authors. + * Copyright (C) 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 @@ -19,7 +19,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package lombok.experimental; +package lombok.extern.flogger; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -27,34 +27,36 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * Generates a lot of code which fits with a class that is a representation of an immutable entity. + * Causes lombok to generate a logger field. * <p> - * Equivalent to {@code @Getter @FieldDefaults(makeFinal=true, level=AccessLevel.PRIVATE) @RequiredArgsConstructor @ToString @EqualsAndHashCode}. + * Complete documentation is found at <a href="https://projectlombok.org/features/Log">the project lombok features page for lombok log annotations</a>. * <p> - * Complete documentation is found at <a href="https://projectlombok.org/features/experimental/Value.html">the project lombok features page for @Value</a>. + * Example: + * <pre> + * @Flogger + * public class LogExample { + * } + * </pre> * - * @see lombok.Getter - * @see Wither - * @see lombok.RequiredArgsConstructor - * @see lombok.ToString - * @see lombok.EqualsAndHashCode - * @see lombok.Data - * @deprecated {@link lombok.Value} has been promoted to the main package, so use that one instead. + * will generate: + * + * <pre> + * public class LogExample { + * private static final com.google.common.flogger.FluentLogger log = com.google.common.flogger.FluentLogger.forEnclosingClass(); + * } + * </pre> + * + * This annotation is valid for classes and enumerations.<br> + * @see <a href="https://google.github.io/flogger/">com.google.common.flogger</a> + * @see lombok.extern.apachecommons.CommonsLog @CommonsLog + * @see lombok.extern.java.Log @Log + * @see lombok.extern.log4j.Log4j @Log4j + * @see lombok.extern.log4j.Log4j2 @Log4j2 + * @see lombok.extern.slf4j.Slf4j @Slf4j + * @see lombok.extern.slf4j.XSlf4j @XSlf4j + * @see lombok.extern.jbosslog.JBossLog @JBossLog */ -@Target(ElementType.TYPE) @Retention(RetentionPolicy.SOURCE) -@Deprecated -public @interface Value { - /** - * If you specify a static constructor name, then the generated constructor will be private, and - * instead a static factory method is created that other classes can use to create instances. - * We suggest the name: "of", like so: - * - * <pre> - * public @Data(staticConstructor = "of") class Point { final int x, y; } - * </pre> - * - * Default: No static constructor, instead the normal constructor is public. - */ - String staticConstructor() default ""; +@Target(ElementType.TYPE) +public @interface Flogger { } diff --git a/src/core/lombok/extern/java/Log.java b/src/core/lombok/extern/java/Log.java index d5515283..9a1ee412 100644 --- a/src/core/lombok/extern/java/Log.java +++ b/src/core/lombok/extern/java/Log.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2013 The Project Lombok Authors. + * Copyright (C) 2010-2017 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 @@ -29,7 +29,7 @@ import java.lang.annotation.Target; /** * Causes lombok to generate a logger field. * <p> - * Complete documentation is found at <a href="https://projectlombok.org/features/Log.html">the project lombok features page for lombok log annotations</a>. + * Complete documentation is found at <a href="https://projectlombok.org/features/Log">the project lombok features page for lombok log annotations</a>. * <p> * Example: * <pre> @@ -46,21 +46,20 @@ import java.lang.annotation.Target; * } * </pre> * - * This annotation is valid for classes and enumerations.<br /> - * @see java.util.logging.Logger java.util.logging.Logger - * @see java.util.logging.Logger#getLogger(java.lang.String) java.util.logging.Logger.getLogger(String name) + * This annotation is valid for classes and enumerations.<br> + * @see <a href="https://docs.oracle.com/javase/8/docs/api/java/util/logging/Logger.html">java.util.logging.Logger</a> + * @see <a href="https://docs.oracle.com/javase/8/docs/api/java/util/logging/Logger.html#getLogger-java.lang.String-">java.util.logging.Logger#getLogger(java.lang.String)</a> * @see lombok.extern.apachecommons.CommonsLog @CommonsLog * @see lombok.extern.log4j.Log4j @Log4j * @see lombok.extern.log4j.Log4j2 @Log4j2 * @see lombok.extern.slf4j.Slf4j @Slf4j * @see lombok.extern.slf4j.XSlf4j @XSlf4j * @see lombok.extern.jbosslog.JBossLog @JBossLog + * @see lombok.extern.flogger.Flogger @Flogger */ @Retention(RetentionPolicy.SOURCE) @Target(ElementType.TYPE) public @interface Log { - /** - * Sets the category of the constructed Logger. By default, it will use the type where the annotation is placed. - */ + /** @return The category of the constructed Logger. By default, it will use the type where the annotation is placed. */ String topic() default ""; } diff --git a/src/core/lombok/extern/jbosslog/JBossLog.java b/src/core/lombok/extern/jbosslog/JBossLog.java index 2735290c..684585e0 100644 --- a/src/core/lombok/extern/jbosslog/JBossLog.java +++ b/src/core/lombok/extern/jbosslog/JBossLog.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Project Lombok Authors. + * Copyright (C) 2016-2017 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 @@ -29,7 +29,7 @@ import java.lang.annotation.Target; /** * Causes lombok to generate a logger field. * <p> - * Complete documentation is found at <a href="https://projectlombok.org/features/Log.html">the project lombok features page for lombok log annotations</a>. + * Complete documentation is found at <a href="https://projectlombok.org/features/Log">the project lombok features page for lombok log annotations</a>. * <p> * Example: * <pre> @@ -46,22 +46,20 @@ import java.lang.annotation.Target; * } * </pre> * - * This annotation is valid for classes and enumerations.<br /> - * @see org.jboss.logging.Logger org.jboss.logging.Logger - * @see org.jboss.logging.Logger#getLogger(java.lang.Class) org.jboss.logging.Logger.getLogger(Class target) + * This annotation is valid for classes and enumerations.<br> + * @see <a href="https://docs.jboss.org/jbosslogging/latest/org/jboss/logging/Logger.html">org.jboss.logging.Logger</a> + * @see <a href="https://docs.jboss.org/jbosslogging/latest/org/jboss/logging/Logger.html#getLogger(java.lang.Class)">org.jboss.logging.Logger#getLogger(java.lang.Class)</a> * @see lombok.extern.apachecommons.CommonsLog @CommonsLog * @see lombok.extern.java.Log @Log * @see lombok.extern.log4j.Log4j @Log4j * @see lombok.extern.log4j.Log4j2 @Log4j2 + * @see lombok.extern.slf4j.Slf4j @Slf4j * @see lombok.extern.slf4j.XSlf4j @XSlf4j - * @see lombok.extern.jbosslog.JBossLog @JBossLog + * @see lombok.extern.flogger.Flogger @Flogger * */ @Retention(RetentionPolicy.SOURCE) @Target(ElementType.TYPE) public @interface JBossLog { - - /** - * Sets the category of the constructed Logger. By default, it will use the type where the annotation is placed. - */ + /** @return The category of the constructed Logger. By default, it will use the type where the annotation is placed. */ String topic() default ""; } diff --git a/src/core/lombok/extern/log4j/Log4j.java b/src/core/lombok/extern/log4j/Log4j.java index 6c6ef1da..249ef71a 100644 --- a/src/core/lombok/extern/log4j/Log4j.java +++ b/src/core/lombok/extern/log4j/Log4j.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2013 The Project Lombok Authors. + * Copyright (C) 2010-2017 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 @@ -29,7 +29,7 @@ import java.lang.annotation.Target; /** * Causes lombok to generate a logger field. * <p> - * Complete documentation is found at <a href="https://projectlombok.org/features/Log.html">the project lombok features page for lombok log annotations</a>. + * Complete documentation is found at <a href="https://projectlombok.org/features/Log">the project lombok features page for lombok log annotations</a>. * <p> * Example: * <pre> @@ -46,22 +46,21 @@ import java.lang.annotation.Target; * } * </pre> * - * This annotation is valid for classes and enumerations.<br /> + * This annotation is valid for classes and enumerations.<br> * - * @see org.apache.log4j.Logger org.apache.log4j.Logger - * @see org.apache.log4j.Logger#getLogger(java.lang.Class) org.apache.log4j.Logger.getLogger(Class target) + * @see <a href="https://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/Logger.html">org.apache.log4j.Logger</a> + * @see <a href="https://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/Logger.html#getLogger(java.lang.Class)">org.apache.log4j.Logger#getLogger(java.lang.Class)</a> * @see lombok.extern.log4j.Log4j2 @Log4j2 * @see lombok.extern.apachecommons.CommonsLog @CommonsLog * @see lombok.extern.java.Log @Log * @see lombok.extern.slf4j.Slf4j @Slf4j * @see lombok.extern.slf4j.XSlf4j @XSlf4j * @see lombok.extern.jbosslog.JBossLog @JBossLog + * @see lombok.extern.flogger.Flogger @Flogger */ @Retention(RetentionPolicy.SOURCE) @Target(ElementType.TYPE) public @interface Log4j { - /** - * Sets the category of the constructed Logger. By default, it will use the type where the annotation is placed. - */ + /** @return The category of the constructed Logger. By default, it will use the type where the annotation is placed. */ String topic() default ""; } diff --git a/src/core/lombok/extern/log4j/Log4j2.java b/src/core/lombok/extern/log4j/Log4j2.java index a1b2e0e7..a6aa90c0 100644 --- a/src/core/lombok/extern/log4j/Log4j2.java +++ b/src/core/lombok/extern/log4j/Log4j2.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 The Project Lombok Authors. + * Copyright (C) 2013-2017 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 @@ -29,7 +29,7 @@ import java.lang.annotation.Target; /** * Causes lombok to generate a logger field. * <p> - * Complete documentation is found at <a href="https://projectlombok.org/features/Log.html">the project lombok features page for lombok log annotations</a>. + * Complete documentation is found at <a href="https://projectlombok.org/features/Log">the project lombok features page for lombok log annotations</a>. * <p> * Example: * <pre> @@ -46,22 +46,21 @@ import java.lang.annotation.Target; * } * </pre> * - * This annotation is valid for classes and enumerations.<br /> + * This annotation is valid for classes and enumerations.<br> * - * @see org.apache.logging.log4j.Logger org.apache.logging.log4j.Logger - * @see org.apache.logging.log4j.LogManager#getLogger(java.lang.Class) org.apache.logging.log4j.LogManager.getLogger(Class target) + * @see <a href="https://logging.apache.org/log4j/2.x/log4j-api/apidocs/org/apache/logging/log4j/Logger.html">org.apache.logging.log4j.Logger</a> + * @see <a href="https://logging.apache.org/log4j/2.x/log4j-api/apidocs/org/apache/logging/log4j/LogManager.html#getLogger-java.lang.Class-">org.apache.logging.log4j.LogManager#getLogger(java.lang.Class)</a> * @see lombok.extern.log4j.Log4j @Log4j * @see lombok.extern.apachecommons.CommonsLog @CommonsLog * @see lombok.extern.java.Log @Log * @see lombok.extern.slf4j.Slf4j @Slf4j * @see lombok.extern.slf4j.XSlf4j @XSlf4j * @see lombok.extern.jbosslog.JBossLog @JBossLog + * @see lombok.extern.flogger.Flogger @Flogger */ @Retention(RetentionPolicy.SOURCE) @Target(ElementType.TYPE) public @interface Log4j2 { - /** - * Sets the category of the constructed Logger. By default, it will use the type where the annotation is placed. - */ + /** @return The category of the constructed Logger. By default, it will use the type where the annotation is placed. */ String topic() default ""; } diff --git a/src/core/lombok/extern/slf4j/Slf4j.java b/src/core/lombok/extern/slf4j/Slf4j.java index efa6105f..347d81d2 100644 --- a/src/core/lombok/extern/slf4j/Slf4j.java +++ b/src/core/lombok/extern/slf4j/Slf4j.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2013 The Project Lombok Authors. + * Copyright (C) 2010-2017 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 @@ -29,7 +29,7 @@ import java.lang.annotation.Target; /** * Causes lombok to generate a logger field. * <p> - * Complete documentation is found at <a href="https://projectlombok.org/features/Log.html">the project lombok features page for lombok log annotations</a>. + * Complete documentation is found at <a href="https://projectlombok.org/features/Log">the project lombok features page for lombok log annotations</a>. * <p> * Example: * <pre> @@ -46,22 +46,21 @@ import java.lang.annotation.Target; * } * </pre> * - * This annotation is valid for classes and enumerations.<br /> - * @see org.slf4j.Logger org.slf4j.Logger - * @see org.slf4j.LoggerFactory#getLogger(java.lang.Class) org.slf4j.LoggerFactory.getLogger(Class target) + * This annotation is valid for classes and enumerations.<br> + * @see <a href="https://www.slf4j.org/api/org/slf4j/Logger.html">org.slf4j.Logger</a> + * @see <a href="https://www.slf4j.org/api/org/slf4j/LoggerFactory.html#getLogger(java.lang.Class)">org.slf4j.LoggerFactory#getLogger(java.lang.Class)</a> * @see lombok.extern.apachecommons.CommonsLog @CommonsLog * @see lombok.extern.java.Log @Log * @see lombok.extern.log4j.Log4j @Log4j * @see lombok.extern.log4j.Log4j2 @Log4j2 * @see lombok.extern.slf4j.XSlf4j @XSlf4j * @see lombok.extern.jbosslog.JBossLog @JBossLog + * @see lombok.extern.flogger.Flogger @Flogger */ @Retention(RetentionPolicy.SOURCE) @Target(ElementType.TYPE) public @interface Slf4j { - /** - * Sets the category of the constructed Logger. By default, it will use the type where the annotation is placed. - */ + /** @return The category of the constructed Logger. By default, it will use the type where the annotation is placed. */ String topic() default ""; } diff --git a/src/core/lombok/extern/slf4j/XSlf4j.java b/src/core/lombok/extern/slf4j/XSlf4j.java index ce23ab9e..4d53a1eb 100644 --- a/src/core/lombok/extern/slf4j/XSlf4j.java +++ b/src/core/lombok/extern/slf4j/XSlf4j.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2013 The Project Lombok Authors. + * Copyright (C) 2012-2017 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 @@ -29,7 +29,7 @@ import java.lang.annotation.Target; /** * Causes lombok to generate a logger field. * <p> - * Complete documentation is found at <a href="https://projectlombok.org/features/Log.html">the project lombok features page for lombok log annotations</a>. + * Complete documentation is found at <a href="https://projectlombok.org/features/Log">the project lombok features page for lombok log annotations</a>. * <p> * Example: * <pre> @@ -46,21 +46,20 @@ import java.lang.annotation.Target; * } * </pre> * - * This annotation is valid for classes and enumerations.<br /> - * @see org.slf4j.ext.XLogger org.slf4j.ext.XLogger - * @see org.slf4j.ext.XLoggerFactory#getLogger(java.lang.Class) org.slf4j.ext.XLoggerFactory.getXLogger(Class target) + * This annotation is valid for classes and enumerations.<br> + * @see <a href="https://www.slf4j.org/api/org/slf4j/ext/XLogger.html">org.slf4j.ext.XLogger</a> + * @see <a href="https://www.slf4j.org/api/org/slf4j/ext/XLoggerFactory.html#getXLogger(java.lang.Class)">org.slf4j.ext.XLoggerFactory#getLogger(java.lang.Class)</a> * @see lombok.extern.apachecommons.CommonsLog @CommonsLog * @see lombok.extern.java.Log @Log * @see lombok.extern.log4j.Log4j @Log4j * @see lombok.extern.log4j.Log4j2 @Log4j2 * @see lombok.extern.slf4j.Slf4j @Slf4j * @see lombok.extern.jbosslog.JBossLog @JBossLog + * @see lombok.extern.flogger.Flogger @Flogger */ @Retention(RetentionPolicy.SOURCE) @Target(ElementType.TYPE) public @interface XSlf4j { - /** - * Sets the category of the constructed Logger. By default, it will use the type where the annotation is placed. - */ + /** @return The category of the constructed Logger. By default, it will use the type where the annotation is placed. */ String topic() default ""; } diff --git a/src/core/lombok/javac/CapturingDiagnosticListener.java b/src/core/lombok/javac/CapturingDiagnosticListener.java index a0ac6adc..0e64ed8d 100644 --- a/src/core/lombok/javac/CapturingDiagnosticListener.java +++ b/src/core/lombok/javac/CapturingDiagnosticListener.java @@ -52,6 +52,10 @@ public class CapturingDiagnosticListener implements DiagnosticListener<JavaFileO "^" + Pattern.quote(file.getAbsolutePath()) + "\\s*:\\s*\\d+\\s*:\\s*(?:warning:\\s*)?(.*)$", Pattern.DOTALL).matcher(msg); if (m.matches()) msg = m.group(1); + if (msg.equals("deprecated item is not annotated with @Deprecated")) { + // This is new in JDK9; prior to that you don't see this. We shall ignore these. + return; + } messages.add(new CompilerMessage(d.getLineNumber(), d.getStartPosition(), d.getKind() == Kind.ERROR, msg)); } diff --git a/src/core/lombok/javac/CompilerMessageSuppressor.java b/src/core/lombok/javac/CompilerMessageSuppressor.java index a17e0c62..391ec64a 100644 --- a/src/core/lombok/javac/CompilerMessageSuppressor.java +++ b/src/core/lombok/javac/CompilerMessageSuppressor.java @@ -26,6 +26,7 @@ import java.io.OutputStream; import java.io.PrintWriter; import java.lang.reflect.Field; import java.util.LinkedList; +import java.util.Map; import java.util.Queue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -43,38 +44,40 @@ import com.sun.tools.javac.util.Log; * then they will be generated AGAIN, this time with proper names and line numbers, at the end. Therefore, we want to suppress the logger. */ public final class CompilerMessageSuppressor { + private final Log log; - private static final Field errWriterField, warnWriterField, noticeWriterField, dumpOnErrorField, promptOnErrorField, diagnosticListenerField; + private static final WriterField errWriterField, warnWriterField, noticeWriterField; + private static final Field dumpOnErrorField, promptOnErrorField, diagnosticListenerField; private static final Field deferDiagnosticsField, deferredDiagnosticsField, diagnosticHandlerField; private static final ConcurrentMap<Class<?>, Field> handlerDeferredFields = new ConcurrentHashMap<Class<?>, Field>(); private static final Field NULL_FIELD; - private PrintWriter errWriter, warnWriter, noticeWriter; private Boolean dumpOnError, promptOnError; private DiagnosticListener<?> contextDiagnosticListener, logDiagnosticListener; private final Context context; - // If this is true, the fields changed. Better to print weird error messages than to fail outright. - private static final boolean dontBother; - private static final ThreadLocal<Queue<?>> queueCache = new ThreadLocal<Queue<?>>(); + enum Writers { + ERROR("errWriter", "ERROR"), + WARNING("warnWriter", "WARNING"), + NOTICE("noticeWriter", "NOTICE"); + + final String fieldName; + final String keyName; + + Writers(String fieldName, String keyName) { + this.fieldName = fieldName; + this.keyName = keyName; + } + } + static { - errWriterField = getDeclaredField(Log.class, "errWriter"); - warnWriterField = getDeclaredField(Log.class, "warnWriter"); - noticeWriterField = getDeclaredField(Log.class, "noticeWriter"); + errWriterField = createWriterField(Writers.ERROR); + warnWriterField = createWriterField(Writers.WARNING); + noticeWriterField = createWriterField(Writers.NOTICE); dumpOnErrorField = getDeclaredField(Log.class, "dumpOnError"); promptOnErrorField = getDeclaredField(Log.class, "promptOnError"); diagnosticListenerField = getDeclaredField(Log.class, "diagListener"); - - dontBother = - errWriterField == null || - warnWriterField == null || - noticeWriterField == null || - dumpOnErrorField == null || - promptOnErrorField == null || - diagnosticListenerField == null; - - deferDiagnosticsField = getDeclaredField(Log.class, "deferDiagnostics"); deferredDiagnosticsField = getDeclaredField(Log.class, "deferredDiagnostics"); @@ -100,17 +103,13 @@ public final class CompilerMessageSuppressor { this.context = context; } - public boolean disableLoggers() { + public void disableLoggers() { contextDiagnosticListener = context.get(DiagnosticListener.class); context.put(DiagnosticListener.class, (DiagnosticListener<?>) null); - if (dontBother) return false; - boolean dontBotherInstance = false; - - PrintWriter dummyWriter = new PrintWriter(new OutputStream() { - @Override public void write(int b) throws IOException { - // Do nothing on purpose - } - }); + + errWriterField.pauze(log); + warnWriterField.pauze(log); + noticeWriterField.pauze(log); if (deferDiagnosticsField != null) try { if (Boolean.TRUE.equals(deferDiagnosticsField.get(log))) { @@ -130,50 +129,23 @@ public final class CompilerMessageSuppressor { } } catch (Exception e) {} - if (!dontBotherInstance) try { - errWriter = (PrintWriter) errWriterField.get(log); - errWriterField.set(log, dummyWriter); - } catch (Exception e) { - dontBotherInstance = true; - } - - if (!dontBotherInstance) try { - warnWriter = (PrintWriter) warnWriterField.get(log); - warnWriterField.set(log, dummyWriter); - } catch (Exception e) { - dontBotherInstance = true; - } - - if (!dontBotherInstance) try { - noticeWriter = (PrintWriter) noticeWriterField.get(log); - noticeWriterField.set(log, dummyWriter); - } catch (Exception e) { - dontBotherInstance = true; - } - - if (!dontBotherInstance) try { + if (dumpOnErrorField != null) try { dumpOnError = (Boolean) dumpOnErrorField.get(log); dumpOnErrorField.set(log, false); } catch (Exception e) { - dontBotherInstance = true; } - if (!dontBotherInstance) try { + if (promptOnErrorField != null) try { promptOnError = (Boolean) promptOnErrorField.get(log); promptOnErrorField.set(log, false); } catch (Exception e) { - dontBotherInstance = true; } - if (!dontBotherInstance) try { + if (diagnosticListenerField != null) try { logDiagnosticListener = (DiagnosticListener<?>) diagnosticListenerField.get(log); diagnosticListenerField.set(log, null); } catch (Exception e) { - dontBotherInstance = true; } - - if (dontBotherInstance) enableLoggers(); - return !dontBotherInstance; } private static Field getDeferredField(Object handler) { @@ -193,20 +165,9 @@ public final class CompilerMessageSuppressor { contextDiagnosticListener = null; } - if (errWriter != null) try { - errWriterField.set(log, errWriter); - errWriter = null; - } catch (Exception e) {} - - if (warnWriter != null) try { - warnWriterField.set(log, warnWriter); - warnWriter = null; - } catch (Exception e) {} - - if (noticeWriter != null) try { - noticeWriterField.set(log, noticeWriter); - noticeWriter = null; - } catch (Exception e) {} + errWriterField.resume(log); + warnWriterField.resume(log); + noticeWriterField.resume(log); if (dumpOnError != null) try { dumpOnErrorField.set(log, dumpOnError); @@ -283,4 +244,107 @@ public final class CompilerMessageSuppressor { // javac will contain rather a lot of messages, but this is a lot better than just crashing during compilation! } } + + private static WriterField createWriterField(Writers w) { + // jdk9 + try { + Field writers = getDeclaredField(Log.class, "writer"); + if (writers != null) { + Class<?> kindsClass = Class.forName("com.sun.tools.javac.util.Log$WriterKind"); + for (Object enumConstant : kindsClass.getEnumConstants()) { + if (enumConstant.toString().equals(w.keyName)) { + return new Java9WriterField(writers, enumConstant); + } + } + return WriterField.NONE; + } + } catch (Exception e) { + } + + // jdk8 + Field writerField = getDeclaredField(Log.class, w.fieldName); + if (writerField != null) return new Java8WriterField(writerField); + + // other jdk + return WriterField.NONE; + } + + interface WriterField { + final PrintWriter NO_WRITER = new PrintWriter(new OutputStream() { + @Override public void write(int b) throws IOException { + // Do nothing on purpose + } + }); + + final WriterField NONE = new WriterField() { + @Override public void pauze(Log log) { + // do nothing + } + @Override public void resume(Log log) { + // no nothing + } + }; + + void pauze(Log log); + void resume(Log log); + } + + static class Java8WriterField implements WriterField { + private final Field field; + private PrintWriter writer; + + public Java8WriterField(Field field) { + this.field = field; + } + + @Override public void pauze(Log log) { + try { + writer = (PrintWriter) field.get(log); + field.set(log, NO_WRITER); + } catch (Exception e) { + } + } + + @Override public void resume(Log log) { + if (writer != null) { + try { + field.set(log, writer); + } catch (Exception e) { + } + } + writer = null; + } + } + + + static class Java9WriterField implements WriterField { + private final Field field; + private final Object key; + private PrintWriter writer; + + public Java9WriterField(Field field, Object key) { + this.field = field; + this.key = key; + } + + @Override public void pauze(Log log) { + try { + @SuppressWarnings("unchecked") Map<Object,PrintWriter> map = (Map<Object,PrintWriter>)field.get(log); + writer = map.get(key); + map.put(key, NO_WRITER); + } catch (Exception e) { + } + } + + @Override public void resume(Log log) { + if (writer != null) { + try { + @SuppressWarnings("unchecked") Map<Object,PrintWriter> map = (Map<Object,PrintWriter>)field.get(log); + map.put(key, writer); + } catch (Exception e) { + } + } + writer = null; + } + } }
\ No newline at end of file diff --git a/src/core/lombok/javac/HandlerLibrary.java b/src/core/lombok/javac/HandlerLibrary.java index 30aeff73..3c61696b 100644 --- a/src/core/lombok/javac/HandlerLibrary.java +++ b/src/core/lombok/javac/HandlerLibrary.java @@ -44,6 +44,7 @@ import lombok.core.TypeResolver; import lombok.core.configuration.ConfigurationKeysLoader; import lombok.javac.handlers.JavacHandlerUtil; +import com.sun.source.util.Trees; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.JCAnnotation; import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; @@ -148,12 +149,12 @@ public class HandlerLibrary { * then uses SPI discovery to load all annotation and visitor based handlers so that future calls * to the handle methods will defer to these handlers. */ - public static HandlerLibrary load(Messager messager) { + public static HandlerLibrary load(Messager messager, Trees trees) { HandlerLibrary library = new HandlerLibrary(messager); try { - loadAnnotationHandlers(library); - loadVisitorHandlers(library); + loadAnnotationHandlers(library, trees); + loadVisitorHandlers(library, trees); } catch (IOException e) { System.err.println("Lombok isn't running due to misconfigured SPI files: " + e); } @@ -165,9 +166,10 @@ public class HandlerLibrary { /** Uses SPI Discovery to find implementations of {@link JavacAnnotationHandler}. */ @SuppressWarnings({"rawtypes", "unchecked"}) - private static void loadAnnotationHandlers(HandlerLibrary lib) throws IOException { + private static void loadAnnotationHandlers(HandlerLibrary lib, Trees trees) throws IOException { //No, that seemingly superfluous reference to JavacAnnotationHandler's classloader is not in fact superfluous! for (JavacAnnotationHandler handler : SpiLoadUtil.findServices(JavacAnnotationHandler.class, JavacAnnotationHandler.class.getClassLoader())) { + handler.setTrees(trees); Class<? extends Annotation> annotationClass = handler.getAnnotationHandledByThisHandler(); AnnotationHandlerContainer<?> container = new AnnotationHandlerContainer(handler, annotationClass); String annotationClassName = container.annotationClass.getName().replace("$", "."); @@ -179,9 +181,10 @@ public class HandlerLibrary { } /** Uses SPI Discovery to find implementations of {@link JavacASTVisitor}. */ - private static void loadVisitorHandlers(HandlerLibrary lib) throws IOException { + private static void loadVisitorHandlers(HandlerLibrary lib, Trees trees) throws IOException { //No, that seemingly superfluous reference to JavacASTVisitor's classloader is not in fact superfluous! for (JavacASTVisitor visitor : SpiLoadUtil.findServices(JavacASTVisitor.class, JavacASTVisitor.class.getClassLoader())) { + visitor.setTrees(trees); lib.visitorHandlers.add(new VisitorContainer(visitor)); } } diff --git a/src/core/lombok/javac/Javac8BasedLombokOptions.java b/src/core/lombok/javac/Javac8BasedLombokOptions.java index 3fdea890..9a662490 100644 --- a/src/core/lombok/javac/Javac8BasedLombokOptions.java +++ b/src/core/lombok/javac/Javac8BasedLombokOptions.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 The Project Lombok Authors. + * Copyright (C) 2013-2017 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 @@ -28,7 +28,7 @@ import com.sun.tools.javac.util.Options; public class Javac8BasedLombokOptions extends LombokOptions { public static Javac8BasedLombokOptions replaceWithDelombokOptions(Context context) { Options options = Options.instance(context); - context.put(optionsKey, (Options)null); + context.put(optionsKey, (Options) null); Javac8BasedLombokOptions result = new Javac8BasedLombokOptions(context); result.putAll(options); return result; diff --git a/src/core/lombok/javac/Javac9BasedLombokOptions.java b/src/core/lombok/javac/Javac9BasedLombokOptions.java new file mode 100644 index 00000000..e786346d --- /dev/null +++ b/src/core/lombok/javac/Javac9BasedLombokOptions.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2017 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.javac; + +import com.sun.tools.javac.main.Option; +import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.Options; + +public class Javac9BasedLombokOptions extends LombokOptions { + public static Javac9BasedLombokOptions replaceWithDelombokOptions(Context context) { + Options options = Options.instance(context); + context.put(optionsKey, (Options) null); + Javac9BasedLombokOptions result = new Javac9BasedLombokOptions(context); + result.putAll(options); + return result; + } + + private Javac9BasedLombokOptions(Context context) { + super(context); + } + + @Override public void putJavacOption(String optionName, String value) { + if (optionName.equals("CLASSPATH")) optionName = "CLASS_PATH"; + if (optionName.equals("SOURCEPATH")) optionName = "SOURCE_PATH"; + if (optionName.equals("BOOTCLASSPATH")) optionName = "BOOT_CLASS_PATH"; + String optionText = Option.valueOf(optionName).primaryName; + put(optionText, value); + } +} diff --git a/src/core/lombok/javac/JavacAST.java b/src/core/lombok/javac/JavacAST.java index da61361d..4ca2c050 100644 --- a/src/core/lombok/javac/JavacAST.java +++ b/src/core/lombok/javac/JavacAST.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2015 The Project Lombok Authors. + * Copyright (C) 2009-2017 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 @@ -28,11 +28,14 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import javax.annotation.processing.Messager; import javax.tools.Diagnostic; import javax.tools.JavaFileObject; +import com.sun.tools.javac.util.JCDiagnostic; import lombok.core.AST; import com.sun.tools.javac.code.Source; @@ -46,8 +49,6 @@ import com.sun.tools.javac.tree.JCTree.JCCatch; import com.sun.tools.javac.tree.JCTree.JCClassDecl; import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; import com.sun.tools.javac.tree.JCTree.JCExpression; -import com.sun.tools.javac.tree.JCTree.JCFieldAccess; -import com.sun.tools.javac.tree.JCTree.JCIdent; import com.sun.tools.javac.tree.JCTree.JCMethodDecl; import com.sun.tools.javac.tree.JCTree.JCStatement; import com.sun.tools.javac.tree.JCTree.JCTry; @@ -63,12 +64,12 @@ import com.sun.tools.javac.util.Name; * something javac's own AST system does not offer. */ public class JavacAST extends AST<JavacAST, JavacNode, JCTree> { - private final Messager messager; private final JavacElements elements; private final JavacTreeMaker treeMaker; private final Symtab symtab; private final JavacTypes javacTypes; private final Log log; + private final ErrorLog errorLogger; private final Context context; /** @@ -79,11 +80,11 @@ public class JavacAST extends AST<JavacAST, JavacNode, JCTree> { * @param top The compilation unit, which serves as the top level node in the tree to be built. */ public JavacAST(Messager messager, Context context, JCCompilationUnit top) { - super(sourceName(top), PackageName.getPackageName(top), new JavacImportList(top)); + super(sourceName(top), PackageName.getPackageName(top), new JavacImportList(top), statementTypes()); setTop(buildCompilationUnit(top)); this.context = context; - this.messager = messager; this.log = Log.instance(context); + this.errorLogger = ErrorLog.create(messager, log); this.elements = JavacElements.instance(context); this.treeMaker = new JavacTreeMaker(TreeMaker.instance(context)); this.symtab = Symtab.instance(context); @@ -104,27 +105,6 @@ public class JavacAST extends AST<JavacAST, JavacNode, JCTree> { return cu.sourcefile == null ? null : cu.sourcefile.toString(); } - // jdk9 support, types have changed, names stay the same - static class PackageName { - private static final Method packageNameMethod; - - static { - Method m = null; - try { - m = JCCompilationUnit.class.getDeclaredMethod("getPackageName"); - } catch (Exception e) {} - packageNameMethod = m; - } - - static String getPackageName(JCCompilationUnit cu) { - try { - Object pkg = packageNameMethod.invoke(cu); - return (pkg instanceof JCFieldAccess || pkg instanceof JCIdent) ? pkg.toString() : null; - } catch (Exception e) {} - return null; - } - } - public Context getContext() { return context; } @@ -146,6 +126,8 @@ public class JavacAST extends AST<JavacAST, JavacNode, JCTree> { String nm = Source.instance(context).name(); int underscoreIdx = nm.indexOf('_'); if (underscoreIdx > -1) return Integer.parseInt(nm.substring(underscoreIdx + 1)); + // assume java9+ + return Integer.parseInt(nm); } catch (Exception ignore) {} return 6; } @@ -207,7 +189,7 @@ public class JavacAST extends AST<JavacAST, JavacNode, JCTree> { List<JavacNode> childNodes = new ArrayList<JavacNode>(); for (JCTree s : top.defs) { if (s instanceof JCClassDecl) { - addIfNotNull(childNodes, buildType((JCClassDecl)s)); + addIfNotNull(childNodes, buildType((JCClassDecl) s)); } // else they are import statements, which we don't care about. Or Skip objects, whatever those are. } @@ -226,10 +208,10 @@ public class JavacAST extends AST<JavacAST, JavacNode, JCTree> { * JCVariableDecl for fields * JCBlock for (static) initializers */ - if (def instanceof JCMethodDecl) addIfNotNull(childNodes, buildMethod((JCMethodDecl)def)); - else if (def instanceof JCClassDecl) addIfNotNull(childNodes, buildType((JCClassDecl)def)); - else if (def instanceof JCVariableDecl) addIfNotNull(childNodes, buildField((JCVariableDecl)def)); - else if (def instanceof JCBlock) addIfNotNull(childNodes, buildInitializer((JCBlock)def)); + if (def instanceof JCMethodDecl) addIfNotNull(childNodes, buildMethod((JCMethodDecl) def)); + else if (def instanceof JCClassDecl) addIfNotNull(childNodes, buildType((JCClassDecl) def)); + else if (def instanceof JCVariableDecl) addIfNotNull(childNodes, buildField((JCVariableDecl) def)); + else if (def instanceof JCBlock) addIfNotNull(childNodes, buildInitializer((JCBlock) def)); } return putInMap(new JavacNode(this, type, childNodes, Kind.TYPE)); @@ -315,7 +297,6 @@ public class JavacAST extends AST<JavacAST, JavacNode, JCTree> { // @Foo int x, y; is handled in javac by putting the same annotation node on 2 JCVariableDecls. return null; } - return putInMap(new JavacNode(this, annotation, null, Kind.ANNOTATION)); } @@ -333,12 +314,40 @@ public class JavacAST extends AST<JavacAST, JavacNode, JCTree> { if (statement instanceof JCClassDecl) return buildType((JCClassDecl)statement); if (statement instanceof JCVariableDecl) return buildLocalVar((JCVariableDecl)statement, Kind.LOCAL); if (statement instanceof JCTry) return buildTry((JCTry) statement); - + if (statement.getClass().getSimpleName().equals("JCLambda")) return buildLambda(statement); if (setAndGetAsHandled(statement)) return null; return drill(statement); } + private JavacNode buildLambda(JCTree jcTree) { + return buildStatementOrExpression(getBody(jcTree)); + } + + private JCTree getBody(JCTree jcTree) { + try { + return (JCTree) getBodyMethod(jcTree.getClass()).invoke(jcTree); + } catch (Exception e) { + throw Javac.sneakyThrow(e); + } + } + + private final static ConcurrentMap<Class<?>, Method> getBodyMethods = new ConcurrentHashMap<Class<?>, Method>(); + + private Method getBodyMethod(Class<?> c) { + Method m = getBodyMethods.get(c); + if (m != null) { + return m; + } + try { + m = c.getMethod("getBody"); + } catch (NoSuchMethodException e) { + throw Javac.sneakyThrow(e); + } + getBodyMethods.putIfAbsent(c, m); + return getBodyMethods.get(c); + } + private JavacNode drill(JCTree statement) { try { List<JavacNode> childNodes = new ArrayList<JavacNode>(); @@ -354,9 +363,8 @@ public class JavacAST extends AST<JavacAST, JavacNode, JCTree> { } } - /** For javac, both JCExpression and JCStatement are considered as valid children types. */ - @Override - protected Collection<Class<? extends JCTree>> getStatementTypes() { + /* For javac, both JCExpression and JCStatement are considered as valid children types. */ + private static Collection<Class<? extends JCTree>> statementTypes() { Collection<Class<? extends JCTree>> collection = new ArrayList<Class<? extends JCTree>>(3); collection.add(JCStatement.class); collection.add(JCExpression.class); @@ -388,28 +396,27 @@ public class JavacAST extends AST<JavacAST, JavacNode, JCTree> { oldSource = log.useSource(newSource); if (pos == null) pos = astObject.pos(); } - if (pos != null && attemptToRemoveErrorsInRange) { + if (pos != null && node != null && attemptToRemoveErrorsInRange) { removeFromDeferredDiagnostics(pos.getStartPosition(), node.getEndPosition(pos)); } try { switch (kind) { case ERROR: - increaseErrorCount(messager); - boolean prev = log.multipleErrors; - log.multipleErrors = true; - try { - log.error(pos, "proc.messager", message); - } finally { - log.multipleErrors = prev; - } + errorLogger.error(pos, message); + break; + case MANDATORY_WARNING: + errorLogger.mandatoryWarning(pos, message); break; - default: case WARNING: - log.warning(pos, "proc.messager", message); + errorLogger.warning(pos, message); + break; + default: + case NOTE: + errorLogger.note(pos, message); break; } } finally { - if (oldSource != null) log.useSource(oldSource); + if (newSource != null) log.useSource(oldSource); } } @@ -447,16 +454,183 @@ public class JavacAST extends AST<JavacAST, JavacNode, JCTree> { return oldL; } - private void increaseErrorCount(Messager m) { - try { - Field f = m.getClass().getDeclaredField("errorCount"); - f.setAccessible(true); - if (f.getType() == int.class) { - int val = ((Number)f.get(m)).intValue(); - f.set(m, val +1); + abstract static class ErrorLog { + final Log log; + private final Messager messager; + private final Field errorCount; + private final Field warningCount; + + private ErrorLog(Log log, Messager messager, Field errorCount, Field warningCount) { + this.log = log; + this.messager = messager; + this.errorCount = errorCount; + this.warningCount = warningCount; + } + + final void error(DiagnosticPosition pos, String message) { + increment(errorCount); + error1(pos, message); + } + + final void warning(DiagnosticPosition pos, String message) { + increment(warningCount); + warning1(pos, message); + } + + final void mandatoryWarning(DiagnosticPosition pos, String message) { + increment(warningCount); + mandatoryWarning1(pos, message); + } + + abstract void error1(DiagnosticPosition pos, String message); + abstract void warning1(DiagnosticPosition pos, String message); + abstract void mandatoryWarning1(DiagnosticPosition pos, String message); + abstract void note(DiagnosticPosition pos, String message); + + private void increment(Field field) { + if (field == null) return; + try { + int val = ((Number)field.get(messager)).intValue(); + field.set(messager, val +1); + } catch (Throwable t) { + //Very unfortunate, but in most cases it still works fine, so we'll silently swallow it. + } + } + + static ErrorLog create(Messager messager, Log log) { + Field errorCount = null; + try { + Field f = messager.getClass().getDeclaredField("errorCount"); + f.setAccessible(true); + errorCount = f; + } catch (Throwable t) {} + boolean hasMultipleErrors = false; + for (Field field : log.getClass().getFields()) { + if (field.getName().equals("multipleErrors")) { + hasMultipleErrors = true; + break; + } + } + if (hasMultipleErrors) return new JdkBefore9(log, messager, errorCount); + + Field warningCount = null; + try { + Field f = messager.getClass().getDeclaredField("warningCount"); + f.setAccessible(true); + warningCount = f; + } catch (Throwable t) {} + + return new Jdk9Plus(log, messager, errorCount, warningCount); + } + } + + static class JdkBefore9 extends ErrorLog { + private JdkBefore9(Log log, Messager messager, Field errorCount) { + super(log, messager, errorCount, null); + } + + @Override void error1(DiagnosticPosition pos, String message) { + boolean prev = log.multipleErrors; + log.multipleErrors = true; + try { + log.error(pos, "proc.messager", message); + } finally { + log.multipleErrors = prev; + } + } + + @Override void warning1(DiagnosticPosition pos, String message) { + log.warning(pos, "proc.messager", message); + } + + @Override void mandatoryWarning1(DiagnosticPosition pos, String message) { + log.mandatoryWarning(pos, "proc.messager", message); + } + + @Override void note(DiagnosticPosition pos, String message) { + log.note(pos, "proc.messager", message); + } + } + + static class Jdk9Plus extends ErrorLog { + private static final String PROC_MESSAGER = "proc.messager"; + private Object multiple; + private Method errorMethod, warningMethod, mandatoryWarningMethod, noteMethod; + private Method errorKey, warningKey, noteKey; + private JCDiagnostic.Factory diags; + + private Jdk9Plus(Log log, Messager messager, Field errorCount, Field warningCount) { + super(log, messager, errorCount, warningCount); + + try { + final String jcd = "com.sun.tools.javac.util.JCDiagnostic"; + Class<?> df = Class.forName(jcd + "$DiagnosticFlag"); + for (Object constant : df.getEnumConstants()) { + if (constant.toString().equals("MULTIPLE")) this.multiple = constant; + } + + Class<?> errorCls = Class.forName(jcd + "$Error"); + Class<?> warningCls = Class.forName(jcd + "$Warning"); + Class<?> noteCls = Class.forName(jcd + "$Note"); + + Class<?> lc = log.getClass(); + this.errorMethod = lc.getMethod("error", df, DiagnosticPosition.class, errorCls); + this.warningMethod = lc.getMethod("warning", DiagnosticPosition.class, warningCls); + this.mandatoryWarningMethod = lc.getMethod("mandatoryWarning", DiagnosticPosition.class, warningCls); + this.noteMethod = lc.getMethod("note", DiagnosticPosition.class, noteCls); + + Field diagsField = lc.getSuperclass().getDeclaredField("diags"); + diagsField.setAccessible(true); + this.diags = (JCDiagnostic.Factory)diagsField.get(log); + + Class<?> dc = this.diags.getClass(); + this.errorKey = dc.getMethod("errorKey", String.class, Object[].class); + this.warningKey = dc.getDeclaredMethod("warningKey", String.class, Object[].class); + this.warningKey.setAccessible(true); + this.noteKey = dc.getDeclaredMethod("noteKey", String.class, Object[].class); + this.noteKey.setAccessible(true); + } catch (Throwable t) { + //t.printStackTrace(); + } + } + + @Override void error1(DiagnosticPosition pos, String message) { + try { + Object error = this.errorKey.invoke(diags, PROC_MESSAGER, new Object[] { message }); + errorMethod.invoke(log, multiple, pos, error); + } catch (Throwable t) { + //t.printStackTrace(); + } + } + + @Override + void warning1(DiagnosticPosition pos, String message) { + try { + Object warning = this.warningKey.invoke(diags, PROC_MESSAGER, new Object[] { message }); + warningMethod.invoke(log, pos, warning); + } catch (Throwable t) { + //t.printStackTrace(); + } + } + + @Override + void mandatoryWarning1(DiagnosticPosition pos, String message) { + try { + Object warning = this.warningKey.invoke(diags, PROC_MESSAGER, new Object[] { message }); + mandatoryWarningMethod.invoke(log, pos, warning); + } catch (Throwable t) { + //t.printStackTrace(); + } + } + + @Override + void note(DiagnosticPosition pos, String message) { + try { + Object note = this.noteKey.invoke(diags, PROC_MESSAGER, new Object[] { message }); + noteMethod.invoke(log, pos, note); + } catch (Throwable t) { + //t.printStackTrace(); } - } catch (Throwable t) { - //Very unfortunate, but in most cases it still works fine, so we'll silently swallow it. } } } diff --git a/src/core/lombok/javac/JavacASTAdapter.java b/src/core/lombok/javac/JavacASTAdapter.java index 5d120a77..6af53e3d 100644 --- a/src/core/lombok/javac/JavacASTAdapter.java +++ b/src/core/lombok/javac/JavacASTAdapter.java @@ -21,6 +21,7 @@ */ package lombok.javac; +import com.sun.source.util.Trees; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.JCAnnotation; import com.sun.tools.javac.tree.JCTree.JCBlock; @@ -35,6 +36,9 @@ import com.sun.tools.javac.tree.JCTree.JCVariableDecl; */ public class JavacASTAdapter implements JavacASTVisitor { /** {@inheritDoc} */ + @Override public void setTrees(Trees trees) {} + + /** {@inheritDoc} */ @Override public void visitCompilationUnit(JavacNode top, JCCompilationUnit unit) {} /** {@inheritDoc} */ diff --git a/src/core/lombok/javac/JavacASTVisitor.java b/src/core/lombok/javac/JavacASTVisitor.java index c57e657a..565980f9 100644 --- a/src/core/lombok/javac/JavacASTVisitor.java +++ b/src/core/lombok/javac/JavacASTVisitor.java @@ -23,6 +23,7 @@ package lombok.javac; import java.io.PrintStream; +import com.sun.source.util.Trees; import com.sun.tools.javac.code.Flags; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.JCAnnotation; @@ -37,6 +38,8 @@ import com.sun.tools.javac.tree.JCTree.JCVariableDecl; * calling the appropriate visit and endVisit methods. */ public interface JavacASTVisitor { + void setTrees(Trees trees); + /** * Called at the very beginning and end. */ @@ -121,6 +124,8 @@ public interface JavacASTVisitor { this.out = out; } + @Override public void setTrees(Trees trees) {} + private void forcePrint(String text, Object... params) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < indent; i++) sb.append(" "); diff --git a/src/core/lombok/javac/JavacAnnotationHandler.java b/src/core/lombok/javac/JavacAnnotationHandler.java index a86aa6c6..dd4e7098 100644 --- a/src/core/lombok/javac/JavacAnnotationHandler.java +++ b/src/core/lombok/javac/JavacAnnotationHandler.java @@ -26,6 +26,7 @@ import java.lang.annotation.Annotation; import lombok.core.AnnotationValues; import lombok.core.SpiLoadUtil; +import com.sun.source.util.Trees; import com.sun.tools.javac.tree.JCTree.JCAnnotation; /** @@ -40,6 +41,8 @@ import com.sun.tools.javac.tree.JCTree.JCAnnotation; * You also need to register yourself via SPI discovery as being an implementation of {@code JavacAnnotationHandler}. */ public abstract class JavacAnnotationHandler<T extends Annotation> { + protected Trees trees; + /** * Called when an annotation is found that is likely to match the annotation you're interested in. * @@ -63,4 +66,8 @@ public abstract class JavacAnnotationHandler<T extends Annotation> { @SuppressWarnings("unchecked") public Class<T> getAnnotationHandledByThisHandler() { return (Class<T>) SpiLoadUtil.findAnnotationClass(getClass(), JavacAnnotationHandler.class); } + + public void setTrees(Trees trees) { + this.trees = trees; + } } diff --git a/src/core/lombok/javac/JavacImportList.java b/src/core/lombok/javac/JavacImportList.java index 0f789f45..468d8c7b 100644 --- a/src/core/lombok/javac/JavacImportList.java +++ b/src/core/lombok/javac/JavacImportList.java @@ -26,7 +26,6 @@ import java.util.Collection; import lombok.core.ImportList; import lombok.core.LombokInternalAliasing; -import lombok.javac.JavacAST.PackageName; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; diff --git a/src/core/lombok/javac/JavacNode.java b/src/core/lombok/javac/JavacNode.java index 727692ac..2bce6e3a 100644 --- a/src/core/lombok/javac/JavacNode.java +++ b/src/core/lombok/javac/JavacNode.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2013 The Project Lombok Authors. + * Copyright (C) 2009-2017 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 @@ -21,12 +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; @@ -35,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; @@ -51,6 +57,13 @@ public class JavacNode extends lombok.core.LombokNode<JavacAST, JavacNode, JCTre super(ast, node, children, kind); } + public Element getElement() { + if (node instanceof JCClassDecl) return ((JCClassDecl) node).sym; + if (node instanceof JCMethodDecl) return ((JCMethodDecl) node).sym; + if (node instanceof JCVariableDecl) return ((JCVariableDecl) node).sym; + return null; + } + public int getEndPosition(DiagnosticPosition pos) { JCCompilationUnit cu = (JCCompilationUnit) top().get(); return Javac.getEndPosition(pos, cu); @@ -248,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/JavacResolution.java b/src/core/lombok/javac/JavacResolution.java index 67dbaac6..8cc239e1 100644 --- a/src/core/lombok/javac/JavacResolution.java +++ b/src/core/lombok/javac/JavacResolution.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2015 The Project Lombok Authors. + * Copyright (C) 2011-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,7 @@ import java.util.ArrayDeque; import java.util.Map; import javax.lang.model.type.TypeKind; +import javax.tools.JavaFileObject; import lombok.Lombok; import lombok.core.debug.AssertionLogger; @@ -59,6 +60,7 @@ import com.sun.tools.javac.tree.JCTree.JCVariableDecl; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.List; import com.sun.tools.javac.util.ListBuffer; +import com.sun.tools.javac.util.Log; public class JavacResolution { private final Attr attr; @@ -142,9 +144,14 @@ public class JavacResolution { TreeMirrorMaker mirrorMaker = new TreeMirrorMaker(node.getTreeMaker(), node.getContext()); JCTree copy = mirrorMaker.copy(finder.copyAt()); - - memberEnterAndAttribute(copy, finder.get(), node.getContext()); - return mirrorMaker.getOriginalToCopyMap(); + Log log = Log.instance(node.getContext()); + JavaFileObject oldFileObject = log.useSource(((JCCompilationUnit) node.top().get()).getSourceFile()); + try { + memberEnterAndAttribute(copy, finder.get(), node.getContext()); + return mirrorMaker.getOriginalToCopyMap(); + } finally { + log.useSource(oldFileObject); + } } finally { messageSuppressor.enableLoggers(); } @@ -222,8 +229,13 @@ public class JavacResolution { } private void attrib(JCTree tree, Env<AttrContext> env) { + if (env.enclClass.type == null) try { + env.enclClass.type = Type.noType; + } catch (Throwable ignore) { + // This addresses issue #1553 which involves JDK9; if it doesn't exist, we probably don't need to set it. + } if (tree instanceof JCBlock) attr.attribStat(tree, env); - else if (tree instanceof JCMethodDecl) attr.attribStat(((JCMethodDecl)tree).body, env); + else if (tree instanceof JCMethodDecl) attr.attribStat(((JCMethodDecl) tree).body, env); else if (tree instanceof JCVariableDecl) attr.attribStat(tree, env); else throw new IllegalStateException("Called with something that isn't a block, method decl, or variable decl"); } @@ -261,6 +273,7 @@ public class JavacResolution { } public static Type ifTypeIsIterableToComponent(Type type, JavacAST ast) { + if (type == null) return null; Types types = Types.instance(ast.getContext()); Symtab syms = Symtab.instance(ast.getContext()); Type boundType = ReflectiveAccess.Types_upperBound(types, type); diff --git a/src/core/lombok/javac/JavacTransformer.java b/src/core/lombok/javac/JavacTransformer.java index 004a6035..54977a59 100644 --- a/src/core/lombok/javac/JavacTransformer.java +++ b/src/core/lombok/javac/JavacTransformer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2012 The Project Lombok Authors. + * Copyright (C) 2009-2017 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 @@ -26,6 +26,7 @@ import java.util.SortedSet; import javax.annotation.processing.Messager; +import com.sun.source.util.Trees; import com.sun.tools.javac.tree.JCTree.JCAnnotation; import com.sun.tools.javac.tree.JCTree.JCClassDecl; import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; @@ -38,9 +39,9 @@ public class JavacTransformer { private final HandlerLibrary handlers; private final Messager messager; - public JavacTransformer(Messager messager) { + public JavacTransformer(Messager messager, Trees trees) { this.messager = messager; - this.handlers = HandlerLibrary.load(messager); + this.handlers = HandlerLibrary.load(messager, trees); } public SortedSet<Long> getPriorities() { @@ -54,7 +55,7 @@ public class JavacTransformer { public void transform(long priority, Context context, java.util.List<JCCompilationUnit> compilationUnitsRaw) { List<JCCompilationUnit> compilationUnits; if (compilationUnitsRaw instanceof List<?>) { - compilationUnits = (List<JCCompilationUnit>)compilationUnitsRaw; + compilationUnits = (List<JCCompilationUnit>) compilationUnitsRaw; } else { compilationUnits = List.nil(); for (int i = compilationUnitsRaw.size() -1; i >= 0; i--) { diff --git a/src/core/lombok/javac/apt/EmptyLombokFileObject.java b/src/core/lombok/javac/apt/EmptyLombokFileObject.java index 5a3a7def..84bb00e4 100644 --- a/src/core/lombok/javac/apt/EmptyLombokFileObject.java +++ b/src/core/lombok/javac/apt/EmptyLombokFileObject.java @@ -57,7 +57,7 @@ class EmptyLombokFileObject implements LombokFileObject { } @Override public URI toUri() { - return URI.create(name); + return URI.create("file:///" + (name.startsWith("/") ? name.substring(1) : name)); } @Override public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { diff --git a/src/core/lombok/javac/apt/InterceptingJavaFileManager.java b/src/core/lombok/javac/apt/InterceptingJavaFileManager.java index 303bdc2f..9b58d111 100644 --- a/src/core/lombok/javac/apt/InterceptingJavaFileManager.java +++ b/src/core/lombok/javac/apt/InterceptingJavaFileManager.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2011 The Project Lombok Authors. + * Copyright (C) 2010-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 @@ -42,14 +42,14 @@ final class InterceptingJavaFileManager extends ForwardingJavaFileManager<JavaFi } @Override public JavaFileObject getJavaFileForOutput(Location location, String className, final Kind kind, FileObject sibling) throws IOException { - if (className.startsWith("lombok.dummy.ForceNewRound")) { + if (className.contains("lombok.dummy.ForceNewRound")) { final String name = className.replace(".", "/") + kind.extension; return LombokFileObjects.createEmpty(compiler, name, kind); } + JavaFileObject fileObject = fileManager.getJavaFileForOutput(location, className, kind, sibling); - if (kind != Kind.CLASS) { - return fileObject; - } + if (kind != Kind.CLASS) return fileObject; + return LombokFileObjects.createIntercepting(compiler, fileObject, className, diagnostics); } }
\ No newline at end of file diff --git a/src/core/lombok/javac/apt/Javac9BaseFileObjectWrapper.java b/src/core/lombok/javac/apt/Javac9BaseFileObjectWrapper.java new file mode 100644 index 00000000..f9fe2a7d --- /dev/null +++ b/src/core/lombok/javac/apt/Javac9BaseFileObjectWrapper.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2010-2017 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.javac.apt; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.Writer; +import java.net.URI; +import java.nio.file.Path; + +import javax.lang.model.element.Modifier; +import javax.lang.model.element.NestingKind; + +import com.sun.tools.javac.file.BaseFileManager; + +class Javac9BaseFileObjectWrapper extends com.sun.tools.javac.file.PathFileObject { + private final LombokFileObject delegate; + + public Javac9BaseFileObjectWrapper(BaseFileManager fileManager, Path path, LombokFileObject delegate) { + super(fileManager, path); + this.delegate = delegate; + } + + @Override public boolean isNameCompatible(String simpleName, Kind kind) { + return delegate.isNameCompatible(simpleName, kind); + } + + @Override public URI toUri() { + return delegate.toUri(); + } + + @SuppressWarnings("all") + @Override public String getName() { + return delegate.getName(); + } + + @Override public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { + return delegate.getCharContent(ignoreEncodingErrors); + } + + @Override public InputStream openInputStream() throws IOException { + return delegate.openInputStream(); + } + + @Override public Reader openReader(boolean ignoreEncodingErrors) throws IOException { + return delegate.openReader(ignoreEncodingErrors); + } + + @Override public Writer openWriter() throws IOException { + return delegate.openWriter(); + } + + @Override public OutputStream openOutputStream() throws IOException { + return delegate.openOutputStream(); + } + + @Override public long getLastModified() { + return delegate.getLastModified(); + } + + @Override public boolean delete() { + return delegate.delete(); + } + + @Override public Kind getKind() { + return delegate.getKind(); + } + + @Override public NestingKind getNestingKind() { + return delegate.getNestingKind(); + } + + @Override public Modifier getAccessLevel() { + return delegate.getAccessLevel(); + } + + @Override public boolean equals(Object obj) { + if (!(obj instanceof Javac9BaseFileObjectWrapper)) { + return false; + } + return delegate.equals(((Javac9BaseFileObjectWrapper)obj).delegate); + } + + @Override public int hashCode() { + return delegate.hashCode(); + } + + @Override public String toString() { + return delegate.toString(); + } +}
\ No newline at end of file diff --git a/src/core/lombok/javac/apt/LombokFileObjects.java b/src/core/lombok/javac/apt/LombokFileObjects.java index 412e449b..aba10540 100644 --- a/src/core/lombok/javac/apt/LombokFileObjects.java +++ b/src/core/lombok/javac/apt/LombokFileObjects.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2011 The Project Lombok Authors. + * Copyright (C) 2010-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 @@ -22,20 +22,32 @@ package lombok.javac.apt; +import java.io.IOException; import java.lang.reflect.Method; +import java.net.URI; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; +import javax.tools.FileObject; import javax.tools.JavaFileManager; import javax.tools.JavaFileObject; import javax.tools.JavaFileObject.Kind; import lombok.core.DiagnosticsReceiver; +import com.sun.tools.javac.file.BaseFileManager; + //Can't use SimpleJavaFileObject so we copy/paste most of its content here, because javac doesn't follow the interface, //and casts to its own BaseFileObject type. D'oh! final class LombokFileObjects { - enum Compiler { - JAVAC6 { + + interface Compiler { + Compiler JAVAC6 = new Compiler() { private Method decoderMethod = null; private final AtomicBoolean decoderIsSet = new AtomicBoolean(); @@ -46,13 +58,13 @@ final class LombokFileObjects { @Override public Method getDecoderMethod() { synchronized (decoderIsSet) { if (decoderIsSet.get()) return decoderMethod; - decoderMethod = getDecoderMethod("com.sun.tools.javac.util.BaseFileObject"); + decoderMethod = LombokFileObjects.getDecoderMethod("com.sun.tools.javac.util.BaseFileObject"); decoderIsSet.set(true); return decoderMethod; } } - }, - JAVAC7 { + }; + Compiler JAVAC7 = new Compiler() { private Method decoderMethod = null; private final AtomicBoolean decoderIsSet = new AtomicBoolean(); @@ -63,46 +75,82 @@ final class LombokFileObjects { @Override public Method getDecoderMethod() { synchronized (decoderIsSet) { if (decoderIsSet.get()) return decoderMethod; - decoderMethod = getDecoderMethod("com.sun.tools.javac.file.BaseFileObject"); + decoderMethod = LombokFileObjects.getDecoderMethod("com.sun.tools.javac.file.BaseFileObject"); decoderIsSet.set(true); return decoderMethod; } } }; - static Method getDecoderMethod(String className) { - Method m = null; - try { - m = Class.forName(className).getDeclaredMethod("getDecoder", boolean.class); - m.setAccessible(true); - } catch (NoSuchMethodException e) { - // Intentional fallthrough - getDecoder(boolean) is not always present. - } catch (ClassNotFoundException e) { - // Intentional fallthrough - getDecoder(boolean) is not always present. - } - return m; - } + JavaFileObject wrap(LombokFileObject fileObject); + Method getDecoderMethod(); + } - abstract JavaFileObject wrap(LombokFileObject fileObject); - abstract Method getDecoderMethod(); + static Method getDecoderMethod(String className) { + Method m = null; + try { + m = Class.forName(className).getDeclaredMethod("getDecoder", boolean.class); + m.setAccessible(true); + } catch (NoSuchMethodException e) { + // Intentional fallthrough - getDecoder(boolean) is not always present. + } catch (ClassNotFoundException e) { + // Intentional fallthrough - getDecoder(boolean) is not always present. + } + return m; } private LombokFileObjects() {} + private static final List<String> KNOWN_JAVA9_FILE_MANAGERS = Arrays.asList( + "com.google.errorprone.MaskedClassLoader$MaskedFileManager", + "com.google.devtools.build.buildjar.javac.BlazeJavacMain$ClassloaderMaskingFileManager", + "com.google.devtools.build.java.turbine.javac.JavacTurbineCompiler$ClassloaderMaskingFileManager", + "org.netbeans.modules.java.source.parsing.ProxyFileManager", + "com.sun.tools.javac.api.ClientCodeWrapper$WrappedStandardJavaFileManager", + "com.sun.tools.javac.main.DelegatingJavaFileManager$DelegatingSJFM" // IntelliJ + JDK10 + ); + static Compiler getCompiler(JavaFileManager jfm) { String jfmClassName = jfm != null ? jfm.getClass().getName() : "null"; if (jfmClassName.equals("com.sun.tools.javac.util.DefaultFileManager")) return Compiler.JAVAC6; if (jfmClassName.equals("com.sun.tools.javac.util.JavacFileManager")) return Compiler.JAVAC6; - if (jfmClassName.equals("com.sun.tools.javac.file.JavacFileManager")) return Compiler.JAVAC7; + if (jfmClassName.equals("com.sun.tools.javac.file.JavacFileManager")) { + try { + Class<?> superType = Class.forName("com.sun.tools.javac.file.BaseFileManager"); + if (superType.isInstance(jfm)) { + return new Java9Compiler(jfm); + } + } + catch (Throwable e) {} + return Compiler.JAVAC7; + } + if (KNOWN_JAVA9_FILE_MANAGERS.contains(jfmClassName)) { + try { + return new Java9Compiler(jfm); + } + catch (Throwable e) {} + } + try { + if (Class.forName("com.sun.tools.javac.file.PathFileObject") == null) throw new NullPointerException(); + return new Java9Compiler(jfm); + } catch (Throwable e) {} try { if (Class.forName("com.sun.tools.javac.file.BaseFileObject") == null) throw new NullPointerException(); return Compiler.JAVAC7; - } catch (Exception e) {} + } catch (Throwable e) {} try { if (Class.forName("com.sun.tools.javac.util.BaseFileObject") == null) throw new NullPointerException(); return Compiler.JAVAC6; - } catch (Exception e) {} - return null; + } catch (Throwable e) {} + + StringBuilder sb = new StringBuilder(jfmClassName); + if (jfm != null) { + sb.append(" extends ").append(jfm.getClass().getSuperclass().getName()); + for (Class<?> cls : jfm.getClass().getInterfaces()) { + sb.append(" implements ").append(cls.getName()); + } + } + throw new IllegalArgumentException(sb.toString()); } static JavaFileObject createEmpty(Compiler compiler, String name, Kind kind) { @@ -112,4 +160,113 @@ final class LombokFileObjects { static JavaFileObject createIntercepting(Compiler compiler, JavaFileObject delegate, String fileName, DiagnosticsReceiver diagnostics) { return compiler.wrap(new InterceptingJavaFileObject(delegate, fileName, diagnostics, compiler.getDecoderMethod())); } + + static class Java9Compiler implements Compiler { + private final BaseFileManager fileManager; + + public Java9Compiler(JavaFileManager jfm) { + fileManager = asBaseFileManager(jfm); + } + + @Override public JavaFileObject wrap(LombokFileObject fileObject) { + return new Javac9BaseFileObjectWrapper(fileManager, toPath(fileObject), fileObject); + } + + @Override public Method getDecoderMethod() { + return null; + } + + private static Path toPath(LombokFileObject fileObject) { + URI uri = fileObject.toUri(); + if (uri.getScheme() == null) { + uri = URI.create("file:///" + uri); + } + try { + return Paths.get(uri); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("Problems in URI '" + uri + "' (" + fileObject.toUri() + ")", e); + } + } + + private static BaseFileManager asBaseFileManager(JavaFileManager jfm) { + if (jfm instanceof BaseFileManager) { + return (BaseFileManager) jfm; + } + return new FileManagerWrapper(jfm); + } + + static class FileManagerWrapper extends BaseFileManager { + JavaFileManager manager; + + public FileManagerWrapper(JavaFileManager manager) { + super(null); + this.manager = manager; + } + + @Override + public int isSupportedOption(String option) { + return manager.isSupportedOption(option); + } + + @Override + public ClassLoader getClassLoader(Location location) { + return manager.getClassLoader(location); + } + + @Override + public Iterable<JavaFileObject> list(Location location, String packageName, Set<Kind> kinds, boolean recurse) throws IOException { + return manager.list(location, packageName, kinds, recurse); + } + + @Override + public String inferBinaryName(Location location, JavaFileObject file) { + return manager.inferBinaryName(location, file); + } + + @Override + public boolean isSameFile(FileObject a, FileObject b) { + return manager.isSameFile(a, b); + } + + @Override + public boolean handleOption(String current, Iterator<String> remaining) { + return manager.handleOption(current, remaining); + } + + @Override + public boolean hasLocation(Location location) { + return manager.hasLocation(location); + } + + @Override + public JavaFileObject getJavaFileForInput(Location location, String className, Kind kind) throws IOException { + return manager.getJavaFileForInput(location, className, kind); + } + + @Override + public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind, FileObject sibling) throws IOException { + return manager.getJavaFileForOutput(location, className, kind, sibling); + } + + @Override + public FileObject getFileForInput(Location location, String packageName, String relativeName) throws IOException { + return manager.getFileForInput(location, packageName, relativeName); + } + + @Override + public FileObject getFileForOutput(Location location, String packageName, String relativeName, FileObject sibling) throws IOException { + return manager.getFileForOutput(location, packageName, relativeName, sibling); + } + + @Override + public void flush() throws IOException { + manager.flush(); + } + + @Override + public void close() throws IOException { + manager.close(); + } + } + } } diff --git a/src/core/lombok/javac/apt/LombokProcessor.java b/src/core/lombok/javac/apt/LombokProcessor.java index 6547c143..04b494bf 100644 --- a/src/core/lombok/javac/apt/LombokProcessor.java +++ b/src/core/lombok/javac/apt/LombokProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2015 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 @@ -54,6 +54,8 @@ import lombok.javac.JavacTransformer; import com.sun.source.util.TreePath; import com.sun.source.util.Trees; +import com.sun.tools.javac.jvm.ClassWriter; +import com.sun.tools.javac.main.JavaCompiler; import com.sun.tools.javac.processing.JavacFiler; import com.sun.tools.javac.processing.JavacProcessingEnvironment; import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; @@ -68,7 +70,9 @@ import com.sun.tools.javac.util.Context; @SupportedAnnotationTypes("*") public class LombokProcessor extends AbstractProcessor { - private JavacProcessingEnvironment processingEnv; + private ProcessingEnvironment processingEnv; + private JavacProcessingEnvironment javacProcessingEnv; + private JavacFiler javacFiler; private JavacTransformer transformer; private Trees trees; private boolean lombokDisabled = false; @@ -80,11 +84,14 @@ public class LombokProcessor extends AbstractProcessor { lombokDisabled = true; return; } - - this.processingEnv = (JavacProcessingEnvironment) procEnv; + + this.processingEnv = procEnv; + this.javacProcessingEnv = getJavacProcessingEnvironment(procEnv); + this.javacFiler = getJavacFiler(procEnv.getFiler()); + placePostCompileAndDontMakeForceRoundDummiesHook(); - transformer = new JavacTransformer(procEnv.getMessager()); - trees = Trees.instance(procEnv); + trees = Trees.instance(javacProcessingEnv); + transformer = new JavacTransformer(procEnv.getMessager(), trees); SortedSet<Long> p = transformer.getPriorities(); if (p.isEmpty()) { this.priorityLevels = new long[] {0L}; @@ -97,11 +104,55 @@ public class LombokProcessor extends AbstractProcessor { } } + private static final String JPE = "com.sun.tools.javac.processing.JavacProcessingEnvironment"; + private static final Field javacProcessingEnvironment_discoveredProcs = getFieldAccessor(JPE, "discoveredProcs"); + private static final Field discoveredProcessors_procStateList = getFieldAccessor(JPE + "$DiscoveredProcessors", "procStateList"); + private static final Field processorState_processor = getFieldAccessor(JPE + "$processor", "processor"); + + private static final Field getFieldAccessor(String typeName, String fieldName) { + try { + Class<?> c = Class.forName(typeName); + Field f = c.getDeclaredField(fieldName); + f.setAccessible(true); + return f; + } catch (ClassNotFoundException e) { + return null; + } catch (NoSuchFieldException e) { + return null; + } + } + + // The intent of this method is to have lombok emit a warning if it's not 'first in line'. However, pragmatically speaking, you're always looking at one of two cases: + // (A) The other processor(s) running before lombok require lombok to have run or they crash. So, they crash, and unfortunately we are never even init-ed; the warning is never emitted. + // (B) The other processor(s) don't care about it at all. So, it doesn't actually matter that lombok isn't first. + // Hence, for now, no warnings. + @SuppressWarnings("unused") + private String listAnnotationProcessorsBeforeOurs() { + try { + Object discoveredProcessors = javacProcessingEnvironment_discoveredProcs.get(this.javacProcessingEnv); + ArrayList<?> states = (ArrayList<?>) discoveredProcessors_procStateList.get(discoveredProcessors); + if (states == null || states.isEmpty()) return null; + if (states.size() == 1) return processorState_processor.get(states.get(0)).getClass().getName(); + + int idx = 0; + StringBuilder out = new StringBuilder(); + for (Object processState : states) { + idx++; + String name = processorState_processor.get(processState).getClass().getName(); + if (out.length() > 0) out.append(", "); + out.append("[").append(idx).append("] ").append(name); + } + return out.toString(); + } catch (Exception e) { + return null; + } + } + private void placePostCompileAndDontMakeForceRoundDummiesHook() { stopJavacProcessingEnvironmentFromClosingOurClassloader(); forceMultipleRoundsInNetBeansEditor(); - Context context = processingEnv.getContext(); + Context context = javacProcessingEnv.getContext(); disablePartialReparseInNetBeansEditor(context); try { Method keyMethod = Context.class.getDeclaredMethod("key", Class.class); @@ -112,27 +163,52 @@ public class LombokProcessor extends AbstractProcessor { @SuppressWarnings("unchecked") Map<Object,Object> ht = (Map<Object,Object>) htField.get(context); final JavaFileManager originalFiler = (JavaFileManager) ht.get(key); - if (!(originalFiler instanceof InterceptingJavaFileManager)) { final Messager messager = processingEnv.getMessager(); DiagnosticsReceiver receiver = new MessagerDiagnosticsReceiver(messager); - - JavaFileManager newFiler = new InterceptingJavaFileManager(originalFiler, receiver); - ht.put(key, newFiler); + + JavaFileManager newFilerManager = new InterceptingJavaFileManager(originalFiler, receiver); + ht.put(key, newFilerManager); Field filerFileManagerField = JavacFiler.class.getDeclaredField("fileManager"); filerFileManagerField.setAccessible(true); - filerFileManagerField.set(processingEnv.getFiler(), newFiler); + filerFileManagerField.set(javacFiler, newFilerManager); + + replaceFileManagerJdk9(context, newFilerManager); } } catch (Exception e) { throw Lombok.sneakyThrow(e); } } + + private void replaceFileManagerJdk9(Context context, JavaFileManager newFiler) { + try { + JavaCompiler compiler = (JavaCompiler) JavaCompiler.class.getDeclaredMethod("instance", Context.class).invoke(null, context); + try { + Field fileManagerField = JavaCompiler.class.getDeclaredField("fileManager"); + fileManagerField.setAccessible(true); + fileManagerField.set(compiler, newFiler); + } + catch (Exception e) {} + + try { + Field writerField = JavaCompiler.class.getDeclaredField("writer"); + writerField.setAccessible(true); + ClassWriter writer = (ClassWriter) writerField.get(compiler); + Field fileManagerField = ClassWriter.class.getDeclaredField("fileManager"); + fileManagerField.setAccessible(true); + fileManagerField.set(writer, newFiler); + } + catch (Exception e) {} + } + catch (Exception e) { + } + } private void forceMultipleRoundsInNetBeansEditor() { try { Field f = JavacProcessingEnvironment.class.getDeclaredField("isBackgroundCompilation"); f.setAccessible(true); - f.set(processingEnv, true); + f.set(javacProcessingEnv, true); } catch (NoSuchFieldException e) { // only NetBeans has it } catch (Throwable t) { @@ -206,10 +282,10 @@ public class LombokProcessor extends AbstractProcessor { try { Field f = JavacProcessingEnvironment.class.getDeclaredField("processorClassLoader"); f.setAccessible(true); - ClassLoader unwrapped = (ClassLoader) f.get(processingEnv); + ClassLoader unwrapped = (ClassLoader) f.get(javacProcessingEnv); if (unwrapped == null) return; ClassLoader wrapped = wrapClassLoader(unwrapped); - f.set(processingEnv, wrapped); + f.set(javacProcessingEnv, wrapped); } catch (NoSuchFieldException e) { // Some versions of javac have this (and call close on it), some don't. I guess this one doesn't have it. } catch (Throwable t) { @@ -230,7 +306,10 @@ public class LombokProcessor extends AbstractProcessor { // Step 1: Take all CUs which aren't already in the map. Give them the first priority level. + String randomModuleName = null; + for (Element element : roundEnv.getRootElements()) { + if (randomModuleName == null) randomModuleName = getModuleNameFor(element); JCCompilationUnit unit = toUnit(element); if (unit == null) continue; if (roots.containsKey(unit)) continue; @@ -247,7 +326,7 @@ public class LombokProcessor extends AbstractProcessor { if (prioOfCu == null || prioOfCu != prio) continue; cusForThisRound.add(entry.getKey()); } - transformer.transform(prio, processingEnv.getContext(), cusForThisRound); + transformer.transform(prio, javacProcessingEnv.getContext(), cusForThisRound); } // Step 3: Push up all CUs to the next level. Set level to null if there is no next level. @@ -273,20 +352,22 @@ public class LombokProcessor extends AbstractProcessor { if (newLevels.isEmpty()) return false; newLevels.retainAll(priorityLevelsRequiringResolutionReset); - if (!newLevels.isEmpty()){ + if (!newLevels.isEmpty()) { // Force a new round to reset resolution. The next round will cause this method (process) to be called again. - forceNewRound((JavacFiler) processingEnv.getFiler()); + forceNewRound(randomModuleName, javacFiler); return false; } - // None of the new levels need resolution, so just keep going. + // None of the new levels need resolution, so just keep going. } } private int dummyCount = 0; - private void forceNewRound(JavacFiler filer) { + private void forceNewRound(String randomModuleName, JavacFiler filer) { if (!filer.newFiles()) { try { - JavaFileObject dummy = filer.createSourceFile("lombok.dummy.ForceNewRound" + (dummyCount++)); + String name = "lombok.dummy.ForceNewRound" + (dummyCount++); + if (randomModuleName != null) name = randomModuleName + "/" + name; + JavaFileObject dummy = filer.createSourceFile(name); Writer w = dummy.openWriter(); w.close(); } catch (Exception e) { @@ -297,6 +378,19 @@ public class LombokProcessor extends AbstractProcessor { } } + private String getModuleNameFor(Element element) { + while (element != null) { + if (element.getKind().name().equals("MODULE")) { + String n = element.getSimpleName().toString().trim(); + return n.isEmpty() ? null : n; + } + Element n = element.getEnclosingElement(); + if (n == element) return null; + element = n; + } + return null; + } + private JCCompilationUnit toUnit(Element element) { TreePath path = trees == null ? null : trees.getPath(element); if (path == null) return null; @@ -310,4 +404,57 @@ public class LombokProcessor extends AbstractProcessor { @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latest(); } + + /** + * This class casts the given processing environment to a JavacProcessingEnvironment. In case of + * gradle incremental compilation, the delegate ProcessingEnvironment of the gradle wrapper is returned. + */ + public JavacProcessingEnvironment getJavacProcessingEnvironment(Object procEnv) { + if (procEnv instanceof JavacProcessingEnvironment) { + return (JavacProcessingEnvironment) procEnv; + } + + // try to find a "delegate" field in the object, and use this to try to obtain a JavacProcessingEnvironment + for (Class<?> procEnvClass = procEnv.getClass(); procEnvClass != null; procEnvClass = procEnvClass.getSuperclass()) { + try { + return getJavacProcessingEnvironment(tryGetDelegateField(procEnvClass, procEnv)); + } catch (final Exception e) { + // delegate field was not found, try on superclass + } + } + + processingEnv.getMessager().printMessage(Kind.WARNING, + "Can't get the delegate of the gradle IncrementalProcessingEnvironment. Lombok won't work."); + return null; + } + + /** + * This class returns the given filer as a JavacFiler. In case the case that the filer is no + * JavacFiler (e.g. the Gradle IncrementalFiler), its "delegate" field is used to get the JavacFiler + * (directly or through a delegate field again) + */ + public JavacFiler getJavacFiler(Object filer) { + if (filer instanceof JavacFiler) { + return (JavacFiler) filer; + } + + // try to find a "delegate" field in the object, and use this to check for a JavacFiler + for (Class<?> filerClass = filer.getClass(); filerClass != null; filerClass = filerClass.getSuperclass()) { + try { + return getJavacFiler(tryGetDelegateField(filerClass, filer)); + } catch (final Exception e) { + // delegate field was not found, try on superclass + } + } + + processingEnv.getMessager().printMessage(Kind.WARNING, + "Can't get a JavacFiler from " + filer.getClass().getName() + ". Lombok won't work."); + return null; + } + + private Object tryGetDelegateField(Class<?> delegateClass, Object instance) throws Exception { + Field field = delegateClass.getDeclaredField("delegate"); + field.setAccessible(true); + return field.get(instance); + } } diff --git a/src/core/lombok/javac/handlers/HandleBuilder.java b/src/core/lombok/javac/handlers/HandleBuilder.java index b83e3d5f..d56d6ac2 100644 --- a/src/core/lombok/javac/handlers/HandleBuilder.java +++ b/src/core/lombok/javac/handlers/HandleBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2015 The Project Lombok Authors. + * Copyright (C) 2013-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 @@ -21,7 +21,6 @@ */ package lombok.javac.handlers; -import java.lang.annotation.Annotation; import java.util.ArrayList; import javax.lang.model.element.Modifier; @@ -55,10 +54,12 @@ import lombok.Builder; import lombok.Builder.ObtainVia; import lombok.ConfigurationKeys; import lombok.Singular; +import lombok.ToString; import lombok.core.AST.Kind; import lombok.core.AnnotationValues; import lombok.core.HandlerPriority; import lombok.core.handlers.HandlerUtil; +import lombok.core.handlers.InclusionExclusionUtils.Included; import lombok.experimental.NonFinal; import lombok.javac.Javac; import lombok.javac.JavacAnnotationHandler; @@ -75,6 +76,8 @@ import static lombok.javac.JavacTreeMaker.TypeTag.*; @ProviderFor(JavacAnnotationHandler.class) @HandlerPriority(-1024) //-2^10; to ensure we've picked up @FieldDefault's changes (-2048) but @Value hasn't removed itself yet (-512), so that we can error on presence of it on the builder classes. public class HandleBuilder extends JavacAnnotationHandler<Builder> { + private HandleConstructor handleConstructor = new HandleConstructor(); + private static final boolean toBoolean(Object expr, boolean defaultValue) { if (expr == null) return defaultValue; if (expr instanceof JCLiteral) return ((Integer) ((JCLiteral) expr).value) != 0; @@ -85,9 +88,12 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { JCExpression type; Name rawName; Name name; + Name nameOfDefaultProvider; + Name nameOfSetFlag; SingularData singularData; ObtainVia obtainVia; JavacNode obtainViaNode; + JavacNode originalFieldNode; java.util.List<JavacNode> createdFields = new ArrayList<JavacNode>(); } @@ -116,9 +122,7 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { if (!checkName("builderClassName", builderClassName, annotationNode)) return; } - @SuppressWarnings("deprecation") - Class<? extends Annotation> oldExperimentalBuilder = lombok.experimental.Builder.class; - deleteAnnotationIfNeccessary(annotationNode, Builder.class, oldExperimentalBuilder); + deleteAnnotationIfNeccessary(annotationNode, Builder.class, "lombok.experimental.Builder"); JavacNode parent = annotationNode.up(); @@ -137,25 +141,46 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { tdParent = parent; JCClassDecl td = (JCClassDecl) tdParent.get(); ListBuffer<JavacNode> allFields = new ListBuffer<JavacNode>(); - @SuppressWarnings("deprecation") - boolean valuePresent = (hasAnnotation(lombok.Value.class, parent) || hasAnnotation(lombok.experimental.Value.class, parent)); - for (JavacNode fieldNode : HandleConstructor.findAllFields(tdParent)) { + boolean valuePresent = (hasAnnotation(lombok.Value.class, parent) || hasAnnotation("lombok.experimental.Value", parent)); + for (JavacNode fieldNode : HandleConstructor.findAllFields(tdParent, true)) { JCVariableDecl fd = (JCVariableDecl) fieldNode.get(); - // final fields with an initializer cannot be written to, so they can't be 'builderized'. Unfortunately presence of @Value makes - // non-final fields final, but @Value's handler hasn't done this yet, so we have to do this math ourselves. - // Value will only skip making a field final if it has an explicit @NonFinal annotation, so we check for that. - if (fd.init != null && valuePresent && !hasAnnotation(NonFinal.class, fieldNode)) continue; + JavacNode isDefault = findAnnotation(Builder.Default.class, fieldNode, true); + boolean isFinal = (fd.mods.flags & Flags.FINAL) != 0 || (valuePresent && !hasAnnotation(NonFinal.class, fieldNode)); BuilderFieldData bfd = new BuilderFieldData(); bfd.rawName = fd.name; bfd.name = removePrefixFromField(fieldNode); bfd.type = fd.vartype; bfd.singularData = getSingularData(fieldNode); + bfd.originalFieldNode = fieldNode; + + if (bfd.singularData != null && isDefault != null) { + isDefault.addError("@Builder.Default and @Singular cannot be mixed."); + isDefault = null; + } + + if (fd.init == null && isDefault != null) { + isDefault.addWarning("@Builder.Default requires an initializing expression (' = something;')."); + isDefault = null; + } + + if (fd.init != null && isDefault == null) { + if (isFinal) continue; + fieldNode.addWarning("@Builder will ignore the initializing expression entirely. If you want the initializing expression to serve as default, add @Builder.Default. If it is not supposed to be settable during building, make the field final."); + } + + if (isDefault != null) { + bfd.nameOfDefaultProvider = parent.toName("$default$" + bfd.name); + bfd.nameOfSetFlag = parent.toName(bfd.name + "$set"); + JCMethodDecl md = generateDefaultProvider(bfd.nameOfDefaultProvider, fieldNode, td.typarams); + recursiveSetGeneratedBy(md, ast, annotationNode.getContext()); + if (md != null) injectMethod(tdParent, md); + } addObtainVia(bfd, fieldNode); builderFields.add(bfd); allFields.append(fieldNode); } - new HandleConstructor().generateConstructor(tdParent, AccessLevel.PACKAGE, List.<JCAnnotation>nil(), allFields.toList(), false, null, SkipIfConstructorExists.I_AM_BUILDER, null, annotationNode); + handleConstructor.generateConstructor(tdParent, AccessLevel.PACKAGE, List.<JCAnnotation>nil(), allFields.toList(), false, null, SkipIfConstructorExists.I_AM_BUILDER, annotationNode); returnType = namePlusTypeParamsToTypeReference(tdParent.getTreeMaker(), td.name, td.typarams); typeParams = td.typarams; @@ -187,7 +212,7 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { thrownExceptions = jmd.thrown; nameOfBuilderMethod = jmd.name; if (returnType instanceof JCTypeApply) { - returnType = ((JCTypeApply) returnType).clazz; + returnType = cloneType(tdParent.getTreeMaker(), returnType, ast, annotationNode.getContext()); } if (builderClassName.isEmpty()) { if (returnType instanceof JCFieldAccess) { @@ -207,7 +232,16 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { if (Character.isLowerCase(builderClassName.charAt(0))) { builderClassName = Character.toTitleCase(builderClassName.charAt(0)) + builderClassName.substring(1); } - } else { + } else if (returnType instanceof JCTypeApply) { + JCExpression clazz = ((JCTypeApply) returnType).clazz; + if (clazz instanceof JCFieldAccess) { + builderClassName = ((JCFieldAccess) clazz).name + "Builder"; + } else if (clazz instanceof JCIdent) { + builderClassName = ((JCIdent) clazz).name + "Builder"; + } + } + + if (builderClassName.isEmpty()) { // This shouldn't happen. System.err.println("Lombok bug ID#20140614-1651: javac HandleBuilder: return type to name conversion failed: " + returnType.getClass()); builderClassName = td.name.toString() + "Builder"; @@ -228,11 +262,14 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { tpOnRet = ((JCTypeApply) fullReturnType).arguments; } - if (returnType instanceof JCIdent) { - simpleName = ((JCIdent) returnType).name; + JCExpression namingType = returnType; + if (returnType instanceof JCTypeApply) namingType = ((JCTypeApply) returnType).clazz; + + if (namingType instanceof JCIdent) { + simpleName = ((JCIdent) namingType).name; pkg = null; - } else if (returnType instanceof JCFieldAccess) { - JCFieldAccess jcfa = (JCFieldAccess) returnType; + } else if (namingType instanceof JCFieldAccess) { + JCFieldAccess jcfa = (JCFieldAccess) namingType; simpleName = jcfa.name; pkg = unpack(jcfa.selected); if (pkg.startsWith("ERR:")) { @@ -241,7 +278,7 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { return; } } else { - annotationNode.addError("Expected a (parameterized) type here instead of a " + returnType.getClass().getName()); + annotationNode.addError("Expected a (parameterized) type here instead of a " + namingType.getClass().getName()); return; } @@ -270,8 +307,7 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { } if (pos == -1 || tpOnType.size() <= pos) { - annotationNode.addError("**" + returnType.getClass().toString()); -// annotationNode.addError("@Builder(toBuilder=true) requires that each type parameter on the static method is part of the typeargs of the return value. Type parameter " + tp.name + " is not part of the return type."); + annotationNode.addError("@Builder(toBuilder=true) requires that each type parameter on the static method is part of the typeargs of the return value. Type parameter " + tp.name + " is not part of the return type."); return; } typeArgsForToBuilder.add(tpOnType.get(pos).name); @@ -291,6 +327,7 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { bfd.rawName = raw.name; bfd.type = raw.vartype; bfd.singularData = getSingularData(param); + bfd.originalFieldNode = param; addObtainVia(bfd, param); builderFields.add(bfd); } @@ -298,7 +335,7 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { JavacNode builderType = findInnerClass(tdParent, builderClassName); if (builderType == null) { - builderType = makeBuilderClass(isStatic, tdParent, builderClassName, typeParams, ast); + builderType = makeBuilderClass(isStatic, annotationNode, tdParent, builderClassName, typeParams, ast); } else { JCClassDecl builderTypeDeclaration = (JCClassDecl) builderType.get(); if (isStatic && !builderTypeDeclaration.getModifiers().getFlags().contains(Modifier.STATIC)) { @@ -320,7 +357,6 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { } } } - } for (BuilderFieldData bfd : builderFields) { @@ -350,7 +386,7 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { } if (constructorExists(builderType) == MemberExistsResult.NOT_EXISTS) { - JCMethodDecl cd = HandleConstructor.createConstructor(AccessLevel.PACKAGE, List.<JCAnnotation>nil(), builderType, List.<JavacNode>nil(), false, null, annotationNode); + JCMethodDecl cd = HandleConstructor.createConstructor(AccessLevel.PACKAGE, List.<JCAnnotation>nil(), builderType, List.<JavacNode>nil(), false, annotationNode); if (cd != null) injectMethod(builderType, cd); } @@ -359,15 +395,18 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { } if (methodExists(buildMethodName, builderType, -1) == MemberExistsResult.NOT_EXISTS) { - JCMethodDecl md = generateBuildMethod(isStatic, buildMethodName, nameOfBuilderMethod, returnType, builderFields, builderType, thrownExceptions, ast, addCleaning); + JCMethodDecl md = generateBuildMethod(tdParent, isStatic, buildMethodName, nameOfBuilderMethod, returnType, builderFields, builderType, thrownExceptions, ast, addCleaning); if (md != null) injectMethod(builderType, md); } if (methodExists("toString", builderType, 0) == MemberExistsResult.NOT_EXISTS) { - java.util.List<JavacNode> fieldNodes = new ArrayList<JavacNode>(); + java.util.List<Included<JavacNode, ToString.Include>> fieldNodes = new ArrayList<Included<JavacNode, ToString.Include>>(); for (BuilderFieldData bfd : builderFields) { - fieldNodes.addAll(bfd.createdFields); + for (JavacNode f : bfd.createdFields) { + fieldNodes.add(new Included<JavacNode, ToString.Include>(f, null, true)); + } } + JCMethodDecl md = HandleToString.createToString(builderType, fieldNodes, true, false, FieldAccess.ALWAYS_FIELD, ast); if (md != null) injectMethod(builderType, md); } @@ -375,7 +414,7 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { if (addCleaning) injectMethod(builderType, generateCleanMethod(builderFields, builderType, ast)); if (methodExists(builderMethodName, tdParent, -1) == MemberExistsResult.NOT_EXISTS) { - JCMethodDecl md = generateBuilderMethod(isStatic, builderMethodName, builderClassName, tdParent, typeParams); + JCMethodDecl md = generateBuilderMethod(isStatic, builderMethodName, builderClassName, annotationNode, tdParent, typeParams); recursiveSetGeneratedBy(md, ast, annotationNode.getContext()); if (md != null) injectMethod(tdParent, md); } @@ -479,9 +518,9 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { } } - statements.append(maker.Exec(maker.Assign(maker.Select(maker.Ident(type.toName("this")), type.toName("$lombokUnclean")), maker.Literal(CTC_BOOLEAN, false)))); + statements.append(maker.Exec(maker.Assign(maker.Select(maker.Ident(type.toName("this")), type.toName("$lombokUnclean")), maker.Literal(CTC_BOOLEAN, 0)))); JCBlock body = maker.Block(0, statements.toList()); - return maker.MethodDef(maker.Modifiers(Flags.PUBLIC), type.toName("$lombokClean"), maker.Type(Javac.createVoidType(maker, CTC_VOID)), List.<JCTypeParameter>nil(), List.<JCVariableDecl>nil(), List.<JCExpression>nil(), body, null); + return maker.MethodDef(maker.Modifiers(Flags.PUBLIC), type.toName("$lombokClean"), maker.Type(Javac.createVoidType(type.getSymbolTable(), CTC_VOID)), List.<JCTypeParameter>nil(), List.<JCVariableDecl>nil(), List.<JCExpression>nil(), body, null); /* * if (shouldReturnThis) { methodType = cloneSelfType(field); @@ -496,7 +535,7 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { */ } - private JCMethodDecl generateBuildMethod(boolean isStatic, String buildName, Name builderName, JCExpression returnType, java.util.List<BuilderFieldData> builderFields, JavacNode type, List<JCExpression> thrownExceptions, JCTree source, boolean addCleaning) { + private JCMethodDecl generateBuildMethod(JavacNode tdParent, boolean isStatic, String buildName, Name builderName, JCExpression returnType, java.util.List<BuilderFieldData> builderFields, JavacNode type, List<JCExpression> thrownExceptions, JCTree source, boolean addCleaning) { JavacTreeMaker maker = type.getTreeMaker(); JCExpression call; @@ -511,31 +550,33 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { for (BuilderFieldData bfd : builderFields) { if (bfd.singularData != null && bfd.singularData.getSingularizer() != null) { - bfd.singularData.getSingularizer().appendBuildCode(bfd.singularData, type, source, statements, bfd.name); + bfd.singularData.getSingularizer().appendBuildCode(bfd.singularData, type, source, statements, bfd.name, "this"); } } ListBuffer<JCExpression> args = new ListBuffer<JCExpression>(); for (BuilderFieldData bfd : builderFields) { + if (bfd.nameOfSetFlag != null) { + statements.append(maker.VarDef(maker.Modifiers(0L), bfd.name, cloneType(maker, bfd.type, source, tdParent.getContext()), maker.Select(maker.Ident(type.toName("this")), bfd.name))); + statements.append(maker.If(maker.Unary(CTC_NOT, maker.Ident(bfd.nameOfSetFlag)), maker.Exec(maker.Assign(maker.Ident(bfd.name),maker.Apply(typeParameterNames(maker, ((JCClassDecl) tdParent.get()).typarams), maker.Select(maker.Ident(((JCClassDecl) tdParent.get()).name), bfd.nameOfDefaultProvider), List.<JCExpression>nil()))), null)); + } args.append(maker.Ident(bfd.name)); } if (addCleaning) { - statements.append(maker.Exec(maker.Assign(maker.Select(maker.Ident(type.toName("this")), type.toName("$lombokUnclean")), maker.Literal(CTC_BOOLEAN, true)))); + statements.append(maker.Exec(maker.Assign(maker.Select(maker.Ident(type.toName("this")), type.toName("$lombokUnclean")), maker.Literal(CTC_BOOLEAN, 1)))); } if (builderName == null) { call = maker.NewClass(null, List.<JCExpression>nil(), returnType, args.toList(), null); statements.append(maker.Return(call)); } else { - ListBuffer<JCExpression> typeParams = new ListBuffer<JCExpression>(); for (JCTypeParameter tp : ((JCClassDecl) type.get()).typarams) { typeParams.append(maker.Ident(tp.name)); } JCExpression callee = maker.Ident(((JCClassDecl) type.up().get()).name); - if (!isStatic) - callee = maker.Select(callee, type.up().toName("this")); + if (!isStatic) callee = maker.Select(callee, type.up().toName("this")); JCExpression fn = maker.Select(callee, builderName); call = maker.Apply(typeParams.toList(), fn, args.toList()); if (returnType instanceof JCPrimitiveTypeTree && CTC_VOID.equals(typeTag(returnType))) { @@ -550,7 +591,19 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { return maker.MethodDef(maker.Modifiers(Flags.PUBLIC), type.toName(buildName), returnType, List.<JCTypeParameter>nil(), List.<JCVariableDecl>nil(), thrownExceptions, body, null); } - public JCMethodDecl generateBuilderMethod(boolean isStatic, String builderMethodName, String builderClassName, JavacNode type, List<JCTypeParameter> typeParams) { + public JCMethodDecl generateDefaultProvider(Name methodName, JavacNode fieldNode, List<JCTypeParameter> params) { + JavacTreeMaker maker = fieldNode.getTreeMaker(); + JCVariableDecl field = (JCVariableDecl) fieldNode.get(); + + JCStatement statement = maker.Return(field.init); + field.init = null; + + JCBlock body = maker.Block(0, List.<JCStatement>of(statement)); + int modifiers = Flags.PRIVATE | Flags.STATIC; + return maker.MethodDef(maker.Modifiers(modifiers), methodName, cloneType(maker, field.vartype, field, fieldNode.getContext()), copyTypeParams(fieldNode, params), List.<JCVariableDecl>nil(), List.<JCExpression>nil(), body, null); + } + + public JCMethodDecl generateBuilderMethod(boolean isStatic, String builderMethodName, String builderClassName, JavacNode source, JavacNode type, List<JCTypeParameter> typeParams) { JavacTreeMaker maker = type.getTreeMaker(); ListBuffer<JCExpression> typeArgs = new ListBuffer<JCExpression>(); @@ -564,7 +617,7 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { JCBlock body = maker.Block(0, List.<JCStatement>of(statement)); int modifiers = Flags.PUBLIC; if (isStatic) modifiers |= Flags.STATIC; - return maker.MethodDef(maker.Modifiers(modifiers), type.toName(builderMethodName), namePlusTypeParamsToTypeReference(maker, type.toName(builderClassName), typeParams), copyTypeParams(maker, typeParams), List.<JCVariableDecl>nil(), List.<JCExpression>nil(), body, null); + return maker.MethodDef(maker.Modifiers(modifiers), type.toName(builderMethodName), namePlusTypeParamsToTypeReference(maker, type.toName(builderClassName), typeParams), copyTypeParams(source, typeParams), List.<JCVariableDecl>nil(), List.<JCExpression>nil(), body, null); } public void generateBuilderFields(JavacNode builderType, java.util.List<BuilderFieldData> builderFields, JCTree source) { @@ -574,48 +627,58 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { if (child.getKind() == Kind.FIELD) existing.add(child); } - top: for (int i = len - 1; i >= 0; i--) { BuilderFieldData bfd = builderFields.get(i); if (bfd.singularData != null && bfd.singularData.getSingularizer() != null) { bfd.createdFields.addAll(bfd.singularData.getSingularizer().generateFields(bfd.singularData, builderType, source)); } else { + JavacNode field = null, setFlag = null; for (JavacNode exists : existing) { Name n = ((JCVariableDecl) exists.get()).name; - if (n.equals(bfd.name)) { - bfd.createdFields.add(exists); - continue top; - } + if (n.equals(bfd.name)) field = exists; + if (n.equals(bfd.nameOfSetFlag)) setFlag = exists; } JavacTreeMaker maker = builderType.getTreeMaker(); - JCModifiers mods = maker.Modifiers(Flags.PRIVATE); - JCVariableDecl newField = maker.VarDef(mods, bfd.name, cloneType(maker, bfd.type, source, builderType.getContext()), null); - bfd.createdFields.add(injectFieldAndMarkGenerated(builderType, newField)); + if (field == null) { + JCModifiers mods = maker.Modifiers(Flags.PRIVATE); + JCVariableDecl newField = maker.VarDef(mods, bfd.name, cloneType(maker, bfd.type, source, builderType.getContext()), null); + field = injectFieldAndMarkGenerated(builderType, newField); + } + if (setFlag == null && bfd.nameOfSetFlag != null) { + JCModifiers mods = maker.Modifiers(Flags.PRIVATE); + JCVariableDecl newField = maker.VarDef(mods, bfd.nameOfSetFlag, maker.TypeIdent(CTC_BOOLEAN), null); + injectFieldAndMarkGenerated(builderType, newField); + } + bfd.createdFields.add(field); } } } public void makeSetterMethodsForBuilder(JavacNode builderType, BuilderFieldData fieldNode, JavacNode source, boolean fluent, boolean chain) { + boolean deprecate = isFieldDeprecated(fieldNode.originalFieldNode); if (fieldNode.singularData == null || fieldNode.singularData.getSingularizer() == null) { - makeSimpleSetterMethodForBuilder(builderType, fieldNode.createdFields.get(0), source, fluent, chain); + makeSimpleSetterMethodForBuilder(builderType, deprecate, fieldNode.createdFields.get(0), fieldNode.nameOfSetFlag, source, fluent, chain); } else { - fieldNode.singularData.getSingularizer().generateMethods(fieldNode.singularData, builderType, source.get(), fluent, chain); + fieldNode.singularData.getSingularizer().generateMethods(fieldNode.singularData, deprecate, builderType, source.get(), fluent, chain); } } - private void makeSimpleSetterMethodForBuilder(JavacNode builderType, JavacNode fieldNode, JavacNode source, boolean fluent, boolean chain) { + private void makeSimpleSetterMethodForBuilder(JavacNode builderType, boolean deprecate, JavacNode fieldNode, Name nameOfSetFlag, JavacNode source, boolean fluent, boolean chain) { Name fieldName = ((JCVariableDecl) fieldNode.get()).name; for (JavacNode child : builderType.down()) { if (child.getKind() != Kind.METHOD) continue; - Name existingName = ((JCMethodDecl) child.get()).name; - if (existingName.equals(fieldName)) return; + JCMethodDecl methodDecl = (JCMethodDecl) child.get(); + Name existingName = methodDecl.name; + if (existingName.equals(fieldName) && !isTolerate(fieldNode, methodDecl)) return; } String setterName = fluent ? fieldNode.getName() : HandlerUtil.buildAccessorName("set", fieldNode.getName()); JavacTreeMaker maker = fieldNode.getTreeMaker(); - JCMethodDecl newMethod = HandleSetter.createSetter(Flags.PUBLIC, fieldNode, maker, setterName, chain, source, List.<JCAnnotation>nil(), List.<JCAnnotation>nil()); + + JCMethodDecl newMethod = HandleSetter.createSetter(Flags.PUBLIC, deprecate, fieldNode, maker, setterName, nameOfSetFlag, chain, source, List.<JCAnnotation>nil(), List.<JCAnnotation>nil()); + injectMethod(builderType, newMethod); } @@ -628,12 +691,12 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { return null; } - public JavacNode makeBuilderClass(boolean isStatic, JavacNode tdParent, String builderClassName, List<JCTypeParameter> typeParams, JCAnnotation ast) { + public JavacNode makeBuilderClass(boolean isStatic, JavacNode source, JavacNode tdParent, String builderClassName, List<JCTypeParameter> typeParams, JCAnnotation ast) { JavacTreeMaker maker = tdParent.getTreeMaker(); int modifiers = Flags.PUBLIC; if (isStatic) modifiers |= Flags.STATIC; JCModifiers mods = maker.Modifiers(modifiers); - JCClassDecl builder = maker.ClassDef(mods, tdParent.toName(builderClassName), copyTypeParams(maker, typeParams), null, List.<JCExpression>nil(), List.<JCTree>nil()); + JCClassDecl builder = maker.ClassDef(mods, tdParent.toName(builderClassName), copyTypeParams(source, typeParams), null, List.<JCExpression>nil(), List.<JCTree>nil()); return injectType(tdParent, builder); } diff --git a/src/core/lombok/javac/handlers/HandleBuilderDefault.java b/src/core/lombok/javac/handlers/HandleBuilderDefault.java new file mode 100644 index 00000000..af45a620 --- /dev/null +++ b/src/core/lombok/javac/handlers/HandleBuilderDefault.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2017-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.javac.handlers; + +import static lombok.javac.handlers.JavacHandlerUtil.*; + +import org.mangosdk.spi.ProviderFor; + +import com.sun.tools.javac.tree.JCTree.JCAnnotation; + +import lombok.Builder; +import lombok.core.AST.Kind; +import lombok.core.AnnotationValues; +import lombok.core.HandlerPriority; +import lombok.experimental.SuperBuilder; +import lombok.javac.JavacAnnotationHandler; +import lombok.javac.JavacNode; + +@ProviderFor(JavacAnnotationHandler.class) +@HandlerPriority(-1025) //HandleBuilder's level, minus one. +public class HandleBuilderDefault extends JavacAnnotationHandler<Builder.Default> { + @Override public void handle(AnnotationValues<Builder.Default> annotation, JCAnnotation ast, JavacNode annotationNode) { + JavacNode annotatedField = annotationNode.up(); + if (annotatedField.getKind() != Kind.FIELD) return; + JavacNode classWithAnnotatedField = annotatedField.up(); + if (!hasAnnotation(Builder.class, classWithAnnotatedField) && !hasAnnotation("lombok.experimental.Builder", classWithAnnotatedField) + && !hasAnnotation(SuperBuilder.class, classWithAnnotatedField)) { + annotationNode.addWarning("@Builder.Default requires @Builder or @SuperBuilder on the class for it to mean anything."); + deleteAnnotationIfNeccessary(annotationNode, Builder.Default.class); + } + } +} diff --git a/src/core/lombok/javac/handlers/HandleConstructor.java b/src/core/lombok/javac/handlers/HandleConstructor.java index 4a4ec49c..32eb43b2 100644 --- a/src/core/lombok/javac/handlers/HandleConstructor.java +++ b/src/core/lombok/javac/handlers/HandleConstructor.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2014 The Project Lombok Authors. + * Copyright (C) 2010-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 @@ -22,25 +22,14 @@ package lombok.javac.handlers; import static lombok.core.handlers.HandlerUtil.*; -import static lombok.javac.handlers.JavacHandlerUtil.*; import static lombok.javac.Javac.*; - -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.ConfigurationKeys; -import lombok.NoArgsConstructor; -import lombok.RequiredArgsConstructor; -import lombok.core.AnnotationValues; -import lombok.core.AST.Kind; -import lombok.delombok.LombokOptionsFactory; -import lombok.javac.JavacAnnotationHandler; -import lombok.javac.JavacNode; -import lombok.javac.JavacTreeMaker; +import static lombok.javac.handlers.JavacHandlerUtil.*; import org.mangosdk.spi.ProviderFor; import com.sun.tools.javac.code.Flags; +import com.sun.tools.javac.code.Symbol.ClassSymbol; +import com.sun.tools.javac.code.Type; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.JCAnnotation; import com.sun.tools.javac.tree.JCTree.JCBlock; @@ -58,9 +47,26 @@ import com.sun.tools.javac.util.List; import com.sun.tools.javac.util.ListBuffer; import com.sun.tools.javac.util.Name; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.ConfigurationKeys; +import lombok.NoArgsConstructor; +import lombok.RequiredArgsConstructor; +import lombok.core.AST.Kind; +import lombok.core.AnnotationValues; +import lombok.delombok.LombokOptionsFactory; +import lombok.javac.Javac; +import lombok.javac.JavacAnnotationHandler; +import lombok.javac.JavacNode; +import lombok.javac.JavacTreeMaker; +import lombok.javac.handlers.JavacHandlerUtil.MemberExistsResult; + public class HandleConstructor { @ProviderFor(JavacAnnotationHandler.class) public static class HandleNoArgsConstructor extends JavacAnnotationHandler<NoArgsConstructor> { + private HandleConstructor handleConstructor = new HandleConstructor(); + @Override public void handle(AnnotationValues<NoArgsConstructor> annotation, JCAnnotation ast, JavacNode annotationNode) { handleFlagUsage(annotationNode, ConfigurationKeys.NO_ARGS_CONSTRUCTOR_FLAG_USAGE, "@NoArgsConstructor", ConfigurationKeys.ANY_CONSTRUCTOR_FLAG_USAGE, "any @xArgsConstructor"); @@ -68,19 +74,21 @@ public class HandleConstructor { deleteImportFromCompilationUnit(annotationNode, "lombok.AccessLevel"); JavacNode typeNode = annotationNode.up(); if (!checkLegality(typeNode, annotationNode, NoArgsConstructor.class.getSimpleName())) return; - List<JCAnnotation> onConstructor = unboxAndRemoveAnnotationParameter(ast, "onConstructor", "@NoArgsConstructor(onConstructor=", annotationNode); + List<JCAnnotation> onConstructor = unboxAndRemoveAnnotationParameter(ast, "onConstructor", "@NoArgsConstructor(onConstructor", annotationNode); NoArgsConstructor ann = annotation.getInstance(); AccessLevel level = ann.access(); if (level == AccessLevel.NONE) return; String staticName = ann.staticName(); boolean force = ann.force(); List<JavacNode> fields = force ? findFinalFields(typeNode) : List.<JavacNode>nil(); - new HandleConstructor().generateConstructor(typeNode, level, onConstructor, fields, force, staticName, SkipIfConstructorExists.NO, null, annotationNode); + handleConstructor.generateConstructor(typeNode, level, onConstructor, fields, force, staticName, SkipIfConstructorExists.NO, annotationNode); } } @ProviderFor(JavacAnnotationHandler.class) public static class HandleRequiredArgsConstructor extends JavacAnnotationHandler<RequiredArgsConstructor> { + private HandleConstructor handleConstructor = new HandleConstructor(); + @Override public void handle(AnnotationValues<RequiredArgsConstructor> annotation, JCAnnotation ast, JavacNode annotationNode) { handleFlagUsage(annotationNode, ConfigurationKeys.REQUIRED_ARGS_CONSTRUCTOR_FLAG_USAGE, "@RequiredArgsConstructor", ConfigurationKeys.ANY_CONSTRUCTOR_FLAG_USAGE, "any @xArgsConstructor"); @@ -88,19 +96,16 @@ public class HandleConstructor { deleteImportFromCompilationUnit(annotationNode, "lombok.AccessLevel"); JavacNode typeNode = annotationNode.up(); if (!checkLegality(typeNode, annotationNode, RequiredArgsConstructor.class.getSimpleName())) return; - List<JCAnnotation> onConstructor = unboxAndRemoveAnnotationParameter(ast, "onConstructor", "@RequiredArgsConstructor(onConstructor=", annotationNode); + List<JCAnnotation> onConstructor = unboxAndRemoveAnnotationParameter(ast, "onConstructor", "@RequiredArgsConstructor(onConstructor", annotationNode); RequiredArgsConstructor ann = annotation.getInstance(); AccessLevel level = ann.access(); if (level == AccessLevel.NONE) return; String staticName = ann.staticName(); - Boolean suppressConstructorProperties = null; if (annotation.isExplicit("suppressConstructorProperties")) { - @SuppressWarnings("deprecation") - boolean suppress = ann.suppressConstructorProperties(); - suppressConstructorProperties = suppress; + annotationNode.addError("This deprecated feature is no longer supported. Remove it; you can create a lombok.config file with 'lombok.anyConstructor.suppressConstructorProperties = true'."); } - new HandleConstructor().generateConstructor(typeNode, level, onConstructor, findRequiredFields(typeNode), false, staticName, SkipIfConstructorExists.NO, suppressConstructorProperties, annotationNode); + handleConstructor.generateConstructor(typeNode, level, onConstructor, findRequiredFields(typeNode), false, staticName, SkipIfConstructorExists.NO, annotationNode); } } @@ -131,6 +136,8 @@ public class HandleConstructor { @ProviderFor(JavacAnnotationHandler.class) public static class HandleAllArgsConstructor extends JavacAnnotationHandler<AllArgsConstructor> { + private HandleConstructor handleConstructor = new HandleConstructor(); + @Override public void handle(AnnotationValues<AllArgsConstructor> annotation, JCAnnotation ast, JavacNode annotationNode) { handleFlagUsage(annotationNode, ConfigurationKeys.ALL_ARGS_CONSTRUCTOR_FLAG_USAGE, "@AllArgsConstructor", ConfigurationKeys.ANY_CONSTRUCTOR_FLAG_USAGE, "any @xArgsConstructor"); @@ -138,22 +145,23 @@ public class HandleConstructor { deleteImportFromCompilationUnit(annotationNode, "lombok.AccessLevel"); JavacNode typeNode = annotationNode.up(); if (!checkLegality(typeNode, annotationNode, AllArgsConstructor.class.getSimpleName())) return; - List<JCAnnotation> onConstructor = unboxAndRemoveAnnotationParameter(ast, "onConstructor", "@AllArgsConstructor(onConstructor=", annotationNode); + List<JCAnnotation> onConstructor = unboxAndRemoveAnnotationParameter(ast, "onConstructor", "@AllArgsConstructor(onConstructor", annotationNode); AllArgsConstructor ann = annotation.getInstance(); AccessLevel level = ann.access(); if (level == AccessLevel.NONE) return; String staticName = ann.staticName(); - Boolean suppressConstructorProperties = null; if (annotation.isExplicit("suppressConstructorProperties")) { - @SuppressWarnings("deprecation") - boolean suppress = ann.suppressConstructorProperties(); - suppressConstructorProperties = suppress; + annotationNode.addError("This deprecated feature is no longer supported. Remove it; you can create a lombok.config file with 'lombok.anyConstructor.suppressConstructorProperties = true'."); } - new HandleConstructor().generateConstructor(typeNode, level, onConstructor, findAllFields(typeNode), false, staticName, SkipIfConstructorExists.NO, suppressConstructorProperties, annotationNode); + handleConstructor.generateConstructor(typeNode, level, onConstructor, findAllFields(typeNode), false, staticName, SkipIfConstructorExists.NO, annotationNode); } } public static List<JavacNode> findAllFields(JavacNode typeNode) { + return findAllFields(typeNode, false); + } + + public static List<JavacNode> findAllFields(JavacNode typeNode, boolean evenFinalInitialized) { ListBuffer<JavacNode> fields = new ListBuffer<JavacNode>(); for (JavacNode child : typeNode.down()) { if (child.getKind() != Kind.FIELD) continue; @@ -165,7 +173,7 @@ public class HandleConstructor { if ((fieldFlags & Flags.STATIC) != 0) continue; //Skip initialized final fields boolean isFinal = (fieldFlags & Flags.FINAL) != 0; - if (!isFinal || fieldDecl.init == null) fields.append(child); + if (evenFinalInitialized || !isFinal || fieldDecl.init == null) fields.append(child); } return fields.toList(); } @@ -184,19 +192,33 @@ public class HandleConstructor { return true; } - public void generateRequiredArgsConstructor(JavacNode typeNode, AccessLevel level, String staticName, SkipIfConstructorExists skipIfConstructorExists, JavacNode source) { - generateConstructor(typeNode, level, List.<JCAnnotation>nil(), findRequiredFields(typeNode), false, staticName, skipIfConstructorExists, null, source); - } - public enum SkipIfConstructorExists { YES, NO, I_AM_BUILDER; } + public void generateExtraNoArgsConstructor(JavacNode typeNode, JavacNode source) { + if (!isDirectDescendantOfObject(typeNode)) return; + + Boolean v = typeNode.getAst().readConfiguration(ConfigurationKeys.NO_ARGS_CONSTRUCTOR_EXTRA_PRIVATE); + if (v == null || !v) return; + + List<JavacNode> fields = findFinalFields(typeNode); + generate(typeNode, AccessLevel.PRIVATE, List.<JCAnnotation>nil(), fields, true, null, SkipIfConstructorExists.NO, source, true); + } + + public void generateRequiredArgsConstructor(JavacNode typeNode, AccessLevel level, String staticName, SkipIfConstructorExists skipIfConstructorExists, JavacNode source) { + generateConstructor(typeNode, level, List.<JCAnnotation>nil(), findRequiredFields(typeNode), false, staticName, skipIfConstructorExists, source); + } + public void generateAllArgsConstructor(JavacNode typeNode, AccessLevel level, String staticName, SkipIfConstructorExists skipIfConstructorExists, JavacNode source) { - generateConstructor(typeNode, level, List.<JCAnnotation>nil(), findAllFields(typeNode), false, staticName, skipIfConstructorExists, null, source); + generateConstructor(typeNode, level, List.<JCAnnotation>nil(), findAllFields(typeNode), false, staticName, skipIfConstructorExists, source); } - public void generateConstructor(JavacNode typeNode, AccessLevel level, List<JCAnnotation> onConstructor, List<JavacNode> fields, boolean allToDefault, String staticName, SkipIfConstructorExists skipIfConstructorExists, Boolean suppressConstructorProperties, JavacNode source) { + public void generateConstructor(JavacNode typeNode, AccessLevel level, List<JCAnnotation> onConstructor, List<JavacNode> fields, boolean allToDefault, String staticName, SkipIfConstructorExists skipIfConstructorExists, JavacNode source) { + generate(typeNode, level, onConstructor, fields, allToDefault, staticName, skipIfConstructorExists, source, false); + } + + private void generate(JavacNode typeNode, AccessLevel level, List<JCAnnotation> onConstructor, List<JavacNode> fields, boolean allToDefault, String staticName, SkipIfConstructorExists skipIfConstructorExists, JavacNode source, boolean noArgs) { boolean staticConstrRequired = staticName != null && !staticName.equals(""); if (skipIfConstructorExists != SkipIfConstructorExists.NO && constructorExists(typeNode) != MemberExistsResult.NOT_EXISTS) return; @@ -225,14 +247,49 @@ public class HandleConstructor { } } - JCMethodDecl constr = createConstructor(staticConstrRequired ? AccessLevel.PRIVATE : level, onConstructor, typeNode, fields, allToDefault, suppressConstructorProperties, source); - injectMethod(typeNode, constr); + if (noArgs && noArgsConstructorExists(typeNode)) return; + + JCMethodDecl constr = createConstructor(staticConstrRequired ? AccessLevel.PRIVATE : level, onConstructor, typeNode, fields, allToDefault, source); + ListBuffer<Type> argTypes = new ListBuffer<Type>(); + for (JavacNode fieldNode : fields) { + Type mirror = getMirrorForFieldType(fieldNode); + if (mirror == null) { + argTypes = null; + break; + } + argTypes.append(mirror); + } + List<Type> argTypes_ = argTypes == null ? null : argTypes.toList(); + injectMethod(typeNode, constr, argTypes_, Javac.createVoidType(typeNode.getSymbolTable(), CTC_VOID)); if (staticConstrRequired) { + ClassSymbol sym = ((JCClassDecl) typeNode.get()).sym; + Type returnType = sym == null ? null : sym.type; JCMethodDecl staticConstr = createStaticConstructor(staticName, level, typeNode, allToDefault ? List.<JavacNode>nil() : fields, source.get()); - injectMethod(typeNode, staticConstr); + injectMethod(typeNode, staticConstr, argTypes_, returnType); } } + private static boolean noArgsConstructorExists(JavacNode node) { + node = upToTypeNode(node); + + if (node != null && node.get() instanceof JCClassDecl) { + for (JCTree def : ((JCClassDecl) node.get()).defs) { + if (def instanceof JCMethodDecl) { + JCMethodDecl md = (JCMethodDecl) def; + if (md.name.contentEquals("<init>") && md.params.size() == 0) return true; + } + } + } + + for (JavacNode child : node.down()) { + if (annotationTypeMatches(NoArgsConstructor.class, child)) return true; + if (annotationTypeMatches(RequiredArgsConstructor.class, child) && findRequiredFields(node).isEmpty()) return true; + if (annotationTypeMatches(AllArgsConstructor.class, child) && findAllFields(node).isEmpty()) return true; + } + + return false; + } + public static void addConstructorProperties(JCModifiers mods, JavacNode node, List<JavacNode> fields) { if (fields.isEmpty()) return; JavacTreeMaker maker = node.getTreeMaker(); @@ -247,18 +304,20 @@ public class HandleConstructor { mods.annotations = mods.annotations.append(annotation); } - public static JCMethodDecl createConstructor(AccessLevel level, List<JCAnnotation> onConstructor, JavacNode typeNode, List<JavacNode> fields, boolean allToDefault, Boolean suppressConstructorProperties, JavacNode source) { + @SuppressWarnings("deprecation") public static JCMethodDecl createConstructor(AccessLevel level, List<JCAnnotation> onConstructor, JavacNode typeNode, List<JavacNode> fields, boolean allToDefault, JavacNode source) { JavacTreeMaker maker = typeNode.getTreeMaker(); boolean isEnum = (((JCClassDecl) typeNode.get()).mods.flags & Flags.ENUM) != 0; if (isEnum) level = AccessLevel.PRIVATE; - if (suppressConstructorProperties == null) { - if (fields.isEmpty()) { - suppressConstructorProperties = false; - } else { - suppressConstructorProperties = Boolean.TRUE.equals(typeNode.getAst().readConfiguration(ConfigurationKeys.ANY_CONSTRUCTOR_SUPPRESS_CONSTRUCTOR_PROPERTIES)); - } + boolean addConstructorProperties; + + if (fields.isEmpty()) { + addConstructorProperties = false; + } else { + Boolean v = typeNode.getAst().readConfiguration(ConfigurationKeys.ANY_CONSTRUCTOR_ADD_CONSTRUCTOR_PROPERTIES); + addConstructorProperties = v != null ? v.booleanValue() : + Boolean.FALSE.equals(typeNode.getAst().readConfiguration(ConfigurationKeys.ANY_CONSTRUCTOR_SUPPRESS_CONSTRUCTOR_PROPERTIES)); } ListBuffer<JCStatement> nullChecks = new ListBuffer<JCStatement>(); @@ -276,7 +335,7 @@ public class HandleConstructor { JCVariableDecl param = maker.VarDef(maker.Modifiers(flags, nonNulls.appendList(nullables)), fieldName, field.vartype, null); params.append(param); if (!nonNulls.isEmpty()) { - JCStatement nullCheck = generateNullCheck(maker, fieldNode, source); + JCStatement nullCheck = generateNullCheck(maker, fieldNode, param, source); if (nullCheck != null) nullChecks.append(nullCheck); } } @@ -286,7 +345,7 @@ public class HandleConstructor { } JCModifiers mods = maker.Modifiers(toJavacModifier(level), List.<JCAnnotation>nil()); - if (!allToDefault && !suppressConstructorProperties && level != AccessLevel.PRIVATE && level != AccessLevel.PACKAGE && !isLocalType(typeNode) && LombokOptionsFactory.getDelombokOptions(typeNode.getContext()).getFormatPreferences().generateConstructorProperties()) { + if (!allToDefault && addConstructorProperties && !isLocalType(typeNode) && LombokOptionsFactory.getDelombokOptions(typeNode.getContext()).getFormatPreferences().generateConstructorProperties()) { addConstructorProperties(mods, typeNode, fields); } if (onConstructor != null) mods.annotations = mods.annotations.appendList(copyAnnotations(onConstructor)); diff --git a/src/core/lombok/javac/handlers/HandleData.java b/src/core/lombok/javac/handlers/HandleData.java index 9ecf8754..15c9c9e7 100644 --- a/src/core/lombok/javac/handlers/HandleData.java +++ b/src/core/lombok/javac/handlers/HandleData.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 @@ -34,12 +34,19 @@ import lombok.javac.handlers.HandleConstructor.SkipIfConstructorExists; import org.mangosdk.spi.ProviderFor; import com.sun.tools.javac.tree.JCTree.JCAnnotation; +import com.sun.tools.javac.util.List; /** * Handles the {@code lombok.Data} annotation for javac. */ @ProviderFor(JavacAnnotationHandler.class) public class HandleData extends JavacAnnotationHandler<Data> { + private HandleConstructor handleConstructor = new HandleConstructor(); + private HandleGetter handleGetter = new HandleGetter(); + private HandleSetter handleSetter = new HandleSetter(); + private HandleEqualsAndHashCode handleEqualsAndHashCode = new HandleEqualsAndHashCode(); + private HandleToString handleToString = new HandleToString(); + @Override public void handle(AnnotationValues<Data> annotation, JCAnnotation ast, JavacNode annotationNode) { handleFlagUsage(annotationNode, ConfigurationKeys.DATA_FLAG_USAGE, "@Data"); @@ -55,10 +62,11 @@ public class HandleData extends JavacAnnotationHandler<Data> { String staticConstructorName = annotation.getInstance().staticConstructor(); // TODO move this to the end OR move it to the top in eclipse. - new HandleConstructor().generateRequiredArgsConstructor(typeNode, AccessLevel.PUBLIC, staticConstructorName, SkipIfConstructorExists.YES, annotationNode); - new HandleGetter().generateGetterForType(typeNode, annotationNode, AccessLevel.PUBLIC, true); - new HandleSetter().generateSetterForType(typeNode, annotationNode, AccessLevel.PUBLIC, true); - new HandleEqualsAndHashCode().generateEqualsAndHashCodeForType(typeNode, annotationNode); - new HandleToString().generateToStringForType(typeNode, annotationNode); + handleConstructor.generateRequiredArgsConstructor(typeNode, AccessLevel.PUBLIC, staticConstructorName, SkipIfConstructorExists.YES, annotationNode); + handleConstructor.generateExtraNoArgsConstructor(typeNode, annotationNode); + handleGetter.generateGetterForType(typeNode, annotationNode, AccessLevel.PUBLIC, true, List.<JCAnnotation>nil()); + handleSetter.generateSetterForType(typeNode, annotationNode, AccessLevel.PUBLIC, true, List.<JCAnnotation>nil(), List.<JCAnnotation>nil()); + handleEqualsAndHashCode.generateEqualsAndHashCodeForType(typeNode, annotationNode); + handleToString.generateToStringForType(typeNode, annotationNode); } } diff --git a/src/core/lombok/javac/handlers/HandleDelegate.java b/src/core/lombok/javac/handlers/HandleDelegate.java index 680e7745..49bef769 100644 --- a/src/core/lombok/javac/handlers/HandleDelegate.java +++ b/src/core/lombok/javac/handlers/HandleDelegate.java @@ -312,7 +312,7 @@ public class HandleDelegate extends JavacAnnotationHandler<Delegate> { for (TypeMirror param : sig.type.getTypeVariables()) { Name name = ((TypeVar) param).tsym.name; - ListBuffer<JCExpression> bounds = types.getBounds((TypeVar) param).isEmpty() ? null : new ListBuffer<JCExpression>(); + ListBuffer<JCExpression> bounds = new ListBuffer<JCExpression>(); for (Type type : types.getBounds((TypeVar) param)) { bounds.append(JavacResolution.typeToJCTree(type, annotation.getAst(), true)); } diff --git a/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java b/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java index 2265bf36..aa0fe633 100644 --- a/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java +++ b/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.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 @@ -21,7 +21,7 @@ */ package lombok.javac.handlers; -import static lombok.core.handlers.HandlerUtil.*; +import static lombok.core.handlers.HandlerUtil.handleFlagUsage; import static lombok.javac.Javac.*; import static lombok.javac.handlers.JavacHandlerUtil.*; @@ -29,19 +29,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import lombok.ConfigurationKeys; -import lombok.EqualsAndHashCode; -import lombok.core.AST.Kind; -import lombok.core.configuration.CallSuperType; -import lombok.core.AnnotationValues; -import lombok.core.handlers.HandlerUtil; -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; import com.sun.tools.javac.code.BoundKind; @@ -66,6 +53,20 @@ import com.sun.tools.javac.util.List; import com.sun.tools.javac.util.ListBuffer; import com.sun.tools.javac.util.Name; +import lombok.ConfigurationKeys; +import lombok.EqualsAndHashCode; +import lombok.core.AST.Kind; +import lombok.core.AnnotationValues; +import lombok.core.configuration.CallSuperType; +import lombok.core.handlers.HandlerUtil; +import lombok.core.handlers.HandlerUtil.FieldAccess; +import lombok.core.handlers.InclusionExclusionUtils; +import lombok.core.handlers.InclusionExclusionUtils.Included; +import lombok.javac.JavacAnnotationHandler; +import lombok.javac.JavacNode; +import lombok.javac.JavacTreeMaker; +import lombok.javac.handlers.JavacHandlerUtil.MemberExistsResult; + /** * Handles the {@code lombok.EqualsAndHashCode} annotation for javac. */ @@ -74,45 +75,23 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler<EqualsAndHas private static final String RESULT_NAME = "result"; private static final String PRIME_NAME = "PRIME"; - public void checkForBogusFieldNames(JavacNode type, AnnotationValues<EqualsAndHashCode> annotation) { - if (annotation.isExplicit("exclude")) { - for (int i : createListOfNonExistentFields(List.from(annotation.getInstance().exclude()), type, true, true)) { - 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<EqualsAndHashCode> annotation, JCAnnotation ast, JavacNode annotationNode) { handleFlagUsage(annotationNode, ConfigurationKeys.EQUALS_AND_HASH_CODE_FLAG_USAGE, "@EqualsAndHashCode"); deleteAnnotationIfNeccessary(annotationNode, EqualsAndHashCode.class); EqualsAndHashCode ann = annotation.getInstance(); - List<String> excludes = List.from(ann.exclude()); - List<String> includes = List.from(ann.of()); + java.util.List<Included<JavacNode, EqualsAndHashCode.Include>> members = InclusionExclusionUtils.handleEqualsAndHashCodeMarking(annotationNode.up(), annotation, annotationNode); JavacNode typeNode = annotationNode.up(); - List<JCAnnotation> onParam = unboxAndRemoveAnnotationParameter(ast, "onParam", "@EqualsAndHashCode(onParam=", annotationNode); - checkForBogusFieldNames(typeNode, annotation); + List<JCAnnotation> onParam = unboxAndRemoveAnnotationParameter(ast, "onParam", "@EqualsAndHashCode(onParam", annotationNode); 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.EQUALS_AND_HASH_CODE_DO_NOT_USE_GETTERS); boolean doNotUseGetters = annotation.isExplicit("doNotUseGetters") || doNotUseGettersConfiguration == null ? ann.doNotUseGetters() : doNotUseGettersConfiguration; FieldAccess fieldAccess = doNotUseGetters ? FieldAccess.PREFER_FIELD : FieldAccess.GETTER; - generateMethods(typeNode, annotationNode, excludes, includes, callSuper, true, fieldAccess, onParam); + generateMethods(typeNode, annotationNode, members, callSuper, true, fieldAccess, onParam); } public void generateEqualsAndHashCodeForType(JavacNode typeNode, JavacNode source) { @@ -121,10 +100,15 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler<EqualsAndHas return; } - generateMethods(typeNode, source, null, null, null, false, FieldAccess.GETTER, List.<JCAnnotation>nil()); + Boolean doNotUseGettersConfiguration = typeNode.getAst().readConfiguration(ConfigurationKeys.EQUALS_AND_HASH_CODE_DO_NOT_USE_GETTERS); + FieldAccess access = doNotUseGettersConfiguration == null || !doNotUseGettersConfiguration ? FieldAccess.GETTER : FieldAccess.PREFER_FIELD; + + java.util.List<Included<JavacNode, EqualsAndHashCode.Include>> members = InclusionExclusionUtils.handleEqualsAndHashCodeMarking(typeNode, null, null); + + generateMethods(typeNode, source, members, null, false, access, List.<JCAnnotation>nil()); } - public void generateMethods(JavacNode typeNode, JavacNode source, List<String> excludes, List<String> includes, + public void generateMethods(JavacNode typeNode, JavacNode source, java.util.List<Included<JavacNode, EqualsAndHashCode.Include>> members, Boolean callSuper, boolean whineIfExists, FieldAccess fieldAccess, List<JCAnnotation> onParam) { boolean notAClass = true; @@ -138,7 +122,6 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler<EqualsAndHas return; } - boolean isDirectDescendantOfObject = true; boolean implicitCallSuper = callSuper == null; if (callSuper == null) { try { @@ -148,11 +131,7 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler<EqualsAndHas } } - JCTree extending = Javac.getExtendsClause((JCClassDecl)typeNode.get()); - if (extending != null) { - String p = extending.toString(); - isDirectDescendantOfObject = p.equals("Object") || p.equals("java.lang.Object"); - } + boolean isDirectDescendantOfObject = isDirectDescendantOfObject(typeNode); if (isDirectDescendantOfObject && callSuper) { source.addError("Generating equals/hashCode with a supercall to java.lang.Object is pointless."); @@ -178,29 +157,6 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler<EqualsAndHas } } - ListBuffer<JavacNode> nodesForEquality = 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())) nodesForEquality.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 transient fields. - if ((fieldDecl.mods.flags & Flags.TRANSIENT) != 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; - nodesForEquality.append(child); - } - } - boolean isFinal = (((JCClassDecl) typeNode.get()).mods.flags & Flags.FINAL) != 0; boolean needsCanEqual = !isFinal || !isDirectDescendantOfObject; MemberExistsResult equalsExists = methodExists("equals", typeNode, 1); @@ -229,7 +185,8 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler<EqualsAndHas //fallthrough } - JCMethodDecl equalsMethod = createEquals(typeNode, nodesForEquality.toList(), callSuper, fieldAccess, needsCanEqual, source.get(), onParam); + JCMethodDecl equalsMethod = createEquals(typeNode, members, callSuper, fieldAccess, needsCanEqual, source.get(), onParam); + injectMethod(typeNode, equalsMethod); if (needsCanEqual && canEqualExists == MemberExistsResult.NOT_EXISTS) { @@ -237,11 +194,11 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler<EqualsAndHas injectMethod(typeNode, canEqualMethod); } - JCMethodDecl hashCodeMethod = createHashCode(typeNode, nodesForEquality.toList(), callSuper, fieldAccess, source.get()); + JCMethodDecl hashCodeMethod = createHashCode(typeNode, members, callSuper, fieldAccess, source.get()); injectMethod(typeNode, hashCodeMethod); } - public JCMethodDecl createHashCode(JavacNode typeNode, List<JavacNode> fields, boolean callSuper, FieldAccess fieldAccess, JCTree source) { + public JCMethodDecl createHashCode(JavacNode typeNode, java.util.List<Included<JavacNode, EqualsAndHashCode.Include>> members, boolean callSuper, FieldAccess fieldAccess, JCTree source) { JavacTreeMaker maker = typeNode.getTreeMaker(); JCAnnotation overrideAnnotation = maker.Annotation(genJavaLangTypeRef(typeNode, "Override"), List.<JCExpression>nil()); @@ -254,26 +211,31 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler<EqualsAndHas long finalFlag = JavacHandlerUtil.addFinalIfNeeded(0L, typeNode.getContext()); /* final int PRIME = X; */ { - if (!fields.isEmpty() || callSuper) { + if (!members.isEmpty()) { statements.append(maker.VarDef(maker.Modifiers(finalFlag), primeName, maker.TypeIdent(CTC_INT), maker.Literal(HandlerUtil.primeForHashcode()))); } } - /* int result = 1; */ { - statements.append(maker.VarDef(maker.Modifiers(0), resultName, maker.TypeIdent(CTC_INT), maker.Literal(1))); - } - - if (callSuper) { - JCMethodInvocation callToSuper = maker.Apply(List.<JCExpression>nil(), - maker.Select(maker.Ident(typeNode.toName("super")), typeNode.toName("hashCode")), - List.<JCExpression>nil()); - statements.append(createResultCalculation(typeNode, callToSuper)); + /* int result = ... */ { + final JCExpression init; + if (callSuper) { + /* ... super.hashCode(); */ + init = maker.Apply(List.<JCExpression>nil(), + maker.Select(maker.Ident(typeNode.toName("super")), typeNode.toName("hashCode")), + List.<JCExpression>nil()); + } else { + /* ... 1; */ + init = maker.Literal(1); + } + statements.append(maker.VarDef(maker.Modifiers(0), resultName, maker.TypeIdent(CTC_INT), init)); } - Name dollar = typeNode.toName("$"); - for (JavacNode fieldNode : fields) { - JCExpression fType = getFieldType(fieldNode, fieldAccess); - JCExpression fieldAccessor = createFieldAccessor(maker, fieldNode, fieldAccess); + for (Included<JavacNode, EqualsAndHashCode.Include> member : members) { + JavacNode memberNode = member.getNode(); + JCExpression fType = getFieldType(memberNode, fieldAccess); + boolean isMethod = memberNode.getKind() == Kind.METHOD; + + JCExpression fieldAccessor = isMethod ? createMethodAccessor(maker, memberNode) : createFieldAccessor(maker, memberNode, fieldAccess); if (fType instanceof JCPrimitiveTypeTree) { switch (((JCPrimitiveTypeTree) fType).getPrimitiveTypeKind()) { case BOOLEAN: @@ -282,7 +244,7 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler<EqualsAndHas maker.Literal(HandlerUtil.primeForTrue()), maker.Literal(HandlerUtil.primeForFalse()))))); break; case LONG: { - Name dollarFieldName = dollar.append(((JCVariableDecl) fieldNode.get()).name); + Name dollarFieldName = memberNode.toName((isMethod ? "$$" : "$") + memberNode.getName()); statements.append(maker.VarDef(maker.Modifiers(finalFlag), dollarFieldName, maker.TypeIdent(CTC_LONG), fieldAccessor)); statements.append(createResultCalculation(typeNode, longToIntForHashCode(maker, maker.Ident(dollarFieldName), maker.Ident(dollarFieldName)))); } @@ -296,7 +258,7 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler<EqualsAndHas break; case DOUBLE: { /* longToIntForHashCode(Double.doubleToLongBits(this.fieldName)) */ - Name dollarFieldName = dollar.append(((JCVariableDecl) fieldNode.get()).name); + Name dollarFieldName = memberNode.toName((isMethod ? "$$" : "$") + memberNode.getName()); JCExpression init = maker.Apply( List.<JCExpression>nil(), genJavaLangTypeRef(typeNode, "Double", "doubleToLongBits"), @@ -326,7 +288,7 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler<EqualsAndHas /* final java.lang.Object $fieldName = this.fieldName; */ /* ($fieldName == null ? NULL_PRIME : $fieldName.hashCode()) */ - Name dollarFieldName = dollar.append(((JCVariableDecl) fieldNode.get()).name); + Name dollarFieldName = memberNode.toName((isMethod ? "$$" : "$") + memberNode.getName()); statements.append(maker.VarDef(maker.Modifiers(finalFlag), dollarFieldName, genJavaLangTypeRef(typeNode, "Object"), fieldAccessor)); JCExpression hcCall = maker.Apply(List.<JCExpression>nil(), maker.Select(maker.Ident(dollarFieldName), typeNode.toName("hashCode")), @@ -362,29 +324,50 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler<EqualsAndHas return maker.TypeCast(maker.TypeIdent(CTC_INT), maker.Parens(xorBits)); } - public JCExpression createTypeReference(JavacNode type) { + public JCExpression createTypeReference(JavacNode type, boolean addWildcards) { java.util.List<String> list = new ArrayList<String>(); + java.util.List<Integer> genericsCount = addWildcards ? new ArrayList<Integer>() : null; + list.add(type.getName()); + if (addWildcards) genericsCount.add(((JCClassDecl) type.get()).typarams.size()); + boolean staticContext = (((JCClassDecl) type.get()).getModifiers().flags & Flags.STATIC) != 0; JavacNode tNode = type.up(); + while (tNode != null && tNode.getKind() == Kind.TYPE) { list.add(tNode.getName()); + if (addWildcards) genericsCount.add(staticContext ? 0 : ((JCClassDecl) tNode.get()).typarams.size()); + if (!staticContext) staticContext = (((JCClassDecl) tNode.get()).getModifiers().flags & Flags.STATIC) != 0; tNode = tNode.up(); } Collections.reverse(list); + if (addWildcards) Collections.reverse(genericsCount); JavacTreeMaker maker = type.getTreeMaker(); + JCExpression chain = maker.Ident(type.toName(list.get(0))); + if (addWildcards) chain = wildcardify(maker, chain, genericsCount.get(0)); for (int i = 1; i < list.size(); i++) { chain = maker.Select(chain, type.toName(list.get(i))); + if (addWildcards) chain = wildcardify(maker, chain, genericsCount.get(i)); } return chain; } - public JCMethodDecl createEquals(JavacNode typeNode, List<JavacNode> fields, boolean callSuper, FieldAccess fieldAccess, boolean needsCanEqual, JCTree source, List<JCAnnotation> onParam) { + private JCExpression wildcardify(JavacTreeMaker maker, JCExpression expr, int count) { + if (count == 0) return expr; + + ListBuffer<JCExpression> wildcards = new ListBuffer<JCExpression>(); + for (int i = 0 ; i < count ; i++) { + wildcards.append(maker.Wildcard(maker.TypeBoundKind(BoundKind.UNBOUND), null)); + } + + return maker.TypeApply(expr, wildcards.toList()); + } + + public JCMethodDecl createEquals(JavacNode typeNode, java.util.List<Included<JavacNode, EqualsAndHashCode.Include>> members, boolean callSuper, FieldAccess fieldAccess, boolean needsCanEqual, JCTree source, List<JCAnnotation> onParam) { JavacTreeMaker maker = typeNode.getTreeMaker(); - JCClassDecl type = (JCClassDecl) typeNode.get(); Name oName = typeNode.toName("o"); Name otherName = typeNode.toName("other"); @@ -407,27 +390,13 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler<EqualsAndHas /* if (!(o instanceof Outer.Inner.MyType)) return false; */ { - JCUnary notInstanceOf = maker.Unary(CTC_NOT, maker.Parens(maker.TypeTest(maker.Ident(oName), createTypeReference(typeNode)))); + JCUnary notInstanceOf = maker.Unary(CTC_NOT, maker.Parens(maker.TypeTest(maker.Ident(oName), createTypeReference(typeNode, false)))); statements.append(maker.If(notInstanceOf, returnBool(maker, false), null)); } - /* MyType<?> other = (MyType<?>) o; */ { - if (!fields.isEmpty() || needsCanEqual) { - final JCExpression selfType1, selfType2; - ListBuffer<JCExpression> wildcards1 = new ListBuffer<JCExpression>(); - ListBuffer<JCExpression> wildcards2 = new ListBuffer<JCExpression>(); - for (int i = 0 ; i < type.typarams.length() ; i++) { - wildcards1.append(maker.Wildcard(maker.TypeBoundKind(BoundKind.UNBOUND), null)); - wildcards2.append(maker.Wildcard(maker.TypeBoundKind(BoundKind.UNBOUND), null)); - } - - if (type.typarams.isEmpty()) { - selfType1 = maker.Ident(type.name); - selfType2 = maker.Ident(type.name); - } else { - selfType1 = maker.TypeApply(maker.Ident(type.name), wildcards1.toList()); - selfType2 = maker.TypeApply(maker.Ident(type.name), wildcards2.toList()); - } + /* Outer.Inner.MyType<?> other = (Outer.Inner.MyType<?>) o; */ { + if (!members.isEmpty() || needsCanEqual) { + final JCExpression selfType1 = createTypeReference(typeNode, true), selfType2 = createTypeReference(typeNode, true); statements.append( maker.VarDef(maker.Modifiers(finalFlag), otherName, selfType1, maker.TypeCast(selfType2, maker.Ident(oName)))); @@ -455,12 +424,13 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler<EqualsAndHas statements.append(maker.If(superNotEqual, returnBool(maker, false), null)); } - Name thisDollar = typeNode.toName("this$"); - Name otherDollar = typeNode.toName("other$"); - for (JavacNode fieldNode : fields) { - JCExpression fType = getFieldType(fieldNode, fieldAccess); - JCExpression thisFieldAccessor = createFieldAccessor(maker, fieldNode, fieldAccess); - JCExpression otherFieldAccessor = createFieldAccessor(maker, fieldNode, fieldAccess, maker.Ident(otherName)); + for (Included<JavacNode, EqualsAndHashCode.Include> member : members) { + JavacNode memberNode = member.getNode(); + boolean isMethod = memberNode.getKind() == Kind.METHOD; + + JCExpression fType = getFieldType(memberNode, fieldAccess); + JCExpression thisFieldAccessor = isMethod ? createMethodAccessor(maker, memberNode) : createFieldAccessor(maker, memberNode, fieldAccess); + JCExpression otherFieldAccessor = isMethod ? createMethodAccessor(maker, memberNode, maker.Ident(otherName)) : createFieldAccessor(maker, memberNode, fieldAccess, maker.Ident(otherName)); if (fType instanceof JCPrimitiveTypeTree) { switch (((JCPrimitiveTypeTree)fType).getPrimitiveTypeKind()) { case FLOAT: @@ -491,9 +461,8 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler<EqualsAndHas /* final java.lang.Object this$fieldName = this.fieldName; */ /* final java.lang.Object other$fieldName = other.fieldName; */ /* if (this$fieldName == null ? other$fieldName != null : !this$fieldName.equals(other$fieldName)) return false; */ - Name fieldName = ((JCVariableDecl) fieldNode.get()).name; - Name thisDollarFieldName = thisDollar.append(fieldName); - Name otherDollarFieldName = otherDollar.append(fieldName); + Name thisDollarFieldName = memberNode.toName("this" + (isMethod ? "$$" : "$") + memberNode.getName()); + Name otherDollarFieldName = memberNode.toName("other" + (isMethod ? "$$" : "$") + memberNode.getName()); statements.append(maker.VarDef(maker.Modifiers(finalFlag), thisDollarFieldName, genJavaLangTypeRef(typeNode, "Object"), thisFieldAccessor)); statements.append(maker.VarDef(maker.Modifiers(finalFlag), otherDollarFieldName, genJavaLangTypeRef(typeNode, "Object"), otherFieldAccessor)); @@ -532,7 +501,7 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler<EqualsAndHas List<JCVariableDecl> params = List.of(maker.VarDef(maker.Modifiers(flags, onParam), otherName, objectType, null)); JCBlock body = maker.Block(0, List.<JCStatement>of( - maker.Return(maker.TypeTest(maker.Ident(otherName), createTypeReference(typeNode))))); + maker.Return(maker.TypeTest(maker.Ident(otherName), createTypeReference(typeNode, false))))); return recursiveSetGeneratedBy(maker.MethodDef(mods, canEqualName, returnType, List.<JCTypeParameter>nil(), params, List.<JCExpression>nil(), body, null), source, typeNode.getContext()); } diff --git a/src/core/lombok/javac/handlers/HandleFieldDefaults.java b/src/core/lombok/javac/handlers/HandleFieldDefaults.java index 12c22059..52f6c39c 100644 --- a/src/core/lombok/javac/handlers/HandleFieldDefaults.java +++ b/src/core/lombok/javac/handlers/HandleFieldDefaults.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2015 The Project Lombok Authors. + * Copyright (C) 2012-2016 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 @@ -84,14 +84,16 @@ public class HandleFieldDefaults extends JavacASTAdapter { if (level != null && level != AccessLevel.NONE) { if ((field.mods.flags & (Flags.PUBLIC | Flags.PRIVATE | Flags.PROTECTED)) == 0) { if (!hasAnnotationAndDeleteIfNeccessary(PackagePrivate.class, fieldNode)) { - field.mods.flags |= toJavacModifier(level); + if ((field.mods.flags & Flags.STATIC) == 0) { + field.mods.flags |= toJavacModifier(level); + } } } } if (makeFinal && (field.mods.flags & Flags.FINAL) == 0) { if (!hasAnnotationAndDeleteIfNeccessary(NonFinal.class, fieldNode)) { - if ((field.mods.flags & Flags.STATIC) == 0 || field.init != null) { + if ((field.mods.flags & Flags.STATIC) == 0) { field.mods.flags |= Flags.FINAL; } } diff --git a/src/core/lombok/javac/handlers/HandleFieldNameConstants.java b/src/core/lombok/javac/handlers/HandleFieldNameConstants.java new file mode 100644 index 00000000..8ff136fc --- /dev/null +++ b/src/core/lombok/javac/handlers/HandleFieldNameConstants.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2014-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.javac.handlers; + +import static lombok.core.handlers.HandlerUtil.handleExperimentalFlagUsage; +import static lombok.javac.handlers.JavacHandlerUtil.*; + +import java.lang.reflect.Modifier; +import java.util.Collection; + +import lombok.AccessLevel; +import lombok.ConfigurationKeys; +import lombok.core.AST.Kind; +import lombok.core.handlers.HandlerUtil; +import lombok.core.AnnotationValues; +import lombok.experimental.FieldNameConstants; +import lombok.javac.JavacAnnotationHandler; +import lombok.javac.JavacNode; +import lombok.javac.JavacTreeMaker; + +import org.mangosdk.spi.ProviderFor; + +import com.sun.tools.javac.code.Flags; +import com.sun.tools.javac.tree.JCTree.JCAnnotation; +import com.sun.tools.javac.tree.JCTree.JCClassDecl; +import com.sun.tools.javac.tree.JCTree.JCExpression; +import com.sun.tools.javac.tree.JCTree.JCModifiers; +import com.sun.tools.javac.tree.JCTree.JCVariableDecl; +import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; + +@ProviderFor(JavacAnnotationHandler.class) +public class HandleFieldNameConstants extends JavacAnnotationHandler<FieldNameConstants> { + public void generateFieldNameConstantsForType(JavacNode typeNode, JavacNode errorNode, AccessLevel level, String prefix, String suffix) { + JCClassDecl typeDecl = null; + if (typeNode.get() instanceof JCClassDecl) typeDecl = (JCClassDecl) typeNode.get(); + + long modifiers = typeDecl == null ? 0 : typeDecl.mods.flags; + boolean notAClass = (modifiers & (Flags.INTERFACE | Flags.ANNOTATION)) != 0; + + if (typeDecl == null || notAClass) { + errorNode.addError("@FieldNameConstants is only supported on a class, an enum, or a field."); + return; + } + + for (JavacNode field : typeNode.down()) { + if (fieldQualifiesForFieldNameConstantsGeneration(field)) generateFieldNameConstantsForField(field, errorNode.get(), level, prefix, suffix); + } + } + + private void generateFieldNameConstantsForField(JavacNode fieldNode, DiagnosticPosition pos, AccessLevel level, String prefix, String suffix) { + if (hasAnnotation(FieldNameConstants.class, fieldNode)) return; + createFieldNameConstantsForField(level, prefix, suffix, fieldNode, fieldNode, false); + } + + private boolean fieldQualifiesForFieldNameConstantsGeneration(JavacNode field) { + if (field.getKind() != Kind.FIELD) return false; + JCVariableDecl fieldDecl = (JCVariableDecl) field.get(); + if (fieldDecl.name.toString().startsWith("$")) return false; + if ((fieldDecl.mods.flags & Flags.STATIC) != 0) return false; + return true; + } + + public void handle(AnnotationValues<FieldNameConstants> annotation, JCAnnotation ast, JavacNode annotationNode) { + handleExperimentalFlagUsage(annotationNode, ConfigurationKeys.FIELD_NAME_CONSTANTS_FLAG_USAGE, "@FieldNameConstants"); + + Collection<JavacNode> fields = annotationNode.upFromAnnotationToFields(); + deleteAnnotationIfNeccessary(annotationNode, FieldNameConstants.class); + deleteImportFromCompilationUnit(annotationNode, "lombok.AccessLevel"); + JavacNode node = annotationNode.up(); + FieldNameConstants annotatationInstance = annotation.getInstance(); + AccessLevel level = annotatationInstance.level(); + String prefix = annotatationInstance.prefix(); + String suffix = annotatationInstance.suffix(); + if (prefix.equals(" CONFIG DEFAULT ")) prefix = annotationNode.getAst().readConfiguration(ConfigurationKeys.FIELD_NAME_CONSTANTS_PREFIX); + if (suffix.equals(" CONFIG DEFAULT ")) suffix = annotationNode.getAst().readConfiguration(ConfigurationKeys.FIELD_NAME_CONSTANTS_SUFFIX); + if (prefix == null) prefix = "FIELD_"; + if (suffix == null) suffix = ""; + if (node == null) return; + switch (node.getKind()) { + case FIELD: + if (level != AccessLevel.NONE) createFieldNameConstantsForFields(level, prefix, suffix, fields, annotationNode, annotationNode, true); + break; + case TYPE: + if (level == AccessLevel.NONE) { + annotationNode.addWarning("type-level '@FieldNameConstants' does not work with AccessLevel.NONE."); + return; + } + generateFieldNameConstantsForType(node, annotationNode, level, prefix, suffix); + break; + } + } + + private void createFieldNameConstantsForFields(AccessLevel level, String prefix, String suffix, Collection<JavacNode> fieldNodes, JavacNode annotationNode, JavacNode errorNode, boolean whineIfExists) { + for (JavacNode fieldNode : fieldNodes) createFieldNameConstantsForField(level, prefix, suffix, fieldNode, errorNode, whineIfExists); + } + + private void createFieldNameConstantsForField(AccessLevel level, String prefix, String suffix, JavacNode fieldNode, JavacNode source, boolean whineIfExists) { + if (fieldNode.getKind() != Kind.FIELD) { + source.addError("@FieldNameConstants is only supported on a class, an enum, or a field"); + return; + } + + JCVariableDecl field = (JCVariableDecl) fieldNode.get(); + String fieldName = field.name.toString(); + String constantName = prefix + HandlerUtil.camelCaseToConstant(fieldName) + suffix; + if (constantName.equals(fieldName)) { + fieldNode.addWarning("Not generating constant for this field: The name of the constant would be equal to the name of this field."); + return; + } + + JavacTreeMaker treeMaker = fieldNode.getTreeMaker(); + JCModifiers modifiers = treeMaker.Modifiers(toJavacModifier(level) | Modifier.STATIC | Modifier.FINAL); + JCExpression returnType = chainDots(fieldNode, "java", "lang", "String"); + JCExpression init = treeMaker.Literal(fieldNode.getName()); + JCVariableDecl fieldConstant = treeMaker.VarDef(modifiers, fieldNode.toName(constantName), returnType, init); + injectField(fieldNode.up(), fieldConstant); + } +}
\ No newline at end of file diff --git a/src/core/lombok/javac/handlers/HandleGetter.java b/src/core/lombok/javac/handlers/HandleGetter.java index a330dbc1..4fc6155c 100644 --- a/src/core/lombok/javac/handlers/HandleGetter.java +++ b/src/core/lombok/javac/handlers/HandleGetter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2014 The Project Lombok Authors. + * Copyright (C) 2009-2017 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 @@ -41,11 +41,11 @@ 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; import com.sun.tools.javac.code.Flags; +import com.sun.tools.javac.code.Type; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.JCAnnotation; import com.sun.tools.javac.tree.JCTree.JCBinary; @@ -71,7 +71,7 @@ import com.sun.tools.javac.util.Name; */ @ProviderFor(JavacAnnotationHandler.class) public class HandleGetter extends JavacAnnotationHandler<Getter> { - public void generateGetterForType(JavacNode typeNode, JavacNode errorNode, AccessLevel level, boolean checkForTypeLevelGetter) { + public void generateGetterForType(JavacNode typeNode, JavacNode errorNode, AccessLevel level, boolean checkForTypeLevelGetter, List<JCAnnotation> onMethod) { if (checkForTypeLevelGetter) { if (hasAnnotation(Getter.class, typeNode)) { //The annotation will make it happen, so we can skip it. @@ -90,11 +90,11 @@ public class HandleGetter extends JavacAnnotationHandler<Getter> { } for (JavacNode field : typeNode.down()) { - if (fieldQualifiesForGetterGeneration(field)) generateGetterForField(field, errorNode.get(), level, false); + if (fieldQualifiesForGetterGeneration(field)) generateGetterForField(field, errorNode.get(), level, false, onMethod); } } - public boolean fieldQualifiesForGetterGeneration(JavacNode field) { + public static boolean fieldQualifiesForGetterGeneration(JavacNode field) { if (field.getKind() != Kind.FIELD) return false; JCVariableDecl fieldDecl = (JCVariableDecl) field.get(); //Skip fields that start with $ @@ -119,12 +119,12 @@ public class HandleGetter extends JavacAnnotationHandler<Getter> { * @param fieldNode The node representing the field you want a getter for. * @param pos The node responsible for generating the getter (the {@code @Data} or {@code @Getter} annotation). */ - public void generateGetterForField(JavacNode fieldNode, DiagnosticPosition pos, AccessLevel level, boolean lazy) { + public void generateGetterForField(JavacNode fieldNode, DiagnosticPosition pos, AccessLevel level, boolean lazy, List<JCAnnotation> onMethod) { if (hasAnnotation(Getter.class, fieldNode)) { //The annotation will make it happen, so we can skip it. return; } - createGetterForField(level, fieldNode, fieldNode, false, lazy, List.<JCAnnotation>nil()); + createGetterForField(level, fieldNode, fieldNode, false, lazy, onMethod); } @Override public void handle(AnnotationValues<Getter> annotation, JCAnnotation ast, JavacNode annotationNode) { @@ -146,18 +146,15 @@ public class HandleGetter extends JavacAnnotationHandler<Getter> { if (node == null) return; - List<JCAnnotation> onMethod = unboxAndRemoveAnnotationParameter(ast, "onMethod", "@Getter(onMethod=", annotationNode); + List<JCAnnotation> onMethod = unboxAndRemoveAnnotationParameter(ast, "onMethod", "@Getter(onMethod", annotationNode); switch (node.getKind()) { case FIELD: createGetterForFields(level, fields, annotationNode, true, lazy, onMethod); break; case TYPE: - if (!onMethod.isEmpty()) { - annotationNode.addError("'onMethod' is not supported for @Getter on a type."); - } if (lazy) annotationNode.addError("'lazy' is not supported for @Getter on a type."); - generateGetterForType(node, annotationNode, level, false); + generateGetterForType(node, annotationNode, level, false, onMethod); break; } } @@ -182,6 +179,10 @@ public class HandleGetter extends JavacAnnotationHandler<Getter> { source.addError("'lazy' requires the field to be private and final."); return; } + if ((fieldDecl.mods.flags & Flags.TRANSIENT) != 0) { + source.addError("'lazy' is not supported on transient fields."); + return; + } if (fieldDecl.init == null) { source.addError("'lazy' requires field initialization."); return; @@ -215,7 +216,7 @@ public class HandleGetter extends JavacAnnotationHandler<Getter> { long access = toJavacModifier(level) | (fieldDecl.mods.flags & Flags.STATIC); - injectMethod(fieldNode.up(), createGetter(access, fieldNode, fieldNode.getTreeMaker(), source.get(), lazy, onMethod)); + injectMethod(fieldNode.up(), createGetter(access, fieldNode, fieldNode.getTreeMaker(), source.get(), lazy, onMethod), List.<Type>nil(), getMirrorForFieldType(fieldNode)); } public JCMethodDecl createGetter(long access, JavacNode field, JavacTreeMaker treeMaker, JCTree source, boolean lazy, List<JCAnnotation> onMethod) { diff --git a/src/core/lombok/javac/handlers/HandleLog.java b/src/core/lombok/javac/handlers/HandleLog.java index d0d709e3..6d742e76 100644 --- a/src/core/lombok/javac/handlers/HandleLog.java +++ b/src/core/lombok/javac/handlers/HandleLog.java @@ -91,16 +91,18 @@ public class HandleLog { // private static final <loggerType> log = <factoryMethod>(<parameter>); JCExpression loggerType = chainDotsString(typeNode, framework.getLoggerTypeName()); JCExpression factoryMethod = chainDotsString(typeNode, framework.getLoggerFactoryMethodName()); - + JCExpression loggerName; - if (loggerTopic == null || loggerTopic.trim().length() == 0) { + if (!framework.passTypeName) { + loggerName = null; + } else if (loggerTopic == null || loggerTopic.trim().length() == 0) { loggerName = framework.createFactoryParameter(typeNode, loggingType); } else { loggerName = maker.Literal(loggerTopic); } - - JCMethodInvocation factoryMethodCall = maker.Apply(List.<JCExpression>nil(), factoryMethod, List.<JCExpression>of(loggerName)); - + + JCMethodInvocation factoryMethodCall = maker.Apply(List.<JCExpression>nil(), factoryMethod, loggerName != null ? List.<JCExpression>of(loggerName) : List.<JCExpression>nil()); + JCVariableDecl fieldDecl = recursiveSetGeneratedBy(maker.VarDef( maker.Modifiers(Flags.PRIVATE | Flags.FINAL | (useStatic ? Flags.STATIC : 0)), typeNode.toName(logFieldName), loggerType, factoryMethodCall), source, typeNode.getContext()); @@ -186,6 +188,17 @@ public class HandleLog { } } + /** + * Handles the {@link lombok.extern.flogger.Flogger} annotation for javac. + */ + @ProviderFor(JavacAnnotationHandler.class) + public static class HandleFloggerLog extends JavacAnnotationHandler<lombok.extern.flogger.Flogger> { + @Override public void handle(AnnotationValues<lombok.extern.flogger.Flogger> annotation, JCAnnotation ast, JavacNode annotationNode) { + handleFlagUsage(annotationNode, ConfigurationKeys.LOG_FLOGGER_FLAG_USAGE, "@Flogger", ConfigurationKeys.LOG_ANY_FLAG_USAGE, "any @Log"); + processAnnotation(LoggingFramework.FLOGGER, annotation, annotationNode, ""); + } + } + enum LoggingFramework { // private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(TargetType.class); COMMONS(lombok.extern.apachecommons.CommonsLog.class, "org.apache.commons.logging.Log", "org.apache.commons.logging.LogFactory.getLog"), @@ -212,17 +225,29 @@ public class HandleLog { XSLF4J(lombok.extern.slf4j.XSlf4j.class, "org.slf4j.ext.XLogger", "org.slf4j.ext.XLoggerFactory.getXLogger"), // private static final org.jboss.logging.Logger log = org.jboss.logging.Logger.getLogger(TargetType.class); - JBOSSLOG(lombok.extern.jbosslog.JBossLog.class, "org.jboss.logging.Logger", "org.jboss.logging.Logger.getLogger") + JBOSSLOG(lombok.extern.jbosslog.JBossLog.class, "org.jboss.logging.Logger", "org.jboss.logging.Logger.getLogger"), + + // private static final com.google.common.flogger.FluentLogger log = com.google.common.flogger.FluentLogger.forEnclosingClass(); + FLOGGER(lombok.extern.flogger.Flogger.class, "com.google.common.flogger.FluentLogger", "com.google.common.flogger.FluentLogger.forEnclosingClass", false), ; private final Class<? extends Annotation> annotationClass; private final String loggerTypeName; private final String loggerFactoryName; + private final boolean passTypeName; + + LoggingFramework(Class<? extends Annotation> annotationClass, String loggerTypeName, String loggerFactoryName, boolean passTypeName) { + this.annotationClass = annotationClass; + this.loggerTypeName = loggerTypeName; + this.loggerFactoryName = loggerFactoryName; + this.passTypeName = passTypeName; + } LoggingFramework(Class<? extends Annotation> annotationClass, String loggerTypeName, String loggerFactoryName) { this.annotationClass = annotationClass; this.loggerTypeName = loggerTypeName; this.loggerFactoryName = loggerFactoryName; + this.passTypeName = true; } final Class<? extends Annotation> getAnnotationClass() { diff --git a/src/core/lombok/javac/handlers/HandleSetter.java b/src/core/lombok/javac/handlers/HandleSetter.java index 3c4329b2..0ddaa7d7 100644 --- a/src/core/lombok/javac/handlers/HandleSetter.java +++ b/src/core/lombok/javac/handlers/HandleSetter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2014 The Project Lombok Authors. + * Copyright (C) 2009-2017 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 @@ -36,11 +36,12 @@ 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; import com.sun.tools.javac.code.Flags; +import com.sun.tools.javac.code.Symbol.ClassSymbol; +import com.sun.tools.javac.code.Type; import com.sun.tools.javac.tree.JCTree.JCAnnotation; import com.sun.tools.javac.tree.JCTree.JCAssign; import com.sun.tools.javac.tree.JCTree.JCBlock; @@ -60,7 +61,7 @@ import com.sun.tools.javac.util.Name; */ @ProviderFor(JavacAnnotationHandler.class) public class HandleSetter extends JavacAnnotationHandler<Setter> { - public void generateSetterForType(JavacNode typeNode, JavacNode errorNode, AccessLevel level, boolean checkForTypeLevelSetter) { + public void generateSetterForType(JavacNode typeNode, JavacNode errorNode, AccessLevel level, boolean checkForTypeLevelSetter, List<JCAnnotation> onMethod, List<JCAnnotation> onParam) { if (checkForTypeLevelSetter) { if (hasAnnotation(Setter.class, typeNode)) { //The annotation will make it happen, so we can skip it. @@ -88,7 +89,7 @@ public class HandleSetter extends JavacAnnotationHandler<Setter> { //Skip final fields. if ((fieldDecl.mods.flags & Flags.FINAL) != 0) continue; - generateSetterForField(field, errorNode, level); + generateSetterForField(field, errorNode, level, onMethod, onParam); } } @@ -107,13 +108,13 @@ public class HandleSetter extends JavacAnnotationHandler<Setter> { * @param fieldNode The node representing the field you want a setter for. * @param pos The node responsible for generating the setter (the {@code @Data} or {@code @Setter} annotation). */ - public void generateSetterForField(JavacNode fieldNode, JavacNode sourceNode, AccessLevel level) { + public void generateSetterForField(JavacNode fieldNode, JavacNode sourceNode, AccessLevel level, List<JCAnnotation> onMethod, List<JCAnnotation> onParam) { if (hasAnnotation(Setter.class, fieldNode)) { //The annotation will make it happen, so we can skip it. return; } - createSetterForField(level, fieldNode, sourceNode, false, List.<JCAnnotation>nil(), List.<JCAnnotation>nil()); + createSetterForField(level, fieldNode, sourceNode, false, onMethod, onParam); } @Override public void handle(AnnotationValues<Setter> annotation, JCAnnotation ast, JavacNode annotationNode) { @@ -127,17 +128,15 @@ public class HandleSetter extends JavacAnnotationHandler<Setter> { if (level == AccessLevel.NONE || node == null) return; - List<JCAnnotation> onMethod = unboxAndRemoveAnnotationParameter(ast, "onMethod", "@Setter(onMethod=", annotationNode); - List<JCAnnotation> onParam = unboxAndRemoveAnnotationParameter(ast, "onParam", "@Setter(onParam=", annotationNode); + List<JCAnnotation> onMethod = unboxAndRemoveAnnotationParameter(ast, "onMethod", "@Setter(onMethod", annotationNode); + List<JCAnnotation> onParam = unboxAndRemoveAnnotationParameter(ast, "onParam", "@Setter(onParam", annotationNode); switch (node.getKind()) { case FIELD: createSetterForFields(level, fields, annotationNode, true, onMethod, onParam); break; case TYPE: - if (!onMethod.isEmpty()) annotationNode.addError("'onMethod' is not supported for @Setter on a type."); - if (!onParam.isEmpty()) annotationNode.addError("'onParam' is not supported for @Setter on a type."); - generateSetterForType(node, annotationNode, level, false); + generateSetterForType(node, annotationNode, level, false, onMethod, onParam); break; } } @@ -154,7 +153,7 @@ public class HandleSetter extends JavacAnnotationHandler<Setter> { return; } - JCVariableDecl fieldDecl = (JCVariableDecl)fieldNode.get(); + JCVariableDecl fieldDecl = (JCVariableDecl) fieldNode.get(); String methodName = toSetterName(fieldNode); if (methodName == null) { @@ -188,16 +187,37 @@ public class HandleSetter extends JavacAnnotationHandler<Setter> { long access = toJavacModifier(level) | (fieldDecl.mods.flags & Flags.STATIC); JCMethodDecl createdSetter = createSetter(access, fieldNode, fieldNode.getTreeMaker(), sourceNode, onMethod, onParam); - injectMethod(fieldNode.up(), createdSetter); + Type fieldType = getMirrorForFieldType(fieldNode); + Type returnType; + + if (shouldReturnThis(fieldNode)) { + ClassSymbol sym = ((JCClassDecl) fieldNode.up().get()).sym; + returnType = sym == null ? null : sym.type; + } else { + returnType = Javac.createVoidType(fieldNode.getSymbolTable(), CTC_VOID); + } + + injectMethod(fieldNode.up(), createdSetter, fieldType == null ? null : List.of(fieldType), returnType); } public static JCMethodDecl createSetter(long access, JavacNode field, JavacTreeMaker treeMaker, JavacNode source, List<JCAnnotation> onMethod, List<JCAnnotation> onParam) { String setterName = toSetterName(field); boolean returnThis = shouldReturnThis(field); - return createSetter(access, field, treeMaker, setterName, returnThis, source, onMethod, onParam); + return createSetter(access, false, field, treeMaker, setterName, null, returnThis, source, onMethod, onParam); + } + + public static JCMethodDecl createSetter(long access, boolean deprecate, JavacNode field, JavacTreeMaker treeMaker, String setterName, Name booleanFieldToSet, boolean shouldReturnThis, JavacNode source, List<JCAnnotation> onMethod, List<JCAnnotation> onParam) { + JCExpression returnType = null; + JCReturn returnStatement = null; + if (shouldReturnThis) { + returnType = cloneSelfType(field); + returnStatement = treeMaker.Return(treeMaker.Ident(field.toName("this"))); + } + + return createSetter(access, deprecate, field, treeMaker, setterName, booleanFieldToSet, returnType, returnStatement, source, onMethod, onParam); } - public static JCMethodDecl createSetter(long access, JavacNode field, JavacTreeMaker treeMaker, String setterName, boolean shouldReturnThis, JavacNode source, List<JCAnnotation> onMethod, List<JCAnnotation> onParam) { + public static JCMethodDecl createSetter(long access, boolean deprecate, JavacNode field, JavacTreeMaker treeMaker, String setterName, Name booleanFieldToSet, JCExpression methodType, JCStatement returnStatement, JavacNode source, List<JCAnnotation> onMethod, List<JCAnnotation> onParam) { if (setterName == null) return null; JCVariableDecl fieldDecl = (JCVariableDecl) field.get(); @@ -223,21 +243,18 @@ public class HandleSetter extends JavacAnnotationHandler<Setter> { statements.append(treeMaker.Exec(assign)); } - JCExpression methodType = null; - if (shouldReturnThis) { - methodType = cloneSelfType(field); + if (booleanFieldToSet != null) { + JCAssign setBool = treeMaker.Assign(treeMaker.Ident(booleanFieldToSet), treeMaker.Literal(CTC_BOOLEAN, 1)); + statements.append(treeMaker.Exec(setBool)); } if (methodType == null) { //WARNING: Do not use field.getSymbolTable().voidType - that field has gone through non-backwards compatible API changes within javac1.6. - methodType = treeMaker.Type(Javac.createVoidType(treeMaker, CTC_VOID)); - shouldReturnThis = false; + methodType = treeMaker.Type(Javac.createVoidType(field.getSymbolTable(), CTC_VOID)); + returnStatement = null; } - if (shouldReturnThis) { - JCReturn returnStatement = treeMaker.Return(treeMaker.Ident(field.toName("this"))); - statements.append(returnStatement); - } + if (returnStatement != null) statements.append(returnStatement); JCBlock methodBody = treeMaker.Block(0, statements.toList()); List<JCTypeParameter> methodGenericParams = List.nil(); @@ -246,7 +263,7 @@ public class HandleSetter extends JavacAnnotationHandler<Setter> { JCExpression annotationMethodDefaultValue = null; List<JCAnnotation> annsOnMethod = copyAnnotations(onMethod); - if (isFieldDeprecated(field)) { + if (isFieldDeprecated(field) || deprecate) { annsOnMethod = annsOnMethod.prepend(treeMaker.Annotation(genJavaLangTypeRef(field, "Deprecated"), List.<JCExpression>nil())); } diff --git a/src/core/lombok/javac/handlers/HandleSuperBuilder.java b/src/core/lombok/javac/handlers/HandleSuperBuilder.java new file mode 100644 index 00000000..38fec2f6 --- /dev/null +++ b/src/core/lombok/javac/handlers/HandleSuperBuilder.java @@ -0,0 +1,738 @@ +/* + * Copyright (C) 2013-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.javac.handlers; + +import static lombok.core.handlers.HandlerUtil.*; +import static lombok.javac.Javac.*; +import static lombok.javac.handlers.JavacHandlerUtil.*; + +import java.util.ArrayList; + +import org.mangosdk.spi.ProviderFor; + +import com.sun.tools.javac.code.BoundKind; +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.JCBlock; +import com.sun.tools.javac.tree.JCTree.JCClassDecl; +import com.sun.tools.javac.tree.JCTree.JCExpression; +import com.sun.tools.javac.tree.JCTree.JCFieldAccess; +import com.sun.tools.javac.tree.JCTree.JCIdent; +import com.sun.tools.javac.tree.JCTree.JCIf; +import com.sun.tools.javac.tree.JCTree.JCMethodDecl; +import com.sun.tools.javac.tree.JCTree.JCMethodInvocation; +import com.sun.tools.javac.tree.JCTree.JCModifiers; +import com.sun.tools.javac.tree.JCTree.JCStatement; +import com.sun.tools.javac.tree.JCTree.JCTypeApply; +import com.sun.tools.javac.tree.JCTree.JCTypeParameter; +import com.sun.tools.javac.tree.JCTree.JCVariableDecl; +import com.sun.tools.javac.tree.JCTree.JCWildcard; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.util.ListBuffer; +import com.sun.tools.javac.util.Name; + +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Builder.ObtainVia; +import lombok.ConfigurationKeys; +import lombok.Singular; +import lombok.ToString; +import lombok.core.AST.Kind; +import lombok.core.AnnotationValues; +import lombok.core.HandlerPriority; +import lombok.core.handlers.HandlerUtil; +import lombok.core.handlers.InclusionExclusionUtils.Included; +import lombok.experimental.NonFinal; +import lombok.experimental.SuperBuilder; +import lombok.javac.Javac; +import lombok.javac.JavacAnnotationHandler; +import lombok.javac.JavacNode; +import lombok.javac.JavacTreeMaker; +import lombok.javac.handlers.JavacHandlerUtil.MemberExistsResult; +import lombok.javac.handlers.JavacSingularsRecipes.ExpressionMaker; +import lombok.javac.handlers.JavacSingularsRecipes.StatementMaker; +import lombok.javac.handlers.JavacSingularsRecipes.JavacSingularizer; +import lombok.javac.handlers.JavacSingularsRecipes.SingularData; + +@ProviderFor(JavacAnnotationHandler.class) +@HandlerPriority(-1024) //-2^10; to ensure we've picked up @FieldDefault's changes (-2048) but @Value hasn't removed itself yet (-512), so that we can error on presence of it on the builder classes. +public class HandleSuperBuilder extends JavacAnnotationHandler<SuperBuilder> { + private static final String SELF_METHOD = "self"; + + private static class BuilderFieldData { + JCExpression type; + Name rawName; + Name name; + Name nameOfSetFlag; + SingularData singularData; + ObtainVia obtainVia; + JavacNode obtainViaNode; + JavacNode originalFieldNode; + + java.util.List<JavacNode> createdFields = new ArrayList<JavacNode>(); + } + + @Override + public void handle(AnnotationValues<SuperBuilder> annotation, JCAnnotation ast, JavacNode annotationNode) { + handleExperimentalFlagUsage(annotationNode, ConfigurationKeys.SUPERBUILDER_FLAG_USAGE, "@SuperBuilder"); + + SuperBuilder superbuilderAnnotation = annotation.getInstance(); + deleteAnnotationIfNeccessary(annotationNode, SuperBuilder.class); + + String builderMethodName = superbuilderAnnotation.builderMethodName(); + String buildMethodName = superbuilderAnnotation.buildMethodName(); + + if (builderMethodName == null) builderMethodName = "builder"; + if (buildMethodName == null) buildMethodName = "build"; + + if (!checkName("builderMethodName", builderMethodName, annotationNode)) return; + if (!checkName("buildMethodName", buildMethodName, annotationNode)) return; + + JavacNode tdParent = annotationNode.up(); + + java.util.List<BuilderFieldData> builderFields = new ArrayList<BuilderFieldData>(); + JCExpression returnType; + List<JCTypeParameter> typeParams = List.nil(); + List<JCExpression> thrownExceptions = List.nil(); + List<JCExpression> superclassTypeParams = List.nil(); + + boolean addCleaning = false; + + if (!(tdParent.get() instanceof JCClassDecl)) { + annotationNode.addError("@SuperBuilder is only supported on types."); + return; + } + + // Gather all fields of the class that should be set by the builder. + JCClassDecl td = (JCClassDecl) tdParent.get(); + ListBuffer<JavacNode> allFields = new ListBuffer<JavacNode>(); + boolean valuePresent = (hasAnnotation(lombok.Value.class, tdParent) || hasAnnotation("lombok.experimental.Value", tdParent)); + for (JavacNode fieldNode : HandleConstructor.findAllFields(tdParent, true)) { + JCVariableDecl fd = (JCVariableDecl) fieldNode.get(); + JavacNode isDefault = findAnnotation(Builder.Default.class, fieldNode, true); + boolean isFinal = (fd.mods.flags & Flags.FINAL) != 0 || (valuePresent && !hasAnnotation(NonFinal.class, fieldNode)); + BuilderFieldData bfd = new BuilderFieldData(); + bfd.rawName = fd.name; + bfd.name = removePrefixFromField(fieldNode); + bfd.type = fd.vartype; + bfd.singularData = getSingularData(fieldNode); + bfd.originalFieldNode = fieldNode; + + if (bfd.singularData != null && isDefault != null) { + isDefault.addError("@Builder.Default and @Singular cannot be mixed."); + isDefault = null; + } + + if (fd.init == null && isDefault != null) { + isDefault.addWarning("@Builder.Default requires an initializing expression (' = something;')."); + isDefault = null; + } + + if (fd.init != null && isDefault == null) { + if (isFinal) continue; + fieldNode.addWarning("@SuperBuilder will ignore the initializing expression entirely. If you want the initializing expression to serve as default, add @Builder.Default. If it is not supposed to be settable during building, make the field final."); + } + + if (isDefault != null) { + bfd.nameOfSetFlag = tdParent.toName(bfd.name + "$set"); + // The @Builder annotation removes the initializing expression on the field and moves + // it to a method called "$default$FIELDNAME". This method is then called upon building. + // We do NOT do this, because this is unexpected and may lead to bugs when using other + // constructors (see, e.g., issue #1347). + // Instead, we keep the init expression and only set a new value in the builder-based + // constructor if it was set in the builder. Drawback is that the init expression is + // always executed, even if it was unnecessary because its value is overwritten by the + // builder. + // TODO: Once the issue is resolved in @Builder, we can adapt the solution here. + } + addObtainVia(bfd, fieldNode); + builderFields.add(bfd); + allFields.append(fieldNode); + } + + // Set the names of the builder classes. + String builderClassName = td.name.toString() + "Builder"; + String builderImplClassName = builderClassName + "Impl"; + JCTree extendsClause = Javac.getExtendsClause(td); + JCExpression superclassBuilderClassExpression = null; + if (extendsClause instanceof JCTypeApply) { + // Remember the type arguments, because we need them for the extends clause of our abstract builder class. + superclassTypeParams = ((JCTypeApply) extendsClause).getTypeArguments(); + // A class name with a generics type, e.g., "Superclass<A>". + extendsClause = ((JCTypeApply) extendsClause).getType(); + } + if (extendsClause instanceof JCFieldAccess) { + Name superclassClassName = ((JCFieldAccess)extendsClause).getIdentifier(); + String superclassBuilderClassName = superclassClassName + "Builder"; + superclassBuilderClassExpression = tdParent.getTreeMaker().Select((JCFieldAccess) extendsClause, + tdParent.toName(superclassBuilderClassName)); + } else if (extendsClause != null) { + String superclassBuilderClassName = extendsClause.toString() + "Builder"; + superclassBuilderClassExpression = chainDots(tdParent, extendsClause.toString(), superclassBuilderClassName); + } + // If there is no superclass, superclassBuilderClassExpression is still == null at this point. + // You can use it to check whether to inherit or not. + + returnType = namePlusTypeParamsToTypeReference(tdParent.getTreeMaker(), td.name, td.typarams); + typeParams = td.typarams; + + // <C, B> are the generics for our builder. + String classGenericName = "C"; + String builderGenericName = "B"; + // If these generics' names collide with any generics on the annotated class, modify them. + // For instance, if there are generics <B, B2, C> on the annotated class, use "C2" and "B3" for our builder. + java.util.List<String> typeParamStrings = new ArrayList<String>(); + for (JCTypeParameter typeParam : typeParams) typeParamStrings.add(typeParam.getName().toString()); + classGenericName = generateNonclashingNameFor(classGenericName, typeParamStrings); + builderGenericName = generateNonclashingNameFor(builderGenericName, typeParamStrings); + + thrownExceptions = List.nil(); + + // Check validity of @ObtainVia fields, and add check if adding cleaning for @Singular is necessary. + for (BuilderFieldData bfd : builderFields) { + if (bfd.singularData != null && bfd.singularData.getSingularizer() != null) { + if (bfd.singularData.getSingularizer().requiresCleaning()) { + addCleaning = true; + break; + } + } + if (bfd.obtainVia != null) { + if (bfd.obtainVia.field().isEmpty() == bfd.obtainVia.method().isEmpty()) { + bfd.obtainViaNode.addError("The syntax is either @ObtainVia(field = \"fieldName\") or @ObtainVia(method = \"methodName\")."); + return; + } + if (bfd.obtainVia.method().isEmpty() && bfd.obtainVia.isStatic()) { + bfd.obtainViaNode.addError("@ObtainVia(isStatic = true) is not valid unless 'method' has been set."); + return; + } + } + } + + // Create the abstract builder class. + JavacNode builderType = findInnerClass(tdParent, builderClassName); + if (builderType == null) { + builderType = generateBuilderAbstractClass(annotationNode, tdParent, builderClassName, superclassBuilderClassExpression, + typeParams, superclassTypeParams, ast, classGenericName, builderGenericName); + } else { + annotationNode.addError("@SuperBuilder does not support customized builders. Use @Builder instead."); + return; + } + + // Generate the fields in the abstract builder class that hold the values for the instance. + generateBuilderFields(builderType, builderFields, ast); + if (addCleaning) { + JavacTreeMaker maker = builderType.getTreeMaker(); + JCVariableDecl uncleanField = maker.VarDef(maker.Modifiers(Flags.PRIVATE), builderType.toName("$lombokUnclean"), maker.TypeIdent(CTC_BOOLEAN), null); + injectFieldAndMarkGenerated(builderType, uncleanField); + } + + // Generate abstract self() and build() methods in the abstract builder. + injectMethod(builderType, generateAbstractSelfMethod(tdParent, superclassBuilderClassExpression != null, builderGenericName)); + injectMethod(builderType, generateAbstractBuildMethod(tdParent, buildMethodName, superclassBuilderClassExpression != null, classGenericName)); + + // Create the setter methods in the abstract builder. + for (BuilderFieldData bfd : builderFields) { + generateSetterMethodsForBuilder(builderType, bfd, annotationNode, builderGenericName); + } + + // Create the toString() method for the abstract builder. + java.util.List<Included<JavacNode, ToString.Include>> fieldNodes = new ArrayList<Included<JavacNode, ToString.Include>>(); + for (BuilderFieldData bfd : builderFields) { + for (JavacNode f : bfd.createdFields) { + fieldNodes.add(new Included<JavacNode, ToString.Include>(f, null, true)); + } + } + + // Let toString() call super.toString() if there is a superclass, so that it also shows fields from the superclass' builder. + JCMethodDecl toStringMethod = HandleToString.createToString(builderType, fieldNodes, true, superclassBuilderClassExpression != null, FieldAccess.ALWAYS_FIELD, ast); + if (toStringMethod != null) injectMethod(builderType, toStringMethod); + + // If clean methods are requested, add them now. + if (addCleaning) injectMethod(builderType, generateCleanMethod(builderFields, builderType, ast)); + + recursiveSetGeneratedBy(builderType.get(), ast, annotationNode.getContext()); + + if ((td.mods.flags & Flags.ABSTRACT) == 0) { + // Only non-abstract classes get the Builder implementation. + + // Create the builder implementation class. + JavacNode builderImplType = findInnerClass(tdParent, builderImplClassName); + if (builderImplType == null) { + builderImplType = generateBuilderImplClass(annotationNode, tdParent, builderImplClassName, builderClassName, typeParams, ast); + } else { + annotationNode.addError("@SuperBuilder does not support customized builders. Use @Builder instead."); + return; + } + + // Create a simple constructor for the BuilderImpl class. + JCMethodDecl cd = HandleConstructor.createConstructor(AccessLevel.PRIVATE, List.<JCAnnotation>nil(), builderImplType, List.<JavacNode>nil(), false, annotationNode); + if (cd != null) injectMethod(builderImplType, cd); + + // Create the self() and build() methods in the BuilderImpl. + injectMethod(builderImplType, generateSelfMethod(builderImplType)); + injectMethod(builderImplType, generateBuildMethod(buildMethodName, returnType, builderImplType, thrownExceptions)); + + recursiveSetGeneratedBy(builderImplType.get(), ast, annotationNode.getContext()); + } + + // Generate a constructor in the annotated class that takes a builder as argument. + generateBuilderBasedConstructor(tdParent, typeParams, builderFields, annotationNode, builderClassName, + superclassBuilderClassExpression != null); + + if ((td.mods.flags & Flags.ABSTRACT) == 0) { + // Only non-abstract classes get the Builder implementation and the builder() method. + + // Add the builder() method to the annotated class. + // Allow users to specify their own builder() methods, e.g., to provide default values. + if (methodExists(builderMethodName, tdParent, -1) == MemberExistsResult.NOT_EXISTS) { + JCMethodDecl builderMethod = generateBuilderMethod(builderMethodName, builderClassName, builderImplClassName, annotationNode, tdParent, typeParams); + recursiveSetGeneratedBy(builderMethod, ast, annotationNode.getContext()); + if (builderMethod != null) injectMethod(tdParent, builderMethod); + } + } + } + + /** + * Creates and returns the abstract builder class and injects it into the annotated class. + */ + private JavacNode generateBuilderAbstractClass(JavacNode source, JavacNode tdParent, String builderClass, + JCExpression superclassBuilderClassExpression, List<JCTypeParameter> typeParams, + List<JCExpression> superclassTypeParams, JCAnnotation ast, String classGenericName, String builderGenericName) { + + JavacTreeMaker maker = tdParent.getTreeMaker(); + JCModifiers mods = maker.Modifiers(Flags.STATIC | Flags.ABSTRACT | Flags.PUBLIC); + + // Keep any type params of the annotated class. + ListBuffer<JCTypeParameter> allTypeParams = new ListBuffer<JCTypeParameter>(); + allTypeParams.addAll(copyTypeParams(source, typeParams)); + // Add builder-specific type params required for inheritable builders. + // 1. The return type for the build() method, named "C", which extends the annotated class. + JCExpression annotatedClass = maker.Ident(tdParent.toName(tdParent.getName())); + if (typeParams.nonEmpty()) { + // Add type params of the annotated class. + annotatedClass = maker.TypeApply(annotatedClass, getTypeParamExpressions(typeParams, maker).toList()); + } + allTypeParams.add(maker.TypeParameter(tdParent.toName(classGenericName), List.<JCExpression>of(annotatedClass))); + // 2. The return type for all setter methods, named "B", which extends this builder class. + Name builderClassName = tdParent.toName(builderClass); + ListBuffer<JCExpression> typeParamsForBuilder = getTypeParamExpressions(typeParams, maker); + typeParamsForBuilder.add(maker.Ident(tdParent.toName(classGenericName))); + typeParamsForBuilder.add(maker.Ident(tdParent.toName(builderGenericName))); + JCTypeApply typeApply = maker.TypeApply(maker.Ident(builderClassName), typeParamsForBuilder.toList()); + allTypeParams.add(maker.TypeParameter(tdParent.toName(builderGenericName), List.<JCExpression>of(typeApply))); + + JCExpression extending = null; + if (superclassBuilderClassExpression != null) { + // If the annotated class extends another class, we want this builder to extend the builder of the superclass. + // 1. Add the type parameters of the superclass. + typeParamsForBuilder = getTypeParamExpressions(superclassTypeParams, maker); + // 2. Add the builder type params <C, B>. + typeParamsForBuilder.add(maker.Ident(tdParent.toName(classGenericName))); + typeParamsForBuilder.add(maker.Ident(tdParent.toName(builderGenericName))); + extending = maker.TypeApply(superclassBuilderClassExpression, typeParamsForBuilder.toList()); + } + + JCClassDecl builder = maker.ClassDef(mods, builderClassName, allTypeParams.toList(), extending, List.<JCExpression>nil(), List.<JCTree>nil()); + return injectType(tdParent, builder); + } + + /** + * Creates and returns the concrete builder implementation class and injects it into the annotated class. + */ + private JavacNode generateBuilderImplClass(JavacNode source, JavacNode tdParent, String builderImplClass, String builderAbstractClass, List<JCTypeParameter> typeParams, JCAnnotation ast) { + JavacTreeMaker maker = tdParent.getTreeMaker(); + JCModifiers mods = maker.Modifiers(Flags.STATIC | Flags.PRIVATE | Flags.FINAL); + + // Extend the abstract builder. + JCExpression extending = maker.Ident(tdParent.toName(builderAbstractClass)); + // Add any type params of the annotated class. + ListBuffer<JCTypeParameter> allTypeParams = new ListBuffer<JCTypeParameter>(); + allTypeParams.addAll(copyTypeParams(source, typeParams)); + // Add builder-specific type params required for inheritable builders. + // 1. The return type for the build() method (named "C" in the abstract builder), which is the annotated class. + JCExpression annotatedClass = maker.Ident(tdParent.toName(tdParent.getName())); + if (typeParams.nonEmpty()) { + // Add type params of the annotated class. + annotatedClass = maker.TypeApply(annotatedClass, getTypeParamExpressions(typeParams, maker).toList()); + } + // 2. The return type for all setter methods (named "B" in the abstract builder), which is this builder class. + JCExpression builderImplClassExpression = maker.Ident(tdParent.toName(builderImplClass)); + if (typeParams.nonEmpty()) { + builderImplClassExpression = maker.TypeApply(builderImplClassExpression, getTypeParamExpressions(typeParams, maker).toList()); + } + ListBuffer<JCExpression> typeParamsForBuilder = getTypeParamExpressions(typeParams, maker); + typeParamsForBuilder.add(annotatedClass); + typeParamsForBuilder.add(builderImplClassExpression); + extending = maker.TypeApply(extending, typeParamsForBuilder.toList()); + + JCClassDecl builder = maker.ClassDef(mods, tdParent.toName(builderImplClass), copyTypeParams(source, typeParams), extending, List.<JCExpression>nil(), List.<JCTree>nil()); + return injectType(tdParent, builder); + } + + /** + * Generates a constructor that has a builder as the only parameter. + * The values from the builder are used to initialize the fields of new instances. + * + * @param typeNode + * the type (with the {@code @Builder} annotation) for which a + * constructor should be generated. + * @param typeParams + * @param builderFields a list of fields in the builder which should be assigned to new instances. + * @param source the annotation (used for setting source code locations for the generated code). + * @param callBuilderBasedSuperConstructor + * If {@code true}, the constructor will explicitly call a super + * constructor with the builder as argument. Requires + * {@code builderClassAsParameter != null}. + */ + private void generateBuilderBasedConstructor(JavacNode typeNode, List<JCTypeParameter> typeParams, java.util.List<BuilderFieldData> builderFields, JavacNode source, String builderClassName, boolean callBuilderBasedSuperConstructor) { + JavacTreeMaker maker = typeNode.getTreeMaker(); + + AccessLevel level = AccessLevel.PROTECTED; + + ListBuffer<JCStatement> nullChecks = new ListBuffer<JCStatement>(); + ListBuffer<JCStatement> statements = new ListBuffer<JCStatement>(); + + Name builderVariableName = typeNode.toName("b"); + for (BuilderFieldData bfd : builderFields) { + List<JCAnnotation> nonNulls = findAnnotations(bfd.originalFieldNode, NON_NULL_PATTERN); + if (!nonNulls.isEmpty()) { + JCStatement nullCheck = generateNullCheck(maker, bfd.originalFieldNode, source); + if (nullCheck != null) nullChecks.append(nullCheck); + } + + JCExpression rhs; + if (bfd.singularData != null && bfd.singularData.getSingularizer() != null) { + bfd.singularData.getSingularizer().appendBuildCode(bfd.singularData, bfd.originalFieldNode, bfd.type, statements, bfd.name, "b"); + rhs = maker.Ident(bfd.singularData.getPluralName()); + } else { + rhs = maker.Select(maker.Ident(builderVariableName), bfd.rawName); + } + JCFieldAccess thisX = maker.Select(maker.Ident(typeNode.toName("this")), bfd.rawName); + + JCStatement assign = maker.Exec(maker.Assign(thisX, rhs)); + + // In case of @Builder.Default, only set the value if it really was set in the builder. + if (bfd.nameOfSetFlag != null) { + JCFieldAccess setField = maker.Select(maker.Ident(builderVariableName), bfd.nameOfSetFlag); + JCIf ifSet = maker.If(setField, assign, null); + statements.append(ifSet); + } else { + statements.append(assign); + } + } + + JCModifiers mods = maker.Modifiers(toJavacModifier(level), List.<JCAnnotation>nil()); + + // Create a constructor that has just the builder as parameter. + ListBuffer<JCVariableDecl> params = new ListBuffer<JCVariableDecl>(); + long flags = JavacHandlerUtil.addFinalIfNeeded(Flags.PARAMETER, typeNode.getContext()); + Name builderClassname = typeNode.toName(builderClassName); + // First add all generics that are present on the parent type. + ListBuffer<JCExpression> typeParamsForBuilderParameter = getTypeParamExpressions(typeParams, maker); + // Now add the <?, ?>. + JCWildcard wildcard = maker.Wildcard(maker.TypeBoundKind(BoundKind.UNBOUND), null); + typeParamsForBuilderParameter.add(wildcard); + wildcard = maker.Wildcard(maker.TypeBoundKind(BoundKind.UNBOUND), null); + typeParamsForBuilderParameter.add(wildcard); + JCTypeApply paramType = maker.TypeApply(maker.Ident(builderClassname), typeParamsForBuilderParameter.toList()); + JCVariableDecl param = maker.VarDef(maker.Modifiers(flags), builderVariableName, paramType, null); + params.append(param); + + if (callBuilderBasedSuperConstructor) { + // The first statement must be the call to the super constructor. + JCMethodInvocation callToSuperConstructor = maker.Apply(List.<JCExpression>nil(), + maker.Ident(typeNode.toName("super")), + List.<JCExpression>of(maker.Ident(builderVariableName))); + statements.prepend(maker.Exec(callToSuperConstructor)); + } + + JCMethodDecl constr = recursiveSetGeneratedBy(maker.MethodDef(mods, typeNode.toName("<init>"), + null, List.<JCTypeParameter>nil(), params.toList(), List.<JCExpression>nil(), + maker.Block(0L, nullChecks.appendList(statements).toList()), null), source.get(), typeNode.getContext()); + + injectMethod(typeNode, constr, null, Javac.createVoidType(typeNode.getSymbolTable(), CTC_VOID)); + } + + private JCMethodDecl generateBuilderMethod(String builderMethodName, String builderClassName, String builderImplClassName, JavacNode source, JavacNode type, List<JCTypeParameter> typeParams) { + JavacTreeMaker maker = type.getTreeMaker(); + + ListBuffer<JCExpression> typeArgs = new ListBuffer<JCExpression>(); + for (JCTypeParameter typeParam : typeParams) typeArgs.append(maker.Ident(typeParam.name)); + + JCExpression call = maker.NewClass(null, List.<JCExpression>nil(), namePlusTypeParamsToTypeReference(maker, type.toName(builderImplClassName), typeParams), List.<JCExpression>nil(), null); + JCStatement statement = maker.Return(call); + + JCBlock body = maker.Block(0, List.<JCStatement>of(statement)); + int modifiers = Flags.PUBLIC; + modifiers |= Flags.STATIC; + + // Add any type params of the annotated class to the return type. + ListBuffer<JCExpression> typeParameterNames = new ListBuffer<JCExpression>(); + typeParameterNames.addAll(typeParameterNames(maker, typeParams)); + // Now add the <?, ?>. + JCWildcard wildcard = maker.Wildcard(maker.TypeBoundKind(BoundKind.UNBOUND), null); + typeParameterNames.add(wildcard); + typeParameterNames.add(wildcard); + JCTypeApply returnType = maker.TypeApply(maker.Ident(type.toName(builderClassName)), typeParameterNames.toList()); + + return maker.MethodDef(maker.Modifiers(modifiers), type.toName(builderMethodName), returnType, copyTypeParams(source, typeParams), List.<JCVariableDecl>nil(), List.<JCExpression>nil(), body, null); + } + + private JCMethodDecl generateAbstractSelfMethod(JavacNode type, boolean override, String builderGenericName) { + JavacTreeMaker maker = type.getTreeMaker(); + List<JCAnnotation> annotations = List.nil(); + if (override) { + JCAnnotation overrideAnnotation = maker.Annotation(genJavaLangTypeRef(type, "Override"), List.<JCExpression>nil()); + annotations = List.of(overrideAnnotation); + } + JCModifiers modifiers = maker.Modifiers(Flags.PROTECTED | Flags.ABSTRACT, annotations); + Name name = type.toName(SELF_METHOD); + JCExpression returnType = maker.Ident(type.toName(builderGenericName)); + + return maker.MethodDef(modifiers, name, returnType, List.<JCTypeParameter>nil(), List.<JCVariableDecl>nil(), List.<JCExpression>nil(), null, null); + } + + private JCMethodDecl generateSelfMethod(JavacNode builderImplType) { + JavacTreeMaker maker = builderImplType.getTreeMaker(); + + JCAnnotation overrideAnnotation = maker.Annotation(genJavaLangTypeRef(builderImplType, "Override"), List.<JCExpression>nil()); + JCModifiers modifiers = maker.Modifiers(Flags.PROTECTED, List.of(overrideAnnotation)); + Name name = builderImplType.toName(SELF_METHOD); + JCExpression returnType = maker.Ident(builderImplType.toName(builderImplType.getName())); + + JCStatement statement = maker.Return(maker.Ident(builderImplType.toName("this"))); + JCBlock body = maker.Block(0, List.<JCStatement>of(statement)); + + return maker.MethodDef(modifiers, name, returnType, List.<JCTypeParameter>nil(), List.<JCVariableDecl>nil(), List.<JCExpression>nil(), body, null); + } + + private JCMethodDecl generateAbstractBuildMethod(JavacNode type, String methodName, boolean override, String classGenericName) { + JavacTreeMaker maker = type.getTreeMaker(); + List<JCAnnotation> annotations = List.nil(); + if (override) { + JCAnnotation overrideAnnotation = maker.Annotation(genJavaLangTypeRef(type, "Override"), List.<JCExpression>nil()); + annotations = List.of(overrideAnnotation); + } + JCModifiers modifiers = maker.Modifiers(Flags.PUBLIC | Flags.ABSTRACT, annotations); + Name name = type.toName(methodName); + JCExpression returnType = maker.Ident(type.toName(classGenericName)); + + return maker.MethodDef(modifiers, name, returnType, List.<JCTypeParameter>nil(), List.<JCVariableDecl>nil(), List.<JCExpression>nil(), null, null); + } + + private JCMethodDecl generateBuildMethod(String buildName, JCExpression returnType, JavacNode type, List<JCExpression> thrownExceptions) { + JavacTreeMaker maker = type.getTreeMaker(); + + JCExpression call; + ListBuffer<JCStatement> statements = new ListBuffer<JCStatement>(); + + // Use a constructor that only has this builder as parameter. + List<JCExpression> builderArg = List.<JCExpression>of(maker.Ident(type.toName("this"))); + call = maker.NewClass(null, List.<JCExpression>nil(), returnType, builderArg, null); + statements.append(maker.Return(call)); + + JCBlock body = maker.Block(0, statements.toList()); + + JCAnnotation overrideAnnotation = maker.Annotation(genJavaLangTypeRef(type, "Override"), List.<JCExpression>nil()); + JCModifiers modifiers = maker.Modifiers(Flags.PUBLIC, List.of(overrideAnnotation)); + + return maker.MethodDef(modifiers, type.toName(buildName), returnType, List.<JCTypeParameter>nil(), List.<JCVariableDecl>nil(), thrownExceptions, body, null); + } + + private JCMethodDecl generateCleanMethod(java.util.List<BuilderFieldData> builderFields, JavacNode type, JCTree source) { + JavacTreeMaker maker = type.getTreeMaker(); + ListBuffer<JCStatement> statements = new ListBuffer<JCStatement>(); + + for (BuilderFieldData bfd : builderFields) { + if (bfd.singularData != null && bfd.singularData.getSingularizer() != null) { + bfd.singularData.getSingularizer().appendCleaningCode(bfd.singularData, type, source, statements); + } + } + + statements.append(maker.Exec(maker.Assign(maker.Select(maker.Ident(type.toName("this")), type.toName("$lombokUnclean")), maker.Literal(CTC_BOOLEAN, 0)))); + JCBlock body = maker.Block(0, statements.toList()); + return maker.MethodDef(maker.Modifiers(Flags.PUBLIC), type.toName("$lombokClean"), maker.Type(Javac.createVoidType(type.getSymbolTable(), CTC_VOID)), List.<JCTypeParameter>nil(), List.<JCVariableDecl>nil(), List.<JCExpression>nil(), body, null); + } + + private void generateBuilderFields(JavacNode builderType, java.util.List<BuilderFieldData> builderFields, JCTree source) { + int len = builderFields.size(); + java.util.List<JavacNode> existing = new ArrayList<JavacNode>(); + for (JavacNode child : builderType.down()) { + if (child.getKind() == Kind.FIELD) existing.add(child); + } + + for (int i = len - 1; i >= 0; i--) { + BuilderFieldData bfd = builderFields.get(i); + if (bfd.singularData != null && bfd.singularData.getSingularizer() != null) { + bfd.createdFields.addAll(bfd.singularData.getSingularizer().generateFields(bfd.singularData, builderType, source)); + } else { + JavacNode field = null, setFlag = null; + for (JavacNode exists : existing) { + Name n = ((JCVariableDecl) exists.get()).name; + if (n.equals(bfd.name)) field = exists; + if (n.equals(bfd.nameOfSetFlag)) setFlag = exists; + } + JavacTreeMaker maker = builderType.getTreeMaker(); + if (field == null) { + JCModifiers mods = maker.Modifiers(Flags.PRIVATE); + JCVariableDecl newField = maker.VarDef(mods, bfd.name, cloneType(maker, bfd.type, source, builderType.getContext()), null); + field = injectFieldAndMarkGenerated(builderType, newField); + } + if (setFlag == null && bfd.nameOfSetFlag != null) { + JCModifiers mods = maker.Modifiers(Flags.PRIVATE); + JCVariableDecl newField = maker.VarDef(mods, bfd.nameOfSetFlag, maker.TypeIdent(CTC_BOOLEAN), null); + injectFieldAndMarkGenerated(builderType, newField); + } + bfd.createdFields.add(field); + } + } + } + + private void generateSetterMethodsForBuilder(final JavacNode builderType, BuilderFieldData fieldNode, JavacNode source, final String builderGenericName) { + boolean deprecate = isFieldDeprecated(fieldNode.originalFieldNode); + final JavacTreeMaker maker = builderType.getTreeMaker(); + ExpressionMaker returnTypeMaker = new ExpressionMaker() { @Override public JCExpression make() { + return maker.Ident(builderType.toName(builderGenericName)); + }}; + + StatementMaker returnStatementMaker = new StatementMaker() { @Override public JCStatement make() { + return maker.Return(maker.Apply(List.<JCExpression>nil(), maker.Ident(builderType.toName(SELF_METHOD)), List.<JCExpression>nil())); + }}; + + if (fieldNode.singularData == null || fieldNode.singularData.getSingularizer() == null) { + generateSimpleSetterMethodForBuilder(builderType, deprecate, fieldNode.createdFields.get(0), fieldNode.nameOfSetFlag, source, true, true, returnTypeMaker.make(), returnStatementMaker.make()); + } else { + fieldNode.singularData.getSingularizer().generateMethods(fieldNode.singularData, deprecate, builderType, source.get(), true, returnTypeMaker, returnStatementMaker); + } + } + + private void generateSimpleSetterMethodForBuilder(JavacNode builderType, boolean deprecate, JavacNode fieldNode, Name nameOfSetFlag, JavacNode source, boolean fluent, boolean chain, JCExpression returnType, JCStatement returnStatement) { + Name fieldName = ((JCVariableDecl) fieldNode.get()).name; + + for (JavacNode child : builderType.down()) { + if (child.getKind() != Kind.METHOD) continue; + JCMethodDecl methodDecl = (JCMethodDecl) child.get(); + Name existingName = methodDecl.name; + if (existingName.equals(fieldName) && !isTolerate(fieldNode, methodDecl)) return; + } + + String setterName = fluent ? fieldNode.getName() : HandlerUtil.buildAccessorName("set", fieldNode.getName()); + + JavacTreeMaker maker = fieldNode.getTreeMaker(); + + JCMethodDecl newMethod = HandleSetter.createSetter(Flags.PUBLIC, deprecate, fieldNode, maker, setterName, nameOfSetFlag, returnType, returnStatement, source, List.<JCAnnotation>nil(), List.<JCAnnotation>nil()); + + injectMethod(builderType, newMethod); + } + + private void addObtainVia(BuilderFieldData bfd, JavacNode node) { + for (JavacNode child : node.down()) { + if (!annotationTypeMatches(ObtainVia.class, child)) continue; + AnnotationValues<ObtainVia> ann = createAnnotation(ObtainVia.class, child); + bfd.obtainVia = ann.getInstance(); + bfd.obtainViaNode = child; + deleteAnnotationIfNeccessary(child, ObtainVia.class); + return; + } + } + + /** + * Returns the explicitly requested singular annotation on this node (field + * or parameter), or null if there's no {@code @Singular} annotation on it. + * + * @param node The node (field or method param) to inspect for its name and potential {@code @Singular} annotation. + */ + private SingularData getSingularData(JavacNode node) { + for (JavacNode child : node.down()) { + if (!annotationTypeMatches(Singular.class, child)) continue; + Name pluralName = node.getKind() == Kind.FIELD ? removePrefixFromField(node) : ((JCVariableDecl) node.get()).name; + AnnotationValues<Singular> ann = createAnnotation(Singular.class, child); + deleteAnnotationIfNeccessary(child, Singular.class); + String explicitSingular = ann.getInstance().value(); + if (explicitSingular.isEmpty()) { + if (Boolean.FALSE.equals(node.getAst().readConfiguration(ConfigurationKeys.SINGULAR_AUTO))) { + node.addError("The singular must be specified explicitly (e.g. @Singular(\"task\")) because auto singularization is disabled."); + explicitSingular = pluralName.toString(); + } else { + explicitSingular = autoSingularize(pluralName.toString()); + if (explicitSingular == null) { + node.addError("Can't singularize this name; please specify the singular explicitly (i.e. @Singular(\"sheep\"))"); + explicitSingular = pluralName.toString(); + } + } + } + Name singularName = node.toName(explicitSingular); + + JCExpression type = null; + if (node.get() instanceof JCVariableDecl) type = ((JCVariableDecl) node.get()).vartype; + + String name = null; + List<JCExpression> typeArgs = List.nil(); + if (type instanceof JCTypeApply) { + typeArgs = ((JCTypeApply) type).arguments; + type = ((JCTypeApply) type).clazz; + } + + name = type.toString(); + + String targetFqn = JavacSingularsRecipes.get().toQualified(name); + JavacSingularizer singularizer = JavacSingularsRecipes.get().getSingularizer(targetFqn); + if (singularizer == null) { + node.addError("Lombok does not know how to create the singular-form builder methods for type '" + name + "'; they won't be generated."); + return null; + } + + return new SingularData(child, singularName, pluralName, typeArgs, targetFqn, singularizer); + } + + return null; + } + + private String generateNonclashingNameFor(String classGenericName, java.util.List<String> typeParamStrings) { + if (!typeParamStrings.contains(classGenericName)) return classGenericName; + int counter = 2; + while (typeParamStrings.contains(classGenericName + counter)) counter++; + return classGenericName + counter; + } + + private JavacNode findInnerClass(JavacNode parent, String name) { + for (JavacNode child : parent.down()) { + if (child.getKind() != Kind.TYPE) continue; + JCClassDecl td = (JCClassDecl) child.get(); + if (td.name.contentEquals(name)) return child; + } + return null; + } + + private ListBuffer<JCExpression> getTypeParamExpressions(List<? extends JCTree> typeParams, JavacTreeMaker maker) { + ListBuffer<JCExpression> typeParamsForBuilderParameter = new ListBuffer<JCExpression>(); + for (JCTree typeParam : typeParams) { + if (typeParam instanceof JCTypeParameter) { + typeParamsForBuilderParameter.add(maker.Ident(((JCTypeParameter)typeParam).getName())); + } else if (typeParam instanceof JCIdent) { + typeParamsForBuilderParameter.add(maker.Ident(((JCIdent)typeParam).getName())); + } + } + return typeParamsForBuilderParameter; + } +} diff --git a/src/core/lombok/javac/handlers/HandleToString.java b/src/core/lombok/javac/handlers/HandleToString.java index 743e7b26..28d18357 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.Included; 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<Included<JavacNode, ToString.Include>> 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,26 +89,31 @@ public class HandleToString extends JavacAnnotationHandler<ToString> { return; } - boolean includeFieldNames = true; try { Boolean configuration = typeNode.getAst().readConfiguration(ConfigurationKeys.TO_STRING_INCLUDE_FIELD_NAMES); includeFieldNames = configuration != null ? configuration : ((Boolean)ToString.class.getMethod("includeFieldNames").getDefaultValue()).booleanValue(); } catch (Exception ignore) {} - generateToString(typeNode, errorNode, null, null, includeFieldNames, null, false, FieldAccess.GETTER); + + Boolean doNotUseGettersConfiguration = typeNode.getAst().readConfiguration(ConfigurationKeys.TO_STRING_DO_NOT_USE_GETTERS); + FieldAccess access = doNotUseGettersConfiguration == null || !doNotUseGettersConfiguration ? FieldAccess.GETTER : FieldAccess.PREFER_FIELD; + + java.util.List<Included<JavacNode, ToString.Include>> 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<Included<JavacNode, ToString.Include>> 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) {} } @@ -139,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: @@ -176,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<Included<JavacNode, ToString.Include>> members, + boolean includeNames, boolean callSuper, FieldAccess fieldAccess, JCTree source) { + JavacTreeMaker maker = typeNode.getTreeMaker(); JCAnnotation overrideAnnotation = maker.Annotation(genJavaLangTypeRef(typeNode, "Override"), List.<JCExpression>nil()); @@ -191,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) { + Included<JavacNode, ToString.Include> 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 + "("; } @@ -203,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 (Included<JavacNode, ToString.Include> 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); @@ -234,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)); } @@ -250,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/HandleUtilityClass.java b/src/core/lombok/javac/handlers/HandleUtilityClass.java index 010c05a5..ee8081d6 100644 --- a/src/core/lombok/javac/handlers/HandleUtilityClass.java +++ b/src/core/lombok/javac/handlers/HandleUtilityClass.java @@ -21,19 +21,14 @@ */ package lombok.javac.handlers; -import static lombok.core.handlers.HandlerUtil.*; +import static lombok.core.handlers.HandlerUtil.handleExperimentalFlagUsage; +import static lombok.javac.Javac.CTC_VOID; import static lombok.javac.handlers.JavacHandlerUtil.*; -import lombok.ConfigurationKeys; -import lombok.core.AST.Kind; -import lombok.core.AnnotationValues; -import lombok.experimental.UtilityClass; -import lombok.javac.JavacAnnotationHandler; -import lombok.javac.JavacNode; -import lombok.javac.JavacTreeMaker; import org.mangosdk.spi.ProviderFor; import com.sun.tools.javac.code.Flags; +import com.sun.tools.javac.code.Type; import com.sun.tools.javac.tree.JCTree.JCAnnotation; import com.sun.tools.javac.tree.JCTree.JCBlock; import com.sun.tools.javac.tree.JCTree.JCClassDecl; @@ -46,9 +41,20 @@ import com.sun.tools.javac.tree.JCTree.JCVariableDecl; import com.sun.tools.javac.util.List; import com.sun.tools.javac.util.Name; +import lombok.ConfigurationKeys; +import lombok.core.AST.Kind; +import lombok.core.AnnotationValues; +import lombok.core.HandlerPriority; +import lombok.experimental.UtilityClass; +import lombok.javac.Javac; +import lombok.javac.JavacAnnotationHandler; +import lombok.javac.JavacNode; +import lombok.javac.JavacTreeMaker; + /** * Handles the {@code @UtilityClass} annotation for javac. */ +@HandlerPriority(-4096) //-2^12; to ensure @FieldDefaults picks up on the 'static' we set here. @ProviderFor(JavacAnnotationHandler.class) public class HandleUtilityClass extends JavacAnnotationHandler<UtilityClass> { @Override public void handle(AnnotationValues<UtilityClass> annotation, JCAnnotation ast, JavacNode annotationNode) { @@ -141,7 +147,7 @@ public class HandleUtilityClass extends JavacAnnotationHandler<UtilityClass> { JCBlock block = maker.Block(0L, createThrowStatement(typeNode, maker)); JCMethodDecl methodDef = maker.MethodDef(mods, name, null, List.<JCTypeParameter>nil(), List.<JCVariableDecl>nil(), List.<JCExpression>nil(), block, null); JCMethodDecl constructor = recursiveSetGeneratedBy(methodDef, typeNode.get(), typeNode.getContext()); - JavacHandlerUtil.injectMethod(typeNode, constructor); + JavacHandlerUtil.injectMethod(typeNode, constructor, List.<Type>nil(), Javac.createVoidType(typeNode.getSymbolTable(), CTC_VOID)); } private List<JCStatement> createThrowStatement(JavacNode typeNode, JavacTreeMaker maker) { diff --git a/src/core/lombok/javac/handlers/HandleVal.java b/src/core/lombok/javac/handlers/HandleVal.java index 337ab2d7..14130bc4 100644 --- a/src/core/lombok/javac/handlers/HandleVal.java +++ b/src/core/lombok/javac/handlers/HandleVal.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2015 The Project Lombok Authors. + * Copyright (C) 2010-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 @@ -21,11 +21,12 @@ */ package lombok.javac.handlers; -import static lombok.core.handlers.HandlerUtil.*; +import static lombok.core.handlers.HandlerUtil.handleFlagUsage; import static lombok.javac.handlers.JavacHandlerUtil.*; import lombok.ConfigurationKeys; import lombok.val; import lombok.core.HandlerPriority; +import lombok.var; import lombok.javac.JavacASTAdapter; import lombok.javac.JavacASTVisitor; import lombok.javac.JavacNode; @@ -42,6 +43,7 @@ import com.sun.tools.javac.tree.JCTree.JCAnnotation; import com.sun.tools.javac.tree.JCTree.JCEnhancedForLoop; import com.sun.tools.javac.tree.JCTree.JCExpression; import com.sun.tools.javac.tree.JCTree.JCForLoop; +import com.sun.tools.javac.tree.JCTree.JCLiteral; import com.sun.tools.javac.tree.JCTree.JCNewArray; import com.sun.tools.javac.tree.JCTree.JCVariableDecl; import com.sun.tools.javac.util.List; @@ -50,21 +52,36 @@ import com.sun.tools.javac.util.List; @HandlerPriority(65536) // 2^16; resolution needs to work, so if the RHS expression is i.e. a call to a generated getter, we have to run after that getter has been generated. @ResolutionResetNeeded public class HandleVal extends JavacASTAdapter { - @Override public void visitLocal(JavacNode localNode, JCVariableDecl local) { + + private static boolean eq(String typeTreeToString, String key) { + return typeTreeToString.equals(key) || typeTreeToString.equals("lombok." + key) || typeTreeToString.equals("lombok.experimental." + key); + } + + @SuppressWarnings("deprecation") @Override + public void visitLocal(JavacNode localNode, JCVariableDecl local) { 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"); + if (!(eq(typeTreeToString, "val") || eq(typeTreeToString, "var"))) return; + boolean isVal = typeMatches(val.class, localNode, typeTree); + boolean isVar = typeMatches(var.class, localNode, typeTree); + if (!(isVal || isVar)) return; + + if (isVal) handleFlagUsage(localNode, ConfigurationKeys.VAL_FLAG_USAGE, "val"); + if (isVar) handleFlagUsage(localNode, ConfigurationKeys.VAR_FLAG_USAGE, "var"); JCTree parentRaw = localNode.directUp().get(); - if (parentRaw instanceof JCForLoop) { + if (isVal && parentRaw instanceof JCForLoop) { localNode.addError("'val' is not allowed in old-style for loops"); return; } + if (parentRaw instanceof JCForLoop && ((JCForLoop) parentRaw).getInitializer().size() > 1) { + localNode.addError("'var' is not allowed in old-style for loops if there is more than 1 initializer"); + return; + } + JCExpression rhsOfEnhancedForLoop = null; if (local.init == null) { if (parentRaw instanceof JCEnhancedForLoop) { @@ -73,19 +90,24 @@ public class HandleVal extends JavacASTAdapter { } } + final String annotation = typeTreeToString; if (rhsOfEnhancedForLoop == null && local.init == null) { - localNode.addError("'val' on a local variable requires an initializer expression"); + localNode.addError("'" + annotation + "' on a local variable requires an initializer expression"); return; } if (local.init instanceof JCNewArray && ((JCNewArray)local.init).elemtype == null) { - localNode.addError("'val' is not compatible with array initializer expressions. Use the full form (new int[] { ... } instead of just { ... })"); + localNode.addError("'" + annotation + "' is not compatible with array initializer expressions. Use the full form (new int[] { ... } instead of just { ... })"); return; } - if (localNode.shouldDeleteLombokAnnotations()) JavacHandlerUtil.deleteImportFromCompilationUnit(localNode, "lombok.val"); + if (localNode.shouldDeleteLombokAnnotations()) { + JavacHandlerUtil.deleteImportFromCompilationUnit(localNode, val.class.getName()); + JavacHandlerUtil.deleteImportFromCompilationUnit(localNode, lombok.experimental.var.class.getName()); + JavacHandlerUtil.deleteImportFromCompilationUnit(localNode, var.class.getName()); + } - local.mods.flags |= Flags.FINAL; + if (isVal) local.mods.flags |= Flags.FINAL; if (!localNode.shouldDeleteLombokAnnotations()) { JCAnnotation valAnnotation = recursiveSetGeneratedBy(localNode.getTreeMaker().Annotation(local.vartype, List.<JCExpression>nil()), typeTree, localNode.getContext()); @@ -102,11 +124,14 @@ public class HandleVal extends JavacASTAdapter { try { if (rhsOfEnhancedForLoop == null) { if (local.init.type == null) { + if (isVar && local.init instanceof JCLiteral && ((JCLiteral) local.init).value == null) { + localNode.addError("variable initializer is 'null'"); + } JavacResolution resolver = new JavacResolution(localNode.getContext()); try { type = ((JCExpression) resolver.resolveMethodMember(localNode).get(local.init)).type; } catch (RuntimeException e) { - System.err.println("Exception while resolving: " + localNode); + System.err.println("Exception while resolving: " + localNode + "(" + localNode.getFileName() + ")"); throw e; } } else { @@ -117,7 +142,7 @@ public class HandleVal extends JavacASTAdapter { local.type = Symtab.instance(localNode.getContext()).unknownType; type = ((JCExpression) resolver.resolveMethodMember(localNode).get(local.init)).type; } catch (RuntimeException e) { - System.err.println("Exception while resolving: " + localNode); + System.err.println("Exception while resolving: " + localNode + "(" + localNode.getFileName() + ")"); throw e; } } @@ -149,7 +174,7 @@ public class HandleVal extends JavacASTAdapter { } localNode.getAst().setChanged(); } catch (JavacResolution.TypeNotConvertibleException e) { - localNode.addError("Cannot use 'val' here because initializer expression does not have a representable type: " + e.getMessage()); + localNode.addError("Cannot use '" + annotation + "' here because initializer expression does not have a representable type: " + e.getMessage()); local.vartype = JavacResolution.createJavaLangObject(localNode.getAst()); } } catch (RuntimeException e) { diff --git a/src/core/lombok/javac/handlers/HandleValue.java b/src/core/lombok/javac/handlers/HandleValue.java index 90f6a98d..abc5a5ca 100644 --- a/src/core/lombok/javac/handlers/HandleValue.java +++ b/src/core/lombok/javac/handlers/HandleValue.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2014 The Project Lombok Authors. + * Copyright (C) 2012-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,8 +24,6 @@ package lombok.javac.handlers; import static lombok.core.handlers.HandlerUtil.*; import static lombok.javac.handlers.JavacHandlerUtil.*; -import java.lang.annotation.Annotation; - import lombok.AccessLevel; import lombok.ConfigurationKeys; import lombok.core.AnnotationValues; @@ -42,6 +40,7 @@ import com.sun.tools.javac.code.Flags; import com.sun.tools.javac.tree.JCTree.JCAnnotation; import com.sun.tools.javac.tree.JCTree.JCClassDecl; import com.sun.tools.javac.tree.JCTree.JCModifiers; +import com.sun.tools.javac.util.List; /** * Handles the {@code lombok.Value} annotation for javac. @@ -49,13 +48,16 @@ import com.sun.tools.javac.tree.JCTree.JCModifiers; @ProviderFor(JavacAnnotationHandler.class) @HandlerPriority(-512) //-2^9; to ensure @EqualsAndHashCode and such pick up on this handler making the class final and messing with the fields' access levels, run earlier. public class HandleValue extends JavacAnnotationHandler<Value> { + private HandleFieldDefaults handleFieldDefaults = new HandleFieldDefaults(); + private HandleConstructor handleConstructor = new HandleConstructor(); + private HandleGetter handleGetter = new HandleGetter(); + private HandleEqualsAndHashCode handleEqualsAndHashCode = new HandleEqualsAndHashCode(); + private HandleToString handleToString = new HandleToString(); + @Override public void handle(AnnotationValues<Value> annotation, JCAnnotation ast, JavacNode annotationNode) { - @SuppressWarnings("deprecation") - Class<? extends Annotation> oldExperimentalValue = lombok.experimental.Value.class; - handleFlagUsage(annotationNode, ConfigurationKeys.VALUE_FLAG_USAGE, "@Value"); - deleteAnnotationIfNeccessary(annotationNode, Value.class, oldExperimentalValue); + deleteAnnotationIfNeccessary(annotationNode, Value.class, "lombok.experimental.Value"); JavacNode typeNode = annotationNode.up(); boolean notAClass = !isClass(typeNode); @@ -73,12 +75,11 @@ public class HandleValue extends JavacAnnotationHandler<Value> { typeNode.rebuild(); } } - new HandleFieldDefaults().generateFieldDefaultsForType(typeNode, annotationNode, AccessLevel.PRIVATE, true, true); - - // TODO move this to the end OR move it to the top in eclipse. - new HandleConstructor().generateAllArgsConstructor(typeNode, AccessLevel.PUBLIC, staticConstructorName, SkipIfConstructorExists.YES, annotationNode); - new HandleGetter().generateGetterForType(typeNode, annotationNode, AccessLevel.PUBLIC, true); - new HandleEqualsAndHashCode().generateEqualsAndHashCodeForType(typeNode, annotationNode); - new HandleToString().generateToStringForType(typeNode, annotationNode); + handleFieldDefaults.generateFieldDefaultsForType(typeNode, annotationNode, AccessLevel.PRIVATE, true, true); + handleConstructor.generateAllArgsConstructor(typeNode, AccessLevel.PUBLIC, staticConstructorName, SkipIfConstructorExists.YES, annotationNode); + handleConstructor.generateExtraNoArgsConstructor(typeNode, annotationNode); + handleGetter.generateGetterForType(typeNode, annotationNode, AccessLevel.PUBLIC, true, List.<JCAnnotation>nil()); + handleEqualsAndHashCode.generateEqualsAndHashCodeForType(typeNode, annotationNode); + handleToString.generateToStringForType(typeNode, annotationNode); } } diff --git a/src/core/lombok/javac/handlers/HandleWither.java b/src/core/lombok/javac/handlers/HandleWither.java index f8b6152a..87f3c16a 100644 --- a/src/core/lombok/javac/handlers/HandleWither.java +++ b/src/core/lombok/javac/handlers/HandleWither.java @@ -36,11 +36,12 @@ 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; import com.sun.tools.javac.code.Flags; +import com.sun.tools.javac.code.Type; +import com.sun.tools.javac.code.Symbol.ClassSymbol; import com.sun.tools.javac.tree.JCTree.JCAnnotation; import com.sun.tools.javac.tree.JCTree.JCBlock; import com.sun.tools.javac.tree.JCTree.JCClassDecl; @@ -129,8 +130,8 @@ public class HandleWither extends JavacAnnotationHandler<Wither> { if (level == AccessLevel.NONE || node == null) return; - List<JCAnnotation> onMethod = unboxAndRemoveAnnotationParameter(ast, "onMethod", "@Wither(onMethod=", annotationNode); - List<JCAnnotation> onParam = unboxAndRemoveAnnotationParameter(ast, "onParam", "@Wither(onParam=", annotationNode); + List<JCAnnotation> onMethod = unboxAndRemoveAnnotationParameter(ast, "onMethod", "@Wither(onMethod", annotationNode); + List<JCAnnotation> onParam = unboxAndRemoveAnnotationParameter(ast, "onParam", "@Wither(onParam", annotationNode); switch (node.getKind()) { case FIELD: @@ -209,7 +210,10 @@ public class HandleWither extends JavacAnnotationHandler<Wither> { long access = toJavacModifier(level); JCMethodDecl createdWither = createWither(access, fieldNode, fieldNode.getTreeMaker(), source, onMethod, onParam, makeAbstract); - injectMethod(typeNode, createdWither); + ClassSymbol sym = ((JCClassDecl) fieldNode.up().get()).sym; + Type returnType = sym == null ? null : sym.type; + + injectMethod(typeNode, createdWither, List.<Type>of(getMirrorForFieldType(fieldNode)), returnType); } public JCMethodDecl createWither(long access, JavacNode field, JavacTreeMaker maker, JavacNode source, List<JCAnnotation> onMethod, List<JCAnnotation> onParam, boolean makeAbstract) { diff --git a/src/core/lombok/javac/handlers/JavacHandlerUtil.java b/src/core/lombok/javac/handlers/JavacHandlerUtil.java index fdc8a262..f335cf94 100644 --- a/src/core/lombok/javac/handlers/JavacHandlerUtil.java +++ b/src/core/lombok/javac/handlers/JavacHandlerUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2015 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 @@ -37,6 +37,8 @@ import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; +import javax.lang.model.element.Element; + import lombok.AccessLevel; import lombok.ConfigurationKeys; import lombok.Data; @@ -59,7 +61,12 @@ import com.sun.tools.javac.code.BoundKind; import com.sun.tools.javac.code.Flags; import com.sun.tools.javac.code.Scope; import com.sun.tools.javac.code.Symbol; +import com.sun.tools.javac.code.Symtab; +import com.sun.tools.javac.code.Type; import com.sun.tools.javac.code.Symbol.ClassSymbol; +import com.sun.tools.javac.code.Symbol.MethodSymbol; +import com.sun.tools.javac.code.Symbol.VarSymbol; +import com.sun.tools.javac.code.Type.MethodType; import com.sun.tools.javac.parser.Tokens.Comment; import com.sun.tools.javac.tree.DocCommentTable; import com.sun.tools.javac.tree.JCTree; @@ -85,6 +92,7 @@ import com.sun.tools.javac.tree.JCTree.JCTypeParameter; import com.sun.tools.javac.tree.JCTree.JCVariableDecl; import com.sun.tools.javac.tree.JCTree.JCWildcard; import com.sun.tools.javac.tree.JCTree.TypeBoundKind; +import com.sun.tools.javac.tree.TreeMaker; import com.sun.tools.javac.tree.TreeScanner; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; @@ -154,6 +162,10 @@ public class JavacHandlerUtil { return node; } + public static boolean hasAnnotation(String type, JavacNode node) { + return hasAnnotation(type, node, false); + } + public static boolean hasAnnotation(Class<? extends Annotation> type, JavacNode node) { return hasAnnotation(type, node, false); } @@ -183,6 +195,52 @@ public class JavacHandlerUtil { } } + private static boolean hasAnnotation(String type, JavacNode node, boolean delete) { + if (node == null) return false; + if (type == null) return false; + switch (node.getKind()) { + case ARGUMENT: + case FIELD: + case LOCAL: + case TYPE: + case METHOD: + for (JavacNode child : node.down()) { + if (annotationTypeMatches(type, child)) { + if (delete) deleteAnnotationIfNeccessary(child, type); + return true; + } + } + // intentional fallthrough + default: + return false; + } + } + + 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()) { + case ARGUMENT: + case FIELD: + case LOCAL: + case TYPE: + case METHOD: + for (JavacNode child : node.down()) { + if (annotationTypeMatches(type, child)) { + if (delete) deleteAnnotationIfNeccessary(child, type); + return child; + } + } + // intentional fallthrough + default: + return null; + } + } + /** * Checks if the Annotation AST Node provided is likely to be an instance of the provided annotation type. * @@ -195,6 +253,17 @@ public class JavacHandlerUtil { } /** + * Checks if the Annotation AST Node provided is likely to be an instance of the provided annotation type. + * + * @param type An actual annotation type, such as {@code lombok.Getter.class}. + * @param node A Lombok AST node representing an annotation in source code. + */ + public static boolean annotationTypeMatches(String type, JavacNode node) { + if (node.getKind() != Kind.ANNOTATION) return false; + return typeMatches(type, node, ((JCAnnotation)node.get()).annotationType); + } + + /** * Checks if the given TypeReference node is likely to be a reference to the provided class. * * @param type An actual type. This method checks if {@code typeNode} is likely to be a reference to this type. @@ -202,10 +271,21 @@ public class JavacHandlerUtil { * @param typeNode A type reference to check. */ public static boolean typeMatches(Class<?> type, JavacNode node, JCTree typeNode) { + return typeMatches(type.getName(), node, typeNode); + } + + /** + * Checks if the given TypeReference node is likely to be a reference to the provided class. + * + * @param type An actual type. This method checks if {@code typeNode} is likely to be a reference to this type. + * @param node A Lombok AST node. Any node in the appropriate compilation unit will do (used to get access to import statements). + * @param typeNode A type reference to check. + */ + public static boolean typeMatches(String type, JavacNode node, JCTree typeNode) { String typeName = typeNode.toString(); TypeResolver resolver = new TypeResolver(node.getImportList()); - return resolver.typeMatches(node, type.getName(), typeName); + return resolver.typeMatches(node, type, typeName); } /** @@ -214,6 +294,7 @@ public class JavacHandlerUtil { * @return {@code true} if a field is marked deprecated, either by {@code @Deprecated} or in javadoc, otherwise {@code false} */ public static boolean isFieldDeprecated(JavacNode field) { + if (!(field.get() instanceof JCVariableDecl)) return false; JCVariableDecl fieldNode = (JCVariableDecl) field.get(); if ((fieldNode.mods.flags & Flags.DEPRECATED) != 0) { return true; @@ -244,8 +325,19 @@ public class JavacHandlerUtil { * @param node A Lombok AST node representing an annotation in source code. */ public static <A extends Annotation> AnnotationValues<A> createAnnotation(Class<A> type, final JavacNode node) { + return createAnnotation(type, (JCAnnotation) node.get(), node); + } + + /** + * Creates an instance of {@code AnnotationValues} for the provided AST Node + * and Annotation expression. + * + * @param type An annotation class type, such as {@code lombok.Getter.class}. + * @param anno the annotation expression + * @param node A Lombok AST node representing an annotation in source code. + */ + public static <A extends Annotation> AnnotationValues<A> createAnnotation(Class<A> type, JCAnnotation anno, final JavacNode node) { Map<String, AnnotationValue> values = new HashMap<String, AnnotationValue>(); - JCAnnotation anno = (JCAnnotation) node.get(); List<JCExpression> arguments = anno.getArguments(); for (JCExpression arg : arguments) { @@ -266,17 +358,39 @@ public class JavacHandlerUtil { } if (rhs instanceof JCNewArray) { - List<JCExpression> elems = ((JCNewArray)rhs).elems; + List<JCExpression> elems = ((JCNewArray) rhs).elems; for (JCExpression inner : elems) { raws.add(inner.toString()); expressions.add(inner); - guesses.add(calculateGuess(inner)); + if (inner instanceof JCAnnotation) { + try { + @SuppressWarnings("unchecked") + Class<A> innerClass = (Class<A>) Class.forName(inner.type.toString()); + + guesses.add(createAnnotation(innerClass, (JCAnnotation) inner, node)); + } catch (ClassNotFoundException ex) { + guesses.add(calculateGuess(inner)); + } + } else { + guesses.add(calculateGuess(inner)); + } positions.add(inner.pos()); } } else { raws.add(rhs.toString()); expressions.add(rhs); - guesses.add(calculateGuess(rhs)); + if (rhs instanceof JCAnnotation) { + try { + @SuppressWarnings("unchecked") + Class<A> innerClass = (Class<A>) Class.forName(rhs.type.toString()); + + guesses.add(createAnnotation(innerClass, (JCAnnotation) rhs, node)); + } catch (ClassNotFoundException ex) { + guesses.add(calculateGuess(rhs)); + } + } else { + guesses.add(calculateGuess(rhs)); + } positions.add(rhs.pos()); } @@ -316,8 +430,7 @@ public class JavacHandlerUtil { * then removes any import statement that imports this exact annotation (not star imports). * Only does this if the DeleteLombokAnnotations class is in the context. */ - @SuppressWarnings("unchecked") - public static void deleteAnnotationIfNeccessary(JavacNode annotation, Class<? extends Annotation> annotationType) { + public static void deleteAnnotationIfNeccessary(JavacNode annotation, String annotationType) { deleteAnnotationIfNeccessary0(annotation, annotationType); } @@ -326,12 +439,29 @@ public class JavacHandlerUtil { * then removes any import statement that imports this exact annotation (not star imports). * Only does this if the DeleteLombokAnnotations class is in the context. */ - @SuppressWarnings("unchecked") + public static void deleteAnnotationIfNeccessary(JavacNode annotation, Class<? extends Annotation> annotationType) { + deleteAnnotationIfNeccessary0(annotation, annotationType.getName()); + } + + /** + * Removes the annotation from javac's AST (it remains in lombok's AST), + * then removes any import statement that imports this exact annotation (not star imports). + * Only does this if the DeleteLombokAnnotations class is in the context. + */ public static void deleteAnnotationIfNeccessary(JavacNode annotation, Class<? extends Annotation> annotationType1, Class<? extends Annotation> annotationType2) { - deleteAnnotationIfNeccessary0(annotation, annotationType1, annotationType2); + deleteAnnotationIfNeccessary0(annotation, annotationType1.getName(), annotationType2.getName()); + } + + /** + * Removes the annotation from javac's AST (it remains in lombok's AST), + * then removes any import statement that imports this exact annotation (not star imports). + * Only does this if the DeleteLombokAnnotations class is in the context. + */ + public static void deleteAnnotationIfNeccessary(JavacNode annotation, Class<? extends Annotation> annotationType1, String annotationType2) { + deleteAnnotationIfNeccessary0(annotation, annotationType1.getName(), annotationType2); } - private static void deleteAnnotationIfNeccessary0(JavacNode annotation, Class<? extends Annotation>... annotationTypes) { + private static void deleteAnnotationIfNeccessary0(JavacNode annotation, String... annotationTypes) { if (inNetbeansEditor(annotation)) return; if (!annotation.shouldDeleteLombokAnnotations()) return; JavacNode parentNode = annotation.directUp(); @@ -360,8 +490,8 @@ public class JavacHandlerUtil { } parentNode.getAst().setChanged(); - for (Class<?> annotationType : annotationTypes) { - deleteImportFromCompilationUnit(annotation, annotationType.getName()); + for (String annotationType : annotationTypes) { + deleteImportFromCompilationUnit(annotation, annotationType); } } @@ -605,10 +735,7 @@ public class JavacHandlerUtil { if (params < minArgs || params > maxArgs) continue; } - List<JCAnnotation> annotations = md.getModifiers().getAnnotations(); - if (annotations != null) for (JCAnnotation anno : annotations) { - if (typeMatches(Tolerate.class, node, anno.getAnnotationType())) continue top; - } + if (isTolerate(node, md)) continue top; return getGeneratedBy(def) == null ? MemberExistsResult.EXISTS_BY_USER : MemberExistsResult.EXISTS_BY_LOMBOK; } @@ -619,6 +746,14 @@ public class JavacHandlerUtil { return MemberExistsResult.NOT_EXISTS; } + public static boolean isTolerate(JavacNode node, JCTree.JCMethodDecl md) { + List<JCAnnotation> annotations = md.getModifiers().getAnnotations(); + if (annotations != null) for (JCTree.JCAnnotation anno : annotations) { + if (typeMatches(Tolerate.class, node, anno.getAnnotationType())) return true; + } + return false; + } + /** * Checks if there is a (non-default) constructor. In case of multiple constructors (overloading), only * the first constructor decides if EXISTS_BY_USER or EXISTS_BY_LOMBOK is returned. @@ -629,15 +764,12 @@ public class JavacHandlerUtil { node = upToTypeNode(node); if (node != null && node.get() instanceof JCClassDecl) { - top: for (JCTree def : ((JCClassDecl)node.get()).defs) { + for (JCTree def : ((JCClassDecl)node.get()).defs) { if (def instanceof JCMethodDecl) { JCMethodDecl md = (JCMethodDecl) def; if (md.name.contentEquals("<init>")) { if ((md.mods.flags & Flags.GENERATEDCONSTR) != 0) continue; - List<JCAnnotation> annotations = md.getModifiers().getAnnotations(); - if (annotations != null) for (JCAnnotation anno : annotations) { - if (typeMatches(Tolerate.class, node, anno.getAnnotationType())) continue top; - } + if (isTolerate(node, md)) continue; return getGeneratedBy(def) == null ? MemberExistsResult.EXISTS_BY_USER : MemberExistsResult.EXISTS_BY_LOMBOK; } } @@ -723,7 +855,7 @@ public class JavacHandlerUtil { // Check if the class has a @Getter annotation. - if (!hasGetterAnnotation && new HandleGetter().fieldQualifiesForGetterGeneration(field)) { + if (!hasGetterAnnotation && HandleGetter.fieldQualifiesForGetterGeneration(field)) { //Check if the class has @Getter or @Data annotation. JavacNode containingType = field.up(); @@ -746,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; @@ -771,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; @@ -813,10 +943,38 @@ 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) { + return createMethodAccessor(maker, method, null); + } + + static JCExpression createMethodAccessor(JavacTreeMaker maker, JavacNode method, JCExpression receiver) { + JCMethodDecl methodDecl = (JCMethodDecl) method.get(); + + if (receiver == null && (methodDecl.mods.flags & Flags.STATIC) == 0) { + receiver = maker.Ident(method.toName("this")); + } else if (receiver == null) { + JavacNode containerNode = method.up(); + if (containerNode != null && containerNode.get() instanceof JCClassDecl) { + JCClassDecl container = (JCClassDecl) method.up().get(); + receiver = maker.Ident(container.name); + } + } + + JCMethodInvocation call = maker.Apply(List.<JCExpression>nil(), + receiver == null ? maker.Ident(methodDecl.name) : maker.Select(receiver, methodDecl.name), List.<JCExpression>nil()); + return call; + } + + public static Type getMirrorForFieldType(JavacNode fieldNode) { + Element fieldElement = fieldNode.getElement(); + if (fieldElement instanceof VarSymbol) return ((VarSymbol) fieldElement).type; + return null; + } + /** * Adds the given new field declaration to the provided type AST Node. * The field carries the @{@link SuppressWarnings}("all") annotation. @@ -879,16 +1037,20 @@ public class JavacHandlerUtil { static class ClassSymbolMembersField { private static final Field membersField; private static final Method removeMethod; + private static final Method enterMethod; static { Field f = null; - Method m = null; + Method r = null; + Method e = null; try { f = ClassSymbol.class.getField("members_field"); - m = f.getType().getMethod("remove", Symbol.class); - } catch (Exception e) {} + r = f.getType().getMethod("remove", Symbol.class); + e = f.getType().getMethod("enter", Symbol.class); + } catch (Exception ex) {} membersField = f; - removeMethod = m; + removeMethod = r; + enterMethod = e; } static void remove(ClassSymbol from, Symbol toRemove) { @@ -899,6 +1061,19 @@ public class JavacHandlerUtil { removeMethod.invoke(scope, toRemove); } catch (Exception e) {} } + + static void enter(ClassSymbol from, Symbol toEnter) { + if (from == null) return; + try { + Scope scope = (Scope) membersField.get(from); + if (scope == null) return; + enterMethod.invoke(scope, toEnter); + } catch (Exception e) {} + } + } + + public static void injectMethod(JavacNode typeNode, JCMethodDecl method) { + injectMethod(typeNode, method, null, null); } /** @@ -907,7 +1082,7 @@ public class JavacHandlerUtil { * * Also takes care of updating the JavacAST. */ - public static void injectMethod(JavacNode typeNode, JCMethodDecl method) { + public static void injectMethod(JavacNode typeNode, JCMethodDecl method, List<Type> paramTypes, Type returnType) { JCClassDecl type = (JCClassDecl) typeNode.get(); if (method.getName().contentEquals("<init>")) { @@ -915,7 +1090,7 @@ public class JavacHandlerUtil { int idx = 0; for (JCTree def : type.defs) { if (def instanceof JCMethodDecl) { - if ((((JCMethodDecl)def).mods.flags & Flags.GENERATEDCONSTR) != 0) { + if ((((JCMethodDecl) def).mods.flags & Flags.GENERATEDCONSTR) != 0) { JavacNode tossMe = typeNode.getNodeFor(def); if (tossMe != null) tossMe.up().removeChild(tossMe); type.defs = addAllButOne(type.defs, idx); @@ -931,9 +1106,18 @@ public class JavacHandlerUtil { addGenerated(method.mods, typeNode, method.pos, getGeneratedBy(method), typeNode.getContext()); type.defs = type.defs.append(method); + fixMethodMirror(typeNode.getContext(), typeNode.getElement(), method.getModifiers().flags, method.getName(), paramTypes, returnType); + typeNode.add(method, Kind.METHOD); } + private static void fixMethodMirror(Context context, Element typeMirror, long access, Name methodName, List<Type> paramTypes, Type returnType) { + if (typeMirror == null || paramTypes == null || returnType == null) return; + ClassSymbol cs = (ClassSymbol) typeMirror; + MethodSymbol methodSymbol = new MethodSymbol(access, methodName, new MethodType(paramTypes, returnType, List.<Type>nil(), Symtab.instance(context).methodClass), cs); + ClassSymbolMembersField.enter(cs, methodSymbol); + } + /** * Adds an inner type (class, interface, enum) to the given type. Cannot inject top-level types. * @@ -997,9 +1181,12 @@ public class JavacHandlerUtil { public static void addGenerated(JCModifiers mods, JavacNode node, int pos, JCTree source, Context context) { if (!LombokOptionsFactory.getDelombokOptions(context).getFormatPreferences().generateGenerated()) return; - if (!Boolean.FALSE.equals(node.getAst().readConfiguration(ConfigurationKeys.ADD_GENERATED_ANNOTATIONS))) { + if (HandlerUtil.shouldAddGenerated(node)) { addAnnotation(mods, node, pos, source, context, "javax.annotation.Generated", node.getTreeMaker().Literal("lombok")); } + if (Boolean.TRUE.equals(node.getAst().readConfiguration(ConfigurationKeys.ADD_LOMBOK_GENERATED_ANNOTATIONS))) { + addAnnotation(mods, node, pos, source, context, "lombok.Generated", null); + } } private static void addAnnotation(JCModifiers mods, JavacNode node, int pos, JCTree source, Context context, String annotationTypeFqn, JCExpression arg) { @@ -1013,12 +1200,16 @@ public class JavacHandlerUtil { for (JCAnnotation ann : mods.annotations) { JCTree annType = ann.getAnnotationType(); - Name lastPart = null; - if (annType instanceof JCIdent) lastPart = ((JCIdent) annType).name; - else if (annType instanceof JCFieldAccess) lastPart = ((JCFieldAccess) annType).name; + if (annType instanceof JCIdent) { + Name lastPart = ((JCIdent) annType).name; + if (lastPart.contentEquals(simpleName)) return; + } - if (lastPart != null && lastPart.contentEquals(simpleName)) return; + if (annType instanceof JCFieldAccess) { + if (annType.toString().equals(annotationTypeFqn)) return; + } } + JavacTreeMaker maker = node.getTreeMaker(); JCExpression annType = isJavaLangBased ? genJavaLangTypeRef(node, simpleName) : chainDotsString(node, annotationTypeFqn); annType.pos = pos; @@ -1140,16 +1331,25 @@ public class JavacHandlerUtil { } /** - * Generates a new statement that checks if the given variable is null, and if so, throws a specified exception with the + * Generates a new statement that checks if the given variable is null, and if so, throws a configured exception with the * variable name as message. - * - * @param exName The name of the exception to throw; normally {@code java.lang.NullPointerException}. */ public static JCStatement generateNullCheck(JavacTreeMaker maker, JavacNode variable, JavacNode source) { + return generateNullCheck(maker, variable, (JCVariableDecl)variable.get(), source); + } + + /** + * Generates a new statement that checks if the given variable is null, and if so, throws a configured exception with the + * variable name as message. + * + * This is a special case method reserved for use when the provided declaration differs from the + * variable's declaration, i.e. in a constructor or setter where the local parameter is named the same but with the prefix + * stripped as a result of @Accessors.prefix. + */ + public static JCStatement generateNullCheck(JavacTreeMaker maker, JavacNode variable, JCVariableDecl varDecl, JavacNode source) { NullCheckExceptionType exceptionType = source.getAst().readConfiguration(ConfigurationKeys.NON_NULL_EXCEPTION_TYPE); if (exceptionType == null) exceptionType = NullCheckExceptionType.NULL_POINTER_EXCEPTION; - JCVariableDecl varDecl = (JCVariableDecl) variable.get(); if (isPrimitive(varDecl.vartype)) return null; Name fieldName = varDecl.name; JCExpression exType = genTypeRef(variable, exceptionType.getExceptionType()); @@ -1191,19 +1391,9 @@ public class JavacHandlerUtil { ListBuffer<JCExpression> params = new ListBuffer<JCExpression>(); ListBuffer<JCAnnotation> result = new ListBuffer<JCAnnotation>(); - try { - for (JCExpression arg : ast.args) { - String argName = "value"; - if (arg instanceof JCAssign) { - JCAssign as = (JCAssign) arg; - argName = as.lhs.toString(); - } - if (!argName.equals(parameterName)) continue; - } - } catch (Exception ignore) {} - outer: for (JCExpression param : ast.args) { + boolean allowRaw; String nameOfParam = "value"; JCExpression valueOfParam = null; if (param instanceof JCAssign) { @@ -1215,6 +1405,15 @@ public class JavacHandlerUtil { valueOfParam = assign.rhs; } + /* strip trailing underscores */ { + int lastIdx; + for (lastIdx = nameOfParam.length() ; lastIdx > 0; lastIdx--) { + if (nameOfParam.charAt(lastIdx - 1) != '_') break; + } + allowRaw = lastIdx < nameOfParam.length(); + nameOfParam = nameOfParam.substring(0, lastIdx); + } + if (!parameterName.equals(nameOfParam)) { params.append(param); continue outer; @@ -1227,75 +1426,109 @@ public class JavacHandlerUtil { String dummyAnnotationName = ((JCAnnotation) valueOfParam).annotationType.toString(); dummyAnnotationName = dummyAnnotationName.replace("_", "").replace("$", "").replace("x", "").replace("X", ""); if (dummyAnnotationName.length() > 0) { - annotationNode.addError("The correct format is " + errorName + "@__({@SomeAnnotation, @SomeOtherAnnotation}))"); - continue outer; - } - for (JCExpression expr : ((JCAnnotation) valueOfParam).args) { - if (expr instanceof JCAssign && ((JCAssign) expr).lhs instanceof JCIdent) { - JCIdent id = (JCIdent) ((JCAssign) expr).lhs; - if ("value".equals(id.name.toString())) { - expr = ((JCAssign) expr).rhs; - } else { - annotationNode.addError("The correct format is " + errorName + "@__({@SomeAnnotation, @SomeOtherAnnotation}))"); - continue outer; - } + if (allowRaw) { + result.append((JCAnnotation) valueOfParam); + } else { + addError(errorName, annotationNode); + continue outer; } - - if (expr instanceof JCAnnotation) { - result.append((JCAnnotation) expr); - } else if (expr instanceof JCNewArray) { - for (JCExpression expr2 : ((JCNewArray) expr).elems) { - if (expr2 instanceof JCAnnotation) { - result.append((JCAnnotation) expr2); + } else { + for (JCExpression expr : ((JCAnnotation) valueOfParam).args) { + if (expr instanceof JCAssign && ((JCAssign) expr).lhs instanceof JCIdent) { + JCIdent id = (JCIdent) ((JCAssign) expr).lhs; + if ("value".equals(id.name.toString())) { + expr = ((JCAssign) expr).rhs; } else { - annotationNode.addError("The correct format is " + errorName + "@__({@SomeAnnotation, @SomeOtherAnnotation}))"); - continue outer; + addError(errorName, annotationNode); } } - } else { - annotationNode.addError("The correct format is " + errorName + "@__({@SomeAnnotation, @SomeOtherAnnotation}))"); - continue outer; + + if (expr instanceof JCAnnotation) { + result.append((JCAnnotation) expr); + } else if (expr instanceof JCNewArray) { + for (JCExpression expr2 : ((JCNewArray) expr).elems) { + if (expr2 instanceof JCAnnotation) { + result.append((JCAnnotation) expr2); + } else { + addError(errorName, annotationNode); + continue outer; + } + } + } else { + addError(errorName, annotationNode); + continue outer; + } } } - } else { - if (valueOfParam instanceof JCNewArray && ((JCNewArray) valueOfParam).elems.isEmpty()) { - // Then we just remove it and move on (it's onMethod={} for example). + } else if (valueOfParam instanceof JCNewArray) { + JCNewArray arr = (JCNewArray) valueOfParam; + if (arr.elems.isEmpty()) { + // Just remove it, this is always fine. + } else if (allowRaw) { + for (JCExpression jce : arr.elems) { + if (jce instanceof JCAnnotation) result.append((JCAnnotation) jce); + else addError(errorName, annotationNode); + } } else { - annotationNode.addError("The correct format is " + errorName + "@__({@SomeAnnotation, @SomeOtherAnnotation}))"); + addError(errorName, annotationNode); } + } else { + addError(errorName, annotationNode); } } ast.args = params.toList(); return result.toList(); } - public static List<JCTypeParameter> copyTypeParams(JavacTreeMaker maker, List<JCTypeParameter> params) { + private static void addError(String errorName, JavacNode node) { + if (node.getLatestJavaSpecSupported() < 8) { + node.addError("The correct format up to JDK7 is " + errorName + "=@__({@SomeAnnotation, @SomeOtherAnnotation}))"); + } else { + node.addError("The correct format for JDK8+ is " + errorName + "_={@SomeAnnotation, @SomeOtherAnnotation})"); + } + } + + public static List<JCTypeParameter> copyTypeParams(JavacNode source, List<JCTypeParameter> params) { if (params == null || params.isEmpty()) return params; ListBuffer<JCTypeParameter> out = new ListBuffer<JCTypeParameter>(); - for (JCTypeParameter tp : params) out.append(maker.TypeParameter(tp.name, tp.bounds)); + JavacTreeMaker maker = source.getTreeMaker(); + Context context = source.getContext(); + for (JCTypeParameter tp : params) { + List<JCExpression> bounds = tp.bounds; + if (bounds != null && !bounds.isEmpty()) { + ListBuffer<JCExpression> boundsCopy = new ListBuffer<JCExpression>(); + for (JCExpression expr : tp.bounds) { + boundsCopy.append(cloneType(maker, expr, source.get(), context)); + } + bounds = boundsCopy.toList(); + } + out.append(maker.TypeParameter(tp.name, bounds)); + } return out.toList(); } public static JCExpression namePlusTypeParamsToTypeReference(JavacTreeMaker maker, Name typeName, List<JCTypeParameter> params) { + if (params.isEmpty()) { + return maker.Ident(typeName); + } + return maker.TypeApply(maker.Ident(typeName), typeParameterNames(maker, params)); + } + + public static List<JCExpression> typeParameterNames(JavacTreeMaker maker, List<JCTypeParameter> params) { ListBuffer<JCExpression> typeArgs = new ListBuffer<JCExpression>(); - - if (!params.isEmpty()) { - for (JCTypeParameter param : params) { - typeArgs.append(maker.Ident(param.name)); - } - - return maker.TypeApply(maker.Ident(typeName), typeArgs.toList()); + for (JCTypeParameter param : params) { + typeArgs.append(maker.Ident(param.name)); } - - return maker.Ident(typeName); + return typeArgs.toList(); } public static void sanityCheckForMethodGeneratingAnnotationsOnBuilderClass(JavacNode typeNode, JavacNode errorNode) { List<String> disallowed = List.nil(); for (JavacNode child : typeNode.down()) { - for (Class<? extends java.lang.annotation.Annotation> annType : INVALID_ON_BUILDERS) { + for (String annType : INVALID_ON_BUILDERS) { if (annotationTypeMatches(annType, child)) { - disallowed = disallowed.append(annType.getSimpleName()); + int lastIndex = annType.lastIndexOf('.'); + disallowed = disallowed.append(lastIndex == -1 ? annType : annType.substring(lastIndex + 1)); } } } @@ -1580,4 +1813,12 @@ public class JavacHandlerUtil { docComments.put(from.get(), filtered[1]); } } + + public static boolean isDirectDescendantOfObject(JavacNode typeNode) { + if (!(typeNode.get() instanceof JCClassDecl)) throw new IllegalArgumentException("not a type node"); + JCTree extending = Javac.getExtendsClause((JCClassDecl)typeNode.get()); + if (extending == null) return true; + String p = extending.toString(); + return p.equals("Object") || p.equals("java.lang.Object"); + } } diff --git a/src/core/lombok/javac/handlers/JavacSingularsRecipes.java b/src/core/lombok/javac/handlers/JavacSingularsRecipes.java index c6d601bd..ab8c3a42 100644 --- a/src/core/lombok/javac/handlers/JavacSingularsRecipes.java +++ b/src/core/lombok/javac/handlers/JavacSingularsRecipes.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 The Project Lombok Authors. + * Copyright (C) 2015-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 @@ -38,9 +38,12 @@ import lombok.javac.JavacTreeMaker; import com.sun.source.tree.Tree.Kind; import com.sun.tools.javac.code.BoundKind; +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.JCExpression; import com.sun.tools.javac.tree.JCTree.JCMethodDecl; +import com.sun.tools.javac.tree.JCTree.JCModifiers; import com.sun.tools.javac.tree.JCTree.JCStatement; import com.sun.tools.javac.tree.JCTree.JCVariableDecl; import com.sun.tools.javac.tree.JCTree.JCWildcard; @@ -50,6 +53,14 @@ import com.sun.tools.javac.util.ListBuffer; import com.sun.tools.javac.util.Name; public class JavacSingularsRecipes { + public interface ExpressionMaker { + JCExpression make(); + } + + public interface StatementMaker { + JCStatement make(); + } + private static final JavacSingularsRecipes INSTANCE = new JavacSingularsRecipes(); private final Map<String, JavacSingularizer> singularizers = new HashMap<String, JavacSingularizer>(); private final TypeLibrary singularizableTypes = new TypeLibrary(); @@ -141,6 +152,11 @@ public class JavacSingularsRecipes { public static abstract class JavacSingularizer { public abstract LombokImmutableList<String> getSupportedTypes(); + protected JCModifiers makeMods(JavacTreeMaker maker, JavacNode node, boolean deprecate) { + if (deprecate) return maker.Modifiers(Flags.PUBLIC, List.<JCAnnotation>of(maker.Annotation(genJavaLangTypeRef(node, "Deprecated"), List.<JCExpression>nil()))); + return maker.Modifiers(Flags.PUBLIC); + } + /** Checks if any of the to-be-generated nodes (fields, methods) already exist. If so, errors on these (singulars don't support manually writing some of it, and returns true). */ public boolean checkForAlreadyExistingNodesAndGenerateError(JavacNode builderType, SingularData data) { for (JavacNode child : builderType.down()) { @@ -186,8 +202,36 @@ public class JavacSingularsRecipes { } public abstract java.util.List<JavacNode> generateFields(SingularData data, JavacNode builderType, JCTree source); - public abstract void generateMethods(SingularData data, JavacNode builderType, JCTree source, boolean fluent, boolean chain); - public abstract void appendBuildCode(SingularData data, JavacNode builderType, JCTree source, ListBuffer<JCStatement> statements, Name targetVariableName); + + /** + * Generates the singular, plural, and clear methods for the given {@link SingularData}. + * Uses the given {@code builderType} as return type if {@code chain == true}, {@code void} otherwise. + * If you need more control over the return type and value, use + * {@link #generateMethods(SingularData, boolean, JavacNode, JCTree, boolean, ExpressionMaker, StatementMaker)}. + */ + public void generateMethods(SingularData data, boolean deprecate, final JavacNode builderType, JCTree source, boolean fluent, final boolean chain) { + final JavacTreeMaker maker = builderType.getTreeMaker(); + + ExpressionMaker returnTypeMaker = new ExpressionMaker() { @Override public JCExpression make() { + return chain ? + cloneSelfType(builderType) : + maker.Type(createVoidType(builderType.getSymbolTable(), CTC_VOID)); + }}; + + StatementMaker returnStatementMaker = new StatementMaker() { @Override public JCStatement make() { + return chain ? maker.Return(maker.Ident(builderType.toName("this"))) : null; + }}; + + generateMethods(data, deprecate, builderType, source, fluent, returnTypeMaker, returnStatementMaker); + } + + /** + * Generates the singular, plural, and clear methods for the given {@link SingularData}. + * Uses the given {@code returnTypeMaker} and {@code returnStatementMaker} for the generated methods. + */ + public abstract void generateMethods(SingularData data, boolean deprecate, JavacNode builderType, JCTree source, boolean fluent, ExpressionMaker returnTypeMaker, StatementMaker returnStatementMaker); + + public abstract void appendBuildCode(SingularData data, JavacNode builderType, JCTree source, ListBuffer<JCStatement> statements, Name targetVariableName, String builderVariable); public boolean requiresCleaning() { try { @@ -266,9 +310,9 @@ public class JavacSingularsRecipes { return arguments.toList(); } - /** Generates 'this.<em>name</em>.size()' as an expression; if nullGuard is true, it's this.name == null ? 0 : this.name.size(). */ - protected JCExpression getSize(JavacTreeMaker maker, JavacNode builderType, Name name, boolean nullGuard, boolean parens) { - Name thisName = builderType.toName("this"); + /** Generates '<em>builderVariable</em>.<em>name</em>.size()' as an expression; if nullGuard is true, it's this.name == null ? 0 : this.name.size(). */ + protected JCExpression getSize(JavacTreeMaker maker, JavacNode builderType, Name name, boolean nullGuard, boolean parens, String builderVariable) { + Name thisName = builderType.toName(builderVariable); JCExpression fn = maker.Select(maker.Select(maker.Ident(thisName), name), builderType.toName("size")); JCExpression sizeInvoke = maker.Apply(List.<JCExpression>nil(), fn, List.<JCExpression>nil()); if (nullGuard) { diff --git a/src/core/lombok/javac/handlers/singulars/JavacGuavaSingularizer.java b/src/core/lombok/javac/handlers/singulars/JavacGuavaSingularizer.java index 4de39d98..ffaf6674 100644 --- a/src/core/lombok/javac/handlers/singulars/JavacGuavaSingularizer.java +++ b/src/core/lombok/javac/handlers/singulars/JavacGuavaSingularizer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 The Project Lombok Authors. + * Copyright (C) 2015-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 @@ -32,8 +32,10 @@ import lombok.core.handlers.HandlerUtil; import lombok.javac.JavacNode; import lombok.javac.JavacTreeMaker; import lombok.javac.handlers.JavacHandlerUtil; +import lombok.javac.handlers.JavacSingularsRecipes.ExpressionMaker; import lombok.javac.handlers.JavacSingularsRecipes.JavacSingularizer; import lombok.javac.handlers.JavacSingularsRecipes.SingularData; +import lombok.javac.handlers.JavacSingularsRecipes.StatementMaker; import com.sun.tools.javac.code.Flags; import com.sun.tools.javac.tree.JCTree; @@ -69,23 +71,15 @@ abstract class JavacGuavaSingularizer extends JavacSingularizer { return Collections.singletonList(injectFieldAndMarkGenerated(builderType, buildField)); } - @Override public void generateMethods(SingularData data, JavacNode builderType, JCTree source, boolean fluent, boolean chain) { + @Override public void generateMethods(SingularData data, boolean deprecate, JavacNode builderType, JCTree source, boolean fluent, ExpressionMaker returnTypeMaker, StatementMaker returnStatementMaker) { JavacTreeMaker maker = builderType.getTreeMaker(); - JCExpression returnType = chain ? cloneSelfType(builderType) : maker.Type(createVoidType(maker, CTC_VOID)); - JCStatement returnStatement = chain ? maker.Return(maker.Ident(builderType.toName("this"))) : null; - generateSingularMethod(maker, returnType, returnStatement, data, builderType, source, fluent); - - returnType = chain ? cloneSelfType(builderType) : maker.Type(createVoidType(maker, CTC_VOID)); - returnStatement = chain ? maker.Return(maker.Ident(builderType.toName("this"))) : null; - generatePluralMethod(maker, returnType, returnStatement, data, builderType, source, fluent); - - returnType = chain ? cloneSelfType(builderType) : maker.Type(createVoidType(maker, CTC_VOID)); - returnStatement = chain ? maker.Return(maker.Ident(builderType.toName("this"))) : null; - generateClearMethod(maker, returnType, returnStatement, data, builderType, source); + generateSingularMethod(deprecate, maker, returnTypeMaker.make(), returnStatementMaker.make(), data, builderType, source, fluent); + generatePluralMethod(deprecate, maker, returnTypeMaker.make(), returnStatementMaker.make(), data, builderType, source, fluent); + generateClearMethod(deprecate, maker, returnTypeMaker.make(), returnStatementMaker.make(), data, builderType, source); } - private void generateClearMethod(JavacTreeMaker maker, JCExpression returnType, JCStatement returnStatement, SingularData data, JavacNode builderType, JCTree source) { - JCModifiers mods = maker.Modifiers(Flags.PUBLIC); + private void generateClearMethod(boolean deprecate, JavacTreeMaker maker, JCExpression returnType, JCStatement returnStatement, SingularData data, JavacNode builderType, JCTree source) { + JCModifiers mods = makeMods(maker, builderType, deprecate); List<JCTypeParameter> typeParams = List.nil(); List<JCExpression> thrown = List.nil(); List<JCVariableDecl> params = List.nil(); @@ -100,7 +94,7 @@ abstract class JavacGuavaSingularizer extends JavacSingularizer { injectMethod(builderType, method); } - void generateSingularMethod(JavacTreeMaker maker, JCExpression returnType, JCStatement returnStatement, SingularData data, JavacNode builderType, JCTree source, boolean fluent) { + void generateSingularMethod(boolean deprecate, JavacTreeMaker maker, JCExpression returnType, JCStatement returnStatement, SingularData data, JavacNode builderType, JCTree source, boolean fluent) { List<JCTypeParameter> typeParams = List.nil(); List<JCExpression> thrown = List.nil(); @@ -112,7 +106,7 @@ abstract class JavacGuavaSingularizer extends JavacSingularizer { names[i] = s.isEmpty() ? n : builderType.toName(s); } - JCModifiers mods = maker.Modifiers(Flags.PUBLIC); + JCModifiers mods = makeMods(maker, builderType, deprecate); ListBuffer<JCStatement> statements = new ListBuffer<JCStatement>(); statements.append(createConstructBuilderVarIfNeeded(maker, data, builderType, source)); JCExpression thisDotFieldDotAdd = chainDots(builderType, "this", data.getPluralName().toString(), getAddMethodName()); @@ -139,11 +133,10 @@ abstract class JavacGuavaSingularizer extends JavacSingularizer { injectMethod(builderType, method); } - protected void generatePluralMethod(JavacTreeMaker maker, JCExpression returnType, JCStatement returnStatement, SingularData data, JavacNode builderType, JCTree source, boolean fluent) { + protected void generatePluralMethod(boolean deprecate, JavacTreeMaker maker, JCExpression returnType, JCStatement returnStatement, SingularData data, JavacNode builderType, JCTree source, boolean fluent) { List<JCTypeParameter> typeParams = List.nil(); List<JCExpression> thrown = List.nil(); - - JCModifiers mods = maker.Modifiers(Flags.PUBLIC); + JCModifiers mods = makeMods(maker, builderType, deprecate); ListBuffer<JCStatement> statements = new ListBuffer<JCStatement>(); statements.append(createConstructBuilderVarIfNeeded(maker, data, builderType, source)); JCExpression thisDotFieldDotAddAll = chainDots(builderType, "this", data.getPluralName().toString(), getAddMethodName() + "All"); @@ -167,7 +160,7 @@ abstract class JavacGuavaSingularizer extends JavacSingularizer { injectMethod(builderType, method); } - @Override public void appendBuildCode(SingularData data, JavacNode builderType, JCTree source, ListBuffer<JCStatement> statements, Name targetVariableName) { + @Override public void appendBuildCode(SingularData data, JavacNode builderType, JCTree source, ListBuffer<JCStatement> statements, Name targetVariableName, String builderVariable) { JavacTreeMaker maker = builderType.getTreeMaker(); List<JCExpression> jceBlank = List.nil(); @@ -184,12 +177,12 @@ abstract class JavacGuavaSingularizer extends JavacSingularizer { JCExpression invokeBuild; { //this.pluralName.build(); - invokeBuild = maker.Apply(jceBlank, chainDots(builderType, "this", data.getPluralName().toString(), "build"), jceBlank); + invokeBuild = maker.Apply(jceBlank, chainDots(builderType, builderVariable, data.getPluralName().toString(), "build"), jceBlank); } JCExpression isNull; { //this.pluralName == null - isNull = maker.Binary(CTC_EQUAL, maker.Select(maker.Ident(builderType.toName("this")), data.getPluralName()), maker.Literal(CTC_BOT, null)); + isNull = maker.Binary(CTC_EQUAL, maker.Select(maker.Ident(builderType.toName(builderVariable)), data.getPluralName()), maker.Literal(CTC_BOT, null)); } JCExpression init = maker.Conditional(isNull, empty, invokeBuild); // this.pluralName == null ? ImmutableX.of() : this.pluralName.build() diff --git a/src/core/lombok/javac/handlers/singulars/JavacJavaUtilListSetSingularizer.java b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilListSetSingularizer.java index e167c7e2..39e53ebb 100644 --- a/src/core/lombok/javac/handlers/singulars/JavacJavaUtilListSetSingularizer.java +++ b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilListSetSingularizer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 The Project Lombok Authors. + * Copyright (C) 2015-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 @@ -30,7 +30,9 @@ import lombok.core.handlers.HandlerUtil; import lombok.javac.JavacNode; import lombok.javac.JavacTreeMaker; import lombok.javac.handlers.JavacHandlerUtil; +import lombok.javac.handlers.JavacSingularsRecipes.ExpressionMaker; import lombok.javac.handlers.JavacSingularsRecipes.SingularData; +import lombok.javac.handlers.JavacSingularsRecipes.StatementMaker; import com.sun.tools.javac.code.Flags; import com.sun.tools.javac.tree.JCTree; @@ -75,30 +77,20 @@ abstract class JavacJavaUtilListSetSingularizer extends JavacJavaUtilSingularize return Collections.singletonList(injectFieldAndMarkGenerated(builderType, buildField)); } - @Override public void generateMethods(SingularData data, JavacNode builderType, JCTree source, boolean fluent, boolean chain) { + @Override public void generateMethods(SingularData data, boolean deprecate, JavacNode builderType, JCTree source, boolean fluent, ExpressionMaker returnTypeMaker, StatementMaker returnStatementMaker) { if (useGuavaInstead(builderType)) { - guavaListSetSingularizer.generateMethods(data, builderType, source, fluent, chain); + guavaListSetSingularizer.generateMethods(data, deprecate, builderType, source, fluent, returnTypeMaker, returnStatementMaker); return; } JavacTreeMaker maker = builderType.getTreeMaker(); - Name thisName = builderType.toName("this"); - - JCExpression returnType = chain ? cloneSelfType(builderType) : maker.Type(createVoidType(maker, CTC_VOID)); - JCStatement returnStatement = chain ? maker.Return(maker.Ident(thisName)) : null; - generateSingularMethod(maker, returnType, returnStatement, data, builderType, source, fluent); - - returnType = chain ? cloneSelfType(builderType) : maker.Type(createVoidType(maker, CTC_VOID)); - returnStatement = chain ? maker.Return(maker.Ident(thisName)) : null; - generatePluralMethod(maker, returnType, returnStatement, data, builderType, source, fluent); - - returnType = chain ? cloneSelfType(builderType) : maker.Type(createVoidType(maker, CTC_VOID)); - returnStatement = chain ? maker.Return(maker.Ident(thisName)) : null; - generateClearMethod(maker, returnType, returnStatement, data, builderType, source); + generateSingularMethod(deprecate, maker, returnTypeMaker.make(), returnStatementMaker.make(), data, builderType, source, fluent); + generatePluralMethod(deprecate, maker, returnTypeMaker.make(), returnStatementMaker.make(), data, builderType, source, fluent); + generateClearMethod(deprecate, maker, returnTypeMaker.make(), returnStatementMaker.make(), data, builderType, source); } - private void generateClearMethod(JavacTreeMaker maker, JCExpression returnType, JCStatement returnStatement, SingularData data, JavacNode builderType, JCTree source) { - JCModifiers mods = maker.Modifiers(Flags.PUBLIC); + private void generateClearMethod(boolean deprecate, JavacTreeMaker maker, JCExpression returnType, JCStatement returnStatement, SingularData data, JavacNode builderType, JCTree source) { + JCModifiers mods = makeMods(maker, builderType, deprecate); List<JCTypeParameter> typeParams = List.nil(); List<JCExpression> thrown = List.nil(); List<JCVariableDecl> params = List.nil(); @@ -117,11 +109,10 @@ abstract class JavacJavaUtilListSetSingularizer extends JavacJavaUtilSingularize injectMethod(builderType, method); } - void generateSingularMethod(JavacTreeMaker maker, JCExpression returnType, JCStatement returnStatement, SingularData data, JavacNode builderType, JCTree source, boolean fluent) { + void generateSingularMethod(boolean deprecate, JavacTreeMaker maker, JCExpression returnType, JCStatement returnStatement, SingularData data, JavacNode builderType, JCTree source, boolean fluent) { List<JCTypeParameter> typeParams = List.nil(); List<JCExpression> thrown = List.nil(); - - JCModifiers mods = maker.Modifiers(Flags.PUBLIC); + JCModifiers mods = makeMods(maker, builderType, deprecate); ListBuffer<JCStatement> statements = new ListBuffer<JCStatement>(); statements.append(createConstructBuilderVarIfNeeded(maker, data, builderType, false, source)); JCExpression thisDotFieldDotAdd = chainDots(builderType, "this", data.getPluralName().toString(), "add"); @@ -138,11 +129,10 @@ abstract class JavacJavaUtilListSetSingularizer extends JavacJavaUtilSingularize injectMethod(builderType, method); } - void generatePluralMethod(JavacTreeMaker maker, JCExpression returnType, JCStatement returnStatement, SingularData data, JavacNode builderType, JCTree source, boolean fluent) { + void generatePluralMethod(boolean deprecate, JavacTreeMaker maker, JCExpression returnType, JCStatement returnStatement, SingularData data, JavacNode builderType, JCTree source, boolean fluent) { List<JCTypeParameter> typeParams = List.nil(); List<JCExpression> thrown = List.nil(); - - JCModifiers mods = maker.Modifiers(Flags.PUBLIC); + JCModifiers mods = makeMods(maker, builderType, deprecate); ListBuffer<JCStatement> statements = new ListBuffer<JCStatement>(); statements.append(createConstructBuilderVarIfNeeded(maker, data, builderType, false, source)); JCExpression thisDotFieldDotAdd = chainDots(builderType, "this", data.getPluralName().toString(), "addAll"); diff --git a/src/core/lombok/javac/handlers/singulars/JavacJavaUtilListSingularizer.java b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilListSingularizer.java index 3002a98f..b1375151 100644 --- a/src/core/lombok/javac/handlers/singulars/JavacJavaUtilListSingularizer.java +++ b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilListSingularizer.java @@ -46,9 +46,9 @@ public class JavacJavaUtilListSingularizer extends JavacJavaUtilListSetSingulari return LombokImmutableList.of("java.util.List", "java.util.Collection", "java.lang.Iterable"); } - @Override public void appendBuildCode(SingularData data, JavacNode builderType, JCTree source, ListBuffer<JCStatement> statements, Name targetVariableName) { + @Override public void appendBuildCode(SingularData data, JavacNode builderType, JCTree source, ListBuffer<JCStatement> statements, Name targetVariableName, String builderVariable) { if (useGuavaInstead(builderType)) { - guavaListSetSingularizer.appendBuildCode(data, builderType, source, statements, targetVariableName); + guavaListSetSingularizer.appendBuildCode(data, builderType, source, statements, targetVariableName, builderVariable); return; } @@ -71,7 +71,7 @@ public class JavacJavaUtilListSingularizer extends JavacJavaUtilListSetSingulari JCStatement assignStat; { // pluralName = java.util.Collections.singletonList(this.pluralName.get(0)); JCExpression zeroLiteral = maker.Literal(CTC_INT, 0); - JCExpression arg = maker.Apply(jceBlank, chainDots(builderType, "this", data.getPluralName().toString(), "get"), List.of(zeroLiteral)); + JCExpression arg = maker.Apply(jceBlank, chainDots(builderType, builderVariable, data.getPluralName().toString(), "get"), List.of(zeroLiteral)); List<JCExpression> args = List.of(arg); JCExpression invoke = maker.Apply(jceBlank, chainDots(builderType, "java", "util", "Collections", "singletonList"), args); assignStat = maker.Exec(maker.Assign(maker.Ident(data.getPluralName()), invoke)); @@ -82,12 +82,12 @@ public class JavacJavaUtilListSingularizer extends JavacJavaUtilListSetSingulari } /* default: Create with right size, then addAll */ { - List<JCStatement> defStats = createListCopy(maker, data, builderType, source); + List<JCStatement> defStats = createListCopy(maker, data, builderType, source, builderVariable); JCCase defaultCase = maker.Case(null, defStats); cases.append(defaultCase); } - JCStatement switchStat = maker.Switch(getSize(maker, builderType, data.getPluralName(), true, false), cases.toList()); + JCStatement switchStat = maker.Switch(getSize(maker, builderType, data.getPluralName(), true, false, builderVariable), cases.toList()); JCExpression localShadowerType = chainDotsString(builderType, data.getTargetFqn()); localShadowerType = addTypeArgs(1, false, builderType, localShadowerType, data.getTypeArgs(), source); JCStatement varDefStat = maker.VarDef(maker.Modifiers(0), data.getPluralName(), localShadowerType, null); @@ -95,9 +95,9 @@ public class JavacJavaUtilListSingularizer extends JavacJavaUtilListSetSingulari statements.append(switchStat); } - private List<JCStatement> createListCopy(JavacTreeMaker maker, SingularData data, JavacNode builderType, JCTree source) { + private List<JCStatement> createListCopy(JavacTreeMaker maker, SingularData data, JavacNode builderType, JCTree source, String builderVariable) { List<JCExpression> jceBlank = List.nil(); - Name thisName = builderType.toName("this"); + Name thisName = builderType.toName(builderVariable); JCExpression argToUnmodifiable; { // new java.util.ArrayList<Generics>(this.pluralName); diff --git a/src/core/lombok/javac/handlers/singulars/JavacJavaUtilMapSingularizer.java b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilMapSingularizer.java index 1acae7e3..34350f40 100644 --- a/src/core/lombok/javac/handlers/singulars/JavacJavaUtilMapSingularizer.java +++ b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilMapSingularizer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 The Project Lombok Authors. + * Copyright (C) 2015-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,8 +31,10 @@ import lombok.core.handlers.HandlerUtil; import lombok.javac.JavacNode; import lombok.javac.JavacTreeMaker; import lombok.javac.handlers.JavacHandlerUtil; +import lombok.javac.handlers.JavacSingularsRecipes.ExpressionMaker; import lombok.javac.handlers.JavacSingularsRecipes.JavacSingularizer; import lombok.javac.handlers.JavacSingularsRecipes.SingularData; +import lombok.javac.handlers.JavacSingularsRecipes.StatementMaker; import org.mangosdk.spi.ProviderFor; @@ -100,29 +102,21 @@ public class JavacJavaUtilMapSingularizer extends JavacJavaUtilSingularizer { return Arrays.asList(keyFieldNode, valueFieldNode); } - @Override public void generateMethods(SingularData data, JavacNode builderType, JCTree source, boolean fluent, boolean chain) { + @Override public void generateMethods(SingularData data, boolean deprecate, JavacNode builderType, JCTree source, boolean fluent, ExpressionMaker returnTypeMaker, StatementMaker returnStatementMaker) { if (useGuavaInstead(builderType)) { - guavaMapSingularizer.generateMethods(data, builderType, source, fluent, chain); + guavaMapSingularizer.generateMethods(data, deprecate, builderType, source, fluent, returnTypeMaker, returnStatementMaker); return; } JavacTreeMaker maker = builderType.getTreeMaker(); - - JCExpression returnType = chain ? cloneSelfType(builderType) : maker.Type(createVoidType(maker, CTC_VOID)); - JCStatement returnStatement = chain ? maker.Return(maker.Ident(builderType.toName("this"))) : null; - generateSingularMethod(maker, returnType, returnStatement, data, builderType, source, fluent); - - returnType = chain ? cloneSelfType(builderType) : maker.Type(createVoidType(maker, CTC_VOID)); - returnStatement = chain ? maker.Return(maker.Ident(builderType.toName("this"))) : null; - generatePluralMethod(maker, returnType, returnStatement, data, builderType, source, fluent); - - returnType = chain ? cloneSelfType(builderType) : maker.Type(createVoidType(maker, CTC_VOID)); - returnStatement = chain ? maker.Return(maker.Ident(builderType.toName("this"))) : null; - generateClearMethod(maker, returnType, returnStatement, data, builderType, source); + generateSingularMethod(deprecate, maker, returnTypeMaker.make(), returnStatementMaker.make(), data, builderType, source, fluent); + generatePluralMethod(deprecate, maker, returnTypeMaker.make(), returnStatementMaker.make(), data, builderType, source, fluent); + generateClearMethod(deprecate, maker, returnTypeMaker.make(), returnStatementMaker.make(), data, builderType, source); } - private void generateClearMethod(JavacTreeMaker maker, JCExpression returnType, JCStatement returnStatement, SingularData data, JavacNode builderType, JCTree source) { - JCModifiers mods = maker.Modifiers(Flags.PUBLIC); + private void generateClearMethod(boolean deprecate, JavacTreeMaker maker, JCExpression returnType, JCStatement returnStatement, SingularData data, JavacNode builderType, JCTree source) { + JCModifiers mods = makeMods(maker, builderType, deprecate); + List<JCTypeParameter> typeParams = List.nil(); List<JCExpression> thrown = List.nil(); List<JCVariableDecl> params = List.nil(); @@ -144,10 +138,11 @@ public class JavacJavaUtilMapSingularizer extends JavacJavaUtilSingularizer { injectMethod(builderType, method); } - private void generateSingularMethod(JavacTreeMaker maker, JCExpression returnType, JCStatement returnStatement, SingularData data, JavacNode builderType, JCTree source, boolean fluent) { + private void generateSingularMethod(boolean deprecate, JavacTreeMaker maker, JCExpression returnType, JCStatement returnStatement, SingularData data, JavacNode builderType, JCTree source, boolean fluent) { List<JCTypeParameter> typeParams = List.nil(); List<JCExpression> thrown = List.nil(); - JCModifiers mods = maker.Modifiers(Flags.PUBLIC); + JCModifiers mods = makeMods(maker, builderType, deprecate); + ListBuffer<JCStatement> statements = new ListBuffer<JCStatement>(); statements.append(createConstructBuilderVarIfNeeded(maker, data, builderType, true, source)); Name keyName = builderType.toName(data.getSingularName().toString() + "Key"); @@ -176,10 +171,10 @@ public class JavacJavaUtilMapSingularizer extends JavacJavaUtilSingularizer { injectMethod(builderType, method); } - private void generatePluralMethod(JavacTreeMaker maker, JCExpression returnType, JCStatement returnStatement, SingularData data, JavacNode builderType, JCTree source, boolean fluent) { + private void generatePluralMethod(boolean deprecate, JavacTreeMaker maker, JCExpression returnType, JCStatement returnStatement, SingularData data, JavacNode builderType, JCTree source, boolean fluent) { List<JCTypeParameter> typeParams = List.nil(); List<JCExpression> jceBlank = List.nil(); - JCModifiers mods = maker.Modifiers(Flags.PUBLIC); + JCModifiers mods = makeMods(maker, builderType, deprecate); ListBuffer<JCStatement> statements = new ListBuffer<JCStatement>(); statements.append(createConstructBuilderVarIfNeeded(maker, data, builderType, true, source)); long paramFlags = JavacHandlerUtil.addFinalIfNeeded(Flags.PARAMETER, builderType.getContext()); @@ -208,18 +203,18 @@ public class JavacJavaUtilMapSingularizer extends JavacJavaUtilSingularizer { injectMethod(builderType, method); } - @Override public void appendBuildCode(SingularData data, JavacNode builderType, JCTree source, ListBuffer<JCStatement> statements, Name targetVariableName) { + @Override public void appendBuildCode(SingularData data, JavacNode builderType, JCTree source, ListBuffer<JCStatement> statements, Name targetVariableName, String builderVariable) { if (useGuavaInstead(builderType)) { - guavaMapSingularizer.appendBuildCode(data, builderType, source, statements, targetVariableName); + guavaMapSingularizer.appendBuildCode(data, builderType, source, statements, targetVariableName, builderVariable); return; } JavacTreeMaker maker = builderType.getTreeMaker(); if (data.getTargetFqn().equals("java.util.Map")) { - statements.appendList(createJavaUtilSetMapInitialCapacitySwitchStatements(maker, data, builderType, true, "emptyMap", "singletonMap", "LinkedHashMap", source)); + statements.appendList(createJavaUtilSetMapInitialCapacitySwitchStatements(maker, data, builderType, true, "emptyMap", "singletonMap", "LinkedHashMap", source, builderVariable)); } else { - statements.appendList(createJavaUtilSimpleCreationAndFillStatements(maker, data, builderType, true, true, false, true, "TreeMap", source)); + statements.appendList(createJavaUtilSimpleCreationAndFillStatements(maker, data, builderType, true, true, false, true, "TreeMap", source, builderVariable)); } } } diff --git a/src/core/lombok/javac/handlers/singulars/JavacJavaUtilSetSingularizer.java b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilSetSingularizer.java index 317233cb..71a36c0e 100644 --- a/src/core/lombok/javac/handlers/singulars/JavacJavaUtilSetSingularizer.java +++ b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilSetSingularizer.java @@ -40,18 +40,18 @@ public class JavacJavaUtilSetSingularizer extends JavacJavaUtilListSetSingulariz return LombokImmutableList.of("java.util.Set", "java.util.SortedSet", "java.util.NavigableSet"); } - @Override public void appendBuildCode(SingularData data, JavacNode builderType, JCTree source, ListBuffer<JCStatement> statements, Name targetVariableName) { + @Override public void appendBuildCode(SingularData data, JavacNode builderType, JCTree source, ListBuffer<JCStatement> statements, Name targetVariableName, String builderVariable) { if (useGuavaInstead(builderType)) { - guavaListSetSingularizer.appendBuildCode(data, builderType, source, statements, targetVariableName); + guavaListSetSingularizer.appendBuildCode(data, builderType, source, statements, targetVariableName, builderVariable); return; } JavacTreeMaker maker = builderType.getTreeMaker(); if (data.getTargetFqn().equals("java.util.Set")) { - statements.appendList(createJavaUtilSetMapInitialCapacitySwitchStatements(maker, data, builderType, false, "emptySet", "singleton", "LinkedHashSet", source)); + statements.appendList(createJavaUtilSetMapInitialCapacitySwitchStatements(maker, data, builderType, false, "emptySet", "singleton", "LinkedHashSet", source, builderVariable)); } else { - statements.appendList(createJavaUtilSimpleCreationAndFillStatements(maker, data, builderType, false, true, false, true, "TreeSet", source)); + statements.appendList(createJavaUtilSimpleCreationAndFillStatements(maker, data, builderType, false, true, false, true, "TreeSet", source, builderVariable)); } } } diff --git a/src/core/lombok/javac/handlers/singulars/JavacJavaUtilSingularizer.java b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilSingularizer.java index 25669721..df521fd8 100644 --- a/src/core/lombok/javac/handlers/singulars/JavacJavaUtilSingularizer.java +++ b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilSingularizer.java @@ -46,7 +46,7 @@ abstract class JavacJavaUtilSingularizer extends JavacSingularizer { return Boolean.TRUE.equals(node.getAst().readConfiguration(ConfigurationKeys.SINGULAR_USE_GUAVA)); } - protected List<JCStatement> createJavaUtilSetMapInitialCapacitySwitchStatements(JavacTreeMaker maker, SingularData data, JavacNode builderType, boolean mapMode, String emptyCollectionMethod, String singletonCollectionMethod, String targetType, JCTree source) { + protected List<JCStatement> createJavaUtilSetMapInitialCapacitySwitchStatements(JavacTreeMaker maker, SingularData data, JavacNode builderType, boolean mapMode, String emptyCollectionMethod, String singletonCollectionMethod, String targetType, JCTree source, String builderVariable) { List<JCExpression> jceBlank = List.nil(); ListBuffer<JCCase> cases = new ListBuffer<JCCase>(); @@ -66,11 +66,11 @@ abstract class JavacJavaUtilSingularizer extends JavacSingularizer { // !mapMode: pluralName = java.util.Collections.singletonCollectionMethod(this.pluralName.get(0)); // mapMode: pluralName = java.util.Collections.singletonCollectionMethod(this.pluralName$key.get(0), this.pluralName$value.get(0)); JCExpression zeroLiteral = maker.Literal(CTC_INT, 0); - JCExpression arg = maker.Apply(jceBlank, chainDots(builderType, "this", data.getPluralName() + (mapMode ? "$key" : ""), "get"), List.of(zeroLiteral)); + JCExpression arg = maker.Apply(jceBlank, chainDots(builderType, builderVariable, data.getPluralName() + (mapMode ? "$key" : ""), "get"), List.of(zeroLiteral)); List<JCExpression> args; if (mapMode) { JCExpression zeroLiteralClone = maker.Literal(CTC_INT, 0); - JCExpression arg2 = maker.Apply(jceBlank, chainDots(builderType, "this", data.getPluralName() + (mapMode ? "$value" : ""), "get"), List.of(zeroLiteralClone)); + JCExpression arg2 = maker.Apply(jceBlank, chainDots(builderType, builderVariable, data.getPluralName() + (mapMode ? "$value" : ""), "get"), List.of(zeroLiteralClone)); args = List.of(arg, arg2); } else { args = List.of(arg); @@ -84,12 +84,12 @@ abstract class JavacJavaUtilSingularizer extends JavacSingularizer { } { // default: - List<JCStatement> statements = createJavaUtilSimpleCreationAndFillStatements(maker, data, builderType, mapMode, false, true, emptyCollectionMethod == null, targetType, source); + List<JCStatement> statements = createJavaUtilSimpleCreationAndFillStatements(maker, data, builderType, mapMode, false, true, emptyCollectionMethod == null, targetType, source, builderVariable); JCCase defaultCase = maker.Case(null, statements); cases.append(defaultCase); } - JCStatement switchStat = maker.Switch(getSize(maker, builderType, mapMode ? builderType.toName(data.getPluralName() + "$key") : data.getPluralName(), true, false), cases.toList()); + JCStatement switchStat = maker.Switch(getSize(maker, builderType, mapMode ? builderType.toName(data.getPluralName() + "$key") : data.getPluralName(), true, false, builderVariable), cases.toList()); JCExpression localShadowerType = chainDotsString(builderType, data.getTargetFqn()); localShadowerType = addTypeArgs(mapMode ? 2 : 1, false, builderType, localShadowerType, data.getTypeArgs(), source); JCStatement varDefStat = maker.VarDef(maker.Modifiers(0), data.getPluralName(), localShadowerType, null); @@ -125,9 +125,9 @@ abstract class JavacJavaUtilSingularizer extends JavacSingularizer { return maker.If(cond, thenPart, null); } - protected List<JCStatement> createJavaUtilSimpleCreationAndFillStatements(JavacTreeMaker maker, SingularData data, JavacNode builderType, boolean mapMode, boolean defineVar, boolean addInitialCapacityArg, boolean nullGuard, String targetType, JCTree source) { + protected List<JCStatement> createJavaUtilSimpleCreationAndFillStatements(JavacTreeMaker maker, SingularData data, JavacNode builderType, boolean mapMode, boolean defineVar, boolean addInitialCapacityArg, boolean nullGuard, String targetType, JCTree source, String builderVariable) { List<JCExpression> jceBlank = List.nil(); - Name thisName = builderType.toName("this"); + Name thisName = builderType.toName(builderVariable); JCStatement createStat; { // pluralName = new java.util.TargetType(initialCap); @@ -136,10 +136,10 @@ abstract class JavacJavaUtilSingularizer extends JavacSingularizer { Name varName = mapMode ? builderType.toName(data.getPluralName() + "$key") : data.getPluralName(); // this.varName.size() < MAX_POWER_OF_2 ? 1 + this.varName.size() + (this.varName.size() - 3) / 3 : Integer.MAX_VALUE; // lessThanCutOff = this.varName.size() < MAX_POWER_OF_2 - JCExpression lessThanCutoff = maker.Binary(CTC_LESS_THAN, getSize(maker, builderType, varName, nullGuard, true), maker.Literal(CTC_INT, 0x40000000)); + JCExpression lessThanCutoff = maker.Binary(CTC_LESS_THAN, getSize(maker, builderType, varName, nullGuard, true, builderVariable), maker.Literal(CTC_INT, 0x40000000)); JCExpression integerMaxValue = genJavaLangTypeRef(builderType, "Integer", "MAX_VALUE"); - JCExpression sizeFormulaLeft = maker.Binary(CTC_PLUS, maker.Literal(CTC_INT, 1), getSize(maker, builderType, varName, nullGuard, true)); - JCExpression sizeFormulaRightLeft = maker.Parens(maker.Binary(CTC_MINUS, getSize(maker, builderType, varName, nullGuard, true), maker.Literal(CTC_INT, 3))); + JCExpression sizeFormulaLeft = maker.Binary(CTC_PLUS, maker.Literal(CTC_INT, 1), getSize(maker, builderType, varName, nullGuard, true, builderVariable)); + JCExpression sizeFormulaRightLeft = maker.Parens(maker.Binary(CTC_MINUS, getSize(maker, builderType, varName, nullGuard, true, builderVariable), maker.Literal(CTC_INT, 3))); JCExpression sizeFormulaRight = maker.Binary(CTC_DIV, sizeFormulaRightLeft, maker.Literal(CTC_INT, 3)); JCExpression sizeFormula = maker.Binary(CTC_PLUS, sizeFormulaLeft, sizeFormulaRight); constructorArgs = List.<JCExpression>of(maker.Conditional(lessThanCutoff, sizeFormula, integerMaxValue)); @@ -163,11 +163,14 @@ abstract class JavacJavaUtilSingularizer extends JavacSingularizer { Name ivar = builderType.toName("$i"); Name keyVarName = builderType.toName(data.getPluralName() + "$key"); JCExpression pluralnameDotPut = maker.Select(maker.Ident(data.getPluralName()), builderType.toName("put")); - JCExpression arg1 = maker.Apply(jceBlank, chainDots(builderType, "this", data.getPluralName() + "$key", "get"), List.<JCExpression>of(maker.Ident(ivar))); - JCExpression arg2 = maker.Apply(jceBlank, chainDots(builderType, "this", data.getPluralName() + "$value", "get"), List.<JCExpression>of(maker.Ident(ivar))); + JCExpression arg1 = maker.Apply(jceBlank, chainDots(builderType, builderVariable, data.getPluralName() + "$key", "get"), List.<JCExpression>of(maker.Ident(ivar))); + JCExpression arg2 = maker.Apply(jceBlank, chainDots(builderType, builderVariable, data.getPluralName() + "$value", "get"), List.<JCExpression>of(maker.Ident(ivar))); + // [jdk9] We add an unneccessary (V) cast here. Not doing so gives an error in javac (build 9-ea+156-jigsaw-nightly-h6072-20170212): + // error: method put in interface Map<K#2,V#2> cannot be applied to given types; + arg2 = maker.TypeCast(createTypeArgs(2, false, builderType, data.getTypeArgs(), source).get(1), arg2); JCStatement putStatement = maker.Exec(maker.Apply(jceBlank, pluralnameDotPut, List.of(arg1, arg2))); JCStatement forInit = maker.VarDef(maker.Modifiers(0), ivar, maker.TypeIdent(CTC_INT), maker.Literal(CTC_INT, 0)); - JCExpression checkExpr = maker.Binary(CTC_LESS_THAN, maker.Ident(ivar), getSize(maker, builderType, keyVarName, nullGuard, true)); + JCExpression checkExpr = maker.Binary(CTC_LESS_THAN, maker.Ident(ivar), getSize(maker, builderType, keyVarName, nullGuard, true, builderVariable)); JCExpression incrementExpr = maker.Unary(CTC_POSTINC, maker.Ident(ivar)); fillStat = maker.ForLoop(List.of(forInit), checkExpr, List.of(maker.Exec(incrementExpr)), putStatement); } else { diff --git a/src/core/lombok/package-info.java b/src/core/lombok/package-info.java index 5e34c914..1c01dd0d 100644 --- a/src/core/lombok/package-info.java +++ b/src/core/lombok/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2014 The Project Lombok Authors. + * Copyright (C) 2009-2017 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 @@ -29,6 +29,6 @@ * <li>{@code lombok.experimental} – This package contains lombok features that are new or likely to change before committing to long-term support. * </ul> * - * @see <a href="https://projectlombok.org/features/index.html">Lombok features</a> + * @see <a href="https://projectlombok.org/features/all">Lombok features</a> */ package lombok; diff --git a/src/core/lombok/val.java b/src/core/lombok/val.java index a398fd34..a69d514c 100644 --- a/src/core/lombok/val.java +++ b/src/core/lombok/val.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2013 The Project Lombok Authors. + * Copyright (C) 2010-2017 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 @@ -28,7 +28,7 @@ package lombok; * <p> * Note that this is an annotation type because {@code val x = 10;} will be desugared to {@code @val final int x = 10;} * <p> - * Complete documentation is found at <a href="https://projectlombok.org/features/val.html">the project lombok features page for @val</a>. + * Complete documentation is found at <a href="https://projectlombok.org/features/val">the project lombok features page for @val</a>. */ public @interface val { } diff --git a/src/core/lombok/var.java b/src/core/lombok/var.java new file mode 100644 index 00000000..63a70213 --- /dev/null +++ b/src/core/lombok/var.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2010-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; + +/** + * Use {@code var} as the type of any local variable declaration (even in a {@code for} statement), and the type will be inferred from the initializing expression + * (any further assignments to the variable are not involved in this type inference). + * <p> + * For example: {@code var x = 10.0;} will infer {@code double}, and {@code var y = new ArrayList<String>();} will infer {@code ArrayList<String>}. + * <p> + * Note that this is an annotation type because {@code var x = 10;} will be desugared to {@code @var int x = 10;} + * <p> + * Complete documentation is found at <a href="https://projectlombok.org/features/var">the project lombok features page for @var</a>. + */ +public @interface var { +} |