diff options
Diffstat (limited to 'src')
79 files changed, 3210 insertions, 2266 deletions
diff --git a/src/core/lombok/AllArgsConstructor.java b/src/core/lombok/AllArgsConstructor.java index cc494967..3037c9db 100644 --- a/src/core/lombok/AllArgsConstructor.java +++ b/src/core/lombok/AllArgsConstructor.java @@ -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.html">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. * diff --git a/src/core/lombok/Builder.java b/src/core/lombok/Builder.java index c56c4004..6a92028c 100644 --- a/src/core/lombok/Builder.java +++ b/src/core/lombok/Builder.java @@ -48,7 +48,7 @@ import java.lang.annotation.Target; * as the relevant class, unless a method has been annotated, in which case it'll be equal to the * return type of that method. * <p> - * Complete documentation is found at <a href="http://projectlombok.org/features/experimental/Builder.html">the project lombok features page for @Builder</a>. + * Complete documentation is found at <a href="https://projectlombok.org/features/experimental/Builder.html">the project lombok features page for @Builder</a>. * <p> * <p> * Before: @@ -113,9 +113,40 @@ public @interface Builder { /** Name of the method in the builder class that creates an instance of your {@code @Builder}-annotated class. */ String buildMethodName() default "build"; - /** Name of the builder class. + /** + * Name of the builder class. + * * Default for {@code @Builder} on types and constructors: {@code (TypeName)Builder}. + * <p> * Default for {@code @Builder} on methods: {@code (ReturnTypeName)Builder}. */ String builderClassName() default ""; + + /** + * 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. + */ + boolean toBuilder() default false; + + /** + * 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> + * The default behaviour is to obtain a value by referencing the name of the parameter as a field on 'this'. + */ + public @interface ObtainVia { + /** Tells lombok to obtain a value with the expression {@code this.value}. */ + String field() default ""; + + /** Tells lombok to obtain a value with the expression {@code this.method()}. */ + String method() default ""; + + /** 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..c95c03c5 100644 --- a/src/core/lombok/Cleanup.java +++ b/src/core/lombok/Cleanup.java @@ -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.html">the project lombok features page for @Cleanup</a>. * <p> * Example: * <pre> diff --git a/src/core/lombok/ConfigurationKeys.java b/src/core/lombok/ConfigurationKeys.java index 6c595504..dd6732ed 100644 --- a/src/core/lombok/ConfigurationKeys.java +++ b/src/core/lombok/ConfigurationKeys.java @@ -396,20 +396,34 @@ 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.") {}; - // ----- Wither ----- + // ----- Helper ----- /** - * lombok configuration: {@code lombok.wither.flagUsage} = {@code WARNING} | {@code ERROR}. + * lombok configuration: {@code lombok.helper.flagUsage} = {@code WARNING} | {@code ERROR}. * - * If set, <em>any</em> usage of {@code @Wither} results in a warning / error. + * If set, <em>any</em> usage of {@code @Helper} results in a warning / error. */ - public static final ConfigurationKey<FlagUsageType> WITHER_FLAG_USAGE = new ConfigurationKey<FlagUsageType>("lombok.wither.flagUsage", "Emit a warning or error if @Wither is used.") {}; + public static final ConfigurationKey<FlagUsageType> HELPER_FLAG_USAGE = new ConfigurationKey<FlagUsageType>("lombok.helper.flagUsage", "Emit a warning or error if @Helper is used.") {}; // ----- UtilityClass ----- @@ -418,7 +432,16 @@ public class ConfigurationKeys { * * If set, <em>any</em> usage of {@code @UtilityClass} results in a warning / error. */ - public static final ConfigurationKey<FlagUsageType> UTLITY_CLASS_FLAG_USAGE = new ConfigurationKey<FlagUsageType>("lombok.utilityClass.flagUsage", "Emit a warning or error if @UtilityClass is used.") {}; + 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 ----- + + /** + * lombok configuration: {@code lombok.wither.flagUsage} = {@code WARNING} | {@code ERROR}. + * + * If set, <em>any</em> usage of {@code @Wither} results in a warning / error. + */ + 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..4c6c2e5d 100644 --- a/src/core/lombok/Data.java +++ b/src/core/lombok/Data.java @@ -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.html">the project lombok features page for @Data</a>. * * @see Getter * @see Setter diff --git a/src/core/lombok/EqualsAndHashCode.java b/src/core/lombok/EqualsAndHashCode.java index dbce23b8..605815ed 100644 --- a/src/core/lombok/EqualsAndHashCode.java +++ b/src/core/lombok/EqualsAndHashCode.java @@ -29,7 +29,7 @@ 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.html">the project lombok features page for @EqualsAndHashCode</a>. */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.SOURCE) diff --git a/src/core/lombok/Getter.java b/src/core/lombok/Getter.java index 428f53ef..906ae60f 100644 --- a/src/core/lombok/Getter.java +++ b/src/core/lombok/Getter.java @@ -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.html">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> diff --git a/src/core/lombok/NoArgsConstructor.java b/src/core/lombok/NoArgsConstructor.java index cd3e5568..ff437bba 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-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 @@ -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.html">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> @@ -61,6 +61,12 @@ public @interface NoArgsConstructor { AccessLevel access() default lombok.AccessLevel.PUBLIC; /** + * If {@code true}, initializes all final fields to 0 / null / false. + * Otherwise, a compile time error occurs. + */ + 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..7a9da3f9 100644 --- a/src/core/lombok/RequiredArgsConstructor.java +++ b/src/core/lombok/RequiredArgsConstructor.java @@ -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.html">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. * diff --git a/src/core/lombok/Setter.java b/src/core/lombok/Setter.java index 5e07b802..ba9e9759 100644 --- a/src/core/lombok/Setter.java +++ b/src/core/lombok/Setter.java @@ -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.html">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> diff --git a/src/core/lombok/SneakyThrows.java b/src/core/lombok/SneakyThrows.java index 929b4578..489e13e4 100644 --- a/src/core/lombok/SneakyThrows.java +++ b/src/core/lombok/SneakyThrows.java @@ -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.html">the project lombok features page for @SneakyThrows</a>. * <p> * Example: * <pre> diff --git a/src/core/lombok/Synchronized.java b/src/core/lombok/Synchronized.java index c5601a0c..9a39c212 100644 --- a/src/core/lombok/Synchronized.java +++ b/src/core/lombok/Synchronized.java @@ -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.html">the project lombok features page for @Synchronized</a>. */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) diff --git a/src/core/lombok/ToString.java b/src/core/lombok/ToString.java index e87c71e7..b9926148 100644 --- a/src/core/lombok/ToString.java +++ b/src/core/lombok/ToString.java @@ -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.html">the project lombok features page for @ToString</a>. */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.SOURCE) diff --git a/src/core/lombok/Value.java b/src/core/lombok/Value.java index 2cffe15b..2cecea63 100644 --- a/src/core/lombok/Value.java +++ b/src/core/lombok/Value.java @@ -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/experimental/Value.html">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 diff --git a/src/core/lombok/core/Main.java b/src/core/lombok/core/Main.java index dc613b0d..6952ab78 100644 --- a/src/core/lombok/core/Main.java +++ b/src/core/lombok/core/Main.java @@ -95,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; } } diff --git a/src/core/lombok/core/TypeLibrary.java b/src/core/lombok/core/TypeLibrary.java index dc557c47..cdaf7a70 100644 --- a/src/core/lombok/core/TypeLibrary.java +++ b/src/core/lombok/core/TypeLibrary.java @@ -50,13 +50,20 @@ public class TypeLibrary { } 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; } @@ -71,18 +78,29 @@ public class TypeLibrary { * @param fullyQualifiedTypeName the FQN type name, such as 'java.lang.String'. */ public void addType(String fullyQualifiedTypeName) { + String dotBased = fullyQualifiedTypeName.replace("$", "."); + if (locked) throw new IllegalStateException("locked"); - fullyQualifiedTypeName = fullyQualifiedTypeName.replace("$", "."); 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 f072a632..a0f98ad1 100644 --- a/src/core/lombok/core/Version.java +++ b/src/core/lombok/core/Version.java @@ -30,7 +30,7 @@ public class Version { // ** CAREFUL ** - this class must always compile with 0 dependencies (it must not refer to any other sources or libraries). // Note: In 'X.Y.Z', if Z is odd, its a snapshot build built from the repository, so many different 0.10.3 versions can exist, for example. // Official builds always end in an even number. (Since 0.10.2). - private static final String VERSION = "1.16.3"; + private static final String VERSION = "1.16.7"; private static final String RELEASE_NAME = "Edgy Guinea Pig"; // private static final String RELEASE_NAME = "Candid Duck"; 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/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/handlers/HandlerUtil.java b/src/core/lombok/core/handlers/HandlerUtil.java index 87462921..a9a4be78 100644 --- a/src/core/lombok/core/handlers/HandlerUtil.java +++ b/src/core/lombok/core/handlers/HandlerUtil.java @@ -68,6 +68,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. diff --git a/src/core/lombok/eclipse/EclipseAST.java b/src/core/lombok/eclipse/EclipseAST.java index 6df5c4d7..6741b33a 100644 --- a/src/core/lombok/eclipse/EclipseAST.java +++ b/src/core/lombok/eclipse/EclipseAST.java @@ -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() { 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 836a51c6..0ff5a7f6 100644 --- a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java +++ b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java @@ -450,7 +450,7 @@ 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); } public static TypeReference cloneSelfType(EclipseNode context) { diff --git a/src/core/lombok/eclipse/handlers/HandleBuilder.java b/src/core/lombok/eclipse/handlers/HandleBuilder.java index 2d4db64f..be14653a 100644 --- a/src/core/lombok/eclipse/handlers/HandleBuilder.java +++ b/src/core/lombok/eclipse/handlers/HandleBuilder.java @@ -70,6 +70,7 @@ 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; @@ -99,12 +100,33 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { private static class BuilderFieldData { TypeReference type; + char[] rawName; char[] name; SingularData singularData; + ObtainVia obtainVia; + EclipseNode obtainViaNode; 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; + } + @Override public void handle(AnnotationValues<Builder> annotation, Annotation ast, EclipseNode annotationNode) { long p = (long) ast.sourceStart << 32 | ast.sourceEnd; @@ -117,6 +139,9 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { 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"; @@ -155,15 +180,17 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { // 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; BuilderFieldData bfd = new BuilderFieldData(); + bfd.rawName = fieldNode.getName().toCharArray(); bfd.name = removePrefixFromField(fieldNode); bfd.type = fd.type; bfd.singularData = getSingularData(fieldNode, ast); + addObtainVia(bfd, fieldNode); builderFields.add(bfd); allFields.add(fieldNode); } - new HandleConstructor().generateConstructor(tdParent, AccessLevel.PACKAGE, allFields, null, SkipIfConstructorExists.I_AM_BUILDER, null, - Collections.<Annotation>emptyList(), annotationNode); + new HandleConstructor().generateConstructor(tdParent, AccessLevel.PACKAGE, allFields, false, null, SkipIfConstructorExists.I_AM_BUILDER, null, + Collections.<Annotation>emptyList(), annotationNode); returnType = namePlusTypeParamsToTypeReference(td.name, td.typeParameters, p); typeParams = td.typeParameters; @@ -188,6 +215,78 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { MethodDeclaration md = (MethodDeclaration) parent.get(); tdParent = parent.up(); 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); + } + } + returnType = copyType(md.returnType, ast); typeParams = md.typeParameters; thrownExceptions = md.thrownExceptions; @@ -231,9 +330,11 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { 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); + addObtainVia(bfd, param); builderFields.add(bfd); } } @@ -271,6 +372,16 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { 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); @@ -279,14 +390,13 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { cleanDecl.declarationSourceEnd = -1; cleanDecl.modifiers = ClassFileConstants.AccPrivate; cleanDecl.type = TypeReference.baseTypeReference(TypeIds.T_boolean, 0); - System.out.println("INJECTING: cleaning"); 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, null, + annotationNode, Collections.<Annotation>emptyList()); if (cd != null) injectMethod(builderType, cd); } @@ -317,6 +427,69 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { 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); + } + } + + 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 = methodName.toCharArray(); + out.modifiers = ClassFileConstants.AccPublic; + out.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + out.returnType = namePlusTypeParamsToTypeReference(builderClassName.toCharArray(), typeParams, p); + AllocationExpression invoke = new AllocationExpression(); + invoke.type = namePlusTypeParamsToTypeReference(builderClassName.toCharArray(), typeParams, p); + + 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; + } private MethodDeclaration generateCleanMethod(List<BuilderFieldData> builderFields, EclipseNode builderType, ASTNode source) { @@ -342,8 +515,7 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { } public MethodDeclaration generateBuildMethod(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); + MethodDeclaration out = new MethodDeclaration(((CompilationUnitDeclaration) type.top().get()).compilationResult); out.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; List<Statement> statements = new ArrayList<Statement>(); @@ -415,8 +587,7 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { int pS = source.sourceStart, pE = source.sourceEnd; long p = (long) pS << 32 | pE; - MethodDeclaration out = new MethodDeclaration( - ((CompilationUnitDeclaration) type.top().get()).compilationResult); + MethodDeclaration out = new MethodDeclaration(((CompilationUnitDeclaration) type.top().get()).compilationResult); out.selector = builderMethodName.toCharArray(); out.modifiers = ClassFileConstants.AccPublic; if (isStatic) out.modifiers |= ClassFileConstants.AccStatic; @@ -487,7 +658,7 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { String setterName = fluent ? fieldNode.getName() : HandlerUtil.buildAccessorName("set", fieldNode.getName()); MethodDeclaration setter = HandleSetter.createSetter(td, fieldNode, setterName, chain, ClassFileConstants.AccPublic, - sourceNode, Collections.<Annotation>emptyList(), Collections.<Annotation>emptyList()); + sourceNode, Collections.<Annotation>emptyList(), Collections.<Annotation>emptyList()); injectMethod(builderType, setter); } @@ -513,6 +684,16 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { 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. @@ -521,53 +702,52 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { */ private SingularData getSingularData(EclipseNode node, ASTNode source) { for (EclipseNode child : node.down()) { - if (child.getKind() == Kind.ANNOTATION && annotationTypeMatches(Singular.class, child)) { - 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."); + 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(node.getName()); + if (explicitSingular == null) { + node.addError("Can't singularize this name; please specify the singular explicitly (i.e. @Singular(\"sheep\"))"); explicitSingular = new String(pluralName); - } else { - explicitSingular = autoSingularize(node.getName()); - 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; + } + 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]); } - - return new SingularData(child, singularName, pluralName, typeArgs == null ? Collections.<TypeReference>emptyList() : Arrays.asList(typeArgs), targetFqn, singularizer, source); + 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/HandleConstructor.java b/src/core/lombok/eclipse/handlers/HandleConstructor.java index 5bcc803a..85d1d4ed 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-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 @@ -29,6 +29,7 @@ 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; @@ -48,13 +49,20 @@ 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.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,7 +71,9 @@ 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 { @@ -78,11 +88,13 @@ public class HandleConstructor { AccessLevel level = ann.access(); String staticName = ann.staticName(); if (level == AccessLevel.NONE) return; - List<EclipseNode> fields = new ArrayList<EclipseNode>(); + 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); + new HandleConstructor().generateConstructor(typeNode, level, fields, force, staticName, SkipIfConstructorExists.NO, null, onConstructor, annotationNode); } } @@ -107,19 +119,27 @@ public class HandleConstructor { List<Annotation> onConstructor = unboxAndRemoveAnnotationParameter(ast, "onConstructor", "@RequiredArgsConstructor(onConstructor=", annotationNode); new HandleConstructor().generateConstructor( - typeNode, level, findRequiredFields(typeNode), staticName, SkipIfConstructorExists.NO, - suppressConstructorProperties, onConstructor, annotationNode); + typeNode, level, findRequiredFields(typeNode), false, staticName, SkipIfConstructorExists.NO, + suppressConstructorProperties, 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; @@ -161,8 +181,8 @@ public class HandleConstructor { List<Annotation> onConstructor = unboxAndRemoveAnnotationParameter(ast, "onConstructor", "@AllArgsConstructor(onConstructor=", annotationNode); new HandleConstructor().generateConstructor( - typeNode, level, findAllFields(typeNode), staticName, SkipIfConstructorExists.NO, - suppressConstructorProperties, onConstructor, annotationNode); + typeNode, level, findAllFields(typeNode), false, staticName, SkipIfConstructorExists.NO, + suppressConstructorProperties, onConstructor, annotationNode); } } @@ -184,14 +204,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, null, 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, null, onConstructor, sourceNode); } public enum SkipIfConstructorExists { @@ -199,8 +219,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, + Boolean suppressConstructorProperties, List<Annotation> onConstructor, EclipseNode sourceNode) { ASTNode source = sourceNode.get(); boolean staticConstrRequired = staticName != null && !staticName.equals(""); @@ -210,8 +230,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 +244,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 +254,11 @@ public class HandleConstructor { } ConstructorDeclaration constr = createConstructor( - staticConstrRequired ? AccessLevel.PRIVATE : level, typeNode, fields, - suppressConstructorProperties, sourceNode, onConstructor); + staticConstrRequired ? AccessLevel.PRIVATE : level, typeNode, fields, allToDefault, + suppressConstructorProperties, 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 +268,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); @@ -276,14 +296,14 @@ public class HandleConstructor { } public static ConstructorDeclaration createConstructor( - AccessLevel level, EclipseNode type, Collection<EclipseNode> fields, - Boolean suppressConstructorProperties, EclipseNode sourceNode, List<Annotation> onConstructor) { + AccessLevel level, EclipseNode type, Collection<EclipseNode> fields, boolean allToDefault, + Boolean suppressConstructorProperties, 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; @@ -295,8 +315,7 @@ public class HandleConstructor { } } - 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,22 +338,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(field, sourceNode); + if (nullCheck != null) nullChecks.add(nullCheck); + } + parameter.annotations = copyAnnotations(source, nonNulls, nullables); + params.add(parameter); } - parameter.annotations = copyAnnotations(source, nonNulls, nullables); - params.add(parameter); } nullChecks.addAll(assigns); @@ -343,19 +367,32 @@ 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 && !suppressConstructorProperties && level != AccessLevel.PRIVATE && level != AccessLevel.PACKAGE && !isLocalType(type)) { constructorProperties = createConstructorProperties(source, fields); } constructor.annotations = copyAnnotations(source, - onConstructor.toArray(new Annotation[0]), - constructorProperties); + 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) { + 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; @@ -365,10 +402,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(); @@ -376,7 +412,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; @@ -389,7 +425,7 @@ 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); @@ -400,7 +436,7 @@ public class HandleConstructor { 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/HandleEqualsAndHashCode.java b/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java index 7e2ff513..77fe3a52 100644 --- a/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java +++ b/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java @@ -358,7 +358,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 +375,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)); diff --git a/src/core/lombok/eclipse/handlers/HandleFieldDefaults.java b/src/core/lombok/eclipse/handlers/HandleFieldDefaults.java index 7d0702db..5ea5a210 100644 --- a/src/core/lombok/eclipse/handlers/HandleFieldDefaults.java +++ b/src/core/lombok/eclipse/handlers/HandleFieldDefaults.java @@ -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)) { @@ -96,36 +104,69 @@ public class HandleFieldDefaults extends EclipseAnnotationHandler<FieldDefaults> if (makeFinal && (field.modifiers & ClassFileConstants.AccFinal) == 0) { if (!hasAnnotation(NonFinal.class, fieldNode)) { - field.modifiers |= ClassFileConstants.AccFinal; + if ((field.modifiers & ClassFileConstants.AccStatic) == 0 || field.initialization != null) { + 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/HandleHelper.java b/src/core/lombok/eclipse/handlers/HandleHelper.java new file mode 100644 index 00000000..4e9a7c68 --- /dev/null +++ b/src/core/lombok/eclipse/handlers/HandleHelper.java @@ -0,0 +1,137 @@ +/* + * 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.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.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.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.Eclipse; +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> { + @Override public void handle(AnnotationValues<Helper> annotation, Annotation ast, EclipseNode annotationNode) { + handleExperimentalFlagUsage(annotationNode, ConfigurationKeys.HELPER_FLAG_USAGE, "@Helper"); + + EclipseNode annotatedType = annotationNode.up(); + EclipseNode containingMethod = annotatedType == null ? null : annotatedType.up(); + if (annotatedType == null || containingMethod == null || annotatedType.getKind() != Kind.TYPE || containingMethod.getKind() != Kind.METHOD) { + annotationNode.addError("@Helper is legal only on method-local classes."); + return; + } + + TypeDeclaration annotatedType_ = (TypeDeclaration) annotatedType.get(); + AbstractMethodDeclaration amd = (AbstractMethodDeclaration) containingMethod.get(); + Statement[] origStatements = amd.statements; + 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, Eclipse.pos(messageSend)); + 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; + amd.statements = newStatements; + } +} 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/HandleUtilityClass.java b/src/core/lombok/eclipse/handlers/HandleUtilityClass.java index 176ff2d8..199ce102 100644 --- a/src/core/lombok/eclipse/handlers/HandleUtilityClass.java +++ b/src/core/lombok/eclipse/handlers/HandleUtilityClass.java @@ -31,6 +31,7 @@ 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; @@ -59,7 +60,7 @@ import lombok.experimental.UtilityClass; @ProviderFor(EclipseAnnotationHandler.class) public class HandleUtilityClass extends EclipseAnnotationHandler<UtilityClass> { @Override public void handle(AnnotationValues<UtilityClass> annotation, Annotation ast, EclipseNode annotationNode) { - handleFlagUsage(annotationNode, ConfigurationKeys.UTLITY_CLASS_FLAG_USAGE, "@UtilityClass"); + handleFlagUsage(annotationNode, ConfigurationKeys.UTILITY_CLASS_FLAG_USAGE, "@UtilityClass"); EclipseNode typeNode = annotationNode.up(); if (!checkLegality(typeNode, annotationNode)) return; @@ -104,6 +105,8 @@ public class HandleUtilityClass extends EclipseAnnotationHandler<UtilityClass> { 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) { @@ -116,7 +119,10 @@ public class HandleUtilityClass extends EclipseAnnotationHandler<UtilityClass> { for (EclipseNode element : typeNode.down()) { if (element.getKind() == Kind.FIELD) { FieldDeclaration fieldDecl = (FieldDeclaration) element.get(); - fieldDecl.modifiers |= ClassFileConstants.AccStatic; + 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) { @@ -128,6 +134,8 @@ public class HandleUtilityClass extends EclipseAnnotationHandler<UtilityClass> { } } 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; @@ -135,6 +143,7 @@ public class HandleUtilityClass extends EclipseAnnotationHandler<UtilityClass> { } if (makeConstructor) createPrivateDefaultConstructor(typeNode, annotationNode); + if (requiresClInit && !alreadyHasClinit) classDecl.addClinit(); } private static final char[][] JAVA_LANG_UNSUPPORTED_OPERATION_EXCEPTION = new char[][] { diff --git a/src/core/lombok/experimental/Accessors.java b/src/core/lombok/experimental/Accessors.java index c2a0ca16..7d9fdc5c 100644 --- a/src/core/lombok/experimental/Accessors.java +++ b/src/core/lombok/experimental/Accessors.java @@ -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.html">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. diff --git a/src/core/lombok/experimental/Builder.java b/src/core/lombok/experimental/Builder.java index 39f51460..4d5e0f67 100644 --- a/src/core/lombok/experimental/Builder.java +++ b/src/core/lombok/experimental/Builder.java @@ -48,7 +48,7 @@ import java.lang.annotation.Target; * as the relevant class, unless a method has been annotated, in which case it'll be equal to the * return type of that method. * <p> - * Complete documentation is found at <a href="http://projectlombok.org/features/experimental/Builder.html">the project lombok features page for @Builder</a>. + * Complete documentation is found at <a href="https://projectlombok.org/features/experimental/Builder.html">the project lombok features page for @Builder</a>. * <p> * <p> * Before: diff --git a/src/core/lombok/experimental/Delegate.java b/src/core/lombok/experimental/Delegate.java index 806d5871..d94389b0 100644 --- a/src/core/lombok/experimental/Delegate.java +++ b/src/core/lombok/experimental/Delegate.java @@ -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.html">the project lombok features page for @Delegate</a>. */ @Target({ElementType.FIELD, ElementType.METHOD}) @Retention(RetentionPolicy.SOURCE) diff --git a/src/core/lombok/experimental/ExtensionMethod.java b/src/core/lombok/experimental/ExtensionMethod.java index 7de8a136..3e64cf78 100644 --- a/src/core/lombok/experimental/ExtensionMethod.java +++ b/src/core/lombok/experimental/ExtensionMethod.java @@ -31,7 +31,7 @@ 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>. + * Complete documentation is found at <a href="https://projectlombok.org/features/experimental/ExtensionMethod.html">the project lombok features page for @ExtensionMethod</a>. * <p> * <p> * Before: diff --git a/src/core/lombok/experimental/FieldDefaults.java b/src/core/lombok/experimental/FieldDefaults.java index 1c621f3c..dbc4993b 100644 --- a/src/core/lombok/experimental/FieldDefaults.java +++ b/src/core/lombok/experimental/FieldDefaults.java @@ -31,7 +31,7 @@ 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.html">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. * <p> diff --git a/src/core/lombok/experimental/Helper.java b/src/core/lombok/experimental/Helper.java new file mode 100644 index 00000000..34745cbe --- /dev/null +++ b/src/core/lombok/experimental/Helper.java @@ -0,0 +1,35 @@ +/* + * 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; + +/** + * 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) +public @interface Helper {} diff --git a/src/core/lombok/experimental/Value.java b/src/core/lombok/experimental/Value.java index b7700bb5..46ec7a05 100644 --- a/src/core/lombok/experimental/Value.java +++ b/src/core/lombok/experimental/Value.java @@ -31,7 +31,7 @@ import java.lang.annotation.Target; * <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>. + * Complete documentation is found at <a href="https://projectlombok.org/features/experimental/Value.html">the project lombok features page for @Value</a>. * * @see lombok.Getter * @see Wither diff --git a/src/core/lombok/experimental/Wither.java b/src/core/lombok/experimental/Wither.java index fe113bb0..b4131187 100644 --- a/src/core/lombok/experimental/Wither.java +++ b/src/core/lombok/experimental/Wither.java @@ -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.html">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> diff --git a/src/core/lombok/experimental/package-info.java b/src/core/lombok/experimental/package-info.java index 776f2c27..d85e2969 100644 --- a/src/core/lombok/experimental/package-info.java +++ b/src/core/lombok/experimental/package-info.java @@ -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/index.html">Lombok features (experimental)</a> */ package lombok.experimental; diff --git a/src/core/lombok/extern/apachecommons/CommonsLog.java b/src/core/lombok/extern/apachecommons/CommonsLog.java index 45345098..6e64d2d8 100644 --- a/src/core/lombok/extern/apachecommons/CommonsLog.java +++ b/src/core/lombok/extern/apachecommons/CommonsLog.java @@ -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.html">the project lombok features page for lombok log annotations</a>. * <p> * Example: * <pre> diff --git a/src/core/lombok/extern/java/Log.java b/src/core/lombok/extern/java/Log.java index bac2742e..d2cf8c17 100644 --- a/src/core/lombok/extern/java/Log.java +++ b/src/core/lombok/extern/java/Log.java @@ -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.html">the project lombok features page for lombok log annotations</a>. * <p> * Example: * <pre> diff --git a/src/core/lombok/extern/log4j/Log4j.java b/src/core/lombok/extern/log4j/Log4j.java index 9490acb8..31fedbe7 100644 --- a/src/core/lombok/extern/log4j/Log4j.java +++ b/src/core/lombok/extern/log4j/Log4j.java @@ -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.html">the project lombok features page for lombok log annotations</a>. * <p> * Example: * <pre> diff --git a/src/core/lombok/extern/log4j/Log4j2.java b/src/core/lombok/extern/log4j/Log4j2.java index 43125e6b..96d793f7 100644 --- a/src/core/lombok/extern/log4j/Log4j2.java +++ b/src/core/lombok/extern/log4j/Log4j2.java @@ -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.html">the project lombok features page for lombok log annotations</a>. * <p> * Example: * <pre> @@ -42,7 +42,7 @@ 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> * diff --git a/src/core/lombok/extern/slf4j/Slf4j.java b/src/core/lombok/extern/slf4j/Slf4j.java index 04df6498..571ebd58 100644 --- a/src/core/lombok/extern/slf4j/Slf4j.java +++ b/src/core/lombok/extern/slf4j/Slf4j.java @@ -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.html">the project lombok features page for lombok log annotations</a>. * <p> * Example: * <pre> diff --git a/src/core/lombok/extern/slf4j/XSlf4j.java b/src/core/lombok/extern/slf4j/XSlf4j.java index 8a311c79..369728c4 100644 --- a/src/core/lombok/extern/slf4j/XSlf4j.java +++ b/src/core/lombok/extern/slf4j/XSlf4j.java @@ -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.html">the project lombok features page for lombok log annotations</a>. * <p> * Example: * <pre> diff --git a/src/core/lombok/javac/handlers/HandleBuilder.java b/src/core/lombok/javac/handlers/HandleBuilder.java index ab833195..efe40da3 100644 --- a/src/core/lombok/javac/handlers/HandleBuilder.java +++ b/src/core/lombok/javac/handlers/HandleBuilder.java @@ -31,6 +31,7 @@ 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; @@ -51,6 +52,7 @@ 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; @@ -81,8 +83,11 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { private static class BuilderFieldData { JCExpression type; + Name rawName; Name name; SingularData singularData; + ObtainVia obtainVia; + JavacNode obtainViaNode; java.util.List<JavacNode> createdFields = new ArrayList<JavacNode>(); } @@ -97,6 +102,9 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { 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"; @@ -138,14 +146,16 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { // 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; BuilderFieldData bfd = new BuilderFieldData(); + bfd.rawName = fd.name; bfd.name = removePrefixFromField(fieldNode); bfd.type = fd.vartype; bfd.singularData = getSingularData(fieldNode); + 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); + new HandleConstructor().generateConstructor(tdParent, AccessLevel.PACKAGE, List.<JCAnnotation>nil(), allFields.toList(), false, null, SkipIfConstructorExists.I_AM_BUILDER, null, annotationNode); returnType = namePlusTypeParamsToTypeReference(tdParent.getTreeMaker(), td.name, td.typarams); typeParams = td.typarams; @@ -171,14 +181,15 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { JCClassDecl td = (JCClassDecl) tdParent.get(); JCMethodDecl jmd = (JCMethodDecl) fillParametersFrom.get(); isStatic = (jmd.mods.flags & Flags.STATIC) != 0; - returnType = jmd.restype; + JCExpression fullReturnType = jmd.restype; + returnType = fullReturnType; typeParams = jmd.typarams; thrownExceptions = jmd.thrown; nameOfBuilderMethod = jmd.name; + if (returnType instanceof JCTypeApply) { + returnType = ((JCTypeApply) returnType).clazz; + } 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) { @@ -196,13 +207,76 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { if (Character.isLowerCase(builderClassName.charAt(0))) { builderClassName = Character.toTitleCase(builderClassName.charAt(0)) + builderClassName.substring(1); } - } else { // 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; + } + + if (returnType instanceof JCIdent) { + simpleName = ((JCIdent) returnType).name; + pkg = null; + } else if (returnType instanceof JCFieldAccess) { + JCFieldAccess jcfa = (JCFieldAccess) returnType; + 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 " + returnType.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("**" + returnType.getClass().toString()); +// annotationNode.addError("@Builder(toBuilder=true) requires that each type parameter on the static method is part of the typeargs of the return value. Type parameter " + tp.name + " is not part of the return type."); + return; + } + typeArgsForToBuilder.add(tpOnType.get(pos).name); + } + } } else { annotationNode.addError("@Builder is only supported on types, constructors, and methods."); return; @@ -214,8 +288,10 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { 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); + addObtainVia(bfd, param); builderFields.add(bfd); } } @@ -254,6 +330,16 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { 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); @@ -264,7 +350,7 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { } if (constructorExists(builderType) == MemberExistsResult.NOT_EXISTS) { - JCMethodDecl cd = HandleConstructor.createConstructor(AccessLevel.PACKAGE, List.<JCAnnotation>nil(), builderType, List.<JavacNode>nil(), null, annotationNode); + JCMethodDecl cd = HandleConstructor.createConstructor(AccessLevel.PACKAGE, List.<JCAnnotation>nil(), builderType, List.<JavacNode>nil(), false, null, annotationNode); if (cd != null) injectMethod(builderType, cd); } @@ -294,9 +380,95 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { 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__"); + } + + 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>(); @@ -465,6 +637,17 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { 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. @@ -473,48 +656,47 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { */ private SingularData getSingularData(JavacNode node) { for (JavacNode child : node.down()) { - if (child.getKind() == Kind.ANNOTATION && annotationTypeMatches(Singular.class, child)) { - 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."); + 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(node.getName()); + if (explicitSingular == null) { + node.addError("Can't singularize this name; please specify the singular explicitly (i.e. @Singular(\"sheep\"))"); explicitSingular = pluralName.toString(); - } else { - explicitSingular = autoSingularize(node.getName()); - 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); } + 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/HandleConstructor.java b/src/core/lombok/javac/handlers/HandleConstructor.java index c5b309c2..4a4ec49c 100644 --- a/src/core/lombok/javac/handlers/HandleConstructor.java +++ b/src/core/lombok/javac/handlers/HandleConstructor.java @@ -23,6 +23,8 @@ 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; @@ -41,13 +43,13 @@ 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.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; @@ -71,8 +73,9 @@ public class HandleConstructor { 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(); + new HandleConstructor().generateConstructor(typeNode, level, onConstructor, fields, force, staticName, SkipIfConstructorExists.NO, null, annotationNode); } } @@ -97,11 +100,19 @@ public class HandleConstructor { suppressConstructorProperties = suppress; } - new HandleConstructor().generateConstructor(typeNode, level, onConstructor, findRequiredFields(typeNode), staticName, SkipIfConstructorExists.NO, suppressConstructorProperties, annotationNode); + new HandleConstructor().generateConstructor(typeNode, level, onConstructor, findRequiredFields(typeNode), false, staticName, SkipIfConstructorExists.NO, suppressConstructorProperties, 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 +123,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(); @@ -138,7 +149,7 @@ public class HandleConstructor { boolean suppress = ann.suppressConstructorProperties(); suppressConstructorProperties = suppress; } - new HandleConstructor().generateConstructor(typeNode, level, onConstructor, findAllFields(typeNode), staticName, SkipIfConstructorExists.NO, suppressConstructorProperties, annotationNode); + new HandleConstructor().generateConstructor(typeNode, level, onConstructor, findAllFields(typeNode), false, staticName, SkipIfConstructorExists.NO, suppressConstructorProperties, annotationNode); } } @@ -174,7 +185,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, null, source); } public enum SkipIfConstructorExists { @@ -182,10 +193,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, null, 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, Boolean suppressConstructorProperties, JavacNode source) { boolean staticConstrRequired = staticName != null && !staticName.equals(""); if (skipIfConstructorExists != SkipIfConstructorExists.NO && constructorExists(typeNode) != MemberExistsResult.NOT_EXISTS) return; @@ -193,8 +204,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,10 +225,10 @@ public class HandleConstructor { } } - JCMethodDecl constr = createConstructor(staticConstrRequired ? AccessLevel.PRIVATE : level, onConstructor, typeNode, fields, suppressConstructorProperties, source); + JCMethodDecl constr = createConstructor(staticConstrRequired ? AccessLevel.PRIVATE : level, onConstructor, typeNode, fields, allToDefault, suppressConstructorProperties, source); injectMethod(typeNode, constr); if (staticConstrRequired) { - JCMethodDecl staticConstr = createStaticConstructor(staticName, level, typeNode, fields, source.get()); + JCMethodDecl staticConstr = createStaticConstructor(staticName, level, typeNode, allToDefault ? List.<JavacNode>nil() : fields, source.get()); injectMethod(typeNode, staticConstr); } } @@ -236,7 +247,7 @@ 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) { + public static JCMethodDecl createConstructor(AccessLevel level, List<JCAnnotation> onConstructor, JavacNode typeNode, List<JavacNode> fields, boolean allToDefault, Boolean suppressConstructorProperties, JavacNode source) { JavacTreeMaker maker = typeNode.getTreeMaker(); boolean isEnum = (((JCClassDecl) typeNode.get()).mods.flags & Flags.ENUM) != 0; @@ -259,29 +270,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, 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 && !suppressConstructorProperties && level != AccessLevel.PRIVATE && level != AccessLevel.PACKAGE && !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/HandleEqualsAndHashCode.java b/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java index 1a6f4a8f..4bc79f03 100644 --- a/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java +++ b/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java @@ -124,10 +124,11 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler<EqualsAndHas } 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 +141,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."); } @@ -184,7 +185,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 +203,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; @@ -248,8 +249,8 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler<EqualsAndHas if (callSuper) { JCMethodInvocation callToSuper = maker.Apply(List.<JCExpression>nil(), - maker.Select(maker.Ident(typeNode.toName("super")), typeNode.toName("hashCode")), - List.<JCExpression>nil()); + maker.Select(maker.Ident(typeNode.toName("super")), typeNode.toName("hashCode")), + List.<JCExpression>nil()); statements.append(createResultCalculation(typeNode, callToSuper)); } @@ -258,14 +259,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 +274,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 +300,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 +326,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,10 +340,10 @@ 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) { @@ -385,12 +386,12 @@ 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)))); statements.append(maker.If(notInstanceOf, returnBool(maker, false), null)); } @@ -413,7 +414,7 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler<EqualsAndHas } 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 +424,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 +433,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 +463,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 +485,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)); } @@ -521,12 +522,13 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler<EqualsAndHas } 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..12c22059 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-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 @@ -31,23 +31,24 @@ import lombok.core.HandlerPriority; import lombok.experimental.FieldDefaults; import lombok.experimental.NonFinal; import lombok.experimental.PackagePrivate; -import lombok.javac.JavacAnnotationHandler; +import lombok.javac.JavacASTAdapter; +import lombok.javac.JavacASTVisitor; import lombok.javac.JavacNode; import org.mangosdk.spi.ProviderFor; import com.sun.tools.javac.code.Flags; +import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.JCAnnotation; import com.sun.tools.javac.tree.JCTree.JCClassDecl; import com.sun.tools.javac.tree.JCTree.JCVariableDecl; -import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; /** * Handles the {@code lombok.FieldDefaults} annotation for eclipse. */ -@ProviderFor(JavacAnnotationHandler.class) +@ProviderFor(JavacASTVisitor.class) @HandlerPriority(-2048) //-2^11; to ensure @Value picks up on messing with the fields' 'final' state, run earlier. -public class HandleFieldDefaults extends JavacAnnotationHandler<FieldDefaults> { +public class HandleFieldDefaults extends JavacASTAdapter { public boolean generateFieldDefaultsForType(JavacNode typeNode, JavacNode errorNode, AccessLevel level, boolean makeFinal, boolean checkForTypeLevelFieldDefaults) { if (checkForTypeLevelFieldDefaults) { if (hasAnnotation(FieldDefaults.class, typeNode)) { @@ -72,13 +73,13 @@ public class HandleFieldDefaults extends JavacAnnotationHandler<FieldDefaults> { //Skip fields that start with $ if (fieldDecl.name.toString().startsWith("$")) continue; - setFieldDefaultsForField(field, errorNode.get(), level, makeFinal); + setFieldDefaultsForField(field, level, makeFinal); } return true; } - public void setFieldDefaultsForField(JavacNode fieldNode, DiagnosticPosition pos, AccessLevel level, boolean makeFinal) { + public void setFieldDefaultsForField(JavacNode fieldNode, AccessLevel level, boolean makeFinal) { JCVariableDecl field = (JCVariableDecl) fieldNode.get(); if (level != null && level != AccessLevel.NONE) { if ((field.mods.flags & (Flags.PUBLIC | Flags.PRIVATE | Flags.PROTECTED)) == 0) { @@ -90,38 +91,62 @@ public class HandleFieldDefaults extends JavacAnnotationHandler<FieldDefaults> { 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.init != null) { + 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/HandleHelper.java b/src/core/lombok/javac/handlers/HandleHelper.java new file mode 100644 index 00000000..99131f70 --- /dev/null +++ b/src/core/lombok/javac/handlers/HandleHelper.java @@ -0,0 +1,138 @@ +/* + * 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.*; +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.JCAnnotation; +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.JCMethodDecl; +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> { + @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 containingMethod = annotatedType == null ? null : annotatedType.up(); + + if (annotatedType == null || containingMethod == null || annotatedType.getKind() != Kind.TYPE || containingMethod.getKind() != Kind.METHOD) { + annotationNode.addError("@Helper is legal only on method-local classes."); + return; + } + + JCClassDecl annotatedType_ = (JCClassDecl) annotatedType.get(); + JCMethodDecl amd = (JCMethodDecl) containingMethod.get(); + List<JCStatement> origStatements = amd.body.stats; + 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); + } + amd.body.stats = newStatements.toList(); + } +} 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/HandleUtilityClass.java b/src/core/lombok/javac/handlers/HandleUtilityClass.java index a4f8cb45..010c05a5 100644 --- a/src/core/lombok/javac/handlers/HandleUtilityClass.java +++ b/src/core/lombok/javac/handlers/HandleUtilityClass.java @@ -52,7 +52,7 @@ import com.sun.tools.javac.util.Name; @ProviderFor(JavacAnnotationHandler.class) public class HandleUtilityClass extends JavacAnnotationHandler<UtilityClass> { @Override public void handle(AnnotationValues<UtilityClass> annotation, JCAnnotation ast, JavacNode annotationNode) { - handleExperimentalFlagUsage(annotationNode, ConfigurationKeys.UTLITY_CLASS_FLAG_USAGE, "@UtilityClass"); + handleExperimentalFlagUsage(annotationNode, ConfigurationKeys.UTILITY_CLASS_FLAG_USAGE, "@UtilityClass"); deleteAnnotationIfNeccessary(annotationNode, UtilityClass.class); diff --git a/src/core/lombok/javac/handlers/HandleVal.java b/src/core/lombok/javac/handlers/HandleVal.java index 9eadd750..337ab2d7 100644 --- a/src/core/lombok/javac/handlers/HandleVal.java +++ b/src/core/lombok/javac/handlers/HandleVal.java @@ -51,11 +51,11 @@ import com.sun.tools.javac.util.List; @ResolutionResetNeeded public class HandleVal extends JavacASTAdapter { @Override public void visitLocal(JavacNode localNode, JCVariableDecl local) { - if (local.vartype == null || (!local.vartype.toString().equals("val") && !local.vartype.toString().equals("lombok.val"))) return; - - JCTree source = local.vartype; - - if (!typeMatches(val.class, localNode, local.vartype)) return; + JCTree typeTree = local.vartype; + if (typeTree == null) return; + String typeTreeToString = typeTree.toString(); + if (!typeTreeToString.equals("val") && !typeTreeToString.equals("lombok.val")) return; + if (!typeMatches(val.class, localNode, typeTree)) return; handleFlagUsage(localNode, ConfigurationKeys.VAL_FLAG_USAGE, "val"); @@ -88,7 +88,7 @@ public class HandleVal extends JavacASTAdapter { local.mods.flags |= Flags.FINAL; if (!localNode.shouldDeleteLombokAnnotations()) { - JCAnnotation valAnnotation = recursiveSetGeneratedBy(localNode.getTreeMaker().Annotation(local.vartype, List.<JCExpression>nil()), source, localNode.getContext()); + JCAnnotation valAnnotation = recursiveSetGeneratedBy(localNode.getTreeMaker().Annotation(local.vartype, List.<JCExpression>nil()), typeTree, localNode.getContext()); local.mods.annotations = local.mods.annotations == null ? List.of(valAnnotation) : local.mods.annotations.append(valAnnotation); } @@ -156,7 +156,7 @@ public class HandleVal extends JavacASTAdapter { local.vartype = JavacResolution.createJavaLangObject(localNode.getAst()); throw e; } finally { - recursiveSetGeneratedBy(local.vartype, source, localNode.getContext()); + recursiveSetGeneratedBy(local.vartype, typeTree, localNode.getContext()); } } } diff --git a/src/core/lombok/javac/handlers/JavacHandlerUtil.java b/src/core/lombok/javac/handlers/JavacHandlerUtil.java index 0db59da1..4e9be00b 100644 --- a/src/core/lombok/javac/handlers/JavacHandlerUtil.java +++ b/src/core/lombok/javac/handlers/JavacHandlerUtil.java @@ -21,6 +21,7 @@ */ 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; @@ -840,14 +841,18 @@ public class JavacHandlerUtil { 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; } @@ -1485,6 +1490,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; @@ -1492,6 +1509,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)); } @@ -1525,6 +1545,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 index 7fca01ae..c6d601bd 100644 --- a/src/core/lombok/javac/handlers/JavacSingularsRecipes.java +++ b/src/core/lombok/javac/handlers/JavacSingularsRecipes.java @@ -267,13 +267,15 @@ public class JavacSingularsRecipes { } /** 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) { + 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)); - return maker.Conditional(isNull, maker.Literal(CTC_INT, 0), sizeInvoke); + JCExpression out = maker.Conditional(isNull, maker.Literal(CTC_INT, 0), sizeInvoke); + if (parens) return maker.Parens(out); + return out; } return sizeInvoke; } diff --git a/src/core/lombok/javac/handlers/singulars/JavacJavaUtilListSingularizer.java b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilListSingularizer.java index 9ec77e78..3002a98f 100644 --- a/src/core/lombok/javac/handlers/singulars/JavacJavaUtilListSingularizer.java +++ b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilListSingularizer.java @@ -87,7 +87,7 @@ public class JavacJavaUtilListSingularizer extends JavacJavaUtilListSetSingulari cases.append(defaultCase); } - JCStatement switchStat = maker.Switch(getSize(maker, builderType, data.getPluralName(), true), cases.toList()); + 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); diff --git a/src/core/lombok/javac/handlers/singulars/JavacJavaUtilSingularizer.java b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilSingularizer.java index f419a014..25669721 100644 --- a/src/core/lombok/javac/handlers/singulars/JavacJavaUtilSingularizer.java +++ b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilSingularizer.java @@ -89,7 +89,7 @@ abstract class JavacJavaUtilSingularizer extends JavacSingularizer { cases.append(defaultCase); } - JCStatement switchStat = maker.Switch(getSize(maker, builderType, mapMode ? builderType.toName(data.getPluralName() + "$key") : data.getPluralName(), true), cases.toList()); + 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); @@ -136,10 +136,10 @@ abstract class JavacJavaUtilSingularizer extends JavacSingularizer { Name varName = mapMode ? builderType.toName(data.getPluralName() + "$key") : data.getPluralName(); // this.varName.size() < MAX_POWER_OF_2 ? 1 + this.varName.size() + (this.varName.size() - 3) / 3 : Integer.MAX_VALUE; // lessThanCutOff = this.varName.size() < MAX_POWER_OF_2 - JCExpression lessThanCutoff = maker.Binary(CTC_LESS_THAN, getSize(maker, builderType, varName, nullGuard), maker.Literal(CTC_INT, 0x40000000)); + 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)); - JCExpression sizeFormulaRightLeft = maker.Binary(CTC_MINUS, getSize(maker, builderType, varName, nullGuard), maker.Literal(CTC_INT, 3)); + 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)); @@ -167,7 +167,7 @@ abstract class JavacJavaUtilSingularizer extends JavacSingularizer { JCExpression arg2 = maker.Apply(jceBlank, chainDots(builderType, "this", data.getPluralName() + "$value", "get"), List.<JCExpression>of(maker.Ident(ivar))); 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)); + 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 { diff --git a/src/core/lombok/package-info.java b/src/core/lombok/package-info.java index b5406a74..5e34c914 100644 --- a/src/core/lombok/package-info.java +++ b/src/core/lombok/package-info.java @@ -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/index.html">Lombok features</a> */ package lombok; diff --git a/src/core/lombok/val.java b/src/core/lombok/val.java index cd8652d6..a398fd34 100644 --- a/src/core/lombok/val.java +++ b/src/core/lombok/val.java @@ -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.html">the project lombok features page for @val</a>. */ public @interface val { } 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/PrettyCommentsPrinter.java b/src/delombok/lombok/delombok/PrettyCommentsPrinter.java deleted file mode 100644 index f57b74a2..00000000 --- a/src/delombok/lombok/delombok/PrettyCommentsPrinter.java +++ /dev/null @@ -1,1715 +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.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; - 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(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("\'" + 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()) + "\""); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public static String quoteChars(String s) { - StringBuilder buf = new StringBuilder(); - for (int i = 0; i < s.length(); i++) { - buf.append(quoteChar(s.charAt(i))); - } - return buf.toString(); - } - - /** - * Escapes a character if it has an escape sequence or is non-printable - * ASCII. Leaves non-ASCII characters alone. - */ - public 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: - return ch < 32 ? String.format("\\%03o", (int) ch) : String.valueOf(ch); - } - } - - 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 { - @SuppressWarnings("unchecked") - List<JCVariableDecl> params = (List<JCVariableDecl>) readTreeList(tree, "params"); - boolean explicit = true; - int paramLength = params.size(); - if (paramLength != 1) print("("); - 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 = ", "; - } - } - if (paramLength != 1) print(")"); - 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..d3b65cb8 --- /dev/null +++ b/src/delombok/lombok/delombok/PrettyPrinter.java @@ -0,0 +1,1488 @@ +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.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); + if (tree.pid != null) { + consumeComments(tree); + aPrint("package "); + print(tree.pid); + println(";", tree.pid); + } + + 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("."); + } + + print("new "); + if (!tree.typeargs.isEmpty()) { + print("<"); + print(tree.typeargs, ", "); + print(">"); + } + print(tree.clazz); + print("("); + 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()) { + 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 { + print(tree.init, ", "); + } + } + 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; + } + } + + @Override 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, " | "); + return; + } else if ("JCTypeIntersection".equals(simpleName)) { + print(readObject(tree, "bounds", List.<JCExpression>nil()), " & "); + return; + } else if ("JCMemberReference".equals(simpleName)) { + printMemberReference0(tree); + return; + } else if ("JCLambda".equals(simpleName)) { + printLambda0(tree); + return; + } else if ("JCAnnotatedType".equals(simpleName)) { + printAnnotatedType0(tree); + return; + } + + 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(); + if (paramLength != 1) print("("); + try { + explicit = readObject(tree, "paramKind", new Object()).toString().equals("EXPLICIT"); + } catch (Exception e) {} + 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 (paramLength != 1) 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/eclipseAgent/lombok/eclipse/agent/EclipseLoaderPatcher.java b/src/eclipseAgent/lombok/eclipse/agent/EclipseLoaderPatcher.java index 8616e9bc..aa01c13d 100644 --- a/src/eclipseAgent/lombok/eclipse/agent/EclipseLoaderPatcher.java +++ b/src/eclipseAgent/lombok/eclipse/agent/EclipseLoaderPatcher.java @@ -44,7 +44,7 @@ public class EclipseLoaderPatcher { .transplant() .request(StackRequest.THIS, StackRequest.PARAM1, StackRequest.PARAM2).build()); - sm.addScript(ScriptBuilder.addField().setPublic() + sm.addScript(ScriptBuilder.addField().setPublic().setVolatile() .fieldType("Ljava/lang/ClassLoader;") .fieldName("lombok$shadowLoader") .targetClass("org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader") @@ -52,6 +52,14 @@ public class EclipseLoaderPatcher { .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") diff --git a/src/eclipseAgent/lombok/eclipse/agent/EclipseLoaderPatcherTransplants.java b/src/eclipseAgent/lombok/eclipse/agent/EclipseLoaderPatcherTransplants.java index f50e6987..ea72f56a 100644 --- a/src/eclipseAgent/lombok/eclipse/agent/EclipseLoaderPatcherTransplants.java +++ b/src/eclipseAgent/lombok/eclipse/agent/EclipseLoaderPatcherTransplants.java @@ -26,6 +26,7 @@ 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; @@ -55,44 +56,54 @@ public class EclipseLoaderPatcherTransplants { Field shadowLoaderField = original.getClass().getField("lombok$shadowLoader"); ClassLoader shadowLoader = (ClassLoader) shadowLoaderField.get(original); if (shadowLoader == null) { - String jarLoc = (String) original.getClass().getField("lombok$location").get(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."); + 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); } - in.close(); - Class classLoaderClass = Class.forName("java.lang.ClassLoader"); - Class shadowClassLoaderClass; { - 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)}); - } - Class[] paramTypes = new Class[4]; - paramTypes[0] = classLoaderClass; - paramTypes[1] = "".getClass(); - paramTypes[2] = paramTypes[1]; - paramTypes[3] = new String[0].getClass(); - Constructor constructor = shadowClassLoaderClass.getDeclaredConstructor(paramTypes); - constructor.setAccessible(true); - shadowLoader = (ClassLoader) constructor.newInstance(new Object[] {original, "lombok", jarLoc, new String[] {"lombok."}}); - shadowLoaderField.set(original, shadowLoader); - } finally { - if (in != null) in.close(); - jf.close(); } } diff --git a/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java b/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java index b8e3a955..7c538b6f 100644 --- a/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java +++ b/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java @@ -34,6 +34,7 @@ import lombok.patcher.MethodTarget; import lombok.patcher.ScriptManager; import lombok.patcher.StackRequest; import lombok.patcher.TargetMatcher; +import lombok.patcher.TransplantMapper; import lombok.patcher.scripts.ScriptBuilder; /** @@ -73,6 +74,14 @@ public class EclipsePatcher implements AgentLauncher.AgentLaunchable { private static void registerPatchScripts(Instrumentation instrumentation, boolean reloadExistingClasses, boolean ecjOnly, Class<?> launchingContext) { ScriptManager sm = new ScriptManager(); sm.registerTransformer(instrumentation); + final boolean forceBaseResourceNames = !"".equals(System.getProperty("shadow.override.lombok", "")); + 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); @@ -305,6 +314,13 @@ public class EclipsePatcher implements AgentLauncher.AgentLaunchable { .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()); + + sm.addScript(ScriptBuilder.setSymbolDuringMethodCall() + .target(new MethodTarget("org.eclipse.jdt.internal.corext.fix.CodeFormatFix", "createCleanUp", "org.eclipse.jdt.ui.cleanup.ICleanUpFix", + "org.eclipse.jdt.core.ICompilationUnit", "org.eclipse.jface.text.IRegion[]", "boolean", "boolean", "boolean", "boolean")) + .callToWrap(new Hook("org.eclipse.jdt.internal.corext.util.CodeFormatterUtil", "reformat", "org.eclipse.text.edits.TextEdit", + "int", "java.lang.String", "int", "java.lang.String", "java.util.Map")) + .symbol("lombok.disable").build()); } private static void patchRefactorScripts(ScriptManager sm) { @@ -353,11 +369,35 @@ public class EclipsePatcher implements AgentLauncher.AgentLaunchable { } 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.launch.PatchFixesHider$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) { @@ -655,6 +695,7 @@ public class EclipsePatcher implements AgentLauncher.AgentLaunchable { 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"; @@ -682,6 +723,13 @@ public class EclipsePatcher implements AgentLauncher.AgentLaunchable { .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/PatchExtensionMethod.java b/src/eclipseAgent/lombok/eclipse/agent/PatchExtensionMethod.java index ca0933fb..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) { @@ -233,7 +271,7 @@ public class PatchExtensionMethod { 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++) { 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/PatchFixesShadowLoaded.java b/src/eclipseAgent/lombok/eclipse/agent/PatchFixesShadowLoaded.java index 6685b6bb..52f63765 100644 --- a/src/eclipseAgent/lombok/eclipse/agent/PatchFixesShadowLoaded.java +++ b/src/eclipseAgent/lombok/eclipse/agent/PatchFixesShadowLoaded.java @@ -32,7 +32,7 @@ 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. http://projectlombok.org/"; + return origReturnValue + "\n\nLombok " + Version.getFullVersion() + " is installed. https://projectlombok.org/"; } return origReturnValue; } diff --git a/src/eclipseAgent/lombok/launch/PatchFixesHider.java b/src/eclipseAgent/lombok/launch/PatchFixesHider.java index 2472ca3c..fae06900 100644 --- a/src/eclipseAgent/lombok/launch/PatchFixesHider.java +++ b/src/eclipseAgent/lombok/launch/PatchFixesHider.java @@ -41,14 +41,17 @@ 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.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; @@ -258,13 +261,14 @@ final class PatchFixesHider { 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; + 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) { @@ -278,6 +282,10 @@ final class PatchFixesHider { 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); + } } /** @@ -469,7 +477,24 @@ final class PatchFixesHider { } public static int fixRetrieveRightBraceOrSemiColonPosition(int original, int end) { - return original == -1 ? end : original; // Need to fix: see issue 325. +// 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 diff --git a/src/installer/lombok/installer/Installer.java b/src/installer/lombok/installer/Installer.java index b9faeebd..7ae01d7a 100644 --- a/src/installer/lombok/installer/Installer.java +++ b/src/installer/lombok/installer/Installer.java @@ -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 { diff --git a/src/installer/lombok/installer/eclipse/EclipseLocation.java b/src/installer/lombok/installer/eclipse/EclipseLocation.java index 0fe60c05..6c63c48d 100644 --- a/src/installer/lombok/installer/eclipse/EclipseLocation.java +++ b/src/installer/lombok/installer/eclipse/EclipseLocation.java @@ -49,6 +49,7 @@ import lombok.installer.UninstallException; public class EclipseLocation extends IdeLocation { private final String name; private final File eclipseIniPath; + private final String pathToLombokJarPrefix; private volatile boolean hasLombok; private static final String OS_NEWLINE = IdeFinder.getOS().getLineEnding(); @@ -64,6 +65,15 @@ public class EclipseLocation extends IdeLocation { EclipseLocation(String nameOfLocation, File pathToEclipseIni) throws CorruptedIdeLocationException { 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) { @@ -333,10 +343,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); + "-javaagent:%s", escapePath(pathPrefix + "lombok.jar"))).append(OS_NEWLINE); FileOutputStream fos = new FileOutputStream(eclipseIniPath); try { diff --git a/src/installer/lombok/installer/eclipse/EclipseLocationProvider.java b/src/installer/lombok/installer/eclipse/EclipseLocationProvider.java index 914de588..29716a1f 100644 --- a/src/installer/lombok/installer/eclipse/EclipseLocationProvider.java +++ b/src/installer/lombok/installer/eclipse/EclipseLocationProvider.java @@ -104,11 +104,16 @@ public class EclipseLocationProvider implements IdeLocationProvider { 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. */ { + /* 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); } + /* 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(), getMacAppName() + "/Contents/Eclipse/" + getIniName()); + if (ini.isFile()) return makeLocation(canonical(exePath), ini); + } + /* If executable is a soft link, follow it and retry. */ { if (loopCounter < 50) { try { diff --git a/src/launch/lombok/launch/Main.java b/src/launch/lombok/launch/Main.java index 63d97d48..b81b6268 100644 --- a/src/launch/lombok/launch/Main.java +++ b/src/launch/lombok/launch/Main.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 @@ -22,10 +22,11 @@ package lombok.launch; import java.lang.reflect.InvocationTargetException; +import java.util.Arrays; class Main { static ClassLoader createShadowClassLoader() { - return new ShadowClassLoader(Main.class.getClassLoader(), "lombok"); + return new ShadowClassLoader(Main.class.getClassLoader(), "lombok", null, Arrays.<String>asList(), Arrays.asList("lombok.patcher.Symbols")); } public static void main(String[] args) throws Throwable { diff --git a/src/launch/lombok/launch/ShadowClassLoader.java b/src/launch/lombok/launch/ShadowClassLoader.java index f8f969ef..37c479ee 100644 --- a/src/launch/lombok/launch/ShadowClassLoader.java +++ b/src/launch/lombok/launch/ShadowClassLoader.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 @@ -33,10 +33,15 @@ 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; @@ -79,6 +84,8 @@ import java.util.jar.JarFile; */ 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; @@ -86,21 +93,16 @@ class ShadowClassLoader extends ClassLoader { private final List<File> override = new ArrayList<File>(); private final String sclSuffix; private final List<String> parentExclusion = new ArrayList<String>(); - - /** - * Calls the {@link ShadowClassLoader(ClassLoader, String, String, String[]) constructor with no exclusions and the source of this class as base. - */ - ShadowClassLoader(ClassLoader source, String sclSuffix) { - this(source, sclSuffix, null); - } + 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, String... parentExclusion) { + 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) { @@ -108,6 +110,9 @@ class ShadowClassLoader extends ClassLoader { 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; @@ -139,62 +144,100 @@ class ShadowClassLoader extends ClassLoader { } } } - - private static final String EMPTY_MARKER = new String("--EMPTY JAR--"); - private Map<String, Object> jarContentsCacheTrackers = new HashMap<String, Object>(); - private static WeakHashMap<Object, String> trackerCache = new WeakHashMap<Object, String>(); - private static WeakHashMap<Object, List<String>> jarContentsCache = new WeakHashMap<Object, List<String>>(); - + + 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 List<String> getOrMakeJarListing(String absolutePathToJar) { - List<String> list = retrieveFromCache(absolutePathToJar); - synchronized (list) { - if (list.isEmpty()) { - try { - JarFile jf = new JarFile(absolutePathToJar); - try { - Enumeration<JarEntry> entries = jf.entries(); - while (entries.hasMoreElements()) { - JarEntry jarEntry = entries.nextElement(); - if (!jarEntry.isDirectory()) list.add(jarEntry.getName()); - } - } finally { - jf.close(); - } - } catch (Exception ignore) {} - if (list.isEmpty()) list.add(EMPTY_MARKER); + 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; } - - if (list.size() == 1 && list.get(0) == EMPTY_MARKER) return Collections.emptyList(); - return list; } - private List<String> retrieveFromCache(String absolutePathToJar) { - synchronized (trackerCache) { - Object tracker = jarContentsCacheTrackers.get(absolutePathToJar); - if (tracker != null) return jarContentsCache.get(tracker); + /** + * 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); - for (Map.Entry<Object, String> entry : trackerCache.entrySet()) { - if (entry.getValue().equals(absolutePathToJar)) { - tracker = entry.getKey(); - break; + /* + * 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(); } - List<String> result = null; - if (tracker != null) result = jarContentsCache.get(tracker); - if (result != null) return result; - - tracker = new Object(); - List<String> list = new ArrayList<String>(); - jarContentsCache.put(tracker, list); - trackerCache.put(tracker, absolutePathToJar); - jarContentsCacheTrackers.put(absolutePathToJar, tracker); - return list; + return jarMembers; + } + catch (Exception newJarFileException) { + return Collections.emptySet(); } } @@ -226,7 +269,7 @@ class ShadowClassLoader extends ClassLoader { absoluteFile = location.getAbsoluteFile(); } } - List<String> jarContents = getOrMakeJarListing(absoluteFile.getAbsolutePath()); + Set<String> jarContents = getOrMakeJarListing(absoluteFile.getAbsolutePath()); String absoluteUri = absoluteFile.toURI().toString(); @@ -234,13 +277,17 @@ class ShadowClassLoader extends ClassLoader { if (jarContents.contains(altName)) { return new URI("jar:" + absoluteUri + "!/" + altName).toURL(); } - } catch (Exception e) {} + } catch (Exception ignore) { + // intentional fallthrough + } try { if (jarContents.contains(name)) { return new URI("jar:" + absoluteUri + "!/" + name).toURL(); } - } catch(Exception e) {} + } catch(Exception ignore) { + // intentional fallthrough + } return null; } @@ -360,7 +407,12 @@ class ShadowClassLoader extends ClassLoader { if (alreadyLoaded != null) return alreadyLoaded; } - String fileNameOfClass = name.replace(".", "/") + ".class"; + 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); @@ -391,7 +443,22 @@ class ShadowClassLoader extends ClassLoader { throw new ClassNotFoundException("I/O exception reading class " + name, e); } - Class<?> c = defineClass(name, b, 0, p); + 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; + } + throw e; + } + + if (highlanders.contains(name)) { + Class<?> alreadyDefined = highlanderMap.putIfAbsent(name, c); + if (alreadyDefined != null) c = alreadyDefined; + } + if (resolve) resolveClass(c); return c; } 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) { |