diff options
Diffstat (limited to 'src')
233 files changed, 15782 insertions, 5219 deletions
diff --git a/src/core/lombok/AllArgsConstructor.java b/src/core/lombok/AllArgsConstructor.java index cc494967..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="http://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/experimental/Builder.java b/src/core/lombok/Builder.java index 1300e7d3..a16717cc 100644 --- a/src/core/lombok/experimental/Builder.java +++ b/src/core/lombok/Builder.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 @@ -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; import static java.lang.annotation.ElementType.*; import static java.lang.annotation.RetentionPolicy.*; @@ -31,25 +31,25 @@ 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 static method. If a class is annotated, + * 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 static + * 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 / static method (each field, when annotating a class), which returns the builder itself. + * 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 static method that was annotated with {@code @Builder}. The return type of this method will be the same - * as the relevant class, unless a static method has been annotated, in which case it'll be equal to the + * 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="http://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: * @@ -107,31 +107,65 @@ import java.lang.annotation.Target; @Target({TYPE, METHOD, CONSTRUCTOR}) @Retention(SOURCE) public @interface Builder { - /** Name of the static 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 instance 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"; - /** Name of the builder class. + /** + * Name of the builder class. + * * Default for {@code @Builder} on types and constructors: {@code (TypeName)Builder}. - * Default for {@code @Builder} on static methods: {@code (ReturnTypeName)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 ""; /** - * 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> + * 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 fluent() default true; + boolean toBuilder() default false; /** - * 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. + * Put on a field (in case of {@code @Builder} on a type) or a parameter (for {@code @Builder} on a constructor or static method) to + * indicate how lombok should obtain a value for this field or parameter given an instance; this is only relevant if {@code toBuilder} is {@code true}. + * + * You do not need to supply an {@code @ObtainVia} annotation unless you wish to change the default behaviour: Use a field with the same name. + * <p> + * Note that one of {@code field} or {@code method} should be set, or an error is generated. * <p> - * <strong>Default: true</strong> + * The default behaviour is to obtain a value by referencing the name of the parameter as a field on 'this'. */ - boolean chain() default true; + @Target({FIELD, PARAMETER}) + @Retention(SOURCE) + public @interface ObtainVia { + /** + * @return Tells lombok to obtain a value with the expression {@code this.value}. + */ + String field() default ""; + + /** + * @return Tells lombok to obtain a value with the expression {@code this.method()}. + */ + String method() default ""; + + /** + * @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 4b5c6fc2..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="http://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 301563b8..29c43d3f 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 @@ -23,6 +23,7 @@ package lombok; import java.util.List; +import lombok.core.configuration.CallSuperType; import lombok.core.configuration.ConfigurationKey; import lombok.core.configuration.FlagUsageType; import lombok.core.configuration.NullCheckExceptionType; @@ -35,6 +36,47 @@ public class ConfigurationKeys { // ##### main package features ##### + // ----- global ----- + + /** + * lombok configuration: {@code lombok.addGeneratedAnnotation} = {@code true} | {@code false}. + * + * 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_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}. + * + * If {@code true}, lombok generates {@code edu.umd.cs.findbugs.annotations.SuppressFBWarnings} on all fields, methods, and types that are generated. + * + * NB: If you enable this option, findbugs must be on the source or classpath, or you'll get errors that the type {@code SuppressFBWarnings} cannot be found. + */ + public static final ConfigurationKey<Boolean> ADD_FINDBUGS_SUPPRESSWARNINGS_ANNOTATIONS = new ConfigurationKey<Boolean>("lombok.extern.findbugs.addSuppressFBWarnings", "Generate @edu.umd.cs.findbugs.annotations.SuppressFBWArnings on all generated code (default: false).") {}; + // ----- *ArgsConstructor ----- /** @@ -51,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. @@ -133,9 +190,16 @@ public class ConfigurationKeys { /** * lombok configuration: {@code lombok.equalsAndHashCode.doNotUseGetters} = {@code true} | {@code false}. * - * For any class without an {@code @EqualsAndHashCode} that explicitly defines the {@code doNotUseGetters} option, this value is used. + * For any class without an {@code @EqualsAndHashCode} that explicitly defines the {@code doNotUseGetters} option, this value is used (default = false). */ - public static final ConfigurationKey<Boolean> EQUALS_AND_HASH_CODE_DO_NOT_USE_GETTERS = new ConfigurationKey<Boolean>("lombok.equalsAndHashCode.doNotUseGetters", "Don't call the getters but use the fields directly in the generated equalsAndHashCode method.") {}; + public static final ConfigurationKey<Boolean> EQUALS_AND_HASH_CODE_DO_NOT_USE_GETTERS = new ConfigurationKey<Boolean>("lombok.equalsAndHashCode.doNotUseGetters", "Don't call the getters but use the fields directly in the generated equals and hashCode method (default = false).") {}; + + /** + * lombok configuration: {@code lombok.equalsAndHashCode.callSuper} = {@code call} | {@code ignore} | {@code warn}. + * + * For any class with an {@code @EqualsAndHashCode} annotation which extends a class other than {@code java.lang.Object}, should a call to superclass's implementation of {@code equals} and {@code hashCode} be included in the generated methods? (Default = warn) + */ + public static final ConfigurationKey<CallSuperType> EQUALS_AND_HASH_CODE_CALL_SUPER = new ConfigurationKey<CallSuperType>("lombok.equalsAndHashCode.callSuper", "When generating equals and hashCode for classes that don't extend Object, either automatically take into account superclass implementation (call), or don't (skip), or warn and don't (warn). (default = warn).") {}; /** * lombok configuration: {@code lombok.equalsAndHashCode.flagUsage} = {@code WARNING} | {@code ERROR}. @@ -149,9 +213,9 @@ public class ConfigurationKeys { /** * lombok configuration: {@code lombok.toString.doNotUseGetters} = {@code true} | {@code false}. * - * For any class without an {@code @ToString} that explicitly defines the {@code doNotUseGetters} option, this value is used. + * For any class without an {@code @ToString} that explicitly defines the {@code doNotUseGetters} option, this value is used (default = false). */ - public static final ConfigurationKey<Boolean> TO_STRING_DO_NOT_USE_GETTERS = new ConfigurationKey<Boolean>("lombok.toString.doNotUseGetters", "Don't call the getters but use the fields directly in the generated toString method.") {}; + public static final ConfigurationKey<Boolean> TO_STRING_DO_NOT_USE_GETTERS = new ConfigurationKey<Boolean>("lombok.toString.doNotUseGetters", "Don't call the getters but use the fields directly in the generated toString method (default = false).") {}; /** * lombok configuration: {@code lombok.toString.flagUsage} = {@code WARNING} | {@code ERROR}. @@ -163,9 +227,35 @@ public class ConfigurationKeys { /** * lombok configuration: {@code lombok.toString.includeFieldNames} = {@code true} | {@code false}. * - * For any class without an {@code @ToString} that explicitly defines the {@code includeFieldNames} option, this value is used. + * For any class without an {@code @ToString} that explicitly defines the {@code includeFieldNames} option, this value is used (default = true). + */ + public static final ConfigurationKey<Boolean> TO_STRING_INCLUDE_FIELD_NAMES = new ConfigurationKey<Boolean>("lombok.toString.includeFieldNames", "Include the field names in the generated toString method (default = true).") {}; + + // ----- Builder ----- + + /** + * lombok configuration: {@code lombok.builder.flagUsage} = {@code WARNING} | {@code ERROR}. + * + * If set, <em>any</em> usage of {@code @Builder} results in a warning / error. + */ + public static final ConfigurationKey<FlagUsageType> BUILDER_FLAG_USAGE = new ConfigurationKey<FlagUsageType>("lombok.builder.flagUsage", "Emit a warning or error if @Builder is used.") {}; + + // ----- Singular ----- + + /** + * lombok configuration: {@code lombok.singular.useGuava} = {@code true} | {@code false}. + * + * If explicitly set to {@code true}, guava's {@code ImmutableList} etc are used to implement the immutable collection datatypes generated by @Singular @Builder for fields/parameters of type {@code java.util.List} and such. + * By default, unmodifiable-wrapped versions of {@code java.util} types are used. */ - public static final ConfigurationKey<Boolean> TO_STRING_INCLUDE_FIELD_NAMES = new ConfigurationKey<Boolean>("lombok.toString.includeFieldNames", "Include the field names in the generated toString method.") {}; + public static final ConfigurationKey<Boolean> SINGULAR_USE_GUAVA = new ConfigurationKey<Boolean>("lombok.singular.useGuava", "Generate backing immutable implementations for @Singular on java.util.* types by using guava's ImmutableList, etc. Normally java.util's mutable types are used and wrapped to make them immutable.") {}; + + /** + * lombok configuration: {@code lombok.singular.auto} = {@code true} | {@code false}. + * + * By default or if explicitly set to {@code true}, lombok will attempt to automatically singularize the name of your variable/parameter when using {@code @Singular}; the name is assumed to be written in english, and a plural. If explicitly to {@code false}, you must always specify the singular form; this is especially useful if your identifiers are in a foreign language. + */ + public static final ConfigurationKey<Boolean> SINGULAR_AUTO = new ConfigurationKey<Boolean>("lombok.singular.auto", "If true (default): Automatically singularize the assumed-to-be-plural name of your variable/parameter when using {@code @Singular}.") {}; // ##### Standalones ##### @@ -190,11 +280,11 @@ 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}. */ - public static final ConfigurationKey<NullCheckExceptionType> NON_NULL_EXCEPTION_TYPE = new ConfigurationKey<NullCheckExceptionType>("lombok.nonNull.exceptionType", "The type of the exception to throw if a passed-in argument is null. Default: NullPointerException.") {}; + public static final ConfigurationKey<NullCheckExceptionType> NON_NULL_EXCEPTION_TYPE = new ConfigurationKey<NullCheckExceptionType>("lombok.nonNull.exceptionType", "The type of the exception to throw if a passed-in argument is null (Default: NullPointerException).") {}; /** * lombok configuration: {@code lombok.nonNull.flagUsage} = {@code WARNING} | {@code ERROR}. @@ -231,7 +321,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 ----- @@ -285,11 +376,18 @@ public class ConfigurationKeys { public static final ConfigurationKey<FlagUsageType> LOG_XSLF4J_FLAG_USAGE = new ConfigurationKey<FlagUsageType>("lombok.log.xslf4j.flagUsage", "Emit a warning or error if @XSlf4j is used.") {}; /** + * lombok configuration: {@code lombok.log.jbosslog.flagUsage} = {@code WARNING} | {@code ERROR}. + * + * If set, <em>any</em> usage of {@code @JBossLog} results in a warning / error. + */ + 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.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. */ - public static final ConfigurationKey<String> LOG_ANY_FIELD_NAME = new ConfigurationKey<String>("lombok.log.fieldName", "Use this name for the generated logger fields (default: 'log')") {}; + public static final ConfigurationKey<String> LOG_ANY_FIELD_NAME = new ConfigurationKey<String>("lombok.log.fieldName", "Use this name for the generated logger fields (default: 'log').") {}; /** * lombok configuration: {@code lombok.log.fieldIsStatic} = {@code true} | {@code false}. @@ -329,25 +427,16 @@ public class ConfigurationKeys { /** * lombok configuration: {@code lombok.accessors.chain} = {@code true} | {@code false}. * - * For any class without an {@code @Accessors} that explicitly defines the {@code chain} option, this value is used. + * For any class without an {@code @Accessors} that explicitly defines the {@code chain} option, this value is used (default = false). */ - public static final ConfigurationKey<Boolean> ACCESSORS_CHAIN = new ConfigurationKey<Boolean>("lombok.accessors.chain", "Generate setters that return 'this' instead of 'void'.") {}; + public static final ConfigurationKey<Boolean> ACCESSORS_CHAIN = new ConfigurationKey<Boolean>("lombok.accessors.chain", "Generate setters that return 'this' instead of 'void' (default: false).") {}; /** * lombok configuration: {@code lombok.accessors.fluent} = {@code true} | {@code false}. * - * For any class without an {@code @Accessors} that explicitly defines the {@code fluent} option, this value is used. - */ - public static final ConfigurationKey<Boolean> ACCESSORS_FLUENT = new ConfigurationKey<Boolean>("lombok.accessors.fluent", "Generate getters and setters using only the field name (no get/set prefix).") {}; - - // ----- Builder ----- - - /** - * lombok configuration: {@code lombok.builder.flagUsage} = {@code WARNING} | {@code ERROR}. - * - * If set, <em>any</em> usage of {@code @Builder} results in a warning / error. + * For any class without an {@code @Accessors} that explicitly defines the {@code fluent} option, this value is used (default = false). */ - public static final ConfigurationKey<FlagUsageType> BUILDER_FLAG_USAGE = new ConfigurationKey<FlagUsageType>("lombok.builder.flagUsage", "Emit a warning or error if @Builder is used.") {}; + public static final ConfigurationKey<Boolean> ACCESSORS_FLUENT = new ConfigurationKey<Boolean>("lombok.accessors.fluent", "Generate getters and setters using only the field name (no get/set prefix) (default: false).") {}; // ----- ExtensionMethod ----- @@ -361,12 +450,55 @@ public class ConfigurationKeys { // ----- FieldDefaults ----- /** + * lombok configuration: {@code lombok.fieldDefaults.defaultPrivate} = {@code true} | {@code false}. + * + * If set to <code>true</code> <em>any</em> field without an access modifier or {@code @PackagePrivate} is marked as {@code private} by lombok, in all source files compiled. + */ + public static final ConfigurationKey<Boolean> FIELD_DEFAULTS_PRIVATE_EVERYWHERE = new ConfigurationKey<Boolean>("lombok.fieldDefaults.defaultPrivate", "If true, fields without any access modifier, in any file (lombok annotated or not) are marked as private. Use @PackagePrivate or an explicit modifier to override this.") {}; + + /** + * lombok configuration: {@code lombok.fieldDefaults.defaultFinal} = {@code true} | {@code false}. + * + * If set to <code>true</code> <em>any</em> field without {@code @NonFinal} is marked as {@code final} by lombok, in all source files compiled. + */ + public static final ConfigurationKey<Boolean> FIELD_DEFAULTS_FINAL_EVERYWHERE = new ConfigurationKey<Boolean>("lombok.fieldDefaults.defaultFinal", "If true, fields, in any file (lombok annotated or not) are marked as final. Use @NonFinal to override this.") {}; + + /** * lombok configuration: {@code lombok.fieldDefaults.flagUsage} = {@code WARNING} | {@code ERROR}. * * If set, <em>any</em> usage of {@code @FieldDefaults} results in a warning / error. */ public static final ConfigurationKey<FlagUsageType> FIELD_DEFAULTS_FLAG_USAGE = new ConfigurationKey<FlagUsageType>("lombok.fieldDefaults.flagUsage", "Emit a warning or error if @FieldDefaults is used.") {}; + // ----- Helper ----- + + /** + * lombok configuration: {@code lombok.helper.flagUsage} = {@code WARNING} | {@code ERROR}. + * + * If set, <em>any</em> usage of {@code @Helper} results in a warning / error. + */ + 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 ----- + + /** + * lombok configuration: {@code lombok.utilityClass.flagUsage} = {@code WARNING} | {@code ERROR}. + * + * If set, <em>any</em> usage of {@code @UtilityClass} results in a warning / error. + */ + public static final ConfigurationKey<FlagUsageType> UTILITY_CLASS_FLAG_USAGE = new ConfigurationKey<FlagUsageType>("lombok.utilityClass.flagUsage", "Emit a warning or error if @UtilityClass is used.") {}; + // ----- Wither ----- /** @@ -376,7 +508,6 @@ 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.") {}; - // ----- Configuration System ----- /** diff --git a/src/core/lombok/Data.java b/src/core/lombok/Data.java index bbc8d920..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="http://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 dbce23b8..2f88ac50 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-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,15 +29,16 @@ 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="http://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()}. + * + * @return A list of fields to exclude. */ String[] exclude() default {}; @@ -46,25 +47,37 @@ public @interface EqualsAndHashCode { * Normally, all non-static, non-transient fields are used for identity. * <p> * Mutually exclusive with {@link #exclude()}. + * + * @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 {}; diff --git a/src/core/lombok/Generated.java b/src/core/lombok/Generated.java new file mode 100644 index 00000000..b99a1b58 --- /dev/null +++ b/src/core/lombok/Generated.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2015-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 + * 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; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Lombok will eventually automatically add this annotation to all generated constructors, methods, fields, and types. + * + * You can mark the presence of this annotation as 'ignore it' for all code style and bug finding tools. + * <p> + * NB: As of v1.16.2 which introduces this annotation, lombok doesn't actually add this annotation; we're setting + * it up so that lombok jars in widespread use start having this, which will make it easier to actually apply it + * later on. + */ +@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.FIELD, ElementType.TYPE}) +@Retention(RetentionPolicy.CLASS) +public @interface Generated { +} diff --git a/src/core/lombok/Getter.java b/src/core/lombok/Getter.java index 428f53ef..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="http://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 cd3e5568..672cd1c2 100644 --- a/src/core/lombok/NoArgsConstructor.java +++ b/src/core/lombok/NoArgsConstructor.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 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="http://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,20 +47,39 @@ 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; + + /** * Placeholder annotation to enable the placement of annotations on the generated code. * @deprecated Don't use this annotation, ever - Read the documentation. */ diff --git a/src/core/lombok/RequiredArgsConstructor.java b/src/core/lombok/RequiredArgsConstructor.java index 8bb57c1a..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="http://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 5e07b802..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="http://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 new file mode 100644 index 00000000..67edab96 --- /dev/null +++ b/src/core/lombok/Singular.java @@ -0,0 +1,38 @@ +/* + * 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 + * 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; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.*; + +import java.lang.annotation.Retention; +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. + */ +@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 929b4578..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="http://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 c5601a0c..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="http://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 e87c71e7..0c43c40b 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-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; /** * 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="http://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,16 @@ 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()}. + * + * @return A list of fields to exclude. */ String[] exclude() default {}; @@ -51,18 +55,24 @@ public @interface ToString { * Normally, all non-static fields are printed. * <p> * Mutually exclusive with {@link #exclude()}. + * + * @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; } diff --git a/src/core/lombok/Value.java b/src/core/lombok/Value.java index 2cffe15b..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 @@ -29,13 +29,13 @@ import java.lang.annotation.Target; /** * Generates a lot of code which fits with a class that is a representation of an immutable entity. * <p> - * Equivalent to {@code @Getter @FieldDefaults(makeFinal=true, level=AccessLevel.PRIVATE) @RequiredArgsConstructor @ToString @EqualsAndHashCode}. + * Equivalent to {@code @Getter @FieldDefaults(makeFinal=true, level=AccessLevel.PRIVATE) @AllArgsConstructor @ToString @EqualsAndHashCode}. * <p> - * Complete documentation is found at <a href="http://projectlombok.org/features/experimental/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 - * @see lombok.RequiredArgsConstructor + * @see lombok.AllArgsConstructor * @see lombok.ToString * @see lombok.EqualsAndHashCode * @see lombok.Data @@ -49,10 +49,12 @@ public @interface Value { * We suggest the name: "of", like so: * * <pre> - * public @Data(staticConstructor = "of") class Point { final int x, y; } + * public @Value(staticConstructor = "of") class Point { final int x, y; } * </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/PostCompilerApp.java b/src/core/lombok/bytecode/PostCompilerApp.java index d2c3157c..5f49bd81 100644 --- a/src/core/lombok/bytecode/PostCompilerApp.java +++ b/src/core/lombok/bytecode/PostCompilerApp.java @@ -98,7 +98,7 @@ public class PostCompilerApp extends LombokApp { byte[] original = readFile(file); byte[] clone = original.clone(); byte[] transformed = PostCompiler.applyTransformations(clone, file.toString(), DiagnosticsReceiver.CONSOLE); - if (clone != transformed && !Arrays.equals(clone, transformed)) { + if (clone != transformed && !Arrays.equals(original, transformed)) { filesTouched++; if (args.verbose) System.out.println("Rewriting " + file.getAbsolutePath()); writeFile(file, transformed); @@ -138,18 +138,24 @@ public class PostCompilerApp extends LombokApp { byte[] buffer = new byte[1024]; ByteArrayOutputStream bytes = new ByteArrayOutputStream(); FileInputStream fileInputStream = new FileInputStream(file); - while (true) { - int read = fileInputStream.read(buffer); - if (read == -1) break; - bytes.write(buffer, 0, read); + try { + while (true) { + int read = fileInputStream.read(buffer); + if (read == -1) break; + bytes.write(buffer, 0, read); + } + } finally { + fileInputStream.close(); } - fileInputStream.close(); return bytes.toByteArray(); } static void writeFile(File file, byte[] transformed) throws IOException { FileOutputStream out = new FileOutputStream(file); - out.write(transformed); - out.close(); + try { + out.write(transformed); + } finally { + out.close(); + } } } 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/Agent.java b/src/core/lombok/core/AgentLauncher.java index 49c03bf3..1d5ab3e6 100644 --- a/src/core/lombok/core/Agent.java +++ b/src/core/lombok/core/AgentLauncher.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009 The Project Lombok Authors. + * Copyright (C) 2009-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 @@ -21,45 +21,32 @@ */ package lombok.core; -import java.lang.instrument.ClassFileTransformer; -import java.lang.instrument.IllegalClassFormatException; import java.lang.instrument.Instrumentation; -import java.security.ProtectionDomain; import java.util.Arrays; import java.util.Collections; import java.util.List; -import javax.swing.JOptionPane; -import javax.swing.SwingUtilities; - -public abstract class Agent { - protected abstract void runAgent(String agentArgs, Instrumentation instrumentation, boolean injected) throws Exception; - - public static void agentmain(String agentArgs, Instrumentation instrumentation) throws Throwable { - runAgents(agentArgs, instrumentation, true); +public class AgentLauncher { + public interface AgentLaunchable { + void runAgent(String agentArgs, Instrumentation instrumentation, boolean injected, Class<?> launchingContext) throws Exception; } - public static void premain(String agentArgs, Instrumentation instrumentation) throws Throwable { - runAgents(agentArgs, instrumentation, false); - } - - private static final List<AgentInfo> AGENTS = Collections.unmodifiableList(Arrays.asList( - new NetbeansPatcherInfo(), - new EclipsePatcherInfo() - )); - - private static void runAgents(String agentArgs, Instrumentation instrumentation, boolean injected) throws Throwable { + public static void runAgents(String agentArgs, Instrumentation instrumentation, boolean injected, Class<?> launchingContext) throws Throwable { for (AgentInfo info : AGENTS) { try { Class<?> agentClass = Class.forName(info.className()); - Agent agent = (Agent) agentClass.newInstance(); - agent.runAgent(agentArgs, instrumentation, injected); + AgentLaunchable agent = (AgentLaunchable) agentClass.newInstance(); + agent.runAgent(agentArgs, instrumentation, injected, launchingContext); } catch (Throwable t) { info.problem(t, instrumentation); } } } + private static final List<AgentInfo> AGENTS = Collections.unmodifiableList(Arrays.<AgentInfo>asList( + new EclipsePatcherInfo() + )); + private static abstract class AgentInfo { abstract String className(); @@ -91,45 +78,6 @@ public abstract class Agent { } } - private static class NetbeansPatcherInfo extends AgentInfo { - @Override String className() { - return "lombok.netbeans.agent.NetbeansPatcher"; - } - - @Override void problem(Throwable in, Instrumentation instrumentation) throws Throwable { - try { - super.problem(in, instrumentation); - } catch (InternalError ie) { - throw ie; - } catch (Throwable t) { - final String error; - - if (t instanceof UnsupportedClassVersionError) { - error = "Lombok only works on netbeans if you start netbeans using a 1.6 or higher JVM.\n" + - "Change your platform's default JVM, or edit etc/netbeans.conf\n" + - "and explicitly tell netbeans your 1.6 JVM's location."; - } else { - error = "Lombok disabled due to error: " + t; - } - - instrumentation.addTransformer(new ClassFileTransformer() { - @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { - if ("org/netbeans/modules/java/source/parsing/JavacParser".equals(className)) { - //If that class gets loaded, this is definitely a netbeans(-esque) environment, and thus we SHOULD tell the user that lombok is not in fact loaded. - SwingUtilities.invokeLater(new Runnable() { - @Override public void run() { - JOptionPane.showMessageDialog(null, error, "Lombok Disabled", JOptionPane.ERROR_MESSAGE); - } - }); - } - - return null; - } - }); - } - } - } - private static class EclipsePatcherInfo extends AgentInfo { @Override String className() { return "lombok.eclipse.agent.EclipsePatcher"; diff --git a/src/core/lombok/core/AnnotationProcessor.java b/src/core/lombok/core/AnnotationProcessor.java index eb44811b..5b4ef393 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 @@ -40,6 +40,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; @@ -75,9 +76,9 @@ public class AnnotationProcessor extends AbstractProcessor { try { ClassLoader classLoader = findAndPatchClassLoader(procEnv); - processor = (Processor)Class.forName("lombok.javac.apt.Processor", false, classLoader).newInstance(); + 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.Processor is not available. Lombok will not run during this compilation: " + trace(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)); return false; } catch (NoClassDefFoundError e) { delayedWarnings.add("Can't load javac processor due to (most likely) a class loader problem: " + trace(e)); @@ -86,7 +87,7 @@ public class AnnotationProcessor extends AbstractProcessor { try { processor.init(procEnv); } catch (Exception e) { - delayedWarnings.add("lombok.javac.apt.Processor could not be initialized. Lombok will not run during this compilation: " + trace(e)); + delayedWarnings.add("lombok.javac.apt.LombokProcessor could not be initialized. Lombok will not run during this compilation: " + trace(e)); return false; } catch (NoClassDefFoundError e) { delayedWarnings.add("Can't initialize javac processor due to (most likely) a class loader problem: " + trace(e)); @@ -103,7 +104,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 +164,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/GuavaTypeMap.java b/src/core/lombok/core/GuavaTypeMap.java new file mode 100644 index 00000000..282d5d81 --- /dev/null +++ b/src/core/lombok/core/GuavaTypeMap.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2015 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; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public final class GuavaTypeMap { + private static final Map<String, String> TYPE_TO_GUAVA_TYPE; static { + Map<String, String> m = new HashMap<String, String>(); + + m.put("java.util.NavigableSet", "ImmutableSortedSet"); + m.put("java.util.NavigableMap", "ImmutableSortedMap"); + m.put("java.util.SortedSet", "ImmutableSortedSet"); + m.put("java.util.SortedMap", "ImmutableSortedMap"); + m.put("java.util.Set", "ImmutableSet"); + m.put("java.util.Map", "ImmutableMap"); + m.put("java.util.Collection", "ImmutableList"); + m.put("java.util.List", "ImmutableList"); + + m.put("com.google.common.collect.ImmutableSet", "ImmutableSet"); + m.put("com.google.common.collect.ImmutableSortedSet", "ImmutableSortedSet"); + m.put("com.google.common.collect.ImmutableMap", "ImmutableMap"); + m.put("com.google.common.collect.ImmutableBiMap", "ImmutableBiMap"); + m.put("com.google.common.collect.ImmutableSortedMap", "ImmutableSortedMap"); + m.put("com.google.common.collect.ImmutableList", "ImmutableList"); + m.put("com.google.common.collect.ImmutableCollection", "ImmutableList"); + m.put("com.google.common.collect.ImmutableTable", "ImmutableTable"); + + TYPE_TO_GUAVA_TYPE = Collections.unmodifiableMap(m); + } + + public static String getGuavaTypeName(String fqn) { + String target = TYPE_TO_GUAVA_TYPE.get(fqn); + return target != null ? target : "ImmutableList"; + } + + private GuavaTypeMap() {} +} diff --git a/src/core/lombok/core/LombokInternalAliasing.java b/src/core/lombok/core/LombokInternalAliasing.java index 8d6794ae..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 @@ -50,6 +50,8 @@ 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/Main.java b/src/core/lombok/core/Main.java index d62fe3e4..6952ab78 100644 --- a/src/core/lombok/core/Main.java +++ b/src/core/lombok/core/Main.java @@ -38,6 +38,7 @@ public class Main { )); public static void main(String[] args) throws IOException { + Thread.currentThread().setContextClassLoader(Main.class.getClassLoader()); int err = new Main(SpiLoadUtil.readAllFromIterator( SpiLoadUtil.findServices(LombokApp.class)), Arrays.asList(args)).go(); System.exit(err); @@ -94,7 +95,7 @@ public class Main { in.close(); } } catch (Exception e) { - System.err.println("License file not found. Check http://projectlombok.org/LICENSE"); + System.err.println("License file not found. Check https://projectlombok.org/LICENSE"); return 1; } } @@ -142,7 +143,7 @@ public class Main { out.println("------------------------------"); } out.println("projectlombok.org " + Version.getFullVersion()); - out.println("Copyright (C) 2009-2012 The Project Lombok Authors."); + out.println("Copyright (C) 2009-2015 The Project Lombok Authors."); out.println("Run 'lombok license' to see the lombok license agreement."); out.println(); out.println("Run lombok without any parameters to start the graphical installer."); diff --git a/src/core/lombok/core/PostCompiler.java b/src/core/lombok/core/PostCompiler.java index 11091cb8..237867cb 100644 --- a/src/core/lombok/core/PostCompiler.java +++ b/src/core/lombok/core/PostCompiler.java @@ -24,6 +24,8 @@ package lombok.core; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; +import java.io.PrintWriter; +import java.io.StringWriter; import java.util.Collections; import java.util.List; @@ -55,10 +57,12 @@ public final class PostCompiler { transformations = SpiLoadUtil.readAllFromIterator(SpiLoadUtil.findServices(PostCompilerTransformation.class, PostCompilerTransformation.class.getClassLoader())); } catch (IOException e) { transformations = Collections.emptyList(); - diagnostics.addWarning("Could not load post-compile transformers: " + e.getMessage()); + StringWriter sw = new StringWriter(); + e.printStackTrace(new PrintWriter(sw, true)); + diagnostics.addWarning("Could not load post-compile transformers: " + e.getMessage() + "\n" + sw.toString()); } } - + public static OutputStream wrapOutputStream(final OutputStream originalStream, final String fileName, final DiagnosticsReceiver diagnostics) throws IOException { if (System.getProperty("lombok.disablePostCompiler", null) != null) return originalStream; return new ByteArrayOutputStream() { 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/TypeLibrary.java b/src/core/lombok/core/TypeLibrary.java index c0e9dc43..cdaf7a70 100644 --- a/src/core/lombok/core/TypeLibrary.java +++ b/src/core/lombok/core/TypeLibrary.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2013 The Project Lombok Authors. + * Copyright (C) 2009-2015 The Project Lombok Authors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -37,6 +37,7 @@ import java.util.Map; public class TypeLibrary { private final Map<String, String> unqualifiedToQualifiedMap; private final String unqualified, qualified; + private boolean locked; public TypeLibrary() { unqualifiedToQualifiedMap = new HashMap<String, String>(); @@ -44,15 +45,27 @@ public class TypeLibrary { qualified = null; } + public void lock() { + this.locked = true; + } + private TypeLibrary(String fqnSingleton) { - unqualifiedToQualifiedMap = null; - qualified = fqnSingleton; - int idx = fqnSingleton.lastIndexOf('.'); - if (idx == -1) { - unqualified = fqnSingleton; + if (fqnSingleton.indexOf("$") != -1) { + unqualifiedToQualifiedMap = new HashMap<String, String>(); + unqualified = null; + qualified = null; + addType(fqnSingleton); } else { - unqualified = fqnSingleton.substring(idx + 1); + unqualifiedToQualifiedMap = null; + qualified = fqnSingleton; + int idx = fqnSingleton.lastIndexOf('.'); + if (idx == -1) { + unqualified = fqnSingleton; + } else { + unqualified = fqnSingleton.substring(idx + 1); + } } + locked = true; } public static TypeLibrary createLibraryForSingleType(String fqnSingleton) { @@ -65,17 +78,29 @@ public class TypeLibrary { * @param fullyQualifiedTypeName the FQN type name, such as 'java.lang.String'. */ public void addType(String fullyQualifiedTypeName) { - fullyQualifiedTypeName = fullyQualifiedTypeName.replace("$", "."); + String dotBased = fullyQualifiedTypeName.replace("$", "."); + + if (locked) throw new IllegalStateException("locked"); int idx = fullyQualifiedTypeName.lastIndexOf('.'); if (idx == -1) throw new IllegalArgumentException( "Only fully qualified types are allowed (and stuff in the default package is not palatable to us either!)"); String unqualified = fullyQualifiedTypeName.substring(idx + 1); if (unqualifiedToQualifiedMap == null) throw new IllegalStateException("SingleType library"); - unqualifiedToQualifiedMap.put(unqualified, fullyQualifiedTypeName); - unqualifiedToQualifiedMap.put(fullyQualifiedTypeName, fullyQualifiedTypeName); + unqualifiedToQualifiedMap.put(unqualified.replace("$", "."), dotBased); + unqualifiedToQualifiedMap.put(unqualified, dotBased); + unqualifiedToQualifiedMap.put(fullyQualifiedTypeName, dotBased); + unqualifiedToQualifiedMap.put(dotBased, dotBased); for (Map.Entry<String, String> e : LombokInternalAliasing.ALIASES.entrySet()) { - if (fullyQualifiedTypeName.equals(e.getValue())) unqualifiedToQualifiedMap.put(e.getKey(), fullyQualifiedTypeName); + if (fullyQualifiedTypeName.equals(e.getValue())) unqualifiedToQualifiedMap.put(e.getKey(), dotBased); + } + + int idx2 = fullyQualifiedTypeName.indexOf('$', idx + 1); + while (idx2 != -1) { + String unq = fullyQualifiedTypeName.substring(idx2 + 1); + unqualifiedToQualifiedMap.put(unq.replace("$", "."), dotBased); + unqualifiedToQualifiedMap.put(unq, dotBased); + idx2 = fullyQualifiedTypeName.indexOf('$', idx2 + 1); } } diff --git a/src/core/lombok/core/TypeResolver.java b/src/core/lombok/core/TypeResolver.java index 287a085f..60ac6b6a 100644 --- a/src/core/lombok/core/TypeResolver.java +++ b/src/core/lombok/core/TypeResolver.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2013 The Project Lombok Authors. + * Copyright (C) 2009-2015 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 @@ -53,10 +53,13 @@ public class TypeResolver { if (typeRef.equals(qualified)) return typeRef; // When asking if 'Getter' could possibly be referring to 'lombok.Getter' if 'import lombok.Getter;' is in the source file, the answer is yes. - String fromExplicitImport = imports.getFullyQualifiedNameForSimpleName(typeRef); + int firstDot = typeRef.indexOf('.'); + if (firstDot == -1) firstDot = typeRef.length(); + String firstTypeRef = typeRef.substring(0, firstDot); + String fromExplicitImport = imports.getFullyQualifiedNameForSimpleName(firstTypeRef); if (fromExplicitImport != null) { // ... and if 'import foobar.Getter;' is in the source file, the answer is no. - return fromExplicitImport.equals(qualified) ? qualified : null; + return (fromExplicitImport + typeRef.substring(firstDot)).equals(qualified) ? qualified : null; } // When asking if 'Getter' could possibly be referring to 'lombok.Getter' and 'import lombok.*; / package lombok;' isn't in the source file. the answer is no. @@ -68,7 +71,7 @@ public class TypeResolver { mainLoop: while (n != null) { - if (n.getKind() == Kind.TYPE && typeRef.equals(n.getName())) { + if (n.getKind() == Kind.TYPE && firstTypeRef.equals(n.getName())) { // Our own class or one of our outer classes is named 'typeRef' so that's what 'typeRef' is referring to, not one of our type library classes. return null; } @@ -81,7 +84,7 @@ public class TypeResolver { for (LombokNode<?, ?, ?> child : newN.down()) { // We found a method local with the same name above our code. That's the one 'typeRef' is referring to, not // anything in the type library we're trying to find, so, no matches. - if (child.getKind() == Kind.TYPE && typeRef.equals(child.getName())) return null; + if (child.getKind() == Kind.TYPE && firstTypeRef.equals(child.getName())) return null; if (child == n) break; } } @@ -92,14 +95,13 @@ public class TypeResolver { if (n.getKind() == Kind.TYPE || n.getKind() == Kind.COMPILATION_UNIT) { for (LombokNode<?, ?, ?> child : n.down()) { // Inner class that's visible to us has 'typeRef' as name, so that's the one being referred to, not one of our type library classes. - if (child.getKind() == Kind.TYPE && typeRef.equals(child.getName())) return null; + if (child.getKind() == Kind.TYPE && firstTypeRef.equals(child.getName())) return null; } } n = n.directUp(); } - return qualified; } } diff --git a/src/core/lombok/core/Version.java b/src/core/lombok/core/Version.java index 9f810525..98f1e575 100644 --- a/src/core/lombok/core/Version.java +++ b/src/core/lombok/core/Version.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 @@ -30,9 +30,15 @@ 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.14.9"; + private static final String VERSION = "1.16.21"; private static final String RELEASE_NAME = "Edgy Guinea Pig"; -// private static final String RELEASE_NAME = "Branching Cobra"; +// private static final String RELEASE_NAME = "Dancing Elephant"; + + // Named version history: + // Angry Butterfly + // Branching Cobra + // Candid Duck + // Dancing Elephant private Version() { //Prevent instantiation @@ -43,7 +49,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/BubblingConfigurationResolver.java b/src/core/lombok/core/configuration/BubblingConfigurationResolver.java index f96b4468..0849eee2 100644 --- a/src/core/lombok/core/configuration/BubblingConfigurationResolver.java +++ b/src/core/lombok/core/configuration/BubblingConfigurationResolver.java @@ -45,32 +45,23 @@ public class BubblingConfigurationResolver implements ConfigurationResolver { Result result = source.resolve(key); if (result == null) continue; if (isList) { - if (listModificationsList == null) { - listModificationsList = new ArrayList<List<ListModification>>(); - } + if (listModificationsList == null) listModificationsList = new ArrayList<List<ListModification>>(); listModificationsList.add((List<ListModification>)result.getValue()); } if (result.isAuthoritative()) { - if (isList) { - break; - } + if (isList) break; return (T) result.getValue(); } } - if (!isList) { - return null; - } - if (listModificationsList == null) { - return (T) Collections.emptyList(); - } + if (!isList) return null; + if (listModificationsList == null) return (T) Collections.emptyList(); + List<Object> listValues = new ArrayList<Object>(); Collections.reverse(listModificationsList); for (List<ListModification> listModifications : listModificationsList) { if (listModifications != null) for (ListModification modification : listModifications) { listValues.remove(modification.getValue()); - if (modification.isAdded()) { - listValues.add(modification.getValue()); - } + if (modification.isAdded()) listValues.add(modification.getValue()); } } return (T) listValues; diff --git a/src/core/lombok/core/configuration/CallSuperType.java b/src/core/lombok/core/configuration/CallSuperType.java new file mode 100644 index 00000000..b7152ea4 --- /dev/null +++ b/src/core/lombok/core/configuration/CallSuperType.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2015 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; + +/** Used for lombok configuration for configuration whether or not to call the super implementation for certain lombok feature. */ +public enum CallSuperType { + CALL, SKIP, WARN; +} 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/ConfigurationParser.java b/src/core/lombok/core/configuration/ConfigurationParser.java index f0a9e142..e80793c1 100644 --- a/src/core/lombok/core/configuration/ConfigurationParser.java +++ b/src/core/lombok/core/configuration/ConfigurationParser.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Project Lombok Authors. + * Copyright (C) 2014-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 @@ -27,7 +27,7 @@ import java.util.regex.Pattern; public class ConfigurationParser { private static final Pattern LINE = Pattern.compile("(?:clear\\s+([^=]+))|(?:(\\S*?)\\s*([-+]?=)\\s*(.*?))"); - private static final Pattern NEWLINE_FINDER = Pattern.compile("^\\s*(.*?)\\s*$", Pattern.MULTILINE); + private static final Pattern NEWLINE_FINDER = Pattern.compile("^[\t ]*(.*?)[\t\r ]*$", Pattern.MULTILINE); private ConfigurationProblemReporter reporter; diff --git a/src/core/lombok/core/configuration/FileSystemSourceCache.java b/src/core/lombok/core/configuration/FileSystemSourceCache.java index dc390eb6..8bc5050b 100644 --- a/src/core/lombok/core/configuration/FileSystemSourceCache.java +++ b/src/core/lombok/core/configuration/FileSystemSourceCache.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Project Lombok Authors. + * Copyright (C) 2014-2015 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,41 +38,68 @@ import lombok.core.debug.ProblemReporter; public class FileSystemSourceCache { private static final String LOMBOK_CONFIG_FILENAME = "lombok.config"; + private static final long FULL_CACHE_CLEAR_INTERVAL = TimeUnit.MINUTES.toMillis(30); private static final long RECHECK_FILESYSTEM = TimeUnit.SECONDS.toMillis(2); private static final long NEVER_CHECKED = -1; private static final long MISSING = -88; // Magic value; any lombok.config with this exact epochmillis last modified will never be read, so, let's ensure nobody accidentally has one with that exact last modified stamp. - private final ConcurrentMap<File, Content> cache = new ConcurrentHashMap<File, Content>(); + private final ConcurrentMap<File, Content> dirCache = new ConcurrentHashMap<File, Content>(); // caches files (representing dirs) to the content object that tracks content. + private final ConcurrentMap<URI, File> uriCache = new ConcurrentHashMap<URI, File>(); // caches URIs of java source files to the dir that contains it. + private volatile long lastCacheClear = System.currentTimeMillis(); + + private void cacheClear() { + // We never clear the caches, generally because it'd be weird if a compile run would continually create an endless stream of new java files. + // Still, eventually that's going to cause a bit of a memory leak, so lets just completely clear them out every many minutes. + long now = System.currentTimeMillis(); + long delta = now - lastCacheClear; + if (delta > FULL_CACHE_CLEAR_INTERVAL) { + lastCacheClear = now; + dirCache.clear(); + uriCache.clear(); + } + } public Iterable<ConfigurationSource> sourcesForJavaFile(URI javaFile, ConfigurationProblemReporter reporter) { if (javaFile == null) return Collections.emptyList(); - URI uri = javaFile.normalize(); - if (!uri.isAbsolute()) uri = URI.create("file:" + uri.toString()); - - File file; - try { - file = new File(uri); - if (!file.exists()) throw new IllegalArgumentException("File does not exist: " + uri); - return sourcesForDirectory(file.getParentFile(), reporter); - } catch (IllegalArgumentException e) { - // This means that the file as passed is not actually a file at all, and some exotic path system is involved. - // examples: sourcecontrol://jazz stuff, or an actual relative path (uri.isAbsolute() is completely different, that checks presence of schema!), - // or it's eclipse trying to parse a snippet, which has "/Foo.java" as uri. - // At some point it might be worth investigating abstracting away the notion of "I can read lombok.config if present in - // current context, and I can give you may parent context", using ResourcesPlugin.getWorkspace().getRoot().findFilesForLocationURI(javaFile) as basis. + cacheClear(); + File dir = uriCache.get(javaFile); + if (dir == null) { + URI uri = javaFile.normalize(); + if (!uri.isAbsolute()) uri = URI.create("file:" + uri.toString()); - // For now, we just carry on as if there is no lombok.config. (intentional fallthrough) - } catch (Exception e) { - // Especially for eclipse's sake, exceptions here make eclipse borderline unusable, so let's play nice. - ProblemReporter.error("Can't find absolute path of file being compiled: " + javaFile, e); + try { + File file = new File(uri); + if (!file.exists()) throw new IllegalArgumentException("File does not exist: " + uri); + dir = file.isDirectory() ? file : file.getParentFile(); + if (dir != null) uriCache.put(javaFile, dir); + } catch (IllegalArgumentException e) { + // This means that the file as passed is not actually a file at all, and some exotic path system is involved. + // examples: sourcecontrol://jazz stuff, or an actual relative path (uri.isAbsolute() is completely different, that checks presence of schema!), + // or it's eclipse trying to parse a snippet, which has "/Foo.java" as uri. + // At some point it might be worth investigating abstracting away the notion of "I can read lombok.config if present in + // current context, and I can give you may parent context", using ResourcesPlugin.getWorkspace().getRoot().findFilesForLocationURI(javaFile) as basis. + + // For now, we just carry on as if there is no lombok.config. (intentional fallthrough) + } catch (Exception e) { + // Especially for eclipse's sake, exceptions here make eclipse borderline unusable, so let's play nice. + ProblemReporter.error("Can't find absolute path of file being compiled: " + javaFile, e); + } + } + + if (dir != null) { + try { + return sourcesForDirectory(dir, reporter); + } catch (Exception e) { + // Especially for eclipse's sake, exceptions here make eclipse borderline unusable, so let's play nice. + ProblemReporter.error("Can't resolve config stack for dir: " + dir.getAbsolutePath(), e); + } } return Collections.emptyList(); } public Iterable<ConfigurationSource> sourcesForDirectory(URI directory, ConfigurationProblemReporter reporter) { - if (directory == null) return Collections.emptyList(); - return sourcesForDirectory(new File(directory.normalize()), reporter); + return sourcesForJavaFile(directory, reporter); } private Iterable<ConfigurationSource> sourcesForDirectory(final File directory, final ConfigurationProblemReporter reporter) { @@ -139,12 +166,12 @@ public class FileSystemSourceCache { } private Content ensureContent(File directory) { - Content content = cache.get(directory); + Content content = dirCache.get(directory); if (content != null) { return content; } - cache.putIfAbsent(directory, Content.empty()); - return cache.get(directory); + dirCache.putIfAbsent(directory, Content.empty()); + return dirCache.get(directory); } private ConfigurationSource parse(File configFile, ConfigurationProblemReporter reporter) { 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/debug/AssertionLogger.java b/src/core/lombok/core/debug/AssertionLogger.java new file mode 100644 index 00000000..6b2c326d --- /dev/null +++ b/src/core/lombok/core/debug/AssertionLogger.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2015 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.debug; + +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Date; +import java.util.Random; +import java.util.concurrent.atomic.AtomicBoolean; + +import lombok.core.Version; + +/** + * We make a number of assumptions in lombok code, and if these assumptions fail, we try to fall back to a 'least bad' scenario. However, we would prefer to + * just know about these cases, without confronting our users with error messages. The 'fix' is to log such assertion failures to this logger, which promptly + * ignores them, _unless_ you specifically enable logging them to a file. If you'd like to help out or want to assist in debugging, turn this on. + */ +public class AssertionLogger { + private static final String LOG_PATH; + + static { + String log = System.getProperty("lombok.assertion.log", null); + if (log != null) { + LOG_PATH = log.isEmpty() ? null : log; + } else { + try { + log = System.getenv("LOMBOK_ASSERTION_LOG"); + } catch (Exception e) { + log = null; + } + LOG_PATH = (log == null || log.isEmpty()) ? null : log; + } + } + + private static final AtomicBoolean loggedIntro = new AtomicBoolean(false); + private static final String PROCESS_ID = generateProcessId(); + + private static final String ID_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + + private static String generateProcessId() { + char[] ID = new char[4]; + Random r = new Random(); + for (int i = 0; i < ID.length; i++) ID[i] = ID_CHARS.charAt(r.nextInt(ID_CHARS.length())); + return new String(ID); + } + + private static synchronized void logToFile(String msg) { + if (msg == null) return; + + try { + OutputStream out = new FileOutputStream(LOG_PATH, true); + out.write(msg.getBytes("UTF-8")); + out.close(); + } catch (Exception e) { + throw new RuntimeException("assertion logging can't write to log file", e); + } + } + + private static void logIntro() { + if (loggedIntro.getAndSet(true)) return; + + String version; + try { + version = Version.getFullVersion(); + } catch (Exception e) { + version = Version.getVersion(); + } + + logToFile(String.format("{%s} [%s -- START %s]\n", PROCESS_ID, new Date(), version)); + } + + public static <T extends Throwable> T assertLog(String message, T throwable) { + if (LOG_PATH == null) return throwable; + + logIntro(); + + if (message == null) message = "(No message)"; + String stackMsg = ""; + if (throwable != null) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + throwable.printStackTrace(pw); + pw.close(); + stackMsg = "\n " + sw.toString().replace("\r", "").replace("\n", "\n ").trim(); + } + logToFile(String.format("{%s} [%ty%<tm%<tdT%<tH%<tM%<tS.%<tL] %s%s\n", PROCESS_ID, new Date(), message, stackMsg)); + return throwable; + } + + public static void assertLog(String message) { + if (LOG_PATH == null) return; + assertLog(message, null); + } +} diff --git a/src/core/lombok/core/handlers/HandlerUtil.java b/src/core/lombok/core/handlers/HandlerUtil.java index 621d8760..6b516904 100644 --- a/src/core/lombok/core/handlers/HandlerUtil.java +++ b/src/core/lombok/core/handlers/HandlerUtil.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 @@ -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; @@ -68,6 +69,10 @@ public class HandlerUtil { return 97; } + public static int primeForNull() { + return 43; + } + /** Checks if the given name is a valid identifier. * * If it is, this returns {@code true} and does nothing else. @@ -87,16 +92,30 @@ public class HandlerUtil { return true; } + public static String autoSingularize(String plural) { + return Singulars.autoSingularize(plural); + } 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"); } @@ -152,11 +171,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. @@ -303,7 +323,7 @@ public class HandlerUtil { return booleanPrefix + fName.substring(2); } - return buildName(isBoolean ? booleanPrefix : normalPrefix, fName); + return buildAccessorName(isBoolean ? booleanPrefix : normalPrefix, fName); } /** @@ -375,8 +395,8 @@ public class HandlerUtil { if (adhereToFluent && fluent) { names.add(baseName); } else { - names.add(buildName(normalPrefix, baseName)); - if (!normalPrefix.equals(booleanPrefix)) names.add(buildName(booleanPrefix, baseName)); + names.add(buildAccessorName(normalPrefix, baseName)); + if (!normalPrefix.equals(booleanPrefix)) names.add(buildAccessorName(booleanPrefix, baseName)); } } @@ -407,7 +427,7 @@ public class HandlerUtil { * @param suffix Something like {@code running}. * @return prefix + smartly title-cased suffix. For example, {@code setRunning}. */ - private static String buildName(String prefix, String suffix) { + public static String buildAccessorName(String prefix, String suffix) { if (suffix.length() == 0) return prefix; if (prefix.length() == 0) return suffix; diff --git a/src/core/lombok/core/handlers/Singulars.java b/src/core/lombok/core/handlers/Singulars.java new file mode 100644 index 00000000..87895790 --- /dev/null +++ b/src/core/lombok/core/handlers/Singulars.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2015 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.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; + +public class Singulars { + private static final List<String> SINGULAR_STORE; // intended to be immutable. + + static { + SINGULAR_STORE = new ArrayList<String>(); + + try { + InputStream in = Singulars.class.getResourceAsStream("singulars.txt"); + try { + BufferedReader br = new BufferedReader(new InputStreamReader(in, "UTF-8")); + for (String line = br.readLine(); line != null; line = br.readLine()) { + line = line.trim(); + if (line.startsWith("#") || line.isEmpty()) continue; + if (line.endsWith(" =")) { + SINGULAR_STORE.add(line.substring(0, line.length() - 2)); + SINGULAR_STORE.add(""); + continue; + } + + int idx = line.indexOf(" = "); + SINGULAR_STORE.add(line.substring(0, idx)); + SINGULAR_STORE.add(line.substring(idx + 3)); + } + } finally { + try { + in.close(); + } catch (Throwable ignore) {} + } + } catch (IOException e) { + SINGULAR_STORE.clear(); + } + } + + public static String autoSingularize(String in) { + final int inLen = in.length(); + for (int i = 0; i < SINGULAR_STORE.size(); i+= 2) { + final String lastPart = SINGULAR_STORE.get(i); + final boolean wholeWord = Character.isUpperCase(lastPart.charAt(0)); + final int endingOnly = lastPart.charAt(0) == '-' ? 1 : 0; + final int len = lastPart.length(); + if (inLen < len) continue; + if (!in.regionMatches(true, inLen - len + endingOnly, lastPart, endingOnly, len - endingOnly)) continue; + if (wholeWord && inLen != len && !Character.isUpperCase(in.charAt(inLen - len))) continue; + + String replacement = SINGULAR_STORE.get(i + 1); + if (replacement.equals("!")) return null; + + boolean capitalizeFirst = !replacement.isEmpty() && Character.isUpperCase(in.charAt(inLen - len + endingOnly)); + String pre = in.substring(0, inLen - len + endingOnly); + String post = capitalizeFirst ? Character.toUpperCase(replacement.charAt(0)) + replacement.substring(1) : replacement; + return pre + post; + } + + return null; + } +} diff --git a/src/core/lombok/core/handlers/singulars.txt b/src/core/lombok/core/handlers/singulars.txt new file mode 100644 index 00000000..d77308cb --- /dev/null +++ b/src/core/lombok/core/handlers/singulars.txt @@ -0,0 +1,56 @@ +#Based on https://github.com/rails/rails/blob/efff6c1fd4b9e2e4c9f705a45879373cb34a5b0e/activesupport/lib/active_support/inflections.rb + +quizzes = quiz +matrices = matrix +indices = index +vertices = vertex +statuses = status +aliases = alias +alias = ! +species = ! +Axes = ! +-axes = axe +sexes = sex +Testes = testis +movies = movie +octopodes = octopus +buses = bus +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, force the explicit singular. +Men = man +Women = woman +minutiae = minutia +shoes = shoe +synopses = synopsis +prognoses = prognosis +theses = thesis +diagnoses = diagnosis +bases = base +analyses = analysis +Crises = crisis +children = child +moves = move +zombies = zombie +-quies = quy +-us = ! +-is = ! +series = ! +-ies = y +-oes = o +hives = hive +-tives = tive +-sses = ss +-ches = ch +-xes = x +-shes = sh +-lves = lf +-rves = rf +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 6df5c4d7..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); @@ -70,7 +70,7 @@ public class EclipseAST extends AST<EclipseAST, EclipseNode, ASTNode> { } private static volatile boolean skipEclipseWorkspaceBasedFileResolver = false; - private static final URI NOT_CALCULATED_MARKER = URI.create("http://projectlombok.org/not/calculated"); + private static final URI NOT_CALCULATED_MARKER = URI.create("https://projectlombok.org/not/calculated"); private URI memoizedAbsoluteFileLocation = NOT_CALCULATED_MARKER; public URI getAbsoluteFileLocation() { @@ -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/EclipseASTVisitor.java b/src/core/lombok/eclipse/EclipseASTVisitor.java index aa19adc6..f5b49cbb 100644 --- a/src/core/lombok/eclipse/EclipseASTVisitor.java +++ b/src/core/lombok/eclipse/EclipseASTVisitor.java @@ -40,7 +40,7 @@ import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; import org.eclipse.jdt.internal.compiler.ast.TypeReference; /** - * Implement so you can ask any JavacAST.Node to traverse depth-first through all children, + * Implement so you can ask any EclipseAST.Node to traverse depth-first through all children, * calling the appropriate visit and endVisit methods. */ public interface EclipseASTVisitor { diff --git a/src/core/lombok/eclipse/EclipseNode.java b/src/core/lombok/eclipse/EclipseNode.java index 2c970db2..49867e62 100644 --- a/src/core/lombok/eclipse/EclipseNode.java +++ b/src/core/lombok/eclipse/EclipseNode.java @@ -54,70 +54,70 @@ public class EclipseNode extends lombok.core.LombokNode<EclipseAST, EclipseNode, switch (getKind()) { case COMPILATION_UNIT: - visitor.visitCompilationUnit(this, (CompilationUnitDeclaration)get()); + visitor.visitCompilationUnit(this, (CompilationUnitDeclaration) get()); ast.traverseChildren(visitor, this); - visitor.endVisitCompilationUnit(this, (CompilationUnitDeclaration)get()); + visitor.endVisitCompilationUnit(this, (CompilationUnitDeclaration) get()); break; case TYPE: - visitor.visitType(this, (TypeDeclaration)get()); + visitor.visitType(this, (TypeDeclaration) get()); ast.traverseChildren(visitor, this); - visitor.endVisitType(this, (TypeDeclaration)get()); + visitor.endVisitType(this, (TypeDeclaration) get()); break; case FIELD: - visitor.visitField(this, (FieldDeclaration)get()); + visitor.visitField(this, (FieldDeclaration) get()); ast.traverseChildren(visitor, this); - visitor.endVisitField(this, (FieldDeclaration)get()); + visitor.endVisitField(this, (FieldDeclaration) get()); break; case INITIALIZER: - visitor.visitInitializer(this, (Initializer)get()); + visitor.visitInitializer(this, (Initializer) get()); ast.traverseChildren(visitor, this); - visitor.endVisitInitializer(this, (Initializer)get()); + visitor.endVisitInitializer(this, (Initializer) get()); break; case METHOD: if (get() instanceof Clinit) return; - visitor.visitMethod(this, (AbstractMethodDeclaration)get()); + visitor.visitMethod(this, (AbstractMethodDeclaration) get()); ast.traverseChildren(visitor, this); - visitor.endVisitMethod(this, (AbstractMethodDeclaration)get()); + visitor.endVisitMethod(this, (AbstractMethodDeclaration) get()); break; case ARGUMENT: - AbstractMethodDeclaration method = (AbstractMethodDeclaration)up().get(); - visitor.visitMethodArgument(this, (Argument)get(), method); + AbstractMethodDeclaration method = (AbstractMethodDeclaration) up().get(); + visitor.visitMethodArgument(this, (Argument) get(), method); ast.traverseChildren(visitor, this); - visitor.endVisitMethodArgument(this, (Argument)get(), method); + visitor.endVisitMethodArgument(this, (Argument) get(), method); break; case LOCAL: - visitor.visitLocal(this, (LocalDeclaration)get()); + visitor.visitLocal(this, (LocalDeclaration) get()); ast.traverseChildren(visitor, this); - visitor.endVisitLocal(this, (LocalDeclaration)get()); + visitor.endVisitLocal(this, (LocalDeclaration) get()); break; case ANNOTATION: switch (up().getKind()) { case TYPE: - visitor.visitAnnotationOnType((TypeDeclaration)up().get(), this, (Annotation)get()); + visitor.visitAnnotationOnType((TypeDeclaration) up().get(), this, (Annotation) get()); break; case FIELD: - visitor.visitAnnotationOnField((FieldDeclaration)up().get(), this, (Annotation)get()); + visitor.visitAnnotationOnField((FieldDeclaration) up().get(), this, (Annotation) get()); break; case METHOD: - visitor.visitAnnotationOnMethod((AbstractMethodDeclaration)up().get(), this, (Annotation)get()); + visitor.visitAnnotationOnMethod((AbstractMethodDeclaration) up().get(), this, (Annotation) get()); break; case ARGUMENT: visitor.visitAnnotationOnMethodArgument( - (Argument)parent.get(), - (AbstractMethodDeclaration)parent.directUp().get(), - this, (Annotation)get()); + (Argument) parent.get(), + (AbstractMethodDeclaration) parent.directUp().get(), + this, (Annotation) get()); break; case LOCAL: - visitor.visitAnnotationOnLocal((LocalDeclaration)parent.get(), this, (Annotation)get()); + visitor.visitAnnotationOnLocal((LocalDeclaration) parent.get(), this, (Annotation) get()); break; default: throw new AssertionError("Annotation not expected as child of a " + up().getKind()); } break; case STATEMENT: - visitor.visitStatement(this, (Statement)get()); + visitor.visitStatement(this, (Statement) get()); ast.traverseChildren(visitor, this); - visitor.endVisitStatement(this, (Statement)get()); + visitor.endVisitStatement(this, (Statement) get()); break; default: throw new AssertionError("Unexpected kind during node traversal: " + getKind()); diff --git a/src/core/lombok/eclipse/TransformEclipseAST.java b/src/core/lombok/eclipse/TransformEclipseAST.java index e6528178..683465c9 100644 --- a/src/core/lombok/eclipse/TransformEclipseAST.java +++ b/src/core/lombok/eclipse/TransformEclipseAST.java @@ -106,7 +106,7 @@ public class TransformEclipseAST { EclipseAST existing = null; if (astCacheField != null) { try { - existing = (EclipseAST)astCacheField.get(ast); + existing = (EclipseAST) astCacheField.get(ast); } catch (Exception e) { // existing remains null } diff --git a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java index 8326e1d0..6617d21a 100644 --- a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java +++ b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.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 @@ -48,6 +48,7 @@ import lombok.core.TypeResolver; import lombok.core.configuration.NullCheckExceptionType; import lombok.core.debug.ProblemReporter; import lombok.core.handlers.HandlerUtil; +import lombok.eclipse.Eclipse; import lombok.eclipse.EclipseAST; import lombok.eclipse.EclipseNode; import lombok.experimental.Accessors; @@ -143,7 +144,7 @@ public class EclipseHandlerUtil { return getGeneratedBy(node) != null; } - public static ASTNode setGeneratedBy(ASTNode node, ASTNode source) { + public static <T extends ASTNode> T setGeneratedBy(T node, ASTNode source) { ASTNode_generatedBy.set(node, source); return node; } @@ -166,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; @@ -195,16 +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()) { - for (Class<? extends java.lang.annotation.Annotation> annType : INVALID_ON_BUILDERS) { + if (child.getKind() != Kind.ANNOTATION) continue; + 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)); } } } @@ -298,6 +320,10 @@ public class EclipseHandlerUtil { return new SingleTypeReference(typeName, p); } + public static TypeReference[] copyTypes(TypeReference[] refs) { + return copyTypes(refs, null); + } + /** * Convenience method that creates a new array and copies each TypeReference in the source array via * {@link #copyType(TypeReference, ASTNode)}. @@ -312,6 +338,10 @@ public class EclipseHandlerUtil { return outs; } + public static TypeReference copyType(TypeReference ref) { + return copyType(ref, null); + } + /** * You can't share TypeReference objects or subtle errors start happening. * Unfortunately the TypeReference type hierarchy is complicated and there's no clone @@ -336,22 +366,23 @@ public class EclipseHandlerUtil { } } } + TypeReference typeRef = new ParameterizedQualifiedTypeReference(iRef.tokens, args, iRef.dimensions(), copy(iRef.sourcePositions)); - setGeneratedBy(typeRef, source); + if (source != null) setGeneratedBy(typeRef, source); return typeRef; } if (ref instanceof ArrayQualifiedTypeReference) { ArrayQualifiedTypeReference iRef = (ArrayQualifiedTypeReference) ref; TypeReference typeRef = new ArrayQualifiedTypeReference(iRef.tokens, iRef.dimensions(), copy(iRef.sourcePositions)); - setGeneratedBy(typeRef, source); + if (source != null) setGeneratedBy(typeRef, source); return typeRef; } if (ref instanceof QualifiedTypeReference) { QualifiedTypeReference iRef = (QualifiedTypeReference) ref; TypeReference typeRef = new QualifiedTypeReference(iRef.tokens, copy(iRef.sourcePositions)); - setGeneratedBy(typeRef, source); + if (source != null) setGeneratedBy(typeRef, source); return typeRef; } @@ -368,14 +399,14 @@ public class EclipseHandlerUtil { } TypeReference typeRef = new ParameterizedSingleTypeReference(iRef.token, args, iRef.dimensions(), (long)iRef.sourceStart << 32 | iRef.sourceEnd); - setGeneratedBy(typeRef, source); + if (source != null) setGeneratedBy(typeRef, source); return typeRef; } if (ref instanceof ArrayTypeReference) { ArrayTypeReference iRef = (ArrayTypeReference) ref; TypeReference typeRef = new ArrayTypeReference(iRef.token, iRef.dimensions(), (long)iRef.sourceStart << 32 | iRef.sourceEnd); - setGeneratedBy(typeRef, source); + if (source != null) setGeneratedBy(typeRef, source); return typeRef; } @@ -386,14 +417,14 @@ public class EclipseHandlerUtil { wildcard.sourceStart = original.sourceStart; wildcard.sourceEnd = original.sourceEnd; if (original.bound != null) wildcard.bound = copyType(original.bound, source); - setGeneratedBy(wildcard, source); + if (source != null) setGeneratedBy(wildcard, source); return wildcard; } if (ref instanceof SingleTypeReference) { SingleTypeReference iRef = (SingleTypeReference) ref; TypeReference typeRef = new SingleTypeReference(iRef.token, (long)iRef.sourceStart << 32 | iRef.sourceEnd); - setGeneratedBy(typeRef, source); + if (source != null) setGeneratedBy(typeRef, source); return typeRef; } @@ -401,19 +432,17 @@ public class EclipseHandlerUtil { } public static Annotation[] copyAnnotations(ASTNode source, Annotation[]... allAnnotations) { - boolean allNull = true; - - List<Annotation> result = new ArrayList<Annotation>(); + List<Annotation> result = null; for (Annotation[] annotations : allAnnotations) { if (annotations != null) { - allNull = false; for (Annotation annotation : annotations) { + if (result == null) result = new ArrayList<Annotation>(); result.add(copyAnnotation(annotation, source)); } } } - if (allNull) return null; - return result.toArray(new Annotation[0]); + + return result == null ? null : result.toArray(new Annotation[0]); } public static boolean hasAnnotation(Class<? extends java.lang.annotation.Annotation> type, EclipseNode node) { @@ -434,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. * @@ -441,11 +506,25 @@ public class EclipseHandlerUtil { */ public static boolean annotationTypeMatches(Class<? extends java.lang.annotation.Annotation> type, EclipseNode node) { if (node.getKind() != Kind.ANNOTATION) return false; - return typeMatches(type, node, ((Annotation)node.get()).type); + 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); } public static TypeReference cloneSelfType(EclipseNode context, ASTNode source) { - int pS = source.sourceStart, pE = source.sourceEnd; + int pS = source == null ? 0 : source.sourceStart, pE = source == null ? 0 : source.sourceEnd; long p = (long)pS << 32 | pE; EclipseNode type = context; TypeReference result = null; @@ -457,7 +536,7 @@ public class EclipseHandlerUtil { int idx = 0; for (TypeParameter param : typeDecl.typeParameters) { TypeReference typeRef = new SingleTypeReference(param.name, (long)param.sourceStart << 32 | param.sourceEnd); - setGeneratedBy(typeRef, source); + if (source != null) setGeneratedBy(typeRef, source); refs[idx++] = typeRef; } result = new ParameterizedSingleTypeReference(typeDecl.name, refs, 0, p); @@ -465,7 +544,7 @@ public class EclipseHandlerUtil { result = new SingleTypeReference(((TypeDeclaration)type.get()).name, p); } } - if (result != null) setGeneratedBy(result, source); + if (result != null && source != null) setGeneratedBy(result, source); return result; } @@ -668,47 +747,39 @@ public class EclipseHandlerUtil { */ public static <A extends java.lang.annotation.Annotation> AnnotationValues<A> createAnnotation(Class<A> type, final EclipseNode annotationNode) { + final Annotation annotation = (Annotation) annotationNode.get(); Map<String, AnnotationValue> values = new HashMap<String, AnnotationValue>(); - final MemberValuePair[] pairs = annotation.memberValuePairs(); - for (Method m : type.getDeclaredMethods()) { - if (!Modifier.isPublic(m.getModifiers())) continue; - String name = m.getName(); + MemberValuePair[] memberValuePairs = annotation.memberValuePairs(); + + if (memberValuePairs != null) for (final MemberValuePair pair : memberValuePairs) { List<String> raws = new ArrayList<String>(); List<Object> expressionValues = new ArrayList<Object>(); List<Object> guesses = new ArrayList<Object>(); - Expression fullExpression = null; Expression[] expressions = null; - if (pairs != null) for (MemberValuePair pair : pairs) { - char[] n = pair.name; - String mName = n == null ? "value" : new String(pair.name); - if (mName.equals(name)) fullExpression = pair.value; + char[] n = pair.name; + 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; + } else if (rhs != null) { + expressions = new Expression[] { rhs }; } - - boolean isExplicit = fullExpression != null; - - if (isExplicit) { - if (fullExpression instanceof ArrayInitializer) { - expressions = ((ArrayInitializer)fullExpression).expressions; - } else expressions = new Expression[] { fullExpression }; - if (expressions != null) for (Expression ex : expressions) { - StringBuffer sb = new StringBuffer(); - ex.print(0, sb); - raws.add(sb.toString()); - expressionValues.add(ex); - guesses.add(calculateValue(ex)); - } + if (expressions != null) for (Expression ex : expressions) { + StringBuffer sb = new StringBuffer(); + ex.print(0, sb); + raws.add(sb.toString()); + expressionValues.add(ex); + guesses.add(calculateValue(ex)); } - final Expression fullExpr = fullExpression; final Expression[] exprs = expressions; - - values.put(name, new AnnotationValue(annotationNode, raws, expressionValues, guesses, isExplicit) { + values.put(mName, new AnnotationValue(annotationNode, raws, expressionValues, guesses, true) { @Override public void setError(String message, int valueIdx) { Expression ex; - if (valueIdx == -1) ex = fullExpr; + if (valueIdx == -1) ex = rhs; else ex = exprs != null ? exprs[valueIdx] : null; if (ex == null) ex = annotation; @@ -721,7 +792,7 @@ public class EclipseHandlerUtil { @Override public void setWarning(String message, int valueIdx) { Expression ex; - if (valueIdx == -1) ex = fullExpr; + if (valueIdx == -1) ex = rhs; else ex = exprs != null ? exprs[valueIdx] : null; if (ex == null) ex = annotation; @@ -734,6 +805,21 @@ public class EclipseHandlerUtil { }); } + for (Method m : type.getDeclaredMethods()) { + if (!Modifier.isPublic(m.getModifiers())) continue; + String name = m.getName(); + if (!values.containsKey(name)) { + values.put(name, new AnnotationValue(annotationNode, new ArrayList<String>(), new ArrayList<Object>(), new ArrayList<Object>(), false) { + @Override public void setError(String message, int valueIdx) { + annotationNode.addError(message); + } + @Override public void setWarning(String message, int valueIdx) { + annotationNode.addWarning(message); + } + }); + } + } + return new AnnotationValues<A>(type, values, annotationNode); } @@ -811,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(); @@ -865,7 +951,7 @@ public class EclipseHandlerUtil { } static Expression createFieldAccessor(EclipseNode field, FieldAccess fieldAccess, ASTNode source) { - int pS = source.sourceStart, pE = source.sourceEnd; + int pS = source == null ? 0 : source.sourceStart, pE = source == null ? 0 : source.sourceEnd; long p = (long)pS << 32 | pE; boolean lookForGetter = lookForGetter(field, fieldAccess); @@ -881,14 +967,17 @@ public class EclipseHandlerUtil { ref.receiver = new SingleNameReference(((TypeDeclaration)containerNode.get()).name, p); } else { Expression smallRef = new FieldReference(field.getName().toCharArray(), p); - setGeneratedBy(smallRef, source); + if (source != null) setGeneratedBy(smallRef, source); return smallRef; } } else { ref.receiver = new ThisReference(pS, pE); } - setGeneratedBy(ref, source); - setGeneratedBy(ref.receiver, source); + + if (source != null) { + setGeneratedBy(ref, source); + setGeneratedBy(ref.receiver, source); + } return ref; } @@ -1145,9 +1234,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; } @@ -1158,6 +1246,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. @@ -1171,13 +1266,11 @@ public class EclipseHandlerUtil { 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; } @@ -1191,8 +1284,9 @@ public class EclipseHandlerUtil { * Inserts a field into an existing type. The type must represent a {@code TypeDeclaration}. * The field carries the @{@link SuppressWarnings}("all") annotation. */ - public static EclipseNode injectFieldSuppressWarnings(EclipseNode type, FieldDeclaration field) { - field.annotations = createSuppressWarningsAll(field, field.annotations); + public static EclipseNode injectFieldAndMarkGenerated(EclipseNode type, FieldDeclaration field) { + field.annotations = addSuppressWarningsAll(type, field, field.annotations); + field.annotations = addGenerated(type, field, field.annotations); return injectField(type, field); } @@ -1237,7 +1331,8 @@ public class EclipseHandlerUtil { * Inserts a method into an existing type. The type must represent a {@code TypeDeclaration}. */ public static EclipseNode injectMethod(EclipseNode type, AbstractMethodDeclaration method) { - method.annotations = createSuppressWarningsAll(method, method.annotations); + method.annotations = addSuppressWarningsAll(type, method, method.annotations); + method.annotations = addGenerated(type, method, method.annotations); TypeDeclaration parent = (TypeDeclaration) type.get(); if (parent.methods == null) { @@ -1279,9 +1374,10 @@ public class EclipseHandlerUtil { * @param type New type (class, interface, etc) to inject. */ public static EclipseNode injectType(final EclipseNode typeNode, final TypeDeclaration type) { - type.annotations = createSuppressWarningsAll(type, type.annotations); + type.annotations = addSuppressWarningsAll(typeNode, type, type.annotations); + type.annotations = addGenerated(typeNode, type, type.annotations); TypeDeclaration parent = (TypeDeclaration) typeNode.get(); - + if (parent.memberTypes == null) { parent.memberTypes = new TypeDeclaration[] { type }; } else { @@ -1295,32 +1391,82 @@ public class EclipseHandlerUtil { } private static final char[] ALL = "all".toCharArray(); + private static final char[] JUSTIFICATION = "justification".toCharArray(); + 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) { + Annotation[] anns = addAnnotation(source, originalAnnotationArray, TypeConstants.JAVA_LANG_SUPPRESSWARNINGS, new StringLiteral(ALL, 0, 0, 0)); + + if (Boolean.TRUE.equals(node.getAst().readConfiguration(ConfigurationKeys.ADD_FINDBUGS_SUPPRESSWARNINGS_ANNOTATIONS))) { + MemberValuePair mvp = new MemberValuePair(JUSTIFICATION, 0, 0, new StringLiteral(GENERATED_CODE, 0, 0, 0)); + anns = addAnnotation(source, anns, EDU_UMD_CS_FINDBUGS_ANNOTATIONS_SUPPRESSFBWARNINGS, mvp); + } + + return anns; + } + + public static Annotation[] addGenerated(EclipseNode node, ASTNode source, Annotation[] originalAnnotationArray) { + 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; + } - public static Annotation[] createSuppressWarningsAll(ASTNode source, Annotation[] originalAnnotationArray) { + 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 && new String(lastToken).equals("SuppressWarnings")) 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; long p = (long)pS << 32 | pE; - long[] poss = new long[3]; + long[] poss = new long[annotationTypeFqn.length]; Arrays.fill(poss, p); - QualifiedTypeReference suppressWarningsType = new QualifiedTypeReference(TypeConstants.JAVA_LANG_SUPPRESSWARNINGS, poss); - setGeneratedBy(suppressWarningsType, source); - SingleMemberAnnotation ann = new SingleMemberAnnotation(suppressWarningsType, pS); - ann.declarationSourceEnd = pE; - ann.memberValue = new StringLiteral(ALL, pS, pE, 0); + QualifiedTypeReference qualifiedType = new QualifiedTypeReference(annotationTypeFqn, poss); + setGeneratedBy(qualifiedType, source); + Annotation ann; + if (arg instanceof Expression) { + SingleMemberAnnotation sma = new SingleMemberAnnotation(qualifiedType, pS); + sma.declarationSourceEnd = pE; + arg.sourceStart = pS; + arg.sourceEnd = pE; + sma.memberValue = (Expression) arg; + setGeneratedBy(sma.memberValue, source); + ann = sma; + } else if (arg instanceof MemberValuePair) { + NormalAnnotation na = new NormalAnnotation(qualifiedType, pS); + na.declarationSourceEnd = pE; + arg.sourceStart = pS; + arg.sourceEnd = pE; + na.memberValuePairs = new MemberValuePair[] {(MemberValuePair) arg}; + setGeneratedBy(na.memberValuePairs[0], source); + setGeneratedBy(na.memberValuePairs[0].value, source); + na.memberValuePairs[0].value.sourceStart = pS; + na.memberValuePairs[0].value.sourceEnd = pE; + ann = na; + } else { + MarkerAnnotation ma = new MarkerAnnotation(qualifiedType, pS); + ma.declarationSourceEnd = pE; + ann = ma; + } setGeneratedBy(ann, source); - setGeneratedBy(ann.memberValue, source); if (originalAnnotationArray == null) return new Annotation[] { ann }; Annotation[] newAnnotationArray = new Annotation[originalAnnotationArray.length + 1]; System.arraycopy(originalAnnotationArray, 0, newAnnotationArray, 0, originalAnnotationArray.length); @@ -1489,7 +1635,7 @@ public class EclipseHandlerUtil { * with eclipse versions before 3.7. */ public static IntLiteral makeIntLiteral(char[] token, ASTNode source) { - int pS = source.sourceStart, pE = source.sourceEnd; + int pS = source == null ? 0 : source.sourceStart, pE = source == null ? 0 : source.sourceEnd; IntLiteral result; try { if (intLiteralConstructor != null) { @@ -1504,7 +1650,8 @@ public class EclipseHandlerUtil { } catch (InstantiationException e) { throw Lombok.sneakyThrow(e); } - setGeneratedBy(result, source); + + if (source != null) setGeneratedBy(result, source); return result; } @@ -1535,6 +1682,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 @@ -1557,51 +1712,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(); } @@ -1613,13 +1787,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(); } } diff --git a/src/core/lombok/eclipse/handlers/EclipseSingularsRecipes.java b/src/core/lombok/eclipse/handlers/EclipseSingularsRecipes.java new file mode 100644 index 00000000..bc779dab --- /dev/null +++ b/src/core/lombok/eclipse/handlers/EclipseSingularsRecipes.java @@ -0,0 +1,349 @@ +/* + * 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 + * 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 java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.jdt.internal.compiler.ast.ASTNode; +import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; +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.FieldDeclaration; +import org.eclipse.jdt.internal.compiler.ast.FieldReference; +import org.eclipse.jdt.internal.compiler.ast.IntLiteral; +import org.eclipse.jdt.internal.compiler.ast.MessageSend; +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.QualifiedTypeReference; +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.TypeReference; +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 lombok.core.LombokImmutableList; +import lombok.core.SpiLoadUtil; +import lombok.core.TypeLibrary; +import lombok.eclipse.EclipseNode; + +public class EclipseSingularsRecipes { + private static final EclipseSingularsRecipes INSTANCE = new EclipseSingularsRecipes(); + private final Map<String, EclipseSingularizer> singularizers = new HashMap<String, EclipseSingularizer>(); + private final TypeLibrary singularizableTypes = new TypeLibrary(); + + private EclipseSingularsRecipes() { + try { + loadAll(singularizableTypes, singularizers); + singularizableTypes.lock(); + } catch (IOException e) { + System.err.println("Lombok's @Singularizable feature is broken due to misconfigured SPI files: " + e); + } + } + + private static void loadAll(TypeLibrary library, Map<String, EclipseSingularizer> map) throws IOException { + for (EclipseSingularizer handler : SpiLoadUtil.findServices(EclipseSingularizer.class, EclipseSingularizer.class.getClassLoader())) { + for (String type : handler.getSupportedTypes()) { + EclipseSingularizer existingSingularizer = map.get(type); + if (existingSingularizer != null) { + EclipseSingularizer toKeep = existingSingularizer.getClass().getName().compareTo(handler.getClass().getName()) > 0 ? handler : existingSingularizer; + System.err.println("Multiple singularizers found for type " + type + "; the alphabetically first class is used: " + toKeep.getClass().getName()); + map.put(type, toKeep); + } else { + map.put(type, handler); + library.addType(type); + } + } + } + } + + public static EclipseSingularsRecipes get() { + return INSTANCE; + } + + public String toQualified(String typeReference) { + return singularizableTypes.toQualified(typeReference); + } + + public EclipseSingularizer getSingularizer(String fqn) { + return singularizers.get(fqn); + } + + public static final class SingularData { + private final EclipseNode annotation; + private final char[] singularName; + private final char[] pluralName; + private final List<TypeReference> typeArgs; + private final String targetFqn; + private final EclipseSingularizer singularizer; + private final ASTNode source; + + public SingularData(EclipseNode annotation, char[] singularName, char[] pluralName, List<TypeReference> typeArgs, String targetFqn, EclipseSingularizer singularizer, ASTNode source) { + this.annotation = annotation; + this.singularName = singularName; + this.pluralName = pluralName; + this.typeArgs = typeArgs; + this.targetFqn = targetFqn; + this.singularizer = singularizer; + this.source = source; + } + + public void setGeneratedByRecursive(ASTNode target) { + SetGeneratedByVisitor visitor = new SetGeneratedByVisitor(source); + + if (target instanceof AbstractMethodDeclaration) { + ((AbstractMethodDeclaration) target).traverse(visitor, (ClassScope) null); + } else if (target instanceof FieldDeclaration) { + ((FieldDeclaration) target).traverse(visitor, (MethodScope) null); + } else { + target.traverse(visitor, null); + } + } + + public ASTNode getSource() { + return source; + } + + public EclipseNode getAnnotation() { + return annotation; + } + + public char[] getSingularName() { + return singularName; + } + + public char[] getPluralName() { + return pluralName; + } + + public List<TypeReference> getTypeArgs() { + return typeArgs; + } + + public String getTargetFqn() { + return targetFqn; + } + + public EclipseSingularizer getSingularizer() { + return singularizer; + } + + public String getTargetSimpleType() { + int idx = targetFqn.lastIndexOf("."); + return idx == -1 ? targetFqn : targetFqn.substring(idx + 1); + } + } + + public static abstract class EclipseSingularizer { + protected static final long[] NULL_POSS = {0L}; + public abstract LombokImmutableList<String> getSupportedTypes(); + + /** 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(EclipseNode builderType, SingularData data) { + for (EclipseNode child : builderType.down()) { + switch (child.getKind()) { + case FIELD: { + FieldDeclaration fd = (FieldDeclaration) child.get(); + char[] name = fd.name; + if (name == null) continue; + if (getGeneratedBy(fd) != null) continue; + for (char[] fieldToBeGenerated : listFieldsToBeGenerated(data, builderType)) { + if (!Arrays.equals(name, fieldToBeGenerated)) continue; + child.addError("Manually adding a field that @Singular @Builder would generate is not supported. If you want to manually manage the builder aspect for this field/parameter, don't use @Singular."); + return true; + } + break; + } + case METHOD: { + AbstractMethodDeclaration method = (AbstractMethodDeclaration) child.get(); + char[] name = method.selector; + if (name == null) continue; + if (getGeneratedBy(method) != null) continue; + for (char[] methodToBeGenerated : listMethodsToBeGenerated(data, builderType)) { + if (!Arrays.equals(name, methodToBeGenerated)) continue; + child.addError("Manually adding a method that @Singular @Builder would generate is not supported. If you want to manually manage the builder aspect for this field/parameter, don't use @Singular."); + return true; + } + break; + }} + } + + return false; + } + + public List<char[]> listFieldsToBeGenerated(SingularData data, EclipseNode builderType) { + return Collections.singletonList(data.pluralName); + } + + public List<char[]> listMethodsToBeGenerated(SingularData data, EclipseNode builderType) { + char[] p = data.pluralName; + char[] s = data.singularName; + if (Arrays.equals(p, s)) return Collections.singletonList(p); + return Arrays.asList(p, s); + } + + public abstract List<EclipseNode> generateFields(SingularData data, EclipseNode builderType); + public abstract void generateMethods(SingularData data, boolean deprecate, EclipseNode builderType, boolean fluent, boolean chain); + public abstract void appendBuildCode(SingularData data, EclipseNode builderType, List<Statement> statements, char[] targetVariableName); + + public boolean requiresCleaning() { + try { + return !getClass().getMethod("appendCleaningCode", SingularData.class, EclipseNode.class, List.class).getDeclaringClass().equals(EclipseSingularizer.class); + } catch (NoSuchMethodException e) { + return false; + } + } + + public void appendCleaningCode(SingularData data, EclipseNode builderType, List<Statement> statements) { + } + + // -- Utility methods -- + + /** + * Adds the requested number of type arguments to the provided type, copying each argument in {@code typeArgs}. If typeArgs is too long, the extra elements are ignored. + * If {@code typeArgs} is null or too short, {@code java.lang.Object} will be substituted for each missing type argument. + * + * @param count The number of type arguments requested. + * @param addExtends If {@code true}, all bounds are either '? extends X' or just '?'. If false, the reverse is applied, and '? extends Foo' is converted to Foo, '?' to Object, etc. + * @param node Some node in the same AST. Just used to obtain makers and contexts and such. + * @param type The type to add generics to. + * @param typeArgs the list of type args to clone. + * @param source The source annotation that is the root cause of this code generation. + */ + protected TypeReference addTypeArgs(int count, boolean addExtends, EclipseNode node, TypeReference type, List<TypeReference> typeArgs) { + TypeReference[] clonedAndFixedArgs = createTypeArgs(count, addExtends, node, typeArgs); + if (type instanceof SingleTypeReference) { + type = new ParameterizedSingleTypeReference(((SingleTypeReference) type).token, clonedAndFixedArgs, 0, 0L); + } else if (type instanceof QualifiedTypeReference) { + QualifiedTypeReference qtr = (QualifiedTypeReference) type; + TypeReference[][] trs = new TypeReference[qtr.tokens.length][]; + trs[qtr.tokens.length - 1] = clonedAndFixedArgs; + type = new ParameterizedQualifiedTypeReference(((QualifiedTypeReference) type).tokens, trs, 0, NULL_POSS); + } else { + node.addError("Don't know how to clone-and-parameterize type: " + type); + } + + return type; + } + + protected TypeReference[] createTypeArgs(int count, boolean addExtends, EclipseNode node, List<TypeReference> typeArgs) { + if (count < 0) throw new IllegalArgumentException("count is negative"); + if (count == 0) return null; + List<TypeReference> arguments = new ArrayList<TypeReference>(); + + if (typeArgs != null) for (TypeReference orig : typeArgs) { + Wildcard wildcard = orig instanceof Wildcard ? (Wildcard) orig : null; + if (!addExtends) { + if (wildcard != null && (wildcard.kind == Wildcard.UNBOUND || wildcard.kind == Wildcard.SUPER)) { + arguments.add(new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, NULL_POSS)); + } else if (wildcard != null && wildcard.kind == Wildcard.EXTENDS) { + try { + arguments.add(copyType(wildcard.bound)); + } catch (Exception e) { + arguments.add(new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, NULL_POSS)); + } + } else { + arguments.add(copyType(orig)); + } + } else { + if (wildcard != null && (wildcard.kind == Wildcard.UNBOUND || wildcard.kind == Wildcard.SUPER)) { + Wildcard w = new Wildcard(Wildcard.UNBOUND); + arguments.add(w); + } else if (wildcard != null && wildcard.kind == Wildcard.EXTENDS) { + arguments.add(copyType(orig)); + } else { + Wildcard w = new Wildcard(Wildcard.EXTENDS); + w.bound = copyType(orig); + arguments.add(w); + } + } + if (--count == 0) break; + } + + while (count-- > 0) { + if (addExtends) { + arguments.add(new Wildcard(Wildcard.UNBOUND)); + } else { + arguments.add(new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, NULL_POSS)); + } + } + + if (arguments.isEmpty()) return null; + return arguments.toArray(new TypeReference[arguments.size()]); + } + + 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) { + MessageSend invoke = new MessageSend(); + ThisReference thisRef = new ThisReference(0, 0); + 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); + FieldReference cdnThisDotName = new FieldReference(name, 0L); + cdnThisDotName.receiver = cdnThisRef; + NullLiteral nullLiteral = new NullLiteral(0, 0); + EqualExpression isNull = new EqualExpression(cdnThisDotName, nullLiteral, OperatorIds.EQUAL_EQUAL); + IntLiteral zeroLiteral = makeIntLiteral(new char[] {'0'}, null); + ConditionalExpression conditional = new ConditionalExpression(isNull, zeroLiteral, invoke); + return conditional; + } + + protected TypeReference cloneParamType(int index, List<TypeReference> typeArgs, EclipseNode builderType) { + if (typeArgs != null && typeArgs.size() > index) { + TypeReference originalType = typeArgs.get(index); + if (originalType instanceof Wildcard) { + Wildcard wOriginalType = (Wildcard) originalType; + if (wOriginalType.kind == Wildcard.EXTENDS) { + try { + return copyType(wOriginalType.bound); + } catch (Exception e) { + // fallthrough + } + } + } else { + return copyType(originalType); + } + } + + return new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, NULL_POSS); + } + } +} diff --git a/src/core/lombok/eclipse/handlers/HandleBuilder.java b/src/core/lombok/eclipse/handlers/HandleBuilder.java index 522501f6..d4cdc654 100644 --- a/src/core/lombok/eclipse/handlers/HandleBuilder.java +++ b/src/core/lombok/eclipse/handlers/HandleBuilder.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 @@ -32,53 +32,132 @@ 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.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; 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.OperatorIds; +import org.eclipse.jdt.internal.compiler.ast.ParameterizedQualifiedTypeReference; import org.eclipse.jdt.internal.compiler.ast.ParameterizedSingleTypeReference; +import org.eclipse.jdt.internal.compiler.ast.QualifiedThisReference; 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.TrueLiteral; 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.UnaryExpression; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; 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 org.mangosdk.spi.ProviderFor; import lombok.AccessLevel; +import lombok.Builder; +import lombok.Builder.ObtainVia; import lombok.ConfigurationKeys; +import lombok.Singular; import lombok.core.AST.Kind; +import lombok.core.handlers.HandlerUtil; import lombok.core.AnnotationValues; import lombok.core.HandlerPriority; import lombok.eclipse.Eclipse; import lombok.eclipse.EclipseAnnotationHandler; import lombok.eclipse.EclipseNode; +import lombok.eclipse.handlers.EclipseSingularsRecipes.EclipseSingularizer; +import lombok.eclipse.handlers.EclipseSingularsRecipes.SingularData; import lombok.eclipse.handlers.HandleConstructor.SkipIfConstructorExists; -import lombok.experimental.Builder; 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> { - @Override public void handle(AnnotationValues<Builder> annotation, Annotation ast, EclipseNode annotationNode) { - handleExperimentalFlagUsage(annotationNode, ConfigurationKeys.BUILDER_FLAG_USAGE, "@Builder"); + private HandleConstructor handleConstructor = new HandleConstructor(); + + private static final char[] CLEAN_FIELD_NAME = "$lombokUnclean".toCharArray(); + private static final char[] CLEAN_METHOD_NAME = "$lombokClean".toCharArray(); + + private static final boolean toBoolean(Object expr, boolean defaultValue) { + if (expr == null) return defaultValue; + if (expr instanceof FalseLiteral) return false; + if (expr instanceof TrueLiteral) return true; + return ((Boolean) expr).booleanValue(); + } + + private static class BuilderFieldData { + TypeReference type; + char[] rawName; + char[] name; + char[] nameOfDefaultProvider; + char[] nameOfSetFlag; + SingularData singularData; + ObtainVia obtainVia; + EclipseNode obtainViaNode; + EclipseNode originalFieldNode; + List<EclipseNode> createdFields = new ArrayList<EclipseNode>(); + } + + private static boolean equals(String a, char[] b) { + if (a.length() != b.length) return false; + for (int i = 0; i < b.length; i++) { + if (a.charAt(i) != b[i]) return false; + } + return true; + } + + private static boolean equals(String a, char[][] b) { + if (a == null || a.isEmpty()) return b.length == 0; + String[] aParts = a.split("\\."); + if (aParts.length != b.length) return false; + for (int i = 0; i < b.length; i++) { + if (!equals(aParts[i], b[i])) return false; + } + 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; Builder builderInstance = annotation.getInstance(); + + // These exist just to support the 'old' lombok.experimental.Builder, which had these properties. lombok.Builder no longer has them. + boolean fluent = toBoolean(annotation.getActualExpression("fluent"), true); + boolean chain = toBoolean(annotation.getActualExpression("chain"), true); + String builderMethodName = builderInstance.builderMethodName(); String buildMethodName = builderInstance.buildMethodName(); String builderClassName = builderInstance.builderClassName(); + String toBuilderMethodName = "toBuilder"; + boolean toBuilder = builderInstance.toBuilder(); + List<char[]> typeArgsForToBuilder = null; if (builderMethodName == null) builderMethodName = "builder"; if (buildMethodName == null) builderMethodName = "build"; @@ -92,36 +171,64 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { EclipseNode parent = annotationNode.up(); - List<TypeReference> typesOfParameters = new ArrayList<TypeReference>(); - List<char[]> namesOfParameters = new ArrayList<char[]>(); + List<BuilderFieldData> builderFields = new ArrayList<BuilderFieldData>(); TypeReference returnType; TypeParameter[] typeParams; TypeReference[] thrownExceptions; char[] nameOfStaticBuilderMethod; EclipseNode tdParent; - AbstractMethodDeclaration fillParametersFrom = null; + EclipseNode fillParametersFrom = parent.get() instanceof AbstractMethodDeclaration ? parent : null; + boolean addCleaning = false; + boolean isStatic = true; if (parent.get() instanceof TypeDeclaration) { tdParent = parent; TypeDeclaration td = (TypeDeclaration) tdParent.get(); - List<EclipseNode> fields = new ArrayList<EclipseNode>(); - @SuppressWarnings("deprecation") - boolean valuePresent = (hasAnnotation(lombok.Value.class, parent) || hasAnnotation(lombok.experimental.Value.class, parent)); - for (EclipseNode fieldNode : HandleConstructor.findAllFields(tdParent)) { + List<EclipseNode> allFields = new ArrayList<EclipseNode>(); + 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; - namesOfParameters.add(removePrefixFromField(fieldNode)); - typesOfParameters.add(fd.type); - fields.add(fieldNode); + 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, fields, null, SkipIfConstructorExists.I_AM_BUILDER, null, - Collections.<Annotation>emptyList(), annotationNode); + handleConstructor.generateConstructor(tdParent, AccessLevel.PACKAGE, allFields, false, null, SkipIfConstructorExists.I_AM_BUILDER, + Collections.<Annotation>emptyList(), annotationNode); returnType = namePlusTypeParamsToTypeReference(td.name, td.typeParameters, p); typeParams = td.typeParameters; @@ -137,7 +244,6 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { tdParent = parent.up(); TypeDeclaration td = (TypeDeclaration) tdParent.get(); - fillParametersFrom = cd; returnType = namePlusTypeParamsToTypeReference(td.name, td.typeParameters, p); typeParams = td.typeParameters; thrownExceptions = cd.thrownExceptions; @@ -146,11 +252,79 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { } else if (parent.get() instanceof MethodDeclaration) { MethodDeclaration md = (MethodDeclaration) parent.get(); tdParent = parent.up(); - if (!md.isStatic()) { - annotationNode.addError("@Builder is only supported on types, constructors, and static methods."); - return; + isStatic = md.isStatic(); + + if (toBuilder) { + final String TO_BUILDER_NOT_SUPPORTED = "@Builder(toBuilder=true) is only supported if you return your own type."; + char[] token; + char[][] pkg = null; + if (md.returnType.dimensions() > 0) { + annotationNode.addError(TO_BUILDER_NOT_SUPPORTED); + return; + } + + if (md.returnType instanceof SingleTypeReference) { + token = ((SingleTypeReference) md.returnType).token; + } else if (md.returnType instanceof QualifiedTypeReference) { + pkg = ((QualifiedTypeReference) md.returnType).tokens; + token = pkg[pkg.length]; + char[][] pkg_ = new char[pkg.length - 1][]; + System.arraycopy(pkg, 0, pkg_, 0, pkg_.length); + pkg = pkg_; + } else { + annotationNode.addError(TO_BUILDER_NOT_SUPPORTED); + return; + } + + if (pkg != null && !equals(parent.getPackageDeclaration(), pkg)) { + annotationNode.addError(TO_BUILDER_NOT_SUPPORTED); + return; + } + + if (tdParent == null || !equals(tdParent.getName(), token)) { + annotationNode.addError(TO_BUILDER_NOT_SUPPORTED); + return; + } + + TypeParameter[] tpOnType = ((TypeDeclaration) tdParent.get()).typeParameters; + TypeParameter[] tpOnMethod = md.typeParameters; + TypeReference[][] tpOnRet_ = null; + if (md.returnType instanceof ParameterizedSingleTypeReference) { + tpOnRet_ = new TypeReference[1][]; + tpOnRet_[0] = ((ParameterizedSingleTypeReference) md.returnType).typeArguments; + } else if (md.returnType instanceof ParameterizedQualifiedTypeReference) { + tpOnRet_ = ((ParameterizedQualifiedTypeReference) md.returnType).typeArguments; + } + + if (tpOnRet_ != null) for (int i = 0; i < tpOnRet_.length - 1; i++) { + if (tpOnRet_[i] != null && tpOnRet_[i].length > 0) { + annotationNode.addError("@Builder(toBuilder=true) is not supported if returning a type with generics applied to an intermediate."); + return; + } + } + TypeReference[] tpOnRet = tpOnRet_ == null ? null : tpOnRet_[tpOnRet_.length - 1]; + typeArgsForToBuilder = new ArrayList<char[]>(); + + // Every typearg on this method needs to be found in the return type, but the reverse is not true. + // We also need to 'map' them. + + + if (tpOnMethod != null) for (TypeParameter onMethod : tpOnMethod) { + int pos = -1; + if (tpOnRet != null) for (int i = 0; i < tpOnRet.length; i++) { + if (tpOnRet[i].getClass() != SingleTypeReference.class) continue; + if (!Arrays.equals(((SingleTypeReference) tpOnRet[i]).token, onMethod.name)) continue; + pos = i; + } + if (pos == -1 || tpOnType == null || tpOnType.length <= pos) { + 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 " + new String(onMethod.name) + " is not part of the return type."); + return; + } + + typeArgsForToBuilder.add(tpOnType[pos].name); + } } - fillParametersFrom = md; + returnType = copyType(md.returnType, ast); typeParams = md.typeParameters; thrownExceptions = md.thrownExceptions; @@ -185,159 +359,373 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { builderClassName = new String(token) + "Builder"; } } else { - annotationNode.addError("@Builder is only supported on types, constructors, and static methods."); + annotationNode.addError("@Builder is only supported on types, constructors, and methods."); return; } if (fillParametersFrom != null) { - if (fillParametersFrom.arguments != null) for (Argument a : fillParametersFrom.arguments) { - namesOfParameters.add(a.name); - typesOfParameters.add(a.type); + for (EclipseNode param : fillParametersFrom.down()) { + if (param.getKind() != Kind.ARGUMENT) continue; + BuilderFieldData bfd = new BuilderFieldData(); + Argument arg = (Argument) param.get(); + bfd.rawName = arg.name; + bfd.name = arg.name; + bfd.type = arg.type; + bfd.singularData = getSingularData(param, ast); + bfd.originalFieldNode = param; + addObtainVia(bfd, param); + builderFields.add(bfd); } } EclipseNode builderType = findInnerClass(tdParent, builderClassName); if (builderType == null) { - builderType = makeBuilderClass(tdParent, builderClassName, typeParams, ast); + builderType = makeBuilderClass(isStatic, tdParent, builderClassName, typeParams, ast); } else { + TypeDeclaration builderTypeDeclaration = (TypeDeclaration) builderType.get(); + if (isStatic && (builderTypeDeclaration.modifiers & ClassFileConstants.AccStatic) == 0) { + annotationNode.addError("Existing Builder must be a static inner class."); + return; + } else if (!isStatic && (builderTypeDeclaration.modifiers & ClassFileConstants.AccStatic) != 0) { + annotationNode.addError("Existing Builder must be a non-static inner class."); + return; + } sanityCheckForMethodGeneratingAnnotationsOnBuilderClass(builderType, annotationNode); + /* generate errors for @Singular BFDs that have one already defined node. */ { + for (BuilderFieldData bfd : builderFields) { + SingularData sd = bfd.singularData; + if (sd == null) continue; + EclipseSingularizer singularizer = sd.getSingularizer(); + if (singularizer == null) continue; + if (singularizer.checkForAlreadyExistingNodesAndGenerateError(builderType, sd)) { + bfd.singularData = null; + } + } + } + } + + 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; + } + } } - List<EclipseNode> fieldNodes = addFieldsToBuilder(builderType, namesOfParameters, typesOfParameters, ast); - List<AbstractMethodDeclaration> newMethods = new ArrayList<AbstractMethodDeclaration>(); - for (EclipseNode fieldNode : fieldNodes) { - MethodDeclaration newMethod = makeSetterMethodForBuilder(builderType, fieldNode, annotationNode, builderInstance.fluent(), builderInstance.chain()); - if (newMethod != null) newMethods.add(newMethod); + + 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); } if (constructorExists(builderType) == MemberExistsResult.NOT_EXISTS) { ConstructorDeclaration cd = HandleConstructor.createConstructor( - AccessLevel.PACKAGE, builderType, Collections.<EclipseNode>emptyList(), null, - annotationNode, Collections.<Annotation>emptyList()); + AccessLevel.PACKAGE, builderType, Collections.<EclipseNode>emptyList(), false, + annotationNode, Collections.<Annotation>emptyList()); if (cd != null) injectMethod(builderType, cd); } - for (AbstractMethodDeclaration newMethod : newMethods) injectMethod(builderType, newMethod); + for (BuilderFieldData bfd : builderFields) { + makeSetterMethodsForBuilder(builderType, bfd, annotationNode, fluent, chain); + } + if (methodExists(buildMethodName, builderType, -1) == MemberExistsResult.NOT_EXISTS) { - MethodDeclaration md = generateBuildMethod(buildMethodName, nameOfStaticBuilderMethod, returnType, namesOfParameters, builderType, ast, thrownExceptions); + 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>(); + for (BuilderFieldData bfd : builderFields) { + fieldNodes.addAll(bfd.createdFields); + } MethodDeclaration md = HandleToString.createToString(builderType, fieldNodes, true, false, ast, FieldAccess.ALWAYS_FIELD); if (md != null) injectMethod(builderType, md); } + if (addCleaning) { + MethodDeclaration cleanMethod = generateCleanMethod(builderFields, builderType, ast); + if (cleanMethod != null) injectMethod(builderType, cleanMethod); + } + if (methodExists(builderMethodName, tdParent, -1) == MemberExistsResult.NOT_EXISTS) { - MethodDeclaration md = generateBuilderMethod(builderMethodName, builderClassName, tdParent, typeParams, ast); + MethodDeclaration md = generateBuilderMethod(isStatic, builderMethodName, builderClassName, tdParent, typeParams, ast); + if (md != null) injectMethod(tdParent, md); + } + + if (toBuilder) switch (methodExists(toBuilderMethodName, tdParent, 0)) { + case EXISTS_BY_USER: + annotationNode.addWarning("Not generating toBuilder() as it already exists."); + break; + case NOT_EXISTS: + TypeParameter[] tps = typeParams; + if (typeArgsForToBuilder != null) { + tps = new TypeParameter[typeArgsForToBuilder.size()]; + for (int i = 0; i < tps.length; i++) { + tps[i] = new TypeParameter(); + tps[i].name = typeArgsForToBuilder.get(i); + } + } + MethodDeclaration md = generateToBuilderMethod(toBuilderMethodName, builderClassName, tdParent, tps, builderFields, fluent, ast); + if (md != null) injectMethod(tdParent, md); } } - public MethodDeclaration generateBuilderMethod(String builderMethodName, String builderClassName, EclipseNode type, TypeParameter[] typeParams, ASTNode source) { + private MethodDeclaration generateToBuilderMethod(String methodName, String builderClassName, EclipseNode type, TypeParameter[] typeParams, List<BuilderFieldData> builderFields, boolean fluent, ASTNode source) { + // return new ThingieBuilder<A, B>().setA(this.a).setB(this.b); + 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.selector = methodName.toCharArray(); + out.modifiers = ClassFileConstants.AccPublic; out.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; out.returnType = namePlusTypeParamsToTypeReference(builderClassName.toCharArray(), typeParams, p); - out.typeParameters = copyTypeParams(typeParams, source); AllocationExpression invoke = new AllocationExpression(); invoke.type = namePlusTypeParamsToTypeReference(builderClassName.toCharArray(), typeParams, p); - out.statements = new Statement[] {new ReturnStatement(invoke, pS, pE)}; + + Expression receiver = invoke; + for (BuilderFieldData bfd : builderFields) { + char[] setterName = fluent ? bfd.name : HandlerUtil.buildAccessorName("set", new String(bfd.name)).toCharArray(); + MessageSend ms = new MessageSend(); + if (bfd.obtainVia == null || !bfd.obtainVia.field().isEmpty()) { + char[] fieldName = bfd.obtainVia == null ? bfd.rawName : bfd.obtainVia.field().toCharArray(); + FieldReference fr = new FieldReference(fieldName, 0); + fr.receiver = new ThisReference(0, 0); + ms.arguments = new Expression[] {fr}; + } else { + String obtainName = bfd.obtainVia.method(); + boolean obtainIsStatic = bfd.obtainVia.isStatic(); + MessageSend obtainExpr = new MessageSend(); + obtainExpr.receiver = obtainIsStatic ? new SingleNameReference(type.getName().toCharArray(), 0) : new ThisReference(0, 0); + obtainExpr.selector = obtainName.toCharArray(); + if (obtainIsStatic) obtainExpr.arguments = new Expression[] {new ThisReference(0, 0)}; + ms.arguments = new Expression[] {obtainExpr}; + } + ms.receiver = receiver; + ms.selector = setterName; + receiver = ms; + } + + out.statements = new Statement[] {new ReturnStatement(receiver, pS, pE)}; out.traverse(new SetGeneratedByVisitor(source), ((TypeDeclaration) type.get()).scope); return out; + } - public MethodDeclaration generateBuildMethod(String name, char[] staticName, TypeReference returnType, List<char[]> fieldNames, EclipseNode type, ASTNode source, TypeReference[] thrownExceptions) { - int pS = source.sourceStart, pE = source.sourceEnd; - long p = (long) pS << 32 | pE; + private MethodDeclaration generateCleanMethod(List<BuilderFieldData> builderFields, EclipseNode builderType, ASTNode source) { + List<Statement> statements = new ArrayList<Statement>(); - MethodDeclaration out = new MethodDeclaration( - ((CompilationUnitDeclaration) type.top().get()).compilationResult); + for (BuilderFieldData bfd : builderFields) { + if (bfd.singularData != null && bfd.singularData.getSingularizer() != null) { + bfd.singularData.getSingularizer().appendCleaningCode(bfd.singularData, builderType, statements); + } + } - out.modifiers = ClassFileConstants.AccPublic; - TypeDeclaration typeDecl = (TypeDeclaration) type.get(); - out.selector = name.toCharArray(); - out.thrownExceptions = copyTypes(thrownExceptions, source); + 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; + } + + 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; - out.returnType = returnType; + List<Statement> statements = new ArrayList<Statement>(); + + if (addCleaning) { + FieldReference thisUnclean = new FieldReference(CLEAN_FIELD_NAME, 0); + thisUnclean.receiver = new ThisReference(0, 0); + Expression notClean = new UnaryExpression(thisUnclean, OperatorIds.NOT); + MessageSend invokeClean = new MessageSend(); + invokeClean.selector = CLEAN_METHOD_NAME; + statements.add(new IfStatement(notClean, invokeClean, 0, 0)); + } + + for (BuilderFieldData bfd : builderFields) { + if (bfd.singularData != null && bfd.singularData.getSingularizer() != null) { + bfd.singularData.getSingularizer().appendBuildCode(bfd.singularData, type, statements, bfd.name); + } + } + + List<Expression> args = new ArrayList<Expression>(); + for (BuilderFieldData bfd : builderFields) { + 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)); + } + } - List<Expression> assigns = new ArrayList<Expression>(); - for (char[] fieldName : fieldNames) { - SingleNameReference nameRef = new SingleNameReference(fieldName, p); - assigns.add(nameRef); + if (addCleaning) { + FieldReference thisUnclean = new FieldReference(CLEAN_FIELD_NAME, 0); + thisUnclean.receiver = new ThisReference(0, 0); + statements.add(new Assignment(thisUnclean, new TrueLiteral(0, 0), 0)); } - Statement statement; + out.modifiers = ClassFileConstants.AccPublic; + out.selector = name.toCharArray(); + out.thrownExceptions = copyTypes(thrownExceptions); + out.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + out.returnType = returnType; if (staticName == null) { AllocationExpression allocationStatement = new AllocationExpression(); - allocationStatement.type = copyType(out.returnType, source); - allocationStatement.arguments = assigns.isEmpty() ? null : assigns.toArray(new Expression[assigns.size()]); - statement = new ReturnStatement(allocationStatement, (int)(p >> 32), (int)p); + allocationStatement.type = copyType(out.returnType); + allocationStatement.arguments = args.isEmpty() ? null : args.toArray(new Expression[args.size()]); + statements.add(new ReturnStatement(allocationStatement, 0, 0)); } else { MessageSend invoke = new MessageSend(); invoke.selector = staticName; - invoke.receiver = new SingleNameReference(type.up().getName().toCharArray(), p); - 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, p); - } - invoke.typeArguments = trs; - } - invoke.arguments = assigns.isEmpty() ? null : assigns.toArray(new Expression[assigns.size()]); + if (isStatic) + invoke.receiver = new SingleNameReference(type.up().getName().toCharArray(), 0); + else + invoke.receiver = new QualifiedThisReference(new SingleTypeReference(type.up().getName().toCharArray(), 0) , 0, 0); + + 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)) { - statement = invoke; + statements.add(invoke); } else { - statement = new ReturnStatement(invoke, (int)(p >> 32), (int)p); + statements.add(new ReturnStatement(invoke, 0, 0)); } } + out.statements = statements.isEmpty() ? null : statements.toArray(new Statement[statements.size()]); + out.traverse(new SetGeneratedByVisitor(source), (ClassScope) null); + return out; + } + + private TypeReference[] typeParameterNames(TypeParameter[] typeParameters) { + if (typeParameters == null) return null; - out.statements = new Statement[] { statement }; + 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 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), typeDecl.scope); + out.traverse(new SetGeneratedByVisitor(source), ((TypeDeclaration) fieldNode.up().get()).scope); return out; } - public List<EclipseNode> addFieldsToBuilder(EclipseNode builderType, List<char[]> namesOfParameters, List<TypeReference> typesOfParameters, ASTNode source) { - int len = namesOfParameters.size(); - TypeDeclaration td = (TypeDeclaration) builderType.get(); - FieldDeclaration[] existing = td.fields; - if (existing == null) existing = new FieldDeclaration[0]; - - List<EclipseNode> out = new ArrayList<EclipseNode>(); - - top: - for (int i = len - 1; i >= 0; i--) { - char[] name = namesOfParameters.get(i); - for (FieldDeclaration exists : existing) { - if (Arrays.equals(exists.name, name)) { - out.add(builderType.getNodeFor(exists)); - continue top; - } - } - TypeReference fieldReference = copyType(typesOfParameters.get(i), source); - FieldDeclaration newField = new FieldDeclaration(name, 0, 0); - newField.bits |= Eclipse.ECLIPSE_DO_NOT_TOUCH_FLAG; - newField.modifiers = ClassFileConstants.AccPrivate; - newField.type = fieldReference; - out.add(injectField(builderType, newField)); - } + 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; - Collections.reverse(out); + MethodDeclaration out = new MethodDeclaration(((CompilationUnitDeclaration) type.top().get()).compilationResult); + out.selector = builderMethodName.toCharArray(); + out.modifiers = ClassFileConstants.AccPublic; + if (isStatic) out.modifiers |= ClassFileConstants.AccStatic; + out.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + out.returnType = namePlusTypeParamsToTypeReference(builderClassName.toCharArray(), typeParams, p); + out.typeParameters = copyTypeParams(typeParams, source); + AllocationExpression invoke = new AllocationExpression(); + invoke.type = namePlusTypeParamsToTypeReference(builderClassName.toCharArray(), typeParams, p); + out.statements = new Statement[] {new ReturnStatement(invoke, pS, pE)}; + out.traverse(new SetGeneratedByVisitor(source), ((TypeDeclaration) type.get()).scope); return out; } + public 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 static final AbstractMethodDeclaration[] EMPTY = {}; - public MethodDeclaration makeSetterMethodForBuilder(EclipseNode builderType, EclipseNode fieldNode, EclipseNode sourceNode, boolean fluent, boolean chain) { + 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, deprecate, bfd.createdFields.get(0), bfd.nameOfSetFlag, sourceNode, fluent, chain); + } else { + bfd.singularData.getSingularizer().generateMethods(bfd.singularData, deprecate, builderType, fluent, 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; @@ -348,14 +736,14 @@ 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 null; + if (Arrays.equals(name, existingName) && !isTolerate(fieldNode, existing[i])) return; } - boolean isBoolean = isBoolean(fd.type); - String setterName = fluent ? fieldNode.getName() : toSetterName(builderType.getAst(), null, fieldNode.getName(), isBoolean); + String setterName = fluent ? fieldNode.getName() : HandlerUtil.buildAccessorName("set", fieldNode.getName()); - return HandleSetter.createSetter(td, fieldNode, setterName, chain, ClassFileConstants.AccPublic, - sourceNode, Collections.<Annotation>emptyList(), Collections.<Annotation>emptyList()); + MethodDeclaration setter = HandleSetter.createSetter(td, deprecate, fieldNode, setterName, nameOfSetFlag, chain, ClassFileConstants.AccPublic, + sourceNode, Collections.<Annotation>emptyList(), Collections.<Annotation>emptyList()); + injectMethod(builderType, setter); } public EclipseNode findInnerClass(EclipseNode parent, String name) { @@ -368,14 +756,84 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { return null; } - public EclipseNode makeBuilderClass(EclipseNode tdParent, String builderClassName, TypeParameter[] typeParams, ASTNode source) { + public EclipseNode makeBuilderClass(boolean isStatic, EclipseNode tdParent, String builderClassName, 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.AccPublic | ClassFileConstants.AccStatic; + builder.modifiers |= ClassFileConstants.AccPublic; + if (isStatic) builder.modifiers |= ClassFileConstants.AccStatic; builder.typeParameters = copyTypeParams(typeParams, source); builder.name = builderClassName.toCharArray(); builder.traverse(new SetGeneratedByVisitor(source), (ClassScope) null); return injectType(tdParent, builder); } + + 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; + } } diff --git a/src/core/lombok/eclipse/handlers/HandleBuilderDefault.java b/src/core/lombok/eclipse/handlers/HandleBuilderDefault.java new file mode 100644 index 00000000..be2b986d --- /dev/null +++ b/src/core/lombok/eclipse/handlers/HandleBuilderDefault.java @@ -0,0 +1,46 @@ +/* + * 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; + +@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)) { + annotationNode.addWarning("@Builder.Default requires @Builder 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 b72d000f..62e2c18c 100644 --- a/src/core/lombok/eclipse/handlers/HandleConstructor.java +++ b/src/core/lombok/eclipse/handlers/HandleConstructor.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 @@ -29,10 +29,12 @@ import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.List; import lombok.AccessLevel; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.ConfigurationKeys; import lombok.NoArgsConstructor; import lombok.RequiredArgsConstructor; @@ -40,21 +42,28 @@ import lombok.core.AST.Kind; import lombok.core.AnnotationValues; import lombok.eclipse.EclipseAnnotationHandler; import lombok.eclipse.EclipseNode; -import lombok.experimental.Builder; import org.eclipse.jdt.internal.compiler.ast.ASTNode; 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; import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration; +import org.eclipse.jdt.internal.compiler.ast.DoubleLiteral; 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.FloatLiteral; +import org.eclipse.jdt.internal.compiler.ast.IntLiteral; +import org.eclipse.jdt.internal.compiler.ast.LongLiteral; import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.NullLiteral; import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference; import org.eclipse.jdt.internal.compiler.ast.ReturnStatement; import org.eclipse.jdt.internal.compiler.ast.SingleMemberAnnotation; @@ -63,12 +72,16 @@ import org.eclipse.jdt.internal.compiler.ast.Statement; import org.eclipse.jdt.internal.compiler.ast.StringLiteral; import org.eclipse.jdt.internal.compiler.ast.ThisReference; import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; +import org.eclipse.jdt.internal.compiler.ast.TypeReference; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; +import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; 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"); @@ -78,16 +91,20 @@ public class HandleConstructor { AccessLevel level = ann.access(); String staticName = ann.staticName(); if (level == AccessLevel.NONE) return; - List<EclipseNode> fields = new ArrayList<EclipseNode>(); - List<Annotation> onConstructor = unboxAndRemoveAnnotationParameter(ast, "onConstructor", "@NoArgsConstructor(onConstructor=", annotationNode); + boolean force = ann.force(); + + List<EclipseNode> fields = force ? findFinalFields(typeNode) : Collections.<EclipseNode>emptyList(); + List<Annotation> onConstructor = unboxAndRemoveAnnotationParameter(ast, "onConstructor", "@NoArgsConstructor(onConstructor", annotationNode); - new HandleConstructor().generateConstructor(typeNode, level, fields, 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"); @@ -97,43 +114,51 @@ 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( - typeNode, level, findRequiredFields(typeNode), staticName, SkipIfConstructorExists.NO, - suppressConstructorProperties, onConstructor, annotationNode); + handleConstructor.generateConstructor( + typeNode, level, findRequiredFields(typeNode), false, staticName, SkipIfConstructorExists.NO, + onConstructor, annotationNode); } } private static List<EclipseNode> findRequiredFields(EclipseNode typeNode) { + return findFields(typeNode, true); + } + + private static List<EclipseNode> findFinalFields(EclipseNode typeNode) { + return findFields(typeNode, false); + } + + private static List<EclipseNode> findFields(EclipseNode typeNode, boolean nullMarked) { 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; boolean isFinal = (fieldDecl.modifiers & ClassFileConstants.AccFinal) != 0; - boolean isNonNull = findAnnotations(fieldDecl, NON_NULL_PATTERN).length != 0; + boolean isNonNull = nullMarked && findAnnotations(fieldDecl, NON_NULL_PATTERN).length != 0; if ((isFinal || isNonNull) && fieldDecl.initialization == null) fields.add(child); } return fields; } 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); } @@ -142,6 +167,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"); @@ -151,18 +178,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( - typeNode, level, findAllFields(typeNode), staticName, SkipIfConstructorExists.NO, - suppressConstructorProperties, onConstructor, annotationNode); + handleConstructor.generateConstructor( + typeNode, level, findAllFields(typeNode), false, staticName, SkipIfConstructorExists.NO, + onConstructor, annotationNode); } } @@ -184,14 +208,14 @@ public class HandleConstructor { EclipseNode typeNode, AccessLevel level, String staticName, SkipIfConstructorExists skipIfConstructorExists, List<Annotation> onConstructor, EclipseNode sourceNode) { - generateConstructor(typeNode, level, findRequiredFields(typeNode), 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), staticName, skipIfConstructorExists, null, onConstructor, sourceNode); + generateConstructor(typeNode, level, findAllFields(typeNode), false, staticName, skipIfConstructorExists, onConstructor, sourceNode); } public enum SkipIfConstructorExists { @@ -199,8 +223,8 @@ public class HandleConstructor { } public void generateConstructor( - EclipseNode typeNode, AccessLevel level, List<EclipseNode> fields, String staticName, SkipIfConstructorExists skipIfConstructorExists, - Boolean suppressConstructorProperties, List<Annotation> onConstructor, EclipseNode sourceNode) { + EclipseNode typeNode, AccessLevel level, List<EclipseNode> fields, boolean allToDefault, String staticName, SkipIfConstructorExists skipIfConstructorExists, + List<Annotation> onConstructor, EclipseNode sourceNode) { ASTNode source = sourceNode.get(); boolean staticConstrRequired = staticName != null && !staticName.equals(""); @@ -210,8 +234,8 @@ public class HandleConstructor { for (EclipseNode child : typeNode.down()) { if (child.getKind() == Kind.ANNOTATION) { boolean skipGeneration = (annotationTypeMatches(NoArgsConstructor.class, child) || - annotationTypeMatches(AllArgsConstructor.class, child) || - annotationTypeMatches(RequiredArgsConstructor.class, child)); + annotationTypeMatches(AllArgsConstructor.class, child) || + annotationTypeMatches(RequiredArgsConstructor.class, child)); if (!skipGeneration && skipIfConstructorExists == SkipIfConstructorExists.YES) { skipGeneration = annotationTypeMatches(Builder.class, child); @@ -224,8 +248,8 @@ public class HandleConstructor { // the 'staticName' parameter of the @XArgsConstructor you've stuck on your type. // We should warn that we're ignoring @Data's 'staticConstructor' param. typeNode.addWarning( - "Ignoring static constructor name: explicit @XxxArgsConstructor annotation present; its `staticName` parameter will be used.", - source.sourceStart, source.sourceEnd); + "Ignoring static constructor name: explicit @XxxArgsConstructor annotation present; its `staticName` parameter will be used.", + source.sourceStart, source.sourceEnd); } return; } @@ -234,11 +258,11 @@ public class HandleConstructor { } ConstructorDeclaration constr = createConstructor( - staticConstrRequired ? AccessLevel.PRIVATE : level, typeNode, fields, - suppressConstructorProperties, sourceNode, onConstructor); + staticConstrRequired ? AccessLevel.PRIVATE : level, typeNode, fields, allToDefault, + sourceNode, onConstructor); injectMethod(typeNode, constr); if (staticConstrRequired) { - MethodDeclaration staticConstr = createStaticConstructor(level, staticName, typeNode, fields, source); + MethodDeclaration staticConstr = createStaticConstructor(level, staticName, typeNode, allToDefault ? Collections.<EclipseNode>emptyList() : fields, source); injectMethod(typeNode, staticConstr); } } @@ -248,7 +272,7 @@ public class HandleConstructor { if (fields.isEmpty()) return null; int pS = source.sourceStart, pE = source.sourceEnd; - long p = (long)pS << 32 | pE; + long p = (long) pS << 32 | pE; long[] poss = new long[3]; Arrays.fill(poss, p); QualifiedTypeReference constructorPropertiesType = new QualifiedTypeReference(JAVA_BEANS_CONSTRUCTORPROPERTIES, poss); @@ -275,28 +299,28 @@ public class HandleConstructor { return new Annotation[] { ann }; } - public static ConstructorDeclaration createConstructor( - AccessLevel level, EclipseNode type, Collection<EclipseNode> fields, - Boolean suppressConstructorProperties, EclipseNode sourceNode, List<Annotation> onConstructor) { + @SuppressWarnings("deprecation") public static ConstructorDeclaration createConstructor( + AccessLevel level, EclipseNode type, Collection<EclipseNode> fields, boolean allToDefault, + EclipseNode sourceNode, List<Annotation> onConstructor) { ASTNode source = sourceNode.get(); - TypeDeclaration typeDeclaration = ((TypeDeclaration)type.get()); - long p = (long)source.sourceStart << 32 | source.sourceEnd; + TypeDeclaration typeDeclaration = ((TypeDeclaration) type.get()); + long p = (long) source.sourceStart << 32 | source.sourceEnd; - boolean isEnum = (((TypeDeclaration)type.get()).modifiers & ClassFileConstants.AccEnum) != 0; + boolean isEnum = (((TypeDeclaration) type.get()).modifiers & ClassFileConstants.AccEnum) != 0; 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); + ConstructorDeclaration constructor = new ConstructorDeclaration(((CompilationUnitDeclaration) type.top().get()).compilationResult); constructor.modifiers = toEclipseModifier(level); constructor.selector = typeDeclaration.name; @@ -319,23 +343,27 @@ public class HandleConstructor { char[] rawName = field.name; char[] fieldName = removePrefixFromField(fieldNode); FieldReference thisX = new FieldReference(rawName, p); - thisX.receiver = new ThisReference((int)(p >> 32), (int)p); + int s = (int) (p >> 32); + int e = (int) p; + thisX.receiver = new ThisReference(s, e); + + Expression assignmentExpr = allToDefault ? getDefaultExpr(field.type, s, e) : new SingleNameReference(fieldName, p); - SingleNameReference assignmentNameRef = new SingleNameReference(fieldName, p); - Assignment assignment = new Assignment(thisX, assignmentNameRef, (int)p); - assignment.sourceStart = (int)(p >> 32); assignment.sourceEnd = assignment.statementEnd = (int)(p >> 32); + Assignment assignment = new Assignment(thisX, assignmentExpr, (int) p); + assignment.sourceStart = (int) (p >> 32); assignment.sourceEnd = assignment.statementEnd = (int) (p >> 32); assigns.add(assignment); - long fieldPos = (((long)field.sourceStart) << 32) | field.sourceEnd; - Argument parameter = new Argument(fieldName, fieldPos, copyType(field.type, source), Modifier.FINAL); - Annotation[] nonNulls = findAnnotations(field, NON_NULL_PATTERN); - Annotation[] nullables = findAnnotations(field, NULLABLE_PATTERN); - if (nonNulls.length != 0) { - Statement nullCheck = generateNullCheck(field, sourceNode); - if (nullCheck != null) nullChecks.add(nullCheck); + if (!allToDefault) { + long fieldPos = (((long) field.sourceStart) << 32) | field.sourceEnd; + Argument parameter = new Argument(fieldName, fieldPos, copyType(field.type, source), Modifier.FINAL); + Annotation[] nonNulls = findAnnotations(field, NON_NULL_PATTERN); + Annotation[] nullables = findAnnotations(field, NULLABLE_PATTERN); + if (nonNulls.length != 0) { + Statement nullCheck = generateNullCheck(parameter, sourceNode); + if (nullCheck != null) nullChecks.add(nullCheck); + } + parameter.annotations = copyAnnotations(source, nonNulls, nullables); + params.add(parameter); } - Annotation[] copiedAnnotations = copyAnnotations(source, nonNulls, nullables); - if (copiedAnnotations.length != 0) parameter.annotations = copiedAnnotations; - params.add(parameter); } nullChecks.addAll(assigns); @@ -344,20 +372,34 @@ public class HandleConstructor { /* Generate annotations that must be put on the generated method, and attach them. */ { Annotation[] constructorProperties = null; - if (!suppressConstructorProperties && level != AccessLevel.PRIVATE && level != AccessLevel.PACKAGE && !isLocalType(type)) { + if (!allToDefault && addConstructorProperties && !isLocalType(type)) { constructorProperties = createConstructorProperties(source, fields); } - Annotation[] copiedAnnotations = copyAnnotations(source, - onConstructor.toArray(new Annotation[0]), - constructorProperties); - if (copiedAnnotations.length != 0) constructor.annotations = copiedAnnotations; + constructor.annotations = copyAnnotations(source, + onConstructor.toArray(new Annotation[0]), + constructorProperties); } constructor.traverse(new SetGeneratedByVisitor(source), typeDeclaration.scope); return constructor; } + 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); + if (Arrays.equals(TypeConstants.BYTE, lastToken) || Arrays.equals(TypeConstants.SHORT, lastToken) || + Arrays.equals(TypeConstants.INT, lastToken)) return IntLiteral.buildIntLiteral(new char[] {'0'}, s, e); + if (Arrays.equals(TypeConstants.LONG, lastToken)) return LongLiteral.buildLongLiteral(new char[] {'0', 'L'}, s, e); + if (Arrays.equals(TypeConstants.FLOAT, lastToken)) return new FloatLiteral(new char[] {'0', 'F'}, s, e); + if (Arrays.equals(TypeConstants.DOUBLE, lastToken)) return new DoubleLiteral(new char[] {'0', 'D'}, s, e); + + return new NullLiteral(s, e); + } + public static boolean isLocalType(EclipseNode type) { Kind kind = type.up().getKind(); if (kind == Kind.COMPILATION_UNIT) return false; @@ -367,10 +409,9 @@ public class HandleConstructor { public MethodDeclaration createStaticConstructor(AccessLevel level, String name, EclipseNode type, Collection<EclipseNode> fields, ASTNode source) { int pS = source.sourceStart, pE = source.sourceEnd; - long p = (long)pS << 32 | pE; + long p = (long) pS << 32 | pE; - MethodDeclaration constructor = new MethodDeclaration( - ((CompilationUnitDeclaration) type.top().get()).compilationResult); + MethodDeclaration constructor = new MethodDeclaration(((CompilationUnitDeclaration) type.top().get()).compilationResult); constructor.modifiers = toEclipseModifier(level) | ClassFileConstants.AccStatic; TypeDeclaration typeDecl = (TypeDeclaration) type.get(); @@ -378,7 +419,7 @@ public class HandleConstructor { constructor.annotations = null; constructor.selector = name.toCharArray(); constructor.thrownExceptions = null; - constructor.typeParameters = copyTypeParams(((TypeDeclaration)type.get()).typeParameters, source); + constructor.typeParameters = copyTypeParams(((TypeDeclaration) type.get()).typeParameters, source); constructor.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; constructor.bodyStart = constructor.declarationSourceStart = constructor.sourceStart = source.sourceStart; constructor.bodyEnd = constructor.declarationSourceEnd = constructor.sourceEnd = source.sourceEnd; @@ -391,20 +432,18 @@ public class HandleConstructor { for (EclipseNode fieldNode : fields) { FieldDeclaration field = (FieldDeclaration) fieldNode.get(); - long fieldPos = (((long)field.sourceStart) << 32) | field.sourceEnd; + long fieldPos = (((long) field.sourceStart) << 32) | field.sourceEnd; SingleNameReference nameRef = new SingleNameReference(field.name, fieldPos); assigns.add(nameRef); Argument parameter = new Argument(field.name, fieldPos, copyType(field.type, source), Modifier.FINAL); - - Annotation[] copiedAnnotations = copyAnnotations(source, findAnnotations(field, NON_NULL_PATTERN), findAnnotations(field, NULLABLE_PATTERN)); - if (copiedAnnotations.length != 0) parameter.annotations = copiedAnnotations; + parameter.annotations = copyAnnotations(source, findAnnotations(field, NON_NULL_PATTERN), findAnnotations(field, NULLABLE_PATTERN)); params.add(parameter); } statement.arguments = assigns.isEmpty() ? null : assigns.toArray(new Expression[assigns.size()]); constructor.arguments = params.isEmpty() ? null : params.toArray(new Argument[params.size()]); - constructor.statements = new Statement[] { new ReturnStatement(statement, (int)(p >> 32), (int)p) }; + constructor.statements = new Statement[] { new ReturnStatement(statement, (int) (p >> 32), (int)p) }; constructor.traverse(new SetGeneratedByVisitor(source), typeDecl.scope); return constructor; diff --git a/src/core/lombok/eclipse/handlers/HandleData.java b/src/core/lombok/eclipse/handlers/HandleData.java index 0ff65a47..025ceefd 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,11 +72,11 @@ 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); + handleSetter.generateSetterForType(typeNode, annotationNode, AccessLevel.PUBLIC, true); + handleEqualsAndHashCode.generateEqualsAndHashCodeForType(typeNode, annotationNode); + handleToString.generateToStringForType(typeNode, annotationNode); + handleConstructor.generateRequiredArgsConstructor( typeNode, AccessLevel.PUBLIC, ann.staticConstructor(), SkipIfConstructorExists.YES, Collections.<Annotation>emptyList(), annotationNode); } diff --git a/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java b/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java index e1b02051..75339f7c 100644 --- a/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java +++ b/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2014 The Project Lombok Authors. + * Copyright (C) 2009-2015 The Project Lombok Authors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -39,6 +39,7 @@ import lombok.EqualsAndHashCode; import lombok.core.AST.Kind; import lombok.core.handlers.HandlerUtil; import lombok.core.AnnotationValues; +import lombok.core.configuration.CallSuperType; import lombok.eclipse.Eclipse; import lombok.eclipse.EclipseAnnotationHandler; import lombok.eclipse.EclipseNode; @@ -66,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; @@ -116,7 +118,10 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler<EqualsAndH return; } - generateMethods(typeNode, errorNode, null, null, null, false, FieldAccess.GETTER, new ArrayList<Annotation>()); + 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, null, null, null, false, access, new ArrayList<Annotation>()); } @Override public void handle(AnnotationValues<EqualsAndHashCode> annotation, Annotation ast, EclipseNode annotationNode) { @@ -127,7 +132,7 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler<EqualsAndH List<String> includes = Arrays.asList(ann.of()); EclipseNode typeNode = annotationNode.up(); - List<Annotation> onParam = unboxAndRemoveAnnotationParameter(ast, "onParam", "@EqualsAndHashCode(onParam=", annotationNode); + List<Annotation> onParam = unboxAndRemoveAnnotationParameter(ast, "onParam", "@EqualsAndHashCode(onParam", annotationNode); checkForBogusFieldNames(typeNode, annotation); Boolean callSuper = ann.callSuper(); @@ -185,8 +190,23 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler<EqualsAndH return; } - if (!isDirectDescendantOfObject && !callSuper && implicitCallSuper) { - errorNode.addWarning("Generating equals/hashCode implementation but without a call to superclass, even though this class does not extend java.lang.Object. If this is intentional, add '@EqualsAndHashCode(callSuper=false)' to your type."); + if (implicitCallSuper && !isDirectDescendantOfObject) { + CallSuperType cst = typeNode.getAst().readConfiguration(ConfigurationKeys.EQUALS_AND_HASH_CODE_CALL_SUPER); + if (cst == null) cst = CallSuperType.WARN; + + switch (cst) { + default: + case WARN: + errorNode.addWarning("Generating equals/hashCode implementation but without a call to superclass, even though this class does not extend java.lang.Object. If this is intentional, add '@EqualsAndHashCode(callSuper=false)' to your type."); + callSuper = false; + break; + case SKIP: + callSuper = false; + break; + case CALL: + callSuper = true; + break; + } } List<EclipseNode> nodesForEquality = new ArrayList<EclipseNode>(); @@ -279,7 +299,7 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler<EqualsAndH /* final int PRIME = X; */ { /* Without fields, PRIME isn't used, and that would trigger a 'local variable not used' warning. */ - if (!isEmpty || callSuper) { + if (!isEmpty) { LocalDeclaration primeDecl = new LocalDeclaration(PRIME, pS, pE); setGeneratedBy(primeDecl, source); primeDecl.modifiers |= Modifier.FINAL; @@ -291,26 +311,30 @@ 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(); @@ -358,7 +382,7 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler<EqualsAndH statements.add(createResultCalculation(source, fieldAccessor)); } else /* objects */ { /* final java.lang.Object $fieldName = this.fieldName; */ - /* $fieldName == null ? 0 : $fieldName.hashCode() */ + /* $fieldName == null ? NULL_PRIME : $fieldName.hashCode() */ statements.add(createLocalDeclaration(source, dollarFieldName, generateQualifiedTypeRef(source, TypeConstants.JAVA_LANG_OBJECT), fieldAccessor)); SingleNameReference copy1 = new SingleNameReference(dollarFieldName, p); @@ -375,8 +399,8 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler<EqualsAndH setGeneratedBy(nullLiteral, source); EqualExpression objIsNull = new EqualExpression(copy2, nullLiteral, OperatorIds.EQUAL_EQUAL); setGeneratedBy(objIsNull, source); - IntLiteral int0 = makeIntLiteral("0".toCharArray(), source); - ConditionalExpression nullOrHashCode = new ConditionalExpression(objIsNull, int0, hashCodeCall); + IntLiteral intMagic = makeIntLiteral(String.valueOf(HandlerUtil.primeForNull()).toCharArray(), source); + ConditionalExpression nullOrHashCode = new ConditionalExpression(objIsNull, intMagic, hashCodeCall); nullOrHashCode.sourceStart = pS; nullOrHashCode.sourceEnd = pE; setGeneratedBy(nullOrHashCode, source); statements.add(createResultCalculation(source, nullOrHashCode)); @@ -442,17 +466,44 @@ 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 & Modifier.STATIC) != 0; EclipseNode tNode = type.up(); + 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(); } 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++) { @@ -460,13 +511,31 @@ 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; + } + + private int arraySizeOf(Object[] arr) { + return arr == null ? 0 : arr.length; } public MethodDeclaration createEquals(EclipseNode type, Collection<EclipseNode> fields, 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); @@ -512,7 +581,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); @@ -535,33 +604,17 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler<EqualsAndH char[] otherName = "other".toCharArray(); - /* MyType<?> other = (MyType<?>) o; */ { + /* Outer.Inner.MyType<?> other = (Outer.Inner.MyType<?>) o; */ { if (!fields.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.annotations = createSuppressWarningsAll(source, null); other.initialization = makeCastExpression(oRef, targetType, source); statements.add(other); } @@ -645,7 +698,7 @@ 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;; */ + /* 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(); @@ -725,7 +778,7 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler<EqualsAndH public MethodDeclaration createCanEqual(EclipseNode type, ASTNode source, List<Annotation> onParam) { - /* public boolean canEqual(final java.lang.Object other) { + /* protected boolean canEqual(final java.lang.Object other) { * return other instanceof Outer.Inner.MyType; * } */ @@ -757,7 +810,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 7d0702db..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 @@ -23,12 +23,17 @@ package lombok.eclipse.handlers; import static lombok.core.handlers.HandlerUtil.*; import static lombok.eclipse.handlers.EclipseHandlerUtil.*; + +import java.util.Arrays; + import lombok.AccessLevel; import lombok.ConfigurationKeys; import lombok.core.AST.Kind; import lombok.core.AnnotationValues; import lombok.core.HandlerPriority; -import lombok.eclipse.EclipseAnnotationHandler; +import lombok.eclipse.Eclipse; +import lombok.eclipse.EclipseASTAdapter; +import lombok.eclipse.EclipseASTVisitor; import lombok.eclipse.EclipseNode; import lombok.experimental.FieldDefaults; import lombok.experimental.NonFinal; @@ -37,16 +42,19 @@ import lombok.experimental.PackagePrivate; import org.eclipse.jdt.internal.compiler.ast.ASTNode; import org.eclipse.jdt.internal.compiler.ast.Annotation; import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; +import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference; +import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference; import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; +import org.eclipse.jdt.internal.compiler.ast.TypeReference; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; import org.mangosdk.spi.ProviderFor; /** * Handles the {@code lombok.FieldDefaults} annotation for eclipse. */ -@ProviderFor(EclipseAnnotationHandler.class) +@ProviderFor(EclipseASTVisitor.class) @HandlerPriority(-2048) //-2^11; to ensure @Value picks up on messing with the fields' 'final' state, run earlier. -public class HandleFieldDefaults extends EclipseAnnotationHandler<FieldDefaults> { +public class HandleFieldDefaults extends EclipseASTAdapter { public boolean generateFieldDefaultsForType(EclipseNode typeNode, EclipseNode pos, AccessLevel level, boolean makeFinal, boolean checkForTypeLevelFieldDefaults) { if (checkForTypeLevelFieldDefaults) { if (hasAnnotation(FieldDefaults.class, typeNode)) { @@ -89,43 +97,78 @@ public class HandleFieldDefaults extends EclipseAnnotationHandler<FieldDefaults> 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)) { - field.modifiers |= ClassFileConstants.AccFinal; + if ((field.modifiers & ClassFileConstants.AccStatic) == 0) { + field.modifiers |= ClassFileConstants.AccFinal; + } } } fieldNode.rebuild(); } - public void handle(AnnotationValues<FieldDefaults> annotation, Annotation ast, EclipseNode annotationNode) { - handleExperimentalFlagUsage(annotationNode, ConfigurationKeys.FIELD_DEFAULTS_FLAG_USAGE, "@FieldDefaults"); - - EclipseNode node = annotationNode.up(); - FieldDefaults instance = annotation.getInstance(); - AccessLevel level = instance.level(); - boolean makeFinal = instance.makeFinal(); + private static final char[] FIELD_DEFAULTS = "FieldDefaults".toCharArray(); + + @Override public void visitType(EclipseNode typeNode, TypeDeclaration type) { + AnnotationValues<FieldDefaults> fieldDefaults = null; + EclipseNode source = typeNode; - if (level == AccessLevel.NONE && !makeFinal) { - annotationNode.addError("This does nothing; provide either level or makeFinal or both."); - return; + boolean levelIsExplicit = false; + boolean makeFinalIsExplicit = false; + FieldDefaults fd = null; + for (EclipseNode jn : typeNode.down()) { + if (jn.getKind() != Kind.ANNOTATION) continue; + Annotation ann = (Annotation) jn.get(); + TypeReference typeTree = ann.type; + if (typeTree == null) continue; + if (typeTree instanceof SingleTypeReference) { + char[] t = ((SingleTypeReference) typeTree).token; + if (!Arrays.equals(t, FIELD_DEFAULTS)) continue; + } else if (typeTree instanceof QualifiedTypeReference) { + char[][] t = ((QualifiedTypeReference) typeTree).tokens; + if (!Eclipse.nameEquals(t, "lombok.experimental.FieldDefaults")) continue; + } else { + continue; + } + + if (!typeMatches(FieldDefaults.class, jn, typeTree)) continue; + + source = jn; + fieldDefaults = createAnnotation(FieldDefaults.class, jn); + levelIsExplicit = fieldDefaults.isExplicit("level"); + makeFinalIsExplicit = fieldDefaults.isExplicit("makeFinal"); + + handleExperimentalFlagUsage(jn, ConfigurationKeys.FIELD_DEFAULTS_FLAG_USAGE, "@FieldDefaults"); + + fd = fieldDefaults.getInstance(); + if (!levelIsExplicit && !makeFinalIsExplicit) { + jn.addError("This does nothing; provide either level or makeFinal or both."); + } + + if (levelIsExplicit && fd.level() == AccessLevel.NONE) { + jn.addError("AccessLevel.NONE doesn't mean anything here. Pick another value."); + levelIsExplicit = false; + } + break; } - if (level == AccessLevel.PACKAGE) { - annotationNode.addError("Setting 'level' to PACKAGE does nothing. To force fields as package private, use the @PackagePrivate annotation on the field."); - } + if (fd == null && (type.modifiers & (ClassFileConstants.AccInterface | ClassFileConstants.AccAnnotation)) != 0) return; - if (!makeFinal && annotation.isExplicit("makeFinal")) { - annotationNode.addError("Setting 'makeFinal' to false does nothing. To force fields to be non-final, use the @NonFinal annotation on the field."); - } + boolean defaultToPrivate = levelIsExplicit ? false : Boolean.TRUE.equals(typeNode.getAst().readConfiguration(ConfigurationKeys.FIELD_DEFAULTS_PRIVATE_EVERYWHERE)); + boolean defaultToFinal = makeFinalIsExplicit ? false : Boolean.TRUE.equals(typeNode.getAst().readConfiguration(ConfigurationKeys.FIELD_DEFAULTS_FINAL_EVERYWHERE)); - if (node == null) return; + if (!defaultToPrivate && !defaultToFinal && fieldDefaults == null) return; + AccessLevel fdAccessLevel = (fieldDefaults != null && levelIsExplicit) ? fd.level() : defaultToPrivate ? AccessLevel.PRIVATE : null; + boolean fdToFinal = (fieldDefaults != null && makeFinalIsExplicit) ? fd.makeFinal() : defaultToFinal; - generateFieldDefaultsForType(node, annotationNode, level, makeFinal, false); + generateFieldDefaultsForType(typeNode, source, fdAccessLevel, fdToFinal, false); } } diff --git a/src/core/lombok/eclipse/handlers/HandleGetter.java b/src/core/lombok/eclipse/handlers/HandleGetter.java index 031fff82..f417aca5 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 @@ -105,7 +105,7 @@ public class HandleGetter extends EclipseAnnotationHandler<Getter> { 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); @@ -148,7 +148,7 @@ 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: @@ -183,6 +183,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; @@ -270,14 +274,12 @@ public class HandleGetter extends EclipseAnnotationHandler<Getter> { deprecated = new Annotation[] { generateDeprecatedAnnotation(source) }; } - Annotation[] copiedAnnotations = copyAnnotations(source, + method.annotations = copyAnnotations(source, onMethod.toArray(new Annotation[0]), findAnnotations(field, NON_NULL_PATTERN), findAnnotations(field, NULLABLE_PATTERN), findDelegatesAndMarkAsHandled(fieldNode), deprecated); - - if (copiedAnnotations.length != 0) method.annotations = copiedAnnotations; } method.traverse(new SetGeneratedByVisitor(source), parent.scope); diff --git a/src/core/lombok/eclipse/handlers/HandleHelper.java b/src/core/lombok/eclipse/handlers/HandleHelper.java new file mode 100644 index 00000000..4f06bdfd --- /dev/null +++ b/src/core/lombok/eclipse/handlers/HandleHelper.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2015-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 + * 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 java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.eclipse.jdt.internal.compiler.ASTVisitor; +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.Block; +import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration; +import org.eclipse.jdt.internal.compiler.ast.MessageSend; +import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; +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.SwitchStatement; +import org.eclipse.jdt.internal.compiler.ast.ThisReference; +import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; +import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; +import org.eclipse.jdt.internal.compiler.lookup.BlockScope; +import org.mangosdk.spi.ProviderFor; + +import lombok.ConfigurationKeys; +import lombok.core.AST.Kind; +import lombok.core.AnnotationValues; +import lombok.eclipse.EclipseAnnotationHandler; +import lombok.eclipse.EclipseNode; +import lombok.experimental.Helper; + +/** + * Handles the {@code lombok.Cleanup} annotation for eclipse. + */ +@ProviderFor(EclipseAnnotationHandler.class) +public class HandleHelper extends EclipseAnnotationHandler<Helper> { + private Statement[] getStatementsFromAstNode(ASTNode node) { + if (node instanceof Block) return ((Block) node).statements; + if (node instanceof AbstractMethodDeclaration) return ((AbstractMethodDeclaration) node).statements; + if (node instanceof SwitchStatement) return ((SwitchStatement) node).statements; + return null; + } + + private void setStatementsOfAstNode(ASTNode node, Statement[] statements) { + if (node instanceof Block) ((Block) node).statements = statements; + else if (node instanceof AbstractMethodDeclaration) ((AbstractMethodDeclaration) node).statements = statements; + else if (node instanceof SwitchStatement) ((SwitchStatement) node).statements = statements; + else throw new IllegalArgumentException("Can't set statements on node type: " + node.getClass()); + } + + @Override public void handle(AnnotationValues<Helper> annotation, Annotation ast, EclipseNode annotationNode) { + handleExperimentalFlagUsage(annotationNode, ConfigurationKeys.HELPER_FLAG_USAGE, "@Helper"); + + EclipseNode annotatedType = annotationNode.up(); + EclipseNode containingBlock = annotatedType == null ? null : annotatedType.directUp(); + Statement[] origStatements = getStatementsFromAstNode(containingBlock == null ? null : containingBlock.get()); + + if (annotatedType == null || annotatedType.getKind() != Kind.TYPE || origStatements == null) { + annotationNode.addError("@Helper is legal only on method-local classes."); + return; + } + + TypeDeclaration annotatedType_ = (TypeDeclaration) annotatedType.get(); + int indexOfType = -1; + for (int i = 0; i < origStatements.length; i++) { + if (origStatements[i] == annotatedType_) { + indexOfType = i; + break; + } + } + + final List<String> knownMethodNames = new ArrayList<String>(); + + for (AbstractMethodDeclaration methodOfHelper : annotatedType_.methods) { + if (!(methodOfHelper instanceof MethodDeclaration)) continue; + char[] name = methodOfHelper.selector; + if (name != null && name.length > 0 && name[0] != '<') knownMethodNames.add(new String(name)); + } + + Collections.sort(knownMethodNames); + final String[] knownMethodNames_ = knownMethodNames.toArray(new String[knownMethodNames.size()]); + + final char[] helperName = new char[annotatedType_.name.length + 1]; + final boolean[] helperUsed = new boolean[1]; + helperName[0] = '$'; + System.arraycopy(annotatedType_.name, 0, helperName, 1, helperName.length - 1); + + ASTVisitor visitor = new ASTVisitor() { + @Override public boolean visit(MessageSend messageSend, BlockScope scope) { + if (messageSend.receiver instanceof ThisReference) { + if ((((ThisReference) messageSend.receiver).bits & ASTNode.IsImplicitThis) == 0) return true; + } else if (messageSend.receiver != null) return true; + + char[] name = messageSend.selector; + if (name == null || name.length == 0 || name[0] == '<') return true; + String n = new String(name); + if (Arrays.binarySearch(knownMethodNames_, n) < 0) return true; + messageSend.receiver = new SingleNameReference(helperName, messageSend.nameSourcePosition); + helperUsed[0] = true; + return true; + } + }; + + for (int i = indexOfType + 1; i < origStatements.length; i++) { + origStatements[i].traverse(visitor, null); + } + + if (!helperUsed[0]) { + annotationNode.addWarning("No methods of this helper class are ever used."); + return; + } + + Statement[] newStatements = new Statement[origStatements.length + 1]; + System.arraycopy(origStatements, 0, newStatements, 0, indexOfType + 1); + System.arraycopy(origStatements, indexOfType + 1, newStatements, indexOfType + 2, origStatements.length - indexOfType - 1); + LocalDeclaration decl = new LocalDeclaration(helperName, 0, 0); + decl.modifiers |= ClassFileConstants.AccFinal; + AllocationExpression alloc = new AllocationExpression(); + alloc.type = new SingleTypeReference(annotatedType_.name, 0L); + decl.initialization = alloc; + decl.type = new SingleTypeReference(annotatedType_.name, 0L); + SetGeneratedByVisitor sgbvVisitor = new SetGeneratedByVisitor(annotationNode.get()); + decl.traverse(sgbvVisitor, null); + newStatements[indexOfType + 1] = decl; + setStatementsOfAstNode(containingBlock.get(), newStatements); + } +} diff --git a/src/core/lombok/eclipse/handlers/HandleLog.java b/src/core/lombok/eclipse/handlers/HandleLog.java index 830190a2..c49030d8 100644 --- a/src/core/lombok/eclipse/handlers/HandleLog.java +++ b/src/core/lombok/eclipse/handlers/HandleLog.java @@ -229,6 +229,17 @@ public class HandleLog { } } + /** + * Handles the {@link lombok.extern.jbosslog.JBossLog} annotation for Eclipse. + */ + @ProviderFor(EclipseAnnotationHandler.class) + public static class HandleJBossLog extends EclipseAnnotationHandler<lombok.extern.jbosslog.JBossLog> { + @Override public void handle(AnnotationValues<lombok.extern.jbosslog.JBossLog> annotation, Annotation source, EclipseNode annotationNode) { + handleFlagUsage(annotationNode, ConfigurationKeys.LOG_JBOSSLOG_FLAG_USAGE, "@JBossLog", ConfigurationKeys.LOG_ANY_FLAG_USAGE, "any @Log"); + processAnnotation(LoggingFramework.JBOSSLOG, annotation, source, annotationNode, annotation.getInstance().topic()); + } + } + 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"), @@ -264,7 +275,9 @@ public class HandleLog { // private static final org.slf4j.ext.XLogger log = org.slf4j.ext.XLoggerFactory.getXLogger(TargetType.class); XSLF4J("org.slf4j.ext.XLogger", "org.slf4j.ext.XLoggerFactory", "getXLogger", "@XSlf4j"), - + + // 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 final String loggerTypeName; diff --git a/src/core/lombok/eclipse/handlers/HandleNonNull.java b/src/core/lombok/eclipse/handlers/HandleNonNull.java index d904de2f..d09993ed 100644 --- a/src/core/lombok/eclipse/handlers/HandleNonNull.java +++ b/src/core/lombok/eclipse/handlers/HandleNonNull.java @@ -91,7 +91,7 @@ public class HandleNonNull extends EclipseAnnotationHandler<NonNull> { if (isGenerated(declaration)) return; if (declaration.isAbstract()) { - annotationNode.addWarning("@NonNull is meaningless on a parameter of an abstract method."); + // This used to be a warning, but as @NonNull also has a documentary purpose, better to not warn about this. Since 1.16.7 return; } diff --git a/src/core/lombok/eclipse/handlers/HandlePrintAST.java b/src/core/lombok/eclipse/handlers/HandlePrintAST.java index 0b61bc4d..234e29b8 100644 --- a/src/core/lombok/eclipse/handlers/HandlePrintAST.java +++ b/src/core/lombok/eclipse/handlers/HandlePrintAST.java @@ -59,7 +59,7 @@ public class HandlePrintAST extends EclipseAnnotationHandler<PrintAST> { try { stream.close(); } catch (Exception e) { - Lombok.sneakyThrow(e); + throw Lombok.sneakyThrow(e); } } } diff --git a/src/core/lombok/eclipse/handlers/HandleSetter.java b/src/core/lombok/eclipse/handlers/HandleSetter.java index c22af676..6a9a5123 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 @@ -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; @@ -125,8 +126,8 @@ public class HandleSetter extends EclipseAnnotationHandler<Setter> { 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: @@ -192,11 +193,11 @@ 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, 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, boolean shouldReturnThis, 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; @@ -213,13 +214,10 @@ public class HandleSetter extends EclipseAnnotationHandler<Setter> { shouldReturnThis = false; } Annotation[] deprecated = null; - if (isFieldDeprecated(fieldNode)) { + if (isFieldDeprecated(fieldNode) || deprecate) { deprecated = new Annotation[] { generateDeprecatedAnnotation(source) }; } - Annotation[] copiedAnnotations = copyAnnotations(source, onMethod.toArray(new Annotation[0]), deprecated); - if (copiedAnnotations.length != 0) { - method.annotations = copiedAnnotations; - } + method.annotations = copyAnnotations(source, onMethod.toArray(new Annotation[0]), deprecated); Argument param = new Argument(field.name, p, copyType(field.type, source), Modifier.FINAL); param.sourceStart = pS; param.sourceEnd = pE; method.arguments = new Argument[] { param }; @@ -246,15 +244,17 @@ public class HandleSetter extends EclipseAnnotationHandler<Setter> { statements.add(assignment); } + if (booleanFieldToSet != null) { + statements.add(new Assignment(new SingleNameReference(booleanFieldToSet, p), new TrueLiteral(pS, pE), pE)); + } + if (shouldReturnThis) { ThisReference thisRef = new ThisReference(pS, pE); ReturnStatement returnThis = new ReturnStatement(thisRef, pS, pE); statements.add(returnThis); } method.statements = statements.toArray(new Statement[0]); - - Annotation[] copiedAnnotationsParam = copyAnnotations(source, nonNulls, nullables, onParam.toArray(new Annotation[0])); - if (copiedAnnotationsParam.length != 0) param.annotations = copiedAnnotationsParam; + param.annotations = copyAnnotations(source, nonNulls, nullables, onParam.toArray(new Annotation[0])); method.traverse(new SetGeneratedByVisitor(source), parent.scope); return method; diff --git a/src/core/lombok/eclipse/handlers/HandleToString.java b/src/core/lombok/eclipse/handlers/HandleToString.java index a4ed254a..d8f4c569 100644 --- a/src/core/lombok/eclipse/handlers/HandleToString.java +++ b/src/core/lombok/eclipse/handlers/HandleToString.java @@ -94,7 +94,11 @@ 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); + + Boolean doNotUseGettersConfiguration = typeNode.getAst().readConfiguration(ConfigurationKeys.TO_STRING_DO_NOT_USE_GETTERS); + FieldAccess access = doNotUseGettersConfiguration == null || !doNotUseGettersConfiguration ? FieldAccess.GETTER : FieldAccess.PREFER_FIELD; + + generateToString(typeNode, errorNode, null, null, includeFieldNames, null, false, access); } public void handle(AnnotationValues<ToString> annotation, Annotation ast, EclipseNode annotationNode) { diff --git a/src/core/lombok/eclipse/handlers/HandleUtilityClass.java b/src/core/lombok/eclipse/handlers/HandleUtilityClass.java new file mode 100644 index 00000000..959c1d20 --- /dev/null +++ b/src/core/lombok/eclipse/handlers/HandleUtilityClass.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2015 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.util.Arrays; + +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.Clinit; +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.FieldDeclaration; +import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference; +import org.eclipse.jdt.internal.compiler.ast.Statement; +import org.eclipse.jdt.internal.compiler.ast.StringLiteral; +import org.eclipse.jdt.internal.compiler.ast.ThrowStatement; +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; + +import lombok.ConfigurationKeys; +import lombok.core.AnnotationValues; +import lombok.core.HandlerPriority; +import lombok.core.AST.Kind; +import lombok.eclipse.EclipseAnnotationHandler; +import lombok.eclipse.EclipseNode; +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) { + handleFlagUsage(annotationNode, ConfigurationKeys.UTILITY_CLASS_FLAG_USAGE, "@UtilityClass"); + + EclipseNode typeNode = annotationNode.up(); + if (!checkLegality(typeNode, annotationNode)) return; + changeModifiersAndGenerateConstructor(annotationNode.up(), annotationNode); + } + + private static boolean checkLegality(EclipseNode typeNode, EclipseNode errorNode) { + 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 | ClassFileConstants.AccEnum)) != 0; + + if (typeDecl == null || notAClass) { + errorNode.addError("@UtilityClass is only supported on a class (can't be an interface, enum, or annotation)."); + return false; + } + + // It might be an inner class. This is okay, but only if it is / can be a static inner class. Thus, all of its parents have to be static inner classes until the top-level. + EclipseNode typeWalk = typeNode; + while (true) { + typeWalk = typeWalk.up(); + switch (typeWalk.getKind()) { + case TYPE: + if ((((TypeDeclaration) typeWalk.get()).modifiers & (ClassFileConstants.AccStatic | ClassFileConstants.AccInterface | ClassFileConstants.AccAnnotation | ClassFileConstants.AccEnum)) != 0) continue; + if (typeWalk.up().getKind() == Kind.COMPILATION_UNIT) return true; + errorNode.addError("@UtilityClass automatically makes the class static, however, this class cannot be made static."); + return false; + case COMPILATION_UNIT: + return true; + default: + errorNode.addError("@UtilityClass cannot be placed on a method local or anonymous inner class, or any class nested in such a class."); + return false; + } + } + } + + private void changeModifiersAndGenerateConstructor(EclipseNode typeNode, EclipseNode annotationNode) { + TypeDeclaration classDecl = (TypeDeclaration) typeNode.get(); + + boolean makeConstructor = true; + + classDecl.modifiers |= ClassFileConstants.AccFinal; + + boolean markStatic = true; + boolean requiresClInit = false; + boolean alreadyHasClinit = false; + + if (typeNode.up().getKind() == Kind.COMPILATION_UNIT) markStatic = false; + if (markStatic && typeNode.up().getKind() == Kind.TYPE) { + TypeDeclaration typeDecl = (TypeDeclaration) typeNode.up().get(); + if ((typeDecl.modifiers & ClassFileConstants.AccInterface) != 0) markStatic = false; + } + + if (markStatic) classDecl.modifiers |= ClassFileConstants.AccStatic; + + for (EclipseNode element : typeNode.down()) { + if (element.getKind() == Kind.FIELD) { + FieldDeclaration fieldDecl = (FieldDeclaration) element.get(); + if ((fieldDecl.modifiers & ClassFileConstants.AccStatic) == 0) { + requiresClInit = true; + fieldDecl.modifiers |= ClassFileConstants.AccStatic; + } + } else if (element.getKind() == Kind.METHOD) { + AbstractMethodDeclaration amd = (AbstractMethodDeclaration) element.get(); + if (amd instanceof ConstructorDeclaration) { + ConstructorDeclaration constrDecl = (ConstructorDeclaration) element.get(); + if (getGeneratedBy(constrDecl) == null && (constrDecl.bits & ASTNode.IsDefaultConstructor) == 0) { + element.addError("@UtilityClasses cannot have declared constructors."); + makeConstructor = false; + continue; + } + } else if (amd instanceof MethodDeclaration) { + amd.modifiers |= ClassFileConstants.AccStatic; + } else if (amd instanceof Clinit) { + alreadyHasClinit = true; + } + } else if (element.getKind() == Kind.TYPE) { + ((TypeDeclaration) element.get()).modifiers |= ClassFileConstants.AccStatic; + } + } + + if (makeConstructor) createPrivateDefaultConstructor(typeNode, annotationNode); + if (requiresClInit && !alreadyHasClinit) classDecl.addClinit(); + } + + private static final char[][] JAVA_LANG_UNSUPPORTED_OPERATION_EXCEPTION = new char[][] { + TypeConstants.JAVA, TypeConstants.LANG, "UnsupportedOperationException".toCharArray() + }; + + private static final char[] UNSUPPORTED_MESSAGE = "This is a utility class and cannot be instantiated".toCharArray(); + + private void createPrivateDefaultConstructor(EclipseNode typeNode, EclipseNode sourceNode) { + 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 = ClassFileConstants.AccPrivate; + constructor.selector = typeDeclaration.name; + 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; + constructor.arguments = null; + + AllocationExpression exception = new AllocationExpression(); + setGeneratedBy(exception, source); + long[] ps = new long[JAVA_LANG_UNSUPPORTED_OPERATION_EXCEPTION.length]; + Arrays.fill(ps, p); + exception.type = new QualifiedTypeReference(JAVA_LANG_UNSUPPORTED_OPERATION_EXCEPTION, ps); + setGeneratedBy(exception.type, source); + exception.arguments = new Expression[] { + new StringLiteral(UNSUPPORTED_MESSAGE, source.sourceStart, source.sourceEnd, 0) + }; + setGeneratedBy(exception.arguments[0], source); + ThrowStatement throwStatement = new ThrowStatement(exception, source.sourceStart, source.sourceEnd); + setGeneratedBy(throwStatement, source); + + constructor.statements = new Statement[] {throwStatement}; + + injectMethod(typeNode, constructor); + } +} 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..a61ca6c3 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,10 @@ 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); + handleEqualsAndHashCode.generateEqualsAndHashCodeForType(typeNode, annotationNode); + handleToString.generateToStringForType(typeNode, annotationNode); + handleConstructor.generateAllArgsConstructor(typeNode, AccessLevel.PUBLIC, ann.staticConstructor(), SkipIfConstructorExists.YES, Collections.<Annotation>emptyList(), annotationNode); } } diff --git a/src/core/lombok/eclipse/handlers/HandleWither.java b/src/core/lombok/eclipse/handlers/HandleWither.java index 8b038676..200ebde7 100644 --- a/src/core/lombok/eclipse/handlers/HandleWither.java +++ b/src/core/lombok/eclipse/handlers/HandleWither.java @@ -25,7 +25,6 @@ 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.Collection; import java.util.Collections; @@ -57,6 +56,7 @@ import org.eclipse.jdt.internal.compiler.ast.ThisReference; import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; import org.eclipse.jdt.internal.compiler.ast.TypeReference; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; +import org.eclipse.jdt.internal.compiler.lookup.ExtraCompilerModifiers; import org.mangosdk.spi.ProviderFor; @ProviderFor(EclipseAnnotationHandler.class) @@ -127,8 +127,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", "@Setter(onMethod=", annotationNode); - List<Annotation> onParam = unboxAndRemoveAnnotationParameter(ast, "onParam", "@Setter(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: @@ -163,6 +163,9 @@ public class HandleWither extends EclipseAnnotationHandler<Wither> { return; } + EclipseNode typeNode = fieldNode.up(); + boolean makeAbstract = typeNode != null && typeNode.getKind() == Kind.TYPE && (((TypeDeclaration) typeNode.get()).modifiers & ClassFileConstants.AccAbstract) != 0; + FieldDeclaration field = (FieldDeclaration) fieldNode.get(); TypeReference fieldType = copyType(field.type, source); boolean isBoolean = isBoolean(fieldType); @@ -208,17 +211,18 @@ public class HandleWither extends EclipseAnnotationHandler<Wither> { int modifier = toEclipseModifier(level); - MethodDeclaration method = createWither((TypeDeclaration) fieldNode.up().get(), fieldNode, witherName, modifier, sourceNode, onMethod, onParam); + MethodDeclaration method = createWither((TypeDeclaration) fieldNode.up().get(), fieldNode, witherName, modifier, sourceNode, onMethod, onParam, makeAbstract); injectMethod(fieldNode.up(), method); } - public MethodDeclaration createWither(TypeDeclaration parent, EclipseNode fieldNode, String name, int modifier, EclipseNode sourceNode, List<Annotation> onMethod, List<Annotation> onParam) { + public MethodDeclaration createWither(TypeDeclaration parent, EclipseNode fieldNode, String name, int modifier, EclipseNode sourceNode, List<Annotation> onMethod, List<Annotation> onParam, boolean makeAbstract ) { ASTNode source = sourceNode.get(); if (name == null) return null; FieldDeclaration field = (FieldDeclaration) fieldNode.get(); int pS = source.sourceStart, pE = source.sourceEnd; - long p = (long)pS << 32 | pE; + long p = (long) pS << 32 | pE; MethodDeclaration method = new MethodDeclaration(parent.compilationResult); + if (makeAbstract) modifier = modifier | ClassFileConstants.AccAbstract | ExtraCompilerModifiers.AccSemicolonBody; method.modifiers = modifier; method.returnType = cloneSelfType(fieldNode, source); if (method.returnType == null) return null; @@ -227,11 +231,8 @@ public class HandleWither extends EclipseAnnotationHandler<Wither> { if (isFieldDeprecated(fieldNode)) { deprecated = new Annotation[] { generateDeprecatedAnnotation(source) }; } - Annotation[] copiedAnnotations = copyAnnotations(source, onMethod.toArray(new Annotation[0]), deprecated); - if (copiedAnnotations.length != 0) { - method.annotations = copiedAnnotations; - } - Argument param = new Argument(field.name, p, copyType(field.type, source), Modifier.FINAL); + method.annotations = copyAnnotations(source, onMethod.toArray(new Annotation[0]), deprecated); + Argument param = new Argument(field.name, p, copyType(field.type, source), ClassFileConstants.AccFinal); param.sourceStart = pS; param.sourceEnd = pE; method.arguments = new Argument[] { param }; method.selector = name.toCharArray(); @@ -240,51 +241,52 @@ public class HandleWither extends EclipseAnnotationHandler<Wither> { method.typeParameters = null; method.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; - List<Expression> args = new ArrayList<Expression>(); - for (EclipseNode child : fieldNode.up().down()) { - if (child.getKind() != Kind.FIELD) continue; - FieldDeclaration childDecl = (FieldDeclaration) child.get(); - // Skip fields that start with $ - if (childDecl.name != null && childDecl.name.length > 0 && childDecl.name[0] == '$') continue; - long fieldFlags = childDecl.modifiers; - // Skip static fields. - if ((fieldFlags & ClassFileConstants.AccStatic) != 0) continue; - // Skip initialized final fields. - if (((fieldFlags & ClassFileConstants.AccFinal) != 0) && childDecl.initialization != null) continue; - if (child.get() == fieldNode.get()) { - args.add(new SingleNameReference(field.name, p)); - } else { - args.add(createFieldAccessor(child, FieldAccess.ALWAYS_FIELD, source)); - } - } - - AllocationExpression constructorCall = new AllocationExpression(); - constructorCall.arguments = args.toArray(new Expression[0]); - constructorCall.type = cloneSelfType(fieldNode, source); - - Expression identityCheck = new EqualExpression( - createFieldAccessor(fieldNode, FieldAccess.ALWAYS_FIELD, source), - new SingleNameReference(field.name, p), - OperatorIds.EQUAL_EQUAL); - ThisReference thisRef = new ThisReference(pS, pE); - Expression conditional = new ConditionalExpression(identityCheck, thisRef, constructorCall); - Statement returnStatement = new ReturnStatement(conditional, pS, pE); - method.bodyStart = method.declarationSourceStart = method.sourceStart = source.sourceStart; - method.bodyEnd = method.declarationSourceEnd = method.sourceEnd = source.sourceEnd; - Annotation[] nonNulls = findAnnotations(field, NON_NULL_PATTERN); Annotation[] nullables = findAnnotations(field, NULLABLE_PATTERN); - List<Statement> statements = new ArrayList<Statement>(5); - if (nonNulls.length > 0) { - Statement nullCheck = generateNullCheck(field, sourceNode); - if (nullCheck != null) statements.add(nullCheck); - } - statements.add(returnStatement); - - method.statements = statements.toArray(new Statement[0]); - Annotation[] copiedAnnotationsParam = copyAnnotations(source, nonNulls, nullables, onParam.toArray(new Annotation[0])); - if (copiedAnnotationsParam.length != 0) param.annotations = copiedAnnotationsParam; + if (!makeAbstract) { + List<Expression> args = new ArrayList<Expression>(); + for (EclipseNode child : fieldNode.up().down()) { + if (child.getKind() != Kind.FIELD) continue; + FieldDeclaration childDecl = (FieldDeclaration) child.get(); + // Skip fields that start with $ + if (childDecl.name != null && childDecl.name.length > 0 && childDecl.name[0] == '$') continue; + long fieldFlags = childDecl.modifiers; + // Skip static fields. + if ((fieldFlags & ClassFileConstants.AccStatic) != 0) continue; + // Skip initialized final fields. + if (((fieldFlags & ClassFileConstants.AccFinal) != 0) && childDecl.initialization != null) continue; + if (child.get() == fieldNode.get()) { + args.add(new SingleNameReference(field.name, p)); + } else { + args.add(createFieldAccessor(child, FieldAccess.ALWAYS_FIELD, source)); + } + } + + AllocationExpression constructorCall = new AllocationExpression(); + constructorCall.arguments = args.toArray(new Expression[0]); + constructorCall.type = cloneSelfType(fieldNode, source); + + Expression identityCheck = new EqualExpression( + createFieldAccessor(fieldNode, FieldAccess.ALWAYS_FIELD, source), + new SingleNameReference(field.name, p), + OperatorIds.EQUAL_EQUAL); + ThisReference thisRef = new ThisReference(pS, pE); + Expression conditional = new ConditionalExpression(identityCheck, thisRef, constructorCall); + Statement returnStatement = new ReturnStatement(conditional, pS, pE); + method.bodyStart = method.declarationSourceStart = method.sourceStart = source.sourceStart; + method.bodyEnd = method.declarationSourceEnd = method.sourceEnd = source.sourceEnd; + + List<Statement> statements = new ArrayList<Statement>(5); + if (nonNulls.length > 0) { + Statement nullCheck = generateNullCheck(field, sourceNode); + if (nullCheck != null) statements.add(nullCheck); + } + statements.add(returnStatement); + + method.statements = statements.toArray(new Statement[0]); + } + param.annotations = copyAnnotations(source, nonNulls, nullables, onParam.toArray(new Annotation[0])); method.traverse(new SetGeneratedByVisitor(source), parent.scope); return method; diff --git a/src/core/lombok/eclipse/handlers/SetGeneratedByVisitor.java b/src/core/lombok/eclipse/handlers/SetGeneratedByVisitor.java index 7217a396..89964914 100644 --- a/src/core/lombok/eclipse/handlers/SetGeneratedByVisitor.java +++ b/src/core/lombok/eclipse/handlers/SetGeneratedByVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2013 The Project Lombok Authors. + * Copyright (C) 2011-2015 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 @@ -23,6 +23,8 @@ package lombok.eclipse.handlers; import static lombok.eclipse.handlers.EclipseHandlerUtil.*; +import java.util.Arrays; + import org.eclipse.jdt.internal.compiler.ASTVisitor; import org.eclipse.jdt.internal.compiler.ast.AND_AND_Expression; import org.eclipse.jdt.internal.compiler.ast.ASTNode; @@ -128,881 +130,816 @@ public final class SetGeneratedByVisitor extends ASTVisitor { private static final long INT_TO_LONG_MASK = 0x00000000FFFFFFFFL; private final ASTNode source; - private final int newSourceStart; - private final int newSourceEnd; + private final int sourceStart; + private final int sourceEnd; + private final long sourcePos; public SetGeneratedByVisitor(ASTNode source) { this.source = source; - this.newSourceStart = this.source.sourceStart; - this.newSourceEnd = this.source.sourceEnd; + this.sourceStart = this.source.sourceStart; + this.sourceEnd = this.source.sourceEnd; + this.sourcePos = (long)sourceStart << 32 | (sourceEnd & INT_TO_LONG_MASK); } - private void applyOffset(JavadocAllocationExpression node) { - applyOffsetExpression(node); - node.memberStart = newSourceStart; - node.tagSourceEnd = newSourceEnd; - node.tagSourceStart = newSourceStart; + private void fixPositions(JavadocAllocationExpression node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.statementEnd = sourceEnd; + node.memberStart = sourceStart; + node.tagSourceEnd = sourceEnd; + node.tagSourceStart = sourceStart; } - private void applyOffset(JavadocMessageSend node) { - applyOffsetMessageSend(node); - node.tagSourceEnd = newSourceEnd; - node.tagSourceStart = newSourceStart; + private void fixPositions(JavadocMessageSend node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.statementEnd = sourceEnd; + node.nameSourcePosition = sourcePos; + node.tagSourceEnd = sourceEnd; + node.tagSourceStart = sourceStart; } - private void applyOffset(JavadocSingleNameReference node) { - applyOffsetExpression(node); - node.tagSourceEnd = newSourceEnd; - node.tagSourceStart = newSourceStart; + private void fixPositions(JavadocSingleNameReference node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.statementEnd = sourceEnd; + node.tagSourceEnd = sourceEnd; + node.tagSourceStart = sourceStart; } - private void applyOffset(JavadocSingleTypeReference node) { - applyOffsetExpression(node); - node.tagSourceEnd = newSourceEnd; - node.tagSourceStart = newSourceStart; + private void fixPositions(JavadocSingleTypeReference node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.statementEnd = sourceEnd; + node.tagSourceEnd = sourceEnd; + node.tagSourceStart = sourceStart; } - private void applyOffset(JavadocFieldReference node) { - applyOffsetFieldReference(node); - node.tagSourceEnd = newSourceEnd; - node.tagSourceStart = newSourceStart; + private void fixPositions(JavadocFieldReference node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.statementEnd = sourceEnd; + node.nameSourcePosition = sourcePos; + node.tagSourceEnd = sourceEnd; + node.tagSourceStart = sourceStart; + } + + private void fixPositions(JavadocArrayQualifiedTypeReference node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.statementEnd = sourceEnd; + if (node.sourcePositions == null || node.sourcePositions.length != node.tokens.length) node.sourcePositions = new long[node.tokens.length]; + Arrays.fill(node.sourcePositions, sourcePos); + node.tagSourceEnd = sourceEnd; + node.tagSourceStart = sourceStart; + } + + private void fixPositions(JavadocQualifiedTypeReference node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.statementEnd = sourceEnd; + if (node.sourcePositions == null || node.sourcePositions.length != node.tokens.length) node.sourcePositions = new long[node.tokens.length]; + Arrays.fill(node.sourcePositions, sourcePos); + node.tagSourceEnd = sourceEnd; + node.tagSourceStart = sourceStart; + } + + private void fixPositions(Annotation node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.statementEnd = sourceEnd; + node.declarationSourceEnd = sourceEnd; } - - private void applyOffset(JavadocArrayQualifiedTypeReference node) { - applyOffsetQualifiedTypeReference(node); - node.tagSourceEnd = newSourceEnd; - node.tagSourceStart = newSourceStart; + + private void fixPositions(ArrayTypeReference node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.statementEnd = sourceEnd; + node.originalSourceEnd = sourceEnd; } - private void applyOffset(JavadocQualifiedTypeReference node) { - applyOffsetQualifiedTypeReference(node); - node.tagSourceEnd = newSourceEnd; - node.tagSourceStart = newSourceStart; + private void fixPositions(AbstractMethodDeclaration node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.bodyEnd = sourceEnd; + node.bodyStart = sourceStart; + node.declarationSourceEnd = sourceEnd; + node.declarationSourceStart = sourceStart; + node.modifiersSourceStart = sourceStart; } - private void applyOffset(Annotation node) { - applyOffsetExpression(node); - node.declarationSourceEnd = newSourceEnd; + private void fixPositions(Javadoc node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.valuePositions = sourceStart; } - private void applyOffset(ArrayTypeReference node) { - applyOffsetExpression(node); - node.originalSourceEnd = newSourceEnd; + private void fixPositions(Initializer node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.declarationEnd = sourceEnd; + node.declarationSourceEnd = sourceEnd; + node.declarationSourceStart = sourceStart; + node.modifiersSourceStart = sourceStart; + node.endPart1Position = sourceEnd; + node.endPart2Position = sourceEnd; + node.bodyStart = sourceStart; + node.bodyEnd = sourceEnd; } - - private void applyOffset(AbstractMethodDeclaration node) { - applyOffsetASTNode(node); - node.bodyEnd = newSourceEnd; - node.bodyStart = newSourceStart; - node.declarationSourceEnd = newSourceEnd; - node.declarationSourceStart = newSourceStart; - node.modifiersSourceStart = newSourceStart; + + private void fixPositions(TypeDeclaration node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.bodyEnd = sourceEnd; + node.bodyStart = sourceStart; + node.declarationSourceEnd = sourceEnd; + node.declarationSourceStart = sourceStart; + node.modifiersSourceStart = sourceStart; } - private void applyOffset(Javadoc node) { - applyOffsetASTNode(node); - node.valuePositions = newSourceStart; - for (int i = 0; i < node.inheritedPositions.length; i++) { - node.inheritedPositions[i] = recalcSourcePosition(node.inheritedPositions[i]); - } + private void fixPositions(ImportReference node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.declarationEnd = sourceEnd; + node.declarationSourceEnd = sourceEnd; + node.declarationSourceStart = sourceStart; + if (node.sourcePositions == null || node.sourcePositions.length != node.tokens.length) node.sourcePositions = new long[node.tokens.length]; + Arrays.fill(node.sourcePositions, sourcePos); } - - private void applyOffset(Initializer node) { - applyOffsetFieldDeclaration(node); - node.bodyStart = newSourceStart; - node.bodyEnd = newSourceEnd; - } - - private void applyOffset(TypeDeclaration node) { - applyOffsetASTNode(node); - node.bodyEnd = newSourceEnd; - node.bodyStart = newSourceStart; - node.declarationSourceEnd = newSourceEnd; - node.declarationSourceStart = newSourceStart; - node.modifiersSourceStart = newSourceStart; - } - - private void applyOffset(ImportReference node) { - applyOffsetASTNode(node); - node.declarationEnd = newSourceEnd; - node.declarationSourceEnd = newSourceEnd; - node.declarationSourceStart = newSourceStart; - for (int i = 0; i < node.sourcePositions.length; i++) { - node.sourcePositions[i] = recalcSourcePosition(node.sourcePositions[i]); - } + + private void fixPositions(ASTNode node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; } - - private void applyOffsetASTNode(ASTNode node) { - node.sourceEnd = newSourceEnd; - node.sourceStart = newSourceStart; + + private void fixPositions(SwitchStatement node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.blockStart = sourceStart; } - - private void applyOffsetExpression(Expression node) { - applyOffsetASTNode(node); -// if (node.statementEnd != -1) { - node.statementEnd = newSourceEnd; -// } + + private void fixPositions(Expression node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.statementEnd = sourceEnd; } - - private void applyOffsetVariable(AbstractVariableDeclaration node) { - applyOffsetASTNode(node); - node.declarationEnd = newSourceEnd; - node.declarationSourceEnd = newSourceEnd; - node.declarationSourceStart = newSourceStart; - node.modifiersSourceStart = newSourceStart; + + private void fixPositions(AbstractVariableDeclaration node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.declarationEnd = sourceEnd; + node.declarationSourceEnd = sourceEnd; + node.declarationSourceStart = sourceStart; + node.modifiersSourceStart = sourceStart; } - private void applyOffsetFieldDeclaration(FieldDeclaration node) { - applyOffsetVariable(node); - node.endPart1Position = newSourceEnd; - node.endPart2Position = newSourceEnd; + private void fixPositions(FieldDeclaration node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.declarationEnd = sourceEnd; + node.declarationSourceEnd = sourceEnd; + node.declarationSourceStart = sourceStart; + node.modifiersSourceStart = sourceStart; + node.endPart1Position = sourceEnd; + node.endPart2Position = sourceEnd; } - private void applyOffsetFieldReference(FieldReference node) { - applyOffsetExpression(node); - node.nameSourcePosition = recalcSourcePosition(node.nameSourcePosition); + private void fixPositions(FieldReference node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.statementEnd = sourceEnd; + node.nameSourcePosition = sourcePos; } - private void applyOffsetMessageSend(MessageSend node) { - applyOffsetExpression(node); - node.nameSourcePosition = recalcSourcePosition(node.nameSourcePosition); + private void fixPositions(MessageSend node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.statementEnd = sourceEnd; + node.nameSourcePosition = sourcePos; } - private void applyOffsetQualifiedNameReference(QualifiedNameReference node) { - applyOffsetExpression(node); - for (int i = 0; i < node.sourcePositions.length; i++) { - node.sourcePositions[i] = recalcSourcePosition(node.sourcePositions[i]); - } + private void fixPositions(QualifiedNameReference node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.statementEnd = sourceEnd; + if (node.sourcePositions == null || node.sourcePositions.length != node.tokens.length) node.sourcePositions = new long[node.tokens.length]; + Arrays.fill(node.sourcePositions, sourcePos); } - private void applyOffsetQualifiedTypeReference(QualifiedTypeReference node) { - applyOffsetExpression(node); - for (int i = 0; i < node.sourcePositions.length; i++) { - node.sourcePositions[i] = recalcSourcePosition(node.sourcePositions[i]); - } - } - - /** See {@link FieldReference#nameSourcePosition} for explanation */ - private long recalcSourcePosition(long sourcePosition) { -// long start = (sourcePosition >>> 32); -// long end = (sourcePosition & 0x00000000FFFFFFFFL); -// start = newSourceStart; -// end = newSourceStart; -// return ((start<<32)+end); - return ((long)newSourceStart << 32) | (newSourceEnd & INT_TO_LONG_MASK); + private void fixPositions(QualifiedTypeReference node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.statementEnd = sourceEnd; + if (node.sourcePositions == null || node.sourcePositions.length != node.tokens.length) node.sourcePositions = new long[node.tokens.length]; + Arrays.fill(node.sourcePositions, sourcePos); } @Override public boolean visit(AllocationExpression node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } - + @Override public boolean visit(AND_AND_Expression node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(AnnotationMethodDeclaration node, ClassScope classScope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, classScope); } @Override public boolean visit(Argument node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetVariable(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(Argument node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffsetVariable(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ArrayAllocationExpression node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ArrayInitializer node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ArrayQualifiedTypeReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetQualifiedTypeReference(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ArrayQualifiedTypeReference node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffsetQualifiedTypeReference(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ArrayReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ArrayTypeReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ArrayTypeReference node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(AssertStatement node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(Assignment node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(BinaryExpression node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(Block node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(BreakStatement node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(CaseStatement node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(CastExpression node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(CharLiteral node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ClassLiteralAccess node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(Clinit node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(CompilationUnitDeclaration node, CompilationUnitScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(CompoundAssignment node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ConditionalExpression node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ConstructorDeclaration node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ContinueStatement node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(DoStatement node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(DoubleLiteral node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(EmptyStatement node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(EqualExpression node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ExplicitConstructorCall node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ExtendedStringLiteral node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(FalseLiteral node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(FieldDeclaration node, MethodScope scope) { - setGeneratedBy(node, source); - applyOffsetFieldDeclaration(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(FieldReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetFieldReference(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(FieldReference node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffsetFieldReference(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(FloatLiteral node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ForeachStatement node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ForStatement node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(IfStatement node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ImportReference node, CompilationUnitScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(Initializer node, MethodScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(InstanceOfExpression node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(IntLiteral node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(Javadoc node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(Javadoc node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocAllocationExpression node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocAllocationExpression node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocArgumentExpression node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocArgumentExpression node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocArrayQualifiedTypeReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocArrayQualifiedTypeReference node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocArraySingleTypeReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocArraySingleTypeReference node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocFieldReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocFieldReference node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocImplicitTypeReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocImplicitTypeReference node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocMessageSend node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocMessageSend node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocQualifiedTypeReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocQualifiedTypeReference node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocReturnStatement node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocReturnStatement node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocSingleNameReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocSingleNameReference node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocSingleTypeReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocSingleTypeReference node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(LabeledStatement node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(LocalDeclaration node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetVariable(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(LongLiteral node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(MarkerAnnotation node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(MemberValuePair node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(MessageSend node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetMessageSend(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(MethodDeclaration node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(StringLiteralConcatenation node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(NormalAnnotation node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(NullLiteral node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(OR_OR_Expression node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ParameterizedQualifiedTypeReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetQualifiedTypeReference(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ParameterizedQualifiedTypeReference node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffsetQualifiedTypeReference(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ParameterizedSingleTypeReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ParameterizedSingleTypeReference node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(PostfixExpression node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(PrefixExpression node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(QualifiedAllocationExpression node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(QualifiedNameReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetQualifiedNameReference(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(QualifiedNameReference node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(QualifiedSuperReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(QualifiedSuperReference node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(QualifiedThisReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(QualifiedThisReference node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(QualifiedTypeReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetQualifiedTypeReference(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(QualifiedTypeReference node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffsetQualifiedTypeReference(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ReturnStatement node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(SingleMemberAnnotation node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(SingleNameReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(SingleNameReference node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(SingleTypeReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(SingleTypeReference node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(StringLiteral node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(SuperReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(SwitchStatement node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); - node.blockStart = newSourceStart; + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(SynchronizedStatement node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ThisReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ThisReference node, ClassScope scope) { - setGeneratedBy(node, source); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ThrowStatement node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(TrueLiteral node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(TryStatement node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(TypeDeclaration node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(TypeDeclaration node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(TypeDeclaration node, CompilationUnitScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(TypeParameter node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetVariable(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(TypeParameter node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffsetVariable(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(UnaryExpression node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(WhileStatement node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(Wildcard node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(Wildcard node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } + +// missing methods +// public boolean visit(MarkerAnnotation node, ClassScope scope){ return true;} +// public boolean visit(MemberValuePair node, ClassScope scope){ return true;} +// public boolean visit(NormalAnnotation node, ClassScope scope){ return true;} +// public boolean visit(SingleMemberAnnotation node, ClassScope scope){ return true;} + +// missing methods from later versions +// public boolean visit(UnionTypeReference node, BlockScope scope){ return true;} +// public boolean visit(UnionTypeReference node, ClassScope scope){ return true;} +// public boolean visit(LambdaExpression node, BlockScope scope){ return true;} +// public boolean visit(ReferenceExpression node, BlockScope scope){ return true;} +// public boolean visit(IntersectionCastTypeReference node, ClassScope scope){ return true;} +// public boolean visit(IntersectionCastTypeReference node, BlockScope scope){ return true;} }
\ No newline at end of file diff --git a/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaMapSingularizer.java b/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaMapSingularizer.java new file mode 100644 index 00000000..5956c01b --- /dev/null +++ b/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaMapSingularizer.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2015 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.singulars; + +import org.mangosdk.spi.ProviderFor; + +import lombok.core.LombokImmutableList; +import lombok.eclipse.handlers.EclipseSingularsRecipes.EclipseSingularizer; + +@ProviderFor(EclipseSingularizer.class) +public class EclipseGuavaMapSingularizer extends EclipseGuavaSingularizer { + // TODO cgcc.ImmutableMultimap, cgcc.ImmutableListMultimap, cgcc.ImmutableSetMultimap + // TODO cgcc.ImmutableClassToInstanceMap + // TODO cgcc.ImmutableRangeMap + + private static final LombokImmutableList<String> SUFFIXES = + LombokImmutableList.of("key", "value"); + private static final LombokImmutableList<String> SUPPORTED_TYPES = LombokImmutableList.of( + "com.google.common.collect.ImmutableMap", + "com.google.common.collect.ImmutableBiMap", + "com.google.common.collect.ImmutableSortedMap" + ); + + @Override public LombokImmutableList<String> getSupportedTypes() { + return SUPPORTED_TYPES; + } + + @Override protected LombokImmutableList<String> getArgumentSuffixes() { + return SUFFIXES; + } + + @Override protected String getAddMethodName() { + return "put"; + } + + @Override protected String getAddAllTypeName() { + return "java.util.Map"; + } +} diff --git a/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaSetListSingularizer.java b/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaSetListSingularizer.java new file mode 100644 index 00000000..326a9179 --- /dev/null +++ b/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaSetListSingularizer.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2015 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.singulars; + +import org.mangosdk.spi.ProviderFor; + +import lombok.core.LombokImmutableList; +import lombok.eclipse.handlers.EclipseSingularsRecipes.EclipseSingularizer; + +@ProviderFor(EclipseSingularizer.class) +public class EclipseGuavaSetListSingularizer extends EclipseGuavaSingularizer { + // TODO com.google.common.collect.ImmutableRangeSet + // TODO com.google.common.collect.ImmutableMultiset and com.google.common.collect.ImmutableSortedMultiset + private static final LombokImmutableList<String> SUFFIXES = LombokImmutableList.of(""); + private static final LombokImmutableList<String> SUPPORTED_TYPES = LombokImmutableList.of( + "com.google.common.collect.ImmutableCollection", + "com.google.common.collect.ImmutableList", + "com.google.common.collect.ImmutableSet", + "com.google.common.collect.ImmutableSortedSet" + ); + + @Override public LombokImmutableList<String> getSupportedTypes() { + return SUPPORTED_TYPES; + } + + @Override protected LombokImmutableList<String> getArgumentSuffixes() { + return SUFFIXES; + } + + @Override protected String getAddMethodName() { + return "add"; + } + + @Override protected String getAddAllTypeName() { + return "java.lang.Iterable"; + } +} diff --git a/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaSingularizer.java b/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaSingularizer.java new file mode 100644 index 00000000..8e925b3f --- /dev/null +++ b/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaSingularizer.java @@ -0,0 +1,261 @@ +/* + * 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 + * 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.singulars; + +import static lombok.eclipse.Eclipse.*; +import static lombok.eclipse.handlers.EclipseHandlerUtil.*; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import lombok.core.GuavaTypeMap; +import lombok.core.LombokImmutableList; +import lombok.core.handlers.HandlerUtil; +import lombok.eclipse.EclipseNode; +import lombok.eclipse.handlers.EclipseSingularsRecipes.EclipseSingularizer; +import lombok.eclipse.handlers.EclipseSingularsRecipes.SingularData; + +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.EqualExpression; +import org.eclipse.jdt.internal.compiler.ast.Expression; +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.LocalDeclaration; +import org.eclipse.jdt.internal.compiler.ast.MessageSend; +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.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) { + return GuavaTypeMap.getGuavaTypeName(data.getTargetFqn()); + } + + protected char[] getBuilderMethodName(SingularData data) { + String simpleTypeName = getSimpleTargetTypeName(data); + if ("ImmutableSortedSet".equals(simpleTypeName) || "ImmutableSortedMap".equals(simpleTypeName)) return "naturalOrder".toCharArray(); + return "builder".toCharArray(); + } + + protected char[][] makeGuavaTypeName(String simpleName, boolean addBuilder) { + char[][] tokenizedName = new char[addBuilder ? 6 : 5][]; + tokenizedName[0] = new char[] {'c', 'o', 'm'}; + tokenizedName[1] = new char[] {'g', 'o', 'o', 'g', 'l', 'e'}; + tokenizedName[2] = new char[] {'c', 'o', 'm', 'm', 'o', 'n'}; + tokenizedName[3] = new char[] {'c', 'o', 'l', 'l', 'e', 'c', 't'}; + tokenizedName[4] = simpleName.toCharArray(); + if (addBuilder) tokenizedName[5] = new char[] { 'B', 'u', 'i', 'l', 'd', 'e', 'r'}; + return tokenizedName; + } + + @Override public List<EclipseNode> generateFields(SingularData data, EclipseNode builderType) { + String simpleTypeName = getSimpleTargetTypeName(data); + char[][] tokenizedName = makeGuavaTypeName(simpleTypeName, true); + TypeReference type = new QualifiedTypeReference(tokenizedName, NULL_POSS); + type = addTypeArgs(getTypeArgumentsCount(), false, builderType, type, data.getTypeArgs()); + + FieldDeclaration buildField = new FieldDeclaration(data.getPluralName(), 0, -1); + buildField.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + buildField.modifiers = ClassFileConstants.AccPrivate; + buildField.declarationSourceEnd = -1; + buildField.type = type; + data.setGeneratedByRecursive(buildField); + return Collections.singletonList(injectFieldAndMarkGenerated(builderType, buildField)); + } + + @Override public void generateMethods(SingularData data, boolean deprecate, 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(deprecate, 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(deprecate, 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(deprecate, returnType, returnStatement, data, 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; + + FieldReference thisDotField = new FieldReference(data.getPluralName(), 0L); + thisDotField.receiver = new ThisReference(0, 0); + Assignment a = new Assignment(thisDotField, new NullLiteral(0, 0), 0); + 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(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++) { + String s = suffixes.get(i); + char[] n = data.getSingularName(); + names[i] = s.isEmpty() ? n : s.toCharArray(); + } + + MethodDeclaration md = new MethodDeclaration(((CompilationUnitDeclaration) builderType.top().get()).compilationResult); + md.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + md.modifiers = ClassFileConstants.AccPublic; + + List<Statement> statements = new ArrayList<Statement>(); + statements.add(createConstructBuilderVarIfNeeded(data, builderType)); + + FieldReference thisDotField = new FieldReference(data.getPluralName(), 0L); + thisDotField.receiver = new ThisReference(0, 0); + MessageSend thisDotFieldDotAdd = new MessageSend(); + thisDotFieldDotAdd.arguments = new Expression[suffixes.size()]; + for (int i = 0; i < suffixes.size(); i++) { + thisDotFieldDotAdd.arguments[i] = new SingleNameReference(names[i], 0L); + } + thisDotFieldDotAdd.receiver = thisDotField; + thisDotFieldDotAdd.selector = getAddMethodName().toCharArray(); + statements.add(thisDotFieldDotAdd); + if (returnStatement != null) statements.add(returnStatement); + md.statements = statements.toArray(new Statement[statements.size()]); + md.arguments = new Argument[suffixes.size()]; + for (int i = 0; i < suffixes.size(); i++) { + TypeReference tr = cloneParamType(i, data.getTypeArgs(), builderType); + md.arguments[i] = new Argument(names[i], 0, tr, 0); + } + 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(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; + + List<Statement> statements = new ArrayList<Statement>(); + statements.add(createConstructBuilderVarIfNeeded(data, builderType)); + + FieldReference thisDotField = new FieldReference(data.getPluralName(), 0L); + thisDotField.receiver = new ThisReference(0, 0); + MessageSend thisDotFieldDotAddAll = new MessageSend(); + thisDotFieldDotAddAll.arguments = new Expression[] {new SingleNameReference(data.getPluralName(), 0L)}; + thisDotFieldDotAddAll.receiver = thisDotField; + thisDotFieldDotAddAll.selector = (getAddMethodName() + "All").toCharArray(); + statements.add(thisDotFieldDotAddAll); + if (returnStatement != null) statements.add(returnStatement); + + md.statements = statements.toArray(new Statement[statements.size()]); + + TypeReference paramType; + paramType = new QualifiedTypeReference(fromQualifiedName(getAddAllTypeName()), NULL_POSS); + paramType = addTypeArgs(getTypeArgumentsCount(), true, builderType, paramType, data.getTypeArgs()); + Argument param = new Argument(data.getPluralName(), 0, paramType, 0); + 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) { + TypeReference varType = new QualifiedTypeReference(fromQualifiedName(data.getTargetFqn()), NULL_POSS); + String simpleTypeName = getSimpleTargetTypeName(data); + int agrumentsCount = getTypeArgumentsCount(); + varType = addTypeArgs(agrumentsCount, false, builderType, varType, data.getTypeArgs()); + + MessageSend emptyInvoke; { + //ImmutableX.of() + emptyInvoke = new MessageSend(); + emptyInvoke.selector = new char[] {'o', 'f'}; + emptyInvoke.receiver = new QualifiedNameReference(makeGuavaTypeName(simpleTypeName, false), NULL_POSS, 0, 0); + emptyInvoke.typeArguments = createTypeArgs(agrumentsCount, false, builderType, data.getTypeArgs()); + } + + MessageSend invokeBuild; { + //this.pluralName.build(); + 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); + invokeBuild.receiver = thisDotField; + } + + Expression isNull; { + //this.pluralName == null + FieldReference thisDotField = new FieldReference(data.getPluralName(), 0L); + thisDotField.receiver = new ThisReference(0, 0); + isNull = new EqualExpression(thisDotField, new NullLiteral(0, 0), OperatorIds.EQUAL_EQUAL); + } + + Expression init = new ConditionalExpression(isNull, emptyInvoke, invokeBuild); + LocalDeclaration varDefStat = new LocalDeclaration(data.getPluralName(), 0, 0); + varDefStat.type = varType; + varDefStat.initialization = init; + statements.add(varDefStat); + } + + protected Statement createConstructBuilderVarIfNeeded(SingularData data, EclipseNode builderType) { + FieldReference thisDotField = new FieldReference(data.getPluralName(), 0L); + thisDotField.receiver = new ThisReference(0, 0); + FieldReference thisDotField2 = new FieldReference(data.getPluralName(), 0L); + thisDotField2.receiver = new ThisReference(0, 0); + Expression cond = new EqualExpression(thisDotField, new NullLiteral(0, 0), OperatorIds.EQUAL_EQUAL); + + MessageSend createBuilderInvoke = new MessageSend(); + char[][] tokenizedName = makeGuavaTypeName(getSimpleTargetTypeName(data), false); + createBuilderInvoke.receiver = new QualifiedNameReference(tokenizedName, NULL_POSS, 0, 0); + createBuilderInvoke.selector = getBuilderMethodName(data); + return new IfStatement(cond, new Assignment(thisDotField2, createBuilderInvoke, 0), 0, 0); + } + + protected abstract LombokImmutableList<String> getArgumentSuffixes(); + protected abstract String getAddMethodName(); + protected abstract String getAddAllTypeName(); + + protected int getTypeArgumentsCount() { + return getArgumentSuffixes().size(); + } +} diff --git a/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaTableSingularizer.java b/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaTableSingularizer.java new file mode 100644 index 00000000..4d25811b --- /dev/null +++ b/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaTableSingularizer.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2015 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.singulars; + +import org.mangosdk.spi.ProviderFor; + +import lombok.core.LombokImmutableList; +import lombok.eclipse.handlers.EclipseSingularsRecipes.EclipseSingularizer; + +@ProviderFor(EclipseSingularizer.class) +public class EclipseGuavaTableSingularizer extends EclipseGuavaSingularizer { + private static final LombokImmutableList<String> SUFFIXES = + LombokImmutableList.of("rowKey", "columnKey", "value"); + private static final LombokImmutableList<String> SUPPORTED_TYPES = + LombokImmutableList.of("com.google.common.collect.ImmutableTable"); + + @Override public LombokImmutableList<String> getSupportedTypes() { + return SUPPORTED_TYPES; + } + + @Override protected LombokImmutableList<String> getArgumentSuffixes() { + return SUFFIXES; + } + + @Override protected String getAddMethodName() { + return "put"; + } + + @Override protected String getAddAllTypeName() { + return "com.google.common.collect.Table"; + } +} diff --git a/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilListSetSingularizer.java b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilListSetSingularizer.java new file mode 100644 index 00000000..cfa48eaf --- /dev/null +++ b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilListSetSingularizer.java @@ -0,0 +1,189 @@ +/* + * 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 + * 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.singulars; + +import static lombok.eclipse.Eclipse.ECLIPSE_DO_NOT_TOUCH_FLAG; +import static lombok.eclipse.handlers.EclipseHandlerUtil.*; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import lombok.core.handlers.HandlerUtil; +import lombok.eclipse.EclipseNode; +import lombok.eclipse.handlers.EclipseSingularsRecipes.SingularData; + +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; +import org.eclipse.jdt.internal.compiler.ast.Expression; +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.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) { + if (useGuavaInstead(builderType)) { + return guavaListSetSingularizer.listFieldsToBeGenerated(data, builderType); + } + + return super.listFieldsToBeGenerated(data, builderType); + } + + @Override public List<char[]> listMethodsToBeGenerated(SingularData data, EclipseNode builderType) { + if (useGuavaInstead(builderType)) { + return guavaListSetSingularizer.listMethodsToBeGenerated(data, builderType); + } + + return super.listMethodsToBeGenerated(data, builderType); + } + + @Override public List<EclipseNode> generateFields(SingularData data, EclipseNode builderType) { + if (useGuavaInstead(builderType)) { + return guavaListSetSingularizer.generateFields(data, builderType); + } + + TypeReference type = new QualifiedTypeReference(JAVA_UTIL_ARRAYLIST, NULL_POSS); + type = addTypeArgs(1, false, builderType, type, data.getTypeArgs()); + + FieldDeclaration buildField = new FieldDeclaration(data.getPluralName(), 0, -1); + buildField.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + buildField.modifiers = ClassFileConstants.AccPrivate; + buildField.declarationSourceEnd = -1; + buildField.type = type; + data.setGeneratedByRecursive(buildField); + return Collections.singletonList(injectFieldAndMarkGenerated(builderType, buildField)); + } + + @Override public void generateMethods(SingularData data, boolean deprecate, EclipseNode builderType, boolean fluent, boolean chain) { + if (useGuavaInstead(builderType)) { + guavaListSetSingularizer.generateMethods(data, deprecate, builderType, fluent, chain); + 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(deprecate, 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(deprecate, 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(deprecate, returnType, returnStatement, data, 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; + + FieldReference thisDotField = new FieldReference(data.getPluralName(), 0L); + thisDotField.receiver = new ThisReference(0, 0); + FieldReference thisDotField2 = new FieldReference(data.getPluralName(), 0L); + thisDotField2.receiver = new ThisReference(0, 0); + md.selector = HandlerUtil.buildAccessorName("clear", new String(data.getPluralName())).toCharArray(); + MessageSend clearMsg = new MessageSend(); + clearMsg.receiver = thisDotField2; + clearMsg.selector = "clear".toCharArray(); + 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(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; + + List<Statement> statements = new ArrayList<Statement>(); + statements.add(createConstructBuilderVarIfNeeded(data, builderType, false)); + + FieldReference thisDotField = new FieldReference(data.getPluralName(), 0L); + thisDotField.receiver = new ThisReference(0, 0); + MessageSend thisDotFieldDotAdd = new MessageSend(); + thisDotFieldDotAdd.arguments = new Expression[] {new SingleNameReference(data.getSingularName(), 0L)}; + thisDotFieldDotAdd.receiver = thisDotField; + thisDotFieldDotAdd.selector = "add".toCharArray(); + statements.add(thisDotFieldDotAdd); + if (returnStatement != null) statements.add(returnStatement); + + md.statements = statements.toArray(new Statement[statements.size()]); + TypeReference paramType = cloneParamType(0, data.getTypeArgs(), builderType); + Argument param = new Argument(data.getSingularName(), 0, paramType, 0); + 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(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; + + List<Statement> statements = new ArrayList<Statement>(); + statements.add(createConstructBuilderVarIfNeeded(data, builderType, false)); + + FieldReference thisDotField = new FieldReference(data.getPluralName(), 0L); + thisDotField.receiver = new ThisReference(0, 0); + MessageSend thisDotFieldDotAddAll = new MessageSend(); + thisDotFieldDotAddAll.arguments = new Expression[] {new SingleNameReference(data.getPluralName(), 0L)}; + thisDotFieldDotAddAll.receiver = thisDotField; + thisDotFieldDotAddAll.selector = "addAll".toCharArray(); + statements.add(thisDotFieldDotAddAll); + if (returnStatement != null) statements.add(returnStatement); + + md.statements = statements.toArray(new Statement[statements.size()]); + + TypeReference paramType = new QualifiedTypeReference(TypeConstants.JAVA_UTIL_COLLECTION, NULL_POSS); + paramType = addTypeArgs(1, true, builderType, paramType, data.getTypeArgs()); + Argument param = new Argument(data.getPluralName(), 0, paramType, 0); + 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 new file mode 100644 index 00000000..576115b0 --- /dev/null +++ b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilListSingularizer.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2015 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.singulars; + +import static lombok.eclipse.handlers.EclipseHandlerUtil.makeIntLiteral; + +import java.util.ArrayList; +import java.util.List; + +import lombok.core.LombokImmutableList; +import lombok.eclipse.Eclipse; +import lombok.eclipse.EclipseNode; +import lombok.eclipse.handlers.EclipseSingularsRecipes.EclipseSingularizer; +import lombok.eclipse.handlers.EclipseSingularsRecipes.SingularData; + +import org.eclipse.jdt.internal.compiler.ast.AllocationExpression; +import org.eclipse.jdt.internal.compiler.ast.Assignment; +import org.eclipse.jdt.internal.compiler.ast.BreakStatement; +import org.eclipse.jdt.internal.compiler.ast.CaseStatement; +import org.eclipse.jdt.internal.compiler.ast.Expression; +import org.eclipse.jdt.internal.compiler.ast.FieldReference; +import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration; +import org.eclipse.jdt.internal.compiler.ast.MessageSend; +import org.eclipse.jdt.internal.compiler.ast.QualifiedNameReference; +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; + + +@ProviderFor(EclipseSingularizer.class) +public class EclipseJavaUtilListSingularizer extends EclipseJavaUtilListSetSingularizer { + @Override public LombokImmutableList<String> getSupportedTypes() { + 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) { + if (useGuavaInstead(builderType)) { + guavaListSetSingularizer.appendBuildCode(data, builderType, statements, targetVariableName); + return; + } + + List<Statement> switchContents = new ArrayList<Statement>(); + + /* case 0: (empty) break; */ { + switchContents.add(new CaseStatement(makeIntLiteral(new char[] {'0'}, null), 0, 0)); + MessageSend invoke = new MessageSend(); + invoke.receiver = new QualifiedNameReference(JAVA_UTIL_COLLECTIONS, NULL_POSS, 0, 0); + invoke.selector = "emptyList".toCharArray(); + switchContents.add(new Assignment(new SingleNameReference(data.getPluralName(), 0), invoke, 0)); + switchContents.add(new BreakStatement(null, 0, 0)); + } + + /* 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); + MessageSend thisDotFieldGet0 = new MessageSend(); + thisDotFieldGet0.receiver = thisDotField; + thisDotFieldGet0.selector = new char[] {'g', 'e', 't'}; + thisDotFieldGet0.arguments = new Expression[] {makeIntLiteral(new char[] {'0'}, null)}; + + Expression[] args = new Expression[] {thisDotFieldGet0}; + MessageSend invoke = new MessageSend(); + invoke.receiver = new QualifiedNameReference(JAVA_UTIL_COLLECTIONS, NULL_POSS, 0, 0); + invoke.selector = "singletonList".toCharArray(); + invoke.arguments = args; + switchContents.add(new Assignment(new SingleNameReference(data.getPluralName(), 0), invoke, 0)); + switchContents.add(new BreakStatement(null, 0, 0)); + } + + /* default: Create by passing builder field to constructor. */ { + switchContents.add(new CaseStatement(null, 0, 0)); + + Expression argToUnmodifiable; + /* new j.u.ArrayList<Generics>(this.pluralName); */ { + FieldReference thisDotPluralName = new FieldReference(data.getPluralName(), 0L); + thisDotPluralName.receiver = new ThisReference(0, 0); + TypeReference targetTypeExpr = new QualifiedTypeReference(JAVA_UTIL_ARRAYLIST, NULL_POSS); + targetTypeExpr = addTypeArgs(1, false, builderType, targetTypeExpr, data.getTypeArgs()); + AllocationExpression constructorCall = new AllocationExpression(); + constructorCall.type = targetTypeExpr; + constructorCall.arguments = new Expression[] {thisDotPluralName}; + argToUnmodifiable = constructorCall; + } + + /* pluralname = Collections.unmodifiableList(-newlist-); */ { + MessageSend unmodInvoke = new MessageSend(); + unmodInvoke.receiver = new QualifiedNameReference(JAVA_UTIL_COLLECTIONS, NULL_POSS, 0, 0); + unmodInvoke.selector = "unmodifiableList".toCharArray(); + unmodInvoke.arguments = new Expression[] {argToUnmodifiable}; + switchContents.add(new Assignment(new SingleNameReference(data.getPluralName(), 0), unmodInvoke, 0)); + } + } + + SwitchStatement switchStat = new SwitchStatement(); + switchStat.statements = switchContents.toArray(new Statement[switchContents.size()]); + switchStat.expression = getSize(builderType, data.getPluralName(), true); + + TypeReference localShadowerType = new QualifiedTypeReference(Eclipse.fromQualifiedName(data.getTargetFqn()), NULL_POSS); + localShadowerType = addTypeArgs(1, false, builderType, localShadowerType, data.getTypeArgs()); + LocalDeclaration varDefStat = new LocalDeclaration(data.getPluralName(), 0, 0); + varDefStat.type = localShadowerType; + statements.add(varDefStat); + statements.add(switchStat); + } +} diff --git a/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilMapSingularizer.java b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilMapSingularizer.java new file mode 100644 index 00000000..9aac32a3 --- /dev/null +++ b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilMapSingularizer.java @@ -0,0 +1,313 @@ +/* + * 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 + * 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.singulars; + +import static lombok.eclipse.Eclipse.*; +import static lombok.eclipse.handlers.EclipseHandlerUtil.*; + +import java.util.ArrayList; +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; +import org.eclipse.jdt.internal.compiler.ast.EqualExpression; +import org.eclipse.jdt.internal.compiler.ast.Expression; +import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; +import org.eclipse.jdt.internal.compiler.ast.FieldReference; +import org.eclipse.jdt.internal.compiler.ast.ForeachStatement; +import org.eclipse.jdt.internal.compiler.ast.IfStatement; +import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration; +import org.eclipse.jdt.internal.compiler.ast.MessageSend; +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; +import lombok.core.handlers.HandlerUtil; +import lombok.eclipse.EclipseNode; +import lombok.eclipse.handlers.EclipseSingularsRecipes.EclipseSingularizer; +import lombok.eclipse.handlers.EclipseSingularsRecipes.SingularData; + +@ProviderFor(EclipseSingularizer.class) +public class EclipseJavaUtilMapSingularizer extends EclipseJavaUtilSingularizer { + @Override public LombokImmutableList<String> getSupportedTypes() { + return LombokImmutableList.of("java.util.Map", "java.util.SortedMap", "java.util.NavigableMap"); + } + + @Override public List<char[]> listFieldsToBeGenerated(SingularData data, EclipseNode builderType) { + if (useGuavaInstead(builderType)) { + return guavaMapSingularizer.listFieldsToBeGenerated(data, builderType); + } + + char[] p = data.getPluralName(); + int len = p.length; + char[] k = new char[len + 4]; + char[] v = new char[len + 6]; + System.arraycopy(p, 0, k, 0, len); + System.arraycopy(p, 0, v, 0, len); + k[len] = '$'; + k[len + 1] = 'k'; + k[len + 2] = 'e'; + k[len + 3] = 'y'; + v[len] = '$'; + v[len + 1] = 'v'; + v[len + 2] = 'a'; + v[len + 3] = 'l'; + v[len + 4] = 'u'; + v[len + 5] = 'e'; + return Arrays.asList(k, v); + } + + @Override public List<char[]> listMethodsToBeGenerated(SingularData data, EclipseNode builderType) { + if (useGuavaInstead(builderType)) { + return guavaMapSingularizer.listFieldsToBeGenerated(data, builderType); + } else { + return super.listMethodsToBeGenerated(data, builderType); + } + } + + @Override public List<EclipseNode> generateFields(SingularData data, EclipseNode builderType) { + if (useGuavaInstead(builderType)) { + return guavaMapSingularizer.generateFields(data, builderType); + } + + char[] keyName = (new String(data.getPluralName()) + "$key").toCharArray(); + char[] valueName = (new String(data.getPluralName()) + "$value").toCharArray(); + FieldDeclaration buildKeyField; { + TypeReference type = new QualifiedTypeReference(JAVA_UTIL_ARRAYLIST, NULL_POSS); + type = addTypeArgs(1, false, builderType, type, data.getTypeArgs()); + buildKeyField = new FieldDeclaration(keyName, 0, -1); + buildKeyField.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + buildKeyField.modifiers = ClassFileConstants.AccPrivate; + buildKeyField.declarationSourceEnd = -1; + buildKeyField.type = type; + } + FieldDeclaration buildValueField; { + TypeReference type = new QualifiedTypeReference(JAVA_UTIL_ARRAYLIST, NULL_POSS); + List<TypeReference> tArgs = data.getTypeArgs(); + if (tArgs != null && tArgs.size() > 1) tArgs = Collections.singletonList(tArgs.get(1)); + else tArgs = Collections.emptyList(); + type = addTypeArgs(1, false, builderType, type, tArgs); + buildValueField = new FieldDeclaration(valueName, 0, -1); + buildValueField.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + buildValueField.modifiers = ClassFileConstants.AccPrivate; + buildValueField.declarationSourceEnd = -1; + buildValueField.type = type; + } + data.setGeneratedByRecursive(buildKeyField); + data.setGeneratedByRecursive(buildValueField); + EclipseNode keyFieldNode = injectFieldAndMarkGenerated(builderType, buildKeyField); + EclipseNode valueFieldNode = injectFieldAndMarkGenerated(builderType, buildValueField); + return Arrays.asList(keyFieldNode, valueFieldNode); + } + + @Override public void generateMethods(SingularData data, boolean deprecate, EclipseNode builderType, boolean fluent, boolean chain) { + if (useGuavaInstead(builderType)) { + guavaMapSingularizer.generateMethods(data, deprecate, builderType, fluent, chain); + 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(deprecate, 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(deprecate, 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(deprecate, returnType, returnStatement, data, 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; + + String pN = new String(data.getPluralName()); + char[] keyFieldName = (pN + "$key").toCharArray(); + char[] valueFieldName = (pN + "$value").toCharArray(); + + FieldReference thisDotField = new FieldReference(keyFieldName, 0L); + thisDotField.receiver = new ThisReference(0, 0); + FieldReference thisDotField2 = new FieldReference(keyFieldName, 0L); + thisDotField2.receiver = new ThisReference(0, 0); + FieldReference thisDotField3 = new FieldReference(valueFieldName, 0L); + thisDotField3.receiver = new ThisReference(0, 0); + md.selector = HandlerUtil.buildAccessorName("clear", new String(data.getPluralName())).toCharArray(); + MessageSend clearMsg1 = new MessageSend(); + clearMsg1.receiver = thisDotField2; + clearMsg1.selector = "clear".toCharArray(); + MessageSend clearMsg2 = new MessageSend(); + clearMsg2.receiver = thisDotField3; + clearMsg2.selector = "clear".toCharArray(); + Block clearMsgs = new Block(2); + clearMsgs.statements = new Statement[] {clearMsg1, clearMsg2}; + 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(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; + + List<Statement> statements = new ArrayList<Statement>(); + statements.add(createConstructBuilderVarIfNeeded(data, builderType, true)); + + String sN = new String(data.getSingularName()); + String pN = new String(data.getPluralName()); + char[] keyParamName = (sN + "Key").toCharArray(); + char[] valueParamName = (sN + "Value").toCharArray(); + char[] keyFieldName = (pN + "$key").toCharArray(); + char[] valueFieldName = (pN + "$value").toCharArray(); + + /* this.pluralname$key.add(singularnameKey); */ { + FieldReference thisDotKeyField = new FieldReference(keyFieldName, 0L); + thisDotKeyField.receiver = new ThisReference(0, 0); + MessageSend thisDotKeyFieldDotAdd = new MessageSend(); + thisDotKeyFieldDotAdd.arguments = new Expression[] {new SingleNameReference(keyParamName, 0L)}; + thisDotKeyFieldDotAdd.receiver = thisDotKeyField; + thisDotKeyFieldDotAdd.selector = "add".toCharArray(); + statements.add(thisDotKeyFieldDotAdd); + } + + /* this.pluralname$value.add(singularnameValue); */ { + FieldReference thisDotValueField = new FieldReference(valueFieldName, 0L); + thisDotValueField.receiver = new ThisReference(0, 0); + MessageSend thisDotValueFieldDotAdd = new MessageSend(); + thisDotValueFieldDotAdd.arguments = new Expression[] {new SingleNameReference(valueParamName, 0L)}; + thisDotValueFieldDotAdd.receiver = thisDotValueField; + thisDotValueFieldDotAdd.selector = "add".toCharArray(); + statements.add(thisDotValueFieldDotAdd); + } + if (returnStatement != null) statements.add(returnStatement); + + md.statements = statements.toArray(new Statement[statements.size()]); + TypeReference keyParamType = cloneParamType(0, data.getTypeArgs(), builderType); + Argument keyParam = new Argument(keyParamName, 0, keyParamType, 0); + TypeReference valueParamType = cloneParamType(1, data.getTypeArgs(), builderType); + Argument valueParam = new Argument(valueParamName, 0, valueParamType, 0); + 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(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; + + String pN = new String(data.getPluralName()); + char[] keyFieldName = (pN + "$key").toCharArray(); + char[] valueFieldName = (pN + "$value").toCharArray(); + + List<Statement> statements = new ArrayList<Statement>(); + statements.add(createConstructBuilderVarIfNeeded(data, builderType, true)); + + char[] entryName = "$lombokEntry".toCharArray(); + + TypeReference forEachType = new QualifiedTypeReference(JAVA_UTIL_MAP_ENTRY, NULL_POSS); + forEachType = addTypeArgs(2, true, builderType, forEachType, data.getTypeArgs()); + + MessageSend keyArg = new MessageSend(); + keyArg.receiver = new SingleNameReference(entryName, 0L); + keyArg.selector = "getKey".toCharArray(); + MessageSend addKey = new MessageSend(); + FieldReference thisDotKeyField = new FieldReference(keyFieldName, 0L); + thisDotKeyField.receiver = new ThisReference(0, 0); + addKey.receiver = thisDotKeyField; + addKey.selector = new char[] {'a', 'd', 'd'}; + addKey.arguments = new Expression[] {keyArg}; + + MessageSend valueArg = new MessageSend(); + valueArg.receiver = new SingleNameReference(entryName, 0L); + valueArg.selector = "getValue".toCharArray(); + MessageSend addValue = new MessageSend(); + FieldReference thisDotValueField = new FieldReference(valueFieldName, 0L); + thisDotValueField.receiver = new ThisReference(0, 0); + addValue.receiver = thisDotValueField; + addValue.selector = new char[] {'a', 'd', 'd'}; + addValue.arguments = new Expression[] {valueArg}; + + LocalDeclaration elementVariable = new LocalDeclaration(entryName, 0, 0); + elementVariable.type = forEachType; + ForeachStatement forEach = new ForeachStatement(elementVariable, 0); + MessageSend invokeEntrySet = new MessageSend(); + invokeEntrySet.selector = new char[] { 'e', 'n', 't', 'r', 'y', 'S', 'e', 't'}; + invokeEntrySet.receiver = new SingleNameReference(data.getPluralName(), 0L); + forEach.collection = invokeEntrySet; + Block forEachContent = new Block(0); + forEachContent.statements = new Statement[] {addKey, addValue}; + forEach.action = forEachContent; + statements.add(forEach); + if (returnStatement != null) statements.add(returnStatement); + + md.statements = statements.toArray(new Statement[statements.size()]); + + TypeReference paramType = new QualifiedTypeReference(JAVA_UTIL_MAP, NULL_POSS); + paramType = addTypeArgs(2, true, builderType, paramType, data.getTypeArgs()); + Argument param = new Argument(data.getPluralName(), 0, paramType, 0); + 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) { + if (useGuavaInstead(builderType)) { + guavaMapSingularizer.appendBuildCode(data, builderType, statements, targetVariableName); + return; + } + + if (data.getTargetFqn().equals("java.util.Map")) { + statements.addAll(createJavaUtilSetMapInitialCapacitySwitchStatements(data, builderType, true, "emptyMap", "singletonMap", "LinkedHashMap")); + } else { + statements.addAll(createJavaUtilSimpleCreationAndFillStatements(data, builderType, true, true, false, true, "TreeMap")); + } + } +} diff --git a/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilSetSingularizer.java b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilSetSingularizer.java new file mode 100644 index 00000000..2d16eae0 --- /dev/null +++ b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilSetSingularizer.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2015 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.singulars; + +import java.util.List; + +import lombok.core.LombokImmutableList; +import lombok.eclipse.EclipseNode; +import lombok.eclipse.handlers.EclipseSingularsRecipes.EclipseSingularizer; +import lombok.eclipse.handlers.EclipseSingularsRecipes.SingularData; + +import org.eclipse.jdt.internal.compiler.ast.Statement; +import org.mangosdk.spi.ProviderFor; + +@ProviderFor(EclipseSingularizer.class) +public class EclipseJavaUtilSetSingularizer extends EclipseJavaUtilListSetSingularizer { + @Override public LombokImmutableList<String> getSupportedTypes() { + 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) { + if (useGuavaInstead(builderType)) { + guavaListSetSingularizer.appendBuildCode(data, builderType, statements, targetVariableName); + return; + } + + if (data.getTargetFqn().equals("java.util.Set")) { + statements.addAll(createJavaUtilSetMapInitialCapacitySwitchStatements(data, builderType, false, "emptySet", "singleton", "LinkedHashSet")); + } else { + statements.addAll(createJavaUtilSimpleCreationAndFillStatements(data, builderType, false, true, false, true, "TreeSet")); + } + } +} diff --git a/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilSingularizer.java b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilSingularizer.java new file mode 100644 index 00000000..6661f4af --- /dev/null +++ b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilSingularizer.java @@ -0,0 +1,300 @@ +/* + * Copyright (C) 2015 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.singulars; + +import static lombok.eclipse.Eclipse.*; +import static lombok.eclipse.handlers.EclipseHandlerUtil.*; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.eclipse.jdt.internal.compiler.ast.AllocationExpression; +import org.eclipse.jdt.internal.compiler.ast.Assignment; +import org.eclipse.jdt.internal.compiler.ast.BinaryExpression; +import org.eclipse.jdt.internal.compiler.ast.Block; +import org.eclipse.jdt.internal.compiler.ast.BreakStatement; +import org.eclipse.jdt.internal.compiler.ast.CaseStatement; +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.FieldReference; +import org.eclipse.jdt.internal.compiler.ast.ForStatement; +import org.eclipse.jdt.internal.compiler.ast.IfStatement; +import org.eclipse.jdt.internal.compiler.ast.IntLiteral; +import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration; +import org.eclipse.jdt.internal.compiler.ast.MessageSend; +import org.eclipse.jdt.internal.compiler.ast.NullLiteral; +import org.eclipse.jdt.internal.compiler.ast.OperatorIds; +import org.eclipse.jdt.internal.compiler.ast.PostfixExpression; +import org.eclipse.jdt.internal.compiler.ast.QualifiedNameReference; +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.eclipse.jdt.internal.compiler.lookup.TypeConstants; +import org.eclipse.jdt.internal.compiler.lookup.TypeIds; + +import lombok.ConfigurationKeys; +import lombok.eclipse.EclipseNode; +import lombok.eclipse.handlers.EclipseSingularsRecipes.EclipseSingularizer; +import lombok.eclipse.handlers.EclipseSingularsRecipes.SingularData; + +abstract class EclipseJavaUtilSingularizer extends EclipseSingularizer { + protected static final char[][] JAVA_UTIL_ARRAYLIST = { + {'j', 'a', 'v', 'a'}, {'u', 't', 'i', 'l'}, {'A', 'r', 'r', 'a', 'y', 'L', 'i', 's', 't'} + }; + + protected static final char[][] JAVA_UTIL_LIST = { + {'j', 'a', 'v', 'a'}, {'u', 't', 'i', 'l'}, {'L', 'i', 's', 't'} + }; + + protected static final char[][] JAVA_UTIL_MAP = { + {'j', 'a', 'v', 'a'}, {'u', 't', 'i', 'l'}, {'M', 'a', 'p'} + }; + + protected static final char[][] JAVA_UTIL_MAP_ENTRY = { + {'j', 'a', 'v', 'a'}, {'u', 't', 'i', 'l'}, {'M', 'a', 'p'}, {'E', 'n', 't', 'r', 'y'} + }; + + protected static final char[][] JAVA_UTIL_COLLECTIONS = { + {'j', 'a', 'v', 'a'}, {'u', 't', 'i', 'l'}, {'C', 'o', 'l', 'l', 'e', 'c', 't', 'i', 'o', 'n', 's'} + }; + + protected final EclipseSingularizer guavaListSetSingularizer = new EclipseGuavaSetListSingularizer(); + protected final EclipseSingularizer guavaMapSingularizer = new EclipseGuavaMapSingularizer(); + + protected boolean useGuavaInstead(EclipseNode node) { + 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) { + List<Statement> switchContents = new ArrayList<Statement>(); + char[] keyName = mapMode ? (new String(data.getPluralName()) + "$key").toCharArray() : data.getPluralName(); + + if (emptyCollectionMethod != null) { // case 0: (empty); break; + switchContents.add(new CaseStatement(makeIntLiteral(new char[] {'0'}, null), 0, 0)); + + /* pluralName = java.util.Collections.emptyCollectionMethod(); */ { + MessageSend invoke = new MessageSend(); + invoke.receiver = new QualifiedNameReference(JAVA_UTIL_COLLECTIONS, NULL_POSS, 0, 0); + invoke.selector = emptyCollectionMethod.toCharArray(); + switchContents.add(new Assignment(new SingleNameReference(data.getPluralName(), 0), invoke, 0)); + } + + switchContents.add(new BreakStatement(null, 0, 0)); + } + + if (singletonCollectionMethod != null) { // case 1: (singleton); break; + switchContents.add(new CaseStatement(makeIntLiteral(new char[] {'1'}, null), 0, 0)); + /* !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); + MessageSend thisDotKeyGet0 = new MessageSend(); + thisDotKeyGet0.receiver = thisDotKey; + thisDotKeyGet0.selector = new char[] {'g', 'e', 't'}; + thisDotKeyGet0.arguments = new Expression[] {makeIntLiteral(new char[] {'0'}, null)}; + + Expression[] args; + if (mapMode) { + char[] valueName = (new String(data.getPluralName()) + "$value").toCharArray(); + FieldReference thisDotValue = new FieldReference(valueName, 0L); + thisDotValue.receiver = new ThisReference(0, 0); + MessageSend thisDotValueGet0 = new MessageSend(); + thisDotValueGet0.receiver = thisDotValue; + thisDotValueGet0.selector = new char[] {'g', 'e', 't'}; + thisDotValueGet0.arguments = new Expression[] {makeIntLiteral(new char[] {'0'}, null)}; + args = new Expression[] {thisDotKeyGet0, thisDotValueGet0}; + } else { + args = new Expression[] {thisDotKeyGet0}; + } + + MessageSend invoke = new MessageSend(); + invoke.receiver = new QualifiedNameReference(JAVA_UTIL_COLLECTIONS, NULL_POSS, 0, 0); + invoke.selector = singletonCollectionMethod.toCharArray(); + invoke.arguments = args; + switchContents.add(new Assignment(new SingleNameReference(data.getPluralName(), 0), invoke, 0)); + } + switchContents.add(new BreakStatement(null, 0, 0)); + } + + { // default: + switchContents.add(new CaseStatement(null, 0, 0)); + switchContents.addAll(createJavaUtilSimpleCreationAndFillStatements(data, builderType, mapMode, false, true, emptyCollectionMethod == null, targetType)); + } + + SwitchStatement switchStat = new SwitchStatement(); + switchStat.statements = switchContents.toArray(new Statement[switchContents.size()]); + switchStat.expression = getSize(builderType, keyName, true); + + TypeReference localShadowerType = new QualifiedTypeReference(fromQualifiedName(data.getTargetFqn()), NULL_POSS); + localShadowerType = addTypeArgs(mapMode ? 2 : 1, false, builderType, localShadowerType, data.getTypeArgs()); + LocalDeclaration varDefStat = new LocalDeclaration(data.getPluralName(), 0, 0); + varDefStat.type = localShadowerType; + return Arrays.asList(varDefStat, switchStat); + } + + protected List<Statement> createJavaUtilSimpleCreationAndFillStatements(SingularData data, EclipseNode builderType, boolean mapMode, boolean defineVar, boolean addInitialCapacityArg, boolean nullGuard, String targetType) { + char[] varName = mapMode ? (new String(data.getPluralName()) + "$key").toCharArray() : data.getPluralName(); + + Statement createStat; { + // pluralName = new java.util.TargetType(initialCap); + Expression[] constructorArgs = null; + 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); + 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 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); + constructorArgs = new Expression[] {cond}; + } + + TypeReference targetTypeRef = new QualifiedTypeReference(new char[][] {TypeConstants.JAVA, TypeConstants.UTIL, targetType.toCharArray()}, NULL_POSS); + targetTypeRef = addTypeArgs(mapMode ? 2 : 1, false, builderType, targetTypeRef, data.getTypeArgs()); + AllocationExpression constructorCall = new AllocationExpression(); + constructorCall.type = targetTypeRef; + constructorCall.arguments = constructorArgs; + + if (defineVar) { + TypeReference localShadowerType = new QualifiedTypeReference(fromQualifiedName(data.getTargetFqn()), NULL_POSS); + localShadowerType = addTypeArgs(mapMode ? 2 : 1, false, builderType, localShadowerType, data.getTypeArgs()); + LocalDeclaration localShadowerDecl = new LocalDeclaration(data.getPluralName(), 0, 0); + localShadowerDecl.type = localShadowerType; + localShadowerDecl.initialization = constructorCall; + createStat = localShadowerDecl; + } else { + createStat = new Assignment(new SingleNameReference(data.getPluralName(), 0L), constructorCall, 0); + } + } + + Statement fillStat; { + if (mapMode) { + // for (int $i = 0; $i < this.pluralname$key.size(); i++) pluralname.put(this.pluralname$key.get($i), this.pluralname$value.get($i)); + char[] iVar = new char[] {'$', 'i'}; + MessageSend pluralnameDotPut = new MessageSend(); + 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); + FieldReference thisDotValue = new FieldReference((new String(data.getPluralName()) + "$value").toCharArray(), 0L); + thisDotValue.receiver = new ThisReference(0, 0); + MessageSend keyArg = new MessageSend(); + keyArg.receiver = thisDotKey; + keyArg.arguments = new Expression[] {new SingleNameReference(iVar, 0L)}; + keyArg.selector = new char[] {'g', 'e', 't'}; + MessageSend valueArg = new MessageSend(); + valueArg.receiver = thisDotValue; + valueArg.arguments = new Expression[] {new SingleNameReference(iVar, 0L)}; + valueArg.selector = new char[] {'g', 'e', 't'}; + pluralnameDotPut.arguments = new Expression[] {keyArg, valueArg}; + + 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 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 { + // pluralname.addAll(this.pluralname); + MessageSend pluralnameDotAddAll = new MessageSend(); + 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); + pluralnameDotAddAll.arguments = new Expression[] {thisDotPluralname}; + fillStat = pluralnameDotAddAll; + } + + if (nullGuard) { + FieldReference thisDotField = new FieldReference(varName, 0L); + thisDotField.receiver = new ThisReference(0, 0); + Expression cond = new EqualExpression(thisDotField, new NullLiteral(0, 0), OperatorIds.NOT_EQUAL); + fillStat = new IfStatement(cond, fillStat, 0, 0); + } + } + + Statement unmodifiableStat; { + // pluralname = Collections.unmodifiableInterfaceType(pluralname); + Expression arg = new SingleNameReference(data.getPluralName(), 0L); + MessageSend invoke = new MessageSend(); + invoke.arguments = new Expression[] {arg}; + invoke.selector = ("unmodifiable" + data.getTargetSimpleType()).toCharArray(); + invoke.receiver = new QualifiedNameReference(JAVA_UTIL_COLLECTIONS, NULL_POSS, 0, 0); + unmodifiableStat = new Assignment(new SingleNameReference(data.getPluralName(), 0L), invoke, 0); + } + + return Arrays.asList(createStat, fillStat, unmodifiableStat); + } + + protected Statement createConstructBuilderVarIfNeeded(SingularData data, EclipseNode builderType, boolean mapMode) { + char[] v1Name, v2Name; + if (mapMode) { + String n = new String(data.getPluralName()); + v1Name = (n + "$key").toCharArray(); + v2Name = (n + "$value").toCharArray(); + } else { + v1Name = data.getPluralName(); + v2Name = null; + } + + FieldReference thisDotField = new FieldReference(v1Name, 0L); + thisDotField.receiver = new ThisReference(0, 0); + Expression cond = new EqualExpression(thisDotField, new NullLiteral(0, 0), OperatorIds.EQUAL_EQUAL); + + thisDotField = new FieldReference(v1Name, 0L); + thisDotField.receiver = new ThisReference(0, 0); + TypeReference v1Type = new QualifiedTypeReference(JAVA_UTIL_ARRAYLIST, NULL_POSS); + v1Type = addTypeArgs(1, false, builderType, v1Type, data.getTypeArgs()); + AllocationExpression constructArrayList = new AllocationExpression(); + constructArrayList.type = v1Type; + Assignment initV1 = new Assignment(thisDotField, constructArrayList, 0); + Statement thenPart; + if (mapMode) { + thisDotField = new FieldReference(v2Name, 0L); + thisDotField.receiver = new ThisReference(0, 0); + TypeReference v2Type = new QualifiedTypeReference(JAVA_UTIL_ARRAYLIST, NULL_POSS); + List<TypeReference> tArgs = data.getTypeArgs(); + if (tArgs != null && tArgs.size() > 1) tArgs = Collections.singletonList(tArgs.get(1)); + else tArgs = Collections.emptyList(); + v2Type = addTypeArgs(1, false, builderType, v2Type, tArgs); + constructArrayList = new AllocationExpression(); + constructArrayList.type = v2Type; + Assignment initV2 = new Assignment(thisDotField, constructArrayList, 0); + Block b = new Block(0); + b.statements = new Statement[] {initV1, initV2}; + thenPart = b; + } else { + thenPart = initV1; + } + + return new IfStatement(cond, thenPart, 0, 0); + } +} diff --git a/src/core/lombok/experimental/Accessors.java b/src/core/lombok/experimental/Accessors.java index c2a0ca16..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="http://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/Delegate.java b/src/core/lombok/experimental/Delegate.java index 806d5871..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="http://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 7de8a136..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="http://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 1c621f3c..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="http://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/Value.java b/src/core/lombok/experimental/Helper.java index b7700bb5..34745cbe 100644 --- a/src/core/lombok/experimental/Value.java +++ b/src/core/lombok/experimental/Helper.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2013 The Project Lombok Authors. + * Copyright (C) 2015 The Project Lombok Authors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -27,34 +27,9 @@ 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. - * <p> - * Equivalent to {@code @Getter @FieldDefaults(makeFinal=true, level=AccessLevel.PRIVATE) @RequiredArgsConstructor @ToString @EqualsAndHashCode}. - * <p> - * Complete documentation is found at <a href="http://projectlombok.org/features/experimental/Value.html">the project lombok features page for @Value</a>. - * - * @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. + * Use on a method local class to indicate that all methods inside should be exposed to the rest of + * the method as if they were helper methods. */ @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 ""; -} +public @interface Helper {} 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/UtilityClass.java b/src/core/lombok/experimental/UtilityClass.java new file mode 100644 index 00000000..80f3eee9 --- /dev/null +++ b/src/core/lombok/experimental/UtilityClass.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2015 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; + +/** + * An annotation to create utility classes. + * + * If a class is annotated with {@code @UtilityClass}, the following things happen to it:<ul> + * <li>It is marked final.</li> + * <li>If any constructors are declared in it, an error is generated. Otherwise, a private no-args constructor is generated; it throws a {@code UnsupportedOperationException}.</li> + * <li>All methods, inner classes, and fields in the class are marked static.</li> + * </ul> + */ +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.SOURCE) +public @interface UtilityClass { +} diff --git a/src/core/lombok/experimental/Wither.java b/src/core/lombok/experimental/Wither.java index fe113bb0..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="http://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 776f2c27..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="http://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 45345098..04d5ef93 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="http://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 /> + * 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 */ @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/extern/java/Log.java b/src/core/lombok/extern/java/Log.java index bac2742e..553b7c4a 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="http://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,20 +46,19 @@ 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 */ @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 new file mode 100644 index 00000000..ea520aea --- /dev/null +++ b/src/core/lombok/extern/jbosslog/JBossLog.java @@ -0,0 +1,64 @@ +/* + * 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 + * 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.extern.jbosslog; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +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">the project lombok features page for lombok log annotations</a>. + * <p> + * Example: + * <pre> + * @JBossLog + * public class LogExample { + * } + * </pre> + * + * will generate: + * + * <pre> + * public class LogExample { + * private static final org.jboss.logging.Logger log = org.jboss.logging.Logger.getLogger(LogExample.class); + * } + * </pre> + * + * 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.XSlf4j @XSlf4j + * @see lombok.extern.jbosslog.JBossLog @JBossLog + * */ +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.TYPE) +public @interface JBossLog { + /** @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 9490acb8..ee719407 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="http://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 /> + * 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 */ @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 43125e6b..4a5b166c 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="http://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> @@ -42,25 +42,24 @@ import java.lang.annotation.Target; * * <pre> * public class LogExample { - * private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.Logger.getLogger(LogExample.class); + * private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(LogExample.class); * } * </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 */ @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 04df6498..24586d43 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="http://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.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 + */ @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 8a311c79..85a0fdd8 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="http://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,20 +46,19 @@ 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 */ @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 4e553063..12d919af 100644 --- a/src/core/lombok/javac/JavacAST.java +++ b/src/core/lombok/javac/JavacAST.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 @@ -22,52 +22,54 @@ package lombok.javac; import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.net.URI; 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.Symtab; import com.sun.tools.javac.code.Source; +import com.sun.tools.javac.code.Symtab; import com.sun.tools.javac.model.JavacElements; import com.sun.tools.javac.model.JavacTypes; import com.sun.tools.javac.tree.JCTree; -import com.sun.tools.javac.tree.JCTree.JCCatch; -import com.sun.tools.javac.tree.JCTree.JCTry; -import com.sun.tools.javac.tree.TreeMaker; import com.sun.tools.javac.tree.JCTree.JCAnnotation; import com.sun.tools.javac.tree.JCTree.JCBlock; +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; import com.sun.tools.javac.tree.JCTree.JCVariableDecl; +import com.sun.tools.javac.tree.TreeMaker; import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; import com.sun.tools.javac.util.Log; import com.sun.tools.javac.util.Name; -import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; /** * Wraps around javac's internal AST view to add useful features as well as the ability to visit parents from children, * 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; /** @@ -78,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), packageDeclaration(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); @@ -103,10 +105,6 @@ public class JavacAST extends AST<JavacAST, JavacNode, JCTree> { return cu.sourcefile == null ? null : cu.sourcefile.toString(); } - private static String packageDeclaration(JCCompilationUnit cu) { - return (cu.pid instanceof JCFieldAccess || cu.pid instanceof JCIdent) ? cu.pid.toString() : null; - } - public Context getContext() { return context; } @@ -128,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; } @@ -189,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. } @@ -208,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)); @@ -297,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)); } @@ -315,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>(); @@ -336,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); @@ -376,22 +402,21 @@ public class JavacAST extends AST<JavacAST, JavacNode, JCTree> { 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); } } @@ -429,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 2665ca7c..468d8c7b 100644 --- a/src/core/lombok/javac/JavacImportList.java +++ b/src/core/lombok/javac/JavacImportList.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2014 The Project Lombok Authors. + * Copyright (C) 2013-2015 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,22 +24,21 @@ package lombok.javac; import java.util.ArrayList; import java.util.Collection; +import lombok.core.ImportList; +import lombok.core.LombokInternalAliasing; + import com.sun.tools.javac.tree.JCTree; 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.JCImport; import com.sun.tools.javac.util.List; -import lombok.core.ImportList; -import lombok.core.LombokInternalAliasing; - public class JavacImportList implements ImportList { - private final JCExpression pkg; + private final String pkgStr; private final List<JCTree> defs; public JavacImportList(JCCompilationUnit cud) { - this.pkg = cud.pid; + this.pkgStr = PackageName.getPackageName(cud); this.defs = cud.defs; } @@ -58,7 +57,6 @@ public class JavacImportList implements ImportList { } @Override public boolean hasStarImport(String packageName) { - String pkgStr = pkg == null ? null : pkg.toString(); if (pkgStr != null && pkgStr.equals(packageName)) return true; if ("java.lang".equals(packageName)) return true; @@ -86,7 +84,7 @@ public class JavacImportList implements ImportList { @Override public Collection<String> applyNameToStarImports(String startsWith, String name) { ArrayList<String> out = new ArrayList<String>(); - if (pkg != null && topLevelName(pkg).equals(startsWith)) out.add(pkg.toString() + "." + name); + if (pkgStr != null && topLevelName(pkgStr).equals(startsWith)) out.add(pkgStr + "." + name); for (JCTree def : defs) { if (!(def instanceof JCImport)) continue; @@ -110,8 +108,14 @@ public class JavacImportList implements ImportList { return tree.toString(); } + private String topLevelName(String packageName) { + int idx = packageName.indexOf("."); + if (idx == -1) return packageName; + return packageName.substring(0, idx); + } + @Override public String applyUnqualifiedNameToPackage(String unqualified) { - if (pkg == null) return unqualified; - return pkg.toString() + "." + unqualified; + if (pkgStr == null) return unqualified; + return pkgStr + "." + unqualified; } } diff --git a/src/core/lombok/javac/JavacNode.java b/src/core/lombok/javac/JavacNode.java index 6eef36eb..fa24c2f9 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 @@ -23,6 +23,7 @@ package lombok.javac; import java.util.List; +import javax.lang.model.element.Element; import javax.tools.Diagnostic; import lombok.core.AST.Kind; @@ -51,6 +52,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); @@ -66,40 +74,40 @@ public class JavacNode extends lombok.core.LombokNode<JavacAST, JavacNode, JCTre public void traverse(JavacASTVisitor visitor) { switch (this.getKind()) { case COMPILATION_UNIT: - visitor.visitCompilationUnit(this, (JCCompilationUnit)get()); + visitor.visitCompilationUnit(this, (JCCompilationUnit) get()); ast.traverseChildren(visitor, this); - visitor.endVisitCompilationUnit(this, (JCCompilationUnit)get()); + visitor.endVisitCompilationUnit(this, (JCCompilationUnit) get()); break; case TYPE: - visitor.visitType(this, (JCClassDecl)get()); + visitor.visitType(this, (JCClassDecl) get()); ast.traverseChildren(visitor, this); - visitor.endVisitType(this, (JCClassDecl)get()); + visitor.endVisitType(this, (JCClassDecl) get()); break; case FIELD: - visitor.visitField(this, (JCVariableDecl)get()); + visitor.visitField(this, (JCVariableDecl) get()); ast.traverseChildren(visitor, this); - visitor.endVisitField(this, (JCVariableDecl)get()); + visitor.endVisitField(this, (JCVariableDecl) get()); break; case METHOD: - visitor.visitMethod(this, (JCMethodDecl)get()); + visitor.visitMethod(this, (JCMethodDecl) get()); ast.traverseChildren(visitor, this); - visitor.endVisitMethod(this, (JCMethodDecl)get()); + visitor.endVisitMethod(this, (JCMethodDecl) get()); break; case INITIALIZER: - visitor.visitInitializer(this, (JCBlock)get()); + visitor.visitInitializer(this, (JCBlock) get()); ast.traverseChildren(visitor, this); - visitor.endVisitInitializer(this, (JCBlock)get()); + visitor.endVisitInitializer(this, (JCBlock) get()); break; case ARGUMENT: JCMethodDecl parentMethod = (JCMethodDecl) up().get(); - visitor.visitMethodArgument(this, (JCVariableDecl)get(), parentMethod); + visitor.visitMethodArgument(this, (JCVariableDecl) get(), parentMethod); ast.traverseChildren(visitor, this); - visitor.endVisitMethodArgument(this, (JCVariableDecl)get(), parentMethod); + visitor.endVisitMethodArgument(this, (JCVariableDecl) get(), parentMethod); break; case LOCAL: - visitor.visitLocal(this, (JCVariableDecl)get()); + visitor.visitLocal(this, (JCVariableDecl) get()); ast.traverseChildren(visitor, this); - visitor.endVisitLocal(this, (JCVariableDecl)get()); + visitor.endVisitLocal(this, (JCVariableDecl) get()); break; case STATEMENT: visitor.visitStatement(this, get()); @@ -109,21 +117,21 @@ public class JavacNode extends lombok.core.LombokNode<JavacAST, JavacNode, JCTre case ANNOTATION: switch (up().getKind()) { case TYPE: - visitor.visitAnnotationOnType((JCClassDecl)up().get(), this, (JCAnnotation)get()); + visitor.visitAnnotationOnType((JCClassDecl) up().get(), this, (JCAnnotation) get()); break; case FIELD: - visitor.visitAnnotationOnField((JCVariableDecl)up().get(), this, (JCAnnotation)get()); + visitor.visitAnnotationOnField((JCVariableDecl) up().get(), this, (JCAnnotation) get()); break; case METHOD: - visitor.visitAnnotationOnMethod((JCMethodDecl)up().get(), this, (JCAnnotation)get()); + visitor.visitAnnotationOnMethod((JCMethodDecl) up().get(), this, (JCAnnotation) get()); break; case ARGUMENT: - JCVariableDecl argument = (JCVariableDecl)up().get(); - JCMethodDecl method = (JCMethodDecl)up().up().get(); - visitor.visitAnnotationOnMethodArgument(argument, method, this, (JCAnnotation)get()); + JCVariableDecl argument = (JCVariableDecl) up().get(); + JCMethodDecl method = (JCMethodDecl) up().up().get(); + visitor.visitAnnotationOnMethodArgument(argument, method, this, (JCAnnotation) get()); break; case LOCAL: - visitor.visitAnnotationOnLocal((JCVariableDecl)up().get(), this, (JCAnnotation)get()); + visitor.visitAnnotationOnLocal((JCVariableDecl) up().get(), this, (JCAnnotation) get()); break; default: throw new AssertionError("Annotion not expected as child of a " + up().getKind()); @@ -138,9 +146,9 @@ public class JavacNode extends lombok.core.LombokNode<JavacAST, JavacNode, JCTre @Override public String getName() { final Name n; - if (node instanceof JCClassDecl) n = ((JCClassDecl)node).name; - else if (node instanceof JCMethodDecl) n = ((JCMethodDecl)node).name; - else if (node instanceof JCVariableDecl) n = ((JCVariableDecl)node).name; + if (node instanceof JCClassDecl) n = ((JCClassDecl) node).name; + else if (node instanceof JCMethodDecl) n = ((JCMethodDecl) node).name; + else if (node instanceof JCVariableDecl) n = ((JCVariableDecl) node).name; else n = null; return n == null ? null : n.toString(); diff --git a/src/core/lombok/javac/JavacResolution.java b/src/core/lombok/javac/JavacResolution.java index 23d7d482..8cc239e1 100644 --- a/src/core/lombok/javac/JavacResolution.java +++ b/src/core/lombok/javac/JavacResolution.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2014 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 @@ -24,14 +24,17 @@ package lombok.javac; import static lombok.javac.Javac.*; import static lombok.javac.JavacTreeMaker.TypeTag.typeTag; +import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; 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; import com.sun.tools.javac.code.BoundKind; import com.sun.tools.javac.code.Symbol.TypeSymbol; @@ -57,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; @@ -98,7 +102,7 @@ public class JavacResolution { @Override public void visitClassDef(JCClassDecl tree) { if (copyAt != null) return; - env = enter.getClassEnv(tree.sym); + if (tree.sym != null) env = enter.getClassEnv(tree.sym); } @Override public void visitMethodDef(JCMethodDecl tree) { @@ -138,16 +142,70 @@ public class JavacResolution { EnvFinder finder = new EnvFinder(node.getContext()); while (!stack.isEmpty()) stack.pop().accept(finder); - TreeMirrorMaker mirrorMaker = new TreeMirrorMaker(node.getTreeMaker()); + TreeMirrorMaker mirrorMaker = new TreeMirrorMaker(node.getTreeMaker(), node.getContext()); JCTree copy = mirrorMaker.copy(finder.copyAt()); - - attrib(copy, finder.get()); - 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(); } } + private static Field memberEnterDotEnv; + + private static Field getMemberEnterDotEnv() { + if (memberEnterDotEnv != null) return memberEnterDotEnv; + try { + Field f = MemberEnter.class.getDeclaredField("env"); + f.setAccessible(true); + memberEnterDotEnv = f; + } catch (NoSuchFieldException e) { + return null; + } + + return memberEnterDotEnv; + } + + @SuppressWarnings("unchecked") + private static Env<AttrContext> getEnvOfMemberEnter(MemberEnter memberEnter) { + Field f = getMemberEnterDotEnv(); + try { + return (Env<AttrContext>) f.get(memberEnter); + } catch (Exception e) { + return null; + } + } + + private static void setEnvOfMemberEnter(MemberEnter memberEnter, Env<AttrContext> env) { + Field f = getMemberEnterDotEnv(); + try { + f.set(memberEnter, env); + } catch (Exception e) { + return; + } + } + + private void memberEnterAndAttribute(JCTree copy, Env<AttrContext> env, Context context) { + MemberEnter memberEnter = MemberEnter.instance(context); + Env<AttrContext> oldEnv = getEnvOfMemberEnter(memberEnter); + setEnvOfMemberEnter(memberEnter, env); + try { + copy.accept(memberEnter); + } catch (Exception ignore) { + // intentionally ignored; usually even if this step fails, val will work (but not for val in method local inner classes and anonymous inner classes). + AssertionLogger.assertLog("member enter failed.", ignore); + } finally { + setEnvOfMemberEnter(memberEnter, oldEnv); + } + attrib(copy, env); + } + public void resolveClassMember(JavacNode node) { ArrayDeque<JCTree> stack = new ArrayDeque<JCTree>(); @@ -171,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"); } @@ -210,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); @@ -241,7 +305,7 @@ public class JavacResolution { Type type0 = type; while (type0 instanceof ArrayType) { dims++; - type0 = ((ArrayType)type0).elemtype; + type0 = ((ArrayType) type0).elemtype; } JCExpression result = typeToJCTree0(type0, ast, allowCompound, allowVoid); 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 7298e920..5a3a7def 100644 --- a/src/core/lombok/javac/apt/EmptyLombokFileObject.java +++ b/src/core/lombok/javac/apt/EmptyLombokFileObject.java @@ -19,7 +19,6 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ - package lombok.javac.apt; import java.io.ByteArrayInputStream; 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 new file mode 100644 index 00000000..9c0a2dfa --- /dev/null +++ b/src/core/lombok/javac/apt/LombokProcessor.java @@ -0,0 +1,402 @@ +/* + * 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.javac.apt; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Writer; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.net.URL; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.SortedSet; + +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.Messager; +import javax.annotation.processing.ProcessingEnvironment; +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.TypeElement; +import javax.tools.Diagnostic.Kind; +import javax.tools.JavaFileManager; +import javax.tools.JavaFileObject; + +import lombok.Lombok; +import lombok.core.DiagnosticsReceiver; +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; +import com.sun.tools.javac.util.Context; + +/** + * This Annotation Processor is the standard injection mechanism for lombok-enabling the javac compiler. + * + * To actually enable lombok in a javac compilation run, this class should be in the classpath when + * running javac; that's the only requirement. + */ +@SupportedAnnotationTypes("*") +public class LombokProcessor extends AbstractProcessor { + private JavacProcessingEnvironment processingEnv; + private JavacTransformer transformer; + private Trees trees; + private boolean lombokDisabled = false; + + /** {@inheritDoc} */ + @Override public void init(ProcessingEnvironment procEnv) { + super.init(procEnv); + if (System.getProperty("lombok.disable") != null) { + lombokDisabled = true; + return; + } + + this.processingEnv = (JavacProcessingEnvironment) procEnv; + + placePostCompileAndDontMakeForceRoundDummiesHook(); + trees = Trees.instance(procEnv); + transformer = new JavacTransformer(procEnv.getMessager(), trees); + SortedSet<Long> p = transformer.getPriorities(); + if (p.isEmpty()) { + this.priorityLevels = new long[] {0L}; + this.priorityLevelsRequiringResolutionReset = new HashSet<Long>(); + } else { + this.priorityLevels = new long[p.size()]; + int i = 0; + for (Long prio : p) this.priorityLevels[i++] = prio; + this.priorityLevelsRequiringResolutionReset = transformer.getPrioritiesRequiringResolutionReset(); + } + } + + 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.processingEnv); + 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(); + disablePartialReparseInNetBeansEditor(context); + try { + Method keyMethod = Context.class.getDeclaredMethod("key", Class.class); + keyMethod.setAccessible(true); + Object key = keyMethod.invoke(context, JavaFileManager.class); + Field htField = Context.class.getDeclaredField("ht"); + htField.setAccessible(true); + @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); + Field filerFileManagerField = JavacFiler.class.getDeclaredField("fileManager"); + filerFileManagerField.setAccessible(true); + filerFileManagerField.set(processingEnv.getFiler(), newFiler); + + replaceFileManagerJdk9(context, newFiler); + } + } 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); + } catch (NoSuchFieldException e) { + // only NetBeans has it + } catch (Throwable t) { + throw Lombok.sneakyThrow(t); + } + } + + private void disablePartialReparseInNetBeansEditor(Context context) { + try { + Class<?> cancelServiceClass = Class.forName("com.sun.tools.javac.util.CancelService"); + Method cancelServiceInstace = cancelServiceClass.getDeclaredMethod("instance", Context.class); + Object cancelService = cancelServiceInstace.invoke(null, context); + if (cancelService == null) return; + Field parserField = cancelService.getClass().getDeclaredField("parser"); + parserField.setAccessible(true); + Object parser = parserField.get(cancelService); + Field supportsReparseField = parser.getClass().getDeclaredField("supportsReparse"); + supportsReparseField.setAccessible(true); + supportsReparseField.set(parser, false); + } catch (ClassNotFoundException e) { + // only NetBeans has it + } catch (NoSuchFieldException e) { + // only NetBeans has it + } catch (Throwable t) { + throw Lombok.sneakyThrow(t); + } + } + + private static ClassLoader wrapClassLoader(final ClassLoader parent) { + return new ClassLoader() { + + public Class<?> loadClass(String name) throws ClassNotFoundException { + return parent.loadClass(name); + } + + public String toString() { + return parent.toString(); + } + + public URL getResource(String name) { + return parent.getResource(name); + } + + public Enumeration<URL> getResources(String name) throws IOException { + return parent.getResources(name); + } + + public InputStream getResourceAsStream(String name) { + return parent.getResourceAsStream(name); + } + + public void setDefaultAssertionStatus(boolean enabled) { + parent.setDefaultAssertionStatus(enabled); + } + + public void setPackageAssertionStatus(String packageName, boolean enabled) { + parent.setPackageAssertionStatus(packageName, enabled); + } + + public void setClassAssertionStatus(String className, boolean enabled) { + parent.setClassAssertionStatus(className, enabled); + } + + public void clearAssertionStatus() { + parent.clearAssertionStatus(); + } + }; + } + + private void stopJavacProcessingEnvironmentFromClosingOurClassloader() { + try { + Field f = JavacProcessingEnvironment.class.getDeclaredField("processorClassLoader"); + f.setAccessible(true); + ClassLoader unwrapped = (ClassLoader) f.get(processingEnv); + if (unwrapped == null) return; + ClassLoader wrapped = wrapClassLoader(unwrapped); + f.set(processingEnv, 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) { + throw Lombok.sneakyThrow(t); + } + } + + private final IdentityHashMap<JCCompilationUnit, Long> roots = new IdentityHashMap<JCCompilationUnit, Long>(); + private long[] priorityLevels; + private Set<Long> priorityLevelsRequiringResolutionReset; + + /** {@inheritDoc} */ + @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { + if (lombokDisabled) return false; + if (roundEnv.processingOver()) return false; + + // We have: A sorted set of all priority levels: 'priorityLevels' + + // 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; + roots.put(unit, priorityLevels[0]); + } + + while (true) { + // Step 2: For all CUs (in the map, not the roundEnv!), run them across all handlers at their current prio level. + + for (long prio : priorityLevels) { + List<JCCompilationUnit> cusForThisRound = new ArrayList<JCCompilationUnit>(); + for (Map.Entry<JCCompilationUnit, Long> entry : roots.entrySet()) { + Long prioOfCu = entry.getValue(); + if (prioOfCu == null || prioOfCu != prio) continue; + cusForThisRound.add(entry.getKey()); + } + transformer.transform(prio, processingEnv.getContext(), cusForThisRound); + } + + // Step 3: Push up all CUs to the next level. Set level to null if there is no next level. + + Set<Long> newLevels = new HashSet<Long>(); + for (int i = priorityLevels.length - 1; i >= 0; i--) { + Long curLevel = priorityLevels[i]; + Long nextLevel = (i == priorityLevels.length - 1) ? null : priorityLevels[i + 1]; + List<JCCompilationUnit> cusToAdvance = new ArrayList<JCCompilationUnit>(); + for (Map.Entry<JCCompilationUnit, Long> entry : roots.entrySet()) { + if (curLevel.equals(entry.getValue())) { + cusToAdvance.add(entry.getKey()); + newLevels.add(nextLevel); + } + } + for (JCCompilationUnit unit : cusToAdvance) { + roots.put(unit, nextLevel); + } + } + newLevels.remove(null); + + // Step 4: If ALL values are null, quit. Else, either do another loop right now or force a resolution reset by forcing a new round in the annotation processor. + + if (newLevels.isEmpty()) return false; + newLevels.retainAll(priorityLevelsRequiringResolutionReset); + if (!newLevels.isEmpty()) { + // Force a new round to reset resolution. The next round will cause this method (process) to be called again. + forceNewRound(randomModuleName, (JavacFiler) processingEnv.getFiler()); + return false; + } + // None of the new levels need resolution, so just keep going. + } + } + + private int dummyCount = 0; + private void forceNewRound(String randomModuleName, JavacFiler filer) { + if (!filer.newFiles()) { + try { + 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) { + e.printStackTrace(); + processingEnv.getMessager().printMessage(Kind.WARNING, + "Can't force a new processing round. Lombok won't work."); + } + } + } + + 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; + + return (JCCompilationUnit) path.getCompilationUnit(); + } + + /** + * We just return the latest version of whatever JDK we run on. Stupid? Yeah, but it's either that or warnings on all versions but 1. + */ + @Override public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latest(); + } +} diff --git a/src/core/lombok/javac/apt/Processor.java b/src/core/lombok/javac/apt/Processor.java index ce4d75ff..7a187148 100644 --- a/src/core/lombok/javac/apt/Processor.java +++ b/src/core/lombok/javac/apt/Processor.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2012 The Project Lombok Authors. + * Copyright (C) 2015 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,295 +21,211 @@ */ package lombok.javac.apt; +import static javax.tools.StandardLocation.*; + +import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; -import java.io.Writer; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.StringWriter; import java.lang.reflect.Field; -import java.lang.reflect.Method; import java.net.URL; import java.util.ArrayList; +import java.util.Collections; import java.util.Enumeration; -import java.util.HashSet; -import java.util.IdentityHashMap; -import java.util.List; import java.util.Map; +import java.util.Properties; import java.util.Set; -import java.util.SortedSet; import javax.annotation.processing.AbstractProcessor; -import javax.annotation.processing.Messager; +import javax.annotation.processing.Filer; import javax.annotation.processing.ProcessingEnvironment; 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.TypeElement; -import javax.tools.Diagnostic.Kind; +import javax.tools.Diagnostic; import javax.tools.JavaFileManager; -import javax.tools.JavaFileObject; - -import lombok.Lombok; -import lombok.core.DiagnosticsReceiver; -import lombok.javac.JavacTransformer; -import com.sun.source.util.TreePath; -import com.sun.source.util.Trees; import com.sun.tools.javac.processing.JavacFiler; import com.sun.tools.javac.processing.JavacProcessingEnvironment; -import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; -import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.Options; /** - * This Annotation Processor is the standard injection mechanism for lombok-enabling the javac compiler. - * - * To actually enable lombok in a javac compilation run, this class should be in the classpath when - * running javac; that's the only requirement. + * This processor should not be used. It used to be THE processor. This class is only there to warn people that something went wrong, and for the + * lombok developers to see if what the reason for those failures is. */ +@Deprecated @SupportedAnnotationTypes("*") public class Processor extends AbstractProcessor { - - private JavacProcessingEnvironment processingEnv; - private JavacTransformer transformer; - private Trees trees; - private boolean lombokDisabled = false; /** {@inheritDoc} */ @Override public void init(ProcessingEnvironment procEnv) { super.init(procEnv); if (System.getProperty("lombok.disable") != null) { - lombokDisabled = true; return; } - - this.processingEnv = (JavacProcessingEnvironment) procEnv; - placePostCompileAndDontMakeForceRoundDummiesHook(); - transformer = new JavacTransformer(procEnv.getMessager()); - trees = Trees.instance(procEnv); - SortedSet<Long> p = transformer.getPriorities(); - if (p.isEmpty()) { - this.priorityLevels = new long[] {0L}; - this.priorityLevelsRequiringResolutionReset = new HashSet<Long>(); - } else { - this.priorityLevels = new long[p.size()]; - int i = 0; - for (Long prio : p) this.priorityLevels[i++] = prio; - this.priorityLevelsRequiringResolutionReset = transformer.getPrioritiesRequiringResolutionReset(); - } + procEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, "Wrong usage of 'lombok.javac.apt.Processor'. " + report(procEnv)); } - private void placePostCompileAndDontMakeForceRoundDummiesHook() { - stopJavacProcessingEnvironmentFromClosingOurClassloader(); - - forceMultipleRoundsInNetBeansEditor(); - Context context = processingEnv.getContext(); - disablePartialReparseInNetBeansEditor(context); + private String report(ProcessingEnvironment procEnv) { + String data = collectData(procEnv); try { - Method keyMethod = Context.class.getDeclaredMethod("key", Class.class); - keyMethod.setAccessible(true); - Object key = keyMethod.invoke(context, JavaFileManager.class); - Field htField = Context.class.getDeclaredField("ht"); - htField.setAccessible(true); - @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); - Field filerFileManagerField = JavacFiler.class.getDeclaredField("fileManager"); - filerFileManagerField.setAccessible(true); - filerFileManagerField.set(processingEnv.getFiler(), newFiler); - } + return writeFile(data); } catch (Exception e) { - throw Lombok.sneakyThrow(e); + return "Report:\n\n" + data; } } - private void forceMultipleRoundsInNetBeansEditor() { - try { - Field f = JavacProcessingEnvironment.class.getDeclaredField("isBackgroundCompilation"); - f.setAccessible(true); - f.set(processingEnv, true); - } catch (NoSuchFieldException e) { - // only NetBeans has it - } catch (Throwable t) { - throw Lombok.sneakyThrow(t); - } + private String writeFile(String data) throws IOException { + File file = File.createTempFile("lombok-processor-report-", ".txt"); + OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(file)); + writer.write(data); + writer.close(); + return "Report written to '" + file.getCanonicalPath() + "'\n"; } - - private void disablePartialReparseInNetBeansEditor(Context context) { - try { - Class<?> cancelServiceClass = Class.forName("com.sun.tools.javac.util.CancelService"); - Method cancelServiceInstace = cancelServiceClass.getDeclaredMethod("instance", Context.class); - Object cancelService = cancelServiceInstace.invoke(null, context); - if (cancelService == null) return; - Field parserField = cancelService.getClass().getDeclaredField("parser"); - parserField.setAccessible(true); - Object parser = parserField.get(cancelService); - Field supportsReparseField = parser.getClass().getDeclaredField("supportsReparse"); - supportsReparseField.setAccessible(true); - supportsReparseField.set(parser, false); - } catch (ClassNotFoundException e) { - // only NetBeans has it - } catch (NoSuchFieldException e) { - // only NetBeans has it - } catch (Throwable t) { - throw Lombok.sneakyThrow(t); - } + + private String collectData(ProcessingEnvironment procEnv) { + StringBuilder message = new StringBuilder(); + message.append("Problem report for usages of 'lombok.javac.apt.Processor'\n\n"); + listOptions(message, procEnv); + findServices(message, procEnv.getFiler()); + addStacktrace(message); + listProperties(message); + return message.toString(); } - private static ClassLoader wrapClassLoader(final ClassLoader parent) { - return new ClassLoader() { - - public Class<?> loadClass(String name) throws ClassNotFoundException { - return parent.loadClass(name); - } - - public String toString() { - return parent.toString(); - } - - public URL getResource(String name) { - return parent.getResource(name); - } - - public Enumeration<URL> getResources(String name) throws IOException { - return parent.getResources(name); - } - - public InputStream getResourceAsStream(String name) { - return parent.getResourceAsStream(name); - } - - public void setDefaultAssertionStatus(boolean enabled) { - parent.setDefaultAssertionStatus(enabled); - } - - public void setPackageAssertionStatus(String packageName, boolean enabled) { - parent.setPackageAssertionStatus(packageName, enabled); - } - - public void setClassAssertionStatus(String className, boolean enabled) { - parent.setClassAssertionStatus(className, enabled); + private void listOptions(StringBuilder message, ProcessingEnvironment procEnv) { + try { + JavacProcessingEnvironment environment = (JavacProcessingEnvironment) procEnv; + Options instance = Options.instance(environment.getContext()); + Field field = Options.class.getDeclaredField("values"); + field.setAccessible(true); + @SuppressWarnings("unchecked") Map<String, String> values = (Map<String, String>) field.get(instance); + if (values.isEmpty()) { + message.append("Options: empty\n\n"); + return; } - - public void clearAssertionStatus() { - parent.clearAssertionStatus(); + message.append("Compiler Options:\n"); + for (Map.Entry<String, String> value : values.entrySet()) { + message.append("- "); + string(message, value.getKey()); + message.append(" = "); + string(message, value.getValue()); + message.append("\n"); } - }; - } - - private void stopJavacProcessingEnvironmentFromClosingOurClassloader() { - try { - Field f = JavacProcessingEnvironment.class.getDeclaredField("processorClassLoader"); - f.setAccessible(true); - ClassLoader unwrapped = (ClassLoader) f.get(processingEnv); - if (unwrapped == null) return; - ClassLoader wrapped = wrapClassLoader(unwrapped); - f.set(processingEnv, 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) { - throw Lombok.sneakyThrow(t); - } - } - - private final IdentityHashMap<JCCompilationUnit, Long> roots = new IdentityHashMap<JCCompilationUnit, Long>(); - private long[] priorityLevels; - private Set<Long> priorityLevelsRequiringResolutionReset; - - /** {@inheritDoc} */ - @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { - if (lombokDisabled) return false; - if (roundEnv.processingOver()) return false; - - // We have: A sorted set of all priority levels: 'priorityLevels' - - // Step 1: Take all CUs which aren't already in the map. Give them the first priority level. - - for (Element element : roundEnv.getRootElements()) { - JCCompilationUnit unit = toUnit(element); - if (unit == null) continue; - if (roots.containsKey(unit)) continue; - roots.put(unit, priorityLevels[0]); + message.append("\n"); + } catch (Exception e) { + message.append("No options available\n\n"); } - while (true) { - // Step 2: For all CUs (in the map, not the roundEnv!), run them across all handlers at their current prio level. - - for (long prio : priorityLevels) { - List<JCCompilationUnit> cusForThisRound = new ArrayList<JCCompilationUnit>(); - for (Map.Entry<JCCompilationUnit, Long> entry : roots.entrySet()) { - Long prioOfCu = entry.getValue(); - if (prioOfCu == null || prioOfCu != prio) continue; - cusForThisRound.add(entry.getKey()); - } - transformer.transform(prio, processingEnv.getContext(), cusForThisRound); + } + + private void findServices(StringBuilder message, Filer filer) { + try { + Field filerFileManagerField = JavacFiler.class.getDeclaredField("fileManager"); + filerFileManagerField.setAccessible(true); + JavaFileManager jfm = (JavaFileManager) filerFileManagerField.get(filer); + ClassLoader processorClassLoader = jfm.hasLocation(ANNOTATION_PROCESSOR_PATH) ? jfm.getClassLoader(ANNOTATION_PROCESSOR_PATH) : jfm.getClassLoader(CLASS_PATH); + Enumeration<URL> resources = processorClassLoader.getResources("META-INF/services/javax.annotation.processing.Processor"); + if (!resources.hasMoreElements()) { + message.append("No processors discovered\n\n"); + return; } - - // Step 3: Push up all CUs to the next level. Set level to null if there is no next level. - - Set<Long> newLevels = new HashSet<Long>(); - for (int i = priorityLevels.length - 1; i >= 0; i--) { - Long curLevel = priorityLevels[i]; - Long nextLevel = (i == priorityLevels.length - 1) ? null : priorityLevels[i + 1]; - List<JCCompilationUnit> cusToAdvance = new ArrayList<JCCompilationUnit>(); - for (Map.Entry<JCCompilationUnit, Long> entry : roots.entrySet()) { - if (curLevel.equals(entry.getValue())) { - cusToAdvance.add(entry.getKey()); - newLevels.add(nextLevel); + message.append("Discovered processors:\n"); + while (resources.hasMoreElements()) { + URL processorUrl = resources.nextElement(); + message.append("- '").append(processorUrl).append("'"); + InputStream content = (InputStream) processorUrl.getContent(); + if (content != null) try { + InputStreamReader reader = new InputStreamReader(content, "UTF-8"); + StringWriter sw = new StringWriter(); + char[] buffer = new char[8192]; + int read = 0; + while ((read = reader.read(buffer))!= -1) { + sw.write(buffer, 0, read); } - } - for (JCCompilationUnit unit : cusToAdvance) { - roots.put(unit, nextLevel); + String wholeFile = sw.toString(); + if (wholeFile.contains("lombok.javac.apt.Processor")) { + message.append(" <= problem\n"); + } else { + message.append(" (ok)\n"); + } + message.append(" ").append(wholeFile.replace("\n", "\n ")).append("\n"); + } finally { + content.close(); } } - newLevels.remove(null); - - // Step 4: If ALL values are null, quit. Else, either do another loop right now or force a resolution reset by forcing a new round in the annotation processor. - - if (newLevels.isEmpty()) return false; - newLevels.retainAll(priorityLevelsRequiringResolutionReset); - if (newLevels.isEmpty()) { - // None of the new levels need resolution, so just keep going. - continue; - } else { - // Force a new round to reset resolution. The next round will cause this method (process) to be called again. - forceNewRound((JavacFiler) processingEnv.getFiler()); - return false; - } + } catch (Exception e) { + message.append("Filer information unavailable\n"); } + message.append("\n"); } - - private int dummyCount = 0; - private void forceNewRound(JavacFiler filer) { - if (!filer.newFiles()) { - try { - JavaFileObject dummy = filer.createSourceFile("lombok.dummy.ForceNewRound" + (dummyCount++)); - Writer w = dummy.openWriter(); - w.close(); - } catch (Exception e) { - e.printStackTrace(); - processingEnv.getMessager().printMessage(Kind.WARNING, - "Can't force a new processing round. Lombok won't work."); + + private void addStacktrace(StringBuilder message) { + StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace(); + if (stackTraceElements != null) { + message.append("Called from\n"); + for (int i = 1; i < stackTraceElements.length; i++) { + StackTraceElement element = stackTraceElements[i]; + if (!element.getClassName().equals("lombok.javac.apt.Processor")) message.append("- ").append(element).append("\n"); } + } else { + message.append("No stacktrace available\n"); } + message.append("\n"); + } + + private void listProperties(StringBuilder message) { + Properties properties = System.getProperties(); + ArrayList<String> propertyNames = new ArrayList<String>(properties.stringPropertyNames()); + Collections.sort(propertyNames); + message.append("Properties: \n"); + for (String propertyName : propertyNames) { + if (propertyName.startsWith("user.")) continue; + message.append("- ").append(propertyName).append(" = "); + string(message, System.getProperty(propertyName)); + message.append("\n"); + } + message.append("\n"); } - private JCCompilationUnit toUnit(Element element) { - TreePath path = trees == null ? null : trees.getPath(element); - if (path == null) return null; - - return (JCCompilationUnit) path.getCompilationUnit(); + private static void string(StringBuilder sb, String s) { + if (s == null) { + sb.append("null"); + return; + } + sb.append("\""); + for (int i = 0; i < s.length(); i++) sb.append(escape(s.charAt(i))); + sb.append("\""); } + private static String escape(char ch) { + switch (ch) { + case '\b': return "\\b"; + case '\f': return "\\f"; + case '\n': return "\\n"; + case '\r': return "\\r"; + case '\t': return "\\t"; + case '\'': return "\\'"; + case '\"': return "\\\""; + case '\\': return "\\\\"; + default: + if (ch < 32) return String.format("\\%03o", (int) ch); + return String.valueOf(ch); + } + } + /** * We just return the latest version of whatever JDK we run on. Stupid? Yeah, but it's either that or warnings on all versions but 1. */ @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.values()[SourceVersion.values().length - 1]; } + + @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { + return false; + } } diff --git a/src/core/lombok/javac/handlers/HandleBuilder.java b/src/core/lombok/javac/handlers/HandleBuilder.java index 1885b8b4..86ac00e6 100644 --- a/src/core/lombok/javac/handlers/HandleBuilder.java +++ b/src/core/lombok/javac/handlers/HandleBuilder.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 @@ -22,18 +22,22 @@ package lombok.javac.handlers; import java.util.ArrayList; -import java.util.Collections; + +import javax.lang.model.element.Modifier; import org.mangosdk.spi.ProviderFor; import com.sun.tools.javac.code.Flags; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.JCAnnotation; +import com.sun.tools.javac.tree.JCTree.JCArrayTypeTree; 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.JCLiteral; import com.sun.tools.javac.tree.JCTree.JCMethodDecl; import com.sun.tools.javac.tree.JCTree.JCModifiers; import com.sun.tools.javac.tree.JCTree.JCPrimitiveTypeTree; @@ -46,16 +50,22 @@ 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.core.AST.Kind; import lombok.core.AnnotationValues; import lombok.core.HandlerPriority; -import lombok.experimental.Builder; +import lombok.core.handlers.HandlerUtil; import lombok.experimental.NonFinal; +import lombok.javac.Javac; import lombok.javac.JavacAnnotationHandler; import lombok.javac.JavacNode; import lombok.javac.JavacTreeMaker; import lombok.javac.handlers.HandleConstructor.SkipIfConstructorExists; +import lombok.javac.handlers.JavacSingularsRecipes.JavacSingularizer; +import lombok.javac.handlers.JavacSingularsRecipes.SingularData; import static lombok.core.handlers.HandlerUtil.*; import static lombok.javac.handlers.JavacHandlerUtil.*; import static lombok.javac.Javac.*; @@ -64,13 +74,41 @@ 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> { - @Override public void handle(AnnotationValues<Builder> annotation, JCAnnotation ast, JavacNode annotationNode) { - handleExperimentalFlagUsage(annotationNode, ConfigurationKeys.BUILDER_FLAG_USAGE, "@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; + return ((Boolean) expr).booleanValue(); + } + + private static class BuilderFieldData { + 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>(); + } + + @Override public void handle(AnnotationValues<Builder> annotation, JCAnnotation ast, JavacNode annotationNode) { Builder builderInstance = annotation.getInstance(); + + // These exist just to support the 'old' lombok.experimental.Builder, which had these properties. lombok.Builder no longer has them. + boolean fluent = toBoolean(annotation.getActualExpression("fluent"), true); + boolean chain = toBoolean(annotation.getActualExpression("chain"), true); + String builderMethodName = builderInstance.builderMethodName(); String buildMethodName = builderInstance.buildMethodName(); String builderClassName = builderInstance.builderClassName(); + String toBuilderMethodName = "toBuilder"; + boolean toBuilder = builderInstance.toBuilder(); + java.util.List<Name> typeArgsForToBuilder = null; if (builderMethodName == null) builderMethodName = "builder"; if (buildMethodName == null) buildMethodName = "build"; @@ -82,73 +120,99 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { if (!checkName("builderClassName", builderClassName, annotationNode)) return; } - deleteAnnotationIfNeccessary(annotationNode, Builder.class); - deleteImportFromCompilationUnit(annotationNode, "lombok.experimental.Builder"); + deleteAnnotationIfNeccessary(annotationNode, Builder.class, "lombok.experimental.Builder"); JavacNode parent = annotationNode.up(); - java.util.List<JCExpression> typesOfParameters = new ArrayList<JCExpression>(); - java.util.List<Name> namesOfParameters = new ArrayList<Name>(); + java.util.List<BuilderFieldData> builderFields = new ArrayList<BuilderFieldData>(); JCExpression returnType; List<JCTypeParameter> typeParams = List.nil(); List<JCExpression> thrownExceptions = List.nil(); - Name nameOfStaticBuilderMethod; + Name nameOfBuilderMethod; JavacNode tdParent; - JCMethodDecl fillParametersFrom = parent.get() instanceof JCMethodDecl ? ((JCMethodDecl) parent.get()) : null; + JavacNode fillParametersFrom = parent.get() instanceof JCMethodDecl ? parent : null; + boolean addCleaning = false; + boolean isStatic = true; if (parent.get() instanceof JCClassDecl) { 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; - namesOfParameters.add(removePrefixFromField(fieldNode)); - typesOfParameters.add(fd.vartype); + 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(), 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; thrownExceptions = List.nil(); - nameOfStaticBuilderMethod = null; + nameOfBuilderMethod = null; if (builderClassName.isEmpty()) builderClassName = td.name.toString() + "Builder"; } else if (fillParametersFrom != null && fillParametersFrom.getName().toString().equals("<init>")) { - if (!fillParametersFrom.typarams.isEmpty()) { + JCMethodDecl jmd = (JCMethodDecl) fillParametersFrom.get(); + if (!jmd.typarams.isEmpty()) { annotationNode.addError("@Builder is not supported on constructors with constructor type parameters."); return; } + tdParent = parent.up(); JCClassDecl td = (JCClassDecl) tdParent.get(); returnType = namePlusTypeParamsToTypeReference(tdParent.getTreeMaker(), td.name, td.typarams); typeParams = td.typarams; - thrownExceptions = fillParametersFrom.thrown; - nameOfStaticBuilderMethod = null; + thrownExceptions = jmd.thrown; + nameOfBuilderMethod = null; if (builderClassName.isEmpty()) builderClassName = td.name.toString() + "Builder"; } else if (fillParametersFrom != null) { tdParent = parent.up(); JCClassDecl td = (JCClassDecl) tdParent.get(); - if ((fillParametersFrom.mods.flags & Flags.STATIC) == 0) { - annotationNode.addError("@Builder is only supported on types, constructors, and static methods."); - return; + JCMethodDecl jmd = (JCMethodDecl) fillParametersFrom.get(); + isStatic = (jmd.mods.flags & Flags.STATIC) != 0; + JCExpression fullReturnType = jmd.restype; + returnType = fullReturnType; + typeParams = jmd.typarams; + thrownExceptions = jmd.thrown; + nameOfBuilderMethod = jmd.name; + if (returnType instanceof JCTypeApply) { + returnType = cloneType(tdParent.getTreeMaker(), returnType, ast, annotationNode.getContext()); } - returnType = fillParametersFrom.restype; - typeParams = fillParametersFrom.typarams; - thrownExceptions = fillParametersFrom.thrown; - nameOfStaticBuilderMethod = fillParametersFrom.name; if (builderClassName.isEmpty()) { - if (returnType instanceof JCTypeApply) { - returnType = ((JCTypeApply) returnType).clazz; - } if (returnType instanceof JCFieldAccess) { builderClassName = ((JCFieldAccess) returnType).name.toString() + "Builder"; } else if (returnType instanceof JCIdent) { @@ -166,96 +230,375 @@ 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"; } } + if (toBuilder) { + final String TO_BUILDER_NOT_SUPPORTED = "@Builder(toBuilder=true) is only supported if you return your own type."; + if (returnType instanceof JCArrayTypeTree) { + annotationNode.addError(TO_BUILDER_NOT_SUPPORTED); + return; + } + + Name simpleName; + String pkg; + List<JCExpression> tpOnRet = List.nil(); + + if (fullReturnType instanceof JCTypeApply) { + tpOnRet = ((JCTypeApply) fullReturnType).arguments; + } + + JCExpression namingType = returnType; + if (returnType instanceof JCTypeApply) namingType = ((JCTypeApply) returnType).clazz; + + if (namingType instanceof JCIdent) { + simpleName = ((JCIdent) namingType).name; + pkg = null; + } else if (namingType instanceof JCFieldAccess) { + JCFieldAccess jcfa = (JCFieldAccess) namingType; + simpleName = jcfa.name; + pkg = unpack(jcfa.selected); + if (pkg.startsWith("ERR:")) { + String err = pkg.substring(4, pkg.indexOf("__ERR__")); + annotationNode.addError(err); + return; + } + } else { + annotationNode.addError("Expected a (parameterized) type here instead of a " + namingType.getClass().getName()); + return; + } + + if (pkg != null && !parent.getPackageDeclaration().equals(pkg)) { + annotationNode.addError(TO_BUILDER_NOT_SUPPORTED); + return; + } + + if (!tdParent.getName().contentEquals(simpleName)) { + annotationNode.addError(TO_BUILDER_NOT_SUPPORTED); + return; + } + + List<JCTypeParameter> tpOnMethod = jmd.typarams; + List<JCTypeParameter> tpOnType = ((JCClassDecl) tdParent.get()).typarams; + typeArgsForToBuilder = new ArrayList<Name>(); + + for (JCTypeParameter tp : tpOnMethod) { + int pos = -1; + int idx = -1; + for (JCExpression tOnRet : tpOnRet) { + idx++; + if (!(tOnRet instanceof JCIdent)) continue; + if (((JCIdent) tOnRet).name != tp.name) continue; + pos = idx; + } + + if (pos == -1 || tpOnType.size() <= pos) { + 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); + } + } } else { - annotationNode.addError("@Builder is only supported on types, constructors, and static methods."); + annotationNode.addError("@Builder is only supported on types, constructors, and methods."); return; } if (fillParametersFrom != null) { - for (JCVariableDecl param : fillParametersFrom.params) { - namesOfParameters.add(param.name); - typesOfParameters.add(param.vartype); + for (JavacNode param : fillParametersFrom.down()) { + if (param.getKind() != Kind.ARGUMENT) continue; + BuilderFieldData bfd = new BuilderFieldData(); + JCVariableDecl raw = (JCVariableDecl) param.get(); + bfd.name = raw.name; + bfd.rawName = raw.name; + bfd.type = raw.vartype; + bfd.singularData = getSingularData(param); + bfd.originalFieldNode = param; + addObtainVia(bfd, param); + builderFields.add(bfd); } } JavacNode builderType = findInnerClass(tdParent, builderClassName); if (builderType == null) { - builderType = makeBuilderClass(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)) { + annotationNode.addError("Existing Builder must be a static inner class."); + return; + } else if (!isStatic && builderTypeDeclaration.getModifiers().getFlags().contains(Modifier.STATIC)) { + annotationNode.addError("Existing Builder must be a non-static inner class."); + return; + } sanityCheckForMethodGeneratingAnnotationsOnBuilderClass(builderType, annotationNode); + /* generate errors for @Singular BFDs that have one already defined node. */ { + for (BuilderFieldData bfd : builderFields) { + SingularData sd = bfd.singularData; + if (sd == null) continue; + JavacSingularizer singularizer = sd.getSingularizer(); + if (singularizer == null) continue; + if (singularizer.checkForAlreadyExistingNodesAndGenerateError(builderType, sd)) { + bfd.singularData = null; + } + } + } } - java.util.List<JavacNode> fieldNodes = addFieldsToBuilder(builderType, namesOfParameters, typesOfParameters, ast); - java.util.List<JCMethodDecl> newMethods = new ArrayList<JCMethodDecl>(); - for (JavacNode fieldNode : fieldNodes) { - JCMethodDecl newMethod = makeSetterMethodForBuilder(builderType, fieldNode, annotationNode, builderInstance.fluent(), builderInstance.chain()); - if (newMethod != null) newMethods.add(newMethod); + + 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; + } + } + } + + 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); } if (constructorExists(builderType) == MemberExistsResult.NOT_EXISTS) { - JCMethodDecl cd = HandleConstructor.createConstructor(AccessLevel.PACKAGE, List.<JCAnnotation>nil(), builderType, List.<JavacNode>nil(), null, annotationNode); + JCMethodDecl cd = HandleConstructor.createConstructor(AccessLevel.PACKAGE, List.<JCAnnotation>nil(), builderType, List.<JavacNode>nil(), false, annotationNode); if (cd != null) injectMethod(builderType, cd); } - for (JCMethodDecl newMethod : newMethods) injectMethod(builderType, newMethod); + for (BuilderFieldData bfd : builderFields) { + makeSetterMethodsForBuilder(builderType, bfd, annotationNode, fluent, chain); + } if (methodExists(buildMethodName, builderType, -1) == MemberExistsResult.NOT_EXISTS) { - JCMethodDecl md = generateBuildMethod(buildMethodName, nameOfStaticBuilderMethod, returnType, namesOfParameters, builderType, thrownExceptions); + 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>(); + for (BuilderFieldData bfd : builderFields) { + fieldNodes.addAll(bfd.createdFields); + } JCMethodDecl md = HandleToString.createToString(builderType, fieldNodes, true, false, FieldAccess.ALWAYS_FIELD, ast); if (md != null) injectMethod(builderType, md); } + if (addCleaning) injectMethod(builderType, generateCleanMethod(builderFields, builderType, ast)); + if (methodExists(builderMethodName, tdParent, -1) == MemberExistsResult.NOT_EXISTS) { - JCMethodDecl md = generateBuilderMethod(builderMethodName, builderClassName, tdParent, typeParams); + JCMethodDecl md = generateBuilderMethod(isStatic, builderMethodName, builderClassName, annotationNode, tdParent, typeParams); + recursiveSetGeneratedBy(md, ast, annotationNode.getContext()); if (md != null) injectMethod(tdParent, md); } + + if (toBuilder) { + switch (methodExists(toBuilderMethodName, tdParent, 0)) { + case EXISTS_BY_USER: + annotationNode.addWarning("Not generating toBuilder() as it already exists."); + return; + case NOT_EXISTS: + List<JCTypeParameter> tps = typeParams; + if (typeArgsForToBuilder != null) { + ListBuffer<JCTypeParameter> lb = new ListBuffer<JCTypeParameter>(); + JavacTreeMaker maker = tdParent.getTreeMaker(); + for (Name n : typeArgsForToBuilder) { + lb.append(maker.TypeParameter(n, List.<JCExpression>nil())); + } + tps = lb.toList(); + } + JCMethodDecl md = generateToBuilderMethod(toBuilderMethodName, builderClassName, tdParent, tps, builderFields, fluent, ast); + if (md != null) injectMethod(tdParent, md); + } + } + + recursiveSetGeneratedBy(builderType.get(), ast, annotationNode.getContext()); + } + + private static String unpack(JCExpression expr) { + StringBuilder sb = new StringBuilder(); + unpack(sb, expr); + return sb.toString(); + } + + private static void unpack(StringBuilder sb, JCExpression expr) { + if (expr instanceof JCIdent) { + sb.append(((JCIdent) expr).name.toString()); + return; + } + + if (expr instanceof JCFieldAccess) { + JCFieldAccess jcfa = (JCFieldAccess) expr; + unpack(sb, jcfa.selected); + sb.append(".").append(jcfa.name.toString()); + return; + } + + if (expr instanceof JCTypeApply) { + sb.setLength(0); + sb.append("ERR:"); + sb.append("@Builder(toBuilder=true) is not supported if returning a type with generics applied to an intermediate."); + sb.append("__ERR__"); + return; + } + + sb.setLength(0); + sb.append("ERR:"); + sb.append("Expected a type of some sort, not a " + expr.getClass().getName()); + sb.append("__ERR__"); } - public JCMethodDecl generateBuildMethod(String name, Name staticName, JCExpression returnType, java.util.List<Name> fieldNames, JavacNode type, List<JCExpression> thrownExceptions) { + private JCMethodDecl generateToBuilderMethod(String toBuilderMethodName, String builderClassName, JavacNode type, List<JCTypeParameter> typeParams, java.util.List<BuilderFieldData> builderFields, boolean fluent, JCAnnotation ast) { + // return new ThingieBuilder<A, B>().setA(this.a).setB(this.b); + 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(builderClassName), typeParams), List.<JCExpression>nil(), null); + JCExpression invoke = call; + for (BuilderFieldData bfd : builderFields) { + Name setterName = fluent ? bfd.name : type.toName(HandlerUtil.buildAccessorName("set", bfd.name.toString())); + JCExpression arg; + if (bfd.obtainVia == null || !bfd.obtainVia.field().isEmpty()) { + arg = maker.Select(maker.Ident(type.toName("this")), bfd.obtainVia == null ? bfd.rawName : type.toName(bfd.obtainVia.field())); + } else { + if (bfd.obtainVia.isStatic()) { + JCExpression c = maker.Select(maker.Ident(type.toName(type.getName())), type.toName(bfd.obtainVia.method())); + arg = maker.Apply(List.<JCExpression>nil(), c, List.<JCExpression>of(maker.Ident(type.toName("this")))); + } else { + JCExpression c = maker.Select(maker.Ident(type.toName("this")), type.toName(bfd.obtainVia.method())); + arg = maker.Apply(List.<JCExpression>nil(), c, List.<JCExpression>nil()); + } + } + invoke = maker.Apply(List.<JCExpression>nil(), maker.Select(invoke, setterName), List.of(arg)); + } + JCStatement statement = maker.Return(invoke); + + JCBlock body = maker.Block(0, List.<JCStatement>of(statement)); + return maker.MethodDef(maker.Modifiers(Flags.PUBLIC), type.toName(toBuilderMethodName), namePlusTypeParamsToTypeReference(maker, type.toName(builderClassName), typeParams), List.<JCTypeParameter>nil(), List.<JCVariableDecl>nil(), List.<JCExpression>nil(), 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); + /* + * if (shouldReturnThis) { + methodType = cloneSelfType(field); + } + + 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; + } + + */ + } + + 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; - JCStatement statement; + ListBuffer<JCStatement> statements = new ListBuffer<JCStatement>(); + + if (addCleaning) { + JCExpression notClean = maker.Unary(CTC_NOT, maker.Select(maker.Ident(type.toName("this")), type.toName("$lombokUnclean"))); + JCStatement invokeClean = maker.Exec(maker.Apply(List.<JCExpression>nil(), maker.Ident(type.toName("$lombokClean")), List.<JCExpression>nil())); + JCIf ifUnclean = maker.If(notClean, invokeClean, null); + statements.append(ifUnclean); + } + + for (BuilderFieldData bfd : builderFields) { + if (bfd.singularData != null && bfd.singularData.getSingularizer() != null) { + bfd.singularData.getSingularizer().appendBuildCode(bfd.singularData, type, source, statements, bfd.name); + } + } ListBuffer<JCExpression> args = new ListBuffer<JCExpression>(); - for (Name n : fieldNames) { - args.append(maker.Ident(n)); + 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 (staticName == null) { + if (addCleaning) { + 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); - statement = maker.Return(call); + 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 fn = maker.Select(maker.Ident(((JCClassDecl) type.up().get()).name), staticName); + JCExpression callee = maker.Ident(((JCClassDecl) type.up().get()).name); + 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))) { - statement = maker.Exec(call); + statements.append(maker.Exec(call)); } else { - statement = maker.Return(call); + statements.append(maker.Return(call)); } } - JCBlock body = maker.Block(0, List.<JCStatement>of(statement)); + JCBlock body = maker.Block(0, statements.toList()); - return maker.MethodDef(maker.Modifiers(Flags.PUBLIC), type.toName(name), returnType, List.<JCTypeParameter>nil(), List.<JCVariableDecl>nil(), thrownExceptions, body, null); + return maker.MethodDef(maker.Modifiers(Flags.PUBLIC), type.toName(buildName), returnType, List.<JCTypeParameter>nil(), List.<JCVariableDecl>nil(), thrownExceptions, body, null); } - public JCMethodDecl generateBuilderMethod(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>(); @@ -267,53 +610,71 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { JCStatement statement = maker.Return(call); JCBlock body = maker.Block(0, List.<JCStatement>of(statement)); - return maker.MethodDef(maker.Modifiers(Flags.STATIC | Flags.PUBLIC), type.toName(builderMethodName), namePlusTypeParamsToTypeReference(maker, type.toName(builderClassName), typeParams), copyTypeParams(maker, typeParams), List.<JCVariableDecl>nil(), List.<JCExpression>nil(), body, null); + 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(source, typeParams), List.<JCVariableDecl>nil(), List.<JCExpression>nil(), body, null); } - public java.util.List<JavacNode> addFieldsToBuilder(JavacNode builderType, java.util.List<Name> namesOfParameters, java.util.List<JCExpression> typesOfParameters, JCTree source) { - int len = namesOfParameters.size(); + public 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); } - java.util.List<JavacNode>out = new ArrayList<JavacNode>(); - - top: for (int i = len - 1; i >= 0; i--) { - Name name = namesOfParameters.get(i); - for (JavacNode exists : existing) { - Name n = ((JCVariableDecl) exists.get()).name; - if (n.equals(name)) { - out.add(exists); - continue top; + 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); } - JavacTreeMaker maker = builderType.getTreeMaker(); - JCModifiers mods = maker.Modifiers(Flags.PRIVATE); - JCVariableDecl newField = maker.VarDef(mods, name, cloneType(maker, typesOfParameters.get(i), source, builderType.getContext()), null); - out.add(injectField(builderType, newField)); } - - Collections.reverse(out); - return out; } + 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, deprecate, fieldNode.createdFields.get(0), fieldNode.nameOfSetFlag, source, fluent, chain); + } else { + fieldNode.singularData.getSingularizer().generateMethods(fieldNode.singularData, deprecate, builderType, source.get(), fluent, chain); + } + } - public JCMethodDecl makeSetterMethodForBuilder(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 null; + JCMethodDecl methodDecl = (JCMethodDecl) child.get(); + Name existingName = methodDecl.name; + if (existingName.equals(fieldName) && !isTolerate(fieldNode, methodDecl)) return; } - boolean isBoolean = isBoolean(fieldNode); - String setterName = fluent ? fieldNode.getName() : toSetterName(builderType.getAst(), null, fieldNode.getName(), isBoolean); + String setterName = fluent ? fieldNode.getName() : HandlerUtil.buildAccessorName("set", fieldNode.getName()); + + JavacTreeMaker maker = fieldNode.getTreeMaker(); - JavacTreeMaker maker = builderType.getTreeMaker(); - return 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); } public JavacNode findInnerClass(JavacNode parent, String name) { @@ -325,10 +686,77 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { return null; } - public JavacNode makeBuilderClass(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(); - JCModifiers mods = maker.Modifiers(Flags.PUBLIC | Flags.STATIC); - JCClassDecl builder = maker.ClassDef(mods, tdParent.toName(builderClassName), copyTypeParams(maker, typeParams), null, List.<JCExpression>nil(), List.<JCTree>nil()); + int modifiers = Flags.PUBLIC; + if (isStatic) modifiers |= Flags.STATIC; + JCModifiers mods = maker.Modifiers(modifiers); + JCClassDecl builder = maker.ClassDef(mods, tdParent.toName(builderClassName), copyTypeParams(source, typeParams), null, List.<JCExpression>nil(), List.<JCTree>nil()); return injectType(tdParent, builder); } + + 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; + } } diff --git a/src/core/lombok/javac/handlers/HandleBuilderDefault.java b/src/core/lombok/javac/handlers/HandleBuilderDefault.java new file mode 100644 index 00000000..4c4ba0e8 --- /dev/null +++ b/src/core/lombok/javac/handlers/HandleBuilderDefault.java @@ -0,0 +1,49 @@ +/* + * 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.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)) { + annotationNode.addWarning("@Builder.Default requires @Builder 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 6043d1cb..dca25ee7 100644 --- a/src/core/lombok/javac/handlers/HandleConstructor.java +++ b/src/core/lombok/javac/handlers/HandleConstructor.java @@ -23,15 +23,18 @@ 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.experimental.Builder; +import lombok.javac.Javac; import lombok.javac.JavacAnnotationHandler; import lombok.javac.JavacNode; import lombok.javac.JavacTreeMaker; @@ -39,15 +42,17 @@ import lombok.javac.JavacTreeMaker; 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.JCAssign; 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.JCMethodDecl; import com.sun.tools.javac.tree.JCTree.JCModifiers; +import com.sun.tools.javac.tree.JCTree.JCPrimitiveTypeTree; import com.sun.tools.javac.tree.JCTree.JCReturn; import com.sun.tools.javac.tree.JCTree.JCStatement; import com.sun.tools.javac.tree.JCTree.JCTypeParameter; @@ -59,6 +64,8 @@ import com.sun.tools.javac.util.Name; 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"); @@ -66,18 +73,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(); - List<JavacNode> fields = List.nil(); - new HandleConstructor().generateConstructor(typeNode, level, onConstructor, fields, staticName, SkipIfConstructorExists.NO, null, annotationNode); + boolean force = ann.force(); + List<JavacNode> fields = force ? findFinalFields(typeNode) : List.<JavacNode>nil(); + 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"); @@ -85,23 +95,28 @@ 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), staticName, SkipIfConstructorExists.NO, suppressConstructorProperties, annotationNode); + handleConstructor.generateConstructor(typeNode, level, onConstructor, findRequiredFields(typeNode), false, staticName, SkipIfConstructorExists.NO, annotationNode); } } public static List<JavacNode> findRequiredFields(JavacNode typeNode) { + return findFields(typeNode, true); + } + + public static List<JavacNode> findFinalFields(JavacNode typeNode) { + return findFields(typeNode, false); + } + + public static List<JavacNode> findFields(JavacNode typeNode, boolean nullMarked) { ListBuffer<JavacNode> fields = new ListBuffer<JavacNode>(); for (JavacNode child : typeNode.down()) { if (child.getKind() != Kind.FIELD) continue; @@ -112,7 +127,7 @@ public class HandleConstructor { //Skip static fields. if ((fieldFlags & Flags.STATIC) != 0) continue; boolean isFinal = (fieldFlags & Flags.FINAL) != 0; - boolean isNonNull = !findAnnotations(child, NON_NULL_PATTERN).isEmpty(); + boolean isNonNull = nullMarked && !findAnnotations(child, NON_NULL_PATTERN).isEmpty(); if ((isFinal || isNonNull) && fieldDecl.init == null) fields.append(child); } return fields.toList(); @@ -120,6 +135,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"); @@ -127,22 +144,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), 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; @@ -154,7 +172,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(); } @@ -174,7 +192,7 @@ public class HandleConstructor { } public void generateRequiredArgsConstructor(JavacNode typeNode, AccessLevel level, String staticName, SkipIfConstructorExists skipIfConstructorExists, JavacNode source) { - generateConstructor(typeNode, level, List.<JCAnnotation>nil(), findRequiredFields(typeNode), staticName, skipIfConstructorExists, null, source); + generateConstructor(typeNode, level, List.<JCAnnotation>nil(), findRequiredFields(typeNode), false, staticName, skipIfConstructorExists, source); } public enum SkipIfConstructorExists { @@ -182,10 +200,10 @@ public class HandleConstructor { } public void generateAllArgsConstructor(JavacNode typeNode, AccessLevel level, String staticName, SkipIfConstructorExists skipIfConstructorExists, JavacNode source) { - generateConstructor(typeNode, level, List.<JCAnnotation>nil(), findAllFields(typeNode), 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, 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) { boolean staticConstrRequired = staticName != null && !staticName.equals(""); if (skipIfConstructorExists != SkipIfConstructorExists.NO && constructorExists(typeNode) != MemberExistsResult.NOT_EXISTS) return; @@ -193,8 +211,8 @@ public class HandleConstructor { for (JavacNode child : typeNode.down()) { if (child.getKind() == Kind.ANNOTATION) { boolean skipGeneration = annotationTypeMatches(NoArgsConstructor.class, child) || - annotationTypeMatches(AllArgsConstructor.class, child) || - annotationTypeMatches(RequiredArgsConstructor.class, child); + annotationTypeMatches(AllArgsConstructor.class, child) || + annotationTypeMatches(RequiredArgsConstructor.class, child); if (!skipGeneration && skipIfConstructorExists == SkipIfConstructorExists.YES) { skipGeneration = annotationTypeMatches(Builder.class, child); @@ -214,11 +232,23 @@ public class HandleConstructor { } } - JCMethodDecl constr = createConstructor(staticConstrRequired ? AccessLevel.PRIVATE : level, onConstructor, typeNode, fields, suppressConstructorProperties, source); - injectMethod(typeNode, constr); + 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) { - JCMethodDecl staticConstr = createStaticConstructor(staticName, level, typeNode, fields, source.get()); - injectMethod(typeNode, staticConstr); + 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, argTypes_, returnType); } } @@ -236,18 +266,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 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>(); @@ -259,29 +291,55 @@ public class HandleConstructor { Name fieldName = removePrefixFromField(fieldNode); Name rawName = field.name; List<JCAnnotation> nonNulls = findAnnotations(fieldNode, NON_NULL_PATTERN); - List<JCAnnotation> nullables = findAnnotations(fieldNode, NULLABLE_PATTERN); - long flags = JavacHandlerUtil.addFinalIfNeeded(Flags.PARAMETER, typeNode.getContext()); - JCVariableDecl param = maker.VarDef(maker.Modifiers(flags, nonNulls.appendList(nullables)), fieldName, field.vartype, null); - params.append(param); + if (!allToDefault) { + List<JCAnnotation> nullables = findAnnotations(fieldNode, NULLABLE_PATTERN); + long flags = JavacHandlerUtil.addFinalIfNeeded(Flags.PARAMETER, typeNode.getContext()); + 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, param, source); + if (nullCheck != null) nullChecks.append(nullCheck); + } + } JCFieldAccess thisX = maker.Select(maker.Ident(fieldNode.toName("this")), rawName); - JCAssign assign = maker.Assign(thisX, maker.Ident(fieldName)); + JCExpression assign = maker.Assign(thisX, allToDefault ? getDefaultExpr(maker, field.vartype) : maker.Ident(fieldName)); assigns.append(maker.Exec(assign)); - - if (!nonNulls.isEmpty()) { - JCStatement nullCheck = generateNullCheck(maker, fieldNode, source); - if (nullCheck != null) nullChecks.append(nullCheck); - } } JCModifiers mods = maker.Modifiers(toJavacModifier(level), List.<JCAnnotation>nil()); - if (!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)); return recursiveSetGeneratedBy(maker.MethodDef(mods, typeNode.toName("<init>"), - null, List.<JCTypeParameter>nil(), params.toList(), List.<JCExpression>nil(), - maker.Block(0L, nullChecks.appendList(assigns).toList()), null), source.get(), typeNode.getContext()); + null, List.<JCTypeParameter>nil(), params.toList(), List.<JCExpression>nil(), + maker.Block(0L, nullChecks.appendList(assigns).toList()), null), source.get(), typeNode.getContext()); + } + + private static JCExpression getDefaultExpr(JavacTreeMaker maker, JCExpression type) { + if (type instanceof JCPrimitiveTypeTree) { + switch (((JCPrimitiveTypeTree) type).getPrimitiveTypeKind()) { + case BOOLEAN: + return maker.Literal(CTC_BOOLEAN, 0); + case CHAR: + return maker.Literal(CTC_CHAR, 0); + default: + case BYTE: + case SHORT: + case INT: + return maker.Literal(CTC_INT, 0); + case LONG: + return maker.Literal(CTC_LONG, 0L); + case FLOAT: + return maker.Literal(CTC_FLOAT, 0F); + case DOUBLE: + return maker.Literal(CTC_DOUBLE, 0D); + } + } + + return maker.Literal(CTC_BOT, null); + } public static boolean isLocalType(JavacNode type) { diff --git a/src/core/lombok/javac/handlers/HandleData.java b/src/core/lombok/javac/handlers/HandleData.java index 9ecf8754..15f1fd83 100644 --- a/src/core/lombok/javac/handlers/HandleData.java +++ b/src/core/lombok/javac/handlers/HandleData.java @@ -40,6 +40,12 @@ import com.sun.tools.javac.tree.JCTree.JCAnnotation; */ @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"); @@ -54,11 +60,10 @@ 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); + handleGetter.generateGetterForType(typeNode, annotationNode, AccessLevel.PUBLIC, true); + handleSetter.generateSetterForType(typeNode, annotationNode, AccessLevel.PUBLIC, true); + 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 fc3c81f3..49bef769 100644 --- a/src/core/lombok/javac/handlers/HandleDelegate.java +++ b/src/core/lombok/javac/handlers/HandleDelegate.java @@ -299,7 +299,7 @@ public class HandleDelegate extends JavacAnnotationHandler<Delegate> { annotations = com.sun.tools.javac.util.List.nil(); } - JCModifiers mods = maker.Modifiers(Flags.PUBLIC, annotations); + JCModifiers mods = maker.Modifiers(PUBLIC, annotations); JCExpression returnType = JavacResolution.typeToJCTree((Type) sig.type.getReturnType(), annotation.getAst(), true); boolean useReturn = sig.type.getReturnType().getKind() != TypeKind.VOID; ListBuffer<JCVariableDecl> params = sig.type.getParameterTypes().isEmpty() ? null : new ListBuffer<JCVariableDecl>(); @@ -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)); } @@ -326,11 +326,15 @@ public class HandleDelegate extends JavacAnnotationHandler<Delegate> { } int idx = 0; + String[] paramNames = sig.getParameterNames(); + boolean varargs = sig.elem.isVarArgs(); for (TypeMirror param : sig.type.getParameterTypes()) { long flags = JavacHandlerUtil.addFinalIfNeeded(Flags.PARAMETER, annotation.getContext()); JCModifiers paramMods = maker.Modifiers(flags); - String[] paramNames = sig.getParameterNames(); Name name = annotation.toName(paramNames[idx++]); + if (varargs && idx == paramNames.length) { + paramMods.flags |= VARARGS; + } params.append(maker.VarDef(paramMods, name, JavacResolution.typeToJCTree((Type) param, annotation.getAst(), true), null)); args.append(maker.Ident(name)); } diff --git a/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java b/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java index 1a6f4a8f..d8bfd154 100644 --- a/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java +++ b/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java @@ -32,6 +32,7 @@ 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; @@ -94,7 +95,7 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler<EqualsAndHas List<String> excludes = List.from(ann.exclude()); List<String> includes = List.from(ann.of()); JavacNode typeNode = annotationNode.up(); - List<JCAnnotation> onParam = unboxAndRemoveAnnotationParameter(ast, "onParam", "@EqualsAndHashCode(onParam=", annotationNode); + List<JCAnnotation> onParam = unboxAndRemoveAnnotationParameter(ast, "onParam", "@EqualsAndHashCode(onParam", annotationNode); checkForBogusFieldNames(typeNode, annotation); Boolean callSuper = ann.callSuper(); @@ -120,14 +121,18 @@ 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; + + generateMethods(typeNode, source, null, null, null, false, access, List.<JCAnnotation>nil()); } public void generateMethods(JavacNode typeNode, JavacNode source, List<String> excludes, List<String> includes, - Boolean callSuper, boolean whineIfExists, FieldAccess fieldAccess, List<JCAnnotation> onParam) { + Boolean callSuper, boolean whineIfExists, FieldAccess fieldAccess, List<JCAnnotation> onParam) { + 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 | Flags.ENUM)) != 0; } @@ -140,7 +145,7 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler<EqualsAndHas boolean implicitCallSuper = callSuper == null; 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."); } @@ -157,8 +162,23 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler<EqualsAndHas return; } - if (!isDirectDescendantOfObject && !callSuper && implicitCallSuper) { - source.addWarning("Generating equals/hashCode implementation but without a call to superclass, even though this class does not extend java.lang.Object. If this is intentional, add '@EqualsAndHashCode(callSuper=false)' to your type."); + if (implicitCallSuper && !isDirectDescendantOfObject) { + CallSuperType cst = typeNode.getAst().readConfiguration(ConfigurationKeys.EQUALS_AND_HASH_CODE_CALL_SUPER); + if (cst == null) cst = CallSuperType.WARN; + + switch (cst) { + default: + case WARN: + source.addWarning("Generating equals/hashCode implementation but without a call to superclass, even though this class does not extend java.lang.Object. If this is intentional, add '@EqualsAndHashCode(callSuper=false)' to your type."); + callSuper = false; + break; + case SKIP: + callSuper = false; + break; + case CALL: + callSuper = true; + break; + } } ListBuffer<JavacNode> nodesForEquality = new ListBuffer<JavacNode>(); @@ -184,7 +204,7 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler<EqualsAndHas } } - boolean isFinal = (((JCClassDecl)typeNode.get()).mods.flags & Flags.FINAL) != 0; + boolean isFinal = (((JCClassDecl) typeNode.get()).mods.flags & Flags.FINAL) != 0; boolean needsCanEqual = !isFinal || !isDirectDescendantOfObject; MemberExistsResult equalsExists = methodExists("equals", typeNode, 1); MemberExistsResult hashCodeExists = methodExists("hashCode", typeNode, 0); @@ -202,8 +222,8 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler<EqualsAndHas // The user code couldn't possibly (barring really weird subclassing shenanigans) be in a shippable state anyway; the implementations of these 2 methods are // all inter-related and should be written by the same entity. String msg = String.format("Not generating %s: One of equals or hashCode exists. " + - "You should either write both of these or none of these (in the latter case, lombok generates them).", - equalsExists == MemberExistsResult.NOT_EXISTS ? "equals" : "hashCode"); + "You should either write both of these or none of these (in the latter case, lombok generates them).", + equalsExists == MemberExistsResult.NOT_EXISTS ? "equals" : "hashCode"); source.addWarning(msg); } return; @@ -213,6 +233,7 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler<EqualsAndHas } JCMethodDecl equalsMethod = createEquals(typeNode, nodesForEquality.toList(), callSuper, fieldAccess, needsCanEqual, source.get(), onParam); + injectMethod(typeNode, equalsMethod); if (needsCanEqual && canEqualExists == MemberExistsResult.NOT_EXISTS) { @@ -237,20 +258,23 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler<EqualsAndHas long finalFlag = JavacHandlerUtil.addFinalIfNeeded(0L, typeNode.getContext()); /* final int PRIME = X; */ { - if (!fields.isEmpty() || callSuper) { + if (!fields.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("$"); @@ -258,14 +282,14 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler<EqualsAndHas JCExpression fType = getFieldType(fieldNode, fieldAccess); JCExpression fieldAccessor = createFieldAccessor(maker, fieldNode, fieldAccess); if (fType instanceof JCPrimitiveTypeTree) { - switch (((JCPrimitiveTypeTree)fType).getPrimitiveTypeKind()) { + switch (((JCPrimitiveTypeTree) fType).getPrimitiveTypeKind()) { case BOOLEAN: /* this.fieldName ? X : Y */ - statements.append(createResultCalculation(typeNode, maker.Conditional(fieldAccessor, - maker.Literal(HandlerUtil.primeForTrue()), maker.Literal(HandlerUtil.primeForFalse())))); + statements.append(createResultCalculation(typeNode, maker.Parens(maker.Conditional(fieldAccessor, + maker.Literal(HandlerUtil.primeForTrue()), maker.Literal(HandlerUtil.primeForFalse()))))); break; case LONG: { - Name dollarFieldName = dollar.append(((JCVariableDecl)fieldNode.get()).name); + Name dollarFieldName = dollar.append(((JCVariableDecl) fieldNode.get()).name); 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)))); } @@ -273,17 +297,17 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler<EqualsAndHas case FLOAT: /* Float.floatToIntBits(this.fieldName) */ statements.append(createResultCalculation(typeNode, maker.Apply( - List.<JCExpression>nil(), - genJavaLangTypeRef(typeNode, "Float", "floatToIntBits"), - List.of(fieldAccessor)))); + List.<JCExpression>nil(), + genJavaLangTypeRef(typeNode, "Float", "floatToIntBits"), + List.of(fieldAccessor)))); break; case DOUBLE: { /* longToIntForHashCode(Double.doubleToLongBits(this.fieldName)) */ - Name dollarFieldName = dollar.append(((JCVariableDecl)fieldNode.get()).name); + Name dollarFieldName = dollar.append(((JCVariableDecl) fieldNode.get()).name); JCExpression init = maker.Apply( - List.<JCExpression>nil(), - genJavaLangTypeRef(typeNode, "Double", "doubleToLongBits"), - List.of(fieldAccessor)); + List.<JCExpression>nil(), + genJavaLangTypeRef(typeNode, "Double", "doubleToLongBits"), + List.of(fieldAccessor)); statements.append(maker.VarDef(maker.Modifiers(finalFlag), dollarFieldName, maker.TypeIdent(CTC_LONG), init)); statements.append(createResultCalculation(typeNode, longToIntForHashCode(maker, maker.Ident(dollarFieldName), maker.Ident(dollarFieldName)))); } @@ -299,23 +323,23 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler<EqualsAndHas } } else if (fType instanceof JCArrayTypeTree) { /* java.util.Arrays.deepHashCode(this.fieldName) //use just hashCode() for primitive arrays. */ - boolean multiDim = ((JCArrayTypeTree)fType).elemtype instanceof JCArrayTypeTree; - boolean primitiveArray = ((JCArrayTypeTree)fType).elemtype instanceof JCPrimitiveTypeTree; + boolean multiDim = ((JCArrayTypeTree) fType).elemtype instanceof JCArrayTypeTree; + boolean primitiveArray = ((JCArrayTypeTree) fType).elemtype instanceof JCPrimitiveTypeTree; boolean useDeepHC = multiDim || !primitiveArray; JCExpression hcMethod = chainDots(typeNode, "java", "util", "Arrays", useDeepHC ? "deepHashCode" : "hashCode"); statements.append(createResultCalculation(typeNode, maker.Apply(List.<JCExpression>nil(), hcMethod, List.of(fieldAccessor)))); } else /* objects */ { /* final java.lang.Object $fieldName = this.fieldName; */ - /* $fieldName == null ? 0 : $fieldName.hashCode() */ + /* ($fieldName == null ? NULL_PRIME : $fieldName.hashCode()) */ - Name dollarFieldName = dollar.append(((JCVariableDecl)fieldNode.get()).name); + Name dollarFieldName = dollar.append(((JCVariableDecl) fieldNode.get()).name); 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")), - List.<JCExpression>nil()); + List.<JCExpression>nil()); JCExpression thisEqualsNull = maker.Binary(CTC_EQUAL, maker.Ident(dollarFieldName), maker.Literal(CTC_BOT, null)); - statements.append(createResultCalculation(typeNode, maker.Conditional(thisEqualsNull, maker.Literal(0), hcCall))); + statements.append(createResultCalculation(typeNode, maker.Parens(maker.Conditional(thisEqualsNull, maker.Literal(HandlerUtil.primeForNull()), hcCall)))); } } @@ -325,11 +349,11 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler<EqualsAndHas JCBlock body = maker.Block(0, statements.toList()); return recursiveSetGeneratedBy(maker.MethodDef(mods, typeNode.toName("hashCode"), 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 JCExpressionStatement createResultCalculation(JavacNode typeNode, JCExpression expr) { - /* result = result * PRIME + (expr); */ + /* result = result * PRIME + expr; */ JavacTreeMaker maker = typeNode.getTreeMaker(); Name resultName = typeNode.toName(RESULT_NAME); JCExpression mult = maker.Binary(CTC_MUL, maker.Ident(resultName), maker.Ident(typeNode.toName(PRIME_NAME))); @@ -339,35 +363,56 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler<EqualsAndHas /** The 2 references must be clones of each other. */ public JCExpression longToIntForHashCode(JavacTreeMaker maker, JCExpression ref1, JCExpression ref2) { - /* (int)(ref >>> 32 ^ ref) */ + /* (int) (ref >>> 32 ^ ref) */ JCExpression shift = maker.Binary(CTC_UNSIGNED_SHIFT_RIGHT, ref1, maker.Literal(32)); JCExpression xorBits = maker.Binary(CTC_BITXOR, shift, ref2); - return maker.TypeCast(maker.TypeIdent(CTC_INT), xorBits); + 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; } + 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, List<JavacNode> fields, 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"); @@ -385,35 +430,21 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler<EqualsAndHas /* if (o == this) return true; */ { statements.append(maker.If(maker.Binary(CTC_EQUAL, maker.Ident(oName), - maker.Ident(thisName)), returnBool(maker, true), null)); + maker.Ident(thisName)), returnBool(maker, true), null)); } - /* if (!(o instanceof Outer.Inner.MyType) return false; */ { + /* if (!(o instanceof Outer.Inner.MyType)) return false; */ { - JCUnary notInstanceOf = maker.Unary(CTC_NOT, 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; */ { + /* Outer.Inner.MyType<?> other = (Outer.Inner.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()); - } + 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)))); + maker.VarDef(maker.Modifiers(finalFlag), otherName, selfType1, maker.TypeCast(selfType2, maker.Ident(oName)))); } } @@ -423,8 +454,8 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler<EqualsAndHas JCExpression thisRef = maker.Ident(thisName); JCExpression castThisRef = maker.TypeCast(genJavaLangTypeRef(typeNode, "Object"), thisRef); JCExpression equalityCheck = maker.Apply(exprNil, - maker.Select(maker.Ident(otherName), typeNode.toName("canEqual")), - List.of(castThisRef)); + maker.Select(maker.Ident(otherName), typeNode.toName("canEqual")), + List.of(castThisRef)); statements.append(maker.If(maker.Unary(CTC_NOT, equalityCheck), returnBool(maker, false), null)); } } @@ -432,8 +463,8 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler<EqualsAndHas /* if (!super.equals(o)) return false; */ if (callSuper) { JCMethodInvocation callToSuper = maker.Apply(List.<JCExpression>nil(), - maker.Select(maker.Ident(typeNode.toName("super")), typeNode.toName("equals")), - List.<JCExpression>of(maker.Ident(oName))); + maker.Select(maker.Ident(typeNode.toName("super")), typeNode.toName("equals")), + List.<JCExpression>of(maker.Ident(oName))); JCUnary superNotEqual = maker.Unary(CTC_NOT, callToSuper); statements.append(maker.If(superNotEqual, returnBool(maker, false), null)); } @@ -462,19 +493,19 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler<EqualsAndHas } } else if (fType instanceof JCArrayTypeTree) { /* if (!java.util.Arrays.deepEquals(this.fieldName, other.fieldName)) return false; //use equals for primitive arrays. */ - boolean multiDim = ((JCArrayTypeTree)fType).elemtype instanceof JCArrayTypeTree; - boolean primitiveArray = ((JCArrayTypeTree)fType).elemtype instanceof JCPrimitiveTypeTree; + boolean multiDim = ((JCArrayTypeTree) fType).elemtype instanceof JCArrayTypeTree; + boolean primitiveArray = ((JCArrayTypeTree) fType).elemtype instanceof JCPrimitiveTypeTree; boolean useDeepEquals = multiDim || !primitiveArray; JCExpression eqMethod = chainDots(typeNode, "java", "util", "Arrays", useDeepEquals ? "deepEquals" : "equals"); List<JCExpression> args = List.of(thisFieldAccessor, otherFieldAccessor); statements.append(maker.If(maker.Unary(CTC_NOT, - maker.Apply(List.<JCExpression>nil(), eqMethod, args)), returnBool(maker, false), null)); + maker.Apply(List.<JCExpression>nil(), eqMethod, args)), returnBool(maker, false), null)); } 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;; */ - Name fieldName = ((JCVariableDecl)fieldNode.get()).name; + /* 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); @@ -484,8 +515,8 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler<EqualsAndHas JCExpression thisEqualsNull = maker.Binary(CTC_EQUAL, maker.Ident(thisDollarFieldName), maker.Literal(CTC_BOT, null)); JCExpression otherNotEqualsNull = maker.Binary(CTC_NOT_EQUAL, maker.Ident(otherDollarFieldName), maker.Literal(CTC_BOT, null)); JCExpression thisEqualsThat = maker.Apply(List.<JCExpression>nil(), - maker.Select(maker.Ident(thisDollarFieldName), typeNode.toName("equals")), - List.<JCExpression>of(maker.Ident(otherDollarFieldName))); + maker.Select(maker.Ident(thisDollarFieldName), typeNode.toName("equals")), + List.<JCExpression>of(maker.Ident(otherDollarFieldName))); JCExpression fieldsAreNotEqual = maker.Conditional(thisEqualsNull, otherNotEqualsNull, maker.Unary(CTC_NOT, thisEqualsThat)); statements.append(maker.If(fieldsAreNotEqual, returnBool(maker, false), null)); } @@ -500,7 +531,7 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler<EqualsAndHas } public JCMethodDecl createCanEqual(JavacNode typeNode, JCTree source, List<JCAnnotation> onParam) { - /* public boolean canEqual(final java.lang.Object other) { + /* protected boolean canEqual(final java.lang.Object other) { * return other instanceof Outer.Inner.MyType; * } */ @@ -515,18 +546,19 @@ 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()); } public JCStatement generateCompareFloatOrDouble(JCExpression thisDotField, JCExpression otherDotField, - JavacTreeMaker maker, JavacNode node, boolean isDouble) { + JavacTreeMaker maker, JavacNode node, boolean isDouble) { + /* if (Float.compare(fieldName, other.fieldName) != 0) return false; */ JCExpression clazz = genJavaLangTypeRef(node, isDouble ? "Double" : "Float"); List<JCExpression> args = List.of(thisDotField, otherDotField); JCBinary compareCallEquals0 = maker.Binary(CTC_NOT_EQUAL, maker.Apply( - List.<JCExpression>nil(), maker.Select(clazz, node.toName("compare")), args), maker.Literal(0)); + List.<JCExpression>nil(), maker.Select(clazz, node.toName("compare")), args), maker.Literal(0)); return maker.If(compareCallEquals0, returnBool(maker, false), null); } diff --git a/src/core/lombok/javac/handlers/HandleFieldDefaults.java b/src/core/lombok/javac/handlers/HandleFieldDefaults.java index 335ab1fe..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-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 @@ -31,23 +31,24 @@ import lombok.core.HandlerPriority; import lombok.experimental.FieldDefaults; import lombok.experimental.NonFinal; import lombok.experimental.PackagePrivate; -import lombok.javac.JavacAnnotationHandler; +import lombok.javac.JavacASTAdapter; +import lombok.javac.JavacASTVisitor; import lombok.javac.JavacNode; import org.mangosdk.spi.ProviderFor; import com.sun.tools.javac.code.Flags; +import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.JCAnnotation; import com.sun.tools.javac.tree.JCTree.JCClassDecl; import com.sun.tools.javac.tree.JCTree.JCVariableDecl; -import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; /** * Handles the {@code lombok.FieldDefaults} annotation for eclipse. */ -@ProviderFor(JavacAnnotationHandler.class) +@ProviderFor(JavacASTVisitor.class) @HandlerPriority(-2048) //-2^11; to ensure @Value picks up on messing with the fields' 'final' state, run earlier. -public class HandleFieldDefaults extends JavacAnnotationHandler<FieldDefaults> { +public class HandleFieldDefaults extends JavacASTAdapter { public boolean generateFieldDefaultsForType(JavacNode typeNode, JavacNode errorNode, AccessLevel level, boolean makeFinal, boolean checkForTypeLevelFieldDefaults) { if (checkForTypeLevelFieldDefaults) { if (hasAnnotation(FieldDefaults.class, typeNode)) { @@ -72,56 +73,82 @@ public class HandleFieldDefaults extends JavacAnnotationHandler<FieldDefaults> { //Skip fields that start with $ if (fieldDecl.name.toString().startsWith("$")) continue; - setFieldDefaultsForField(field, errorNode.get(), level, makeFinal); + setFieldDefaultsForField(field, level, makeFinal); } return true; } - public void setFieldDefaultsForField(JavacNode fieldNode, DiagnosticPosition pos, AccessLevel level, boolean makeFinal) { + public void setFieldDefaultsForField(JavacNode fieldNode, AccessLevel level, boolean makeFinal) { JCVariableDecl field = (JCVariableDecl) fieldNode.get(); if (level != null && level != AccessLevel.NONE) { if ((field.mods.flags & (Flags.PUBLIC | Flags.PRIVATE | Flags.PROTECTED)) == 0) { 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)) { - field.mods.flags |= Flags.FINAL; + if ((field.mods.flags & Flags.STATIC) == 0) { + field.mods.flags |= Flags.FINAL; + } } } fieldNode.rebuild(); } - @Override public void handle(AnnotationValues<FieldDefaults> annotation, JCAnnotation ast, JavacNode annotationNode) { - handleExperimentalFlagUsage(annotationNode, ConfigurationKeys.FIELD_DEFAULTS_FLAG_USAGE, "@FieldDefaults"); - - deleteAnnotationIfNeccessary(annotationNode, FieldDefaults.class); - deleteImportFromCompilationUnit(annotationNode, "lombok.AccessLevel"); - JavacNode node = annotationNode.up(); - FieldDefaults instance = annotation.getInstance(); - AccessLevel level = instance.level(); - boolean makeFinal = instance.makeFinal(); + @Override public void visitType(JavacNode typeNode, JCClassDecl type) { + AnnotationValues<FieldDefaults> fieldDefaults = null; + JavacNode source = typeNode; - if (level == AccessLevel.NONE && !makeFinal) { - annotationNode.addError("This does nothing; provide either level or makeFinal or both."); - return; + boolean levelIsExplicit = false; + boolean makeFinalIsExplicit = false; + FieldDefaults fd = null; + for (JavacNode jn : typeNode.down()) { + if (jn.getKind() != Kind.ANNOTATION) continue; + JCAnnotation ann = (JCAnnotation) jn.get(); + JCTree typeTree = ann.annotationType; + if (typeTree == null) continue; + String typeTreeToString = typeTree.toString(); + if (!typeTreeToString.equals("FieldDefaults") && !typeTreeToString.equals("lombok.experimental.FieldDefaults")) continue; + if (!typeMatches(FieldDefaults.class, jn, typeTree)) continue; + + source = jn; + fieldDefaults = createAnnotation(FieldDefaults.class, jn); + levelIsExplicit = fieldDefaults.isExplicit("level"); + makeFinalIsExplicit = fieldDefaults.isExplicit("makeFinal"); + + handleExperimentalFlagUsage(jn, ConfigurationKeys.FIELD_DEFAULTS_FLAG_USAGE, "@FieldDefaults"); + + fd = fieldDefaults.getInstance(); + if (!levelIsExplicit && !makeFinalIsExplicit) { + jn.addError("This does nothing; provide either level or makeFinal or both."); + } + + if (levelIsExplicit && fd.level() == AccessLevel.NONE) { + jn.addError("AccessLevel.NONE doesn't mean anything here. Pick another value."); + levelIsExplicit = false; + } + + deleteAnnotationIfNeccessary(jn, FieldDefaults.class); + deleteImportFromCompilationUnit(jn, "lombok.AccessLevel"); + break; } - if (level == AccessLevel.PACKAGE) { - annotationNode.addError("Setting 'level' to PACKAGE does nothing. To force fields as package private, use the @PackagePrivate annotation on the field."); - } + if (fd == null && (type.mods.flags & (Flags.INTERFACE | Flags.ANNOTATION)) != 0) return; - if (!makeFinal && annotation.isExplicit("makeFinal")) { - annotationNode.addError("Setting 'makeFinal' to false does nothing. To force fields to be non-final, use the @NonFinal annotation on the field."); - } + boolean defaultToPrivate = levelIsExplicit ? false : Boolean.TRUE.equals(typeNode.getAst().readConfiguration(ConfigurationKeys.FIELD_DEFAULTS_PRIVATE_EVERYWHERE)); + boolean defaultToFinal = makeFinalIsExplicit ? false : Boolean.TRUE.equals(typeNode.getAst().readConfiguration(ConfigurationKeys.FIELD_DEFAULTS_FINAL_EVERYWHERE)); - if (node == null) return; + if (!defaultToPrivate && !defaultToFinal && fieldDefaults == null) return; + AccessLevel fdAccessLevel = (fieldDefaults != null && levelIsExplicit) ? fd.level() : defaultToPrivate ? AccessLevel.PRIVATE : null; + boolean fdToFinal = (fieldDefaults != null && makeFinalIsExplicit) ? fd.makeFinal() : defaultToFinal; - generateFieldDefaultsForType(node, annotationNode, level, makeFinal, false); + generateFieldDefaultsForType(typeNode, source, fdAccessLevel, fdToFinal, false); } } diff --git a/src/core/lombok/javac/handlers/HandleGetter.java b/src/core/lombok/javac/handlers/HandleGetter.java index a330dbc1..0540465d 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 @@ -46,6 +46,7 @@ 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; @@ -94,7 +95,7 @@ public class HandleGetter extends JavacAnnotationHandler<Getter> { } } - 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 $ @@ -146,7 +147,7 @@ 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: @@ -182,6 +183,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 +220,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/HandleHelper.java b/src/core/lombok/javac/handlers/HandleHelper.java new file mode 100644 index 00000000..09ace4d6 --- /dev/null +++ b/src/core/lombok/javac/handlers/HandleHelper.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2015-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 + * 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.handlers.JavacHandlerUtil.*; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; + +import org.mangosdk.spi.ProviderFor; + +import com.sun.source.tree.MethodInvocationTree; +import com.sun.source.tree.TreeVisitor; +import com.sun.source.util.TreeScanner; +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.JCCase; +import com.sun.tools.javac.tree.JCTree.JCClassDecl; +import com.sun.tools.javac.tree.JCTree.JCExpression; +import com.sun.tools.javac.tree.JCTree.JCIdent; +import com.sun.tools.javac.tree.JCTree.JCMethodInvocation; +import com.sun.tools.javac.tree.JCTree.JCStatement; +import com.sun.tools.javac.tree.JCTree.JCVariableDecl; +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.core.AST.Kind; +import lombok.core.AnnotationValues; +import lombok.experimental.Helper; +import lombok.javac.JavacAnnotationHandler; +import lombok.javac.JavacNode; +import lombok.javac.JavacTreeMaker; + +@ProviderFor(JavacAnnotationHandler.class) +public class HandleHelper extends JavacAnnotationHandler<Helper> { + private List<JCStatement> getStatementsFromJcNode(JCTree tree) { + if (tree instanceof JCBlock) return ((JCBlock) tree).stats; + if (tree instanceof JCCase) return ((JCCase) tree).stats; + return null; + } + + private void setStatementsOfJcNode(JCTree tree, List<JCStatement> statements) { + if (tree instanceof JCBlock) ((JCBlock) tree).stats = statements; + else if (tree instanceof JCCase) ((JCCase) tree).stats = statements; + else throw new IllegalArgumentException("Can't set statements on node type: " + tree.getClass()); + } + + @Override public void handle(AnnotationValues<Helper> annotation, JCAnnotation ast, JavacNode annotationNode) { + handleExperimentalFlagUsage(annotationNode, ConfigurationKeys.HELPER_FLAG_USAGE, "@Helper"); + + deleteAnnotationIfNeccessary(annotationNode, Helper.class); + JavacNode annotatedType = annotationNode.up(); + JavacNode containingBlock = annotatedType == null ? null : annotatedType.directUp(); + List<JCStatement> origStatements = getStatementsFromJcNode(containingBlock == null ? null : containingBlock.get()); + + if (annotatedType == null || annotatedType.getKind() != Kind.TYPE || origStatements == null) { + annotationNode.addError("@Helper is legal only on method-local classes."); + return; + } + + JCClassDecl annotatedType_ = (JCClassDecl) annotatedType.get(); + Iterator<JCStatement> it = origStatements.iterator(); + while (it.hasNext()) { + if (it.next() == annotatedType_) { + break; + } + } + + java.util.List<String> knownMethodNames = new ArrayList<String>(); + + for (JavacNode ch : annotatedType.down()) { + if (ch.getKind() != Kind.METHOD) continue; + String n = ch.getName(); + if (n == null || n.isEmpty() || n.charAt(0) == '<') continue; + knownMethodNames.add(n); + } + + Collections.sort(knownMethodNames); + final String[] knownMethodNames_ = knownMethodNames.toArray(new String[knownMethodNames.size()]); + + final Name helperName = annotationNode.toName("$" + annotatedType_.name); + final boolean[] helperUsed = new boolean[1]; + final JavacTreeMaker maker = annotationNode.getTreeMaker(); + + TreeVisitor<Void, Void> visitor = new TreeScanner<Void, Void>() { + @Override public Void visitMethodInvocation(MethodInvocationTree node, Void p) { + JCMethodInvocation jcmi = (JCMethodInvocation) node; + apply(jcmi); + return super.visitMethodInvocation(node, p); + } + + private void apply(JCMethodInvocation jcmi) { + if (!(jcmi.meth instanceof JCIdent)) return; + JCIdent jci = (JCIdent) jcmi.meth; + if (Arrays.binarySearch(knownMethodNames_, jci.name.toString()) < 0) return; + jcmi.meth = maker.Select(maker.Ident(helperName), jci.name); + helperUsed[0] = true; + } + }; + + while (it.hasNext()) { + JCStatement stat = it.next(); + stat.accept(visitor, null); + } + + if (!helperUsed[0]) { + annotationNode.addWarning("No methods of this helper class are ever used."); + return; + } + + ListBuffer<JCStatement> newStatements = new ListBuffer<JCStatement>(); + + boolean mark = false; + for (JCStatement stat : origStatements) { + newStatements.append(stat); + if (mark || stat != annotatedType_) continue; + mark = true; + JCExpression init = maker.NewClass(null, List.<JCExpression>nil(), maker.Ident(annotatedType_.name), List.<JCExpression>nil(), null); + JCExpression varType = maker.Ident(annotatedType_.name); + JCVariableDecl decl = maker.VarDef(maker.Modifiers(Flags.FINAL), helperName, varType, init); + newStatements.append(decl); + } + setStatementsOfJcNode(containingBlock.get(), newStatements.toList()); + } +} diff --git a/src/core/lombok/javac/handlers/HandleLog.java b/src/core/lombok/javac/handlers/HandleLog.java index ee7268f7..d0d709e3 100644 --- a/src/core/lombok/javac/handlers/HandleLog.java +++ b/src/core/lombok/javac/handlers/HandleLog.java @@ -105,7 +105,7 @@ public class HandleLog { maker.Modifiers(Flags.PRIVATE | Flags.FINAL | (useStatic ? Flags.STATIC : 0)), typeNode.toName(logFieldName), loggerType, factoryMethodCall), source, typeNode.getContext()); - injectFieldSuppressWarnings(typeNode, fieldDecl); + injectFieldAndMarkGenerated(typeNode, fieldDecl); return true; } @@ -175,6 +175,17 @@ public class HandleLog { } } + /** + * Handles the {@link lombok.extern.jbosslog.JBossLog} annotation for javac. + */ + @ProviderFor(JavacAnnotationHandler.class) + public static class HandleJBossLog extends JavacAnnotationHandler<lombok.extern.jbosslog.JBossLog> { + @Override public void handle(AnnotationValues<lombok.extern.jbosslog.JBossLog> annotation, JCAnnotation ast, JavacNode annotationNode) { + handleFlagUsage(annotationNode, ConfigurationKeys.LOG_JBOSSLOG_FLAG_USAGE, "@JBossLog", ConfigurationKeys.LOG_ANY_FLAG_USAGE, "any @Log"); + processAnnotation(LoggingFramework.JBOSSLOG, annotation, annotationNode, annotation.getInstance().topic()); + } + } + 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"), @@ -200,6 +211,8 @@ public class HandleLog { // private static final org.slf4j.ext.XLogger log = org.slf4j.ext.XLoggerFactory.getXLogger(TargetType.class); 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") ; private final Class<? extends Annotation> annotationClass; diff --git a/src/core/lombok/javac/handlers/HandleNonNull.java b/src/core/lombok/javac/handlers/HandleNonNull.java index cd8e3402..81aa1525 100644 --- a/src/core/lombok/javac/handlers/HandleNonNull.java +++ b/src/core/lombok/javac/handlers/HandleNonNull.java @@ -85,7 +85,7 @@ public class HandleNonNull extends JavacAnnotationHandler<NonNull> { } if (declaration.body == null) { - annotationNode.addWarning("@NonNull is meaningless on a parameter of an abstract method."); + // This used to be a warning, but as @NonNull also has a documentary purpose, better to not warn about this. Since 1.16.7 return; } @@ -141,6 +141,7 @@ public class HandleNonNull extends JavacAnnotationHandler<NonNull> { List<JCStatement> newList = tail.prepend(nullCheck); for (JCStatement stat : head) newList = newList.prepend(stat); declaration.body.stats = newList; + annotationNode.getAst().setChanged(); } public boolean isNullCheck(JCStatement stat) { diff --git a/src/core/lombok/javac/handlers/HandlePrintAST.java b/src/core/lombok/javac/handlers/HandlePrintAST.java index 9a52b9d9..0826d1d1 100644 --- a/src/core/lombok/javac/handlers/HandlePrintAST.java +++ b/src/core/lombok/javac/handlers/HandlePrintAST.java @@ -59,7 +59,7 @@ public class HandlePrintAST extends JavacAnnotationHandler<PrintAST> { try { stream.close(); } catch (Exception e) { - Lombok.sneakyThrow(e); + throw Lombok.sneakyThrow(e); } } } diff --git a/src/core/lombok/javac/handlers/HandleSetter.java b/src/core/lombok/javac/handlers/HandleSetter.java index 3c4329b2..1453aa27 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 @@ -41,6 +41,8 @@ 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; @@ -127,8 +129,8 @@ 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: @@ -154,7 +156,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 +190,26 @@ 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, 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, boolean shouldReturnThis, JavacNode source, List<JCAnnotation> onMethod, List<JCAnnotation> onParam) { if (setterName == null) return null; JCVariableDecl fieldDecl = (JCVariableDecl) field.get(); @@ -223,6 +235,11 @@ public class HandleSetter extends JavacAnnotationHandler<Setter> { statements.append(treeMaker.Exec(assign)); } + if (booleanFieldToSet != null) { + JCAssign setBool = treeMaker.Assign(treeMaker.Ident(booleanFieldToSet), treeMaker.Literal(CTC_BOOLEAN, 1)); + statements.append(treeMaker.Exec(setBool)); + } + JCExpression methodType = null; if (shouldReturnThis) { methodType = cloneSelfType(field); @@ -230,7 +247,7 @@ public class HandleSetter extends JavacAnnotationHandler<Setter> { 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)); + methodType = treeMaker.Type(Javac.createVoidType(field.getSymbolTable(), CTC_VOID)); shouldReturnThis = false; } @@ -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/HandleSynchronized.java b/src/core/lombok/javac/handlers/HandleSynchronized.java index fb6678e6..20e85d7e 100644 --- a/src/core/lombok/javac/handlers/HandleSynchronized.java +++ b/src/core/lombok/javac/handlers/HandleSynchronized.java @@ -99,7 +99,7 @@ public class HandleSynchronized extends JavacAnnotationHandler<Synchronized> { JCVariableDecl fieldDecl = recursiveSetGeneratedBy(maker.VarDef( maker.Modifiers(Flags.PRIVATE | Flags.FINAL | (isStatic ? Flags.STATIC : 0)), methodNode.toName(lockName), objectType, newObjectArray), ast, context); - injectFieldSuppressWarnings(methodNode.up(), fieldDecl); + injectFieldAndMarkGenerated(methodNode.up(), fieldDecl); } if (method.body == null) return; diff --git a/src/core/lombok/javac/handlers/HandleToString.java b/src/core/lombok/javac/handlers/HandleToString.java index 743e7b26..897d5f2c 100644 --- a/src/core/lombok/javac/handlers/HandleToString.java +++ b/src/core/lombok/javac/handlers/HandleToString.java @@ -117,7 +117,11 @@ public class HandleToString extends JavacAnnotationHandler<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); + + Boolean doNotUseGettersConfiguration = typeNode.getAst().readConfiguration(ConfigurationKeys.TO_STRING_DO_NOT_USE_GETTERS); + FieldAccess access = doNotUseGettersConfiguration == null || !doNotUseGettersConfiguration ? FieldAccess.GETTER : FieldAccess.PREFER_FIELD; + + generateToString(typeNode, errorNode, null, null, includeFieldNames, null, false, access); } public void generateToString(JavacNode typeNode, JavacNode source, List<String> excludes, List<String> includes, diff --git a/src/core/lombok/javac/handlers/HandleUtilityClass.java b/src/core/lombok/javac/handlers/HandleUtilityClass.java new file mode 100644 index 00000000..ee8081d6 --- /dev/null +++ b/src/core/lombok/javac/handlers/HandleUtilityClass.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2015 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.Javac.CTC_VOID; +import static lombok.javac.handlers.JavacHandlerUtil.*; + +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; +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.JCTypeParameter; +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) { + handleExperimentalFlagUsage(annotationNode, ConfigurationKeys.UTILITY_CLASS_FLAG_USAGE, "@UtilityClass"); + + deleteAnnotationIfNeccessary(annotationNode, UtilityClass.class); + + JavacNode typeNode = annotationNode.up(); + if (!checkLegality(typeNode, annotationNode)) return; + changeModifiersAndGenerateConstructor(annotationNode.up(), annotationNode); + } + + private static boolean checkLegality(JavacNode typeNode, JavacNode errorNode) { + 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 | Flags.ENUM)) != 0; + + if (typeDecl == null || notAClass) { + errorNode.addError("@UtilityClass is only supported on a class (can't be an interface, enum, or annotation)."); + return false; + } + + // It might be an inner class. This is okay, but only if it is / can be a static inner class. Thus, all of its parents have to be static inner classes until the top-level. + JavacNode typeWalk = typeNode; + while (true) { + typeWalk = typeWalk.up(); + switch (typeWalk.getKind()) { + case TYPE: + JCClassDecl typeDef = (JCClassDecl) typeWalk.get(); + if ((typeDef.mods.flags & (Flags.STATIC | Flags.ANNOTATION | Flags.ENUM | Flags.INTERFACE)) != 0) continue; + if (typeWalk.up().getKind() == Kind.COMPILATION_UNIT) return true; + errorNode.addError("@UtilityClass automatically makes the class static, however, this class cannot be made static."); + return false; + case COMPILATION_UNIT: + return true; + default: + errorNode.addError("@UtilityClass cannot be placed on a method local or anonymous inner class, or any class nested in such a class."); + return false; + } + } + } + + private void changeModifiersAndGenerateConstructor(JavacNode typeNode, JavacNode errorNode) { + JCClassDecl classDecl = (JCClassDecl) typeNode.get(); + + boolean makeConstructor = true; + + classDecl.mods.flags |= Flags.FINAL; + + boolean markStatic = true; + + if (typeNode.up().getKind() == Kind.COMPILATION_UNIT) markStatic = false; + if (markStatic && typeNode.up().getKind() == Kind.TYPE) { + JCClassDecl typeDecl = (JCClassDecl) typeNode.up().get(); + if ((typeDecl.mods.flags & Flags.INTERFACE) != 0) markStatic = false; + } + + if (markStatic) classDecl.mods.flags |= Flags.STATIC; + + for (JavacNode element : typeNode.down()) { + if (element.getKind() == Kind.FIELD) { + JCVariableDecl fieldDecl = (JCVariableDecl) element.get(); + fieldDecl.mods.flags |= Flags.STATIC; + } else if (element.getKind() == Kind.METHOD) { + JCMethodDecl methodDecl = (JCMethodDecl) element.get(); + if (methodDecl.name.contentEquals("<init>")) { + if (getGeneratedBy(methodDecl) == null && (methodDecl.mods.flags & Flags.GENERATEDCONSTR) == 0) { + element.addError("@UtilityClasses cannot have declared constructors."); + makeConstructor = false; + continue; + } + } + + methodDecl.mods.flags |= Flags.STATIC; + } else if (element.getKind() == Kind.TYPE) { + JCClassDecl innerClassDecl = (JCClassDecl) element.get(); + innerClassDecl.mods.flags |= Flags.STATIC; + } + } + + if (makeConstructor) createPrivateDefaultConstructor(typeNode); + } + + private void createPrivateDefaultConstructor(JavacNode typeNode) { + JavacTreeMaker maker = typeNode.getTreeMaker(); + JCModifiers mods = maker.Modifiers(Flags.PRIVATE, List.<JCAnnotation>nil()); + + Name name = typeNode.toName("<init>"); + 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, List.<Type>nil(), Javac.createVoidType(typeNode.getSymbolTable(), CTC_VOID)); + } + + private List<JCStatement> createThrowStatement(JavacNode typeNode, JavacTreeMaker maker) { + JCExpression exceptionType = genJavaLangTypeRef(typeNode, "UnsupportedOperationException"); + List<JCExpression> jceBlank = List.nil(); + JCExpression message = maker.Literal("This is a utility class and cannot be instantiated"); + JCExpression exceptionInstance = maker.NewClass(null, jceBlank, exceptionType, List.of(message), null); + JCStatement throwStatement = maker.Throw(exceptionInstance); + return List.of(throwStatement); + } +} diff --git a/src/core/lombok/javac/handlers/HandleVal.java b/src/core/lombok/javac/handlers/HandleVal.java index 0230e1b0..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-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,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; @@ -35,12 +36,14 @@ import lombok.javac.ResolutionResetNeeded; import org.mangosdk.spi.ProviderFor; import com.sun.tools.javac.code.Flags; +import com.sun.tools.javac.code.Symtab; 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.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; @@ -49,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) { - if (local.vartype == null || (!local.vartype.toString().equals("val") && !local.vartype.toString().equals("lombok.val"))) return; - - JCTree source = local.vartype; + + 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 (!typeMatches(val.class, localNode, local.vartype)) return; + 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; - handleFlagUsage(localNode, ConfigurationKeys.VAL_FLAG_USAGE, "val"); + 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) { @@ -72,22 +90,27 @@ 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()), source, localNode.getContext()); + JCAnnotation valAnnotation = recursiveSetGeneratedBy(localNode.getTreeMaker().Annotation(local.vartype, List.<JCExpression>nil()), typeTree, localNode.getContext()); local.mods.annotations = local.mods.annotations == null ? List.of(valAnnotation) : local.mods.annotations.append(valAnnotation); } @@ -101,15 +124,28 @@ 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 { type = local.init.type; + if (type.isErroneous()) { + try { + JavacResolution resolver = new JavacResolution(localNode.getContext()); + 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 + "(" + localNode.getFileName() + ")"); + throw e; + } + } } } else { if (rhsOfEnhancedForLoop.type == null) { @@ -138,14 +174,14 @@ 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) { local.vartype = JavacResolution.createJavaLangObject(localNode.getAst()); throw e; } finally { - recursiveSetGeneratedBy(local.vartype, source, localNode.getContext()); + recursiveSetGeneratedBy(local.vartype, typeTree, localNode.getContext()); } } } diff --git a/src/core/lombok/javac/handlers/HandleValue.java b/src/core/lombok/javac/handlers/HandleValue.java index 90f6a98d..d1af4168 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; @@ -49,13 +47,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 +74,10 @@ 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); + handleGetter.generateGetterForType(typeNode, annotationNode, AccessLevel.PUBLIC, true); + 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 8a7b49b2..987a3d34 100644 --- a/src/core/lombok/javac/handlers/HandleWither.java +++ b/src/core/lombok/javac/handlers/HandleWither.java @@ -41,6 +41,8 @@ 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 +131,8 @@ public class HandleWither extends JavacAnnotationHandler<Wither> { 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", "@Wither(onMethod", annotationNode); + List<JCAnnotation> onParam = unboxAndRemoveAnnotationParameter(ast, "onParam", "@Wither(onParam", annotationNode); switch (node.getKind()) { case FIELD: @@ -150,7 +152,10 @@ public class HandleWither extends JavacAnnotationHandler<Wither> { } } - public void createWitherForField(AccessLevel level, JavacNode fieldNode, JavacNode source, boolean whineIfExists, List<JCAnnotation> onMethod, List<JCAnnotation> onParam) { + public void createWitherForField(AccessLevel level, JavacNode fieldNode, JavacNode source, boolean strictMode, List<JCAnnotation> onMethod, List<JCAnnotation> onParam) { + JavacNode typeNode = fieldNode.up(); + boolean makeAbstract = typeNode != null && typeNode.getKind() == Kind.TYPE && (((JCClassDecl) typeNode.get()).mods.flags & Flags.ABSTRACT) != 0; + if (fieldNode.getKind() != Kind.FIELD) { fieldNode.addError("@Wither is only supported on a class or a field."); return; @@ -165,17 +170,23 @@ public class HandleWither extends JavacAnnotationHandler<Wither> { } if ((fieldDecl.mods.flags & Flags.STATIC) != 0) { - fieldNode.addWarning("Not generating wither for this field: Withers cannot be generated for static fields."); + if (strictMode) { + fieldNode.addWarning("Not generating wither for this field: Withers cannot be generated for static fields."); + } return; } if ((fieldDecl.mods.flags & Flags.FINAL) != 0 && fieldDecl.init != null) { - fieldNode.addWarning("Not generating wither for this field: Withers cannot be generated for final, initialized fields."); + if (strictMode) { + fieldNode.addWarning("Not generating wither for this field: Withers cannot be generated for final, initialized fields."); + } return; } if (fieldDecl.name.toString().startsWith("$")) { - fieldNode.addWarning("Not generating wither for this field: Withers cannot be generated for fields starting with $."); + if (strictMode) { + fieldNode.addWarning("Not generating wither for this field: Withers cannot be generated for fields starting with $."); + } return; } @@ -184,7 +195,7 @@ public class HandleWither extends JavacAnnotationHandler<Wither> { case EXISTS_BY_LOMBOK: return; case EXISTS_BY_USER: - if (whineIfExists) { + if (strictMode) { String altNameExpl = ""; if (!altName.equals(methodName)) altNameExpl = String.format(" (%s)", altName); fieldNode.addWarning( @@ -199,63 +210,71 @@ public class HandleWither extends JavacAnnotationHandler<Wither> { long access = toJavacModifier(level); - JCMethodDecl createdWither = createWither(access, fieldNode, fieldNode.getTreeMaker(), source, onMethod, onParam); - injectMethod(fieldNode.up(), createdWither); + JCMethodDecl createdWither = createWither(access, fieldNode, fieldNode.getTreeMaker(), source, onMethod, onParam, makeAbstract); + 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) { + public JCMethodDecl createWither(long access, JavacNode field, JavacTreeMaker maker, JavacNode source, List<JCAnnotation> onMethod, List<JCAnnotation> onParam, boolean makeAbstract) { String witherName = toWitherName(field); if (witherName == null) return null; JCVariableDecl fieldDecl = (JCVariableDecl) field.get(); - ListBuffer<JCStatement> statements = new ListBuffer<JCStatement>(); List<JCAnnotation> nonNulls = findAnnotations(field, NON_NULL_PATTERN); List<JCAnnotation> nullables = findAnnotations(field, NULLABLE_PATTERN); Name methodName = field.toName(witherName); - List<JCAnnotation> annsOnParam = copyAnnotations(onParam).appendList(nonNulls).appendList(nullables); + JCExpression returnType = cloneSelfType(field); + + JCBlock methodBody = null; long flags = JavacHandlerUtil.addFinalIfNeeded(Flags.PARAMETER, field.getContext()); - JCVariableDecl param = maker.VarDef(maker.Modifiers(flags, annsOnParam), fieldDecl.name, fieldDecl.vartype, null); + List<JCAnnotation> annsOnParam = copyAnnotations(onParam).appendList(nonNulls).appendList(nullables); - JCExpression selfType = cloneSelfType(field); - if (selfType == null) return null; + JCVariableDecl param = maker.VarDef(maker.Modifiers(flags, annsOnParam), fieldDecl.name, fieldDecl.vartype, null); - ListBuffer<JCExpression> args = new ListBuffer<JCExpression>(); - for (JavacNode child : field.up().down()) { - if (child.getKind() != Kind.FIELD) continue; - JCVariableDecl childDecl = (JCVariableDecl) child.get(); - // Skip fields that start with $ - if (childDecl.name.toString().startsWith("$")) continue; - long fieldFlags = childDecl.mods.flags; - // Skip static fields. - if ((fieldFlags & Flags.STATIC) != 0) continue; - // Skip initialized final fields. - if (((fieldFlags & Flags.FINAL) != 0) && childDecl.init != null) continue; - if (child.get() == field.get()) { - args.append(maker.Ident(fieldDecl.name)); + if (!makeAbstract) { + ListBuffer<JCStatement> statements = new ListBuffer<JCStatement>(); + + JCExpression selfType = cloneSelfType(field); + if (selfType == null) return null; + + ListBuffer<JCExpression> args = new ListBuffer<JCExpression>(); + for (JavacNode child : field.up().down()) { + if (child.getKind() != Kind.FIELD) continue; + JCVariableDecl childDecl = (JCVariableDecl) child.get(); + // Skip fields that start with $ + if (childDecl.name.toString().startsWith("$")) continue; + long fieldFlags = childDecl.mods.flags; + // Skip static fields. + if ((fieldFlags & Flags.STATIC) != 0) continue; + // Skip initialized final fields. + if (((fieldFlags & Flags.FINAL) != 0) && childDecl.init != null) continue; + if (child.get() == field.get()) { + args.append(maker.Ident(fieldDecl.name)); + } else { + args.append(createFieldAccessor(maker, child, FieldAccess.ALWAYS_FIELD)); + } + } + + JCNewClass newClass = maker.NewClass(null, List.<JCExpression>nil(), selfType, args.toList(), null); + JCExpression identityCheck = maker.Binary(CTC_EQUAL, createFieldAccessor(maker, field, FieldAccess.ALWAYS_FIELD), maker.Ident(fieldDecl.name)); + JCConditional conditional = maker.Conditional(identityCheck, maker.Ident(field.toName("this")), newClass); + JCReturn returnStatement = maker.Return(conditional); + + if (nonNulls.isEmpty()) { + statements.append(returnStatement); } else { - args.append(createFieldAccessor(maker, child, FieldAccess.ALWAYS_FIELD)); + JCStatement nullCheck = generateNullCheck(maker, field, source); + if (nullCheck != null) statements.append(nullCheck); + statements.append(returnStatement); } + + methodBody = maker.Block(0, statements.toList()); } - - JCNewClass newClass = maker.NewClass(null, List.<JCExpression>nil(), selfType, args.toList(), null); - JCExpression identityCheck = maker.Binary(CTC_EQUAL, createFieldAccessor(maker, field, FieldAccess.ALWAYS_FIELD), maker.Ident(fieldDecl.name)); - JCConditional conditional = maker.Conditional(identityCheck, maker.Ident(field.toName("this")), newClass); - JCReturn returnStatement = maker.Return(conditional); - - if (nonNulls.isEmpty()) { - statements.append(returnStatement); - } else { - JCStatement nullCheck = generateNullCheck(maker, field, source); - if (nullCheck != null) statements.append(nullCheck); - statements.append(returnStatement); - } - - JCExpression returnType = cloneSelfType(field); - - JCBlock methodBody = maker.Block(0, statements.toList()); List<JCTypeParameter> methodGenericParams = List.nil(); List<JCVariableDecl> parameters = List.of(param); List<JCExpression> throwsClauses = List.nil(); @@ -266,6 +285,7 @@ public class HandleWither extends JavacAnnotationHandler<Wither> { if (isFieldDeprecated(field)) { annsOnMethod = annsOnMethod.prepend(maker.Annotation(genJavaLangTypeRef(field, "Deprecated"), List.<JCExpression>nil())); } + if (makeAbstract) access = access | Flags.ABSTRACT; JCMethodDecl decl = recursiveSetGeneratedBy(maker.MethodDef(maker.Modifiers(access, annsOnMethod), methodName, returnType, methodGenericParams, parameters, throwsClauses, methodBody, annotationMethodDefaultValue), source.get(), field.getContext()); copyJavadoc(field, decl, CopyJavadoc.WITHER); diff --git a/src/core/lombok/javac/handlers/JavacHandlerUtil.java b/src/core/lombok/javac/handlers/JavacHandlerUtil.java index 6413e8ef..65d09a9a 100644 --- a/src/core/lombok/javac/handlers/JavacHandlerUtil.java +++ b/src/core/lombok/javac/handlers/JavacHandlerUtil.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,11 +21,13 @@ */ package lombok.javac.handlers; +import static com.sun.tools.javac.code.Flags.GENERATEDCONSTR; import static lombok.core.handlers.HandlerUtil.*; import static lombok.javac.Javac.*; import static lombok.javac.JavacAugments.JCTree_generatedNode; import java.lang.annotation.Annotation; +import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; @@ -35,12 +37,15 @@ 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; import lombok.Getter; import lombok.core.AST.Kind; import lombok.core.AnnotationValues; +import lombok.core.LombokImmutableList; import lombok.core.AnnotationValues.AnnotationValue; import lombok.core.TypeResolver; import lombok.core.configuration.NullCheckExceptionType; @@ -54,6 +59,14 @@ import lombok.javac.JavacTreeMaker; 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; @@ -68,7 +81,6 @@ import com.sun.tools.javac.tree.JCTree.JCExpressionStatement; import com.sun.tools.javac.tree.JCTree.JCFieldAccess; import com.sun.tools.javac.tree.JCTree.JCIdent; import com.sun.tools.javac.tree.JCTree.JCImport; -import com.sun.tools.javac.tree.JCTree.JCLiteral; import com.sun.tools.javac.tree.JCTree.JCMethodDecl; import com.sun.tools.javac.tree.JCTree.JCMethodInvocation; import com.sun.tools.javac.tree.JCTree.JCModifiers; @@ -80,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; @@ -149,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); } @@ -178,6 +195,48 @@ 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; + } + } + + 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. * @@ -190,6 +249,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. @@ -197,10 +267,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); } /** @@ -209,6 +290,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; @@ -242,51 +324,45 @@ public class JavacHandlerUtil { Map<String, AnnotationValue> values = new HashMap<String, AnnotationValue>(); JCAnnotation anno = (JCAnnotation) node.get(); List<JCExpression> arguments = anno.getArguments(); - for (Method m : type.getDeclaredMethods()) { - if (!Modifier.isPublic(m.getModifiers())) continue; - String name = m.getName(); + + for (JCExpression arg : arguments) { + String mName; + JCExpression rhs; java.util.List<String> raws = new ArrayList<String>(); java.util.List<Object> guesses = new ArrayList<Object>(); java.util.List<Object> expressions = new ArrayList<Object>(); final java.util.List<DiagnosticPosition> positions = new ArrayList<DiagnosticPosition>(); - boolean isExplicit = false; - for (JCExpression arg : arguments) { - String mName; - JCExpression rhs; - - if (arg instanceof JCAssign) { - JCAssign assign = (JCAssign) arg; - mName = assign.lhs.toString(); - rhs = assign.rhs; - } else { - rhs = arg; - mName = "value"; - } - - if (!mName.equals(name)) continue; - isExplicit = true; - if (rhs instanceof JCNewArray) { - List<JCExpression> elems = ((JCNewArray)rhs).elems; - for (JCExpression inner : elems) { - raws.add(inner.toString()); - expressions.add(inner); - guesses.add(calculateGuess(inner)); - positions.add(inner.pos()); - } - } else { - raws.add(rhs.toString()); - expressions.add(rhs); - guesses.add(calculateGuess(rhs)); - positions.add(rhs.pos()); + if (arg instanceof JCAssign) { + JCAssign assign = (JCAssign) arg; + mName = assign.lhs.toString(); + rhs = assign.rhs; + } else { + rhs = arg; + mName = "value"; + } + + if (rhs instanceof JCNewArray) { + List<JCExpression> elems = ((JCNewArray)rhs).elems; + for (JCExpression inner : elems) { + raws.add(inner.toString()); + expressions.add(inner); + guesses.add(calculateGuess(inner)); + positions.add(inner.pos()); } + } else { + raws.add(rhs.toString()); + expressions.add(rhs); + guesses.add(calculateGuess(rhs)); + positions.add(rhs.pos()); } - values.put(name, new AnnotationValue(node, raws, expressions, guesses, isExplicit) { + values.put(mName, new AnnotationValue(node, raws, expressions, guesses, true) { @Override public void setError(String message, int valueIdx) { if (valueIdx < 0) node.addError(message); else node.addError(message, positions.get(valueIdx)); } + @Override public void setWarning(String message, int valueIdx) { if (valueIdx < 0) node.addWarning(message); else node.addWarning(message, positions.get(valueIdx)); @@ -294,6 +370,21 @@ public class JavacHandlerUtil { }); } + for (Method m : type.getDeclaredMethods()) { + if (!Modifier.isPublic(m.getModifiers())) continue; + String name = m.getName(); + if (!values.containsKey(name)) { + values.put(name, new AnnotationValue(node, new ArrayList<String>(), new ArrayList<Object>(), new ArrayList<Object>(), false) { + @Override public void setError(String message, int valueIdx) { + node.addError(message); + } + @Override public void setWarning(String message, int valueIdx) { + node.addWarning(message); + } + }); + } + } + return new AnnotationValues<A>(type, values, node); } @@ -302,8 +393,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); } @@ -312,12 +402,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()); } - private static void deleteAnnotationIfNeccessary0(JavacNode annotation, Class<? extends Annotation>... annotationTypes) { + /** + * 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, String... annotationTypes) { if (inNetbeansEditor(annotation)) return; if (!annotation.shouldDeleteLombokAnnotations()) return; JavacNode parentNode = annotation.directUp(); @@ -346,8 +453,8 @@ public class JavacHandlerUtil { } parentNode.getAst().setChanged(); - for (Class<?> annotationType : annotationTypes) { - deleteImportFromCompilationUnit(annotation, annotationType.getName()); + for (String annotationType : annotationTypes) { + deleteImportFromCompilationUnit(annotation, annotationType); } } @@ -445,9 +552,9 @@ public class JavacHandlerUtil { return HandlerUtil.shouldReturnThis0(accessors, field.getAst()); } - public static JCExpression cloneSelfType(JavacNode field) { - JavacNode typeNode = field; - JavacTreeMaker maker = field.getTreeMaker(); + public static JCExpression cloneSelfType(JavacNode childOfType) { + JavacNode typeNode = childOfType; + JavacTreeMaker maker = childOfType.getTreeMaker(); while (typeNode != null && typeNode.getKind() != Kind.TYPE) typeNode = typeNode.up(); if (typeNode != null && typeNode.get() instanceof JCClassDecl) { JCClassDecl type = (JCClassDecl) typeNode.get(); @@ -591,10 +698,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; } @@ -605,6 +709,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. @@ -615,15 +727,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; } } @@ -709,7 +818,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(); @@ -803,13 +912,19 @@ public class JavacHandlerUtil { 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. * Also takes care of updating the JavacAST. */ - public static void injectFieldSuppressWarnings(JavacNode typeNode, JCVariableDecl field) { - injectField(typeNode, field, true); + public static JavacNode injectFieldAndMarkGenerated(JavacNode typeNode, JCVariableDecl field) { + return injectField(typeNode, field, true); } /** @@ -821,21 +936,28 @@ public class JavacHandlerUtil { return injectField(typeNode, field, false); } - private static JavacNode injectField(JavacNode typeNode, JCVariableDecl field, boolean addSuppressWarnings) { + private static JavacNode injectField(JavacNode typeNode, JCVariableDecl field, boolean addGenerated) { JCClassDecl type = (JCClassDecl) typeNode.get(); - if (addSuppressWarnings) addSuppressWarningsAll(field.mods, typeNode, field.pos, getGeneratedBy(field), typeNode.getContext()); + if (addGenerated) { + addSuppressWarningsAll(field.mods, typeNode, field.pos, getGeneratedBy(field), typeNode.getContext()); + addGenerated(field.mods, typeNode, field.pos, getGeneratedBy(field), typeNode.getContext()); + } List<JCTree> insertAfter = null; List<JCTree> insertBefore = type.defs; - while (insertBefore.tail != null) { + while (true) { + boolean skip = false; if (insertBefore.head instanceof JCVariableDecl) { JCVariableDecl f = (JCVariableDecl) insertBefore.head; - if (isEnumConstant(f) || isGenerated(f)) { - insertAfter = insertBefore; - insertBefore = insertBefore.tail; - continue; - } + if (isEnumConstant(f) || isGenerated(f)) skip = true; + } else if (insertBefore.head instanceof JCMethodDecl) { + if ((((JCMethodDecl) insertBefore.head).mods.flags & GENERATEDCONSTR) != 0) skip = true; + } + if (skip) { + insertAfter = insertBefore; + insertBefore = insertBefore.tail; + continue; } break; } @@ -854,13 +976,56 @@ public class JavacHandlerUtil { return (field.mods.flags & Flags.ENUM) != 0; } + // jdk9 support, types have changed, names stay the same + static class ClassSymbolMembersField { + private static final Field membersField; + private static final Method removeMethod; + private static final Method enterMethod; + + static { + Field f = null; + Method r = null; + Method e = null; + try { + f = ClassSymbol.class.getField("members_field"); + r = f.getType().getMethod("remove", Symbol.class); + e = f.getType().getMethod("enter", Symbol.class); + } catch (Exception ex) {} + membersField = f; + removeMethod = r; + enterMethod = e; + } + + static void remove(ClassSymbol from, Symbol toRemove) { + if (from == null) return; + try { + Scope scope = (Scope) membersField.get(from); + if (scope == null) return; + 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); + } + /** * Adds the given new method declaration to the provided type AST Node. * Can also inject constructors. * * 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>")) { @@ -868,13 +1033,11 @@ 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); - if (type.sym != null && type.sym.members_field != null) { - type.sym.members_field.remove(((JCMethodDecl)def).sym); - } + ClassSymbolMembersField.remove(type.sym, ((JCMethodDecl)def).sym); break; } } @@ -883,11 +1046,21 @@ public class JavacHandlerUtil { } addSuppressWarningsAll(method.mods, typeNode, method.pos, getGeneratedBy(method), typeNode.getContext()); + 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. * @@ -898,6 +1071,7 @@ public class JavacHandlerUtil { public static JavacNode injectType(JavacNode typeNode, final JCClassDecl type) { JCClassDecl typeDecl = (JCClassDecl) typeNode.get(); addSuppressWarningsAll(type.mods, typeNode, type.pos, getGeneratedBy(type), typeNode.getContext()); + addGenerated(type.mods, typeNode, type.pos, getGeneratedBy(type), typeNode.getContext()); typeDecl.defs = typeDecl.defs.append(type); return typeNode.add(type, Kind.TYPE); } @@ -938,20 +1112,59 @@ public class JavacHandlerUtil { public static void addSuppressWarningsAll(JCModifiers mods, JavacNode node, int pos, JCTree source, Context context) { if (!LombokOptionsFactory.getDelombokOptions(context).getFormatPreferences().generateSuppressWarnings()) return; + addAnnotation(mods, node, pos, source, context, "java.lang.SuppressWarnings", node.getTreeMaker().Literal("all")); + + if (Boolean.TRUE.equals(node.getAst().readConfiguration(ConfigurationKeys.ADD_FINDBUGS_SUPPRESSWARNINGS_ANNOTATIONS))) { + JavacTreeMaker maker = node.getTreeMaker(); + JCExpression arg = maker.Assign(maker.Ident(node.toName("justification")), maker.Literal("generated code")); + addAnnotation(mods, node, pos, source, context, "edu.umd.cs.findbugs.annotations.SuppressFBWarnings", arg); + } + } + + public static void addGenerated(JCModifiers mods, JavacNode node, int pos, JCTree source, Context context) { + if (!LombokOptionsFactory.getDelombokOptions(context).getFormatPreferences().generateGenerated()) return; + + 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) { + boolean isJavaLangBased; + String simpleName; { + int idx = annotationTypeFqn.lastIndexOf('.'); + simpleName = idx == -1 ? annotationTypeFqn : annotationTypeFqn.substring(idx + 1); + + isJavaLangBased = idx == 9 && annotationTypeFqn.regionMatches(0, "java.lang.", 0, 10); + } + 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("SuppressWarnings")) return; + if (annType instanceof JCFieldAccess) { + if (annType.toString().equals(annotationTypeFqn)) return; + } } + JavacTreeMaker maker = node.getTreeMaker(); - JCExpression suppressWarningsType = genJavaLangTypeRef(node, "SuppressWarnings"); - JCLiteral allLiteral = maker.Literal("all"); - suppressWarningsType.pos = pos; - allLiteral.pos = pos; - JCAnnotation annotation = recursiveSetGeneratedBy(maker.Annotation(suppressWarningsType, List.<JCExpression>of(allLiteral)), source, context); + JCExpression annType = isJavaLangBased ? genJavaLangTypeRef(node, simpleName) : chainDotsString(node, annotationTypeFqn); + annType.pos = pos; + if (arg != null) { + arg.pos = pos; + if (arg instanceof JCAssign) { + ((JCAssign) arg).lhs.pos = pos; + ((JCAssign) arg).rhs.pos = pos; + } + } + List<JCExpression> argList = arg != null ? List.of(arg) : List.<JCExpression>nil(); + JCAnnotation annotation = recursiveSetGeneratedBy(maker.Annotation(annType, argList), source, context); annotation.pos = pos; mods.annotations = mods.annotations.append(annotation); } @@ -985,6 +1198,17 @@ public class JavacHandlerUtil { return chainDots(node, -1, null, null, elems); } + public static JCExpression chainDots(JavacNode node, LombokImmutableList<String> elems) { + assert elems != null; + + JavacTreeMaker maker = node.getTreeMaker(); + JCExpression e = null; + for (String elem : elems) { + if (e == null) e = maker.Ident(node.toName(elem)); + else e = maker.Select(e, node.toName(elem)); + } + return e; + } /** * In javac, dotted access of any kind, from {@code java.lang.String} to {@code var.methodName} * is represented by a fold-left of {@code Select} nodes with the leftmost string represented by @@ -1013,7 +1237,6 @@ public class JavacHandlerUtil { return e; } - /** * In javac, dotted access of any kind, from {@code java.lang.String} to {@code var.methodName} @@ -1051,16 +1274,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()); @@ -1102,19 +1334,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) { @@ -1126,6 +1348,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; @@ -1138,75 +1369,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)); } } } @@ -1429,6 +1694,18 @@ public class JavacHandlerUtil { } catch (Exception ignore) {} } + private static final Pattern FIND_RETURN = Pattern.compile("^\\s*\\**\\s*@returns?\\s+.*$", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE); + static String addReturnsThisIfNeeded(String in) { + if (FIND_RETURN.matcher(in).find()) return in; + + return addJavadocLine(in, "@return this"); + } + + static String addJavadocLine(String in, String line) { + if (in.endsWith("\n")) return in + line + "\n"; + return in + "\n" + line; + } + private static class CopyJavadoc_8 { static void copyJavadoc(JavacNode from, JCTree to, CopyJavadoc copyMode, Object dc) { DocCommentTable dct = (DocCommentTable) dc; @@ -1436,6 +1713,9 @@ public class JavacHandlerUtil { if (javadoc != null) { String[] filtered = copyMode.split(javadoc.getText()); + if (copyMode == CopyJavadoc.SETTER && shouldReturnThis(from)) { + filtered[0] = addReturnsThisIfNeeded(filtered[0]); + } dct.putComment(to, createJavadocComment(filtered[0], from)); dct.putComment(from.get(), createJavadocComment(filtered[1], from)); } @@ -1469,6 +1749,9 @@ public class JavacHandlerUtil { if (javadoc != null) { String[] filtered = copyMode.split(javadoc); + if (copyMode == CopyJavadoc.SETTER && shouldReturnThis(from)) { + filtered[0] = addReturnsThisIfNeeded(filtered[0]); + } docComments.put(to, filtered[0]); docComments.put(from.get(), filtered[1]); } diff --git a/src/core/lombok/javac/handlers/JavacSingularsRecipes.java b/src/core/lombok/javac/handlers/JavacSingularsRecipes.java new file mode 100644 index 00000000..6a76e1dd --- /dev/null +++ b/src/core/lombok/javac/handlers/JavacSingularsRecipes.java @@ -0,0 +1,310 @@ +/* + * 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 + * 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.Javac.*; +import static lombok.javac.handlers.JavacHandlerUtil.*; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import lombok.core.LombokImmutableList; +import lombok.core.SpiLoadUtil; +import lombok.core.TypeLibrary; +import lombok.javac.JavacNode; +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; +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.Name; + +public class JavacSingularsRecipes { + private static final JavacSingularsRecipes INSTANCE = new JavacSingularsRecipes(); + private final Map<String, JavacSingularizer> singularizers = new HashMap<String, JavacSingularizer>(); + private final TypeLibrary singularizableTypes = new TypeLibrary(); + + private JavacSingularsRecipes() { + try { + loadAll(singularizableTypes, singularizers); + singularizableTypes.lock(); + } catch (IOException e) { + System.err.println("Lombok's @Singularizable feature is broken due to misconfigured SPI files: " + e); + } + } + + private static void loadAll(TypeLibrary library, Map<String, JavacSingularizer> map) throws IOException { + for (JavacSingularizer handler : SpiLoadUtil.findServices(JavacSingularizer.class, JavacSingularizer.class.getClassLoader())) { + for (String type : handler.getSupportedTypes()) { + JavacSingularizer existingSingularizer = map.get(type); + if (existingSingularizer != null) { + JavacSingularizer toKeep = existingSingularizer.getClass().getName().compareTo(handler.getClass().getName()) > 0 ? handler : existingSingularizer; + System.err.println("Multiple singularizers found for type " + type + "; the alphabetically first class is used: " + toKeep.getClass().getName()); + map.put(type, toKeep); + } else { + map.put(type, handler); + library.addType(type); + } + } + } + } + + public static JavacSingularsRecipes get() { + return INSTANCE; + } + + public String toQualified(String typeReference) { + return singularizableTypes.toQualified(typeReference); + } + + public JavacSingularizer getSingularizer(String fqn) { + return singularizers.get(fqn); + } + + public static final class SingularData { + private final JavacNode annotation; + private final Name singularName; + private final Name pluralName; + private final List<JCExpression> typeArgs; + private final String targetFqn; + private final JavacSingularizer singularizer; + + public SingularData(JavacNode annotation, Name singularName, Name pluralName, List<JCExpression> typeArgs, String targetFqn, JavacSingularizer singularizer) { + this.annotation = annotation; + this.singularName = singularName; + this.pluralName = pluralName; + this.typeArgs = typeArgs; + this.targetFqn = targetFqn; + this.singularizer = singularizer; + } + + public JavacNode getAnnotation() { + return annotation; + } + + public Name getSingularName() { + return singularName; + } + + public Name getPluralName() { + return pluralName; + } + + public List<JCExpression> getTypeArgs() { + return typeArgs; + } + + public String getTargetFqn() { + return targetFqn; + } + + public JavacSingularizer getSingularizer() { + return singularizer; + } + + public String getTargetSimpleType() { + int idx = targetFqn.lastIndexOf("."); + return idx == -1 ? targetFqn : targetFqn.substring(idx + 1); + } + } + + 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()) { + switch (child.getKind()) { + case FIELD: { + JCVariableDecl field = (JCVariableDecl) child.get(); + Name name = field.name; + if (name == null) break; + if (getGeneratedBy(field) != null) continue; + for (Name fieldToBeGenerated : listFieldsToBeGenerated(data, builderType)) { + if (!fieldToBeGenerated.equals(name)) continue; + child.addError("Manually adding a field that @Singular @Builder would generate is not supported. If you want to manually manage the builder aspect for this field/parameter, don't use @Singular."); + return true; + } + break; + } + case METHOD: { + JCMethodDecl method = (JCMethodDecl) child.get(); + Name name = method.name; + if (name == null) break; + if (getGeneratedBy(method) != null) continue; + for (Name methodToBeGenerated : listMethodsToBeGenerated(data, builderType)) { + if (!methodToBeGenerated.equals(name)) continue; + child.addError("Manually adding a method that @Singular @Builder would generate is not supported. If you want to manually manage the builder aspect for this field/parameter, don't use @Singular."); + return true; + } + break; + }} + } + + return false; + } + + public java.util.List<Name> listFieldsToBeGenerated(SingularData data, JavacNode builderType) { + return Collections.singletonList(data.pluralName); + } + + public java.util.List<Name> listMethodsToBeGenerated(SingularData data, JavacNode builderType) { + Name p = data.pluralName; + Name s = data.singularName; + if (p.equals(s)) return Collections.singletonList(p); + return Arrays.asList(p, s); + } + + public abstract java.util.List<JavacNode> generateFields(SingularData data, JavacNode builderType, JCTree source); + public abstract void generateMethods(SingularData data, boolean deprecate, JavacNode builderType, JCTree source, boolean fluent, boolean chain); + public abstract void appendBuildCode(SingularData data, JavacNode builderType, JCTree source, ListBuffer<JCStatement> statements, Name targetVariableName); + + public boolean requiresCleaning() { + try { + return !getClass().getMethod("appendCleaningCode", SingularData.class, JavacNode.class, JCTree.class, ListBuffer.class).getDeclaringClass().equals(JavacSingularizer.class); + } catch (NoSuchMethodException e) { + return false; + } + } + + public void appendCleaningCode(SingularData data, JavacNode builderType, JCTree source, ListBuffer<JCStatement> statements) { + } + + // -- Utility methods -- + + /** + * Adds the requested number of type arguments to the provided type, copying each argument in {@code typeArgs}. If typeArgs is too long, the extra elements are ignored. + * If {@code typeArgs} is null or too short, {@code java.lang.Object} will be substituted for each missing type argument. + * + * @param count The number of type arguments requested. + * @param addExtends If {@code true}, all bounds are either '? extends X' or just '?'. If false, the reverse is applied, and '? extends Foo' is converted to Foo, '?' to Object, etc. + * @param node Some node in the same AST. Just used to obtain makers and contexts and such. + * @param type The type to add generics to. + * @param typeArgs the list of type args to clone. + * @param source The source annotation that is the root cause of this code generation. + */ + protected JCExpression addTypeArgs(int count, boolean addExtends, JavacNode node, JCExpression type, List<JCExpression> typeArgs, JCTree source) { + JavacTreeMaker maker = node.getTreeMaker(); + List<JCExpression> clonedAndFixedTypeArgs = createTypeArgs(count, addExtends, node, typeArgs, source); + + return maker.TypeApply(type, clonedAndFixedTypeArgs); + } + + protected List<JCExpression> createTypeArgs(int count, boolean addExtends, JavacNode node, List<JCExpression> typeArgs, JCTree source) { + JavacTreeMaker maker = node.getTreeMaker(); + Context context = node.getContext(); + + if (count < 0) throw new IllegalArgumentException("count is negative"); + if (count == 0) return List.nil(); + ListBuffer<JCExpression> arguments = new ListBuffer<JCExpression>(); + + if (typeArgs != null) for (JCExpression orig : typeArgs) { + if (!addExtends) { + if (orig.getKind() == Kind.UNBOUNDED_WILDCARD || orig.getKind() == Kind.SUPER_WILDCARD) { + arguments.append(genJavaLangTypeRef(node, "Object")); + } else if (orig.getKind() == Kind.EXTENDS_WILDCARD) { + JCExpression inner; + try { + inner = (JCExpression) ((JCWildcard) orig).inner; + } catch (Exception e) { + inner = genJavaLangTypeRef(node, "Object"); + } + arguments.append(cloneType(maker, inner, source, context)); + } else { + arguments.append(cloneType(maker, orig, source, context)); + } + } else { + if (orig.getKind() == Kind.UNBOUNDED_WILDCARD || orig.getKind() == Kind.SUPER_WILDCARD) { + arguments.append(maker.Wildcard(maker.TypeBoundKind(BoundKind.UNBOUND), null)); + } else if (orig.getKind() == Kind.EXTENDS_WILDCARD) { + arguments.append(cloneType(maker, orig, source, context)); + } else { + arguments.append(maker.Wildcard(maker.TypeBoundKind(BoundKind.EXTENDS), cloneType(maker, orig, source, context))); + } + } + if (--count == 0) break; + } + + while (count-- > 0) { + if (addExtends) { + arguments.append(maker.Wildcard(maker.TypeBoundKind(BoundKind.UNBOUND), null)); + } else { + arguments.append(genJavaLangTypeRef(node, "Object")); + } + } + + 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"); + 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) { + JCExpression isNull = maker.Binary(CTC_EQUAL, maker.Select(maker.Ident(thisName), name), maker.Literal(CTC_BOT, 0)); + JCExpression out = maker.Conditional(isNull, maker.Literal(CTC_INT, 0), sizeInvoke); + if (parens) return maker.Parens(out); + return out; + } + return sizeInvoke; + } + + protected JCExpression cloneParamType(int index, JavacTreeMaker maker, List<JCExpression> typeArgs, JavacNode builderType, JCTree source) { + if (typeArgs == null || typeArgs.size() <= index) { + return genJavaLangTypeRef(builderType, "Object"); + } else { + JCExpression originalType = typeArgs.get(index); + if (originalType.getKind() == Kind.UNBOUNDED_WILDCARD || originalType.getKind() == Kind.SUPER_WILDCARD) { + return genJavaLangTypeRef(builderType, "Object"); + } else if (originalType.getKind() == Kind.EXTENDS_WILDCARD) { + try { + return cloneType(maker, (JCExpression) ((JCWildcard) originalType).inner, source, builderType.getContext()); + } catch (Exception e) { + return genJavaLangTypeRef(builderType, "Object"); + } + } else { + return cloneType(maker, originalType, source, builderType.getContext()); + } + } + } + } +} diff --git a/src/core/lombok/javac/handlers/singulars/JavacGuavaMapSingularizer.java b/src/core/lombok/javac/handlers/singulars/JavacGuavaMapSingularizer.java new file mode 100644 index 00000000..e0621cf7 --- /dev/null +++ b/src/core/lombok/javac/handlers/singulars/JavacGuavaMapSingularizer.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2015 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.singulars; + +import lombok.core.LombokImmutableList; +import lombok.javac.handlers.JavacSingularsRecipes.JavacSingularizer; + +import org.mangosdk.spi.ProviderFor; + +@ProviderFor(JavacSingularizer.class) +public class JavacGuavaMapSingularizer extends JavacGuavaSingularizer { + // TODO cgcc.ImmutableMultimap, cgcc.ImmutableListMultimap, cgcc.ImmutableSetMultimap + // TODO cgcc.ImmutableClassToInstanceMap + // TODO cgcc.ImmutableRangeMap + + private static final LombokImmutableList<String> SUFFIXES = + LombokImmutableList.of("key", "value"); + private static final LombokImmutableList<String> SUPPORTED_TYPES = LombokImmutableList.of( + "com.google.common.collect.ImmutableMap", + "com.google.common.collect.ImmutableBiMap", + "com.google.common.collect.ImmutableSortedMap" + ); + + @Override public LombokImmutableList<String> getSupportedTypes() { + return SUPPORTED_TYPES; + } + + @Override protected LombokImmutableList<String> getArgumentSuffixes() { + return SUFFIXES; + } + + @Override protected String getAddMethodName() { + return "put"; + } + + @Override protected String getAddAllTypeName() { + return "java.util.Map"; + } +} diff --git a/src/core/lombok/javac/handlers/singulars/JavacGuavaSetListSingularizer.java b/src/core/lombok/javac/handlers/singulars/JavacGuavaSetListSingularizer.java new file mode 100644 index 00000000..5c7fcab5 --- /dev/null +++ b/src/core/lombok/javac/handlers/singulars/JavacGuavaSetListSingularizer.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2015 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.singulars; + +import org.mangosdk.spi.ProviderFor; + +import lombok.core.LombokImmutableList; +import lombok.javac.handlers.JavacSingularsRecipes.JavacSingularizer; + +@ProviderFor(JavacSingularizer.class) +public class JavacGuavaSetListSingularizer extends JavacGuavaSingularizer { + // TODO com.google.common.collect.ImmutableRangeSet + // TODO com.google.common.collect.ImmutableMultiset and com.google.common.collect.ImmutableSortedMultiset + private static final LombokImmutableList<String> SUFFIXES = LombokImmutableList.of(""); + private static final LombokImmutableList<String> SUPPORTED_TYPES = LombokImmutableList.of( + "com.google.common.collect.ImmutableCollection", + "com.google.common.collect.ImmutableList", + "com.google.common.collect.ImmutableSet", + "com.google.common.collect.ImmutableSortedSet" + ); + + @Override public LombokImmutableList<String> getSupportedTypes() { + return SUPPORTED_TYPES; + } + + @Override protected LombokImmutableList<String> getArgumentSuffixes() { + return SUFFIXES; + } + + @Override protected String getAddMethodName() { + return "add"; + } + + @Override protected String getAddAllTypeName() { + return "java.lang.Iterable"; + } +} diff --git a/src/core/lombok/javac/handlers/singulars/JavacGuavaSingularizer.java b/src/core/lombok/javac/handlers/singulars/JavacGuavaSingularizer.java new file mode 100644 index 00000000..0ab7da54 --- /dev/null +++ b/src/core/lombok/javac/handlers/singulars/JavacGuavaSingularizer.java @@ -0,0 +1,222 @@ +/* + * 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 + * 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.singulars; + +import static lombok.javac.Javac.*; +import static lombok.javac.handlers.JavacHandlerUtil.*; + +import java.util.Collections; + +import lombok.core.GuavaTypeMap; +import lombok.core.LombokImmutableList; +import lombok.core.handlers.HandlerUtil; +import lombok.javac.JavacNode; +import lombok.javac.JavacTreeMaker; +import lombok.javac.handlers.JavacHandlerUtil; +import lombok.javac.handlers.JavacSingularsRecipes.JavacSingularizer; +import lombok.javac.handlers.JavacSingularsRecipes.SingularData; + +import com.sun.tools.javac.code.Flags; +import com.sun.tools.javac.code.Symtab; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.JCBlock; +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.JCTypeParameter; +import com.sun.tools.javac.tree.JCTree.JCVariableDecl; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.util.ListBuffer; +import com.sun.tools.javac.util.Name; + +abstract class JavacGuavaSingularizer extends JavacSingularizer { + protected String getSimpleTargetTypeName(SingularData data) { + return GuavaTypeMap.getGuavaTypeName(data.getTargetFqn()); + } + + protected String getBuilderMethodName(SingularData data) { + String simpleTypeName = getSimpleTargetTypeName(data); + if ("ImmutableSortedSet".equals(simpleTypeName) || "ImmutableSortedMap".equals(simpleTypeName)) return "naturalOrder"; + return "builder"; + } + + @Override public java.util.List<JavacNode> generateFields(SingularData data, JavacNode builderType, JCTree source) { + JavacTreeMaker maker = builderType.getTreeMaker(); + String simpleTypeName = getSimpleTargetTypeName(data); + JCExpression type = JavacHandlerUtil.chainDots(builderType, "com", "google", "common", "collect", simpleTypeName, "Builder"); + type = addTypeArgs(getTypeArgumentsCount(), false, builderType, type, data.getTypeArgs(), source); + + JCVariableDecl buildField = maker.VarDef(maker.Modifiers(Flags.PRIVATE), data.getPluralName(), type, null); + return Collections.singletonList(injectFieldAndMarkGenerated(builderType, buildField)); + } + + @Override public void generateMethods(SingularData data, boolean deprecate, JavacNode builderType, JCTree source, boolean fluent, boolean chain) { + JavacTreeMaker maker = builderType.getTreeMaker(); + Symtab symbolTable = builderType.getSymbolTable(); + JCExpression returnType = chain ? cloneSelfType(builderType) : maker.Type(createVoidType(symbolTable, CTC_VOID)); + JCStatement returnStatement = chain ? maker.Return(maker.Ident(builderType.toName("this"))) : null; + generateSingularMethod(deprecate, maker, returnType, returnStatement, data, builderType, source, fluent); + + returnType = chain ? cloneSelfType(builderType) : maker.Type(createVoidType(symbolTable, CTC_VOID)); + returnStatement = chain ? maker.Return(maker.Ident(builderType.toName("this"))) : null; + generatePluralMethod(deprecate, maker, returnType, returnStatement, data, builderType, source, fluent); + + returnType = chain ? cloneSelfType(builderType) : maker.Type(createVoidType(symbolTable, CTC_VOID)); + returnStatement = chain ? maker.Return(maker.Ident(builderType.toName("this"))) : null; + generateClearMethod(deprecate, maker, returnType, returnStatement, data, builderType, source); + } + + 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(); + + JCExpression thisDotField = maker.Select(maker.Ident(builderType.toName("this")), data.getPluralName()); + JCStatement clearField = maker.Exec(maker.Assign(thisDotField, maker.Literal(CTC_BOT, null))); + List<JCStatement> statements = returnStatement != null ? List.of(clearField, returnStatement) : List.of(clearField); + + JCBlock body = maker.Block(0, statements); + Name methodName = builderType.toName(HandlerUtil.buildAccessorName("clear", data.getPluralName().toString())); + JCMethodDecl method = maker.MethodDef(mods, methodName, returnType, typeParams, params, thrown, body, null); + injectMethod(builderType, method); + } + + 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(); + + LombokImmutableList<String> suffixes = getArgumentSuffixes(); + Name[] names = new Name[suffixes.size()]; + for (int i = 0; i < suffixes.size(); i++) { + String s = suffixes.get(i); + Name n = data.getSingularName(); + names[i] = s.isEmpty() ? n : builderType.toName(s); + } + + 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()); + ListBuffer<JCExpression> invokeAddExprBuilder = new ListBuffer<JCExpression>(); + for (int i = 0; i < suffixes.size(); i++) { + invokeAddExprBuilder.append(maker.Ident(names[i])); + } + List<JCExpression> invokeAddExpr = invokeAddExprBuilder.toList(); + JCExpression invokeAdd = maker.Apply(List.<JCExpression>nil(), thisDotFieldDotAdd, invokeAddExpr); + statements.append(maker.Exec(invokeAdd)); + if (returnStatement != null) statements.append(returnStatement); + JCBlock body = maker.Block(0, statements.toList()); + Name methodName = data.getSingularName(); + long paramFlags = JavacHandlerUtil.addFinalIfNeeded(Flags.PARAMETER, builderType.getContext()); + if (!fluent) methodName = builderType.toName(HandlerUtil.buildAccessorName(getAddMethodName(), methodName.toString())); + ListBuffer<JCVariableDecl> params = new ListBuffer<JCVariableDecl>(); + for (int i = 0; i < suffixes.size(); i++) { + JCExpression pt = cloneParamType(i, maker, data.getTypeArgs(), builderType, source); + JCVariableDecl p = maker.VarDef(maker.Modifiers(paramFlags), names[i], pt, null); + params.append(p); + } + + JCMethodDecl method = maker.MethodDef(mods, methodName, returnType, typeParams, params.toList(), thrown, body, null); + injectMethod(builderType, method); + } + + 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 = 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"); + JCExpression invokeAddAll = maker.Apply(List.<JCExpression>nil(), thisDotFieldDotAddAll, List.<JCExpression>of(maker.Ident(data.getPluralName()))); + statements.append(maker.Exec(invokeAddAll)); + if (returnStatement != null) statements.append(returnStatement); + JCBlock body = maker.Block(0, statements.toList()); + Name methodName = data.getPluralName(); + long paramFlags = JavacHandlerUtil.addFinalIfNeeded(Flags.PARAMETER, builderType.getContext()); + if (!fluent) methodName = builderType.toName(HandlerUtil.buildAccessorName(getAddMethodName() + "All", methodName.toString())); + JCExpression paramType; + String aaTypeName = getAddAllTypeName(); + if (aaTypeName.startsWith("java.lang.") && aaTypeName.indexOf('.', 11) == -1) { + paramType = genJavaLangTypeRef(builderType, aaTypeName.substring(10)); + } else { + paramType = chainDotsString(builderType, aaTypeName); + } + paramType = addTypeArgs(getTypeArgumentsCount(), true, builderType, paramType, data.getTypeArgs(), source); + JCVariableDecl param = maker.VarDef(maker.Modifiers(paramFlags), data.getPluralName(), paramType, null); + JCMethodDecl method = maker.MethodDef(mods, methodName, returnType, typeParams, List.of(param), thrown, body, null); + injectMethod(builderType, method); + } + + @Override public void appendBuildCode(SingularData data, JavacNode builderType, JCTree source, ListBuffer<JCStatement> statements, Name targetVariableName) { + JavacTreeMaker maker = builderType.getTreeMaker(); + List<JCExpression> jceBlank = List.nil(); + + JCExpression varType = chainDotsString(builderType, data.getTargetFqn()); + int agrumentsCount = getTypeArgumentsCount(); + varType = addTypeArgs(agrumentsCount, false, builderType, varType, data.getTypeArgs(), source); + + JCExpression empty; { + //ImmutableX.of() + JCExpression emptyMethod = chainDots(builderType, "com", "google", "common", "collect", getSimpleTargetTypeName(data), "of"); + List<JCExpression> invokeTypeArgs = createTypeArgs(agrumentsCount, false, builderType, data.getTypeArgs(), source); + empty = maker.Apply(invokeTypeArgs, emptyMethod, jceBlank); + } + + JCExpression invokeBuild; { + //this.pluralName.build(); + invokeBuild = maker.Apply(jceBlank, chainDots(builderType, "this", 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)); + } + + JCExpression init = maker.Conditional(isNull, empty, invokeBuild); // this.pluralName == null ? ImmutableX.of() : this.pluralName.build() + + JCStatement jcs = maker.VarDef(maker.Modifiers(0), data.getPluralName(), varType, init); + statements.append(jcs); + } + + protected JCStatement createConstructBuilderVarIfNeeded(JavacTreeMaker maker, SingularData data, JavacNode builderType, JCTree source) { + List<JCExpression> jceBlank = List.nil(); + + JCExpression thisDotField = maker.Select(maker.Ident(builderType.toName("this")), data.getPluralName()); + JCExpression thisDotField2 = maker.Select(maker.Ident(builderType.toName("this")), data.getPluralName()); + JCExpression cond = maker.Binary(CTC_EQUAL, thisDotField, maker.Literal(CTC_BOT, null)); + + JCExpression create = maker.Apply(jceBlank, chainDots(builderType, "com", "google", "common", "collect", getSimpleTargetTypeName(data), getBuilderMethodName(data)), jceBlank); + JCStatement thenPart = maker.Exec(maker.Assign(thisDotField2, create)); + + return maker.If(cond, thenPart, null); + } + + protected abstract LombokImmutableList<String> getArgumentSuffixes(); + protected abstract String getAddMethodName(); + protected abstract String getAddAllTypeName(); + + protected int getTypeArgumentsCount() { + return getArgumentSuffixes().size(); + } +} diff --git a/src/core/lombok/javac/handlers/singulars/JavacGuavaTableSingularizer.java b/src/core/lombok/javac/handlers/singulars/JavacGuavaTableSingularizer.java new file mode 100644 index 00000000..080266b8 --- /dev/null +++ b/src/core/lombok/javac/handlers/singulars/JavacGuavaTableSingularizer.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2015 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.singulars; + +import org.mangosdk.spi.ProviderFor; + +import lombok.core.LombokImmutableList; +import lombok.javac.handlers.JavacSingularsRecipes.JavacSingularizer; + +@ProviderFor(JavacSingularizer.class) +public class JavacGuavaTableSingularizer extends JavacGuavaSingularizer { + private static final LombokImmutableList<String> SUFFIXES = + LombokImmutableList.of("rowKey", "columnKey", "value"); + private static final LombokImmutableList<String> SUPPORTED_TYPES = + LombokImmutableList.of("com.google.common.collect.ImmutableTable"); + + @Override public LombokImmutableList<String> getSupportedTypes() { + return SUPPORTED_TYPES; + } + + @Override protected LombokImmutableList<String> getArgumentSuffixes() { + return SUFFIXES; + } + + @Override protected String getAddMethodName() { + return "put"; + } + + @Override protected String getAddAllTypeName() { + return "com.google.common.collect.Table"; + } +} diff --git a/src/core/lombok/javac/handlers/singulars/JavacJavaUtilListSetSingularizer.java b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilListSetSingularizer.java new file mode 100644 index 00000000..196ce45d --- /dev/null +++ b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilListSetSingularizer.java @@ -0,0 +1,162 @@ +/* + * 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 + * 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.singulars; + +import static lombok.javac.Javac.*; +import static lombok.javac.handlers.JavacHandlerUtil.*; + +import java.util.Collections; + +import lombok.core.handlers.HandlerUtil; +import lombok.javac.JavacNode; +import lombok.javac.JavacTreeMaker; +import lombok.javac.handlers.JavacHandlerUtil; +import lombok.javac.handlers.JavacSingularsRecipes.SingularData; + +import com.sun.tools.javac.code.Flags; +import com.sun.tools.javac.code.Symtab; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.JCBlock; +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.JCTypeParameter; +import com.sun.tools.javac.tree.JCTree.JCVariableDecl; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.util.ListBuffer; +import com.sun.tools.javac.util.Name; + +abstract class JavacJavaUtilListSetSingularizer extends JavacJavaUtilSingularizer { + @Override public java.util.List<Name> listFieldsToBeGenerated(SingularData data, JavacNode builderType) { + if (useGuavaInstead(builderType)) { + return guavaListSetSingularizer.listFieldsToBeGenerated(data, builderType); + } + + return super.listFieldsToBeGenerated(data, builderType); + } + + @Override public java.util.List<Name> listMethodsToBeGenerated(SingularData data, JavacNode builderType) { + if (useGuavaInstead(builderType)) { + return guavaListSetSingularizer.listMethodsToBeGenerated(data, builderType); + } + + return super.listMethodsToBeGenerated(data, builderType); + } + + @Override public java.util.List<JavacNode> generateFields(SingularData data, JavacNode builderType, JCTree source) { + if (useGuavaInstead(builderType)) { + return guavaListSetSingularizer.generateFields(data, builderType, source); + } + + JavacTreeMaker maker = builderType.getTreeMaker(); + JCExpression type = JavacHandlerUtil.chainDots(builderType, "java", "util", "ArrayList"); + type = addTypeArgs(1, false, builderType, type, data.getTypeArgs(), source); + + JCVariableDecl buildField = maker.VarDef(maker.Modifiers(Flags.PRIVATE), data.getPluralName(), type, null); + return Collections.singletonList(injectFieldAndMarkGenerated(builderType, buildField)); + } + + @Override public void generateMethods(SingularData data, boolean deprecate, JavacNode builderType, JCTree source, boolean fluent, boolean chain) { + if (useGuavaInstead(builderType)) { + guavaListSetSingularizer.generateMethods(data, deprecate, builderType, source, fluent, chain); + return; + } + + JavacTreeMaker maker = builderType.getTreeMaker(); + Symtab symbolTable = builderType.getSymbolTable(); + Name thisName = builderType.toName("this"); + + JCExpression returnType = chain ? cloneSelfType(builderType) : maker.Type(createVoidType(symbolTable, CTC_VOID)); + JCStatement returnStatement = chain ? maker.Return(maker.Ident(thisName)) : null; + generateSingularMethod(deprecate, maker, returnType, returnStatement, data, builderType, source, fluent); + + returnType = chain ? cloneSelfType(builderType) : maker.Type(createVoidType(symbolTable, CTC_VOID)); + returnStatement = chain ? maker.Return(maker.Ident(thisName)) : null; + generatePluralMethod(deprecate, maker, returnType, returnStatement, data, builderType, source, fluent); + + returnType = chain ? cloneSelfType(builderType) : maker.Type(createVoidType(symbolTable, CTC_VOID)); + returnStatement = chain ? maker.Return(maker.Ident(thisName)) : null; + generateClearMethod(deprecate, maker, returnType, returnStatement, data, builderType, source); + } + + 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(); + List<JCExpression> jceBlank = List.nil(); + + JCExpression thisDotField = maker.Select(maker.Ident(builderType.toName("this")), data.getPluralName()); + JCExpression thisDotFieldDotClear = maker.Select(maker.Select(maker.Ident(builderType.toName("this")), data.getPluralName()), builderType.toName("clear")); + JCStatement clearCall = maker.Exec(maker.Apply(jceBlank, thisDotFieldDotClear, jceBlank)); + JCExpression cond = maker.Binary(CTC_NOT_EQUAL, thisDotField, maker.Literal(CTC_BOT, null)); + JCStatement ifSetCallClear = maker.If(cond, clearCall, null); + List<JCStatement> statements = returnStatement != null ? List.of(ifSetCallClear, returnStatement) : List.of(ifSetCallClear); + + JCBlock body = maker.Block(0, statements); + Name methodName = builderType.toName(HandlerUtil.buildAccessorName("clear", data.getPluralName().toString())); + JCMethodDecl method = maker.MethodDef(mods, methodName, returnType, typeParams, params, thrown, body, null); + injectMethod(builderType, method); + } + + 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 = 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"); + JCExpression invokeAdd = maker.Apply(List.<JCExpression>nil(), thisDotFieldDotAdd, List.<JCExpression>of(maker.Ident(data.getSingularName()))); + statements.append(maker.Exec(invokeAdd)); + if (returnStatement != null) statements.append(returnStatement); + JCBlock body = maker.Block(0, statements.toList()); + Name name = data.getSingularName(); + long paramFlags = JavacHandlerUtil.addFinalIfNeeded(Flags.PARAMETER, builderType.getContext()); + if (!fluent) name = builderType.toName(HandlerUtil.buildAccessorName("add", name.toString())); + JCExpression paramType = cloneParamType(0, maker, data.getTypeArgs(), builderType, source); + JCVariableDecl param = maker.VarDef(maker.Modifiers(paramFlags), data.getSingularName(), paramType, null); + JCMethodDecl method = maker.MethodDef(mods, name, returnType, typeParams, List.of(param), thrown, body, null); + injectMethod(builderType, method); + } + + 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 = 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"); + JCExpression invokeAdd = maker.Apply(List.<JCExpression>nil(), thisDotFieldDotAdd, List.<JCExpression>of(maker.Ident(data.getPluralName()))); + statements.append(maker.Exec(invokeAdd)); + if (returnStatement != null) statements.append(returnStatement); + JCBlock body = maker.Block(0, statements.toList()); + Name name = data.getPluralName(); + long paramFlags = JavacHandlerUtil.addFinalIfNeeded(Flags.PARAMETER, builderType.getContext()); + if (!fluent) name = builderType.toName(HandlerUtil.buildAccessorName("addAll", name.toString())); + JCExpression paramType = chainDots(builderType, "java", "util", "Collection"); + paramType = addTypeArgs(1, true, builderType, paramType, data.getTypeArgs(), source); + JCVariableDecl param = maker.VarDef(maker.Modifiers(paramFlags), data.getPluralName(), paramType, null); + JCMethodDecl method = maker.MethodDef(mods, name, returnType, typeParams, List.of(param), thrown, body, null); + injectMethod(builderType, method); + } +} diff --git a/src/core/lombok/javac/handlers/singulars/JavacJavaUtilListSingularizer.java b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilListSingularizer.java new file mode 100644 index 00000000..3002a98f --- /dev/null +++ b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilListSingularizer.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2015 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.singulars; + +import static lombok.javac.Javac.*; +import static lombok.javac.handlers.JavacHandlerUtil.*; + +import org.mangosdk.spi.ProviderFor; + +import lombok.core.LombokImmutableList; +import lombok.javac.JavacNode; +import lombok.javac.JavacTreeMaker; +import lombok.javac.handlers.JavacSingularsRecipes.JavacSingularizer; +import lombok.javac.handlers.JavacSingularsRecipes.SingularData; + +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.JCCase; +import com.sun.tools.javac.tree.JCTree.JCExpression; +import com.sun.tools.javac.tree.JCTree.JCStatement; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.util.ListBuffer; +import com.sun.tools.javac.util.Name; + +@ProviderFor(JavacSingularizer.class) +public class JavacJavaUtilListSingularizer extends JavacJavaUtilListSetSingularizer { + @Override public LombokImmutableList<String> getSupportedTypes() { + 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) { + if (useGuavaInstead(builderType)) { + guavaListSetSingularizer.appendBuildCode(data, builderType, source, statements, targetVariableName); + return; + } + + JavacTreeMaker maker = builderType.getTreeMaker(); + List<JCExpression> jceBlank = List.nil(); + ListBuffer<JCCase> cases = new ListBuffer<JCCase>(); + + /* case 0: (empty); break; */ { + JCStatement assignStat; { + // pluralName = java.util.Collections.emptyList(); + JCExpression invoke = maker.Apply(jceBlank, chainDots(builderType, "java", "util", "Collections", "emptyList"), jceBlank); + assignStat = maker.Exec(maker.Assign(maker.Ident(data.getPluralName()), invoke)); + } + JCStatement breakStat = maker.Break(null); + JCCase emptyCase = maker.Case(maker.Literal(CTC_INT, 0), List.of(assignStat, breakStat)); + cases.append(emptyCase); + } + + /* case 1: (singletonList); break; */ { + 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)); + 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)); + } + JCStatement breakStat = maker.Break(null); + JCCase singletonCase = maker.Case(maker.Literal(CTC_INT, 1), List.of(assignStat, breakStat)); + cases.append(singletonCase); + } + + /* default: Create with right size, then addAll */ { + List<JCStatement> defStats = createListCopy(maker, data, builderType, source); + JCCase defaultCase = maker.Case(null, defStats); + cases.append(defaultCase); + } + + JCStatement switchStat = maker.Switch(getSize(maker, builderType, data.getPluralName(), true, false), 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); + statements.append(varDefStat); + statements.append(switchStat); + } + + private List<JCStatement> createListCopy(JavacTreeMaker maker, SingularData data, JavacNode builderType, JCTree source) { + List<JCExpression> jceBlank = List.nil(); + Name thisName = builderType.toName("this"); + + JCExpression argToUnmodifiable; { + // new java.util.ArrayList<Generics>(this.pluralName); + List<JCExpression> constructorArgs = List.nil(); + JCExpression thisDotPluralName = maker.Select(maker.Ident(thisName), data.getPluralName()); + constructorArgs = List.<JCExpression>of(thisDotPluralName); + JCExpression targetTypeExpr = chainDots(builderType, "java", "util", "ArrayList"); + targetTypeExpr = addTypeArgs(1, false, builderType, targetTypeExpr, data.getTypeArgs(), source); + argToUnmodifiable = maker.NewClass(null, jceBlank, targetTypeExpr, constructorArgs, null); + } + + JCStatement unmodifiableStat; { + // pluralname = Collections.unmodifiableInterfaceType(-newlist-); + JCExpression invoke = maker.Apply(jceBlank, chainDots(builderType, "java", "util", "Collections", "unmodifiableList"), List.of(argToUnmodifiable)); + unmodifiableStat = maker.Exec(maker.Assign(maker.Ident(data.getPluralName()), invoke)); + } + + return List.of(unmodifiableStat); + } +} diff --git a/src/core/lombok/javac/handlers/singulars/JavacJavaUtilMapSingularizer.java b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilMapSingularizer.java new file mode 100644 index 00000000..fd699275 --- /dev/null +++ b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilMapSingularizer.java @@ -0,0 +1,229 @@ +/* + * 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 + * 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.singulars; + +import static lombok.javac.Javac.*; +import static lombok.javac.handlers.JavacHandlerUtil.*; + +import java.util.Arrays; + +import lombok.core.LombokImmutableList; +import lombok.core.handlers.HandlerUtil; +import lombok.javac.JavacNode; +import lombok.javac.JavacTreeMaker; +import lombok.javac.handlers.JavacHandlerUtil; +import lombok.javac.handlers.JavacSingularsRecipes.JavacSingularizer; +import lombok.javac.handlers.JavacSingularsRecipes.SingularData; + +import org.mangosdk.spi.ProviderFor; + +import com.sun.tools.javac.code.Flags; +import com.sun.tools.javac.code.Symtab; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.JCBlock; +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.JCTypeParameter; +import com.sun.tools.javac.tree.JCTree.JCVariableDecl; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.util.ListBuffer; +import com.sun.tools.javac.util.Name; + +@ProviderFor(JavacSingularizer.class) +public class JavacJavaUtilMapSingularizer extends JavacJavaUtilSingularizer { + @Override public LombokImmutableList<String> getSupportedTypes() { + return LombokImmutableList.of("java.util.Map", "java.util.SortedMap", "java.util.NavigableMap"); + } + + @Override public java.util.List<Name> listFieldsToBeGenerated(SingularData data, JavacNode builderType) { + if (useGuavaInstead(builderType)) { + return guavaMapSingularizer.listFieldsToBeGenerated(data, builderType); + } + + String p = data.getPluralName().toString(); + return Arrays.asList(builderType.toName(p + "$key"), builderType.toName(p + "$value")); + } + + @Override public java.util.List<Name> listMethodsToBeGenerated(SingularData data, JavacNode builderType) { + if (useGuavaInstead(builderType)) { + return guavaMapSingularizer.listMethodsToBeGenerated(data, builderType); + } + + return super.listMethodsToBeGenerated(data, builderType); + } + + @Override public java.util.List<JavacNode> generateFields(SingularData data, JavacNode builderType, JCTree source) { + if (useGuavaInstead(builderType)) { + return guavaMapSingularizer.generateFields(data, builderType, source); + } + + JavacTreeMaker maker = builderType.getTreeMaker(); + + JCVariableDecl buildKeyField; { + JCExpression type = JavacHandlerUtil.chainDots(builderType, "java", "util", "ArrayList"); + type = addTypeArgs(1, false, builderType, type, data.getTypeArgs(), source); + buildKeyField = maker.VarDef(maker.Modifiers(Flags.PRIVATE), builderType.toName(data.getPluralName() + "$key"), type, null); + } + + JCVariableDecl buildValueField; { + JCExpression type = JavacHandlerUtil.chainDots(builderType, "java", "util", "ArrayList"); + List<JCExpression> tArgs = data.getTypeArgs(); + if (tArgs != null && tArgs.size() > 1) tArgs = tArgs.tail; + else tArgs = List.nil(); + type = addTypeArgs(1, false, builderType, type, tArgs, source); + buildValueField = maker.VarDef(maker.Modifiers(Flags.PRIVATE), builderType.toName(data.getPluralName() + "$value"), type, null); + } + + JavacNode valueFieldNode = injectFieldAndMarkGenerated(builderType, buildValueField); + JavacNode keyFieldNode = injectFieldAndMarkGenerated(builderType, buildKeyField); + + return Arrays.asList(keyFieldNode, valueFieldNode); + } + + @Override public void generateMethods(SingularData data, boolean deprecate, JavacNode builderType, JCTree source, boolean fluent, boolean chain) { + if (useGuavaInstead(builderType)) { + guavaMapSingularizer.generateMethods(data, deprecate, builderType, source, fluent, chain); + return; + } + + JavacTreeMaker maker = builderType.getTreeMaker(); + Symtab symbolTable = builderType.getSymbolTable(); + + JCExpression returnType = chain ? cloneSelfType(builderType) : maker.Type(createVoidType(symbolTable, CTC_VOID)); + JCStatement returnStatement = chain ? maker.Return(maker.Ident(builderType.toName("this"))) : null; + generateSingularMethod(deprecate, maker, returnType, returnStatement, data, builderType, source, fluent); + + returnType = chain ? cloneSelfType(builderType) : maker.Type(createVoidType(symbolTable, CTC_VOID)); + returnStatement = chain ? maker.Return(maker.Ident(builderType.toName("this"))) : null; + generatePluralMethod(deprecate, maker, returnType, returnStatement, data, builderType, source, fluent); + + returnType = chain ? cloneSelfType(builderType) : maker.Type(createVoidType(symbolTable, CTC_VOID)); + returnStatement = chain ? maker.Return(maker.Ident(builderType.toName("this"))) : null; + generateClearMethod(deprecate, maker, returnType, returnStatement, data, builderType, source); + } + + 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(); + List<JCExpression> jceBlank = List.nil(); + + JCExpression thisDotKeyField = chainDots(builderType, "this", data.getPluralName() + "$key"); + JCExpression thisDotKeyFieldDotClear = chainDots(builderType, "this", data.getPluralName() + "$key", "clear"); + JCExpression thisDotValueFieldDotClear = chainDots(builderType, "this", data.getPluralName() + "$value", "clear"); + JCStatement clearKeyCall = maker.Exec(maker.Apply(jceBlank, thisDotKeyFieldDotClear, jceBlank)); + JCStatement clearValueCall = maker.Exec(maker.Apply(jceBlank, thisDotValueFieldDotClear, jceBlank)); + JCExpression cond = maker.Binary(CTC_NOT_EQUAL, thisDotKeyField, maker.Literal(CTC_BOT, null)); + JCBlock clearCalls = maker.Block(0, List.of(clearKeyCall, clearValueCall)); + JCStatement ifSetCallClear = maker.If(cond, clearCalls, null); + List<JCStatement> statements = returnStatement != null ? List.of(ifSetCallClear, returnStatement) : List.of(ifSetCallClear); + + JCBlock body = maker.Block(0, statements); + Name methodName = builderType.toName(HandlerUtil.buildAccessorName("clear", data.getPluralName().toString())); + JCMethodDecl method = maker.MethodDef(mods, methodName, returnType, typeParams, params, thrown, body, null); + injectMethod(builderType, method); + } + + 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 = 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"); + Name valueName = builderType.toName(data.getSingularName().toString() + "Value"); + /* this.pluralname$key.add(singularnameKey); */ { + JCExpression thisDotKeyFieldDotAdd = chainDots(builderType, "this", data.getPluralName() + "$key", "add"); + JCExpression invokeAdd = maker.Apply(List.<JCExpression>nil(), thisDotKeyFieldDotAdd, List.<JCExpression>of(maker.Ident(keyName))); + statements.append(maker.Exec(invokeAdd)); + } + /* this.pluralname$value.add(singularnameValue); */ { + JCExpression thisDotValueFieldDotAdd = chainDots(builderType, "this", data.getPluralName() + "$value", "add"); + JCExpression invokeAdd = maker.Apply(List.<JCExpression>nil(), thisDotValueFieldDotAdd, List.<JCExpression>of(maker.Ident(valueName))); + statements.append(maker.Exec(invokeAdd)); + } + if (returnStatement != null) statements.append(returnStatement); + JCBlock body = maker.Block(0, statements.toList()); + long paramFlags = JavacHandlerUtil.addFinalIfNeeded(Flags.PARAMETER, builderType.getContext()); + + Name name = data.getSingularName(); + if (!fluent) name = builderType.toName(HandlerUtil.buildAccessorName("put", name.toString())); + JCExpression paramTypeKey = cloneParamType(0, maker, data.getTypeArgs(), builderType, source); + JCExpression paramTypeValue = cloneParamType(1, maker, data.getTypeArgs(), builderType, source); + JCVariableDecl paramKey = maker.VarDef(maker.Modifiers(paramFlags), keyName, paramTypeKey, null); + JCVariableDecl paramValue = maker.VarDef(maker.Modifiers(paramFlags), valueName, paramTypeValue, null); + JCMethodDecl method = maker.MethodDef(mods, name, returnType, typeParams, List.of(paramKey, paramValue), thrown, body, null); + injectMethod(builderType, method); + } + + 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 = 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()); + long baseFlags = JavacHandlerUtil.addFinalIfNeeded(0, builderType.getContext()); + Name entryName = builderType.toName("$lombokEntry"); + + JCExpression forEachType = chainDots(builderType, "java", "util", "Map", "Entry"); + forEachType = addTypeArgs(2, true, builderType, forEachType, data.getTypeArgs(), source); + JCExpression keyArg = maker.Apply(List.<JCExpression>nil(), maker.Select(maker.Ident(entryName), builderType.toName("getKey")), List.<JCExpression>nil()); + JCExpression valueArg = maker.Apply(List.<JCExpression>nil(), maker.Select(maker.Ident(entryName), builderType.toName("getValue")), List.<JCExpression>nil()); + JCExpression addKey = maker.Apply(List.<JCExpression>nil(), chainDots(builderType, "this", data.getPluralName() + "$key", "add"), List.of(keyArg)); + JCExpression addValue = maker.Apply(List.<JCExpression>nil(), chainDots(builderType, "this", data.getPluralName() + "$value", "add"), List.of(valueArg)); + JCBlock forEachBody = maker.Block(0, List.<JCStatement>of(maker.Exec(addKey), maker.Exec(addValue))); + JCExpression entrySetInvocation = maker.Apply(jceBlank, maker.Select(maker.Ident(data.getPluralName()), builderType.toName("entrySet")), jceBlank); + JCStatement forEach = maker.ForeachLoop(maker.VarDef(maker.Modifiers(baseFlags), entryName, forEachType, null), entrySetInvocation, forEachBody); + statements.append(forEach); + + if (returnStatement != null) statements.append(returnStatement); + JCBlock body = maker.Block(0, statements.toList()); + Name name = data.getPluralName(); + if (!fluent) name = builderType.toName(HandlerUtil.buildAccessorName("putAll", name.toString())); + JCExpression paramType = chainDots(builderType, "java", "util", "Map"); + paramType = addTypeArgs(2, true, builderType, paramType, data.getTypeArgs(), source); + JCVariableDecl param = maker.VarDef(maker.Modifiers(paramFlags), data.getPluralName(), paramType, null); + JCMethodDecl method = maker.MethodDef(mods, name, returnType, typeParams, List.of(param), jceBlank, body, null); + injectMethod(builderType, method); + } + + @Override public void appendBuildCode(SingularData data, JavacNode builderType, JCTree source, ListBuffer<JCStatement> statements, Name targetVariableName) { + if (useGuavaInstead(builderType)) { + guavaMapSingularizer.appendBuildCode(data, builderType, source, statements, targetVariableName); + return; + } + + JavacTreeMaker maker = builderType.getTreeMaker(); + + if (data.getTargetFqn().equals("java.util.Map")) { + statements.appendList(createJavaUtilSetMapInitialCapacitySwitchStatements(maker, data, builderType, true, "emptyMap", "singletonMap", "LinkedHashMap", source)); + } else { + statements.appendList(createJavaUtilSimpleCreationAndFillStatements(maker, data, builderType, true, true, false, true, "TreeMap", source)); + } + } +} diff --git a/src/core/lombok/javac/handlers/singulars/JavacJavaUtilSetSingularizer.java b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilSetSingularizer.java new file mode 100644 index 00000000..317233cb --- /dev/null +++ b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilSetSingularizer.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2015 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.singulars; + +import org.mangosdk.spi.ProviderFor; + +import lombok.core.LombokImmutableList; +import lombok.javac.JavacNode; +import lombok.javac.JavacTreeMaker; +import lombok.javac.handlers.JavacSingularsRecipes.JavacSingularizer; +import lombok.javac.handlers.JavacSingularsRecipes.SingularData; + +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.JCStatement; +import com.sun.tools.javac.util.ListBuffer; +import com.sun.tools.javac.util.Name; + +@ProviderFor(JavacSingularizer.class) +public class JavacJavaUtilSetSingularizer extends JavacJavaUtilListSetSingularizer { + @Override public LombokImmutableList<String> getSupportedTypes() { + 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) { + if (useGuavaInstead(builderType)) { + guavaListSetSingularizer.appendBuildCode(data, builderType, source, statements, targetVariableName); + return; + } + + JavacTreeMaker maker = builderType.getTreeMaker(); + + if (data.getTargetFqn().equals("java.util.Set")) { + statements.appendList(createJavaUtilSetMapInitialCapacitySwitchStatements(maker, data, builderType, false, "emptySet", "singleton", "LinkedHashSet", source)); + } else { + statements.appendList(createJavaUtilSimpleCreationAndFillStatements(maker, data, builderType, false, true, false, true, "TreeSet", source)); + } + } +} diff --git a/src/core/lombok/javac/handlers/singulars/JavacJavaUtilSingularizer.java b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilSingularizer.java new file mode 100644 index 00000000..0589ac34 --- /dev/null +++ b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilSingularizer.java @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2015 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.singulars; + +import static lombok.javac.Javac.*; +import static lombok.javac.handlers.JavacHandlerUtil.*; + +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.JCCase; +import com.sun.tools.javac.tree.JCTree.JCExpression; +import com.sun.tools.javac.tree.JCTree.JCStatement; +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.javac.JavacNode; +import lombok.javac.JavacTreeMaker; +import lombok.javac.handlers.JavacSingularsRecipes.JavacSingularizer; +import lombok.javac.handlers.JavacSingularsRecipes.SingularData; + +abstract class JavacJavaUtilSingularizer extends JavacSingularizer { + protected final JavacSingularizer guavaListSetSingularizer = new JavacGuavaSetListSingularizer(); + protected final JavacSingularizer guavaMapSingularizer = new JavacGuavaMapSingularizer(); + + protected boolean useGuavaInstead(JavacNode node) { + 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) { + List<JCExpression> jceBlank = List.nil(); + ListBuffer<JCCase> cases = new ListBuffer<JCCase>(); + + if (emptyCollectionMethod != null) { // case 0: (empty); break; + JCStatement assignStat; { + // pluralName = java.util.Collections.emptyCollectionMethod(); + JCExpression invoke = maker.Apply(jceBlank, chainDots(builderType, "java", "util", "Collections", emptyCollectionMethod), jceBlank); + assignStat = maker.Exec(maker.Assign(maker.Ident(data.getPluralName()), invoke)); + } + JCStatement breakStat = maker.Break(null); + JCCase emptyCase = maker.Case(maker.Literal(CTC_INT, 0), List.of(assignStat, breakStat)); + cases.append(emptyCase); + } + + if (singletonCollectionMethod != null) { // case 1: (singleton); break; + JCStatement assignStat; { + // !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)); + 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)); + args = List.of(arg, arg2); + } else { + args = List.of(arg); + } + JCExpression invoke = maker.Apply(jceBlank, chainDots(builderType, "java", "util", "Collections", singletonCollectionMethod), args); + assignStat = maker.Exec(maker.Assign(maker.Ident(data.getPluralName()), invoke)); + } + JCStatement breakStat = maker.Break(null); + JCCase singletonCase = maker.Case(maker.Literal(CTC_INT, 1), List.of(assignStat, breakStat)); + cases.append(singletonCase); + } + + { // default: + List<JCStatement> statements = createJavaUtilSimpleCreationAndFillStatements(maker, data, builderType, mapMode, false, true, emptyCollectionMethod == null, targetType, source); + 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()); + 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); + return List.of(varDefStat, switchStat); + } + + protected JCStatement createConstructBuilderVarIfNeeded(JavacTreeMaker maker, SingularData data, JavacNode builderType, boolean mapMode, JCTree source) { + List<JCExpression> jceBlank = List.nil(); + + Name v1Name = mapMode ? builderType.toName(data.getPluralName() + "$key") : data.getPluralName(); + Name v2Name = mapMode ? builderType.toName(data.getPluralName() + "$value") : null; + JCExpression thisDotField = maker.Select(maker.Ident(builderType.toName("this")), v1Name); + JCExpression cond = maker.Binary(CTC_EQUAL, thisDotField, maker.Literal(CTC_BOT, null)); + thisDotField = maker.Select(maker.Ident(builderType.toName("this")), v1Name); + JCExpression v1Type = chainDots(builderType, "java", "util", "ArrayList"); + v1Type = addTypeArgs(1, false, builderType, v1Type, data.getTypeArgs(), source); + JCExpression constructArrayList = maker.NewClass(null, jceBlank, v1Type, jceBlank, null); + JCStatement initV1 = maker.Exec(maker.Assign(thisDotField, constructArrayList)); + JCStatement thenPart; + if (mapMode) { + thisDotField = maker.Select(maker.Ident(builderType.toName("this")), v2Name); + JCExpression v2Type = chainDots(builderType, "java", "util", "ArrayList"); + List<JCExpression> tArgs = data.getTypeArgs(); + if (tArgs != null && tArgs.tail != null) tArgs = tArgs.tail; + else tArgs = List.nil(); + v2Type = addTypeArgs(1, false, builderType, v2Type, tArgs, source); + constructArrayList = maker.NewClass(null, jceBlank, v2Type, jceBlank, null); + JCStatement initV2 = maker.Exec(maker.Assign(thisDotField, constructArrayList)); + thenPart = maker.Block(0, List.of(initV1, initV2)); + } else { + thenPart = initV1; + } + 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) { + List<JCExpression> jceBlank = List.nil(); + Name thisName = builderType.toName("this"); + + JCStatement createStat; { + // pluralName = new java.util.TargetType(initialCap); + List<JCExpression> constructorArgs = List.nil(); + if (addInitialCapacityArg) { + 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 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 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)); + } + + JCExpression targetTypeExpr = chainDots(builderType, "java", "util", targetType); + targetTypeExpr = addTypeArgs(mapMode ? 2 : 1, false, builderType, targetTypeExpr, data.getTypeArgs(), source); + JCExpression constructorCall = maker.NewClass(null, jceBlank, targetTypeExpr, constructorArgs, null); + if (defineVar) { + JCExpression localShadowerType = chainDotsString(builderType, data.getTargetFqn()); + localShadowerType = addTypeArgs(mapMode ? 2 : 1, false, builderType, localShadowerType, data.getTypeArgs(), source); + createStat = maker.VarDef(maker.Modifiers(0), data.getPluralName(), localShadowerType, constructorCall); + } else { + createStat = maker.Exec(maker.Assign(maker.Ident(data.getPluralName()), constructorCall)); + } + } + + JCStatement fillStat; { + if (mapMode) { + // for (int $i = 0; $i < this.pluralname$key.size(); i++) pluralname.put(this.pluralname$key.get($i), this.pluralname$value.get($i)); + 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))); + // [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 incrementExpr = maker.Unary(CTC_POSTINC, maker.Ident(ivar)); + fillStat = maker.ForLoop(List.of(forInit), checkExpr, List.of(maker.Exec(incrementExpr)), putStatement); + } else { + // pluralname.addAll(this.pluralname); + JCExpression thisDotPluralName = maker.Select(maker.Ident(thisName), data.getPluralName()); + fillStat = maker.Exec(maker.Apply(jceBlank, maker.Select(maker.Ident(data.getPluralName()), builderType.toName("addAll")), List.of(thisDotPluralName))); + } + if (nullGuard) { + JCExpression thisDotField = maker.Select(maker.Ident(thisName), mapMode ? builderType.toName(data.getPluralName() + "$key") : data.getPluralName()); + JCExpression nullCheck = maker.Binary(CTC_NOT_EQUAL, thisDotField, maker.Literal(CTC_BOT, null)); + fillStat = maker.If(nullCheck, fillStat, null); + } + } + JCStatement unmodifiableStat; { + // pluralname = Collections.unmodifiableInterfaceType(pluralname); + JCExpression arg = maker.Ident(data.getPluralName()); + JCExpression invoke = maker.Apply(jceBlank, chainDots(builderType, "java", "util", "Collections", "unmodifiable" + data.getTargetSimpleType()), List.of(arg)); + unmodifiableStat = maker.Exec(maker.Assign(maker.Ident(data.getPluralName()), invoke)); + } + + return List.of(createStat, fillStat, unmodifiableStat); + } +} diff --git a/src/core/lombok/package-info.java b/src/core/lombok/package-info.java index b5406a74..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="http://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 cd8652d6..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="http://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 { +} diff --git a/src/core9/module-info.java b/src/core9/module-info.java new file mode 100644 index 00000000..87f819e2 --- /dev/null +++ b/src/core9/module-info.java @@ -0,0 +1,38 @@ +/* + * 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. + */ +module lombok { + requires java.compiler; + requires java.instrument; + requires jdk.unsupported; + + exports lombok; + exports lombok.experimental; + exports lombok.extern.apachecommons; + exports lombok.extern.java; + exports lombok.extern.jbosslog; + exports lombok.extern.log4j; + exports lombok.extern.slf4j; + + provides javax.annotation.processing.Processor with lombok.launch.AnnotationProcessorHider.AnnotationProcessor; + provides org.mapstruct.ap.spi.AstModifyingAnnotationProcessor with lombok.launch.AnnotationProcessorHider.AstModificationNotifier; +} + diff --git a/src/delombok/lombok/delombok/Delombok.java b/src/delombok/lombok/delombok/Delombok.java index f64e36a1..0d887cb9 100644 --- a/src/delombok/lombok/delombok/Delombok.java +++ b/src/delombok/lombok/delombok/Delombok.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2010 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,12 +30,14 @@ import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintStream; import java.io.Writer; +import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URI; import java.nio.charset.Charset; import java.nio.charset.UnsupportedCharsetException; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.IdentityHashMap; @@ -43,15 +45,23 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.ListIterator; import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; import javax.tools.DiagnosticListener; +import javax.tools.JavaFileManager; import javax.tools.JavaFileObject; import lombok.Lombok; import lombok.javac.CommentCatcher; +import lombok.javac.Javac; import lombok.javac.LombokOptions; +import lombok.javac.apt.LombokProcessor; +import com.sun.tools.javac.code.Symtab; import com.sun.tools.javac.comp.Todo; +import com.sun.tools.javac.file.BaseFileManager; +import com.sun.tools.javac.main.Arguments; import com.sun.tools.javac.main.JavaCompiler; import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; import com.sun.tools.javac.util.Context; @@ -204,7 +214,7 @@ public class Delombok { System.out.println(indentAndWordbreak(e.getValue(), 4, 70)); } System.out.println("Example: -f indent:4 -f emptyLines:indent"); - System.out.println("The '-f pretty' option is shorthand for '-f suppressWarnings:skip -f danceAroundIdeChecks:skip -f generateDelombokComment:skip -f javaLangAsFQN:skip'"); + System.out.println("The '-f pretty' option is shorthand for '-f suppressWarnings:skip -f generated:skip -f danceAroundIdeChecks:skip -f generateDelombokComment:skip -f javaLangAsFQN:skip'"); System.exit(0); return; } @@ -301,6 +311,7 @@ public class Delombok { if (prettyEnabled) { if (!formatPrefs.containsKey("suppresswarnings")) formatPrefs.put("suppresswarnings", "skip"); + if (!formatPrefs.containsKey("generated")) formatPrefs.put("generated", "skip"); if (!formatPrefs.containsKey("dancearoundidechecks")) formatPrefs.put("dancearoundidechecks", "skip"); if (!formatPrefs.containsKey("generatedelombokcomment")) formatPrefs.put("generatedelombokcomment", "skip"); if (!formatPrefs.containsKey("javalangasfqn")) formatPrefs.put("javalangasfqn", "skip"); @@ -465,26 +476,83 @@ public class Delombok { return out; } + private static final Field MODULE_FIELD = getModuleField(); + private static Field getModuleField() { + try { + return JCCompilationUnit.class.getField("modle"); + } catch (NoSuchFieldException e) { + return null; + } catch (SecurityException e) { + return null; + } + } + public boolean delombok() throws IOException { LombokOptions options = LombokOptionsFactory.getDelombokOptions(context); options.deleteLombokAnnotations(); options.putJavacOption("ENCODING", charset.name()); - if (classpath != null) options.putJavacOption("CLASSPATH", classpath); + if (classpath != null) options.putJavacOption("CLASSPATH", unpackClasspath(classpath)); if (sourcepath != null) options.putJavacOption("SOURCEPATH", sourcepath); - if (bootclasspath != null) options.putJavacOption("BOOTCLASSPATH", bootclasspath); + if (bootclasspath != null) options.putJavacOption("BOOTCLASSPATH", unpackClasspath(bootclasspath)); options.setFormatPreferences(new FormatPreferences(formatPrefs)); options.put("compilePolicy", "check"); + if (Javac.getJavaCompilerVersion() >= 9) { + Arguments args = Arguments.instance(context); + List<String> argsList = new ArrayList<String>(); + if (classpath != null) { + argsList.add("--class-path"); + argsList.add(options.get("--class-path")); + } + if (sourcepath != null) { + argsList.add("--source-path"); + argsList.add(options.get("--source-path")); + } + if (bootclasspath != null) { + argsList.add("--boot-class-path"); + argsList.add(options.get("--boot-class-path")); + } + if (charset != null) { + argsList.add("-encoding"); + argsList.add(charset.name()); + } + String[] argv = argsList.toArray(new String[0]); + args.init("javac", argv); + } + CommentCatcher catcher = CommentCatcher.create(context); JavaCompiler compiler = catcher.getCompiler(); List<JCCompilationUnit> roots = new ArrayList<JCCompilationUnit>(); Map<JCCompilationUnit, File> baseMap = new IdentityHashMap<JCCompilationUnit, File>(); - compiler.initProcessAnnotations(Collections.singleton(new lombok.javac.apt.Processor())); + Set<LombokProcessor> processors = Collections.singleton(new lombok.javac.apt.LombokProcessor()); + + if (Javac.getJavaCompilerVersion() >= 9) { + JavaFileManager jfm_ = context.get(JavaFileManager.class); + if (jfm_ instanceof BaseFileManager) { + Arguments args = Arguments.instance(context); + ((BaseFileManager) jfm_).setContext(context); // reinit with options + ((BaseFileManager) jfm_).handleOptions(args.getDeferredFileManagerOptions()); + } + } + + if (Javac.getJavaCompilerVersion() < 9) { + compiler.initProcessAnnotations(processors); + } else { + compiler.initProcessAnnotations(processors, Collections.<JavaFileObject>emptySet(), Collections.<String>emptySet()); + } + + Object unnamedModule = null; + if (Javac.getJavaCompilerVersion() >= 9) unnamedModule = Symtab.instance(context).unnamedModule; for (File fileToParse : filesToParse) { - @SuppressWarnings("deprecation") JCCompilationUnit unit = compiler.parse(fileToParse.getAbsolutePath()); + JCCompilationUnit unit = compiler.parse(fileToParse.getAbsolutePath()); + if (Javac.getJavaCompilerVersion() >= 9) try { + MODULE_FIELD.set(unit, unnamedModule); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } baseMap.put(unit, fileToBase.get(fileToParse)); roots.add(unit); } @@ -497,9 +565,19 @@ public class Delombok { catcher.setComments(unit, new DocCommentIntegrator().integrate(catcher.getComments(unit), unit)); } + if (Javac.getJavaCompilerVersion() >= 9) { + compiler.initModules(com.sun.tools.javac.util.List.from(roots.toArray(new JCCompilationUnit[0]))); + } com.sun.tools.javac.util.List<JCCompilationUnit> trees = compiler.enterTrees(toJavacList(roots)); - JavaCompiler delegate = compiler.processAnnotations(trees); + JavaCompiler delegate; + if (Javac.getJavaCompilerVersion() < 9) { + delegate = compiler.processAnnotations(trees, com.sun.tools.javac.util.List.<String>nil()); + } else { + delegate = compiler; + Collection<String> c = com.sun.tools.javac.util.List.nil(); + compiler.processAnnotations(trees, c); + } Object care = callAttributeMethodOnJavaCompiler(delegate, delegate.todo); @@ -507,9 +585,9 @@ public class Delombok { FormatPreferences fps = new FormatPreferences(formatPrefs); for (JCCompilationUnit unit : roots) { DelombokResult result = new DelombokResult(catcher.getComments(unit), unit, force || options.isChanged(unit), fps); - if (verbose) feedback.printf("File: %s [%s]\n", unit.sourcefile.getName(), result.isChanged() ? "delomboked" : "unchanged"); + if (verbose) feedback.printf("File: %s [%s%s]\n", unit.sourcefile.getName(), result.isChanged() ? "delomboked" : "unchanged", force && !options.isChanged(unit) ? " (forced)" : ""); Writer rawWriter; - if (presetWriter != null) rawWriter = presetWriter; + if (presetWriter != null) rawWriter = createUnicodeEscapeWriter(presetWriter); else if (output == null) rawWriter = createStandardOutWriter(); else rawWriter = createFileWriter(output, baseMap.get(unit), unit.sourcefile.toUri()); BufferedWriter writer = new BufferedWriter(rawWriter); @@ -528,6 +606,29 @@ public class Delombok { return true; } + private String unpackClasspath(String cp) { + String[] parts = cp.split(Pattern.quote(File.pathSeparator)); + StringBuilder out = new StringBuilder(); + for (String p : parts) { + if (!p.endsWith("*")) { + if (out.length() > 0) out.append(File.pathSeparator); + out.append(p); + continue; + } + File f = new File(p.substring(0, p.length() - 2)); + File[] files = f.listFiles(); + if (files == null) continue; + for (File file : files) { + if (file.isFile()) { + if (out.length() > 0) out.append(File.pathSeparator); + out.append(p, 0, p.length() - 1); + out.append(file.getName()); + } + } + } + return out.toString(); + } + private static Method attributeMethod; /** Method is needed because the call signature has changed between javac6 and javac7; no matter what we compile against, using delombok in the other means VerifyErrors. */ private static Object callAttributeMethodOnJavaCompiler(JavaCompiler compiler, Todo arg) { @@ -605,6 +706,10 @@ public class Delombok { return createUnicodeEscapeWriter(System.out); } + private Writer createUnicodeEscapeWriter(Writer writer) { + return new UnicodeEscapeWriter(writer, charset); + } + private Writer createUnicodeEscapeWriter(OutputStream out) { return new UnicodeEscapeWriter(new OutputStreamWriter(out, charset), charset); } diff --git a/src/delombok/lombok/delombok/DelombokApp.java b/src/delombok/lombok/delombok/DelombokApp.java index 276bd7de..aa753fc8 100644 --- a/src/delombok/lombok/delombok/DelombokApp.java +++ b/src/delombok/lombok/delombok/DelombokApp.java @@ -88,7 +88,7 @@ public class DelombokApp extends LombokApp { // Since we only read from it, not closing it should not be a problem. @SuppressWarnings({"resource", "all"}) final JarFile toolsJarFile = new JarFile(toolsJar); - ClassLoader loader = new ClassLoader() { + ClassLoader loader = new ClassLoader(DelombokApp.class.getClassLoader()) { private Class<?> loadStreamAsClass(String name, boolean resolve, InputStream in) throws ClassNotFoundException { try { try { @@ -107,16 +107,24 @@ public class DelombokApp extends LombokApp { } finally { in.close(); } - } catch (IOException e2) { + } catch (Exception e2) { throw new ClassNotFoundException(name, e2); } } @Override protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { - String rawName = name.replace(".", "/") + ".class"; + String rawName, altName; { + String binName = name.replace(".", "/"); + rawName = binName + ".class"; + altName = binName + ".SCL.lombok"; + } JarEntry entry = toolsJarFile.getJarEntry(rawName); if (entry == null) { - if (name.startsWith("lombok.")) return loadStreamAsClass(name, resolve, super.getResourceAsStream(rawName)); + if (name.startsWith("lombok.")) { + InputStream res = getParent().getResourceAsStream(rawName); + if (res == null) res = getParent().getResourceAsStream(altName); + return loadStreamAsClass(name, resolve, res); + } return super.loadClass(name, resolve); } diff --git a/src/delombok/lombok/delombok/DelombokResult.java b/src/delombok/lombok/delombok/DelombokResult.java index 84aeb68b..8985b257 100644 --- a/src/delombok/lombok/delombok/DelombokResult.java +++ b/src/delombok/lombok/delombok/DelombokResult.java @@ -65,7 +65,8 @@ public class DelombokResult { else comments_ = com.sun.tools.javac.util.List.from(comments.toArray(new CommentInfo[0])); FormatPreferences preferences = new FormatPreferenceScanner().scan(formatPreferences, getContent()); - compilationUnit.accept(new PrettyCommentsPrinter(out, compilationUnit, comments_, preferences)); + //compilationUnit.accept(new PrettyCommentsPrinter(out, compilationUnit, comments_, preferences)); + compilationUnit.accept(new PrettyPrinter(out, compilationUnit, comments_, preferences)); } private CharSequence getContent() throws IOException { diff --git a/src/delombok/lombok/delombok/DocCommentIntegrator.java b/src/delombok/lombok/delombok/DocCommentIntegrator.java index c66ff0ec..bab0abd8 100644 --- a/src/delombok/lombok/delombok/DocCommentIntegrator.java +++ b/src/delombok/lombok/delombok/DocCommentIntegrator.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2010 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,6 +29,7 @@ import java.util.regex.Pattern; import lombok.javac.CommentInfo; import lombok.javac.Javac; +import lombok.javac.PackageName; import lombok.javac.handlers.JavacHandlerUtil; import com.sun.tools.javac.parser.Tokens.Comment; @@ -73,7 +74,7 @@ public class DocCommentIntegrator { return out; } - private static final Pattern CONTENT_STRIPPER = Pattern.compile("^(?:\\s*\\*)?[ \\t]*(.*?)$", Pattern.MULTILINE); + private static final Pattern CONTENT_STRIPPER = Pattern.compile("^(?:\\s*\\*)?(.*?)$", Pattern.MULTILINE); @SuppressWarnings("unchecked") private boolean attach(JCCompilationUnit top, final JCTree node, CommentInfo cmt) { String docCommentContent = cmt.content; if (docCommentContent.startsWith("/**")) docCommentContent = docCommentContent.substring(3); @@ -120,7 +121,8 @@ public class DocCommentIntegrator { } private JCTree findJavadocableNodeOnOrAfter(JCCompilationUnit unit, int endPos) { - if (unit.pid != null && endPos <= unit.pid.pos) return null; + JCTree pid = PackageName.getPackageNode(unit); + if (pid != null && endPos <= pid.pos) return null; Iterator<JCTree> it = unit.defs.iterator(); while (it.hasNext()) { diff --git a/src/delombok/lombok/delombok/FormatPreferences.java b/src/delombok/lombok/delombok/FormatPreferences.java index 9bd668a5..3861685d 100644 --- a/src/delombok/lombok/delombok/FormatPreferences.java +++ b/src/delombok/lombok/delombok/FormatPreferences.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 The Project Lombok Authors. + * Copyright (C) 2013-2015 The Project Lombok Authors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -33,6 +33,7 @@ public final class FormatPreferences { private final boolean generateConstructorProperties; private final boolean generateSuppressWarnings, danceAroundIdeChecks, generateDelombokComment, javaLangAsFqn; final Map<String, String> rawMap; + private final boolean generateGenerated; static final Map<String, String> KEYS; @@ -42,7 +43,8 @@ public final class FormatPreferences { keys.put("emptyLines", "Either 'indent' or 'blank'. indent means: Indent an empty line to the right level. Default: 'blank'"); keys.put("finalParams", "Either 'generate' or 'skip'. generate means: All lombok-generated methods set all parameters to final. Default: 'generate'"); keys.put("constructorProperties", "Either 'generate' or 'skip'. generate means: All lombok-generated constructors with 1 or more arguments get an @ConstructorProperties annotation. Default: 'generate'"); - keys.put("suppressWarnings", "Either 'generate' or 'skip'. generate means: All lombok-generated methods get a @SuppressWarnings annotation. Default: 'generate'"); + keys.put("suppressWarnings", "Either 'generate' or 'skip'. generate means: All lombok-generated methods, types, and fields get a @SuppressWarnings annotation. Default: 'generate'"); + keys.put("generated", "Either 'generate' or 'skip'. generate means: All lombok-generated methods, types, and fields get a @javax.annotation.Generated(\"lombok\") annotation. Default: 'generate'"); keys.put("danceAroundIdeChecks", "Either 'generate' or 'skip'. generate means: Lombok will intentionally obfuscate some generated code to avoid IDE warnings. Default: 'generate'"); keys.put("generateDelombokComment", "Either 'generate' or 'skip'. generate means: Any file modified by delombok will have a comment stating this at the top. Default: 'generate'"); keys.put("javaLangAsFQN", "Either 'generate' or 'skip'. generate means: Any generated reference to java.lang classes are prefixed with `java.lang.`. Default: 'generate'"); @@ -82,6 +84,7 @@ public final class FormatPreferences { this.generateFinalParams = unrollBoolean(preferences, "finalParams", "generate", "skip", true); this.generateConstructorProperties = unrollBoolean(preferences, "constructorProperties", "generate", "skip", true); this.generateSuppressWarnings = unrollBoolean(preferences, "suppressWarnings", "generate", "skip", true); + this.generateGenerated = unrollBoolean(preferences, "generated", "generate", "skip", true); this.danceAroundIdeChecks = unrollBoolean(preferences, "danceAroundIdeChecks", "generate", "skip", true); this.generateDelombokComment = unrollBoolean(preferences, "generateDelombokComment", "generate", "skip", true); this.javaLangAsFqn = unrollBoolean(preferences, "javaLangAsFQN", "generate", "skip", true); @@ -112,6 +115,10 @@ public final class FormatPreferences { return generateSuppressWarnings; } + public boolean generateGenerated() { + return generateGenerated; + } + public boolean generateFinalParams() { return generateFinalParams; } @@ -131,4 +138,8 @@ public final class FormatPreferences { public boolean generateConstructorProperties() { return generateConstructorProperties; } + + @Override public String toString() { + return rawMap.toString(); + } } diff --git a/src/delombok/lombok/delombok/LombokOptionsFactory.java b/src/delombok/lombok/delombok/LombokOptionsFactory.java index 47921931..62dc953a 100644 --- a/src/delombok/lombok/delombok/LombokOptionsFactory.java +++ b/src/delombok/lombok/delombok/LombokOptionsFactory.java @@ -24,6 +24,7 @@ package lombok.delombok; import lombok.javac.Javac; import lombok.javac.Javac6BasedLombokOptions; import lombok.javac.Javac8BasedLombokOptions; +import lombok.javac.Javac9BasedLombokOptions; import lombok.javac.LombokOptions; import com.sun.tools.javac.util.Context; @@ -41,9 +42,15 @@ public class LombokOptionsFactory { @Override LombokOptions createAndRegisterOptions(Context context) { return Javac8BasedLombokOptions.replaceWithDelombokOptions(context); } + }, + + JDK9 { + @Override LombokOptions createAndRegisterOptions(Context context) { + return Javac9BasedLombokOptions.replaceWithDelombokOptions(context); + } }; - abstract LombokOptions createAndRegisterOptions(Context context); + abstract LombokOptions createAndRegisterOptions(Context context); } public static LombokOptions getDelombokOptions(Context context) { @@ -53,8 +60,10 @@ public class LombokOptionsFactory { LombokOptions options; if (Javac.getJavaCompilerVersion() < 8) { options = LombokOptionCompilerVersion.JDK7_AND_LOWER.createAndRegisterOptions(context); - } else { + } else if (Javac.getJavaCompilerVersion() == 8) { options = LombokOptionCompilerVersion.JDK8.createAndRegisterOptions(context); + } else { + options = LombokOptionCompilerVersion.JDK9.createAndRegisterOptions(context); } return options; } diff --git a/src/delombok/lombok/delombok/PrettyCommentsPrinter.java b/src/delombok/lombok/delombok/PrettyCommentsPrinter.java deleted file mode 100644 index 9dd7e40e..00000000 --- a/src/delombok/lombok/delombok/PrettyCommentsPrinter.java +++ /dev/null @@ -1,1680 +0,0 @@ -/* - * Copyright 1999-2006 Sun Microsystems, Inc. All Rights Reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Sun designates this - * particular file as subject to the "Classpath" exception as provided - * by Sun in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, - * CA 95054 USA or visit www.sun.com if you need additional information or - * have any questions. - */ - -/* - * Code derived from com.sun.tools.javac.tree.Pretty, from the langtools project. - * A version can be found at, for example, http://hg.openjdk.java.net/jdk7/build/langtools - */ -package lombok.delombok; - -import static com.sun.tools.javac.code.Flags.*; -import static lombok.javac.Javac.*; -import static lombok.javac.JavacTreeMaker.TreeTag.treeTag; -import static lombok.javac.JavacTreeMaker.TypeTag.typeTag; - -import java.io.IOException; -import java.io.Writer; -import java.lang.reflect.Field; -import java.util.HashMap; -import java.util.Map; - -import lombok.javac.CommentInfo; -import lombok.javac.CommentInfo.EndConnection; -import lombok.javac.CommentInfo.StartConnection; -import lombok.javac.JavacTreeMaker.TreeTag; -import lombok.javac.JavacTreeMaker.TypeTag; - -import com.sun.source.tree.Tree; -import com.sun.tools.javac.code.Flags; -import com.sun.tools.javac.code.Symbol; -import com.sun.tools.javac.tree.DocCommentTable; -import com.sun.tools.javac.tree.JCTree; -import com.sun.tools.javac.tree.JCTree.JCAnnotation; -import com.sun.tools.javac.tree.JCTree.JCArrayAccess; -import com.sun.tools.javac.tree.JCTree.JCArrayTypeTree; -import com.sun.tools.javac.tree.JCTree.JCAssert; -import com.sun.tools.javac.tree.JCTree.JCAssign; -import com.sun.tools.javac.tree.JCTree.JCAssignOp; -import com.sun.tools.javac.tree.JCTree.JCBinary; -import com.sun.tools.javac.tree.JCTree.JCBlock; -import com.sun.tools.javac.tree.JCTree.JCBreak; -import com.sun.tools.javac.tree.JCTree.JCCase; -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.JCConditional; -import com.sun.tools.javac.tree.JCTree.JCContinue; -import com.sun.tools.javac.tree.JCTree.JCDoWhileLoop; -import com.sun.tools.javac.tree.JCTree.JCEnhancedForLoop; -import com.sun.tools.javac.tree.JCTree.JCErroneous; -import com.sun.tools.javac.tree.JCTree.JCExpression; -import com.sun.tools.javac.tree.JCTree.JCExpressionStatement; -import com.sun.tools.javac.tree.JCTree.JCFieldAccess; -import com.sun.tools.javac.tree.JCTree.JCForLoop; -import com.sun.tools.javac.tree.JCTree.JCIdent; -import com.sun.tools.javac.tree.JCTree.JCIf; -import com.sun.tools.javac.tree.JCTree.JCImport; -import com.sun.tools.javac.tree.JCTree.JCInstanceOf; -import com.sun.tools.javac.tree.JCTree.JCLabeledStatement; -import com.sun.tools.javac.tree.JCTree.JCLiteral; -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.JCNewArray; -import com.sun.tools.javac.tree.JCTree.JCNewClass; -import com.sun.tools.javac.tree.JCTree.JCParens; -import com.sun.tools.javac.tree.JCTree.JCPrimitiveTypeTree; -import com.sun.tools.javac.tree.JCTree.JCReturn; -import com.sun.tools.javac.tree.JCTree.JCSkip; -import com.sun.tools.javac.tree.JCTree.JCStatement; -import com.sun.tools.javac.tree.JCTree.JCSwitch; -import com.sun.tools.javac.tree.JCTree.JCSynchronized; -import com.sun.tools.javac.tree.JCTree.JCThrow; -import com.sun.tools.javac.tree.JCTree.JCTry; -import com.sun.tools.javac.tree.JCTree.JCTypeApply; -import com.sun.tools.javac.tree.JCTree.JCTypeCast; -import com.sun.tools.javac.tree.JCTree.JCTypeParameter; -import com.sun.tools.javac.tree.JCTree.JCUnary; -import com.sun.tools.javac.tree.JCTree.JCVariableDecl; -import com.sun.tools.javac.tree.JCTree.JCWhileLoop; -import com.sun.tools.javac.tree.JCTree.JCWildcard; -import com.sun.tools.javac.tree.JCTree.LetExpr; -import com.sun.tools.javac.tree.JCTree.TypeBoundKind; -import com.sun.tools.javac.tree.TreeInfo; -import com.sun.tools.javac.tree.TreeScanner; -import com.sun.tools.javac.util.Convert; -import com.sun.tools.javac.util.List; -import com.sun.tools.javac.util.Name; -import com.sun.tools.javac.util.Position; -//import com.sun.tools.javac.code.TypeTags; - -/** Prints out a tree as an indented Java source program. - * - * <p><b>This is NOT part of any API supported by Sun Microsystems. If - * you write code that depends on this, you do so at your own risk. - * This code and its internal interfaces are subject to change or - * deletion without notice.</b> - */ -@SuppressWarnings("all") // Mainly sun code that has other warning settings -public class PrettyCommentsPrinter extends JCTree.Visitor { - private static final TreeTag PARENS = treeTag("PARENS"); - private static final TreeTag IMPORT = treeTag("IMPORT"); - private static final TreeTag VARDEF = treeTag("VARDEF"); - private static final TreeTag SELECT = treeTag("SELECT"); - - private static final Map<TreeTag, String> OPERATORS; - - // StandardFlags | DEFAULT - private static final long EXTENDED_STANDARD_FLAGS = 0x0fffL | 1L<<43; - - static { - Map<TreeTag, String> map = new HashMap<TreeTag, String>(); - - map.put(treeTag("POS"), "+"); - map.put(treeTag("NEG"), "-"); - map.put(treeTag("NOT"), "!"); - map.put(treeTag("COMPL"), "~"); - map.put(treeTag("PREINC"), "++"); - map.put(treeTag("PREDEC"), "--"); - map.put(treeTag("POSTINC"), "++"); - map.put(treeTag("POSTDEC"), "--"); - map.put(treeTag("NULLCHK"), "<*nullchk*>"); - map.put(treeTag("OR"), "||"); - map.put(treeTag("AND"), "&&"); - map.put(treeTag("EQ"), "=="); - map.put(treeTag("NE"), "!="); - map.put(treeTag("LT"), "<"); - map.put(treeTag("GT"), ">"); - map.put(treeTag("LE"), "<="); - map.put(treeTag("GE"), ">="); - map.put(treeTag("BITOR"), "|"); - map.put(treeTag("BITXOR"), "^"); - map.put(treeTag("BITAND"), "&"); - map.put(treeTag("SL"), "<<"); - map.put(treeTag("SR"), ">>"); - map.put(treeTag("USR"), ">>>"); - map.put(treeTag("PLUS"), "+"); - map.put(treeTag("MINUS"), "-"); - map.put(treeTag("MUL"), "*"); - map.put(treeTag("DIV"), "/"); - map.put(treeTag("MOD"), "%"); - - map.put(treeTag("BITOR_ASG"), "|="); - map.put(treeTag("BITXOR_ASG"), "^="); - map.put(treeTag("BITAND_ASG"), "&="); - map.put(treeTag("SL_ASG"), "<<="); - map.put(treeTag("SR_ASG"), ">>="); - map.put(treeTag("USR_ASG"), ">>>="); - map.put(treeTag("PLUS_ASG"), "+="); - map.put(treeTag("MINUS_ASG"), "-="); - map.put(treeTag("MUL_ASG"), "*="); - map.put(treeTag("DIV_ASG"), "/="); - map.put(treeTag("MOD_ASG"), "%="); - - OPERATORS = map; - } - - private List<CommentInfo> comments; - private final JCCompilationUnit cu; - private boolean onNewLine = true; - private boolean aligned = false; - private boolean inParams = false; - - private boolean needsSpace = false; - private boolean needsNewLine = false; - private boolean needsAlign = false; - - // Flag for try-with-resources to make them not final and not print the last semicolon. - // This flag is set just before printing the vardef and cleared when printing its modifiers. - private boolean suppressFinalAndSemicolonsInTry = false; - - private final FormatPreferences formatPreferences; - - public PrettyCommentsPrinter(Writer out, JCCompilationUnit cu, List<CommentInfo> comments, FormatPreferences preferences) { - this.out = out; - this.comments = comments; - this.cu = cu; - this.formatPreferences = preferences; - } - - private int endPos(JCTree tree) { - return getEndPosition(tree, cu); - } - - private void consumeComments(int until) throws IOException { - consumeComments(until, null); - } - - private void consumeComments(int until, JCTree tree) throws IOException { - boolean prevNewLine = onNewLine; - boolean found = false; - CommentInfo head = comments.head; - while (comments.nonEmpty() && head.pos < until) { - printComment(head); - comments = comments.tail; - head = comments.head; - } - if (!onNewLine && prevNewLine) { - println(); - } - } - - private void consumeTrailingComments(int from) throws IOException { - boolean prevNewLine = onNewLine; - CommentInfo head = comments.head; - boolean stop = false; - while (comments.nonEmpty() && head.prevEndPos == from && !stop && !(head.start == StartConnection.ON_NEXT_LINE || head.start == StartConnection.START_OF_LINE)) { - from = head.endPos; - printComment(head); - stop = (head.end == EndConnection.ON_NEXT_LINE); - comments = comments.tail; - head = comments.head; - } - if (!onNewLine && prevNewLine) { - println(); - } - } - - private void printComment(CommentInfo comment) throws IOException { - prepareComment(comment.start); - print(comment.content); - switch (comment.end) { - case ON_NEXT_LINE: - if (!aligned) { - needsNewLine = true; - needsAlign = true; - } - break; - case AFTER_COMMENT: - needsSpace = true; - break; - case DIRECT_AFTER_COMMENT: - // do nothing - break; - } - } - - private void prepareComment(StartConnection start) throws IOException { - switch (start) { - case DIRECT_AFTER_PREVIOUS: - needsSpace = false; - break; - case AFTER_PREVIOUS: - needsSpace = true; - break; - case START_OF_LINE: - needsNewLine = true; - needsAlign = false; - break; - case ON_NEXT_LINE: - if (!aligned) { - needsNewLine = true; - needsAlign = true; - } - break; - } - } - - /** The output stream on which trees are printed. - */ - Writer out; - - /** The current left margin. - */ - int lmargin = 0; - - /** The enclosing class name. - */ - Name enclClassName; - - /** A hashtable mapping trees to their documentation comments - * (can be null) - */ - Map<JCTree, String> docComments = null; - DocCommentTable docTable = null; - - String getJavadocFor(JCTree node) { - if (docComments != null) return docComments.get(node); - if (docTable != null) return docTable.getCommentText(node); - return null; - } - - /** Align code to be indented to left margin. - */ - void align() throws IOException { - onNewLine = false; - aligned = true; - needsAlign = false; - for (int i = 0; i < lmargin; i++) out.write(formatPreferences.indent()); - } - - /** Increase left margin by indentation width. - */ - void indent() { - lmargin++; - } - - /** Decrease left margin by indentation width. - */ - void undent() { - lmargin--; - } - - /** Enter a new precedence level. Emit a `(' if new precedence level - * is less than precedence level so far. - * @param contextPrec The precedence level in force so far. - * @param ownPrec The new precedence level. - */ - void open(int contextPrec, int ownPrec) throws IOException { - if (ownPrec < contextPrec) out.write("("); - } - - /** Leave precedence level. Emit a `(' if inner precedence level - * is less than precedence level we revert to. - * @param contextPrec The precedence level we revert to. - * @param ownPrec The inner precedence level. - */ - void close(int contextPrec, int ownPrec) throws IOException { - if (ownPrec < contextPrec) out.write(")"); - } - - /** Print string, replacing all non-ascii character with unicode escapes. - */ - public void print(Object s) throws IOException { - boolean align = needsAlign; - if (needsNewLine && !onNewLine) { - println(); - } - if (align && !aligned) { - align(); - } - if (needsSpace && !onNewLine && !aligned) { - out.write(' '); - } - needsSpace = false; - - out.write(Convert.escapeUnicode(s.toString())); - - onNewLine = false; - aligned = false; - } - - /** Print new line. - */ - public void println() throws IOException { - onNewLine = true; - aligned = false; - needsNewLine = false; - out.write(lineSep); - } - - String lineSep = System.getProperty("line.separator"); - - /************************************************************************** - * Traversal methods - *************************************************************************/ - - /** Exception to propagate IOException through visitXXX methods */ - private static class UncheckedIOException extends Error { - static final long serialVersionUID = -4032692679158424751L; - UncheckedIOException(IOException e) { - super(e.getMessage(), e); - } - } - - /** Visitor argument: the current precedence level. - */ - int prec; - - /** Visitor method: print expression tree. - * @param prec The current precedence level. - */ - public void printExpr(JCTree tree, int prec) throws IOException { - - int prevPrec = this.prec; - try { - this.prec = prec; - if (tree == null) print("/*missing*/"); - else { - consumeComments(tree.pos, tree); - tree.accept(this); - int endPos = endPos(tree); - consumeTrailingComments(endPos); - } - } catch (UncheckedIOException ex) { - IOException e = new IOException(ex.getMessage()); - e.initCause(ex); - throw e; - } finally { - this.prec = prevPrec; - } - } - - /** Derived visitor method: print expression tree at minimum precedence level - * for expression. - */ - public void printExpr(JCTree tree) throws IOException { - printExpr(tree, TreeInfo.noPrec); - } - - /** Derived visitor method: print statement tree. - */ - public void printStat(JCTree tree) throws IOException { - if (isEmptyStat(tree)) { - // printEmptyStat(); // -- starting in java 7, these get lost, so to be consistent, we never print them. - } else { - printExpr(tree, TreeInfo.notExpression); - } - } - - public void printEmptyStat() throws IOException { - print(";"); - } - - public boolean isEmptyStat(JCTree tree) { - if (!(tree instanceof JCBlock)) return false; - JCBlock block = (JCBlock) tree; - return (Position.NOPOS == block.pos) && block.stats.isEmpty(); - } - - /** Derived visitor method: print list of expression trees, separated by given string. - * @param sep the separator string - */ - public <T extends JCTree> void printExprs(List<T> trees, String sep) throws IOException { - if (trees.nonEmpty()) { - printExpr(trees.head); - for (List<T> l = trees.tail; l.nonEmpty(); l = l.tail) { - print(sep); - printExpr(l.head); - } - } - } - - /** Derived visitor method: print list of expression trees, separated by commas. - */ - public <T extends JCTree> void printExprs(List<T> trees) throws IOException { - printExprs(trees, ", "); - } - - /** Derived visitor method: print list of statements, each on a separate line. - */ - public void printStats(List<? extends JCTree> trees) throws IOException { - for (List<? extends JCTree> l = trees; l.nonEmpty(); l = l.tail) { - if (isSuppressed(l.head)) continue; - if (!suppressAlignmentForEmptyLines(l.head)) align(); - printStat(l.head); - println(); - } - } - - private boolean suppressAlignmentForEmptyLines(JCTree tree) { - return !formatPreferences.fillEmpties() && startsWithNewLine(tree); - } - - private boolean startsWithNewLine(JCTree tree) { - return tree instanceof JCMethodDecl || tree instanceof JCClassDecl; - } - - private boolean isSuppressed(JCTree tree) { - if (isEmptyStat(tree)) { - return true; - } - if (tree instanceof JCExpressionStatement) { - return isNoArgsSuperCall(((JCExpressionStatement)tree).expr); - } - return false; - } - - /** Print a set of modifiers. - */ - public void printFlags(long flags) throws IOException { - if ((flags & SYNTHETIC) != 0) print("/*synthetic*/ "); - if (suppressFinalAndSemicolonsInTry) { - flags = flags & ~FINAL; - suppressFinalAndSemicolonsInTry = false; - } - print(TreeInfo.flagNames(flags)); - if ((flags & EXTENDED_STANDARD_FLAGS) != 0) print(" "); - if ((flags & ANNOTATION) != 0) print("@"); - } - - public void printAnnotations(List<JCAnnotation> trees) throws IOException { - for (List<JCAnnotation> l = trees; l.nonEmpty(); l = l.tail) { - printStat(l.head); - if (inParams) { - print(" "); - } - else { - println(); - align(); - } - } - } - - /** Print documentation comment, if it exists - * @param tree The tree for which a documentation comment should be printed. - */ - public void printDocComment(JCTree tree) throws IOException { - String dc = getJavadocFor(tree); - if (dc == null) return; - print("/**"); println(); - int pos = 0; - int endpos = lineEndPos(dc, pos); - boolean atStart = true; - while (pos < dc.length()) { - String line = dc.substring(pos, endpos); - if (line.trim().isEmpty() && atStart) { - atStart = false; - continue; - } - atStart = false; - align(); - print(" *"); - if (pos < dc.length() && dc.charAt(pos) > ' ') print(" "); - print(dc.substring(pos, endpos)); println(); - pos = endpos + 1; - endpos = lineEndPos(dc, pos); - } - align(); print(" */"); println(); - align(); - } -//where - static int lineEndPos(String s, int start) { - int pos = s.indexOf('\n', start); - if (pos < 0) pos = s.length(); - return pos; - } - - /** If type parameter list is non-empty, print it enclosed in "<...>" brackets. - */ - public void printTypeParameters(List<JCTypeParameter> trees) throws IOException { - if (trees.nonEmpty()) { - print("<"); - printExprs(trees); - print(">"); - } - } - - /** Print a block. - */ - public void printBlock(List<? extends JCTree> stats, JCTree container) throws IOException { - print("{"); - println(); - indent(); - printStats(stats); - consumeComments(endPos(container)); - undent(); - align(); - print("}"); - } - - /** Print a block. - */ - public void printEnumBody(List<JCTree> stats) throws IOException { - print("{"); - println(); - indent(); - boolean first = true; - for (List<JCTree> l = stats; l.nonEmpty(); l = l.tail) { - if (isEnumerator(l.head)) { - if (!first) { - print(","); - println(); - } - align(); - printStat(l.head); - first = false; - } - } - print(";"); - println(); - int x = 0; - for (List<JCTree> l = stats; l.nonEmpty(); l = l.tail) { - x++; - if (!isEnumerator(l.head)) { - if (!suppressAlignmentForEmptyLines(l.head)) align(); - printStat(l.head); - println(); - } - } - undent(); - align(); - print("}"); - } - - public void printEnumMember(JCVariableDecl tree) throws IOException { - printAnnotations(tree.mods.annotations); - print(tree.name); - if (tree.init instanceof JCNewClass) { - JCNewClass constructor = (JCNewClass) tree.init; - if (constructor.args != null && constructor.args.nonEmpty()) { - print("("); - printExprs(constructor.args); - print(")"); - } - if (constructor.def != null && constructor.def.defs != null) { - print(" "); - printBlock(constructor.def.defs, constructor.def); - } - } - } - - /** Is the given tree an enumerator definition? */ - boolean isEnumerator(JCTree t) { - return VARDEF.equals(treeTag(t)) && (((JCVariableDecl) t).mods.flags & ENUM) != 0; - } - - /** Print unit consisting of package clause and import statements in toplevel, - * followed by class definition. if class definition == null, - * print all definitions in toplevel. - * @param tree The toplevel tree - * @param cdef The class definition, which is assumed to be part of the - * toplevel tree. - */ - public void printUnit(JCCompilationUnit tree, JCClassDecl cdef) throws IOException { - Object dc = getDocComments(tree); - loadDocCommentsTable(dc); - printDocComment(tree); - if (tree.pid != null) { - consumeComments(tree.pos, tree); - print("package "); - printExpr(tree.pid); - print(";"); - println(); - } - boolean firstImport = true; - for (List<JCTree> l = tree.defs; - l.nonEmpty() && (cdef == null || IMPORT.equals(treeTag(l.head))); - l = l.tail) { - if (IMPORT.equals(treeTag(l.head))) { - JCImport imp = (JCImport)l.head; - Name name = TreeInfo.name(imp.qualid); - if (name == name.table.fromChars(new char[] {'*'}, 0, 1) || - cdef == null || - isUsed(TreeInfo.symbol(imp.qualid), cdef)) { - if (firstImport) { - firstImport = false; - println(); - } - printStat(imp); - } - } else { - printStat(l.head); - } - } - if (cdef != null) { - printStat(cdef); - println(); - } - } - // where - @SuppressWarnings("unchecked") - private void loadDocCommentsTable(Object dc) { - if (dc instanceof Map<?, ?>) this.docComments = (Map) dc; - else if (dc instanceof DocCommentTable) this.docTable = (DocCommentTable) dc; - } - - boolean isUsed(final Symbol t, JCTree cdef) { - class UsedVisitor extends TreeScanner { - public void scan(JCTree tree) { - if (tree!=null && !result) tree.accept(this); - } - boolean result = false; - public void visitIdent(JCIdent tree) { - if (tree.sym == t) result = true; - } - } - UsedVisitor v = new UsedVisitor(); - v.scan(cdef); - return v.result; - } - - /************************************************************************** - * Visitor methods - *************************************************************************/ - - public void visitTopLevel(JCCompilationUnit tree) { - try { - printUnit(tree, null); - consumeComments(Integer.MAX_VALUE); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public void visitImport(JCImport tree) { - try { - print("import "); - if (tree.staticImport) print("static "); - printExpr(tree.qualid); - print(";"); - println(); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public void visitClassDef(JCClassDecl tree) { - try { - consumeComments(tree.pos, tree); - println(); align(); - printDocComment(tree); - printAnnotations(tree.mods.annotations); - printFlags(tree.mods.flags & ~INTERFACE); - Name enclClassNamePrev = enclClassName; - enclClassName = tree.name; - if ((tree.mods.flags & INTERFACE) != 0) { - print("interface " + tree.name); - printTypeParameters(tree.typarams); - if (tree.implementing.nonEmpty()) { - print(" extends "); - printExprs(tree.implementing); - } - } else { - if ((tree.mods.flags & ENUM) != 0) - print("enum " + tree.name); - else - print("class " + tree.name); - printTypeParameters(tree.typarams); - if (getExtendsClause(tree) != null) { - print(" extends "); - printExpr(getExtendsClause(tree)); - } - if (tree.implementing.nonEmpty()) { - print(" implements "); - printExprs(tree.implementing); - } - } - print(" "); - // <Added for delombok by Reinier Zwitserloot> - if ((tree.mods.flags & INTERFACE) != 0) { - removeImplicitModifiersForInterfaceMembers(tree.defs); - } - // </Added for delombok by Reinier Zwitserloot> - if ((tree.mods.flags & ENUM) != 0) { - printEnumBody(tree.defs); - } else { - printBlock(tree.defs, tree); - } - enclClassName = enclClassNamePrev; - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - // Added for delombok by Reinier Zwitserloot - private void removeImplicitModifiersForInterfaceMembers(List<JCTree> defs) { - for (JCTree def :defs) { - if (def instanceof JCVariableDecl) { - ((JCVariableDecl) def).mods.flags &= ~(Flags.PUBLIC | Flags.STATIC | Flags.FINAL); - } - if (def instanceof JCMethodDecl) { - ((JCMethodDecl) def).mods.flags &= ~(Flags.PUBLIC | Flags.ABSTRACT); - } - if (def instanceof JCClassDecl) { - ((JCClassDecl) def).mods.flags &= ~(Flags.PUBLIC | Flags.STATIC); - } - } - } - - public void visitMethodDef(JCMethodDecl tree) { - try { - boolean isConstructor = tree.name == tree.name.table.fromChars("<init>".toCharArray(), 0, 6); - // when producing source output, omit anonymous constructors - if (isConstructor && enclClassName == null) return; - boolean isGeneratedConstructor = isConstructor && ((tree.mods.flags & Flags.GENERATEDCONSTR) != 0); - if (isGeneratedConstructor) return; - println(); align(); - printDocComment(tree); - printExpr(tree.mods); - printTypeParameters(tree.typarams); - if (tree.typarams != null && tree.typarams.length() > 0) print(" "); - if (tree.name == tree.name.table.fromChars("<init>".toCharArray(), 0, 6)) { - print(enclClassName != null ? enclClassName : tree.name); - } else { - printExpr(tree.restype); - print(" " + tree.name); - } - print("("); - inParams = true; - printExprs(tree.params); - inParams = false; - print(")"); - if (tree.thrown.nonEmpty()) { - print(" throws "); - printExprs(tree.thrown); - } - if (tree.defaultValue != null) { - print(" default "); - print(tree.defaultValue); - } - if (tree.body != null) { - print(" "); - printBlock(tree.body.stats, tree.body); - } else { - print(";"); - } - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public void visitVarDef(JCVariableDecl tree) { - try { - boolean suppressSemi = suppressFinalAndSemicolonsInTry; - if (getJavadocFor(tree) != null) { - println(); align(); - } - printDocComment(tree); - if ((tree.mods.flags & ENUM) != 0) { - printEnumMember(tree); - } else { - printExpr(tree.mods); - if ((tree.mods.flags & VARARGS) != 0) { - printExpr(((JCArrayTypeTree) tree.vartype).elemtype); - print("... " + tree.name); - } else { - printExpr(tree.vartype); - print(" " + tree.name); - } - if (tree.init != null) { - print(" = "); - printExpr(tree.init); - } - if (prec == TreeInfo.notExpression && !suppressSemi) print(";"); - } - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public void visitSkip(JCSkip tree) { - try { - print(";"); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public void visitBlock(JCBlock tree) { - try { - consumeComments(tree.pos); - printFlags(tree.flags); - printBlock(tree.stats, tree); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public void visitDoLoop(JCDoWhileLoop tree) { - try { - print("do "); - printStat(tree.body); - align(); - print(" while "); - if (PARENS.equals(treeTag(tree.cond))) { - printExpr(tree.cond); - } else { - print("("); - printExpr(tree.cond); - print(")"); - } - print(";"); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public void visitWhileLoop(JCWhileLoop tree) { - try { - print("while "); - if (PARENS.equals(treeTag(tree.cond))) { - printExpr(tree.cond); - } else { - print("("); - printExpr(tree.cond); - print(")"); - } - print(" "); - printStat(tree.body); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public void visitForLoop(JCForLoop tree) { - try { - print("for ("); - if (tree.init.nonEmpty()) { - if (VARDEF.equals(treeTag(tree.init.head))) { - printExpr(tree.init.head); - for (List<JCStatement> l = tree.init.tail; l.nonEmpty(); l = l.tail) { - JCVariableDecl vdef = (JCVariableDecl)l.head; - print(", " + vdef.name + " = "); - printExpr(vdef.init); - } - } else { - printExprs(tree.init); - } - } - print("; "); - if (tree.cond != null) printExpr(tree.cond); - print("; "); - printExprs(tree.step); - print(") "); - printStat(tree.body); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public void visitForeachLoop(JCEnhancedForLoop tree) { - try { - print("for ("); - printExpr(tree.var); - print(" : "); - printExpr(tree.expr); - print(") "); - printStat(tree.body); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public void visitLabelled(JCLabeledStatement tree) { - try { - print(tree.label + ":"); - if (isEmptyStat(tree.body) || tree.body instanceof JCSkip) { - print(" ;"); - } else if (tree.body instanceof JCBlock) { - print(" "); - printStat(tree.body); - } else { - println(); - align(); - printStat(tree.body); - } - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public void visitSwitch(JCSwitch tree) { - try { - print("switch "); - if (PARENS.equals(treeTag(tree.selector))) { - printExpr(tree.selector); - } else { - print("("); - printExpr(tree.selector); - print(")"); - } - print(" {"); - println(); - printStats(tree.cases); - align(); - print("}"); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public void visitCase(JCCase tree) { - try { - if (tree.pat == null) { - print("default"); - } else { - print("case "); - printExpr(tree.pat); - } - print(": "); - println(); - indent(); - printStats(tree.stats); - undent(); - align(); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public void visitSynchronized(JCSynchronized tree) { - try { - print("synchronized "); - if (PARENS.equals(treeTag(tree.lock))) { - printExpr(tree.lock); - } else { - print("("); - printExpr(tree.lock); - print(")"); - } - print(" "); - printStat(tree.body); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public void visitTry(JCTry tree) { - try { - print("try "); - List<?> resources = null; - try { - Field f = JCTry.class.getField("resources"); - resources = (List<?>) f.get(tree); - } catch (Exception ignore) { - // In JDK6 and down this field does not exist; resources will retain its initializer value which is what we want. - } - - if (resources != null && resources.nonEmpty()) { - print("("); - int remaining = resources.size(); - if (remaining == 1) { - JCTree var = (JCTree) resources.get(0); - suppressFinalAndSemicolonsInTry = true; - printStat(var); - print(") "); - } else { - indent(); indent(); - for (Object var0 : resources) { - println(); - align(); - JCTree var = (JCTree) var0; - suppressFinalAndSemicolonsInTry = true; - printStat(var); - remaining--; - if (remaining > 0) print(";"); - } - print(") "); - undent(); undent(); - } - } - - printStat(tree.body); - for (List<JCCatch> l = tree.catchers; l.nonEmpty(); l = l.tail) { - printStat(l.head); - } - if (tree.finalizer != null) { - print(" finally "); - printStat(tree.finalizer); - } - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public void visitCatch(JCCatch tree) { - try { - print(" catch ("); - printExpr(tree.param); - print(") "); - printStat(tree.body); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public void visitConditional(JCConditional tree) { - try { - open(prec, TreeInfo.condPrec); - printExpr(tree.cond, TreeInfo.condPrec); - print(" ? "); - printExpr(tree.truepart, TreeInfo.condPrec); - print(" : "); - printExpr(tree.falsepart, TreeInfo.condPrec); - close(prec, TreeInfo.condPrec); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public void visitIf(JCIf tree) { - try { - print("if "); - if (PARENS.equals(treeTag(tree.cond))) { - printExpr(tree.cond); - } else { - print("("); - printExpr(tree.cond); - print(")"); - } - print(" "); - printStat(tree.thenpart); - if (tree.elsepart != null) { - print(" else "); - printStat(tree.elsepart); - } - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - private boolean isNoArgsSuperCall(JCExpression expr) { - if (!(expr instanceof JCMethodInvocation)) return false; - JCMethodInvocation tree = (JCMethodInvocation) expr; - if (!tree.typeargs.isEmpty() || !tree.args.isEmpty()) return false; - if (!(tree.meth instanceof JCIdent)) return false; - return ((JCIdent) tree.meth).name.toString().equals("super"); - } - - public void visitExec(JCExpressionStatement tree) { - if (isNoArgsSuperCall(tree.expr)) return; - try { - printExpr(tree.expr); - if (prec == TreeInfo.notExpression) print(";"); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public void visitBreak(JCBreak tree) { - try { - print("break"); - if (tree.label != null) print(" " + tree.label); - print(";"); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public void visitContinue(JCContinue tree) { - try { - print("continue"); - if (tree.label != null) print(" " + tree.label); - print(";"); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public void visitReturn(JCReturn tree) { - try { - print("return"); - if (tree.expr != null) { - print(" "); - printExpr(tree.expr); - } - print(";"); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public void visitThrow(JCThrow tree) { - try { - print("throw "); - printExpr(tree.expr); - print(";"); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public void visitAssert(JCAssert tree) { - try { - print("assert "); - printExpr(tree.cond); - if (tree.detail != null) { - print(" : "); - printExpr(tree.detail); - } - print(";"); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public void visitApply(JCMethodInvocation tree) { - try { - if (!tree.typeargs.isEmpty()) { - if (SELECT.equals(treeTag(tree.meth))) { - JCFieldAccess left = (JCFieldAccess)tree.meth; - printExpr(left.selected); - print(".<"); - printExprs(tree.typeargs); - print(">" + left.name); - } else { - print("<"); - printExprs(tree.typeargs); - print(">"); - printExpr(tree.meth); - } - } else { - printExpr(tree.meth); - } - print("("); - printExprs(tree.args); - print(")"); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public void visitNewClass(JCNewClass tree) { - try { - if (tree.encl != null) { - printExpr(tree.encl); - print("."); - } - print("new "); - if (!tree.typeargs.isEmpty()) { - print("<"); - printExprs(tree.typeargs); - print(">"); - } - printExpr(tree.clazz); - print("("); - printExprs(tree.args); - print(")"); - if (tree.def != null) { - Name enclClassNamePrev = enclClassName; - enclClassName = - tree.def.name != null ? tree.def.name : - tree.type != null && tree.type.tsym.name != tree.type.tsym.name.table.fromChars(new char[0], 0, 0) ? tree.type.tsym.name : - null; - if ((tree.def.mods.flags & Flags.ENUM) != 0) print("/*enum*/"); - printBlock(tree.def.defs, tree.def); - enclClassName = enclClassNamePrev; - } - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public void visitNewArray(JCNewArray tree) { - try { - if (tree.elemtype != null) { - print("new "); - JCTree elem = tree.elemtype; - if (elem instanceof JCArrayTypeTree) - printBaseElementType((JCArrayTypeTree) elem); - else - printExpr(elem); - for (List<JCExpression> l = tree.dims; l.nonEmpty(); l = l.tail) { - print("["); - printExpr(l.head); - print("]"); - } - if (elem instanceof JCArrayTypeTree) - printBrackets((JCArrayTypeTree) elem); - } - if (tree.elems != null) { - if (tree.elemtype != null) print("[]"); - print("{"); - printExprs(tree.elems); - print("}"); - } - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public void visitParens(JCParens tree) { - try { - print("("); - printExpr(tree.expr); - print(")"); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public void visitAssign(JCAssign tree) { - try { - open(prec, TreeInfo.assignPrec); - printExpr(tree.lhs, TreeInfo.assignPrec + 1); - print(" = "); - printExpr(tree.rhs, TreeInfo.assignPrec); - close(prec, TreeInfo.assignPrec); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public String operatorName(TreeTag tag) { - String result = OPERATORS.get(tag); - if (result == null) throw new Error(); - return result; - } - - public void visitAssignop(JCAssignOp tree) { - try { - open(prec, TreeInfo.assignopPrec); - printExpr(tree.lhs, TreeInfo.assignopPrec + 1); - String opname = operatorName(treeTag(tree)); - print(" " + opname + " "); - printExpr(tree.rhs, TreeInfo.assignopPrec); - close(prec, TreeInfo.assignopPrec); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public void visitUnary(JCUnary tree) { - try { - int ownprec = isOwnPrec(tree); - String opname = operatorName(treeTag(tree)); - open(prec, ownprec); - if (isPrefixUnary(tree)) { - print(opname); - printExpr(tree.arg, ownprec); - } else { - printExpr(tree.arg, ownprec); - print(opname); - } - close(prec, ownprec); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - private int isOwnPrec(JCExpression tree) { - return treeTag(tree).getOperatorPrecedenceLevel(); - } - - private boolean isPrefixUnary(JCUnary tree) { - return treeTag(tree).isPrefixUnaryOp(); - } - - public void visitBinary(JCBinary tree) { - try { - int ownprec = isOwnPrec(tree); - String opname = operatorName(treeTag(tree)); - open(prec, ownprec); - printExpr(tree.lhs, ownprec); - print(" " + opname + " "); - printExpr(tree.rhs, ownprec + 1); - close(prec, ownprec); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public void visitTypeCast(JCTypeCast tree) { - try { - open(prec, TreeInfo.prefixPrec); - print("("); - printExpr(tree.clazz); - print(")"); - printExpr(tree.expr, TreeInfo.prefixPrec); - close(prec, TreeInfo.prefixPrec); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public void visitTypeTest(JCInstanceOf tree) { - try { - open(prec, TreeInfo.ordPrec); - printExpr(tree.expr, TreeInfo.ordPrec); - print(" instanceof "); - printExpr(tree.clazz, TreeInfo.ordPrec + 1); - close(prec, TreeInfo.ordPrec); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public void visitIndexed(JCArrayAccess tree) { - try { - printExpr(tree.indexed, TreeInfo.postfixPrec); - print("["); - printExpr(tree.index); - print("]"); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public void visitSelect(JCFieldAccess tree) { - try { - printExpr(tree.selected, TreeInfo.postfixPrec); - print("." + tree.name); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public void visitIdent(JCIdent tree) { - try { - print(tree.name); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public void visitLiteral(JCLiteral tree) { - TypeTag typeTag = typeTag(tree); - try { - if (CTC_INT.equals(typeTag)) print(tree.value.toString()); - else if (CTC_LONG.equals(typeTag)) print(tree.value + "L"); - else if (CTC_FLOAT.equals(typeTag)) print(tree.value + "F"); - else if (CTC_DOUBLE.equals(typeTag)) print(tree.value.toString()); - else if (CTC_CHAR.equals(typeTag)) { - print("\'" + Convert.quote(String.valueOf((char)((Number)tree.value).intValue())) + "\'"); - } - else if (CTC_BOOLEAN.equals(typeTag)) print(((Number)tree.value).intValue() == 1 ? "true" : "false"); - else if (CTC_BOT.equals(typeTag)) print("null"); - else print("\"" + Convert.quote(tree.value.toString()) + "\""); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public void visitTypeIdent(JCPrimitiveTypeTree tree) { - TypeTag typetag = typeTag(tree); - try { - if (CTC_BYTE.equals(typetag)) print ("byte"); - else if (CTC_CHAR.equals(typetag)) print ("char"); - else if (CTC_SHORT.equals(typetag)) print ("short"); - else if (CTC_INT.equals(typetag)) print ("int"); - else if (CTC_LONG.equals(typetag)) print ("long"); - else if (CTC_FLOAT.equals(typetag)) print ("float"); - else if (CTC_DOUBLE.equals(typetag)) print ("double"); - else if (CTC_BOOLEAN.equals(typetag)) print ("boolean"); - else if (CTC_VOID.equals(typetag)) print ("void"); - else print("error"); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public void visitTypeArray(JCArrayTypeTree tree) { - try { - printBaseElementType(tree); - printBrackets(tree); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - // Prints the inner element type of a nested array - private void printBaseElementType(JCArrayTypeTree tree) throws IOException { - JCTree elem = tree.elemtype; - while (elem instanceof JCWildcard) - elem = ((JCWildcard) elem).inner; - if (elem instanceof JCArrayTypeTree) - printBaseElementType((JCArrayTypeTree) elem); - else - printExpr(elem); - } - - // prints the brackets of a nested array in reverse order - private void printBrackets(JCArrayTypeTree tree) throws IOException { - JCTree elem; - while (true) { - elem = tree.elemtype; - print("[]"); - if (!(elem instanceof JCArrayTypeTree)) break; - tree = (JCArrayTypeTree) elem; - } - } - - public void visitTypeApply(JCTypeApply tree) { - try { - printExpr(tree.clazz); - print("<"); - printExprs(tree.arguments); - print(">"); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public void visitTypeParameter(JCTypeParameter tree) { - try { - print(tree.name); - if (tree.bounds.nonEmpty()) { - print(" extends "); - printExprs(tree.bounds, " & "); - } - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - @Override - public void visitWildcard(JCWildcard tree) { - try { - Object kind = tree.getClass().getField("kind").get(tree); - print(kind); - if (kind != null && kind.getClass().getSimpleName().equals("TypeBoundKind")) { - kind = kind.getClass().getField("kind").get(kind); - } - - if (tree.getKind() != Tree.Kind.UNBOUNDED_WILDCARD) - printExpr(tree.inner); - } catch (IOException e) { - throw new UncheckedIOException(e); - } catch (NoSuchFieldException e) { - throw new RuntimeException(e); - } catch (IllegalAccessException e) { - throw new RuntimeException(e); - } - } - - public void visitTypeBoundKind(TypeBoundKind tree) { - try { - print(String.valueOf(tree.kind)); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public void visitErroneous(JCErroneous tree) { - try { - print("(ERROR)"); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public void visitLetExpr(LetExpr tree) { - try { - print("(let " + tree.defs + " in " + tree.expr + ")"); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public void visitModifiers(JCModifiers mods) { - try { - printAnnotations(mods.annotations); - printFlags(mods.flags); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public void visitAnnotation(JCAnnotation tree) { - try { - print("@"); - printExpr(tree.annotationType); - if (tree.args.nonEmpty()) { - print("("); - if (tree.args.length() == 1 && tree.args.get(0) instanceof JCAssign) { - JCExpression lhs = ((JCAssign)tree.args.get(0)).lhs; - if (lhs instanceof JCIdent && ((JCIdent)lhs).name.toString().equals("value")) tree.args = List.of(((JCAssign)tree.args.get(0)).rhs); - } - printExprs(tree.args); - print(")"); - } - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public void visitTree(JCTree tree) { - try { - String simpleName = tree.getClass().getSimpleName(); - if ("JCTypeUnion".equals(simpleName)) { - printExprs(readExpressionList(tree, "alternatives"), " | "); - return; - } else if ("JCTypeIntersection".equals(simpleName)) { - printExprs(readExpressionList(tree, "bounds"), " & "); - return; - } else if ("JCLambda".equals(simpleName)) { - visitLambda0(tree); - return; - } else if ("JCMemberReference".equals(simpleName)) { - visitReference0(tree); - return; - } else { - print("(UNKNOWN[" + tree.getClass().getSimpleName() + "]: " + tree + ")"); - println(); - } - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - private void visitLambda0(JCTree tree) { - try { - print("("); - @SuppressWarnings("unchecked") - List<JCVariableDecl> params = (List<JCVariableDecl>) readTreeList(tree, "params"); - boolean explicit = true; - try { - explicit = readObject(tree, "paramKind").toString().equals("EXPLICIT"); - } catch (Exception e) {} - if (explicit) { - printExprs(params); - } else { - String sep = ""; - for (JCVariableDecl param : params) { - print(sep); - print(param.name); - sep = ", "; - } - } - print(") -> "); - printExpr(readTree(tree, "body")); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - - public void visitReference0(JCTree tree) { - try { - printExpr(readTree(tree, "expr")); - print("::"); - List<JCExpression> typeArgs = readExpressionList(tree, "typeargs"); - if (typeArgs != null) { - print("<"); - printExprs(typeArgs); - print(">"); - } - ; - print(readObject(tree, "mode").toString().equals("INVOKE") ? readObject(tree, "name") : "new"); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - private JCTree readTree(JCTree tree, String fieldName) { - try { - return (JCTree) readObject0(tree, fieldName); - } catch (Exception e) { - return null; - } - } - - @SuppressWarnings("unchecked") - private List<? extends JCTree> readTreeList(JCTree tree, String fieldName) throws IOException { - try { - return (List<? extends JCTree>) readObject0(tree, fieldName); - } catch (Exception e) { - return List.nil(); - } - } - - @SuppressWarnings("unchecked") - private List<JCExpression> readExpressionList(JCTree tree, String fieldName) throws IOException { - try { - return (List<JCExpression>) readObject0(tree, fieldName); - } catch (Exception e) { - return List.nil(); - } - } - - private Object readObject(JCTree tree, String fieldName) { - try { - return readObject0(tree, fieldName); - } catch (Exception e) { - return null; - } - } - - @SuppressWarnings("unchecked") - private Object readObject0(JCTree tree, String fieldName) throws Exception { - try { - return tree.getClass().getDeclaredField(fieldName).get(tree); - } catch (Exception e) { - print("ERROR_READING_FIELD"); - throw e; - } - } -} diff --git a/src/delombok/lombok/delombok/PrettyPrinter.java b/src/delombok/lombok/delombok/PrettyPrinter.java new file mode 100644 index 00000000..4261a558 --- /dev/null +++ b/src/delombok/lombok/delombok/PrettyPrinter.java @@ -0,0 +1,1524 @@ +/* + * Copyright (C) 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 + * 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.delombok; + +import static com.sun.tools.javac.code.Flags.*; +import static lombok.javac.Javac.*; +import static lombok.javac.JavacTreeMaker.TreeTag.treeTag; +import static lombok.javac.JavacTreeMaker.TypeTag.typeTag; + +import java.io.IOException; +import java.io.Writer; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +import com.sun.tools.javac.tree.DocCommentTable; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.JCAnnotation; +import com.sun.tools.javac.tree.JCTree.JCArrayAccess; +import com.sun.tools.javac.tree.JCTree.JCArrayTypeTree; +import com.sun.tools.javac.tree.JCTree.JCAssert; +import com.sun.tools.javac.tree.JCTree.JCAssign; +import com.sun.tools.javac.tree.JCTree.JCAssignOp; +import com.sun.tools.javac.tree.JCTree.JCBinary; +import com.sun.tools.javac.tree.JCTree.JCBlock; +import com.sun.tools.javac.tree.JCTree.JCBreak; +import com.sun.tools.javac.tree.JCTree.JCCase; +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.JCConditional; +import com.sun.tools.javac.tree.JCTree.JCContinue; +import com.sun.tools.javac.tree.JCTree.JCDoWhileLoop; +import com.sun.tools.javac.tree.JCTree.JCEnhancedForLoop; +import com.sun.tools.javac.tree.JCTree.JCErroneous; +import com.sun.tools.javac.tree.JCTree.JCExpression; +import com.sun.tools.javac.tree.JCTree.JCExpressionStatement; +import com.sun.tools.javac.tree.JCTree.JCFieldAccess; +import com.sun.tools.javac.tree.JCTree.JCForLoop; +import com.sun.tools.javac.tree.JCTree.JCIdent; +import com.sun.tools.javac.tree.JCTree.JCIf; +import com.sun.tools.javac.tree.JCTree.JCImport; +import com.sun.tools.javac.tree.JCTree.JCInstanceOf; +import com.sun.tools.javac.tree.JCTree.JCLabeledStatement; +import com.sun.tools.javac.tree.JCTree.JCLiteral; +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.JCNewArray; +import com.sun.tools.javac.tree.JCTree.JCNewClass; +import com.sun.tools.javac.tree.JCTree.JCParens; +import com.sun.tools.javac.tree.JCTree.JCPrimitiveTypeTree; +import com.sun.tools.javac.tree.JCTree.JCReturn; +import com.sun.tools.javac.tree.JCTree.JCSkip; +import com.sun.tools.javac.tree.JCTree.JCStatement; +import com.sun.tools.javac.tree.JCTree.JCSwitch; +import com.sun.tools.javac.tree.JCTree.JCSynchronized; +import com.sun.tools.javac.tree.JCTree.JCThrow; +import com.sun.tools.javac.tree.JCTree.JCTry; +import com.sun.tools.javac.tree.JCTree.JCTypeApply; +import com.sun.tools.javac.tree.JCTree.JCTypeCast; +import com.sun.tools.javac.tree.JCTree.JCTypeParameter; +import com.sun.tools.javac.tree.JCTree.JCUnary; +import com.sun.tools.javac.tree.JCTree.JCVariableDecl; +import com.sun.tools.javac.tree.JCTree.JCWhileLoop; +import com.sun.tools.javac.tree.JCTree.JCWildcard; +import com.sun.tools.javac.tree.JCTree.TypeBoundKind; +import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.util.Name; +import com.sun.tools.javac.util.Position; + +import lombok.javac.CommentInfo; +import lombok.javac.PackageName; +import lombok.javac.CommentInfo.EndConnection; +import lombok.javac.CommentInfo.StartConnection; +import lombok.javac.JavacTreeMaker.TreeTag; +import lombok.javac.JavacTreeMaker.TypeTag; + +public class PrettyPrinter extends JCTree.Visitor { + private static final String LINE_SEP = System.getProperty("line.separator"); + private static final Map<TreeTag, String> OPERATORS; + + static { + Map<TreeTag, String> map = new HashMap<TreeTag, String>(); + + map.put(treeTag("POS"), "+"); + map.put(treeTag("NEG"), "-"); + map.put(treeTag("NOT"), "!"); + map.put(treeTag("COMPL"), "~"); + map.put(treeTag("PREINC"), "++"); + map.put(treeTag("PREDEC"), "--"); + map.put(treeTag("POSTINC"), "++"); + map.put(treeTag("POSTDEC"), "--"); + map.put(treeTag("NULLCHK"), "<*nullchk*>"); + map.put(treeTag("OR"), "||"); + map.put(treeTag("AND"), "&&"); + map.put(treeTag("EQ"), "=="); + map.put(treeTag("NE"), "!="); + map.put(treeTag("LT"), "<"); + map.put(treeTag("GT"), ">"); + map.put(treeTag("LE"), "<="); + map.put(treeTag("GE"), ">="); + map.put(treeTag("BITOR"), "|"); + map.put(treeTag("BITXOR"), "^"); + map.put(treeTag("BITAND"), "&"); + map.put(treeTag("SL"), "<<"); + map.put(treeTag("SR"), ">>"); + map.put(treeTag("USR"), ">>>"); + map.put(treeTag("PLUS"), "+"); + map.put(treeTag("MINUS"), "-"); + map.put(treeTag("MUL"), "*"); + map.put(treeTag("DIV"), "/"); + map.put(treeTag("MOD"), "%"); + + map.put(treeTag("BITOR_ASG"), "|="); + map.put(treeTag("BITXOR_ASG"), "^="); + map.put(treeTag("BITAND_ASG"), "&="); + map.put(treeTag("SL_ASG"), "<<="); + map.put(treeTag("SR_ASG"), ">>="); + map.put(treeTag("USR_ASG"), ">>>="); + map.put(treeTag("PLUS_ASG"), "+="); + map.put(treeTag("MINUS_ASG"), "-="); + map.put(treeTag("MUL_ASG"), "*="); + map.put(treeTag("DIV_ASG"), "/="); + map.put(treeTag("MOD_ASG"), "%="); + + OPERATORS = map; + } + + private final Writer out; + private final JCCompilationUnit compilationUnit; + private List<CommentInfo> comments; + private final FormatPreferences formatPreferences; + + private final Map<JCTree, String> docComments; + private final DocCommentTable docTable; + private int indent = 0; + + @SuppressWarnings({"unchecked", "rawtypes"}) + public PrettyPrinter(Writer out, JCCompilationUnit cu, List<CommentInfo> comments, FormatPreferences preferences) { + this.out = out; + this.comments = comments; + this.compilationUnit = cu; + this.formatPreferences = preferences; + + /* load doc comments */ { + Object dc = getDocComments(compilationUnit); + if (dc instanceof Map<?, ?>) { + this.docComments = (Map) dc; + this.docTable = null; + } else if (dc instanceof DocCommentTable) { + this.docComments = null; + this.docTable = (DocCommentTable) dc; + } else { + this.docComments = null; + this.docTable = null; + } + } + } + + private int endPos(JCTree tree) { + return getEndPosition(tree, compilationUnit); + } + + private static int lineEndPos(String s, int start) { + int pos = s.indexOf('\n', start); + if (pos < 0) pos = s.length(); + return pos; + } + + private boolean needsAlign, needsNewLine, onNewLine = true, needsSpace, aligned; + + public static final class UncheckedIOException extends RuntimeException { + UncheckedIOException(IOException source) { + super(toMsg(source)); + setStackTrace(source.getStackTrace()); + } + + private static String toMsg(Throwable t) { + String msg = t.getMessage(); + String n = t.getClass().getSimpleName(); + if (msg == null || msg.isEmpty()) return n; + return n + ": " + msg; + } + } + + private void align() { + if (!onNewLine) return; + try { + for (int i = 0; i < indent; i++) out.write(formatPreferences.indent()); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + + onNewLine = false; + aligned = true; + needsAlign = false; + } + + private void print(JCTree tree) { + if (tree == null) { + print("/*missing*/"); + return; + } + + consumeComments(tree); + tree.accept(this); + consumeTrailingComments(endPos(tree)); + } + + private void print(List<? extends JCTree> trees, String infix) { + boolean first = true; + JCTree prev = null; + for (JCTree tree : trees) { + if (suppress(tree)) continue; + if (!first && infix != null && !infix.isEmpty()) { + if ("\n".equals(infix)) println(prev); + else print(infix); + } + first = false; + print(tree); + prev = tree; + } + } + + private boolean suppress(JCTree tree) { + if (tree instanceof JCBlock) { + JCBlock block = (JCBlock) tree; + return (Position.NOPOS == block.pos) && block.stats.isEmpty(); + } + + if (tree instanceof JCExpressionStatement) { + JCExpression expr = ((JCExpressionStatement)tree).expr; + if (expr instanceof JCMethodInvocation) { + JCMethodInvocation inv = (JCMethodInvocation) expr; + if (!inv.typeargs.isEmpty() || !inv.args.isEmpty()) return false; + if (!(inv.meth instanceof JCIdent)) return false; + return ((JCIdent) inv.meth).name.toString().equals("super"); + } + } + + return false; + } + + private void print(CharSequence s) { + boolean align = needsAlign; + if (needsNewLine && !onNewLine) println(); + if (align && !aligned) align(); + try { + if (needsSpace && !onNewLine && !aligned) out.write(' '); + out.write(s.toString()); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + + needsSpace = false; + onNewLine = false; + aligned = false; + } + + + private void println() { + try { + out.write(LINE_SEP); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + + onNewLine = true; + aligned = false; + needsNewLine = false; + } + + private void println(JCTree completed) { + if (completed != null) { + int endPos = endPos(completed); + consumeTrailingComments(endPos); + } + try { + out.write(LINE_SEP); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + + onNewLine = true; + aligned = false; + needsNewLine = false; + } + + private void println(CharSequence s) { + print(s); + println(); + } + + private void println(CharSequence s, JCTree completed) { + print(s); + println(completed); + } + + private void aPrint(CharSequence s) { + align(); + print(s); + } + + private void aPrintln(CharSequence s) { + align(); + print(s); + println(); + } + + private void aPrintln(CharSequence s, JCTree completed) { + align(); + print(s); + println(completed); + } + + private void consumeComments(int until) { + CommentInfo head = comments.head; + while (comments.nonEmpty() && head.pos < until) { + printComment(head); + comments = comments.tail; + head = comments.head; + } + } + + private void consumeComments(JCTree tree) { + consumeComments(tree.pos); + } + + private void consumeTrailingComments(int from) { + boolean prevNewLine = onNewLine; + CommentInfo head = comments.head; + boolean stop = false; + + while (comments.nonEmpty() && head.prevEndPos == from && !stop && !(head.start == StartConnection.ON_NEXT_LINE || head.start == StartConnection.START_OF_LINE)) { + from = head.endPos; + printComment(head); + stop = (head.end == EndConnection.ON_NEXT_LINE); + comments = comments.tail; + head = comments.head; + } + + if (!onNewLine && prevNewLine) { + println(); + } + } + + private String getJavadocFor(JCTree node) { + if (docComments != null) return docComments.get(node); + if (docTable != null) return docTable.getCommentText(node); + return null; + } + + private int dims(JCExpression vartype) { + if (vartype instanceof JCArrayTypeTree) { + return 1 + dims(((JCArrayTypeTree) vartype).elemtype); + } + + return 0; + } + + private void printComment(CommentInfo comment) { + switch (comment.start) { + case DIRECT_AFTER_PREVIOUS: + needsSpace = false; + break; + case AFTER_PREVIOUS: + needsSpace = true; + break; + case START_OF_LINE: + needsNewLine = true; + needsAlign = false; + break; + case ON_NEXT_LINE: + if (!onNewLine) { + needsNewLine = true; + needsAlign = true; + } else if (!aligned) { + needsAlign = true; + } + break; + } + + if (onNewLine && !aligned && comment.start != StartConnection.START_OF_LINE) needsAlign = true; + + print(comment.content); + + switch (comment.end) { + case ON_NEXT_LINE: + if (!aligned) { + needsNewLine = true; + needsAlign = true; + } + break; + case AFTER_COMMENT: + needsSpace = true; + break; + case DIRECT_AFTER_COMMENT: + // do nothing + break; + } + } + + private void printDocComment(JCTree tree) { + String dc = getJavadocFor(tree); + if (dc == null) return; + aPrintln("/**"); + int pos = 0; + int endpos = lineEndPos(dc, pos); + boolean atStart = true; + while (pos < dc.length()) { + String line = dc.substring(pos, endpos); + if (line.trim().isEmpty() && atStart) { + atStart = false; + continue; + } + atStart = false; + aPrint(" *"); + if (pos < dc.length() && dc.charAt(pos) > ' ') print(" "); + println(dc.substring(pos, endpos)); + pos = endpos + 1; + endpos = lineEndPos(dc, pos); + } + aPrintln(" */"); + } + + private Name __INIT__, __VALUE__; + private Name name_init(Name someName) { + if (__INIT__ == null) __INIT__ = someName.table.fromChars("<init>".toCharArray(), 0, 6); + return __INIT__; + } + private Name name_value(Name someName) { + if (__VALUE__ == null) __VALUE__ = someName.table.fromChars("value".toCharArray(), 0, 5); + return __VALUE__; + } + + @Override public void visitTopLevel(JCCompilationUnit tree) { + printDocComment(tree); + JCTree n = PackageName.getPackageNode(tree); + if (n != null) { + consumeComments(tree); + aPrint("package "); + print(n); + println(";", n); + } + + boolean first = true; + + for (JCTree child : tree.defs) { + if (!(child instanceof JCImport)) continue; + if (first) println(); + first = false; + print(child); + } + + for (JCTree child : tree.defs) { + if (child instanceof JCImport) continue; + print(child); + } + consumeComments(Integer.MAX_VALUE); + } + + @Override public void visitImport(JCImport tree) { + aPrint("import "); + if (tree.staticImport) print("static "); + print(tree.qualid); + println(";", tree); + } + + private Name currentTypeName; + @Override public void visitClassDef(JCClassDecl tree) { + println(); + printDocComment(tree); + align(); + print(tree.mods); + + boolean isInterface = (tree.mods.flags & INTERFACE) != 0; + boolean isAnnotationInterface = isInterface && (tree.mods.flags & ANNOTATION) != 0; + boolean isEnum = (tree.mods.flags & ENUM) != 0; + + if (isAnnotationInterface) print("@interface "); + else if (isInterface) print("interface "); + else if (isEnum) print("enum "); + else print("class "); + + print(tree.name); + Name prevTypeName = currentTypeName; + currentTypeName = tree.name; + + if (tree.typarams.nonEmpty()) { + print("<"); + print(tree.typarams, ", "); + print(">"); + } + JCTree extendsClause = getExtendsClause(tree); + if (extendsClause != null) { + print(" extends "); + print(extendsClause); + } + + if (tree.implementing.nonEmpty()) { + print(isInterface ? " extends " : " implements "); + print(tree.implementing, ", "); + } + + println(" {"); + indent++; + printClassMembers(tree.defs, isEnum, isInterface); + consumeComments(endPos(tree)); + indent--; + aPrintln("}", tree); + currentTypeName = prevTypeName; + } + + private void printClassMembers(List<JCTree> members, boolean isEnum, boolean isInterface) { + Class<?> prefType = null; + int typeOfPrevEnumMember = isEnum ? 3 : 0; // 1 = normal, 2 = with body, 3 = no enum field yet. + boolean prevWasEnumMember = isEnum; + + for (JCTree member : members) { + if (typeOfPrevEnumMember == 3 && member instanceof JCMethodDecl && (((JCMethodDecl) member).mods.flags & GENERATEDCONSTR) != 0) continue; + boolean isEnumVar = isEnum && member instanceof JCVariableDecl && (((JCVariableDecl) member).mods.flags & ENUM) != 0; + if (!isEnumVar && prevWasEnumMember) { + prevWasEnumMember = false; + if (typeOfPrevEnumMember == 3) align(); + println(";"); + } + + if (isEnumVar) { + if (prefType != null && prefType != JCVariableDecl.class) println(); + switch (typeOfPrevEnumMember) { + case 1: + print(", "); + break; + case 2: + println(","); + align(); + break; + } + print(member); + JCTree init = ((JCVariableDecl) member).init; + typeOfPrevEnumMember = init instanceof JCNewClass && ((JCNewClass) init).def != null ? 2 : 1; + } else if (member instanceof JCVariableDecl) { + if (prefType != null && prefType != JCVariableDecl.class) println(); + if (isInterface) flagMod = -1L & ~(PUBLIC | STATIC | FINAL); + print(member); + } else if (member instanceof JCMethodDecl) { + if ((((JCMethodDecl) member).mods.flags & GENERATEDCONSTR) != 0) continue; + if (prefType != null) println(); + if (isInterface) flagMod = -1L & ~(PUBLIC | ABSTRACT); + print(member); + } else if (member instanceof JCClassDecl) { + if (prefType != null) println(); + if (isInterface) flagMod = -1L & ~(PUBLIC | STATIC); + print(member); + } else { + if (prefType != null) println(); + print(member); + } + + prefType = member.getClass(); + } + + if (prevWasEnumMember) { + prevWasEnumMember = false; + if (typeOfPrevEnumMember == 3) align(); + println(";"); + } + } + + @Override public void visitTypeParameter(JCTypeParameter tree) { + List<JCExpression> annotations = readObject(tree, "annotations", List.<JCExpression>nil()); + if (!annotations.isEmpty()) { + print(annotations, " "); + print(" "); + } + print(tree.name); + if (tree.bounds.nonEmpty()) { + print(" extends "); + print(tree.bounds, " & "); + } + consumeComments(tree); + } + + @Override public void visitVarDef(JCVariableDecl tree) { + printDocComment(tree); + align(); + if ((tree.mods.flags & ENUM) != 0) { + printEnumMember(tree); + return; + } + printAnnotations(tree.mods.annotations, true); + printModifierKeywords(tree.mods); + printVarDef0(tree); + println(";", tree); + } + + private void printVarDefInline(JCVariableDecl tree) { + printAnnotations(tree.mods.annotations, false); + printModifierKeywords(tree.mods); + printVarDef0(tree); + } + + private void printVarDef0(JCVariableDecl tree) { + boolean varargs = (tree.mods.flags & VARARGS) != 0; + if (varargs && tree.vartype instanceof JCArrayTypeTree) { + print(((JCArrayTypeTree) tree.vartype).elemtype); + print("..."); + } else { + print(tree.vartype); + } + print(" "); + print(tree.name); + if (tree.init != null) { + print(" = "); + print(tree.init); + } + } + + private void printEnumMember(JCVariableDecl tree) { + printAnnotations(tree.mods.annotations, true); + print(tree.name); + if (tree.init instanceof JCNewClass) { + JCNewClass constructor = (JCNewClass) tree.init; + if (constructor.args != null && constructor.args.nonEmpty()) { + print("("); + print(constructor.args, ", "); + print(")"); + } + + if (constructor.def != null && constructor.def.defs != null) { + println(" {"); + indent++; + printClassMembers(constructor.def.defs, false, false); + consumeComments(endPos(tree)); + indent--; + aPrint("}"); + } + } + } + + // TODO: Test postfix syntax for methods (?), for decls. Multiline vardefs, possibly with comments. enums with bodies. constructor-local generics, method-local generics, also do/while, finally, try-with-resources, lambdas, annotations in java8 places... + // TODO: Whatever is JCAnnotatedType? We handle it in the 7+ bucket in the old one... + + @Override public void visitTypeApply(JCTypeApply tree) { + print(tree.clazz); + print("<"); + print(tree.arguments, ", "); + print(">"); + } + + @Override public void visitWildcard(JCWildcard tree) { + switch (tree.getKind()) { + default: + case UNBOUNDED_WILDCARD: + print("?"); + return; + case EXTENDS_WILDCARD: + print("? extends "); + print(tree.inner); + return; + case SUPER_WILDCARD: + print("? super "); + print(tree.inner); + return; + } + } + + @Override public void visitLiteral(JCLiteral tree) { + TypeTag typeTag = typeTag(tree); + if (CTC_INT.equals(typeTag)) print("" + tree.value); + else if (CTC_LONG.equals(typeTag)) print(tree.value + "L"); + else if (CTC_FLOAT.equals(typeTag)) print(tree.value + "F"); + else if (CTC_DOUBLE.equals(typeTag)) print("" + tree.value); + else if (CTC_CHAR.equals(typeTag)) { + print("\'" + quoteChar((char)((Number)tree.value).intValue()) + "\'"); + } + else if (CTC_BOOLEAN.equals(typeTag)) print(((Number)tree.value).intValue() == 1 ? "true" : "false"); + else if (CTC_BOT.equals(typeTag)) print("null"); + else print("\"" + quoteChars(tree.value.toString()) + "\""); + } + + @Override public void visitMethodDef(JCMethodDecl tree) { + boolean isConstructor = tree.name == name_init(tree.name); + if (isConstructor && (tree.mods.flags & GENERATEDCONSTR) != 0) return; + printDocComment(tree); + align(); + print(tree.mods); + if (tree.typarams != null && tree.typarams.nonEmpty()) { + print("<"); + print(tree.typarams, ", "); + print("> "); + } + + if (isConstructor) { + print(currentTypeName == null ? "<init>" : currentTypeName); + } else { + print(tree.restype); + print(" "); + print(tree.name); + } + + print("("); + boolean first = true; + for (JCVariableDecl param : tree.params) { + if (!first) print(", "); + first = false; + printVarDefInline(param); + } + print(")"); + + if (tree.thrown.nonEmpty()) { + print(" throws "); + print(tree.thrown, ", "); + } + + if (tree.defaultValue != null) { + print(" default "); + print(tree.defaultValue); + } + + if (tree.body != null) { + print(" "); + print(tree.body); + } else println(";", tree); + } + + @Override public void visitSkip(JCSkip that) { + if (onNewLine && !aligned) { + align(); + } + println(";"); + } + + @Override public void visitAnnotation(JCAnnotation tree) { + print("@"); + print(tree.annotationType); + if (tree.args.isEmpty()) return; + print("("); + boolean done = false; + if (tree.args.length() == 1 && tree.args.get(0) instanceof JCAssign) { + JCAssign arg1 = (JCAssign) tree.args.get(0); + JCIdent arg1Name = arg1.lhs instanceof JCIdent ? ((JCIdent) arg1.lhs) : null; + if (arg1Name != null && arg1Name.name == name_value(arg1Name.name)) { + print(arg1.rhs); + done = true; + } + } + if (!done) print(tree.args, ", "); + print(")"); + } + + @Override public void visitTypeArray(JCArrayTypeTree tree) { + JCTree elem = tree.elemtype; + while (elem instanceof JCWildcard) elem = ((JCWildcard) elem).inner; + print(elem); + print("[]"); + } + + @Override public void visitNewArray(JCNewArray tree) { + JCTree elem = tree.elemtype; + int dims = 0; + if (elem != null) { + print("new "); + + while (elem instanceof JCArrayTypeTree) { + dims++; + elem = ((JCArrayTypeTree) elem).elemtype; + } + print(elem); + + for (JCExpression expr : tree.dims) { + print("["); + print(expr); + print("]"); + } + } + + for (int i = 0; i < dims; i++) print("[]"); + + if (tree.elems != null) { + if (elem != null) print("[] "); + print("{"); + print(tree.elems, ", "); + print("}"); + } + } + + @Override public void visitNewClass(JCNewClass tree) { + if (tree.encl != null) { + print(tree.encl); + print("."); + } + boolean moveFirstParameter = tree.args.nonEmpty() && tree.args.head instanceof JCUnary && tree.args.head.toString().startsWith("<*nullchk*>"); + if (moveFirstParameter) { + print(((JCUnary) tree.args.head).arg); + print("."); + } + + print("new "); + if (!tree.typeargs.isEmpty()) { + print("<"); + print(tree.typeargs, ", "); + print(">"); + } + print(tree.clazz); + print("("); + if (moveFirstParameter) { + print(tree.args.tail, ", "); + } else { + print(tree.args, ", "); + } + print(")"); + if (tree.def != null) { + Name previousTypeName = currentTypeName; + currentTypeName = null; + println(" {"); + indent++; + print(tree.def.defs, ""); + indent--; + aPrint("}"); + currentTypeName = previousTypeName; + } + } + + @Override public void visitIndexed(JCArrayAccess tree) { + print(tree.indexed); + print("["); + print(tree.index); + print("]"); + } + + @Override public void visitTypeIdent(JCPrimitiveTypeTree tree) { + TypeTag typeTag = typeTag(tree); + + if (CTC_BYTE.equals(typeTag)) print("byte"); + else if (CTC_CHAR.equals(typeTag)) print("char"); + else if (CTC_SHORT.equals(typeTag)) print("short"); + else if (CTC_INT.equals(typeTag)) print("int"); + else if (CTC_LONG.equals(typeTag)) print("long"); + else if (CTC_FLOAT.equals(typeTag)) print("float"); + else if (CTC_DOUBLE.equals(typeTag)) print("double"); + else if (CTC_BOOLEAN.equals(typeTag)) print("boolean"); + else if (CTC_VOID.equals(typeTag)) print("void"); + else print("error"); + } + + @Override public void visitLabelled(JCLabeledStatement tree) { + aPrint(tree.label); + print(":"); + if (tree.body instanceof JCSkip || suppress(tree)) { + println(" ;", tree); + } else if (tree.body instanceof JCBlock) { + print(" "); + print(tree.body); + } else { + println(tree); + print(tree.body); + } + } + + private long flagMod = -1L; + private static final long DEFAULT = 1L<<43; + + @Override public void visitModifiers(JCModifiers tree) { + printAnnotations(tree.annotations, true); + printModifierKeywords(tree); + } + + private void printAnnotations(List<JCAnnotation> annotations, boolean newlines) { + for (JCAnnotation ann : annotations) { + print(ann); + if (newlines) { + println(); + align(); + } else print(" "); + } + } + + private void printModifierKeywords(JCModifiers tree) { + long v = flagMod & tree.flags; + flagMod = -1L; + + if ((v & SYNTHETIC) != 0) print("/* synthetic */ "); + if ((v & PUBLIC) != 0) print("public "); + if ((v & PRIVATE) != 0) print("private "); + if ((v & PROTECTED) != 0) print("protected "); + if ((v & STATIC) != 0) print("static "); + if ((v & FINAL) != 0) print("final "); + if ((v & SYNCHRONIZED) != 0) print("synchronized "); + if ((v & VOLATILE) != 0) print("volatile "); + if ((v & TRANSIENT) != 0) print("transient "); + if ((v & NATIVE) != 0) print("native "); + if ((v & ABSTRACT) != 0) print("abstract "); + if ((v & STRICTFP) != 0) print("strictfp "); + if ((v & DEFAULT) != 0 && (v & INTERFACE) == 0) print("default "); + } + + @Override public void visitSelect(JCFieldAccess tree) { + print(tree.selected); + print("."); + print(tree.name); + } + + @Override public void visitIdent(JCIdent tree) { + print(tree.name); + } + + @Override public void visitApply(JCMethodInvocation tree) { + if (tree.typeargs.nonEmpty()) { + if (tree.meth instanceof JCFieldAccess) { + JCFieldAccess fa = (JCFieldAccess) tree.meth; + print(fa.selected); + print(".<"); + print(tree.typeargs, ", "); + print(">"); + print(fa.name); + } else { + print("<"); + print(tree.typeargs, ", "); + print(">"); + print(tree.meth); + } + } else { + print(tree.meth); + } + + print("("); + print(tree.args, ", "); + print(")"); + } + + @Override public void visitAssert(JCAssert tree) { + aPrint("assert "); + print(tree.cond); + if (tree.detail != null) { + print(" : "); + print(tree.detail); + } + println(";", tree); + } + + @Override public void visitAssign(JCAssign tree) { + print(tree.lhs); + print(" = "); + print(tree.rhs); + } + + @Override public void visitAssignop(JCAssignOp tree) { + print(tree.lhs); + String opname = operator(treeTag(tree)); + print(" " + opname + " "); + print(tree.rhs); + } + + private static final int PREFIX = 14; + + @Override public void visitUnary(JCUnary tree) { + String op = operator(treeTag(tree)); + if (treeTag(tree).getOperatorPrecedenceLevel() == PREFIX) { + print(op); + print(tree.arg); + } else { + print(tree.arg); + print(op); + } + } + + @Override public void visitBinary(JCBinary tree) { + String op = operator(treeTag(tree)); + print(tree.lhs); + print(" "); + print(op); + print(" "); + print(tree.rhs); + } + + @Override public void visitTypeTest(JCInstanceOf tree) { + print(tree.expr); + print(" instanceof "); + print(tree.clazz); + } + + @Override public void visitTypeCast(JCTypeCast tree) { + print("("); + print(tree.clazz); + print(") "); + print(tree.expr); + } + + @Override public void visitBlock(JCBlock tree) { + if (tree.pos == Position.NOPOS && tree.stats.isEmpty()) return; + if (onNewLine) align(); + if ((tree.flags & STATIC) != 0) print("static "); + println("{"); + indent++; + print(tree.stats, ""); + consumeComments(endPos(tree)); + indent--; + aPrintln("}", tree); + } + + @Override public void visitBreak(JCBreak tree) { + aPrint("break"); + if (tree.label != null) { + print(" "); + print(tree.label); + } + println(";", tree); + } + + @Override public void visitContinue(JCContinue tree) { + aPrint("continue"); + if (tree.label != null) { + print(" "); + print(tree.label); + } + println(";", tree); + } + + @Override public void visitConditional(JCConditional tree) { + print(tree.cond); + print(" ? "); + print(tree.truepart); + print(" : "); + print(tree.falsepart); + } + + @Override public void visitParens(JCParens tree) { + print("("); + print(tree.expr); + print(")"); + } + + @Override public void visitReturn(JCReturn tree) { + aPrint("return"); + if (tree.expr != null) { + print(" "); + print(tree.expr); + } + println(";", tree); + } + + @Override public void visitThrow(JCThrow tree) { + aPrint("throw "); + print(tree.expr); + println(";", tree); + } + + @Override public void visitWhileLoop(JCWhileLoop tree) { + aPrint("while "); + if (tree.cond instanceof JCParens) { + print(tree.cond); + } else { + print("("); + print(tree.cond); + print(")"); + } + print(" "); + print(tree.body); + // make sure to test while (true) ; and while(true){} and while(true) x = 5; + } + + @Override public void visitForLoop(JCForLoop tree) { + aPrint("for ("); + if (tree.init.nonEmpty()) { + // ForInit is either a StatementExpressionList or a LocalVariableDeclaration + if (tree.init.head instanceof JCVariableDecl) { + boolean first = true; + int dims = 0; + for (JCStatement i : tree.init) { + JCVariableDecl vd = (JCVariableDecl) i; + if (first) { + printVarDefInline(vd); + dims = dims(vd.vartype); + } else { + print(", "); + print(vd.name); + int dimDiff = dims(vd.vartype) - dims; + for (int j = 0; j < dimDiff; j++) print("[]"); + if (vd.init != null) { + print(" = "); + print(vd.init); + } + } + first = false; + } + } else { + boolean first = true; + for (JCStatement exprStatement : tree.init) { + if (!first) print(", "); + first = false; + print(((JCExpressionStatement) exprStatement).expr); + } + } + } + print("; "); + if (tree.cond != null) print(tree.cond); + print("; "); + boolean first = true; + for (JCExpressionStatement exprStatement : tree.step) { + if (!first) print(", "); + first = false; + print(exprStatement.expr); + } + print(") "); + print(tree.body); + } + + @Override public void visitForeachLoop(JCEnhancedForLoop tree) { + aPrint("for ("); + printVarDefInline(tree.var); + print(" : "); + print(tree.expr); + print(") "); + print(tree.body); + } + + @Override public void visitIf(JCIf tree) { + aPrint("if "); + if (tree.cond instanceof JCParens) { + print(tree.cond); + } else { + print("("); + print(tree.cond); + print(")"); + } + print(" "); + if (tree.thenpart instanceof JCBlock) { + println("{"); + indent++; + print(((JCBlock) tree.thenpart).stats, ""); + indent--; + if (tree.elsepart == null) { + aPrintln("}", tree); + } else { + aPrint("}"); + } + } else { + print(tree.thenpart); + } + if (tree.elsepart != null) { + aPrint(" else "); + print(tree.elsepart); + } + } + + @Override public void visitExec(JCExpressionStatement tree) { + align(); + print(tree.expr); + println(";", tree); + } + + @Override public void visitDoLoop(JCDoWhileLoop tree) { + aPrint("do "); + if (tree.body instanceof JCBlock) { + println("{"); + indent++; + print(((JCBlock) tree.body).stats, ""); + indent--; + aPrint("}"); + + } else print(tree.body); + print(" while "); + if (tree.cond instanceof JCParens) { + print(tree.cond); + } else { + print("("); + print(tree.cond); + print(")"); + } + println(";", tree); + } + + @Override public void visitSynchronized(JCSynchronized tree) { + aPrint("synchronized "); + if (tree.lock instanceof JCParens) { + print(tree.lock); + } else { + print("("); + print(tree.lock); + print(")"); + } + print(" "); + print(tree.body); + } + + @Override public void visitCase(JCCase tree) { + if (tree.pat == null) { + aPrint("default"); + } else { + aPrint("case "); + print(tree.pat); + } + println(": "); + indent++; + print(tree.stats, ""); + indent--; + } + + @Override public void visitCatch(JCCatch tree) { + print(" catch ("); + print(tree.param); + print(") "); + print(tree.body); + } + + @Override public void visitSwitch(JCSwitch tree) { + aPrint("switch "); + if (tree.selector instanceof JCParens) { + print(tree.selector); + } else { + print("("); + print(tree.selector); + print(")"); + } + println(" {"); + print(tree.cases, "\n"); + aPrintln("}", tree); + } + + @Override public void visitTry(JCTry tree) { + aPrint("try "); + List<?> resources = readObject(tree, "resources", List.nil()); + int len = resources.length(); + switch (len) { + case 0: + break; + case 1: + print("("); + JCVariableDecl decl = (JCVariableDecl) resources.get(0); + flagMod = -1L & ~FINAL; + printVarDefInline(decl); + print(") "); + break; + default: + println("("); + indent++; + int c = 0; + for (Object i : resources) { + align(); + flagMod = -1L & ~FINAL; + printVarDefInline((JCVariableDecl) i); + if (++c == len) { + print(") "); + } else { + println(";", (JCTree) i); + } + } + indent--; + } + println("{"); + indent++; + for (JCStatement stat : tree.body.stats) print(stat); + indent--; + aPrint("}"); + for (JCCatch catchBlock : tree.catchers) { + printCatch(catchBlock); + } + if (tree.finalizer != null) { + println(" finally {"); + indent++; + for (JCStatement stat : tree.finalizer.stats) print(stat); + indent--; + aPrint("}"); + } + println(tree); + } + + private void printCatch(JCCatch catchBlock) { + print(" catch ("); + printVarDefInline(catchBlock.param); // ExprType1 | ExprType2 handled via JCTypeUnion. + println(") {"); + indent++; + for (JCStatement stat : catchBlock.body.stats) print(stat); + indent--; + aPrint("}"); + } + + public void visitErroneous(JCErroneous tree) { + print("(ERROR)"); + } + + private static String operator(TreeTag tag) { + String op = OPERATORS.get(tag); + if (op == null) return "(?op?)"; + return op; + } + + private static String quoteChars(String s) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < s.length(); i++) sb.append(quoteChar(s.charAt(i))); + return sb.toString(); + } + + private static String quoteChar(char ch) { + switch (ch) { + case '\b': return "\\b"; + case '\f': return "\\f"; + case '\n': return "\\n"; + case '\r': return "\\r"; + case '\t': return "\\t"; + case '\'': return "\\'"; + case '\"': return "\\\""; + case '\\': return "\\\\"; + default: + if (ch < 32) return String.format("\\%03o", (int) ch); + return String.valueOf(ch); + } + } + + private static final Method getExtendsClause, getEndPosition, storeEnd; + + static { + getExtendsClause = getMethod(JCClassDecl.class, "getExtendsClause", new Class<?>[0]); + getExtendsClause.setAccessible(true); + + if (getJavaCompilerVersion() < 8) { + getEndPosition = getMethod(DiagnosticPosition.class, "getEndPosition", java.util.Map.class); + storeEnd = getMethod(java.util.Map.class, "put", Object.class, Object.class); + } else { + getEndPosition = getMethod(DiagnosticPosition.class, "getEndPosition", "com.sun.tools.javac.tree.EndPosTable"); + Method storeEndMethodTemp; + Class<?> endPosTable; + try { + endPosTable = Class.forName("com.sun.tools.javac.tree.EndPosTable"); + } catch (ClassNotFoundException ex) { + throw sneakyThrow(ex); + } + try { + storeEndMethodTemp = endPosTable.getMethod("storeEnd", JCTree.class, int.class); + } catch (NoSuchMethodException e) { + try { + endPosTable = Class.forName("com.sun.tools.javac.parser.JavacParser$AbstractEndPosTable"); + storeEndMethodTemp = endPosTable.getDeclaredMethod("storeEnd", JCTree.class, int.class); + } catch (NoSuchMethodException ex) { + throw sneakyThrow(ex); + } catch (ClassNotFoundException ex) { + throw sneakyThrow(ex); + } + } + storeEnd = storeEndMethodTemp; + } + getEndPosition.setAccessible(true); + storeEnd.setAccessible(true); + } + + private static Method getMethod(Class<?> clazz, String name, Class<?>... paramTypes) { + try { + return clazz.getMethod(name, paramTypes); + } catch (NoSuchMethodException e) { + throw sneakyThrow(e); + } + } + + private static Method getMethod(Class<?> clazz, String name, String... paramTypes) { + try { + Class<?>[] c = new Class[paramTypes.length]; + for (int i = 0; i < paramTypes.length; i++) c[i] = Class.forName(paramTypes[i]); + return clazz.getMethod(name, c); + } catch (NoSuchMethodException e) { + throw sneakyThrow(e); + } catch (ClassNotFoundException e) { + throw sneakyThrow(e); + } + } + + public static JCTree getExtendsClause(JCClassDecl decl) { + try { + return (JCTree) getExtendsClause.invoke(decl); + } catch (IllegalAccessException e) { + throw sneakyThrow(e); + } catch (InvocationTargetException e) { + throw sneakyThrow(e.getCause()); + } + } + + static RuntimeException sneakyThrow(Throwable t) { + if (t == null) throw new NullPointerException("t"); + PrettyPrinter.<RuntimeException>sneakyThrow0(t); + return null; + } + + @SuppressWarnings("unchecked") + private static <T extends Throwable> void sneakyThrow0(Throwable t) throws T { + throw (T)t; + } + + private static final Map<Class<?>, Map<String, Field>> reflectionCache = new HashMap<Class<?>, Map<String, Field>>(); + + @SuppressWarnings("unchecked") + private <T> T readObject(JCTree tree, String fieldName, T defaultValue) { + Class<?> tClass = tree.getClass(); + Map<String, Field> c = reflectionCache.get(tClass); + if (c == null) reflectionCache.put(tClass, c = new HashMap<String, Field>()); + Field f = c.get(fieldName); + if (f == null) { + try { + f = tClass.getDeclaredField(fieldName); + } catch (Exception e) { + return defaultValue; + } + f.setAccessible(true); + c.put(fieldName, f); + } + + try { + return (T) f.get(tree); + } catch (Exception e) { + return defaultValue; + } + } + + public void visitTypeBoundKind(TypeBoundKind tree) { + print(String.valueOf(tree.kind)); + } + + @Override public void visitTree(JCTree tree) { + String simpleName = tree.getClass().getSimpleName(); + if ("JCTypeUnion".equals(simpleName)) { + List<JCExpression> types = readObject(tree, "alternatives", List.<JCExpression>nil()); + print(types, " | "); + } else if ("JCTypeIntersection".equals(simpleName)) { + print(readObject(tree, "bounds", List.<JCExpression>nil()), " & "); + } else if ("JCMemberReference".equals(simpleName)) { + printMemberReference0(tree); + } else if ("JCLambda".equals(simpleName)) { + printLambda0(tree); + } else if ("JCAnnotatedType".equals(simpleName)) { + printAnnotatedType0(tree); + } else if ("JCPackageDecl".equals(simpleName)) { + // Starting with JDK9, this is inside the import list, but we've already printed it. Just ignore it. + } else { + throw new AssertionError("Unhandled tree type: " + tree.getClass() + ": " + tree); + } + } + + private void printMemberReference0(JCTree tree) { + print(readObject(tree, "expr", (JCExpression) null)); + print("::"); + List<JCExpression> typeArgs = readObject(tree, "typeargs", List.<JCExpression>nil()); + if (typeArgs != null && !typeArgs.isEmpty()) { + print("<"); + print(typeArgs, ", "); + print(">"); + } + print(readObject(tree, "mode", new Object()).toString().equals("INVOKE") ? readObject(tree, "name", (Name) null) : "new"); + } + + private void printLambda0(JCTree tree) { + List<JCVariableDecl> params = readObject(tree, "params", List.<JCVariableDecl>nil()); + boolean explicit = true; + int paramLength = params.size(); + try { + explicit = readObject(tree, "paramKind", new Object()).toString().equals("EXPLICIT"); + } catch (Exception e) {} + boolean useParens = paramLength != 1 || explicit; + if (useParens) print("("); + if (explicit) { + boolean first = true; + for (JCVariableDecl vd : params) { + if (!first) print(", "); + first = false; + printVarDefInline(vd); + } + } else { + String sep = ""; + for (JCVariableDecl param : params) { + print(sep); + print(param.name); + sep = ", "; + } + } + if (useParens) print(")"); + print(" -> "); + JCTree body = readObject(tree, "body", (JCTree) null); + if (body instanceof JCBlock) { + println("{"); + indent++; + print(((JCBlock) body).stats, ""); + indent--; + aPrint("}"); + } else { + print(body); + } + } + + private void printAnnotatedType0(JCTree tree) { + JCTree underlyingType = readObject(tree, "underlyingType", (JCTree) null); + if (underlyingType instanceof JCFieldAccess) { + print(((JCFieldAccess) underlyingType).selected); + print("."); + print(readObject(tree, "annotations", List.<JCExpression>nil()), " "); + print(" "); + print(((JCFieldAccess) underlyingType).name); + } else { + print(readObject(tree, "annotations", List.<JCExpression>nil()), " "); + print(" "); + print(underlyingType); + } + } +} diff --git a/src/delombok/lombok/delombok/UnicodeEscapeWriter.java b/src/delombok/lombok/delombok/UnicodeEscapeWriter.java index 95b55078..9c2ef190 100644 --- a/src/delombok/lombok/delombok/UnicodeEscapeWriter.java +++ b/src/delombok/lombok/delombok/UnicodeEscapeWriter.java @@ -63,6 +63,6 @@ public class UnicodeEscapeWriter extends Writer { } protected void writeUnicodeEscape(char c) throws IOException { - writer.write("\\u" + Integer.toHexString(c)); + writer.write(String.format("\\u%04x", (int) c)); } }
\ No newline at end of file diff --git a/src/delombok/lombok/delombok/ant/DelombokTask.java b/src/delombok/lombok/delombok/ant/DelombokTask.java index d5c17fe7..06bbe3e0 100644 --- a/src/delombok/lombok/delombok/ant/DelombokTask.java +++ b/src/delombok/lombok/delombok/ant/DelombokTask.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009 The Project Lombok Authors. + * Copyright (C) 2009-2015 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,142 +22,189 @@ package lombok.delombok.ant; import java.io.File; -import java.io.IOException; -import java.nio.charset.UnsupportedCharsetException; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; import java.util.ArrayList; -import java.util.Iterator; import java.util.List; -import lombok.delombok.Delombok; -import lombok.delombok.Delombok.InvalidFormatOptionException; +import lombok.Lombok; import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Location; import org.apache.tools.ant.Task; import org.apache.tools.ant.types.FileSet; import org.apache.tools.ant.types.Path; import org.apache.tools.ant.types.Reference; -import org.apache.tools.ant.types.resources.FileResource; -public class DelombokTask extends Task { - private File fromDir, toDir; - private Path classpath; - private Path sourcepath; - private boolean verbose; - private String encoding; - private Path path; - private List<Format> formatOptions = new ArrayList<Format>(); - - public void setClasspath(Path classpath) { - if (this.classpath == null) { - this.classpath = classpath; - } else { - this.classpath.append(classpath); +@SuppressWarnings("unused") // we use reflection to transfer fields. +class Tasks { + public static class Format { + private String value; + + @Override public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((value == null) ? 0 : value.hashCode()); + return result; } - } - - public Path createClasspath() { - if (classpath == null) { - classpath = new Path(getProject()); + + @Override public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + Format other = (Format) obj; + if (value == null) { + if (other.value != null) return false; + } else if (!value.equals(other.value)) return false; + return true; } - return classpath.createPath(); - } - - public void setClasspathRef(Reference r) { - createClasspath().setRefid(r); - } - - public void setSourcepath(Path sourcepath) { - if (this.sourcepath == null) { - this.sourcepath = sourcepath; - } else { - this.sourcepath.append(sourcepath); + + @Override public String toString() { + return "FormatOption [value=" + value + "]"; } - } - - public Path createSourcepath() { - if (sourcepath == null) { - sourcepath = new Path(getProject()); + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; } - return sourcepath.createPath(); - } - - public void setSourcepathRef(Reference r) { - createSourcepath().setRefid(r); - } - - public void setFrom(File dir) { - this.fromDir = dir; - } - - public void setTo(File dir) { - this.toDir = dir; - } - - public void setVerbose(boolean verbose) { - this.verbose = verbose; - } - - public void setEncoding(String encoding) { - this.encoding = encoding; - } - - public void addFileset(FileSet set) { - if (path == null) path = new Path(getProject()); - path.add(set); - } - - public void addFormat(Format format) { - formatOptions.add(format); } - @Override - public void execute() throws BuildException { - if (fromDir == null && path == null) throw new BuildException("Either 'from' attribute, or nested <fileset> tags are required."); - if (fromDir != null && path != null) throw new BuildException("You can't specify both 'from' attribute and nested filesets. You need one or the other."); - if (toDir == null) throw new BuildException("The to attribute is required."); - - Delombok delombok = new Delombok(); - if (verbose) delombok.setVerbose(true); - try { - if (encoding != null) delombok.setCharset(encoding); - } catch (UnsupportedCharsetException e) { - throw new BuildException("Unknown charset: " + encoding, getLocation()); - } - - if (classpath != null) delombok.setClasspath(classpath.toString()); - if (sourcepath != null) delombok.setSourcepath(sourcepath.toString()); - - try { - List<String> fo = new ArrayList<String>(); - for (Format f : formatOptions) { - String v = f.getValue(); - if (v == null) throw new BuildException("'value' property required for <format>"); - fo.add(v); + public static class Delombok extends Task { + private File fromDir, toDir; + private Path classpath; + private Path sourcepath; + private boolean verbose; + private String encoding; + private Path path; + private List<Format> formatOptions = new ArrayList<Format>(); + + public void setClasspath(Path classpath) { + if (this.classpath == null) { + this.classpath = classpath; + } else { + this.classpath.append(classpath); + } + } + + public Path createClasspath() { + if (classpath == null) { + classpath = new Path(getProject()); } - delombok.setFormatPreferences(Delombok.formatOptionsToMap(fo)); - } catch (InvalidFormatOptionException e) { - throw new BuildException(e.getMessage() + " Run java -jar lombok.jar --format-help for detailed format help."); - } - - delombok.setOutput(toDir); - try { - if (fromDir != null) delombok.addDirectory(fromDir); - else { - Iterator<?> it = path.iterator(); - while (it.hasNext()) { - FileResource fileResource = (FileResource) it.next(); - File baseDir = fileResource.getBaseDir(); - if (baseDir == null) { - File file = fileResource.getFile(); - delombok.addFile(file.getParentFile(), file.getName()); + return classpath.createPath(); + } + + public void setClasspathRef(Reference r) { + createClasspath().setRefid(r); + } + + public void setSourcepath(Path sourcepath) { + if (this.sourcepath == null) { + this.sourcepath = sourcepath; + } else { + this.sourcepath.append(sourcepath); + } + } + + public Path createSourcepath() { + if (sourcepath == null) { + sourcepath = new Path(getProject()); + } + return sourcepath.createPath(); + } + + public void setSourcepathRef(Reference r) { + createSourcepath().setRefid(r); + } + + public void setFrom(File dir) { + this.fromDir = dir; + } + + public void setTo(File dir) { + this.toDir = dir; + } + + public void setVerbose(boolean verbose) { + this.verbose = verbose; + } + + public void setEncoding(String encoding) { + this.encoding = encoding; + } + + public void addFileset(FileSet set) { + if (path == null) path = new Path(getProject()); + path.add(set); + } + + public Format createFormat() { + return new Format(); + } + + public void addFormat(Format format) { + formatOptions.add(format); + } + + private static ClassLoader shadowLoader; + + public static Class<?> shadowLoadClass(String name) { + try { + if (shadowLoader == null) { + try { + Class.forName("lombok.core.LombokNode"); + // If we get here, then lombok is already available. + shadowLoader = Delombok.class.getClassLoader(); + } catch (ClassNotFoundException e) { + // If we get here, it isn't, and we should use the shadowloader. + Class<?> launcherMain = Class.forName("lombok.launch.Main"); + Method m = launcherMain.getDeclaredMethod("createShadowClassLoader"); + m.setAccessible(true); + shadowLoader = (ClassLoader) m.invoke(null); + } + } + + return Class.forName(name, true, shadowLoader); + } catch (Exception e) { + throw Lombok.sneakyThrow(e); + } + } + + @Override + public void execute() throws BuildException { + Location loc = getLocation(); + + try { + Object instance = shadowLoadClass("lombok.delombok.ant.DelombokTaskImpl").newInstance(); + for(Field selfField : getClass().getDeclaredFields()) { + if (selfField.isSynthetic() || Modifier.isStatic(selfField.getModifiers())) continue; + Field otherField = instance.getClass().getDeclaredField(selfField.getName()); + otherField.setAccessible(true); + selfField.setAccessible(true); + if (selfField.getName().equals("formatOptions")) { + List<String> rep = new ArrayList<String>(); + for (Format f : formatOptions) { + if (f.getValue() == null) throw new BuildException("'value' property required for <format>"); + rep.add(f.getValue()); + } + otherField.set(instance, rep); } else { - delombok.addFile(baseDir, fileResource.getName()); + otherField.set(instance, selfField.get(this)); } } + + Method m = instance.getClass().getMethod("execute", Location.class); + m.setAccessible(true); + m.invoke(instance, loc); + } catch (InvocationTargetException e) { + throw Lombok.sneakyThrow(e.getCause()); + } catch (Exception e) { + throw Lombok.sneakyThrow(e); } - delombok.delombok(); - } catch (IOException e) { - throw new BuildException("I/O problem during delombok", e, getLocation()); } } } diff --git a/src/delombok/lombok/delombok/ant/DelombokTaskImpl.java b/src/delombok/lombok/delombok/ant/DelombokTaskImpl.java new file mode 100644 index 00000000..470819cd --- /dev/null +++ b/src/delombok/lombok/delombok/ant/DelombokTaskImpl.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2009-2015 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.delombok.ant; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.UnsupportedCharsetException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import lombok.delombok.Delombok; +import lombok.delombok.Delombok.InvalidFormatOptionException; + +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Location; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.resources.FileResource; + +public class DelombokTaskImpl { + private File fromDir, toDir; + private Path classpath; + private Path sourcepath; + private boolean verbose; + private String encoding; + private Path path; + private List<String> formatOptions = new ArrayList<String>(); + + public void execute(Location location) throws BuildException { + if (fromDir == null && path == null) throw new BuildException("Either 'from' attribute, or nested <fileset> tags are required."); + if (fromDir != null && path != null) throw new BuildException("You can't specify both 'from' attribute and nested filesets. You need one or the other."); + if (toDir == null) throw new BuildException("The to attribute is required."); + + Delombok delombok = new Delombok(); + if (verbose) delombok.setVerbose(true); + try { + if (encoding != null) delombok.setCharset(encoding); + } catch (UnsupportedCharsetException e) { + throw new BuildException("Unknown charset: " + encoding, location); + } + + if (classpath != null) delombok.setClasspath(classpath.toString()); + if (sourcepath != null) delombok.setSourcepath(sourcepath.toString()); + + try { + delombok.setFormatPreferences(Delombok.formatOptionsToMap(formatOptions)); + } catch (InvalidFormatOptionException e) { + throw new BuildException(e.getMessage() + " Run java -jar lombok.jar --format-help for detailed format help."); + } + + delombok.setOutput(toDir); + try { + if (fromDir != null) delombok.addDirectory(fromDir); + else { + Iterator<?> it = path.iterator(); + while (it.hasNext()) { + FileResource fileResource = (FileResource) it.next(); + File baseDir = fileResource.getBaseDir(); + if (baseDir == null) { + File file = fileResource.getFile(); + delombok.addFile(file.getParentFile(), file.getName()); + } else { + delombok.addFile(baseDir, fileResource.getName()); + } + } + } + delombok.delombok(); + } catch (IOException e) { + throw new BuildException("I/O problem during delombok", e, location); + } + } +} diff --git a/src/delombok/lombok/delombok/ant/Format.java b/src/delombok/lombok/delombok/ant/Format.java deleted file mode 100644 index bb2b5e87..00000000 --- a/src/delombok/lombok/delombok/ant/Format.java +++ /dev/null @@ -1,35 +0,0 @@ -package lombok.delombok.ant; - -public class Format { - private String value; - - @Override public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((value == null) ? 0 : value.hashCode()); - return result; - } - - @Override public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null) return false; - if (getClass() != obj.getClass()) return false; - Format other = (Format) obj; - if (value == null) { - if (other.value != null) return false; - } else if (!value.equals(other.value)) return false; - return true; - } - - @Override public String toString() { - return "FormatOption [value=" + value + "]"; - } - - public String getValue() { - return value; - } - - public void setValue(String value) { - this.value = value; - } -} diff --git a/src/eclipseAgent/lombok/eclipse/agent/EclipseLoaderPatcher.java b/src/eclipseAgent/lombok/eclipse/agent/EclipseLoaderPatcher.java new file mode 100644 index 00000000..aa01c13d --- /dev/null +++ b/src/eclipseAgent/lombok/eclipse/agent/EclipseLoaderPatcher.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2015 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.agent; + +import lombok.patcher.ClassRootFinder; +import lombok.patcher.Hook; +import lombok.patcher.MethodTarget; +import lombok.patcher.ScriptManager; +import lombok.patcher.StackRequest; +import lombok.patcher.scripts.ScriptBuilder; + +public class EclipseLoaderPatcher { + private static final String TRANSPLANTS_CLASS_NAME = "lombok.eclipse.agent.EclipseLoaderPatcherTransplants"; + + public static void patchEquinoxLoaders(ScriptManager sm, Class<?> launchingContext) { + sm.addScript(ScriptBuilder.exitEarly() + .target(new MethodTarget("org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader", "loadClass", + "java.lang.Class", "java.lang.String", "boolean")) + .target(new MethodTarget("org.eclipse.osgi.framework.adapter.core.AbstractClassLoader", "loadClass", + "java.lang.Class", "java.lang.String", "boolean")) + .target(new MethodTarget("org.eclipse.osgi.internal.loader.ModuleClassLoader", "loadClass", + "java.lang.Class", "java.lang.String", "boolean")) + .decisionMethod(new Hook(TRANSPLANTS_CLASS_NAME, "overrideLoadDecide", "boolean", "java.lang.ClassLoader", "java.lang.String", "boolean")) + .valueMethod(new Hook(TRANSPLANTS_CLASS_NAME, "overrideLoadResult", "java.lang.Class", "java.lang.ClassLoader", "java.lang.String", "boolean")) + .transplant() + .request(StackRequest.THIS, StackRequest.PARAM1, StackRequest.PARAM2).build()); + + sm.addScript(ScriptBuilder.addField().setPublic().setVolatile() + .fieldType("Ljava/lang/ClassLoader;") + .fieldName("lombok$shadowLoader") + .targetClass("org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader") + .targetClass("org.eclipse.osgi.framework.adapter.core.AbstractClassLoader") + .targetClass("org.eclipse.osgi.internal.loader.ModuleClassLoader") + .build()); + + sm.addScript(ScriptBuilder.addField().setPublic().setVolatile().setStatic() + .fieldType("Ljava/lang/Class;") + .fieldName("lombok$shadowLoaderClass") + .targetClass("org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader") + .targetClass("org.eclipse.osgi.framework.adapter.core.AbstractClassLoader") + .targetClass("org.eclipse.osgi.internal.loader.ModuleClassLoader") + .build()); + + sm.addScript(ScriptBuilder.addField().setPublic().setStatic().setFinal() + .fieldType("Ljava/lang/String;") + .fieldName("lombok$location") + .targetClass("org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader") + .targetClass("org.eclipse.osgi.framework.adapter.core.AbstractClassLoader") + .targetClass("org.eclipse.osgi.internal.loader.ModuleClassLoader") + .value(ClassRootFinder.findClassRootOfClass(launchingContext)) + .build()); + } +} diff --git a/src/eclipseAgent/lombok/eclipse/agent/EclipseLoaderPatcherTransplants.java b/src/eclipseAgent/lombok/eclipse/agent/EclipseLoaderPatcherTransplants.java new file mode 100644 index 00000000..ea72f56a --- /dev/null +++ b/src/eclipseAgent/lombok/eclipse/agent/EclipseLoaderPatcherTransplants.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2015 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.agent; + +import java.io.InputStream; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.jar.JarFile; +import java.util.zip.ZipEntry; + +/** + * Contains all the code to be transplanted into eclipse. + * + * Do not use: + * + * * Annotations + * * Generics + * * Varargs + * * Auto (un)boxing + * * class literals + * + * The above because this code is compiled with -source 1.4, and is transplanted. + * + * NB: The suppress warnings will be stripped out before compilation. + */ +@SuppressWarnings("all") +public class EclipseLoaderPatcherTransplants { + public static boolean overrideLoadDecide(ClassLoader original, String name, boolean resolve) { + return name.startsWith("lombok."); + } + + public static Class overrideLoadResult(ClassLoader original, String name, boolean resolve) throws ClassNotFoundException { + try { + Field shadowLoaderField = original.getClass().getField("lombok$shadowLoader"); + ClassLoader shadowLoader = (ClassLoader) shadowLoaderField.get(original); + if (shadowLoader == null) { + synchronized ("lombok$shadowLoader$globalLock".intern()) { + shadowLoader = (ClassLoader) shadowLoaderField.get(original); + if (shadowLoader == null) { + Class shadowClassLoaderClass = (Class) original.getClass().getField("lombok$shadowLoaderClass").get(null); + Class classLoaderClass = Class.forName("java.lang.ClassLoader"); + String jarLoc = (String) original.getClass().getField("lombok$location").get(null); + if (shadowClassLoaderClass == null) { + JarFile jf = new JarFile(jarLoc); + InputStream in = null; + try { + ZipEntry entry = jf.getEntry("lombok/launch/ShadowClassLoader.class"); + in = jf.getInputStream(entry); + byte[] bytes = new byte[65536]; + int len = 0; + while (true) { + int r = in.read(bytes, len, bytes.length - len); + if (r == -1) break; + len += r; + if (len == bytes.length) throw new IllegalStateException("lombok.launch.ShadowClassLoader too large."); + } + in.close(); + { + Class[] paramTypes = new Class[4]; + paramTypes[0] = "".getClass(); + paramTypes[1] = new byte[0].getClass(); + paramTypes[2] = Integer.TYPE; + paramTypes[3] = paramTypes[2]; + Method defineClassMethod = classLoaderClass.getDeclaredMethod("defineClass", paramTypes); + defineClassMethod.setAccessible(true); + shadowClassLoaderClass = (Class) defineClassMethod.invoke(original, new Object[] {"lombok.launch.ShadowClassLoader", bytes, new Integer(0), new Integer(len)}); + original.getClass().getField("lombok$shadowLoaderClass").set(null, shadowClassLoaderClass); + } + } finally { + if (in != null) in.close(); + jf.close(); + } + } + Class[] paramTypes = new Class[5]; + paramTypes[0] = classLoaderClass; + paramTypes[1] = "".getClass(); + paramTypes[2] = paramTypes[1]; + paramTypes[3] = Class.forName("java.util.List"); + paramTypes[4] = paramTypes[3]; + Constructor constructor = shadowClassLoaderClass.getDeclaredConstructor(paramTypes); + constructor.setAccessible(true); + shadowLoader = (ClassLoader) constructor.newInstance(new Object[] {original, "lombok", jarLoc, Arrays.asList(new Object[] {"lombok."}), Arrays.asList(new Object[] {"lombok.patcher.Symbols"})}); + shadowLoaderField.set(original, shadowLoader); + } + } + } + + if (resolve) { + Class[] paramTypes = new Class[2]; + paramTypes[0] = "".getClass(); + paramTypes[1] = Boolean.TYPE; + Method m = shadowLoader.getClass().getDeclaredMethod("loadClass", new Class[] {String.class, boolean.class}); + m.setAccessible(true); + return (Class) m.invoke(shadowLoader, new Object[] {name, Boolean.TRUE}); + } else { + return shadowLoader.loadClass(name); + } + } catch (Exception ex) { + Throwable t = ex; + if (t instanceof InvocationTargetException) t = t.getCause(); + if (t instanceof RuntimeException) throw (RuntimeException) t; + if (t instanceof Error) throw (Error) t; + throw new RuntimeException(t); + } + } +} diff --git a/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java b/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java index e14d1367..a6d745b6 100644 --- a/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java +++ b/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2012 The Project Lombok Authors. + * Copyright (C) 2009-2015 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 @@ -23,18 +23,22 @@ package lombok.eclipse.agent; import static lombok.patcher.scripts.ScriptBuilder.*; +import java.io.File; import java.lang.instrument.Instrumentation; +import java.net.URLClassLoader; +import java.security.ProtectionDomain; import java.util.Collection; import java.util.Collections; import java.util.List; -import lombok.core.Agent; +import lombok.core.AgentLauncher; +import lombok.patcher.Filter; import lombok.patcher.Hook; import lombok.patcher.MethodTarget; import lombok.patcher.ScriptManager; import lombok.patcher.StackRequest; import lombok.patcher.TargetMatcher; -import lombok.patcher.equinox.EquinoxClassLoader; +import lombok.patcher.TransplantMapper; import lombok.patcher.scripts.ScriptBuilder; /** @@ -44,13 +48,12 @@ import lombok.patcher.scripts.ScriptBuilder; * classes in this package for more information about which classes are transformed and how they are * transformed. */ -public class EclipsePatcher extends Agent { +public class EclipsePatcher implements AgentLauncher.AgentLaunchable { // At some point I'd like the agent to be capable of auto-detecting if its on eclipse or on ecj. This class is a sure sign we're not in ecj but in eclipse. -ReinierZ @SuppressWarnings("unused") private static final String ECLIPSE_SIGNATURE_CLASS = "org/eclipse/core/runtime/adaptor/EclipseStarter"; - @Override - public void runAgent(String agentArgs, Instrumentation instrumentation, boolean injected) throws Exception { + @Override public void runAgent(String agentArgs, Instrumentation instrumentation, boolean injected, Class<?> launchingContext) throws Exception { String[] args = agentArgs == null ? new String[0] : agentArgs.split(":"); boolean forceEcj = false; boolean forceEclipse = false; @@ -69,18 +72,31 @@ public class EclipsePatcher extends Agent { else if (forceEclipse) ecj = false; else ecj = injected; - registerPatchScripts(instrumentation, injected, ecj); + registerPatchScripts(instrumentation, injected, ecj, launchingContext); } - private static void registerPatchScripts(Instrumentation instrumentation, boolean reloadExistingClasses, boolean ecjOnly) { + private static void registerPatchScripts(Instrumentation instrumentation, boolean reloadExistingClasses, boolean ecjOnly, Class<?> launchingContext) { ScriptManager sm = new ScriptManager(); sm.registerTransformer(instrumentation); - if (!ecjOnly) { - EquinoxClassLoader.addPrefix("lombok."); - EquinoxClassLoader.registerScripts(sm); - } + sm.setFilter(new Filter() { + @Override public boolean shouldTransform(ClassLoader loader, String className, Class<?> classBeingDefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) { + if (!(loader instanceof URLClassLoader)) return true; + ClassLoader parent = loader.getParent(); + if (parent == null) return true; + return !parent.getClass().getName().startsWith("org.eclipse.jdt.apt.core.internal.AnnotationProcessorFactoryLoader"); + } + }); + + final boolean forceBaseResourceNames = shouldForceBaseResourceNames(); + sm.setTransplantMapper(new TransplantMapper() { + public String mapResourceName(int classFileFormatVersion, String resourceName) { + if (classFileFormatVersion < 50 || forceBaseResourceNames) return resourceName; + return "Class50/" + resourceName; + } + }); if (!ecjOnly) { + EclipseLoaderPatcher.patchEquinoxLoaders(sm, launchingContext); patchCatchReparse(sm); patchIdentifierEndReparse(sm); patchRetrieveEllipsisStartPosition(sm); @@ -90,7 +106,7 @@ public class EclipsePatcher extends Agent { patchHideGeneratedNodes(sm); patchPostCompileHookEclipse(sm); patchFixSourceTypeConverter(sm); - patchDisableLombokForCodeFormatterAndCleanup(sm); + patchDisableLombokForCodeCleanup(sm); patchListRewriteHandleGeneratedMethods(sm); patchSyntaxAndOccurrencesHighlighting(sm); patchSortMembersOperation(sm); @@ -105,16 +121,43 @@ public class EclipsePatcher extends Agent { patchLombokizeAST(sm); patchEcjTransformers(sm, ecjOnly); patchExtensionMethod(sm, ecjOnly); + patchRenameField(sm); if (reloadExistingClasses) sm.reloadClasses(instrumentation); } + private static boolean shouldForceBaseResourceNames() { + String shadowOverride = System.getProperty("shadow.override.lombok", ""); + if (shadowOverride == null || shadowOverride.length() == 0) return false; + for (String part : shadowOverride.split("\\s*" + (File.pathSeparatorChar == ';' ? ";" : ":") + "\\s*")) { + if (part.equalsIgnoreCase("lombok.jar")) return false; + } + return true; + } + + private static void patchRenameField(ScriptManager sm) { + /* RefactoringSearchEngine.search will not return results when renaming field and Data Annotation is present. Return a fake Element to make checks pass */ + sm.addScript(ScriptBuilder.wrapMethodCall() + .target(new MethodTarget("org.eclipse.jdt.internal.corext.refactoring.rename.RenameFieldProcessor", "checkAccessorDeclarations", "org.eclipse.ltk.core.refactoring.RefactoringStatus", "org.eclipse.core.runtime.IProgressMonitor", "org.eclipse.jdt.core.IMethod")) + .methodToWrap(new Hook("org.eclipse.jdt.internal.corext.refactoring.RefactoringSearchEngine", "search", "org.eclipse.jdt.internal.corext.refactoring.SearchResultGroup[]", "org.eclipse.jdt.core.search.SearchPattern","org.eclipse.jdt.core.search.IJavaSearchScope","org.eclipse.core.runtime.IProgressMonitor","org.eclipse.ltk.core.refactoring.RefactoringStatus")) + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "createFakeSearchResult", "org.eclipse.jdt.internal.corext.refactoring.SearchResultGroup[]", "org.eclipse.jdt.internal.corext.refactoring.SearchResultGroup[]", "java.lang.Object")) + .requestExtra(StackRequest.THIS) + .transplant().build()); + + /* Filter search results which are Generated and based on Fields, e.g. Generated getters/setters */ + sm.addScript(ScriptBuilder.wrapMethodCall() + .target(new MethodTarget("org.eclipse.jdt.internal.corext.refactoring.rename.RenameFieldProcessor", "addAccessorOccurrences", "void", "org.eclipse.core.runtime.IProgressMonitor", "org.eclipse.jdt.core.IMethod", "java.lang.String","java.lang.String","org.eclipse.ltk.core.refactoring.RefactoringStatus")) + .methodToWrap(new Hook("org.eclipse.jdt.internal.corext.refactoring.SearchResultGroup", "getSearchResults", "org.eclipse.jdt.core.search.SearchMatch[]")) + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "removeGenerated", "org.eclipse.jdt.core.search.SearchMatch[]", "org.eclipse.jdt.core.search.SearchMatch[]")) + .transplant().build()); + } + private static void patchExtractInterface(ScriptManager sm) { /* Fix sourceEnding for generated nodes to avoid null pointer */ sm.addScript(ScriptBuilder.wrapMethodCall() .target(new MethodTarget("org.eclipse.jdt.internal.compiler.SourceElementNotifier", "notifySourceElementRequestor", "void", "org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration", "org.eclipse.jdt.internal.compiler.ast.TypeDeclaration", "org.eclipse.jdt.internal.compiler.ast.ImportReference")) .methodToWrap(new Hook("org.eclipse.jdt.internal.compiler.util.HashtableOfObjectToInt", "get", "int", "java.lang.Object")) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "getSourceEndFixed", "int", "int", "org.eclipse.jdt.internal.compiler.ast.ASTNode")) + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "getSourceEndFixed", "int", "int", "org.eclipse.jdt.internal.compiler.ast.ASTNode")) .requestExtra(StackRequest.PARAM1) .transplant().build()); @@ -127,7 +170,7 @@ public class EclipsePatcher extends Agent { "org.eclipse.jdt.core.dom.MethodDeclaration" )) .methodToWrap(new Hook("org.eclipse.jface.text.IDocument", "get", "java.lang.String", "int", "int")) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "getRealMethodDeclarationSource", "java.lang.String", "java.lang.String", "java.lang.Object", "org.eclipse.jdt.core.dom.MethodDeclaration")) + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "getRealMethodDeclarationSource", "java.lang.String", "java.lang.String", "java.lang.Object", "org.eclipse.jdt.core.dom.MethodDeclaration")) .requestExtra(StackRequest.THIS, StackRequest.PARAM4) .transplant().build()); @@ -136,20 +179,20 @@ public class EclipsePatcher extends Agent { .target(new MethodTarget("org.eclipse.jdt.internal.corext.refactoring.structure.ExtractInterfaceProcessor", "createMemberDeclarations")) .target(new MethodTarget("org.eclipse.jdt.internal.corext.refactoring.structure.ExtractInterfaceProcessor", "createMethodComments")) .methodToReplace(new Hook("org.eclipse.jdt.internal.corext.refactoring.structure.ASTNodeSearchUtil", "getMethodDeclarationNode", "org.eclipse.jdt.core.dom.MethodDeclaration", "org.eclipse.jdt.core.IMethod", "org.eclipse.jdt.core.dom.CompilationUnit")) - .replacementMethod(new Hook("lombok.eclipse.agent.PatchFixes", "getRealMethodDeclarationNode", "org.eclipse.jdt.core.dom.MethodDeclaration", "org.eclipse.jdt.core.IMethod", "org.eclipse.jdt.core.dom.CompilationUnit")) + .replacementMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "getRealMethodDeclarationNode", "org.eclipse.jdt.core.dom.MethodDeclaration", "org.eclipse.jdt.core.IMethod", "org.eclipse.jdt.core.dom.CompilationUnit")) .transplant().build()); /* Do not add @Override's for generated methods */ sm.addScript(ScriptBuilder.exitEarly() .target(new MethodTarget("org.eclipse.jdt.core.dom.rewrite.ListRewrite", "insertFirst")) - .decisionMethod(new Hook("lombok.eclipse.agent.PatchFixes", "isListRewriteOnGeneratedNode", "boolean", "org.eclipse.jdt.core.dom.rewrite.ListRewrite")) + .decisionMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "isListRewriteOnGeneratedNode", "boolean", "org.eclipse.jdt.core.dom.rewrite.ListRewrite")) .request(StackRequest.THIS) .transplant().build()); /* Do not add comments for generated methods */ sm.addScript(ScriptBuilder.exitEarly() .target(new MethodTarget("org.eclipse.jdt.internal.corext.refactoring.structure.ExtractInterfaceProcessor", "createMethodComment")) - .decisionMethod(new Hook("lombok.eclipse.agent.PatchFixes", "isGenerated", "boolean", "org.eclipse.jdt.core.dom.ASTNode")) + .decisionMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "isGenerated", "boolean", "org.eclipse.jdt.core.dom.ASTNode")) .request(StackRequest.PARAM2) .transplant().build()); } @@ -162,7 +205,7 @@ public class EclipsePatcher extends Agent { */ sm.addScript(ScriptBuilder.wrapReturnValue() .target(new MethodTarget("org.eclipse.core.internal.runtime.Product", "getProperty", "java.lang.String", "java.lang.String")) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "addLombokNotesToEclipseAboutDialog", "java.lang.String", "java.lang.String", "java.lang.String")) + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$LombokDeps", "addLombokNotesToEclipseAboutDialog", "java.lang.String", "java.lang.String", "java.lang.String")) .request(StackRequest.RETURN_VALUE, StackRequest.PARAM1) .transplant().build()); } @@ -175,19 +218,13 @@ public class EclipsePatcher extends Agent { .target(new MethodTarget("org.eclipse.jdt.internal.ui.search.OccurrencesFinder", "addUsage")) .target(new MethodTarget("org.eclipse.jdt.internal.ui.search.OccurrencesFinder", "addWrite")) .target(new MethodTarget("org.eclipse.jdt.internal.ui.javaeditor.SemanticHighlightingReconciler$PositionCollector", "visit", "boolean", "org.eclipse.jdt.core.dom.SimpleName")) - .decisionMethod(new Hook("lombok.eclipse.agent.PatchFixes", "isGenerated", "boolean", "org.eclipse.jdt.core.dom.ASTNode")) - .valueMethod(new Hook("lombok.eclipse.agent.PatchFixes", "returnFalse", "boolean", "java.lang.Object")) + .decisionMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "isGenerated", "boolean", "org.eclipse.jdt.core.dom.ASTNode")) + .valueMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "returnFalse", "boolean", "java.lang.Object")) .request(StackRequest.PARAM1) .build()); } - private static void patchDisableLombokForCodeFormatterAndCleanup(ScriptManager sm) { - sm.addScript(ScriptBuilder.setSymbolDuringMethodCall() - .target(new MethodTarget("org.eclipse.jdt.internal.formatter.DefaultCodeFormatter", "formatCompilationUnit")) - .callToWrap(new Hook("org.eclipse.jdt.internal.core.util.CodeSnippetParsingUtil", "parseCompilationUnit", "org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration", "char[]", "java.util.Map", "boolean")) - .symbol("lombok.disable") - .build()); - + private static void patchDisableLombokForCodeCleanup(ScriptManager sm) { sm.addScript(ScriptBuilder.exitEarly() .target(new MethodTarget("org.eclipse.jdt.internal.corext.fix.ControlStatementsFix$ControlStatementFinder", "visit", "boolean", "org.eclipse.jdt.core.dom.DoStatement")) .target(new MethodTarget("org.eclipse.jdt.internal.corext.fix.ControlStatementsFix$ControlStatementFinder", "visit", "boolean", "org.eclipse.jdt.core.dom.EnhancedForStatement")) @@ -201,9 +238,9 @@ public class EclipsePatcher extends Agent { .target(new MethodTarget("org.eclipse.jdt.internal.corext.fix.CodeStyleFix$CodeStyleVisitor", "visit", "boolean", "org.eclipse.jdt.core.dom.QualifiedName")) .target(new MethodTarget("org.eclipse.jdt.internal.corext.fix.CodeStyleFix$CodeStyleVisitor", "visit", "boolean", "org.eclipse.jdt.core.dom.SimpleName")) // if a generated node has children we can just ignore them as well; - .decisionMethod(new Hook("lombok.eclipse.agent.PatchFixes", "isGenerated", "boolean", "org.eclipse.jdt.core.dom.ASTNode")) + .decisionMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "isGenerated", "boolean", "org.eclipse.jdt.core.dom.ASTNode")) .request(StackRequest.PARAM1) - .valueMethod(new Hook("lombok.eclipse.agent.PatchFixes", "returnFalse", "boolean", "java.lang.Object")) + .valueMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "returnFalse", "boolean", "java.lang.Object")) .build()); } @@ -211,7 +248,7 @@ public class EclipsePatcher extends Agent { sm.addScript(ScriptBuilder.replaceMethodCall() .target(new MethodTarget("org.eclipse.jdt.internal.core.dom.rewrite.ASTRewriteAnalyzer$ListRewriter", "rewriteList")) .methodToReplace(new Hook("org.eclipse.jdt.internal.core.dom.rewrite.RewriteEvent", "getChildren", "org.eclipse.jdt.internal.core.dom.rewrite.RewriteEvent[]")) - .replacementMethod(new Hook("lombok.eclipse.agent.PatchFixes", "listRewriteHandleGeneratedMethods", "org.eclipse.jdt.internal.core.dom.rewrite.RewriteEvent[]", "org.eclipse.jdt.internal.core.dom.rewrite.RewriteEvent")) + .replacementMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "listRewriteHandleGeneratedMethods", "org.eclipse.jdt.internal.core.dom.rewrite.RewriteEvent[]", "org.eclipse.jdt.internal.core.dom.rewrite.RewriteEvent")) .build()); } @@ -223,37 +260,37 @@ public class EclipsePatcher extends Agent { sm.addScript(ScriptBuilder.wrapMethodCall() .target(new MethodTarget("org.eclipse.jdt.internal.core.SortElementsOperation$2", "visit", "boolean", "org.eclipse.jdt.core.dom.CompilationUnit")) .methodToWrap(new Hook("org.eclipse.jdt.core.dom.CompilationUnit", "types", "java.util.List")) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "removeGeneratedNodes", "java.util.List", "java.util.List")) + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "removeGeneratedNodes", "java.util.List", "java.util.List")) .transplant().build()); sm.addScript(ScriptBuilder.wrapMethodCall() .target(new MethodTarget("org.eclipse.jdt.internal.core.SortElementsOperation$2", "visit", "boolean", "org.eclipse.jdt.core.dom.AnnotationTypeDeclaration")) .methodToWrap(new Hook("org.eclipse.jdt.core.dom.AnnotationTypeDeclaration", "bodyDeclarations", "java.util.List")) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "removeGeneratedNodes", "java.util.List", "java.util.List")) + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "removeGeneratedNodes", "java.util.List", "java.util.List")) .transplant().build()); sm.addScript(ScriptBuilder.wrapMethodCall() .target(new MethodTarget("org.eclipse.jdt.internal.core.SortElementsOperation$2", "visit", "boolean", "org.eclipse.jdt.core.dom.AnonymousClassDeclaration")) .methodToWrap(new Hook("org.eclipse.jdt.core.dom.AnonymousClassDeclaration", "bodyDeclarations", "java.util.List")) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "removeGeneratedNodes", "java.util.List", "java.util.List")) + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "removeGeneratedNodes", "java.util.List", "java.util.List")) .transplant().build()); sm.addScript(ScriptBuilder.wrapMethodCall() .target(new MethodTarget("org.eclipse.jdt.internal.core.SortElementsOperation$2", "visit", "boolean", "org.eclipse.jdt.core.dom.TypeDeclaration")) .methodToWrap(new Hook("org.eclipse.jdt.core.dom.TypeDeclaration", "bodyDeclarations", "java.util.List")) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "removeGeneratedNodes", "java.util.List", "java.util.List")) + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "removeGeneratedNodes", "java.util.List", "java.util.List")) .transplant().build()); sm.addScript(ScriptBuilder.wrapMethodCall() .target(new MethodTarget("org.eclipse.jdt.internal.core.SortElementsOperation$2", "visit", "boolean", "org.eclipse.jdt.core.dom.EnumDeclaration")) .methodToWrap(new Hook("org.eclipse.jdt.core.dom.EnumDeclaration", "bodyDeclarations", "java.util.List")) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "removeGeneratedNodes", "java.util.List", "java.util.List")) + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "removeGeneratedNodes", "java.util.List", "java.util.List")) .transplant().build()); sm.addScript(ScriptBuilder.wrapMethodCall() .target(new MethodTarget("org.eclipse.jdt.internal.core.SortElementsOperation$2", "visit", "boolean", "org.eclipse.jdt.core.dom.EnumDeclaration")) .methodToWrap(new Hook("org.eclipse.jdt.core.dom.EnumDeclaration", "enumConstants", "java.util.List")) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "removeGeneratedNodes", "java.util.List", "java.util.List")) + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "removeGeneratedNodes", "java.util.List", "java.util.List")) .transplant().build()); } @@ -261,7 +298,7 @@ public class EclipsePatcher extends Agent { sm.addScript(ScriptBuilder.replaceMethodCall() .target(new MethodTarget("org.eclipse.jdt.internal.core.dom.rewrite.ASTRewriteAnalyzer", "visit")) .methodToReplace(new Hook("org.eclipse.jdt.internal.core.dom.rewrite.TokenScanner", "getTokenEndOffset", "int", "int", "int")) - .replacementMethod(new Hook("lombok.eclipse.agent.PatchFixes", "getTokenEndOffsetFixed", "int", "org.eclipse.jdt.internal.core.dom.rewrite.TokenScanner", "int", "int", "java.lang.Object")) + .replacementMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "getTokenEndOffsetFixed", "int", "org.eclipse.jdt.internal.core.dom.rewrite.TokenScanner", "int", "int", "java.lang.Object")) .requestExtra(StackRequest.PARAM1) .transplant() .build()); @@ -273,7 +310,7 @@ public class EclipsePatcher extends Agent { .target(new MethodTarget("org.eclipse.jdt.internal.core.builder.IncrementalImageBuilder", "writeClassFileContents")) .target(new MethodTarget("org.eclipse.jdt.internal.core.builder.AbstractImageBuilder", "writeClassFileContents")) .methodToWrap(new Hook("org.eclipse.jdt.internal.compiler.ClassFile", "getBytes", "byte[]")) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "runPostCompiler", "byte[]", "byte[]", "java.lang.String")) + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$LombokDeps", "runPostCompiler", "byte[]", "byte[]", "java.lang.String")) .requestExtra(StackRequest.PARAM3) .build()); } @@ -282,24 +319,22 @@ public class EclipsePatcher extends Agent { sm.addScript(ScriptBuilder.wrapMethodCall() .target(new MethodTarget("org.eclipse.jdt.internal.compiler.tool.EclipseCompilerImpl", "outputClassFiles")) .methodToWrap(new Hook("javax.tools.JavaFileObject", "openOutputStream", "java.io.OutputStream")) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "runPostCompiler", "java.io.OutputStream", "java.io.OutputStream")) - .transplant() - .build()); + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$LombokDeps", "runPostCompiler", "java.io.OutputStream", "java.io.OutputStream")) + .transplant().build()); sm.addScript(ScriptBuilder.wrapMethodCall() .target(new MethodTarget("org.eclipse.jdt.internal.compiler.util.Util", "writeToDisk")) .methodToWrap(new Hook("java.io.BufferedOutputStream", "<init>", "void", "java.io.OutputStream", "int")) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "runPostCompiler", "java.io.BufferedOutputStream", "java.io.BufferedOutputStream", "java.lang.String", "java.lang.String")) + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$LombokDeps", "runPostCompiler", "java.io.BufferedOutputStream", "java.io.BufferedOutputStream", "java.lang.String", "java.lang.String")) .requestExtra(StackRequest.PARAM2, StackRequest.PARAM3) - .transplant() - .build()); + .transplant().build()); } private static void patchHideGeneratedNodes(ScriptManager sm) { sm.addScript(ScriptBuilder.wrapReturnValue() .target(new MethodTarget("org.eclipse.jdt.internal.corext.dom.LinkedNodeFinder", "findByNode")) .target(new MethodTarget("org.eclipse.jdt.internal.corext.dom.LinkedNodeFinder", "findByBinding")) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "removeGeneratedSimpleNames", "org.eclipse.jdt.core.dom.SimpleName[]", + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "removeGeneratedSimpleNames", "org.eclipse.jdt.core.dom.SimpleName[]", "org.eclipse.jdt.core.dom.SimpleName[]")) .request(StackRequest.RETURN_VALUE).build()); @@ -308,33 +343,41 @@ public class EclipsePatcher extends Agent { } private static void patchFormatters(ScriptManager sm) { + // before Eclipse Mars sm.addScript(ScriptBuilder.setSymbolDuringMethodCall() - .target(new MethodTarget("org.eclipse.jdt.internal.ui.text.java.JavaFormattingStrategy", "format", "void")) - .callToWrap(new Hook("org.eclipse.jdt.internal.corext.util.CodeFormatterUtil", "reformat", "org.eclipse.text.edits.TextEdit", - "int", "java.lang.String", "int", "int", "int", "java.lang.String", "java.util.Map")) - .symbol("lombok.disable").build()); + .target(new MethodTarget("org.eclipse.jdt.internal.formatter.DefaultCodeFormatter", "formatCompilationUnit")) + .callToWrap(new Hook("org.eclipse.jdt.internal.core.util.CodeSnippetParsingUtil", "parseCompilationUnit", "org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration", "char[]", "java.util.Map", "boolean")) + .symbol("lombok.disable") + .build()); + + // Eclipse Mars and beyond + sm.addScript(ScriptBuilder.setSymbolDuringMethodCall() + .target(new MethodTarget("org.eclipse.jdt.internal.formatter.DefaultCodeFormatter", "parseSourceCode")) + .callToWrap(new Hook("org.eclipse.jdt.core.dom.ASTParser", "createAST", "org.eclipse.jdt.core.dom.ASTNode", "org.eclipse.core.runtime.IProgressMonitor")) + .symbol("lombok.disable") + .build()); } private static void patchRefactorScripts(ScriptManager sm) { sm.addScript(ScriptBuilder.exitEarly() .target(new MethodTarget("org.eclipse.jdt.core.dom.rewrite.ASTRewrite", "replace")) .target(new MethodTarget("org.eclipse.jdt.core.dom.rewrite.ASTRewrite", "remove")) - .decisionMethod(new Hook("lombok.eclipse.agent.PatchFixes", "skipRewritingGeneratedNodes", "boolean", + .decisionMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "skipRewritingGeneratedNodes", "boolean", "org.eclipse.jdt.core.dom.ASTNode")) .transplant().request(StackRequest.PARAM1).build()); sm.addScript(ScriptBuilder.wrapMethodCall() .target(new MethodTarget("org.eclipse.jdt.internal.corext.refactoring.rename.RenameTypeProcessor", "addConstructorRenames")) .methodToWrap(new Hook("org.eclipse.jdt.core.IType", "getMethods", "org.eclipse.jdt.core.IMethod[]")) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "removeGeneratedMethods", "org.eclipse.jdt.core.IMethod[]", + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "removeGeneratedMethods", "org.eclipse.jdt.core.IMethod[]", "org.eclipse.jdt.core.IMethod[]")) .transplant().build()); sm.addScript(ScriptBuilder.exitEarly() .target(new MethodTarget("org.eclipse.jdt.internal.corext.refactoring.rename.TempOccurrenceAnalyzer", "visit", "boolean", "org.eclipse.jdt.core.dom.SimpleName")) .target(new MethodTarget("org.eclipse.jdt.internal.corext.refactoring.rename.RenameAnalyzeUtil$ProblemNodeFinder$NameNodeVisitor", "visit", "boolean", "org.eclipse.jdt.core.dom.SimpleName")) - .decisionMethod(new Hook("lombok.eclipse.agent.PatchFixes", "isGenerated", "boolean", "org.eclipse.jdt.core.dom.ASTNode")) - .valueMethod(new Hook("lombok.eclipse.agent.PatchFixes", "returnTrue", "boolean", "java.lang.Object")) + .decisionMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "isGenerated", "boolean", "org.eclipse.jdt.core.dom.ASTNode")) + .valueMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "returnTrue", "boolean", "java.lang.Object")) .request(StackRequest.PARAM1) .transplant().build()); } @@ -342,30 +385,54 @@ public class EclipsePatcher extends Agent { private static void patchCatchReparse(ScriptManager sm) { sm.addScript(ScriptBuilder.wrapReturnValue() .target(new MethodTarget("org.eclipse.jdt.core.dom.ASTConverter", "retrieveStartingCatchPosition")) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "fixRetrieveStartingCatchPosition", "int", "int", "int")) + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "fixRetrieveStartingCatchPosition", "int", "int", "int")) .transplant().request(StackRequest.RETURN_VALUE, StackRequest.PARAM1).build()); } private static void patchIdentifierEndReparse(ScriptManager sm) { sm.addScript(ScriptBuilder.wrapReturnValue() .target(new MethodTarget("org.eclipse.jdt.core.dom.ASTConverter", "retrieveIdentifierEndPosition")) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "fixRetrieveIdentifierEndPosition", "int", "int", "int")) + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "fixRetrieveIdentifierEndPosition", "int", "int", "int")) .transplant().request(StackRequest.RETURN_VALUE, StackRequest.PARAM2).build()); } private static void patchRetrieveEllipsisStartPosition(ScriptManager sm) { sm.addScript(ScriptBuilder.wrapReturnValue() .target(new MethodTarget("org.eclipse.jdt.core.dom.ASTConverter", "retrieveEllipsisStartPosition")) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "fixRetrieveEllipsisStartPosition", "int", "int", "int")) + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "fixRetrieveEllipsisStartPosition", "int", "int", "int")) .transplant().request(StackRequest.RETURN_VALUE, StackRequest.PARAM2).build()); } private static void patchRetrieveRightBraceOrSemiColonPosition(ScriptManager sm) { - sm.addScript(ScriptBuilder.wrapReturnValue() - .target(new MethodTarget("org.eclipse.jdt.core.dom.ASTConverter", "retrieveRightBraceOrSemiColonPosition")) - .target(new MethodTarget("org.eclipse.jdt.core.dom.ASTConverter", "retrieveRightBrace")) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "fixRetrieveRightBraceOrSemiColonPosition", "int", "int", "int")) - .transplant().request(StackRequest.RETURN_VALUE, StackRequest.PARAM2).build()); + sm.addScript(ScriptBuilder.wrapMethodCall() + .target(new MethodTarget("org.eclipse.jdt.core.dom.ASTConverter", "convert", "org.eclipse.jdt.core.dom.ASTNode", "boolean", "org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration")) + .methodToWrap(new Hook("org.eclipse.jdt.core.dom.ASTConverter", "retrieveRightBraceOrSemiColonPosition", "int", "int", "int")) + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "fixRetrieveRightBraceOrSemiColonPosition", "int", "int", "org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration")) + .requestExtra(StackRequest.PARAM2) + .transplant() + .build()); + + sm.addScript(ScriptBuilder.wrapMethodCall() + .target(new MethodTarget("org.eclipse.jdt.core.dom.ASTConverter", "convert", "org.eclipse.jdt.core.dom.ASTNode", "boolean", "org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration")) + .methodToWrap(new Hook("org.eclipse.jdt.core.dom.ASTConverter", "retrieveRightBrace", "int", "int", "int")) + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "fixRetrieveRightBraceOrSemiColonPosition", "int", "int", "org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration")) + .requestExtra(StackRequest.PARAM2) + .transplant() + .build()); + + sm.addScript(ScriptBuilder.wrapMethodCall() + .target(new MethodTarget("org.eclipse.jdt.core.dom.ASTConverter", "convert", "org.eclipse.jdt.core.dom.ASTNode", "org.eclipse.jdt.internal.compiler.ast.FieldDeclaration")) + .methodToWrap(new Hook("org.eclipse.jdt.core.dom.ASTConverter", "retrieveRightBrace", "int", "int", "int")) + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "fixRetrieveRightBraceOrSemiColonPosition", "int", "int", "org.eclipse.jdt.internal.compiler.ast.FieldDeclaration")) + .requestExtra(StackRequest.PARAM1) + .transplant() + .build()); + +// sm.addScript(ScriptBuilder.wrapReturnValue() +// .target(new MethodTarget("org.eclipse.jdt.core.dom.ASTConverter", "retrieveRightBraceOrSemiColonPosition")) +// .target(new MethodTarget("org.eclipse.jdt.core.dom.ASTConverter", "retrieveRightBrace")) +// .wrapMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "fixRetrieveRightBraceOrSemiColonPosition", "int", "int", "int")) +// .transplant().request(StackRequest.RETURN_VALUE, StackRequest.PARAM2).build()); } private static void patchSetGeneratedFlag(ScriptManager sm) { @@ -396,14 +463,14 @@ public class EclipsePatcher extends Agent { return Collections.singleton("org.eclipse.jdt.core.dom.ASTConverter"); } }).request(StackRequest.PARAM1, StackRequest.RETURN_VALUE) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "setIsGeneratedFlag", "void", + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "setIsGeneratedFlag", "void", "org.eclipse.jdt.core.dom.ASTNode", "org.eclipse.jdt.internal.compiler.ast.ASTNode")) .transplant().build()); sm.addScript(ScriptBuilder.wrapReturnValue() .target(new MethodTarget("org.eclipse.jdt.core.dom.ASTConverter", "convert", "org.eclipse.jdt.core.dom.ASTNode", "boolean", "org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration")) .request(StackRequest.PARAM2, StackRequest.RETURN_VALUE) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "setIsGeneratedFlag", "void", + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "setIsGeneratedFlag", "void", "org.eclipse.jdt.core.dom.ASTNode", "org.eclipse.jdt.internal.compiler.ast.ASTNode")) .transplant().build()); @@ -421,7 +488,7 @@ public class EclipsePatcher extends Agent { .target(new MethodTarget("org.eclipse.jdt.core.dom.ASTConverter", "convertToVariableDeclarationStatement", "org.eclipse.jdt.core.dom.VariableDeclarationStatement", "org.eclipse.jdt.internal.compiler.ast.LocalDeclaration")) /* Targets above are only patched because the resulting dom nodes should be marked if generated. */ .request(StackRequest.PARAM1, StackRequest.RETURN_VALUE) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "setIsGeneratedFlag", "void", + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "setIsGeneratedFlag", "void", "org.eclipse.jdt.core.dom.ASTNode", "org.eclipse.jdt.internal.compiler.ast.ASTNode")) .transplant().build()); @@ -443,7 +510,7 @@ public class EclipsePatcher extends Agent { } }).methodToWrap(new Hook("org.eclipse.jdt.core.dom.SimpleName", "<init>", "void", "org.eclipse.jdt.core.dom.AST")) .requestExtra(StackRequest.PARAM1) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "setIsGeneratedFlagForName", "void", + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "setIsGeneratedFlagForName", "void", "org.eclipse.jdt.core.dom.Name", "java.lang.Object")) .transplant().build()); @@ -451,7 +518,7 @@ public class EclipsePatcher extends Agent { .target(new MethodTarget("org.eclipse.jdt.core.dom.ASTConverter", "convert", "org.eclipse.jdt.core.dom.ASTNode", "boolean", "org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration")) .methodToWrap(new Hook("org.eclipse.jdt.core.dom.SimpleName", "<init>", "void", "org.eclipse.jdt.core.dom.AST")) .requestExtra(StackRequest.PARAM2) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "setIsGeneratedFlagForName", "void", + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "setIsGeneratedFlagForName", "void", "org.eclipse.jdt.core.dom.Name", "java.lang.Object")) .transplant().build()); @@ -460,7 +527,7 @@ public class EclipsePatcher extends Agent { .target(new MethodTarget("org.eclipse.jdt.core.dom.ASTConverter", "setQualifiedNameNameAndSourceRanges", "org.eclipse.jdt.core.dom.QualifiedName", "char[][]", "long[]", "int", "org.eclipse.jdt.internal.compiler.ast.ASTNode")) .methodToWrap(new Hook("org.eclipse.jdt.core.dom.SimpleName", "<init>", "void", "org.eclipse.jdt.core.dom.AST")) .requestExtra(StackRequest.PARAM4) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "setIsGeneratedFlagForName", "void", + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "setIsGeneratedFlagForName", "void", "org.eclipse.jdt.core.dom.Name", "java.lang.Object")) .transplant().build()); @@ -468,7 +535,7 @@ public class EclipsePatcher extends Agent { .target(new MethodTarget("org.eclipse.jdt.core.dom.ASTConverter", "setQualifiedNameNameAndSourceRanges", "org.eclipse.jdt.core.dom.QualifiedName", "char[][]", "long[]", "int", "org.eclipse.jdt.internal.compiler.ast.ASTNode")) .methodToWrap(new Hook("org.eclipse.jdt.core.dom.QualifiedName", "<init>", "void", "org.eclipse.jdt.core.dom.AST")) .requestExtra(StackRequest.PARAM4) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "setIsGeneratedFlagForName", "void", + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "setIsGeneratedFlagForName", "void", "org.eclipse.jdt.core.dom.Name", "java.lang.Object")) .transplant().build()); @@ -476,7 +543,7 @@ public class EclipsePatcher extends Agent { .target(new MethodTarget("org.eclipse.jdt.core.dom.ASTConverter", "setQualifiedNameNameAndSourceRanges", "org.eclipse.jdt.core.dom.QualifiedName", "char[][]", "long[]", "org.eclipse.jdt.internal.compiler.ast.ASTNode")) .methodToWrap(new Hook("org.eclipse.jdt.core.dom.SimpleName", "<init>", "void", "org.eclipse.jdt.core.dom.AST")) .requestExtra(StackRequest.PARAM3) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "setIsGeneratedFlagForName", "void", + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "setIsGeneratedFlagForName", "void", "org.eclipse.jdt.core.dom.Name", "java.lang.Object")) .transplant().build()); @@ -484,7 +551,7 @@ public class EclipsePatcher extends Agent { .target(new MethodTarget("org.eclipse.jdt.core.dom.ASTConverter", "setQualifiedNameNameAndSourceRanges", "org.eclipse.jdt.core.dom.QualifiedName", "char[][]", "long[]", "org.eclipse.jdt.internal.compiler.ast.ASTNode")) .methodToWrap(new Hook("org.eclipse.jdt.core.dom.QualifiedName", "<init>", "void", "org.eclipse.jdt.core.dom.AST")) .requestExtra(StackRequest.PARAM3) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "setIsGeneratedFlagForName", "void", + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "setIsGeneratedFlagForName", "void", "org.eclipse.jdt.core.dom.Name", "java.lang.Object")) .transplant().build()); } @@ -495,7 +562,7 @@ public class EclipsePatcher extends Agent { .target(new MethodTarget(PARSER_SIG, "parse", "void", "org.eclipse.jdt.internal.compiler.ast.MethodDeclaration", "org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration")) - .decisionMethod(new Hook("lombok.eclipse.agent.PatchFixes", "checkBit24", "boolean", "java.lang.Object")) + .decisionMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "checkBit24", "boolean", "java.lang.Object")) .transplant() .request(StackRequest.PARAM1).build()); @@ -503,7 +570,7 @@ public class EclipsePatcher extends Agent { .target(new MethodTarget(PARSER_SIG, "parse", "void", "org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration", "org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration", "boolean")) - .decisionMethod(new Hook("lombok.eclipse.agent.PatchFixes", "checkBit24", "boolean", "java.lang.Object")) + .decisionMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "checkBit24", "boolean", "java.lang.Object")) .transplant() .request(StackRequest.PARAM1).build()); @@ -512,7 +579,7 @@ public class EclipsePatcher extends Agent { "org.eclipse.jdt.internal.compiler.ast.Initializer", "org.eclipse.jdt.internal.compiler.ast.TypeDeclaration", "org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration")) - .decisionMethod(new Hook("lombok.eclipse.agent.PatchFixes", "checkBit24", "boolean", "java.lang.Object")) + .decisionMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "checkBit24", "boolean", "java.lang.Object")) .transplant() .request(StackRequest.PARAM1).build()); } @@ -528,12 +595,12 @@ public class EclipsePatcher extends Agent { sm.addScript(ScriptBuilder.wrapReturnValue() .target(new MethodTarget(PARSER_SIG, "getMethodBodies", "void", CUD_SIG)) - .wrapMethod(new Hook("lombok.eclipse.TransformEclipseAST", "transform", "void", PARSER_SIG, CUD_SIG)) + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$Transform", "transform", "void", PARSER_SIG, CUD_SIG)) .request(StackRequest.THIS, StackRequest.PARAM1).build()); sm.addScript(ScriptBuilder.wrapReturnValue() .target(new MethodTarget(PARSER_SIG, "endParse", CUD_SIG, "int")) - .wrapMethod(new Hook("lombok.eclipse.TransformEclipseAST", "transform_swapped", "void", CUD_SIG, PARSER_SIG)) + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$Transform", "transform_swapped", "void", CUD_SIG, PARSER_SIG)) .request(StackRequest.THIS, StackRequest.RETURN_VALUE).build()); } @@ -549,7 +616,7 @@ public class EclipsePatcher extends Agent { sm.addScript(ScriptBuilder.exitEarly() .target(new MethodTarget(CLASSSCOPE_SIG, "buildFieldsAndMethods", "void")) .request(StackRequest.THIS) - .decisionMethod(new Hook("lombok.eclipse.agent.PatchDelegatePortal", "handleDelegateForType", "boolean", "java.lang.Object")) + .decisionMethod(new Hook("lombok.launch.PatchFixesHider$Delegate", "handleDelegateForType", "boolean", "java.lang.Object")) .build()); } @@ -579,24 +646,24 @@ public class EclipsePatcher extends Agent { sm.addScript(ScriptBuilder.wrapReturnValue() .target(new MethodTarget(PARSER_SIG, "consumeExitVariableWithInitialization", "void")) .request(StackRequest.THIS) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchValEclipsePortal", "copyInitializationOfLocalDeclaration", "void", "java.lang.Object")) + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$ValPortal", "copyInitializationOfLocalDeclaration", "void", "java.lang.Object")) .build()); sm.addScript(ScriptBuilder.wrapReturnValue() .target(new MethodTarget(PARSER_SIG, "consumeEnhancedForStatementHeader", "void")) .request(StackRequest.THIS) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchValEclipsePortal", "copyInitializationOfForEachIterable", "void", "java.lang.Object")) + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$ValPortal", "copyInitializationOfForEachIterable", "void", "java.lang.Object")) .build()); sm.addScript(ScriptBuilder.wrapReturnValue() .target(new MethodTarget(ASTCONVERTER_SIG, "setModifiers", "void", VARIABLEDECLARATIONSTATEMENT_SIG, LOCALDECLARATION_SIG)) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchValEclipsePortal", "addFinalAndValAnnotationToVariableDeclarationStatement", + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$ValPortal", "addFinalAndValAnnotationToVariableDeclarationStatement", "void", "java.lang.Object", "java.lang.Object", "java.lang.Object")) .request(StackRequest.THIS, StackRequest.PARAM1, StackRequest.PARAM2).build()); sm.addScript(ScriptBuilder.wrapReturnValue() .target(new MethodTarget(ASTCONVERTER_SIG, "setModifiers", "void", SINGLEVARIABLEDECLARATION_SIG, LOCALDECLARATION_SIG)) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchValEclipsePortal", "addFinalAndValAnnotationToSingleVariableDeclaration", + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$ValPortal", "addFinalAndValAnnotationToSingleVariableDeclaration", "void", "java.lang.Object", "java.lang.Object", "java.lang.Object")) .request(StackRequest.THIS, StackRequest.PARAM1, StackRequest.PARAM2).build()); } @@ -611,26 +678,26 @@ public class EclipsePatcher extends Agent { sm.addScript(ScriptBuilder.exitEarly() .target(new MethodTarget(LOCALDECLARATION_SIG, "resolve", "void", BLOCKSCOPE_SIG)) .request(StackRequest.THIS, StackRequest.PARAM1) - .decisionMethod(new Hook("lombok.eclipse.agent.PatchVal", "handleValForLocalDeclaration", "boolean", LOCALDECLARATION_SIG, BLOCKSCOPE_SIG)) + .decisionMethod(new Hook("lombok.launch.PatchFixesHider$Val", "handleValForLocalDeclaration", "boolean", LOCALDECLARATION_SIG, BLOCKSCOPE_SIG)) .build()); sm.addScript(ScriptBuilder.replaceMethodCall() .target(new MethodTarget(LOCALDECLARATION_SIG, "resolve", "void", BLOCKSCOPE_SIG)) .methodToReplace(new Hook(EXPRESSION_SIG, "resolveType", TYPEBINDING_SIG, BLOCKSCOPE_SIG)) .requestExtra(StackRequest.THIS) - .replacementMethod(new Hook("lombok.eclipse.agent.PatchVal", "skipResolveInitializerIfAlreadyCalled2", TYPEBINDING_SIG, EXPRESSION_SIG, BLOCKSCOPE_SIG, LOCALDECLARATION_SIG)) + .replacementMethod(new Hook("lombok.launch.PatchFixesHider$Val", "skipResolveInitializerIfAlreadyCalled2", TYPEBINDING_SIG, EXPRESSION_SIG, BLOCKSCOPE_SIG, LOCALDECLARATION_SIG)) .build()); sm.addScript(ScriptBuilder.replaceMethodCall() .target(new MethodTarget(FOREACHSTATEMENT_SIG, "resolve", "void", BLOCKSCOPE_SIG)) .methodToReplace(new Hook(EXPRESSION_SIG, "resolveType", TYPEBINDING_SIG, BLOCKSCOPE_SIG)) - .replacementMethod(new Hook("lombok.eclipse.agent.PatchVal", "skipResolveInitializerIfAlreadyCalled", TYPEBINDING_SIG, EXPRESSION_SIG, BLOCKSCOPE_SIG)) + .replacementMethod(new Hook("lombok.launch.PatchFixesHider$Val", "skipResolveInitializerIfAlreadyCalled", TYPEBINDING_SIG, EXPRESSION_SIG, BLOCKSCOPE_SIG)) .build()); sm.addScript(ScriptBuilder.exitEarly() .target(new MethodTarget(FOREACHSTATEMENT_SIG, "resolve", "void", BLOCKSCOPE_SIG)) .request(StackRequest.THIS, StackRequest.PARAM1) - .decisionMethod(new Hook("lombok.eclipse.agent.PatchVal", "handleValForForEach", "boolean", FOREACHSTATEMENT_SIG, BLOCKSCOPE_SIG)) + .decisionMethod(new Hook("lombok.launch.PatchFixesHider$Val", "handleValForForEach", "boolean", FOREACHSTATEMENT_SIG, BLOCKSCOPE_SIG)) .build()); } @@ -641,7 +708,7 @@ public class EclipsePatcher extends Agent { sm.addScript(ScriptBuilder.wrapReturnValue() .target(new MethodTarget(SOURCE_TYPE_CONVERTER_SIG, "convertAnnotations", ANNOTATION_SIG + "[]", I_ANNOTATABLE_SIG)) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "convertAnnotations", ANNOTATION_SIG + "[]", ANNOTATION_SIG + "[]", I_ANNOTATABLE_SIG)) + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "convertAnnotations", ANNOTATION_SIG + "[]", ANNOTATION_SIG + "[]", I_ANNOTATABLE_SIG)) .request(StackRequest.PARAM1, StackRequest.RETURN_VALUE).build()); } @@ -659,10 +726,11 @@ public class EclipsePatcher extends Agent { } private static void patchExtensionMethod(ScriptManager sm, boolean ecj) { - final String PATCH_EXTENSIONMETHOD = "lombok.eclipse.agent.PatchExtensionMethod"; + final String PATCH_EXTENSIONMETHOD = "lombok.launch.PatchFixesHider$ExtensionMethod"; final String PATCH_EXTENSIONMETHOD_COMPLETIONPROPOSAL_PORTAL = "lombok.eclipse.agent.PatchExtensionMethodCompletionProposalPortal"; final String MESSAGE_SEND_SIG = "org.eclipse.jdt.internal.compiler.ast.MessageSend"; final String TYPE_BINDING_SIG = "org.eclipse.jdt.internal.compiler.lookup.TypeBinding"; + final String SCOPE_SIG = "org.eclipse.jdt.internal.compiler.lookup.Scope"; final String BLOCK_SCOPE_SIG = "org.eclipse.jdt.internal.compiler.lookup.BlockScope"; final String TYPE_BINDINGS_SIG = "org.eclipse.jdt.internal.compiler.lookup.TypeBinding[]"; final String PROBLEM_REPORTER_SIG = "org.eclipse.jdt.internal.compiler.problem.ProblemReporter"; @@ -690,6 +758,13 @@ public class EclipsePatcher extends Agent { .replacementMethod(new Hook(PATCH_EXTENSIONMETHOD, "invalidMethod", "void", PROBLEM_REPORTER_SIG, MESSAGE_SEND_SIG, METHOD_BINDING_SIG)) .build()); + // Since eclipse mars; they added a param. + sm.addScript(replaceMethodCall() + .target(new MethodTarget(MESSAGE_SEND_SIG, "resolveType", TYPE_BINDING_SIG, BLOCK_SCOPE_SIG)) + .methodToReplace(new Hook(PROBLEM_REPORTER_SIG, "invalidMethod", "void", MESSAGE_SEND_SIG, METHOD_BINDING_SIG, SCOPE_SIG)) + .replacementMethod(new Hook(PATCH_EXTENSIONMETHOD, "invalidMethod", "void", PROBLEM_REPORTER_SIG, MESSAGE_SEND_SIG, METHOD_BINDING_SIG, SCOPE_SIG)) + .build()); + if (!ecj) { sm.addScript(wrapReturnValue() .target(new MethodTarget(COMPLETION_PROPOSAL_COLLECTOR_SIG, "getJavaCompletionProposals", I_JAVA_COMPLETION_PROPOSAL_SIG)) diff --git a/src/eclipseAgent/lombok/eclipse/agent/PatchDelegate.java b/src/eclipseAgent/lombok/eclipse/agent/PatchDelegate.java index b1f5a43a..02760e35 100644 --- a/src/eclipseAgent/lombok/eclipse/agent/PatchDelegate.java +++ b/src/eclipseAgent/lombok/eclipse/agent/PatchDelegate.java @@ -699,7 +699,10 @@ public class PatchDelegate { } private static void addAllMethodBindings0(List<BindingTuple> list, TypeBinding binding, Set<String> banList, char[] fieldName, ASTNode responsible) throws DelegateRecursion { - if (binding instanceof SourceTypeBinding) ((SourceTypeBinding) binding).scope.environment().globalOptions.storeAnnotations = true; + if (binding instanceof SourceTypeBinding) { + ClassScope scope = ((SourceTypeBinding) binding).scope; + if (scope != null) scope.environment().globalOptions.storeAnnotations = true; + } if (binding == null) return; TypeBinding inner; @@ -721,42 +724,44 @@ public class PatchDelegate { } } - if (binding instanceof ReferenceBinding) { - ReferenceBinding rb = (ReferenceBinding) binding; - MethodBinding[] availableMethods = rb.availableMethods(); - FieldBinding[] availableFields = rb.availableFields(); - failIfContainsAnnotation(binding, availableMethods); - failIfContainsAnnotation(binding, availableFields); - - MethodBinding[] parameterizedSigs = availableMethods; - MethodBinding[] baseSigs = parameterizedSigs; - if (binding instanceof ParameterizedTypeBinding) { - baseSigs = ((ParameterizedTypeBinding)binding).genericType().availableMethods(); - if (baseSigs.length != parameterizedSigs.length) { - // The last known state of eclipse source says this can't happen, so we rely on it, - // but if this invariant is broken, better to go with 'arg0' naming instead of crashing. - baseSigs = parameterizedSigs; - } - } - for (int i = 0; i < parameterizedSigs.length; i++) { - MethodBinding mb = parameterizedSigs[i]; - String sig = printSig(mb); - if (mb.isStatic()) continue; - if (mb.isBridge()) continue; - if (mb.isConstructor()) continue; - if (mb.isDefaultAbstract()) continue; - if (!mb.isPublic()) continue; - if (mb.isSynthetic()) continue; - if (!banList.add(sig)) continue; // If add returns false, it was already in there. - BindingTuple pair = new BindingTuple(mb, baseSigs[i], fieldName, responsible); - list.add(pair); - } - addAllMethodBindings0(list, rb.superclass(), banList, fieldName, responsible); - ReferenceBinding[] interfaces = rb.superInterfaces(); - if (interfaces != null) { - for (ReferenceBinding iface : interfaces) addAllMethodBindings0(list, iface, banList, fieldName, responsible); + if (!(binding instanceof ReferenceBinding)) { + return; + } + + ReferenceBinding rb = (ReferenceBinding) binding; + MethodBinding[] availableMethods = rb.availableMethods(); + FieldBinding[] availableFields = rb.availableFields(); + failIfContainsAnnotation(binding, availableMethods); + failIfContainsAnnotation(binding, availableFields); + + MethodBinding[] parameterizedSigs = availableMethods; + MethodBinding[] baseSigs = parameterizedSigs; + if (binding instanceof ParameterizedTypeBinding) { + baseSigs = ((ParameterizedTypeBinding)binding).genericType().availableMethods(); + if (baseSigs.length != parameterizedSigs.length) { + // The last known state of eclipse source says this can't happen, so we rely on it, + // but if this invariant is broken, better to go with 'arg0' naming instead of crashing. + baseSigs = parameterizedSigs; } } + for (int i = 0; i < parameterizedSigs.length; i++) { + MethodBinding mb = parameterizedSigs[i]; + String sig = printSig(mb); + if (mb.isStatic()) continue; + if (mb.isBridge()) continue; + if (mb.isConstructor()) continue; + if (mb.isDefaultAbstract()) continue; + if (!mb.isPublic()) continue; + if (mb.isSynthetic()) continue; + if (!banList.add(sig)) continue; // If add returns false, it was already in there. + BindingTuple pair = new BindingTuple(mb, baseSigs[i], fieldName, responsible); + list.add(pair); + } + addAllMethodBindings0(list, rb.superclass(), banList, fieldName, responsible); + ReferenceBinding[] interfaces = rb.superInterfaces(); + if (interfaces != null) { + for (ReferenceBinding iface : interfaces) addAllMethodBindings0(list, iface, banList, fieldName, responsible); + } } private static final char[] STRING_LOMBOK = new char[] {'l', 'o', 'm', 'b', 'o', 'k'}; diff --git a/src/eclipseAgent/lombok/eclipse/agent/PatchExtensionMethod.java b/src/eclipseAgent/lombok/eclipse/agent/PatchExtensionMethod.java index 8eec27fb..5d586dff 100644 --- a/src/eclipseAgent/lombok/eclipse/agent/PatchExtensionMethod.java +++ b/src/eclipseAgent/lombok/eclipse/agent/PatchExtensionMethod.java @@ -24,6 +24,8 @@ package lombok.eclipse.agent; import static lombok.eclipse.handlers.EclipseHandlerUtil.createAnnotation; import java.lang.ref.WeakReference; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -56,6 +58,7 @@ import org.eclipse.jdt.internal.compiler.lookup.CompilationUnitScope; import org.eclipse.jdt.internal.compiler.lookup.MethodBinding; import org.eclipse.jdt.internal.compiler.lookup.ProblemMethodBinding; import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding; +import org.eclipse.jdt.internal.compiler.lookup.Scope; import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; import org.eclipse.jdt.internal.compiler.lookup.TypeIds; import org.eclipse.jdt.internal.compiler.problem.ProblemReporter; @@ -89,16 +92,47 @@ public class PatchExtensionMethod { private final ProblemReporter problemReporter; private final WeakReference<MessageSend> messageSendRef; private final MethodBinding method; + private final Scope scope; - PostponedInvalidMethodError(ProblemReporter problemReporter, MessageSend messageSend, MethodBinding method) { + private static final Method shortMethod = getMethod("invalidMethod", MessageSend.class, MethodBinding.class); + private static final Method longMethod = getMethod("invalidMethod", MessageSend.class, MethodBinding.class, Scope.class); + + private static Method getMethod(String name, Class<?>... types) { + try { + Method m = ProblemReporter.class.getMethod(name, types); + m.setAccessible(true); + return m; + } catch (Exception e) { + return null; + } + } + + PostponedInvalidMethodError(ProblemReporter problemReporter, MessageSend messageSend, MethodBinding method, Scope scope) { this.problemReporter = problemReporter; this.messageSendRef = new WeakReference<MessageSend>(messageSend); this.method = method; + this.scope = scope; + } + + static void invoke(ProblemReporter problemReporter, MessageSend messageSend, MethodBinding method, Scope scope) { + if (messageSend != null) { + try { + if (shortMethod != null) shortMethod.invoke(problemReporter, messageSend, method); + else if (longMethod != null) longMethod.invoke(problemReporter, messageSend, method, scope); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + Throwable t = e.getCause(); + if (t instanceof Error) throw (Error) t; + if (t instanceof RuntimeException) throw (RuntimeException) t; + throw new RuntimeException(t); + } + } } public void fire() { MessageSend messageSend = messageSendRef.get(); - if (messageSend != null) problemReporter.invalidMethod(messageSend, method); + invoke(problemReporter, messageSend, method, scope); } } @@ -185,7 +219,11 @@ public class PatchExtensionMethod { } public static void invalidMethod(ProblemReporter problemReporter, MessageSend messageSend, MethodBinding method) { - MessageSend_postponedErrors.set(messageSend, new PostponedInvalidMethodError(problemReporter, messageSend, method)); + MessageSend_postponedErrors.set(messageSend, new PostponedInvalidMethodError(problemReporter, messageSend, method, null)); + } + + public static void invalidMethod(ProblemReporter problemReporter, MessageSend messageSend, MethodBinding method, Scope scope) { + MessageSend_postponedErrors.set(messageSend, new PostponedInvalidMethodError(problemReporter, messageSend, method, scope)); } public static TypeBinding resolveType(TypeBinding resolvedType, MessageSend methodCall, BlockScope scope) { @@ -224,12 +262,16 @@ public class PatchExtensionMethod { if (methodCall.arguments != null) arguments.addAll(Arrays.asList(methodCall.arguments)); List<TypeBinding> argumentTypes = new ArrayList<TypeBinding>(); for (Expression argument : arguments) { - argumentTypes.add(argument.resolvedType); + if (argument.resolvedType != null) argumentTypes.add(argument.resolvedType); + // TODO: Instead of just skipping nulls entirely, there is probably a 'unresolved type' placeholder. THAT is what we ought to be adding here! } + Expression[] originalArgs = methodCall.arguments; + methodCall.arguments = arguments.toArray(new Expression[0]); MethodBinding fixedBinding = scope.getMethod(extensionMethod.declaringClass, methodCall.selector, argumentTypes.toArray(new TypeBinding[0]), methodCall); if (fixedBinding instanceof ProblemMethodBinding) { + methodCall.arguments = originalArgs; if (fixedBinding.declaringClass != null) { - scope.problemReporter().invalidMethod(methodCall, fixedBinding); + PostponedInvalidMethodError.invoke(scope.problemReporter(), methodCall, fixedBinding, scope); } } else { for (int i = 0, iend = arguments.size(); i < iend; i++) { @@ -246,7 +288,6 @@ public class PatchExtensionMethod { arg.implicitConversion = TypeIds.UNBOXING | (id + (id << 4)); // magic see TypeIds } } - methodCall.arguments = arguments.toArray(new Expression[0]); methodCall.receiver = createNameRef(extensionMethod.declaringClass, methodCall); methodCall.actualReceiverType = extensionMethod.declaringClass; diff --git a/src/eclipseAgent/lombok/eclipse/agent/PatchExtensionMethodCompletionProposal.java b/src/eclipseAgent/lombok/eclipse/agent/PatchExtensionMethodCompletionProposal.java index 97ca5a7e..c11a49cd 100644 --- a/src/eclipseAgent/lombok/eclipse/agent/PatchExtensionMethodCompletionProposal.java +++ b/src/eclipseAgent/lombok/eclipse/agent/PatchExtensionMethodCompletionProposal.java @@ -60,8 +60,6 @@ import org.eclipse.jdt.ui.text.java.CompletionProposalCollector; import org.eclipse.jdt.ui.text.java.IJavaCompletionProposal; public class PatchExtensionMethodCompletionProposal { - - public static IJavaCompletionProposal[] getJavaCompletionProposals(IJavaCompletionProposal[] javaCompletionProposals, CompletionProposalCollector completionProposalCollector) { @@ -178,7 +176,7 @@ public class PatchExtensionMethodCompletionProposal { return !proposals.isEmpty() && Reflection.isComplete(); } - private static int getReplacementOffset(IJavaCompletionProposal proposal) { + private static int getReplacementOffset(Object proposal) { try { return Reflection.replacementOffsetField.getInt(proposal); } catch (Exception ignore) { diff --git a/src/eclipseAgent/lombok/eclipse/agent/PatchExtensionMethodCompletionProposalPortal.java b/src/eclipseAgent/lombok/eclipse/agent/PatchExtensionMethodCompletionProposalPortal.java index 6dca1901..19e1952e 100644 --- a/src/eclipseAgent/lombok/eclipse/agent/PatchExtensionMethodCompletionProposalPortal.java +++ b/src/eclipseAgent/lombok/eclipse/agent/PatchExtensionMethodCompletionProposalPortal.java @@ -33,7 +33,6 @@ public class PatchExtensionMethodCompletionProposalPortal { private static final String COMPLETION_PROPOSAL_COLLECTOR = "org.eclipse.jdt.ui.text.java.CompletionProposalCollector"; private static final String I_JAVA_COMPLETION_PROPOSAL_ARRAY = "[Lorg.eclipse.jdt.ui.text.java.IJavaCompletionProposal;"; - public static IJavaCompletionProposal[] getJavaCompletionProposals(Object[] javaCompletionProposals, Object completionProposalCollector) { try { return (IJavaCompletionProposal[]) ReflectionForUi.getJavaCompletionProposals.invoke(null, javaCompletionProposals, completionProposalCollector); @@ -52,7 +51,7 @@ public class PatchExtensionMethodCompletionProposalPortal { } //ignore, we don't have access to the correct ECJ classes, so lombok can't possibly //do anything useful here. - return (IJavaCompletionProposal[])javaCompletionProposals; + return (IJavaCompletionProposal[]) javaCompletionProposals; } } diff --git a/src/eclipseAgent/lombok/eclipse/agent/PatchFixes.java b/src/eclipseAgent/lombok/eclipse/agent/PatchFixes.java deleted file mode 100644 index d1c668a0..00000000 --- a/src/eclipseAgent/lombok/eclipse/agent/PatchFixes.java +++ /dev/null @@ -1,384 +0,0 @@ -/* - * Copyright (C) 2010-2013 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.agent; - -import java.io.BufferedOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.List; -import java.util.Stack; - -import lombok.core.DiagnosticsReceiver; -import lombok.core.PostCompiler; -import lombok.core.Version; -import lombok.eclipse.EclipseAugments; - -import org.eclipse.core.runtime.CoreException; -import org.eclipse.jdt.core.IAnnotatable; -import org.eclipse.jdt.core.IAnnotation; -import org.eclipse.jdt.core.IMethod; -import org.eclipse.jdt.core.IType; -import org.eclipse.jdt.core.JavaModelException; -import org.eclipse.jdt.core.dom.MethodDeclaration; -import org.eclipse.jdt.core.dom.SimpleName; -import org.eclipse.jdt.internal.compiler.ast.Annotation; -import org.eclipse.jdt.internal.core.dom.rewrite.NodeRewriteEvent; -import org.eclipse.jdt.internal.core.dom.rewrite.RewriteEvent; -import org.eclipse.jdt.internal.core.dom.rewrite.TokenScanner; -import org.eclipse.jdt.internal.corext.refactoring.structure.ASTNodeSearchUtil; - -public class PatchFixes { - public static String addLombokNotesToEclipseAboutDialog(String origReturnValue, String key) { - if ("aboutText".equals(key)) { - return origReturnValue + "\n\nLombok " + Version.getFullVersion() + " is installed. http://projectlombok.org/"; - } - - return origReturnValue; - } - - public static boolean isGenerated(org.eclipse.jdt.core.dom.ASTNode node) { - boolean result = false; - try { - result = ((Boolean)node.getClass().getField("$isGenerated").get(node)).booleanValue(); - if (!result && node.getParent() != null && node.getParent() instanceof org.eclipse.jdt.core.dom.QualifiedName) - result = isGenerated(node.getParent()); - } catch (Exception e) { - // better to assume it isn't generated - } - return result; - } - - public static boolean isListRewriteOnGeneratedNode(org.eclipse.jdt.core.dom.rewrite.ListRewrite rewrite) { - return isGenerated(rewrite.getParent()); - } - - public static boolean returnFalse(java.lang.Object object) { - return false; - } - - public static boolean returnTrue(java.lang.Object object) { - return true; - } - - @java.lang.SuppressWarnings({"unchecked", "rawtypes"}) public static java.util.List removeGeneratedNodes(java.util.List list) { - try { - java.util.List realNodes = new java.util.ArrayList(list.size()); - for (java.lang.Object node : list) { - if(!isGenerated(((org.eclipse.jdt.core.dom.ASTNode)node))) { - realNodes.add(node); - } - } - return realNodes; - } catch (Exception e) { - } - return list; - } - - public static java.lang.String getRealMethodDeclarationSource(java.lang.String original, Object processor, org.eclipse.jdt.core.dom.MethodDeclaration declaration) throws Exception { - if (!isGenerated(declaration)) return original; - - List<org.eclipse.jdt.core.dom.Annotation> annotations = new ArrayList<org.eclipse.jdt.core.dom.Annotation>(); - for (Object modifier : declaration.modifiers()) { - if (modifier instanceof org.eclipse.jdt.core.dom.Annotation) { - org.eclipse.jdt.core.dom.Annotation annotation = (org.eclipse.jdt.core.dom.Annotation)modifier; - String qualifiedAnnotationName = annotation.resolveTypeBinding().getQualifiedName(); - if (!"java.lang.Override".equals(qualifiedAnnotationName) && !"java.lang.SuppressWarnings".equals(qualifiedAnnotationName)) annotations.add(annotation); - } - } - - StringBuilder signature = new StringBuilder(); - addAnnotations(annotations, signature); - - if ((Boolean)processor.getClass().getDeclaredField("fPublic").get(processor)) signature.append("public "); - if ((Boolean)processor.getClass().getDeclaredField("fAbstract").get(processor)) signature.append("abstract "); - - signature - .append(declaration.getReturnType2().toString()) - .append(" ").append(declaration.getName().getFullyQualifiedName()) - .append("("); - - boolean first = true; - for (Object parameter : declaration.parameters()) { - if (!first) signature.append(", "); - first = false; - // We should also add the annotations of the parameters - signature.append(parameter); - } - - signature.append(");"); - return signature.toString(); - } - - // part of getRealMethodDeclarationSource(...) - public static void addAnnotations(List<org.eclipse.jdt.core.dom.Annotation> annotations, StringBuilder signature) { - /* - * We SHOULD be able to handle the following cases: - * @Override - * @Override() - * @SuppressWarnings("all") - * @SuppressWarnings({"all", "unused"}) - * @SuppressWarnings(value = "all") - * @SuppressWarnings(value = {"all", "unused"}) - * @EqualsAndHashCode(callSuper=true, of="id") - * - * Currently, we only seem to correctly support: - * @Override - * @Override() N.B. We lose the parentheses here, since there are no values. No big deal. - * @SuppressWarnings("all") - */ - for (org.eclipse.jdt.core.dom.Annotation annotation : annotations) { - List<String> values = new ArrayList<String>(); - if (annotation.isSingleMemberAnnotation()) { - org.eclipse.jdt.core.dom.SingleMemberAnnotation smAnn = (org.eclipse.jdt.core.dom.SingleMemberAnnotation) annotation; - values.add(smAnn.getValue().toString()); - } else if (annotation.isNormalAnnotation()) { - org.eclipse.jdt.core.dom.NormalAnnotation normalAnn = (org.eclipse.jdt.core.dom.NormalAnnotation) annotation; - for (Object value : normalAnn.values()) values.add(value.toString()); - } - - signature.append("@").append(annotation.resolveTypeBinding().getQualifiedName()); - if (!values.isEmpty()) { - signature.append("("); - boolean first = true; - for (String string : values) { - if (!first) signature.append(", "); - first = false; - signature.append('"').append(string).append('"'); - } - signature.append(")"); - } - signature.append(" "); - } - } - - public static org.eclipse.jdt.core.dom.MethodDeclaration getRealMethodDeclarationNode(org.eclipse.jdt.core.IMethod sourceMethod, org.eclipse.jdt.core.dom.CompilationUnit cuUnit) throws JavaModelException { - MethodDeclaration methodDeclarationNode = ASTNodeSearchUtil.getMethodDeclarationNode(sourceMethod, cuUnit); - if (isGenerated(methodDeclarationNode)) { - IType declaringType = sourceMethod.getDeclaringType(); - Stack<IType> typeStack = new Stack<IType>(); - while (declaringType != null) { - typeStack.push(declaringType); - declaringType = declaringType.getDeclaringType(); - } - - IType rootType = typeStack.pop(); - org.eclipse.jdt.core.dom.AbstractTypeDeclaration typeDeclaration = findTypeDeclaration(rootType, cuUnit.types()); - while (!typeStack.isEmpty() && typeDeclaration != null) { - typeDeclaration = findTypeDeclaration(typeStack.pop(), typeDeclaration.bodyDeclarations()); - } - - if (typeStack.isEmpty() && typeDeclaration != null) { - String methodName = sourceMethod.getElementName(); - for (Object declaration : typeDeclaration.bodyDeclarations()) { - if (declaration instanceof org.eclipse.jdt.core.dom.MethodDeclaration) { - org.eclipse.jdt.core.dom.MethodDeclaration methodDeclaration = (org.eclipse.jdt.core.dom.MethodDeclaration) declaration; - if (methodDeclaration.getName().toString().equals(methodName)) { - return methodDeclaration; - } - } - } - } - } - return methodDeclarationNode; - } - - // part of getRealMethodDeclarationNode - public static org.eclipse.jdt.core.dom.AbstractTypeDeclaration findTypeDeclaration(IType searchType, List<?> nodes) { - for (Object object : nodes) { - if (object instanceof org.eclipse.jdt.core.dom.AbstractTypeDeclaration) { - org.eclipse.jdt.core.dom.AbstractTypeDeclaration typeDeclaration = (org.eclipse.jdt.core.dom.AbstractTypeDeclaration) object; - if (typeDeclaration.getName().toString().equals(searchType.getElementName())) - return typeDeclaration; - } - } - return null; - } - - public static int getSourceEndFixed(int sourceEnd, org.eclipse.jdt.internal.compiler.ast.ASTNode node) throws Exception { - if (sourceEnd == -1) { - org.eclipse.jdt.internal.compiler.ast.ASTNode object = (org.eclipse.jdt.internal.compiler.ast.ASTNode)node.getClass().getField("$generatedBy").get(node); - if (object != null) { - return object.sourceEnd; - } - } - return sourceEnd; - } - - public static int fixRetrieveStartingCatchPosition(int original, int start) { - return original == -1 ? start : original; - } - - public static int fixRetrieveIdentifierEndPosition(int original, int end) { - return original == -1 ? end : original; - } - - public static int fixRetrieveEllipsisStartPosition(int original, int end) { - return original == -1 ? end : original; - } - - public static int fixRetrieveRightBraceOrSemiColonPosition(int original, int end) { -// return original; - return original == -1 ? end : original; // Need to fix: see issue 325. - } - - public static final int ALREADY_PROCESSED_FLAG = 0x800000; //Bit 24 - - public static boolean checkBit24(Object node) throws Exception { - int bits = (Integer)(node.getClass().getField("bits").get(node)); - return (bits & ALREADY_PROCESSED_FLAG) != 0; - } - - public static boolean skipRewritingGeneratedNodes(org.eclipse.jdt.core.dom.ASTNode node) throws Exception { - return ((Boolean)node.getClass().getField("$isGenerated").get(node)).booleanValue(); - } - - public static void setIsGeneratedFlag(org.eclipse.jdt.core.dom.ASTNode domNode, - org.eclipse.jdt.internal.compiler.ast.ASTNode internalNode) throws Exception { - if (internalNode == null || domNode == null) return; - boolean isGenerated = EclipseAugments.ASTNode_generatedBy.get(internalNode) != null; - if (isGenerated) domNode.getClass().getField("$isGenerated").set(domNode, true); - } - - public static void setIsGeneratedFlagForName(org.eclipse.jdt.core.dom.Name name, Object internalNode) throws Exception { - if (internalNode instanceof org.eclipse.jdt.internal.compiler.ast.ASTNode) { - if (EclipseAugments.ASTNode_generatedBy.get((org.eclipse.jdt.internal.compiler.ast.ASTNode) internalNode) != null) { - name.getClass().getField("$isGenerated").set(name, true); - } - } - } - - public static RewriteEvent[] listRewriteHandleGeneratedMethods(RewriteEvent parent) { - RewriteEvent[] children = parent.getChildren(); - List<RewriteEvent> newChildren = new ArrayList<RewriteEvent>(); - List<RewriteEvent> modifiedChildren = new ArrayList<RewriteEvent>(); - for (int i=0; i<children.length; i++) { - RewriteEvent child = children[i]; - boolean isGenerated = isGenerated( (org.eclipse.jdt.core.dom.ASTNode)child.getOriginalValue() ); - if (isGenerated) { - if ((child.getChangeKind() == RewriteEvent.REPLACED || child.getChangeKind() == RewriteEvent.REMOVED) - && child.getOriginalValue() instanceof org.eclipse.jdt.core.dom.MethodDeclaration - && child.getNewValue() != null - ) { - modifiedChildren.add(new NodeRewriteEvent(null, child.getNewValue())); - } - } else { - newChildren.add(child); - } - } - // Since Eclipse doesn't honor the "insert at specified location" for already existing members, - // we'll just add them last - newChildren.addAll(modifiedChildren); - return newChildren.toArray(new RewriteEvent[newChildren.size()]); - } - - public static int getTokenEndOffsetFixed(TokenScanner scanner, int token, int startOffset, Object domNode) throws CoreException { - boolean isGenerated = false; - try { - isGenerated = (Boolean) domNode.getClass().getField("$isGenerated").get(domNode); - } catch (Exception e) { - // If this fails, better to break some refactor scripts than to crash eclipse. - } - if (isGenerated) return -1; - return scanner.getTokenEndOffset(token, startOffset); - } - - public static IMethod[] removeGeneratedMethods(IMethod[] methods) throws Exception { - List<IMethod> result = new ArrayList<IMethod>(); - for (IMethod m : methods) { - if (m.getNameRange().getLength() > 0 && !m.getNameRange().equals(m.getSourceRange())) result.add(m); - } - return result.size() == methods.length ? methods : result.toArray(new IMethod[result.size()]); - } - - public static SimpleName[] removeGeneratedSimpleNames(SimpleName[] in) throws Exception { - Field f = SimpleName.class.getField("$isGenerated"); - - int count = 0; - for (int i = 0; i < in.length; i++) { - if (in[i] == null || !((Boolean)f.get(in[i])).booleanValue()) count++; - } - if (count == in.length) return in; - SimpleName[] newSimpleNames = new SimpleName[count]; - count = 0; - for (int i = 0; i < in.length; i++) { - if (in[i] == null || !((Boolean)f.get(in[i])).booleanValue()) newSimpleNames[count++] = in[i]; - } - return newSimpleNames; - } - - public static byte[] runPostCompiler(byte[] bytes, String fileName) { - byte[] transformed = PostCompiler.applyTransformations(bytes, fileName, DiagnosticsReceiver.CONSOLE); - return transformed == null ? bytes : transformed; - } - - public static OutputStream runPostCompiler(OutputStream out) throws IOException { - return PostCompiler.wrapOutputStream(out, "TEST", DiagnosticsReceiver.CONSOLE); - } - - public static BufferedOutputStream runPostCompiler(BufferedOutputStream out, String path, String name) throws IOException { - String fileName = path + "/" + name; - return new BufferedOutputStream(PostCompiler.wrapOutputStream(out, fileName, DiagnosticsReceiver.CONSOLE)); - } - - public static Annotation[] convertAnnotations(Annotation[] out, IAnnotatable annotatable) { - IAnnotation[] in; - - try { - in = annotatable.getAnnotations(); - } catch (Exception e) { - return out; - } - - if (out == null) return null; - int toWrite = 0; - - for (int idx = 0; idx < out.length; idx++) { - String oName = new String(out[idx].type.getLastToken()); - boolean found = false; - for (IAnnotation i : in) { - String name = i.getElementName(); - int li = name.lastIndexOf('.'); - if (li > -1) name = name.substring(li + 1); - if (name.equals(oName)) { - found = true; - break; - } - } - if (!found) out[idx] = null; - else toWrite++; - } - - Annotation[] replace = out; - if (toWrite < out.length) { - replace = new Annotation[toWrite]; - int idx = 0; - for (int i = 0; i < out.length; i++) { - if (out[i] == null) continue; - replace[idx++] = out[i]; - } - } - - return replace; - } -} diff --git a/src/eclipseAgent/lombok/eclipse/agent/PatchFixesShadowLoaded.java b/src/eclipseAgent/lombok/eclipse/agent/PatchFixesShadowLoaded.java new file mode 100644 index 00000000..52f63765 --- /dev/null +++ b/src/eclipseAgent/lombok/eclipse/agent/PatchFixesShadowLoaded.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2015 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.agent; + +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +import lombok.core.DiagnosticsReceiver; +import lombok.core.PostCompiler; +import lombok.core.Version; + +public class PatchFixesShadowLoaded { + public static String addLombokNotesToEclipseAboutDialog(String origReturnValue, String key) { + if ("aboutText".equals(key)) { + return origReturnValue + "\n\nLombok " + Version.getFullVersion() + " is installed. https://projectlombok.org/"; + } + return origReturnValue; + } + + public static byte[] runPostCompiler(byte[] bytes, String fileName) { + byte[] transformed = PostCompiler.applyTransformations(bytes, fileName, DiagnosticsReceiver.CONSOLE); + return transformed == null ? bytes : transformed; + } + + public static OutputStream runPostCompiler(OutputStream out) throws IOException { + return PostCompiler.wrapOutputStream(out, "TEST", DiagnosticsReceiver.CONSOLE); + } + + public static BufferedOutputStream runPostCompiler(BufferedOutputStream out, String path, String name) throws IOException { + String fileName = path + "/" + name; + return new BufferedOutputStream(PostCompiler.wrapOutputStream(out, fileName, DiagnosticsReceiver.CONSOLE)); + } +} diff --git a/src/eclipseAgent/lombok/eclipse/agent/PatchVal.java b/src/eclipseAgent/lombok/eclipse/agent/PatchVal.java index e734dceb..3da37869 100644 --- a/src/eclipseAgent/lombok/eclipse/agent/PatchVal.java +++ b/src/eclipseAgent/lombok/eclipse/agent/PatchVal.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 @@ -21,11 +21,6 @@ */ package lombok.eclipse.agent; -import static lombok.eclipse.handlers.EclipseHandlerUtil.*; -import static lombok.eclipse.Eclipse.*; - -import java.lang.reflect.Field; - import org.eclipse.jdt.internal.compiler.ast.Annotation; import org.eclipse.jdt.internal.compiler.ast.Expression; import org.eclipse.jdt.internal.compiler.ast.ForeachStatement; @@ -43,6 +38,11 @@ import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; import org.eclipse.jdt.internal.compiler.lookup.TypeIds; +import java.lang.reflect.Field; + +import static lombok.eclipse.Eclipse.poss; +import static lombok.eclipse.handlers.EclipseHandlerUtil.makeType; + public class PatchVal { // This is half of the work for 'val' support - the other half is in PatchValEclipse. This half is enough for ecj. @@ -57,6 +57,9 @@ public class PatchVal { return expr.resolveType(scope); } catch (NullPointerException e) { return null; + } catch (ArrayIndexOutOfBoundsException e) { + // This will occur internally due to for example 'val x = mth("X");', where mth takes 2 arguments. + return null; } } @@ -66,6 +69,9 @@ public class PatchVal { return expr.resolveType(scope); } catch (NullPointerException e) { return null; + } catch (ArrayIndexOutOfBoundsException e) { + // This will occur internally due to for example 'val x = mth("X");', where mth takes 2 arguments. + return null; } } @@ -78,31 +84,44 @@ public class PatchVal { return true; } - public static boolean couldBeVal(TypeReference ref) { + public static boolean couldBe(String key, TypeReference ref) { + String[] keyParts = key.split("\\."); if (ref instanceof SingleTypeReference) { char[] token = ((SingleTypeReference)ref).token; - return matches("val", token); + return matches(keyParts[keyParts.length - 1], token); } if (ref instanceof QualifiedTypeReference) { char[][] tokens = ((QualifiedTypeReference)ref).tokens; - if (tokens == null || tokens.length != 2) return false; - return matches("lombok", tokens[0]) && matches("val", tokens[1]); + if (keyParts.length != tokens.length) return false; + for(int i = 0; i < tokens.length; ++i) { + String part = keyParts[i]; + char[] token = tokens[i]; + if (!matches(part, token)) return false; + } + return true; } return false; } - - private static boolean isVal(TypeReference ref, BlockScope scope) { - if (!couldBeVal(ref)) return false; - + + private static boolean is(TypeReference ref, BlockScope scope, String key) { + if (!couldBe(key, ref)) return false; + TypeBinding resolvedType = ref.resolvedType; if (resolvedType == null) resolvedType = ref.resolveType(scope, false); if (resolvedType == null) return false; char[] pkg = resolvedType.qualifiedPackageName(); char[] nm = resolvedType.qualifiedSourceName(); - return matches("lombok", pkg) && matches("val", nm); + int pkgFullLength = pkg.length > 0 ? pkg.length + 1: 0; + char[] fullName = new char[pkgFullLength + nm.length]; + if(pkg.length > 0) { + System.arraycopy(pkg, 0, fullName, 0, pkg.length); + fullName[pkg.length] = '.'; + } + System.arraycopy(nm, 0, fullName, pkgFullLength, nm.length); + return matches(key, fullName); } public static final class Reflection { @@ -126,9 +145,20 @@ public class PatchVal { if (local == null || !LocalDeclaration.class.equals(local.getClass())) return false; boolean decomponent = false; - if (!isVal(local.type, scope)) return false; + boolean val = isVal(local, scope); + boolean var = isVar(local, scope); + if (!(val || var)) return false; - if (new Throwable().getStackTrace()[2].getClassName().contains("ForStatement")) return false; + StackTraceElement[] st = new Throwable().getStackTrace(); + for (int i = 0; i < st.length - 2 && i < 10; i++) { + if (st[i].getClassName().equals("lombok.launch.PatchFixesHider$Val")) { + boolean valInForStatement = val && + st[i + 1].getClassName().equals("org.eclipse.jdt.internal.compiler.ast.LocalDeclaration") && + st[i + 2].getClassName().equals("org.eclipse.jdt.internal.compiler.ast.ForStatement"); + if (valInForStatement) return false; + break; + } + } Expression init = local.initialization; if (init == null && Reflection.initCopyField != null) { @@ -157,7 +187,7 @@ public class PatchVal { TypeBinding resolved = null; try { - resolved = decomponent ? getForEachComponentType(init, scope) : init.resolveType(scope); + resolved = decomponent ? getForEachComponentType(init, scope) : resolveForExpression(init, scope); } catch (NullPointerException e) { // This definitely occurs if as part of resolving the initializer expression, a // lambda expression in it must also be resolved (such as when lambdas are part of @@ -174,23 +204,33 @@ public class PatchVal { } } - local.modifiers |= ClassFileConstants.AccFinal; + if(val) local.modifiers |= ClassFileConstants.AccFinal; local.annotations = addValAnnotation(local.annotations, local.type, scope); local.type = replacement != null ? replacement : new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, poss(local.type, 3)); return false; } + private static boolean isVar(LocalDeclaration local, BlockScope scope) { + return is(local.type, scope, "lombok.experimental.var") || is(local.type, scope, "lombok.var"); + } + + private static boolean isVal(LocalDeclaration local, BlockScope scope) { + return is(local.type, scope, "lombok.val"); + } + public static boolean handleValForForEach(ForeachStatement forEach, BlockScope scope) { if (forEach.elementVariable == null) return false; - if (!isVal(forEach.elementVariable.type, scope)) return false; + boolean val = isVal(forEach.elementVariable, scope); + boolean var = isVar(forEach.elementVariable, scope); + if (!(val || var)) return false; TypeBinding component = getForEachComponentType(forEach.collection, scope); if (component == null) return false; TypeReference replacement = makeType(component, forEach.elementVariable.type, false); - forEach.elementVariable.modifiers |= ClassFileConstants.AccFinal; + if (val) forEach.elementVariable.modifiers |= ClassFileConstants.AccFinal; forEach.elementVariable.annotations = addValAnnotation(forEach.elementVariable.annotations, forEach.elementVariable.type, scope); forEach.elementVariable.type = replacement != null ? replacement : new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, poss(forEach.elementVariable.type, 3)); @@ -215,7 +255,7 @@ public class PatchVal { private static TypeBinding getForEachComponentType(Expression collection, BlockScope scope) { if (collection != null) { TypeBinding resolved = collection.resolvedType; - if (resolved == null) resolved = collection.resolveType(scope); + if (resolved == null) resolved = resolveForExpression(collection, scope); if (resolved == null) return null; if (resolved.isArrayType()) { resolved = ((ArrayBinding) resolved).elementsType(); @@ -243,4 +283,13 @@ public class PatchVal { return null; } + + private static TypeBinding resolveForExpression(Expression collection, BlockScope scope) { + try { + return collection.resolveType(scope); + } catch (ArrayIndexOutOfBoundsException e) { + // Known cause of issues; for example: val e = mth("X"), where mth takes 2 arguments. + return null; + } + } } diff --git a/src/eclipseAgent/lombok/eclipse/agent/PatchValEclipse.java b/src/eclipseAgent/lombok/eclipse/agent/PatchValEclipse.java index 7d5f36f4..46237dcf 100644 --- a/src/eclipseAgent/lombok/eclipse/agent/PatchValEclipse.java +++ b/src/eclipseAgent/lombok/eclipse/agent/PatchValEclipse.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 @@ -47,6 +47,7 @@ import org.eclipse.jdt.internal.compiler.ast.Annotation; import org.eclipse.jdt.internal.compiler.ast.ForeachStatement; import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration; import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference; +import org.eclipse.jdt.internal.compiler.ast.TypeReference; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; import org.eclipse.jdt.internal.compiler.parser.Parser; @@ -65,7 +66,9 @@ public class PatchValEclipse { ForeachStatement foreachDecl = (ForeachStatement) astStack[astPtr]; ASTNode init = foreachDecl.collection; if (init == null) return; - if (foreachDecl.elementVariable == null || !PatchVal.couldBeVal(foreachDecl.elementVariable.type)) return; + boolean val = couldBeVal(foreachDecl.elementVariable.type); + boolean var = couldBeVar(foreachDecl.elementVariable.type); + if (foreachDecl.elementVariable == null || !(val || var)) return; try { if (Reflection.iterableCopyField != null) Reflection.iterableCopyField.set(foreachDecl.elementVariable, init); @@ -88,7 +91,9 @@ public class PatchValEclipse { if (!(variableDecl instanceof LocalDeclaration)) return; ASTNode init = variableDecl.initialization; if (init == null) return; - if (!PatchVal.couldBeVal(variableDecl.type)) return; + boolean val = couldBeVal(variableDecl.type); + boolean var = couldBeVar(variableDecl.type); + if (!(val || var)) return; try { if (Reflection.initCopyField != null) Reflection.initCopyField.set(variableDecl, init); @@ -97,6 +102,10 @@ public class PatchValEclipse { } } + private static boolean couldBeVar(TypeReference type) { + return PatchVal.couldBe("lombok.experimental.var", type) || PatchVal.couldBe("lombok.var", type); + } + public static void addFinalAndValAnnotationToSingleVariableDeclaration(Object converter, SingleVariableDeclaration out, LocalDeclaration in) { @SuppressWarnings("unchecked") List<IExtendedModifier> modifiers = out.modifiers(); addFinalAndValAnnotationToModifierList(converter, modifiers, out.getAST(), in); @@ -115,7 +124,7 @@ public class PatchValEclipse { Annotation valAnnotation = null; for (Annotation ann : in.annotations) { - if (PatchVal.couldBeVal(ann.type)) { + if (couldBeVal(ann.type)) { found = true; valAnnotation = ann; break; @@ -167,6 +176,10 @@ public class PatchValEclipse { } } + private static boolean couldBeVal(TypeReference type) { + return PatchVal.couldBe("lombok.val", type); + } + public static Modifier createModifier(AST ast, ModifierKeyword keyword, int start, int end) { Modifier modifier = null; try { diff --git a/src/eclipseAgent/lombok/launch/PatchFixesHider.java b/src/eclipseAgent/lombok/launch/PatchFixesHider.java new file mode 100644 index 00000000..b1d73352 --- /dev/null +++ b/src/eclipseAgent/lombok/launch/PatchFixesHider.java @@ -0,0 +1,671 @@ +/* + * Copyright (C) 2010-2015 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.launch; + +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.Stack; + +import lombok.eclipse.EclipseAugments; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.jdt.core.IAnnotatable; +import org.eclipse.jdt.core.IAnnotation; +import org.eclipse.jdt.core.IField; +import org.eclipse.jdt.core.IMethod; +import org.eclipse.jdt.core.IType; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.core.dom.MethodDeclaration; +import org.eclipse.jdt.core.dom.SimpleName; +import org.eclipse.jdt.core.search.SearchMatch; +import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.Annotation; +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.ForeachStatement; +import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration; +import org.eclipse.jdt.internal.compiler.ast.MessageSend; +import org.eclipse.jdt.internal.compiler.lookup.BlockScope; +import org.eclipse.jdt.internal.compiler.lookup.MethodBinding; +import org.eclipse.jdt.internal.compiler.lookup.Scope; +import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; +import org.eclipse.jdt.internal.compiler.parser.Parser; +import org.eclipse.jdt.internal.compiler.problem.ProblemReporter; +import org.eclipse.jdt.internal.core.SourceField; +import org.eclipse.jdt.internal.core.dom.rewrite.NodeRewriteEvent; +import org.eclipse.jdt.internal.core.dom.rewrite.RewriteEvent; +import org.eclipse.jdt.internal.core.dom.rewrite.TokenScanner; +import org.eclipse.jdt.internal.corext.refactoring.SearchResultGroup; +import org.eclipse.jdt.internal.corext.refactoring.structure.ASTNodeSearchUtil; + +/** These contain a mix of the following: + * <ul> + * <li> 'dependency free' method wrappers that cross the shadowloader barrier. + * <li> methods that directly patch, <em>but</em>, these should ALWAYS be transplanted. + * </ul> + * + * <strong>This class lives on the outside of the shadowloader barrier, and as a consequence, cannot access any other lombok code except other + * code in the {@code lombok.launch} package!</strong>. + * <p> + * This class is package private with lots of public inner static classes. This hides all of them from IDE autocomplete dialogs and such but at the JVM + * level the inner static class are just plain public, which is important, because calls to the contents of these inner static classes are injected into + * various eclipse classes verbatim, and if they weren't public, the verifier wouldn't accept it. + */ +final class PatchFixesHider { + + /** These utility methods are only used 'internally', but because of transplant methods, the class (and its methods) still have to be public! */ + public static final class Util { + private static ClassLoader shadowLoader; + + public static Class<?> shadowLoadClass(String name) { + try { + if (shadowLoader == null) { + try { + Class.forName("lombok.core.LombokNode"); + // If we get here, then lombok is already available. + shadowLoader = Util.class.getClassLoader(); + } catch (ClassNotFoundException e) { + // If we get here, it isn't, and we should use the shadowloader. + shadowLoader = Main.createShadowClassLoader(); + } + } + + return Class.forName(name, true, shadowLoader); + } catch (ClassNotFoundException e) { + throw sneakyThrow(e); + } + } + + public static Method findMethod(Class<?> type, String name, Class<?>... parameterTypes) { + try { + return type.getDeclaredMethod(name, parameterTypes); + } catch (NoSuchMethodException e) { + throw sneakyThrow(e); + } + } + + public static Object invokeMethod(Method method, Object... args) { + try { + return method.invoke(null, args); + } catch (IllegalAccessException e) { + throw sneakyThrow(e); + } catch (InvocationTargetException e) { + throw sneakyThrow(e.getCause()); + } + } + + private static RuntimeException sneakyThrow(Throwable t) { + if (t == null) throw new NullPointerException("t"); + Util.<RuntimeException>sneakyThrow0(t); + return null; + } + + @SuppressWarnings("unchecked") + private static <T extends Throwable> void sneakyThrow0(Throwable t) throws T { + throw (T)t; + } + } + + /** Contains patch fixes that are dependent on lombok internals. */ + public static final class LombokDeps { + public static final Method ADD_LOMBOK_NOTES; + public static final Method POST_COMPILER_BYTES_STRING; + public static final Method POST_COMPILER_OUTPUTSTREAM; + public static final Method POST_COMPILER_BUFFEREDOUTPUTSTREAM_STRING_STRING; + + static { + Class<?> shadowed = Util.shadowLoadClass("lombok.eclipse.agent.PatchFixesShadowLoaded"); + ADD_LOMBOK_NOTES = Util.findMethod(shadowed, "addLombokNotesToEclipseAboutDialog", String.class, String.class); + POST_COMPILER_BYTES_STRING = Util.findMethod(shadowed, "runPostCompiler", byte[].class, String.class); + POST_COMPILER_OUTPUTSTREAM = Util.findMethod(shadowed, "runPostCompiler", OutputStream.class); + POST_COMPILER_BUFFEREDOUTPUTSTREAM_STRING_STRING = Util.findMethod(shadowed, "runPostCompiler", BufferedOutputStream.class, String.class, String.class); + } + + public static String addLombokNotesToEclipseAboutDialog(String origReturnValue, String key) { + return (String) Util.invokeMethod(LombokDeps.ADD_LOMBOK_NOTES, origReturnValue, key); + } + + public static byte[] runPostCompiler(byte[] bytes, String fileName) { + return (byte[]) Util.invokeMethod(LombokDeps.POST_COMPILER_BYTES_STRING, bytes, fileName); + } + + public static OutputStream runPostCompiler(OutputStream out) throws IOException { + return (OutputStream) Util.invokeMethod(LombokDeps.POST_COMPILER_OUTPUTSTREAM, out); + } + + public static BufferedOutputStream runPostCompiler(BufferedOutputStream out, String path, String name) throws IOException { + return (BufferedOutputStream) Util.invokeMethod(LombokDeps.POST_COMPILER_BUFFEREDOUTPUTSTREAM_STRING_STRING, out, path, name); + } + } + + public static final class Transform { + private static final Method TRANSFORM; + private static final Method TRANSFORM_SWAPPED; + + static { + Class<?> shadowed = Util.shadowLoadClass("lombok.eclipse.TransformEclipseAST"); + TRANSFORM = Util.findMethod(shadowed, "transform", Parser.class, CompilationUnitDeclaration.class); + TRANSFORM_SWAPPED = Util.findMethod(shadowed, "transform_swapped", CompilationUnitDeclaration.class, Parser.class); + } + + public static void transform(Parser parser, CompilationUnitDeclaration ast) throws IOException { + Util.invokeMethod(TRANSFORM, parser, ast); + } + + public static void transform_swapped(CompilationUnitDeclaration ast, Parser parser) throws IOException { + Util.invokeMethod(TRANSFORM_SWAPPED, ast, parser); + } + } + + /** Contains patch code to support {@code @Delegate} */ + public static final class Delegate { + private static final Method HANDLE_DELEGATE_FOR_TYPE; + + static { + Class<?> shadowed = Util.shadowLoadClass("lombok.eclipse.agent.PatchDelegatePortal"); + HANDLE_DELEGATE_FOR_TYPE = Util.findMethod(shadowed, "handleDelegateForType", Object.class); + } + + public static boolean handleDelegateForType(Object classScope) { + return (Boolean) Util.invokeMethod(HANDLE_DELEGATE_FOR_TYPE, classScope); + } + } + + /** Contains patch code to support {@code val} (eclipse specific) */ + public static final class ValPortal { + private static final Method COPY_INITIALIZATION_OF_FOR_EACH_ITERABLE; + private static final Method COPY_INITIALIZATION_OF_LOCAL_DECLARATION; + private static final Method ADD_FINAL_AND_VAL_ANNOTATION_TO_VARIABLE_DECLARATION_STATEMENT; + private static final Method ADD_FINAL_AND_VAL_ANNOTATION_TO_SINGLE_VARIABLE_DECLARATION; + + static { + Class<?> shadowed = Util.shadowLoadClass("lombok.eclipse.agent.PatchValEclipsePortal"); + COPY_INITIALIZATION_OF_FOR_EACH_ITERABLE = Util.findMethod(shadowed, "copyInitializationOfForEachIterable", Object.class); + COPY_INITIALIZATION_OF_LOCAL_DECLARATION = Util.findMethod(shadowed, "copyInitializationOfLocalDeclaration", Object.class); + ADD_FINAL_AND_VAL_ANNOTATION_TO_VARIABLE_DECLARATION_STATEMENT = Util.findMethod(shadowed, "addFinalAndValAnnotationToVariableDeclarationStatement", Object.class, Object.class, Object.class); + ADD_FINAL_AND_VAL_ANNOTATION_TO_SINGLE_VARIABLE_DECLARATION = Util.findMethod(shadowed, "addFinalAndValAnnotationToSingleVariableDeclaration", Object.class, Object.class, Object.class); + } + + public static void copyInitializationOfForEachIterable(Object parser) { + Util.invokeMethod(COPY_INITIALIZATION_OF_FOR_EACH_ITERABLE, parser); + } + + public static void copyInitializationOfLocalDeclaration(Object parser) { + Util.invokeMethod(COPY_INITIALIZATION_OF_LOCAL_DECLARATION, parser); + } + + public static void addFinalAndValAnnotationToVariableDeclarationStatement(Object converter, Object out, Object in) { + Util.invokeMethod(ADD_FINAL_AND_VAL_ANNOTATION_TO_VARIABLE_DECLARATION_STATEMENT, converter, out, in); + } + + public static void addFinalAndValAnnotationToSingleVariableDeclaration(Object converter, Object out, Object in) { + Util.invokeMethod(ADD_FINAL_AND_VAL_ANNOTATION_TO_SINGLE_VARIABLE_DECLARATION, converter, out, in); + } + } + + /** Contains patch code to support {@code val} (eclipse and ecj) */ + public static final class Val { + private static final Method SKIP_RESOLVE_INITIALIZER_IF_ALREADY_CALLED; + private static final Method SKIP_RESOLVE_INITIALIZER_IF_ALREADY_CALLED2; + private static final Method HANDLE_VAL_FOR_LOCAL_DECLARATION; + private static final Method HANDLE_VAL_FOR_FOR_EACH; + + static { + Class<?> shadowed = Util.shadowLoadClass("lombok.eclipse.agent.PatchVal"); + SKIP_RESOLVE_INITIALIZER_IF_ALREADY_CALLED = Util.findMethod(shadowed, "skipResolveInitializerIfAlreadyCalled", Expression.class, BlockScope.class); + SKIP_RESOLVE_INITIALIZER_IF_ALREADY_CALLED2 = Util.findMethod(shadowed, "skipResolveInitializerIfAlreadyCalled2", Expression.class, BlockScope.class, LocalDeclaration.class); + HANDLE_VAL_FOR_LOCAL_DECLARATION = Util.findMethod(shadowed, "handleValForLocalDeclaration", LocalDeclaration.class, BlockScope.class); + HANDLE_VAL_FOR_FOR_EACH = Util.findMethod(shadowed, "handleValForForEach", ForeachStatement.class, BlockScope.class); + } + + public static TypeBinding skipResolveInitializerIfAlreadyCalled(Expression expr, BlockScope scope) { + return (TypeBinding) Util.invokeMethod(SKIP_RESOLVE_INITIALIZER_IF_ALREADY_CALLED, expr, scope); + } + + public static TypeBinding skipResolveInitializerIfAlreadyCalled2(Expression expr, BlockScope scope, LocalDeclaration decl) { + return (TypeBinding) Util.invokeMethod(SKIP_RESOLVE_INITIALIZER_IF_ALREADY_CALLED2, expr, scope, decl); + } + + public static boolean handleValForLocalDeclaration(LocalDeclaration local, BlockScope scope) { + return (Boolean) Util.invokeMethod(HANDLE_VAL_FOR_LOCAL_DECLARATION, local, scope); + } + + public static boolean handleValForForEach(ForeachStatement forEach, BlockScope scope) { + return (Boolean) Util.invokeMethod(HANDLE_VAL_FOR_FOR_EACH, forEach, scope); + } + } + + /** Contains patch code to support {@code @ExtensionMethod} */ + public static final class ExtensionMethod { + private static final Method RESOLVE_TYPE; + private static final Method ERROR_NO_METHOD_FOR; + private static final Method INVALID_METHOD, INVALID_METHOD2; + + static { + Class<?> shadowed = Util.shadowLoadClass("lombok.eclipse.agent.PatchExtensionMethod"); + RESOLVE_TYPE = Util.findMethod(shadowed, "resolveType", TypeBinding.class, MessageSend.class, BlockScope.class); + ERROR_NO_METHOD_FOR = Util.findMethod(shadowed, "errorNoMethodFor", ProblemReporter.class, MessageSend.class, TypeBinding.class, TypeBinding[].class); + INVALID_METHOD = Util.findMethod(shadowed, "invalidMethod", ProblemReporter.class, MessageSend.class, MethodBinding.class); + INVALID_METHOD2 = Util.findMethod(shadowed, "invalidMethod", ProblemReporter.class, MessageSend.class, MethodBinding.class, Scope.class); + } + + public static TypeBinding resolveType(TypeBinding resolvedType, MessageSend methodCall, BlockScope scope) { + return (TypeBinding) Util.invokeMethod(RESOLVE_TYPE, resolvedType, methodCall, scope); + } + + public static void errorNoMethodFor(ProblemReporter problemReporter, MessageSend messageSend, TypeBinding recType, TypeBinding[] params) { + Util.invokeMethod(ERROR_NO_METHOD_FOR, problemReporter, messageSend, recType, params); + } + + public static void invalidMethod(ProblemReporter problemReporter, MessageSend messageSend, MethodBinding method) { + Util.invokeMethod(INVALID_METHOD, problemReporter, messageSend, method); + } + + public static void invalidMethod(ProblemReporter problemReporter, MessageSend messageSend, MethodBinding method, Scope scope) { + Util.invokeMethod(INVALID_METHOD2, problemReporter, messageSend, method, scope); + } + } + + /** + * Contains a mix of methods: ecj only, ecj+eclipse, and eclipse only. As a consequence, _EVERY_ method from here used for ecj MUST be + * transplanted, as ecj itself cannot load this class (signatures refer to things that don't exist in ecj-only mode). + * <p> + * Because of usage of transplant(), a bunch of these contain direct code and don't try to cross the shadowloader barrier. + */ + public static final class PatchFixes { + public static boolean isGenerated(org.eclipse.jdt.core.dom.ASTNode node) { + boolean result = false; + try { + result = ((Boolean)node.getClass().getField("$isGenerated").get(node)).booleanValue(); + if (!result && node.getParent() != null && node.getParent() instanceof org.eclipse.jdt.core.dom.QualifiedName) { + result = isGenerated(node.getParent()); + } + } catch (Exception e) { + // better to assume it isn't generated + } + return result; + } + + public static boolean isListRewriteOnGeneratedNode(org.eclipse.jdt.core.dom.rewrite.ListRewrite rewrite) { + return isGenerated(rewrite.getParent()); + } + + public static boolean returnFalse(java.lang.Object object) { + return false; + } + + public static boolean returnTrue(java.lang.Object object) { + return true; + } + + @java.lang.SuppressWarnings({"unchecked", "rawtypes"}) public static java.util.List removeGeneratedNodes(java.util.List list) { + try { + java.util.List realNodes = new java.util.ArrayList(list.size()); + for (java.lang.Object node : list) { + if(!isGenerated(((org.eclipse.jdt.core.dom.ASTNode)node))) { + realNodes.add(node); + } + } + return realNodes; + } catch (Exception e) { + } + return list; + } + + public static java.lang.String getRealMethodDeclarationSource(java.lang.String original, Object processor, org.eclipse.jdt.core.dom.MethodDeclaration declaration) throws Exception { + if (!isGenerated(declaration)) return original; + + List<org.eclipse.jdt.core.dom.Annotation> annotations = new ArrayList<org.eclipse.jdt.core.dom.Annotation>(); + for (Object modifier : declaration.modifiers()) { + if (modifier instanceof org.eclipse.jdt.core.dom.Annotation) { + org.eclipse.jdt.core.dom.Annotation annotation = (org.eclipse.jdt.core.dom.Annotation)modifier; + String qualifiedAnnotationName = annotation.resolveTypeBinding().getQualifiedName(); + if (!"java.lang.Override".equals(qualifiedAnnotationName) && !"java.lang.SuppressWarnings".equals(qualifiedAnnotationName)) annotations.add(annotation); + } + } + + StringBuilder signature = new StringBuilder(); + addAnnotations(annotations, signature); + + if ((Boolean)processor.getClass().getDeclaredField("fPublic").get(processor)) signature.append("public "); + if ((Boolean)processor.getClass().getDeclaredField("fAbstract").get(processor)) signature.append("abstract "); + + signature + .append(declaration.getReturnType2().toString()) + .append(" ").append(declaration.getName().getFullyQualifiedName()) + .append("("); + + boolean first = true; + for (Object parameter : declaration.parameters()) { + if (!first) signature.append(", "); + first = false; + // We should also add the annotations of the parameters + signature.append(parameter); + } + + signature.append(");"); + return signature.toString(); + } + + // part of getRealMethodDeclarationSource(...) + public static void addAnnotations(List<org.eclipse.jdt.core.dom.Annotation> annotations, StringBuilder signature) { + /* + * We SHOULD be able to handle the following cases: + * @Override + * @Override() + * @SuppressWarnings("all") + * @SuppressWarnings({"all", "unused"}) + * @SuppressWarnings(value = "all") + * @SuppressWarnings(value = {"all", "unused"}) + * @EqualsAndHashCode(callSuper=true, of="id") + * + * Currently, we only seem to correctly support: + * @Override + * @Override() N.B. We lose the parentheses here, since there are no values. No big deal. + * @SuppressWarnings("all") + */ + for (org.eclipse.jdt.core.dom.Annotation annotation : annotations) { + List<String> values = new ArrayList<String>(); + if (annotation.isSingleMemberAnnotation()) { + org.eclipse.jdt.core.dom.SingleMemberAnnotation smAnn = (org.eclipse.jdt.core.dom.SingleMemberAnnotation) annotation; + values.add(smAnn.getValue().toString()); + } else if (annotation.isNormalAnnotation()) { + org.eclipse.jdt.core.dom.NormalAnnotation normalAnn = (org.eclipse.jdt.core.dom.NormalAnnotation) annotation; + for (Object value : normalAnn.values()) values.add(value.toString()); + } + + signature.append("@").append(annotation.resolveTypeBinding().getQualifiedName()); + if (!values.isEmpty()) { + signature.append("("); + boolean first = true; + for (String string : values) { + if (!first) signature.append(", "); + first = false; + signature.append('"').append(string).append('"'); + } + signature.append(")"); + } + signature.append(" "); + } + } + + public static org.eclipse.jdt.core.dom.MethodDeclaration getRealMethodDeclarationNode(org.eclipse.jdt.core.IMethod sourceMethod, org.eclipse.jdt.core.dom.CompilationUnit cuUnit) throws JavaModelException { + MethodDeclaration methodDeclarationNode = ASTNodeSearchUtil.getMethodDeclarationNode(sourceMethod, cuUnit); + if (isGenerated(methodDeclarationNode)) { + IType declaringType = sourceMethod.getDeclaringType(); + Stack<IType> typeStack = new Stack<IType>(); + while (declaringType != null) { + typeStack.push(declaringType); + declaringType = declaringType.getDeclaringType(); + } + + IType rootType = typeStack.pop(); + org.eclipse.jdt.core.dom.AbstractTypeDeclaration typeDeclaration = findTypeDeclaration(rootType, cuUnit.types()); + while (!typeStack.isEmpty() && typeDeclaration != null) { + typeDeclaration = findTypeDeclaration(typeStack.pop(), typeDeclaration.bodyDeclarations()); + } + + if (typeStack.isEmpty() && typeDeclaration != null) { + String methodName = sourceMethod.getElementName(); + for (Object declaration : typeDeclaration.bodyDeclarations()) { + if (declaration instanceof org.eclipse.jdt.core.dom.MethodDeclaration) { + org.eclipse.jdt.core.dom.MethodDeclaration methodDeclaration = (org.eclipse.jdt.core.dom.MethodDeclaration) declaration; + if (methodDeclaration.getName().toString().equals(methodName)) { + return methodDeclaration; + } + } + } + } + } + return methodDeclarationNode; + } + + // part of getRealMethodDeclarationNode + public static org.eclipse.jdt.core.dom.AbstractTypeDeclaration findTypeDeclaration(IType searchType, List<?> nodes) { + for (Object object : nodes) { + if (object instanceof org.eclipse.jdt.core.dom.AbstractTypeDeclaration) { + org.eclipse.jdt.core.dom.AbstractTypeDeclaration typeDeclaration = (org.eclipse.jdt.core.dom.AbstractTypeDeclaration) object; + if (typeDeclaration.getName().toString().equals(searchType.getElementName())) + return typeDeclaration; + } + } + return null; + } + + public static int getSourceEndFixed(int sourceEnd, org.eclipse.jdt.internal.compiler.ast.ASTNode node) throws Exception { + if (sourceEnd == -1) { + org.eclipse.jdt.internal.compiler.ast.ASTNode object = (org.eclipse.jdt.internal.compiler.ast.ASTNode)node.getClass().getField("$generatedBy").get(node); + if (object != null) { + return object.sourceEnd; + } + } + return sourceEnd; + } + + public static int fixRetrieveStartingCatchPosition(int original, int start) { + return original == -1 ? start : original; + } + + public static int fixRetrieveIdentifierEndPosition(int original, int end) { + return original == -1 ? end : original; + } + + public static int fixRetrieveEllipsisStartPosition(int original, int end) { + return original == -1 ? end : original; + } + + public static int fixRetrieveRightBraceOrSemiColonPosition(int original, int end) { +// if (original == -1) { +// Thread.dumpStack(); +// } + return original == -1 ? end : original; + } + + public static int fixRetrieveRightBraceOrSemiColonPosition(int retVal, AbstractMethodDeclaration amd) { + if (retVal != -1 || amd == null) return retVal; + boolean isGenerated = EclipseAugments.ASTNode_generatedBy.get(amd) != null; + if (isGenerated) return amd.declarationSourceEnd; + return -1; + } + + public static int fixRetrieveRightBraceOrSemiColonPosition(int retVal, FieldDeclaration fd) { + if (retVal != -1 || fd == null) return retVal; + boolean isGenerated = EclipseAugments.ASTNode_generatedBy.get(fd) != null; + if (isGenerated) return fd.declarationSourceEnd; + return -1; + } + + public static final int ALREADY_PROCESSED_FLAG = 0x800000; //Bit 24 + + public static boolean checkBit24(Object node) throws Exception { + int bits = (Integer)(node.getClass().getField("bits").get(node)); + return (bits & ALREADY_PROCESSED_FLAG) != 0; + } + + public static boolean skipRewritingGeneratedNodes(org.eclipse.jdt.core.dom.ASTNode node) throws Exception { + return ((Boolean) node.getClass().getField("$isGenerated").get(node)).booleanValue(); + } + + public static void setIsGeneratedFlag(org.eclipse.jdt.core.dom.ASTNode domNode, + org.eclipse.jdt.internal.compiler.ast.ASTNode internalNode) throws Exception { + + if (internalNode == null || domNode == null) return; + boolean isGenerated = EclipseAugments.ASTNode_generatedBy.get(internalNode) != null; + if (isGenerated) domNode.getClass().getField("$isGenerated").set(domNode, true); + } + + public static void setIsGeneratedFlagForName(org.eclipse.jdt.core.dom.Name name, Object internalNode) throws Exception { + if (internalNode instanceof org.eclipse.jdt.internal.compiler.ast.ASTNode) { + boolean isGenerated = EclipseAugments.ASTNode_generatedBy.get((org.eclipse.jdt.internal.compiler.ast.ASTNode) internalNode) != null; + if (isGenerated) name.getClass().getField("$isGenerated").set(name, true); + } + } + + public static RewriteEvent[] listRewriteHandleGeneratedMethods(RewriteEvent parent) { + RewriteEvent[] children = parent.getChildren(); + List<RewriteEvent> newChildren = new ArrayList<RewriteEvent>(); + List<RewriteEvent> modifiedChildren = new ArrayList<RewriteEvent>(); + for (int i = 0; i < children.length; i++) { + RewriteEvent child = children[i]; + boolean isGenerated = isGenerated((org.eclipse.jdt.core.dom.ASTNode) child.getOriginalValue()); + if (isGenerated) { + boolean isReplacedOrRemoved = child.getChangeKind() == RewriteEvent.REPLACED || child.getChangeKind() == RewriteEvent.REMOVED; + boolean convertingFromMethod = child.getOriginalValue() instanceof org.eclipse.jdt.core.dom.MethodDeclaration; + if (isReplacedOrRemoved && convertingFromMethod && child.getNewValue() != null) { + modifiedChildren.add(new NodeRewriteEvent(null, child.getNewValue())); + } + } else { + newChildren.add(child); + } + } + // Since Eclipse doesn't honor the "insert at specified location" for already existing members, + // we'll just add them last + newChildren.addAll(modifiedChildren); + return newChildren.toArray(new RewriteEvent[newChildren.size()]); + } + + public static int getTokenEndOffsetFixed(TokenScanner scanner, int token, int startOffset, Object domNode) throws CoreException { + boolean isGenerated = false; + try { + isGenerated = (Boolean) domNode.getClass().getField("$isGenerated").get(domNode); + } catch (Exception e) { + // If this fails, better to break some refactor scripts than to crash eclipse. + } + if (isGenerated) return -1; + return scanner.getTokenEndOffset(token, startOffset); + } + + public static IMethod[] removeGeneratedMethods(IMethod[] methods) throws Exception { + List<IMethod> result = new ArrayList<IMethod>(); + for (IMethod m : methods) { + if (m.getNameRange().getLength() > 0 && !m.getNameRange().equals(m.getSourceRange())) result.add(m); + } + return result.size() == methods.length ? methods : result.toArray(new IMethod[result.size()]); + } + + public static SearchMatch[] removeGenerated(SearchMatch[] returnValue) { + List<SearchMatch> result = new ArrayList<SearchMatch>(); + for (int j = 0; j < returnValue.length; j++) { + SearchMatch searchResult = returnValue[j]; + if (searchResult.getElement() instanceof IField) { + IField field = (IField) searchResult.getElement(); + + // can not check for value=lombok because annotation is + // not fully resolved + IAnnotation annotation = field.getAnnotation("Generated"); + if (annotation != null) { + // Method generated at field location, skip + continue; + } + + } + result.add(searchResult); + } + return result.toArray(new SearchMatch[result.size()]); + } + + public static SearchResultGroup[] createFakeSearchResult(SearchResultGroup[] returnValue, + Object/* + * org.eclipse.jdt.internal.corext.refactoring.rename. + * RenameFieldProcessor + */ processor) throws Exception { + if (returnValue == null || returnValue.length == 0) { + // if no matches were found, check if Data annotation is present on the class + Field declaredField = processor.getClass().getDeclaredField("fField"); + if (declaredField != null) { + declaredField.setAccessible(true); + SourceField fField = (SourceField) declaredField.get(processor); + IAnnotation dataAnnotation = fField.getDeclaringType().getAnnotation("Data"); + if (dataAnnotation != null) { + // add fake item, to make refactoring checks pass + return new SearchResultGroup[] {new SearchResultGroup(null, new SearchMatch[1])}; + } + } + } + return returnValue; + } + + public static SimpleName[] removeGeneratedSimpleNames(SimpleName[] in) throws Exception { + Field f = SimpleName.class.getField("$isGenerated"); + + int count = 0; + for (int i = 0; i < in.length; i++) { + if (in[i] == null || !((Boolean)f.get(in[i])).booleanValue()) count++; + } + if (count == in.length) return in; + SimpleName[] newSimpleNames = new SimpleName[count]; + count = 0; + for (int i = 0; i < in.length; i++) { + if (in[i] == null || !((Boolean)f.get(in[i])).booleanValue()) newSimpleNames[count++] = in[i]; + } + return newSimpleNames; + } + + public static Annotation[] convertAnnotations(Annotation[] out, IAnnotatable annotatable) { + IAnnotation[] in; + + try { + in = annotatable.getAnnotations(); + } catch (Exception e) { + return out; + } + + if (out == null) return null; + int toWrite = 0; + + for (int idx = 0; idx < out.length; idx++) { + String oName = new String(out[idx].type.getLastToken()); + boolean found = false; + for (IAnnotation i : in) { + String name = i.getElementName(); + int li = name.lastIndexOf('.'); + if (li > -1) name = name.substring(li + 1); + if (name.equals(oName)) { + found = true; + break; + } + } + if (!found) out[idx] = null; + else toWrite++; + } + + Annotation[] replace = out; + if (toWrite < out.length) { + replace = new Annotation[toWrite]; + int idx = 0; + for (int i = 0; i < out.length; i++) { + if (out[i] == null) continue; + replace[idx++] = out[i]; + } + } + + return replace; + } + } +} diff --git a/src/installer/lombok/installer/IdeLocation.java b/src/installer/lombok/installer/IdeLocation.java index 4e3a7e41..c3853867 100644 --- a/src/installer/lombok/installer/IdeLocation.java +++ b/src/installer/lombok/installer/IdeLocation.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 @@ -25,7 +25,6 @@ import java.io.File; import java.io.IOException; import java.net.URL; -import lombok.installer.eclipse.EclipseFinder; import lombok.patcher.ClassRootFinder; /** @@ -46,7 +45,7 @@ public abstract class IdeLocation { * a jar that wasn't accessed via the file-system, or if its started via e.g. unpacking the jar. */ public static File findOurJar() { - return new File(ClassRootFinder.findClassRootOfClass(IdeFinder.class)); + return new File(ClassRootFinder.findClassRootOfClass(OsUtils.class)); } @Override public String toString() { @@ -70,7 +69,7 @@ public abstract class IdeLocation { private static final String LEGAL_PATH_CHARS_WINDOWS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_/:\\ "; public static String escapePath(String path) { StringBuilder out = new StringBuilder(); - String legalChars = IdeFinder.getOS() == EclipseFinder.OS.UNIX ? LEGAL_PATH_CHARS : LEGAL_PATH_CHARS_WINDOWS; + String legalChars = OsUtils.getOS() == OsUtils.OS.UNIX ? LEGAL_PATH_CHARS : LEGAL_PATH_CHARS_WINDOWS; for (char c : path.toCharArray()) { if (legalChars.indexOf(c) == -1) out.append('\\'); out.append(c); diff --git a/src/installer/lombok/installer/IdeLocationProvider.java b/src/installer/lombok/installer/IdeLocationProvider.java index 933a5989..c4b64141 100644 --- a/src/installer/lombok/installer/IdeLocationProvider.java +++ b/src/installer/lombok/installer/IdeLocationProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009 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,20 +21,30 @@ */ package lombok.installer; +import java.util.List; import java.util.regex.Pattern; -import lombok.installer.IdeFinder.OS; - public interface IdeLocationProvider { /** * @throws CorruptedIdeLocationException * Only throw this exception if the location seems like a proper installation except there's something wrong with it. * Do not throw it (just return {@code null}) if there's nothing there or it looks absolutely nothing like your IDE. */ - public abstract IdeLocation create(String path) throws CorruptedIdeLocationException; + IdeLocation create(String path) throws CorruptedIdeLocationException; + + /** + * Return the usual name of the IDE executable or other obvious marker of an IDE installation on the current platform. + */ + Pattern getLocationSelectors(); /** - * Return the usual name of the IDE executable or other obvious marker of an IDE installation on the provided platform. + * Look for installations of your IDE in the usual places. + * + * @param locations Add to this list any valid locations that you found. + * @param problems + * Add to this list any locations that look like installations, + * but have problems that prevent you from installing/uninstalling from them. DONT add to this list + * any common locations that have no installation at all - only add near misses. */ - public abstract Pattern getLocationSelectors(OS os); + void findIdes(List<IdeLocation> locations, List<CorruptedIdeLocationException> problems); } diff --git a/src/installer/lombok/installer/Installer.java b/src/installer/lombok/installer/Installer.java index b9faeebd..94cc1a45 100644 --- a/src/installer/lombok/installer/Installer.java +++ b/src/installer/lombok/installer/Installer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2010 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 @@ -38,7 +38,7 @@ import lombok.Lombok; import lombok.core.LombokApp; import lombok.core.SpiLoadUtil; import lombok.core.Version; -import lombok.installer.IdeFinder.OS; +import lombok.installer.OsUtils.OS; import lombok.patcher.ClassRootFinder; import org.mangosdk.spi.ProviderFor; @@ -56,7 +56,7 @@ import com.zwitserloot.cmdreader.Shorthand; * and looks in some common places on Mac OS X, Linux and Windows. */ public class Installer { - static final URI ABOUT_LOMBOK_URL = URI.create("http://projectlombok.org"); + static final URI ABOUT_LOMBOK_URL = URI.create("https://projectlombok.org"); static final List<IdeLocationProvider> locationProviders; static { @@ -72,10 +72,9 @@ public class Installer { } static List<Pattern> getIdeExecutableNames() { - OS os = IdeFinder.getOS(); List<Pattern> list = new ArrayList<Pattern>(); for (IdeLocationProvider provider : locationProviders) { - Pattern p = provider.getLocationSelectors(os); + Pattern p = provider.getLocationSelectors(); if (p != null) list.add(p); } return list; @@ -91,12 +90,8 @@ public class Installer { } static void autoDiscover(List<IdeLocation> locations, List<CorruptedIdeLocationException> problems) { - try { - for (IdeFinder finder : SpiLoadUtil.findServices(IdeFinder.class)) { - finder.findIdes(locations, problems); - } - } catch (IOException e) { - throw Lombok.sneakyThrow(e); + for (IdeLocationProvider provider : locationProviders) { + provider.findIdes(locations, problems); } } @@ -160,7 +155,7 @@ public class Installer { } private static int guiInstaller() { - if (IdeFinder.getOS() == OS.MAC_OS_X) { + if (OsUtils.getOS() == OS.MAC_OS_X) { System.setProperty("com.apple.mrj.application.apple.menu.about.name", "Lombok Installer"); System.setProperty("com.apple.macos.use-file-dialog-packages", "true"); } diff --git a/src/installer/lombok/installer/InstallerGUI.java b/src/installer/lombok/installer/InstallerGUI.java index 5717948a..231e2d3c 100644 --- a/src/installer/lombok/installer/InstallerGUI.java +++ b/src/installer/lombok/installer/InstallerGUI.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2010 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 @@ -39,6 +39,8 @@ import java.awt.event.ActionListener; import java.awt.font.TextAttribute; import java.io.File; import java.io.FilenameFilter; +import java.io.InputStream; +import java.io.InputStreamReader; import java.net.URI; import java.util.ArrayList; import java.util.Collections; @@ -62,12 +64,15 @@ import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; +import javax.swing.JTextPane; import javax.swing.Scrollable; import javax.swing.SwingUtilities; +import javax.swing.UIManager; import javax.swing.filechooser.FileFilter; +import javax.swing.text.html.HTMLDocument; import lombok.core.Version; -import lombok.installer.IdeFinder.OS; +import lombok.installer.OsUtils.OS; /** * The lombok GUI installer. @@ -75,6 +80,7 @@ import lombok.installer.IdeFinder.OS; * Also offers info on what this installer does in case people want to instrument their IDE manually. */ public class InstallerGUI { + private static final int INSTALLER_WINDOW_WIDTH = 662; static final AtomicReference<Integer> exitMarker = new AtomicReference<Integer>(); private JFrame appWindow; @@ -85,6 +91,7 @@ public class InstallerGUI { private Component ideArea; private Component uninstallArea; private Component howIWorkArea; + private Component successArea; private Box uninstallBox; private JHyperLink uninstallButton; @@ -101,6 +108,7 @@ public class InstallerGUI { public InstallerGUI() { appWindow = new JFrame(String.format("Project Lombok v%s - Installer", Version.getVersion())); + appWindow.setLocationByPlatform(true); appWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); appWindow.setResizable(false); appWindow.setIconImage(Toolkit.getDefaultToolkit().getImage(Installer.class.getResource("lombokIcon.png"))); @@ -112,6 +120,8 @@ public class InstallerGUI { uninstallArea.setVisible(false); howIWorkArea = buildHowIWorkArea(); howIWorkArea.setVisible(false); + successArea = buildSuccessArea(); + successArea.setVisible(false); buildChrome(appWindow.getContentPane()); appWindow.pack(); } catch (Throwable t) { @@ -152,6 +162,7 @@ public class InstallerGUI { howIWorkArea.setVisible(false); javacArea.setVisible(true); ideArea.setVisible(true); + successArea.setVisible(false); appWindow.pack(); } }); @@ -159,9 +170,86 @@ public class InstallerGUI { constraints.gridy = 2; container.add(buttonBar, constraints); + container.setPreferredSize(new Dimension(INSTALLER_WINDOW_WIDTH, 415)); + container.setMinimumSize(new Dimension(INSTALLER_WINDOW_WIDTH, 415)); return container; } + private void showSuccess(String installSpecific) { + successExplanation.setText(SUCCESS_EXPLANATION.replace("%%%", installSpecific)); + howIWorkArea.setVisible(false); + javacArea.setVisible(false); + ideArea.setVisible(false); + successArea.setVisible(true); + appWindow.pack(); + } + + private JLabel successExplanation; + + private Component buildSuccessArea() { + JPanel container = new JPanel(); + + container.setLayout(new GridBagLayout()); + GridBagConstraints constraints = new GridBagConstraints(); + constraints.anchor = GridBagConstraints.WEST; + + JLabel title; + container.add(title = new JLabel(SUCCESS_TITLE), constraints); + title.setPreferredSize(new Dimension(INSTALLER_WINDOW_WIDTH - 82, 20)); + title.setMinimumSize(new Dimension(INSTALLER_WINDOW_WIDTH - 82, 20)); + + constraints.gridy = 1; + constraints.insets = new Insets(8, 0, 0, 16); + container.add(successExplanation = new JLabel(SUCCESS_EXPLANATION), constraints); + successExplanation.setPreferredSize(new Dimension(INSTALLER_WINDOW_WIDTH - 82, 175)); + successExplanation.setMinimumSize(new Dimension(INSTALLER_WINDOW_WIDTH - 82, 175)); + + constraints.gridy++; + constraints.fill = GridBagConstraints.BOTH; + + JTextPane notes = new JTextPane(); + notes.setContentType("text/html"); + notes.setText(readChangeLog()); + notes.setEditable(false); + notes.setOpaque(false); + notes.setBorder(null); + notes.setSelectionStart(0); + notes.setSelectionEnd(0); + + Font font = UIManager.getFont("Label.font"); + String bodyRule = "body { font-family: " + font.getFamily() + "; font-size: " + font.getSize() + "pt; }"; + ((HTMLDocument) notes.getDocument()).getStyleSheet().addRule(bodyRule); + JScrollPane scroller = new JScrollPane(notes); + container.add(scroller, constraints); + scroller.setPreferredSize(new Dimension(INSTALLER_WINDOW_WIDTH - 82, 200)); + scroller.setMinimumSize(new Dimension(INSTALLER_WINDOW_WIDTH - 82, 200)); + container.setPreferredSize(new Dimension(INSTALLER_WINDOW_WIDTH, 415)); + container.setMinimumSize(new Dimension(INSTALLER_WINDOW_WIDTH, 415)); + return container; + } + + private String readChangeLog() { + InputStream in = Installer.class.getResourceAsStream("/latestchanges.html"); + try { + char[] buff = new char[8192]; + StringBuilder contents = new StringBuilder(); + InputStreamReader reader = new InputStreamReader(in, "UTF-8"); + while (true) { + int read = reader.read(buff); + if (read == -1) break; + contents.append(buff, 0, read); + } + return "<html>" + contents + "</html>"; + } catch (Exception e) { + return "No Changelog available"; + } + finally { + try { + in.close(); + } catch (Exception ignore){ /**/} + } + } + private Component buildUninstallArea() { JPanel container = new JPanel(); @@ -209,6 +297,8 @@ public class InstallerGUI { constraints.gridy = 4; container.add(buttonBar, constraints); + container.setPreferredSize(new Dimension(INSTALLER_WINDOW_WIDTH, 415)); + container.setMinimumSize(new Dimension(INSTALLER_WINDOW_WIDTH, 415)); return container; } @@ -231,6 +321,8 @@ public class InstallerGUI { constraints.gridy = 2; container.add(example, constraints); + container.setPreferredSize(new Dimension(INSTALLER_WINDOW_WIDTH, 105)); + container.setMinimumSize(new Dimension(INSTALLER_WINDOW_WIDTH, 105)); return container; } @@ -288,11 +380,11 @@ public class InstallerGUI { if (locations.size() + problems.size() == 0) { JOptionPane.showMessageDialog(appWindow, - "I can't find any IDEs on your computer.\n" + - "If you have IDEs installed on this computer, please use the " + - "'Specify Location...' button to manually point out the \n" + - "location of your IDE installation to me. Thanks!", - "Can't find IDE", JOptionPane.INFORMATION_MESSAGE); + "I can't find any IDEs on your computer.\n" + + "If you have IDEs installed on this computer, please use the " + + "'Specify Location...' button to manually point out the \n" + + "location of your IDE installation to me. Thanks!", + "Can't find IDE", JOptionPane.INFORMATION_MESSAGE); } } }); @@ -312,7 +404,7 @@ public class InstallerGUI { final List<Pattern> exeNames = Installer.getIdeExecutableNames(); String file = null; - if (IdeFinder.getOS() == OS.MAC_OS_X) { + if (OsUtils.getOS() == OS.MAC_OS_X) { FileDialog chooser = new FileDialog(appWindow); chooser.setMode(FileDialog.LOAD); @@ -418,7 +510,8 @@ public class InstallerGUI { uninstallPlaceholder.setVisible(false); container.add(uninstallPlaceholder, constraints); - + container.setPreferredSize(new Dimension(INSTALLER_WINDOW_WIDTH, 296)); + container.setMinimumSize(new Dimension(INSTALLER_WINDOW_WIDTH, 296)); return container; } @@ -426,6 +519,7 @@ public class InstallerGUI { javacArea.setVisible(false); ideArea.setVisible(false); howIWorkArea.setVisible(true); + successArea.setVisible(false); appWindow.pack(); } @@ -452,6 +546,7 @@ public class InstallerGUI { spinner.setOpaque(true); spinner.setLayout(new FlowLayout()); spinner.add(new JLabel(new ImageIcon(Installer.class.getResource("loading.gif")))); + final Container appWindowContent = appWindow.getContentPane(); appWindow.setContentPane(spinner); final AtomicInteger successes = new AtomicInteger(); @@ -499,20 +594,13 @@ public class InstallerGUI { try { SwingUtilities.invokeAndWait(new Runnable() { @Override public void run() { + appWindow.setContentPane(appWindowContent); + appWindow.pack(); StringBuilder installSpecific = new StringBuilder(); for (String installSpecificMessage : installSpecificMessages) { installSpecific.append("<br>").append(installSpecificMessage); } - JOptionPane.showMessageDialog(appWindow, - "<html>Lombok has been installed on the selected IDE installations.<br>" + - "Don't forget to add <code>lombok.jar</code> to your projects, and restart your IDE!" + installSpecific.toString() + "</html>", - "Install successful", - JOptionPane.INFORMATION_MESSAGE); - appWindow.setVisible(false); - synchronized (exitMarker) { - exitMarker.set(0); - exitMarker.notifyAll(); - } + showSuccess(installSpecific.toString()); } }); } catch (Exception e) { @@ -710,6 +798,8 @@ public class InstallerGUI { appWindowContainer.add(howIWorkArea, constraints); + appWindowContainer.add(successArea, constraints); + constraints.gridy++; constraints.gridwidth = 2; constraints.gridx = 0; @@ -718,9 +808,33 @@ public class InstallerGUI { constraints.ipadx = 0; constraints.ipady = 0; constraints.fill = GridBagConstraints.HORIZONTAL; - constraints.anchor = GridBagConstraints.SOUTHEAST; + constraints.anchor = GridBagConstraints.SOUTHWEST; constraints.insets = new Insets(0, 16, 8, 8); + + appWindow.add(buildButtonBar(), constraints); + } + + private Box buildButtonBar() { Box buttonBar = Box.createHorizontalBox(); + + JHyperLink aboutLink = new JHyperLink(Installer.ABOUT_LOMBOK_URL.toString()); + aboutLink.addActionListener(openBrowser(aboutLink, Installer.ABOUT_LOMBOK_URL)); + buttonBar.add(aboutLink); + + buttonBar.add(Box.createRigidArea(new Dimension(16, 1))); + + JLabel versionLabel = new JLabel(); + versionLabel.setText("v" + Version.getVersion()); + + buttonBar.add(versionLabel); + buttonBar.add(Box.createRigidArea(new Dimension(16, 1))); + + JHyperLink changelogLink = new JHyperLink("View full changelog"); + changelogLink.addActionListener(openBrowser(changelogLink, Installer.ABOUT_LOMBOK_URL.resolve("/changelog.html"))); + buttonBar.add(changelogLink); + + buttonBar.add(Box.createHorizontalGlue()); + JButton quitButton = new JButton("Quit Installer"); quitButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { @@ -728,51 +842,48 @@ public class InstallerGUI { System.exit(0); } }); - final JHyperLink hyperlink = new JHyperLink(Installer.ABOUT_LOMBOK_URL.toString()); - hyperlink.addActionListener(new ActionListener() { + buttonBar.add(quitButton); + return buttonBar; + } + + private ActionListener openBrowser(final JHyperLink hyperlink, final URI location) { + return new ActionListener() { @Override public void actionPerformed(ActionEvent event) { hyperlink.setForeground(new Color(85, 145, 90)); try { //java.awt.Desktop doesn't exist in 1.5. Object desktop = Class.forName("java.awt.Desktop").getMethod("getDesktop").invoke(null); - Class.forName("java.awt.Desktop").getMethod("browse", URI.class).invoke(desktop, Installer.ABOUT_LOMBOK_URL); + Class.forName("java.awt.Desktop").getMethod("browse", URI.class).invoke(desktop, location); } catch (Exception e) { Runtime rt = Runtime.getRuntime(); try { - switch (IdeFinder.getOS()) { + switch (OsUtils.getOS()) { case WINDOWS: String[] cmd = new String[4]; cmd[0] = "cmd.exe"; cmd[1] = "/C"; cmd[2] = "start"; - cmd[3] = Installer.ABOUT_LOMBOK_URL.toString(); + cmd[3] = location.toString(); rt.exec(cmd); break; case MAC_OS_X: - rt.exec("open " + Installer.ABOUT_LOMBOK_URL.toString()); + rt.exec("open " + location.toString()); break; default: case UNIX: - rt.exec("firefox " + Installer.ABOUT_LOMBOK_URL.toString()); + rt.exec("firefox " + location.toString()); break; } } catch (Exception e2) { JOptionPane.showMessageDialog(appWindow, "Well, this is embarrassing. I don't know how to open a webbrowser.\n" + - "I guess you'll have to open it. Browse to:\n" + Installer.ABOUT_LOMBOK_URL + + "I guess you'll have to open it. Browse to:\n" + location + " for more information about Lombok.", "I'm embarrassed", JOptionPane.INFORMATION_MESSAGE); } } } - }); - buttonBar.add(hyperlink); - buttonBar.add(Box.createRigidArea(new Dimension(16, 1))); - buttonBar.add(new JLabel("<html><font size=\"-1\">v" + Version.getVersion() + "</font></html>")); - - buttonBar.add(Box.createHorizontalGlue()); - buttonBar.add(quitButton); - appWindow.add(buttonBar, constraints); + }; } /** @@ -780,7 +891,7 @@ public class InstallerGUI { */ public void show() { appWindow.setVisible(true); - if (IdeFinder.getOS() == OS.MAC_OS_X) { + if (OsUtils.getOS() == OS.MAC_OS_X) { try { AppleNativeLook.go(); } catch (Throwable ignore) { @@ -790,7 +901,7 @@ public class InstallerGUI { } private static final String IDE_TITLE = - "<html><font size=\"+1\"><b><i>IDEs</i></b></font></html>"; + "<html><font size=\"+1\"><b><i>IDEs </i></b></font></html>"; private static final String IDE_EXPLANATION = "<html>Lombok can update your Eclipse or eclipse-based IDE to fully support all Lombok features.<br>" + @@ -800,7 +911,7 @@ public class InstallerGUI { "Scanning your drives for IDE installations..."; private static final String JAVAC_TITLE = - "<html><font size=\"+1\"><b><i>Javac</i></b></font> (and tools that invoke javac such as <i>ant</i> and <i>maven</i>)</html>"; + "<html><font size=\"+1\"><b><i>Javac </i></b></font> (and tools that invoke javac such as <i>ant</i> and <i>maven</i>)</html>"; private static final String JAVAC_EXPLANATION = "<html>Lombok works 'out of the box' with javac.<br>Just make sure the lombok.jar is in your classpath when you compile."; @@ -809,23 +920,27 @@ public class InstallerGUI { "<html>Example: <code>javac -cp lombok.jar MyCode.java</code></html>"; private static final String UNINSTALL_TITLE = - "<html><font size=\"+1\"><b><i>Uninstall</i></b></font></html>"; + "<html><font size=\"+1\"><b><i>Uninstall </i></b></font></html>"; private static final String UNINSTALL_EXPLANATION = "<html>Uninstall Lombok from the following IDE Installations?</html>"; private static final String HOW_I_WORK_TITLE = - "<html><font size=\"+1\"><b><i>What this installer does</i></b></font></html>"; + "<html><font size=\"+1\"><b><i>What this installer does </i></b></font></html>"; private static final String HOW_I_WORK_EXPLANATION = "<html><h2>Eclipse</h2><ol>" + "<li>First, I copy myself (lombok.jar) to your Eclipse install directory.</li>" + - "<li>Then, I edit the <i>eclipse.ini</i> file to add the following two entries:<br>" + - "<pre>-Xbootclasspath/a:lombok.jar<br>" + - "-javaagent:lombok.jar</pre></li></ol>" + + "<li>Then, I edit the <i>eclipse.ini</i> file to add the following entry:<br>" + + "<pre>-javaagent:lombok.jar</pre></li></ol>" + "On Mac OS X, eclipse.ini is hidden in<br>" + "<code>Eclipse.app/Contents/MacOS</code> so that's where I place the jar files.</html>"; + private static final String SUCCESS_TITLE = "<html><font size=\"+1\"><b><i>Install successful </i></b></font></html>"; + private static final String SUCCESS_EXPLANATION = "<html>Lombok has been installed on the selected IDE installations.<br>" + + "Don't forget to:<ul><li> add <code>lombok.jar</code> to your projects,<li><b>exit and start</b> your IDE,<li><b>rebuild</b> all projects!</ul>%%%</html>"; + + private static class JHyperLink extends JButton { private static final long serialVersionUID = 1L; diff --git a/src/installer/lombok/installer/IdeFinder.java b/src/installer/lombok/installer/OsUtils.java index f68a0e4c..2da7de09 100644 --- a/src/installer/lombok/installer/IdeFinder.java +++ b/src/installer/lombok/installer/OsUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2011 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 @@ -34,9 +34,13 @@ import lombok.core.Version; /** * Implement and provide this class to add auto-finding a certain brand of IDEs to the lombok installer. */ -public abstract class IdeFinder { +public final class OsUtils { private static final AtomicBoolean windowsDriveInfoLibLoaded = new AtomicBoolean(false); + private OsUtils() { + // Prevent instantiation + } + private static void loadWindowsDriveInfoLib() throws IOException { if (!windowsDriveInfoLibLoaded.compareAndSet(false, true)) return; @@ -63,7 +67,7 @@ public abstract class IdeFinder { } private static boolean unpackDLL(String dllName, File target) throws IOException { - InputStream in = IdeFinder.class.getResourceAsStream(dllName); + InputStream in = OsUtils.class.getResourceAsStream(dllName); try { try { FileOutputStream out = new FileOutputStream(target); @@ -130,15 +134,4 @@ public abstract class IdeFinder { return OS.UNIX; } - - /** - * Look for installations of your IDE in the usual places. - * - * @param locations Add to this list any valid locations that you found. - * @param problems - * Add to this list any locations that look like installations, - * but have problems that prevent you from installing/uninstalling from them. DONT add to this list - * any common locations that have no installation at all - only add near misses. - */ - public abstract void findIdes(List<IdeLocation> locations, List<CorruptedIdeLocationException> problems); } diff --git a/src/installer/lombok/installer/eclipse/EclipseLocationProvider.java b/src/installer/lombok/installer/eclipse/EclipseLocationProvider.java index 914de588..fa2ce958 100644 --- a/src/installer/lombok/installer/eclipse/EclipseLocationProvider.java +++ b/src/installer/lombok/installer/eclipse/EclipseLocationProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2011 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,140 +21,24 @@ */ package lombok.installer.eclipse; -import static lombok.installer.IdeLocation.canonical; +import java.util.Collections; -import java.io.File; -import java.io.IOException; -import java.util.Arrays; -import java.util.List; -import java.util.regex.Pattern; - -import lombok.installer.IdeLocation; import lombok.installer.IdeLocationProvider; -import lombok.installer.CorruptedIdeLocationException; -import lombok.installer.IdeFinder.OS; import org.mangosdk.spi.ProviderFor; @ProviderFor(IdeLocationProvider.class) -public class EclipseLocationProvider implements IdeLocationProvider { - @Override public IdeLocation create(String path) throws CorruptedIdeLocationException { - return create0(path); - } - - protected List<String> getEclipseExecutableNames() { - return Arrays.asList("eclipse.app", "eclipse.exe", "eclipse"); - } - - protected String getIniName() { - return "eclipse.ini"; - } - - protected IdeLocation makeLocation(String name, File ini) throws CorruptedIdeLocationException { - return new EclipseLocation(name, ini); - } - - protected String getMacAppName() { - return "Eclipse.app"; - } - - protected String getUnixAppName() { - return "eclipse"; - } - - /** - * Create a new EclipseLocation by pointing at either the directory contains the Eclipse executable, or the executable itself, - * or an eclipse.ini file. - * - * @throws NotAnIdeLocationException - * If this isn't an Eclipse executable or a directory with an - * Eclipse executable. - */ - protected IdeLocation create0(String path) throws CorruptedIdeLocationException { - if (path == null) throw new NullPointerException("path"); - File p = new File(path); - - if (!p.exists()) return null; - if (p.isDirectory()) { - for (String possibleExeName : getEclipseExecutableNames()) { - File f = new File(p, possibleExeName); - if (f.exists()) return findEclipseIniFromExe(f, 0); - } - - File f = new File(p, getIniName()); - if (f.exists()) return new EclipseLocation(canonical(p), f); - } - - if (p.isFile()) { - if (p.getName().equalsIgnoreCase(getIniName())) { - return new EclipseLocation(canonical(p.getParentFile()), p); - } - } - - if (getEclipseExecutableNames().contains(p.getName().toLowerCase())) { - return findEclipseIniFromExe(p, 0); - } - - return null; - } - - private IdeLocation findEclipseIniFromExe(File exePath, int loopCounter) throws CorruptedIdeLocationException { - /* Try looking for eclipse.ini as sibling to the executable */ { - File ini = new File(exePath.getParentFile(), getIniName()); - if (ini.isFile()) return makeLocation(canonical(exePath), ini); - } - - /* Try looking for Eclipse/app/Contents/MacOS/eclipse.ini as sibling to executable; this works on Mac OS X. */ { - File ini = new File(exePath.getParentFile(), getMacAppName() + "/Contents/MacOS/" + getIniName()); - if (ini.isFile()) return makeLocation(canonical(exePath), ini); - } - - /* If executable is a soft link, follow it and retry. */ { - if (loopCounter < 50) { - try { - String oPath = exePath.getAbsolutePath(); - String nPath = exePath.getCanonicalPath(); - if (!oPath.equals(nPath)) try { - IdeLocation loc = findEclipseIniFromExe(new File(nPath), loopCounter + 1); - if (loc != null) return loc; - } catch (CorruptedIdeLocationException ignore) { - // Unlinking didn't help find an eclipse, so continue. - } - } catch (IOException ignore) { /* okay, that didn't work, assume it isn't a soft link then. */ } - } - } - - /* If executable is a linux LSB-style path, then look in the usual places that package managers like apt-get use.*/ { - String path = exePath.getAbsolutePath(); - try { - path = exePath.getCanonicalPath(); - } catch (IOException ignore) { /* We'll stick with getAbsolutePath()'s result then. */ } - - if (path.equals("/usr/bin/" + getUnixAppName()) || path.equals("/bin/" + getUnixAppName()) || path.equals("/usr/local/bin/" + getUnixAppName())) { - File ini = new File("/usr/lib/" + getUnixAppName() + "/" + getIniName()); - if (ini.isFile()) return makeLocation(path, ini); - ini = new File("/usr/local/lib/" + getUnixAppName() + "/" + getIniName()); - if (ini.isFile()) return makeLocation(path, ini); - ini = new File("/usr/local/etc/" + getUnixAppName() + "/" + getIniName()); - if (ini.isFile()) return makeLocation(path, ini); - ini = new File("/etc/" + getIniName()); - if (ini.isFile()) return makeLocation(path, ini); - } - } - - /* If we get this far, we lose. */ - return null; - } - - @Override public Pattern getLocationSelectors(OS os) { - switch (os) { - case MAC_OS_X: - return Pattern.compile("^(eclipse|eclipse\\.ini|eclipse\\.app)$", Pattern.CASE_INSENSITIVE); - case WINDOWS: - return Pattern.compile("^(eclipse\\.exe|eclipse\\.ini)$", Pattern.CASE_INSENSITIVE); - default: - case UNIX: - return Pattern.compile("^(eclipse|eclipse\\.ini)$", Pattern.CASE_INSENSITIVE); - } +public class EclipseLocationProvider extends EclipseProductLocationProvider { + + private static final EclipseProductDescriptor ECLIPSE = new StandardProductDescriptor( + "Eclipse", + "eclipse", + "eclipse", + EclipseLocationProvider.class.getResource("eclipse.png"), + Collections.<String>emptySet() + ); + + public EclipseLocationProvider() { + super(ECLIPSE); } } diff --git a/src/installer/lombok/installer/eclipse/STSLocation.java b/src/installer/lombok/installer/eclipse/EclipseProductDescriptor.java index 40ade40a..8f736a57 100644 --- a/src/installer/lombok/installer/eclipse/STSLocation.java +++ b/src/installer/lombok/installer/eclipse/EclipseProductDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009 The Project Lombok Authors. + * Copyright (C) 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,25 +21,21 @@ */ package lombok.installer.eclipse; -import java.io.File; import java.net.URL; +import java.util.List; +import java.util.regex.Pattern; -import lombok.installer.CorruptedIdeLocationException; - -public class STSLocation extends EclipseLocation { - public STSLocation(String nameOfLocation, File pathToEclipseIni) throws CorruptedIdeLocationException { - super(nameOfLocation, pathToEclipseIni); - } - - @Override public URL getIdeIcon() { - return STSLocation.class.getResource("STS.png"); - } - - @Override protected String getIniFileName() { - return "STS.ini"; - } - - @Override protected String getTypeName() { - return "STS"; - } -} +public interface EclipseProductDescriptor { + String getProductName(); + String getWindowsExecutableName(); + String getUnixAppName(); + String getMacAppName(); + String getDirectoryName(); + List<String> getExecutableNames(); + List<String> getSourceDirsOnWindows(); + List<String> getSourceDirsOnMac(); + List<String> getSourceDirsOnUnix(); + String getIniFileName(); + Pattern getLocationSelectors(); + URL getIdeIcon(); +}
\ No newline at end of file diff --git a/src/installer/lombok/installer/eclipse/EclipseLocation.java b/src/installer/lombok/installer/eclipse/EclipseProductLocation.java index e347cd98..aa97a3e5 100644 --- a/src/installer/lombok/installer/eclipse/EclipseLocation.java +++ b/src/installer/lombok/installer/eclipse/EclipseProductLocation.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 @@ -35,7 +35,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import lombok.installer.CorruptedIdeLocationException; -import lombok.installer.IdeFinder; +import lombok.installer.OsUtils; import lombok.installer.IdeLocation; import lombok.installer.InstallException; import lombok.installer.Installer; @@ -46,30 +46,35 @@ import lombok.installer.UninstallException; * An instance can figure out if an Eclipse installation has been lombok-ified, and can * install and uninstall lombok from the Eclipse installation. */ -public class EclipseLocation extends IdeLocation { - private final String name; - private final File eclipseIniPath; - private volatile boolean hasLombok; - - private static final String OS_NEWLINE = IdeFinder.getOS().getLineEnding(); +public final class EclipseProductLocation extends IdeLocation { - protected String getTypeName() { - return "eclipse"; - } + private static final String OS_NEWLINE = OsUtils.getOS().getLineEnding(); - protected String getIniFileName() { - return "eclipse.ini"; - } + private final EclipseProductDescriptor descriptor; + private final String name; + private final File eclipseIniPath; + private final String pathToLombokJarPrefix; + private final boolean hasLombok; - EclipseLocation(String nameOfLocation, File pathToEclipseIni) throws CorruptedIdeLocationException { + EclipseProductLocation(EclipseProductDescriptor descriptor, String nameOfLocation, File pathToEclipseIni) throws CorruptedIdeLocationException { + this.descriptor = descriptor; this.name = nameOfLocation; this.eclipseIniPath = pathToEclipseIni; + File p1 = pathToEclipseIni.getParentFile(); + File p2 = p1 == null ? null : p1.getParentFile(); + File p3 = p2 == null ? null : p2.getParentFile(); + if (p1 != null && p1.getName().equals("Eclipse") && p2 != null && p2.getName().equals("Contents") && p3 != null && p3.getName().endsWith(".app")) { + this.pathToLombokJarPrefix = "../Eclipse/"; + } else { + this.pathToLombokJarPrefix = ""; + } + try { this.hasLombok = checkForLombok(eclipseIniPath); } catch (IOException e) { throw new CorruptedIdeLocationException( - "I can't read the configuration file of the " + getTypeName() + " installed at " + name + "\n" + - "You may need to run this installer with root privileges if you want to modify that " + getTypeName() + ".", getTypeName(), e); + "I can't read the configuration file of the " + descriptor.getProductName() + " installed at " + name + "\n" + + "You may need to run this installer with root privileges if you want to modify that " + descriptor.getProductName() + ".", descriptor.getProductName(), e); } } @@ -78,8 +83,8 @@ public class EclipseLocation extends IdeLocation { } @Override public boolean equals(Object o) { - if (!(o instanceof EclipseLocation)) return false; - return ((EclipseLocation)o).eclipseIniPath.equals(eclipseIniPath); + if (!(o instanceof EclipseProductLocation)) return false; + return ((EclipseProductLocation)o).eclipseIniPath.equals(eclipseIniPath); } /** @@ -98,13 +103,13 @@ public class EclipseLocation extends IdeLocation { return hasLombok; } - private final Pattern JAVA_AGENT_LINE_MATCHER = Pattern.compile( + private static final Pattern JAVA_AGENT_LINE_MATCHER = Pattern.compile( "^\\-javaagent\\:.*lombok.*\\.jar$", Pattern.CASE_INSENSITIVE); - private final Pattern BOOTCLASSPATH_LINE_MATCHER = Pattern.compile( + private static final Pattern BOOTCLASSPATH_LINE_MATCHER = Pattern.compile( "^\\-Xbootclasspath\\/a\\:(.*lombok.*\\.jar.*)$", Pattern.CASE_INSENSITIVE); - private boolean checkForLombok(File iniFile) throws IOException { + private static boolean checkForLombok(File iniFile) throws IOException { if (!iniFile.exists()) return false; FileInputStream fis = new FileInputStream(iniFile); try { @@ -196,7 +201,7 @@ public class EclipseLocation extends IdeLocation { File lombokJar = new File(dir, "lombok.jar"); if (lombokJar.exists()) { if (!lombokJar.delete()) { - if (IdeFinder.getOS() == IdeFinder.OS.WINDOWS && Installer.isSelf(lombokJar.getAbsolutePath())) { + if (OsUtils.getOS() == OsUtils.OS.WINDOWS && Installer.isSelf(lombokJar.getAbsolutePath())) { lombokJarsForWhichCantDeleteSelf.add(lombokJar); } else { throw new UninstallException( @@ -218,14 +223,14 @@ public class EclipseLocation extends IdeLocation { throw new UninstallException(true, String.format( "lombok.jar cannot delete itself on windows.\nHowever, lombok has been uncoupled from your %s.\n" + "You can safely delete this jar file. You can find it at:\n%s", - getTypeName(), lombokJarsForWhichCantDeleteSelf.get(0).getAbsolutePath()), null); + descriptor.getProductName(), lombokJarsForWhichCantDeleteSelf.get(0).getAbsolutePath()), null); } } private static String generateWriteErrorMessage() { String osSpecificError; - switch (IdeFinder.getOS()) { + switch (OsUtils.getOS()) { default: case MAC_OS_X: case UNIX: @@ -251,11 +256,11 @@ public class EclipseLocation extends IdeLocation { */ @Override public String install() throws InstallException { - // For whatever reason, relative paths in your eclipse.ini file don't work on linux, but only for -javaagent. - // If someone knows how to fix this, please do so, as this current hack solution (putting the absolute path - // to the jar files in your eclipse.ini) means you can't move your eclipse around on linux without lombok - // breaking it. NB: rerunning lombok.jar installer and hitting 'update' will fix it if you do that. - boolean fullPathRequired = IdeFinder.getOS() == EclipseFinder.OS.UNIX || System.getProperty("lombok.installer.fullpath") != null; + // On Linux, for whatever reason, relative paths in your eclipse.ini file don't work, but only for -javaagent. + // On Windows, since the Oomph, the generated shortcut starts in the wrong directory. + // So the default is to use absolute paths, breaking lombok when you move the eclipse directory. + // Or not break when you copy your directory, but break later when you remove the original one. + boolean fullPathRequired = !"false".equals(System.getProperty("lombok.installer.fullpath", "true")); boolean installSucceeded = false; StringBuilder newContents = new StringBuilder(); @@ -293,7 +298,7 @@ public class EclipseLocation extends IdeLocation { "I can't read my own jar file. I think you've found a bug in this installer!\nI suggest you restart it " + "and use the 'what do I do' link, to manually install lombok. Also, tell us about this at:\n" + "http://groups.google.com/group/project-lombok - Thanks!", e); - throw new InstallException("I can't write to your " + getTypeName() + " directory at " + name + generateWriteErrorMessage(), e); + throw new InstallException("I can't write to your " + descriptor.getProductName() + " directory at " + name + generateWriteErrorMessage(), e); } } @@ -333,12 +338,15 @@ public class EclipseLocation extends IdeLocation { fis.close(); } - String fullPathToLombok = fullPathRequired ? (lombokJar.getParentFile().getCanonicalPath() + File.separator) : ""; + String pathPrefix; + if (fullPathRequired) { + pathPrefix = lombokJar.getParentFile().getCanonicalPath() + File.separator; + } else { + pathPrefix = pathToLombokJarPrefix; + } newContents.append(String.format( - "-javaagent:%s", escapePath(fullPathToLombok + "lombok.jar"))).append(OS_NEWLINE); - newContents.append(String.format( - "-Xbootclasspath/a:%s", escapePath(fullPathToLombok + "lombok.jar"))).append(OS_NEWLINE); + "-javaagent:%s", escapePath(pathPrefix + "lombok.jar"))).append(OS_NEWLINE); FileOutputStream fos = new FileOutputStream(eclipseIniPath); try { @@ -356,15 +364,14 @@ public class EclipseLocation extends IdeLocation { } if (!installSucceeded) { - throw new InstallException("I can't find the " + getIniFileName() + " file. Is this a real " + getTypeName() + " installation?", null); + throw new InstallException("I can't find the " + descriptor.getIniFileName() + " file. Is this a real " + descriptor.getProductName() + " installation?", null); } - return "If you start " + getTypeName() + " with a custom -vm parameter, you'll need to add:<br>" + - "<code>-vmargs -Xbootclasspath/a:lombok.jar -javaagent:lombok.jar</code><br>" + - "as parameter as well."; + return "If you start " + descriptor.getProductName() + " with a custom -vm parameter, you'll need to add:<br>" + + "<code>-vmargs -javaagent:lombok.jar</code><br>as parameter as well."; } @Override public URL getIdeIcon() { - return EclipseLocation.class.getResource("eclipse.png"); + return descriptor.getIdeIcon(); } } diff --git a/src/installer/lombok/installer/eclipse/EclipseFinder.java b/src/installer/lombok/installer/eclipse/EclipseProductLocationProvider.java index 8a1a689a..b807f02b 100644 --- a/src/installer/lombok/installer/eclipse/EclipseFinder.java +++ b/src/installer/lombok/installer/eclipse/EclipseProductLocationProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2011 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 @@ -22,109 +22,130 @@ package lombok.installer.eclipse; import static java.util.Arrays.asList; +import static lombok.installer.IdeLocation.canonical; import java.io.File; +import java.io.IOException; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.regex.Pattern; -import lombok.installer.IdeFinder; -import lombok.installer.IdeLocation; import lombok.installer.CorruptedIdeLocationException; +import lombok.installer.OsUtils; +import lombok.installer.IdeLocation; +import lombok.installer.IdeLocationProvider; -import org.mangosdk.spi.ProviderFor; +public class EclipseProductLocationProvider implements IdeLocationProvider { + private final EclipseProductDescriptor descriptor; -@ProviderFor(IdeFinder.class) -public class EclipseFinder extends IdeFinder { - /** should be lowercase! */ - protected String getDirName() { - return "eclipse"; - } - - protected String getWindowsExecutableName() { - return "eclipse.exe"; - } - - protected String getUnixExecutableName() { - return "eclipse"; - } - - protected String getMacExecutableName() { - return "Eclipse.app"; + EclipseProductLocationProvider(EclipseProductDescriptor descriptor) { + this.descriptor = descriptor; } - protected IdeLocation createLocation(String guess) throws CorruptedIdeLocationException { - return new EclipseLocationProvider().create0(guess); - } - - protected List<String> getSourceDirsOnWindows() { - return Arrays.asList("\\", "\\Program Files", "\\Program Files (x86)", System.getProperty("user.home", ".")); + @Override public final IdeLocation create(String path) throws CorruptedIdeLocationException { + return create0(path); } /** - * Returns a list of paths of Eclipse installations. + * Create a new EclipseLocation by pointing at either the directory contains the Eclipse executable, or the executable itself, + * or an eclipse.ini file. * - * The search process works by scanning for each 'source dir' for either an eclipse installation or a folder containing the text returned - * by getDirName(). If such a folder is found, this process is applied recursively. On windows, this process is run on each drive letter - * which represents a physical hard disk. If the native windows API call to determine these drive letters fails, only 'C:' is checked. + * @throws NotAnIdeLocationException + * If this isn't an Eclipse executable or a directory with an + * Eclipse executable. */ - private List<String> getSourceDirsOnWindowsWithDriveLetters() { - List<String> driveLetters = asList("C"); - try { - driveLetters = getDrivesOnWindows(); - } catch (Throwable ignore) { - ignore.printStackTrace(); + private IdeLocation create0(String path) throws CorruptedIdeLocationException { + if (path == null) throw new NullPointerException("path"); + String iniName = descriptor.getIniFileName(); + File p = new File(path); + + if (!p.exists()) return null; + if (p.isDirectory()) { + for (String possibleExeName : descriptor.getExecutableNames()) { + File f = new File(p, possibleExeName); + if (f.exists()) return findEclipseIniFromExe(f, 0); + } + + File f = new File(p, iniName); + if (f.exists()) return makeLocation(canonical(p), f); } - List<String> sourceDirs = new ArrayList<String>(); - for (String letter : driveLetters) { - for (String possibleSource : getSourceDirsOnWindows()) { - if (!isDriveSpecificOnWindows(possibleSource)) { - sourceDirs.add(letter + ":" + possibleSource); - } + + if (p.isFile()) { + if (p.getName().equalsIgnoreCase(iniName)) { + return makeLocation(canonical(p.getParentFile()), p); } } - for (String possibleSource : getSourceDirsOnWindows()) { - if (isDriveSpecificOnWindows(possibleSource)) sourceDirs.add(possibleSource); + + if (descriptor.getExecutableNames().contains(p.getName().toLowerCase())) { + return findEclipseIniFromExe(p, 0); } - return sourceDirs; + return null; } - public boolean isDriveSpecificOnWindows(String path) { - return path.length() > 1 && path.charAt(1) == ':'; - } - - protected List<String> getSourceDirsOnMac() { - return Arrays.asList("/Applications", System.getProperty("user.home", ".")); - } - - protected List<String> getSourceDirsOnUnix() { - return Arrays.asList(System.getProperty("user.home", ".")); - } - - private List<File> transformToFiles(List<String> fileNames) { - List<File> files = new ArrayList<File>(); - for (String fileName : fileNames) { - files.add(new File(fileName)); + private IdeLocation findEclipseIniFromExe(File exePath, int loopCounter) throws CorruptedIdeLocationException { + String iniName = descriptor.getIniFileName(); + /* Try looking for eclipse.ini as sibling to the executable */ { + File ini = new File(exePath.getParentFile(), iniName); + if (ini.isFile()) return makeLocation(canonical(exePath), ini); } - return files; + + String macAppName = descriptor.getMacAppName(); + /* Try looking for Eclipse.app/Contents/MacOS/eclipse.ini as sibling to executable; this works on Mac OS X. */ { + File ini = new File(exePath.getParentFile(), macAppName + "/Contents/MacOS/" + iniName); + if (ini.isFile()) return makeLocation(canonical(exePath), ini); + } + + /* Starting with Eclipse Mars (with the oomph installer), the structure has changed, and it's now at Eclipse.app/Contents/Eclipse/eclipse.ini*/ { + File ini = new File(exePath.getParentFile(), macAppName + "/Contents/Eclipse/" + iniName); + if (ini.isFile()) return makeLocation(canonical(exePath), ini); + } + + /* If executable is a soft link, follow it and retry. */ { + if (loopCounter < 50) { + try { + String oPath = exePath.getAbsolutePath(); + String nPath = exePath.getCanonicalPath(); + if (!oPath.equals(nPath)) try { + IdeLocation loc = findEclipseIniFromExe(new File(nPath), loopCounter + 1); + if (loc != null) return loc; + } catch (CorruptedIdeLocationException ignore) { + // Unlinking didn't help find an eclipse, so continue. + } + } catch (IOException ignore) { /* okay, that didn't work, assume it isn't a soft link then. */ } + } + } + + /* If executable is a linux LSB-style path, then look in the usual places that package managers like apt-get use.*/ { + String path = exePath.getAbsolutePath(); + try { + path = exePath.getCanonicalPath(); + } catch (IOException ignore) { /* We'll stick with getAbsolutePath()'s result then. */ } + + String unixAppName = descriptor.getUnixAppName(); + if (path.equals("/usr/bin/" + unixAppName) || path.equals("/bin/" + unixAppName) || path.equals("/usr/local/bin/" + unixAppName)) { + File ini = new File("/usr/lib/" + unixAppName + "/" + iniName); + if (ini.isFile()) return makeLocation(path, ini); + ini = new File("/usr/local/lib/" + unixAppName + "/" + iniName); + if (ini.isFile()) return makeLocation(path, ini); + ini = new File("/usr/local/etc/" + unixAppName + "/" + iniName); + if (ini.isFile()) return makeLocation(path, ini); + ini = new File("/etc/" + iniName); + if (ini.isFile()) return makeLocation(path, ini); + } + } + + /* If we get this far, we lose. */ + return null; } - private List<File> getFlatSourceLocationsOnUnix() { - List<File> dirs = new ArrayList<File>(); - dirs.add(new File("/usr/bin/")); - dirs.add(new File("/usr/local/bin/")); - dirs.add(new File(System.getProperty("user.home", "."), "bin/")); - return dirs; + private IdeLocation makeLocation(String name, File ini) throws CorruptedIdeLocationException { + return new EclipseProductLocation(descriptor, name, ini); } - private List<File> getNestedSourceLocationOnUnix() { - List<File> dirs = new ArrayList<File>(); - dirs.add(new File("/usr/local/share")); - dirs.add(new File("/usr/local")); - dirs.add(new File("/usr/share")); - return dirs; + @Override public Pattern getLocationSelectors() { + return descriptor.getLocationSelectors(); } /** @@ -141,7 +162,7 @@ public class EclipseFinder extends IdeFinder { */ @Override public void findIdes(List<IdeLocation> locations, List<CorruptedIdeLocationException> problems) { - switch (getOS()) { + switch (OsUtils.getOS()) { case WINDOWS: new WindowsFinder().findEclipse(locations, problems); break; @@ -155,17 +176,74 @@ public class EclipseFinder extends IdeFinder { } } + private List<File> transformToFiles(List<String> fileNames) { + List<File> files = new ArrayList<File>(); + for (String fileName : fileNames) { + files.add(new File(fileName)); + } + return files; + } + + private List<File> getFlatSourceLocationsOnUnix() { + List<File> dirs = new ArrayList<File>(); + dirs.add(new File("/usr/bin/")); + dirs.add(new File("/usr/local/bin/")); + dirs.add(new File(System.getProperty("user.home", "."), "bin/")); + return dirs; + } + + private List<File> getNestedSourceLocationOnUnix() { + List<File> dirs = new ArrayList<File>(); + dirs.add(new File("/usr/local/share")); + dirs.add(new File("/usr/local")); + dirs.add(new File("/usr/share")); + return dirs; + } + private class UnixFinder extends DirectoryFinder { UnixFinder() { super(getNestedSourceLocationOnUnix(), getFlatSourceLocationsOnUnix()); } @Override protected String findEclipseOnPlatform(File dir) { - File possible = new File(dir, getUnixExecutableName()); + File possible = new File(dir, descriptor.getUnixAppName()); return (possible.exists()) ? possible.getAbsolutePath() : null; } } + /** + * Returns a list of paths of Eclipse installations. + * + * The search process works by scanning for each 'source dir' for either an eclipse installation or a folder containing the text returned + * by getDirName(). If such a folder is found, this process is applied recursively. On windows, this process is run on each drive letter + * which represents a physical hard disk. If the native windows API call to determine these drive letters fails, only 'C:' is checked. + */ + private List<String> getSourceDirsOnWindowsWithDriveLetters() { + List<String> driveLetters = asList("C"); + try { + driveLetters = OsUtils.getDrivesOnWindows(); + } catch (Throwable ignore) { + ignore.printStackTrace(); + } + List<String> sourceDirs = new ArrayList<String>(); + for (String letter : driveLetters) { + for (String possibleSource : descriptor.getSourceDirsOnWindows()) { + if (!isDriveSpecificOnWindows(possibleSource)) { + sourceDirs.add(letter + ":" + possibleSource); + } + } + } + for (String possibleSource : descriptor.getSourceDirsOnWindows()) { + if (isDriveSpecificOnWindows(possibleSource)) sourceDirs.add(possibleSource); + } + + return sourceDirs; + } + + private boolean isDriveSpecificOnWindows(String path) { + return path.length() > 1 && path.charAt(1) == ':'; + } + private class WindowsFinder extends DirectoryFinder { WindowsFinder() { super(transformToFiles(getSourceDirsOnWindowsWithDriveLetters()), Collections.<File>emptyList()); @@ -174,20 +252,20 @@ public class EclipseFinder extends IdeFinder { /** Checks if the provided directory contains 'eclipse.exe', and if so, returns the directory, otherwise null. */ @Override protected String findEclipseOnPlatform(File dir) { - File possible = new File(dir, getWindowsExecutableName()); + File possible = new File(dir, descriptor.getWindowsExecutableName()); return (possible.isFile()) ? dir.getAbsolutePath() : null; } } private class MacFinder extends DirectoryFinder { MacFinder() { - super(transformToFiles(getSourceDirsOnMac()), Collections.<File>emptyList()); + super(transformToFiles(descriptor.getSourceDirsOnMac()), Collections.<File>emptyList()); } protected String findEclipseOnPlatform(File dir) { - if (dir.getName().toLowerCase().equals(getMacExecutableName().toLowerCase())) return dir.getParent(); - if (dir.getName().toLowerCase().contains(getDirName())) { - if (new File(dir, getMacExecutableName()).exists()) return dir.toString(); + if (dir.getName().toLowerCase().equals(descriptor.getMacAppName().toLowerCase())) return dir.getParent(); + if (dir.getName().toLowerCase().contains(descriptor.getDirectoryName())) { + if (new File(dir, descriptor.getMacAppName()).exists()) return dir.toString(); } return null; } @@ -202,18 +280,18 @@ public class EclipseFinder extends IdeFinder { this.flatSourceDirs = flatSourceDirs; } - public void findEclipse(List<IdeLocation> locations, List<CorruptedIdeLocationException> problems) { + void findEclipse(List<IdeLocation> locations, List<CorruptedIdeLocationException> problems) { for (File dir : nestedSourceDirs) recurseDirectory(locations, problems, dir); for (File dir : flatSourceDirs) findEclipse(locations, problems, dir); } - protected abstract String findEclipseOnPlatform(File dir); + abstract String findEclipseOnPlatform(File dir); - protected void recurseDirectory(List<IdeLocation> locations, List<CorruptedIdeLocationException> problems, File dir) { - recurseDirectory0(locations, problems, dir, 0); + void recurseDirectory(List<IdeLocation> locations, List<CorruptedIdeLocationException> problems, File dir) { + recurseDirectory0(locations, problems, dir, 0, false); } - private void recurseDirectory0(List<IdeLocation> locations, List<CorruptedIdeLocationException> problems, File f, int loopCounter) { + private void recurseDirectory0(List<IdeLocation> locations, List<CorruptedIdeLocationException> problems, File f, int loopCounter, boolean nameFound) { //Various try/catch/ignore statements are in this for loop. Weird conditions on the disk can cause exceptions, //such as an unformatted drive causing a NullPointerException on listFiles. Best action is almost invariably to just //continue onwards. @@ -223,19 +301,19 @@ public class EclipseFinder extends IdeFinder { for (File dir : listFiles) { if (!dir.isDirectory()) continue; try { - if (dir.getName().toLowerCase().contains(getDirName())) { + if (nameFound || dir.getName().toLowerCase().contains(descriptor.getDirectoryName())) { findEclipse(locations, problems, dir); - if (loopCounter < 50) recurseDirectory0(locations, problems, dir, loopCounter + 1); + if (loopCounter < 50) recurseDirectory0(locations, problems, dir, loopCounter + 1, true); } } catch (Exception ignore) {} } } - + private void findEclipse(List<IdeLocation> locations, List<CorruptedIdeLocationException> problems, File dir) { String eclipseLocation = findEclipseOnPlatform(dir); if (eclipseLocation != null) { try { - IdeLocation newLocation = createLocation(eclipseLocation); + IdeLocation newLocation = create(eclipseLocation); if (newLocation != null) locations.add(newLocation); } catch (CorruptedIdeLocationException e) { problems.add(e); diff --git a/src/installer/lombok/installer/eclipse/JbdsFinder.java b/src/installer/lombok/installer/eclipse/JbdsFinder.java deleted file mode 100644 index 2dfaacba..00000000 --- a/src/installer/lombok/installer/eclipse/JbdsFinder.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2013 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.installer.eclipse; - -import java.util.Arrays; -import java.util.List; - -import lombok.installer.CorruptedIdeLocationException; -import lombok.installer.IdeFinder; -import lombok.installer.IdeLocation; - -import org.mangosdk.spi.ProviderFor; - -/** - * JBDS (JBoss Developer Studio) is an eclipse variant. - * Other than different executable names, it's the same as eclipse, as far as lombok support goes. - */ -@ProviderFor(IdeFinder.class) -public class JbdsFinder extends EclipseFinder { - @Override protected IdeLocation createLocation(String guess) throws CorruptedIdeLocationException { - return new JbdsLocationProvider().create0(guess); - } - - @Override protected String getDirName() { - return "studio"; - } - - @Override protected String getMacExecutableName() { - return "jbdevstudio.app"; - } - - @Override protected String getUnixExecutableName() { - return "jbdevstudio"; - } - - @Override protected String getWindowsExecutableName() { - return "jbdevstudio.exe"; - } - - @Override protected List<String> getSourceDirsOnWindows() { - return Arrays.asList("\\", "\\Program Files", "\\Program Files (x86)", System.getProperty("user.home", ".")); - } - - @Override protected List<String> getSourceDirsOnMac() { - return Arrays.asList("/Applications", System.getProperty("user.home", ".")); - } - - @Override protected List<String> getSourceDirsOnUnix() { - return Arrays.asList(System.getProperty("user.home", ".")); - } -} diff --git a/src/installer/lombok/installer/eclipse/JbdsLocationProvider.java b/src/installer/lombok/installer/eclipse/JbdsLocationProvider.java index e6df0e43..635f304a 100644 --- a/src/installer/lombok/installer/eclipse/JbdsLocationProvider.java +++ b/src/installer/lombok/installer/eclipse/JbdsLocationProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 The Project Lombok Authors. + * Copyright (C) 2013-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,49 +21,24 @@ */ package lombok.installer.eclipse; -import java.io.File; -import java.util.Arrays; -import java.util.List; -import java.util.regex.Pattern; +import java.util.Collections; -import lombok.installer.CorruptedIdeLocationException; -import lombok.installer.IdeLocation; import lombok.installer.IdeLocationProvider; -import lombok.installer.IdeFinder.OS; import org.mangosdk.spi.ProviderFor; @ProviderFor(IdeLocationProvider.class) -public class JbdsLocationProvider extends EclipseLocationProvider { - @Override protected List<String> getEclipseExecutableNames() { - return Arrays.asList("jbdevstudio.app", "jbdevstudio.exe", "jbdevstudioc.exe", "jbdevstudio"); - } - - @Override protected String getIniName() { - return "jbdevstudio.ini"; - } - - @Override protected IdeLocation makeLocation(String name, File ini) throws CorruptedIdeLocationException { - return new JbdsLocation(name, ini); - } - - @Override protected String getMacAppName() { - return "jbdevstudio.app"; - } +public class JbdsLocationProvider extends EclipseProductLocationProvider { - @Override protected String getUnixAppName() { - return "jbdevstudio"; - } + private static final EclipseProductDescriptor JBDS = new StandardProductDescriptor( + "JBoss Developer Studio", + "jbdevstudio", + "studio", + JbdsLocationProvider.class.getResource("jbds.png"), + Collections.<String>emptySet() + ); - @Override public Pattern getLocationSelectors(OS os) { - switch (os) { - case MAC_OS_X: - return Pattern.compile("^(jbdevstudio|jbdevstudio\\.ini|jbdevstudio\\.app)$", Pattern.CASE_INSENSITIVE); - case WINDOWS: - return Pattern.compile("^(jbdevstudioc?\\.exe|jbdevstudio\\.ini)$", Pattern.CASE_INSENSITIVE); - default: - case UNIX: - return Pattern.compile("^(jbdevstudio|jbdevstudio\\.ini)$", Pattern.CASE_INSENSITIVE); - } + public JbdsLocationProvider() { + super(JBDS); } } diff --git a/src/installer/lombok/installer/eclipse/JbdsLocation.java b/src/installer/lombok/installer/eclipse/MyEclipseLocationProvider.java index 81fb5261..298cabd6 100644 --- a/src/installer/lombok/installer/eclipse/JbdsLocation.java +++ b/src/installer/lombok/installer/eclipse/MyEclipseLocationProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 The Project Lombok Authors. + * Copyright (C) 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,25 +21,24 @@ */ package lombok.installer.eclipse; -import java.io.File; -import java.net.URL; +import java.util.Collections; -import lombok.installer.CorruptedIdeLocationException; +import lombok.installer.IdeLocationProvider; -public class JbdsLocation extends EclipseLocation { - public JbdsLocation(String nameOfLocation, File pathToEclipseIni) throws CorruptedIdeLocationException { - super(nameOfLocation, pathToEclipseIni); - } - - @Override public URL getIdeIcon() { - return JbdsLocation.class.getResource("jbds.png"); - } +import org.mangosdk.spi.ProviderFor; + +@ProviderFor(IdeLocationProvider.class) +public class MyEclipseLocationProvider extends EclipseProductLocationProvider { - @Override protected String getIniFileName() { - return "jbdevstudio.ini"; - } + private static final EclipseProductDescriptor MY_ECLIPSE = new StandardProductDescriptor( + "MyEclipse", + "myeclipse", + "myeclipse", + MyEclipseLocationProvider.class.getResource("myeclipse.png"), + Collections.<String>emptySet() + ); - @Override protected String getTypeName() { - return "JBoss Developer Studio"; + public MyEclipseLocationProvider() { + super(MY_ECLIPSE); } } diff --git a/src/installer/lombok/installer/eclipse/RhdsLocationProvider.java b/src/installer/lombok/installer/eclipse/RhdsLocationProvider.java new file mode 100644 index 00000000..5e1d303d --- /dev/null +++ b/src/installer/lombok/installer/eclipse/RhdsLocationProvider.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2013-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 + * 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.installer.eclipse; + +import java.util.Collections; + +import lombok.installer.IdeLocationProvider; + +import org.mangosdk.spi.ProviderFor; + +@ProviderFor(IdeLocationProvider.class) +public class RhdsLocationProvider extends EclipseProductLocationProvider { + + private static final EclipseProductDescriptor RHDS = new StandardProductDescriptor( + "Red Hat JBoss Developer Studio", + "devstudio", + "studio", + RhdsLocationProvider.class.getResource("rhds.png"), + Collections.<String>emptySet() + ); + + public RhdsLocationProvider() { + super(RHDS); + } +} diff --git a/src/installer/lombok/installer/eclipse/STS4LocationProvider.java b/src/installer/lombok/installer/eclipse/STS4LocationProvider.java new file mode 100644 index 00000000..47a07bdd --- /dev/null +++ b/src/installer/lombok/installer/eclipse/STS4LocationProvider.java @@ -0,0 +1,44 @@ +/* + * 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.installer.eclipse; + +import java.util.Arrays; +import java.util.Collections; + +import lombok.installer.IdeLocationProvider; + +import org.mangosdk.spi.ProviderFor; + +@ProviderFor(IdeLocationProvider.class) +public class STS4LocationProvider extends EclipseProductLocationProvider { + + private static final EclipseProductDescriptor STS4 = new StandardProductDescriptor("Spring Tools Suite 4", + "SpringToolSuite4", + "sts", + STS4LocationProvider.class.getResource("STS.png"), + Collections.unmodifiableList(Arrays.asList("springsource", "spring-tool-suite")) + ); + + public STS4LocationProvider() { + super(STS4); + } +} diff --git a/src/installer/lombok/installer/eclipse/STSFinder.java b/src/installer/lombok/installer/eclipse/STSFinder.java deleted file mode 100644 index 82bc9b80..00000000 --- a/src/installer/lombok/installer/eclipse/STSFinder.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2009 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.installer.eclipse; - -import java.util.Arrays; -import java.util.List; - -import lombok.installer.CorruptedIdeLocationException; -import lombok.installer.IdeFinder; -import lombok.installer.IdeLocation; - -import org.mangosdk.spi.ProviderFor; - -/** - * STS (Springsource Tool Suite) is an eclipse variant. - * Other than different executable names, it's the same as eclipse, as far as lombok support goes. - */ -@ProviderFor(IdeFinder.class) -public class STSFinder extends EclipseFinder { - @Override protected IdeLocation createLocation(String guess) throws CorruptedIdeLocationException { - return new STSLocationProvider().create0(guess); - } - - @Override protected String getDirName() { - return "sts"; - } - - @Override protected String getMacExecutableName() { - return "STS.app"; - } - - @Override protected String getUnixExecutableName() { - return "STS"; - } - - @Override protected String getWindowsExecutableName() { - return "STS.exe"; - } - - @Override protected List<String> getSourceDirsOnWindows() { - return Arrays.asList("\\", "\\springsource", "\\Program Files", "\\Program Files (x86)", "\\Program Files\\springsource", "\\Program Files (x86)\\springsource", System.getProperty("user.home", "."), System.getProperty("user.home", ".") + "\\springsource"); - } - - @Override protected List<String> getSourceDirsOnMac() { - return Arrays.asList("/Applications", "/Applications/springsource", System.getProperty("user.home", "."), System.getProperty("user.home", ".") + "/springsource"); - } - - @Override protected List<String> getSourceDirsOnUnix() { - return Arrays.asList(System.getProperty("user.home", "."), System.getProperty("user.home", ".") + "/springsource"); - } -} diff --git a/src/installer/lombok/installer/eclipse/STSLocationProvider.java b/src/installer/lombok/installer/eclipse/STSLocationProvider.java index 7d129838..d2efb956 100644 --- a/src/installer/lombok/installer/eclipse/STSLocationProvider.java +++ b/src/installer/lombok/installer/eclipse/STSLocationProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009 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,49 +21,23 @@ */ package lombok.installer.eclipse; -import java.io.File; -import java.util.Arrays; -import java.util.List; -import java.util.regex.Pattern; +import java.util.Collections; -import lombok.installer.CorruptedIdeLocationException; -import lombok.installer.IdeLocation; import lombok.installer.IdeLocationProvider; -import lombok.installer.IdeFinder.OS; import org.mangosdk.spi.ProviderFor; @ProviderFor(IdeLocationProvider.class) -public class STSLocationProvider extends EclipseLocationProvider { - @Override protected List<String> getEclipseExecutableNames() { - return Arrays.asList("sts.app", "sts.exe", "stsc.exe", "sts"); - } - - @Override protected String getIniName() { - return "STS.ini"; - } - - @Override protected IdeLocation makeLocation(String name, File ini) throws CorruptedIdeLocationException { - return new STSLocation(name, ini); - } - - @Override protected String getMacAppName() { - return "STS.app"; - } +public class STSLocationProvider extends EclipseProductLocationProvider { - @Override protected String getUnixAppName() { - return "STS"; - } + private static final EclipseProductDescriptor STS = new StandardProductDescriptor("STS", + "STS", + "sts", + STSLocationProvider.class.getResource("STS.png"), + Collections.singleton("springsource") + ); - @Override public Pattern getLocationSelectors(OS os) { - switch (os) { - case MAC_OS_X: - return Pattern.compile("^(sts|sts\\.ini|sts\\.app)$", Pattern.CASE_INSENSITIVE); - case WINDOWS: - return Pattern.compile("^(stsc?\\.exe|sts\\.ini)$", Pattern.CASE_INSENSITIVE); - default: - case UNIX: - return Pattern.compile("^(sts|sts\\.ini)$", Pattern.CASE_INSENSITIVE); - } + public STSLocationProvider() { + super(STS); } } diff --git a/src/installer/lombok/installer/eclipse/StandardProductDescriptor.java b/src/installer/lombok/installer/eclipse/StandardProductDescriptor.java new file mode 100644 index 00000000..47e103aa --- /dev/null +++ b/src/installer/lombok/installer/eclipse/StandardProductDescriptor.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) 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 + * 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.installer.eclipse; + +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.regex.Pattern; + +import lombok.installer.OsUtils; + +public class StandardProductDescriptor implements EclipseProductDescriptor { + + private static final String USER_HOME = System.getProperty("user.home", "."); + private static final String[] WINDOWS_ROOTS = {"\\", "\\Program Files", "\\Program Files (x86)", USER_HOME}; + private static final String[] MAC_ROOTS = {"/Applications", USER_HOME}; + private static final String[] UNIX_ROOTS = {USER_HOME}; + + private final String productName; + private final String windowsName; + private final String unixName; + private final String macAppName; + private final List<String> executableNames; + private final List<String> sourceDirsOnWindows; + private final List<String> sourceDirsOnMac; + private final List<String> sourceDirsOnUnix; + private final String iniFileName; + private final Pattern locationSelectors; + private final String directoryName; + private final URL ideIcon; + + public StandardProductDescriptor(String productName, String baseName, String directoryName, URL ideIcon, Collection<String> alternativeDirectoryNames) { + this.productName = productName; + this.windowsName = baseName + ".exe"; + this.unixName = baseName; + this.macAppName = baseName + ".app"; + this.executableNames = executableNames(baseName); + this.sourceDirsOnWindows = generateAlternatives(WINDOWS_ROOTS, "\\", alternativeDirectoryNames); + this.sourceDirsOnMac = generateAlternatives(MAC_ROOTS, "/", alternativeDirectoryNames); + this.sourceDirsOnUnix = generateAlternatives(UNIX_ROOTS, "/", alternativeDirectoryNames); + this.iniFileName = baseName + ".ini"; + this.locationSelectors = getLocationSelectors(baseName); + this.directoryName = directoryName.toLowerCase(); + this.ideIcon = ideIcon; + } + + @Override public String getProductName() { + return productName; + } + + @Override public String getWindowsExecutableName() { + return windowsName; + } + + @Override public String getUnixAppName() { + return unixName; + } + + @Override public String getMacAppName() { + return macAppName; + } + + @Override public String getDirectoryName() { + return directoryName; + } + + @Override public List<String> getExecutableNames() { + return executableNames; + } + + @Override public List<String> getSourceDirsOnWindows() { + return sourceDirsOnWindows; + } + + @Override public List<String> getSourceDirsOnMac() { + return sourceDirsOnMac; + } + + @Override public List<String> getSourceDirsOnUnix() { + return sourceDirsOnUnix; + } + + @Override public String getIniFileName() { + return iniFileName; + } + + @Override public Pattern getLocationSelectors() { + return locationSelectors; + } + + @Override public URL getIdeIcon() { + return ideIcon; + } + + private static Pattern getLocationSelectors(String baseName) { + return Pattern.compile(String.format(platformPattern(), baseName.toLowerCase()), Pattern.CASE_INSENSITIVE); + } + + private static String platformPattern() { + switch (OsUtils.getOS()) { + case MAC_OS_X: + return "^(%s|%<s\\.ini|%<s\\.app)$"; + case WINDOWS: + return "^(%sc?\\.exe|%<s\\.ini)$"; + default: + case UNIX: + return "^(%s|%<s\\.ini)$"; + } + } + + private static List<String> executableNames(String baseName) { + String base = baseName.toLowerCase(); + return Collections.unmodifiableList(Arrays.asList(base, base + ".app", base + ".exe", base + "c.exe")); + } + + private static List<String> generateAlternatives(String[] roots, String pathSeparator, Collection<String> alternatives) { + List<String> result = new ArrayList<String>(); + for (String root : roots) { + result.add(concat(root, pathSeparator, "")); + for (String alternative : alternatives) { + result.add(concat(root, pathSeparator, alternative)); + } + } + return Collections.unmodifiableList(result); + } + + private static String concat(String base, String pathSeparator, String alternative) { + if (alternative.isEmpty()) { + return base; + } + if (base.endsWith(pathSeparator)) { + return base + alternative.replaceAll("[\\/]", "\\" + pathSeparator); + } + return base + pathSeparator + alternative.replaceAll("[\\/]", "\\" + pathSeparator); + } +} diff --git a/src/installer/lombok/installer/eclipse/myeclipse.png b/src/installer/lombok/installer/eclipse/myeclipse.png Binary files differnew file mode 100644 index 00000000..49c4fab3 --- /dev/null +++ b/src/installer/lombok/installer/eclipse/myeclipse.png diff --git a/src/installer/lombok/installer/eclipse/rhds.png b/src/installer/lombok/installer/eclipse/rhds.png Binary files differnew file mode 100644 index 00000000..ca7738e6 --- /dev/null +++ b/src/installer/lombok/installer/eclipse/rhds.png diff --git a/src/j9stubs/org/mapstruct/ap/spi/AstModifyingAnnotationProcessor.java b/src/j9stubs/org/mapstruct/ap/spi/AstModifyingAnnotationProcessor.java new file mode 100644 index 00000000..ffb99030 --- /dev/null +++ b/src/j9stubs/org/mapstruct/ap/spi/AstModifyingAnnotationProcessor.java @@ -0,0 +1,48 @@ +/** + * Copyright 2012-2017 Gunnar Morling (http://www.gunnarmorling.de/) + * and/or other contributors as indicated by the @authors tag. See the + * copyright.txt file in the distribution for a full listing of all + * contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.mapstruct.ap.spi; + +import javax.lang.model.type.TypeMirror; + +/** + * A contract to be implemented by other annotation processors which - against the design philosophy of JSR 269 - alter + * the types under compilation. + * <p> + * This contract will be queried by MapStruct when examining types referenced by mappers to be generated, most notably + * the source and target types of mapping methods. If at least one AST-modifying processor announces further changes to + * such type, the generation of the affected mapper(s) will be deferred to a future round in the annnotation processing + * cycle. + * <p> + * Implementations are discovered via the service loader, i.e. a JAR providing an AST-modifying processor needs to + * declare its implementation in a file {@code META-INF/services/org.mapstruct.ap.spi.AstModifyingAnnotationProcessor}. + * + * @author Gunnar Morling + */ +//@org.mapstruct.util.Experimental +public interface AstModifyingAnnotationProcessor { + + /** + * Whether the specified type has been fully processed by this processor or not (i.e. this processor will amend the + * given type's structure after this invocation). + * + * @param type The type of interest + * @return {@code true} if this processor has fully processed the given type, {@code false} otherwise. + */ + boolean isTypeComplete(TypeMirror type); +}
\ No newline at end of file diff --git a/src/launch/lombok/launch/Agent.java b/src/launch/lombok/launch/Agent.java new file mode 100644 index 00000000..7989e51f --- /dev/null +++ b/src/launch/lombok/launch/Agent.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 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.launch; + +import java.lang.instrument.Instrumentation; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +final class Agent { + public static void agentmain(String agentArgs, Instrumentation instrumentation) throws Throwable { + runLauncher(agentArgs, instrumentation, true); + } + + public static void premain(String agentArgs, Instrumentation instrumentation) throws Throwable { + runLauncher(agentArgs, instrumentation, false); + } + + private static void runLauncher(String agentArgs, Instrumentation instrumentation, boolean injected) throws Throwable { + ClassLoader cl = Main.createShadowClassLoader(); + try { + Class<?> c = cl.loadClass("lombok.core.AgentLauncher"); + Method m = c.getDeclaredMethod("runAgents", String.class, Instrumentation.class, boolean.class, Class.class); + m.invoke(null, agentArgs, instrumentation, injected, Agent.class); + } catch (InvocationTargetException e) { + throw e.getCause(); + } + } +} diff --git a/src/launch/lombok/launch/AnnotationProcessor.java b/src/launch/lombok/launch/AnnotationProcessor.java new file mode 100644 index 00000000..c4f922b9 --- /dev/null +++ b/src/launch/lombok/launch/AnnotationProcessor.java @@ -0,0 +1,129 @@ +/* + * 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.launch; + +import java.lang.reflect.Field; +import java.util.Set; + +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.Completion; +import javax.annotation.processing.ProcessingEnvironment; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; + +import org.mapstruct.ap.spi.AstModifyingAnnotationProcessor; + +import sun.misc.Unsafe; + +class AnnotationProcessorHider { + public static class AstModificationNotifier implements AstModifyingAnnotationProcessor { + @Override public boolean isTypeComplete(TypeMirror type) { + if (System.getProperty("lombok.disable") != null) return true; + return AstModificationNotifierData.lombokInvoked; + } + } + + static class AstModificationNotifierData { + volatile static boolean lombokInvoked = false; + } + + public static class AnnotationProcessor extends AbstractProcessor { + private final AbstractProcessor instance = createWrappedInstance(); + + @Override public Set<String> getSupportedOptions() { + return instance.getSupportedOptions(); + } + + @Override public Set<String> getSupportedAnnotationTypes() { + return instance.getSupportedAnnotationTypes(); + } + + @Override public SourceVersion getSupportedSourceVersion() { + return instance.getSupportedSourceVersion(); + } + + @Override public void init(ProcessingEnvironment processingEnv) { + disableJava9SillyWarning(); + AstModificationNotifierData.lombokInvoked = true; + instance.init(processingEnv); + super.init(processingEnv); + } + + // sunapi suppresses javac's warning about using Unsafe; 'all' suppresses eclipse's warning about the unspecified 'sunapi' key. Leave them both. + // Yes, javac's definition of the word 'all' is quite contrary to what the dictionary says it means. 'all' does NOT include 'sunapi' according to javac. + @SuppressWarnings({"sunapi", "all"}) + private void disableJava9SillyWarning() { + // JVM9 complains about using reflection to access packages from a module that aren't exported. This makes no sense; the whole point of reflection + // is to get past such issues. The only comment from the jigsaw team lead on this was some unspecified mumbling about security which makes no sense, + // as the SecurityManager is invoked to check such things. Therefore this warning is a bug, so we shall patch java to fix it. + + try { + Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe"); + theUnsafe.setAccessible(true); + Unsafe u = (Unsafe) theUnsafe.get(null); + + Class<?> cls = Class.forName("jdk.internal.module.IllegalAccessLogger"); + Field logger = cls.getDeclaredField("logger"); + u.putObjectVolatile(cls, u.staticFieldOffset(logger), null); + } catch (Throwable t) { + // We shall ignore it; the effect of this code failing is that the user gets to see a warning they remove with various --add-opens magic. + } + } + + @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { + return instance.process(annotations, roundEnv); + } + + @Override public Iterable<? extends Completion> getCompletions(Element element, AnnotationMirror annotation, ExecutableElement member, String userText) { + return instance.getCompletions(element, annotation, member, userText); + } + + private static AbstractProcessor createWrappedInstance() { + ClassLoader cl = Main.createShadowClassLoader(); + try { + Class<?> mc = cl.loadClass("lombok.core.AnnotationProcessor"); + return (AbstractProcessor) mc.getDeclaredConstructor().newInstance(); + } catch (Throwable t) { + if (t instanceof Error) throw (Error) t; + if (t instanceof RuntimeException) throw (RuntimeException) t; + throw new RuntimeException(t); + } + } + } + + @SupportedAnnotationTypes("lombok.*") + public static class ClaimingProcessor extends AbstractProcessor { + @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { + return true; + } + + @Override public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latest(); + } + } +} diff --git a/src/launch/lombok/launch/Main.java b/src/launch/lombok/launch/Main.java new file mode 100644 index 00000000..b81b6268 --- /dev/null +++ b/src/launch/lombok/launch/Main.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2014-2015 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.launch; + +import java.lang.reflect.InvocationTargetException; +import java.util.Arrays; + +class Main { + static ClassLoader createShadowClassLoader() { + return new ShadowClassLoader(Main.class.getClassLoader(), "lombok", null, Arrays.<String>asList(), Arrays.asList("lombok.patcher.Symbols")); + } + + public static void main(String[] args) throws Throwable { + ClassLoader cl = createShadowClassLoader(); + Class<?> mc = cl.loadClass("lombok.core.Main"); + try { + mc.getMethod("main", String[].class).invoke(null, new Object[] {args}); + } catch (InvocationTargetException e) { + throw e.getCause(); + } + } +} diff --git a/src/launch/lombok/launch/ShadowClassLoader.java b/src/launch/lombok/launch/ShadowClassLoader.java new file mode 100644 index 00000000..72f006e8 --- /dev/null +++ b/src/launch/lombok/launch/ShadowClassLoader.java @@ -0,0 +1,485 @@ +/* + * Copyright (C) 2014-2015 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.launch; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.net.URLDecoder; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Vector; +import java.util.WeakHashMap; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +/** + * The shadow classloader serves to completely hide almost all classes in a given jar file by using a different file ending. + * + * The shadow classloader also serves to link in a project as it is being developed (a 'bin' dir from an IDE for example). + * <p> + * Classes loaded by the shadowloader use ".SCL.<em>sclSuffix</em>" in addition to ".class". In other words, most of the class files in a given jar end in this suffix, which + * serves to hide them from any tool that isn't aware of the suffix (such as IDEs generating auto-complete dialogs, and javac's classpath in general). Only shadowloader can actually + * load these classes. + * <p> + * The shadowloader will pick up an alternate (priority) classpath, using normal class files, from the system property "<code>shadow.override.<em>sclSuffix</em></code>". + * This shadow classpath looks just like a normal java classpath; the path separator is applied (semi-colon on windows, colon elsewhere), and entries can consist of directories, + * jar files, or directories ending in "/*" to pick up all jars inside it. + * <p> + * Load order is as follows if at least one override is present: + * <li>First, if the resource is found in one of the paths stated in the shadow classpath, find that. + * <li>Next, ask the <code>parent</code> loader, which is passed during construction of the ShadowClassLoader. + * <li>Notably, this jar's contents are always skipped! (The idea of the shadow classpath is that this jar only functions as a launcher, not as a source of your actual application). + * </ul> + * + * If no overrides are present, the load order is as follows: + * <li>First, if the resource is found in our own jar (trying ".SCL.<em>sclSuffix</em>" first for any resource request ending in ".class"), return that. + * <li>Next, ask the <code>parent</code> loader. + * </ul> + * + * Use ShadowClassLoader to accomplish the following things:<ul> + * <li>Avoid contaminating the namespace of any project using an SCL-based jar. Autocompleters in IDEs will NOT suggest anything other than actual public API. + * <li>Like jarjar, allows folding in dependencies such as ASM without foisting these dependencies on projects that use this jar. shadowloader obviates the need for jarjar. + * <li>Allows an agent (which MUST be in jar form) to still load everything except this loader infrastructure from class files generated by the IDE, which should + * considerably help debugging, as you can now rely on the IDE's built-in auto-recompile features instead of having to run a full build everytime, and it should help + * with hot code replace and the like (this is what the {@code shadow.override} feature is for). + * </ul> + * + * Implementation note: {@code lombok.eclipse.agent.EclipseLoaderPatcher} <em>relies</em> on this class having no dependencies on any other class except the JVM boot class, notably + * including any other classes in this package, <strong>including</strong> inner classes. So, don't write closures, anonymous inner class literals, + * enums, or anything else that could cause the compilation of this file to produce more than 1 class file. In general, actually passing load control to this loader is a bit tricky + * so ensure that this class has zero dependencies on anything except java core classes. + */ +class ShadowClassLoader extends ClassLoader { + private static final String SELF_NAME = "lombok/launch/ShadowClassLoader.class"; + private static final ConcurrentMap<String, Class<?>> highlanderMap = new ConcurrentHashMap<String, Class<?>>(); + + private final String SELF_BASE; + private final File SELF_BASE_FILE; + private final int SELF_BASE_LENGTH; + + private final List<File> override = new ArrayList<File>(); + private final String sclSuffix; + private final List<String> parentExclusion = new ArrayList<String>(); + private final List<String> highlanders = new ArrayList<String>(); + + /** + * @param source The 'parent' classloader. + * @param sclSuffix The suffix of the shadowed class files in our own jar. For example, if this is {@code lombok}, then the class files in your jar should be {@code foo/Bar.SCL.lombok} and not {@code foo/Bar.class}. + * @param selfBase The (preferably absolute) path to our own jar. This jar will be searched for class/SCL.sclSuffix files. + * @param parentExclusion For example {@code "lombok."}; upon invocation of loadClass of this loader, the parent loader ({@code source}) will NOT be invoked if the class to be loaded begins with anything in the parent exclusion list. No exclusion is applied for getResource(s). + * @param highlanders SCL will put in extra effort to ensure that these classes (in simple class spec, so {@code foo.bar.baz.ClassName}) are only loaded once as a class, even if many different classloaders try to load classes, such as equinox/OSGi. + */ + ShadowClassLoader(ClassLoader source, String sclSuffix, String selfBase, List<String> parentExclusion, List<String> highlanders) { + super(source); + this.sclSuffix = sclSuffix; + if (parentExclusion != null) for (String pe : parentExclusion) { + pe = pe.replace(".", "/"); + if (!pe.endsWith("/")) pe = pe + "/"; + this.parentExclusion.add(pe); + } + if (highlanders != null) for (String hl : highlanders) { + this.highlanders.add(hl); + } + + if (selfBase != null) { + SELF_BASE = selfBase; + SELF_BASE_LENGTH = selfBase.length(); + } else { + URL sclClassUrl = ShadowClassLoader.class.getResource("ShadowClassLoader.class"); + String sclClassStr = sclClassUrl == null ? null : sclClassUrl.toString(); + if (sclClassStr == null || !sclClassStr.endsWith(SELF_NAME)) { + ClassLoader cl = ShadowClassLoader.class.getClassLoader(); + throw new RuntimeException("ShadowLoader can't find itself. SCL loader type: " + (cl == null ? "*NULL*" : cl.getClass().toString())); + } + SELF_BASE_LENGTH = sclClassStr.length() - SELF_NAME.length(); + String decoded; + try { + decoded = URLDecoder.decode(sclClassStr.substring(0, SELF_BASE_LENGTH), "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new InternalError("UTF-8 not available"); + } + SELF_BASE = decoded; + } + + if (SELF_BASE.startsWith("jar:file:") && SELF_BASE.endsWith("!/")) SELF_BASE_FILE = new File(SELF_BASE.substring(9, SELF_BASE.length() - 2)); + else if (SELF_BASE.startsWith("file:")) SELF_BASE_FILE = new File(SELF_BASE.substring(5)); + else SELF_BASE_FILE = new File(SELF_BASE); + String scl = System.getProperty("shadow.override." + sclSuffix); + if (scl != null && !scl.isEmpty()) { + for (String part : scl.split("\\s*" + (File.pathSeparatorChar == ';' ? ";" : ":") + "\\s*")) { + if (part.endsWith("/*") || part.endsWith(File.separator + "*")) { + addOverrideJarDir(part.substring(0, part.length() - 2)); + } else { + addOverrideClasspathEntry(part); + } + } + } + } + + private final Map<String, Object> mapJarPathToTracker = new HashMap<String, Object>(); + private static final Map<Object, String> mapTrackerToJarPath = new WeakHashMap<Object, String>(); + private static final Map<Object, Set<String>> mapTrackerToJarContents = new WeakHashMap<Object, Set<String>>(); + + /** + * This cache ensures that any given jar file is only opened once in order to determine the full contents of it. + * We use 'trackers' to make sure that the bulk of the memory taken up by this cache (the list of strings representing the content of a jar file) + * gets garbage collected if all ShadowClassLoaders that ever tried to request a listing of this jar file, are garbage collected. + */ + private Set<String> getOrMakeJarListing(final String absolutePathToJar) { + synchronized (mapTrackerToJarPath) { + /* + * 1) Check our private instance JarPath-to-Tracker Mappings: + */ + Object ourTracker = mapJarPathToTracker.get(absolutePathToJar); + if (ourTracker != null) { + /* + * Yes, we are already tracking this Jar. Just return its contents... + */ + return mapTrackerToJarContents.get(ourTracker); + } + + /* + * 2) Not tracked by us as yet. Check statically whether others have tracked this JarPath: + */ + for (Entry<Object, String> entry : mapTrackerToJarPath.entrySet()) { + if (entry.getValue().equals(absolutePathToJar)) { + /* + * Yes, 3rd party is tracking this jar. We must track too, then return its contents. + */ + Object otherTracker = entry.getKey(); + mapJarPathToTracker.put(absolutePathToJar, otherTracker); + return mapTrackerToJarContents.get(otherTracker); + } + } + + /* + * 3) Not tracked by anyone so far. Build, publish, track & return Jar contents... + */ + Object newTracker = new Object(); + Set<String> jarMembers = getJarMemberSet(absolutePathToJar); + + mapTrackerToJarContents.put(newTracker, jarMembers); + mapTrackerToJarPath.put(newTracker, absolutePathToJar); + mapJarPathToTracker.put(absolutePathToJar, newTracker); + + return jarMembers; + } + } + + /** + * Return a {@link Set} of members in the Jar identified by {@code absolutePathToJar}. + * + * @param absolutePathToJar Cache key + * @return a Set with the Jar member-names + */ + private Set<String> getJarMemberSet(String absolutePathToJar) { + /* + * Note: + * Our implementation returns a HashSet. initialCapacity and loadFactor are carefully tweaked for speed and RAM optimization purposes. + * + * Benchmark: + * The HashSet implementation is about 10% slower to build (only happens once) than the ArrayList. + * The HashSet with shiftBits = 1 was about 33 times(!) faster than the ArrayList for retrievals. + */ + try { + int shiftBits = 1; // (fast, but big) 0 <= shiftBits <= 5, say (slower & compact) + JarFile jar = new JarFile(absolutePathToJar); + + /* + * Find the first power of 2 >= JarSize (as calculated in HashSet constructor) + */ + int jarSizePower2 = Integer.highestOneBit(jar.size()); + if (jarSizePower2 != jar.size()) jarSizePower2 <<= 1; + if (jarSizePower2 == 0) jarSizePower2 = 1; + + Set<String> jarMembers = new HashSet<String>(jarSizePower2 >> shiftBits, 1 << shiftBits); + try { + Enumeration<JarEntry> entries = jar.entries(); + while (entries.hasMoreElements()) { + JarEntry jarEntry = entries.nextElement(); + if (jarEntry.isDirectory()) continue; + jarMembers.add(jarEntry.getName()); + } + } catch (Exception ignore) { + // ignored; if the jar can't be read, treating it as if the jar contains no classes is just what we want. + } finally { + jar.close(); + } + return jarMembers; + } + catch (Exception newJarFileException) { + return Collections.emptySet(); + } + } + + /** + * Looks up {@code altName} in {@code location}, and if that isn't found, looks up {@code name}; {@code altName} can be null in which case it is skipped. + */ + private URL getResourceFromLocation(String name, String altName, File location) { + if (location.isDirectory()) { + try { + if (altName != null) { + File f = new File(location, altName); + if (f.isFile() && f.canRead()) return f.toURI().toURL(); + } + + File f = new File(location, name); + if (f.isFile() && f.canRead()) return f.toURI().toURL(); + return null; + } catch (MalformedURLException e) { + return null; + } + } + + if (!location.isFile() || !location.canRead()) return null; + + File absoluteFile; { + try { + absoluteFile = location.getCanonicalFile(); + } catch (Exception e) { + absoluteFile = location.getAbsoluteFile(); + } + } + Set<String> jarContents = getOrMakeJarListing(absoluteFile.getAbsolutePath()); + + String absoluteUri = absoluteFile.toURI().toString(); + + try { + if (jarContents.contains(altName)) { + return new URI("jar:" + absoluteUri + "!/" + altName).toURL(); + } + } catch (Exception ignore) { + // intentional fallthrough + } + + try { + if (jarContents.contains(name)) { + return new URI("jar:" + absoluteUri + "!/" + name).toURL(); + } + } catch(Exception ignore) { + // intentional fallthrough + } + + return null; + } + + /** + * Checks if the stated item is located inside the same classpath root as the jar that hosts ShadowClassLoader.class. {@code item} and {@code name} refer to the same thing. + */ + private boolean inOwnBase(URL item, String name) { + if (item == null) return false; + String itemString = item.toString(); + return (itemString.length() == SELF_BASE_LENGTH + name.length()) && SELF_BASE.regionMatches(0, itemString, 0, SELF_BASE_LENGTH); + } + + @Override public Enumeration<URL> getResources(String name) throws IOException { + String altName = null; + if (name.endsWith(".class")) altName = name.substring(0, name.length() - 6) + ".SCL." + sclSuffix; + + // Vector? Yes, we need one: + // * We can NOT make inner classes here (this class is loaded with special voodoo magic in eclipse, as a one off, it's not a full loader. + // * We need to return an enumeration. + // * We can't make one on the fly. + // * ArrayList can't make these. + Vector<URL> vector = new Vector<URL>(); + + for (File ce : override) { + URL url = getResourceFromLocation(name, altName, ce); + if (url != null) vector.add(url); + } + + if (override.isEmpty()) { + URL fromSelf = getResourceFromLocation(name, altName, SELF_BASE_FILE); + if (fromSelf != null) vector.add(fromSelf); + } + + Enumeration<URL> sec = super.getResources(name); + while (sec.hasMoreElements()) { + URL item = sec.nextElement(); + if (!inOwnBase(item, name)) vector.add(item); + } + + if (altName != null) { + Enumeration<URL> tern = super.getResources(altName); + while (tern.hasMoreElements()) { + URL item = tern.nextElement(); + if (!inOwnBase(item, altName)) vector.add(item); + } + } + + return vector.elements(); + } + + @Override public URL getResource(String name) { + return getResource_(name, false); + } + + private URL getResource_(String name, boolean noSuper) { + String altName = null; + if (name.endsWith(".class")) altName = name.substring(0, name.length() - 6) + ".SCL." + sclSuffix; + for (File ce : override) { + URL url = getResourceFromLocation(name, altName, ce); + if (url != null) return url; + } + + if (!override.isEmpty()) { + if (noSuper) return null; + if (altName != null) { + try { + URL res = getResourceSkippingSelf(altName); + if (res != null) return res; + } catch (IOException ignore) {} + } + + try { + return getResourceSkippingSelf(name); + } catch (IOException e) { + return null; + } + } + + URL url = getResourceFromLocation(name, altName, SELF_BASE_FILE); + if (url != null) return url; + + if (altName != null) { + URL res = super.getResource(altName); + if (res != null && (!noSuper || inOwnBase(res, altName))) return res; + } + + URL res = super.getResource(name); + if (res != null && (!noSuper || inOwnBase(res, name))) return res; + return null; + } + + private boolean exclusionListMatch(String name) { + for (String pe : parentExclusion) { + if (name.startsWith(pe)) return true; + } + return false; + } + + private URL getResourceSkippingSelf(String name) throws IOException { + URL candidate = super.getResource(name); + if (candidate == null) return null; + if (!inOwnBase(candidate, name)) return candidate; + + Enumeration<URL> en = super.getResources(name); + while (en.hasMoreElements()) { + candidate = en.nextElement(); + if (!inOwnBase(candidate, name)) return candidate; + } + + return null; + } + + @Override public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { + { + Class<?> alreadyLoaded = findLoadedClass(name); + if (alreadyLoaded != null) return alreadyLoaded; + } + + if (highlanders.contains(name)) { + Class<?> c = highlanderMap.get(name); + if (c != null) return c; + } + + String fileNameOfClass = name.replace(".", "/") + ".class"; + URL res = getResource_(fileNameOfClass, true); + if (res == null) { + if (!exclusionListMatch(fileNameOfClass)) return super.loadClass(name, resolve); + throw new ClassNotFoundException(name); + } + + byte[] b; + int p = 0; + try { + InputStream in = res.openStream(); + + try { + b = new byte[65536]; + while (true) { + int r = in.read(b, p, b.length - p); + if (r == -1) break; + p += r; + if (p == b.length) { + byte[] nb = new byte[b.length * 2]; + System.arraycopy(b, 0, nb, 0, p); + b = nb; + } + } + } finally { + in.close(); + } + } catch (IOException e) { + throw new ClassNotFoundException("I/O exception reading class " + name, e); + } + + Class<?> c; + try { + c = defineClass(name, b, 0, p); + } catch (LinkageError e) { + if (highlanders.contains(name)) { + Class<?> alreadyDefined = highlanderMap.get(name); + if (alreadyDefined != null) return alreadyDefined; + } + try { + c = this.findLoadedClass(name); + } catch (LinkageError e2) { + throw e; + } + if (c == null) throw e; + } + + if (highlanders.contains(name)) { + Class<?> alreadyDefined = highlanderMap.putIfAbsent(name, c); + if (alreadyDefined != null) c = alreadyDefined; + } + + if (resolve) resolveClass(c); + return c; + } + + public void addOverrideJarDir(String dir) { + File f = new File(dir); + for (File j : f.listFiles()) { + if (j.getName().toLowerCase().endsWith(".jar") && j.canRead() && j.isFile()) override.add(j); + } + } + + public void addOverrideClasspathEntry(String entry) { + override.add(new File(entry)); + } +} diff --git a/src/stubs/com/sun/tools/javac/code/Symbol.java b/src/stubs/com/sun/tools/javac/code/Symbol.java new file mode 100644 index 00000000..4aef63ad --- /dev/null +++ b/src/stubs/com/sun/tools/javac/code/Symbol.java @@ -0,0 +1,84 @@ +/* + * These are stub versions of various bits of javac-internal API (for various different versions of javac). Lombok is compiled against these. + */ +package com.sun.tools.javac.code; + +import java.lang.annotation.Annotation; +import java.util.Set; + +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ElementVisitor; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.NestingKind; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.TypeParameterElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; + +import com.sun.tools.javac.util.Name; + +public abstract class Symbol implements Element { + public Type type; + public Name name; + + public long flags() { return 0; } + public boolean isStatic() { return false; } + public boolean isConstructor() { return false; } + public boolean isLocal() { return false; } + public Name flatName() { return null; } + public Name getQualifiedName() { return null; } + public <A extends Annotation> A[] getAnnotationsByType(Class<A> annoType) { return null; } + @Override public java.util.List<Attribute.Compound> getAnnotationMirrors() { return null; } + @Override public TypeMirror asType() { return null; } + public <A extends java.lang.annotation.Annotation> A getAnnotation(Class<A> annoType) { return null; } + @Override public Name getSimpleName() { return null; } + @Override public java.util.List<Symbol> getEnclosedElements() { return null; } + @Override public Element getEnclosingElement() { return null; } + + public static abstract class TypeSymbol extends Symbol {} + + public static class MethodSymbol extends Symbol implements ExecutableElement { + public MethodSymbol(long flags, Name name, Type type, Symbol owner) {} + @Override public ElementKind getKind() { return null; } + @Override public Set<Modifier> getModifiers() { return null; } + @Override public <R, P> R accept(ElementVisitor<R, P> v, P p) { return null; } + @Override public java.util.List<? extends TypeParameterElement> getTypeParameters() { return null; } + @Override public TypeMirror getReturnType() { return null; } + @Override public java.util.List<? extends VariableElement> getParameters() { return null; } + @Override public boolean isVarArgs() { return false; } + @Override public java.util.List<? extends TypeMirror> getThrownTypes() { return null; } + @Override public AnnotationValue getDefaultValue() { return null; } + public TypeMirror getReceiverType() { return null; } + public boolean isDefault() { return false; } + public com.sun.tools.javac.util.List<VarSymbol> params() { return null; } + } + + public static class VarSymbol extends Symbol implements VariableElement { + public Type type; + @Override public ElementKind getKind() { return null; } + @Override public Set<Modifier> getModifiers() { return null; } + @Override public <R, P> R accept(ElementVisitor<R, P> v, P p) { return null; } + @Override public Object getConstantValue() { return null; } + } + + public static class ClassSymbol extends TypeSymbol implements TypeElement { + @Override public Name getQualifiedName() { return null; } + @Override public java.util.List<? extends TypeMirror> getInterfaces() { return null; } + @Override public TypeMirror getSuperclass() { return null; } + @Override public ElementKind getKind() { return null; } + @Override public Set<Modifier> getModifiers() { return null; } + @Override public NestingKind getNestingKind() { return null; } + @Override public <R, P> R accept(ElementVisitor<R, P> v, P p) { return null; } + @Override public java.util.List<? extends TypeParameterElement> getTypeParameters() { return null; } + } + + // JDK9 + public static class ModuleSymbol extends TypeSymbol { + @Override public ElementKind getKind() { return null; } + @Override public Set<Modifier> getModifiers() { return null; } + @Override public <R, P> R accept(ElementVisitor<R, P> v, P p) { return null; } + } +} diff --git a/src/stubs/com/sun/tools/javac/code/Symtab.java b/src/stubs/com/sun/tools/javac/code/Symtab.java new file mode 100644 index 00000000..2b524e4c --- /dev/null +++ b/src/stubs/com/sun/tools/javac/code/Symtab.java @@ -0,0 +1,20 @@ +/* + * These are stub versions of various bits of javac-internal API (for various different versions of javac). Lombok is compiled against these. + */ +package com.sun.tools.javac.code; + +import com.sun.tools.javac.code.Symbol.ClassSymbol; +import com.sun.tools.javac.code.Symbol.ModuleSymbol; +import com.sun.tools.javac.util.Context; + +public class Symtab { + // Shared by JDK6-9 + public ClassSymbol methodClass; + public Type iterableType; + public Type objectType; + public static Symtab instance(Context context) {return null;} + public Type unknownType; + + // JDK 9 + public ModuleSymbol unnamedModule; +} diff --git a/src/stubs/com/sun/tools/javac/file/BaseFileManager.java b/src/stubs/com/sun/tools/javac/file/BaseFileManager.java new file mode 100644 index 00000000..a56a2430 --- /dev/null +++ b/src/stubs/com/sun/tools/javac/file/BaseFileManager.java @@ -0,0 +1,18 @@ +/* + * These are stub versions of various bits of javac-internal API (for various different versions of javac). Lombok is compiled against these. + */ +package com.sun.tools.javac.file; + +import javax.tools.JavaFileManager; + +import com.sun.tools.javac.main.Option; +import com.sun.tools.javac.util.Context; + +import java.nio.charset.Charset; +import java.util.Map; + +public abstract class BaseFileManager implements JavaFileManager { + protected BaseFileManager(Charset charset) {} + public void setContext(Context context) {} + public boolean handleOptions(Map<Option, String> deferredFileManagerOptions) { return false; } +} diff --git a/src/stubs/com/sun/tools/javac/file/PathFileObject.java b/src/stubs/com/sun/tools/javac/file/PathFileObject.java new file mode 100644 index 00000000..c1ee6074 --- /dev/null +++ b/src/stubs/com/sun/tools/javac/file/PathFileObject.java @@ -0,0 +1,12 @@ +/* + * These are stub versions of various bits of javac-internal API (for various different versions of javac). Lombok is compiled against these. + */ +package com.sun.tools.javac.file; + +import java.nio.file.Path; + +import javax.tools.JavaFileObject; + +public abstract class PathFileObject implements JavaFileObject { + protected PathFileObject(BaseFileManager fileManager, Path path) {} +} diff --git a/src/stubs/com/sun/tools/javac/main/Arguments.java b/src/stubs/com/sun/tools/javac/main/Arguments.java new file mode 100644 index 00000000..ea866b6e --- /dev/null +++ b/src/stubs/com/sun/tools/javac/main/Arguments.java @@ -0,0 +1,13 @@ +package com.sun.tools.javac.main; + +import java.util.Map; + +import com.sun.tools.javac.util.Context; + +public class Arguments { + public static final Context.Key<Arguments> argsKey = new Context.Key<Arguments>(); + public static Arguments instance(Context context) { return null; } + public void init(String ownName, String... argv) {} + public Map<Option, String> getDeferredFileManagerOptions() { return null; } + public boolean validate() { return false; } +} diff --git a/src/stubs/com/sun/tools/javac/main/JavaCompiler.java b/src/stubs/com/sun/tools/javac/main/JavaCompiler.java new file mode 100644 index 00000000..d0e7b38f --- /dev/null +++ b/src/stubs/com/sun/tools/javac/main/JavaCompiler.java @@ -0,0 +1,37 @@ +/* + * These are stub versions of various bits of javac-internal API (for various different versions of javac). Lombok is compiled against these. + */ +package com.sun.tools.javac.main; + +import java.io.IOException; +import java.util.Collection; +import javax.annotation.processing.Processor; +import javax.tools.JavaFileObject; + +import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; +import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.comp.Todo; + +public class JavaCompiler { + // Shared by JDK6-9 + public boolean keepComments; + public boolean genEndPos; + public Todo todo; + + public JavaCompiler(Context context) {} + public int errorCount() { return 0; } + public static String version() { return "<stub>"; } + public JCCompilationUnit parse(String fileName) throws IOException { return null; } + public List<JCCompilationUnit> enterTrees(List<JCCompilationUnit> roots) {return null;} + + //JDK up to 8 + public void initProcessAnnotations(Iterable<? extends Processor> processors) throws IOException {} + public JavaCompiler processAnnotations(List<JCCompilationUnit> roots, List<String> classnames) {return this;} + + // JDK 9 + public void initProcessAnnotations(Iterable<? extends Processor> processors, Collection<? extends JavaFileObject> initialFiles, Collection<String> initialClassNames) {} + public void processAnnotations(List<JCCompilationUnit> roots, Collection<String> classnames) {} + public void close() {} + public List<JCCompilationUnit> initModules(List<JCCompilationUnit> roots) { return null; } +} diff --git a/src/stubs/com/sun/tools/javac/main/Option.java b/src/stubs/com/sun/tools/javac/main/Option.java index f3229c78..ae955772 100644 --- a/src/stubs/com/sun/tools/javac/main/Option.java +++ b/src/stubs/com/sun/tools/javac/main/Option.java @@ -7,4 +7,5 @@ package com.sun.tools.javac.main; public enum Option { ; public String text; + public String primaryName; } diff --git a/src/stubs/com/sun/tools/javac/parser/JavacParser.java b/src/stubs/com/sun/tools/javac/parser/JavacParser.java index da42f37a..4f1f3380 100644 --- a/src/stubs/com/sun/tools/javac/parser/JavacParser.java +++ b/src/stubs/com/sun/tools/javac/parser/JavacParser.java @@ -1,3 +1,6 @@ +/* + * These are stub versions of various bits of javac-internal API (for various different versions of javac). Lombok is compiled against these. + */ package com.sun.tools.javac.parser; import com.sun.tools.javac.tree.JCTree; @@ -6,6 +9,9 @@ public class JavacParser { protected JavacParser(ParserFactory fac, Lexer S, boolean keepDocComments, boolean keepLineMap, boolean keepEndPositions) { } + protected JavacParser(ParserFactory fac, Lexer S, boolean keepDocComments, boolean keepLineMap, boolean keepEndPositions, boolean parseModuleInfo) { + } + public JCTree.JCCompilationUnit parseCompilationUnit() { return null; } diff --git a/src/stubs/com/sun/tools/javac/util/Options.java b/src/stubs/com/sun/tools/javac/util/Options.java new file mode 100644 index 00000000..e7ba8960 --- /dev/null +++ b/src/stubs/com/sun/tools/javac/util/Options.java @@ -0,0 +1,20 @@ +package com.sun.tools.javac.util; + +import java.util.Set; + +import com.sun.tools.javac.main.Option; +import com.sun.tools.javac.main.OptionName; +import com.sun.tools.javac.main.JavacOption; + +public class Options { + public Options(Context context) {} + public static final Context.Key<Options> optionsKey = new Context.Key<Options>(); + public static Options instance(Context context) { return null; } + public String get(String key) { return null; } + public String get(Option opt) { return null; } + public String get(OptionName name) { return null; } + public String get(JavacOption.Option opt) { return null; } + public void putAll(Options o) {} + public void put(String key, String value) {} + public Set<String> keySet() { return null; } +} diff --git a/src/stubs/org/mapstruct/ap/spi/AstModifyingAnnotationProcessor.java b/src/stubs/org/mapstruct/ap/spi/AstModifyingAnnotationProcessor.java new file mode 100644 index 00000000..ffb99030 --- /dev/null +++ b/src/stubs/org/mapstruct/ap/spi/AstModifyingAnnotationProcessor.java @@ -0,0 +1,48 @@ +/** + * Copyright 2012-2017 Gunnar Morling (http://www.gunnarmorling.de/) + * and/or other contributors as indicated by the @authors tag. See the + * copyright.txt file in the distribution for a full listing of all + * contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.mapstruct.ap.spi; + +import javax.lang.model.type.TypeMirror; + +/** + * A contract to be implemented by other annotation processors which - against the design philosophy of JSR 269 - alter + * the types under compilation. + * <p> + * This contract will be queried by MapStruct when examining types referenced by mappers to be generated, most notably + * the source and target types of mapping methods. If at least one AST-modifying processor announces further changes to + * such type, the generation of the affected mapper(s) will be deferred to a future round in the annnotation processing + * cycle. + * <p> + * Implementations are discovered via the service loader, i.e. a JAR providing an AST-modifying processor needs to + * declare its implementation in a file {@code META-INF/services/org.mapstruct.ap.spi.AstModifyingAnnotationProcessor}. + * + * @author Gunnar Morling + */ +//@org.mapstruct.util.Experimental +public interface AstModifyingAnnotationProcessor { + + /** + * Whether the specified type has been fully processed by this processor or not (i.e. this processor will amend the + * given type's structure after this invocation). + * + * @param type The type of interest + * @return {@code true} if this processor has fully processed the given type, {@code false} otherwise. + */ + boolean isTypeComplete(TypeMirror type); +}
\ No newline at end of file diff --git a/src/stubsstubs/com/sun/tools/javac/code/Attribute.java b/src/stubsstubs/com/sun/tools/javac/code/Attribute.java new file mode 100644 index 00000000..29bd54a9 --- /dev/null +++ b/src/stubsstubs/com/sun/tools/javac/code/Attribute.java @@ -0,0 +1,15 @@ +package com.sun.tools.javac.code; + +import java.util.Map; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.DeclaredType; + +public abstract class Attribute { + public static class Compound extends Attribute implements AnnotationMirror { + public DeclaredType getAnnotationType() { return null; } + public Map<? extends ExecutableElement, ? extends AnnotationValue> getElementValues() { return null; } + } +}
\ No newline at end of file diff --git a/src/stubsstubs/com/sun/tools/javac/code/Type.java b/src/stubsstubs/com/sun/tools/javac/code/Type.java new file mode 100644 index 00000000..c130ae9c --- /dev/null +++ b/src/stubsstubs/com/sun/tools/javac/code/Type.java @@ -0,0 +1,3 @@ +package com.sun.tools.javac.code; + +public class Type {}
\ No newline at end of file diff --git a/src/stubsstubs/com/sun/tools/javac/comp/Todo.java b/src/stubsstubs/com/sun/tools/javac/comp/Todo.java new file mode 100644 index 00000000..006a7fd3 --- /dev/null +++ b/src/stubsstubs/com/sun/tools/javac/comp/Todo.java @@ -0,0 +1,3 @@ +package com.sun.tools.javac.comp; + +public class Todo {}
\ No newline at end of file diff --git a/src/stubsstubs/com/sun/tools/javac/main/JavacOption.java b/src/stubsstubs/com/sun/tools/javac/main/JavacOption.java new file mode 100644 index 00000000..8e74d3d3 --- /dev/null +++ b/src/stubsstubs/com/sun/tools/javac/main/JavacOption.java @@ -0,0 +1,5 @@ +package com.sun.tools.javac.main; + +public class JavacOption { + public static class Option {} +}
\ No newline at end of file diff --git a/src/stubsstubs/com/sun/tools/javac/main/Option.java b/src/stubsstubs/com/sun/tools/javac/main/Option.java new file mode 100644 index 00000000..45988e4c --- /dev/null +++ b/src/stubsstubs/com/sun/tools/javac/main/Option.java @@ -0,0 +1,3 @@ +package com.sun.tools.javac.main; + +public class Option {}
\ No newline at end of file diff --git a/src/stubsstubs/com/sun/tools/javac/main/OptionName.java b/src/stubsstubs/com/sun/tools/javac/main/OptionName.java new file mode 100644 index 00000000..b1866633 --- /dev/null +++ b/src/stubsstubs/com/sun/tools/javac/main/OptionName.java @@ -0,0 +1,3 @@ +package com.sun.tools.javac.main; + +public class OptionName {}
\ No newline at end of file diff --git a/src/stubsstubs/com/sun/tools/javac/util/Context.java b/src/stubsstubs/com/sun/tools/javac/util/Context.java new file mode 100644 index 00000000..a090714e --- /dev/null +++ b/src/stubsstubs/com/sun/tools/javac/util/Context.java @@ -0,0 +1,5 @@ +package com.sun.tools.javac.util; + +public class Context { + public static class Key<T> {} +}
\ No newline at end of file diff --git a/src/stubsstubs/com/sun/tools/javac/util/List.java b/src/stubsstubs/com/sun/tools/javac/util/List.java new file mode 100644 index 00000000..16418a2b --- /dev/null +++ b/src/stubsstubs/com/sun/tools/javac/util/List.java @@ -0,0 +1,3 @@ +package com.sun.tools.javac.util; + +public class List<T> {}
\ No newline at end of file diff --git a/src/stubsstubs/com/sun/tools/javac/util/Name.java b/src/stubsstubs/com/sun/tools/javac/util/Name.java new file mode 100644 index 00000000..c0e81926 --- /dev/null +++ b/src/stubsstubs/com/sun/tools/javac/util/Name.java @@ -0,0 +1,8 @@ +package com.sun.tools.javac.util; + +public class Name implements javax.lang.model.element.Name { + public boolean contentEquals(CharSequence cs) { return false; } + public int length() { return 0; } + public char charAt(int idx) { return '\0'; } + public CharSequence subSequence(int a, int b) { return null; } +}
\ No newline at end of file diff --git a/src/testAP/org/projectlombok/testAp/ExampleAnnotation.java b/src/testAP/org/projectlombok/testAp/ExampleAnnotation.java new file mode 100644 index 00000000..b419326b --- /dev/null +++ b/src/testAP/org/projectlombok/testAp/ExampleAnnotation.java @@ -0,0 +1,10 @@ +package org.projectlombok.testAp; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.CLASS) +@Target(ElementType.TYPE) +public @interface ExampleAnnotation {} diff --git a/src/testAP/org/projectlombok/testAp/TestAp.java b/src/testAP/org/projectlombok/testAp/TestAp.java new file mode 100644 index 00000000..b5f20d21 --- /dev/null +++ b/src/testAP/org/projectlombok/testAp/TestAp.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 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 + * 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 org.projectlombok.testAp; + +import java.util.Set; + +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.ProcessingEnvironment; +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.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; + +@SupportedAnnotationTypes("org.projectlombok.testAp.ExampleAnnotation") +public final class TestAp extends AbstractProcessor { + private int roundCounter = 0; + private static final long START = System.currentTimeMillis(); + + private void log(String txt) { + System.out.printf("***[%3d]: %s\n", System.currentTimeMillis() - START, txt); + } + + @Override public void init(ProcessingEnvironment processingEnv) { + log("TestAP in init"); + super.init(processingEnv); + } + + @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { + roundCounter++; + log("TestAP in round " + roundCounter); + boolean foundGetTest = false; + int annotatedElemCount = 0; + for (Element annotated : roundEnv.getElementsAnnotatedWith(ExampleAnnotation.class)) { + annotatedElemCount++; + for (Element child : annotated.getEnclosedElements()) { + if (child.getSimpleName().toString().equals("getTest") && child.getKind() == ElementKind.METHOD) foundGetTest = true; + if (child instanceof ExecutableElement) { + TypeMirror returnType = ((ExecutableElement) child).getReturnType(); + System.out.println("RETURN TYPE for " + child.getSimpleName() + ": " + returnType.getClass() + " -- " + returnType.toString()); + } + } + } + + if (foundGetTest) log("RESULT: POSITIVE -- found the getTest method"); + else if (annotatedElemCount > 0) log("RESULT: NEGATIVE -- found the example class but there's no getTest method in it according to the type mirror."); + else log("RESULT: AMBIVALENT -- The example class is not provided by 'getElementsAnnotatedWith' in this round. Not an issue, unless previously you got a NEGATIVE result."); + + return false; + } + + @Override public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latestSupported(); + } +} diff --git a/src/useTestAP/UseTestAp.java b/src/useTestAP/UseTestAp.java new file mode 100644 index 00000000..2d7eab7b --- /dev/null +++ b/src/useTestAP/UseTestAp.java @@ -0,0 +1,13 @@ +@org.projectlombok.testAp.ExampleAnnotation +//@lombok.experimental.Accessors(chain=true) +public class UseTestAp { + @lombok.Setter @lombok.Getter String test; + + public void confirmGetTestExists() { + System.out.println(getTest()); + } + + public UseTestAp returningSelf() { + return this; + } +} diff --git a/src/utils/lombok/core/LombokImmutableList.java b/src/utils/lombok/core/LombokImmutableList.java index e0e1136c..4603f2ad 100644 --- a/src/utils/lombok/core/LombokImmutableList.java +++ b/src/utils/lombok/core/LombokImmutableList.java @@ -80,6 +80,12 @@ public final class LombokImmutableList<T> implements Iterable<T> { return copyOf(list); } + public static <T> LombokImmutableList<T> copyOf(T[] array) { + Object[] content = new Object[array.length]; + System.arraycopy(array, 0, content, 0, array.length); + return new LombokImmutableList<T>(content); + } + private LombokImmutableList(Object[] content) { this.content = content; } diff --git a/src/utils/lombok/eclipse/Eclipse.java b/src/utils/lombok/eclipse/Eclipse.java index c2a863d5..18b22256 100644 --- a/src/utils/lombok/eclipse/Eclipse.java +++ b/src/utils/lombok/eclipse/Eclipse.java @@ -98,15 +98,20 @@ public class Eclipse { * string containing the same fully qualified name with dots in the string. */ public static boolean nameEquals(char[][] typeName, String string) { - StringBuilder sb = new StringBuilder(); - boolean first = true; - for (char[] elem : typeName) { - if (first) first = false; - else sb.append('.'); - sb.append(elem); + int pos = 0, len = string.length(); + for (int i = 0; i < typeName.length; i++) { + char[] t = typeName[i]; + if (i > 0) { + if (pos == len) return false; + if (string.charAt(pos++) != '.') return false; + } + for (int j = 0; j < t.length; j++) { + if (pos == len) return false; + if (string.charAt(pos++) != t[j]) return false; + } } - return string.contentEquals(sb); + return true; } public static boolean hasClinit(TypeDeclaration parent) { diff --git a/src/utils/lombok/javac/CommentCatcher.java b/src/utils/lombok/javac/CommentCatcher.java index c32da68b..afbd7b52 100644 --- a/src/utils/lombok/javac/CommentCatcher.java +++ b/src/utils/lombok/javac/CommentCatcher.java @@ -95,8 +95,10 @@ public class CommentCatcher { parserFactory = Class.forName("lombok.javac.java6.CommentCollectingParserFactory"); } else if (javaCompilerVersion == 7) { parserFactory = Class.forName("lombok.javac.java7.CommentCollectingParserFactory"); - } else { + } else if (javaCompilerVersion == 8) { parserFactory = Class.forName("lombok.javac.java8.CommentCollectingParserFactory"); + } else { + parserFactory = Class.forName("lombok.javac.java9.CommentCollectingParserFactory"); } parserFactory.getMethod("setInCompiler", JavaCompiler.class, Context.class).invoke(null, compiler, context); } catch (InvocationTargetException e) { diff --git a/src/utils/lombok/javac/Javac.java b/src/utils/lombok/javac/Javac.java index e207c44a..9ff4d22f 100644 --- a/src/utils/lombok/javac/Javac.java +++ b/src/utils/lombok/javac/Javac.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 @@ -40,6 +40,7 @@ import lombok.javac.JavacTreeMaker.TreeTag; import lombok.javac.JavacTreeMaker.TypeTag; import com.sun.tools.javac.code.Source; +import com.sun.tools.javac.code.Symtab; import com.sun.tools.javac.code.Type; import com.sun.tools.javac.main.JavaCompiler; import com.sun.tools.javac.tree.JCTree; @@ -62,8 +63,8 @@ public class Javac { /** Matches any of the 8 primitive names, such as {@code boolean}. */ private static final Pattern PRIMITIVE_TYPE_NAME_PATTERN = Pattern.compile("^(boolean|byte|short|int|long|float|double|char)$"); - private static final Pattern VERSION_PARSER = Pattern.compile("^(\\d{1,6})\\.(\\d{1,6}).*$"); - private static final Pattern SOURCE_PARSER = Pattern.compile("^JDK(\\d{1,6})_(\\d{1,6}).*$"); + private static final Pattern VERSION_PARSER = Pattern.compile("^(\\d{1,6})\\.?(\\d{1,6})?.*$"); + private static final Pattern SOURCE_PARSER = Pattern.compile("^JDK(\\d{1,6})_?(\\d{1,6})?.*$"); private static final AtomicInteger compilerVersion = new AtomicInteger(-1); @@ -78,11 +79,11 @@ public class Javac { Matcher m = VERSION_PARSER.matcher(JavaCompiler.version()); if (m.matches()) { int major = Integer.parseInt(m.group(1)); - int minor = Integer.parseInt(m.group(2)); if (major == 1) { - compilerVersion.set(minor); - return minor; + int minor = Integer.parseInt(m.group(2)); + return setVersion(minor); } + if (major >= 9) return setVersion(major); } } @@ -91,16 +92,19 @@ public class Javac { Matcher m = SOURCE_PARSER.matcher(name); if (m.matches()) { int major = Integer.parseInt(m.group(1)); - int minor = Integer.parseInt(m.group(2)); if (major == 1) { - compilerVersion.set(minor); - return minor; + int minor = Integer.parseInt(m.group(2)); + return setVersion(minor); } + if (major >= 9) return setVersion(major); } } - - compilerVersion.set(6); - return 6; + return setVersion(6); + } + + private static int setVersion(int version) { + compilerVersion.set(version); + return version; } private static final Class<?> DOCCOMMENTTABLE_CLASS; @@ -162,9 +166,16 @@ public class Javac { public static final TypeTag CTC_VOID = typeTag("VOID"); public static final TypeTag CTC_NONE = typeTag("NONE"); public static final TypeTag CTC_BOT = typeTag("BOT"); + public static final TypeTag CTC_ERROR = typeTag("ERROR"); + public static final TypeTag CTC_UNKNOWN = typeTag("UNKNOWN"); + public static final TypeTag CTC_UNDETVAR = typeTag("UNDETVAR"); public static final TypeTag CTC_CLASS = typeTag("CLASS"); public static final TreeTag CTC_NOT_EQUAL = treeTag("NE"); + public static final TreeTag CTC_LESS_THAN = treeTag("LT"); + public static final TreeTag CTC_GREATER_THAN = treeTag("GT"); + public static final TreeTag CTC_LESS_OR_EQUAL= treeTag("LE"); + public static final TreeTag CTC_GREATER_OR_EQUAL = treeTag("GE"); public static final TreeTag CTC_POS = treeTag("POS"); public static final TreeTag CTC_NEG = treeTag("NEG"); public static final TreeTag CTC_NOT = treeTag("NOT"); @@ -172,10 +183,14 @@ public class Javac { public static final TreeTag CTC_BITXOR = treeTag("BITXOR"); public static final TreeTag CTC_UNSIGNED_SHIFT_RIGHT = treeTag("USR"); public static final TreeTag CTC_MUL = treeTag("MUL"); + public static final TreeTag CTC_DIV = treeTag("DIV"); public static final TreeTag CTC_PLUS = treeTag("PLUS"); + public static final TreeTag CTC_MINUS = treeTag("MINUS"); public static final TreeTag CTC_EQUAL = treeTag("EQ"); public static final TreeTag CTC_PREINC = treeTag("PREINC"); public static final TreeTag CTC_PREDEC = treeTag("PREDEC"); + public static final TreeTag CTC_POSTINC = treeTag("POSTINC"); + public static final TreeTag CTC_POSTDEC = treeTag("POSTDEC"); private static final Method getExtendsClause, getEndPosition, storeEnd; @@ -298,7 +313,21 @@ public class Javac { JC_NO_TYPE = c; } - public static Type createVoidType(JavacTreeMaker maker, TypeTag tag) { + private static final Field symtabVoidType = getFieldIfExists(Symtab.class, "voidType"); + + private static Field getFieldIfExists(Class<?> c, String fieldName) { + try { + return c.getField("voidType"); + } catch (Exception e) { + return null; + } + } + + public static Type createVoidType(Symtab symbolTable, TypeTag tag) { + if (symtabVoidType != null) try { + return (Type) symtabVoidType.get(symbolTable); + } catch (IllegalAccessException ignore) {} + if (Javac.getJavaCompilerVersion() < 8) { return new JCNoType(((Integer) tag.value).intValue()); } else { diff --git a/src/utils/lombok/javac/JavacTreeMaker.java b/src/utils/lombok/javac/JavacTreeMaker.java index 12baf5af..5f4fb09c 100644 --- a/src/utils/lombok/javac/JavacTreeMaker.java +++ b/src/utils/lombok/javac/JavacTreeMaker.java @@ -226,6 +226,7 @@ public class JavacTreeMaker { } public static TypeTag typeTag(Type t) { + if (t == null) return Javac.CTC_VOID; try { return new TypeTag(getFieldCached(FIELD_CACHE, t, "tag")); } catch (NoSuchFieldException e) { diff --git a/src/utils/lombok/javac/PackageName.java b/src/utils/lombok/javac/PackageName.java new file mode 100644 index 00000000..e4dd6b20 --- /dev/null +++ b/src/utils/lombok/javac/PackageName.java @@ -0,0 +1,55 @@ +/* + * 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 java.lang.reflect.Method; + +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; +import com.sun.tools.javac.tree.JCTree.JCFieldAccess; +import com.sun.tools.javac.tree.JCTree.JCIdent; + +// Supports JDK6-9 +public class PackageName { + private static final Method packageNameMethod = getPackageNameMethod(); + + private static Method getPackageNameMethod() { + try { + return JCCompilationUnit.class.getDeclaredMethod("getPackageName"); + } catch (Exception e) { + return null; + } + } + + public static String getPackageName(JCCompilationUnit cu) { + JCTree t = getPackageNode(cu); + return t != null ? t.toString() : null; + } + + public static JCTree getPackageNode(JCCompilationUnit cu) { + if (packageNameMethod != null) try { + Object pkg = packageNameMethod.invoke(cu); + return (pkg instanceof JCFieldAccess || pkg instanceof JCIdent) ? (JCTree) pkg : null; + } catch (Exception e) {} + return cu.pid instanceof JCFieldAccess || cu.pid instanceof JCIdent ? cu.pid : null; + } +} diff --git a/src/utils/lombok/javac/TreeMirrorMaker.java b/src/utils/lombok/javac/TreeMirrorMaker.java index 23ec2406..8cd8cffe 100644 --- a/src/utils/lombok/javac/TreeMirrorMaker.java +++ b/src/utils/lombok/javac/TreeMirrorMaker.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2011 The Project Lombok Authors. + * Copyright (C) 2010-2015 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,11 +26,15 @@ import java.util.IdentityHashMap; import java.util.Iterator; import java.util.Map; +import static lombok.javac.Javac.*; +import lombok.javac.JavacTreeMaker.TypeTag; + import com.sun.source.tree.LabeledStatementTree; import com.sun.source.tree.VariableTree; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.JCVariableDecl; import com.sun.tools.javac.tree.TreeCopier; +import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.List; /** @@ -46,7 +50,7 @@ import com.sun.tools.javac.util.List; public class TreeMirrorMaker extends TreeCopier<Void> { private final IdentityHashMap<JCTree, JCTree> originalToCopy = new IdentityHashMap<JCTree, JCTree>(); - public TreeMirrorMaker(JavacTreeMaker maker) { + public TreeMirrorMaker(JavacTreeMaker maker, Context context) { super(maker.getUnderlyingTreeMaker()); } @@ -86,15 +90,32 @@ public class TreeMirrorMaker extends TreeCopier<Void> { return Collections.unmodifiableMap(originalToCopy); } - // Fix for NPE in HandleVal. See http://code.google.com/p/projectlombok/issues/detail?id=205 - // Maybe this should be done elsewhere... + // Monitor the following issues when making changes here. + // - https://github.com/rzwitserloot/lombok/issues/278 + // - https://github.com/rzwitserloot/lombok/issues/729 @Override public JCTree visitVariable(VariableTree node, Void p) { + JCVariableDecl original = node instanceof JCVariableDecl ? (JCVariableDecl) node : null; JCVariableDecl copy = (JCVariableDecl) super.visitVariable(node, p); - copy.sym = ((JCVariableDecl) node).sym; + if (original == null) return copy; + copy.sym = original.sym; + if (copy.sym != null) copy.type = original.type; + if (copy.type != null) { + boolean wipeSymAndType = copy.type.isErroneous(); + if (!wipeSymAndType) { + TypeTag typeTag = TypeTag.typeTag(copy.type); + wipeSymAndType = (CTC_NONE.equals(typeTag) || CTC_ERROR.equals(typeTag) || CTC_UNKNOWN.equals(typeTag) || CTC_UNDETVAR.equals(typeTag)); + } + + if (wipeSymAndType) { + copy.sym = null; + copy.type = null; + } + } + return copy; } - // Fix for NPE in HandleVal. See http://code.google.com/p/projectlombok/issues/detail?id=299 + // Fix for NPE in HandleVal. See https://github.com/rzwitserloot/lombok/issues/372 // This and visitVariable is rather hacky but we're working around evident bugs or at least inconsistencies in javac. @Override public JCTree visitLabeledStatement(LabeledStatementTree node, Void p) { return node.getStatement().accept(this, p); diff --git a/src/utils/lombok/javac/java8/CommentCollectingParserFactory.java b/src/utils/lombok/javac/java8/CommentCollectingParserFactory.java index 45f865ad..2fdaddfe 100644 --- a/src/utils/lombok/javac/java8/CommentCollectingParserFactory.java +++ b/src/utils/lombok/javac/java8/CommentCollectingParserFactory.java @@ -52,6 +52,16 @@ public class CommentCollectingParserFactory extends ParserFactory { //Either way this will work out. } + public JavacParser newParser(CharSequence input, boolean keepDocComments, boolean keepEndPos, boolean keepLineMap, boolean parseModuleInfo) { + ScannerFactory scannerFactory = ScannerFactory.instance(context); + Lexer lexer = scannerFactory.newScanner(input, true); + Object x = new CommentCollectingParser(this, lexer, true, keepLineMap, keepEndPos); + return (JavacParser) x; + // CCP is based on a stub which extends nothing, but at runtime the stub is replaced with either + //javac6's EndPosParser which extends Parser, or javac8's JavacParser which implements Parser. + //Either way this will work out. + } + public static void setInCompiler(JavaCompiler compiler, Context context) { context.put(CommentCollectingParserFactory.key(), (ParserFactory) null); Field field; diff --git a/src/utils/lombok/javac/java9/CommentCollectingParser.java b/src/utils/lombok/javac/java9/CommentCollectingParser.java new file mode 100644 index 00000000..307be405 --- /dev/null +++ b/src/utils/lombok/javac/java9/CommentCollectingParser.java @@ -0,0 +1,53 @@ +/* + * 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 + * 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.java9; + +import static lombok.javac.CommentCatcher.JCCompilationUnit_comments; + +import java.util.List; + +import lombok.javac.CommentInfo; +import lombok.javac.java8.CommentCollectingScanner; + +import com.sun.tools.javac.parser.JavacParser; +import com.sun.tools.javac.parser.Lexer; +import com.sun.tools.javac.parser.ParserFactory; +import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; + +class CommentCollectingParser extends JavacParser { + private final Lexer lexer; + + protected CommentCollectingParser(ParserFactory fac, Lexer S, + boolean keepDocComments, boolean keepLineMap, boolean keepEndPositions, boolean parseModuleInfo) { + super(fac, S, keepDocComments, keepLineMap, keepEndPositions, parseModuleInfo); + lexer = S; + } + + public JCCompilationUnit parseCompilationUnit() { + JCCompilationUnit result = super.parseCompilationUnit(); + if (lexer instanceof CommentCollectingScanner) { + List<CommentInfo> comments = ((CommentCollectingScanner)lexer).getComments(); + JCCompilationUnit_comments.set(result, comments); + } + return result; + } +}
\ No newline at end of file diff --git a/src/utils/lombok/javac/java9/CommentCollectingParserFactory.java b/src/utils/lombok/javac/java9/CommentCollectingParserFactory.java new file mode 100644 index 00000000..5af4a419 --- /dev/null +++ b/src/utils/lombok/javac/java9/CommentCollectingParserFactory.java @@ -0,0 +1,70 @@ +/* + * 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 + * 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.java9; + +import java.lang.reflect.Field; + +import com.sun.tools.javac.main.JavaCompiler; +import com.sun.tools.javac.parser.JavacParser; +import com.sun.tools.javac.parser.Lexer; +import com.sun.tools.javac.parser.ParserFactory; +import com.sun.tools.javac.parser.ScannerFactory; +import com.sun.tools.javac.util.Context; + +public class CommentCollectingParserFactory extends ParserFactory { + private final Context context; + + static Context.Key<ParserFactory> key() { + return parserFactoryKey; + } + + protected CommentCollectingParserFactory(Context context) { + super(context); + this.context = context; + } + + public JavacParser newParser(CharSequence input, boolean keepDocComments, boolean keepEndPos, boolean keepLineMap) { + return newParser(input, keepDocComments, keepEndPos, keepLineMap, false); + } + + public JavacParser newParser(CharSequence input, boolean keepDocComments, boolean keepEndPos, boolean keepLineMap, boolean parseModuleInfo) { + ScannerFactory scannerFactory = ScannerFactory.instance(context); + Lexer lexer = scannerFactory.newScanner(input, true); + Object x = new CommentCollectingParser(this, lexer, true, keepLineMap, keepEndPos, parseModuleInfo); + return (JavacParser) x; + // CCP is based on a stub which extends nothing, but at runtime the stub is replaced with either + //javac6's EndPosParser which extends Parser, or javac-9's JavacParser which implements Parser. + //Either way this will work out. + } + + public static void setInCompiler(JavaCompiler compiler, Context context) { + context.put(CommentCollectingParserFactory.key(), (ParserFactory) null); + Field field; + try { + field = JavaCompiler.class.getDeclaredField("parserFactory"); + field.setAccessible(true); + field.set(compiler, new CommentCollectingParserFactory(context)); + } catch (Exception e) { + throw new IllegalStateException("Could not set comment sensitive parser in the compiler", e); + } + } +}
\ No newline at end of file diff --git a/src/website/log4j.properties b/src/website/log4j.properties new file mode 100644 index 00000000..9cafcc3b --- /dev/null +++ b/src/website/log4j.properties @@ -0,0 +1,6 @@ +log4j.rootLogger=INFO, stdout + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.Target=System.out +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n diff --git a/src/website/lombok/website/CompileChangelog.java b/src/website/lombok/website/CompileChangelog.java new file mode 100644 index 00000000..8912434e --- /dev/null +++ b/src/website/lombok/website/CompileChangelog.java @@ -0,0 +1,147 @@ +package lombok.website; + +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.StringReader; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.petebevin.markdown.MarkdownProcessor; + +public class CompileChangelog { + public static void main(String[] args) { + String fileIn = args[0]; + String fileOut = args[1]; + boolean edge = args.length > 3 && "-edge".equals(args[2]); + boolean latest = args.length > 3 && "-latest".equals(args[2]); + String version = args.length > 3 ? args[3] : null; + + try { + FileInputStream in = new FileInputStream(fileIn); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + byte[] b = new byte[65536]; + while (true) { + int r = in.read(b); + if ( r == -1 ) break; + out.write(b, 0, r); + } + in.close(); + String markdown = new String(out.toByteArray(), "UTF-8"); + + String result; + if (edge) { + result = buildEdge(sectionByVersion(markdown, version)); + } else if (latest) { + result = buildLatest(sectionByVersion(markdown, version)); + } else { + result = markdownToHtml(sectionStartingAt(markdown, version)); + } + + FileOutputStream file = new FileOutputStream(fileOut); + file.write(result.getBytes("UTF-8")); + file.close(); + System.exit(0); + } catch (Throwable e) { + e.printStackTrace(); + System.exit(1); + } + } + + public static String getHtmlForEdge(File root, String edgeVersion) throws IOException { + File f = new File(root, "doc/changelog.markdown"); + String raw = readFile(f); + return buildEdge(sectionByVersion(raw, edgeVersion)); + } + + public static String getHtmlForLatest(File root, String latestVersion) throws IOException { + File f = new File(root, "doc/changelog.markdown"); + String raw = readFile(f); + return buildLatest(sectionByVersion(raw, latestVersion)); + } + + public static String getHtml(File root) throws IOException { + File f = new File(root, "doc/changelog.markdown"); + String raw = readFile(f); + return markdownToHtml(raw); + } + + public static String getHtmlStartingAtSection(File root, String version) throws IOException { + File f = new File(root, "doc/changelog.markdown"); + String raw = readFile(f); + return markdownToHtml(sectionStartingAt(raw, version)); + } + + private static String readFile(File f) throws IOException { + byte[] b = new byte[65536]; + FileInputStream in = new FileInputStream(f); + try { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + while (true) { + int r = in.read(b); + if ( r == -1 ) break; + out.write(b, 0, r); + } + in.close(); + return new String(out.toByteArray(), "UTF-8"); + } finally { + in.close(); + } + } + + private static String markdownToHtml(String markdown) { + return new MarkdownProcessor().markdown(markdown); + } + + private static String buildEdge(String section) { + String latest = section != null ? section : "* No changelog records for this edge release."; + return markdownToHtml(latest); + } + + private static String buildLatest(String section) { + String latest = section != null ? section : "* No changelog records for this release."; + String noIssueLinks = latest.replaceAll("\\[[^]]*[Ii]ssue[^]]*\\]\\([^)]*\\)", ""); + String noLinks = noIssueLinks.replaceAll("\\[([^]]*)\\]\\([^)]*\\)", "$1"); + return markdownToHtml(noLinks); + } + + private static String sectionStartingAt(String markdown, String version) { + if (version.toUpperCase().endsWith("-HEAD") || version.toUpperCase().endsWith("-EDGE")) { + version = version.substring(0, version.length() - 5); + } + + Pattern p = Pattern.compile("^.*###\\s*v(.*)$"); + BufferedReader br = new BufferedReader(new StringReader(markdown)); + StringBuilder out = new StringBuilder(); + int state = 0; + try { + for (String line = br.readLine(); line != null; line = br.readLine()) { + if (state < 2) { + Matcher m = p.matcher(line); + if (m.matches()) state = m.group(1).startsWith(version) ? 2 : 1; + } + if (state != 1) { + out.append(line); + out.append("\n"); + } + } + return out.toString(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private static String sectionByVersion(String markdown, String version) { + if (version.toUpperCase().endsWith("-HEAD") || version.toUpperCase().endsWith("-EDGE")) { + version = version.substring(0, version.length() - 5); + } + + Pattern p = Pattern.compile("(?is-m)^.*###\\s*v" + version + ".*?\n(.*?)(?:###\\s*v.*)?$"); + Matcher m = p.matcher(markdown); + return m.matches() ? m.group(1) : null; + } +}
\ No newline at end of file diff --git a/src/website/lombok/website/FetchCurrentVersion.java b/src/website/lombok/website/FetchCurrentVersion.java new file mode 100644 index 00000000..0d789256 --- /dev/null +++ b/src/website/lombok/website/FetchCurrentVersion.java @@ -0,0 +1,33 @@ +package lombok.website; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class FetchCurrentVersion { + private FetchCurrentVersion() {} + + private static final Pattern VERSION_PATTERN = Pattern.compile("^.*<\\s*span\\s+id\\s*=\\s*[\"'](currentVersion|currentVersionFull)[\"'](?:\\s+style\\s*=\\s*[\"']display\\s*:\\s*none;?[\"'])?\\s*>\\s*([^\t<]+)\\s*<\\s*/\\s*span\\s*>.*$"); + + public static void main(String[] args) throws IOException { + System.out.print(fetchVersionFromSite(args.length == 0 || args[0].equals("full"))); + } + + public static String fetchVersionFromSite(boolean fetchFull) throws IOException { + InputStream in = new URL("https://projectlombok.org/download").openStream(); + try { + BufferedReader br = new BufferedReader(new InputStreamReader(in, "UTF-8")); + for (String line = br.readLine(); line != null; line = br.readLine()) { + Matcher m = VERSION_PATTERN.matcher(line); + if (m.matches() && m.group(1).equals("currentVersionFull") == fetchFull) return m.group(2).replace(""", "\""); + } + throw new IOException("Expected a span with id 'currentVersion'"); + } finally { + in.close(); + } + } +} diff --git a/src/website/lombok/website/WebsiteMaker.java b/src/website/lombok/website/WebsiteMaker.java new file mode 100644 index 00000000..88556b97 --- /dev/null +++ b/src/website/lombok/website/WebsiteMaker.java @@ -0,0 +1,403 @@ +package lombok.website; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.lang.reflect.Method; +import java.net.URL; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.TimeZone; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import de.java2html.Java2Html; +import freemarker.cache.FileTemplateLoader; +import freemarker.cache.TemplateLoader; +import freemarker.core.HTMLOutputFormat; +import freemarker.template.Configuration; +import freemarker.template.Template; +import freemarker.template.TemplateExceptionHandler; + +public class WebsiteMaker { + private final String version, fullVersion; + private final File baseDir, outputDir; + + public WebsiteMaker(String version, String fullVersion, File baseDir, File outputDir) { + this.version = version; + this.fullVersion = fullVersion; + this.baseDir = baseDir; + this.outputDir = outputDir; + } + + private static final class VersionFinder { + public static String getVersion() { + return getVersion0("getVersion"); + } + + public static String getFullVersion() { + return getVersion0("getFullVersion"); + } + + private static String getVersion0(String mName) { + try { + Class<?> c = Class.forName("lombok.core.Version"); + Method m = c.getMethod(mName); + return (String) m.invoke(null); + } catch (ClassNotFoundException e) { + System.err.println("You need to specify the version string, and the full version string, as first 2 arguments."); + System.exit(1); + return null; + } catch (Exception e) { + if (e instanceof RuntimeException) throw (RuntimeException) e; + throw new RuntimeException(e); + } + } + } + + private static void buildAll(String version, String fullVersion, String argIn, String argOut) throws Exception { + File in, out; + if (argIn == null) { + in = new File("."); + if (new File(in, "build.xml").isFile() && new File(in, "website").isDirectory()) in = new File(in, "website"); + } else { + in = new File(argIn); + } + + if (argOut == null) { + if (new File("./build.xml").isFile() && new File("./website").isDirectory() && new File("./build").isDirectory()) { + out = new File("./build/website"); + } else { + out = new File(in, "output"); + } + } else { + out = new File(argOut); + } + WebsiteMaker maker = new WebsiteMaker(version, fullVersion, in, out); + maker.buildWebsite(); + } + + private static void buildChangelog(String version, String fullVersion, String argIn, String argOut) throws Exception { + File in, out; + if (argIn == null) { + in = new File("."); + if (new File(in, "build.xml").isFile() && new File(in, "website").isDirectory()) in = new File(in, "website"); + } else { + in = new File(argIn); + } + + if (argOut == null) { + if (new File("./build.xml").isFile() && new File("./website").isDirectory() && new File("./build").isDirectory()) { + out = new File("./build/website/changelog.html"); + } else { + out = new File(in, "output/changelog.html"); + } + } else { + out = new File(argOut); + } + WebsiteMaker maker = new WebsiteMaker(version, fullVersion, in, out.getParentFile()); + maker.buildChangelog(out); + } + + private static void buildDownloadEdge(String version, String fullVersion, String argIn, String argOut) throws Exception { + File in, out; + if (argIn == null) { + in = new File("."); + if (new File(in, "build.xml").isFile() && new File(in, "website").isDirectory()) in = new File(in, "website"); + } else { + in = new File(argIn); + } + + if (argOut == null) { + if (new File("./build.xml").isFile() && new File("./website").isDirectory() && new File("./build").isDirectory()) { + out = new File("./build/website-edge/download-edge.html"); + } else { + out = new File(in, "output/download-edge.html"); + } + } else { + out = new File(argOut); + } + WebsiteMaker maker = new WebsiteMaker(version, fullVersion, in, out.getParentFile()); + maker.buildDownloadEdge(out); + } + + private static void buildChangelogLatest(String version, String fullVersion, String argIn, String argOut) throws Exception { + File in, out; + if (argIn == null) { + in = new File("."); + if (new File(in, "build.xml").isFile() && new File(in, "website").isDirectory()) in = new File(in, "website"); + } else { + in = new File(argIn); + } + + if (argOut == null) { + if (new File("./build.xml").isFile() && new File("./website").isDirectory() && new File("./build").isDirectory()) { + out = new File("./build/latestchanges.html"); + } else { + out = new File(in, "output/latestchanges.html"); + } + } else { + out = new File(argOut); + } + WebsiteMaker maker = new WebsiteMaker(version, fullVersion, in, out.getParentFile()); + maker.buildChangelogLatest(out); + } + + public static void main(String[] args) throws Exception { + String version, fullVersion; + + if (args.length < 2) { + version = VersionFinder.getVersion(); + fullVersion = VersionFinder.getFullVersion(); + } else { + version = args[0]; + fullVersion = args[1]; + } + + if (args.length < 3 || args[2].equalsIgnoreCase("all")) { + buildAll(version, fullVersion, args.length < 4 ? null : args[3], args.length < 5 ? null : args[4]); + } else if (args[2].equalsIgnoreCase("changelog")) { + buildChangelog(version, fullVersion, args.length < 4 ? null : args[3], args.length < 5 ? null : args[4]); + } else if (args[2].equalsIgnoreCase("download-edge")) { + buildDownloadEdge(version, fullVersion, args.length < 4 ? null : args[3], args.length < 5 ? null : args[4]); + } else if (args[2].equalsIgnoreCase("changelog-latest")) { + buildChangelogLatest(version, fullVersion, args.length < 4 ? null : args[3], args.length < 5 ? null : args[4]); + } else { + throw new IllegalArgumentException("3rd argument must be one of 'all', 'changelog', 'download-edge', 'changelog-latest'"); + } + } + + private Configuration makeFreemarkerConfig() throws IOException { + Configuration freemarkerConfig = new Configuration(Configuration.VERSION_2_3_25); + freemarkerConfig.setEncoding(Locale.ENGLISH, "UTF-8"); + freemarkerConfig.setOutputEncoding("UTF-8"); + freemarkerConfig.setOutputFormat(HTMLOutputFormat.INSTANCE); + freemarkerConfig.setTemplateLoader(createLoader()); + freemarkerConfig.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); + return freemarkerConfig; + } + + public void buildChangelog(File out) throws Exception { + Configuration freemarkerConfig = makeFreemarkerConfig(); + outputDir.mkdirs(); + convertChangelog(freemarkerConfig, out); + } + + public void buildChangelogLatest(File out) throws Exception { + outputDir.mkdirs(); + String htmlForLatest = CompileChangelog.getHtmlForLatest(baseDir.getParentFile(), version); + FileOutputStream fos = new FileOutputStream(out); + try { + BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(fos, "UTF-8")); + bw.write(htmlForLatest); + bw.close(); + } finally { + fos.close(); + } + } + + public void buildDownloadEdge(File out) throws Exception { + Configuration freemarkerConfig = makeFreemarkerConfig(); + + outputDir.mkdirs(); + convertDownloadEdge(freemarkerConfig, out); + } + + public void buildHtAccess(File out) throws Exception { + Configuration freemarkerConfig = new Configuration(Configuration.VERSION_2_3_25); + freemarkerConfig.setEncoding(Locale.ENGLISH, "UTF-8"); + freemarkerConfig.setOutputEncoding("UTF-8"); + freemarkerConfig.setOutputFormat(HTMLOutputFormat.INSTANCE); + freemarkerConfig.setTemplateLoader(createLoader("extra")); + freemarkerConfig.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); + + outputDir.mkdirs(); + convertHtAccess(freemarkerConfig, out); + } + + public void buildWebsite() throws Exception { + Configuration freemarkerConfig = makeFreemarkerConfig(); + + outputDir.mkdirs(); + convertTemplates(freemarkerConfig); + buildHtAccess(new File(outputDir, ".htaccess")); + } + + private TemplateLoader createLoader() throws IOException { + return createLoader("templates"); + } + + private TemplateLoader createLoader(String base) throws IOException { + return new FileTemplateLoader(new File(baseDir, base)); + } + + private void convertHtAccess(Configuration freemarker, File outFile) throws Exception { + Map<String, Object> dataModel = new HashMap<String, Object>(); + dataModel.put("setupPages", listHtmlNames(new File(outputDir, "setup"))); + dataModel.put("featurePages", listHtmlNames(new File(outputDir, "features"))); + dataModel.put("experimentalPages", listHtmlNames(new File(outputDir, "features/experimental"))); + Template template = freemarker.getTemplate("htaccess"); + FileOutputStream fileOut = new FileOutputStream(outFile); + try { + Writer wr = new BufferedWriter(new OutputStreamWriter(fileOut, "UTF-8")); + template.process(dataModel, wr); + wr.close(); + } finally { + fileOut.close(); + } + } + + private List<String> listHtmlNames(File dir) { + List<String> out = new ArrayList<String>(); + for (String s : dir.list()) { + if (s.endsWith(".html") && !s.equals("index.html")) out.add(s.substring(0, s.length() - 5)); + } + return out; + } + + private void convertChangelog(Configuration freemarker, File outFile) throws Exception { + Map<String, Object> dataModel = createBasicDataModel(); + + Template template = freemarker.getTemplate("changelog.html"); + FileOutputStream fileOut = new FileOutputStream(outFile); + try { + Writer wr = new BufferedWriter(new OutputStreamWriter(fileOut, "UTF-8")); + template.process(dataModel, wr); + wr.close(); + } finally { + fileOut.close(); + } + } + + private void convertDownloadEdge(Configuration freemarker, File outFile) throws Exception { + Map<String, Object> dataModel = createBasicDataModel(); + + Template template = freemarker.getTemplate("_download-edge.html"); + FileOutputStream fileOut = new FileOutputStream(outFile); + try { + Writer wr = new BufferedWriter(new OutputStreamWriter(fileOut, "UTF-8")); + template.process(dataModel, wr); + wr.close(); + } finally { + fileOut.close(); + } + } + + private void convertTemplates(Configuration freemarker) throws Exception { + File basePagesLoc = new File(baseDir, "templates"); + Map<String, Object> dataModel = createBasicDataModel(); + dataModel.putAll(createExtendedDataModel()); + convertTemplates_(freemarker, "", basePagesLoc, outputDir, 0, dataModel); + } + + private void convertTemplates_(Configuration freemarker, String prefix, File from, File to, int depth, Map<String, Object> dataModel) throws Exception { + if (depth > 50) throw new IllegalArgumentException("50 levels is too deep: " + from); + + for (File f : from.listFiles()) { + if (f.isDirectory()) convertTemplates_(freemarker, prefix + f.getName() + "/", f, new File(to, f.getName()), depth + 1, dataModel); + if (!f.isFile() || !f.getName().endsWith(".html") || f.getName().startsWith("_")) continue; + to.mkdirs(); + Template template = freemarker.getTemplate(prefix + f.getName()); + FileOutputStream fileOut = new FileOutputStream(new File(to, f.getName())); + try { + Writer wr = new BufferedWriter(new OutputStreamWriter(fileOut, "UTF-8")); + template.process(dataModel, wr); + wr.close(); + } finally { + fileOut.close(); + } + } + } + + private Map<String, Object> createBasicDataModel() throws IOException { + Map<String, Object> data = new HashMap<String, Object>(); + + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss 'UTC'"); + sdf.setTimeZone(TimeZone.getTimeZone("UTC")); + String currentTime = sdf.format(new Date()); + + data.put("version", version); + data.put("fullVersion", fullVersion); + data.put("timestampString", currentTime); + data.put("year", "" + new GregorianCalendar().get(Calendar.YEAR)); + data.put("changelog", CompileChangelog.getHtmlStartingAtSection(baseDir.getParentFile(), version)); + data.put("changelogEdge", CompileChangelog.getHtmlForEdge(baseDir.getParentFile(), version)); + + return data; + } + + private static final Pattern LOMBOK_LINK = Pattern.compile("^.*<a(?: (?:id|class|rel|rev|download|target|type)(?:=\"[^\"]*\")?)* href=\"(downloads/[^\"]+)\"(?: (?:id|class|rel|rev|download|target|type)(?:=\"[^\"]*\")?)*>([^<]+)</a>.*$"); + private Map<String, Object> createExtendedDataModel() throws IOException { + Map<String, Object> data = new HashMap<String, Object>(); + + data.put("usages", new HtmlMaker(new File(baseDir, "usageExamples"))); + InputStream in = new URL("https://projectlombok.org/all-versions.html").openStream(); + ArrayList<List<String>> links = new ArrayList<List<String>>(); + try { + BufferedReader br = new BufferedReader(new InputStreamReader(in, "UTF-8")); + for (String line = br.readLine(); line != null; line = br.readLine()) { + Matcher m = LOMBOK_LINK.matcher(line); + if (m.matches()) links.add(Arrays.asList(m.group(1), m.group(2))); + } + } finally { + in.close(); + } + + data.put("linksToVersions", links); + + return data; + } + + public static class HtmlMaker { + private final File usagesDir; + + HtmlMaker(File usagesDir) { + this.usagesDir = usagesDir; + } + + public String pre(String name) throws IOException { + return convert(new File(usagesDir, name + "Example_pre.jpage")); + } + + public String post(String name) throws IOException { + return convert(new File(usagesDir, name + "Example_post.jpage")); + } + + public String convert(File file) throws IOException { + String rawJava = readFully(file); + return Java2Html.convertToHtml(rawJava); + } + } + + public static String readFully(File file) throws IOException { + FileInputStream fis = new FileInputStream(file); + try { + InputStreamReader isr = new InputStreamReader(fis, "UTF-8"); + StringBuilder out = new StringBuilder(); + char[] b = new char[65536]; + while (true) { + int r = isr.read(b); + if (r == -1) break; + out.append(b, 0, r); + } + return out.toString(); + } finally { + fis.close(); + } + } +} |