diff options
Diffstat (limited to 'src')
66 files changed, 5770 insertions, 1239 deletions
diff --git a/src/core/lombok/Builder.java b/src/core/lombok/Builder.java new file mode 100644 index 00000000..9cbd2d58 --- /dev/null +++ b/src/core/lombok/Builder.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2013-2014 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.*; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * The builder annotation creates a so-called 'builder' aspect to the class that is annotated or the class + * that contains a member which is annotated with {@code @Builder}. + * <p> + * If a member is annotated, it must be either a constructor or a static method. If a class is annotated, + * then a private constructor is generated with all fields as arguments + * (as if {@code @AllArgsConstructor(AccessLevel.PRIVATE)} is present + * on the class), and it is as if this constructor has been annotated with {@code @Builder} instead. + * <p> + * The effect of {@code @Builder} is that an inner class is generated named <code><strong>T</strong>Builder</code>, + * with a private constructor. Instances of <code><strong>T</strong>Builder</code> are made with the static + * method named {@code builder()} which is also generated for you in the class itself (not in the builder class). + * <p> + * The <code><strong>T</strong>Builder</code> class contains 1 method for each parameter of the annotated + * constructor / static method (each field, when annotating a class), which returns the builder itself. + * The builder also has a <code>build()</code> method which returns a completed instance of the original type, + * created by passing all parameters as set via the various other methods in the builder to the constructor + * or static method that was annotated with {@code @Builder}. The return type of this method will be the same + * as the relevant class, unless a static method has been annotated, in which case it'll be equal to the + * return type of that method. + * <p> + * Complete documentation is found at <a href="http://projectlombok.org/features/experimental/Builder.html">the project lombok features page for @Builder</a>. + * <p> + * <p> + * Before: + * + * <pre> + * @Builder + * class Example { + * private int foo; + * private final String bar; + * } + * </pre> + * + * After: + * + * <pre> + * class Example<T> { + * private T foo; + * private final String bar; + * + * private Example(T foo, String bar) { + * this.foo = foo; + * this.bar = bar; + * } + * + * public static <T> ExampleBuilder<T> builder() { + * return new ExampleBuilder<T>(); + * } + * + * public static class ExampleBuilder<T> { + * private T foo; + * private String bar; + * + * private ExampleBuilder() {} + * + * public ExampleBuilder foo(T foo) { + * this.foo = foo; + * return this; + * } + * + * public ExampleBuilder bar(String bar) { + * this.bar = bar; + * return this; + * } + * + * @java.lang.Override public String toString() { + * return "ExampleBuilder(foo = " + foo + ", bar = " + bar + ")"; + * } + * + * public Example build() { + * return new Example(foo, bar); + * } + * } + * } + * </pre> + */ +@Target({TYPE, METHOD, CONSTRUCTOR}) +@Retention(SOURCE) +public @interface Builder { + /** Name of the static method that creates a new builder instance. Default: {@code builder}. */ + String builderMethodName() default "builder"; + + /** Name of the instance method in the builder class that creates an instance of your {@code @Builder}-annotated class. */ + String buildMethodName() default "build"; + + /** Name of the builder class. + * Default for {@code @Builder} on types and constructors: {@code (TypeName)Builder}. + * Default for {@code @Builder} on static methods: {@code (ReturnTypeName)Builder}. + */ + String builderClassName() default ""; +} diff --git a/src/core/lombok/ConfigurationKeys.java b/src/core/lombok/ConfigurationKeys.java index 301563b8..8d8fb03b 100644 --- a/src/core/lombok/ConfigurationKeys.java +++ b/src/core/lombok/ConfigurationKeys.java @@ -133,9 +133,9 @@ public class ConfigurationKeys { /** * lombok configuration: {@code lombok.equalsAndHashCode.doNotUseGetters} = {@code true} | {@code false}. * - * For any class without an {@code @EqualsAndHashCode} that explicitly defines the {@code doNotUseGetters} option, this value is used. + * For any class without an {@code @EqualsAndHashCode} that explicitly defines the {@code doNotUseGetters} option, this value is used (default = false). */ - public static final ConfigurationKey<Boolean> EQUALS_AND_HASH_CODE_DO_NOT_USE_GETTERS = new ConfigurationKey<Boolean>("lombok.equalsAndHashCode.doNotUseGetters", "Don't call the getters but use the fields directly in the generated equalsAndHashCode method.") {}; + public static final ConfigurationKey<Boolean> EQUALS_AND_HASH_CODE_DO_NOT_USE_GETTERS = new ConfigurationKey<Boolean>("lombok.equalsAndHashCode.doNotUseGetters", "Don't call the getters but use the fields directly in the generated equalsAndHashCode method (default = false).") {}; /** * lombok configuration: {@code lombok.equalsAndHashCode.flagUsage} = {@code WARNING} | {@code ERROR}. @@ -149,9 +149,9 @@ public class ConfigurationKeys { /** * lombok configuration: {@code lombok.toString.doNotUseGetters} = {@code true} | {@code false}. * - * For any class without an {@code @ToString} that explicitly defines the {@code doNotUseGetters} option, this value is used. + * For any class without an {@code @ToString} that explicitly defines the {@code doNotUseGetters} option, this value is used (default = false). */ - public static final ConfigurationKey<Boolean> TO_STRING_DO_NOT_USE_GETTERS = new ConfigurationKey<Boolean>("lombok.toString.doNotUseGetters", "Don't call the getters but use the fields directly in the generated toString method.") {}; + public static final ConfigurationKey<Boolean> TO_STRING_DO_NOT_USE_GETTERS = new ConfigurationKey<Boolean>("lombok.toString.doNotUseGetters", "Don't call the getters but use the fields directly in the generated toString method (default = false).") {}; /** * lombok configuration: {@code lombok.toString.flagUsage} = {@code WARNING} | {@code ERROR}. @@ -163,9 +163,35 @@ public class ConfigurationKeys { /** * lombok configuration: {@code lombok.toString.includeFieldNames} = {@code true} | {@code false}. * - * For any class without an {@code @ToString} that explicitly defines the {@code includeFieldNames} option, this value is used. + * For any class without an {@code @ToString} that explicitly defines the {@code includeFieldNames} option, this value is used (default = true). */ - public static final ConfigurationKey<Boolean> TO_STRING_INCLUDE_FIELD_NAMES = new ConfigurationKey<Boolean>("lombok.toString.includeFieldNames", "Include the field names in the generated toString method.") {}; + public static final ConfigurationKey<Boolean> TO_STRING_INCLUDE_FIELD_NAMES = new ConfigurationKey<Boolean>("lombok.toString.includeFieldNames", "Include the field names in the generated toString method (default = true).") {}; + + // ----- Builder ----- + + /** + * lombok configuration: {@code lombok.builder.flagUsage} = {@code WARNING} | {@code ERROR}. + * + * If set, <em>any</em> usage of {@code @Builder} results in a warning / error. + */ + public static final ConfigurationKey<FlagUsageType> BUILDER_FLAG_USAGE = new ConfigurationKey<FlagUsageType>("lombok.builder.flagUsage", "Emit a warning or error if @Builder is used.") {}; + + // ----- Singular ----- + + /** + * lombok configuration: {@code lombok.singular.useGuava} = {@code true} | {@code false}. + * + * If explicitly set to {@code true}, guava's {@code ImmutableList} etc are used to implement the immutable collection datatypes generated by @Singular @Builder for fields/parameters of type {@code java.util.List} and such. + * By default, unmodifiable-wrapped versions of {@code java.util} types are used. + */ + public static final ConfigurationKey<Boolean> SINGULAR_USE_GUAVA = new ConfigurationKey<Boolean>("lombok.singular.useGuava", "Generate backing immutable implementations for @Singular on java.util.* types by using guava's ImmutableList, etc. Normally java.util's mutable types are used and wrapped to make them immutable.") {}; + + /** + * lombok configuration: {@code lombok.singular.auto} = {@code true} | {@code false}. + * + * By default or if explicitly set to {@code true}, lombok will attempt to automatically singularize the name of your variable/parameter when using {@code @Singular}; the name is assumed to be written in english, and a plural. If explicitly to {@code false}, you must always specify the singular form; this is especially useful if your identifiers are in a foreign language. + */ + public static final ConfigurationKey<Boolean> SINGULAR_AUTO = new ConfigurationKey<Boolean>("lombok.singular.auto", "If true (default): Automatically singularize the assumed-to-be-plural name of your variable/parameter when using {@code @Singular}.") {}; // ##### Standalones ##### @@ -194,7 +220,7 @@ public class ConfigurationKeys { * * Sets the exception to throw if {@code @NonNull} is applied to a method parameter, and a caller passes in {@code null}. */ - public static final ConfigurationKey<NullCheckExceptionType> NON_NULL_EXCEPTION_TYPE = new ConfigurationKey<NullCheckExceptionType>("lombok.nonNull.exceptionType", "The type of the exception to throw if a passed-in argument is null. Default: NullPointerException.") {}; + public static final ConfigurationKey<NullCheckExceptionType> NON_NULL_EXCEPTION_TYPE = new ConfigurationKey<NullCheckExceptionType>("lombok.nonNull.exceptionType", "The type of the exception to throw if a passed-in argument is null (Default: NullPointerException).") {}; /** * lombok configuration: {@code lombok.nonNull.flagUsage} = {@code WARNING} | {@code ERROR}. @@ -289,7 +315,7 @@ public class ConfigurationKeys { * * If set the various log annotations (which make a log field) will use the stated identifier instead of {@code log} as a name. */ - public static final ConfigurationKey<String> LOG_ANY_FIELD_NAME = new ConfigurationKey<String>("lombok.log.fieldName", "Use this name for the generated logger fields (default: 'log')") {}; + public static final ConfigurationKey<String> LOG_ANY_FIELD_NAME = new ConfigurationKey<String>("lombok.log.fieldName", "Use this name for the generated logger fields (default: 'log').") {}; /** * lombok configuration: {@code lombok.log.fieldIsStatic} = {@code true} | {@code false}. @@ -329,25 +355,16 @@ public class ConfigurationKeys { /** * lombok configuration: {@code lombok.accessors.chain} = {@code true} | {@code false}. * - * For any class without an {@code @Accessors} that explicitly defines the {@code chain} option, this value is used. + * For any class without an {@code @Accessors} that explicitly defines the {@code chain} option, this value is used (default = false). */ - public static final ConfigurationKey<Boolean> ACCESSORS_CHAIN = new ConfigurationKey<Boolean>("lombok.accessors.chain", "Generate setters that return 'this' instead of 'void'.") {}; + public static final ConfigurationKey<Boolean> ACCESSORS_CHAIN = new ConfigurationKey<Boolean>("lombok.accessors.chain", "Generate setters that return 'this' instead of 'void' (default: false).") {}; /** * lombok configuration: {@code lombok.accessors.fluent} = {@code true} | {@code false}. * - * For any class without an {@code @Accessors} that explicitly defines the {@code fluent} option, this value is used. + * For any class without an {@code @Accessors} that explicitly defines the {@code fluent} option, this value is used (default = false). */ - public static final ConfigurationKey<Boolean> ACCESSORS_FLUENT = new ConfigurationKey<Boolean>("lombok.accessors.fluent", "Generate getters and setters using only the field name (no get/set prefix).") {}; - - // ----- Builder ----- - - /** - * lombok configuration: {@code lombok.builder.flagUsage} = {@code WARNING} | {@code ERROR}. - * - * If set, <em>any</em> usage of {@code @Builder} results in a warning / error. - */ - public static final ConfigurationKey<FlagUsageType> BUILDER_FLAG_USAGE = new ConfigurationKey<FlagUsageType>("lombok.builder.flagUsage", "Emit a warning or error if @Builder is used.") {}; + public static final ConfigurationKey<Boolean> ACCESSORS_FLUENT = new ConfigurationKey<Boolean>("lombok.accessors.fluent", "Generate getters and setters using only the field name (no get/set prefix) (default: false).") {}; // ----- ExtensionMethod ----- diff --git a/src/core/lombok/Singular.java b/src/core/lombok/Singular.java new file mode 100644 index 00000000..15dec4a5 --- /dev/null +++ b/src/core/lombok/Singular.java @@ -0,0 +1,38 @@ +/* + * 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; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.*; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * The singular annotation is used together with {@code @Builder} to create single element 'add' methods in the builder for collections. + * <p> + */ +@Target({FIELD, PARAMETER}) +@Retention(SOURCE) +public @interface Singular { + String value() default ""; +} diff --git a/src/core/lombok/bytecode/PostCompilerApp.java b/src/core/lombok/bytecode/PostCompilerApp.java index d2c3157c..5f49bd81 100644 --- a/src/core/lombok/bytecode/PostCompilerApp.java +++ b/src/core/lombok/bytecode/PostCompilerApp.java @@ -98,7 +98,7 @@ public class PostCompilerApp extends LombokApp { byte[] original = readFile(file); byte[] clone = original.clone(); byte[] transformed = PostCompiler.applyTransformations(clone, file.toString(), DiagnosticsReceiver.CONSOLE); - if (clone != transformed && !Arrays.equals(clone, transformed)) { + if (clone != transformed && !Arrays.equals(original, transformed)) { filesTouched++; if (args.verbose) System.out.println("Rewriting " + file.getAbsolutePath()); writeFile(file, transformed); @@ -138,18 +138,24 @@ public class PostCompilerApp extends LombokApp { byte[] buffer = new byte[1024]; ByteArrayOutputStream bytes = new ByteArrayOutputStream(); FileInputStream fileInputStream = new FileInputStream(file); - while (true) { - int read = fileInputStream.read(buffer); - if (read == -1) break; - bytes.write(buffer, 0, read); + try { + while (true) { + int read = fileInputStream.read(buffer); + if (read == -1) break; + bytes.write(buffer, 0, read); + } + } finally { + fileInputStream.close(); } - fileInputStream.close(); return bytes.toByteArray(); } static void writeFile(File file, byte[] transformed) throws IOException { FileOutputStream out = new FileOutputStream(file); - out.write(transformed); - out.close(); + try { + out.write(transformed); + } finally { + out.close(); + } } } diff --git a/src/core/lombok/core/Agent.java b/src/core/lombok/core/AgentLauncher.java index 49c03bf3..1d5ab3e6 100644 --- a/src/core/lombok/core/Agent.java +++ b/src/core/lombok/core/AgentLauncher.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009 The Project Lombok Authors. + * Copyright (C) 2009-2014 The Project Lombok Authors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,45 +21,32 @@ */ package lombok.core; -import java.lang.instrument.ClassFileTransformer; -import java.lang.instrument.IllegalClassFormatException; import java.lang.instrument.Instrumentation; -import java.security.ProtectionDomain; import java.util.Arrays; import java.util.Collections; import java.util.List; -import javax.swing.JOptionPane; -import javax.swing.SwingUtilities; - -public abstract class Agent { - protected abstract void runAgent(String agentArgs, Instrumentation instrumentation, boolean injected) throws Exception; - - public static void agentmain(String agentArgs, Instrumentation instrumentation) throws Throwable { - runAgents(agentArgs, instrumentation, true); +public class AgentLauncher { + public interface AgentLaunchable { + void runAgent(String agentArgs, Instrumentation instrumentation, boolean injected, Class<?> launchingContext) throws Exception; } - public static void premain(String agentArgs, Instrumentation instrumentation) throws Throwable { - runAgents(agentArgs, instrumentation, false); - } - - private static final List<AgentInfo> AGENTS = Collections.unmodifiableList(Arrays.asList( - new NetbeansPatcherInfo(), - new EclipsePatcherInfo() - )); - - private static void runAgents(String agentArgs, Instrumentation instrumentation, boolean injected) throws Throwable { + public static void runAgents(String agentArgs, Instrumentation instrumentation, boolean injected, Class<?> launchingContext) throws Throwable { for (AgentInfo info : AGENTS) { try { Class<?> agentClass = Class.forName(info.className()); - Agent agent = (Agent) agentClass.newInstance(); - agent.runAgent(agentArgs, instrumentation, injected); + AgentLaunchable agent = (AgentLaunchable) agentClass.newInstance(); + agent.runAgent(agentArgs, instrumentation, injected, launchingContext); } catch (Throwable t) { info.problem(t, instrumentation); } } } + private static final List<AgentInfo> AGENTS = Collections.unmodifiableList(Arrays.<AgentInfo>asList( + new EclipsePatcherInfo() + )); + private static abstract class AgentInfo { abstract String className(); @@ -91,45 +78,6 @@ public abstract class Agent { } } - private static class NetbeansPatcherInfo extends AgentInfo { - @Override String className() { - return "lombok.netbeans.agent.NetbeansPatcher"; - } - - @Override void problem(Throwable in, Instrumentation instrumentation) throws Throwable { - try { - super.problem(in, instrumentation); - } catch (InternalError ie) { - throw ie; - } catch (Throwable t) { - final String error; - - if (t instanceof UnsupportedClassVersionError) { - error = "Lombok only works on netbeans if you start netbeans using a 1.6 or higher JVM.\n" + - "Change your platform's default JVM, or edit etc/netbeans.conf\n" + - "and explicitly tell netbeans your 1.6 JVM's location."; - } else { - error = "Lombok disabled due to error: " + t; - } - - instrumentation.addTransformer(new ClassFileTransformer() { - @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { - if ("org/netbeans/modules/java/source/parsing/JavacParser".equals(className)) { - //If that class gets loaded, this is definitely a netbeans(-esque) environment, and thus we SHOULD tell the user that lombok is not in fact loaded. - SwingUtilities.invokeLater(new Runnable() { - @Override public void run() { - JOptionPane.showMessageDialog(null, error, "Lombok Disabled", JOptionPane.ERROR_MESSAGE); - } - }); - } - - return null; - } - }); - } - } - } - private static class EclipsePatcherInfo extends AgentInfo { @Override String className() { return "lombok.eclipse.agent.EclipsePatcher"; diff --git a/src/core/lombok/core/GuavaTypeMap.java b/src/core/lombok/core/GuavaTypeMap.java new file mode 100644 index 00000000..900d2b72 --- /dev/null +++ b/src/core/lombok/core/GuavaTypeMap.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2015 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.core; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public final class GuavaTypeMap { + private static final Map<String, String> TYPE_TO_GUAVA_TYPE; static { + Map<String, String> m = new HashMap<String, String>(); + + m.put("java.util.NavigableSet", "ImmutableSortedSet"); + m.put("java.util.NavigableMap", "ImmutableSortedMap"); + m.put("java.util.SortedSet", "ImmutableSortedSet"); + m.put("java.util.SortedMap", "ImmutableSortedMap"); + m.put("java.util.Set", "ImmutableSet"); + m.put("java.util.Map", "ImmutableMap"); + m.put("java.util.Collection", "ImmutableList"); + m.put("java.util.List", "ImmutableList"); + + m.put("com.google.common.collect.ImmutableSet", "ImmutableSet"); + m.put("com.google.common.collect.ImmutableSortedSet", "ImmutableSortedSet"); + m.put("com.google.common.collect.ImmutableMap", "ImmutableMap"); + m.put("com.google.common.collect.ImmutableBiMap", "ImmutableBiMap"); + m.put("com.google.common.collect.ImmutableSortedMap", "ImmutableSortedMap"); + m.put("com.google.common.collect.ImmutableList", "ImmutableList"); + m.put("com.google.common.collect.ImmutableCollection", "ImmutableList"); + + TYPE_TO_GUAVA_TYPE = Collections.unmodifiableMap(m); + } + + public static String getGuavaTypeName(String fqn) { + String target = TYPE_TO_GUAVA_TYPE.get(fqn); + return target != null ? target : "ImmutableList"; + } + + private GuavaTypeMap() {} +} diff --git a/src/core/lombok/core/LombokInternalAliasing.java b/src/core/lombok/core/LombokInternalAliasing.java index 8d6794ae..08764a5c 100644 --- a/src/core/lombok/core/LombokInternalAliasing.java +++ b/src/core/lombok/core/LombokInternalAliasing.java @@ -50,6 +50,7 @@ public class LombokInternalAliasing { Map<String, String> m2 = new HashMap<String, String>(); m2.put("lombok.experimental.Value", "lombok.Value"); + m2.put("lombok.experimental.Builder", "lombok.Builder"); m2.put("lombok.Delegate", "lombok.experimental.Delegate"); ALIASES = Collections.unmodifiableMap(m2); } diff --git a/src/core/lombok/core/Main.java b/src/core/lombok/core/Main.java index d62fe3e4..dc613b0d 100644 --- a/src/core/lombok/core/Main.java +++ b/src/core/lombok/core/Main.java @@ -38,6 +38,7 @@ public class Main { )); public static void main(String[] args) throws IOException { + Thread.currentThread().setContextClassLoader(Main.class.getClassLoader()); int err = new Main(SpiLoadUtil.readAllFromIterator( SpiLoadUtil.findServices(LombokApp.class)), Arrays.asList(args)).go(); System.exit(err); @@ -142,7 +143,7 @@ public class Main { out.println("------------------------------"); } out.println("projectlombok.org " + Version.getFullVersion()); - out.println("Copyright (C) 2009-2012 The Project Lombok Authors."); + out.println("Copyright (C) 2009-2015 The Project Lombok Authors."); out.println("Run 'lombok license' to see the lombok license agreement."); out.println(); out.println("Run lombok without any parameters to start the graphical installer."); diff --git a/src/core/lombok/core/PostCompiler.java b/src/core/lombok/core/PostCompiler.java index 11091cb8..237867cb 100644 --- a/src/core/lombok/core/PostCompiler.java +++ b/src/core/lombok/core/PostCompiler.java @@ -24,6 +24,8 @@ package lombok.core; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; +import java.io.PrintWriter; +import java.io.StringWriter; import java.util.Collections; import java.util.List; @@ -55,10 +57,12 @@ public final class PostCompiler { transformations = SpiLoadUtil.readAllFromIterator(SpiLoadUtil.findServices(PostCompilerTransformation.class, PostCompilerTransformation.class.getClassLoader())); } catch (IOException e) { transformations = Collections.emptyList(); - diagnostics.addWarning("Could not load post-compile transformers: " + e.getMessage()); + StringWriter sw = new StringWriter(); + e.printStackTrace(new PrintWriter(sw, true)); + diagnostics.addWarning("Could not load post-compile transformers: " + e.getMessage() + "\n" + sw.toString()); } } - + public static OutputStream wrapOutputStream(final OutputStream originalStream, final String fileName, final DiagnosticsReceiver diagnostics) throws IOException { if (System.getProperty("lombok.disablePostCompiler", null) != null) return originalStream; return new ByteArrayOutputStream() { diff --git a/src/core/lombok/core/TypeLibrary.java b/src/core/lombok/core/TypeLibrary.java index c0e9dc43..dc557c47 100644 --- a/src/core/lombok/core/TypeLibrary.java +++ b/src/core/lombok/core/TypeLibrary.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2013 The Project Lombok Authors. + * Copyright (C) 2009-2015 The Project Lombok Authors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -37,6 +37,7 @@ import java.util.Map; public class TypeLibrary { private final Map<String, String> unqualifiedToQualifiedMap; private final String unqualified, qualified; + private boolean locked; public TypeLibrary() { unqualifiedToQualifiedMap = new HashMap<String, String>(); @@ -44,6 +45,10 @@ public class TypeLibrary { qualified = null; } + public void lock() { + this.locked = true; + } + private TypeLibrary(String fqnSingleton) { unqualifiedToQualifiedMap = null; qualified = fqnSingleton; @@ -53,6 +58,7 @@ public class TypeLibrary { } else { unqualified = fqnSingleton.substring(idx + 1); } + locked = true; } public static TypeLibrary createLibraryForSingleType(String fqnSingleton) { @@ -65,6 +71,7 @@ public class TypeLibrary { * @param fullyQualifiedTypeName the FQN type name, such as 'java.lang.String'. */ public void addType(String fullyQualifiedTypeName) { + if (locked) throw new IllegalStateException("locked"); fullyQualifiedTypeName = fullyQualifiedTypeName.replace("$", "."); int idx = fullyQualifiedTypeName.lastIndexOf('.'); if (idx == -1) throw new IllegalArgumentException( diff --git a/src/core/lombok/core/Version.java b/src/core/lombok/core/Version.java index 9f810525..b0f12916 100644 --- a/src/core/lombok/core/Version.java +++ b/src/core/lombok/core/Version.java @@ -30,9 +30,9 @@ public class Version { // ** CAREFUL ** - this class must always compile with 0 dependencies (it must not refer to any other sources or libraries). // Note: In 'X.Y.Z', if Z is odd, its a snapshot build built from the repository, so many different 0.10.3 versions can exist, for example. // Official builds always end in an even number. (Since 0.10.2). - private static final String VERSION = "1.14.9"; + private static final String VERSION = "1.16.1"; private static final String RELEASE_NAME = "Edgy Guinea Pig"; -// private static final String RELEASE_NAME = "Branching Cobra"; +// private static final String RELEASE_NAME = "Candid Duck"; private Version() { //Prevent instantiation diff --git a/src/core/lombok/core/handlers/HandlerUtil.java b/src/core/lombok/core/handlers/HandlerUtil.java index 621d8760..87462921 100644 --- a/src/core/lombok/core/handlers/HandlerUtil.java +++ b/src/core/lombok/core/handlers/HandlerUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2014 The Project Lombok Authors. + * Copyright (C) 2013-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 @@ -87,6 +87,9 @@ public class HandlerUtil { return true; } + public static String autoSingularize(String plural) { + return Singulars.autoSingularize(plural); + } public static void handleFlagUsage(LombokNode<?, ?, ?> node, ConfigurationKey<FlagUsageType> key, String featureName) { FlagUsageType fut = node.getAst().readConfiguration(key); @@ -303,7 +306,7 @@ public class HandlerUtil { return booleanPrefix + fName.substring(2); } - return buildName(isBoolean ? booleanPrefix : normalPrefix, fName); + return buildAccessorName(isBoolean ? booleanPrefix : normalPrefix, fName); } /** @@ -375,8 +378,8 @@ public class HandlerUtil { if (adhereToFluent && fluent) { names.add(baseName); } else { - names.add(buildName(normalPrefix, baseName)); - if (!normalPrefix.equals(booleanPrefix)) names.add(buildName(booleanPrefix, baseName)); + names.add(buildAccessorName(normalPrefix, baseName)); + if (!normalPrefix.equals(booleanPrefix)) names.add(buildAccessorName(booleanPrefix, baseName)); } } @@ -407,7 +410,7 @@ public class HandlerUtil { * @param suffix Something like {@code running}. * @return prefix + smartly title-cased suffix. For example, {@code setRunning}. */ - private static String buildName(String prefix, String suffix) { + public static String buildAccessorName(String prefix, String suffix) { if (suffix.length() == 0) return prefix; if (prefix.length() == 0) return suffix; diff --git a/src/core/lombok/core/handlers/Singulars.java b/src/core/lombok/core/handlers/Singulars.java new file mode 100644 index 00000000..87895790 --- /dev/null +++ b/src/core/lombok/core/handlers/Singulars.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2015 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.core.handlers; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; + +public class Singulars { + private static final List<String> SINGULAR_STORE; // intended to be immutable. + + static { + SINGULAR_STORE = new ArrayList<String>(); + + try { + InputStream in = Singulars.class.getResourceAsStream("singulars.txt"); + try { + BufferedReader br = new BufferedReader(new InputStreamReader(in, "UTF-8")); + for (String line = br.readLine(); line != null; line = br.readLine()) { + line = line.trim(); + if (line.startsWith("#") || line.isEmpty()) continue; + if (line.endsWith(" =")) { + SINGULAR_STORE.add(line.substring(0, line.length() - 2)); + SINGULAR_STORE.add(""); + continue; + } + + int idx = line.indexOf(" = "); + SINGULAR_STORE.add(line.substring(0, idx)); + SINGULAR_STORE.add(line.substring(idx + 3)); + } + } finally { + try { + in.close(); + } catch (Throwable ignore) {} + } + } catch (IOException e) { + SINGULAR_STORE.clear(); + } + } + + public static String autoSingularize(String in) { + final int inLen = in.length(); + for (int i = 0; i < SINGULAR_STORE.size(); i+= 2) { + final String lastPart = SINGULAR_STORE.get(i); + final boolean wholeWord = Character.isUpperCase(lastPart.charAt(0)); + final int endingOnly = lastPart.charAt(0) == '-' ? 1 : 0; + final int len = lastPart.length(); + if (inLen < len) continue; + if (!in.regionMatches(true, inLen - len + endingOnly, lastPart, endingOnly, len - endingOnly)) continue; + if (wholeWord && inLen != len && !Character.isUpperCase(in.charAt(inLen - len))) continue; + + String replacement = SINGULAR_STORE.get(i + 1); + if (replacement.equals("!")) return null; + + boolean capitalizeFirst = !replacement.isEmpty() && Character.isUpperCase(in.charAt(inLen - len + endingOnly)); + String pre = in.substring(0, inLen - len + endingOnly); + String post = capitalizeFirst ? Character.toUpperCase(replacement.charAt(0)) + replacement.substring(1) : replacement; + return pre + post; + } + + return null; + } +} diff --git a/src/core/lombok/core/handlers/singulars.txt b/src/core/lombok/core/handlers/singulars.txt new file mode 100644 index 00000000..9976c76c --- /dev/null +++ b/src/core/lombok/core/handlers/singulars.txt @@ -0,0 +1,54 @@ +#Based on https://github.com/rails/rails/blob/efff6c1fd4b9e2e4c9f705a45879373cb34a5b0e/activesupport/lib/active_support/inflections.rb + +quizzes = quiz +matrices = matrix +indices = index +vertices = vertex +statuses = status +aliases = alias +alias = ! +species = ! +Axes = ! +-axes = axe +sexes = sex +Testes = testis +movies = movie +octopodes = octopus +buses = bus +Mice = mouse +Lice = louse +News = ! +# We could add more detail (axemen, boatsmen, boogymen, cavemen, gentlemen, etc, but (A) there's stuff like 'cerumen', and (B) the 'men' ending is common in singulars and other languages.) +# Therefore, the odds of a mistake are too high, so other than these 2 well known cases, so force the explicit singular. +Men = man +Women = woman +minutiae = minutia +shoes = shoe +synopses = synopsis +prognoses = prognosis +theses = thesis +diagnoses = diagnosis +bases = base +analyses = analysis +Crises = crisis +children = child +moves = move +zombies = zombie +-quies = quy +-us = ! +-is = ! +series = ! +-ies = y +-oes = o +hives = hive +-tives = tive +-sses = ss +-ches = ch +-xes = x +-shes = sh +-lves = lf +-rves = rf +-ves = fe +-ss = ! +-us = ! +-s = diff --git a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java index 8326e1d0..87e35269 100644 --- a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java +++ b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2014 The Project Lombok Authors. + * Copyright (C) 2009-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 @@ -143,7 +143,7 @@ public class EclipseHandlerUtil { return getGeneratedBy(node) != null; } - public static ASTNode setGeneratedBy(ASTNode node, ASTNode source) { + public static <T extends ASTNode> T setGeneratedBy(T node, ASTNode source) { ASTNode_generatedBy.set(node, source); return node; } @@ -201,6 +201,7 @@ public class EclipseHandlerUtil { public static void sanityCheckForMethodGeneratingAnnotationsOnBuilderClass(EclipseNode typeNode, EclipseNode errorNode) { List<String> disallowed = null; for (EclipseNode child : typeNode.down()) { + if (child.getKind() != Kind.ANNOTATION) continue; for (Class<? extends java.lang.annotation.Annotation> annType : INVALID_ON_BUILDERS) { if (annotationTypeMatches(annType, child)) { if (disallowed == null) disallowed = new ArrayList<String>(); @@ -298,6 +299,10 @@ public class EclipseHandlerUtil { return new SingleTypeReference(typeName, p); } + public static TypeReference[] copyTypes(TypeReference[] refs) { + return copyTypes(refs, null); + } + /** * Convenience method that creates a new array and copies each TypeReference in the source array via * {@link #copyType(TypeReference, ASTNode)}. @@ -312,6 +317,10 @@ public class EclipseHandlerUtil { return outs; } + public static TypeReference copyType(TypeReference ref) { + return copyType(ref, null); + } + /** * You can't share TypeReference objects or subtle errors start happening. * Unfortunately the TypeReference type hierarchy is complicated and there's no clone @@ -336,22 +345,23 @@ public class EclipseHandlerUtil { } } } + TypeReference typeRef = new ParameterizedQualifiedTypeReference(iRef.tokens, args, iRef.dimensions(), copy(iRef.sourcePositions)); - setGeneratedBy(typeRef, source); + if (source != null) setGeneratedBy(typeRef, source); return typeRef; } if (ref instanceof ArrayQualifiedTypeReference) { ArrayQualifiedTypeReference iRef = (ArrayQualifiedTypeReference) ref; TypeReference typeRef = new ArrayQualifiedTypeReference(iRef.tokens, iRef.dimensions(), copy(iRef.sourcePositions)); - setGeneratedBy(typeRef, source); + if (source != null) setGeneratedBy(typeRef, source); return typeRef; } if (ref instanceof QualifiedTypeReference) { QualifiedTypeReference iRef = (QualifiedTypeReference) ref; TypeReference typeRef = new QualifiedTypeReference(iRef.tokens, copy(iRef.sourcePositions)); - setGeneratedBy(typeRef, source); + if (source != null) setGeneratedBy(typeRef, source); return typeRef; } @@ -368,14 +378,14 @@ public class EclipseHandlerUtil { } TypeReference typeRef = new ParameterizedSingleTypeReference(iRef.token, args, iRef.dimensions(), (long)iRef.sourceStart << 32 | iRef.sourceEnd); - setGeneratedBy(typeRef, source); + if (source != null) setGeneratedBy(typeRef, source); return typeRef; } if (ref instanceof ArrayTypeReference) { ArrayTypeReference iRef = (ArrayTypeReference) ref; TypeReference typeRef = new ArrayTypeReference(iRef.token, iRef.dimensions(), (long)iRef.sourceStart << 32 | iRef.sourceEnd); - setGeneratedBy(typeRef, source); + if (source != null) setGeneratedBy(typeRef, source); return typeRef; } @@ -386,14 +396,14 @@ public class EclipseHandlerUtil { wildcard.sourceStart = original.sourceStart; wildcard.sourceEnd = original.sourceEnd; if (original.bound != null) wildcard.bound = copyType(original.bound, source); - setGeneratedBy(wildcard, source); + if (source != null) setGeneratedBy(wildcard, source); return wildcard; } if (ref instanceof SingleTypeReference) { SingleTypeReference iRef = (SingleTypeReference) ref; TypeReference typeRef = new SingleTypeReference(iRef.token, (long)iRef.sourceStart << 32 | iRef.sourceEnd); - setGeneratedBy(typeRef, source); + if (source != null) setGeneratedBy(typeRef, source); return typeRef; } @@ -401,19 +411,17 @@ public class EclipseHandlerUtil { } public static Annotation[] copyAnnotations(ASTNode source, Annotation[]... allAnnotations) { - boolean allNull = true; - - List<Annotation> result = new ArrayList<Annotation>(); + List<Annotation> result = null; for (Annotation[] annotations : allAnnotations) { if (annotations != null) { - allNull = false; for (Annotation annotation : annotations) { + if (result == null) result = new ArrayList<Annotation>(); result.add(copyAnnotation(annotation, source)); } } } - if (allNull) return null; - return result.toArray(new Annotation[0]); + + return result == null ? null : result.toArray(new Annotation[0]); } public static boolean hasAnnotation(Class<? extends java.lang.annotation.Annotation> type, EclipseNode node) { @@ -444,8 +452,12 @@ public class EclipseHandlerUtil { return typeMatches(type, node, ((Annotation)node.get()).type); } + public static TypeReference cloneSelfType(EclipseNode context) { + return cloneSelfType(context, null); + } + public static TypeReference cloneSelfType(EclipseNode context, ASTNode source) { - int pS = source.sourceStart, pE = source.sourceEnd; + int pS = source == null ? 0 : source.sourceStart, pE = source == null ? 0 : source.sourceEnd; long p = (long)pS << 32 | pE; EclipseNode type = context; TypeReference result = null; @@ -457,7 +469,7 @@ public class EclipseHandlerUtil { int idx = 0; for (TypeParameter param : typeDecl.typeParameters) { TypeReference typeRef = new SingleTypeReference(param.name, (long)param.sourceStart << 32 | param.sourceEnd); - setGeneratedBy(typeRef, source); + if (source != null) setGeneratedBy(typeRef, source); refs[idx++] = typeRef; } result = new ParameterizedSingleTypeReference(typeDecl.name, refs, 0, p); @@ -465,7 +477,7 @@ public class EclipseHandlerUtil { result = new SingleTypeReference(((TypeDeclaration)type.get()).name, p); } } - if (result != null) setGeneratedBy(result, source); + if (result != null && source != null) setGeneratedBy(result, source); return result; } @@ -668,47 +680,39 @@ public class EclipseHandlerUtil { */ public static <A extends java.lang.annotation.Annotation> AnnotationValues<A> createAnnotation(Class<A> type, final EclipseNode annotationNode) { + final Annotation annotation = (Annotation) annotationNode.get(); Map<String, AnnotationValue> values = new HashMap<String, AnnotationValue>(); - final MemberValuePair[] pairs = annotation.memberValuePairs(); - for (Method m : type.getDeclaredMethods()) { - if (!Modifier.isPublic(m.getModifiers())) continue; - String name = m.getName(); + MemberValuePair[] memberValuePairs = annotation.memberValuePairs(); + + if (memberValuePairs != null) for (final MemberValuePair pair : memberValuePairs) { List<String> raws = new ArrayList<String>(); List<Object> expressionValues = new ArrayList<Object>(); List<Object> guesses = new ArrayList<Object>(); - Expression fullExpression = null; Expression[] expressions = null; - if (pairs != null) for (MemberValuePair pair : pairs) { - char[] n = pair.name; - String mName = n == null ? "value" : new String(pair.name); - if (mName.equals(name)) fullExpression = pair.value; + char[] n = pair.name; + String mName = (n == null || n.length == 0) ? "value" : new String(pair.name); + final Expression rhs = pair.value; + if (rhs instanceof ArrayInitializer) { + expressions = ((ArrayInitializer)rhs).expressions; + } else if (rhs != null) { + expressions = new Expression[] { rhs }; } - - boolean isExplicit = fullExpression != null; - - if (isExplicit) { - if (fullExpression instanceof ArrayInitializer) { - expressions = ((ArrayInitializer)fullExpression).expressions; - } else expressions = new Expression[] { fullExpression }; - if (expressions != null) for (Expression ex : expressions) { - StringBuffer sb = new StringBuffer(); - ex.print(0, sb); - raws.add(sb.toString()); - expressionValues.add(ex); - guesses.add(calculateValue(ex)); - } + if (expressions != null) for (Expression ex : expressions) { + StringBuffer sb = new StringBuffer(); + ex.print(0, sb); + raws.add(sb.toString()); + expressionValues.add(ex); + guesses.add(calculateValue(ex)); } - final Expression fullExpr = fullExpression; final Expression[] exprs = expressions; - - values.put(name, new AnnotationValue(annotationNode, raws, expressionValues, guesses, isExplicit) { + values.put(mName, new AnnotationValue(annotationNode, raws, expressionValues, guesses, true) { @Override public void setError(String message, int valueIdx) { Expression ex; - if (valueIdx == -1) ex = fullExpr; + if (valueIdx == -1) ex = rhs; else ex = exprs != null ? exprs[valueIdx] : null; if (ex == null) ex = annotation; @@ -721,7 +725,7 @@ public class EclipseHandlerUtil { @Override public void setWarning(String message, int valueIdx) { Expression ex; - if (valueIdx == -1) ex = fullExpr; + if (valueIdx == -1) ex = rhs; else ex = exprs != null ? exprs[valueIdx] : null; if (ex == null) ex = annotation; @@ -734,6 +738,21 @@ public class EclipseHandlerUtil { }); } + for (Method m : type.getDeclaredMethods()) { + if (!Modifier.isPublic(m.getModifiers())) continue; + String name = m.getName(); + if (!values.containsKey(name)) { + values.put(name, new AnnotationValue(annotationNode, new ArrayList<String>(), new ArrayList<Object>(), new ArrayList<Object>(), false) { + @Override public void setError(String message, int valueIdx) { + annotationNode.addError(message); + } + @Override public void setWarning(String message, int valueIdx) { + annotationNode.addWarning(message); + } + }); + } + } + return new AnnotationValues<A>(type, values, annotationNode); } @@ -865,7 +884,7 @@ public class EclipseHandlerUtil { } static Expression createFieldAccessor(EclipseNode field, FieldAccess fieldAccess, ASTNode source) { - int pS = source.sourceStart, pE = source.sourceEnd; + int pS = source == null ? 0 : source.sourceStart, pE = source == null ? 0 : source.sourceEnd; long p = (long)pS << 32 | pE; boolean lookForGetter = lookForGetter(field, fieldAccess); @@ -881,14 +900,17 @@ public class EclipseHandlerUtil { ref.receiver = new SingleNameReference(((TypeDeclaration)containerNode.get()).name, p); } else { Expression smallRef = new FieldReference(field.getName().toCharArray(), p); - setGeneratedBy(smallRef, source); + if (source != null) setGeneratedBy(smallRef, source); return smallRef; } } else { ref.receiver = new ThisReference(pS, pE); } - setGeneratedBy(ref, source); - setGeneratedBy(ref.receiver, source); + + if (source != null) { + setGeneratedBy(ref, source); + setGeneratedBy(ref.receiver, source); + } return ref; } @@ -1489,7 +1511,7 @@ public class EclipseHandlerUtil { * with eclipse versions before 3.7. */ public static IntLiteral makeIntLiteral(char[] token, ASTNode source) { - int pS = source.sourceStart, pE = source.sourceEnd; + int pS = source == null ? 0 : source.sourceStart, pE = source == null ? 0 : source.sourceEnd; IntLiteral result; try { if (intLiteralConstructor != null) { @@ -1504,7 +1526,8 @@ public class EclipseHandlerUtil { } catch (InstantiationException e) { throw Lombok.sneakyThrow(e); } - setGeneratedBy(result, source); + + if (source != null) setGeneratedBy(result, source); return result; } diff --git a/src/core/lombok/eclipse/handlers/EclipseSingularsRecipes.java b/src/core/lombok/eclipse/handlers/EclipseSingularsRecipes.java new file mode 100644 index 00000000..4cb41d4f --- /dev/null +++ b/src/core/lombok/eclipse/handlers/EclipseSingularsRecipes.java @@ -0,0 +1,345 @@ +/* + * 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.eclipse.handlers.EclipseHandlerUtil.*; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.jdt.internal.compiler.ast.ASTNode; +import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.ConditionalExpression; +import org.eclipse.jdt.internal.compiler.ast.EqualExpression; +import org.eclipse.jdt.internal.compiler.ast.Expression; +import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; +import org.eclipse.jdt.internal.compiler.ast.FieldReference; +import org.eclipse.jdt.internal.compiler.ast.IntLiteral; +import org.eclipse.jdt.internal.compiler.ast.MessageSend; +import org.eclipse.jdt.internal.compiler.ast.NullLiteral; +import org.eclipse.jdt.internal.compiler.ast.OperatorIds; +import org.eclipse.jdt.internal.compiler.ast.ParameterizedQualifiedTypeReference; +import org.eclipse.jdt.internal.compiler.ast.ParameterizedSingleTypeReference; +import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference; +import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference; +import org.eclipse.jdt.internal.compiler.ast.Statement; +import org.eclipse.jdt.internal.compiler.ast.ThisReference; +import org.eclipse.jdt.internal.compiler.ast.TypeReference; +import org.eclipse.jdt.internal.compiler.ast.Wildcard; +import org.eclipse.jdt.internal.compiler.lookup.ClassScope; +import org.eclipse.jdt.internal.compiler.lookup.MethodScope; +import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; + +import lombok.core.LombokImmutableList; +import lombok.core.SpiLoadUtil; +import lombok.core.TypeLibrary; +import lombok.eclipse.EclipseNode; + +public class EclipseSingularsRecipes { + private static final EclipseSingularsRecipes INSTANCE = new EclipseSingularsRecipes(); + private final Map<String, EclipseSingularizer> singularizers = new HashMap<String, EclipseSingularizer>(); + private final TypeLibrary singularizableTypes = new TypeLibrary(); + + private EclipseSingularsRecipes() { + try { + loadAll(singularizableTypes, singularizers); + singularizableTypes.lock(); + } catch (IOException e) { + System.err.println("Lombok's @Singularizable feature is broken due to misconfigured SPI files: " + e); + } + } + + private static void loadAll(TypeLibrary library, Map<String, EclipseSingularizer> map) throws IOException { + for (EclipseSingularizer handler : SpiLoadUtil.findServices(EclipseSingularizer.class, EclipseSingularizer.class.getClassLoader())) { + for (String type : handler.getSupportedTypes()) { + EclipseSingularizer existingSingularizer = map.get(type); + if (existingSingularizer != null) { + EclipseSingularizer toKeep = existingSingularizer.getClass().getName().compareTo(handler.getClass().getName()) > 0 ? handler : existingSingularizer; + System.err.println("Multiple singularizers found for type " + type + "; the alphabetically first class is used: " + toKeep.getClass().getName()); + map.put(type, toKeep); + } else { + map.put(type, handler); + library.addType(type); + } + } + } + } + + public static EclipseSingularsRecipes get() { + return INSTANCE; + } + + public String toQualified(String typeReference) { + return singularizableTypes.toQualified(typeReference); + } + + public EclipseSingularizer getSingularizer(String fqn) { + return singularizers.get(fqn); + } + + public static final class SingularData { + private final EclipseNode annotation; + private final char[] singularName; + private final char[] pluralName; + private final List<TypeReference> typeArgs; + private final String targetFqn; + private final EclipseSingularizer singularizer; + private final ASTNode source; + + public SingularData(EclipseNode annotation, char[] singularName, char[] pluralName, List<TypeReference> typeArgs, String targetFqn, EclipseSingularizer singularizer, ASTNode source) { + this.annotation = annotation; + this.singularName = singularName; + this.pluralName = pluralName; + this.typeArgs = typeArgs; + this.targetFqn = targetFqn; + this.singularizer = singularizer; + this.source = source; + } + + public void setGeneratedByRecursive(ASTNode target) { + SetGeneratedByVisitor visitor = new SetGeneratedByVisitor(source); + + if (target instanceof AbstractMethodDeclaration) { + ((AbstractMethodDeclaration) target).traverse(visitor, (ClassScope) null); + } else if (target instanceof FieldDeclaration) { + ((FieldDeclaration) target).traverse(visitor, (MethodScope) null); + } else { + target.traverse(visitor, null); + } + } + + public EclipseNode getAnnotation() { + return annotation; + } + + public char[] getSingularName() { + return singularName; + } + + public char[] getPluralName() { + return pluralName; + } + + public List<TypeReference> getTypeArgs() { + return typeArgs; + } + + public String getTargetFqn() { + return targetFqn; + } + + public EclipseSingularizer getSingularizer() { + return singularizer; + } + + public String getTargetSimpleType() { + int idx = targetFqn.lastIndexOf("."); + return idx == -1 ? targetFqn : targetFqn.substring(idx + 1); + } + } + + public static abstract class EclipseSingularizer { + protected static final long[] NULL_POSS = {0L}; + public abstract LombokImmutableList<String> getSupportedTypes(); + + /** Checks if any of the to-be-generated nodes (fields, methods) already exist. If so, errors on these (singulars don't support manually writing some of it, and returns true). */ + public boolean checkForAlreadyExistingNodesAndGenerateError(EclipseNode builderType, SingularData data) { + for (EclipseNode child : builderType.down()) { + switch (child.getKind()) { + case FIELD: { + FieldDeclaration fd = (FieldDeclaration) child.get(); + char[] name = fd.name; + if (name == null) continue; + if (getGeneratedBy(fd) != null) continue; + for (char[] fieldToBeGenerated : listFieldsToBeGenerated(data, builderType)) { + if (!Arrays.equals(name, fieldToBeGenerated)) continue; + child.addError("Manually adding a field that @Singular @Builder would generate is not supported. If you want to manually manage the builder aspect for this field/parameter, don't use @Singular."); + return true; + } + break; + } + case METHOD: { + AbstractMethodDeclaration method = (AbstractMethodDeclaration) child.get(); + char[] name = method.selector; + if (name == null) continue; + if (getGeneratedBy(method) != null) continue; + for (char[] methodToBeGenerated : listMethodsToBeGenerated(data, builderType)) { + if (!Arrays.equals(name, methodToBeGenerated)) continue; + child.addError("Manually adding a method that @Singular @Builder would generate is not supported. If you want to manually manage the builder aspect for this field/parameter, don't use @Singular."); + return true; + } + break; + }} + } + + return false; + } + + public List<char[]> listFieldsToBeGenerated(SingularData data, EclipseNode builderType) { + return Collections.singletonList(data.pluralName); + } + + public List<char[]> listMethodsToBeGenerated(SingularData data, EclipseNode builderType) { + char[] p = data.pluralName; + char[] s = data.singularName; + if (Arrays.equals(p, s)) return Collections.singletonList(p); + return Arrays.asList(p, s); + } + + public abstract List<EclipseNode> generateFields(SingularData data, EclipseNode builderType); + public abstract void generateMethods(SingularData data, EclipseNode builderType, boolean fluent, boolean chain); + public abstract void appendBuildCode(SingularData data, EclipseNode builderType, List<Statement> statements, char[] targetVariableName); + + public boolean requiresCleaning() { + try { + return !getClass().getMethod("appendCleaningCode", SingularData.class, EclipseNode.class, List.class).getDeclaringClass().equals(EclipseSingularizer.class); + } catch (NoSuchMethodException e) { + return false; + } + } + + public void appendCleaningCode(SingularData data, EclipseNode builderType, List<Statement> statements) { + } + + // -- Utility methods -- + + /** + * Adds the requested number of type arguments to the provided type, copying each argument in {@code typeArgs}. If typeArgs is too long, the extra elements are ignored. + * If {@code typeArgs} is null or too short, {@code java.lang.Object} will be substituted for each missing type argument. + * + * @param count The number of type arguments requested. + * @param addExtends If {@code true}, all bounds are either '? extends X' or just '?'. If false, the reverse is applied, and '? extends Foo' is converted to Foo, '?' to Object, etc. + * @param node Some node in the same AST. Just used to obtain makers and contexts and such. + * @param type The type to add generics to. + * @param typeArgs the list of type args to clone. + * @param source The source annotation that is the root cause of this code generation. + */ + protected TypeReference addTypeArgs(int count, boolean addExtends, EclipseNode node, TypeReference type, List<TypeReference> typeArgs) { + TypeReference[] clonedAndFixedArgs = createTypeArgs(count, addExtends, node, typeArgs); + if (type instanceof SingleTypeReference) { + type = new ParameterizedSingleTypeReference(((SingleTypeReference) type).token, clonedAndFixedArgs, 0, 0L); + } else if (type instanceof QualifiedTypeReference) { + QualifiedTypeReference qtr = (QualifiedTypeReference) type; + TypeReference[][] trs = new TypeReference[qtr.tokens.length][]; + trs[qtr.tokens.length - 1] = clonedAndFixedArgs; + type = new ParameterizedQualifiedTypeReference(((QualifiedTypeReference) type).tokens, trs, 0, NULL_POSS); + } else { + node.addError("Don't know how to clone-and-parameterize type: " + type); + } + + return type; + } + + protected TypeReference[] createTypeArgs(int count, boolean addExtends, EclipseNode node, List<TypeReference> typeArgs) { + if (count < 0) throw new IllegalArgumentException("count is negative"); + if (count == 0) return null; + List<TypeReference> arguments = new ArrayList<TypeReference>(); + + if (typeArgs != null) for (TypeReference orig : typeArgs) { + Wildcard wildcard = orig instanceof Wildcard ? (Wildcard) orig : null; + if (!addExtends) { + if (wildcard != null && (wildcard.kind == Wildcard.UNBOUND || wildcard.kind == Wildcard.SUPER)) { + arguments.add(new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, NULL_POSS)); + } else if (wildcard != null && wildcard.kind == Wildcard.EXTENDS) { + try { + arguments.add(copyType(wildcard.bound)); + } catch (Exception e) { + arguments.add(new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, NULL_POSS)); + } + } else { + arguments.add(copyType(orig)); + } + } else { + if (wildcard != null && (wildcard.kind == Wildcard.UNBOUND || wildcard.kind == Wildcard.SUPER)) { + Wildcard w = new Wildcard(Wildcard.UNBOUND); + arguments.add(w); + } else if (wildcard != null && wildcard.kind == Wildcard.EXTENDS) { + arguments.add(copyType(orig)); + } else { + Wildcard w = new Wildcard(Wildcard.EXTENDS); + w.bound = copyType(orig); + arguments.add(w); + } + } + if (--count == 0) break; + } + + while (count-- > 0) { + if (addExtends) { + arguments.add(new Wildcard(Wildcard.UNBOUND)); + } else { + arguments.add(new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, NULL_POSS)); + } + } + + if (arguments.isEmpty()) return null; + return arguments.toArray(new TypeReference[arguments.size()]); + } + + private static final char[] SIZE_TEXT = new char[] {'s', 'i', 'z', 'e'}; + + /** Generates 'this.<em>name</em>.size()' as an expression; if nullGuard is true, it's this.name == null ? 0 : this.name.size(). */ + protected Expression getSize(EclipseNode builderType, char[] name, boolean nullGuard) { + MessageSend invoke = new MessageSend(); + ThisReference thisRef = new ThisReference(0, 0); + FieldReference thisDotName = new FieldReference(name, 0L); + thisDotName.receiver = thisRef; + invoke.receiver = thisDotName; + invoke.selector = SIZE_TEXT; + if (!nullGuard) return invoke; + + ThisReference cdnThisRef = new ThisReference(0, 0); + FieldReference cdnThisDotName = new FieldReference(name, 0L); + cdnThisDotName.receiver = cdnThisRef; + NullLiteral nullLiteral = new NullLiteral(0, 0); + EqualExpression isNull = new EqualExpression(cdnThisDotName, nullLiteral, OperatorIds.EQUAL_EQUAL); + IntLiteral zeroLiteral = makeIntLiteral(new char[] {'0'}, null); + ConditionalExpression conditional = new ConditionalExpression(isNull, zeroLiteral, invoke); + return conditional; + } + + protected TypeReference cloneParamType(int index, List<TypeReference> typeArgs, EclipseNode builderType) { + if (typeArgs != null && typeArgs.size() > index) { + TypeReference originalType = typeArgs.get(index); + if (originalType instanceof Wildcard) { + Wildcard wOriginalType = (Wildcard) originalType; + if (wOriginalType.kind == Wildcard.EXTENDS) { + try { + return copyType(wOriginalType.bound); + } catch (Exception e) { + // fallthrough + } + } + } else { + return copyType(originalType); + } + } + + return new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, NULL_POSS); + } + } +} diff --git a/src/core/lombok/eclipse/handlers/HandleBuilder.java b/src/core/lombok/eclipse/handlers/HandleBuilder.java index 522501f6..45f4342e 100644 --- a/src/core/lombok/eclipse/handlers/HandleBuilder.java +++ b/src/core/lombok/eclipse/handlers/HandleBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2014 The Project Lombok Authors. + * Copyright (C) 2013-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 @@ -32,50 +32,87 @@ import java.util.List; import org.eclipse.jdt.internal.compiler.ast.ASTNode; import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.AbstractVariableDeclaration; import org.eclipse.jdt.internal.compiler.ast.AllocationExpression; import org.eclipse.jdt.internal.compiler.ast.Annotation; import org.eclipse.jdt.internal.compiler.ast.Argument; +import org.eclipse.jdt.internal.compiler.ast.Assignment; import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration; import org.eclipse.jdt.internal.compiler.ast.Expression; +import org.eclipse.jdt.internal.compiler.ast.FalseLiteral; import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; +import org.eclipse.jdt.internal.compiler.ast.FieldReference; +import org.eclipse.jdt.internal.compiler.ast.IfStatement; import org.eclipse.jdt.internal.compiler.ast.MessageSend; import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.OperatorIds; +import org.eclipse.jdt.internal.compiler.ast.ParameterizedQualifiedTypeReference; import org.eclipse.jdt.internal.compiler.ast.ParameterizedSingleTypeReference; import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference; import org.eclipse.jdt.internal.compiler.ast.ReturnStatement; import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference; import org.eclipse.jdt.internal.compiler.ast.Statement; +import org.eclipse.jdt.internal.compiler.ast.ThisReference; +import org.eclipse.jdt.internal.compiler.ast.TrueLiteral; import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; import org.eclipse.jdt.internal.compiler.ast.TypeParameter; import org.eclipse.jdt.internal.compiler.ast.TypeReference; +import org.eclipse.jdt.internal.compiler.ast.UnaryExpression; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; import org.eclipse.jdt.internal.compiler.lookup.ClassScope; +import org.eclipse.jdt.internal.compiler.lookup.MethodScope; import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; +import org.eclipse.jdt.internal.compiler.lookup.TypeIds; import org.mangosdk.spi.ProviderFor; import lombok.AccessLevel; +import lombok.Builder; import lombok.ConfigurationKeys; +import lombok.Singular; import lombok.core.AST.Kind; +import lombok.core.handlers.HandlerUtil; import lombok.core.AnnotationValues; import lombok.core.HandlerPriority; import lombok.eclipse.Eclipse; import lombok.eclipse.EclipseAnnotationHandler; import lombok.eclipse.EclipseNode; +import lombok.eclipse.handlers.EclipseSingularsRecipes.EclipseSingularizer; +import lombok.eclipse.handlers.EclipseSingularsRecipes.SingularData; import lombok.eclipse.handlers.HandleConstructor.SkipIfConstructorExists; -import lombok.experimental.Builder; import lombok.experimental.NonFinal; @ProviderFor(EclipseAnnotationHandler.class) @HandlerPriority(-1024) //-2^10; to ensure we've picked up @FieldDefault's changes (-2048) but @Value hasn't removed itself yet (-512), so that we can error on presence of it on the builder classes. public class HandleBuilder extends EclipseAnnotationHandler<Builder> { - @Override public void handle(AnnotationValues<Builder> annotation, Annotation ast, EclipseNode annotationNode) { - handleExperimentalFlagUsage(annotationNode, ConfigurationKeys.BUILDER_FLAG_USAGE, "@Builder"); + private static final char[] CLEAN_FIELD_NAME = "$lombokUnclean".toCharArray(); + private static final char[] CLEAN_METHOD_NAME = "$lombokClean".toCharArray(); + + private static final boolean toBoolean(Object expr, boolean defaultValue) { + if (expr == null) return defaultValue; + if (expr instanceof FalseLiteral) return false; + if (expr instanceof TrueLiteral) return true; + return ((Boolean) expr).booleanValue(); + } + + private static class BuilderFieldData { + TypeReference type; + char[] name; + SingularData singularData; + List<EclipseNode> createdFields = new ArrayList<EclipseNode>(); + } + + @Override public void handle(AnnotationValues<Builder> annotation, Annotation ast, EclipseNode annotationNode) { long p = (long) ast.sourceStart << 32 | ast.sourceEnd; Builder builderInstance = annotation.getInstance(); + + // These exist just to support the 'old' lombok.experimental.Builder, which had these properties. lombok.Builder no longer has them. + boolean fluent = toBoolean(annotation.getActualExpression("fluent"), true); + boolean chain = toBoolean(annotation.getActualExpression("chain"), true); + String builderMethodName = builderInstance.builderMethodName(); String buildMethodName = builderInstance.buildMethodName(); String builderClassName = builderInstance.builderClassName(); @@ -92,21 +129,21 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { EclipseNode parent = annotationNode.up(); - List<TypeReference> typesOfParameters = new ArrayList<TypeReference>(); - List<char[]> namesOfParameters = new ArrayList<char[]>(); + List<BuilderFieldData> builderFields = new ArrayList<BuilderFieldData>(); TypeReference returnType; TypeParameter[] typeParams; TypeReference[] thrownExceptions; char[] nameOfStaticBuilderMethod; EclipseNode tdParent; - AbstractMethodDeclaration fillParametersFrom = null; + EclipseNode fillParametersFrom = parent.get() instanceof AbstractMethodDeclaration ? parent : null; + boolean addCleaning = false; if (parent.get() instanceof TypeDeclaration) { tdParent = parent; TypeDeclaration td = (TypeDeclaration) tdParent.get(); - List<EclipseNode> fields = new ArrayList<EclipseNode>(); + List<EclipseNode> allFields = new ArrayList<EclipseNode>(); @SuppressWarnings("deprecation") boolean valuePresent = (hasAnnotation(lombok.Value.class, parent) || hasAnnotation(lombok.experimental.Value.class, parent)); for (EclipseNode fieldNode : HandleConstructor.findAllFields(tdParent)) { @@ -115,12 +152,15 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { // non-final fields final, but @Value's handler hasn't done this yet, so we have to do this math ourselves. // Value will only skip making a field final if it has an explicit @NonFinal annotation, so we check for that. if (fd.initialization != null && valuePresent && !hasAnnotation(NonFinal.class, fieldNode)) continue; - namesOfParameters.add(removePrefixFromField(fieldNode)); - typesOfParameters.add(fd.type); - fields.add(fieldNode); + BuilderFieldData bfd = new BuilderFieldData(); + bfd.name = removePrefixFromField(fieldNode); + bfd.type = fd.type; + bfd.singularData = getSingularData(fieldNode, ast); + builderFields.add(bfd); + allFields.add(fieldNode); } - new HandleConstructor().generateConstructor(tdParent, AccessLevel.PACKAGE, fields, null, SkipIfConstructorExists.I_AM_BUILDER, null, + new HandleConstructor().generateConstructor(tdParent, AccessLevel.PACKAGE, allFields, null, SkipIfConstructorExists.I_AM_BUILDER, null, Collections.<Annotation>emptyList(), annotationNode); returnType = namePlusTypeParamsToTypeReference(td.name, td.typeParameters, p); @@ -137,7 +177,6 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { tdParent = parent.up(); TypeDeclaration td = (TypeDeclaration) tdParent.get(); - fillParametersFrom = cd; returnType = namePlusTypeParamsToTypeReference(td.name, td.typeParameters, p); typeParams = td.typeParameters; thrownExceptions = cd.thrownExceptions; @@ -150,7 +189,6 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { annotationNode.addError("@Builder is only supported on types, constructors, and static methods."); return; } - fillParametersFrom = md; returnType = copyType(md.returnType, ast); typeParams = md.typeParameters; thrownExceptions = md.thrownExceptions; @@ -190,9 +228,14 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { } if (fillParametersFrom != null) { - if (fillParametersFrom.arguments != null) for (Argument a : fillParametersFrom.arguments) { - namesOfParameters.add(a.name); - typesOfParameters.add(a.type); + for (EclipseNode param : fillParametersFrom.down()) { + if (param.getKind() != Kind.ARGUMENT) continue; + BuilderFieldData bfd = new BuilderFieldData(); + Argument arg = (Argument) param.get(); + bfd.name = arg.name; + bfd.type = arg.type; + bfd.singularData = getSingularData(param, ast); + builderFields.add(bfd); } } @@ -201,12 +244,36 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { builderType = makeBuilderClass(tdParent, builderClassName, typeParams, ast); } else { sanityCheckForMethodGeneratingAnnotationsOnBuilderClass(builderType, annotationNode); + /* generate errors for @Singular BFDs that have one already defined node. */ { + for (BuilderFieldData bfd : builderFields) { + SingularData sd = bfd.singularData; + if (sd == null) continue; + EclipseSingularizer singularizer = sd.getSingularizer(); + if (singularizer == null) continue; + if (singularizer.checkForAlreadyExistingNodesAndGenerateError(builderType, sd)) { + bfd.singularData = null; + } + } + } + } + + for (BuilderFieldData bfd : builderFields) { + if (bfd.singularData != null && bfd.singularData.getSingularizer() != null) { + if (bfd.singularData.getSingularizer().requiresCleaning()) { + addCleaning = true; + break; + } + } } - List<EclipseNode> fieldNodes = addFieldsToBuilder(builderType, namesOfParameters, typesOfParameters, ast); - List<AbstractMethodDeclaration> newMethods = new ArrayList<AbstractMethodDeclaration>(); - for (EclipseNode fieldNode : fieldNodes) { - MethodDeclaration newMethod = makeSetterMethodForBuilder(builderType, fieldNode, annotationNode, builderInstance.fluent(), builderInstance.chain()); - if (newMethod != null) newMethods.add(newMethod); + + generateBuilderFields(builderType, builderFields, ast); + if (addCleaning) { + FieldDeclaration cleanDecl = new FieldDeclaration(CLEAN_FIELD_NAME, 0, -1); + cleanDecl.declarationSourceEnd = -1; + cleanDecl.modifiers = ClassFileConstants.AccPrivate; + cleanDecl.type = TypeReference.baseTypeReference(TypeIds.T_boolean, 0); + System.out.println("INJECTING: cleaning"); + injectField(builderType, cleanDecl); } if (constructorExists(builderType) == MemberExistsResult.NOT_EXISTS) { @@ -216,128 +283,183 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { if (cd != null) injectMethod(builderType, cd); } - for (AbstractMethodDeclaration newMethod : newMethods) injectMethod(builderType, newMethod); + for (BuilderFieldData bfd : builderFields) { + makeSetterMethodsForBuilder(builderType, bfd, annotationNode, fluent, chain); + } + if (methodExists(buildMethodName, builderType, -1) == MemberExistsResult.NOT_EXISTS) { - MethodDeclaration md = generateBuildMethod(buildMethodName, nameOfStaticBuilderMethod, returnType, namesOfParameters, builderType, ast, thrownExceptions); + MethodDeclaration md = generateBuildMethod(buildMethodName, nameOfStaticBuilderMethod, returnType, builderFields, builderType, thrownExceptions, addCleaning, ast); if (md != null) injectMethod(builderType, md); } if (methodExists("toString", builderType, 0) == MemberExistsResult.NOT_EXISTS) { + List<EclipseNode> fieldNodes = new ArrayList<EclipseNode>(); + for (BuilderFieldData bfd : builderFields) { + fieldNodes.addAll(bfd.createdFields); + } MethodDeclaration md = HandleToString.createToString(builderType, fieldNodes, true, false, ast, FieldAccess.ALWAYS_FIELD); if (md != null) injectMethod(builderType, md); } + if (addCleaning) { + MethodDeclaration cleanMethod = generateCleanMethod(builderFields, builderType, ast); + if (cleanMethod != null) injectMethod(builderType, cleanMethod); + } + if (methodExists(builderMethodName, tdParent, -1) == MemberExistsResult.NOT_EXISTS) { MethodDeclaration md = generateBuilderMethod(builderMethodName, builderClassName, tdParent, typeParams, ast); if (md != null) injectMethod(tdParent, md); } } - public MethodDeclaration generateBuilderMethod(String builderMethodName, String builderClassName, EclipseNode type, TypeParameter[] typeParams, ASTNode source) { - int pS = source.sourceStart, pE = source.sourceEnd; - long p = (long) pS << 32 | pE; + private MethodDeclaration generateCleanMethod(List<BuilderFieldData> builderFields, EclipseNode builderType, ASTNode source) { + List<Statement> statements = new ArrayList<Statement>(); - MethodDeclaration out = new MethodDeclaration( - ((CompilationUnitDeclaration) type.top().get()).compilationResult); - out.selector = builderMethodName.toCharArray(); - out.modifiers = ClassFileConstants.AccPublic | ClassFileConstants.AccStatic; - out.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; - out.returnType = namePlusTypeParamsToTypeReference(builderClassName.toCharArray(), typeParams, p); - out.typeParameters = copyTypeParams(typeParams, source); - AllocationExpression invoke = new AllocationExpression(); - invoke.type = namePlusTypeParamsToTypeReference(builderClassName.toCharArray(), typeParams, p); - out.statements = new Statement[] {new ReturnStatement(invoke, pS, pE)}; + for (BuilderFieldData bfd : builderFields) { + if (bfd.singularData != null && bfd.singularData.getSingularizer() != null) { + bfd.singularData.getSingularizer().appendCleaningCode(bfd.singularData, builderType, statements); + } + } - out.traverse(new SetGeneratedByVisitor(source), ((TypeDeclaration) type.get()).scope); - return out; + FieldReference thisUnclean = new FieldReference(CLEAN_FIELD_NAME, 0); + thisUnclean.receiver = new ThisReference(0, 0); + statements.add(new Assignment(thisUnclean, new FalseLiteral(0, 0), 0)); + MethodDeclaration decl = new MethodDeclaration(((CompilationUnitDeclaration) builderType.top().get()).compilationResult); + decl.selector = CLEAN_METHOD_NAME; + decl.modifiers = ClassFileConstants.AccPrivate; + decl.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + decl.returnType = TypeReference.baseTypeReference(TypeIds.T_void, 0); + decl.statements = statements.toArray(new Statement[0]); + decl.traverse(new SetGeneratedByVisitor(source), (ClassScope) null); + return decl; } - public MethodDeclaration generateBuildMethod(String name, char[] staticName, TypeReference returnType, List<char[]> fieldNames, EclipseNode type, ASTNode source, TypeReference[] thrownExceptions) { - int pS = source.sourceStart, pE = source.sourceEnd; - long p = (long) pS << 32 | pE; - + public MethodDeclaration generateBuildMethod(String name, char[] staticName, TypeReference returnType, List<BuilderFieldData> builderFields, EclipseNode type, TypeReference[] thrownExceptions, boolean addCleaning, ASTNode source) { MethodDeclaration out = new MethodDeclaration( ((CompilationUnitDeclaration) type.top().get()).compilationResult); + out.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + List<Statement> statements = new ArrayList<Statement>(); + + if (addCleaning) { + FieldReference thisUnclean = new FieldReference(CLEAN_FIELD_NAME, 0); + thisUnclean.receiver = new ThisReference(0, 0); + Expression notClean = new UnaryExpression(thisUnclean, OperatorIds.NOT); + MessageSend invokeClean = new MessageSend(); + invokeClean.selector = CLEAN_METHOD_NAME; + statements.add(new IfStatement(notClean, invokeClean, 0, 0)); + } + + for (BuilderFieldData bfd : builderFields) { + if (bfd.singularData != null && bfd.singularData.getSingularizer() != null) { + bfd.singularData.getSingularizer().appendBuildCode(bfd.singularData, type, statements, bfd.name); + } + } + + List<Expression> args = new ArrayList<Expression>(); + for (BuilderFieldData bfd : builderFields) { + args.add(new SingleNameReference(bfd.name, 0L)); + } + + if (addCleaning) { + FieldReference thisUnclean = new FieldReference(CLEAN_FIELD_NAME, 0); + thisUnclean.receiver = new ThisReference(0, 0); + statements.add(new Assignment(thisUnclean, new TrueLiteral(0, 0), 0)); + } out.modifiers = ClassFileConstants.AccPublic; - TypeDeclaration typeDecl = (TypeDeclaration) type.get(); out.selector = name.toCharArray(); - out.thrownExceptions = copyTypes(thrownExceptions, source); + out.thrownExceptions = copyTypes(thrownExceptions); out.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; out.returnType = returnType; - List<Expression> assigns = new ArrayList<Expression>(); - for (char[] fieldName : fieldNames) { - SingleNameReference nameRef = new SingleNameReference(fieldName, p); - assigns.add(nameRef); - } - - Statement statement; - if (staticName == null) { AllocationExpression allocationStatement = new AllocationExpression(); - allocationStatement.type = copyType(out.returnType, source); - allocationStatement.arguments = assigns.isEmpty() ? null : assigns.toArray(new Expression[assigns.size()]); - statement = new ReturnStatement(allocationStatement, (int)(p >> 32), (int)p); + allocationStatement.type = copyType(out.returnType); + allocationStatement.arguments = args.isEmpty() ? null : args.toArray(new Expression[args.size()]); + statements.add(new ReturnStatement(allocationStatement, 0, 0)); } else { MessageSend invoke = new MessageSend(); invoke.selector = staticName; - invoke.receiver = new SingleNameReference(type.up().getName().toCharArray(), p); + invoke.receiver = new SingleNameReference(type.up().getName().toCharArray(), 0); TypeParameter[] tps = ((TypeDeclaration) type.get()).typeParameters; if (tps != null) { TypeReference[] trs = new TypeReference[tps.length]; for (int i = 0; i < trs.length; i++) { - trs[i] = new SingleTypeReference(tps[i].name, p); + trs[i] = new SingleTypeReference(tps[i].name, 0); } invoke.typeArguments = trs; } - invoke.arguments = assigns.isEmpty() ? null : assigns.toArray(new Expression[assigns.size()]); + invoke.arguments = args.isEmpty() ? null : args.toArray(new Expression[args.size()]); if (returnType instanceof SingleTypeReference && Arrays.equals(TypeConstants.VOID, ((SingleTypeReference) returnType).token)) { - statement = invoke; + statements.add(invoke); } else { - statement = new ReturnStatement(invoke, (int)(p >> 32), (int)p); + statements.add(new ReturnStatement(invoke, 0, 0)); } } + out.statements = statements.isEmpty() ? null : statements.toArray(new Statement[statements.size()]); + out.traverse(new SetGeneratedByVisitor(source), (ClassScope) null); + return out; + } + + public MethodDeclaration generateBuilderMethod(String builderMethodName, String builderClassName, EclipseNode type, TypeParameter[] typeParams, ASTNode source) { + int pS = source.sourceStart, pE = source.sourceEnd; + long p = (long) pS << 32 | pE; - out.statements = new Statement[] { statement }; + MethodDeclaration out = new MethodDeclaration( + ((CompilationUnitDeclaration) type.top().get()).compilationResult); + out.selector = builderMethodName.toCharArray(); + out.modifiers = ClassFileConstants.AccPublic | ClassFileConstants.AccStatic; + out.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + out.returnType = namePlusTypeParamsToTypeReference(builderClassName.toCharArray(), typeParams, p); + out.typeParameters = copyTypeParams(typeParams, source); + AllocationExpression invoke = new AllocationExpression(); + invoke.type = namePlusTypeParamsToTypeReference(builderClassName.toCharArray(), typeParams, p); + out.statements = new Statement[] {new ReturnStatement(invoke, pS, pE)}; - out.traverse(new SetGeneratedByVisitor(source), typeDecl.scope); + out.traverse(new SetGeneratedByVisitor(source), ((TypeDeclaration) type.get()).scope); return out; } - public List<EclipseNode> addFieldsToBuilder(EclipseNode builderType, List<char[]> namesOfParameters, List<TypeReference> typesOfParameters, ASTNode source) { - int len = namesOfParameters.size(); - TypeDeclaration td = (TypeDeclaration) builderType.get(); - FieldDeclaration[] existing = td.fields; - if (existing == null) existing = new FieldDeclaration[0]; - - List<EclipseNode> out = new ArrayList<EclipseNode>(); + public void generateBuilderFields(EclipseNode builderType, List<BuilderFieldData> builderFields, ASTNode source) { + List<EclipseNode> existing = new ArrayList<EclipseNode>(); + for (EclipseNode child : builderType.down()) { + if (child.getKind() == Kind.FIELD) existing.add(child); + } top: - for (int i = len - 1; i >= 0; i--) { - char[] name = namesOfParameters.get(i); - for (FieldDeclaration exists : existing) { - if (Arrays.equals(exists.name, name)) { - out.add(builderType.getNodeFor(exists)); - continue top; + for (BuilderFieldData bfd : builderFields) { + if (bfd.singularData != null && bfd.singularData.getSingularizer() != null) { + bfd.createdFields.addAll(bfd.singularData.getSingularizer().generateFields(bfd.singularData, builderType)); + } else { + for (EclipseNode exists : existing) { + char[] n = ((FieldDeclaration) exists.get()).name; + if (Arrays.equals(n, bfd.name)) { + bfd.createdFields.add(exists); + continue top; + } } + + FieldDeclaration fd = new FieldDeclaration(bfd.name, 0, 0); + fd.bits |= Eclipse.ECLIPSE_DO_NOT_TOUCH_FLAG; + fd.modifiers = ClassFileConstants.AccPrivate; + fd.type = copyType(bfd.type); + fd.traverse(new SetGeneratedByVisitor(source), (MethodScope) null); + bfd.createdFields.add(injectField(builderType, fd)); } - TypeReference fieldReference = copyType(typesOfParameters.get(i), source); - FieldDeclaration newField = new FieldDeclaration(name, 0, 0); - newField.bits |= Eclipse.ECLIPSE_DO_NOT_TOUCH_FLAG; - newField.modifiers = ClassFileConstants.AccPrivate; - newField.type = fieldReference; - out.add(injectField(builderType, newField)); } - - Collections.reverse(out); - - return out; } private static final AbstractMethodDeclaration[] EMPTY = {}; - public MethodDeclaration makeSetterMethodForBuilder(EclipseNode builderType, EclipseNode fieldNode, EclipseNode sourceNode, boolean fluent, boolean chain) { + public void makeSetterMethodsForBuilder(EclipseNode builderType, BuilderFieldData bfd, EclipseNode sourceNode, boolean fluent, boolean chain) { + if (bfd.singularData == null || bfd.singularData.getSingularizer() == null) { + makeSimpleSetterMethodForBuilder(builderType, bfd.createdFields.get(0), sourceNode, fluent, chain); + } else { + bfd.singularData.getSingularizer().generateMethods(bfd.singularData, builderType, fluent, chain); + } + } + + private void makeSimpleSetterMethodForBuilder(EclipseNode builderType, EclipseNode fieldNode, EclipseNode sourceNode, boolean fluent, boolean chain) { TypeDeclaration td = (TypeDeclaration) builderType.get(); AbstractMethodDeclaration[] existing = td.methods; if (existing == null) existing = EMPTY; @@ -348,14 +470,14 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { for (int i = 0; i < len; i++) { if (!(existing[i] instanceof MethodDeclaration)) continue; char[] existingName = existing[i].selector; - if (Arrays.equals(name, existingName)) return null; + if (Arrays.equals(name, existingName)) return; } - boolean isBoolean = isBoolean(fd.type); - String setterName = fluent ? fieldNode.getName() : toSetterName(builderType.getAst(), null, fieldNode.getName(), isBoolean); + String setterName = fluent ? fieldNode.getName() : HandlerUtil.buildAccessorName("set", fieldNode.getName()); - return HandleSetter.createSetter(td, fieldNode, setterName, chain, ClassFileConstants.AccPublic, + MethodDeclaration setter = HandleSetter.createSetter(td, fieldNode, setterName, chain, ClassFileConstants.AccPublic, sourceNode, Collections.<Annotation>emptyList(), Collections.<Annotation>emptyList()); + injectMethod(builderType, setter); } public EclipseNode findInnerClass(EclipseNode parent, String name) { @@ -378,4 +500,64 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { builder.traverse(new SetGeneratedByVisitor(source), (ClassScope) null); return injectType(tdParent, builder); } + + /** + * Returns the explicitly requested singular annotation on this node (field + * or parameter), or null if there's no {@code @Singular} annotation on it. + * + * @param node The node (field or method param) to inspect for its name and potential {@code @Singular} annotation. + */ + private SingularData getSingularData(EclipseNode node, ASTNode source) { + for (EclipseNode child : node.down()) { + if (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."); + 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; + } + + 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 b72d000f..5bcc803a 100644 --- a/src/core/lombok/eclipse/handlers/HandleConstructor.java +++ b/src/core/lombok/eclipse/handlers/HandleConstructor.java @@ -33,6 +33,7 @@ import java.util.List; import lombok.AccessLevel; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.ConfigurationKeys; import lombok.NoArgsConstructor; import lombok.RequiredArgsConstructor; @@ -40,7 +41,6 @@ import lombok.core.AST.Kind; import lombok.core.AnnotationValues; import lombok.eclipse.EclipseAnnotationHandler; import lombok.eclipse.EclipseNode; -import lombok.experimental.Builder; import org.eclipse.jdt.internal.compiler.ast.ASTNode; import org.eclipse.jdt.internal.compiler.ast.AllocationExpression; @@ -333,8 +333,7 @@ public class HandleConstructor { Statement nullCheck = generateNullCheck(field, sourceNode); if (nullCheck != null) nullChecks.add(nullCheck); } - Annotation[] copiedAnnotations = copyAnnotations(source, nonNulls, nullables); - if (copiedAnnotations.length != 0) parameter.annotations = copiedAnnotations; + parameter.annotations = copyAnnotations(source, nonNulls, nullables); params.add(parameter); } @@ -348,10 +347,9 @@ public class HandleConstructor { constructorProperties = createConstructorProperties(source, fields); } - Annotation[] copiedAnnotations = copyAnnotations(source, + constructor.annotations = copyAnnotations(source, onConstructor.toArray(new Annotation[0]), constructorProperties); - if (copiedAnnotations.length != 0) constructor.annotations = copiedAnnotations; } constructor.traverse(new SetGeneratedByVisitor(source), typeDeclaration.scope); @@ -396,9 +394,7 @@ public class HandleConstructor { assigns.add(nameRef); Argument parameter = new Argument(field.name, fieldPos, copyType(field.type, source), Modifier.FINAL); - - Annotation[] copiedAnnotations = copyAnnotations(source, findAnnotations(field, NON_NULL_PATTERN), findAnnotations(field, NULLABLE_PATTERN)); - if (copiedAnnotations.length != 0) parameter.annotations = copiedAnnotations; + parameter.annotations = copyAnnotations(source, findAnnotations(field, NON_NULL_PATTERN), findAnnotations(field, NULLABLE_PATTERN)); params.add(parameter); } diff --git a/src/core/lombok/eclipse/handlers/HandleGetter.java b/src/core/lombok/eclipse/handlers/HandleGetter.java index 031fff82..14f2fb72 100644 --- a/src/core/lombok/eclipse/handlers/HandleGetter.java +++ b/src/core/lombok/eclipse/handlers/HandleGetter.java @@ -270,14 +270,12 @@ public class HandleGetter extends EclipseAnnotationHandler<Getter> { deprecated = new Annotation[] { generateDeprecatedAnnotation(source) }; } - Annotation[] copiedAnnotations = copyAnnotations(source, + method.annotations = copyAnnotations(source, onMethod.toArray(new Annotation[0]), findAnnotations(field, NON_NULL_PATTERN), findAnnotations(field, NULLABLE_PATTERN), findDelegatesAndMarkAsHandled(fieldNode), deprecated); - - if (copiedAnnotations.length != 0) method.annotations = copiedAnnotations; } method.traverse(new SetGeneratedByVisitor(source), parent.scope); diff --git a/src/core/lombok/eclipse/handlers/HandlePrintAST.java b/src/core/lombok/eclipse/handlers/HandlePrintAST.java index 0b61bc4d..234e29b8 100644 --- a/src/core/lombok/eclipse/handlers/HandlePrintAST.java +++ b/src/core/lombok/eclipse/handlers/HandlePrintAST.java @@ -59,7 +59,7 @@ public class HandlePrintAST extends EclipseAnnotationHandler<PrintAST> { try { stream.close(); } catch (Exception e) { - Lombok.sneakyThrow(e); + throw Lombok.sneakyThrow(e); } } } diff --git a/src/core/lombok/eclipse/handlers/HandleSetter.java b/src/core/lombok/eclipse/handlers/HandleSetter.java index c22af676..1fcf751d 100644 --- a/src/core/lombok/eclipse/handlers/HandleSetter.java +++ b/src/core/lombok/eclipse/handlers/HandleSetter.java @@ -216,10 +216,7 @@ public class HandleSetter extends EclipseAnnotationHandler<Setter> { if (isFieldDeprecated(fieldNode)) { deprecated = new Annotation[] { generateDeprecatedAnnotation(source) }; } - Annotation[] copiedAnnotations = copyAnnotations(source, onMethod.toArray(new Annotation[0]), deprecated); - if (copiedAnnotations.length != 0) { - method.annotations = copiedAnnotations; - } + method.annotations = copyAnnotations(source, onMethod.toArray(new Annotation[0]), deprecated); Argument param = new Argument(field.name, p, copyType(field.type, source), Modifier.FINAL); param.sourceStart = pS; param.sourceEnd = pE; method.arguments = new Argument[] { param }; @@ -252,9 +249,7 @@ public class HandleSetter extends EclipseAnnotationHandler<Setter> { statements.add(returnThis); } method.statements = statements.toArray(new Statement[0]); - - Annotation[] copiedAnnotationsParam = copyAnnotations(source, nonNulls, nullables, onParam.toArray(new Annotation[0])); - if (copiedAnnotationsParam.length != 0) param.annotations = copiedAnnotationsParam; + param.annotations = copyAnnotations(source, nonNulls, nullables, onParam.toArray(new Annotation[0])); method.traverse(new SetGeneratedByVisitor(source), parent.scope); return method; diff --git a/src/core/lombok/eclipse/handlers/HandleWither.java b/src/core/lombok/eclipse/handlers/HandleWither.java index 8b038676..cb06d888 100644 --- a/src/core/lombok/eclipse/handlers/HandleWither.java +++ b/src/core/lombok/eclipse/handlers/HandleWither.java @@ -227,10 +227,7 @@ public class HandleWither extends EclipseAnnotationHandler<Wither> { if (isFieldDeprecated(fieldNode)) { deprecated = new Annotation[] { generateDeprecatedAnnotation(source) }; } - Annotation[] copiedAnnotations = copyAnnotations(source, onMethod.toArray(new Annotation[0]), deprecated); - if (copiedAnnotations.length != 0) { - method.annotations = copiedAnnotations; - } + method.annotations = copyAnnotations(source, onMethod.toArray(new Annotation[0]), deprecated); Argument param = new Argument(field.name, p, copyType(field.type, source), Modifier.FINAL); param.sourceStart = pS; param.sourceEnd = pE; method.arguments = new Argument[] { param }; @@ -283,8 +280,7 @@ public class HandleWither extends EclipseAnnotationHandler<Wither> { method.statements = statements.toArray(new Statement[0]); - Annotation[] copiedAnnotationsParam = copyAnnotations(source, nonNulls, nullables, onParam.toArray(new Annotation[0])); - if (copiedAnnotationsParam.length != 0) param.annotations = copiedAnnotationsParam; + param.annotations = copyAnnotations(source, nonNulls, nullables, onParam.toArray(new Annotation[0])); method.traverse(new SetGeneratedByVisitor(source), parent.scope); return method; diff --git a/src/core/lombok/eclipse/handlers/SetGeneratedByVisitor.java b/src/core/lombok/eclipse/handlers/SetGeneratedByVisitor.java index 7217a396..df839a94 100644 --- a/src/core/lombok/eclipse/handlers/SetGeneratedByVisitor.java +++ b/src/core/lombok/eclipse/handlers/SetGeneratedByVisitor.java @@ -23,6 +23,8 @@ package lombok.eclipse.handlers; import static lombok.eclipse.handlers.EclipseHandlerUtil.*; +import java.util.Arrays; + import org.eclipse.jdt.internal.compiler.ASTVisitor; import org.eclipse.jdt.internal.compiler.ast.AND_AND_Expression; import org.eclipse.jdt.internal.compiler.ast.ASTNode; @@ -128,881 +130,802 @@ public final class SetGeneratedByVisitor extends ASTVisitor { private static final long INT_TO_LONG_MASK = 0x00000000FFFFFFFFL; private final ASTNode source; - private final int newSourceStart; - private final int newSourceEnd; + private final int sourceStart; + private final int sourceEnd; + private final long sourcePos; public SetGeneratedByVisitor(ASTNode source) { this.source = source; - this.newSourceStart = this.source.sourceStart; - this.newSourceEnd = this.source.sourceEnd; + this.sourceStart = this.source.sourceStart; + this.sourceEnd = this.source.sourceEnd; + this.sourcePos = (long)sourceStart << 32 | (sourceEnd & INT_TO_LONG_MASK); } - private void applyOffset(JavadocAllocationExpression node) { - applyOffsetExpression(node); - node.memberStart = newSourceStart; - node.tagSourceEnd = newSourceEnd; - node.tagSourceStart = newSourceStart; + private void fixPositions(JavadocAllocationExpression node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.statementEnd = sourceEnd; + node.memberStart = sourceStart; + node.tagSourceEnd = sourceEnd; + node.tagSourceStart = sourceStart; } - private void applyOffset(JavadocMessageSend node) { - applyOffsetMessageSend(node); - node.tagSourceEnd = newSourceEnd; - node.tagSourceStart = newSourceStart; + private void fixPositions(JavadocMessageSend node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.statementEnd = sourceEnd; + node.nameSourcePosition = sourcePos; + node.tagSourceEnd = sourceEnd; + node.tagSourceStart = sourceStart; } - private void applyOffset(JavadocSingleNameReference node) { - applyOffsetExpression(node); - node.tagSourceEnd = newSourceEnd; - node.tagSourceStart = newSourceStart; + private void fixPositions(JavadocSingleNameReference node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.statementEnd = sourceEnd; + node.tagSourceEnd = sourceEnd; + node.tagSourceStart = sourceStart; } - private void applyOffset(JavadocSingleTypeReference node) { - applyOffsetExpression(node); - node.tagSourceEnd = newSourceEnd; - node.tagSourceStart = newSourceStart; + private void fixPositions(JavadocSingleTypeReference node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.statementEnd = sourceEnd; + node.tagSourceEnd = sourceEnd; + node.tagSourceStart = sourceStart; } - private void applyOffset(JavadocFieldReference node) { - applyOffsetFieldReference(node); - node.tagSourceEnd = newSourceEnd; - node.tagSourceStart = newSourceStart; + private void fixPositions(JavadocFieldReference node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.statementEnd = sourceEnd; + node.nameSourcePosition = sourcePos; + node.tagSourceEnd = sourceEnd; + node.tagSourceStart = sourceStart; + } + + private void fixPositions(JavadocArrayQualifiedTypeReference node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.statementEnd = sourceEnd; + if (node.sourcePositions == null || node.sourcePositions.length != node.tokens.length) node.sourcePositions = new long[node.tokens.length]; + Arrays.fill(node.sourcePositions, sourcePos); + node.tagSourceEnd = sourceEnd; + node.tagSourceStart = sourceStart; + } + + private void fixPositions(JavadocQualifiedTypeReference node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.statementEnd = sourceEnd; + if (node.sourcePositions == null || node.sourcePositions.length != node.tokens.length) node.sourcePositions = new long[node.tokens.length]; + Arrays.fill(node.sourcePositions, sourcePos); + node.tagSourceEnd = sourceEnd; + node.tagSourceStart = sourceStart; + } + + private void fixPositions(Annotation node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.statementEnd = sourceEnd; + node.declarationSourceEnd = sourceEnd; } - - private void applyOffset(JavadocArrayQualifiedTypeReference node) { - applyOffsetQualifiedTypeReference(node); - node.tagSourceEnd = newSourceEnd; - node.tagSourceStart = newSourceStart; + + private void fixPositions(ArrayTypeReference node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.statementEnd = sourceEnd; + node.originalSourceEnd = sourceEnd; } - private void applyOffset(JavadocQualifiedTypeReference node) { - applyOffsetQualifiedTypeReference(node); - node.tagSourceEnd = newSourceEnd; - node.tagSourceStart = newSourceStart; + private void fixPositions(AbstractMethodDeclaration node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.bodyEnd = sourceEnd; + node.bodyStart = sourceStart; + node.declarationSourceEnd = sourceEnd; + node.declarationSourceStart = sourceStart; + node.modifiersSourceStart = sourceStart; } - private void applyOffset(Annotation node) { - applyOffsetExpression(node); - node.declarationSourceEnd = newSourceEnd; + private void fixPositions(Javadoc node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.valuePositions = sourceStart; } - private void applyOffset(ArrayTypeReference node) { - applyOffsetExpression(node); - node.originalSourceEnd = newSourceEnd; + private void fixPositions(Initializer node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.declarationEnd = sourceEnd; + node.declarationSourceEnd = sourceEnd; + node.declarationSourceStart = sourceStart; + node.modifiersSourceStart = sourceStart; + node.endPart1Position = sourceEnd; + node.endPart2Position = sourceEnd; + node.bodyStart = sourceStart; + node.bodyEnd = sourceEnd; } - - private void applyOffset(AbstractMethodDeclaration node) { - applyOffsetASTNode(node); - node.bodyEnd = newSourceEnd; - node.bodyStart = newSourceStart; - node.declarationSourceEnd = newSourceEnd; - node.declarationSourceStart = newSourceStart; - node.modifiersSourceStart = newSourceStart; + + private void fixPositions(TypeDeclaration node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.bodyEnd = sourceEnd; + node.bodyStart = sourceStart; + node.declarationSourceEnd = sourceEnd; + node.declarationSourceStart = sourceStart; + node.modifiersSourceStart = sourceStart; } - private void applyOffset(Javadoc node) { - applyOffsetASTNode(node); - node.valuePositions = newSourceStart; - for (int i = 0; i < node.inheritedPositions.length; i++) { - node.inheritedPositions[i] = recalcSourcePosition(node.inheritedPositions[i]); - } + private void fixPositions(ImportReference node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.declarationEnd = sourceEnd; + node.declarationSourceEnd = sourceEnd; + node.declarationSourceStart = sourceStart; + if (node.sourcePositions == null || node.sourcePositions.length != node.tokens.length) node.sourcePositions = new long[node.tokens.length]; + Arrays.fill(node.sourcePositions, sourcePos); } - - private void applyOffset(Initializer node) { - applyOffsetFieldDeclaration(node); - node.bodyStart = newSourceStart; - node.bodyEnd = newSourceEnd; - } - - private void applyOffset(TypeDeclaration node) { - applyOffsetASTNode(node); - node.bodyEnd = newSourceEnd; - node.bodyStart = newSourceStart; - node.declarationSourceEnd = newSourceEnd; - node.declarationSourceStart = newSourceStart; - node.modifiersSourceStart = newSourceStart; - } - - private void applyOffset(ImportReference node) { - applyOffsetASTNode(node); - node.declarationEnd = newSourceEnd; - node.declarationSourceEnd = newSourceEnd; - node.declarationSourceStart = newSourceStart; - for (int i = 0; i < node.sourcePositions.length; i++) { - node.sourcePositions[i] = recalcSourcePosition(node.sourcePositions[i]); - } + + private void fixPositions(ASTNode node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; } - - private void applyOffsetASTNode(ASTNode node) { - node.sourceEnd = newSourceEnd; - node.sourceStart = newSourceStart; + + private void fixPositions(SwitchStatement node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.blockStart = sourceStart; } - - private void applyOffsetExpression(Expression node) { - applyOffsetASTNode(node); -// if (node.statementEnd != -1) { - node.statementEnd = newSourceEnd; -// } + + private void fixPositions(Expression node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.statementEnd = sourceEnd; } - - private void applyOffsetVariable(AbstractVariableDeclaration node) { - applyOffsetASTNode(node); - node.declarationEnd = newSourceEnd; - node.declarationSourceEnd = newSourceEnd; - node.declarationSourceStart = newSourceStart; - node.modifiersSourceStart = newSourceStart; + + private void fixPositions(AbstractVariableDeclaration node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.declarationEnd = sourceEnd; + node.declarationSourceEnd = sourceEnd; + node.declarationSourceStart = sourceStart; + node.modifiersSourceStart = sourceStart; } - private void applyOffsetFieldDeclaration(FieldDeclaration node) { - applyOffsetVariable(node); - node.endPart1Position = newSourceEnd; - node.endPart2Position = newSourceEnd; + private void fixPositions(FieldDeclaration node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.declarationEnd = sourceEnd; + node.declarationSourceEnd = sourceEnd; + node.declarationSourceStart = sourceStart; + node.modifiersSourceStart = sourceStart; + node.endPart1Position = sourceEnd; + node.endPart2Position = sourceEnd; } - private void applyOffsetFieldReference(FieldReference node) { - applyOffsetExpression(node); - node.nameSourcePosition = recalcSourcePosition(node.nameSourcePosition); + private void fixPositions(FieldReference node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.statementEnd = sourceEnd; + node.nameSourcePosition = sourcePos; } - private void applyOffsetMessageSend(MessageSend node) { - applyOffsetExpression(node); - node.nameSourcePosition = recalcSourcePosition(node.nameSourcePosition); + private void fixPositions(MessageSend node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.statementEnd = sourceEnd; + node.nameSourcePosition = sourcePos; } - private void applyOffsetQualifiedNameReference(QualifiedNameReference node) { - applyOffsetExpression(node); - for (int i = 0; i < node.sourcePositions.length; i++) { - node.sourcePositions[i] = recalcSourcePosition(node.sourcePositions[i]); - } + private void fixPositions(QualifiedNameReference node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.statementEnd = sourceEnd; + if (node.sourcePositions == null || node.sourcePositions.length != node.tokens.length) node.sourcePositions = new long[node.tokens.length]; + Arrays.fill(node.sourcePositions, sourcePos); } - private void applyOffsetQualifiedTypeReference(QualifiedTypeReference node) { - applyOffsetExpression(node); - for (int i = 0; i < node.sourcePositions.length; i++) { - node.sourcePositions[i] = recalcSourcePosition(node.sourcePositions[i]); - } - } - - /** See {@link FieldReference#nameSourcePosition} for explanation */ - private long recalcSourcePosition(long sourcePosition) { -// long start = (sourcePosition >>> 32); -// long end = (sourcePosition & 0x00000000FFFFFFFFL); -// start = newSourceStart; -// end = newSourceStart; -// return ((start<<32)+end); - return ((long)newSourceStart << 32) | (newSourceEnd & INT_TO_LONG_MASK); + private void fixPositions(QualifiedTypeReference node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.statementEnd = sourceEnd; + if (node.sourcePositions == null || node.sourcePositions.length != node.tokens.length) node.sourcePositions = new long[node.tokens.length]; + Arrays.fill(node.sourcePositions, sourcePos); } @Override public boolean visit(AllocationExpression node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } - + @Override public boolean visit(AND_AND_Expression node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(AnnotationMethodDeclaration node, ClassScope classScope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, classScope); } @Override public boolean visit(Argument node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetVariable(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(Argument node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffsetVariable(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ArrayAllocationExpression node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ArrayInitializer node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ArrayQualifiedTypeReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetQualifiedTypeReference(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ArrayQualifiedTypeReference node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffsetQualifiedTypeReference(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ArrayReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ArrayTypeReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ArrayTypeReference node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(AssertStatement node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(Assignment node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(BinaryExpression node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(Block node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(BreakStatement node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(CaseStatement node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(CastExpression node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(CharLiteral node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ClassLiteralAccess node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(Clinit node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(CompilationUnitDeclaration node, CompilationUnitScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(CompoundAssignment node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ConditionalExpression node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ConstructorDeclaration node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ContinueStatement node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(DoStatement node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(DoubleLiteral node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(EmptyStatement node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(EqualExpression node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ExplicitConstructorCall node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ExtendedStringLiteral node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(FalseLiteral node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(FieldDeclaration node, MethodScope scope) { - setGeneratedBy(node, source); - applyOffsetFieldDeclaration(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(FieldReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetFieldReference(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(FieldReference node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffsetFieldReference(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(FloatLiteral node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ForeachStatement node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ForStatement node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(IfStatement node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ImportReference node, CompilationUnitScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(Initializer node, MethodScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(InstanceOfExpression node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(IntLiteral node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(Javadoc node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(Javadoc node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocAllocationExpression node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocAllocationExpression node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocArgumentExpression node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocArgumentExpression node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocArrayQualifiedTypeReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocArrayQualifiedTypeReference node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocArraySingleTypeReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocArraySingleTypeReference node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocFieldReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocFieldReference node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocImplicitTypeReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocImplicitTypeReference node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocMessageSend node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocMessageSend node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocQualifiedTypeReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocQualifiedTypeReference node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocReturnStatement node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocReturnStatement node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocSingleNameReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocSingleNameReference node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocSingleTypeReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocSingleTypeReference node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(LabeledStatement node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(LocalDeclaration node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetVariable(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(LongLiteral node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(MarkerAnnotation node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(MemberValuePair node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(MessageSend node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetMessageSend(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(MethodDeclaration node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(StringLiteralConcatenation node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(NormalAnnotation node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(NullLiteral node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(OR_OR_Expression node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ParameterizedQualifiedTypeReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetQualifiedTypeReference(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ParameterizedQualifiedTypeReference node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffsetQualifiedTypeReference(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ParameterizedSingleTypeReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ParameterizedSingleTypeReference node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(PostfixExpression node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(PrefixExpression node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(QualifiedAllocationExpression node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(QualifiedNameReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetQualifiedNameReference(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(QualifiedNameReference node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(QualifiedSuperReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(QualifiedSuperReference node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(QualifiedThisReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(QualifiedThisReference node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(QualifiedTypeReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetQualifiedTypeReference(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(QualifiedTypeReference node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffsetQualifiedTypeReference(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ReturnStatement node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(SingleMemberAnnotation node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(SingleNameReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(SingleNameReference node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(SingleTypeReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(SingleTypeReference node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(StringLiteral node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(SuperReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(SwitchStatement node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); - node.blockStart = newSourceStart; + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(SynchronizedStatement node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ThisReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ThisReference node, ClassScope scope) { - setGeneratedBy(node, source); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ThrowStatement node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(TrueLiteral node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(TryStatement node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(TypeDeclaration node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(TypeDeclaration node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(TypeDeclaration node, CompilationUnitScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(TypeParameter node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetVariable(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(TypeParameter node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffsetVariable(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(UnaryExpression node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(WhileStatement node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(Wildcard node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(Wildcard node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } }
\ No newline at end of file diff --git a/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaMapSingularizer.java b/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaMapSingularizer.java new file mode 100644 index 00000000..95fd8935 --- /dev/null +++ b/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaMapSingularizer.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2015 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.eclipse.handlers.singulars; + +import org.mangosdk.spi.ProviderFor; + +import lombok.core.LombokImmutableList; +import lombok.eclipse.handlers.EclipseSingularsRecipes.EclipseSingularizer; + +@ProviderFor(EclipseSingularizer.class) +public class EclipseGuavaMapSingularizer extends EclipseGuavaSingularizer { + // TODO cgcc.ImmutableMultimap, cgcc.ImmutableListMultimap, cgcc.ImmutableSetMultimap + // TODO cgcc.ImmutableClassToInstanceMap + // TODO cgcc.ImmutableRangeMap + + @Override public LombokImmutableList<String> getSupportedTypes() { + return LombokImmutableList.of( + "com.google.common.collect.ImmutableMap", + "com.google.common.collect.ImmutableBiMap", + "com.google.common.collect.ImmutableSortedMap"); + } + + @Override protected boolean isMap() { + return true; + } +} diff --git a/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaSetListSingularizer.java b/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaSetListSingularizer.java new file mode 100644 index 00000000..bc2893bf --- /dev/null +++ b/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaSetListSingularizer.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2015 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.eclipse.handlers.singulars; + +import org.mangosdk.spi.ProviderFor; + +import lombok.core.LombokImmutableList; +import lombok.eclipse.handlers.EclipseSingularsRecipes.EclipseSingularizer; + +@ProviderFor(EclipseSingularizer.class) +public class EclipseGuavaSetListSingularizer extends EclipseGuavaSingularizer { + // TODO com.google.common.collect.ImmutableTable + // TODO com.google.common.collect.ImmutableRangeSet + // TODO com.google.common.collect.ImmutableMultiset and com.google.common.collect.ImmutableSortedMultiset + @Override public LombokImmutableList<String> getSupportedTypes() { + return LombokImmutableList.of( + "com.google.common.collect.ImmutableCollection", + "com.google.common.collect.ImmutableList", + "com.google.common.collect.ImmutableSet", + "com.google.common.collect.ImmutableSortedSet"); + } + + @Override protected boolean isMap() { + return false; + } +} diff --git a/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaSingularizer.java b/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaSingularizer.java new file mode 100644 index 00000000..3b2ca875 --- /dev/null +++ b/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaSingularizer.java @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2015 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.eclipse.handlers.singulars; + +import static lombok.eclipse.Eclipse.*; +import static lombok.eclipse.handlers.EclipseHandlerUtil.*; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import lombok.core.GuavaTypeMap; +import lombok.core.handlers.HandlerUtil; +import lombok.eclipse.EclipseNode; +import lombok.eclipse.handlers.EclipseSingularsRecipes.EclipseSingularizer; +import lombok.eclipse.handlers.EclipseSingularsRecipes.SingularData; + +import org.eclipse.jdt.internal.compiler.ast.Argument; +import org.eclipse.jdt.internal.compiler.ast.Assignment; +import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; +import org.eclipse.jdt.internal.compiler.ast.ConditionalExpression; +import org.eclipse.jdt.internal.compiler.ast.EqualExpression; +import org.eclipse.jdt.internal.compiler.ast.Expression; +import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; +import org.eclipse.jdt.internal.compiler.ast.FieldReference; +import org.eclipse.jdt.internal.compiler.ast.IfStatement; +import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration; +import org.eclipse.jdt.internal.compiler.ast.MessageSend; +import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.NullLiteral; +import org.eclipse.jdt.internal.compiler.ast.OperatorIds; +import org.eclipse.jdt.internal.compiler.ast.QualifiedNameReference; +import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference; +import org.eclipse.jdt.internal.compiler.ast.ReturnStatement; +import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; +import org.eclipse.jdt.internal.compiler.ast.Statement; +import org.eclipse.jdt.internal.compiler.ast.ThisReference; +import org.eclipse.jdt.internal.compiler.ast.TypeReference; +import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; +import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; +import org.eclipse.jdt.internal.compiler.lookup.TypeIds; + +abstract class EclipseGuavaSingularizer extends EclipseSingularizer { + protected static final char[][] JAVA_UTIL_MAP = { + {'j', 'a', 'v', 'a'}, {'u', 't', 'i', 'l'}, {'M', 'a', 'p'} + }; + + protected String getSimpleTargetTypeName(SingularData data) { + return GuavaTypeMap.getGuavaTypeName(data.getTargetFqn()); + } + + protected char[] getBuilderMethodName(SingularData data) { + String simpleTypeName = getSimpleTargetTypeName(data); + if ("ImmutableSortedSet".equals(simpleTypeName) || "ImmutableSortedMap".equals(simpleTypeName)) return "naturalOrder".toCharArray(); + return "builder".toCharArray(); + } + + protected abstract boolean isMap(); + + protected char[][] makeGuavaTypeName(String simpleName, boolean addBuilder) { + char[][] tokenizedName = new char[addBuilder ? 6 : 5][]; + tokenizedName[0] = new char[] {'c', 'o', 'm'}; + tokenizedName[1] = new char[] {'g', 'o', 'o', 'g', 'l', 'e'}; + tokenizedName[2] = new char[] {'c', 'o', 'm', 'm', 'o', 'n'}; + tokenizedName[3] = new char[] {'c', 'o', 'l', 'l', 'e', 'c', 't'}; + tokenizedName[4] = simpleName.toCharArray(); + if (addBuilder) tokenizedName[5] = new char[] { 'B', 'u', 'i', 'l', 'd', 'e', 'r'}; + return tokenizedName; + } + + @Override public List<EclipseNode> generateFields(SingularData data, EclipseNode builderType) { + char[][] tokenizedName = makeGuavaTypeName(getSimpleTargetTypeName(data), true); + TypeReference type = new QualifiedTypeReference(tokenizedName, NULL_POSS); + type = addTypeArgs(isMap() ? 2 : 1, false, builderType, type, data.getTypeArgs()); + + FieldDeclaration buildField = new FieldDeclaration(data.getPluralName(), 0, -1); + buildField.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + buildField.modifiers = ClassFileConstants.AccPrivate; + buildField.declarationSourceEnd = -1; + buildField.type = type; + data.setGeneratedByRecursive(buildField); + return Collections.singletonList(injectField(builderType, buildField)); + } + + @Override public void generateMethods(SingularData data, EclipseNode builderType, boolean fluent, boolean chain) { + TypeReference returnType = chain ? cloneSelfType(builderType) : TypeReference.baseTypeReference(TypeIds.T_void, 0); + Statement returnStatement = chain ? new ReturnStatement(new ThisReference(0, 0), 0, 0) : null; + generateSingularMethod(returnType, returnStatement, data, builderType, fluent); + + returnType = chain ? cloneSelfType(builderType) : TypeReference.baseTypeReference(TypeIds.T_void, 0); + returnStatement = chain ? new ReturnStatement(new ThisReference(0, 0), 0, 0) : null; + generatePluralMethod(returnType, returnStatement, data, builderType, fluent); + } + + void generateSingularMethod(TypeReference returnType, Statement returnStatement, SingularData data, EclipseNode builderType, boolean fluent) { + boolean mapMode = isMap(); + char[] keyName = !mapMode ? data.getSingularName() : (new String(data.getSingularName()) + "$key").toCharArray(); + char[] valueName = !mapMode ? null : (new String(data.getSingularName()) + "$value").toCharArray(); + + MethodDeclaration md = new MethodDeclaration(((CompilationUnitDeclaration) builderType.top().get()).compilationResult); + md.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + md.modifiers = ClassFileConstants.AccPublic; + + List<Statement> statements = new ArrayList<Statement>(); + statements.add(createConstructBuilderVarIfNeeded(data, builderType)); + + FieldReference thisDotField = new FieldReference(data.getPluralName(), 0L); + thisDotField.receiver = new ThisReference(0, 0); + MessageSend thisDotFieldDotAdd = new MessageSend(); + if (mapMode) { + thisDotFieldDotAdd.arguments = new Expression[] { + new SingleNameReference(keyName, 0L), + new SingleNameReference(valueName, 0L)}; + } else { + thisDotFieldDotAdd.arguments = new Expression[] {new SingleNameReference(keyName, 0L)}; + } + thisDotFieldDotAdd.receiver = thisDotField; + thisDotFieldDotAdd.selector = (mapMode ? "put" : "add").toCharArray(); + statements.add(thisDotFieldDotAdd); + if (returnStatement != null) statements.add(returnStatement); + md.statements = statements.toArray(new Statement[statements.size()]); + + if (mapMode) { + TypeReference keyType = cloneParamType(0, data.getTypeArgs(), builderType); + Argument keyParam = new Argument(keyName, 0, keyType, 0); + TypeReference valueType = cloneParamType(1, data.getTypeArgs(), builderType); + Argument valueParam = new Argument(valueName, 0, valueType, 0); + md.arguments = new Argument[] {keyParam, valueParam}; + } else { + TypeReference paramType = cloneParamType(0, data.getTypeArgs(), builderType); + Argument param = new Argument(keyName, 0, paramType, 0); + md.arguments = new Argument[] {param}; + } + md.returnType = returnType; + md.selector = fluent ? data.getSingularName() : HandlerUtil.buildAccessorName(mapMode ? "put" : "add", new String(data.getSingularName())).toCharArray(); + + data.setGeneratedByRecursive(md); + injectMethod(builderType, md); + } + + void generatePluralMethod(TypeReference returnType, Statement returnStatement, SingularData data, EclipseNode builderType, boolean fluent) { + boolean mapMode = isMap(); + + MethodDeclaration md = new MethodDeclaration(((CompilationUnitDeclaration) builderType.top().get()).compilationResult); + md.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + md.modifiers = ClassFileConstants.AccPublic; + + List<Statement> statements = new ArrayList<Statement>(); + statements.add(createConstructBuilderVarIfNeeded(data, builderType)); + + FieldReference thisDotField = new FieldReference(data.getPluralName(), 0L); + thisDotField.receiver = new ThisReference(0, 0); + MessageSend thisDotFieldDotAddAll = new MessageSend(); + thisDotFieldDotAddAll.arguments = new Expression[] {new SingleNameReference(data.getPluralName(), 0L)}; + thisDotFieldDotAddAll.receiver = thisDotField; + thisDotFieldDotAddAll.selector = (mapMode ? "putAll" : "addAll").toCharArray(); + statements.add(thisDotFieldDotAddAll); + if (returnStatement != null) statements.add(returnStatement); + + md.statements = statements.toArray(new Statement[statements.size()]); + + TypeReference paramType; + if (mapMode) { + paramType = new QualifiedTypeReference(JAVA_UTIL_MAP, NULL_POSS); + paramType = addTypeArgs(2, true, builderType, paramType, data.getTypeArgs()); + } else { + paramType = new QualifiedTypeReference(TypeConstants.JAVA_LANG_ITERABLE, NULL_POSS); + paramType = addTypeArgs(1, true, builderType, paramType, data.getTypeArgs()); + } + Argument param = new Argument(data.getPluralName(), 0, paramType, 0); + md.arguments = new Argument[] {param}; + md.returnType = returnType; + md.selector = fluent ? data.getPluralName() : HandlerUtil.buildAccessorName(mapMode ? "putAll" : "addAll", new String(data.getPluralName())).toCharArray(); + + data.setGeneratedByRecursive(md); + injectMethod(builderType, md); + } + + @Override public void appendBuildCode(SingularData data, EclipseNode builderType, List<Statement> statements, char[] targetVariableName) { + boolean mapMode = isMap(); + TypeReference varType = new QualifiedTypeReference(fromQualifiedName(data.getTargetFqn()), NULL_POSS); + varType = addTypeArgs(mapMode ? 2 : 1, false, builderType, varType, data.getTypeArgs()); + + MessageSend emptyInvoke; { + //ImmutableX.of() + emptyInvoke = new MessageSend(); + emptyInvoke.selector = new char[] {'o', 'f'}; + emptyInvoke.receiver = new QualifiedNameReference(makeGuavaTypeName(getSimpleTargetTypeName(data), false), NULL_POSS, 0, 0); + emptyInvoke.typeArguments = createTypeArgs(mapMode ? 2 : 1, false, builderType, data.getTypeArgs()); + } + + MessageSend invokeBuild; { + //this.pluralName.build(); + invokeBuild = new MessageSend(); + invokeBuild.selector = new char[] {'b', 'u', 'i', 'l', 'd'}; + FieldReference thisDotField = new FieldReference(data.getPluralName(), 0L); + thisDotField.receiver = new ThisReference(0, 0); + invokeBuild.receiver = thisDotField; + } + + Expression isNull; { + //this.pluralName == null + FieldReference thisDotField = new FieldReference(data.getPluralName(), 0L); + thisDotField.receiver = new ThisReference(0, 0); + isNull = new EqualExpression(thisDotField, new NullLiteral(0, 0), OperatorIds.EQUAL_EQUAL); + } + + Expression init = new ConditionalExpression(isNull, emptyInvoke, invokeBuild); + LocalDeclaration varDefStat = new LocalDeclaration(data.getPluralName(), 0, 0); + varDefStat.type = varType; + varDefStat.initialization = init; + statements.add(varDefStat); + } + + protected Statement createConstructBuilderVarIfNeeded(SingularData data, EclipseNode builderType) { + FieldReference thisDotField = new FieldReference(data.getPluralName(), 0L); + thisDotField.receiver = new ThisReference(0, 0); + FieldReference thisDotField2 = new FieldReference(data.getPluralName(), 0L); + thisDotField2.receiver = new ThisReference(0, 0); + Expression cond = new EqualExpression(thisDotField, new NullLiteral(0, 0), OperatorIds.EQUAL_EQUAL); + + MessageSend createBuilderInvoke = new MessageSend(); + char[][] tokenizedName = makeGuavaTypeName(getSimpleTargetTypeName(data), false); + createBuilderInvoke.receiver = new QualifiedNameReference(tokenizedName, NULL_POSS, 0, 0); + createBuilderInvoke.selector = getBuilderMethodName(data); + return new IfStatement(cond, new Assignment(thisDotField2, createBuilderInvoke, 0), 0, 0); + } +} diff --git a/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilListSetSingularizer.java b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilListSetSingularizer.java new file mode 100644 index 00000000..1d1c4dbd --- /dev/null +++ b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilListSetSingularizer.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2015 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.eclipse.handlers.singulars; + +import static lombok.eclipse.Eclipse.ECLIPSE_DO_NOT_TOUCH_FLAG; +import static lombok.eclipse.handlers.EclipseHandlerUtil.*; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import lombok.core.handlers.HandlerUtil; +import lombok.eclipse.EclipseNode; +import lombok.eclipse.handlers.EclipseSingularsRecipes.SingularData; + +import org.eclipse.jdt.internal.compiler.ast.Argument; +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.FieldReference; +import org.eclipse.jdt.internal.compiler.ast.MessageSend; +import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference; +import org.eclipse.jdt.internal.compiler.ast.ReturnStatement; +import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; +import org.eclipse.jdt.internal.compiler.ast.Statement; +import org.eclipse.jdt.internal.compiler.ast.ThisReference; +import org.eclipse.jdt.internal.compiler.ast.TypeReference; +import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; +import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; +import org.eclipse.jdt.internal.compiler.lookup.TypeIds; + +abstract class EclipseJavaUtilListSetSingularizer extends EclipseJavaUtilSingularizer { + @Override public List<char[]> listFieldsToBeGenerated(SingularData data, EclipseNode builderType) { + if (useGuavaInstead(builderType)) { + return guavaListSetSingularizer.listFieldsToBeGenerated(data, builderType); + } + + return super.listFieldsToBeGenerated(data, builderType); + } + + @Override public List<char[]> listMethodsToBeGenerated(SingularData data, EclipseNode builderType) { + if (useGuavaInstead(builderType)) { + return guavaListSetSingularizer.listMethodsToBeGenerated(data, builderType); + } + + return super.listMethodsToBeGenerated(data, builderType); + } + + @Override public List<EclipseNode> generateFields(SingularData data, EclipseNode builderType) { + if (useGuavaInstead(builderType)) { + return guavaListSetSingularizer.generateFields(data, builderType); + } + + TypeReference type = new QualifiedTypeReference(JAVA_UTIL_ARRAYLIST, NULL_POSS); + type = addTypeArgs(1, false, builderType, type, data.getTypeArgs()); + + FieldDeclaration buildField = new FieldDeclaration(data.getPluralName(), 0, -1); + buildField.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + buildField.modifiers = ClassFileConstants.AccPrivate; + buildField.declarationSourceEnd = -1; + buildField.type = type; + data.setGeneratedByRecursive(buildField); + return Collections.singletonList(injectField(builderType, buildField)); + } + + @Override public void generateMethods(SingularData data, EclipseNode builderType, boolean fluent, boolean chain) { + if (useGuavaInstead(builderType)) { + guavaListSetSingularizer.generateMethods(data, builderType, fluent, chain); + return; + } + + TypeReference returnType = chain ? cloneSelfType(builderType) : TypeReference.baseTypeReference(TypeIds.T_void, 0); + Statement returnStatement = chain ? new ReturnStatement(new ThisReference(0, 0), 0, 0) : null; + generateSingularMethod(returnType, returnStatement, data, builderType, fluent); + + returnType = chain ? cloneSelfType(builderType) : TypeReference.baseTypeReference(TypeIds.T_void, 0); + returnStatement = chain ? new ReturnStatement(new ThisReference(0, 0), 0, 0) : null; + generatePluralMethod(returnType, returnStatement, data, builderType, fluent); + } + + void generateSingularMethod(TypeReference returnType, Statement returnStatement, SingularData data, EclipseNode builderType, boolean fluent) { + MethodDeclaration md = new MethodDeclaration(((CompilationUnitDeclaration) builderType.top().get()).compilationResult); + md.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + md.modifiers = ClassFileConstants.AccPublic; + + List<Statement> statements = new ArrayList<Statement>(); + statements.add(createConstructBuilderVarIfNeeded(data, builderType, false)); + + FieldReference thisDotField = new FieldReference(data.getPluralName(), 0L); + thisDotField.receiver = new ThisReference(0, 0); + MessageSend thisDotFieldDotAdd = new MessageSend(); + thisDotFieldDotAdd.arguments = new Expression[] {new SingleNameReference(data.getSingularName(), 0L)}; + thisDotFieldDotAdd.receiver = thisDotField; + thisDotFieldDotAdd.selector = "add".toCharArray(); + statements.add(thisDotFieldDotAdd); + if (returnStatement != null) statements.add(returnStatement); + + md.statements = statements.toArray(new Statement[statements.size()]); + TypeReference paramType = cloneParamType(0, data.getTypeArgs(), builderType); + Argument param = new Argument(data.getSingularName(), 0, paramType, 0); + md.arguments = new Argument[] {param}; + md.returnType = returnType; + md.selector = fluent ? data.getSingularName() : HandlerUtil.buildAccessorName("add", new String(data.getSingularName())).toCharArray(); + + data.setGeneratedByRecursive(md); + injectMethod(builderType, md); + } + + void generatePluralMethod(TypeReference returnType, Statement returnStatement, SingularData data, EclipseNode builderType, boolean fluent) { + MethodDeclaration md = new MethodDeclaration(((CompilationUnitDeclaration) builderType.top().get()).compilationResult); + md.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + md.modifiers = ClassFileConstants.AccPublic; + + List<Statement> statements = new ArrayList<Statement>(); + statements.add(createConstructBuilderVarIfNeeded(data, builderType, false)); + + FieldReference thisDotField = new FieldReference(data.getPluralName(), 0L); + thisDotField.receiver = new ThisReference(0, 0); + MessageSend thisDotFieldDotAddAll = new MessageSend(); + thisDotFieldDotAddAll.arguments = new Expression[] {new SingleNameReference(data.getPluralName(), 0L)}; + thisDotFieldDotAddAll.receiver = thisDotField; + thisDotFieldDotAddAll.selector = "addAll".toCharArray(); + statements.add(thisDotFieldDotAddAll); + if (returnStatement != null) statements.add(returnStatement); + + md.statements = statements.toArray(new Statement[statements.size()]); + + TypeReference paramType = new QualifiedTypeReference(TypeConstants.JAVA_UTIL_COLLECTION, NULL_POSS); + paramType = addTypeArgs(1, true, builderType, paramType, data.getTypeArgs()); + Argument param = new Argument(data.getPluralName(), 0, paramType, 0); + md.arguments = new Argument[] {param}; + md.returnType = returnType; + md.selector = fluent ? data.getPluralName() : HandlerUtil.buildAccessorName("addAll", new String(data.getPluralName())).toCharArray(); + + data.setGeneratedByRecursive(md); + injectMethod(builderType, md); + } +} diff --git a/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilListSingularizer.java b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilListSingularizer.java new file mode 100644 index 00000000..0a9eaf75 --- /dev/null +++ b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilListSingularizer.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2015 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.eclipse.handlers.singulars; + +import static lombok.eclipse.handlers.EclipseHandlerUtil.makeIntLiteral; + +import java.util.ArrayList; +import java.util.List; + +import lombok.core.LombokImmutableList; +import lombok.eclipse.Eclipse; +import lombok.eclipse.EclipseNode; +import lombok.eclipse.handlers.EclipseSingularsRecipes.EclipseSingularizer; +import lombok.eclipse.handlers.EclipseSingularsRecipes.SingularData; + +import org.eclipse.jdt.internal.compiler.ast.AllocationExpression; +import org.eclipse.jdt.internal.compiler.ast.Assignment; +import org.eclipse.jdt.internal.compiler.ast.BreakStatement; +import org.eclipse.jdt.internal.compiler.ast.CaseStatement; +import org.eclipse.jdt.internal.compiler.ast.Expression; +import org.eclipse.jdt.internal.compiler.ast.FieldReference; +import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration; +import org.eclipse.jdt.internal.compiler.ast.MessageSend; +import org.eclipse.jdt.internal.compiler.ast.QualifiedNameReference; +import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference; +import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; +import org.eclipse.jdt.internal.compiler.ast.Statement; +import org.eclipse.jdt.internal.compiler.ast.SwitchStatement; +import org.eclipse.jdt.internal.compiler.ast.ThisReference; +import org.eclipse.jdt.internal.compiler.ast.TypeReference; +import org.mangosdk.spi.ProviderFor; + + +@ProviderFor(EclipseSingularizer.class) +public class EclipseJavaUtilListSingularizer extends EclipseJavaUtilListSetSingularizer { + @Override public LombokImmutableList<String> getSupportedTypes() { + return LombokImmutableList.of("java.util.List", "java.util.Collection", "java.util.Iterable"); + } + + @Override public void appendBuildCode(SingularData data, EclipseNode builderType, List<Statement> statements, char[] targetVariableName) { + if (useGuavaInstead(builderType)) { + guavaListSetSingularizer.appendBuildCode(data, builderType, statements, targetVariableName); + return; + } + + List<Statement> switchContents = new ArrayList<Statement>(); + + /* case 0: (empty) break; */ { + switchContents.add(new CaseStatement(makeIntLiteral(new char[] {'0'}, null), 0, 0)); + MessageSend invoke = new MessageSend(); + invoke.receiver = new QualifiedNameReference(JAVA_UTIL_COLLECTIONS, NULL_POSS, 0, 0); + invoke.selector = "emptyList".toCharArray(); + switchContents.add(new Assignment(new SingleNameReference(data.getPluralName(), 0), invoke, 0)); + switchContents.add(new BreakStatement(null, 0, 0)); + } + + /* case 1: (singleton) break; */ { + switchContents.add(new CaseStatement(makeIntLiteral(new char[] {'1'}, null), 0, 0)); + FieldReference thisDotField = new FieldReference(data.getPluralName(), 0L); + thisDotField.receiver = new ThisReference(0, 0); + MessageSend thisDotFieldGet0 = new MessageSend(); + thisDotFieldGet0.receiver = thisDotField; + thisDotFieldGet0.selector = new char[] {'g', 'e', 't'}; + thisDotFieldGet0.arguments = new Expression[] {makeIntLiteral(new char[] {'0'}, null)}; + + Expression[] args = new Expression[] {thisDotFieldGet0}; + MessageSend invoke = new MessageSend(); + invoke.receiver = new QualifiedNameReference(JAVA_UTIL_COLLECTIONS, NULL_POSS, 0, 0); + invoke.selector = "singletonList".toCharArray(); + invoke.arguments = args; + switchContents.add(new Assignment(new SingleNameReference(data.getPluralName(), 0), invoke, 0)); + switchContents.add(new BreakStatement(null, 0, 0)); + } + + /* default: Create by passing builder field to constructor. */ { + switchContents.add(new CaseStatement(null, 0, 0)); + + Expression argToUnmodifiable; + /* new j.u.ArrayList<Generics>(this.pluralName); */ { + FieldReference thisDotPluralName = new FieldReference(data.getPluralName(), 0L); + thisDotPluralName.receiver = new ThisReference(0, 0); + TypeReference targetTypeExpr = new QualifiedTypeReference(JAVA_UTIL_ARRAYLIST, NULL_POSS); + targetTypeExpr = addTypeArgs(1, false, builderType, targetTypeExpr, data.getTypeArgs()); + AllocationExpression constructorCall = new AllocationExpression(); + constructorCall.type = targetTypeExpr; + constructorCall.arguments = new Expression[] {thisDotPluralName}; + argToUnmodifiable = constructorCall; + } + + /* pluralname = Collections.unmodifiableList(-newlist-); */ { + MessageSend unmodInvoke = new MessageSend(); + unmodInvoke.receiver = new QualifiedNameReference(JAVA_UTIL_COLLECTIONS, NULL_POSS, 0, 0); + unmodInvoke.selector = "unmodifiableList".toCharArray(); + unmodInvoke.arguments = new Expression[] {argToUnmodifiable}; + switchContents.add(new Assignment(new SingleNameReference(data.getPluralName(), 0), unmodInvoke, 0)); + } + } + + SwitchStatement switchStat = new SwitchStatement(); + switchStat.statements = switchContents.toArray(new Statement[switchContents.size()]); + switchStat.expression = getSize(builderType, data.getPluralName(), true); + + TypeReference localShadowerType = new QualifiedTypeReference(Eclipse.fromQualifiedName(data.getTargetFqn()), NULL_POSS); + localShadowerType = addTypeArgs(1, false, builderType, localShadowerType, data.getTypeArgs()); + LocalDeclaration varDefStat = new LocalDeclaration(data.getPluralName(), 0, 0); + varDefStat.type = localShadowerType; + statements.add(varDefStat); + statements.add(switchStat); + } +} diff --git a/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilMapSingularizer.java b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilMapSingularizer.java new file mode 100644 index 00000000..640bd396 --- /dev/null +++ b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilMapSingularizer.java @@ -0,0 +1,270 @@ +/* + * Copyright (C) 2015 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.eclipse.handlers.singulars; + +import static lombok.eclipse.Eclipse.*; +import static lombok.eclipse.handlers.EclipseHandlerUtil.*; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.eclipse.jdt.internal.compiler.ast.Argument; +import org.eclipse.jdt.internal.compiler.ast.Block; +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.FieldReference; +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.ast.MethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference; +import org.eclipse.jdt.internal.compiler.ast.ReturnStatement; +import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; +import org.eclipse.jdt.internal.compiler.ast.Statement; +import org.eclipse.jdt.internal.compiler.ast.ThisReference; +import org.eclipse.jdt.internal.compiler.ast.TypeReference; +import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; +import org.eclipse.jdt.internal.compiler.lookup.TypeIds; +import org.mangosdk.spi.ProviderFor; + +import lombok.core.LombokImmutableList; +import lombok.core.handlers.HandlerUtil; +import lombok.eclipse.EclipseNode; +import lombok.eclipse.handlers.EclipseSingularsRecipes.EclipseSingularizer; +import lombok.eclipse.handlers.EclipseSingularsRecipes.SingularData; + +@ProviderFor(EclipseSingularizer.class) +public class EclipseJavaUtilMapSingularizer extends EclipseJavaUtilSingularizer { + @Override public LombokImmutableList<String> getSupportedTypes() { + return LombokImmutableList.of("java.util.Map", "java.util.SortedMap", "java.util.NavigableMap"); + } + + @Override public List<char[]> listFieldsToBeGenerated(SingularData data, EclipseNode builderType) { + if (useGuavaInstead(builderType)) { + return guavaMapSingularizer.listFieldsToBeGenerated(data, builderType); + } + + char[] p = data.getPluralName(); + int len = p.length; + char[] k = new char[len + 4]; + char[] v = new char[len + 6]; + System.arraycopy(p, 0, k, 0, len); + System.arraycopy(p, 0, v, 0, len); + k[len] = '$'; + k[len + 1] = 'k'; + k[len + 2] = 'e'; + k[len + 3] = 'y'; + v[len] = '$'; + v[len + 1] = 'v'; + v[len + 2] = 'a'; + v[len + 3] = 'l'; + v[len + 4] = 'u'; + v[len + 5] = 'e'; + return Arrays.asList(k, v); + } + + @Override public List<char[]> listMethodsToBeGenerated(SingularData data, EclipseNode builderType) { + if (useGuavaInstead(builderType)) { + return guavaMapSingularizer.listFieldsToBeGenerated(data, builderType); + } else { + return super.listMethodsToBeGenerated(data, builderType); + } + } + + @Override public List<EclipseNode> generateFields(SingularData data, EclipseNode builderType) { + if (useGuavaInstead(builderType)) { + return guavaMapSingularizer.generateFields(data, builderType); + } + + char[] keyName = (new String(data.getPluralName()) + "$key").toCharArray(); + char[] valueName = (new String(data.getPluralName()) + "$value").toCharArray(); + FieldDeclaration buildKeyField; { + TypeReference type = new QualifiedTypeReference(JAVA_UTIL_ARRAYLIST, NULL_POSS); + type = addTypeArgs(1, false, builderType, type, data.getTypeArgs()); + buildKeyField = new FieldDeclaration(keyName, 0, -1); + buildKeyField.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + buildKeyField.modifiers = ClassFileConstants.AccPrivate; + buildKeyField.declarationSourceEnd = -1; + buildKeyField.type = type; + } + FieldDeclaration buildValueField; { + TypeReference type = new QualifiedTypeReference(JAVA_UTIL_ARRAYLIST, NULL_POSS); + List<TypeReference> tArgs = data.getTypeArgs(); + if (tArgs != null && tArgs.size() > 1) tArgs = Collections.singletonList(tArgs.get(1)); + else tArgs = Collections.emptyList(); + type = addTypeArgs(1, false, builderType, type, tArgs); + buildValueField = new FieldDeclaration(valueName, 0, -1); + buildValueField.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + buildValueField.modifiers = ClassFileConstants.AccPrivate; + buildValueField.declarationSourceEnd = -1; + buildValueField.type = type; + } + data.setGeneratedByRecursive(buildKeyField); + data.setGeneratedByRecursive(buildValueField); + EclipseNode keyFieldNode = injectField(builderType, buildKeyField); + EclipseNode valueFieldNode = injectField(builderType, buildValueField); + return Arrays.asList(keyFieldNode, valueFieldNode); + } + + @Override public void generateMethods(SingularData data, EclipseNode builderType, boolean fluent, boolean chain) { + if (useGuavaInstead(builderType)) { + guavaMapSingularizer.generateMethods(data, builderType, fluent, chain); + return; + } + + TypeReference returnType = chain ? cloneSelfType(builderType) : TypeReference.baseTypeReference(TypeIds.T_void, 0); + Statement returnStatement = chain ? new ReturnStatement(new ThisReference(0, 0), 0, 0) : null; + generateSingularMethod(returnType, returnStatement, data, builderType, fluent); + + returnType = chain ? cloneSelfType(builderType) : TypeReference.baseTypeReference(TypeIds.T_void, 0); + returnStatement = chain ? new ReturnStatement(new ThisReference(0, 0), 0, 0) : null; + generatePluralMethod(returnType, returnStatement, data, builderType, fluent); + } + + private void generateSingularMethod(TypeReference returnType, Statement returnStatement, SingularData data, EclipseNode builderType, boolean fluent) { + MethodDeclaration md = new MethodDeclaration(((CompilationUnitDeclaration) builderType.top().get()).compilationResult); + md.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + md.modifiers = ClassFileConstants.AccPublic; + + List<Statement> statements = new ArrayList<Statement>(); + statements.add(createConstructBuilderVarIfNeeded(data, builderType, true)); + + String sN = new String(data.getSingularName()); + String pN = new String(data.getPluralName()); + char[] keyParamName = (sN + "Key").toCharArray(); + char[] valueParamName = (sN + "Value").toCharArray(); + char[] keyFieldName = (pN + "$key").toCharArray(); + char[] valueFieldName = (pN + "$value").toCharArray(); + + /* this.pluralname$key.add(singularnameKey); */ { + FieldReference thisDotKeyField = new FieldReference(keyFieldName, 0L); + thisDotKeyField.receiver = new ThisReference(0, 0); + MessageSend thisDotKeyFieldDotAdd = new MessageSend(); + thisDotKeyFieldDotAdd.arguments = new Expression[] {new SingleNameReference(keyParamName, 0L)}; + thisDotKeyFieldDotAdd.receiver = thisDotKeyField; + thisDotKeyFieldDotAdd.selector = "add".toCharArray(); + statements.add(thisDotKeyFieldDotAdd); + } + + /* this.pluralname$value.add(singularnameValue); */ { + FieldReference thisDotValueField = new FieldReference(valueFieldName, 0L); + thisDotValueField.receiver = new ThisReference(0, 0); + MessageSend thisDotValueFieldDotAdd = new MessageSend(); + thisDotValueFieldDotAdd.arguments = new Expression[] {new SingleNameReference(valueParamName, 0L)}; + thisDotValueFieldDotAdd.receiver = thisDotValueField; + thisDotValueFieldDotAdd.selector = "add".toCharArray(); + statements.add(thisDotValueFieldDotAdd); + } + if (returnStatement != null) statements.add(returnStatement); + + md.statements = statements.toArray(new Statement[statements.size()]); + TypeReference keyParamType = cloneParamType(0, data.getTypeArgs(), builderType); + Argument keyParam = new Argument(keyParamName, 0, keyParamType, 0); + TypeReference valueParamType = cloneParamType(1, data.getTypeArgs(), builderType); + Argument valueParam = new Argument(valueParamName, 0, valueParamType, 0); + md.arguments = new Argument[] {keyParam, valueParam}; + md.returnType = returnType; + md.selector = fluent ? data.getSingularName() : HandlerUtil.buildAccessorName("put", new String(data.getSingularName())).toCharArray(); + + data.setGeneratedByRecursive(md); + injectMethod(builderType, md); + } + + private void generatePluralMethod(TypeReference returnType, Statement returnStatement, SingularData data, EclipseNode builderType, boolean fluent) { + MethodDeclaration md = new MethodDeclaration(((CompilationUnitDeclaration) builderType.top().get()).compilationResult); + md.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + md.modifiers = ClassFileConstants.AccPublic; + + String pN = new String(data.getPluralName()); + char[] keyFieldName = (pN + "$key").toCharArray(); + char[] valueFieldName = (pN + "$value").toCharArray(); + + List<Statement> statements = new ArrayList<Statement>(); + statements.add(createConstructBuilderVarIfNeeded(data, builderType, true)); + + char[] entryName = "$lombokEntry".toCharArray(); + + TypeReference forEachType = new QualifiedTypeReference(JAVA_UTIL_MAP_ENTRY, NULL_POSS); + forEachType = addTypeArgs(2, true, builderType, forEachType, data.getTypeArgs()); + + MessageSend keyArg = new MessageSend(); + keyArg.receiver = new SingleNameReference(entryName, 0L); + keyArg.selector = "getKey".toCharArray(); + MessageSend addKey = new MessageSend(); + FieldReference thisDotKeyField = new FieldReference(keyFieldName, 0L); + thisDotKeyField.receiver = new ThisReference(0, 0); + addKey.receiver = thisDotKeyField; + addKey.selector = new char[] {'a', 'd', 'd'}; + addKey.arguments = new Expression[] {keyArg}; + + MessageSend valueArg = new MessageSend(); + valueArg.receiver = new SingleNameReference(entryName, 0L); + valueArg.selector = "getValue".toCharArray(); + MessageSend addValue = new MessageSend(); + FieldReference thisDotValueField = new FieldReference(valueFieldName, 0L); + thisDotValueField.receiver = new ThisReference(0, 0); + addValue.receiver = thisDotValueField; + addValue.selector = new char[] {'a', 'd', 'd'}; + addValue.arguments = new Expression[] {valueArg}; + + LocalDeclaration elementVariable = new LocalDeclaration(entryName, 0, 0); + elementVariable.type = forEachType; + ForeachStatement forEach = new ForeachStatement(elementVariable, 0); + MessageSend invokeEntrySet = new MessageSend(); + invokeEntrySet.selector = new char[] { 'e', 'n', 't', 'r', 'y', 'S', 'e', 't'}; + invokeEntrySet.receiver = new SingleNameReference(data.getPluralName(), 0L); + forEach.collection = invokeEntrySet; + Block forEachContent = new Block(0); + forEachContent.statements = new Statement[] {addKey, addValue}; + forEach.action = forEachContent; + statements.add(forEach); + if (returnStatement != null) statements.add(returnStatement); + + md.statements = statements.toArray(new Statement[statements.size()]); + + TypeReference paramType = new QualifiedTypeReference(JAVA_UTIL_MAP, NULL_POSS); + paramType = addTypeArgs(2, true, builderType, paramType, data.getTypeArgs()); + Argument param = new Argument(data.getPluralName(), 0, paramType, 0); + md.arguments = new Argument[] {param}; + md.returnType = returnType; + md.selector = fluent ? data.getPluralName() : HandlerUtil.buildAccessorName("putAll", new String(data.getPluralName())).toCharArray(); + + data.setGeneratedByRecursive(md); + injectMethod(builderType, md); + } + + @Override public void appendBuildCode(SingularData data, EclipseNode builderType, List<Statement> statements, char[] targetVariableName) { + if (useGuavaInstead(builderType)) { + guavaMapSingularizer.appendBuildCode(data, builderType, statements, targetVariableName); + return; + } + + if (data.getTargetFqn().equals("java.util.Map")) { + statements.addAll(createJavaUtilSetMapInitialCapacitySwitchStatements(data, builderType, true, "emptyMap", "singletonMap", "LinkedHashMap")); + } else { + statements.addAll(createJavaUtilSimpleCreationAndFillStatements(data, builderType, true, true, false, true, "TreeMap")); + } + } +} diff --git a/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilSetSingularizer.java b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilSetSingularizer.java new file mode 100644 index 00000000..2d16eae0 --- /dev/null +++ b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilSetSingularizer.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2015 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.eclipse.handlers.singulars; + +import java.util.List; + +import lombok.core.LombokImmutableList; +import lombok.eclipse.EclipseNode; +import lombok.eclipse.handlers.EclipseSingularsRecipes.EclipseSingularizer; +import lombok.eclipse.handlers.EclipseSingularsRecipes.SingularData; + +import org.eclipse.jdt.internal.compiler.ast.Statement; +import org.mangosdk.spi.ProviderFor; + +@ProviderFor(EclipseSingularizer.class) +public class EclipseJavaUtilSetSingularizer extends EclipseJavaUtilListSetSingularizer { + @Override public LombokImmutableList<String> getSupportedTypes() { + return LombokImmutableList.of("java.util.Set", "java.util.SortedSet", "java.util.NavigableSet"); + } + + @Override public void appendBuildCode(SingularData data, EclipseNode builderType, List<Statement> statements, char[] targetVariableName) { + if (useGuavaInstead(builderType)) { + guavaListSetSingularizer.appendBuildCode(data, builderType, statements, targetVariableName); + return; + } + + if (data.getTargetFqn().equals("java.util.Set")) { + statements.addAll(createJavaUtilSetMapInitialCapacitySwitchStatements(data, builderType, false, "emptySet", "singleton", "LinkedHashSet")); + } else { + statements.addAll(createJavaUtilSimpleCreationAndFillStatements(data, builderType, false, true, false, true, "TreeSet")); + } + } +} diff --git a/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilSingularizer.java b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilSingularizer.java new file mode 100644 index 00000000..6661f4af --- /dev/null +++ b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilSingularizer.java @@ -0,0 +1,300 @@ +/* + * Copyright (C) 2015 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.eclipse.handlers.singulars; + +import static lombok.eclipse.Eclipse.*; +import static lombok.eclipse.handlers.EclipseHandlerUtil.*; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.eclipse.jdt.internal.compiler.ast.AllocationExpression; +import org.eclipse.jdt.internal.compiler.ast.Assignment; +import org.eclipse.jdt.internal.compiler.ast.BinaryExpression; +import org.eclipse.jdt.internal.compiler.ast.Block; +import org.eclipse.jdt.internal.compiler.ast.BreakStatement; +import org.eclipse.jdt.internal.compiler.ast.CaseStatement; +import org.eclipse.jdt.internal.compiler.ast.ConditionalExpression; +import org.eclipse.jdt.internal.compiler.ast.EqualExpression; +import org.eclipse.jdt.internal.compiler.ast.Expression; +import org.eclipse.jdt.internal.compiler.ast.FieldReference; +import org.eclipse.jdt.internal.compiler.ast.ForStatement; +import org.eclipse.jdt.internal.compiler.ast.IfStatement; +import org.eclipse.jdt.internal.compiler.ast.IntLiteral; +import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration; +import org.eclipse.jdt.internal.compiler.ast.MessageSend; +import org.eclipse.jdt.internal.compiler.ast.NullLiteral; +import org.eclipse.jdt.internal.compiler.ast.OperatorIds; +import org.eclipse.jdt.internal.compiler.ast.PostfixExpression; +import org.eclipse.jdt.internal.compiler.ast.QualifiedNameReference; +import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference; +import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; +import org.eclipse.jdt.internal.compiler.ast.Statement; +import org.eclipse.jdt.internal.compiler.ast.SwitchStatement; +import org.eclipse.jdt.internal.compiler.ast.ThisReference; +import org.eclipse.jdt.internal.compiler.ast.TypeReference; +import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; +import org.eclipse.jdt.internal.compiler.lookup.TypeIds; + +import lombok.ConfigurationKeys; +import lombok.eclipse.EclipseNode; +import lombok.eclipse.handlers.EclipseSingularsRecipes.EclipseSingularizer; +import lombok.eclipse.handlers.EclipseSingularsRecipes.SingularData; + +abstract class EclipseJavaUtilSingularizer extends EclipseSingularizer { + protected static final char[][] JAVA_UTIL_ARRAYLIST = { + {'j', 'a', 'v', 'a'}, {'u', 't', 'i', 'l'}, {'A', 'r', 'r', 'a', 'y', 'L', 'i', 's', 't'} + }; + + protected static final char[][] JAVA_UTIL_LIST = { + {'j', 'a', 'v', 'a'}, {'u', 't', 'i', 'l'}, {'L', 'i', 's', 't'} + }; + + protected static final char[][] JAVA_UTIL_MAP = { + {'j', 'a', 'v', 'a'}, {'u', 't', 'i', 'l'}, {'M', 'a', 'p'} + }; + + protected static final char[][] JAVA_UTIL_MAP_ENTRY = { + {'j', 'a', 'v', 'a'}, {'u', 't', 'i', 'l'}, {'M', 'a', 'p'}, {'E', 'n', 't', 'r', 'y'} + }; + + protected static final char[][] JAVA_UTIL_COLLECTIONS = { + {'j', 'a', 'v', 'a'}, {'u', 't', 'i', 'l'}, {'C', 'o', 'l', 'l', 'e', 'c', 't', 'i', 'o', 'n', 's'} + }; + + protected final EclipseSingularizer guavaListSetSingularizer = new EclipseGuavaSetListSingularizer(); + protected final EclipseSingularizer guavaMapSingularizer = new EclipseGuavaMapSingularizer(); + + protected boolean useGuavaInstead(EclipseNode node) { + return Boolean.TRUE.equals(node.getAst().readConfiguration(ConfigurationKeys.SINGULAR_USE_GUAVA)); + } + + protected List<Statement> createJavaUtilSetMapInitialCapacitySwitchStatements(SingularData data, EclipseNode builderType, boolean mapMode, String emptyCollectionMethod, String singletonCollectionMethod, String targetType) { + List<Statement> switchContents = new ArrayList<Statement>(); + char[] keyName = mapMode ? (new String(data.getPluralName()) + "$key").toCharArray() : data.getPluralName(); + + if (emptyCollectionMethod != null) { // case 0: (empty); break; + switchContents.add(new CaseStatement(makeIntLiteral(new char[] {'0'}, null), 0, 0)); + + /* pluralName = java.util.Collections.emptyCollectionMethod(); */ { + MessageSend invoke = new MessageSend(); + invoke.receiver = new QualifiedNameReference(JAVA_UTIL_COLLECTIONS, NULL_POSS, 0, 0); + invoke.selector = emptyCollectionMethod.toCharArray(); + switchContents.add(new Assignment(new SingleNameReference(data.getPluralName(), 0), invoke, 0)); + } + + switchContents.add(new BreakStatement(null, 0, 0)); + } + + if (singletonCollectionMethod != null) { // case 1: (singleton); break; + switchContents.add(new CaseStatement(makeIntLiteral(new char[] {'1'}, null), 0, 0)); + /* !mapMode: pluralName = java.util.Collections.singletonCollectionMethod(this.pluralName.get(0)); + mapMode: pluralName = java.util.Collections.singletonCollectionMethod(this.pluralName$key.get(0), this.pluralName$value.get(0)); */ { + FieldReference thisDotKey = new FieldReference(keyName, 0L); + thisDotKey.receiver = new ThisReference(0, 0); + MessageSend thisDotKeyGet0 = new MessageSend(); + thisDotKeyGet0.receiver = thisDotKey; + thisDotKeyGet0.selector = new char[] {'g', 'e', 't'}; + thisDotKeyGet0.arguments = new Expression[] {makeIntLiteral(new char[] {'0'}, null)}; + + Expression[] args; + if (mapMode) { + char[] valueName = (new String(data.getPluralName()) + "$value").toCharArray(); + FieldReference thisDotValue = new FieldReference(valueName, 0L); + thisDotValue.receiver = new ThisReference(0, 0); + MessageSend thisDotValueGet0 = new MessageSend(); + thisDotValueGet0.receiver = thisDotValue; + thisDotValueGet0.selector = new char[] {'g', 'e', 't'}; + thisDotValueGet0.arguments = new Expression[] {makeIntLiteral(new char[] {'0'}, null)}; + args = new Expression[] {thisDotKeyGet0, thisDotValueGet0}; + } else { + args = new Expression[] {thisDotKeyGet0}; + } + + MessageSend invoke = new MessageSend(); + invoke.receiver = new QualifiedNameReference(JAVA_UTIL_COLLECTIONS, NULL_POSS, 0, 0); + invoke.selector = singletonCollectionMethod.toCharArray(); + invoke.arguments = args; + switchContents.add(new Assignment(new SingleNameReference(data.getPluralName(), 0), invoke, 0)); + } + switchContents.add(new BreakStatement(null, 0, 0)); + } + + { // default: + switchContents.add(new CaseStatement(null, 0, 0)); + switchContents.addAll(createJavaUtilSimpleCreationAndFillStatements(data, builderType, mapMode, false, true, emptyCollectionMethod == null, targetType)); + } + + SwitchStatement switchStat = new SwitchStatement(); + switchStat.statements = switchContents.toArray(new Statement[switchContents.size()]); + switchStat.expression = getSize(builderType, keyName, true); + + TypeReference localShadowerType = new QualifiedTypeReference(fromQualifiedName(data.getTargetFqn()), NULL_POSS); + localShadowerType = addTypeArgs(mapMode ? 2 : 1, false, builderType, localShadowerType, data.getTypeArgs()); + LocalDeclaration varDefStat = new LocalDeclaration(data.getPluralName(), 0, 0); + varDefStat.type = localShadowerType; + return Arrays.asList(varDefStat, switchStat); + } + + protected List<Statement> createJavaUtilSimpleCreationAndFillStatements(SingularData data, EclipseNode builderType, boolean mapMode, boolean defineVar, boolean addInitialCapacityArg, boolean nullGuard, String targetType) { + char[] varName = mapMode ? (new String(data.getPluralName()) + "$key").toCharArray() : data.getPluralName(); + + Statement createStat; { + // pluralName = new java.util.TargetType(initialCap); + Expression[] constructorArgs = null; + if (addInitialCapacityArg) { + // this.varName.size() < MAX_POWER_OF_2 ? 1 + this.varName.size() + (this.varName.size() - 3) / 3 : Integer.MAX_VALUE; + // lessThanCutOff = this.varName.size() < MAX_POWER_OF_2 + Expression lessThanCutoff = new BinaryExpression(getSize(builderType, varName, nullGuard), makeIntLiteral("0x40000000".toCharArray(), null), OperatorIds.LESS); + FieldReference integerMaxValue = new FieldReference("MAX_VALUE".toCharArray(), 0L); + integerMaxValue.receiver = new QualifiedNameReference(TypeConstants.JAVA_LANG_INTEGER, NULL_POSS, 0, 0); + Expression sizeFormulaLeft = new BinaryExpression(makeIntLiteral(new char[] {'1'}, null), getSize(builderType, varName, nullGuard), OperatorIds.PLUS); + Expression sizeFormulaRightLeft = new BinaryExpression(getSize(builderType, varName, nullGuard), makeIntLiteral(new char[] {'3'}, null), OperatorIds.MINUS); + Expression sizeFormulaRight = new BinaryExpression(sizeFormulaRightLeft, makeIntLiteral(new char[] {'3'}, null), OperatorIds.DIVIDE); + Expression sizeFormula = new BinaryExpression(sizeFormulaLeft, sizeFormulaRight, OperatorIds.PLUS); + Expression cond = new ConditionalExpression(lessThanCutoff, sizeFormula, integerMaxValue); + constructorArgs = new Expression[] {cond}; + } + + TypeReference targetTypeRef = new QualifiedTypeReference(new char[][] {TypeConstants.JAVA, TypeConstants.UTIL, targetType.toCharArray()}, NULL_POSS); + targetTypeRef = addTypeArgs(mapMode ? 2 : 1, false, builderType, targetTypeRef, data.getTypeArgs()); + AllocationExpression constructorCall = new AllocationExpression(); + constructorCall.type = targetTypeRef; + constructorCall.arguments = constructorArgs; + + if (defineVar) { + TypeReference localShadowerType = new QualifiedTypeReference(fromQualifiedName(data.getTargetFqn()), NULL_POSS); + localShadowerType = addTypeArgs(mapMode ? 2 : 1, false, builderType, localShadowerType, data.getTypeArgs()); + LocalDeclaration localShadowerDecl = new LocalDeclaration(data.getPluralName(), 0, 0); + localShadowerDecl.type = localShadowerType; + localShadowerDecl.initialization = constructorCall; + createStat = localShadowerDecl; + } else { + createStat = new Assignment(new SingleNameReference(data.getPluralName(), 0L), constructorCall, 0); + } + } + + Statement fillStat; { + if (mapMode) { + // for (int $i = 0; $i < this.pluralname$key.size(); i++) pluralname.put(this.pluralname$key.get($i), this.pluralname$value.get($i)); + char[] iVar = new char[] {'$', 'i'}; + MessageSend pluralnameDotPut = new MessageSend(); + pluralnameDotPut.selector = new char[] {'p', 'u', 't'}; + pluralnameDotPut.receiver = new SingleNameReference(data.getPluralName(), 0L); + FieldReference thisDotKey = new FieldReference(varName, 0L); + thisDotKey.receiver = new ThisReference(0, 0); + FieldReference thisDotValue = new FieldReference((new String(data.getPluralName()) + "$value").toCharArray(), 0L); + thisDotValue.receiver = new ThisReference(0, 0); + MessageSend keyArg = new MessageSend(); + keyArg.receiver = thisDotKey; + keyArg.arguments = new Expression[] {new SingleNameReference(iVar, 0L)}; + keyArg.selector = new char[] {'g', 'e', 't'}; + MessageSend valueArg = new MessageSend(); + valueArg.receiver = thisDotValue; + valueArg.arguments = new Expression[] {new SingleNameReference(iVar, 0L)}; + valueArg.selector = new char[] {'g', 'e', 't'}; + pluralnameDotPut.arguments = new Expression[] {keyArg, valueArg}; + + LocalDeclaration forInit = new LocalDeclaration(iVar, 0, 0); + forInit.type = TypeReference.baseTypeReference(TypeIds.T_int, 0); + forInit.initialization = makeIntLiteral(new char[] {'0'}, null); + Expression checkExpr = new BinaryExpression(new SingleNameReference(iVar, 0L), getSize(builderType, varName, nullGuard), OperatorIds.LESS); + Expression incrementExpr = new PostfixExpression(new SingleNameReference(iVar, 0L), IntLiteral.One, OperatorIds.PLUS, 0); + fillStat = new ForStatement(new Statement[] {forInit}, checkExpr, new Statement[] {incrementExpr}, pluralnameDotPut, true, 0, 0); + } else { + // pluralname.addAll(this.pluralname); + MessageSend pluralnameDotAddAll = new MessageSend(); + pluralnameDotAddAll.selector = new char[] {'a', 'd', 'd', 'A', 'l', 'l'}; + pluralnameDotAddAll.receiver = new SingleNameReference(data.getPluralName(), 0L); + FieldReference thisDotPluralname = new FieldReference(varName, 0L); + thisDotPluralname.receiver = new ThisReference(0, 0); + pluralnameDotAddAll.arguments = new Expression[] {thisDotPluralname}; + fillStat = pluralnameDotAddAll; + } + + if (nullGuard) { + FieldReference thisDotField = new FieldReference(varName, 0L); + thisDotField.receiver = new ThisReference(0, 0); + Expression cond = new EqualExpression(thisDotField, new NullLiteral(0, 0), OperatorIds.NOT_EQUAL); + fillStat = new IfStatement(cond, fillStat, 0, 0); + } + } + + Statement unmodifiableStat; { + // pluralname = Collections.unmodifiableInterfaceType(pluralname); + Expression arg = new SingleNameReference(data.getPluralName(), 0L); + MessageSend invoke = new MessageSend(); + invoke.arguments = new Expression[] {arg}; + invoke.selector = ("unmodifiable" + data.getTargetSimpleType()).toCharArray(); + invoke.receiver = new QualifiedNameReference(JAVA_UTIL_COLLECTIONS, NULL_POSS, 0, 0); + unmodifiableStat = new Assignment(new SingleNameReference(data.getPluralName(), 0L), invoke, 0); + } + + return Arrays.asList(createStat, fillStat, unmodifiableStat); + } + + protected Statement createConstructBuilderVarIfNeeded(SingularData data, EclipseNode builderType, boolean mapMode) { + char[] v1Name, v2Name; + if (mapMode) { + String n = new String(data.getPluralName()); + v1Name = (n + "$key").toCharArray(); + v2Name = (n + "$value").toCharArray(); + } else { + v1Name = data.getPluralName(); + v2Name = null; + } + + FieldReference thisDotField = new FieldReference(v1Name, 0L); + thisDotField.receiver = new ThisReference(0, 0); + Expression cond = new EqualExpression(thisDotField, new NullLiteral(0, 0), OperatorIds.EQUAL_EQUAL); + + thisDotField = new FieldReference(v1Name, 0L); + thisDotField.receiver = new ThisReference(0, 0); + TypeReference v1Type = new QualifiedTypeReference(JAVA_UTIL_ARRAYLIST, NULL_POSS); + v1Type = addTypeArgs(1, false, builderType, v1Type, data.getTypeArgs()); + AllocationExpression constructArrayList = new AllocationExpression(); + constructArrayList.type = v1Type; + Assignment initV1 = new Assignment(thisDotField, constructArrayList, 0); + Statement thenPart; + if (mapMode) { + thisDotField = new FieldReference(v2Name, 0L); + thisDotField.receiver = new ThisReference(0, 0); + TypeReference v2Type = new QualifiedTypeReference(JAVA_UTIL_ARRAYLIST, NULL_POSS); + List<TypeReference> tArgs = data.getTypeArgs(); + if (tArgs != null && tArgs.size() > 1) tArgs = Collections.singletonList(tArgs.get(1)); + else tArgs = Collections.emptyList(); + v2Type = addTypeArgs(1, false, builderType, v2Type, tArgs); + constructArrayList = new AllocationExpression(); + constructArrayList.type = v2Type; + Assignment initV2 = new Assignment(thisDotField, constructArrayList, 0); + Block b = new Block(0); + b.statements = new Statement[] {initV1, initV2}; + thenPart = b; + } else { + thenPart = initV1; + } + + return new IfStatement(cond, thenPart, 0, 0); + } +} diff --git a/src/core/lombok/experimental/Builder.java b/src/core/lombok/experimental/Builder.java index 1300e7d3..7d89109f 100644 --- a/src/core/lombok/experimental/Builder.java +++ b/src/core/lombok/experimental/Builder.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 The Project Lombok Authors. + * Copyright (C) 2013-2014 The Project Lombok Authors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -103,9 +103,12 @@ import java.lang.annotation.Target; * } * } * </pre> + * + * @deprecated {@link lombok.Builder} has been promoted to the main package, so use that one instead. */ @Target({TYPE, METHOD, CONSTRUCTOR}) @Retention(SOURCE) +@Deprecated public @interface Builder { /** Name of the static method that creates a new builder instance. Default: {@code builder}. */ String builderMethodName() default "builder"; diff --git a/src/core/lombok/javac/JavacNode.java b/src/core/lombok/javac/JavacNode.java index 6eef36eb..727692ac 100644 --- a/src/core/lombok/javac/JavacNode.java +++ b/src/core/lombok/javac/JavacNode.java @@ -66,40 +66,40 @@ public class JavacNode extends lombok.core.LombokNode<JavacAST, JavacNode, JCTre public void traverse(JavacASTVisitor visitor) { switch (this.getKind()) { case COMPILATION_UNIT: - visitor.visitCompilationUnit(this, (JCCompilationUnit)get()); + visitor.visitCompilationUnit(this, (JCCompilationUnit) get()); ast.traverseChildren(visitor, this); - visitor.endVisitCompilationUnit(this, (JCCompilationUnit)get()); + visitor.endVisitCompilationUnit(this, (JCCompilationUnit) get()); break; case TYPE: - visitor.visitType(this, (JCClassDecl)get()); + visitor.visitType(this, (JCClassDecl) get()); ast.traverseChildren(visitor, this); - visitor.endVisitType(this, (JCClassDecl)get()); + visitor.endVisitType(this, (JCClassDecl) get()); break; case FIELD: - visitor.visitField(this, (JCVariableDecl)get()); + visitor.visitField(this, (JCVariableDecl) get()); ast.traverseChildren(visitor, this); - visitor.endVisitField(this, (JCVariableDecl)get()); + visitor.endVisitField(this, (JCVariableDecl) get()); break; case METHOD: - visitor.visitMethod(this, (JCMethodDecl)get()); + visitor.visitMethod(this, (JCMethodDecl) get()); ast.traverseChildren(visitor, this); - visitor.endVisitMethod(this, (JCMethodDecl)get()); + visitor.endVisitMethod(this, (JCMethodDecl) get()); break; case INITIALIZER: - visitor.visitInitializer(this, (JCBlock)get()); + visitor.visitInitializer(this, (JCBlock) get()); ast.traverseChildren(visitor, this); - visitor.endVisitInitializer(this, (JCBlock)get()); + visitor.endVisitInitializer(this, (JCBlock) get()); break; case ARGUMENT: JCMethodDecl parentMethod = (JCMethodDecl) up().get(); - visitor.visitMethodArgument(this, (JCVariableDecl)get(), parentMethod); + visitor.visitMethodArgument(this, (JCVariableDecl) get(), parentMethod); ast.traverseChildren(visitor, this); - visitor.endVisitMethodArgument(this, (JCVariableDecl)get(), parentMethod); + visitor.endVisitMethodArgument(this, (JCVariableDecl) get(), parentMethod); break; case LOCAL: - visitor.visitLocal(this, (JCVariableDecl)get()); + visitor.visitLocal(this, (JCVariableDecl) get()); ast.traverseChildren(visitor, this); - visitor.endVisitLocal(this, (JCVariableDecl)get()); + visitor.endVisitLocal(this, (JCVariableDecl) get()); break; case STATEMENT: visitor.visitStatement(this, get()); @@ -109,21 +109,21 @@ public class JavacNode extends lombok.core.LombokNode<JavacAST, JavacNode, JCTre case ANNOTATION: switch (up().getKind()) { case TYPE: - visitor.visitAnnotationOnType((JCClassDecl)up().get(), this, (JCAnnotation)get()); + visitor.visitAnnotationOnType((JCClassDecl) up().get(), this, (JCAnnotation) get()); break; case FIELD: - visitor.visitAnnotationOnField((JCVariableDecl)up().get(), this, (JCAnnotation)get()); + visitor.visitAnnotationOnField((JCVariableDecl) up().get(), this, (JCAnnotation) get()); break; case METHOD: - visitor.visitAnnotationOnMethod((JCMethodDecl)up().get(), this, (JCAnnotation)get()); + visitor.visitAnnotationOnMethod((JCMethodDecl) up().get(), this, (JCAnnotation) get()); break; case ARGUMENT: - JCVariableDecl argument = (JCVariableDecl)up().get(); - JCMethodDecl method = (JCMethodDecl)up().up().get(); - visitor.visitAnnotationOnMethodArgument(argument, method, this, (JCAnnotation)get()); + JCVariableDecl argument = (JCVariableDecl) up().get(); + JCMethodDecl method = (JCMethodDecl) up().up().get(); + visitor.visitAnnotationOnMethodArgument(argument, method, this, (JCAnnotation) get()); break; case LOCAL: - visitor.visitAnnotationOnLocal((JCVariableDecl)up().get(), this, (JCAnnotation)get()); + visitor.visitAnnotationOnLocal((JCVariableDecl) up().get(), this, (JCAnnotation) get()); break; default: throw new AssertionError("Annotion not expected as child of a " + up().getKind()); @@ -138,9 +138,9 @@ public class JavacNode extends lombok.core.LombokNode<JavacAST, JavacNode, JCTre @Override public String getName() { final Name n; - if (node instanceof JCClassDecl) n = ((JCClassDecl)node).name; - else if (node instanceof JCMethodDecl) n = ((JCMethodDecl)node).name; - else if (node instanceof JCVariableDecl) n = ((JCVariableDecl)node).name; + if (node instanceof JCClassDecl) n = ((JCClassDecl) node).name; + else if (node instanceof JCMethodDecl) n = ((JCMethodDecl) node).name; + else if (node instanceof JCVariableDecl) n = ((JCVariableDecl) node).name; else n = null; return n == null ? null : n.toString(); diff --git a/src/core/lombok/javac/apt/EmptyLombokFileObject.java b/src/core/lombok/javac/apt/EmptyLombokFileObject.java index 7298e920..5a3a7def 100644 --- a/src/core/lombok/javac/apt/EmptyLombokFileObject.java +++ b/src/core/lombok/javac/apt/EmptyLombokFileObject.java @@ -19,7 +19,6 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ - package lombok.javac.apt; import java.io.ByteArrayInputStream; diff --git a/src/core/lombok/javac/handlers/HandleBuilder.java b/src/core/lombok/javac/handlers/HandleBuilder.java index 1885b8b4..4f7f79d9 100644 --- a/src/core/lombok/javac/handlers/HandleBuilder.java +++ b/src/core/lombok/javac/handlers/HandleBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2014 The Project Lombok Authors. + * Copyright (C) 2013-2015 The Project Lombok Authors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,8 +21,8 @@ */ package lombok.javac.handlers; +import java.lang.annotation.Annotation; import java.util.ArrayList; -import java.util.Collections; import org.mangosdk.spi.ProviderFor; @@ -34,6 +34,8 @@ import com.sun.tools.javac.tree.JCTree.JCClassDecl; import com.sun.tools.javac.tree.JCTree.JCExpression; import com.sun.tools.javac.tree.JCTree.JCFieldAccess; import com.sun.tools.javac.tree.JCTree.JCIdent; +import com.sun.tools.javac.tree.JCTree.JCIf; +import com.sun.tools.javac.tree.JCTree.JCLiteral; import com.sun.tools.javac.tree.JCTree.JCMethodDecl; import com.sun.tools.javac.tree.JCTree.JCModifiers; import com.sun.tools.javac.tree.JCTree.JCPrimitiveTypeTree; @@ -46,16 +48,21 @@ import com.sun.tools.javac.util.ListBuffer; import com.sun.tools.javac.util.Name; import lombok.AccessLevel; +import lombok.Builder; import lombok.ConfigurationKeys; +import lombok.Singular; import lombok.core.AST.Kind; import lombok.core.AnnotationValues; import lombok.core.HandlerPriority; -import lombok.experimental.Builder; +import lombok.core.handlers.HandlerUtil; import lombok.experimental.NonFinal; +import lombok.javac.Javac; import lombok.javac.JavacAnnotationHandler; import lombok.javac.JavacNode; import lombok.javac.JavacTreeMaker; import lombok.javac.handlers.HandleConstructor.SkipIfConstructorExists; +import lombok.javac.handlers.JavacSingularsRecipes.JavacSingularizer; +import lombok.javac.handlers.JavacSingularsRecipes.SingularData; import static lombok.core.handlers.HandlerUtil.*; import static lombok.javac.handlers.JavacHandlerUtil.*; import static lombok.javac.Javac.*; @@ -64,10 +71,27 @@ import static lombok.javac.JavacTreeMaker.TypeTag.*; @ProviderFor(JavacAnnotationHandler.class) @HandlerPriority(-1024) //-2^10; to ensure we've picked up @FieldDefault's changes (-2048) but @Value hasn't removed itself yet (-512), so that we can error on presence of it on the builder classes. public class HandleBuilder extends JavacAnnotationHandler<Builder> { - @Override public void handle(AnnotationValues<Builder> annotation, JCAnnotation ast, JavacNode annotationNode) { - handleExperimentalFlagUsage(annotationNode, ConfigurationKeys.BUILDER_FLAG_USAGE, "@Builder"); + private static final boolean toBoolean(Object expr, boolean defaultValue) { + if (expr == null) return defaultValue; + if (expr instanceof JCLiteral) return ((Integer) ((JCLiteral) expr).value) != 0; + return ((Boolean) expr).booleanValue(); + } + + private static class BuilderFieldData { + JCExpression type; + Name name; + SingularData singularData; + java.util.List<JavacNode> createdFields = new ArrayList<JavacNode>(); + } + + @Override public void handle(AnnotationValues<Builder> annotation, JCAnnotation ast, JavacNode annotationNode) { Builder builderInstance = annotation.getInstance(); + + // These exist just to support the 'old' lombok.experimental.Builder, which had these properties. lombok.Builder no longer has them. + boolean fluent = toBoolean(annotation.getActualExpression("fluent"), true); + boolean chain = toBoolean(annotation.getActualExpression("chain"), true); + String builderMethodName = builderInstance.builderMethodName(); String buildMethodName = builderInstance.buildMethodName(); String builderClassName = builderInstance.builderClassName(); @@ -82,20 +106,21 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { if (!checkName("builderClassName", builderClassName, annotationNode)) return; } - deleteAnnotationIfNeccessary(annotationNode, Builder.class); - deleteImportFromCompilationUnit(annotationNode, "lombok.experimental.Builder"); + @SuppressWarnings("deprecation") + Class<? extends Annotation> oldExperimentalBuilder = lombok.experimental.Builder.class; + deleteAnnotationIfNeccessary(annotationNode, Builder.class, oldExperimentalBuilder); JavacNode parent = annotationNode.up(); - java.util.List<JCExpression> typesOfParameters = new ArrayList<JCExpression>(); - java.util.List<Name> namesOfParameters = new ArrayList<Name>(); + java.util.List<BuilderFieldData> builderFields = new ArrayList<BuilderFieldData>(); JCExpression returnType; List<JCTypeParameter> typeParams = List.nil(); List<JCExpression> thrownExceptions = List.nil(); Name nameOfStaticBuilderMethod; JavacNode tdParent; - JCMethodDecl fillParametersFrom = parent.get() instanceof JCMethodDecl ? ((JCMethodDecl) parent.get()) : null; + JavacNode fillParametersFrom = parent.get() instanceof JCMethodDecl ? parent : null; + boolean addCleaning = false; if (parent.get() instanceof JCClassDecl) { tdParent = parent; @@ -109,12 +134,14 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { // non-final fields final, but @Value's handler hasn't done this yet, so we have to do this math ourselves. // Value will only skip making a field final if it has an explicit @NonFinal annotation, so we check for that. if (fd.init != null && valuePresent && !hasAnnotation(NonFinal.class, fieldNode)) continue; - namesOfParameters.add(removePrefixFromField(fieldNode)); - typesOfParameters.add(fd.vartype); + BuilderFieldData bfd = new BuilderFieldData(); + bfd.name = removePrefixFromField(fieldNode); + bfd.type = fd.vartype; + bfd.singularData = getSingularData(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); returnType = namePlusTypeParamsToTypeReference(tdParent.getTreeMaker(), td.name, td.typarams); @@ -123,28 +150,31 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { nameOfStaticBuilderMethod = null; if (builderClassName.isEmpty()) builderClassName = td.name.toString() + "Builder"; } else if (fillParametersFrom != null && fillParametersFrom.getName().toString().equals("<init>")) { - if (!fillParametersFrom.typarams.isEmpty()) { + JCMethodDecl jmd = (JCMethodDecl) fillParametersFrom.get(); + if (!jmd.typarams.isEmpty()) { annotationNode.addError("@Builder is not supported on constructors with constructor type parameters."); return; } + tdParent = parent.up(); JCClassDecl td = (JCClassDecl) tdParent.get(); returnType = namePlusTypeParamsToTypeReference(tdParent.getTreeMaker(), td.name, td.typarams); typeParams = td.typarams; - thrownExceptions = fillParametersFrom.thrown; + thrownExceptions = jmd.thrown; nameOfStaticBuilderMethod = null; if (builderClassName.isEmpty()) builderClassName = td.name.toString() + "Builder"; } else if (fillParametersFrom != null) { tdParent = parent.up(); JCClassDecl td = (JCClassDecl) tdParent.get(); - if ((fillParametersFrom.mods.flags & Flags.STATIC) == 0) { + JCMethodDecl jmd = (JCMethodDecl) fillParametersFrom.get(); + if ((jmd.mods.flags & Flags.STATIC) == 0) { annotationNode.addError("@Builder is only supported on types, constructors, and static methods."); return; } - returnType = fillParametersFrom.restype; - typeParams = fillParametersFrom.typarams; - thrownExceptions = fillParametersFrom.thrown; - nameOfStaticBuilderMethod = fillParametersFrom.name; + returnType = jmd.restype; + typeParams = jmd.typarams; + thrownExceptions = jmd.thrown; + nameOfStaticBuilderMethod = jmd.name; if (builderClassName.isEmpty()) { if (returnType instanceof JCTypeApply) { returnType = ((JCTypeApply) returnType).clazz; @@ -179,9 +209,14 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { } if (fillParametersFrom != null) { - for (JCVariableDecl param : fillParametersFrom.params) { - namesOfParameters.add(param.name); - typesOfParameters.add(param.vartype); + for (JavacNode param : fillParametersFrom.down()) { + if (param.getKind() != Kind.ARGUMENT) continue; + BuilderFieldData bfd = new BuilderFieldData(); + JCVariableDecl raw = (JCVariableDecl) param.get(); + bfd.name = raw.name; + bfd.type = raw.vartype; + bfd.singularData = getSingularData(param); + builderFields.add(bfd); } } @@ -190,12 +225,34 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { builderType = makeBuilderClass(tdParent, builderClassName, typeParams, ast); } else { sanityCheckForMethodGeneratingAnnotationsOnBuilderClass(builderType, annotationNode); + /* generate errors for @Singular BFDs that have one already defined node. */ { + for (BuilderFieldData bfd : builderFields) { + SingularData sd = bfd.singularData; + if (sd == null) continue; + JavacSingularizer singularizer = sd.getSingularizer(); + if (singularizer == null) continue; + if (singularizer.checkForAlreadyExistingNodesAndGenerateError(builderType, sd)) { + bfd.singularData = null; + } + } + } + + } + + for (BuilderFieldData bfd : builderFields) { + if (bfd.singularData != null && bfd.singularData.getSingularizer() != null) { + if (bfd.singularData.getSingularizer().requiresCleaning()) { + addCleaning = true; + break; + } + } } - java.util.List<JavacNode> fieldNodes = addFieldsToBuilder(builderType, namesOfParameters, typesOfParameters, ast); - java.util.List<JCMethodDecl> newMethods = new ArrayList<JCMethodDecl>(); - for (JavacNode fieldNode : fieldNodes) { - JCMethodDecl newMethod = makeSetterMethodForBuilder(builderType, fieldNode, annotationNode, builderInstance.fluent(), builderInstance.chain()); - if (newMethod != null) newMethods.add(newMethod); + + generateBuilderFields(builderType, builderFields, ast); + if (addCleaning) { + JavacTreeMaker maker = builderType.getTreeMaker(); + JCVariableDecl uncleanField = maker.VarDef(maker.Modifiers(Flags.PRIVATE), builderType.toName("$lombokUnclean"), maker.TypeIdent(CTC_BOOLEAN), null); + injectField(builderType, uncleanField); } if (constructorExists(builderType) == MemberExistsResult.NOT_EXISTS) { @@ -203,38 +260,93 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { if (cd != null) injectMethod(builderType, cd); } - for (JCMethodDecl newMethod : newMethods) injectMethod(builderType, newMethod); + for (BuilderFieldData bfd : builderFields) { + makeSetterMethodsForBuilder(builderType, bfd, annotationNode, fluent, chain); + } if (methodExists(buildMethodName, builderType, -1) == MemberExistsResult.NOT_EXISTS) { - JCMethodDecl md = generateBuildMethod(buildMethodName, nameOfStaticBuilderMethod, returnType, namesOfParameters, builderType, thrownExceptions); + JCMethodDecl md = generateBuildMethod(buildMethodName, nameOfStaticBuilderMethod, returnType, builderFields, builderType, thrownExceptions, ast, addCleaning); if (md != null) injectMethod(builderType, md); } if (methodExists("toString", builderType, 0) == MemberExistsResult.NOT_EXISTS) { + java.util.List<JavacNode> fieldNodes = new ArrayList<JavacNode>(); + for (BuilderFieldData bfd : builderFields) { + fieldNodes.addAll(bfd.createdFields); + } JCMethodDecl md = HandleToString.createToString(builderType, fieldNodes, true, false, FieldAccess.ALWAYS_FIELD, ast); if (md != null) injectMethod(builderType, md); } + if (addCleaning) injectMethod(builderType, generateCleanMethod(builderFields, builderType, ast)); + if (methodExists(builderMethodName, tdParent, -1) == MemberExistsResult.NOT_EXISTS) { JCMethodDecl md = generateBuilderMethod(builderMethodName, builderClassName, tdParent, typeParams); + recursiveSetGeneratedBy(md, ast, annotationNode.getContext()); if (md != null) injectMethod(tdParent, md); } + + recursiveSetGeneratedBy(builderType.get(), ast, annotationNode.getContext()); } - public JCMethodDecl generateBuildMethod(String name, Name staticName, JCExpression returnType, java.util.List<Name> fieldNames, JavacNode type, List<JCExpression> thrownExceptions) { + private JCMethodDecl generateCleanMethod(java.util.List<BuilderFieldData> builderFields, JavacNode type, JCTree source) { + JavacTreeMaker maker = type.getTreeMaker(); + ListBuffer<JCStatement> statements = new ListBuffer<JCStatement>(); + + for (BuilderFieldData bfd : builderFields) { + if (bfd.singularData != null && bfd.singularData.getSingularizer() != null) { + bfd.singularData.getSingularizer().appendCleaningCode(bfd.singularData, type, source, statements); + } + } + + statements.append(maker.Exec(maker.Assign(maker.Select(maker.Ident(type.toName("this")), type.toName("$lombokUnclean")), maker.Literal(CTC_BOOLEAN, false)))); + JCBlock body = maker.Block(0, statements.toList()); + return maker.MethodDef(maker.Modifiers(Flags.PUBLIC), type.toName("$lombokClean"), maker.Type(Javac.createVoidType(maker, CTC_VOID)), List.<JCTypeParameter>nil(), List.<JCVariableDecl>nil(), List.<JCExpression>nil(), body, null); + /* + * if (shouldReturnThis) { + methodType = cloneSelfType(field); + } + + if (methodType == null) { + //WARNING: Do not use field.getSymbolTable().voidType - that field has gone through non-backwards compatible API changes within javac1.6. + methodType = treeMaker.Type(Javac.createVoidType(treeMaker, CTC_VOID)); + shouldReturnThis = false; + } + + */ + } + + private JCMethodDecl generateBuildMethod(String name, Name staticName, JCExpression returnType, java.util.List<BuilderFieldData> builderFields, JavacNode type, List<JCExpression> thrownExceptions, JCTree source, boolean addCleaning) { JavacTreeMaker maker = type.getTreeMaker(); JCExpression call; - JCStatement statement; + ListBuffer<JCStatement> statements = new ListBuffer<JCStatement>(); + + if (addCleaning) { + JCExpression notClean = maker.Unary(CTC_NOT, maker.Select(maker.Ident(type.toName("this")), type.toName("$lombokUnclean"))); + JCStatement invokeClean = maker.Exec(maker.Apply(List.<JCExpression>nil(), maker.Ident(type.toName("$lombokClean")), List.<JCExpression>nil())); + JCIf ifUnclean = maker.If(notClean, invokeClean, null); + statements.append(ifUnclean); + } + + for (BuilderFieldData bfd : builderFields) { + if (bfd.singularData != null && bfd.singularData.getSingularizer() != null) { + bfd.singularData.getSingularizer().appendBuildCode(bfd.singularData, type, source, statements, bfd.name); + } + } ListBuffer<JCExpression> args = new ListBuffer<JCExpression>(); - for (Name n : fieldNames) { - args.append(maker.Ident(n)); + for (BuilderFieldData bfd : builderFields) { + args.append(maker.Ident(bfd.name)); + } + + if (addCleaning) { + statements.append(maker.Exec(maker.Assign(maker.Select(maker.Ident(type.toName("this")), type.toName("$lombokUnclean")), maker.Literal(CTC_BOOLEAN, true)))); } if (staticName == null) { call = maker.NewClass(null, List.<JCExpression>nil(), returnType, args.toList(), null); - statement = maker.Return(call); + statements.append(maker.Return(call)); } else { ListBuffer<JCExpression> typeParams = new ListBuffer<JCExpression>(); for (JCTypeParameter tp : ((JCClassDecl) type.get()).typarams) { @@ -244,13 +356,13 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { JCExpression fn = maker.Select(maker.Ident(((JCClassDecl) type.up().get()).name), staticName); call = maker.Apply(typeParams.toList(), fn, args.toList()); if (returnType instanceof JCPrimitiveTypeTree && CTC_VOID.equals(typeTag(returnType))) { - statement = maker.Exec(call); + statements.append(maker.Exec(call)); } else { - statement = maker.Return(call); + statements.append(maker.Return(call)); } } - JCBlock body = maker.Block(0, List.<JCStatement>of(statement)); + JCBlock body = maker.Block(0, statements.toList()); return maker.MethodDef(maker.Modifiers(Flags.PUBLIC), type.toName(name), returnType, List.<JCTypeParameter>nil(), List.<JCVariableDecl>nil(), thrownExceptions, body, null); } @@ -270,50 +382,56 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { return maker.MethodDef(maker.Modifiers(Flags.STATIC | Flags.PUBLIC), type.toName(builderMethodName), namePlusTypeParamsToTypeReference(maker, type.toName(builderClassName), typeParams), copyTypeParams(maker, typeParams), List.<JCVariableDecl>nil(), List.<JCExpression>nil(), body, null); } - public java.util.List<JavacNode> addFieldsToBuilder(JavacNode builderType, java.util.List<Name> namesOfParameters, java.util.List<JCExpression> typesOfParameters, JCTree source) { - int len = namesOfParameters.size(); + public void generateBuilderFields(JavacNode builderType, java.util.List<BuilderFieldData> builderFields, JCTree source) { + int len = builderFields.size(); java.util.List<JavacNode> existing = new ArrayList<JavacNode>(); for (JavacNode child : builderType.down()) { if (child.getKind() == Kind.FIELD) existing.add(child); } - java.util.List<JavacNode>out = new ArrayList<JavacNode>(); - top: for (int i = len - 1; i >= 0; i--) { - Name name = namesOfParameters.get(i); - for (JavacNode exists : existing) { - Name n = ((JCVariableDecl) exists.get()).name; - if (n.equals(name)) { - out.add(exists); - continue top; + BuilderFieldData bfd = builderFields.get(i); + if (bfd.singularData != null && bfd.singularData.getSingularizer() != null) { + bfd.createdFields.addAll(bfd.singularData.getSingularizer().generateFields(bfd.singularData, builderType, source)); + } else { + for (JavacNode exists : existing) { + Name n = ((JCVariableDecl) exists.get()).name; + if (n.equals(bfd.name)) { + bfd.createdFields.add(exists); + continue top; + } } + JavacTreeMaker maker = builderType.getTreeMaker(); + JCModifiers mods = maker.Modifiers(Flags.PRIVATE); + JCVariableDecl newField = maker.VarDef(mods, bfd.name, cloneType(maker, bfd.type, source, builderType.getContext()), null); + bfd.createdFields.add(injectField(builderType, newField)); } - JavacTreeMaker maker = builderType.getTreeMaker(); - JCModifiers mods = maker.Modifiers(Flags.PRIVATE); - JCVariableDecl newField = maker.VarDef(mods, name, cloneType(maker, typesOfParameters.get(i), source, builderType.getContext()), null); - out.add(injectField(builderType, newField)); } - - Collections.reverse(out); - return out; } + public void makeSetterMethodsForBuilder(JavacNode builderType, BuilderFieldData fieldNode, JavacNode source, boolean fluent, boolean chain) { + if (fieldNode.singularData == null || fieldNode.singularData.getSingularizer() == null) { + makeSimpleSetterMethodForBuilder(builderType, fieldNode.createdFields.get(0), source, fluent, chain); + } else { + fieldNode.singularData.getSingularizer().generateMethods(fieldNode.singularData, builderType, source.get(), fluent, chain); + } + } - public JCMethodDecl makeSetterMethodForBuilder(JavacNode builderType, JavacNode fieldNode, JavacNode source, boolean fluent, boolean chain) { + private void makeSimpleSetterMethodForBuilder(JavacNode builderType, JavacNode fieldNode, JavacNode source, boolean fluent, boolean chain) { Name fieldName = ((JCVariableDecl) fieldNode.get()).name; for (JavacNode child : builderType.down()) { if (child.getKind() != Kind.METHOD) continue; Name existingName = ((JCMethodDecl) child.get()).name; - if (existingName.equals(fieldName)) return null; + if (existingName.equals(fieldName)) return; } - boolean isBoolean = isBoolean(fieldNode); - String setterName = fluent ? fieldNode.getName() : toSetterName(builderType.getAst(), null, fieldNode.getName(), isBoolean); + String setterName = fluent ? fieldNode.getName() : HandlerUtil.buildAccessorName("set", fieldNode.getName()); - JavacTreeMaker maker = builderType.getTreeMaker(); - return HandleSetter.createSetter(Flags.PUBLIC, fieldNode, maker, setterName, chain, source, List.<JCAnnotation>nil(), List.<JCAnnotation>nil()); + JavacTreeMaker maker = fieldNode.getTreeMaker(); + JCMethodDecl newMethod = HandleSetter.createSetter(Flags.PUBLIC, fieldNode, maker, setterName, chain, source, List.<JCAnnotation>nil(), List.<JCAnnotation>nil()); + injectMethod(builderType, newMethod); } public JavacNode findInnerClass(JavacNode parent, String name) { @@ -331,4 +449,59 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { JCClassDecl builder = maker.ClassDef(mods, tdParent.toName(builderClassName), copyTypeParams(maker, typeParams), null, List.<JCExpression>nil(), List.<JCTree>nil()); return injectType(tdParent, builder); } + + /** + * Returns the explicitly requested singular annotation on this node (field + * or parameter), or null if there's no {@code @Singular} annotation on it. + * + * @param node The node (field or method param) to inspect for its name and potential {@code @Singular} annotation. + */ + private SingularData getSingularData(JavacNode node) { + for (JavacNode child : node.down()) { + if (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."); + 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); + } + } + + return null; + } } diff --git a/src/core/lombok/javac/handlers/HandleConstructor.java b/src/core/lombok/javac/handlers/HandleConstructor.java index 6043d1cb..c5b309c2 100644 --- a/src/core/lombok/javac/handlers/HandleConstructor.java +++ b/src/core/lombok/javac/handlers/HandleConstructor.java @@ -25,13 +25,13 @@ import static lombok.core.handlers.HandlerUtil.*; import static lombok.javac.handlers.JavacHandlerUtil.*; import lombok.AccessLevel; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.ConfigurationKeys; import lombok.NoArgsConstructor; import lombok.RequiredArgsConstructor; import lombok.core.AnnotationValues; import lombok.core.AST.Kind; import lombok.delombok.LombokOptionsFactory; -import lombok.experimental.Builder; import lombok.javac.JavacAnnotationHandler; import lombok.javac.JavacNode; import lombok.javac.JavacTreeMaker; diff --git a/src/core/lombok/javac/handlers/HandlePrintAST.java b/src/core/lombok/javac/handlers/HandlePrintAST.java index 9a52b9d9..0826d1d1 100644 --- a/src/core/lombok/javac/handlers/HandlePrintAST.java +++ b/src/core/lombok/javac/handlers/HandlePrintAST.java @@ -59,7 +59,7 @@ public class HandlePrintAST extends JavacAnnotationHandler<PrintAST> { try { stream.close(); } catch (Exception e) { - Lombok.sneakyThrow(e); + throw Lombok.sneakyThrow(e); } } } diff --git a/src/core/lombok/javac/handlers/JavacHandlerUtil.java b/src/core/lombok/javac/handlers/JavacHandlerUtil.java index 6413e8ef..8a8c5acd 100644 --- a/src/core/lombok/javac/handlers/JavacHandlerUtil.java +++ b/src/core/lombok/javac/handlers/JavacHandlerUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2014 The Project Lombok Authors. + * Copyright (C) 2009-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 @@ -41,6 +41,7 @@ import lombok.Data; import lombok.Getter; import lombok.core.AST.Kind; import lombok.core.AnnotationValues; +import lombok.core.LombokImmutableList; import lombok.core.AnnotationValues.AnnotationValue; import lombok.core.TypeResolver; import lombok.core.configuration.NullCheckExceptionType; @@ -242,51 +243,45 @@ public class JavacHandlerUtil { Map<String, AnnotationValue> values = new HashMap<String, AnnotationValue>(); JCAnnotation anno = (JCAnnotation) node.get(); List<JCExpression> arguments = anno.getArguments(); - for (Method m : type.getDeclaredMethods()) { - if (!Modifier.isPublic(m.getModifiers())) continue; - String name = m.getName(); + + for (JCExpression arg : arguments) { + String mName; + JCExpression rhs; java.util.List<String> raws = new ArrayList<String>(); java.util.List<Object> guesses = new ArrayList<Object>(); java.util.List<Object> expressions = new ArrayList<Object>(); final java.util.List<DiagnosticPosition> positions = new ArrayList<DiagnosticPosition>(); - boolean isExplicit = false; - for (JCExpression arg : arguments) { - String mName; - JCExpression rhs; - - if (arg instanceof JCAssign) { - JCAssign assign = (JCAssign) arg; - mName = assign.lhs.toString(); - rhs = assign.rhs; - } else { - rhs = arg; - mName = "value"; - } - - if (!mName.equals(name)) continue; - isExplicit = true; - if (rhs instanceof JCNewArray) { - List<JCExpression> elems = ((JCNewArray)rhs).elems; - for (JCExpression inner : elems) { - raws.add(inner.toString()); - expressions.add(inner); - guesses.add(calculateGuess(inner)); - positions.add(inner.pos()); - } - } else { - raws.add(rhs.toString()); - expressions.add(rhs); - guesses.add(calculateGuess(rhs)); - positions.add(rhs.pos()); + if (arg instanceof JCAssign) { + JCAssign assign = (JCAssign) arg; + mName = assign.lhs.toString(); + rhs = assign.rhs; + } else { + rhs = arg; + mName = "value"; + } + + if (rhs instanceof JCNewArray) { + List<JCExpression> elems = ((JCNewArray)rhs).elems; + for (JCExpression inner : elems) { + raws.add(inner.toString()); + expressions.add(inner); + guesses.add(calculateGuess(inner)); + positions.add(inner.pos()); } + } else { + raws.add(rhs.toString()); + expressions.add(rhs); + guesses.add(calculateGuess(rhs)); + positions.add(rhs.pos()); } - values.put(name, new AnnotationValue(node, raws, expressions, guesses, isExplicit) { + values.put(mName, new AnnotationValue(node, raws, expressions, guesses, true) { @Override public void setError(String message, int valueIdx) { if (valueIdx < 0) node.addError(message); else node.addError(message, positions.get(valueIdx)); } + @Override public void setWarning(String message, int valueIdx) { if (valueIdx < 0) node.addWarning(message); else node.addWarning(message, positions.get(valueIdx)); @@ -294,6 +289,21 @@ public class JavacHandlerUtil { }); } + for (Method m : type.getDeclaredMethods()) { + if (!Modifier.isPublic(m.getModifiers())) continue; + String name = m.getName(); + if (!values.containsKey(name)) { + values.put(name, new AnnotationValue(node, new ArrayList<String>(), new ArrayList<Object>(), new ArrayList<Object>(), false) { + @Override public void setError(String message, int valueIdx) { + node.addError(message); + } + @Override public void setWarning(String message, int valueIdx) { + node.addWarning(message); + } + }); + } + } + return new AnnotationValues<A>(type, values, node); } @@ -445,9 +455,9 @@ public class JavacHandlerUtil { return HandlerUtil.shouldReturnThis0(accessors, field.getAst()); } - public static JCExpression cloneSelfType(JavacNode field) { - JavacNode typeNode = field; - JavacTreeMaker maker = field.getTreeMaker(); + public static JCExpression cloneSelfType(JavacNode childOfType) { + JavacNode typeNode = childOfType; + JavacTreeMaker maker = childOfType.getTreeMaker(); while (typeNode != null && typeNode.getKind() != Kind.TYPE) typeNode = typeNode.up(); if (typeNode != null && typeNode.get() instanceof JCClassDecl) { JCClassDecl type = (JCClassDecl) typeNode.get(); @@ -985,6 +995,17 @@ public class JavacHandlerUtil { return chainDots(node, -1, null, null, elems); } + public static JCExpression chainDots(JavacNode node, LombokImmutableList<String> elems) { + assert elems != null; + + JavacTreeMaker maker = node.getTreeMaker(); + JCExpression e = null; + for (String elem : elems) { + if (e == null) e = maker.Ident(node.toName(elem)); + else e = maker.Select(e, node.toName(elem)); + } + return e; + } /** * In javac, dotted access of any kind, from {@code java.lang.String} to {@code var.methodName} * is represented by a fold-left of {@code Select} nodes with the leftmost string represented by @@ -1013,7 +1034,6 @@ public class JavacHandlerUtil { return e; } - /** * In javac, dotted access of any kind, from {@code java.lang.String} to {@code var.methodName} diff --git a/src/core/lombok/javac/handlers/JavacSingularsRecipes.java b/src/core/lombok/javac/handlers/JavacSingularsRecipes.java new file mode 100644 index 00000000..53e01ebb --- /dev/null +++ b/src/core/lombok/javac/handlers/JavacSingularsRecipes.java @@ -0,0 +1,300 @@ +/* + * Copyright (C) 2015 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.javac.handlers; + +import static lombok.javac.Javac.*; +import static lombok.javac.handlers.JavacHandlerUtil.*; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import lombok.core.LombokImmutableList; +import lombok.core.SpiLoadUtil; +import lombok.core.TypeLibrary; +import lombok.javac.JavacNode; +import lombok.javac.JavacTreeMaker; + +import com.sun.source.tree.Tree.Kind; +import com.sun.tools.javac.code.BoundKind; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.JCExpression; +import com.sun.tools.javac.tree.JCTree.JCMethodDecl; +import com.sun.tools.javac.tree.JCTree.JCStatement; +import com.sun.tools.javac.tree.JCTree.JCVariableDecl; +import com.sun.tools.javac.tree.JCTree.JCWildcard; +import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.util.ListBuffer; +import com.sun.tools.javac.util.Name; + +public class JavacSingularsRecipes { + private static final JavacSingularsRecipes INSTANCE = new JavacSingularsRecipes(); + private final Map<String, JavacSingularizer> singularizers = new HashMap<String, JavacSingularizer>(); + private final TypeLibrary singularizableTypes = new TypeLibrary(); + + private JavacSingularsRecipes() { + try { + loadAll(singularizableTypes, singularizers); + singularizableTypes.lock(); + } catch (IOException e) { + System.err.println("Lombok's @Singularizable feature is broken due to misconfigured SPI files: " + e); + } + } + + private static void loadAll(TypeLibrary library, Map<String, JavacSingularizer> map) throws IOException { + for (JavacSingularizer handler : SpiLoadUtil.findServices(JavacSingularizer.class, JavacSingularizer.class.getClassLoader())) { + for (String type : handler.getSupportedTypes()) { + JavacSingularizer existingSingularizer = map.get(type); + if (existingSingularizer != null) { + JavacSingularizer toKeep = existingSingularizer.getClass().getName().compareTo(handler.getClass().getName()) > 0 ? handler : existingSingularizer; + System.err.println("Multiple singularizers found for type " + type + "; the alphabetically first class is used: " + toKeep.getClass().getName()); + map.put(type, toKeep); + } else { + map.put(type, handler); + library.addType(type); + } + } + } + } + + public static JavacSingularsRecipes get() { + return INSTANCE; + } + + public String toQualified(String typeReference) { + return singularizableTypes.toQualified(typeReference); + } + + public JavacSingularizer getSingularizer(String fqn) { + return singularizers.get(fqn); + } + + public static final class SingularData { + private final JavacNode annotation; + private final Name singularName; + private final Name pluralName; + private final List<JCExpression> typeArgs; + private final String targetFqn; + private final JavacSingularizer singularizer; + + public SingularData(JavacNode annotation, Name singularName, Name pluralName, List<JCExpression> typeArgs, String targetFqn, JavacSingularizer singularizer) { + this.annotation = annotation; + this.singularName = singularName; + this.pluralName = pluralName; + this.typeArgs = typeArgs; + this.targetFqn = targetFqn; + this.singularizer = singularizer; + } + + public JavacNode getAnnotation() { + return annotation; + } + + public Name getSingularName() { + return singularName; + } + + public Name getPluralName() { + return pluralName; + } + + public List<JCExpression> getTypeArgs() { + return typeArgs; + } + + public String getTargetFqn() { + return targetFqn; + } + + public JavacSingularizer getSingularizer() { + return singularizer; + } + + public String getTargetSimpleType() { + int idx = targetFqn.lastIndexOf("."); + return idx == -1 ? targetFqn : targetFqn.substring(idx + 1); + } + } + + public static abstract class JavacSingularizer { + public abstract LombokImmutableList<String> getSupportedTypes(); + + /** Checks if any of the to-be-generated nodes (fields, methods) already exist. If so, errors on these (singulars don't support manually writing some of it, and returns true). */ + public boolean checkForAlreadyExistingNodesAndGenerateError(JavacNode builderType, SingularData data) { + for (JavacNode child : builderType.down()) { + switch (child.getKind()) { + case FIELD: { + JCVariableDecl field = (JCVariableDecl) child.get(); + Name name = field.name; + if (name == null) break; + if (getGeneratedBy(field) != null) continue; + for (Name fieldToBeGenerated : listFieldsToBeGenerated(data, builderType)) { + if (!fieldToBeGenerated.equals(name)) continue; + child.addError("Manually adding a field that @Singular @Builder would generate is not supported. If you want to manually manage the builder aspect for this field/parameter, don't use @Singular."); + return true; + } + break; + } + case METHOD: { + JCMethodDecl method = (JCMethodDecl) child.get(); + Name name = method.name; + if (name == null) break; + if (getGeneratedBy(method) != null) continue; + for (Name methodToBeGenerated : listMethodsToBeGenerated(data, builderType)) { + if (!methodToBeGenerated.equals(name)) continue; + child.addError("Manually adding a method that @Singular @Builder would generate is not supported. If you want to manually manage the builder aspect for this field/parameter, don't use @Singular."); + return true; + } + break; + }} + } + + return false; + } + + public java.util.List<Name> listFieldsToBeGenerated(SingularData data, JavacNode builderType) { + return Collections.singletonList(data.pluralName); + } + + public java.util.List<Name> listMethodsToBeGenerated(SingularData data, JavacNode builderType) { + Name p = data.pluralName; + Name s = data.singularName; + if (p.equals(s)) return Collections.singletonList(p); + return Arrays.asList(p, s); + } + + public abstract java.util.List<JavacNode> generateFields(SingularData data, JavacNode builderType, JCTree source); + public abstract void generateMethods(SingularData data, JavacNode builderType, JCTree source, boolean fluent, boolean chain); + public abstract void appendBuildCode(SingularData data, JavacNode builderType, JCTree source, ListBuffer<JCStatement> statements, Name targetVariableName); + + public boolean requiresCleaning() { + try { + return !getClass().getMethod("appendCleaningCode", SingularData.class, JavacNode.class, JCTree.class, ListBuffer.class).getDeclaringClass().equals(JavacSingularizer.class); + } catch (NoSuchMethodException e) { + return false; + } + } + + public void appendCleaningCode(SingularData data, JavacNode builderType, JCTree source, ListBuffer<JCStatement> statements) { + } + + // -- Utility methods -- + + /** + * Adds the requested number of type arguments to the provided type, copying each argument in {@code typeArgs}. If typeArgs is too long, the extra elements are ignored. + * If {@code typeArgs} is null or too short, {@code java.lang.Object} will be substituted for each missing type argument. + * + * @param count The number of type arguments requested. + * @param addExtends If {@code true}, all bounds are either '? extends X' or just '?'. If false, the reverse is applied, and '? extends Foo' is converted to Foo, '?' to Object, etc. + * @param node Some node in the same AST. Just used to obtain makers and contexts and such. + * @param type The type to add generics to. + * @param typeArgs the list of type args to clone. + * @param source The source annotation that is the root cause of this code generation. + */ + protected JCExpression addTypeArgs(int count, boolean addExtends, JavacNode node, JCExpression type, List<JCExpression> typeArgs, JCTree source) { + JavacTreeMaker maker = node.getTreeMaker(); + List<JCExpression> clonedAndFixedTypeArgs = createTypeArgs(count, addExtends, node, typeArgs, source); + + return maker.TypeApply(type, clonedAndFixedTypeArgs); + } + + protected List<JCExpression> createTypeArgs(int count, boolean addExtends, JavacNode node, List<JCExpression> typeArgs, JCTree source) { + JavacTreeMaker maker = node.getTreeMaker(); + Context context = node.getContext(); + + if (count < 0) throw new IllegalArgumentException("count is negative"); + if (count == 0) return List.nil(); + ListBuffer<JCExpression> arguments = new ListBuffer<JCExpression>(); + + if (typeArgs != null) for (JCExpression orig : typeArgs) { + if (!addExtends) { + if (orig.getKind() == Kind.UNBOUNDED_WILDCARD || orig.getKind() == Kind.SUPER_WILDCARD) { + arguments.append(chainDots(node, "java", "lang", "Object")); + } else if (orig.getKind() == Kind.EXTENDS_WILDCARD) { + JCExpression inner; + try { + inner = (JCExpression) ((JCWildcard) orig).inner; + } catch (Exception e) { + inner = chainDots(node, "java", "lang", "Object"); + } + arguments.append(cloneType(maker, inner, source, context)); + } else { + arguments.append(cloneType(maker, orig, source, context)); + } + } else { + if (orig.getKind() == Kind.UNBOUNDED_WILDCARD || orig.getKind() == Kind.SUPER_WILDCARD) { + arguments.append(maker.Wildcard(maker.TypeBoundKind(BoundKind.UNBOUND), null)); + } else if (orig.getKind() == Kind.EXTENDS_WILDCARD) { + arguments.append(cloneType(maker, orig, source, context)); + } else { + arguments.append(maker.Wildcard(maker.TypeBoundKind(BoundKind.EXTENDS), cloneType(maker, orig, source, context))); + } + } + if (--count == 0) break; + } + + while (count-- > 0) { + if (addExtends) { + arguments.append(maker.Wildcard(maker.TypeBoundKind(BoundKind.UNBOUND), null)); + } else { + arguments.append(chainDots(node, "java", "lang", "Object")); + } + } + + return arguments.toList(); + } + + /** Generates 'this.<em>name</em>.size()' as an expression; if nullGuard is true, it's this.name == null ? 0 : this.name.size(). */ + protected JCExpression getSize(JavacTreeMaker maker, JavacNode builderType, Name name, boolean nullGuard) { + 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); + } + return sizeInvoke; + } + + protected JCExpression cloneParamType(int index, JavacTreeMaker maker, List<JCExpression> typeArgs, JavacNode builderType, JCTree source) { + if (typeArgs == null || typeArgs.size() <= index) { + return chainDots(builderType, "java", "lang", "Object"); + } else { + JCExpression originalType = typeArgs.get(index); + if (originalType.getKind() == Kind.UNBOUNDED_WILDCARD || originalType.getKind() == Kind.SUPER_WILDCARD) { + return chainDots(builderType, "java", "lang", "Object"); + } else if (originalType.getKind() == Kind.EXTENDS_WILDCARD) { + try { + return cloneType(maker, (JCExpression) ((JCWildcard) originalType).inner, source, builderType.getContext()); + } catch (Exception e) { + return chainDots(builderType, "java", "lang", "Object"); + } + } else { + return cloneType(maker, originalType, source, builderType.getContext()); + } + } + } + } +} diff --git a/src/core/lombok/javac/handlers/singulars/JavacGuavaMapSingularizer.java b/src/core/lombok/javac/handlers/singulars/JavacGuavaMapSingularizer.java new file mode 100644 index 00000000..0700e2e5 --- /dev/null +++ b/src/core/lombok/javac/handlers/singulars/JavacGuavaMapSingularizer.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2015 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.javac.handlers.singulars; + +import lombok.core.LombokImmutableList; +import lombok.javac.handlers.JavacSingularsRecipes.JavacSingularizer; + +import org.mangosdk.spi.ProviderFor; + +@ProviderFor(JavacSingularizer.class) +public class JavacGuavaMapSingularizer extends JavacGuavaSingularizer { + // TODO cgcc.ImmutableMultimap, cgcc.ImmutableListMultimap, cgcc.ImmutableSetMultimap + // TODO cgcc.ImmutableClassToInstanceMap + // TODO cgcc.ImmutableRangeMap + + @Override public LombokImmutableList<String> getSupportedTypes() { + return LombokImmutableList.of( + "com.google.common.collect.ImmutableMap", + "com.google.common.collect.ImmutableBiMap", + "com.google.common.collect.ImmutableSortedMap"); + } + + @Override protected boolean isMap() { + return true; + } +} diff --git a/src/core/lombok/javac/handlers/singulars/JavacGuavaSetListSingularizer.java b/src/core/lombok/javac/handlers/singulars/JavacGuavaSetListSingularizer.java new file mode 100644 index 00000000..2e404ca8 --- /dev/null +++ b/src/core/lombok/javac/handlers/singulars/JavacGuavaSetListSingularizer.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2015 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.javac.handlers.singulars; + +import lombok.core.LombokImmutableList; +import lombok.javac.handlers.JavacSingularsRecipes.JavacSingularizer; + +import org.mangosdk.spi.ProviderFor; + +@ProviderFor(JavacSingularizer.class) +public class JavacGuavaSetListSingularizer extends JavacGuavaSingularizer { + // TODO com.google.common.collect.ImmutableTable + // TODO com.google.common.collect.ImmutableRangeSet + // TODO com.google.common.collect.ImmutableMultiset and com.google.common.collect.ImmutableSortedMultiset + @Override public LombokImmutableList<String> getSupportedTypes() { + return LombokImmutableList.of( + "com.google.common.collect.ImmutableCollection", + "com.google.common.collect.ImmutableList", + "com.google.common.collect.ImmutableSet", + "com.google.common.collect.ImmutableSortedSet"); + } + + @Override protected boolean isMap() { + return false; + } +} diff --git a/src/core/lombok/javac/handlers/singulars/JavacGuavaSingularizer.java b/src/core/lombok/javac/handlers/singulars/JavacGuavaSingularizer.java new file mode 100644 index 00000000..2474ce7b --- /dev/null +++ b/src/core/lombok/javac/handlers/singulars/JavacGuavaSingularizer.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2015 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.javac.handlers.singulars; + +import static lombok.javac.Javac.*; +import static lombok.javac.handlers.JavacHandlerUtil.*; + +import java.util.Collections; + +import lombok.core.GuavaTypeMap; +import lombok.core.handlers.HandlerUtil; +import lombok.javac.JavacNode; +import lombok.javac.JavacTreeMaker; +import lombok.javac.handlers.JavacHandlerUtil; +import lombok.javac.handlers.JavacSingularsRecipes.JavacSingularizer; +import lombok.javac.handlers.JavacSingularsRecipes.SingularData; + +import com.sun.tools.javac.code.Flags; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.JCBlock; +import com.sun.tools.javac.tree.JCTree.JCExpression; +import com.sun.tools.javac.tree.JCTree.JCMethodDecl; +import com.sun.tools.javac.tree.JCTree.JCModifiers; +import com.sun.tools.javac.tree.JCTree.JCStatement; +import com.sun.tools.javac.tree.JCTree.JCTypeParameter; +import com.sun.tools.javac.tree.JCTree.JCVariableDecl; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.util.ListBuffer; +import com.sun.tools.javac.util.Name; + +abstract class JavacGuavaSingularizer extends JavacSingularizer { + protected String getSimpleTargetTypeName(SingularData data) { + return GuavaTypeMap.getGuavaTypeName(data.getTargetFqn()); + } + + protected String getBuilderMethodName(SingularData data) { + String simpleTypeName = getSimpleTargetTypeName(data); + if ("ImmutableSortedSet".equals(simpleTypeName) || "ImmutableSortedMap".equals(simpleTypeName)) return "naturalOrder"; + return "builder"; + } + + protected abstract boolean isMap(); + + @Override public java.util.List<JavacNode> generateFields(SingularData data, JavacNode builderType, JCTree source) { + JavacTreeMaker maker = builderType.getTreeMaker(); + JCExpression type = JavacHandlerUtil.chainDots(builderType, "com", "google", "common", "collect", getSimpleTargetTypeName(data), "Builder"); + type = addTypeArgs(isMap() ? 2 : 1, false, builderType, type, data.getTypeArgs(), source); + + JCVariableDecl buildField = maker.VarDef(maker.Modifiers(Flags.PRIVATE), data.getPluralName(), type, null); + return Collections.singletonList(injectField(builderType, buildField)); + } + + @Override public void generateMethods(SingularData data, JavacNode builderType, JCTree source, boolean fluent, boolean chain) { + JavacTreeMaker maker = builderType.getTreeMaker(); + JCExpression returnType = chain ? cloneSelfType(builderType) : maker.Type(createVoidType(maker, CTC_VOID)); + JCStatement returnStatement = chain ? maker.Return(maker.Ident(builderType.toName("this"))) : null; + generateSingularMethod(maker, returnType, returnStatement, data, builderType, source, fluent); + + returnType = chain ? cloneSelfType(builderType) : maker.Type(createVoidType(maker, CTC_VOID)); + returnStatement = chain ? maker.Return(maker.Ident(builderType.toName("this"))) : null; + generatePluralMethod(maker, returnType, returnStatement, data, builderType, source, fluent); + } + + void generateSingularMethod(JavacTreeMaker maker, JCExpression returnType, JCStatement returnStatement, SingularData data, JavacNode builderType, JCTree source, boolean fluent) { + List<JCTypeParameter> typeParams = List.nil(); + List<JCExpression> thrown = List.nil(); + boolean mapMode = isMap(); + + Name keyName = !mapMode ? data.getSingularName() : builderType.toName(data.getSingularName() + "$key"); + Name valueName = !mapMode ? null : builderType.toName(data.getSingularName() + "$value"); + + JCModifiers mods = maker.Modifiers(Flags.PUBLIC); + ListBuffer<JCStatement> statements = new ListBuffer<JCStatement>(); + statements.append(createConstructBuilderVarIfNeeded(maker, data, builderType, mapMode, source)); + JCExpression thisDotFieldDotAdd = chainDots(builderType, "this", data.getPluralName().toString(), mapMode ? "put" : "add"); + List<JCExpression> invokeAddExpr; + if (mapMode) { + invokeAddExpr = List.<JCExpression>of(maker.Ident(keyName), maker.Ident(valueName)); + } else { + invokeAddExpr = List.<JCExpression>of(maker.Ident(keyName)); + } + JCExpression invokeAdd = maker.Apply(List.<JCExpression>nil(), thisDotFieldDotAdd, invokeAddExpr); + statements.append(maker.Exec(invokeAdd)); + if (returnStatement != null) statements.append(returnStatement); + JCBlock body = maker.Block(0, statements.toList()); + Name methodName = data.getSingularName(); + long paramFlags = JavacHandlerUtil.addFinalIfNeeded(Flags.PARAMETER, builderType.getContext()); + if (!fluent) methodName = builderType.toName(HandlerUtil.buildAccessorName(mapMode ? "put" : "add", methodName.toString())); + List<JCVariableDecl> params; + if (mapMode) { + JCExpression keyType = cloneParamType(0, maker, data.getTypeArgs(), builderType, source); + JCExpression valueType = cloneParamType(1, maker, data.getTypeArgs(), builderType, source); + JCVariableDecl paramKey = maker.VarDef(maker.Modifiers(paramFlags), keyName, keyType, null); + JCVariableDecl paramValue = maker.VarDef(maker.Modifiers(paramFlags), valueName, valueType, null); + params = List.of(paramKey, paramValue); + } else { + JCExpression paramType = cloneParamType(0, maker, data.getTypeArgs(), builderType, source); + params = List.of(maker.VarDef(maker.Modifiers(paramFlags), data.getSingularName(), paramType, null)); + } + JCMethodDecl method = maker.MethodDef(mods, methodName, returnType, typeParams, params, thrown, body, null); + injectMethod(builderType, method); + } + + protected void generatePluralMethod(JavacTreeMaker maker, JCExpression returnType, JCStatement returnStatement, SingularData data, JavacNode builderType, JCTree source, boolean fluent) { + List<JCTypeParameter> typeParams = List.nil(); + List<JCExpression> thrown = List.nil(); + boolean mapMode = isMap(); + + JCModifiers mods = maker.Modifiers(Flags.PUBLIC); + ListBuffer<JCStatement> statements = new ListBuffer<JCStatement>(); + statements.append(createConstructBuilderVarIfNeeded(maker, data, builderType, mapMode, source)); + JCExpression thisDotFieldDotAddAll = chainDots(builderType, "this", data.getPluralName().toString(), mapMode ? "putAll" : "addAll"); + JCExpression invokeAddAll = maker.Apply(List.<JCExpression>nil(), thisDotFieldDotAddAll, List.<JCExpression>of(maker.Ident(data.getPluralName()))); + statements.append(maker.Exec(invokeAddAll)); + if (returnStatement != null) statements.append(returnStatement); + JCBlock body = maker.Block(0, statements.toList()); + Name methodName = data.getPluralName(); + long paramFlags = JavacHandlerUtil.addFinalIfNeeded(Flags.PARAMETER, builderType.getContext()); + if (!fluent) methodName = builderType.toName(HandlerUtil.buildAccessorName(mapMode ? "putAll" : "addAll", methodName.toString())); + JCExpression paramType; + if (mapMode) { + paramType = chainDots(builderType, "java", "util", "Map"); + } else { + paramType = chainDots(builderType, "java", "lang", "Iterable"); + } + paramType = addTypeArgs(mapMode ? 2 : 1, true, builderType, paramType, data.getTypeArgs(), source); + JCVariableDecl param = maker.VarDef(maker.Modifiers(paramFlags), data.getPluralName(), paramType, null); + JCMethodDecl method = maker.MethodDef(mods, methodName, returnType, typeParams, List.of(param), thrown, body, null); + injectMethod(builderType, method); + } + + @Override public void appendBuildCode(SingularData data, JavacNode builderType, JCTree source, ListBuffer<JCStatement> statements, Name targetVariableName) { + JavacTreeMaker maker = builderType.getTreeMaker(); + List<JCExpression> jceBlank = List.nil(); + boolean mapMode = isMap(); + + JCExpression varType = chainDotsString(builderType, data.getTargetFqn()); + varType = addTypeArgs(mapMode ? 2 : 1, false, builderType, varType, data.getTypeArgs(), source); + + JCExpression empty; { + //ImmutableX.of() + JCExpression emptyMethod = chainDots(builderType, "com", "google", "common", "collect", getSimpleTargetTypeName(data), "of"); + List<JCExpression> invokeTypeArgs = createTypeArgs(mapMode ? 2 : 1, false, builderType, data.getTypeArgs(), source); + empty = maker.Apply(invokeTypeArgs, emptyMethod, jceBlank); + } + + JCExpression invokeBuild; { + //this.pluralName.build(); + invokeBuild = maker.Apply(jceBlank, chainDots(builderType, "this", data.getPluralName().toString(), "build"), jceBlank); + } + + JCExpression isNull; { + //this.pluralName == null + isNull = maker.Binary(CTC_EQUAL, maker.Select(maker.Ident(builderType.toName("this")), data.getPluralName()), maker.Literal(CTC_BOT, null)); + } + + JCExpression init = maker.Conditional(isNull, empty, invokeBuild); // this.pluralName == null ? ImmutableX.of() : this.pluralName.build() + + JCStatement jcs = maker.VarDef(maker.Modifiers(0), data.getPluralName(), varType, init); + statements.append(jcs); + } + + protected JCStatement createConstructBuilderVarIfNeeded(JavacTreeMaker maker, SingularData data, JavacNode builderType, boolean mapMode, JCTree source) { + List<JCExpression> jceBlank = List.nil(); + + JCExpression thisDotField = maker.Select(maker.Ident(builderType.toName("this")), data.getPluralName()); + JCExpression thisDotField2 = maker.Select(maker.Ident(builderType.toName("this")), data.getPluralName()); + JCExpression cond = maker.Binary(CTC_EQUAL, thisDotField, maker.Literal(CTC_BOT, null)); + + JCExpression create = maker.Apply(jceBlank, chainDots(builderType, "com", "google", "common", "collect", getSimpleTargetTypeName(data), getBuilderMethodName(data)), jceBlank); + JCStatement thenPart = maker.Exec(maker.Assign(thisDotField2, create)); + + return maker.If(cond, thenPart, null); + } +} diff --git a/src/core/lombok/javac/handlers/singulars/JavacJavaUtilListSetSingularizer.java b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilListSetSingularizer.java new file mode 100644 index 00000000..6f8ff705 --- /dev/null +++ b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilListSetSingularizer.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.singulars; + +import static lombok.javac.Javac.*; +import static lombok.javac.handlers.JavacHandlerUtil.*; + +import java.util.Collections; + +import lombok.core.handlers.HandlerUtil; +import lombok.javac.JavacNode; +import lombok.javac.JavacTreeMaker; +import lombok.javac.handlers.JavacHandlerUtil; +import lombok.javac.handlers.JavacSingularsRecipes.SingularData; + +import com.sun.tools.javac.code.Flags; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.JCBlock; +import com.sun.tools.javac.tree.JCTree.JCExpression; +import com.sun.tools.javac.tree.JCTree.JCMethodDecl; +import com.sun.tools.javac.tree.JCTree.JCModifiers; +import com.sun.tools.javac.tree.JCTree.JCStatement; +import com.sun.tools.javac.tree.JCTree.JCTypeParameter; +import com.sun.tools.javac.tree.JCTree.JCVariableDecl; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.util.ListBuffer; +import com.sun.tools.javac.util.Name; + +abstract class JavacJavaUtilListSetSingularizer extends JavacJavaUtilSingularizer { + @Override public java.util.List<Name> listFieldsToBeGenerated(SingularData data, JavacNode builderType) { + if (useGuavaInstead(builderType)) { + return guavaListSetSingularizer.listFieldsToBeGenerated(data, builderType); + } + + return super.listFieldsToBeGenerated(data, builderType); + } + + @Override public java.util.List<Name> listMethodsToBeGenerated(SingularData data, JavacNode builderType) { + if (useGuavaInstead(builderType)) { + return guavaListSetSingularizer.listMethodsToBeGenerated(data, builderType); + } + + return super.listMethodsToBeGenerated(data, builderType); + } + + @Override public java.util.List<JavacNode> generateFields(SingularData data, JavacNode builderType, JCTree source) { + if (useGuavaInstead(builderType)) { + return guavaListSetSingularizer.generateFields(data, builderType, source); + } + + JavacTreeMaker maker = builderType.getTreeMaker(); + JCExpression type = JavacHandlerUtil.chainDots(builderType, "java", "util", "ArrayList"); + type = addTypeArgs(1, false, builderType, type, data.getTypeArgs(), source); + + JCVariableDecl buildField = maker.VarDef(maker.Modifiers(Flags.PRIVATE), data.getPluralName(), type, null); + return Collections.singletonList(injectField(builderType, buildField)); + } + + @Override public void generateMethods(SingularData data, JavacNode builderType, JCTree source, boolean fluent, boolean chain) { + if (useGuavaInstead(builderType)) { + guavaListSetSingularizer.generateMethods(data, builderType, source, fluent, chain); + return; + } + + JavacTreeMaker maker = builderType.getTreeMaker(); + Name thisName = builderType.toName("this"); + + JCExpression returnType = chain ? cloneSelfType(builderType) : maker.Type(createVoidType(maker, CTC_VOID)); + JCStatement returnStatement = chain ? maker.Return(maker.Ident(thisName)) : null; + generateSingularMethod(maker, returnType, returnStatement, data, builderType, source, fluent); + + returnType = chain ? cloneSelfType(builderType) : maker.Type(createVoidType(maker, CTC_VOID)); + returnStatement = chain ? maker.Return(maker.Ident(thisName)) : null; + generatePluralMethod(maker, returnType, returnStatement, data, builderType, source, fluent); + } + + void generateSingularMethod(JavacTreeMaker maker, JCExpression returnType, JCStatement returnStatement, SingularData data, JavacNode builderType, JCTree source, boolean fluent) { + List<JCTypeParameter> typeParams = List.nil(); + List<JCExpression> thrown = List.nil(); + + JCModifiers mods = maker.Modifiers(Flags.PUBLIC); + ListBuffer<JCStatement> statements = new ListBuffer<JCStatement>(); + statements.append(createConstructBuilderVarIfNeeded(maker, data, builderType, false, source)); + JCExpression thisDotFieldDotAdd = chainDots(builderType, "this", data.getPluralName().toString(), "add"); + JCExpression invokeAdd = maker.Apply(List.<JCExpression>nil(), thisDotFieldDotAdd, List.<JCExpression>of(maker.Ident(data.getSingularName()))); + statements.append(maker.Exec(invokeAdd)); + if (returnStatement != null) statements.append(returnStatement); + JCBlock body = maker.Block(0, statements.toList()); + Name name = data.getSingularName(); + long paramFlags = JavacHandlerUtil.addFinalIfNeeded(Flags.PARAMETER, builderType.getContext()); + if (!fluent) name = builderType.toName(HandlerUtil.buildAccessorName("add", name.toString())); + JCExpression paramType = cloneParamType(0, maker, data.getTypeArgs(), builderType, source); + JCVariableDecl param = maker.VarDef(maker.Modifiers(paramFlags), data.getSingularName(), paramType, null); + JCMethodDecl method = maker.MethodDef(mods, name, returnType, typeParams, List.of(param), thrown, body, null); + injectMethod(builderType, method); + } + + void generatePluralMethod(JavacTreeMaker maker, JCExpression returnType, JCStatement returnStatement, SingularData data, JavacNode builderType, JCTree source, boolean fluent) { + List<JCTypeParameter> typeParams = List.nil(); + List<JCExpression> thrown = List.nil(); + + JCModifiers mods = maker.Modifiers(Flags.PUBLIC); + ListBuffer<JCStatement> statements = new ListBuffer<JCStatement>(); + statements.append(createConstructBuilderVarIfNeeded(maker, data, builderType, false, source)); + JCExpression thisDotFieldDotAdd = chainDots(builderType, "this", data.getPluralName().toString(), "addAll"); + JCExpression invokeAdd = maker.Apply(List.<JCExpression>nil(), thisDotFieldDotAdd, List.<JCExpression>of(maker.Ident(data.getPluralName()))); + statements.append(maker.Exec(invokeAdd)); + if (returnStatement != null) statements.append(returnStatement); + JCBlock body = maker.Block(0, statements.toList()); + Name name = data.getPluralName(); + long paramFlags = JavacHandlerUtil.addFinalIfNeeded(Flags.PARAMETER, builderType.getContext()); + if (!fluent) name = builderType.toName(HandlerUtil.buildAccessorName("addAll", name.toString())); + JCExpression paramType = chainDots(builderType, "java", "util", "Collection"); + paramType = addTypeArgs(1, true, builderType, paramType, data.getTypeArgs(), source); + JCVariableDecl param = maker.VarDef(maker.Modifiers(paramFlags), data.getPluralName(), paramType, null); + JCMethodDecl method = maker.MethodDef(mods, name, returnType, typeParams, List.of(param), thrown, body, null); + injectMethod(builderType, method); + } +} diff --git a/src/core/lombok/javac/handlers/singulars/JavacJavaUtilListSingularizer.java b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilListSingularizer.java new file mode 100644 index 00000000..65e91fa0 --- /dev/null +++ b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilListSingularizer.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2015 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.javac.handlers.singulars; + +import static lombok.javac.Javac.*; +import static lombok.javac.handlers.JavacHandlerUtil.*; + +import org.mangosdk.spi.ProviderFor; + +import lombok.core.LombokImmutableList; +import lombok.javac.JavacNode; +import lombok.javac.JavacTreeMaker; +import lombok.javac.handlers.JavacSingularsRecipes.JavacSingularizer; +import lombok.javac.handlers.JavacSingularsRecipes.SingularData; + +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.JCCase; +import com.sun.tools.javac.tree.JCTree.JCExpression; +import com.sun.tools.javac.tree.JCTree.JCStatement; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.util.ListBuffer; +import com.sun.tools.javac.util.Name; + +@ProviderFor(JavacSingularizer.class) +public class JavacJavaUtilListSingularizer extends JavacJavaUtilListSetSingularizer { + @Override public LombokImmutableList<String> getSupportedTypes() { + return LombokImmutableList.of("java.util.List", "java.util.Collection", "java.util.Iterable"); + } + + @Override public void appendBuildCode(SingularData data, JavacNode builderType, JCTree source, ListBuffer<JCStatement> statements, Name targetVariableName) { + if (useGuavaInstead(builderType)) { + guavaListSetSingularizer.appendBuildCode(data, builderType, source, statements, targetVariableName); + return; + } + + JavacTreeMaker maker = builderType.getTreeMaker(); + List<JCExpression> jceBlank = List.nil(); + ListBuffer<JCCase> cases = new ListBuffer<JCCase>(); + + /* case 0: (empty); break; */ { + JCStatement assignStat; { + // pluralName = java.util.Collections.emptyList(); + JCExpression invoke = maker.Apply(jceBlank, chainDots(builderType, "java", "util", "Collections", "emptyList"), jceBlank); + assignStat = maker.Exec(maker.Assign(maker.Ident(data.getPluralName()), invoke)); + } + JCStatement breakStat = maker.Break(null); + JCCase emptyCase = maker.Case(maker.Literal(CTC_INT, 0), List.of(assignStat, breakStat)); + cases.append(emptyCase); + } + + /* case 1: (singletonList); break; */ { + JCStatement assignStat; { + // pluralName = java.util.Collections.singletonList(this.pluralName.get(0)); + JCExpression zeroLiteral = maker.Literal(CTC_INT, 0); + JCExpression arg = maker.Apply(jceBlank, chainDots(builderType, "this", data.getPluralName().toString(), "get"), List.of(zeroLiteral)); + List<JCExpression> args = List.of(arg); + JCExpression invoke = maker.Apply(jceBlank, chainDots(builderType, "java", "util", "Collections", "singletonList"), args); + assignStat = maker.Exec(maker.Assign(maker.Ident(data.getPluralName()), invoke)); + } + JCStatement breakStat = maker.Break(null); + JCCase singletonCase = maker.Case(maker.Literal(CTC_INT, 1), List.of(assignStat, breakStat)); + cases.append(singletonCase); + } + + /* default: Create with right size, then addAll */ { + List<JCStatement> defStats = createListCopy(maker, data, builderType, source); + JCCase defaultCase = maker.Case(null, defStats); + cases.append(defaultCase); + } + + JCStatement switchStat = maker.Switch(getSize(maker, builderType, data.getPluralName(), true), cases.toList()); + JCExpression localShadowerType = chainDotsString(builderType, data.getTargetFqn()); + localShadowerType = addTypeArgs(1, false, builderType, localShadowerType, data.getTypeArgs(), source); + JCStatement varDefStat = maker.VarDef(maker.Modifiers(0), data.getPluralName(), localShadowerType, null); + statements.append(varDefStat); + statements.append(switchStat); + } + + private List<JCStatement> createListCopy(JavacTreeMaker maker, SingularData data, JavacNode builderType, JCTree source) { + List<JCExpression> jceBlank = List.nil(); + Name thisName = builderType.toName("this"); + + JCExpression argToUnmodifiable; { + // new java.util.ArrayList<Generics>(this.pluralName); + List<JCExpression> constructorArgs = List.nil(); + JCExpression thisDotPluralName = maker.Select(maker.Ident(thisName), data.getPluralName()); + constructorArgs = List.<JCExpression>of(thisDotPluralName); + JCExpression targetTypeExpr = chainDots(builderType, "java", "util", "ArrayList"); + targetTypeExpr = addTypeArgs(1, false, builderType, targetTypeExpr, data.getTypeArgs(), source); + argToUnmodifiable = maker.NewClass(null, jceBlank, targetTypeExpr, constructorArgs, null); + } + + JCStatement unmodifiableStat; { + // pluralname = Collections.unmodifiableInterfaceType(-newlist-); + JCExpression invoke = maker.Apply(jceBlank, chainDots(builderType, "java", "util", "Collections", "unmodifiableList"), List.of(argToUnmodifiable)); + unmodifiableStat = maker.Exec(maker.Assign(maker.Ident(data.getPluralName()), invoke)); + } + + return List.of(unmodifiableStat); + } +} diff --git a/src/core/lombok/javac/handlers/singulars/JavacJavaUtilMapSingularizer.java b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilMapSingularizer.java new file mode 100644 index 00000000..ed91698d --- /dev/null +++ b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilMapSingularizer.java @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2015 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.javac.handlers.singulars; + +import static lombok.javac.Javac.*; +import static lombok.javac.handlers.JavacHandlerUtil.*; + +import java.util.Arrays; + +import lombok.core.LombokImmutableList; +import lombok.core.handlers.HandlerUtil; +import lombok.javac.JavacNode; +import lombok.javac.JavacTreeMaker; +import lombok.javac.handlers.JavacHandlerUtil; +import lombok.javac.handlers.JavacSingularsRecipes.JavacSingularizer; +import lombok.javac.handlers.JavacSingularsRecipes.SingularData; + +import org.mangosdk.spi.ProviderFor; + +import com.sun.tools.javac.code.Flags; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.JCBlock; +import com.sun.tools.javac.tree.JCTree.JCExpression; +import com.sun.tools.javac.tree.JCTree.JCMethodDecl; +import com.sun.tools.javac.tree.JCTree.JCModifiers; +import com.sun.tools.javac.tree.JCTree.JCStatement; +import com.sun.tools.javac.tree.JCTree.JCTypeParameter; +import com.sun.tools.javac.tree.JCTree.JCVariableDecl; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.util.ListBuffer; +import com.sun.tools.javac.util.Name; + +@ProviderFor(JavacSingularizer.class) +public class JavacJavaUtilMapSingularizer extends JavacJavaUtilSingularizer { + @Override public LombokImmutableList<String> getSupportedTypes() { + return LombokImmutableList.of("java.util.Map", "java.util.SortedMap", "java.util.NavigableMap"); + } + + @Override public java.util.List<Name> listFieldsToBeGenerated(SingularData data, JavacNode builderType) { + if (useGuavaInstead(builderType)) { + return guavaMapSingularizer.listFieldsToBeGenerated(data, builderType); + } + + String p = data.getPluralName().toString(); + return Arrays.asList(builderType.toName(p + "$key"), builderType.toName(p + "$value")); + } + + @Override public java.util.List<Name> listMethodsToBeGenerated(SingularData data, JavacNode builderType) { + if (useGuavaInstead(builderType)) { + return guavaMapSingularizer.listMethodsToBeGenerated(data, builderType); + } + + return super.listMethodsToBeGenerated(data, builderType); + } + + @Override public java.util.List<JavacNode> generateFields(SingularData data, JavacNode builderType, JCTree source) { + if (useGuavaInstead(builderType)) { + return guavaMapSingularizer.generateFields(data, builderType, source); + } + + JavacTreeMaker maker = builderType.getTreeMaker(); + + JCVariableDecl buildKeyField; { + JCExpression type = JavacHandlerUtil.chainDots(builderType, "java", "util", "ArrayList"); + type = addTypeArgs(1, false, builderType, type, data.getTypeArgs(), source); + buildKeyField = maker.VarDef(maker.Modifiers(Flags.PRIVATE), builderType.toName(data.getPluralName() + "$key"), type, null); + } + + JCVariableDecl buildValueField; { + JCExpression type = JavacHandlerUtil.chainDots(builderType, "java", "util", "ArrayList"); + List<JCExpression> tArgs = data.getTypeArgs(); + if (tArgs != null && tArgs.size() > 1) tArgs = tArgs.tail; + else tArgs = List.nil(); + type = addTypeArgs(1, false, builderType, type, tArgs, source); + buildValueField = maker.VarDef(maker.Modifiers(Flags.PRIVATE), builderType.toName(data.getPluralName() + "$value"), type, null); + } + + JavacNode valueFieldNode = injectField(builderType, buildValueField); + JavacNode keyFieldNode = injectField(builderType, buildKeyField); + + return Arrays.asList(keyFieldNode, valueFieldNode); + } + + @Override public void generateMethods(SingularData data, JavacNode builderType, JCTree source, boolean fluent, boolean chain) { + if (useGuavaInstead(builderType)) { + guavaMapSingularizer.generateMethods(data, builderType, source, fluent, chain); + return; + } + + JavacTreeMaker maker = builderType.getTreeMaker(); + + JCExpression returnType = chain ? cloneSelfType(builderType) : maker.Type(createVoidType(maker, CTC_VOID)); + JCStatement returnStatement = chain ? maker.Return(maker.Ident(builderType.toName("this"))) : null; + generateSingularMethod(maker, returnType, returnStatement, data, builderType, source, fluent); + + returnType = chain ? cloneSelfType(builderType) : maker.Type(createVoidType(maker, CTC_VOID)); + returnStatement = chain ? maker.Return(maker.Ident(builderType.toName("this"))) : null; + generatePluralMethod(maker, returnType, returnStatement, data, builderType, source, fluent); + } + + private void generateSingularMethod(JavacTreeMaker maker, JCExpression returnType, JCStatement returnStatement, SingularData data, JavacNode builderType, JCTree source, boolean fluent) { + List<JCTypeParameter> typeParams = List.nil(); + List<JCExpression> thrown = List.nil(); + JCModifiers mods = maker.Modifiers(Flags.PUBLIC); + ListBuffer<JCStatement> statements = new ListBuffer<JCStatement>(); + statements.append(createConstructBuilderVarIfNeeded(maker, data, builderType, true, source)); + Name keyName = builderType.toName(data.getSingularName().toString() + "Key"); + Name valueName = builderType.toName(data.getSingularName().toString() + "Value"); + /* this.pluralname$key.add(singularnameKey); */ { + JCExpression thisDotKeyFieldDotAdd = chainDots(builderType, "this", data.getPluralName() + "$key", "add"); + JCExpression invokeAdd = maker.Apply(List.<JCExpression>nil(), thisDotKeyFieldDotAdd, List.<JCExpression>of(maker.Ident(keyName))); + statements.append(maker.Exec(invokeAdd)); + } + /* this.pluralname$value.add(singularnameValue); */ { + JCExpression thisDotValueFieldDotAdd = chainDots(builderType, "this", data.getPluralName() + "$value", "add"); + JCExpression invokeAdd = maker.Apply(List.<JCExpression>nil(), thisDotValueFieldDotAdd, List.<JCExpression>of(maker.Ident(valueName))); + statements.append(maker.Exec(invokeAdd)); + } + if (returnStatement != null) statements.append(returnStatement); + JCBlock body = maker.Block(0, statements.toList()); + long paramFlags = JavacHandlerUtil.addFinalIfNeeded(Flags.PARAMETER, builderType.getContext()); + + Name name = data.getSingularName(); + if (!fluent) name = builderType.toName(HandlerUtil.buildAccessorName("put", name.toString())); + JCExpression paramTypeKey = cloneParamType(0, maker, data.getTypeArgs(), builderType, source); + JCExpression paramTypeValue = cloneParamType(1, maker, data.getTypeArgs(), builderType, source); + JCVariableDecl paramKey = maker.VarDef(maker.Modifiers(paramFlags), keyName, paramTypeKey, null); + JCVariableDecl paramValue = maker.VarDef(maker.Modifiers(paramFlags), valueName, paramTypeValue, null); + JCMethodDecl method = maker.MethodDef(mods, name, returnType, typeParams, List.of(paramKey, paramValue), thrown, body, null); + injectMethod(builderType, method); + } + + private void generatePluralMethod(JavacTreeMaker maker, JCExpression returnType, JCStatement returnStatement, SingularData data, JavacNode builderType, JCTree source, boolean fluent) { + List<JCTypeParameter> typeParams = List.nil(); + List<JCExpression> jceBlank = List.nil(); + JCModifiers mods = maker.Modifiers(Flags.PUBLIC); + ListBuffer<JCStatement> statements = new ListBuffer<JCStatement>(); + statements.append(createConstructBuilderVarIfNeeded(maker, data, builderType, true, source)); + long paramFlags = JavacHandlerUtil.addFinalIfNeeded(Flags.PARAMETER, builderType.getContext()); + long baseFlags = JavacHandlerUtil.addFinalIfNeeded(0, builderType.getContext()); + Name entryName = builderType.toName("$lombokEntry"); + + JCExpression forEachType = chainDots(builderType, "java", "util", "Map", "Entry"); + forEachType = addTypeArgs(2, true, builderType, forEachType, data.getTypeArgs(), source); + JCExpression keyArg = maker.Apply(List.<JCExpression>nil(), maker.Select(maker.Ident(entryName), builderType.toName("getKey")), List.<JCExpression>nil()); + JCExpression valueArg = maker.Apply(List.<JCExpression>nil(), maker.Select(maker.Ident(entryName), builderType.toName("getValue")), List.<JCExpression>nil()); + JCExpression addKey = maker.Apply(List.<JCExpression>nil(), chainDots(builderType, "this", data.getPluralName() + "$key", "add"), List.of(keyArg)); + JCExpression addValue = maker.Apply(List.<JCExpression>nil(), chainDots(builderType, "this", data.getPluralName() + "$value", "add"), List.of(valueArg)); + JCBlock forEachBody = maker.Block(0, List.<JCStatement>of(maker.Exec(addKey), maker.Exec(addValue))); + JCExpression entrySetInvocation = maker.Apply(jceBlank, maker.Select(maker.Ident(data.getPluralName()), builderType.toName("entrySet")), jceBlank); + JCStatement forEach = maker.ForeachLoop(maker.VarDef(maker.Modifiers(baseFlags), entryName, forEachType, null), entrySetInvocation, forEachBody); + statements.append(forEach); + + if (returnStatement != null) statements.append(returnStatement); + JCBlock body = maker.Block(0, statements.toList()); + Name name = data.getPluralName(); + if (!fluent) name = builderType.toName(HandlerUtil.buildAccessorName("putAll", name.toString())); + JCExpression paramType = chainDots(builderType, "java", "util", "Map"); + paramType = addTypeArgs(2, true, builderType, paramType, data.getTypeArgs(), source); + JCVariableDecl param = maker.VarDef(maker.Modifiers(paramFlags), data.getPluralName(), paramType, null); + JCMethodDecl method = maker.MethodDef(mods, name, returnType, typeParams, List.of(param), jceBlank, body, null); + injectMethod(builderType, method); + } + + @Override public void appendBuildCode(SingularData data, JavacNode builderType, JCTree source, ListBuffer<JCStatement> statements, Name targetVariableName) { + if (useGuavaInstead(builderType)) { + guavaMapSingularizer.appendBuildCode(data, builderType, source, statements, targetVariableName); + return; + } + + JavacTreeMaker maker = builderType.getTreeMaker(); + + if (data.getTargetFqn().equals("java.util.Map")) { + statements.appendList(createJavaUtilSetMapInitialCapacitySwitchStatements(maker, data, builderType, true, "emptyMap", "singletonMap", "LinkedHashMap", source)); + } else { + statements.appendList(createJavaUtilSimpleCreationAndFillStatements(maker, data, builderType, true, true, false, true, "TreeMap", source)); + } + } +} diff --git a/src/core/lombok/javac/handlers/singulars/JavacJavaUtilSetSingularizer.java b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilSetSingularizer.java new file mode 100644 index 00000000..317233cb --- /dev/null +++ b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilSetSingularizer.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2015 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.javac.handlers.singulars; + +import org.mangosdk.spi.ProviderFor; + +import lombok.core.LombokImmutableList; +import lombok.javac.JavacNode; +import lombok.javac.JavacTreeMaker; +import lombok.javac.handlers.JavacSingularsRecipes.JavacSingularizer; +import lombok.javac.handlers.JavacSingularsRecipes.SingularData; + +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.JCStatement; +import com.sun.tools.javac.util.ListBuffer; +import com.sun.tools.javac.util.Name; + +@ProviderFor(JavacSingularizer.class) +public class JavacJavaUtilSetSingularizer extends JavacJavaUtilListSetSingularizer { + @Override public LombokImmutableList<String> getSupportedTypes() { + return LombokImmutableList.of("java.util.Set", "java.util.SortedSet", "java.util.NavigableSet"); + } + + @Override public void appendBuildCode(SingularData data, JavacNode builderType, JCTree source, ListBuffer<JCStatement> statements, Name targetVariableName) { + if (useGuavaInstead(builderType)) { + guavaListSetSingularizer.appendBuildCode(data, builderType, source, statements, targetVariableName); + return; + } + + JavacTreeMaker maker = builderType.getTreeMaker(); + + if (data.getTargetFqn().equals("java.util.Set")) { + statements.appendList(createJavaUtilSetMapInitialCapacitySwitchStatements(maker, data, builderType, false, "emptySet", "singleton", "LinkedHashSet", source)); + } else { + statements.appendList(createJavaUtilSimpleCreationAndFillStatements(maker, data, builderType, false, true, false, true, "TreeSet", source)); + } + } +} diff --git a/src/core/lombok/javac/handlers/singulars/JavacJavaUtilSingularizer.java b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilSingularizer.java new file mode 100644 index 00000000..c75dfbdd --- /dev/null +++ b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilSingularizer.java @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2015 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.javac.handlers.singulars; + +import static lombok.javac.Javac.*; +import static lombok.javac.handlers.JavacHandlerUtil.*; + +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.JCCase; +import com.sun.tools.javac.tree.JCTree.JCExpression; +import com.sun.tools.javac.tree.JCTree.JCStatement; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.util.ListBuffer; +import com.sun.tools.javac.util.Name; + +import lombok.ConfigurationKeys; +import lombok.javac.JavacNode; +import lombok.javac.JavacTreeMaker; +import lombok.javac.handlers.JavacSingularsRecipes.JavacSingularizer; +import lombok.javac.handlers.JavacSingularsRecipes.SingularData; + +abstract class JavacJavaUtilSingularizer extends JavacSingularizer { + protected final JavacSingularizer guavaListSetSingularizer = new JavacGuavaSetListSingularizer(); + protected final JavacSingularizer guavaMapSingularizer = new JavacGuavaMapSingularizer(); + + protected boolean useGuavaInstead(JavacNode node) { + return Boolean.TRUE.equals(node.getAst().readConfiguration(ConfigurationKeys.SINGULAR_USE_GUAVA)); + } + + protected List<JCStatement> createJavaUtilSetMapInitialCapacitySwitchStatements(JavacTreeMaker maker, SingularData data, JavacNode builderType, boolean mapMode, String emptyCollectionMethod, String singletonCollectionMethod, String targetType, JCTree source) { + List<JCExpression> jceBlank = List.nil(); + ListBuffer<JCCase> cases = new ListBuffer<JCCase>(); + + if (emptyCollectionMethod != null) { // case 0: (empty); break; + JCStatement assignStat; { + // pluralName = java.util.Collections.emptyCollectionMethod(); + JCExpression invoke = maker.Apply(jceBlank, chainDots(builderType, "java", "util", "Collections", emptyCollectionMethod), jceBlank); + assignStat = maker.Exec(maker.Assign(maker.Ident(data.getPluralName()), invoke)); + } + JCStatement breakStat = maker.Break(null); + JCCase emptyCase = maker.Case(maker.Literal(CTC_INT, 0), List.of(assignStat, breakStat)); + cases.append(emptyCase); + } + + if (singletonCollectionMethod != null) { // case 1: (singleton); break; + JCStatement assignStat; { + // !mapMode: pluralName = java.util.Collections.singletonCollectionMethod(this.pluralName.get(0)); + // mapMode: pluralName = java.util.Collections.singletonCollectionMethod(this.pluralName$key.get(0), this.pluralName$value.get(0)); + JCExpression zeroLiteral = maker.Literal(CTC_INT, 0); + JCExpression arg = maker.Apply(jceBlank, chainDots(builderType, "this", data.getPluralName() + (mapMode ? "$key" : ""), "get"), List.of(zeroLiteral)); + List<JCExpression> args; + if (mapMode) { + JCExpression zeroLiteralClone = maker.Literal(CTC_INT, 0); + JCExpression arg2 = maker.Apply(jceBlank, chainDots(builderType, "this", data.getPluralName() + (mapMode ? "$value" : ""), "get"), List.of(zeroLiteralClone)); + args = List.of(arg, arg2); + } else { + args = List.of(arg); + } + JCExpression invoke = maker.Apply(jceBlank, chainDots(builderType, "java", "util", "Collections", singletonCollectionMethod), args); + assignStat = maker.Exec(maker.Assign(maker.Ident(data.getPluralName()), invoke)); + } + JCStatement breakStat = maker.Break(null); + JCCase singletonCase = maker.Case(maker.Literal(CTC_INT, 1), List.of(assignStat, breakStat)); + cases.append(singletonCase); + } + + { // default: + List<JCStatement> statements = createJavaUtilSimpleCreationAndFillStatements(maker, data, builderType, mapMode, false, true, emptyCollectionMethod == null, targetType, source); + JCCase defaultCase = maker.Case(null, statements); + cases.append(defaultCase); + } + + JCStatement switchStat = maker.Switch(getSize(maker, builderType, mapMode ? builderType.toName(data.getPluralName() + "$key") : data.getPluralName(), true), cases.toList()); + JCExpression localShadowerType = chainDotsString(builderType, data.getTargetFqn()); + localShadowerType = addTypeArgs(mapMode ? 2 : 1, false, builderType, localShadowerType, data.getTypeArgs(), source); + JCStatement varDefStat = maker.VarDef(maker.Modifiers(0), data.getPluralName(), localShadowerType, null); + return List.of(varDefStat, switchStat); + } + + protected JCStatement createConstructBuilderVarIfNeeded(JavacTreeMaker maker, SingularData data, JavacNode builderType, boolean mapMode, JCTree source) { + List<JCExpression> jceBlank = List.nil(); + + Name v1Name = mapMode ? builderType.toName(data.getPluralName() + "$key") : data.getPluralName(); + Name v2Name = mapMode ? builderType.toName(data.getPluralName() + "$value") : null; + JCExpression thisDotField = maker.Select(maker.Ident(builderType.toName("this")), v1Name); + JCExpression cond = maker.Binary(CTC_EQUAL, thisDotField, maker.Literal(CTC_BOT, null)); + thisDotField = maker.Select(maker.Ident(builderType.toName("this")), v1Name); + JCExpression v1Type = chainDots(builderType, "java", "util", "ArrayList"); + v1Type = addTypeArgs(1, false, builderType, v1Type, data.getTypeArgs(), source); + JCExpression constructArrayList = maker.NewClass(null, jceBlank, v1Type, jceBlank, null); + JCStatement initV1 = maker.Exec(maker.Assign(thisDotField, constructArrayList)); + JCStatement thenPart; + if (mapMode) { + thisDotField = maker.Select(maker.Ident(builderType.toName("this")), v2Name); + JCExpression v2Type = chainDots(builderType, "java", "util", "ArrayList"); + List<JCExpression> tArgs = data.getTypeArgs(); + if (tArgs != null && tArgs.tail != null) tArgs = tArgs.tail; + else tArgs = List.nil(); + v2Type = addTypeArgs(1, false, builderType, v2Type, tArgs, source); + constructArrayList = maker.NewClass(null, jceBlank, v2Type, jceBlank, null); + JCStatement initV2 = maker.Exec(maker.Assign(thisDotField, constructArrayList)); + thenPart = maker.Block(0, List.of(initV1, initV2)); + } else { + thenPart = initV1; + } + return maker.If(cond, thenPart, null); + } + + protected List<JCStatement> createJavaUtilSimpleCreationAndFillStatements(JavacTreeMaker maker, SingularData data, JavacNode builderType, boolean mapMode, boolean defineVar, boolean addInitialCapacityArg, boolean nullGuard, String targetType, JCTree source) { + List<JCExpression> jceBlank = List.nil(); + Name thisName = builderType.toName("this"); + + JCStatement createStat; { + // pluralName = new java.util.TargetType(initialCap); + List<JCExpression> constructorArgs = List.nil(); + if (addInitialCapacityArg) { + Name varName = mapMode ? builderType.toName(data.getPluralName() + "$key") : data.getPluralName(); + // this.varName.size() < MAX_POWER_OF_2 ? 1 + this.varName.size() + (this.varName.size() - 3) / 3 : Integer.MAX_VALUE; + // lessThanCutOff = this.varName.size() < MAX_POWER_OF_2 + JCExpression lessThanCutoff = maker.Binary(CTC_LESS_THAN, getSize(maker, builderType, varName, nullGuard), maker.Literal(CTC_INT, 0x40000000)); + JCExpression integerMaxValue = chainDots(builderType, "java", "lang", "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 sizeFormulaRight = maker.Binary(CTC_DIV, sizeFormulaRightLeft, maker.Literal(CTC_INT, 3)); + JCExpression sizeFormula = maker.Binary(CTC_PLUS, sizeFormulaLeft, sizeFormulaRight); + constructorArgs = List.<JCExpression>of(maker.Conditional(lessThanCutoff, sizeFormula, integerMaxValue)); + } + + JCExpression targetTypeExpr = chainDots(builderType, "java", "util", targetType); + targetTypeExpr = addTypeArgs(mapMode ? 2 : 1, false, builderType, targetTypeExpr, data.getTypeArgs(), source); + JCExpression constructorCall = maker.NewClass(null, jceBlank, targetTypeExpr, constructorArgs, null); + if (defineVar) { + JCExpression localShadowerType = chainDotsString(builderType, data.getTargetFqn()); + localShadowerType = addTypeArgs(mapMode ? 2 : 1, false, builderType, localShadowerType, data.getTypeArgs(), source); + createStat = maker.VarDef(maker.Modifiers(0), data.getPluralName(), localShadowerType, constructorCall); + } else { + createStat = maker.Exec(maker.Assign(maker.Ident(data.getPluralName()), constructorCall)); + } + } + + JCStatement fillStat; { + if (mapMode) { + // for (int $i = 0; $i < this.pluralname$key.size(); i++) pluralname.put(this.pluralname$key.get($i), this.pluralname$value.get($i)); + Name ivar = builderType.toName("$i"); + Name keyVarName = builderType.toName(data.getPluralName() + "$key"); + JCExpression pluralnameDotPut = maker.Select(maker.Ident(data.getPluralName()), builderType.toName("put")); + JCExpression arg1 = maker.Apply(jceBlank, chainDots(builderType, "this", data.getPluralName() + "$key", "get"), List.<JCExpression>of(maker.Ident(ivar))); + JCExpression arg2 = maker.Apply(jceBlank, chainDots(builderType, "this", data.getPluralName() + "$value", "get"), List.<JCExpression>of(maker.Ident(ivar))); + 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 incrementExpr = maker.Unary(CTC_POSTINC, maker.Ident(ivar)); + fillStat = maker.ForLoop(List.of(forInit), checkExpr, List.of(maker.Exec(incrementExpr)), putStatement); + } else { + // pluralname.addAll(this.pluralname); + JCExpression thisDotPluralName = maker.Select(maker.Ident(thisName), data.getPluralName()); + fillStat = maker.Exec(maker.Apply(jceBlank, maker.Select(maker.Ident(data.getPluralName()), builderType.toName("addAll")), List.of(thisDotPluralName))); + } + if (nullGuard) { + JCExpression thisDotField = maker.Select(maker.Ident(thisName), mapMode ? builderType.toName(data.getPluralName() + "$key") : data.getPluralName()); + JCExpression nullCheck = maker.Binary(CTC_NOT_EQUAL, thisDotField, maker.Literal(CTC_BOT, null)); + fillStat = maker.If(nullCheck, fillStat, null); + } + } + JCStatement unmodifiableStat; { + // pluralname = Collections.unmodifiableInterfaceType(pluralname); + JCExpression arg = maker.Ident(data.getPluralName()); + JCExpression invoke = maker.Apply(jceBlank, chainDots(builderType, "java", "util", "Collections", "unmodifiable" + data.getTargetSimpleType()), List.of(arg)); + unmodifiableStat = maker.Exec(maker.Assign(maker.Ident(data.getPluralName()), invoke)); + } + + return List.of(createStat, fillStat, unmodifiableStat); + } +} diff --git a/src/delombok/lombok/delombok/Delombok.java b/src/delombok/lombok/delombok/Delombok.java index f64e36a1..059bb004 100644 --- a/src/delombok/lombok/delombok/Delombok.java +++ b/src/delombok/lombok/delombok/Delombok.java @@ -509,7 +509,7 @@ public class Delombok { DelombokResult result = new DelombokResult(catcher.getComments(unit), unit, force || options.isChanged(unit), fps); if (verbose) feedback.printf("File: %s [%s]\n", unit.sourcefile.getName(), result.isChanged() ? "delomboked" : "unchanged"); Writer rawWriter; - if (presetWriter != null) rawWriter = presetWriter; + if (presetWriter != null) rawWriter = createUnicodeEscapeWriter(presetWriter); else if (output == null) rawWriter = createStandardOutWriter(); else rawWriter = createFileWriter(output, baseMap.get(unit), unit.sourcefile.toUri()); BufferedWriter writer = new BufferedWriter(rawWriter); @@ -605,6 +605,10 @@ public class Delombok { return createUnicodeEscapeWriter(System.out); } + private Writer createUnicodeEscapeWriter(Writer writer) { + return new UnicodeEscapeWriter(writer, charset); + } + private Writer createUnicodeEscapeWriter(OutputStream out) { return new UnicodeEscapeWriter(new OutputStreamWriter(out, charset), charset); } diff --git a/src/delombok/lombok/delombok/DelombokApp.java b/src/delombok/lombok/delombok/DelombokApp.java index 276bd7de..aa753fc8 100644 --- a/src/delombok/lombok/delombok/DelombokApp.java +++ b/src/delombok/lombok/delombok/DelombokApp.java @@ -88,7 +88,7 @@ public class DelombokApp extends LombokApp { // Since we only read from it, not closing it should not be a problem. @SuppressWarnings({"resource", "all"}) final JarFile toolsJarFile = new JarFile(toolsJar); - ClassLoader loader = new ClassLoader() { + ClassLoader loader = new ClassLoader(DelombokApp.class.getClassLoader()) { private Class<?> loadStreamAsClass(String name, boolean resolve, InputStream in) throws ClassNotFoundException { try { try { @@ -107,16 +107,24 @@ public class DelombokApp extends LombokApp { } finally { in.close(); } - } catch (IOException e2) { + } catch (Exception e2) { throw new ClassNotFoundException(name, e2); } } @Override protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { - String rawName = name.replace(".", "/") + ".class"; + String rawName, altName; { + String binName = name.replace(".", "/"); + rawName = binName + ".class"; + altName = binName + ".SCL.lombok"; + } JarEntry entry = toolsJarFile.getJarEntry(rawName); if (entry == null) { - if (name.startsWith("lombok.")) return loadStreamAsClass(name, resolve, super.getResourceAsStream(rawName)); + if (name.startsWith("lombok.")) { + InputStream res = getParent().getResourceAsStream(rawName); + if (res == null) res = getParent().getResourceAsStream(altName); + return loadStreamAsClass(name, resolve, res); + } return super.loadClass(name, resolve); } diff --git a/src/delombok/lombok/delombok/PrettyCommentsPrinter.java b/src/delombok/lombok/delombok/PrettyCommentsPrinter.java index 9dd7e40e..72a8ff59 100644 --- a/src/delombok/lombok/delombok/PrettyCommentsPrinter.java +++ b/src/delombok/lombok/delombok/PrettyCommentsPrinter.java @@ -104,7 +104,6 @@ import com.sun.tools.javac.tree.JCTree.LetExpr; import com.sun.tools.javac.tree.JCTree.TypeBoundKind; import com.sun.tools.javac.tree.TreeInfo; import com.sun.tools.javac.tree.TreeScanner; -import com.sun.tools.javac.util.Convert; import com.sun.tools.javac.util.List; import com.sun.tools.javac.util.Name; import com.sun.tools.javac.util.Position; @@ -209,7 +208,6 @@ public class PrettyCommentsPrinter extends JCTree.Visitor { private void consumeComments(int until, JCTree tree) throws IOException { boolean prevNewLine = onNewLine; - boolean found = false; CommentInfo head = comments.head; while (comments.nonEmpty() && head.pos < until) { printComment(head); @@ -355,7 +353,7 @@ public class PrettyCommentsPrinter extends JCTree.Visitor { } needsSpace = false; - out.write(Convert.escapeUnicode(s.toString())); + out.write(s.toString()); onNewLine = false; aligned = false; @@ -1413,16 +1411,51 @@ public class PrettyCommentsPrinter extends JCTree.Visitor { else if (CTC_FLOAT.equals(typeTag)) print(tree.value + "F"); else if (CTC_DOUBLE.equals(typeTag)) print(tree.value.toString()); else if (CTC_CHAR.equals(typeTag)) { - print("\'" + Convert.quote(String.valueOf((char)((Number)tree.value).intValue())) + "\'"); + 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("\"" + Convert.quote(tree.value.toString()) + "\""); + 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 { diff --git a/src/delombok/lombok/delombok/UnicodeEscapeWriter.java b/src/delombok/lombok/delombok/UnicodeEscapeWriter.java index 95b55078..9c2ef190 100644 --- a/src/delombok/lombok/delombok/UnicodeEscapeWriter.java +++ b/src/delombok/lombok/delombok/UnicodeEscapeWriter.java @@ -63,6 +63,6 @@ public class UnicodeEscapeWriter extends Writer { } protected void writeUnicodeEscape(char c) throws IOException { - writer.write("\\u" + Integer.toHexString(c)); + writer.write(String.format("\\u%04x", (int) c)); } }
\ No newline at end of file diff --git a/src/eclipseAgent/lombok/eclipse/agent/EclipseLoaderPatcher.java b/src/eclipseAgent/lombok/eclipse/agent/EclipseLoaderPatcher.java new file mode 100644 index 00000000..0d21c212 --- /dev/null +++ b/src/eclipseAgent/lombok/eclipse/agent/EclipseLoaderPatcher.java @@ -0,0 +1,106 @@ +package lombok.eclipse.agent; + +import java.io.InputStream; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.jar.JarFile; +import java.util.zip.ZipEntry; + +import lombok.patcher.ClassRootFinder; +import lombok.patcher.Hook; +import lombok.patcher.MethodTarget; +import lombok.patcher.ScriptManager; +import lombok.patcher.StackRequest; +import lombok.patcher.scripts.ScriptBuilder; + +public class EclipseLoaderPatcher { + public static boolean overrideLoadDecide(ClassLoader original, String name, boolean resolve) { + return name.startsWith("lombok."); + } + + public static Class<?> overrideLoadResult(ClassLoader original, String name, boolean resolve) throws ClassNotFoundException { + try { + Field shadowLoaderField = original.getClass().getField("lombok$shadowLoader"); + ClassLoader shadowLoader = (ClassLoader) shadowLoaderField.get(original); + if (shadowLoader == null) { + 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."); + } + in.close(); + Class<?> shadowClassLoaderClass; { + Method defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class); + defineClassMethod.setAccessible(true); + shadowClassLoaderClass = (Class<?>) defineClassMethod.invoke(original, "lombok.launch.ShadowClassLoader", bytes, 0, len); + } + Constructor<?> constructor = shadowClassLoaderClass.getDeclaredConstructor(ClassLoader.class, String.class, String.class, String[].class); + constructor.setAccessible(true); + shadowLoader = (ClassLoader) constructor.newInstance(original, "lombok", jarLoc, new String[] {"lombok."}); + shadowLoaderField.set(original, shadowLoader); + } finally { + if (in != null) in.close(); + jf.close(); + } + } + + if (resolve) { + Method m = shadowLoader.getClass().getDeclaredMethod("loadClass", String.class, boolean.class); + m.setAccessible(true); + return (Class<?>) m.invoke(shadowLoader, name, true); + } else { + return shadowLoader.loadClass(name); + } + } catch (Exception ex) { + Throwable t = ex; + if (t instanceof InvocationTargetException) t = t.getCause(); + if (t instanceof RuntimeException) throw (RuntimeException) t; + if (t instanceof Error) throw (Error) t; + throw new RuntimeException(t); + } + } + + private static final String SELF_NAME = "lombok.eclipse.agent.EclipseLoaderPatcher"; + + public static void patchEquinoxLoaders(ScriptManager sm, Class<?> launchingContext) { + sm.addScript(ScriptBuilder.exitEarly() + .target(new MethodTarget("org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader", "loadClass", + "java.lang.Class", "java.lang.String", "boolean")) + .target(new MethodTarget("org.eclipse.osgi.framework.adapter.core.AbstractClassLoader", "loadClass", + "java.lang.Class", "java.lang.String", "boolean")) + .target(new MethodTarget("org.eclipse.osgi.internal.loader.ModuleClassLoader", "loadClass", + "java.lang.Class", "java.lang.String", "boolean")) + .decisionMethod(new Hook(SELF_NAME, "overrideLoadDecide", "boolean", "java.lang.ClassLoader", "java.lang.String", "boolean")) + .valueMethod(new Hook(SELF_NAME, "overrideLoadResult", "java.lang.Class", "java.lang.ClassLoader", "java.lang.String", "boolean")) + .transplant() + .request(StackRequest.THIS, StackRequest.PARAM1, StackRequest.PARAM2).build()); + + sm.addScript(ScriptBuilder.addField().setPublic() + .fieldType("Ljava/lang/ClassLoader;") + .fieldName("lombok$shadowLoader") + .targetClass("org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader") + .targetClass("org.eclipse.osgi.framework.adapter.core.AbstractClassLoader") + .targetClass("org.eclipse.osgi.internal.loader.ModuleClassLoader") + .build()); + + sm.addScript(ScriptBuilder.addField().setPublic().setStatic().setFinal() + .fieldType("Ljava/lang/String;") + .fieldName("lombok$location") + .targetClass("org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader") + .targetClass("org.eclipse.osgi.framework.adapter.core.AbstractClassLoader") + .targetClass("org.eclipse.osgi.internal.loader.ModuleClassLoader") + .value(ClassRootFinder.findClassRootOfClass(launchingContext)) + .build()); + } +} diff --git a/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java b/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java index e14d1367..efc7ce94 100644 --- a/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java +++ b/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2012 The Project Lombok Authors. + * Copyright (C) 2009-2015 The Project Lombok Authors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -28,13 +28,13 @@ import java.util.Collection; import java.util.Collections; import java.util.List; -import lombok.core.Agent; +import lombok.core.AgentLauncher; + import lombok.patcher.Hook; import lombok.patcher.MethodTarget; import lombok.patcher.ScriptManager; import lombok.patcher.StackRequest; import lombok.patcher.TargetMatcher; -import lombok.patcher.equinox.EquinoxClassLoader; import lombok.patcher.scripts.ScriptBuilder; /** @@ -44,13 +44,12 @@ import lombok.patcher.scripts.ScriptBuilder; * classes in this package for more information about which classes are transformed and how they are * transformed. */ -public class EclipsePatcher extends Agent { +public class EclipsePatcher implements AgentLauncher.AgentLaunchable { // At some point I'd like the agent to be capable of auto-detecting if its on eclipse or on ecj. This class is a sure sign we're not in ecj but in eclipse. -ReinierZ @SuppressWarnings("unused") private static final String ECLIPSE_SIGNATURE_CLASS = "org/eclipse/core/runtime/adaptor/EclipseStarter"; - @Override - public void runAgent(String agentArgs, Instrumentation instrumentation, boolean injected) throws Exception { + @Override public void runAgent(String agentArgs, Instrumentation instrumentation, boolean injected, Class<?> launchingContext) throws Exception { String[] args = agentArgs == null ? new String[0] : agentArgs.split(":"); boolean forceEcj = false; boolean forceEclipse = false; @@ -69,18 +68,14 @@ public class EclipsePatcher extends Agent { else if (forceEclipse) ecj = false; else ecj = injected; - registerPatchScripts(instrumentation, injected, ecj); + registerPatchScripts(instrumentation, injected, ecj, launchingContext); } - private static void registerPatchScripts(Instrumentation instrumentation, boolean reloadExistingClasses, boolean ecjOnly) { + private static void registerPatchScripts(Instrumentation instrumentation, boolean reloadExistingClasses, boolean ecjOnly, Class<?> launchingContext) { ScriptManager sm = new ScriptManager(); sm.registerTransformer(instrumentation); if (!ecjOnly) { - EquinoxClassLoader.addPrefix("lombok."); - EquinoxClassLoader.registerScripts(sm); - } - - if (!ecjOnly) { + EclipseLoaderPatcher.patchEquinoxLoaders(sm, launchingContext); patchCatchReparse(sm); patchIdentifierEndReparse(sm); patchRetrieveEllipsisStartPosition(sm); @@ -114,7 +109,7 @@ public class EclipsePatcher extends Agent { sm.addScript(ScriptBuilder.wrapMethodCall() .target(new MethodTarget("org.eclipse.jdt.internal.compiler.SourceElementNotifier", "notifySourceElementRequestor", "void", "org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration", "org.eclipse.jdt.internal.compiler.ast.TypeDeclaration", "org.eclipse.jdt.internal.compiler.ast.ImportReference")) .methodToWrap(new Hook("org.eclipse.jdt.internal.compiler.util.HashtableOfObjectToInt", "get", "int", "java.lang.Object")) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "getSourceEndFixed", "int", "int", "org.eclipse.jdt.internal.compiler.ast.ASTNode")) + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "getSourceEndFixed", "int", "int", "org.eclipse.jdt.internal.compiler.ast.ASTNode")) .requestExtra(StackRequest.PARAM1) .transplant().build()); @@ -127,7 +122,7 @@ public class EclipsePatcher extends Agent { "org.eclipse.jdt.core.dom.MethodDeclaration" )) .methodToWrap(new Hook("org.eclipse.jface.text.IDocument", "get", "java.lang.String", "int", "int")) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "getRealMethodDeclarationSource", "java.lang.String", "java.lang.String", "java.lang.Object", "org.eclipse.jdt.core.dom.MethodDeclaration")) + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "getRealMethodDeclarationSource", "java.lang.String", "java.lang.String", "java.lang.Object", "org.eclipse.jdt.core.dom.MethodDeclaration")) .requestExtra(StackRequest.THIS, StackRequest.PARAM4) .transplant().build()); @@ -136,20 +131,20 @@ public class EclipsePatcher extends Agent { .target(new MethodTarget("org.eclipse.jdt.internal.corext.refactoring.structure.ExtractInterfaceProcessor", "createMemberDeclarations")) .target(new MethodTarget("org.eclipse.jdt.internal.corext.refactoring.structure.ExtractInterfaceProcessor", "createMethodComments")) .methodToReplace(new Hook("org.eclipse.jdt.internal.corext.refactoring.structure.ASTNodeSearchUtil", "getMethodDeclarationNode", "org.eclipse.jdt.core.dom.MethodDeclaration", "org.eclipse.jdt.core.IMethod", "org.eclipse.jdt.core.dom.CompilationUnit")) - .replacementMethod(new Hook("lombok.eclipse.agent.PatchFixes", "getRealMethodDeclarationNode", "org.eclipse.jdt.core.dom.MethodDeclaration", "org.eclipse.jdt.core.IMethod", "org.eclipse.jdt.core.dom.CompilationUnit")) + .replacementMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "getRealMethodDeclarationNode", "org.eclipse.jdt.core.dom.MethodDeclaration", "org.eclipse.jdt.core.IMethod", "org.eclipse.jdt.core.dom.CompilationUnit")) .transplant().build()); /* Do not add @Override's for generated methods */ sm.addScript(ScriptBuilder.exitEarly() .target(new MethodTarget("org.eclipse.jdt.core.dom.rewrite.ListRewrite", "insertFirst")) - .decisionMethod(new Hook("lombok.eclipse.agent.PatchFixes", "isListRewriteOnGeneratedNode", "boolean", "org.eclipse.jdt.core.dom.rewrite.ListRewrite")) + .decisionMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "isListRewriteOnGeneratedNode", "boolean", "org.eclipse.jdt.core.dom.rewrite.ListRewrite")) .request(StackRequest.THIS) .transplant().build()); /* Do not add comments for generated methods */ sm.addScript(ScriptBuilder.exitEarly() .target(new MethodTarget("org.eclipse.jdt.internal.corext.refactoring.structure.ExtractInterfaceProcessor", "createMethodComment")) - .decisionMethod(new Hook("lombok.eclipse.agent.PatchFixes", "isGenerated", "boolean", "org.eclipse.jdt.core.dom.ASTNode")) + .decisionMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "isGenerated", "boolean", "org.eclipse.jdt.core.dom.ASTNode")) .request(StackRequest.PARAM2) .transplant().build()); } @@ -162,7 +157,7 @@ public class EclipsePatcher extends Agent { */ sm.addScript(ScriptBuilder.wrapReturnValue() .target(new MethodTarget("org.eclipse.core.internal.runtime.Product", "getProperty", "java.lang.String", "java.lang.String")) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "addLombokNotesToEclipseAboutDialog", "java.lang.String", "java.lang.String", "java.lang.String")) + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$LombokDeps", "addLombokNotesToEclipseAboutDialog", "java.lang.String", "java.lang.String", "java.lang.String")) .request(StackRequest.RETURN_VALUE, StackRequest.PARAM1) .transplant().build()); } @@ -175,8 +170,8 @@ public class EclipsePatcher extends Agent { .target(new MethodTarget("org.eclipse.jdt.internal.ui.search.OccurrencesFinder", "addUsage")) .target(new MethodTarget("org.eclipse.jdt.internal.ui.search.OccurrencesFinder", "addWrite")) .target(new MethodTarget("org.eclipse.jdt.internal.ui.javaeditor.SemanticHighlightingReconciler$PositionCollector", "visit", "boolean", "org.eclipse.jdt.core.dom.SimpleName")) - .decisionMethod(new Hook("lombok.eclipse.agent.PatchFixes", "isGenerated", "boolean", "org.eclipse.jdt.core.dom.ASTNode")) - .valueMethod(new Hook("lombok.eclipse.agent.PatchFixes", "returnFalse", "boolean", "java.lang.Object")) + .decisionMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "isGenerated", "boolean", "org.eclipse.jdt.core.dom.ASTNode")) + .valueMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "returnFalse", "boolean", "java.lang.Object")) .request(StackRequest.PARAM1) .build()); } @@ -201,9 +196,9 @@ public class EclipsePatcher extends Agent { .target(new MethodTarget("org.eclipse.jdt.internal.corext.fix.CodeStyleFix$CodeStyleVisitor", "visit", "boolean", "org.eclipse.jdt.core.dom.QualifiedName")) .target(new MethodTarget("org.eclipse.jdt.internal.corext.fix.CodeStyleFix$CodeStyleVisitor", "visit", "boolean", "org.eclipse.jdt.core.dom.SimpleName")) // if a generated node has children we can just ignore them as well; - .decisionMethod(new Hook("lombok.eclipse.agent.PatchFixes", "isGenerated", "boolean", "org.eclipse.jdt.core.dom.ASTNode")) + .decisionMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "isGenerated", "boolean", "org.eclipse.jdt.core.dom.ASTNode")) .request(StackRequest.PARAM1) - .valueMethod(new Hook("lombok.eclipse.agent.PatchFixes", "returnFalse", "boolean", "java.lang.Object")) + .valueMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "returnFalse", "boolean", "java.lang.Object")) .build()); } @@ -211,7 +206,7 @@ public class EclipsePatcher extends Agent { sm.addScript(ScriptBuilder.replaceMethodCall() .target(new MethodTarget("org.eclipse.jdt.internal.core.dom.rewrite.ASTRewriteAnalyzer$ListRewriter", "rewriteList")) .methodToReplace(new Hook("org.eclipse.jdt.internal.core.dom.rewrite.RewriteEvent", "getChildren", "org.eclipse.jdt.internal.core.dom.rewrite.RewriteEvent[]")) - .replacementMethod(new Hook("lombok.eclipse.agent.PatchFixes", "listRewriteHandleGeneratedMethods", "org.eclipse.jdt.internal.core.dom.rewrite.RewriteEvent[]", "org.eclipse.jdt.internal.core.dom.rewrite.RewriteEvent")) + .replacementMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "listRewriteHandleGeneratedMethods", "org.eclipse.jdt.internal.core.dom.rewrite.RewriteEvent[]", "org.eclipse.jdt.internal.core.dom.rewrite.RewriteEvent")) .build()); } @@ -223,37 +218,37 @@ public class EclipsePatcher extends Agent { sm.addScript(ScriptBuilder.wrapMethodCall() .target(new MethodTarget("org.eclipse.jdt.internal.core.SortElementsOperation$2", "visit", "boolean", "org.eclipse.jdt.core.dom.CompilationUnit")) .methodToWrap(new Hook("org.eclipse.jdt.core.dom.CompilationUnit", "types", "java.util.List")) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "removeGeneratedNodes", "java.util.List", "java.util.List")) + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "removeGeneratedNodes", "java.util.List", "java.util.List")) .transplant().build()); sm.addScript(ScriptBuilder.wrapMethodCall() .target(new MethodTarget("org.eclipse.jdt.internal.core.SortElementsOperation$2", "visit", "boolean", "org.eclipse.jdt.core.dom.AnnotationTypeDeclaration")) .methodToWrap(new Hook("org.eclipse.jdt.core.dom.AnnotationTypeDeclaration", "bodyDeclarations", "java.util.List")) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "removeGeneratedNodes", "java.util.List", "java.util.List")) + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "removeGeneratedNodes", "java.util.List", "java.util.List")) .transplant().build()); sm.addScript(ScriptBuilder.wrapMethodCall() .target(new MethodTarget("org.eclipse.jdt.internal.core.SortElementsOperation$2", "visit", "boolean", "org.eclipse.jdt.core.dom.AnonymousClassDeclaration")) .methodToWrap(new Hook("org.eclipse.jdt.core.dom.AnonymousClassDeclaration", "bodyDeclarations", "java.util.List")) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "removeGeneratedNodes", "java.util.List", "java.util.List")) + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "removeGeneratedNodes", "java.util.List", "java.util.List")) .transplant().build()); sm.addScript(ScriptBuilder.wrapMethodCall() .target(new MethodTarget("org.eclipse.jdt.internal.core.SortElementsOperation$2", "visit", "boolean", "org.eclipse.jdt.core.dom.TypeDeclaration")) .methodToWrap(new Hook("org.eclipse.jdt.core.dom.TypeDeclaration", "bodyDeclarations", "java.util.List")) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "removeGeneratedNodes", "java.util.List", "java.util.List")) + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "removeGeneratedNodes", "java.util.List", "java.util.List")) .transplant().build()); sm.addScript(ScriptBuilder.wrapMethodCall() .target(new MethodTarget("org.eclipse.jdt.internal.core.SortElementsOperation$2", "visit", "boolean", "org.eclipse.jdt.core.dom.EnumDeclaration")) .methodToWrap(new Hook("org.eclipse.jdt.core.dom.EnumDeclaration", "bodyDeclarations", "java.util.List")) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "removeGeneratedNodes", "java.util.List", "java.util.List")) + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "removeGeneratedNodes", "java.util.List", "java.util.List")) .transplant().build()); sm.addScript(ScriptBuilder.wrapMethodCall() .target(new MethodTarget("org.eclipse.jdt.internal.core.SortElementsOperation$2", "visit", "boolean", "org.eclipse.jdt.core.dom.EnumDeclaration")) .methodToWrap(new Hook("org.eclipse.jdt.core.dom.EnumDeclaration", "enumConstants", "java.util.List")) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "removeGeneratedNodes", "java.util.List", "java.util.List")) + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "removeGeneratedNodes", "java.util.List", "java.util.List")) .transplant().build()); } @@ -261,7 +256,7 @@ public class EclipsePatcher extends Agent { sm.addScript(ScriptBuilder.replaceMethodCall() .target(new MethodTarget("org.eclipse.jdt.internal.core.dom.rewrite.ASTRewriteAnalyzer", "visit")) .methodToReplace(new Hook("org.eclipse.jdt.internal.core.dom.rewrite.TokenScanner", "getTokenEndOffset", "int", "int", "int")) - .replacementMethod(new Hook("lombok.eclipse.agent.PatchFixes", "getTokenEndOffsetFixed", "int", "org.eclipse.jdt.internal.core.dom.rewrite.TokenScanner", "int", "int", "java.lang.Object")) + .replacementMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "getTokenEndOffsetFixed", "int", "org.eclipse.jdt.internal.core.dom.rewrite.TokenScanner", "int", "int", "java.lang.Object")) .requestExtra(StackRequest.PARAM1) .transplant() .build()); @@ -273,7 +268,7 @@ public class EclipsePatcher extends Agent { .target(new MethodTarget("org.eclipse.jdt.internal.core.builder.IncrementalImageBuilder", "writeClassFileContents")) .target(new MethodTarget("org.eclipse.jdt.internal.core.builder.AbstractImageBuilder", "writeClassFileContents")) .methodToWrap(new Hook("org.eclipse.jdt.internal.compiler.ClassFile", "getBytes", "byte[]")) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "runPostCompiler", "byte[]", "byte[]", "java.lang.String")) + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$LombokDeps", "runPostCompiler", "byte[]", "byte[]", "java.lang.String")) .requestExtra(StackRequest.PARAM3) .build()); } @@ -282,24 +277,22 @@ public class EclipsePatcher extends Agent { sm.addScript(ScriptBuilder.wrapMethodCall() .target(new MethodTarget("org.eclipse.jdt.internal.compiler.tool.EclipseCompilerImpl", "outputClassFiles")) .methodToWrap(new Hook("javax.tools.JavaFileObject", "openOutputStream", "java.io.OutputStream")) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "runPostCompiler", "java.io.OutputStream", "java.io.OutputStream")) - .transplant() - .build()); + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$LombokDeps", "runPostCompiler", "java.io.OutputStream", "java.io.OutputStream")) + .transplant().build()); sm.addScript(ScriptBuilder.wrapMethodCall() .target(new MethodTarget("org.eclipse.jdt.internal.compiler.util.Util", "writeToDisk")) .methodToWrap(new Hook("java.io.BufferedOutputStream", "<init>", "void", "java.io.OutputStream", "int")) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "runPostCompiler", "java.io.BufferedOutputStream", "java.io.BufferedOutputStream", "java.lang.String", "java.lang.String")) + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$LombokDeps", "runPostCompiler", "java.io.BufferedOutputStream", "java.io.BufferedOutputStream", "java.lang.String", "java.lang.String")) .requestExtra(StackRequest.PARAM2, StackRequest.PARAM3) - .transplant() - .build()); + .transplant().build()); } private static void patchHideGeneratedNodes(ScriptManager sm) { sm.addScript(ScriptBuilder.wrapReturnValue() .target(new MethodTarget("org.eclipse.jdt.internal.corext.dom.LinkedNodeFinder", "findByNode")) .target(new MethodTarget("org.eclipse.jdt.internal.corext.dom.LinkedNodeFinder", "findByBinding")) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "removeGeneratedSimpleNames", "org.eclipse.jdt.core.dom.SimpleName[]", + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "removeGeneratedSimpleNames", "org.eclipse.jdt.core.dom.SimpleName[]", "org.eclipse.jdt.core.dom.SimpleName[]")) .request(StackRequest.RETURN_VALUE).build()); @@ -319,22 +312,22 @@ public class EclipsePatcher extends Agent { sm.addScript(ScriptBuilder.exitEarly() .target(new MethodTarget("org.eclipse.jdt.core.dom.rewrite.ASTRewrite", "replace")) .target(new MethodTarget("org.eclipse.jdt.core.dom.rewrite.ASTRewrite", "remove")) - .decisionMethod(new Hook("lombok.eclipse.agent.PatchFixes", "skipRewritingGeneratedNodes", "boolean", + .decisionMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "skipRewritingGeneratedNodes", "boolean", "org.eclipse.jdt.core.dom.ASTNode")) .transplant().request(StackRequest.PARAM1).build()); sm.addScript(ScriptBuilder.wrapMethodCall() .target(new MethodTarget("org.eclipse.jdt.internal.corext.refactoring.rename.RenameTypeProcessor", "addConstructorRenames")) .methodToWrap(new Hook("org.eclipse.jdt.core.IType", "getMethods", "org.eclipse.jdt.core.IMethod[]")) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "removeGeneratedMethods", "org.eclipse.jdt.core.IMethod[]", + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "removeGeneratedMethods", "org.eclipse.jdt.core.IMethod[]", "org.eclipse.jdt.core.IMethod[]")) .transplant().build()); sm.addScript(ScriptBuilder.exitEarly() .target(new MethodTarget("org.eclipse.jdt.internal.corext.refactoring.rename.TempOccurrenceAnalyzer", "visit", "boolean", "org.eclipse.jdt.core.dom.SimpleName")) .target(new MethodTarget("org.eclipse.jdt.internal.corext.refactoring.rename.RenameAnalyzeUtil$ProblemNodeFinder$NameNodeVisitor", "visit", "boolean", "org.eclipse.jdt.core.dom.SimpleName")) - .decisionMethod(new Hook("lombok.eclipse.agent.PatchFixes", "isGenerated", "boolean", "org.eclipse.jdt.core.dom.ASTNode")) - .valueMethod(new Hook("lombok.eclipse.agent.PatchFixes", "returnTrue", "boolean", "java.lang.Object")) + .decisionMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "isGenerated", "boolean", "org.eclipse.jdt.core.dom.ASTNode")) + .valueMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "returnTrue", "boolean", "java.lang.Object")) .request(StackRequest.PARAM1) .transplant().build()); } @@ -342,21 +335,21 @@ public class EclipsePatcher extends Agent { private static void patchCatchReparse(ScriptManager sm) { sm.addScript(ScriptBuilder.wrapReturnValue() .target(new MethodTarget("org.eclipse.jdt.core.dom.ASTConverter", "retrieveStartingCatchPosition")) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "fixRetrieveStartingCatchPosition", "int", "int", "int")) + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "fixRetrieveStartingCatchPosition", "int", "int", "int")) .transplant().request(StackRequest.RETURN_VALUE, StackRequest.PARAM1).build()); } private static void patchIdentifierEndReparse(ScriptManager sm) { sm.addScript(ScriptBuilder.wrapReturnValue() .target(new MethodTarget("org.eclipse.jdt.core.dom.ASTConverter", "retrieveIdentifierEndPosition")) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "fixRetrieveIdentifierEndPosition", "int", "int", "int")) + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "fixRetrieveIdentifierEndPosition", "int", "int", "int")) .transplant().request(StackRequest.RETURN_VALUE, StackRequest.PARAM2).build()); } private static void patchRetrieveEllipsisStartPosition(ScriptManager sm) { sm.addScript(ScriptBuilder.wrapReturnValue() .target(new MethodTarget("org.eclipse.jdt.core.dom.ASTConverter", "retrieveEllipsisStartPosition")) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "fixRetrieveEllipsisStartPosition", "int", "int", "int")) + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "fixRetrieveEllipsisStartPosition", "int", "int", "int")) .transplant().request(StackRequest.RETURN_VALUE, StackRequest.PARAM2).build()); } @@ -364,7 +357,7 @@ public class EclipsePatcher extends Agent { sm.addScript(ScriptBuilder.wrapReturnValue() .target(new MethodTarget("org.eclipse.jdt.core.dom.ASTConverter", "retrieveRightBraceOrSemiColonPosition")) .target(new MethodTarget("org.eclipse.jdt.core.dom.ASTConverter", "retrieveRightBrace")) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "fixRetrieveRightBraceOrSemiColonPosition", "int", "int", "int")) + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "fixRetrieveRightBraceOrSemiColonPosition", "int", "int", "int")) .transplant().request(StackRequest.RETURN_VALUE, StackRequest.PARAM2).build()); } @@ -396,14 +389,14 @@ public class EclipsePatcher extends Agent { return Collections.singleton("org.eclipse.jdt.core.dom.ASTConverter"); } }).request(StackRequest.PARAM1, StackRequest.RETURN_VALUE) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "setIsGeneratedFlag", "void", + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "setIsGeneratedFlag", "void", "org.eclipse.jdt.core.dom.ASTNode", "org.eclipse.jdt.internal.compiler.ast.ASTNode")) .transplant().build()); sm.addScript(ScriptBuilder.wrapReturnValue() .target(new MethodTarget("org.eclipse.jdt.core.dom.ASTConverter", "convert", "org.eclipse.jdt.core.dom.ASTNode", "boolean", "org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration")) .request(StackRequest.PARAM2, StackRequest.RETURN_VALUE) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "setIsGeneratedFlag", "void", + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "setIsGeneratedFlag", "void", "org.eclipse.jdt.core.dom.ASTNode", "org.eclipse.jdt.internal.compiler.ast.ASTNode")) .transplant().build()); @@ -421,7 +414,7 @@ public class EclipsePatcher extends Agent { .target(new MethodTarget("org.eclipse.jdt.core.dom.ASTConverter", "convertToVariableDeclarationStatement", "org.eclipse.jdt.core.dom.VariableDeclarationStatement", "org.eclipse.jdt.internal.compiler.ast.LocalDeclaration")) /* Targets above are only patched because the resulting dom nodes should be marked if generated. */ .request(StackRequest.PARAM1, StackRequest.RETURN_VALUE) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "setIsGeneratedFlag", "void", + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "setIsGeneratedFlag", "void", "org.eclipse.jdt.core.dom.ASTNode", "org.eclipse.jdt.internal.compiler.ast.ASTNode")) .transplant().build()); @@ -443,7 +436,7 @@ public class EclipsePatcher extends Agent { } }).methodToWrap(new Hook("org.eclipse.jdt.core.dom.SimpleName", "<init>", "void", "org.eclipse.jdt.core.dom.AST")) .requestExtra(StackRequest.PARAM1) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "setIsGeneratedFlagForName", "void", + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "setIsGeneratedFlagForName", "void", "org.eclipse.jdt.core.dom.Name", "java.lang.Object")) .transplant().build()); @@ -451,7 +444,7 @@ public class EclipsePatcher extends Agent { .target(new MethodTarget("org.eclipse.jdt.core.dom.ASTConverter", "convert", "org.eclipse.jdt.core.dom.ASTNode", "boolean", "org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration")) .methodToWrap(new Hook("org.eclipse.jdt.core.dom.SimpleName", "<init>", "void", "org.eclipse.jdt.core.dom.AST")) .requestExtra(StackRequest.PARAM2) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "setIsGeneratedFlagForName", "void", + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "setIsGeneratedFlagForName", "void", "org.eclipse.jdt.core.dom.Name", "java.lang.Object")) .transplant().build()); @@ -460,7 +453,7 @@ public class EclipsePatcher extends Agent { .target(new MethodTarget("org.eclipse.jdt.core.dom.ASTConverter", "setQualifiedNameNameAndSourceRanges", "org.eclipse.jdt.core.dom.QualifiedName", "char[][]", "long[]", "int", "org.eclipse.jdt.internal.compiler.ast.ASTNode")) .methodToWrap(new Hook("org.eclipse.jdt.core.dom.SimpleName", "<init>", "void", "org.eclipse.jdt.core.dom.AST")) .requestExtra(StackRequest.PARAM4) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "setIsGeneratedFlagForName", "void", + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "setIsGeneratedFlagForName", "void", "org.eclipse.jdt.core.dom.Name", "java.lang.Object")) .transplant().build()); @@ -468,7 +461,7 @@ public class EclipsePatcher extends Agent { .target(new MethodTarget("org.eclipse.jdt.core.dom.ASTConverter", "setQualifiedNameNameAndSourceRanges", "org.eclipse.jdt.core.dom.QualifiedName", "char[][]", "long[]", "int", "org.eclipse.jdt.internal.compiler.ast.ASTNode")) .methodToWrap(new Hook("org.eclipse.jdt.core.dom.QualifiedName", "<init>", "void", "org.eclipse.jdt.core.dom.AST")) .requestExtra(StackRequest.PARAM4) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "setIsGeneratedFlagForName", "void", + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "setIsGeneratedFlagForName", "void", "org.eclipse.jdt.core.dom.Name", "java.lang.Object")) .transplant().build()); @@ -476,7 +469,7 @@ public class EclipsePatcher extends Agent { .target(new MethodTarget("org.eclipse.jdt.core.dom.ASTConverter", "setQualifiedNameNameAndSourceRanges", "org.eclipse.jdt.core.dom.QualifiedName", "char[][]", "long[]", "org.eclipse.jdt.internal.compiler.ast.ASTNode")) .methodToWrap(new Hook("org.eclipse.jdt.core.dom.SimpleName", "<init>", "void", "org.eclipse.jdt.core.dom.AST")) .requestExtra(StackRequest.PARAM3) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "setIsGeneratedFlagForName", "void", + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "setIsGeneratedFlagForName", "void", "org.eclipse.jdt.core.dom.Name", "java.lang.Object")) .transplant().build()); @@ -484,7 +477,7 @@ public class EclipsePatcher extends Agent { .target(new MethodTarget("org.eclipse.jdt.core.dom.ASTConverter", "setQualifiedNameNameAndSourceRanges", "org.eclipse.jdt.core.dom.QualifiedName", "char[][]", "long[]", "org.eclipse.jdt.internal.compiler.ast.ASTNode")) .methodToWrap(new Hook("org.eclipse.jdt.core.dom.QualifiedName", "<init>", "void", "org.eclipse.jdt.core.dom.AST")) .requestExtra(StackRequest.PARAM3) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "setIsGeneratedFlagForName", "void", + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "setIsGeneratedFlagForName", "void", "org.eclipse.jdt.core.dom.Name", "java.lang.Object")) .transplant().build()); } @@ -495,7 +488,7 @@ public class EclipsePatcher extends Agent { .target(new MethodTarget(PARSER_SIG, "parse", "void", "org.eclipse.jdt.internal.compiler.ast.MethodDeclaration", "org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration")) - .decisionMethod(new Hook("lombok.eclipse.agent.PatchFixes", "checkBit24", "boolean", "java.lang.Object")) + .decisionMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "checkBit24", "boolean", "java.lang.Object")) .transplant() .request(StackRequest.PARAM1).build()); @@ -503,7 +496,7 @@ public class EclipsePatcher extends Agent { .target(new MethodTarget(PARSER_SIG, "parse", "void", "org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration", "org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration", "boolean")) - .decisionMethod(new Hook("lombok.eclipse.agent.PatchFixes", "checkBit24", "boolean", "java.lang.Object")) + .decisionMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "checkBit24", "boolean", "java.lang.Object")) .transplant() .request(StackRequest.PARAM1).build()); @@ -512,7 +505,7 @@ public class EclipsePatcher extends Agent { "org.eclipse.jdt.internal.compiler.ast.Initializer", "org.eclipse.jdt.internal.compiler.ast.TypeDeclaration", "org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration")) - .decisionMethod(new Hook("lombok.eclipse.agent.PatchFixes", "checkBit24", "boolean", "java.lang.Object")) + .decisionMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "checkBit24", "boolean", "java.lang.Object")) .transplant() .request(StackRequest.PARAM1).build()); } @@ -528,12 +521,12 @@ public class EclipsePatcher extends Agent { sm.addScript(ScriptBuilder.wrapReturnValue() .target(new MethodTarget(PARSER_SIG, "getMethodBodies", "void", CUD_SIG)) - .wrapMethod(new Hook("lombok.eclipse.TransformEclipseAST", "transform", "void", PARSER_SIG, CUD_SIG)) + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$Transform", "transform", "void", PARSER_SIG, CUD_SIG)) .request(StackRequest.THIS, StackRequest.PARAM1).build()); sm.addScript(ScriptBuilder.wrapReturnValue() .target(new MethodTarget(PARSER_SIG, "endParse", CUD_SIG, "int")) - .wrapMethod(new Hook("lombok.eclipse.TransformEclipseAST", "transform_swapped", "void", CUD_SIG, PARSER_SIG)) + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$Transform", "transform_swapped", "void", CUD_SIG, PARSER_SIG)) .request(StackRequest.THIS, StackRequest.RETURN_VALUE).build()); } @@ -549,7 +542,7 @@ public class EclipsePatcher extends Agent { sm.addScript(ScriptBuilder.exitEarly() .target(new MethodTarget(CLASSSCOPE_SIG, "buildFieldsAndMethods", "void")) .request(StackRequest.THIS) - .decisionMethod(new Hook("lombok.eclipse.agent.PatchDelegatePortal", "handleDelegateForType", "boolean", "java.lang.Object")) + .decisionMethod(new Hook("lombok.launch.PatchFixesHider$Delegate", "handleDelegateForType", "boolean", "java.lang.Object")) .build()); } @@ -579,24 +572,24 @@ public class EclipsePatcher extends Agent { sm.addScript(ScriptBuilder.wrapReturnValue() .target(new MethodTarget(PARSER_SIG, "consumeExitVariableWithInitialization", "void")) .request(StackRequest.THIS) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchValEclipsePortal", "copyInitializationOfLocalDeclaration", "void", "java.lang.Object")) + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$ValPortal", "copyInitializationOfLocalDeclaration", "void", "java.lang.Object")) .build()); sm.addScript(ScriptBuilder.wrapReturnValue() .target(new MethodTarget(PARSER_SIG, "consumeEnhancedForStatementHeader", "void")) .request(StackRequest.THIS) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchValEclipsePortal", "copyInitializationOfForEachIterable", "void", "java.lang.Object")) + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$ValPortal", "copyInitializationOfForEachIterable", "void", "java.lang.Object")) .build()); sm.addScript(ScriptBuilder.wrapReturnValue() .target(new MethodTarget(ASTCONVERTER_SIG, "setModifiers", "void", VARIABLEDECLARATIONSTATEMENT_SIG, LOCALDECLARATION_SIG)) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchValEclipsePortal", "addFinalAndValAnnotationToVariableDeclarationStatement", + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$ValPortal", "addFinalAndValAnnotationToVariableDeclarationStatement", "void", "java.lang.Object", "java.lang.Object", "java.lang.Object")) .request(StackRequest.THIS, StackRequest.PARAM1, StackRequest.PARAM2).build()); sm.addScript(ScriptBuilder.wrapReturnValue() .target(new MethodTarget(ASTCONVERTER_SIG, "setModifiers", "void", SINGLEVARIABLEDECLARATION_SIG, LOCALDECLARATION_SIG)) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchValEclipsePortal", "addFinalAndValAnnotationToSingleVariableDeclaration", + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$ValPortal", "addFinalAndValAnnotationToSingleVariableDeclaration", "void", "java.lang.Object", "java.lang.Object", "java.lang.Object")) .request(StackRequest.THIS, StackRequest.PARAM1, StackRequest.PARAM2).build()); } @@ -611,26 +604,26 @@ public class EclipsePatcher extends Agent { sm.addScript(ScriptBuilder.exitEarly() .target(new MethodTarget(LOCALDECLARATION_SIG, "resolve", "void", BLOCKSCOPE_SIG)) .request(StackRequest.THIS, StackRequest.PARAM1) - .decisionMethod(new Hook("lombok.eclipse.agent.PatchVal", "handleValForLocalDeclaration", "boolean", LOCALDECLARATION_SIG, BLOCKSCOPE_SIG)) + .decisionMethod(new Hook("lombok.launch.PatchFixesHider$Val", "handleValForLocalDeclaration", "boolean", LOCALDECLARATION_SIG, BLOCKSCOPE_SIG)) .build()); sm.addScript(ScriptBuilder.replaceMethodCall() .target(new MethodTarget(LOCALDECLARATION_SIG, "resolve", "void", BLOCKSCOPE_SIG)) .methodToReplace(new Hook(EXPRESSION_SIG, "resolveType", TYPEBINDING_SIG, BLOCKSCOPE_SIG)) .requestExtra(StackRequest.THIS) - .replacementMethod(new Hook("lombok.eclipse.agent.PatchVal", "skipResolveInitializerIfAlreadyCalled2", TYPEBINDING_SIG, EXPRESSION_SIG, BLOCKSCOPE_SIG, LOCALDECLARATION_SIG)) + .replacementMethod(new Hook("lombok.launch.PatchFixesHider$Val", "skipResolveInitializerIfAlreadyCalled2", TYPEBINDING_SIG, EXPRESSION_SIG, BLOCKSCOPE_SIG, LOCALDECLARATION_SIG)) .build()); sm.addScript(ScriptBuilder.replaceMethodCall() .target(new MethodTarget(FOREACHSTATEMENT_SIG, "resolve", "void", BLOCKSCOPE_SIG)) .methodToReplace(new Hook(EXPRESSION_SIG, "resolveType", TYPEBINDING_SIG, BLOCKSCOPE_SIG)) - .replacementMethod(new Hook("lombok.eclipse.agent.PatchVal", "skipResolveInitializerIfAlreadyCalled", TYPEBINDING_SIG, EXPRESSION_SIG, BLOCKSCOPE_SIG)) + .replacementMethod(new Hook("lombok.launch.PatchFixesHider$Val", "skipResolveInitializerIfAlreadyCalled", TYPEBINDING_SIG, EXPRESSION_SIG, BLOCKSCOPE_SIG)) .build()); sm.addScript(ScriptBuilder.exitEarly() .target(new MethodTarget(FOREACHSTATEMENT_SIG, "resolve", "void", BLOCKSCOPE_SIG)) .request(StackRequest.THIS, StackRequest.PARAM1) - .decisionMethod(new Hook("lombok.eclipse.agent.PatchVal", "handleValForForEach", "boolean", FOREACHSTATEMENT_SIG, BLOCKSCOPE_SIG)) + .decisionMethod(new Hook("lombok.launch.PatchFixesHider$Val", "handleValForForEach", "boolean", FOREACHSTATEMENT_SIG, BLOCKSCOPE_SIG)) .build()); } @@ -641,7 +634,7 @@ public class EclipsePatcher extends Agent { sm.addScript(ScriptBuilder.wrapReturnValue() .target(new MethodTarget(SOURCE_TYPE_CONVERTER_SIG, "convertAnnotations", ANNOTATION_SIG + "[]", I_ANNOTATABLE_SIG)) - .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "convertAnnotations", ANNOTATION_SIG + "[]", ANNOTATION_SIG + "[]", I_ANNOTATABLE_SIG)) + .wrapMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "convertAnnotations", ANNOTATION_SIG + "[]", ANNOTATION_SIG + "[]", I_ANNOTATABLE_SIG)) .request(StackRequest.PARAM1, StackRequest.RETURN_VALUE).build()); } @@ -659,7 +652,7 @@ public class EclipsePatcher extends Agent { } private static void patchExtensionMethod(ScriptManager sm, boolean ecj) { - final String PATCH_EXTENSIONMETHOD = "lombok.eclipse.agent.PatchExtensionMethod"; + final String PATCH_EXTENSIONMETHOD = "lombok.launch.PatchFixesHider$ExtensionMethod"; final String PATCH_EXTENSIONMETHOD_COMPLETIONPROPOSAL_PORTAL = "lombok.eclipse.agent.PatchExtensionMethodCompletionProposalPortal"; final String MESSAGE_SEND_SIG = "org.eclipse.jdt.internal.compiler.ast.MessageSend"; final String TYPE_BINDING_SIG = "org.eclipse.jdt.internal.compiler.lookup.TypeBinding"; diff --git a/src/eclipseAgent/lombok/eclipse/agent/PatchExtensionMethod.java b/src/eclipseAgent/lombok/eclipse/agent/PatchExtensionMethod.java index 8eec27fb..ca0933fb 100644 --- a/src/eclipseAgent/lombok/eclipse/agent/PatchExtensionMethod.java +++ b/src/eclipseAgent/lombok/eclipse/agent/PatchExtensionMethod.java @@ -224,10 +224,14 @@ public class PatchExtensionMethod { if (methodCall.arguments != null) arguments.addAll(Arrays.asList(methodCall.arguments)); List<TypeBinding> argumentTypes = new ArrayList<TypeBinding>(); for (Expression argument : arguments) { - argumentTypes.add(argument.resolvedType); + if (argument.resolvedType != null) argumentTypes.add(argument.resolvedType); + // TODO: Instead of just skipping nulls entirely, there is probably a 'unresolved type' placeholder. THAT is what we ought to be adding here! } + Expression[] originalArgs = methodCall.arguments; + methodCall.arguments = arguments.toArray(new Expression[0]); MethodBinding fixedBinding = scope.getMethod(extensionMethod.declaringClass, methodCall.selector, argumentTypes.toArray(new TypeBinding[0]), methodCall); if (fixedBinding instanceof ProblemMethodBinding) { + methodCall.arguments = originalArgs; if (fixedBinding.declaringClass != null) { scope.problemReporter().invalidMethod(methodCall, fixedBinding); } @@ -246,7 +250,6 @@ public class PatchExtensionMethod { arg.implicitConversion = TypeIds.UNBOXING | (id + (id << 4)); // magic see TypeIds } } - methodCall.arguments = arguments.toArray(new Expression[0]); methodCall.receiver = createNameRef(extensionMethod.declaringClass, methodCall); methodCall.actualReceiverType = extensionMethod.declaringClass; diff --git a/src/eclipseAgent/lombok/eclipse/agent/PatchFixes.java b/src/eclipseAgent/lombok/eclipse/agent/PatchFixes.java deleted file mode 100644 index d1c668a0..00000000 --- a/src/eclipseAgent/lombok/eclipse/agent/PatchFixes.java +++ /dev/null @@ -1,384 +0,0 @@ -/* - * Copyright (C) 2010-2013 The Project Lombok Authors. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package lombok.eclipse.agent; - -import java.io.BufferedOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.List; -import java.util.Stack; - -import lombok.core.DiagnosticsReceiver; -import lombok.core.PostCompiler; -import lombok.core.Version; -import lombok.eclipse.EclipseAugments; - -import org.eclipse.core.runtime.CoreException; -import org.eclipse.jdt.core.IAnnotatable; -import org.eclipse.jdt.core.IAnnotation; -import org.eclipse.jdt.core.IMethod; -import org.eclipse.jdt.core.IType; -import org.eclipse.jdt.core.JavaModelException; -import org.eclipse.jdt.core.dom.MethodDeclaration; -import org.eclipse.jdt.core.dom.SimpleName; -import org.eclipse.jdt.internal.compiler.ast.Annotation; -import org.eclipse.jdt.internal.core.dom.rewrite.NodeRewriteEvent; -import org.eclipse.jdt.internal.core.dom.rewrite.RewriteEvent; -import org.eclipse.jdt.internal.core.dom.rewrite.TokenScanner; -import org.eclipse.jdt.internal.corext.refactoring.structure.ASTNodeSearchUtil; - -public class PatchFixes { - public static String addLombokNotesToEclipseAboutDialog(String origReturnValue, String key) { - if ("aboutText".equals(key)) { - return origReturnValue + "\n\nLombok " + Version.getFullVersion() + " is installed. http://projectlombok.org/"; - } - - return origReturnValue; - } - - public static boolean isGenerated(org.eclipse.jdt.core.dom.ASTNode node) { - boolean result = false; - try { - result = ((Boolean)node.getClass().getField("$isGenerated").get(node)).booleanValue(); - if (!result && node.getParent() != null && node.getParent() instanceof org.eclipse.jdt.core.dom.QualifiedName) - result = isGenerated(node.getParent()); - } catch (Exception e) { - // better to assume it isn't generated - } - return result; - } - - public static boolean isListRewriteOnGeneratedNode(org.eclipse.jdt.core.dom.rewrite.ListRewrite rewrite) { - return isGenerated(rewrite.getParent()); - } - - public static boolean returnFalse(java.lang.Object object) { - return false; - } - - public static boolean returnTrue(java.lang.Object object) { - return true; - } - - @java.lang.SuppressWarnings({"unchecked", "rawtypes"}) public static java.util.List removeGeneratedNodes(java.util.List list) { - try { - java.util.List realNodes = new java.util.ArrayList(list.size()); - for (java.lang.Object node : list) { - if(!isGenerated(((org.eclipse.jdt.core.dom.ASTNode)node))) { - realNodes.add(node); - } - } - return realNodes; - } catch (Exception e) { - } - return list; - } - - public static java.lang.String getRealMethodDeclarationSource(java.lang.String original, Object processor, org.eclipse.jdt.core.dom.MethodDeclaration declaration) throws Exception { - if (!isGenerated(declaration)) return original; - - List<org.eclipse.jdt.core.dom.Annotation> annotations = new ArrayList<org.eclipse.jdt.core.dom.Annotation>(); - for (Object modifier : declaration.modifiers()) { - if (modifier instanceof org.eclipse.jdt.core.dom.Annotation) { - org.eclipse.jdt.core.dom.Annotation annotation = (org.eclipse.jdt.core.dom.Annotation)modifier; - String qualifiedAnnotationName = annotation.resolveTypeBinding().getQualifiedName(); - if (!"java.lang.Override".equals(qualifiedAnnotationName) && !"java.lang.SuppressWarnings".equals(qualifiedAnnotationName)) annotations.add(annotation); - } - } - - StringBuilder signature = new StringBuilder(); - addAnnotations(annotations, signature); - - if ((Boolean)processor.getClass().getDeclaredField("fPublic").get(processor)) signature.append("public "); - if ((Boolean)processor.getClass().getDeclaredField("fAbstract").get(processor)) signature.append("abstract "); - - signature - .append(declaration.getReturnType2().toString()) - .append(" ").append(declaration.getName().getFullyQualifiedName()) - .append("("); - - boolean first = true; - for (Object parameter : declaration.parameters()) { - if (!first) signature.append(", "); - first = false; - // We should also add the annotations of the parameters - signature.append(parameter); - } - - signature.append(");"); - return signature.toString(); - } - - // part of getRealMethodDeclarationSource(...) - public static void addAnnotations(List<org.eclipse.jdt.core.dom.Annotation> annotations, StringBuilder signature) { - /* - * We SHOULD be able to handle the following cases: - * @Override - * @Override() - * @SuppressWarnings("all") - * @SuppressWarnings({"all", "unused"}) - * @SuppressWarnings(value = "all") - * @SuppressWarnings(value = {"all", "unused"}) - * @EqualsAndHashCode(callSuper=true, of="id") - * - * Currently, we only seem to correctly support: - * @Override - * @Override() N.B. We lose the parentheses here, since there are no values. No big deal. - * @SuppressWarnings("all") - */ - for (org.eclipse.jdt.core.dom.Annotation annotation : annotations) { - List<String> values = new ArrayList<String>(); - if (annotation.isSingleMemberAnnotation()) { - org.eclipse.jdt.core.dom.SingleMemberAnnotation smAnn = (org.eclipse.jdt.core.dom.SingleMemberAnnotation) annotation; - values.add(smAnn.getValue().toString()); - } else if (annotation.isNormalAnnotation()) { - org.eclipse.jdt.core.dom.NormalAnnotation normalAnn = (org.eclipse.jdt.core.dom.NormalAnnotation) annotation; - for (Object value : normalAnn.values()) values.add(value.toString()); - } - - signature.append("@").append(annotation.resolveTypeBinding().getQualifiedName()); - if (!values.isEmpty()) { - signature.append("("); - boolean first = true; - for (String string : values) { - if (!first) signature.append(", "); - first = false; - signature.append('"').append(string).append('"'); - } - signature.append(")"); - } - signature.append(" "); - } - } - - public static org.eclipse.jdt.core.dom.MethodDeclaration getRealMethodDeclarationNode(org.eclipse.jdt.core.IMethod sourceMethod, org.eclipse.jdt.core.dom.CompilationUnit cuUnit) throws JavaModelException { - MethodDeclaration methodDeclarationNode = ASTNodeSearchUtil.getMethodDeclarationNode(sourceMethod, cuUnit); - if (isGenerated(methodDeclarationNode)) { - IType declaringType = sourceMethod.getDeclaringType(); - Stack<IType> typeStack = new Stack<IType>(); - while (declaringType != null) { - typeStack.push(declaringType); - declaringType = declaringType.getDeclaringType(); - } - - IType rootType = typeStack.pop(); - org.eclipse.jdt.core.dom.AbstractTypeDeclaration typeDeclaration = findTypeDeclaration(rootType, cuUnit.types()); - while (!typeStack.isEmpty() && typeDeclaration != null) { - typeDeclaration = findTypeDeclaration(typeStack.pop(), typeDeclaration.bodyDeclarations()); - } - - if (typeStack.isEmpty() && typeDeclaration != null) { - String methodName = sourceMethod.getElementName(); - for (Object declaration : typeDeclaration.bodyDeclarations()) { - if (declaration instanceof org.eclipse.jdt.core.dom.MethodDeclaration) { - org.eclipse.jdt.core.dom.MethodDeclaration methodDeclaration = (org.eclipse.jdt.core.dom.MethodDeclaration) declaration; - if (methodDeclaration.getName().toString().equals(methodName)) { - return methodDeclaration; - } - } - } - } - } - return methodDeclarationNode; - } - - // part of getRealMethodDeclarationNode - public static org.eclipse.jdt.core.dom.AbstractTypeDeclaration findTypeDeclaration(IType searchType, List<?> nodes) { - for (Object object : nodes) { - if (object instanceof org.eclipse.jdt.core.dom.AbstractTypeDeclaration) { - org.eclipse.jdt.core.dom.AbstractTypeDeclaration typeDeclaration = (org.eclipse.jdt.core.dom.AbstractTypeDeclaration) object; - if (typeDeclaration.getName().toString().equals(searchType.getElementName())) - return typeDeclaration; - } - } - return null; - } - - public static int getSourceEndFixed(int sourceEnd, org.eclipse.jdt.internal.compiler.ast.ASTNode node) throws Exception { - if (sourceEnd == -1) { - org.eclipse.jdt.internal.compiler.ast.ASTNode object = (org.eclipse.jdt.internal.compiler.ast.ASTNode)node.getClass().getField("$generatedBy").get(node); - if (object != null) { - return object.sourceEnd; - } - } - return sourceEnd; - } - - public static int fixRetrieveStartingCatchPosition(int original, int start) { - return original == -1 ? start : original; - } - - public static int fixRetrieveIdentifierEndPosition(int original, int end) { - return original == -1 ? end : original; - } - - public static int fixRetrieveEllipsisStartPosition(int original, int end) { - return original == -1 ? end : original; - } - - public static int fixRetrieveRightBraceOrSemiColonPosition(int original, int end) { -// return original; - return original == -1 ? end : original; // Need to fix: see issue 325. - } - - public static final int ALREADY_PROCESSED_FLAG = 0x800000; //Bit 24 - - public static boolean checkBit24(Object node) throws Exception { - int bits = (Integer)(node.getClass().getField("bits").get(node)); - return (bits & ALREADY_PROCESSED_FLAG) != 0; - } - - public static boolean skipRewritingGeneratedNodes(org.eclipse.jdt.core.dom.ASTNode node) throws Exception { - return ((Boolean)node.getClass().getField("$isGenerated").get(node)).booleanValue(); - } - - public static void setIsGeneratedFlag(org.eclipse.jdt.core.dom.ASTNode domNode, - org.eclipse.jdt.internal.compiler.ast.ASTNode internalNode) throws Exception { - if (internalNode == null || domNode == null) return; - boolean isGenerated = EclipseAugments.ASTNode_generatedBy.get(internalNode) != null; - if (isGenerated) domNode.getClass().getField("$isGenerated").set(domNode, true); - } - - public static void setIsGeneratedFlagForName(org.eclipse.jdt.core.dom.Name name, Object internalNode) throws Exception { - if (internalNode instanceof org.eclipse.jdt.internal.compiler.ast.ASTNode) { - if (EclipseAugments.ASTNode_generatedBy.get((org.eclipse.jdt.internal.compiler.ast.ASTNode) internalNode) != null) { - name.getClass().getField("$isGenerated").set(name, true); - } - } - } - - public static RewriteEvent[] listRewriteHandleGeneratedMethods(RewriteEvent parent) { - RewriteEvent[] children = parent.getChildren(); - List<RewriteEvent> newChildren = new ArrayList<RewriteEvent>(); - List<RewriteEvent> modifiedChildren = new ArrayList<RewriteEvent>(); - for (int i=0; i<children.length; i++) { - RewriteEvent child = children[i]; - boolean isGenerated = isGenerated( (org.eclipse.jdt.core.dom.ASTNode)child.getOriginalValue() ); - if (isGenerated) { - if ((child.getChangeKind() == RewriteEvent.REPLACED || child.getChangeKind() == RewriteEvent.REMOVED) - && child.getOriginalValue() instanceof org.eclipse.jdt.core.dom.MethodDeclaration - && child.getNewValue() != null - ) { - modifiedChildren.add(new NodeRewriteEvent(null, child.getNewValue())); - } - } else { - newChildren.add(child); - } - } - // Since Eclipse doesn't honor the "insert at specified location" for already existing members, - // we'll just add them last - newChildren.addAll(modifiedChildren); - return newChildren.toArray(new RewriteEvent[newChildren.size()]); - } - - public static int getTokenEndOffsetFixed(TokenScanner scanner, int token, int startOffset, Object domNode) throws CoreException { - boolean isGenerated = false; - try { - isGenerated = (Boolean) domNode.getClass().getField("$isGenerated").get(domNode); - } catch (Exception e) { - // If this fails, better to break some refactor scripts than to crash eclipse. - } - if (isGenerated) return -1; - return scanner.getTokenEndOffset(token, startOffset); - } - - public static IMethod[] removeGeneratedMethods(IMethod[] methods) throws Exception { - List<IMethod> result = new ArrayList<IMethod>(); - for (IMethod m : methods) { - if (m.getNameRange().getLength() > 0 && !m.getNameRange().equals(m.getSourceRange())) result.add(m); - } - return result.size() == methods.length ? methods : result.toArray(new IMethod[result.size()]); - } - - public static SimpleName[] removeGeneratedSimpleNames(SimpleName[] in) throws Exception { - Field f = SimpleName.class.getField("$isGenerated"); - - int count = 0; - for (int i = 0; i < in.length; i++) { - if (in[i] == null || !((Boolean)f.get(in[i])).booleanValue()) count++; - } - if (count == in.length) return in; - SimpleName[] newSimpleNames = new SimpleName[count]; - count = 0; - for (int i = 0; i < in.length; i++) { - if (in[i] == null || !((Boolean)f.get(in[i])).booleanValue()) newSimpleNames[count++] = in[i]; - } - return newSimpleNames; - } - - public static byte[] runPostCompiler(byte[] bytes, String fileName) { - byte[] transformed = PostCompiler.applyTransformations(bytes, fileName, DiagnosticsReceiver.CONSOLE); - return transformed == null ? bytes : transformed; - } - - public static OutputStream runPostCompiler(OutputStream out) throws IOException { - return PostCompiler.wrapOutputStream(out, "TEST", DiagnosticsReceiver.CONSOLE); - } - - public static BufferedOutputStream runPostCompiler(BufferedOutputStream out, String path, String name) throws IOException { - String fileName = path + "/" + name; - return new BufferedOutputStream(PostCompiler.wrapOutputStream(out, fileName, DiagnosticsReceiver.CONSOLE)); - } - - public static Annotation[] convertAnnotations(Annotation[] out, IAnnotatable annotatable) { - IAnnotation[] in; - - try { - in = annotatable.getAnnotations(); - } catch (Exception e) { - return out; - } - - if (out == null) return null; - int toWrite = 0; - - for (int idx = 0; idx < out.length; idx++) { - String oName = new String(out[idx].type.getLastToken()); - boolean found = false; - for (IAnnotation i : in) { - String name = i.getElementName(); - int li = name.lastIndexOf('.'); - if (li > -1) name = name.substring(li + 1); - if (name.equals(oName)) { - found = true; - break; - } - } - if (!found) out[idx] = null; - else toWrite++; - } - - Annotation[] replace = out; - if (toWrite < out.length) { - replace = new Annotation[toWrite]; - int idx = 0; - for (int i = 0; i < out.length; i++) { - if (out[i] == null) continue; - replace[idx++] = out[i]; - } - } - - return replace; - } -} diff --git a/src/eclipseAgent/lombok/eclipse/agent/PatchFixesShadowLoaded.java b/src/eclipseAgent/lombok/eclipse/agent/PatchFixesShadowLoaded.java new file mode 100644 index 00000000..6685b6bb --- /dev/null +++ b/src/eclipseAgent/lombok/eclipse/agent/PatchFixesShadowLoaded.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2015 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.eclipse.agent; + +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +import lombok.core.DiagnosticsReceiver; +import lombok.core.PostCompiler; +import lombok.core.Version; + +public class PatchFixesShadowLoaded { + public static String addLombokNotesToEclipseAboutDialog(String origReturnValue, String key) { + if ("aboutText".equals(key)) { + return origReturnValue + "\n\nLombok " + Version.getFullVersion() + " is installed. http://projectlombok.org/"; + } + return origReturnValue; + } + + public static byte[] runPostCompiler(byte[] bytes, String fileName) { + byte[] transformed = PostCompiler.applyTransformations(bytes, fileName, DiagnosticsReceiver.CONSOLE); + return transformed == null ? bytes : transformed; + } + + public static OutputStream runPostCompiler(OutputStream out) throws IOException { + return PostCompiler.wrapOutputStream(out, "TEST", DiagnosticsReceiver.CONSOLE); + } + + public static BufferedOutputStream runPostCompiler(BufferedOutputStream out, String path, String name) throws IOException { + String fileName = path + "/" + name; + return new BufferedOutputStream(PostCompiler.wrapOutputStream(out, fileName, DiagnosticsReceiver.CONSOLE)); + } +} diff --git a/src/eclipseAgent/lombok/eclipse/agent/PatchVal.java b/src/eclipseAgent/lombok/eclipse/agent/PatchVal.java index e734dceb..30574ea6 100644 --- a/src/eclipseAgent/lombok/eclipse/agent/PatchVal.java +++ b/src/eclipseAgent/lombok/eclipse/agent/PatchVal.java @@ -128,7 +128,14 @@ public class PatchVal { if (!isVal(local.type, scope)) return false; - if (new Throwable().getStackTrace()[2].getClassName().contains("ForStatement")) return false; + StackTraceElement[] st = new Throwable().getStackTrace(); + for (int i = 0; i < st.length - 2 && i < 10; i++) { + if (st[i].getClassName().equals("lombok.launch.PatchFixesHider$Val")) { + if (st[i + 1].getClassName().equals("org.eclipse.jdt.internal.compiler.ast.LocalDeclaration") && + st[i + 2].getClassName().equals("org.eclipse.jdt.internal.compiler.ast.ForStatement")) return false; + break; + } + } Expression init = local.initialization; if (init == null && Reflection.initCopyField != null) { diff --git a/src/eclipseAgent/lombok/launch/PatchFixesHider.java b/src/eclipseAgent/lombok/launch/PatchFixesHider.java new file mode 100644 index 00000000..2472ca3c --- /dev/null +++ b/src/eclipseAgent/lombok/launch/PatchFixesHider.java @@ -0,0 +1,600 @@ +/* + * Copyright (C) 2010-2015 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.launch; + +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.Stack; + +import lombok.eclipse.EclipseAugments; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.jdt.core.IAnnotatable; +import org.eclipse.jdt.core.IAnnotation; +import org.eclipse.jdt.core.IMethod; +import org.eclipse.jdt.core.IType; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.core.dom.MethodDeclaration; +import org.eclipse.jdt.core.dom.SimpleName; +import org.eclipse.jdt.internal.compiler.ast.Annotation; +import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; +import org.eclipse.jdt.internal.compiler.ast.Expression; +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.TypeBinding; +import org.eclipse.jdt.internal.compiler.parser.Parser; +import org.eclipse.jdt.internal.compiler.problem.ProblemReporter; +import org.eclipse.jdt.internal.core.dom.rewrite.NodeRewriteEvent; +import org.eclipse.jdt.internal.core.dom.rewrite.RewriteEvent; +import org.eclipse.jdt.internal.core.dom.rewrite.TokenScanner; +import org.eclipse.jdt.internal.corext.refactoring.structure.ASTNodeSearchUtil; + +/** These contain a mix of the following: + * <ul> + * <li> 'dependency free' method wrappers that cross the shadowloader barrier. + * <li> methods that directly patch, <em>but</em>, these should ALWAYS be transplanted. + * </ul> + * + * <strong>This class lives on the outside of the shadowloader barrier, and as a consequence, cannot access any other lombok code except other + * code in the {@code lombok.launch} package!</strong>. + * <p> + * This class is package private with lots of public inner static classes. This hides all of them from IDE autocomplete dialogs and such but at the JVM + * level the inner static class are just plain public, which is important, because calls to the contents of these inner static classes are injected into + * various eclipse classes verbatim, and if they weren't public, the verifier wouldn't accept it. + */ +final class PatchFixesHider { + + /** These utility methods are only used 'internally', but because of transplant methods, the class (and its methods) still have to be public! */ + public static final class Util { + private static ClassLoader shadowLoader; + + public static Class<?> shadowLoadClass(String name) { + try { + if (shadowLoader == null) { + try { + Class.forName("lombok.core.LombokNode"); + // If we get here, then lombok is already available. + shadowLoader = Util.class.getClassLoader(); + } catch (ClassNotFoundException e) { + // If we get here, it isn't, and we should use the shadowloader. + shadowLoader = Main.createShadowClassLoader(); + } + } + + return Class.forName(name, true, shadowLoader); + } catch (ClassNotFoundException e) { + throw sneakyThrow(e); + } + } + + public static Method findMethod(Class<?> type, String name, Class<?>... parameterTypes) { + try { + return type.getDeclaredMethod(name, parameterTypes); + } catch (NoSuchMethodException e) { + throw sneakyThrow(e); + } + } + + public static Object invokeMethod(Method method, Object... args) { + try { + return method.invoke(null, args); + } catch (IllegalAccessException e) { + throw sneakyThrow(e); + } catch (InvocationTargetException e) { + throw sneakyThrow(e.getCause()); + } + } + + private static RuntimeException sneakyThrow(Throwable t) { + if (t == null) throw new NullPointerException("t"); + Util.<RuntimeException>sneakyThrow0(t); + return null; + } + + @SuppressWarnings("unchecked") + private static <T extends Throwable> void sneakyThrow0(Throwable t) throws T { + throw (T)t; + } + } + + /** Contains patch fixes that are dependent on lombok internals. */ + public static final class LombokDeps { + public static final Method ADD_LOMBOK_NOTES; + public static final Method POST_COMPILER_BYTES_STRING; + public static final Method POST_COMPILER_OUTPUTSTREAM; + public static final Method POST_COMPILER_BUFFEREDOUTPUTSTREAM_STRING_STRING; + + static { + Class<?> shadowed = Util.shadowLoadClass("lombok.eclipse.agent.PatchFixesShadowLoaded"); + ADD_LOMBOK_NOTES = Util.findMethod(shadowed, "addLombokNotesToEclipseAboutDialog", String.class, String.class); + POST_COMPILER_BYTES_STRING = Util.findMethod(shadowed, "runPostCompiler", byte[].class, String.class); + POST_COMPILER_OUTPUTSTREAM = Util.findMethod(shadowed, "runPostCompiler", OutputStream.class); + POST_COMPILER_BUFFEREDOUTPUTSTREAM_STRING_STRING = Util.findMethod(shadowed, "runPostCompiler", BufferedOutputStream.class, String.class, String.class); + } + + public static String addLombokNotesToEclipseAboutDialog(String origReturnValue, String key) { + return (String) Util.invokeMethod(LombokDeps.ADD_LOMBOK_NOTES, origReturnValue, key); + } + + public static byte[] runPostCompiler(byte[] bytes, String fileName) { + return (byte[]) Util.invokeMethod(LombokDeps.POST_COMPILER_BYTES_STRING, bytes, fileName); + } + + public static OutputStream runPostCompiler(OutputStream out) throws IOException { + return (OutputStream) Util.invokeMethod(LombokDeps.POST_COMPILER_OUTPUTSTREAM, out); + } + + public static BufferedOutputStream runPostCompiler(BufferedOutputStream out, String path, String name) throws IOException { + return (BufferedOutputStream) Util.invokeMethod(LombokDeps.POST_COMPILER_BUFFEREDOUTPUTSTREAM_STRING_STRING, out, path, name); + } + } + + public static final class Transform { + private static final Method TRANSFORM; + private static final Method TRANSFORM_SWAPPED; + + static { + Class<?> shadowed = Util.shadowLoadClass("lombok.eclipse.TransformEclipseAST"); + TRANSFORM = Util.findMethod(shadowed, "transform", Parser.class, CompilationUnitDeclaration.class); + TRANSFORM_SWAPPED = Util.findMethod(shadowed, "transform_swapped", CompilationUnitDeclaration.class, Parser.class); + } + + public static void transform(Parser parser, CompilationUnitDeclaration ast) throws IOException { + Util.invokeMethod(TRANSFORM, parser, ast); + } + + public static void transform_swapped(CompilationUnitDeclaration ast, Parser parser) throws IOException { + Util.invokeMethod(TRANSFORM_SWAPPED, ast, parser); + } + } + + /** Contains patch code to support {@code @Delegate} */ + public static final class Delegate { + private static final Method HANDLE_DELEGATE_FOR_TYPE; + + static { + Class<?> shadowed = Util.shadowLoadClass("lombok.eclipse.agent.PatchDelegatePortal"); + HANDLE_DELEGATE_FOR_TYPE = Util.findMethod(shadowed, "handleDelegateForType", Object.class); + } + + public static boolean handleDelegateForType(Object classScope) { + return (Boolean) Util.invokeMethod(HANDLE_DELEGATE_FOR_TYPE, classScope); + } + } + + /** Contains patch code to support {@code val} (eclipse specific) */ + public static final class ValPortal { + private static final Method COPY_INITIALIZATION_OF_FOR_EACH_ITERABLE; + private static final Method COPY_INITIALIZATION_OF_LOCAL_DECLARATION; + private static final Method ADD_FINAL_AND_VAL_ANNOTATION_TO_VARIABLE_DECLARATION_STATEMENT; + private static final Method ADD_FINAL_AND_VAL_ANNOTATION_TO_SINGLE_VARIABLE_DECLARATION; + + static { + Class<?> shadowed = Util.shadowLoadClass("lombok.eclipse.agent.PatchValEclipsePortal"); + COPY_INITIALIZATION_OF_FOR_EACH_ITERABLE = Util.findMethod(shadowed, "copyInitializationOfForEachIterable", Object.class); + COPY_INITIALIZATION_OF_LOCAL_DECLARATION = Util.findMethod(shadowed, "copyInitializationOfLocalDeclaration", Object.class); + ADD_FINAL_AND_VAL_ANNOTATION_TO_VARIABLE_DECLARATION_STATEMENT = Util.findMethod(shadowed, "addFinalAndValAnnotationToVariableDeclarationStatement", Object.class, Object.class, Object.class); + ADD_FINAL_AND_VAL_ANNOTATION_TO_SINGLE_VARIABLE_DECLARATION = Util.findMethod(shadowed, "addFinalAndValAnnotationToSingleVariableDeclaration", Object.class, Object.class, Object.class); + } + + public static void copyInitializationOfForEachIterable(Object parser) { + Util.invokeMethod(COPY_INITIALIZATION_OF_FOR_EACH_ITERABLE, parser); + } + + public static void copyInitializationOfLocalDeclaration(Object parser) { + Util.invokeMethod(COPY_INITIALIZATION_OF_LOCAL_DECLARATION, parser); + } + + public static void addFinalAndValAnnotationToVariableDeclarationStatement(Object converter, Object out, Object in) { + Util.invokeMethod(ADD_FINAL_AND_VAL_ANNOTATION_TO_VARIABLE_DECLARATION_STATEMENT, converter, out, in); + } + + public static void addFinalAndValAnnotationToSingleVariableDeclaration(Object converter, Object out, Object in) { + Util.invokeMethod(ADD_FINAL_AND_VAL_ANNOTATION_TO_SINGLE_VARIABLE_DECLARATION, converter, out, in); + } + } + + /** Contains patch code to support {@code val} (eclipse and ecj) */ + public static final class Val { + private static final Method SKIP_RESOLVE_INITIALIZER_IF_ALREADY_CALLED; + private static final Method SKIP_RESOLVE_INITIALIZER_IF_ALREADY_CALLED2; + private static final Method HANDLE_VAL_FOR_LOCAL_DECLARATION; + private static final Method HANDLE_VAL_FOR_FOR_EACH; + + static { + Class<?> shadowed = Util.shadowLoadClass("lombok.eclipse.agent.PatchVal"); + SKIP_RESOLVE_INITIALIZER_IF_ALREADY_CALLED = Util.findMethod(shadowed, "skipResolveInitializerIfAlreadyCalled", Expression.class, BlockScope.class); + SKIP_RESOLVE_INITIALIZER_IF_ALREADY_CALLED2 = Util.findMethod(shadowed, "skipResolveInitializerIfAlreadyCalled2", Expression.class, BlockScope.class, LocalDeclaration.class); + HANDLE_VAL_FOR_LOCAL_DECLARATION = Util.findMethod(shadowed, "handleValForLocalDeclaration", LocalDeclaration.class, BlockScope.class); + HANDLE_VAL_FOR_FOR_EACH = Util.findMethod(shadowed, "handleValForForEach", ForeachStatement.class, BlockScope.class); + } + + public static TypeBinding skipResolveInitializerIfAlreadyCalled(Expression expr, BlockScope scope) { + return (TypeBinding) Util.invokeMethod(SKIP_RESOLVE_INITIALIZER_IF_ALREADY_CALLED, expr, scope); + } + + public static TypeBinding skipResolveInitializerIfAlreadyCalled2(Expression expr, BlockScope scope, LocalDeclaration decl) { + return (TypeBinding) Util.invokeMethod(SKIP_RESOLVE_INITIALIZER_IF_ALREADY_CALLED2, expr, scope, decl); + } + + public static boolean handleValForLocalDeclaration(LocalDeclaration local, BlockScope scope) { + return (Boolean) Util.invokeMethod(HANDLE_VAL_FOR_LOCAL_DECLARATION, local, scope); + } + + public static boolean handleValForForEach(ForeachStatement forEach, BlockScope scope) { + return (Boolean) Util.invokeMethod(HANDLE_VAL_FOR_FOR_EACH, forEach, scope); + } + } + + /** Contains patch code to support {@code @ExtensionMethod} */ + public static final class ExtensionMethod { + private static final Method RESOLVE_TYPE; + private static final Method ERROR_NO_METHOD_FOR; + private static final Method INVALID_METHOD; + + 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); + } + + public static TypeBinding resolveType(TypeBinding resolvedType, MessageSend methodCall, BlockScope scope) { + return (TypeBinding) Util.invokeMethod(RESOLVE_TYPE, resolvedType, methodCall, scope); + } + + public static void errorNoMethodFor(ProblemReporter problemReporter, MessageSend messageSend, TypeBinding recType, TypeBinding[] params) { + Util.invokeMethod(ERROR_NO_METHOD_FOR, problemReporter, messageSend, recType, params); + } + + public static void invalidMethod(ProblemReporter problemReporter, MessageSend messageSend, MethodBinding method) { + Util.invokeMethod(INVALID_METHOD, problemReporter, messageSend, method); + } + } + + /** + * Contains a mix of methods: ecj only, ecj+eclipse, and eclipse only. As a consequence, _EVERY_ method from here used for ecj MUST be + * transplanted, as ecj itself cannot load this class (signatures refer to things that don't exist in ecj-only mode). + * <p> + * Because of usage of transplant(), a bunch of these contain direct code and don't try to cross the shadowloader barrier. + */ + public static final class PatchFixes { + public static boolean isGenerated(org.eclipse.jdt.core.dom.ASTNode node) { + boolean result = false; + try { + result = ((Boolean)node.getClass().getField("$isGenerated").get(node)).booleanValue(); + if (!result && node.getParent() != null && node.getParent() instanceof org.eclipse.jdt.core.dom.QualifiedName) { + result = isGenerated(node.getParent()); + } + } catch (Exception e) { + // better to assume it isn't generated + } + return result; + } + + public static boolean isListRewriteOnGeneratedNode(org.eclipse.jdt.core.dom.rewrite.ListRewrite rewrite) { + return isGenerated(rewrite.getParent()); + } + + public static boolean returnFalse(java.lang.Object object) { + return false; + } + + public static boolean returnTrue(java.lang.Object object) { + return true; + } + + @java.lang.SuppressWarnings({"unchecked", "rawtypes"}) public static java.util.List removeGeneratedNodes(java.util.List list) { + try { + java.util.List realNodes = new java.util.ArrayList(list.size()); + for (java.lang.Object node : list) { + if(!isGenerated(((org.eclipse.jdt.core.dom.ASTNode)node))) { + realNodes.add(node); + } + } + return realNodes; + } catch (Exception e) { + } + return list; + } + + public static java.lang.String getRealMethodDeclarationSource(java.lang.String original, Object processor, org.eclipse.jdt.core.dom.MethodDeclaration declaration) throws Exception { + if (!isGenerated(declaration)) return original; + + List<org.eclipse.jdt.core.dom.Annotation> annotations = new ArrayList<org.eclipse.jdt.core.dom.Annotation>(); + for (Object modifier : declaration.modifiers()) { + if (modifier instanceof org.eclipse.jdt.core.dom.Annotation) { + org.eclipse.jdt.core.dom.Annotation annotation = (org.eclipse.jdt.core.dom.Annotation)modifier; + String qualifiedAnnotationName = annotation.resolveTypeBinding().getQualifiedName(); + if (!"java.lang.Override".equals(qualifiedAnnotationName) && !"java.lang.SuppressWarnings".equals(qualifiedAnnotationName)) annotations.add(annotation); + } + } + + StringBuilder signature = new StringBuilder(); + addAnnotations(annotations, signature); + + if ((Boolean)processor.getClass().getDeclaredField("fPublic").get(processor)) signature.append("public "); + if ((Boolean)processor.getClass().getDeclaredField("fAbstract").get(processor)) signature.append("abstract "); + + signature + .append(declaration.getReturnType2().toString()) + .append(" ").append(declaration.getName().getFullyQualifiedName()) + .append("("); + + boolean first = true; + for (Object parameter : declaration.parameters()) { + if (!first) signature.append(", "); + first = false; + // We should also add the annotations of the parameters + signature.append(parameter); + } + + signature.append(");"); + return signature.toString(); + } + + // part of getRealMethodDeclarationSource(...) + public static void addAnnotations(List<org.eclipse.jdt.core.dom.Annotation> annotations, StringBuilder signature) { + /* + * We SHOULD be able to handle the following cases: + * @Override + * @Override() + * @SuppressWarnings("all") + * @SuppressWarnings({"all", "unused"}) + * @SuppressWarnings(value = "all") + * @SuppressWarnings(value = {"all", "unused"}) + * @EqualsAndHashCode(callSuper=true, of="id") + * + * Currently, we only seem to correctly support: + * @Override + * @Override() N.B. We lose the parentheses here, since there are no values. No big deal. + * @SuppressWarnings("all") + */ + for (org.eclipse.jdt.core.dom.Annotation annotation : annotations) { + List<String> values = new ArrayList<String>(); + if (annotation.isSingleMemberAnnotation()) { + org.eclipse.jdt.core.dom.SingleMemberAnnotation smAnn = (org.eclipse.jdt.core.dom.SingleMemberAnnotation) annotation; + values.add(smAnn.getValue().toString()); + } else if (annotation.isNormalAnnotation()) { + org.eclipse.jdt.core.dom.NormalAnnotation normalAnn = (org.eclipse.jdt.core.dom.NormalAnnotation) annotation; + for (Object value : normalAnn.values()) values.add(value.toString()); + } + + signature.append("@").append(annotation.resolveTypeBinding().getQualifiedName()); + if (!values.isEmpty()) { + signature.append("("); + boolean first = true; + for (String string : values) { + if (!first) signature.append(", "); + first = false; + signature.append('"').append(string).append('"'); + } + signature.append(")"); + } + signature.append(" "); + } + } + + public static org.eclipse.jdt.core.dom.MethodDeclaration getRealMethodDeclarationNode(org.eclipse.jdt.core.IMethod sourceMethod, org.eclipse.jdt.core.dom.CompilationUnit cuUnit) throws JavaModelException { + MethodDeclaration methodDeclarationNode = ASTNodeSearchUtil.getMethodDeclarationNode(sourceMethod, cuUnit); + if (isGenerated(methodDeclarationNode)) { + IType declaringType = sourceMethod.getDeclaringType(); + Stack<IType> typeStack = new Stack<IType>(); + while (declaringType != null) { + typeStack.push(declaringType); + declaringType = declaringType.getDeclaringType(); + } + + IType rootType = typeStack.pop(); + org.eclipse.jdt.core.dom.AbstractTypeDeclaration typeDeclaration = findTypeDeclaration(rootType, cuUnit.types()); + while (!typeStack.isEmpty() && typeDeclaration != null) { + typeDeclaration = findTypeDeclaration(typeStack.pop(), typeDeclaration.bodyDeclarations()); + } + + if (typeStack.isEmpty() && typeDeclaration != null) { + String methodName = sourceMethod.getElementName(); + for (Object declaration : typeDeclaration.bodyDeclarations()) { + if (declaration instanceof org.eclipse.jdt.core.dom.MethodDeclaration) { + org.eclipse.jdt.core.dom.MethodDeclaration methodDeclaration = (org.eclipse.jdt.core.dom.MethodDeclaration) declaration; + if (methodDeclaration.getName().toString().equals(methodName)) { + return methodDeclaration; + } + } + } + } + } + return methodDeclarationNode; + } + + // part of getRealMethodDeclarationNode + public static org.eclipse.jdt.core.dom.AbstractTypeDeclaration findTypeDeclaration(IType searchType, List<?> nodes) { + for (Object object : nodes) { + if (object instanceof org.eclipse.jdt.core.dom.AbstractTypeDeclaration) { + org.eclipse.jdt.core.dom.AbstractTypeDeclaration typeDeclaration = (org.eclipse.jdt.core.dom.AbstractTypeDeclaration) object; + if (typeDeclaration.getName().toString().equals(searchType.getElementName())) + return typeDeclaration; + } + } + return null; + } + + public static int getSourceEndFixed(int sourceEnd, org.eclipse.jdt.internal.compiler.ast.ASTNode node) throws Exception { + if (sourceEnd == -1) { + org.eclipse.jdt.internal.compiler.ast.ASTNode object = (org.eclipse.jdt.internal.compiler.ast.ASTNode)node.getClass().getField("$generatedBy").get(node); + if (object != null) { + return object.sourceEnd; + } + } + return sourceEnd; + } + + public static int fixRetrieveStartingCatchPosition(int original, int start) { + return original == -1 ? start : original; + } + + public static int fixRetrieveIdentifierEndPosition(int original, int end) { + return original == -1 ? end : original; + } + + public static int fixRetrieveEllipsisStartPosition(int original, int end) { + return original == -1 ? end : original; + } + + public static int fixRetrieveRightBraceOrSemiColonPosition(int original, int end) { + return original == -1 ? end : original; // Need to fix: see issue 325. + } + + public static final int ALREADY_PROCESSED_FLAG = 0x800000; //Bit 24 + + public static boolean checkBit24(Object node) throws Exception { + int bits = (Integer)(node.getClass().getField("bits").get(node)); + return (bits & ALREADY_PROCESSED_FLAG) != 0; + } + + public static boolean skipRewritingGeneratedNodes(org.eclipse.jdt.core.dom.ASTNode node) throws Exception { + return ((Boolean) node.getClass().getField("$isGenerated").get(node)).booleanValue(); + } + + public static void setIsGeneratedFlag(org.eclipse.jdt.core.dom.ASTNode domNode, + org.eclipse.jdt.internal.compiler.ast.ASTNode internalNode) throws Exception { + + if (internalNode == null || domNode == null) return; + boolean isGenerated = EclipseAugments.ASTNode_generatedBy.get(internalNode) != null; + if (isGenerated) domNode.getClass().getField("$isGenerated").set(domNode, true); + } + + public static void setIsGeneratedFlagForName(org.eclipse.jdt.core.dom.Name name, Object internalNode) throws Exception { + if (internalNode instanceof org.eclipse.jdt.internal.compiler.ast.ASTNode) { + boolean isGenerated = EclipseAugments.ASTNode_generatedBy.get((org.eclipse.jdt.internal.compiler.ast.ASTNode) internalNode) != null; + if (isGenerated) name.getClass().getField("$isGenerated").set(name, true); + } + } + + public static RewriteEvent[] listRewriteHandleGeneratedMethods(RewriteEvent parent) { + RewriteEvent[] children = parent.getChildren(); + List<RewriteEvent> newChildren = new ArrayList<RewriteEvent>(); + List<RewriteEvent> modifiedChildren = new ArrayList<RewriteEvent>(); + for (int i = 0; i < children.length; i++) { + RewriteEvent child = children[i]; + boolean isGenerated = isGenerated((org.eclipse.jdt.core.dom.ASTNode) child.getOriginalValue()); + if (isGenerated) { + boolean isReplacedOrRemoved = child.getChangeKind() == RewriteEvent.REPLACED || child.getChangeKind() == RewriteEvent.REMOVED; + boolean convertingFromMethod = child.getOriginalValue() instanceof org.eclipse.jdt.core.dom.MethodDeclaration; + if (isReplacedOrRemoved && convertingFromMethod && child.getNewValue() != null) { + modifiedChildren.add(new NodeRewriteEvent(null, child.getNewValue())); + } + } else { + newChildren.add(child); + } + } + // Since Eclipse doesn't honor the "insert at specified location" for already existing members, + // we'll just add them last + newChildren.addAll(modifiedChildren); + return newChildren.toArray(new RewriteEvent[newChildren.size()]); + } + + public static int getTokenEndOffsetFixed(TokenScanner scanner, int token, int startOffset, Object domNode) throws CoreException { + boolean isGenerated = false; + try { + isGenerated = (Boolean) domNode.getClass().getField("$isGenerated").get(domNode); + } catch (Exception e) { + // If this fails, better to break some refactor scripts than to crash eclipse. + } + if (isGenerated) return -1; + return scanner.getTokenEndOffset(token, startOffset); + } + + public static IMethod[] removeGeneratedMethods(IMethod[] methods) throws Exception { + List<IMethod> result = new ArrayList<IMethod>(); + for (IMethod m : methods) { + if (m.getNameRange().getLength() > 0 && !m.getNameRange().equals(m.getSourceRange())) result.add(m); + } + return result.size() == methods.length ? methods : result.toArray(new IMethod[result.size()]); + } + + public static SimpleName[] removeGeneratedSimpleNames(SimpleName[] in) throws Exception { + Field f = SimpleName.class.getField("$isGenerated"); + + int count = 0; + for (int i = 0; i < in.length; i++) { + if (in[i] == null || !((Boolean)f.get(in[i])).booleanValue()) count++; + } + if (count == in.length) return in; + SimpleName[] newSimpleNames = new SimpleName[count]; + count = 0; + for (int i = 0; i < in.length; i++) { + if (in[i] == null || !((Boolean)f.get(in[i])).booleanValue()) newSimpleNames[count++] = in[i]; + } + return newSimpleNames; + } + + public static Annotation[] convertAnnotations(Annotation[] out, IAnnotatable annotatable) { + IAnnotation[] in; + + try { + in = annotatable.getAnnotations(); + } catch (Exception e) { + return out; + } + + if (out == null) return null; + int toWrite = 0; + + for (int idx = 0; idx < out.length; idx++) { + String oName = new String(out[idx].type.getLastToken()); + boolean found = false; + for (IAnnotation i : in) { + String name = i.getElementName(); + int li = name.lastIndexOf('.'); + if (li > -1) name = name.substring(li + 1); + if (name.equals(oName)) { + found = true; + break; + } + } + if (!found) out[idx] = null; + else toWrite++; + } + + Annotation[] replace = out; + if (toWrite < out.length) { + replace = new Annotation[toWrite]; + int idx = 0; + for (int i = 0; i < out.length; i++) { + if (out[i] == null) continue; + replace[idx++] = out[i]; + } + } + + return replace; + } + } +} diff --git a/src/installer/lombok/installer/InstallerGUI.java b/src/installer/lombok/installer/InstallerGUI.java index c33aed74..6b8a58ab 100644 --- a/src/installer/lombok/installer/InstallerGUI.java +++ b/src/installer/lombok/installer/InstallerGUI.java @@ -821,9 +821,8 @@ public class InstallerGUI { private static final String HOW_I_WORK_EXPLANATION = "<html><h2>Eclipse</h2><ol>" + "<li>First, I copy myself (lombok.jar) to your Eclipse install directory.</li>" + - "<li>Then, I edit the <i>eclipse.ini</i> file to add the following two entries:<br>" + - "<pre>-Xbootclasspath/a:lombok.jar<br>" + - "-javaagent:lombok.jar</pre></li></ol>" + + "<li>Then, I edit the <i>eclipse.ini</i> file to add the following entry:<br>" + + "<pre>-javaagent:lombok.jar</pre></li></ol>" + "On Mac OS X, eclipse.ini is hidden in<br>" + "<code>Eclipse.app/Contents/MacOS</code> so that's where I place the jar files.</html>"; diff --git a/src/installer/lombok/installer/eclipse/EclipseLocation.java b/src/installer/lombok/installer/eclipse/EclipseLocation.java index e347cd98..0fe60c05 100644 --- a/src/installer/lombok/installer/eclipse/EclipseLocation.java +++ b/src/installer/lombok/installer/eclipse/EclipseLocation.java @@ -337,8 +337,6 @@ public class EclipseLocation extends IdeLocation { newContents.append(String.format( "-javaagent:%s", escapePath(fullPathToLombok + "lombok.jar"))).append(OS_NEWLINE); - newContents.append(String.format( - "-Xbootclasspath/a:%s", escapePath(fullPathToLombok + "lombok.jar"))).append(OS_NEWLINE); FileOutputStream fos = new FileOutputStream(eclipseIniPath); try { @@ -360,8 +358,7 @@ public class EclipseLocation extends IdeLocation { } return "If you start " + getTypeName() + " with a custom -vm parameter, you'll need to add:<br>" + - "<code>-vmargs -Xbootclasspath/a:lombok.jar -javaagent:lombok.jar</code><br>" + - "as parameter as well."; + "<code>-vmargs -javaagent:lombok.jar</code><br>as parameter as well."; } @Override public URL getIdeIcon() { diff --git a/src/launch/lombok/launch/Agent.java b/src/launch/lombok/launch/Agent.java new file mode 100644 index 00000000..7989e51f --- /dev/null +++ b/src/launch/lombok/launch/Agent.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2014 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.launch; + +import java.lang.instrument.Instrumentation; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +final class Agent { + public static void agentmain(String agentArgs, Instrumentation instrumentation) throws Throwable { + runLauncher(agentArgs, instrumentation, true); + } + + public static void premain(String agentArgs, Instrumentation instrumentation) throws Throwable { + runLauncher(agentArgs, instrumentation, false); + } + + private static void runLauncher(String agentArgs, Instrumentation instrumentation, boolean injected) throws Throwable { + ClassLoader cl = Main.createShadowClassLoader(); + try { + Class<?> c = cl.loadClass("lombok.core.AgentLauncher"); + Method m = c.getDeclaredMethod("runAgents", String.class, Instrumentation.class, boolean.class, Class.class); + m.invoke(null, agentArgs, instrumentation, injected, Agent.class); + } catch (InvocationTargetException e) { + throw e.getCause(); + } + } +} diff --git a/src/launch/lombok/launch/AnnotationProcessor.java b/src/launch/lombok/launch/AnnotationProcessor.java new file mode 100644 index 00000000..35c26b7c --- /dev/null +++ b/src/launch/lombok/launch/AnnotationProcessor.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2014 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.launch; + +import java.util.Set; + +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.Completion; +import javax.annotation.processing.ProcessingEnvironment; +import javax.annotation.processing.RoundEnvironment; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; + +class AnnotationProcessorHider { + public static class AnnotationProcessor extends AbstractProcessor { + private final AbstractProcessor instance = createWrappedInstance(); + + @Override public Set<String> getSupportedOptions() { + return instance.getSupportedOptions(); + } + + @Override public Set<String> getSupportedAnnotationTypes() { + return instance.getSupportedAnnotationTypes(); + } + + @Override public SourceVersion getSupportedSourceVersion() { + return instance.getSupportedSourceVersion(); + } + + @Override public void init(ProcessingEnvironment processingEnv) { + instance.init(processingEnv); + super.init(processingEnv); + } + + @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { + return instance.process(annotations, roundEnv); + } + + @Override public Iterable<? extends Completion> getCompletions(Element element, AnnotationMirror annotation, ExecutableElement member, String userText) { + return instance.getCompletions(element, annotation, member, userText); + } + + private static AbstractProcessor createWrappedInstance() { + ClassLoader cl = Main.createShadowClassLoader(); + try { + Class<?> mc = cl.loadClass("lombok.core.AnnotationProcessor"); + return (AbstractProcessor) mc.newInstance(); + } catch (Throwable t) { + if (t instanceof Error) throw (Error) t; + if (t instanceof RuntimeException) throw (RuntimeException) t; + throw new RuntimeException(t); + } + } + } +} diff --git a/src/launch/lombok/launch/Main.java b/src/launch/lombok/launch/Main.java new file mode 100644 index 00000000..63d97d48 --- /dev/null +++ b/src/launch/lombok/launch/Main.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2014 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.launch; + +import java.lang.reflect.InvocationTargetException; + +class Main { + static ClassLoader createShadowClassLoader() { + return new ShadowClassLoader(Main.class.getClassLoader(), "lombok"); + } + + public static void main(String[] args) throws Throwable { + ClassLoader cl = createShadowClassLoader(); + Class<?> mc = cl.loadClass("lombok.core.Main"); + try { + mc.getMethod("main", String[].class).invoke(null, new Object[] {args}); + } catch (InvocationTargetException e) { + throw e.getCause(); + } + } +} diff --git a/src/launch/lombok/launch/ShadowClassLoader.java b/src/launch/lombok/launch/ShadowClassLoader.java new file mode 100644 index 00000000..b883bd71 --- /dev/null +++ b/src/launch/lombok/launch/ShadowClassLoader.java @@ -0,0 +1,401 @@ +/* + * Copyright (C) 2014 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.launch; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Vector; +import java.util.WeakHashMap; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +/** + * The shadow classloader serves to completely hide almost all classes in a given jar file by using a different file ending. + * + * The shadow classloader also serves to link in a project as it is being developed (a 'bin' dir from an IDE for example). + * <p> + * Classes loaded by the shadowloader use ".SCL.<em>sclSuffix</em>" in addition to ".class". In other words, most of the class files in a given jar end in this suffix, which + * serves to hide them from any tool that isn't aware of the suffix (such as IDEs generating auto-complete dialogs, and javac's classpath in general). Only shadowloader can actually + * load these classes. + * <p> + * The shadowloader will pick up an alternate (priority) classpath, using normal class files, from the system property "<code>shadow.override.<em>sclSuffix</em></code>". + * This shadow classpath looks just like a normal java classpath; the path separator is applied (semi-colon on windows, colon elsewhere), and entries can consist of directories, + * jar files, or directories ending in "/*" to pick up all jars inside it. + * <p> + * Load order is as follows if at least one override is present: + * <li>First, if the resource is found in one of the paths stated in the shadow classpath, find that. + * <li>Next, ask the <code>parent</code> loader, which is passed during construction of the ShadowClassLoader. + * <li>Notably, this jar's contents are always skipped! (The idea of the shadow classpath is that this jar only functions as a launcher, not as a source of your actual application). + * </ul> + * + * If no overrides are present, the load order is as follows: + * <li>First, if the resource is found in our own jar (trying ".SCL.<em>sclSuffix</em>" first for any resource request ending in ".class"), return that. + * <li>Next, ask the <code>parent</code> loader. + * </ul> + * + * Use ShadowClassLoader to accomplish the following things:<ul> + * <li>Avoid contaminating the namespace of any project using an SCL-based jar. Autocompleters in IDEs will NOT suggest anything other than actual public API. + * <li>Like jarjar, allows folding in dependencies such as ASM without foisting these dependencies on projects that use this jar. shadowloader obviates the need for jarjar. + * <li>Allows an agent (which MUST be in jar form) to still load everything except this loader infrastructure from class files generated by the IDE, which should + * considerably help debugging, as you can now rely on the IDE's built-in auto-recompile features instead of having to run a full build everytime, and it should help + * with hot code replace and the like (this is what the {@code shadow.override} feature is for). + * </ul> + * + * Implementation note: {@code lombok.eclipse.agent.EclipseLoaderPatcher} <em>relies</em> on this class having no dependencies on any other class except the JVM boot class, notably + * including any other classes in this package, <strong>including</strong> inner classes. So, don't write closures, anonymous inner class literals, + * enums, or anything else that could cause the compilation of this file to produce more than 1 class file. In general, actually passing load control to this loader is a bit tricky + * so ensure that this class has zero dependencies on anything except java core classes. + */ +class ShadowClassLoader extends ClassLoader { + private static final String SELF_NAME = "lombok/launch/ShadowClassLoader.class"; + private final String SELF_BASE; + private final File SELF_BASE_FILE; + private final int SELF_BASE_LENGTH; + + private final List<File> override = new ArrayList<File>(); + private final String sclSuffix; + private final List<String> parentExclusion = new ArrayList<String>(); + + /** + * 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); + } + + /** + * @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). + */ + ShadowClassLoader(ClassLoader source, String sclSuffix, String selfBase, String... parentExclusion) { + super(source); + this.sclSuffix = sclSuffix; + if (parentExclusion != null) for (String pe : parentExclusion) { + pe = pe.replace(".", "/"); + if (!pe.endsWith("/")) pe = pe + "/"; + this.parentExclusion.add(pe); + } + + if (selfBase != null) { + SELF_BASE = selfBase; + SELF_BASE_LENGTH = selfBase.length(); + } else { + String sclClassUrl = ShadowClassLoader.class.getResource("ShadowClassLoader.class").toString(); + if (!sclClassUrl.endsWith(SELF_NAME)) throw new InternalError("ShadowLoader can't find itself."); + SELF_BASE_LENGTH = sclClassUrl.length() - SELF_NAME.length(); + SELF_BASE = sclClassUrl.substring(0, SELF_BASE_LENGTH); + } + + if (SELF_BASE.startsWith("jar:file:") && SELF_BASE.endsWith("!/")) SELF_BASE_FILE = new File(SELF_BASE.substring(9, SELF_BASE.length() - 2)); + else if (SELF_BASE.startsWith("file:")) SELF_BASE_FILE = new File(SELF_BASE.substring(5)); + else SELF_BASE_FILE = new File(SELF_BASE); + String scl = System.getProperty("shadow.override." + sclSuffix); + if (scl != null && !scl.isEmpty()) { + for (String part : scl.split("\\s*" + (File.pathSeparatorChar == ';' ? ";" : ":") + "\\s*")) { + if (part.endsWith("/*") || part.endsWith(File.separator + "*")) { + addOverrideJarDir(part.substring(0, part.length() - 2)); + } else { + addOverrideClasspathEntry(part); + } + } + } + } + + private 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>>(); + + /** + * 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); + } + } + + 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); + + for (Map.Entry<Object, String> entry : trackerCache.entrySet()) { + if (entry.getValue().equals(absolutePathToJar)) { + tracker = entry.getKey(); + break; + } + } + 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; + } + } + + /** + * Looks up {@code altName} in {@code location}, and if that isn't found, looks up {@code name}; {@code altName} can be null in which case it is skipped. + */ + private URL getResourceFromLocation(String name, String altName, File location) { + if (location.isDirectory()) { + try { + if (altName != null) { + File f = new File(location, altName); + if (f.isFile() && f.canRead()) return f.toURI().toURL(); + } + + File f = new File(location, name); + if (f.isFile() && f.canRead()) return f.toURI().toURL(); + return null; + } catch (MalformedURLException e) { + return null; + } + } + + if (!location.isFile() || !location.canRead()) return null; + + File absoluteFile; { + try { + absoluteFile = location.getCanonicalFile(); + } catch (Exception e) { + absoluteFile = location.getAbsoluteFile(); + } + } + List<String> jarContents = getOrMakeJarListing(absoluteFile.getAbsolutePath()); + + String absoluteUri = absoluteFile.toURI().toString(); + + try { + if (jarContents.contains(altName)) { + return new URI("jar:" + absoluteUri + "!/" + altName).toURL(); + } + } catch (Exception e) {} + + try { + if (jarContents.contains(name)) { + return new URI("jar:" + absoluteUri + "!/" + name).toURL(); + } + } catch(Exception e) {} + + return null; + } + + /** + * Checks if the stated item is located inside the same classpath root as the jar that hosts ShadowClassLoader.class. {@code item} and {@code name} refer to the same thing. + */ + private boolean inOwnBase(URL item, String name) { + if (item == null) return false; + String itemString = item.toString(); + return (itemString.length() == SELF_BASE_LENGTH + name.length()) && SELF_BASE.regionMatches(0, itemString, 0, SELF_BASE_LENGTH); + } + + @Override public Enumeration<URL> getResources(String name) throws IOException { + String altName = null; + if (name.endsWith(".class")) altName = name.substring(0, name.length() - 6) + ".SCL." + sclSuffix; + + // Vector? Yes, we need one: + // * We can NOT make inner classes here (this class is loaded with special voodoo magic in eclipse, as a one off, it's not a full loader. + // * We need to return an enumeration. + // * We can't make one on the fly. + // * ArrayList can't make these. + Vector<URL> vector = new Vector<URL>(); + + for (File ce : override) { + URL url = getResourceFromLocation(name, altName, ce); + if (url != null) vector.add(url); + } + + if (override.isEmpty()) { + URL fromSelf = getResourceFromLocation(name, altName, SELF_BASE_FILE); + if (fromSelf != null) vector.add(fromSelf); + } + + Enumeration<URL> sec = super.getResources(name); + while (sec.hasMoreElements()) { + URL item = sec.nextElement(); + if (!inOwnBase(item, name)) vector.add(item); + } + + if (altName != null) { + Enumeration<URL> tern = super.getResources(altName); + while (tern.hasMoreElements()) { + URL item = tern.nextElement(); + if (!inOwnBase(item, altName)) vector.add(item); + } + } + + return vector.elements(); + } + + @Override public URL getResource(String name) { + return getResource_(name, false); + } + + private URL getResource_(String name, boolean noSuper) { + String altName = null; + if (name.endsWith(".class")) altName = name.substring(0, name.length() - 6) + ".SCL." + sclSuffix; + for (File ce : override) { + URL url = getResourceFromLocation(name, altName, ce); + if (url != null) return url; + } + + if (!override.isEmpty()) { + if (noSuper) return null; + if (altName != null) { + try { + URL res = getResourceSkippingSelf(altName); + if (res != null) return res; + } catch (IOException ignore) {} + } + + try { + return getResourceSkippingSelf(name); + } catch (IOException e) { + return null; + } + } + + URL url = getResourceFromLocation(name, altName, SELF_BASE_FILE); + if (url != null) return url; + + if (altName != null) { + URL res = super.getResource(altName); + if (res != null && (!noSuper || inOwnBase(res, altName))) return res; + } + + URL res = super.getResource(name); + if (res != null && (!noSuper || inOwnBase(res, name))) return res; + return null; + } + + private boolean exclusionListMatch(String name) { + for (String pe : parentExclusion) { + if (name.startsWith(pe)) return true; + } + return false; + } + + private URL getResourceSkippingSelf(String name) throws IOException { + URL candidate = super.getResource(name); + if (candidate == null) return null; + if (!inOwnBase(candidate, name)) return candidate; + + Enumeration<URL> en = super.getResources(name); + while (en.hasMoreElements()) { + candidate = en.nextElement(); + if (!inOwnBase(candidate, name)) return candidate; + } + + return null; + } + + @Override public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { + { + Class<?> alreadyLoaded = findLoadedClass(name); + if (alreadyLoaded != null) return alreadyLoaded; + } + + String fileNameOfClass = name.replace(".", "/") + ".class"; + URL res = getResource_(fileNameOfClass, true); + if (res == null) { + if (!exclusionListMatch(fileNameOfClass)) return super.loadClass(name, resolve); + throw new ClassNotFoundException(name); + } + + byte[] b; + int p = 0; + try { + InputStream in = res.openStream(); + + try { + b = new byte[65536]; + while (true) { + int r = in.read(b, p, b.length - p); + if (r == -1) break; + p += r; + if (p == b.length) { + byte[] nb = new byte[b.length * 2]; + System.arraycopy(b, 0, nb, 0, p); + b = nb; + } + } + } finally { + in.close(); + } + } catch (IOException e) { + throw new ClassNotFoundException("I/O exception reading class " + name, e); + } + + Class<?> c = defineClass(name, b, 0, p); + if (resolve) resolveClass(c); + return c; + } + + public void addOverrideJarDir(String dir) { + File f = new File(dir); + for (File j : f.listFiles()) { + if (j.getName().toLowerCase().endsWith(".jar") && j.canRead() && j.isFile()) override.add(j); + } + } + + public void addOverrideClasspathEntry(String entry) { + override.add(new File(entry)); + } +} diff --git a/src/utils/lombok/core/LombokImmutableList.java b/src/utils/lombok/core/LombokImmutableList.java index e0e1136c..4603f2ad 100644 --- a/src/utils/lombok/core/LombokImmutableList.java +++ b/src/utils/lombok/core/LombokImmutableList.java @@ -80,6 +80,12 @@ public final class LombokImmutableList<T> implements Iterable<T> { return copyOf(list); } + public static <T> LombokImmutableList<T> copyOf(T[] array) { + Object[] content = new Object[array.length]; + System.arraycopy(array, 0, content, 0, array.length); + return new LombokImmutableList<T>(content); + } + private LombokImmutableList(Object[] content) { this.content = content; } diff --git a/src/utils/lombok/javac/Javac.java b/src/utils/lombok/javac/Javac.java index e207c44a..41ff8242 100644 --- a/src/utils/lombok/javac/Javac.java +++ b/src/utils/lombok/javac/Javac.java @@ -165,6 +165,10 @@ public class Javac { public static final TypeTag CTC_CLASS = typeTag("CLASS"); public static final TreeTag CTC_NOT_EQUAL = treeTag("NE"); + public static final TreeTag CTC_LESS_THAN = treeTag("LT"); + public static final TreeTag CTC_GREATER_THAN = treeTag("GT"); + public static final TreeTag CTC_LESS_OR_EQUAL= treeTag("LE"); + public static final TreeTag CTC_GREATER_OR_EQUAL = treeTag("GE"); public static final TreeTag CTC_POS = treeTag("POS"); public static final TreeTag CTC_NEG = treeTag("NEG"); public static final TreeTag CTC_NOT = treeTag("NOT"); @@ -172,10 +176,14 @@ public class Javac { public static final TreeTag CTC_BITXOR = treeTag("BITXOR"); public static final TreeTag CTC_UNSIGNED_SHIFT_RIGHT = treeTag("USR"); public static final TreeTag CTC_MUL = treeTag("MUL"); + public static final TreeTag CTC_DIV = treeTag("DIV"); public static final TreeTag CTC_PLUS = treeTag("PLUS"); + public static final TreeTag CTC_MINUS = treeTag("MINUS"); public static final TreeTag CTC_EQUAL = treeTag("EQ"); public static final TreeTag CTC_PREINC = treeTag("PREINC"); public static final TreeTag CTC_PREDEC = treeTag("PREDEC"); + public static final TreeTag CTC_POSTINC = treeTag("POSTINC"); + public static final TreeTag CTC_POSTDEC = treeTag("POSTDEC"); private static final Method getExtendsClause, getEndPosition, storeEnd; |