diff options
147 files changed, 8105 insertions, 1487 deletions
@@ -138,7 +138,7 @@ the common tasks and can be called on to run the main aspects of all the sub-scr </target> <target name="compile" depends="version, ensureBuildDeps, -unpackLibs" description="Compiles the code."> - <!-- ant includes the destination dir on the classpath (and there are good reason to do this), but that also means + <!-- ant includes the destination dir on the classpath (and there are good reasons to do this), but that also means the bleeding edge lombok from the previous build is run, which means if there are bugs in it, you can't compile anymore until you 'ant clean'. That's very much not desired, so we kill the processor, which stops lombok from running. We re-create the file at the end of this target. --> @@ -155,16 +155,14 @@ the common tasks and can be called on to run the main aspects of all the sub-scr </ivy:compile> <ivy:compile destdir="build/lombok-utils" source="1.5" target="1.5" includeantruntime="false"> - <compilerarg value="-Xbootclasspath/p:lib/openJDK6Environment/rt-openjdk6.jar" /> - <compilerarg value="-Xbootclasspath/p:build/stubs" /> + <compilerarg value="-Xbootclasspath/p:build/stubs${path.separator}lib/openJDK6Environment/rt-openjdk6.jar" /> <src path="src/utils" /> <exclude name="lombok/javac/**" /> <classpath refid="build.path" /> </ivy:compile> <ivy:compile destdir="build/lombok-utils" source="1.6" target="1.6" includeantruntime="false"> - <compilerarg value="-Xbootclasspath/p:lib/openJDK6Environment/rt-openjdk6.jar" /> - <compilerarg value="-Xbootclasspath/p:build/stubs" /> + <compilerarg value="-Xbootclasspath/p:build/stubs${path.separator}lib/openJDK6Environment/rt-openjdk6.jar" /> <src path="src/utils" /> <include name="lombok/javac/**" /> <classpath location="build/lombok-utils" /> @@ -176,8 +174,8 @@ the common tasks and can be called on to run the main aspects of all the sub-scr </copy> <ivy:compile destdir="build/lombok" source="1.5" target="1.5" includeantruntime="false"> - <compilerarg value="-Xbootclasspath/p:lib/openJDK6Environment/rt-openjdk6.jar" /> - <compilerarg value="-Xbootclasspath/p:build/stubs" /> + <compilerarg value="-Xbootclasspath/p:build/stubs${path.separator}lib/openJDK6Environment/rt-openjdk6.jar" /> + <src path="src/launch" /> <src path="src/core" /> <src path="src/installer" /> <src path="src/eclipseAgent" /> @@ -187,8 +185,7 @@ the common tasks and can be called on to run the main aspects of all the sub-scr </ivy:compile> <ivy:compile destdir="build/lombok" source="1.6" target="1.6" includeantruntime="false"> - <compilerarg value="-Xbootclasspath/p:lib/openJDK6Environment/rt-openjdk6.jar" /> - <compilerarg value="-Xbootclasspath/p:build/stubs" /> + <compilerarg value="-Xbootclasspath/p:build/stubs${path.separator}lib/openJDK6Environment/rt-openjdk6.jar" /> <src path="src/core" /> <src path="src/delombok" /> <include name="lombok/javac/**" /> @@ -198,7 +195,7 @@ the common tasks and can be called on to run the main aspects of all the sub-scr </ivy:compile> <mkdir dir="build/lombok/META-INF" /> <mkdir dir="build/lombok/META-INF/services" /> - <echo file="build/lombok/META-INF/services/javax.annotation.processing.Processor">lombok.core.AnnotationProcessor</echo> + <echo file="build/lombok/META-INF/services/javax.annotation.processing.Processor">lombok.launch.AnnotationProcessorHider$AnnotationProcessor</echo> </target> <target name="dist" description="Builds THE lombok.jar file which contains everything." depends="version, compile"> @@ -208,25 +205,40 @@ the common tasks and can be called on to run the main aspects of all the sub-scr <format property="releaseTimestamp" pattern="yyyy-MM-dd" /> </tstamp> <echo file="release-timestamp.txt">${releaseTimestamp}</echo> - <taskdef name="jarjar" classname="com.tonicsystems.jarjar.JarJarTask" classpath="lib/build/com.googlecode.jarjar-jarjar.jar" /> - <jarjar destfile="dist/lombok-${lombok.version}.jar"> - <fileset dir="build/lombok"> - <exclude name="com/sun/tools/javac/**"/> - </fileset> + <zip destfile="dist/lombok-${lombok.version}.jar"> <fileset dir="build" includes="changelog.txt" /> <fileset dir="." includes="LICENSE" /> <fileset dir="." includes="AUTHORS" /> <fileset dir="." includes="release-timestamp.txt" /> - <rule pattern="com.zwitserloot.cmdreader.**" result="lombok.libs.com.zwitserloot.cmdreader.@1" /> - <rule pattern="org.objectweb.asm.**" result="lombok.libs.org.objectweb.asm.@1" /> + <fileset dir="build/lombok"> + <include name="lombok/*.class" /> + <include name="lombok/experimental/**" /> + <include name="lombok/extern/**" /> + <include name="lombok/launch/**" /> + </fileset> + <mappedresources> + <fileset dir="build/lombok"> + <exclude name="com/sun/tools/javac/**" /> + <exclude name="lombok/*.class" /> + <exclude name="lombok/experimental/**" /> + <exclude name="lombok/extern/**" /> + <exclude name="lombok/launch/**" /> + </fileset> + <firstmatchmapper> + <globmapper from="*.class" to="*.SCL.lombok" /> + <identitymapper /> + </firstmatchmapper> + </mappedresources> + </zip> + <jar destfile="dist/lombok-${lombok.version}.jar" update="true"> <manifest> - <attribute name="Premain-Class" value="lombok.core.Agent" /> - <attribute name="Agent-Class" value="lombok.core.Agent" /> + <attribute name="Premain-Class" value="lombok.launch.Agent" /> + <attribute name="Agent-Class" value="lombok.launch.Agent" /> <attribute name="Can-Redefine-Classes" value="true" /> - <attribute name="Main-Class" value="lombok.core.Main" /> + <attribute name="Main-Class" value="lombok.launch.Main" /> <attribute name="Lombok-Version" value="${lombok.version}" /> </manifest> - </jarjar> + </jar> <delete file="release-timestamp.txt" /> <copy file="dist/lombok-${lombok.version}.jar" tofile="dist/lombok.jar" /> <property name="lombok.dist.built" value="true" /> @@ -251,6 +263,7 @@ the common tasks and can be called on to run the main aspects of all the sub-scr <conf name="test" sources="contrib" /> <module name="lombok" depends="build, test"> <srcdir dir="src/core" /> + <srcdir dir="src/launch" /> <srcdir dir="src/utils" /> <srcdir dir="src/eclipseAgent" /> <srcdir dir="src/installer" /> @@ -280,6 +293,7 @@ the common tasks and can be called on to run the main aspects of all the sub-scr <target name="eclipse" depends="-addEclipseDeps, deps, contrib" description="Creates eclipse project files and downloads all dependencies. Open this directory as project in eclipse after running this target. This will NOT let you start a debug session for eclipse; use target 'eclipseForDebugging' instead to do that."> <ivy:eclipsegen source="1.6"> <srcdir dir="src/core" /> + <srcdir dir="src/launch" /> <srcdir dir="src/utils" /> <srcdir dir="src/eclipseAgent" /> <srcdir dir="src/installer" /> @@ -542,16 +556,7 @@ You can also create your own by writing a 'testenvironment.properties' file. The <classpath path="build/tests" /> <batchtest> <fileset dir="test/core/src"> - <include name="**/Test*.java" /> - </fileset> - <fileset dir="test/transform/src"> - <include name="**/Test*.java" /> - </fileset> - <fileset dir="test/bytecode/src"> - <include name="**/Test*.java" /> - </fileset> - <fileset dir="test/configuration/src"> - <include name="**/Test*.java" /> + <include name="lombok/RunAllTests.java" /> </fileset> </batchtest> </junit> @@ -610,6 +615,7 @@ You can also create your own by writing a 'testenvironment.properties' file. The </jar> <jar destfile="dist/lombok-${lombok.version}-sources.jar"> <fileset dir="src/core" /> + <fileset dir="src/launch" /> <fileset dir="src/utils" /> <fileset dir="src/eclipseAgent" /> <fileset dir="src/installer" /> diff --git a/buildScripts/eclipse-run-tests.template b/buildScripts/eclipse-run-tests.template index b7bc8b0d..a38283dc 100644 --- a/buildScripts/eclipse-run-tests.template +++ b/buildScripts/eclipse-run-tests.template @@ -18,6 +18,7 @@ <listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry containerPath="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.@JAVA_VERSION@" path="1" type="4"/> "/> <listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/lombok/@ECJ_LOCATION@" path="3" type="2"/> "/> <listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/lombok/@JAVAC_LOCATION@" path="3" type="2"/> "/> + <listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/lombok/lib/test/com.google.guava-guava.jar" path="3" type="2"/> "/> <listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry id="org.eclipse.jdt.launching.classpathentry.defaultClasspath"> <memento exportedEntriesOnly="false" project="lombok"/> </runtimeClasspathEntry> "/> </listAttribute> <booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/> diff --git a/buildScripts/ivy-repo/org.projectlombok-lombok.patcher-0.15.xml b/buildScripts/ivy-repo/org.projectlombok-lombok.patcher-0.16.xml index df00b1c6..6e10702b 100644 --- a/buildScripts/ivy-repo/org.projectlombok-lombok.patcher-0.15.xml +++ b/buildScripts/ivy-repo/org.projectlombok-lombok.patcher-0.16.xml @@ -1,5 +1,5 @@ <ivy-module version="2.0"> - <info organisation="org.projectlombok" module="lombok.patcher" revision="0.15" publication="20140522015400"> + <info organisation="org.projectlombok" module="lombok.patcher" revision="0.16" publication="20141204000000"> <license name="MIT License" url="http://www.opensource.org/licenses/mit-license.php" /> <ivyauthor name="rzwitserloot" url="http://zwitserloot.com/" /> <ivyauthor name="rspilker" url="http://github.com/rspilker" /> @@ -9,6 +9,6 @@ <conf name="default" /> </configurations> <publications> - <artifact conf="default" url="http://projectlombok.org/downloads/lombok.patcher-0.15.jar" /> + <artifact conf="default" url="http://projectlombok.org/downloads/lombok.patcher-0.16.jar" /> </publications> </ivy-module> diff --git a/buildScripts/ivy.xml b/buildScripts/ivy.xml index 41f58632..5d362aab 100644 --- a/buildScripts/ivy.xml +++ b/buildScripts/ivy.xml @@ -15,7 +15,7 @@ <conf name="javac7" /> </configurations> <dependencies> - <dependency org="org.projectlombok" name="lombok.patcher" rev="0.15" conf="buildBase->default; runtime->default" /> + <dependency org="org.projectlombok" name="lombok.patcher" rev="0.16" conf="buildBase->default; runtime->default" /> <dependency org="zwitserloot.com" name="cmdreader" rev="1.2" conf="buildBase->runtime; runtime" /> <dependency org="junit" name="junit" rev="4.8.2" conf="test->default; contrib->sources" /> @@ -24,6 +24,7 @@ <dependency org="commons-logging" name="commons-logging" rev="1.1.1" conf="test->default; contrib->sources"/> <dependency org="org.slf4j" name="slf4j-api" rev="1.6.1" conf="test->default; contrib->sources"/> <dependency org="org.slf4j" name="slf4j-ext" rev="1.6.1" conf="test->default; contrib->sources"/> + <dependency org="com.google.guava" name="guava" rev="18.0" conf="test->default; contrib->sources" /> <dependency org="com.googlecode.jarjar" name="jarjar" rev="1.1" conf="buildBase->default" /> <dependency org="org.apache.ant" name="ant" rev="1.8.1" conf="buildBase->default; contrib->sources" /> diff --git a/buildScripts/website.ant.xml b/buildScripts/website.ant.xml index 6f54c0fd..b7a86cf3 100644 --- a/buildScripts/website.ant.xml +++ b/buildScripts/website.ant.xml @@ -149,10 +149,13 @@ such as converting the changelog into HTML, and creating javadoc. <param name="transformationName" value="NonNull" /> </antcall> <antcall target="-integrateSnippet"> - <param name="transformationName" value="experimental/Delegate" /> + <param name="transformationName" value="Builder" /> + </antcall> + <antcall target="-integrateSnippet"> + <param name="transformationName" value="Singular-snippet" /> </antcall> <antcall target="-integrateSnippet"> - <param name="transformationName" value="experimental/Builder" /> + <param name="transformationName" value="experimental/Delegate" /> </antcall> <antcall target="-integrateSnippet"> <param name="transformationName" value="experimental/Accessors" /> diff --git a/doc/changelog.markdown b/doc/changelog.markdown index 72b6e24c..0d72a3cc 100644 --- a/doc/changelog.markdown +++ b/doc/changelog.markdown @@ -1,8 +1,14 @@ Lombok Changelog ---------------- -### v1.14.9 "Edge Guinea Pig" -* edge +### v1.16.1 "Edge Guinea Pig" + +### v1.16.0 "Candid Duck" (January 26th, 2015) +* BUGFIX: `@ExtensionMethod` was broken in Eclipse using java 8. [Issue #742](https://code.google.com/p/projectlombok/issues/detail?id=742), [Issue #747](https://code.google.com/p/projectlombok/issues/detail?id=747) +* BUGFIX: delombok: Using exotic characters in your source files would overzealously backslash-u escape them. Now, all characters are printed unescaped, assuming your chosen encoding can support them. Otherwise, they are escaped. [Issue #759](https://code.google.com/p/projectlombok/issues/detail?id=759) +* PROMOTION: `@Builder` has graduated from experimental to the main package with a few changes (addition of `@Singular`, removal of the `fluent` and `chain` options). The old one still exists and has been deprecated. +* FEATURE: `@Builder` now supports adding the `@Singular` annotation to any field/parameter that represents a collection, which results in a method in the generated builder that takes in one element of that collection and adds it. Lombok takes care of generating the appropriate code to produce a compacted immutable version of the appropriate type. In this version, java.util collections and guava's ImmutableCollections are supported. See the [feature documentation](http://projectlombok.org/features/Builder.html) for more information. +* FEATURE: Added a launcher to the lombok boot process which removes the need for `-Xbootclasspath` to be in your `eclipse.ini` file, and removes all non-public API and third party dependencies (such as ASM) from the lombok jar, thus removing them from your IDE's auto complete offerings in any project that uses lombok. For those debugging lombok, the launcher enables hot code replace which makes debugging a lot easier, as previously one was required to shut down the IDE, rebuild the jar, and relaunch. Add `-Dshadow.override.lombok=/path/to/lombok/bin` to the launch target for hot code replace. ### v1.14.8 (September 15th, 2014) * PERFORMANCE: The configuration system typically hit the filesystem twice per read configuration key instead of hardly ever. This is a continuation of [Issue #682](https://code.google.com/p/projectlombok/issues/detail?id=682). 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; diff --git a/test/configuration/resource/configurationRoot/out.txt b/test/configuration/resource/configurationRoot/out.txt index 625115a4..de219694 100644 --- a/test/configuration/resource/configurationRoot/out.txt +++ b/test/configuration/resource/configurationRoot/out.txt @@ -8,7 +8,7 @@ lombok.accessors.flagUsage = ERROR # BASE/d1/d11/lombok.config: # 3: lombok.accessors.flagUsage = ERROR -# Generate setters that return 'this' instead of 'void'. +# Generate setters that return 'this' instead of 'void' (default: false). lombok.accessors.chain = false # BASE/d1/lombok.config: # <'lombok.accessors.chain' not mentioned> @@ -24,7 +24,7 @@ lombok.accessors.prefix += f # BASE/d1/d11/lombok.config: # 4: lombok.accessors.prefix += f -# Use this name for the generated logger fields (default: 'log') +# Use this name for the generated logger fields (default: 'log'). clear lombok.log.fieldName @@ -43,7 +43,7 @@ lombok.accessors.flagUsage = ERROR # BASE/d1/d11/d111/lombok.config: # <'lombok.accessors.flagUsage' not mentioned> -# Generate setters that return 'this' instead of 'void'. +# Generate setters that return 'this' instead of 'void' (default: false). clear lombok.accessors.chain # BASE/d1/lombok.config: # <'lombok.accessors.chain' not mentioned> @@ -66,7 +66,7 @@ lombok.accessors.prefix += m_ # BASE/d1/d11/d111/lombok.config: # 2: lombok.accessors.prefix += m_ -# Use this name for the generated logger fields (default: 'log') +# Use this name for the generated logger fields (default: 'log'). clear lombok.log.fieldName @@ -75,7 +75,7 @@ Configuration for 'BASE/d1/d12'. # Emit a warning or error if @Accessors is used. clear lombok.accessors.flagUsage -# Generate setters that return 'this' instead of 'void'. +# Generate setters that return 'this' instead of 'void' (default: false). lombok.accessors.chain = true # BASE/d1/lombok.config: # <'lombok.accessors.chain' not mentioned> @@ -86,5 +86,5 @@ lombok.accessors.chain = true # Strip this field prefix, like 'f' or 'm_', from the names of generated getters and setters. clear lombok.accessors.prefix -# Use this name for the generated logger fields (default: 'log') +# Use this name for the generated logger fields (default: 'log'). clear lombok.log.fieldName
\ No newline at end of file diff --git a/test/core/src/lombok/AbstractRunTests.java b/test/core/src/lombok/AbstractRunTests.java index a1f535b4..85d4d4f3 100644 --- a/test/core/src/lombok/AbstractRunTests.java +++ b/test/core/src/lombok/AbstractRunTests.java @@ -75,7 +75,7 @@ public abstract class AbstractRunTests { } }); - transformCode(messages, writer, file); + transformCode(messages, writer, file, sourceDirectives.getSpecifiedEncoding()); compare(file.getName(), expected, writer.toString(), messages, params.printErrors(), sourceDirectives.isSkipCompareContent() || expected.isSkipCompareContent()); return true; @@ -97,7 +97,7 @@ public abstract class AbstractRunTests { return 8; } - protected abstract void transformCode(Collection<CompilerMessage> messages, StringWriter result, File file) throws Throwable; + protected abstract void transformCode(Collection<CompilerMessage> messages, StringWriter result, File file, String encoding) throws Throwable; protected String readFile(File file) throws IOException { BufferedReader reader; diff --git a/test/core/src/lombok/LombokTestSource.java b/test/core/src/lombok/LombokTestSource.java index 402001e9..f31d7be7 100644 --- a/test/core/src/lombok/LombokTestSource.java +++ b/test/core/src/lombok/LombokTestSource.java @@ -49,7 +49,8 @@ public class LombokTestSource { private final boolean skipCompareContent; private final int versionLowerLimit, versionUpperLimit; private final ConfigurationResolver configuration; - + private final String specifiedEncoding; + public boolean versionWithinLimit(int version) { return version >= versionLowerLimit && version <= versionUpperLimit; } @@ -74,6 +75,10 @@ public class LombokTestSource { return skipCompareContent; } + public String getSpecifiedEncoding() { + return specifiedEncoding; + } + public ConfigurationResolver getConfiguration() { return configuration; } @@ -123,6 +128,7 @@ public class LombokTestSource { int versionUpper = Integer.MAX_VALUE; boolean ignore = false; boolean skipCompareContent = false; + String encoding = null; for (String directive : directives) { directive = directive.trim(); @@ -154,10 +160,15 @@ public class LombokTestSource { continue; } + if (lc.startsWith("encoding:")) { + encoding = directive.substring(9).trim(); + continue; + } + Assert.fail("Directive line \"" + directive + "\" in '" + file.getAbsolutePath() + "' invalid: unrecognized directive."); throw new RuntimeException(); } - + this.specifiedEncoding = encoding; this.versionLowerLimit = versionLower; this.versionUpperLimit = versionUpper; this.ignore = ignore; @@ -194,13 +205,17 @@ public class LombokTestSource { } public static LombokTestSource read(File sourceFolder, File messagesFolder, String fileName) throws IOException { + return read0(sourceFolder, messagesFolder, fileName, "UTF-8"); + } + + private static LombokTestSource read0(File sourceFolder, File messagesFolder, String fileName, String encoding) throws IOException { StringBuilder content = null; List<String> directives = new ArrayList<String>(); File sourceFile = new File(sourceFolder, fileName); if (sourceFile.exists()) { @Cleanup val rawIn = new FileInputStream(sourceFile); - BufferedReader in = new BufferedReader(new InputStreamReader(rawIn, "UTF-8")); + BufferedReader in = new BufferedReader(new InputStreamReader(rawIn, encoding)); for (String i = in.readLine(); i != null; i = in.readLine()) { if (content != null) { content.append(i).append("\n"); @@ -234,6 +249,15 @@ public class LombokTestSource { } } - return new LombokTestSource(sourceFile, content.toString(), messages, directives); + LombokTestSource source = new LombokTestSource(sourceFile, content.toString(), messages, directives); + String specifiedEncoding = source.getSpecifiedEncoding(); + + // The source file has an 'encoding' header to test encoding issues. Of course, reading the encoding header + // requires knowing the encoding of the file first. In practice we get away with it, because UTF-8 and US-ASCII are compatible enough. + // The fix is therefore to read in as UTF-8 initially, and if the file requests that it should be read as another encoding, toss it all + // and reread that way. + + if (specifiedEncoding == null || specifiedEncoding.equalsIgnoreCase(encoding)) return source; + return read0(sourceFolder, messagesFolder, fileName, specifiedEncoding); } } diff --git a/test/core/src/lombok/RunAllTests.java b/test/core/src/lombok/RunAllTests.java index 9f56b45b..1ca76af5 100644 --- a/test/core/src/lombok/RunAllTests.java +++ b/test/core/src/lombok/RunAllTests.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2014 The Project Lombok Authors. + * Copyright (C) 2011-2015 The Project Lombok Authors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -26,6 +26,6 @@ import org.junit.runners.Suite; import org.junit.runners.Suite.SuiteClasses; @RunWith(Suite.class) -@SuiteClasses({lombok.transform.RunTransformTests.class, lombok.bytecode.RunBytecodeTests.class, lombok.core.configuration.RunConfigurationTests.class}) +@SuiteClasses({lombok.transform.RunTransformTests.class, lombok.bytecode.RunBytecodeTests.class, lombok.core.configuration.RunConfigurationTests.class, lombok.core.RunCoreTests.class}) public class RunAllTests { } diff --git a/test/core/src/lombok/RunTestsViaDelombok.java b/test/core/src/lombok/RunTestsViaDelombok.java index 6a08642b..1482c865 100644 --- a/test/core/src/lombok/RunTestsViaDelombok.java +++ b/test/core/src/lombok/RunTestsViaDelombok.java @@ -34,10 +34,10 @@ public class RunTestsViaDelombok extends AbstractRunTests { private Delombok delombok = new Delombok(); @Override - public void transformCode(Collection<CompilerMessage> messages, StringWriter result, final File file) throws Throwable { + public void transformCode(Collection<CompilerMessage> messages, StringWriter result, final File file, String encoding) throws Throwable { delombok.setVerbose(false); delombok.setForceProcess(true); - delombok.setCharset("UTF-8"); + delombok.setCharset(encoding == null ? "UTF-8" : encoding); delombok.setDiagnosticsListener(new CapturingDiagnosticListener(file, messages)); diff --git a/test/core/src/lombok/RunTestsViaEcj.java b/test/core/src/lombok/RunTestsViaEcj.java index f4584493..74fe6e92 100644 --- a/test/core/src/lombok/RunTestsViaEcj.java +++ b/test/core/src/lombok/RunTestsViaEcj.java @@ -93,7 +93,7 @@ public class RunTestsViaEcj extends AbstractRunTests { } @Override - public void transformCode(Collection<CompilerMessage> messages, StringWriter result, File file) throws Throwable { + public void transformCode(Collection<CompilerMessage> messages, StringWriter result, File file, String encoding) throws Throwable { final AtomicReference<CompilationResult> compilationResult_ = new AtomicReference<CompilationResult>(); final AtomicReference<CompilationUnitDeclaration> compilationUnit_ = new AtomicReference<CompilationUnitDeclaration>(); ICompilerRequestor bitbucketRequestor = new ICompilerRequestor() { @@ -103,7 +103,7 @@ public class RunTestsViaEcj extends AbstractRunTests { }; String source = readFile(file); - final CompilationUnit sourceUnit = new CompilationUnit(source.toCharArray(), file.getName(), "UTF-8"); + final CompilationUnit sourceUnit = new CompilationUnit(source.toCharArray(), file.getName(), encoding == null ? "UTF-8" : encoding); Compiler ecjCompiler = new Compiler(createFileSystem(file), ecjErrorHandlingPolicy(), ecjCompilerOptions(), bitbucketRequestor, new DefaultProblemFactory(Locale.ENGLISH)) { @Override protected synchronized void addCompilationUnit(ICompilationUnit inUnit, CompilationUnitDeclaration parsedUnit) { @@ -112,8 +112,6 @@ public class RunTestsViaEcj extends AbstractRunTests { } }; - // TODO: Create a configuration based on confLines and set this up so that this compile run will use them. - ecjCompiler.compile(new ICompilationUnit[] {sourceUnit}); CompilationResult compilationResult = compilationResult_.get(); @@ -137,12 +135,14 @@ public class RunTestsViaEcj extends AbstractRunTests { i.remove(); } } + classpath.add("bin"); classpath.add("dist/lombok.jar"); classpath.add("lib/test/commons-logging-commons-logging.jar"); classpath.add("lib/test/org.slf4j-slf4j-api.jar"); classpath.add("lib/test/org.slf4j-slf4j-ext.jar"); classpath.add("lib/test/log4j-log4j.jar"); classpath.add("lib/test/org.apache.logging.log4j-log4j-api.jar"); + classpath.add("lib/test/com.google.guava-guava.jar"); return new FileSystem(classpath.toArray(new String[0]), new String[] {file.getAbsolutePath()}, "UTF-8"); } } diff --git a/test/core/src/lombok/core/RunCoreTests.java b/test/core/src/lombok/core/RunCoreTests.java new file mode 100644 index 00000000..8ac7cf81 --- /dev/null +++ b/test/core/src/lombok/core/RunCoreTests.java @@ -0,0 +1,31 @@ +/* + * 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 org.junit.runner.RunWith; +import org.junit.runners.Suite; +import org.junit.runners.Suite.SuiteClasses; + +@RunWith(Suite.class) +@SuiteClasses({TestSingulars.class}) +public class RunCoreTests { +} diff --git a/test/core/src/lombok/core/TestSingulars.java b/test/core/src/lombok/core/TestSingulars.java new file mode 100644 index 00000000..1134af08 --- /dev/null +++ b/test/core/src/lombok/core/TestSingulars.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.core; + +import static lombok.core.handlers.Singulars.autoSingularize; +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class TestSingulars { + @Test + public void testSingulars() { + assertEquals(null, autoSingularize("axes")); + assertEquals("adjective", autoSingularize("adjectives")); + assertEquals("bus", autoSingularize("buses")); + assertEquals("octopus", autoSingularize("octopodes")); + assertEquals(null, autoSingularize("octopi")); + assertEquals("elf", autoSingularize("elves")); + assertEquals("jack", autoSingularize("jacks")); + assertEquals("colloquy", autoSingularize("colloquies")); + assertEquals(null, autoSingularize("series")); + assertEquals("man", autoSingularize("men")); + assertEquals(null, autoSingularize("highwaymen")); + assertEquals("caveMan", autoSingularize("caveMen")); + assertEquals(null, autoSingularize("jackss")); + assertEquals(null, autoSingularize("virus")); + assertEquals("quiz", autoSingularize("quizzes")); + assertEquals("database", autoSingularize("databases")); + assertEquals("dataBase", autoSingularize("dataBases")); + assertEquals("Query", autoSingularize("Queries")); + assertEquals("Movie", autoSingularize("Movies")); + } +} diff --git a/test/transform/resource/after-delombok/BuilderSingularGuavaListsSets.java b/test/transform/resource/after-delombok/BuilderSingularGuavaListsSets.java new file mode 100644 index 00000000..5b9d620c --- /dev/null +++ b/test/transform/resource/after-delombok/BuilderSingularGuavaListsSets.java @@ -0,0 +1,93 @@ +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSortedSet; +class BuilderSingularGuavaListsSets<T> { + private ImmutableList<T> cards; + private ImmutableCollection<? extends Number> frogs; + @SuppressWarnings("all") + private ImmutableSet rawSet; + private ImmutableSortedSet<String> passes; + @java.lang.SuppressWarnings("all") + BuilderSingularGuavaListsSets(final ImmutableList<T> cards, final ImmutableCollection<? extends Number> frogs, final ImmutableSet rawSet, final ImmutableSortedSet<String> passes) { + this.cards = cards; + this.frogs = frogs; + this.rawSet = rawSet; + this.passes = passes; + } + @java.lang.SuppressWarnings("all") + public static class BuilderSingularGuavaListsSetsBuilder<T> { + private com.google.common.collect.ImmutableList.Builder<T> cards; + private com.google.common.collect.ImmutableList.Builder<Number> frogs; + private com.google.common.collect.ImmutableSet.Builder<java.lang.Object> rawSet; + private com.google.common.collect.ImmutableSortedSet.Builder<String> passes; + @java.lang.SuppressWarnings("all") + BuilderSingularGuavaListsSetsBuilder() { + } + @java.lang.SuppressWarnings("all") + public BuilderSingularGuavaListsSetsBuilder<T> card(final T card) { + if (this.cards == null) this.cards = com.google.common.collect.ImmutableList.builder(); + this.cards.add(card); + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderSingularGuavaListsSetsBuilder<T> cards(final java.lang.Iterable<? extends T> cards) { + if (this.cards == null) this.cards = com.google.common.collect.ImmutableList.builder(); + this.cards.addAll(cards); + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderSingularGuavaListsSetsBuilder<T> frog(final Number frog) { + if (this.frogs == null) this.frogs = com.google.common.collect.ImmutableList.builder(); + this.frogs.add(frog); + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderSingularGuavaListsSetsBuilder<T> frogs(final java.lang.Iterable<? extends Number> frogs) { + if (this.frogs == null) this.frogs = com.google.common.collect.ImmutableList.builder(); + this.frogs.addAll(frogs); + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderSingularGuavaListsSetsBuilder<T> rawSet(final java.lang.Object rawSet) { + if (this.rawSet == null) this.rawSet = com.google.common.collect.ImmutableSet.builder(); + this.rawSet.add(rawSet); + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderSingularGuavaListsSetsBuilder<T> rawSet(final java.lang.Iterable<?> rawSet) { + if (this.rawSet == null) this.rawSet = com.google.common.collect.ImmutableSet.builder(); + this.rawSet.addAll(rawSet); + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderSingularGuavaListsSetsBuilder<T> pass(final String pass) { + if (this.passes == null) this.passes = com.google.common.collect.ImmutableSortedSet.naturalOrder(); + this.passes.add(pass); + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderSingularGuavaListsSetsBuilder<T> passes(final java.lang.Iterable<? extends String> passes) { + if (this.passes == null) this.passes = com.google.common.collect.ImmutableSortedSet.naturalOrder(); + this.passes.addAll(passes); + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderSingularGuavaListsSets<T> build() { + com.google.common.collect.ImmutableList<T> cards = this.cards == null ? com.google.common.collect.ImmutableList.<T>of() : this.cards.build(); + com.google.common.collect.ImmutableCollection<Number> frogs = this.frogs == null ? com.google.common.collect.ImmutableList.<Number>of() : this.frogs.build(); + com.google.common.collect.ImmutableSet<java.lang.Object> rawSet = this.rawSet == null ? com.google.common.collect.ImmutableSet.<java.lang.Object>of() : this.rawSet.build(); + com.google.common.collect.ImmutableSortedSet<String> passes = this.passes == null ? com.google.common.collect.ImmutableSortedSet.<String>of() : this.passes.build(); + return new BuilderSingularGuavaListsSets<T>(cards, frogs, rawSet, passes); + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public java.lang.String toString() { + return "BuilderSingularGuavaListsSets.BuilderSingularGuavaListsSetsBuilder(cards=" + this.cards + ", frogs=" + this.frogs + ", rawSet=" + this.rawSet + ", passes=" + this.passes + ")"; + } + } + @java.lang.SuppressWarnings("all") + public static <T> BuilderSingularGuavaListsSetsBuilder<T> builder() { + return new BuilderSingularGuavaListsSetsBuilder<T>(); + } +}
\ No newline at end of file diff --git a/test/transform/resource/after-delombok/BuilderSingularGuavaMaps.java b/test/transform/resource/after-delombok/BuilderSingularGuavaMaps.java new file mode 100644 index 00000000..ab250e3c --- /dev/null +++ b/test/transform/resource/after-delombok/BuilderSingularGuavaMaps.java @@ -0,0 +1,76 @@ +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableBiMap; +import com.google.common.collect.ImmutableSortedMap; +class BuilderSingularGuavaMaps<K, V> { + private ImmutableMap<K, V> battleaxes; + private ImmutableSortedMap<Integer, ? extends V> vertices; + @SuppressWarnings("all") + private ImmutableBiMap rawMap; + @java.lang.SuppressWarnings("all") + BuilderSingularGuavaMaps(final ImmutableMap<K, V> battleaxes, final ImmutableSortedMap<Integer, ? extends V> vertices, final ImmutableBiMap rawMap) { + this.battleaxes = battleaxes; + this.vertices = vertices; + this.rawMap = rawMap; + } + @java.lang.SuppressWarnings("all") + public static class BuilderSingularGuavaMapsBuilder<K, V> { + private com.google.common.collect.ImmutableMap.Builder<K, V> battleaxes; + private com.google.common.collect.ImmutableSortedMap.Builder<Integer, V> vertices; + private com.google.common.collect.ImmutableBiMap.Builder<java.lang.Object, java.lang.Object> rawMap; + @java.lang.SuppressWarnings("all") + BuilderSingularGuavaMapsBuilder() { + } + @java.lang.SuppressWarnings("all") + public BuilderSingularGuavaMapsBuilder<K, V> battleaxe(final K battleaxe$key, final V battleaxe$value) { + if (this.battleaxes == null) this.battleaxes = com.google.common.collect.ImmutableMap.builder(); + this.battleaxes.put(battleaxe$key, battleaxe$value); + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderSingularGuavaMapsBuilder<K, V> battleaxes(final java.util.Map<? extends K, ? extends V> battleaxes) { + if (this.battleaxes == null) this.battleaxes = com.google.common.collect.ImmutableMap.builder(); + this.battleaxes.putAll(battleaxes); + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderSingularGuavaMapsBuilder<K, V> vertex(final Integer vertex$key, final V vertex$value) { + if (this.vertices == null) this.vertices = com.google.common.collect.ImmutableSortedMap.naturalOrder(); + this.vertices.put(vertex$key, vertex$value); + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderSingularGuavaMapsBuilder<K, V> vertices(final java.util.Map<? extends Integer, ? extends V> vertices) { + if (this.vertices == null) this.vertices = com.google.common.collect.ImmutableSortedMap.naturalOrder(); + this.vertices.putAll(vertices); + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderSingularGuavaMapsBuilder<K, V> rawMap(final java.lang.Object rawMap$key, final java.lang.Object rawMap$value) { + if (this.rawMap == null) this.rawMap = com.google.common.collect.ImmutableBiMap.builder(); + this.rawMap.put(rawMap$key, rawMap$value); + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderSingularGuavaMapsBuilder<K, V> rawMap(final java.util.Map<?, ?> rawMap) { + if (this.rawMap == null) this.rawMap = com.google.common.collect.ImmutableBiMap.builder(); + this.rawMap.putAll(rawMap); + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderSingularGuavaMaps<K, V> build() { + com.google.common.collect.ImmutableMap<K, V> battleaxes = this.battleaxes == null ? com.google.common.collect.ImmutableMap.<K, V>of() : this.battleaxes.build(); + com.google.common.collect.ImmutableSortedMap<Integer, V> vertices = this.vertices == null ? com.google.common.collect.ImmutableSortedMap.<Integer, V>of() : this.vertices.build(); + com.google.common.collect.ImmutableBiMap<java.lang.Object, java.lang.Object> rawMap = this.rawMap == null ? com.google.common.collect.ImmutableBiMap.<java.lang.Object, java.lang.Object>of() : this.rawMap.build(); + return new BuilderSingularGuavaMaps<K, V>(battleaxes, vertices, rawMap); + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public java.lang.String toString() { + return "BuilderSingularGuavaMaps.BuilderSingularGuavaMapsBuilder(battleaxes=" + this.battleaxes + ", vertices=" + this.vertices + ", rawMap=" + this.rawMap + ")"; + } + } + @java.lang.SuppressWarnings("all") + public static <K, V> BuilderSingularGuavaMapsBuilder<K, V> builder() { + return new BuilderSingularGuavaMapsBuilder<K, V>(); + } +}
\ No newline at end of file diff --git a/test/transform/resource/after-delombok/BuilderSingularLists.java b/test/transform/resource/after-delombok/BuilderSingularLists.java new file mode 100644 index 00000000..0d074e92 --- /dev/null +++ b/test/transform/resource/after-delombok/BuilderSingularLists.java @@ -0,0 +1,105 @@ +import java.util.List; +import java.util.Collection; +class BuilderSingularLists<T> { + private List<T> children; + private Collection<? extends Number> scarves; + @SuppressWarnings("all") + private List rawList; + @java.lang.SuppressWarnings("all") + BuilderSingularLists(final List<T> children, final Collection<? extends Number> scarves, final List rawList) { + this.children = children; + this.scarves = scarves; + this.rawList = rawList; + } + @java.lang.SuppressWarnings("all") + public static class BuilderSingularListsBuilder<T> { + private java.util.ArrayList<T> children; + private java.util.ArrayList<Number> scarves; + private java.util.ArrayList<java.lang.Object> rawList; + @java.lang.SuppressWarnings("all") + BuilderSingularListsBuilder() { + } + @java.lang.SuppressWarnings("all") + public BuilderSingularListsBuilder<T> child(final T child) { + if (this.children == null) this.children = new java.util.ArrayList<T>(); + this.children.add(child); + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderSingularListsBuilder<T> children(final java.util.Collection<? extends T> children) { + if (this.children == null) this.children = new java.util.ArrayList<T>(); + this.children.addAll(children); + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderSingularListsBuilder<T> scarf(final Number scarf) { + if (this.scarves == null) this.scarves = new java.util.ArrayList<Number>(); + this.scarves.add(scarf); + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderSingularListsBuilder<T> scarves(final java.util.Collection<? extends Number> scarves) { + if (this.scarves == null) this.scarves = new java.util.ArrayList<Number>(); + this.scarves.addAll(scarves); + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderSingularListsBuilder<T> rawList(final java.lang.Object rawList) { + if (this.rawList == null) this.rawList = new java.util.ArrayList<java.lang.Object>(); + this.rawList.add(rawList); + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderSingularListsBuilder<T> rawList(final java.util.Collection<?> rawList) { + if (this.rawList == null) this.rawList = new java.util.ArrayList<java.lang.Object>(); + this.rawList.addAll(rawList); + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderSingularLists<T> build() { + java.util.List<T> children; + switch (this.children == null ? 0 : this.children.size()) { + case 0: + children = java.util.Collections.emptyList(); + break; + case 1: + children = java.util.Collections.singletonList(this.children.get(0)); + break; + default: + children = java.util.Collections.unmodifiableList(new java.util.ArrayList<T>(this.children)); + } + java.util.Collection<Number> scarves; + switch (this.scarves == null ? 0 : this.scarves.size()) { + case 0: + scarves = java.util.Collections.emptyList(); + break; + case 1: + scarves = java.util.Collections.singletonList(this.scarves.get(0)); + break; + default: + scarves = java.util.Collections.unmodifiableList(new java.util.ArrayList<Number>(this.scarves)); + } + java.util.List<java.lang.Object> rawList; + switch (this.rawList == null ? 0 : this.rawList.size()) { + case 0: + rawList = java.util.Collections.emptyList(); + break; + case 1: + rawList = java.util.Collections.singletonList(this.rawList.get(0)); + break; + default: + rawList = java.util.Collections.unmodifiableList(new java.util.ArrayList<java.lang.Object>(this.rawList)); + } + return new BuilderSingularLists<T>(children, scarves, rawList); + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public java.lang.String toString() { + return "BuilderSingularLists.BuilderSingularListsBuilder(children=" + this.children + ", scarves=" + this.scarves + ", rawList=" + this.rawList + ")"; + } + } + @java.lang.SuppressWarnings("all") + public static <T> BuilderSingularListsBuilder<T> builder() { + return new BuilderSingularListsBuilder<T>(); + } +} diff --git a/test/transform/resource/after-delombok/BuilderSingularMaps.java b/test/transform/resource/after-delombok/BuilderSingularMaps.java new file mode 100644 index 00000000..640ddb94 --- /dev/null +++ b/test/transform/resource/after-delombok/BuilderSingularMaps.java @@ -0,0 +1,173 @@ +import java.util.Map; +import java.util.SortedMap; +class BuilderSingularMaps<K, V> { + private Map<K, V> women; + private SortedMap<K, ? extends Number> men; + @SuppressWarnings("all") + private Map rawMap; + private Map<String, V> stringMap; + @java.lang.SuppressWarnings("all") + BuilderSingularMaps(final Map<K, V> women, final SortedMap<K, ? extends Number> men, final Map rawMap, final Map<String, V> stringMap) { + this.women = women; + this.men = men; + this.rawMap = rawMap; + this.stringMap = stringMap; + } + @java.lang.SuppressWarnings("all") + public static class BuilderSingularMapsBuilder<K, V> { + private java.util.ArrayList<K> women$key; + private java.util.ArrayList<V> women$value; + private java.util.ArrayList<K> men$key; + private java.util.ArrayList<Number> men$value; + private java.util.ArrayList<java.lang.Object> rawMap$key; + private java.util.ArrayList<java.lang.Object> rawMap$value; + private java.util.ArrayList<String> stringMap$key; + private java.util.ArrayList<V> stringMap$value; + @java.lang.SuppressWarnings("all") + BuilderSingularMapsBuilder() { + } + @java.lang.SuppressWarnings("all") + public BuilderSingularMapsBuilder<K, V> woman(final K womanKey, final V womanValue) { + if (this.women$key == null) { + this.women$key = new java.util.ArrayList<K>(); + this.women$value = new java.util.ArrayList<V>(); + } + this.women$key.add(womanKey); + this.women$value.add(womanValue); + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderSingularMapsBuilder<K, V> women(final java.util.Map<? extends K, ? extends V> women) { + if (this.women$key == null) { + this.women$key = new java.util.ArrayList<K>(); + this.women$value = new java.util.ArrayList<V>(); + } + for (final java.util.Map.Entry<? extends K, ? extends V> $lombokEntry : women.entrySet()) { + this.women$key.add($lombokEntry.getKey()); + this.women$value.add($lombokEntry.getValue()); + } + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderSingularMapsBuilder<K, V> man(final K manKey, final Number manValue) { + if (this.men$key == null) { + this.men$key = new java.util.ArrayList<K>(); + this.men$value = new java.util.ArrayList<Number>(); + } + this.men$key.add(manKey); + this.men$value.add(manValue); + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderSingularMapsBuilder<K, V> men(final java.util.Map<? extends K, ? extends Number> men) { + if (this.men$key == null) { + this.men$key = new java.util.ArrayList<K>(); + this.men$value = new java.util.ArrayList<Number>(); + } + for (final java.util.Map.Entry<? extends K, ? extends Number> $lombokEntry : men.entrySet()) { + this.men$key.add($lombokEntry.getKey()); + this.men$value.add($lombokEntry.getValue()); + } + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderSingularMapsBuilder<K, V> rawMap(final java.lang.Object rawMapKey, final java.lang.Object rawMapValue) { + if (this.rawMap$key == null) { + this.rawMap$key = new java.util.ArrayList<java.lang.Object>(); + this.rawMap$value = new java.util.ArrayList<java.lang.Object>(); + } + this.rawMap$key.add(rawMapKey); + this.rawMap$value.add(rawMapValue); + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderSingularMapsBuilder<K, V> rawMap(final java.util.Map<?, ?> rawMap) { + if (this.rawMap$key == null) { + this.rawMap$key = new java.util.ArrayList<java.lang.Object>(); + this.rawMap$value = new java.util.ArrayList<java.lang.Object>(); + } + for (final java.util.Map.Entry<?, ?> $lombokEntry : rawMap.entrySet()) { + this.rawMap$key.add($lombokEntry.getKey()); + this.rawMap$value.add($lombokEntry.getValue()); + } + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderSingularMapsBuilder<K, V> stringMap(final String stringMapKey, final V stringMapValue) { + if (this.stringMap$key == null) { + this.stringMap$key = new java.util.ArrayList<String>(); + this.stringMap$value = new java.util.ArrayList<V>(); + } + this.stringMap$key.add(stringMapKey); + this.stringMap$value.add(stringMapValue); + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderSingularMapsBuilder<K, V> stringMap(final java.util.Map<? extends String, ? extends V> stringMap) { + if (this.stringMap$key == null) { + this.stringMap$key = new java.util.ArrayList<String>(); + this.stringMap$value = new java.util.ArrayList<V>(); + } + for (final java.util.Map.Entry<? extends String, ? extends V> $lombokEntry : stringMap.entrySet()) { + this.stringMap$key.add($lombokEntry.getKey()); + this.stringMap$value.add($lombokEntry.getValue()); + } + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderSingularMaps<K, V> build() { + java.util.Map<K, V> women; + switch (this.women$key == null ? 0 : this.women$key.size()) { + case 0: + women = java.util.Collections.emptyMap(); + break; + case 1: + women = java.util.Collections.singletonMap(this.women$key.get(0), this.women$value.get(0)); + break; + default: + women = new java.util.LinkedHashMap<K, V>(this.women$key.size() < 1073741824 ? 1 + this.women$key.size() + (this.women$key.size() - 3) / 3 : java.lang.Integer.MAX_VALUE); + for (int $i = 0; $i < this.women$key.size(); $i++) women.put(this.women$key.get($i), this.women$value.get($i)); + women = java.util.Collections.unmodifiableMap(women); + } + java.util.SortedMap<K, Number> men = new java.util.TreeMap<K, Number>(); + if (this.men$key != null) for (int $i = 0; $i < (this.men$key == null ? 0 : this.men$key.size()); $i++) men.put(this.men$key.get($i), this.men$value.get($i)); + men = java.util.Collections.unmodifiableSortedMap(men); + java.util.Map<java.lang.Object, java.lang.Object> rawMap; + switch (this.rawMap$key == null ? 0 : this.rawMap$key.size()) { + case 0: + rawMap = java.util.Collections.emptyMap(); + break; + case 1: + rawMap = java.util.Collections.singletonMap(this.rawMap$key.get(0), this.rawMap$value.get(0)); + break; + default: + rawMap = new java.util.LinkedHashMap<java.lang.Object, java.lang.Object>(this.rawMap$key.size() < 1073741824 ? 1 + this.rawMap$key.size() + (this.rawMap$key.size() - 3) / 3 : java.lang.Integer.MAX_VALUE); + for (int $i = 0; $i < this.rawMap$key.size(); $i++) rawMap.put(this.rawMap$key.get($i), this.rawMap$value.get($i)); + rawMap = java.util.Collections.unmodifiableMap(rawMap); + } + java.util.Map<String, V> stringMap; + switch (this.stringMap$key == null ? 0 : this.stringMap$key.size()) { + case 0: + stringMap = java.util.Collections.emptyMap(); + break; + case 1: + stringMap = java.util.Collections.singletonMap(this.stringMap$key.get(0), this.stringMap$value.get(0)); + break; + default: + stringMap = new java.util.LinkedHashMap<String, V>(this.stringMap$key.size() < 1073741824 ? 1 + this.stringMap$key.size() + (this.stringMap$key.size() - 3) / 3 : java.lang.Integer.MAX_VALUE); + for (int $i = 0; $i < this.stringMap$key.size(); $i++) stringMap.put(this.stringMap$key.get($i), this.stringMap$value.get($i)); + stringMap = java.util.Collections.unmodifiableMap(stringMap); + } + return new BuilderSingularMaps<K, V>(women, men, rawMap, stringMap); + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public java.lang.String toString() { + return "BuilderSingularMaps.BuilderSingularMapsBuilder(women$key=" + this.women$key + ", women$value=" + this.women$value + ", men$key=" + this.men$key + ", men$value=" + this.men$value + ", rawMap$key=" + this.rawMap$key + ", rawMap$value=" + this.rawMap$value + ", stringMap$key=" + this.stringMap$key + ", stringMap$value=" + this.stringMap$value + ")"; + } + } + @java.lang.SuppressWarnings("all") + public static <K, V> BuilderSingularMapsBuilder<K, V> builder() { + return new BuilderSingularMapsBuilder<K, V>(); + } +} diff --git a/test/transform/resource/after-delombok/BuilderSingularNoAutosingularize.java b/test/transform/resource/after-delombok/BuilderSingularNoAutosingularize.java new file mode 100644 index 00000000..5f985e16 --- /dev/null +++ b/test/transform/resource/after-delombok/BuilderSingularNoAutosingularize.java @@ -0,0 +1,103 @@ +import java.util.List; +class BuilderSingularNoAutosingularize { + private List<String> things; + private List<String> widgets; + private List<String> items; + @java.lang.SuppressWarnings("all") + BuilderSingularNoAutosingularize(final List<String> things, final List<String> widgets, final List<String> items) { + this.things = things; + this.widgets = widgets; + this.items = items; + } + @java.lang.SuppressWarnings("all") + public static class BuilderSingularNoAutosingularizeBuilder { + private java.util.ArrayList<String> things; + private java.util.ArrayList<String> widgets; + private java.util.ArrayList<String> items; + @java.lang.SuppressWarnings("all") + BuilderSingularNoAutosingularizeBuilder() { + } + @java.lang.SuppressWarnings("all") + public BuilderSingularNoAutosingularizeBuilder things(final String things) { + if (this.things == null) this.things = new java.util.ArrayList<String>(); + this.things.add(things); + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderSingularNoAutosingularizeBuilder things(final java.util.Collection<? extends String> things) { + if (this.things == null) this.things = new java.util.ArrayList<String>(); + this.things.addAll(things); + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderSingularNoAutosingularizeBuilder widget(final String widget) { + if (this.widgets == null) this.widgets = new java.util.ArrayList<String>(); + this.widgets.add(widget); + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderSingularNoAutosingularizeBuilder widgets(final java.util.Collection<? extends String> widgets) { + if (this.widgets == null) this.widgets = new java.util.ArrayList<String>(); + this.widgets.addAll(widgets); + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderSingularNoAutosingularizeBuilder items(final String items) { + if (this.items == null) this.items = new java.util.ArrayList<String>(); + this.items.add(items); + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderSingularNoAutosingularizeBuilder items(final java.util.Collection<? extends String> items) { + if (this.items == null) this.items = new java.util.ArrayList<String>(); + this.items.addAll(items); + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderSingularNoAutosingularize build() { + java.util.List<String> things; + switch (this.things == null ? 0 : this.things.size()) { + case 0: + things = java.util.Collections.emptyList(); + break; + case 1: + things = java.util.Collections.singletonList(this.things.get(0)); + break; + default: + things = java.util.Collections.unmodifiableList(new java.util.ArrayList<String>(this.things)); + } + java.util.List<String> widgets; + switch (this.widgets == null ? 0 : this.widgets.size()) { + case 0: + widgets = java.util.Collections.emptyList(); + break; + case 1: + widgets = java.util.Collections.singletonList(this.widgets.get(0)); + break; + default: + widgets = java.util.Collections.unmodifiableList(new java.util.ArrayList<String>(this.widgets)); + } + java.util.List<String> items; + switch (this.items == null ? 0 : this.items.size()) { + case 0: + items = java.util.Collections.emptyList(); + break; + case 1: + items = java.util.Collections.singletonList(this.items.get(0)); + break; + default: + items = java.util.Collections.unmodifiableList(new java.util.ArrayList<String>(this.items)); + } + return new BuilderSingularNoAutosingularize(things, widgets, items); + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public java.lang.String toString() { + return "BuilderSingularNoAutosingularize.BuilderSingularNoAutosingularizeBuilder(things=" + this.things + ", widgets=" + this.widgets + ", items=" + this.items + ")"; + } + } + @java.lang.SuppressWarnings("all") + public static BuilderSingularNoAutosingularizeBuilder builder() { + return new BuilderSingularNoAutosingularizeBuilder(); + } +}
\ No newline at end of file diff --git a/test/transform/resource/after-delombok/BuilderSingularRedirectToGuava.java b/test/transform/resource/after-delombok/BuilderSingularRedirectToGuava.java new file mode 100644 index 00000000..9c5ad25e --- /dev/null +++ b/test/transform/resource/after-delombok/BuilderSingularRedirectToGuava.java @@ -0,0 +1,75 @@ +import java.util.Set; +import java.util.NavigableMap; +import java.util.Collection; +class BuilderSingularRedirectToGuava { + private Set<String> dangerMice; + private NavigableMap<Integer, Number> things; + private Collection<Class<?>> doohickeys; + @java.lang.SuppressWarnings("all") + BuilderSingularRedirectToGuava(final Set<String> dangerMice, final NavigableMap<Integer, Number> things, final Collection<Class<?>> doohickeys) { + this.dangerMice = dangerMice; + this.things = things; + this.doohickeys = doohickeys; + } + @java.lang.SuppressWarnings("all") + public static class BuilderSingularRedirectToGuavaBuilder { + private com.google.common.collect.ImmutableSet.Builder<String> dangerMice; + private com.google.common.collect.ImmutableSortedMap.Builder<Integer, Number> things; + private com.google.common.collect.ImmutableList.Builder<Class<?>> doohickeys; + @java.lang.SuppressWarnings("all") + BuilderSingularRedirectToGuavaBuilder() { + } + @java.lang.SuppressWarnings("all") + public BuilderSingularRedirectToGuavaBuilder dangerMouse(final String dangerMouse) { + if (this.dangerMice == null) this.dangerMice = com.google.common.collect.ImmutableSet.builder(); + this.dangerMice.add(dangerMouse); + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderSingularRedirectToGuavaBuilder dangerMice(final java.lang.Iterable<? extends String> dangerMice) { + if (this.dangerMice == null) this.dangerMice = com.google.common.collect.ImmutableSet.builder(); + this.dangerMice.addAll(dangerMice); + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderSingularRedirectToGuavaBuilder thing(final Integer thing$key, final Number thing$value) { + if (this.things == null) this.things = com.google.common.collect.ImmutableSortedMap.naturalOrder(); + this.things.put(thing$key, thing$value); + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderSingularRedirectToGuavaBuilder things(final java.util.Map<? extends Integer, ? extends Number> things) { + if (this.things == null) this.things = com.google.common.collect.ImmutableSortedMap.naturalOrder(); + this.things.putAll(things); + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderSingularRedirectToGuavaBuilder doohickey(final Class<?> doohickey) { + if (this.doohickeys == null) this.doohickeys = com.google.common.collect.ImmutableList.builder(); + this.doohickeys.add(doohickey); + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderSingularRedirectToGuavaBuilder doohickeys(final java.lang.Iterable<? extends Class<?>> doohickeys) { + if (this.doohickeys == null) this.doohickeys = com.google.common.collect.ImmutableList.builder(); + this.doohickeys.addAll(doohickeys); + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderSingularRedirectToGuava build() { + java.util.Set<String> dangerMice = this.dangerMice == null ? com.google.common.collect.ImmutableSet.<String>of() : this.dangerMice.build(); + java.util.NavigableMap<Integer, Number> things = this.things == null ? com.google.common.collect.ImmutableSortedMap.<Integer, Number>of() : this.things.build(); + java.util.Collection<Class<?>> doohickeys = this.doohickeys == null ? com.google.common.collect.ImmutableList.<Class<?>>of() : this.doohickeys.build(); + return new BuilderSingularRedirectToGuava(dangerMice, things, doohickeys); + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public java.lang.String toString() { + return "BuilderSingularRedirectToGuava.BuilderSingularRedirectToGuavaBuilder(dangerMice=" + this.dangerMice + ", things=" + this.things + ", doohickeys=" + this.doohickeys + ")"; + } + } + @java.lang.SuppressWarnings("all") + public static BuilderSingularRedirectToGuavaBuilder builder() { + return new BuilderSingularRedirectToGuavaBuilder(); + } +}
\ No newline at end of file diff --git a/test/transform/resource/after-delombok/BuilderSingularSets.java b/test/transform/resource/after-delombok/BuilderSingularSets.java new file mode 100644 index 00000000..d75c0fbf --- /dev/null +++ b/test/transform/resource/after-delombok/BuilderSingularSets.java @@ -0,0 +1,129 @@ +import java.util.Set; +import java.util.SortedSet; +class BuilderSingularSets<T> { + private Set<T> dangerMice; + private SortedSet<? extends Number> octopodes; + @SuppressWarnings("all") + private Set rawSet; + private Set<String> stringSet; + @java.lang.SuppressWarnings("all") + BuilderSingularSets(final Set<T> dangerMice, final SortedSet<? extends Number> octopodes, final Set rawSet, final Set<String> stringSet) { + this.dangerMice = dangerMice; + this.octopodes = octopodes; + this.rawSet = rawSet; + this.stringSet = stringSet; + } + @java.lang.SuppressWarnings("all") + public static class BuilderSingularSetsBuilder<T> { + private java.util.ArrayList<T> dangerMice; + private java.util.ArrayList<Number> octopodes; + private java.util.ArrayList<java.lang.Object> rawSet; + private java.util.ArrayList<String> stringSet; + @java.lang.SuppressWarnings("all") + BuilderSingularSetsBuilder() { + } + @java.lang.SuppressWarnings("all") + public BuilderSingularSetsBuilder<T> dangerMouse(final T dangerMouse) { + if (this.dangerMice == null) this.dangerMice = new java.util.ArrayList<T>(); + this.dangerMice.add(dangerMouse); + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderSingularSetsBuilder<T> dangerMice(final java.util.Collection<? extends T> dangerMice) { + if (this.dangerMice == null) this.dangerMice = new java.util.ArrayList<T>(); + this.dangerMice.addAll(dangerMice); + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderSingularSetsBuilder<T> octopus(final Number octopus) { + if (this.octopodes == null) this.octopodes = new java.util.ArrayList<Number>(); + this.octopodes.add(octopus); + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderSingularSetsBuilder<T> octopodes(final java.util.Collection<? extends Number> octopodes) { + if (this.octopodes == null) this.octopodes = new java.util.ArrayList<Number>(); + this.octopodes.addAll(octopodes); + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderSingularSetsBuilder<T> rawSet(final java.lang.Object rawSet) { + if (this.rawSet == null) this.rawSet = new java.util.ArrayList<java.lang.Object>(); + this.rawSet.add(rawSet); + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderSingularSetsBuilder<T> rawSet(final java.util.Collection<?> rawSet) { + if (this.rawSet == null) this.rawSet = new java.util.ArrayList<java.lang.Object>(); + this.rawSet.addAll(rawSet); + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderSingularSetsBuilder<T> stringSet(final String stringSet) { + if (this.stringSet == null) this.stringSet = new java.util.ArrayList<String>(); + this.stringSet.add(stringSet); + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderSingularSetsBuilder<T> stringSet(final java.util.Collection<? extends String> stringSet) { + if (this.stringSet == null) this.stringSet = new java.util.ArrayList<String>(); + this.stringSet.addAll(stringSet); + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderSingularSets<T> build() { + java.util.Set<T> dangerMice; + switch (this.dangerMice == null ? 0 : this.dangerMice.size()) { + case 0: + dangerMice = java.util.Collections.emptySet(); + break; + case 1: + dangerMice = java.util.Collections.singleton(this.dangerMice.get(0)); + break; + default: + dangerMice = new java.util.LinkedHashSet<T>(this.dangerMice.size() < 1073741824 ? 1 + this.dangerMice.size() + (this.dangerMice.size() - 3) / 3 : java.lang.Integer.MAX_VALUE); + dangerMice.addAll(this.dangerMice); + dangerMice = java.util.Collections.unmodifiableSet(dangerMice); + } + java.util.SortedSet<Number> octopodes = new java.util.TreeSet<Number>(); + if (this.octopodes != null) octopodes.addAll(this.octopodes); + octopodes = java.util.Collections.unmodifiableSortedSet(octopodes); + java.util.Set<java.lang.Object> rawSet; + switch (this.rawSet == null ? 0 : this.rawSet.size()) { + case 0: + rawSet = java.util.Collections.emptySet(); + break; + case 1: + rawSet = java.util.Collections.singleton(this.rawSet.get(0)); + break; + default: + rawSet = new java.util.LinkedHashSet<java.lang.Object>(this.rawSet.size() < 1073741824 ? 1 + this.rawSet.size() + (this.rawSet.size() - 3) / 3 : java.lang.Integer.MAX_VALUE); + rawSet.addAll(this.rawSet); + rawSet = java.util.Collections.unmodifiableSet(rawSet); + } + java.util.Set<String> stringSet; + switch (this.stringSet == null ? 0 : this.stringSet.size()) { + case 0: + stringSet = java.util.Collections.emptySet(); + break; + case 1: + stringSet = java.util.Collections.singleton(this.stringSet.get(0)); + break; + default: + stringSet = new java.util.LinkedHashSet<String>(this.stringSet.size() < 1073741824 ? 1 + this.stringSet.size() + (this.stringSet.size() - 3) / 3 : java.lang.Integer.MAX_VALUE); + stringSet.addAll(this.stringSet); + stringSet = java.util.Collections.unmodifiableSet(stringSet); + } + return new BuilderSingularSets<T>(dangerMice, octopodes, rawSet, stringSet); + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public java.lang.String toString() { + return "BuilderSingularSets.BuilderSingularSetsBuilder(dangerMice=" + this.dangerMice + ", octopodes=" + this.octopodes + ", rawSet=" + this.rawSet + ", stringSet=" + this.stringSet + ")"; + } + } + @java.lang.SuppressWarnings("all") + public static <T> BuilderSingularSetsBuilder<T> builder() { + return new BuilderSingularSetsBuilder<T>(); + } +}
\ No newline at end of file diff --git a/test/transform/resource/after-delombok/EncodingUsAscii.java b/test/transform/resource/after-delombok/EncodingUsAscii.java new file mode 100644 index 00000000..a9e6b1a5 --- /dev/null +++ b/test/transform/resource/after-delombok/EncodingUsAscii.java @@ -0,0 +1,9 @@ +//ENCODING: US-ASCII +class EncodingUsAscii { + String foo\u0e51\u0e51 = "\016\t\b "; + @java.lang.Override + @java.lang.SuppressWarnings("all") + public java.lang.String toString() { + return "EncodingUsAscii(foo\u0e51\u0e51=" + this.foo\u0e51\u0e51 + ")"; + } +}
\ No newline at end of file diff --git a/test/transform/resource/after-delombok/EncodingUtf8.java b/test/transform/resource/after-delombok/EncodingUtf8.java new file mode 100644 index 00000000..9ae3e30a --- /dev/null +++ b/test/transform/resource/after-delombok/EncodingUtf8.java @@ -0,0 +1,8 @@ +class EncodingUtf8 { + String foo๑๑ = "\016\t\b "; + @java.lang.Override + @java.lang.SuppressWarnings("all") + public java.lang.String toString() { + return "EncodingUtf8(foo๑๑=" + this.foo๑๑ + ")"; + } +}
\ No newline at end of file diff --git a/test/transform/resource/after-ecj/BuilderSingularGuavaListsSets.java b/test/transform/resource/after-ecj/BuilderSingularGuavaListsSets.java new file mode 100644 index 00000000..aee837b6 --- /dev/null +++ b/test/transform/resource/after-ecj/BuilderSingularGuavaListsSets.java @@ -0,0 +1,88 @@ +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSortedSet; +import lombok.Singular; +@lombok.Builder class BuilderSingularGuavaListsSets<T> { + public static @java.lang.SuppressWarnings("all") class BuilderSingularGuavaListsSetsBuilder<T> { + private com.google.common.collect.ImmutableList.Builder<T> cards; + private com.google.common.collect.ImmutableList.Builder<Number> frogs; + private com.google.common.collect.ImmutableSet.Builder<java.lang.Object> rawSet; + private com.google.common.collect.ImmutableSortedSet.Builder<String> passes; + @java.lang.SuppressWarnings("all") BuilderSingularGuavaListsSetsBuilder() { + super(); + } + public @java.lang.SuppressWarnings("all") BuilderSingularGuavaListsSetsBuilder<T> card(T card) { + if ((this.cards == null)) + this.cards = com.google.common.collect.ImmutableList.builder(); + this.cards.add(card); + return this; + } + public @java.lang.SuppressWarnings("all") BuilderSingularGuavaListsSetsBuilder<T> cards(java.lang.Iterable<? extends T> cards) { + if ((this.cards == null)) + this.cards = com.google.common.collect.ImmutableList.builder(); + this.cards.addAll(cards); + return this; + } + public @java.lang.SuppressWarnings("all") BuilderSingularGuavaListsSetsBuilder<T> frog(Number frog) { + if ((this.frogs == null)) + this.frogs = com.google.common.collect.ImmutableList.builder(); + this.frogs.add(frog); + return this; + } + public @java.lang.SuppressWarnings("all") BuilderSingularGuavaListsSetsBuilder<T> frogs(java.lang.Iterable<? extends Number> frogs) { + if ((this.frogs == null)) + this.frogs = com.google.common.collect.ImmutableList.builder(); + this.frogs.addAll(frogs); + return this; + } + public @java.lang.SuppressWarnings("all") BuilderSingularGuavaListsSetsBuilder<T> rawSet(java.lang.Object rawSet) { + if ((this.rawSet == null)) + this.rawSet = com.google.common.collect.ImmutableSet.builder(); + this.rawSet.add(rawSet); + return this; + } + public @java.lang.SuppressWarnings("all") BuilderSingularGuavaListsSetsBuilder<T> rawSet(java.lang.Iterable<?> rawSet) { + if ((this.rawSet == null)) + this.rawSet = com.google.common.collect.ImmutableSet.builder(); + this.rawSet.addAll(rawSet); + return this; + } + public @java.lang.SuppressWarnings("all") BuilderSingularGuavaListsSetsBuilder<T> pass(String pass) { + if ((this.passes == null)) + this.passes = com.google.common.collect.ImmutableSortedSet.naturalOrder(); + this.passes.add(pass); + return this; + } + public @java.lang.SuppressWarnings("all") BuilderSingularGuavaListsSetsBuilder<T> passes(java.lang.Iterable<? extends String> passes) { + if ((this.passes == null)) + this.passes = com.google.common.collect.ImmutableSortedSet.naturalOrder(); + this.passes.addAll(passes); + return this; + } + public @java.lang.SuppressWarnings("all") BuilderSingularGuavaListsSets<T> build() { + com.google.common.collect.ImmutableList<T> cards = ((this.cards == null) ? com.google.common.collect.ImmutableList.<T>of() : this.cards.build()); + com.google.common.collect.ImmutableCollection<Number> frogs = ((this.frogs == null) ? com.google.common.collect.ImmutableList.<Number>of() : this.frogs.build()); + com.google.common.collect.ImmutableSet<java.lang.Object> rawSet = ((this.rawSet == null) ? com.google.common.collect.ImmutableSet.<java.lang.Object>of() : this.rawSet.build()); + com.google.common.collect.ImmutableSortedSet<String> passes = ((this.passes == null) ? com.google.common.collect.ImmutableSortedSet.<String>of() : this.passes.build()); + return new BuilderSingularGuavaListsSets<T>(cards, frogs, rawSet, passes); + } + public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() { + return (((((((("BuilderSingularGuavaListsSets.BuilderSingularGuavaListsSetsBuilder(cards=" + this.cards) + ", frogs=") + this.frogs) + ", rawSet=") + this.rawSet) + ", passes=") + this.passes) + ")"); + } + } + private @Singular ImmutableList<T> cards; + private @Singular ImmutableCollection<? extends Number> frogs; + private @SuppressWarnings("all") @Singular("rawSet") ImmutableSet rawSet; + private @Singular ImmutableSortedSet<String> passes; + @java.lang.SuppressWarnings("all") BuilderSingularGuavaListsSets(final ImmutableList<T> cards, final ImmutableCollection<? extends Number> frogs, final ImmutableSet rawSet, final ImmutableSortedSet<String> passes) { + super(); + this.cards = cards; + this.frogs = frogs; + this.rawSet = rawSet; + this.passes = passes; + } + public static @java.lang.SuppressWarnings("all") <T>BuilderSingularGuavaListsSetsBuilder<T> builder() { + return new BuilderSingularGuavaListsSetsBuilder<T>(); + } +} diff --git a/test/transform/resource/after-ecj/BuilderSingularGuavaMaps.java b/test/transform/resource/after-ecj/BuilderSingularGuavaMaps.java new file mode 100644 index 00000000..27961e2b --- /dev/null +++ b/test/transform/resource/after-ecj/BuilderSingularGuavaMaps.java @@ -0,0 +1,71 @@ +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableBiMap; +import com.google.common.collect.ImmutableSortedMap; +import lombok.Singular; +@lombok.Builder class BuilderSingularGuavaMaps<K, V> { + public static @java.lang.SuppressWarnings("all") class BuilderSingularGuavaMapsBuilder<K, V> { + private com.google.common.collect.ImmutableMap.Builder<K, V> battleaxes; + private com.google.common.collect.ImmutableSortedMap.Builder<Integer, V> vertices; + private com.google.common.collect.ImmutableBiMap.Builder<java.lang.Object, java.lang.Object> rawMap; + @java.lang.SuppressWarnings("all") BuilderSingularGuavaMapsBuilder() { + super(); + } + public @java.lang.SuppressWarnings("all") BuilderSingularGuavaMapsBuilder<K, V> battleaxe(K battleaxe$key, V battleaxe$value) { + if ((this.battleaxes == null)) + this.battleaxes = com.google.common.collect.ImmutableMap.builder(); + this.battleaxes.put(battleaxe$key, battleaxe$value); + return this; + } + public @java.lang.SuppressWarnings("all") BuilderSingularGuavaMapsBuilder<K, V> battleaxes(java.util.Map<? extends K, ? extends V> battleaxes) { + if ((this.battleaxes == null)) + this.battleaxes = com.google.common.collect.ImmutableMap.builder(); + this.battleaxes.putAll(battleaxes); + return this; + } + public @java.lang.SuppressWarnings("all") BuilderSingularGuavaMapsBuilder<K, V> vertex(Integer vertex$key, V vertex$value) { + if ((this.vertices == null)) + this.vertices = com.google.common.collect.ImmutableSortedMap.naturalOrder(); + this.vertices.put(vertex$key, vertex$value); + return this; + } + public @java.lang.SuppressWarnings("all") BuilderSingularGuavaMapsBuilder<K, V> vertices(java.util.Map<? extends Integer, ? extends V> vertices) { + if ((this.vertices == null)) + this.vertices = com.google.common.collect.ImmutableSortedMap.naturalOrder(); + this.vertices.putAll(vertices); + return this; + } + public @java.lang.SuppressWarnings("all") BuilderSingularGuavaMapsBuilder<K, V> rawMap(java.lang.Object rawMap$key, java.lang.Object rawMap$value) { + if ((this.rawMap == null)) + this.rawMap = com.google.common.collect.ImmutableBiMap.builder(); + this.rawMap.put(rawMap$key, rawMap$value); + return this; + } + public @java.lang.SuppressWarnings("all") BuilderSingularGuavaMapsBuilder<K, V> rawMap(java.util.Map<?, ?> rawMap) { + if ((this.rawMap == null)) + this.rawMap = com.google.common.collect.ImmutableBiMap.builder(); + this.rawMap.putAll(rawMap); + return this; + } + public @java.lang.SuppressWarnings("all") BuilderSingularGuavaMaps<K, V> build() { + com.google.common.collect.ImmutableMap<K, V> battleaxes = ((this.battleaxes == null) ? com.google.common.collect.ImmutableMap.<K, V>of() : this.battleaxes.build()); + com.google.common.collect.ImmutableSortedMap<Integer, V> vertices = ((this.vertices == null) ? com.google.common.collect.ImmutableSortedMap.<Integer, V>of() : this.vertices.build()); + com.google.common.collect.ImmutableBiMap<java.lang.Object, java.lang.Object> rawMap = ((this.rawMap == null) ? com.google.common.collect.ImmutableBiMap.<java.lang.Object, java.lang.Object>of() : this.rawMap.build()); + return new BuilderSingularGuavaMaps<K, V>(battleaxes, vertices, rawMap); + } + public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() { + return (((((("BuilderSingularGuavaMaps.BuilderSingularGuavaMapsBuilder(battleaxes=" + this.battleaxes) + ", vertices=") + this.vertices) + ", rawMap=") + this.rawMap) + ")"); + } + } + private @Singular ImmutableMap<K, V> battleaxes; + private @Singular ImmutableSortedMap<Integer, ? extends V> vertices; + private @SuppressWarnings("all") @Singular("rawMap") ImmutableBiMap rawMap; + @java.lang.SuppressWarnings("all") BuilderSingularGuavaMaps(final ImmutableMap<K, V> battleaxes, final ImmutableSortedMap<Integer, ? extends V> vertices, final ImmutableBiMap rawMap) { + super(); + this.battleaxes = battleaxes; + this.vertices = vertices; + this.rawMap = rawMap; + } + public static @java.lang.SuppressWarnings("all") <K, V>BuilderSingularGuavaMapsBuilder<K, V> builder() { + return new BuilderSingularGuavaMapsBuilder<K, V>(); + } +} diff --git a/test/transform/resource/after-ecj/BuilderSingularLists.java b/test/transform/resource/after-ecj/BuilderSingularLists.java new file mode 100644 index 00000000..9e4bb894 --- /dev/null +++ b/test/transform/resource/after-ecj/BuilderSingularLists.java @@ -0,0 +1,101 @@ +import java.util.List; +import java.util.Collection; + +import lombok.Singular; +@lombok.Builder class BuilderSingularLists<T> { + public static @java.lang.SuppressWarnings("all") class BuilderSingularListsBuilder<T> { + private java.util.ArrayList<T> children; + private java.util.ArrayList<Number> scarves; + private java.util.ArrayList<java.lang.Object> rawList; + @java.lang.SuppressWarnings("all") BuilderSingularListsBuilder() { + super(); + } + public @java.lang.SuppressWarnings("all") BuilderSingularListsBuilder<T> child(T child) { + if ((this.children == null)) + this.children = new java.util.ArrayList<T>(); + this.children.add(child); + return this; + } + public @java.lang.SuppressWarnings("all") BuilderSingularListsBuilder<T> children(java.util.Collection<? extends T> children) { + if ((this.children == null)) + this.children = new java.util.ArrayList<T>(); + this.children.addAll(children); + return this; + } + public @java.lang.SuppressWarnings("all") BuilderSingularListsBuilder<T> scarf(Number scarf) { + if ((this.scarves == null)) + this.scarves = new java.util.ArrayList<Number>(); + this.scarves.add(scarf); + return this; + } + public @java.lang.SuppressWarnings("all") BuilderSingularListsBuilder<T> scarves(java.util.Collection<? extends Number> scarves) { + if ((this.scarves == null)) + this.scarves = new java.util.ArrayList<Number>(); + this.scarves.addAll(scarves); + return this; + } + public @java.lang.SuppressWarnings("all") BuilderSingularListsBuilder<T> rawList(java.lang.Object rawList) { + if ((this.rawList == null)) + this.rawList = new java.util.ArrayList<java.lang.Object>(); + this.rawList.add(rawList); + return this; + } + public @java.lang.SuppressWarnings("all") BuilderSingularListsBuilder<T> rawList(java.util.Collection<?> rawList) { + if ((this.rawList == null)) + this.rawList = new java.util.ArrayList<java.lang.Object>(); + this.rawList.addAll(rawList); + return this; + } + public @java.lang.SuppressWarnings("all") BuilderSingularLists<T> build() { + java.util.List<T> children; + switch (((this.children == null) ? 0 : this.children.size())) { + case 0 : + children = java.util.Collections.emptyList(); + break; + case 1 : + children = java.util.Collections.singletonList(this.children.get(0)); + break; + default : + children = java.util.Collections.unmodifiableList(new java.util.ArrayList<T>(this.children)); + } + java.util.Collection<Number> scarves; + switch (((this.scarves == null) ? 0 : this.scarves.size())) { + case 0 : + scarves = java.util.Collections.emptyList(); + break; + case 1 : + scarves = java.util.Collections.singletonList(this.scarves.get(0)); + break; + default : + scarves = java.util.Collections.unmodifiableList(new java.util.ArrayList<Number>(this.scarves)); + } + java.util.List<java.lang.Object> rawList; + switch (((this.rawList == null) ? 0 : this.rawList.size())) { + case 0 : + rawList = java.util.Collections.emptyList(); + break; + case 1 : + rawList = java.util.Collections.singletonList(this.rawList.get(0)); + break; + default : + rawList = java.util.Collections.unmodifiableList(new java.util.ArrayList<java.lang.Object>(this.rawList)); + } + return new BuilderSingularLists<T>(children, scarves, rawList); + } + public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() { + return (((((("BuilderSingularLists.BuilderSingularListsBuilder(children=" + this.children) + ", scarves=") + this.scarves) + ", rawList=") + this.rawList) + ")"); + } + } + private @Singular List<T> children; + private @Singular Collection<? extends Number> scarves; + private @SuppressWarnings("all") @Singular("rawList") List rawList; + @java.lang.SuppressWarnings("all") BuilderSingularLists(final List<T> children, final Collection<? extends Number> scarves, final List rawList) { + super(); + this.children = children; + this.scarves = scarves; + this.rawList = rawList; + } + public static @java.lang.SuppressWarnings("all") <T>BuilderSingularListsBuilder<T> builder() { + return new BuilderSingularListsBuilder<T>(); + } +}
\ No newline at end of file diff --git a/test/transform/resource/after-ecj/BuilderSingularMaps.java b/test/transform/resource/after-ecj/BuilderSingularMaps.java new file mode 100644 index 00000000..0ce5a0a9 --- /dev/null +++ b/test/transform/resource/after-ecj/BuilderSingularMaps.java @@ -0,0 +1,177 @@ +import java.util.Map; +import java.util.SortedMap; +import lombok.Singular; +@lombok.Builder class BuilderSingularMaps<K, V> { + public static @java.lang.SuppressWarnings("all") class BuilderSingularMapsBuilder<K, V> { + private java.util.ArrayList<K> women$key; + private java.util.ArrayList<V> women$value; + private java.util.ArrayList<K> men$key; + private java.util.ArrayList<Number> men$value; + private java.util.ArrayList<java.lang.Object> rawMap$key; + private java.util.ArrayList<java.lang.Object> rawMap$value; + private java.util.ArrayList<String> stringMap$key; + private java.util.ArrayList<V> stringMap$value; + @java.lang.SuppressWarnings("all") BuilderSingularMapsBuilder() { + super(); + } + public @java.lang.SuppressWarnings("all") BuilderSingularMapsBuilder<K, V> woman(K womanKey, V womanValue) { + if ((this.women$key == null)) + { + this.women$key = new java.util.ArrayList<K>(); + this.women$value = new java.util.ArrayList<V>(); + } + this.women$key.add(womanKey); + this.women$value.add(womanValue); + return this; + } + public @java.lang.SuppressWarnings("all") BuilderSingularMapsBuilder<K, V> women(java.util.Map<? extends K, ? extends V> women) { + if ((this.women$key == null)) + { + this.women$key = new java.util.ArrayList<K>(); + this.women$value = new java.util.ArrayList<V>(); + } + for (java.util.Map.Entry<? extends K, ? extends V> $lombokEntry : women.entrySet()) + { + this.women$key.add($lombokEntry.getKey()); + this.women$value.add($lombokEntry.getValue()); + } + return this; + } + public @java.lang.SuppressWarnings("all") BuilderSingularMapsBuilder<K, V> man(K manKey, Number manValue) { + if ((this.men$key == null)) + { + this.men$key = new java.util.ArrayList<K>(); + this.men$value = new java.util.ArrayList<Number>(); + } + this.men$key.add(manKey); + this.men$value.add(manValue); + return this; + } + public @java.lang.SuppressWarnings("all") BuilderSingularMapsBuilder<K, V> men(java.util.Map<? extends K, ? extends Number> men) { + if ((this.men$key == null)) + { + this.men$key = new java.util.ArrayList<K>(); + this.men$value = new java.util.ArrayList<Number>(); + } + for (java.util.Map.Entry<? extends K, ? extends Number> $lombokEntry : men.entrySet()) + { + this.men$key.add($lombokEntry.getKey()); + this.men$value.add($lombokEntry.getValue()); + } + return this; + } + public @java.lang.SuppressWarnings("all") BuilderSingularMapsBuilder<K, V> rawMap(java.lang.Object rawMapKey, java.lang.Object rawMapValue) { + if ((this.rawMap$key == null)) + { + this.rawMap$key = new java.util.ArrayList<java.lang.Object>(); + this.rawMap$value = new java.util.ArrayList<java.lang.Object>(); + } + this.rawMap$key.add(rawMapKey); + this.rawMap$value.add(rawMapValue); + return this; + } + public @java.lang.SuppressWarnings("all") BuilderSingularMapsBuilder<K, V> rawMap(java.util.Map<?, ?> rawMap) { + if ((this.rawMap$key == null)) + { + this.rawMap$key = new java.util.ArrayList<java.lang.Object>(); + this.rawMap$value = new java.util.ArrayList<java.lang.Object>(); + } + for (java.util.Map.Entry<?, ?> $lombokEntry : rawMap.entrySet()) + { + this.rawMap$key.add($lombokEntry.getKey()); + this.rawMap$value.add($lombokEntry.getValue()); + } + return this; + } + public @java.lang.SuppressWarnings("all") BuilderSingularMapsBuilder<K, V> stringMap(String stringMapKey, V stringMapValue) { + if ((this.stringMap$key == null)) + { + this.stringMap$key = new java.util.ArrayList<String>(); + this.stringMap$value = new java.util.ArrayList<V>(); + } + this.stringMap$key.add(stringMapKey); + this.stringMap$value.add(stringMapValue); + return this; + } + public @java.lang.SuppressWarnings("all") BuilderSingularMapsBuilder<K, V> stringMap(java.util.Map<? extends String, ? extends V> stringMap) { + if ((this.stringMap$key == null)) + { + this.stringMap$key = new java.util.ArrayList<String>(); + this.stringMap$value = new java.util.ArrayList<V>(); + } + for (java.util.Map.Entry<? extends String, ? extends V> $lombokEntry : stringMap.entrySet()) + { + this.stringMap$key.add($lombokEntry.getKey()); + this.stringMap$value.add($lombokEntry.getValue()); + } + return this; + } + public @java.lang.SuppressWarnings("all") BuilderSingularMaps<K, V> build() { + java.util.Map<K, V> women; + switch (((this.women$key == null) ? 0 : this.women$key.size())) { + case 0 : + women = java.util.Collections.emptyMap(); + break; + case 1 : + women = java.util.Collections.singletonMap(this.women$key.get(0), this.women$value.get(0)); + break; + default : + women = new java.util.LinkedHashMap<K, V>(((this.women$key.size() < 0x40000000) ? ((1 + this.women$key.size()) + ((this.women$key.size() - 3) / 3)) : java.lang.Integer.MAX_VALUE)); + for (int $i = 0;; ($i < this.women$key.size()); $i ++) + women.put(this.women$key.get($i), this.women$value.get($i)); + women = java.util.Collections.unmodifiableMap(women); + } + java.util.SortedMap<K, Number> men = new java.util.TreeMap<K, Number>(); + if ((this.men$key != null)) + for (int $i = 0;; ($i < ((this.men$key == null) ? 0 : this.men$key.size())); $i ++) + men.put(this.men$key.get($i), this.men$value.get($i)); + men = java.util.Collections.unmodifiableSortedMap(men); + java.util.Map<java.lang.Object, java.lang.Object> rawMap; + switch (((this.rawMap$key == null) ? 0 : this.rawMap$key.size())) { + case 0 : + rawMap = java.util.Collections.emptyMap(); + break; + case 1 : + rawMap = java.util.Collections.singletonMap(this.rawMap$key.get(0), this.rawMap$value.get(0)); + break; + default : + rawMap = new java.util.LinkedHashMap<java.lang.Object, java.lang.Object>(((this.rawMap$key.size() < 0x40000000) ? ((1 + this.rawMap$key.size()) + ((this.rawMap$key.size() - 3) / 3)) : java.lang.Integer.MAX_VALUE)); + for (int $i = 0;; ($i < this.rawMap$key.size()); $i ++) + rawMap.put(this.rawMap$key.get($i), this.rawMap$value.get($i)); + rawMap = java.util.Collections.unmodifiableMap(rawMap); + } + java.util.Map<String, V> stringMap; + switch (((this.stringMap$key == null) ? 0 : this.stringMap$key.size())) { + case 0 : + stringMap = java.util.Collections.emptyMap(); + break; + case 1 : + stringMap = java.util.Collections.singletonMap(this.stringMap$key.get(0), this.stringMap$value.get(0)); + break; + default : + stringMap = new java.util.LinkedHashMap<String, V>(((this.stringMap$key.size() < 0x40000000) ? ((1 + this.stringMap$key.size()) + ((this.stringMap$key.size() - 3) / 3)) : java.lang.Integer.MAX_VALUE)); + for (int $i = 0;; ($i < this.stringMap$key.size()); $i ++) + stringMap.put(this.stringMap$key.get($i), this.stringMap$value.get($i)); + stringMap = java.util.Collections.unmodifiableMap(stringMap); + } + return new BuilderSingularMaps<K, V>(women, men, rawMap, stringMap); + } + public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() { + return (((((((((((((((("BuilderSingularMaps.BuilderSingularMapsBuilder(women$key=" + this.women$key) + ", women$value=") + this.women$value) + ", men$key=") + this.men$key) + ", men$value=") + this.men$value) + ", rawMap$key=") + this.rawMap$key) + ", rawMap$value=") + this.rawMap$value) + ", stringMap$key=") + this.stringMap$key) + ", stringMap$value=") + this.stringMap$value) + ")"); + } + } + private @Singular Map<K, V> women; + private @Singular SortedMap<K, ? extends Number> men; + private @SuppressWarnings("all") @Singular("rawMap") Map rawMap; + private @Singular("stringMap") Map<String, V> stringMap; + @java.lang.SuppressWarnings("all") BuilderSingularMaps(final Map<K, V> women, final SortedMap<K, ? extends Number> men, final Map rawMap, final Map<String, V> stringMap) { + super(); + this.women = women; + this.men = men; + this.rawMap = rawMap; + this.stringMap = stringMap; + } + public static @java.lang.SuppressWarnings("all") <K, V>BuilderSingularMapsBuilder<K, V> builder() { + return new BuilderSingularMapsBuilder<K, V>(); + } +}
\ No newline at end of file diff --git a/test/transform/resource/after-ecj/BuilderSingularNoAutosingularize.java b/test/transform/resource/after-ecj/BuilderSingularNoAutosingularize.java new file mode 100644 index 00000000..16febc2e --- /dev/null +++ b/test/transform/resource/after-ecj/BuilderSingularNoAutosingularize.java @@ -0,0 +1,99 @@ +import java.util.List; +import lombok.Singular; +@lombok.Builder class BuilderSingularNoAutosingularize { + public static @java.lang.SuppressWarnings("all") class BuilderSingularNoAutosingularizeBuilder { + private java.util.ArrayList<String> things; + private java.util.ArrayList<String> widgets; + private java.util.ArrayList<String> items; + @java.lang.SuppressWarnings("all") BuilderSingularNoAutosingularizeBuilder() { + super(); + } + public @java.lang.SuppressWarnings("all") BuilderSingularNoAutosingularizeBuilder things(String things) { + if ((this.things == null)) + this.things = new java.util.ArrayList<String>(); + this.things.add(things); + return this; + } + public @java.lang.SuppressWarnings("all") BuilderSingularNoAutosingularizeBuilder things(java.util.Collection<? extends String> things) { + if ((this.things == null)) + this.things = new java.util.ArrayList<String>(); + this.things.addAll(things); + return this; + } + public @java.lang.SuppressWarnings("all") BuilderSingularNoAutosingularizeBuilder widget(String widget) { + if ((this.widgets == null)) + this.widgets = new java.util.ArrayList<String>(); + this.widgets.add(widget); + return this; + } + public @java.lang.SuppressWarnings("all") BuilderSingularNoAutosingularizeBuilder widgets(java.util.Collection<? extends String> widgets) { + if ((this.widgets == null)) + this.widgets = new java.util.ArrayList<String>(); + this.widgets.addAll(widgets); + return this; + } + public @java.lang.SuppressWarnings("all") BuilderSingularNoAutosingularizeBuilder items(String items) { + if ((this.items == null)) + this.items = new java.util.ArrayList<String>(); + this.items.add(items); + return this; + } + public @java.lang.SuppressWarnings("all") BuilderSingularNoAutosingularizeBuilder items(java.util.Collection<? extends String> items) { + if ((this.items == null)) + this.items = new java.util.ArrayList<String>(); + this.items.addAll(items); + return this; + } + public @java.lang.SuppressWarnings("all") BuilderSingularNoAutosingularize build() { + java.util.List<String> things; + switch (((this.things == null) ? 0 : this.things.size())) { + case 0 : + things = java.util.Collections.emptyList(); + break; + case 1 : + things = java.util.Collections.singletonList(this.things.get(0)); + break; + default : + things = java.util.Collections.unmodifiableList(new java.util.ArrayList<String>(this.things)); + } + java.util.List<String> widgets; + switch (((this.widgets == null) ? 0 : this.widgets.size())) { + case 0 : + widgets = java.util.Collections.emptyList(); + break; + case 1 : + widgets = java.util.Collections.singletonList(this.widgets.get(0)); + break; + default : + widgets = java.util.Collections.unmodifiableList(new java.util.ArrayList<String>(this.widgets)); + } + java.util.List<String> items; + switch (((this.items == null) ? 0 : this.items.size())) { + case 0 : + items = java.util.Collections.emptyList(); + break; + case 1 : + items = java.util.Collections.singletonList(this.items.get(0)); + break; + default : + items = java.util.Collections.unmodifiableList(new java.util.ArrayList<String>(this.items)); + } + return new BuilderSingularNoAutosingularize(things, widgets, items); + } + public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() { + return (((((("BuilderSingularNoAutosingularize.BuilderSingularNoAutosingularizeBuilder(things=" + this.things) + ", widgets=") + this.widgets) + ", items=") + this.items) + ")"); + } + } + private @Singular List<String> things; + private @Singular("widget") List<String> widgets; + private @Singular List<String> items; + @java.lang.SuppressWarnings("all") BuilderSingularNoAutosingularize(final List<String> things, final List<String> widgets, final List<String> items) { + super(); + this.things = things; + this.widgets = widgets; + this.items = items; + } + public static @java.lang.SuppressWarnings("all") BuilderSingularNoAutosingularizeBuilder builder() { + return new BuilderSingularNoAutosingularizeBuilder(); + } +} diff --git a/test/transform/resource/after-ecj/BuilderSingularRedirectToGuava.java b/test/transform/resource/after-ecj/BuilderSingularRedirectToGuava.java new file mode 100644 index 00000000..a71090bf --- /dev/null +++ b/test/transform/resource/after-ecj/BuilderSingularRedirectToGuava.java @@ -0,0 +1,71 @@ +import java.util.Set; +import java.util.NavigableMap; +import java.util.Collection; +import lombok.Singular; +@lombok.Builder class BuilderSingularRedirectToGuava { + public static @java.lang.SuppressWarnings("all") class BuilderSingularRedirectToGuavaBuilder { + private com.google.common.collect.ImmutableSet.Builder<String> dangerMice; + private com.google.common.collect.ImmutableSortedMap.Builder<Integer, Number> things; + private com.google.common.collect.ImmutableList.Builder<Class<?>> doohickeys; + @java.lang.SuppressWarnings("all") BuilderSingularRedirectToGuavaBuilder() { + super(); + } + public @java.lang.SuppressWarnings("all") BuilderSingularRedirectToGuavaBuilder dangerMouse(String dangerMouse) { + if ((this.dangerMice == null)) + this.dangerMice = com.google.common.collect.ImmutableSet.builder(); + this.dangerMice.add(dangerMouse); + return this; + } + public @java.lang.SuppressWarnings("all") BuilderSingularRedirectToGuavaBuilder dangerMice(java.lang.Iterable<? extends String> dangerMice) { + if ((this.dangerMice == null)) + this.dangerMice = com.google.common.collect.ImmutableSet.builder(); + this.dangerMice.addAll(dangerMice); + return this; + } + public @java.lang.SuppressWarnings("all") BuilderSingularRedirectToGuavaBuilder thing(Integer thing$key, Number thing$value) { + if ((this.things == null)) + this.things = com.google.common.collect.ImmutableSortedMap.naturalOrder(); + this.things.put(thing$key, thing$value); + return this; + } + public @java.lang.SuppressWarnings("all") BuilderSingularRedirectToGuavaBuilder things(java.util.Map<? extends Integer, ? extends Number> things) { + if ((this.things == null)) + this.things = com.google.common.collect.ImmutableSortedMap.naturalOrder(); + this.things.putAll(things); + return this; + } + public @java.lang.SuppressWarnings("all") BuilderSingularRedirectToGuavaBuilder doohickey(Class<?> doohickey) { + if ((this.doohickeys == null)) + this.doohickeys = com.google.common.collect.ImmutableList.builder(); + this.doohickeys.add(doohickey); + return this; + } + public @java.lang.SuppressWarnings("all") BuilderSingularRedirectToGuavaBuilder doohickeys(java.lang.Iterable<? extends Class<?>> doohickeys) { + if ((this.doohickeys == null)) + this.doohickeys = com.google.common.collect.ImmutableList.builder(); + this.doohickeys.addAll(doohickeys); + return this; + } + public @java.lang.SuppressWarnings("all") BuilderSingularRedirectToGuava build() { + java.util.Set<String> dangerMice = ((this.dangerMice == null) ? com.google.common.collect.ImmutableSet.<String>of() : this.dangerMice.build()); + java.util.NavigableMap<Integer, Number> things = ((this.things == null) ? com.google.common.collect.ImmutableSortedMap.<Integer, Number>of() : this.things.build()); + java.util.Collection<Class<?>> doohickeys = ((this.doohickeys == null) ? com.google.common.collect.ImmutableList.<Class<?>>of() : this.doohickeys.build()); + return new BuilderSingularRedirectToGuava(dangerMice, things, doohickeys); + } + public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() { + return (((((("BuilderSingularRedirectToGuava.BuilderSingularRedirectToGuavaBuilder(dangerMice=" + this.dangerMice) + ", things=") + this.things) + ", doohickeys=") + this.doohickeys) + ")"); + } + } + private @Singular Set<String> dangerMice; + private @Singular NavigableMap<Integer, Number> things; + private @Singular Collection<Class<?>> doohickeys; + @java.lang.SuppressWarnings("all") BuilderSingularRedirectToGuava(final Set<String> dangerMice, final NavigableMap<Integer, Number> things, final Collection<Class<?>> doohickeys) { + super(); + this.dangerMice = dangerMice; + this.things = things; + this.doohickeys = doohickeys; + } + public static @java.lang.SuppressWarnings("all") BuilderSingularRedirectToGuavaBuilder builder() { + return new BuilderSingularRedirectToGuavaBuilder(); + } +}
\ No newline at end of file diff --git a/test/transform/resource/after-ecj/BuilderSingularSets.java b/test/transform/resource/after-ecj/BuilderSingularSets.java new file mode 100644 index 00000000..5d2fcc59 --- /dev/null +++ b/test/transform/resource/after-ecj/BuilderSingularSets.java @@ -0,0 +1,125 @@ +import java.util.Set; +import java.util.SortedSet; +import lombok.Singular; +@lombok.Builder class BuilderSingularSets<T> { + public static @java.lang.SuppressWarnings("all") class BuilderSingularSetsBuilder<T> { + private java.util.ArrayList<T> dangerMice; + private java.util.ArrayList<Number> octopodes; + private java.util.ArrayList<java.lang.Object> rawSet; + private java.util.ArrayList<String> stringSet; + @java.lang.SuppressWarnings("all") BuilderSingularSetsBuilder() { + super(); + } + public @java.lang.SuppressWarnings("all") BuilderSingularSetsBuilder<T> dangerMouse(T dangerMouse) { + if ((this.dangerMice == null)) + this.dangerMice = new java.util.ArrayList<T>(); + this.dangerMice.add(dangerMouse); + return this; + } + public @java.lang.SuppressWarnings("all") BuilderSingularSetsBuilder<T> dangerMice(java.util.Collection<? extends T> dangerMice) { + if ((this.dangerMice == null)) + this.dangerMice = new java.util.ArrayList<T>(); + this.dangerMice.addAll(dangerMice); + return this; + } + public @java.lang.SuppressWarnings("all") BuilderSingularSetsBuilder<T> octopus(Number octopus) { + if ((this.octopodes == null)) + this.octopodes = new java.util.ArrayList<Number>(); + this.octopodes.add(octopus); + return this; + } + public @java.lang.SuppressWarnings("all") BuilderSingularSetsBuilder<T> octopodes(java.util.Collection<? extends Number> octopodes) { + if ((this.octopodes == null)) + this.octopodes = new java.util.ArrayList<Number>(); + this.octopodes.addAll(octopodes); + return this; + } + public @java.lang.SuppressWarnings("all") BuilderSingularSetsBuilder<T> rawSet(java.lang.Object rawSet) { + if ((this.rawSet == null)) + this.rawSet = new java.util.ArrayList<java.lang.Object>(); + this.rawSet.add(rawSet); + return this; + } + public @java.lang.SuppressWarnings("all") BuilderSingularSetsBuilder<T> rawSet(java.util.Collection<?> rawSet) { + if ((this.rawSet == null)) + this.rawSet = new java.util.ArrayList<java.lang.Object>(); + this.rawSet.addAll(rawSet); + return this; + } + public @java.lang.SuppressWarnings("all") BuilderSingularSetsBuilder<T> stringSet(String stringSet) { + if ((this.stringSet == null)) + this.stringSet = new java.util.ArrayList<String>(); + this.stringSet.add(stringSet); + return this; + } + public @java.lang.SuppressWarnings("all") BuilderSingularSetsBuilder<T> stringSet(java.util.Collection<? extends String> stringSet) { + if ((this.stringSet == null)) + this.stringSet = new java.util.ArrayList<String>(); + this.stringSet.addAll(stringSet); + return this; + } + public @java.lang.SuppressWarnings("all") BuilderSingularSets<T> build() { + java.util.Set<T> dangerMice; + switch (((this.dangerMice == null) ? 0 : this.dangerMice.size())) { + case 0 : + dangerMice = java.util.Collections.emptySet(); + break; + case 1 : + dangerMice = java.util.Collections.singleton(this.dangerMice.get(0)); + break; + default : + dangerMice = new java.util.LinkedHashSet<T>(((this.dangerMice.size() < 0x40000000) ? ((1 + this.dangerMice.size()) + ((this.dangerMice.size() - 3) / 3)) : java.lang.Integer.MAX_VALUE)); + dangerMice.addAll(this.dangerMice); + dangerMice = java.util.Collections.unmodifiableSet(dangerMice); + } + java.util.SortedSet<Number> octopodes = new java.util.TreeSet<Number>(); + if ((this.octopodes != null)) + octopodes.addAll(this.octopodes); + octopodes = java.util.Collections.unmodifiableSortedSet(octopodes); + java.util.Set<java.lang.Object> rawSet; + switch (((this.rawSet == null) ? 0 : this.rawSet.size())) { + case 0 : + rawSet = java.util.Collections.emptySet(); + break; + case 1 : + rawSet = java.util.Collections.singleton(this.rawSet.get(0)); + break; + default : + rawSet = new java.util.LinkedHashSet<java.lang.Object>(((this.rawSet.size() < 0x40000000) ? ((1 + this.rawSet.size()) + ((this.rawSet.size() - 3) / 3)) : java.lang.Integer.MAX_VALUE)); + rawSet.addAll(this.rawSet); + rawSet = java.util.Collections.unmodifiableSet(rawSet); + } + java.util.Set<String> stringSet; + switch (((this.stringSet == null) ? 0 : this.stringSet.size())) { + case 0 : + stringSet = java.util.Collections.emptySet(); + break; + case 1 : + stringSet = java.util.Collections.singleton(this.stringSet.get(0)); + break; + default : + stringSet = new java.util.LinkedHashSet<String>(((this.stringSet.size() < 0x40000000) ? ((1 + this.stringSet.size()) + ((this.stringSet.size() - 3) / 3)) : java.lang.Integer.MAX_VALUE)); + stringSet.addAll(this.stringSet); + stringSet = java.util.Collections.unmodifiableSet(stringSet); + } + return new BuilderSingularSets<T>(dangerMice, octopodes, rawSet, stringSet); + } + public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() { + return (((((((("BuilderSingularSets.BuilderSingularSetsBuilder(dangerMice=" + this.dangerMice) + ", octopodes=") + this.octopodes) + ", rawSet=") + this.rawSet) + ", stringSet=") + this.stringSet) + ")"); + } + } + private @Singular Set<T> dangerMice; + private @Singular SortedSet<? extends Number> octopodes; + private @SuppressWarnings("all") @Singular("rawSet") Set rawSet; + private @Singular("stringSet") Set<String> stringSet; + @java.lang.SuppressWarnings("all") BuilderSingularSets(final Set<T> dangerMice, final SortedSet<? extends Number> octopodes, final Set rawSet, final Set<String> stringSet) { + super(); + this.dangerMice = dangerMice; + this.octopodes = octopodes; + this.rawSet = rawSet; + this.stringSet = stringSet; + } + public static @java.lang.SuppressWarnings("all") <T>BuilderSingularSetsBuilder<T> builder() { + return new BuilderSingularSetsBuilder<T>(); + } +}
\ No newline at end of file diff --git a/test/transform/resource/after-ecj/BuilderWithExistingBuilderClass.java b/test/transform/resource/after-ecj/BuilderWithExistingBuilderClass.java index 38cb0038..8fb42a59 100644 --- a/test/transform/resource/after-ecj/BuilderWithExistingBuilderClass.java +++ b/test/transform/resource/after-ecj/BuilderWithExistingBuilderClass.java @@ -1,4 +1,4 @@ -import lombok.experimental.Builder; +import lombok.Builder; class BuilderWithExistingBuilderClass<T, K extends Number> { public static class BuilderWithExistingBuilderClassBuilder<Z extends Number> { private boolean arg2; diff --git a/test/transform/resource/after-ecj/EncodingUsAscii.java b/test/transform/resource/after-ecj/EncodingUsAscii.java new file mode 100644 index 00000000..b66feb0c --- /dev/null +++ b/test/transform/resource/after-ecj/EncodingUsAscii.java @@ -0,0 +1 @@ +//ignore: This test serves to check what happens with 'weird' characters when you use delombok. It's just not relevant for ecj. diff --git a/test/transform/resource/after-ecj/EncodingUtf8.java b/test/transform/resource/after-ecj/EncodingUtf8.java new file mode 100644 index 00000000..5a7a1644 --- /dev/null +++ b/test/transform/resource/after-ecj/EncodingUtf8.java @@ -0,0 +1,9 @@ +@lombok.ToString class EncodingUtf8 { + String foo๑๑ = "\t\b "; + EncodingUtf8() { + super(); + } + public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() { + return (("EncodingUtf8(foo๑๑=" + this.foo๑๑) + ")"); + } +}
\ No newline at end of file diff --git a/test/transform/resource/before/BuilderSingularGuavaListsSets.java b/test/transform/resource/before/BuilderSingularGuavaListsSets.java new file mode 100644 index 00000000..995c00e8 --- /dev/null +++ b/test/transform/resource/before/BuilderSingularGuavaListsSets.java @@ -0,0 +1,14 @@ +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSortedSet; + +import lombok.Singular; + +@lombok.Builder +class BuilderSingularGuavaListsSets<T> { + @Singular private ImmutableList<T> cards; + @Singular private ImmutableCollection<? extends Number> frogs; + @SuppressWarnings("all") @Singular("rawSet") private ImmutableSet rawSet; + @Singular private ImmutableSortedSet<String> passes; +} diff --git a/test/transform/resource/before/BuilderSingularGuavaMaps.java b/test/transform/resource/before/BuilderSingularGuavaMaps.java new file mode 100644 index 00000000..64a53570 --- /dev/null +++ b/test/transform/resource/before/BuilderSingularGuavaMaps.java @@ -0,0 +1,12 @@ +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableBiMap; +import com.google.common.collect.ImmutableSortedMap; + +import lombok.Singular; + +@lombok.Builder +class BuilderSingularGuavaMaps<K, V> { + @Singular private ImmutableMap<K, V> battleaxes; + @Singular private ImmutableSortedMap<Integer, ? extends V> vertices; + @SuppressWarnings("all") @Singular("rawMap") private ImmutableBiMap rawMap; +} diff --git a/test/transform/resource/before/BuilderSingularLists.java b/test/transform/resource/before/BuilderSingularLists.java new file mode 100644 index 00000000..93fced8e --- /dev/null +++ b/test/transform/resource/before/BuilderSingularLists.java @@ -0,0 +1,11 @@ +import java.util.List; +import java.util.Collection; + +import lombok.Singular; + +@lombok.Builder +class BuilderSingularLists<T> { + @Singular private List<T> children; + @Singular private Collection<? extends Number> scarves; + @SuppressWarnings("all") @Singular("rawList") private List rawList; +} diff --git a/test/transform/resource/before/BuilderSingularMaps.java b/test/transform/resource/before/BuilderSingularMaps.java new file mode 100644 index 00000000..e17f74fc --- /dev/null +++ b/test/transform/resource/before/BuilderSingularMaps.java @@ -0,0 +1,12 @@ +import java.util.Map; +import java.util.SortedMap; + +import lombok.Singular; + +@lombok.Builder +class BuilderSingularMaps<K, V> { + @Singular private Map<K, V> women; + @Singular private SortedMap<K, ? extends Number> men; + @SuppressWarnings("all") @Singular("rawMap") private Map rawMap; + @Singular("stringMap") private Map<String, V> stringMap; +} diff --git a/test/transform/resource/before/BuilderSingularNoAutoSingularize.java b/test/transform/resource/before/BuilderSingularNoAutoSingularize.java new file mode 100644 index 00000000..31e2c3ca --- /dev/null +++ b/test/transform/resource/before/BuilderSingularNoAutoSingularize.java @@ -0,0 +1,11 @@ +//CONF: lombok.singular.auto = false +import java.util.List; + +import lombok.Singular; + +@lombok.Builder +class BuilderSingularNoAutosingularize { + @Singular private List<String> things; + @Singular("widget") private List<String> widgets; + @Singular private List<String> items; +} diff --git a/test/transform/resource/before/BuilderSingularRedirectToGuava.java b/test/transform/resource/before/BuilderSingularRedirectToGuava.java new file mode 100644 index 00000000..2dfa7684 --- /dev/null +++ b/test/transform/resource/before/BuilderSingularRedirectToGuava.java @@ -0,0 +1,13 @@ +//CONF: lombok.singular.useGuava = true +import java.util.Set; +import java.util.NavigableMap; +import java.util.Collection; + +import lombok.Singular; + +@lombok.Builder +class BuilderSingularRedirectToGuava { + @Singular private Set<String> dangerMice; + @Singular private NavigableMap<Integer, Number> things; + @Singular private Collection<Class<?>> doohickeys; +} diff --git a/test/transform/resource/before/BuilderSingularSets.java b/test/transform/resource/before/BuilderSingularSets.java new file mode 100644 index 00000000..68c4510a --- /dev/null +++ b/test/transform/resource/before/BuilderSingularSets.java @@ -0,0 +1,12 @@ +import java.util.Set; +import java.util.SortedSet; + +import lombok.Singular; + +@lombok.Builder +class BuilderSingularSets<T> { + @Singular private Set<T> dangerMice; + @Singular private SortedSet<? extends Number> octopodes; + @SuppressWarnings("all") @Singular("rawSet") private Set rawSet; + @Singular("stringSet") private Set<String> stringSet; +} diff --git a/test/transform/resource/before/BuilderWithExistingBuilderClass.java b/test/transform/resource/before/BuilderWithExistingBuilderClass.java index 262e3b85..c8e0a24b 100644 --- a/test/transform/resource/before/BuilderWithExistingBuilderClass.java +++ b/test/transform/resource/before/BuilderWithExistingBuilderClass.java @@ -1,4 +1,4 @@ -import lombok.experimental.Builder; +import lombok.Builder; class BuilderWithExistingBuilderClass<T, K extends Number> { @Builder diff --git a/test/transform/resource/before/EncodingUsAscii.java b/test/transform/resource/before/EncodingUsAscii.java new file mode 100644 index 00000000..dbcd150b --- /dev/null +++ b/test/transform/resource/before/EncodingUsAscii.java @@ -0,0 +1,5 @@ +//ENCODING: US-ASCII +@lombok.ToString +class EncodingUsAscii { + String foo\u0e51\u0e51 = "\u000e \10 "; +}
\ No newline at end of file diff --git a/test/transform/resource/before/EncodingUtf8.java b/test/transform/resource/before/EncodingUtf8.java new file mode 100644 index 00000000..75b0ee00 --- /dev/null +++ b/test/transform/resource/before/EncodingUtf8.java @@ -0,0 +1,5 @@ +//ENCODING: UTF-8 +@lombok.ToString +class EncodingUtf8 { + String foo\u0e51๑ = "\u000e \10 "; +}
\ No newline at end of file diff --git a/test/transform/resource/messages-delombok/BuilderSingularNoAutosingularize.java.messages b/test/transform/resource/messages-delombok/BuilderSingularNoAutosingularize.java.messages new file mode 100644 index 00000000..8719789b --- /dev/null +++ b/test/transform/resource/messages-delombok/BuilderSingularNoAutosingularize.java.messages @@ -0,0 +1,2 @@ +8 The singular must be specified explicitly (e.g. @Singular("task")) because auto singularization is disabled. +10 The singular must be specified explicitly (e.g. @Singular("task")) because auto singularization is disabled. diff --git a/test/transform/resource/messages-ecj/BuilderComplex.java.messages b/test/transform/resource/messages-ecj/BuilderComplex.java.messages new file mode 100644 index 00000000..4856a80d --- /dev/null +++ b/test/transform/resource/messages-ecj/BuilderComplex.java.messages @@ -0,0 +1 @@ +2 The type Builder is deprecated diff --git a/test/transform/resource/messages-ecj/BuilderSingularNoAutosingularize.java.messages b/test/transform/resource/messages-ecj/BuilderSingularNoAutosingularize.java.messages new file mode 100644 index 00000000..8719789b --- /dev/null +++ b/test/transform/resource/messages-ecj/BuilderSingularNoAutosingularize.java.messages @@ -0,0 +1,2 @@ +8 The singular must be specified explicitly (e.g. @Singular("task")) because auto singularization is disabled. +10 The singular must be specified explicitly (e.g. @Singular("task")) because auto singularization is disabled. diff --git a/usage_examples/BuilderExample_post.jpage b/usage_examples/BuilderExample_post.jpage new file mode 100644 index 00000000..863ab19b --- /dev/null +++ b/usage_examples/BuilderExample_post.jpage @@ -0,0 +1,66 @@ +import java.util.Set; + +public class BuilderExample { + private String name; + private int age; + private Set<String> occupations; + + BuilderExample(String name, int age, Set<String> occupations) { + this.name = name; + this.age = age; + this.occupations = occupations; + } + + public static BuilderExampleBuilder builder() { + return new BuilderExampleBuilder(); + } + + public static class BuilderExampleBuilder { + private String name; + private int age; + private java.util.ArrayList<String> occupations; + + BuilderExampleBuilder() { + } + + public BuilderExampleBuilder name(String name) { + this.name = name; + return this; + } + + public BuilderExampleBuilder age(int age) { + this.age = age; + return this; + } + + public BuilderExampleBuilder occupation(String occupation) { + if (this.occupations == null) { + this.occupations = new java.util.ArrayList<String>(); + } + + this.occupations.add(occupation); + return this; + } + + public BuilderExampleBuilder occupations(Collection<? extends String> occupations) { + if (this.occupations == null) { + this.occupations = new java.util.ArrayList<String>(); + } + + this.occupations.addAll(occupations); + return this; + } + + public BuilderExample build() { + // complicated switch statement to produce a compact properly sized immutable set omitted. + // go to http://projectlombok.org/features/Singular-snippet.html to see it. + Set<String> occupations = ...; + return new BuilderExample(name, age, occupations); + } + + @java.lang.Override + public String toString() { + return "BuilderExample.BuilderExampleBuilder(name = " + this.name + ", age = " + this.age + ", occupations = " + this.occupations + ")"; + } + } +}
\ No newline at end of file diff --git a/usage_examples/BuilderExample_pre.jpage b/usage_examples/BuilderExample_pre.jpage new file mode 100644 index 00000000..1557fff4 --- /dev/null +++ b/usage_examples/BuilderExample_pre.jpage @@ -0,0 +1,10 @@ +import lombok.Builder; +import lombok.Singular; +import java.util.Set; + +@Builder +public class BuilderExample { + private String name; + private int age; + @Singular private Set<String> occupations; +} diff --git a/usage_examples/Singular-snippetExample_post.jpage b/usage_examples/Singular-snippetExample_post.jpage new file mode 100644 index 00000000..4e2b0460 --- /dev/null +++ b/usage_examples/Singular-snippetExample_post.jpage @@ -0,0 +1,160 @@ +import java.util.Collection; +import java.util.Set; +import java.util.SortedMap; +import com.google.common.collect.ImmutableList; + +public class SingularExample<T extends Number> { + private Set<String> occupations; + private ImmutableList<String> axes; + private SortedMap<Integer, T> elves; + private Collection<?> minutiae; + + SingularExample(Set<String> occupations, ImmutableList<String> axes, SortedMap<Integer, T> elves, Collection<?> minutiae) { + this.occupations = occupations; + this.axes = axes; + this.elves = elves; + this.minutiae = minutiae; + } + + public static class SingularExampleBuilder<T extends Number> { + private java.util.ArrayList<String> occupations; + private com.google.common.collect.ImmutableList.Builder<String> axes; + private java.util.ArrayList<Integer> elves$key; + private java.util.ArrayList<T> elves$value; + private java.util.ArrayList<java.lang.Object> minutiae; + + SingularExampleBuilder() { + } + + public SingularExampleBuilder<T> occupation(String occupation) { + if (this.occupations == null) { + this.occupations = new java.util.ArrayList<String>(); + } + + this.occupations.add(occupation); + return this; + } + + @java.lang.SuppressWarnings("all") + public SingularExampleBuilder<T> occupations(java.util.Collection<? extends String> occupations) { + if (this.occupations == null) { + this.occupations = new java.util.ArrayList<String>(); + } + + this.occupations.addAll(occupations); + return this; + } + + public SingularExampleBuilder<T> axis(String axis) { + if (this.axes == null) { + this.axes = com.google.common.collect.ImmutableList.builder(); + } + + this.axes.add(axis); + return this; + } + + public SingularExampleBuilder<T> axes(java.lang.Iterable<? extends String> axes) { + if (this.axes == null) { + this.axes = com.google.common.collect.ImmutableList.builder(); + } + + this.axes.addAll(axes); + return this; + } + + public SingularExampleBuilder<T> elf(Integer elfKey, T elfValue) { + if (this.elves$key == null) { + this.elves$key = new java.util.ArrayList<Integer>(); + this.elves$value = new java.util.ArrayList<T>(); + } + + this.elves$key.add(elfKey); + this.elves$value.add(elfValue); + return this; + } + + public SingularExampleBuilder<T> elves(java.util.Map<? extends Integer, ? extends T> elves) { + if (this.elves$key == null) { + this.elves$key = new java.util.ArrayList<Integer>(); + this.elves$value = new java.util.ArrayList<T>(); + } + + for (java.util.Map.Entry<? extends Integer, ? extends T> $lombokEntry : elves.entrySet()) { + this.elves$key.add($lombokEntry.getKey()); + this.elves$value.add($lombokEntry.getValue()); + } + return this; + } + + public SingularExampleBuilder<T> minutia(java.lang.Object minutia) { + if (this.minutiae == null) { + this.minutiae = new java.util.ArrayList<java.lang.Object>(); + } + + this.minutiae.add(minutia); + return this; + } + + public SingularExampleBuilder<T> minutiae(java.util.Collection<?> minutiae) { + if (this.minutiae == null) { + this.minutiae = new java.util.ArrayList<java.lang.Object>(); + } + + this.minutiae.addAll(minutiae); + return this; + } + + public SingularExample<T> build() { + java.util.Set<String> occupations; + switch (this.occupations == null ? 0 : this.occupations.size()) { + case 0: + occupations = java.util.Collections.emptySet(); + break; + + case 1: + occupations = java.util.Collections.singleton(this.occupations.get(0)); + break; + + default: + occupations = new java.util.LinkedHashSet<String>(this.occupations.size() < 1073741824 ? 1 + this.occupations.size() + (this.occupations.size() - 3) / 3 : java.lang.Integer.MAX_VALUE); + occupations.addAll(this.occupations); + occupations = java.util.Collections.unmodifiableSet(occupations); + + } + + com.google.common.collect.ImmutableList<String> axes = this.axes == null ? com.google.common.collect.ImmutableList.<String>of() : this.axes.build(); + + java.util.SortedMap<Integer, T> elves = new java.util.TreeMap<Integer, T>(); + if (this.elves$key != null) for (int $i = 0; $i < (this.elves$key == null ? 0 : this.elves$key.size()); $i++) elves.put(this.elves$key.get($i), this.elves$value.get($i)); + elves = java.util.Collections.unmodifiableSortedMap(elves); + + java.util.Collection<java.lang.Object> minutiae; + switch (this.minutiae == null ? 0 : this.minutiae.size()) { + case 0: + minutiae = java.util.Collections.emptyList(); + break; + + case 1: + minutiae = java.util.Collections.singletonList(this.minutiae.get(0)); + break; + + default: + minutiae = java.util.Collections.unmodifiableList(new java.util.ArrayList<java.lang.Object>(this.minutiae)); + + } + + return new SingularExample<T>(occupations, axes, elves, minutiae); + } + + @java.lang.Override + public java.lang.String toString() { + return "SingularExample.SingularExampleBuilder(occupations=" + this.occupations + ", axes=" + this.axes + ", elves$key=" + this.elves$key + ", elves$value=" + this.elves$value + ", minutiae=" + this.minutiae + ")"; + } + } + + @java.lang.SuppressWarnings("all") + public static <T extends Number> SingularExampleBuilder<T> builder() { + return new SingularExampleBuilder<T>(); + } +} diff --git a/usage_examples/Singular-snippetExample_pre.jpage b/usage_examples/Singular-snippetExample_pre.jpage new file mode 100644 index 00000000..65f6bbc8 --- /dev/null +++ b/usage_examples/Singular-snippetExample_pre.jpage @@ -0,0 +1,14 @@ +import lombok.Builder; +import lombok.Singular; +import java.util.Collection; +import java.util.Set; +import java.util.SortedMap; +import com.google.common.collect.ImmutableList; + +@Builder +public class SingularExample<T extends Number> { + private @Singular Set<String> occupations; + private @Singular("axis") ImmutableList<String> axes; + private @Singular SortedMap<Integer, T> elves; + private @Singular Collection<?> minutiae; +} diff --git a/usage_examples/experimental/BuilderExample_post.jpage b/usage_examples/experimental/BuilderExample_post.jpage deleted file mode 100644 index 624b236b..00000000 --- a/usage_examples/experimental/BuilderExample_post.jpage +++ /dev/null @@ -1,40 +0,0 @@ -public class BuilderExample { - private String name; - private int age; - - BuilderExample(String name, int age) { - this.name = name; - this.age = age; - } - - public static BuilderExampleBuilder builder() { - return new BuilderExampleBuilder(); - } - - public static class BuilderExampleBuilder { - private String name; - private int age; - - BuilderExampleBuilder() { - } - - public BuilderExampleBuilder name(String name) { - this.name = name; - return this; - } - - public BuilderExampleBuilder age(int age) { - this.age = age; - return this; - } - - public BuilderExample build() { - return new BuilderExample(name, age); - } - - @java.lang.Override - public String toString() { - return "BuilderExample.BuilderExampleBuilder(name = " + this.name + ", age = " + this.age + ")"; - } - } -}
\ No newline at end of file diff --git a/usage_examples/experimental/BuilderExample_pre.jpage b/usage_examples/experimental/BuilderExample_pre.jpage deleted file mode 100644 index 9c754352..00000000 --- a/usage_examples/experimental/BuilderExample_pre.jpage +++ /dev/null @@ -1,7 +0,0 @@ -import lombok.experimental.Builder; - -@Builder -public class BuilderExample { - private String name; - private int age; -} diff --git a/website/download.html b/website/download.html index afacde2b..ff96db46 100644 --- a/website/download.html +++ b/website/download.html @@ -81,7 +81,7 @@ <div class="endBar"> </div> <div class="footer"> - <a href="credits.html" class="creditsLink">credits</a> | Copyright © 2009-2014 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>. + <a href="credits.html" class="creditsLink">credits</a> | Copyright © 2009-2015 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>. </div> </div> <script type="text/javascript"> diff --git a/website/features/Builder.html b/website/features/Builder.html new file mode 100644 index 00000000..b4731b07 --- /dev/null +++ b/website/features/Builder.html @@ -0,0 +1,170 @@ +<!DOCTYPE html> +<html><head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <link rel="stylesheet" type="text/css" href="../logi/reset.css" /> + <link rel="stylesheet" type="text/css" href="features.css" /> + <link rel="shortcut icon" href="../favicon.ico" type="image/x-icon" /> + <meta name="description" content="Spice up your java" /> + <title>@Builder</title> +</head><body><div id="pepper"> + <div class="minimumHeight"></div> + <div class="meat"> + <div class="header"><a href="../index.html">Project Lombok</a></div> + <h1>@Builder</h1> + <div class="byline">... and Bob's your uncle: No-hassle fancy-pants APIs for object creation!</div> + <div class="since"> + <h3>Since</h3> + <p> + <code>@Builder</code> was introduced as experimental feature in lombok v0.12.0. + </p><p> + <code>@Builder</code> gained <code>@Singular</code> support and was promoted to the main <code>lombok</code> package since lombok v1.16.0. + </p> + </div> + <div class="overview"> + <h3>Overview</h3> + <p> + The <code>@Builder</code> annotation produces complex builder APIs for your classes. + </p><p> + <code>@Builder</code> lets you automatically produce the code required to have your class be instantiable with code such as:<br /> + <code>Person.builder().name("Adam Savage").city("San Francisco").job("Mythbusters").job("Unchained Reaction").build();</code> + </p><p> + <code>@Builder</code> can be placed on a class, or on a constructor, or on a static method. While the "on a class" and "on a constructor" + mode are the most common use-case, <code>@Builder</code> is most easily explained with the "static method" use-case. + </p><p> + A static method annotated with <code>@Builder</code> (from now on called the <em>target</em>) causes the following 7 things to be generated:<ul> + <li>An inner static class named <code><em>Foo</em>Builder</code>, with the same type arguments as the static method (called the <em>builder</em>).</li> + <li>In the <em>builder</em>: One private non-static non-final field for each parameter of the <em>target</em>.</li> + <li>In the <em>builder</em>: A package private no-args empty constructor.</li> + <li>In the <em>builder</em>: A 'setter'-like method for each parameter of the <em>target</em>: It has the same type as that parameter and the same name. + It returns the builder itself, so that the setter calls can be chained, as in the above example.</li> + <li>In the <em>builder</em>: A <code>build()</code> method which calls the static method, passing in each field. It returns the same type that the + <em>target</em> returns.</li> + <li>In the <em>builder</em>: A sensible <code>toString()</code> implementation.</li> + <li>In the class containing the <em>target</em>: A <code>builder()</code> method, which creates a new instance of the <em>builder</em>.</li> + </ul> + Each listed generated element will be silently skipped if that element already exists (disregarding parameter counts and looking only at names). This + includes the <em>builder</em> itself: If that class already exists, lombok will simply start injecting fields and methods inside this already existing + class, unless of course the fields / methods to be injected already exist. You may not put any other method (or constructor) generating lombok annotation + on a builder class though; for example, you can not put <code>@EqualsAndHashCode</code> on the builder class. + </p><p> + <code>@Builder</code> can generate so-called 'singular' methods for collection parameters/fields. These take 1 element instead of an entire list, and add the + element to the list. For example: <code>Person.builder().job("Mythbusters").job("Unchained Reaction").build();</code> would result in the <code>List<String> jobs</code> + field to have 2 strings in it. To get this behaviour, the field/parameter needs to be annotated with <code>@Singular</code>. The feature has <a href="#singular">its own documentation</a>. + </p><p> + Now that the "static method" mode is clear, putting a <code>@Builder</code> annotation on a constructor functions similarly; effectively, + constructors are just static methods that have a special syntax to invoke them: Their 'return type' is the class they construct, and their + type parameters are the same as the type parameters of the class itself. + </p><p> + Finally, applying <code>@Builder</code> to a class is as if you added <code>@AllArgsConstructor(access = AccessLevel.PACKAGE)</code> to the class and applied the + <code>@Builder</code> annotation to this all-args-constructor. This only works if you haven't written any explicit constructors yourself. If you do have an + explicit constructor, put the <code>@Builder</code> annotation on the constructor instead of on the class. + </p><p> + The name of the builder class is <code><em>Foobar</em>Builder</code>, where <em>Foobar</em> is the simplified, title-cased form of the return type of the + <em>target</em> - that is, the name of your type for <code>@Builder</code> on constructors and types, and the name of the return type for <code>@Builder</code> + on static methods. For example, if <code>@Builder</code> is applied to a class named <code>com.yoyodyne.FancyList<T></code>, then the builder name will be + <code>FancyListBuilder<T></code>. If <code>@Builder</code> is applied to a static method that returns <code>void</code>, the builder will be named + <code>VoidBuilder</code>. + </p><p> + The configurable aspects of builder are:<ul> + <li>The <em>builder's class name</em> (default: return type + 'Builder')</li> + <li>The <em>build()</em> method's name (default: <code>"build"</code>)</li> + <li>The <em>builder()</em> method's name (default: <code>"builder"</code>)</li> + </ul> + Example usage where all options are changed from their defaults:<br /> + <code>@Builder(builderClassName = "HelloWorldBuilder", buildMethodName = "execute", builderMethodName = "helloWorld")</code><br /> + </p> + </div> + <div class="overview"> + <h3><a name="singular">@Singular</a></h3> + <p> + By annotating one of the parameters (if annotating a static method or constructor with <code>@Builder</code>) or fields (if annotating a class with <code>@Builder</code>) with the + <code>@Singular</code> annotation, lombok will treat that builder node as a collection, and it generates 2 'adder' methods instead of a 'setter' method. One which adds a single element to the collection, and one + which adds all elements of another collection to the collection. No setter to just set the collection (replacing whatever was already added) will be generated. These 'singular' builders + are very complicated in order to guarantee the following properties: + <ul> + <li>When invoking <code>build()</code>, the produced collection will be immutable.</li> + <li>Calling one of the 'adder' methods after invoking <code>build()</code> does not modify any already generated objects, and, if <code>build()</code> is later called again, another collection with all the elements added since the creation of the builder is generated.</li> + <li>The produced collection will be compacted to the smallest feasible format while remaining efficient.</li> + </ul> + </p><p> + <code>@Singular</code> can only be applied to collection types known to lombok. Currently, the supported types are: + <ul> + <li><a href="http://docs.oracle.com/javase/8/docs/api/java/util/package-summary.html"><code>java.util</code></a>:<ul> + <li><code>Iterable</code>, <code>Collection</code>, and <code>List</code> (backed by a compacted unmodifiable <code>ArrayList</code> in the general case).</li> + <li><code>Set</code>, <code>SortedSet</code>, and <code>NavigableSet</code> (backed by a smartly sized unmodifiable <code>HashSet</code> or <code>TreeSet</code> in the general case).</li> + <li><code>Map</code>, <code>SortedMap</code>, and <code>NavigableMap</code> (backed by a smartly sized unmodifiable <code>HashMap</code> or <code>TreeMap</code> in the general case).</li> + </ul></li> + <li><a href="https://github.com/google/guava">Guava</a>'s <code>com.google.common.collect</code>:<ul> + <li><code>ImmutableCollection</code> and <code>ImmutableList</code> (backed by the builder feature of <code>ImmutableList</code>).</li> + <li><code>ImmutableSet</code> and <code>ImmutableSortedSet</code> (backed by the builder feature of those types).</li> + <li><code>ImmutableMap</code>, <code>ImmutableBiMap</code>, and <code>ImmutableSortedMap</code> (backed by the builder feature of those types).</li> + </ul></li> + </ul> + </p><p> + If your identifiers are written in common english, lombok assumes that the name of any collection with <code>@Singular</code> on it is an english plural and will attempt to automatically + singularize that name. If this is possible, the add-one method will use this name. For example, if your collection is called <code>statuses</code>, then the add-one method will automatically + be called <code>status</code>. You can also specify the singular form of your identifier explictly by passing the singular form as argument to the annotation like so: <code>@Singular("axis") List<Line> axes;</code>.<br /> + If lombok cannot singularize your identifier, or it is ambiguous, lombok will generate an error and force you to explicitly specify the singular name. + </p><p> + The snippet below does not show what lombok generates for a <code>@Singular</code> field/parameter because it is rather complicated. + You can view a snippet <a href="Singular-snippet.html">here</a>. + </p> + </div> + <div class="snippets"> + <div class="pre"> + <h3>With Lombok</h3> + <div class="snippet">@HTML_PRE@</div> + </div> + <div class="sep"></div> + <div class="post"> + <h3>Vanilla Java</h3> + <div class="snippet">@HTML_POST@</div> + </div> + </div> + <div style="clear: left;"></div> + <div class="overview confKeys"> + <h3>Supported configuration keys:</h3> + <dl> + <dt><code>lombok.builder.flagUsage</code> = [<code>warning</code> | <code>error</code>] (default: not set)</dt> + <dd>Lombok will flag any usage of <code>@Builder</code> as a warning or error if configured.</dd> + <dt><code>lombok.singular.useGuava</code> = [<code>true</code> | <code>false</code>] (default: false)</dt> + <dd>If <code>true</code>, lombok will use guava's <code>ImmutableXxx</code> builders and types to implement <code>java.util</code> collection interfaces, instead of creating + implementations based on <code>Collections.unmodifiableXxx</code>. You must ensure that guava is actually available on the classpath and buildpath if you use this setting. + Guava is used automatically if your field/parameter has one of the guava <code>ImmutableXxx</code> types. + <dt><code>lombok.singular.auto</code> = [<code>true</code> | <code>false</code>] (default: true)</dt> + <dd>If <code>true</code> (which is the default), lombok automatically tries to singularize your identifier name by assuming that it is a common english plural. + If <code>false</code>, you must always explicitly specify the singular name, and lombok will generate an error if you don't (useful if you write your code in a language other than english). + </dl> + </div> + <div class="overview"> + <h3>Small print</h3><div class="smallprint"> + <p> + @Singular support for <code>java.util.NavigableMap/Set</code> only works if you are compiling with JDK1.8 or higher. + </p><p> + You cannot manually provide some or all parts of a <code>@Singular</code> node; the code lombok generates is too complex for this. If you want to + manually control (part of) the builder code associated with some field or parameter, don't use <code>@Singular</code> and add everything you need manually. + </p><p> + The sorted collections (java.util: <code>SortedSet</code>, <code>NavigableSet</code>, <code>SortedMap</code>, <code>NavigableMap</code> and guava: <code>ImmutableSortedSet</code>, <code>ImmutableSortedMap</code>) require that the type argument of the collection has natural order (implements <code>java.util.Comparable</code>). There is no way to pass an explicit <code>Comparator</code> to use in the builder. + </p><p> + An <code>ArrayList</code> is used to store added elements as call methods of a <code>@Singular</code> marked field, if the target collection is from the <code>java.util</code> package, <em>even if the collection is a set or map</em>. Because lombok ensures that generated collections are compacted, a new backing instance of a set or map must be constructed anyway, and storing the data as an <code>ArrayList</code> during the build process is more efficient that storing it as a map or set. This behaviour is not externally visible, an an implementation detail of the current implementation of the <code>java.util</code> recipes for <code>@Singular @Builder</code>. + </p> + </div> + </div> + <div class="footer"> + <a href="index.html">Back to features</a> | <a href="Value.html">Previous feature (@Value)</a> | <a href="SneakyThrows.html">Next feature (@SneakyThrows)</a><br /> + <a href="../credits.html" class="creditsLink">credits</a> | <span class="copyright">Copyright © 2009-2015 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>.</span> + </div> + <div style="clear: both;"></div> + </div> +</div> +<script type="text/javascript"> + var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www."); + document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E")); +</script> +<script type="text/javascript"> + try { + var pageTracker = _gat._getTracker("UA-9884254-1"); + pageTracker._trackPageview(); + } catch(err) {} +</script> +</body></html> diff --git a/website/features/Cleanup.html b/website/features/Cleanup.html index f4b16946..a6f25935 100644 --- a/website/features/Cleanup.html +++ b/website/features/Cleanup.html @@ -68,7 +68,7 @@ </div> <div class="footer"> <a href="index.html">Back to features</a> | <a href="NonNull.html">Previous feature (@NonNull)</a> | <a href="GetterSetter.html">Next feature (@Getter / @Setter)</a><br /> - <a href="../credits.html" class="creditsLink">credits</a> | <span class="copyright">Copyright © 2009-2014 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>.</span> + <a href="../credits.html" class="creditsLink">credits</a> | <span class="copyright">Copyright © 2009-2015 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>.</span> </div> <div style="clear: both;"></div> </div> diff --git a/website/features/Data.html b/website/features/Data.html index c3d0a644..ed06f299 100644 --- a/website/features/Data.html +++ b/website/features/Data.html @@ -66,7 +66,7 @@ </div> <div class="footer"> <a href="index.html">Back to features</a> | <a href="Constructor.html">Previous feature (@<em>X</em>Constructor)</a> | <a href="Value.html">Next feature (@Value)</a><br /> - <a href="../credits.html" class="creditsLink">credits</a> | <span class="copyright">Copyright © 2009-2014 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>.</span> + <a href="../credits.html" class="creditsLink">credits</a> | <span class="copyright">Copyright © 2009-2015 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>.</span> </div> <div style="clear: both;"></div> </div> diff --git a/website/features/EqualsAndHashCode.html b/website/features/EqualsAndHashCode.html index 32dcb6c3..0cad6b1b 100644 --- a/website/features/EqualsAndHashCode.html +++ b/website/features/EqualsAndHashCode.html @@ -76,7 +76,7 @@ </div> <div class="footer"> <a href="index.html">Back to features</a> | <a href="ToString.html">Previous feature (@ToString)</a> | <a href="Constructor.html">Next feature (@<em>X</em>Constructor)</a><br /> - <a href="../credits.html" class="creditsLink">credits</a> | <span class="copyright">Copyright © 2009-2014 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>.</span> + <a href="../credits.html" class="creditsLink">credits</a> | <span class="copyright">Copyright © 2009-2015 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>.</span> </div> <div style="clear: both;"></div> </div> diff --git a/website/features/GetterLazy.html b/website/features/GetterLazy.html index a438f08a..ad08b480 100644 --- a/website/features/GetterLazy.html +++ b/website/features/GetterLazy.html @@ -48,7 +48,7 @@ </div> <div class="footer"> <a href="index.html">Back to features</a> | <a href="Synchronized.html">Previous feature (@Synchronized)</a> | <a href="Log.html">Next feature (@Log)</a><br /> - <a href="../credits.html" class="creditsLink">credits</a> | <span class="copyright">Copyright © 2009-2014 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>.</span> + <a href="../credits.html" class="creditsLink">credits</a> | <span class="copyright">Copyright © 2009-2015 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>.</span> </div> <div style="clear: both;"></div> </div> diff --git a/website/features/GetterSetter.html b/website/features/GetterSetter.html index ad83f86a..760e1876 100644 --- a/website/features/GetterSetter.html +++ b/website/features/GetterSetter.html @@ -87,7 +87,7 @@ </div> <div class="footer"> <a href="index.html">Back to features</a> | <a href="Cleanup.html">Previous feature (@Cleanup)</a> | <a href="ToString.html">Next feature (@ToString)</a><br /> - <a href="../credits.html" class="creditsLink">credits</a> | <span class="copyright">Copyright © 2009-2014 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>.</span> + <a href="../credits.html" class="creditsLink">credits</a> | <span class="copyright">Copyright © 2009-2015 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>.</span> </div> <div style="clear: both;"></div> </div> diff --git a/website/features/Log.html b/website/features/Log.html index 8b635280..22e5d293 100644 --- a/website/features/Log.html +++ b/website/features/Log.html @@ -83,7 +83,7 @@ </div> <div class="footer"> <a href="index.html">Back to features</a> | <a href="GetterLazy.html">Previous feature (@Getter(lazy=true))</a> | <a href="configuration.html">Next feature (configuration)</a><br /> - <a href="../credits.html" class="creditsLink">credits</a> | <span class="copyright">Copyright © 2009-2014 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>.</span> + <a href="../credits.html" class="creditsLink">credits</a> | <span class="copyright">Copyright © 2009-2015 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>.</span> </div> <div style="clear: both;"></div> </div> diff --git a/website/features/Singular-snippet.html b/website/features/Singular-snippet.html new file mode 100644 index 00000000..7afbebf8 --- /dev/null +++ b/website/features/Singular-snippet.html @@ -0,0 +1,43 @@ +<!DOCTYPE html> +<html><head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <link rel="stylesheet" type="text/css" href="../logi/reset.css" /> + <link rel="stylesheet" type="text/css" href="features.css" /> + <link rel="shortcut icon" href="../favicon.ico" type="image/x-icon" /> + <meta name="description" content="Spice up your java" /> + <title>@Builder's @Singular (snippet)</title> +</head><body><div id="pepper"> + <div class="minimumHeight"></div> + <div class="meat"> + <div class="header"><a href="../index.html">Project Lombok</a></div> + <h1>@Singular snippet</h1> + <div class="singleColumnSnippets"> + <div class="pre"> + <h3>With Lombok</h3> + <div class="snippet">@HTML_PRE@</div> + </div> + <div class="sep"></div> + <div class="post"> + <h3>Vanilla Java</h3> + <div class="snippet">@HTML_POST@</div> + </div> + </div> + <div style="clear: left;"></div> + <div class="footer"> + <a href="Builder.html">Back to @Builder</a><br /> + <a href="../credits.html" class="creditsLink">credits</a> | <span class="copyright">Copyright © 2009-2015 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>.</span> + </div> + <div style="clear: both;"></div> + </div> +</div> +<script type="text/javascript"> + var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www."); + document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E")); +</script> +<script type="text/javascript"> + try { + var pageTracker = _gat._getTracker("UA-9884254-1"); + pageTracker._trackPageview(); + } catch(err) {} +</script> +</body></html> diff --git a/website/features/SneakyThrows.html b/website/features/SneakyThrows.html index e1cd6685..55f4a0b8 100644 --- a/website/features/SneakyThrows.html +++ b/website/features/SneakyThrows.html @@ -77,8 +77,8 @@ </div> </div> <div class="footer"> - <a href="index.html">Back to features</a> | <a href="Value.html">Previous feature (@Value)</a> | <a href="Synchronized.html">Next feature (@Synchronized)</a><br /> - <a href="../credits.html" class="creditsLink">credits</a> | <span class="copyright">Copyright © 2009-2014 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>.</span> + <a href="index.html">Back to features</a> | <a href="Builder.html">Previous feature (@Builder)</a> | <a href="Synchronized.html">Next feature (@Synchronized)</a><br /> + <a href="../credits.html" class="creditsLink">credits</a> | <span class="copyright">Copyright © 2009-2015 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>.</span> </div> <div style="clear: both;"></div> </div> diff --git a/website/features/Synchronized.html b/website/features/Synchronized.html index 85c161c3..df9db6b0 100644 --- a/website/features/Synchronized.html +++ b/website/features/Synchronized.html @@ -67,7 +67,7 @@ </div> <div class="footer"> <a href="index.html">Back to features</a> | <a href="SneakyThrows.html">Previous feature (@SneakyThrows)</a> | <a href="GetterLazy.html">Next feature (@Getter(lazy=true))</a><br /> - <a href="../credits.html" class="creditsLink">credits</a> | <span class="copyright">Copyright © 2009-2014 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>.</span> + <a href="../credits.html" class="creditsLink">credits</a> | <span class="copyright">Copyright © 2009-2015 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>.</span> </div> <div style="clear: both;"></div> </div> diff --git a/website/features/ToString.html b/website/features/ToString.html index 57bbd0f0..5071048c 100644 --- a/website/features/ToString.html +++ b/website/features/ToString.html @@ -74,7 +74,7 @@ </div> <div class="footer"> <a href="index.html">Back to features</a> | <a href="GetterSetter.html">Previous feature (@Getter / @Setter)</a> | <a href="EqualsAndHashCode.html">Next feature (@EqualsAndHashCode)</a><br /> - <a href="../credits.html" class="creditsLink">credits</a> | <span class="copyright">Copyright © 2009-2014 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>.</span> + <a href="../credits.html" class="creditsLink">credits</a> | <span class="copyright">Copyright © 2009-2015 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>.</span> </div> <div style="clear: both;"></div> </div> diff --git a/website/features/Value.html b/website/features/Value.html index 0e221b63..25bba650 100644 --- a/website/features/Value.html +++ b/website/features/Value.html @@ -20,6 +20,7 @@ <code>@Value</code> no longer implies <code>@Wither</code> since lombok v0.11.8. </p><p> <code>@Value</code> promoted to the main <code>lombok</code> package since lombok v0.12.0. + </p> </div> <div class="overview"> <h3>Overview</h3> @@ -67,8 +68,8 @@ </div> </div> <div class="footer"> - <a href="index.html">Back to features</a> | <a href="Data.html">Previous feature (@Data)</a> | <a href="SneakyThrows.html">Next feature (@SneakyThrows)</a><br /> - <a href="../credits.html" class="creditsLink">credits</a> | <span class="copyright">Copyright © 2009-2014 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>.</span> + <a href="index.html">Back to features</a> | <a href="Data.html">Previous feature (@Data)</a> | <a href="Builder.html">Next feature (@Builder)</a><br /> + <a href="../credits.html" class="creditsLink">credits</a> | <span class="copyright">Copyright © 2009-2015 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>.</span> </div> <div style="clear: both;"></div> </div> diff --git a/website/features/configuration.html b/website/features/configuration.html index acf874e7..1f31fe01 100644 --- a/website/features/configuration.html +++ b/website/features/configuration.html @@ -73,7 +73,7 @@ <div style="clear: left;"></div> <div class="footer"> <a href="index.html">Back to features</a> | <a href="Log.html">Previous feature (@Log)</a> | <span class="disabled">Next feature</span><br /> - <a href="../credits.html" class="creditsLink">credits</a> | <span class="copyright">Copyright © 2009-2014 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>.</span> + <a href="../credits.html" class="creditsLink">credits</a> | <span class="copyright">Copyright © 2009-2015 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>.</span> </div> <div style="clear: both;"></div> </div> diff --git a/website/features/experimental/Accessors.html b/website/features/experimental/Accessors.html index 20590a07..3934c339 100644 --- a/website/features/experimental/Accessors.html +++ b/website/features/experimental/Accessors.html @@ -97,8 +97,8 @@ </div> </div> <div class="footer"> - <a href="index.html">Back to experimental features</a> | <a href="Builder.html">Previous feature (@Builder)</a> | <a href="ExtensionMethod.html">Next feature (@ExtensionMethod)</a><br /> - <a href="../../credits.html" class="creditsLink">credits</a> | <span class="copyright">Copyright © 2009-2014 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>.</span> + <a href="index.html">Back to experimental features</a> | <span class="disabled">Previous feature</span> | <a href="ExtensionMethod.html">Next feature (@ExtensionMethod)</a><br /> + <a href="../../credits.html" class="creditsLink">credits</a> | <span class="copyright">Copyright © 2009-2015 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>.</span> </div> <div style="clear: both;"></div> </div> diff --git a/website/features/experimental/Builder.html b/website/features/experimental/Builder.html index a8e21b9a..9f1266dc 100644 --- a/website/features/experimental/Builder.html +++ b/website/features/experimental/Builder.html @@ -12,118 +12,13 @@ <div class="header"><a href="../../index.html">Project Lombok</a></div> <h1>@Builder</h1> <div class="byline">... and Bob's your uncle: No-hassle fancy-pants APIs for object creation!</div> - <div class="since"> - <h3>Since</h3> - <p> - @Builder was introduced as experimental feature in lombok v0.12.0. - </p> - </div> - <div class="experimental"> - <h3>Experimental</h3> - <p> - Experimental because: - <ul> - <li>New feature - community feedback requested.</li> - </ul> - Current status: <em>sure thing</em> - This feature will move to the core package soon. - </div> - <div class="overview"> - <h3>Overview</h3> - <p> - The <code>@Builder</code> annotation produces complex builder APIs for your classes. - </p><p> - <code>@Builder</code> lets you automatically produce the code required to have your class be instantiable with code such as:<br /> - <code>Person.builder().name("Adam Savage").city("San Francisco").worksAt("Mythbusters").build();</code> - </p><p> - <code>@Builder</code> can be placed on a class, or on a constructor, or on a static method. While the "on a class" and "on a constructor" - mode are the most common use-case, <code>@Builder</code> is most easily explained with the "static method" use-case. - </p><p> - A static method annotated with <code>@Builder</code> (from now on called the <em>target</em>) causes the following 7 things to be generated:<ul> - <li>An inner static class named <code><em>Foo</em>Builder</code>, with the same type arguments as the static method (called the <em>builder</em>).</li> - <li>In the <em>builder</em>: One private non-static non-final field for each parameter of the <em>target</em>.</li> - <li>In the <em>builder</em>: A package private no-args empty constructor.</li> - <li>In the <em>builder</em>: A 'setter'-like method for each parameter of the <em>target</em>: It has the same type as that parameter and the same name. - It returns the builder itself, so that the setter calls can be chained, as in the above example.</li> - <li>In the <em>builder</em>: A <code>build()</code> method which calls the static method, passing in each field. It returns the same type that the - <em>target</em> returns.</li> - <li>In the <em>builder</em>: A sensible <code>toString()</code> implementation.</li> - <li>In the class containing the <em>target</em>: A <code>builder()</code> method, which creates a new instance of the <em>builder</em>.</li> - </ul> - Each listed generated element will be silently skipped if that element already exists (disregarding parameter counts and looking only at names). This - includes the <em>builder</em> itself: If that class already exists, lombok will simply start injecting fields and methods inside this already existing - class, unless of course the fields / methods to be injected already exist. You may not put any other method (or constructor) generating lombok annotation - on a builder class though; for example, you can not put <code>@EqualsAndHashCode</code> on the builder class. - </p><p> - Now that the "static method" mode is clear, putting a <code>@Builder</code> annotation on a constructor functions similarly; effectively, - constructors are just static methods that have a special syntax to invoke them: Their 'return type' is the class they construct, and their - type parameters are the same as the type parameters of the class itself. - </p><p> - Finally, applying <code>@Builder</code> to a class is as if you added <code>@AllArgsConstructor(access = AccessLevel.PACKAGE)</code> to the class and applied the - <code>@Builder</code> annotation to this all-args-constructor. This only works if you haven't written any explicit constructors yourself. If you do have an - explicit constructor, put the <code>@Builder</code> annotation on the constructor instead of on the class. - </p><p> - The name of the builder class is <code><em>Foobar</em>Builder</code>, where <em>Foobar</em> is the simplified, title-cased form of the return type of the - <em>target</em> - that is, the name of your type for <code>@Builder</code> on constructors and types, and the name of the return type for <code>@Builder</code> - on static methods. For example, if <code>@Builder</code> is applied to a class named <code>com.yoyodyne.FancyList<T></code>, then the builder name will be - <code>FancyListBuilder<T></code>. If <code>@Builder</code> is applied to a static method that returns <code>void</code>, the builder will be named - <code>VoidBuilder</code>. - </p><p> - The configurable aspects of builder are:<ul> - <li>The <em>builder's class name</em> (default: return type + 'Builder')</li> - <li>The <em>build()</em> method's name (default: <code>"build"</code>)</li> - <li>The <em>builder()</em> method's name (default: <code>"builder"</code>)</li> - <li>The 'fluent' nature of the generated 'setter'-like methods. A fluent 'setter' is named just <code>fieldName()</code>, a non-fluent one is named <code>setFieldName()</code>. (default: <code>true</code>)</li> - <li>The 'chain' nature of the generated 'setter'-like methods. A chainable 'setter' returns the builder object itself, letting you chain 'set' calls. A non-chainable setter returns <code>void</code>. (default: <code>true</code>)</li> - </ul> - Example usage where all options are changed from their defaults:<br /> - <code>@Builder(builderClassName = "HelloWorldBuilder", buildMethodName = "execute", builderMethodName = "helloWorld", fluent = false, chain = false)</code><br /> - </p> - </div> - <div class="snippets"> - <div class="pre"> - <h3>With Lombok</h3> - <div class="snippet">@HTML_PRE@</div> - </div> - <div class="sep"></div> - <div class="post"> - <h3>Vanilla Java</h3> - <div class="snippet">@HTML_POST@</div> - </div> - </div> - <div style="clear: left;"></div> - <div class="overview confKeys"> - <h3>Supported configuration keys:</h3> - <dl> - <dt><code>lombok.builder.flagUsage</code> = [<code>warning</code> | <code>error</code>] (default: not set)</dt> - <dd>Lombok will flag any usage of <code>@Builder</code> as a warning or error if configured.</dd> - </dl> - </div> - <div class="overview"> - <h3>Small print</h3><div class="smallprint"> - <p> - Another strategy for fluent APIs is that the programmer using your library statically imports your 'builder' method. In this case, you might want to name your builder - method equal to your type's name. So, the builder method for a class called <code>Person</code> would become <code>person()</code>. This is nicer if the builder method - is statically imported. - </p><p> - If the return type of your target static method is a type parameter (such as <code>T</code>), lombok will enforce an explicit builder class name. - </p><p> - You don't HAVE to use <code>@Builder</code> to build anything; you can for example put it on a static method that has lots of parameter to improve the API of it. - In this case, we suggest you use <code>buildMethodName = </code> to rename the build method to <code>execute()</code> instead. - </p><p> - The builder class will NOT get an auto-generated implementation of <code>hashCode</code> or <code>equals</code> methods! These would suggest that it is sensible to use - instances of a builder as keys in a set or map. However, that's not a sensible thing to do. Hence, no <code>hashCode</code> or <code>equals</code>. - </p><p> - Generics are sorted out for you. - </p><p> - If an explicit constructor is present, but <code>@Builder</code> is placed on the class, then the builder will be generated as if an explicit constructor is present with the - same arguments list as what <code>@AllArgsConstructor</code> would produce. If this constructor does not exist, a compile time error will result. Usually you should just either let - lombok make this constructor (delete your constructor from the source), or, move the <code>@Builder</code> annotation to the constructor. - </p> - </div> + <div class="moved"> + @Builder has been promoted to the core package in lombok release v1.16.0.<br /> + The documentation has been moved here: <a href="../Builder.html">@lombok.Builder</a>. </div> <div class="footer"> - <a href="index.html">Back to experimental features</a> | <span class="disabled">Previous feature</span> | <a href="Accessors.html">Next feature (@Accessors)</a><br /> - <a href="../../credits.html" class="creditsLink">credits</a> | <span class="copyright">Copyright © 2009-2014 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>.</span> + <a href="index.html">Back to experimental features</a><br /> + <a href="../../credits.html" class="creditsLink">credits</a> | <span class="copyright">Copyright © 2009-2015 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>.</span> </div> <div style="clear: both;"></div> </div> diff --git a/website/features/experimental/ExtensionMethod.html b/website/features/experimental/ExtensionMethod.html index 58aaf1b3..f1735376 100644 --- a/website/features/experimental/ExtensionMethod.html +++ b/website/features/experimental/ExtensionMethod.html @@ -98,7 +98,7 @@ System.out.println(x.or("Hello, World!"));</pre> </div> <div class="footer"> <a href="index.html">Back to experimental features</a> | <a href="Accessors.html">Previous feature (@Accessors)</a> | <a href="FieldDefaults.html">Next feature (@FieldDefaults)</a><br /> - <a href="../../credits.html" class="creditsLink">credits</a> | <span class="copyright">Copyright © 2009-2014 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>.</span> + <a href="../../credits.html" class="creditsLink">credits</a> | <span class="copyright">Copyright © 2009-2015 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>.</span> </div> <div style="clear: both;"></div> </div> diff --git a/website/features/experimental/FieldDefaults.html b/website/features/experimental/FieldDefaults.html index 12ea7d80..5ad30952 100644 --- a/website/features/experimental/FieldDefaults.html +++ b/website/features/experimental/FieldDefaults.html @@ -73,7 +73,7 @@ </div> <div class="footer"> <a href="index.html">Back to experimental features</a> | <a href="ExtensionMethod.html">Previous feature (@ExtensionMethod)</a> | <a href="Delegate.html">Next feature (@Delegate)</a><br /> - <a href="../../credits.html" class="creditsLink">credits</a> | <span class="copyright">Copyright © 2009-2014 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>.</span> + <a href="../../credits.html" class="creditsLink">credits</a> | <span class="copyright">Copyright © 2009-2015 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>.</span> </div> <div style="clear: both;"></div> </div> diff --git a/website/features/experimental/Value.html b/website/features/experimental/Value.html new file mode 100644 index 00000000..c2b335e6 --- /dev/null +++ b/website/features/experimental/Value.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<html><head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <link rel="stylesheet" type="text/css" href="../../logi/reset.css" /> + <link rel="stylesheet" type="text/css" href="../features.css" /> + <link rel="shortcut icon" href="../../favicon.ico" type="image/x-icon" /> + <meta name="description" content="Spice up your java" /> + <title>EXPERIMENTAL - @Value</title> +</head><body><div id="pepper"> + <div class="minimumHeight"></div> + <div class="meat"> + <div class="header"><a href="../../index.html">Project Lombok</a></div> + <h1>@Value</h1> + <div class="byline">Immutable classes made very easy.</div> + <div class="moved"> + @Value has been promoted to the core package in lombok release v1.12.0.<br /> + The documentation has been moved here: <a href="../Value.html">@lombok.Value</a>. + </div> + <div class="footer"> + <a href="index.html">Back to experimental features</a><br /> + <a href="../../credits.html" class="creditsLink">credits</a> | <span class="copyright">Copyright © 2009-2015 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>.</span> + </div> + <div style="clear: both;"></div> + </div> +</div> +<script type="text/javascript"> + var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www."); + document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E")); +</script> +<script type="text/javascript"> + try { + var pageTracker = _gat._getTracker("UA-9884254-1"); + pageTracker._trackPageview(); + } catch(err) {} +</script> +</body></html> diff --git a/website/features/experimental/Wither.html b/website/features/experimental/Wither.html index 9ca43a46..81ef475e 100644 --- a/website/features/experimental/Wither.html +++ b/website/features/experimental/Wither.html @@ -93,7 +93,7 @@ </div> <div class="footer"> <a href="index.html">Back to experimental features</a> | <a href="Delegate.html">Previous feature (@Delegate)</a> | <a href="onX.html">Next feature (onX)</a><br /> - <a href="../../credits.html" class="creditsLink">credits</a> | <span class="copyright">Copyright © 2009-2014 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>.</span> + <a href="../../credits.html" class="creditsLink">credits</a> | <span class="copyright">Copyright © 2009-2015 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>.</span> </div> <div style="clear: both;"></div> </div> diff --git a/website/features/experimental/index.html b/website/features/experimental/index.html index f37712e0..a5932b73 100644 --- a/website/features/experimental/index.html +++ b/website/features/experimental/index.html @@ -22,8 +22,6 @@ Features that receive positive community feedback and which seem to produce clean, flexible code will eventually become accepted as a core feature and move out of the experimental package. <dl> - <dt><a href="Builder.html"><code>@Builder</code></a></dt> - <dd>... and Bob's your uncle: No-hassle fancy-pants APIs for object creation!</dd> <dt><a href="Accessors.html"><code>@Accessors</code></a></dt> <dd>A more fluent API for getters and setters.</dd> <dt><a href="ExtensionMethod.html"><code>@ExtensionMethod</code></a></dt> @@ -50,10 +48,12 @@ <dl> <dt><a href="../Value.html"><code>@Value</code></a>: Promoted</dt> <dd><code>@Value</code> has proven its value and has been moved to the main package.</li> + <dt><a href="../Builder.html"><code>@Builder</code></a>: Promoted</dt> + <dd><code>@Builder</code> is a solid base to build APIs on, and has been moved to the main package.</li> </dl> </div> <div class="footer"> - <a href="../../credits.html" class="creditsLink">credits</a> | <span class="copyright">Copyright © 2009-2014 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>.</span> + <a href="../../credits.html" class="creditsLink">credits</a> | <span class="copyright">Copyright © 2009-2015 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>.</span> </div> <div style="clear: both;"></div> </div> diff --git a/website/features/experimental/onX.html b/website/features/experimental/onX.html index ba2ead86..064db557 100644 --- a/website/features/experimental/onX.html +++ b/website/features/experimental/onX.html @@ -70,7 +70,7 @@ </div> <div class="footer"> <a href="index.html">Back to experimental features</a> | <a href="Wither.html">Previous feature (@Wither)</a> | <span class="disabled">Next feature</span><br /> - <a href="../../credits.html" class="creditsLink">credits</a> | <span class="copyright">Copyright © 2009-2014 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>.</span> + <a href="../../credits.html" class="creditsLink">credits</a> | <span class="copyright">Copyright © 2009-2015 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>.</span> </div> <div style="clear: both;"></div> </div> diff --git a/website/features/features.css b/website/features/features.css index b3017fe8..58897ccd 100644 --- a/website/features/features.css +++ b/website/features/features.css @@ -92,7 +92,7 @@ dd { white-space: nowrap; } -.snippets { +.snippets, .singleColumnSnippets { margin-top: 0px; } diff --git a/website/features/index.html b/website/features/index.html index 09a148f0..84457077 100644 --- a/website/features/index.html +++ b/website/features/index.html @@ -32,6 +32,8 @@ <code>@Getter</code> on all fields, and <code>@Setter</code> on all non-final fields, and <code>@RequiredArgsConstructor</code>!</dd> <dt><a href="Value.html"><code>@Value</code></a></dt> <dd>Immutable classes made very easy.</dd> + <dt><a href="Builder.html"><code>@Builder</code></a></dt> + <dd>... and Bob's your uncle: No-hassle fancy-pants APIs for object creation!</dd> <dt><a href="SneakyThrows.html"><code>@SneakyThrows</code></a></dt> <dd>To boldly throw checked exceptions where no one has thrown them before!</dd> <dt><a href="Synchronized.html"><code>@Synchronized</code></a></dt> @@ -70,7 +72,7 @@ your source files for source-level tools such as javadoc and GWT. More information about how to run delombok, including instructions for build tools can be found at the <a href="delombok.html">delombok page</a>.</div> <div class="footer"> - <a href="../credits.html" class="creditsLink">credits</a> | <span class="copyright">Copyright © 2009-2014 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>.</span> + <a href="../credits.html" class="creditsLink">credits</a> | <span class="copyright">Copyright © 2009-2015 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>.</span> </div> <div style="clear: both;"></div> </div> diff --git a/website/index.html b/website/index.html index 9f775fd3..cfcdf9b7 100644 --- a/website/index.html +++ b/website/index.html @@ -80,7 +80,7 @@ <a href="novideo.html">I can't see the video</a> | <a href="http://jnb.ociweb.com/jnb/jnbJan2010.html">Show me a text and images based explanation and tutorial instead!</a> </div> <div class="footer"> - <a href="credits.html" class="creditsLink">credits</a> | Copyright © 2009-2014 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>. + <a href="credits.html" class="creditsLink">credits</a> | Copyright © 2009-2015 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>. </div> </div> <script type="text/javascript"> diff --git a/website/setup/android.html b/website/setup/android.html index 2e3eb023..23decaaa 100644 --- a/website/setup/android.html +++ b/website/setup/android.html @@ -51,7 +51,7 @@ <li>The <code>suppressConstructorProperties</code> property can be set to <code>false</code> when using <a href="/features/Constructor.html">@XArgsConstructor Annotations</a>.</li> <li>Starting with Lombok <code>>= 1.14.0</code> you can instead a <code>lombok.config</code> file to the root of your project to disable <code>ConstructorProperties</code> project wide. Add the following line to <code>lombok.config</code>:<br /> <pre>lombok.anyConstructor.suppressConstructorProperties = true</pre><br /> - See the <a href="/features/experimental/configuration.html">configuration</a> documentation for more information on how to set up your project with a lombok config file. + See the <a href="/features/configuration.html">configuration</a> documentation for more information on how to set up your project with a lombok config file. </ul> </p><p> The instructions listed below are excerpts from <a href="https://github.com/excilys/androidannotations/wiki/Cookbook">The @@ -115,7 +115,7 @@ java -jar lombok.jar publicApi</pre> <div class="endBar"> </div> <div class="footer"> - <a href="../credits.html" class="creditsLink">credits</a> | Copyright © 2009-2014 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>. + <a href="../credits.html" class="creditsLink">credits</a> | Copyright © 2009-2015 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>. </div> </div> <script type="text/javascript"> diff --git a/website/setup/ecj.html b/website/setup/ecj.html index 7c3ff1bb..f9de358d 100644 --- a/website/setup/ecj.html +++ b/website/setup/ecj.html @@ -49,7 +49,7 @@ java <strong>-javaagent:lombok.jar=ECJ -Xbootclasspath/p:lombok.jar</strong> -ja <div class="endBar"> </div> <div class="footer"> - <a href="../credits.html" class="creditsLink">credits</a> | Copyright © 2009-2014 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>. + <a href="../credits.html" class="creditsLink">credits</a> | Copyright © 2009-2015 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>. </div> </div> <script type="text/javascript"> diff --git a/website/setup/gwt.html b/website/setup/gwt.html index 84e760bc..ef7ea678 100644 --- a/website/setup/gwt.html +++ b/website/setup/gwt.html @@ -52,7 +52,7 @@ java <strong>-javaagent:lombok.jar=ECJ</strong> <em>(rest of arguments)</em> <div class="endBar"> </div> <div class="footer"> - <a href="../credits.html" class="creditsLink">credits</a> | Copyright © 2009-2014 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>. + <a href="../credits.html" class="creditsLink">credits</a> | Copyright © 2009-2015 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>. </div> </div> <script type="text/javascript"> diff --git a/website/setup/netbeans.html b/website/setup/netbeans.html index 03775206..e1dafca7 100644 --- a/website/setup/netbeans.html +++ b/website/setup/netbeans.html @@ -49,7 +49,7 @@ <div class="endBar"> </div> <div class="footer"> - <a href="../credits.html" class="creditsLink">credits</a> | Copyright © 2009-2014 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>. + <a href="../credits.html" class="creditsLink">credits</a> | Copyright © 2009-2015 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>. </div> </div> <script type="text/javascript"> |