diff options
219 files changed, 6130 insertions, 977 deletions
@@ -1,4 +1,4 @@ -Copyright (C) 2009-2011 The Project Lombok Authors. +Copyright (C) 2009-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 @@ -27,7 +27,7 @@ the common tasks and can be called on to run the main aspects of all the sub-scr <property name="credentialsFile" value="google.properties" /> <property name="build.compiler" value="javac1.6" /> - <property name="ivy.retrieve.pattern" value="lib/[conf]/[artifact].[ext]" /> + <property name="ivy.retrieve.pattern" value="lib/[conf]/[organisation]-[artifact].[ext]" /> <available file="lib/ivyplusplus.jar" property="ivyplusplus.available" /> <path id="build.path"> @@ -81,7 +81,7 @@ the common tasks and can be called on to run the main aspects of all the sub-scr <target name="load-ipp" depends="download-ipp"> <taskdef classpath="lib/ivyplusplus.jar" resource="com/zwitserloot/ivyplusplus/antlib.xml" uri="antlib:com.zwitserloot.ivyplusplus" /> - <ivy:ensureippversion version="1.7" property="ivyplusplus.minimumAvailable" /> + <ivy:ensureippversion version="1.12" property="ivyplusplus.minimumAvailable" /> </target> <target name="redownload-ipp" unless="ivyplusplus.minimumAvailable"> @@ -147,6 +147,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/stubs" source="1.5" target="1.5" includeantruntime="false"> <src path="src/stubs" /> + <src path="src/javac-only-stubs" /> <classpath location="build/stubsstubs" /> </ivy:compile> @@ -196,7 +197,7 @@ the common tasks and can be called on to run the main aspects of all the sub-scr <target name="dist" description="Builds THE lombok.jar file which contains everything." depends="version, compile"> <mkdir dir="dist" /> <copy file="doc/changelog.markdown" tofile="build/changelog.txt" /> - <taskdef name="jarjar" classname="com.tonicsystems.jarjar.JarJarTask" classpath="lib/build/jarjar.jar" /> + <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/**"/> @@ -255,7 +256,7 @@ the common tasks and can be called on to run the main aspects of all the sub-scr </target> <target name="eclipse" depends="deps, contrib" description="Creates eclipse project files and downloads all dependencies. Open this directory as project in eclipse after running this target."> - <ivy:eclipsegen> + <ivy:eclipsegen source="1.6"> <srcdir dir="src/core" /> <srcdir dir="src/utils" /> <srcdir dir="src/eclipseAgent" /> @@ -271,21 +272,14 @@ the common tasks and can be called on to run the main aspects of all the sub-scr <settings> <url url="http://projectlombok.org/downloads/lombok.eclipse.settings" /> </settings> - <apt location="lib/build/spi.jar" /> + <apt location="lib/build/projectlombok.org-spi.jar" /> </ivy:eclipsegen> - <condition property="startOnFirstThread" value="-XstartOnFirstThread" else=""> - <os family="mac" /> - </condition> - <copy file="buildScripts/eclipse-debug-target.template" tofile="LombokizedEclipse.launch" preservelastmodified="true" overwrite="true"> - <filterset> - <filter token="START_ON_FIRST_THREAD" value="${startOnFirstThread}" /> - </filterset> </copy> </target> @@ -306,7 +300,7 @@ the common tasks and can be called on to run the main aspects of all the sub-scr </target> <target name="test-ecj" depends="dist, contrib" unless="tests.skip"> - <java jar="lib/ecj/ecj.jar" fork="true" failonerror="true"> + <java jar="lib/ecj/org.eclipse.custom-ecj.jar" fork="true" failonerror="true"> <jvmarg value="-javaagent:dist/lombok.jar=ecj" /> <arg value="-source" /> <arg value="1.6" /> diff --git a/buildScripts/eclipse-debug-target.template b/buildScripts/eclipse-debug-target.template index 033f8197..bb45cd69 100644 --- a/buildScripts/eclipse-debug-target.template +++ b/buildScripts/eclipse-debug-target.template @@ -21,7 +21,7 @@ <stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/> <stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-os ${target.os} -ws ${target.ws} -arch ${target.arch} -nl ${target.nl}"/> <stringAttribute key="org.eclipse.jdt.launching.SOURCE_PATH_PROVIDER" value="org.eclipse.pde.ui.workbenchClasspathProvider"/> -<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xms40m -Xmx512m -XX:MaxPermSize=256m @START_ON_FIRST_THREAD@ -Dorg.eclipse.swt.internal.carbon.smallFonts -javaagent:${project_loc:lombok}/dist/lombok.jar -Xbootclasspath/a:${project_loc:lombok}/dist/lombok.jar -Dlombok.patcher.patchDebugDir=${project_loc:lombok}/debug/patchedClasses"/> +<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xms40m -Xmx512m -XX:MaxPermSize=256m -Dorg.eclipse.swt.internal.carbon.smallFonts -javaagent:${project_loc:lombok}/dist/lombok.jar -Xbootclasspath/a:${project_loc:lombok}/dist/lombok.jar -Dlombok.patcher.patchDebugDir=${project_loc:lombok}/debug/patchedClasses"/> <stringAttribute key="pde.version" value="3.3"/> <stringAttribute key="product" value="org.eclipse.sdk.ide"/> <booleanAttribute key="show_selected_only" value="false"/> diff --git a/buildScripts/ivy-repo/org.projectlombok-lombok.patcher-0.7.xml b/buildScripts/ivy-repo/org.projectlombok-lombok.patcher-0.7.xml new file mode 100644 index 00000000..ca41f64c --- /dev/null +++ b/buildScripts/ivy-repo/org.projectlombok-lombok.patcher-0.7.xml @@ -0,0 +1,14 @@ +<ivy-module version="2.0"> + <info organisation="org.projectlombok" module="lombok.patcher" revision="0.7" publication="20130708220900"> + <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" /> + <description homepage="http://projectlombok.org/" /> + </info> + <configurations> + <conf name="default" /> + </configurations> + <publications> + <artifact conf="default" url="http://projectlombok.org/downloads/lombok.patcher-0.7.jar" /> + </publications> +</ivy-module> diff --git a/buildScripts/ivy.xml b/buildScripts/ivy.xml index b1440db5..7afc8a64 100644 --- a/buildScripts/ivy.xml +++ b/buildScripts/ivy.xml @@ -12,11 +12,12 @@ <conf name="javac7" /> </configurations> <dependencies> - <dependency org="org.projectlombok" name="lombok.patcher" rev="0.6" conf="build->default; runtime->default" /> + <dependency org="org.projectlombok" name="lombok.patcher" rev="0.7" conf="build->default; runtime->default" /> <dependency org="zwitserloot.com" name="cmdreader" rev="1.2" conf="build->runtime; runtime" /> <dependency org="junit" name="junit" rev="4.8.2" conf="test->default; contrib->sources" /> <dependency org="log4j" name="log4j" rev="1.2.16" conf="test->default; contrib->sources" /> + <dependency org="org.apache.logging.log4j" name="log4j-api" rev="2.0-beta4" conf="test->default; contrib->sources" /> <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"/> diff --git a/buildScripts/website.ant.xml b/buildScripts/website.ant.xml index 78dc8ed7..b9546ee9 100644 --- a/buildScripts/website.ant.xml +++ b/buildScripts/website.ant.xml @@ -76,7 +76,7 @@ such as converting the changelog into HTML, and creating javadoc. </target> <target name="-website-main" depends="-website-clean, version, javadoc, changelogToHtml"> - <taskdef classpath="lib/build/java2html.jar" name="java2html" classname="de.java2html.anttasks.Java2HtmlTask" /> + <taskdef classpath="lib/build/de.java2html-java2html.jar" name="java2html" classname="de.java2html.anttasks.Java2HtmlTask" /> <mkdir dir="build/website" /> <copy todir="build/website"> <fileset dir="website"> @@ -143,9 +143,18 @@ such as converting the changelog into HTML, and creating javadoc. <param name="transformationName" value="val" /> </antcall> <antcall target="-integrateSnippet"> + <param name="transformationName" value="Value" /> + </antcall> + <antcall target="-integrateSnippet"> <param name="transformationName" value="Delegate" /> </antcall> <antcall target="-integrateSnippet"> + <param name="transformationName" value="NonNull" /> + </antcall> + <antcall target="-integrateSnippet"> + <param name="transformationName" value="experimental/Builder" /> + </antcall> + <antcall target="-integrateSnippet"> <param name="transformationName" value="experimental/Accessors" /> </antcall> <antcall target="-integrateSnippet"> @@ -158,9 +167,6 @@ such as converting the changelog into HTML, and creating javadoc. <param name="transformationName" value="experimental/Wither" /> </antcall> <antcall target="-integrateSnippet"> - <param name="transformationName" value="experimental/Value" /> - </antcall> - <antcall target="-integrateSnippet"> <param name="transformationName" value="experimental/onX" /> </antcall> </target> @@ -327,7 +333,7 @@ such as converting the changelog into HTML, and creating javadoc. <link href="http://commons.apache.org/logging/apidocs/" /> <link href="http://logging.apache.org/log4j/1.2/apidocs/" /> <header><![CDATA[<a href='http://projectlombok.org/' target='_blank'>Lombok</a> - ]]>v${lombok.version}</header> - <bottom><![CDATA[<i>Copyright © 2009-2011 The Project Lombok Authors, licensed under the <a href='http://www.opensource.org/licenses/mit-license.php'>MIT licence</a>.]]></bottom> + <bottom><![CDATA[<i>Copyright © 2009-2013 The Project Lombok Authors, licensed under the <a href='http://www.opensource.org/licenses/mit-license.php'>MIT licence</a>.]]></bottom> </javadoc> <!-- bugfix for boneheaded javadoc bug where ?is-external=true is inserted before an anchor ref, breaking the anchor ref. is-external=true doesn't actually do anything, so, we'll just get rid of it. --> diff --git a/doc/changelog.markdown b/doc/changelog.markdown index a415cd01..0e15e7e2 100644 --- a/doc/changelog.markdown +++ b/doc/changelog.markdown @@ -1,9 +1,25 @@ Lombok Changelog ---------------- -### v0.11.7 (Edgy Guinea Pig) +### v0.12.0 "Angry Butterfly" (July 16th, 2013) +* FEATURE: javadoc on fields will now be copied to generated getters / setters / withers. There are ways to specify separate javadoc for the field, the setter, and the getter, and `@param` and `@return` are handled appropriately. Addresses feature request [Issue #59](https://code.google.com/p/projectlombok/issues/detail?id=59). [@Getter and @Setter documentation](http://projectlombok.org/features/GetterSetter.html). [@Wither documentation](http://projectlombok.org/features/experimental/Wither.html). +* CHANGE: The desugaring of @Getter(lazy=true) is now less object creation intensive. Documentation has been updated to reflect what the new desugaring looks like. [@Getter(lazy=true) documentation](http://projectlombok.org/features/GetterLazy.html). +* PROMOTION: `@Value` has been promoted from experimental to the main package with no changes. The 'old' experimental one is still around but is deprecated, and is an alias for the new main package one. [@Value documentation](http://projectlombok.org/features/Value.html). +* FEATURE: {Experimental} `@Builder` support. One of our earliest feature request issues, [Issue #16](https://code.google.com/p/projectlombok/issues/detail?id=16), has finally been addressed. [@Builder documentation](http://projectlombok.org/features/experimental/Builder.html). +* FEATURE: `@NonNull` on a method or constructor parameter now generates a null-check statement at the start of your method. This nullcheck will throw a `NullPointerException` with the name of the parameter as the message. [Issue #514](https://code.google.com/p/projectlombok/issues/detail?id=514) +* BUGFIX: Usage of `Lombok.sneakyThrow()` or `@SneakyThrows` would sometimes result in invalid classes (classes which fail with `VerifyError`). [Issue #470](https://code.google.com/p/projectlombok/issues/detail?id=470) +* BUGFIX: Using `val` in try-with-resources did not work for javac. [Issue #520](https://code.google.com/p/projectlombok/issues/detail?id=520) +* BUGFIX: When using `@Data`, warnings are not generated if certain aspects are not generated because you wrote explicit versions of them. However, this gets confusing with `equals` / `hashCode` / `canEqual`, as nothing is generated if any one of those methods is present. Now, if one of `equals` or `hashCode` is present but not the other one (or `canEqual` is present but `equals` and/or `hashCode` is missing), a warning is emitted to explain that lombok will not generate any of the equals / hashCode methods, and that you should either write them all yourself or remove them all. [Issue #513](https://code.google.com/p/projectlombok/issues/detail?id=513) +* BUGFIX: Possibly fixed a race condition in patcher [Issue #531](https://code.google.com/p/projectlombok/issues/detail?id=531). + +### v0.11.8 (April 23rd, 2013) +* FEATURE: Major performance improvements in eclipse by profiling the project clean process. * CHANGE: {Experimental} The experimental `@Value` feature no longer implies the also experimental `@Wither`. If you like your `@Value` classes to make withers, add `@Wither` to the class right next to `@Value`. * FEATURE: {Experimental} Reintroduced `onMethod`, `onConstructor` and `onParam` to `@Getter`, `@Setter`, `@Wither`, and `@XArgsConstructor`. These parameters allow you to add annotations to the methods/constructors that lombok will generate. This is a workaround feature: The stability of the feature on future versions of javac is not guaranteed, and if a better way to implement this feature is found, this feature's current incarnation will be removed without a reasonable period of deprecation. [Documentation on the onX feature](http://projectlombok.org/features/experimental/onX.html) +* FEATURE: Added support for Log4j v2.0 via `@Log4j2` [Issue #432](http://code.google.com/p/projectlombok/issues/detail?id=432) +* ENHANCEMENT: The Lombok installer can now find and install lombok into [JBoss Developer Studio](http://www.redhat.com/products/jbossenterprisemiddleware/developer-studio/). The installer will now also look for eclipse and eclipse variants in your home directory. [Issue #434](http://code.google.com/p/projectlombok/issues/detail?id=432) +* BUGFIX: `@ExtensionMethods` no longer causes `VerifyError` exceptions when running eclipse-compiled code if extension methods are called on expressions which are method calls whose return type is a type variable. For example, `someList.get(i).extensionMethod()` would fail that way. [Issue #436](http://code.google.com/p/projectlombok/issues/detail?id=436) +* BUGFIX: java 7's try-with-resources statement did not delombok correctly. [Issue #459](http://code.google.com/p/projectlombok/issues/detail?id=459) ### v0.11.6 (October 30th, 2012) * FEATURE: Lombok can be disabled entirely for any given compile run by using JVM switch `-Dlombok.disable`. This might be useful for code style checkers and such. diff --git a/src/core/lombok/Data.java b/src/core/lombok/Data.java index ee6f2fcb..bbc8d920 100644 --- a/src/core/lombok/Data.java +++ b/src/core/lombok/Data.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2012 The Project Lombok Authors. + * Copyright (C) 2009-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 @@ -39,7 +39,7 @@ import java.lang.annotation.Target; * @see RequiredArgsConstructor * @see ToString * @see EqualsAndHashCode - * @see lombok.experimental.Value + * @see lombok.Value */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.SOURCE) diff --git a/src/core/lombok/NonNull.java b/src/core/lombok/NonNull.java index 5f5d8ed2..96813170 100644 --- a/src/core/lombok/NonNull.java +++ b/src/core/lombok/NonNull.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009 The Project Lombok Authors. + * Copyright (C) 2009-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 @@ -28,12 +28,14 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * Lombok is smart enough to translate any annotation named {@code @NonNull} in any casing and - * with any package name to the return type of generated getters and the parameter of generated setters and constructors, - * as well as generate the appropriate null checks in the setter and constructor. - * - * You can use this annotation for the purpose, though you can also use JSR305's annotation, findbugs's, pmd's, or IDEA's, or just - * about anyone elses. As long as it is named {@code @NonNull}. + * If put on a parameter, lombok will insert a null-check at the start of the method / constructor's body, throwing a + * {@code NullPointerException} with the parameter's name as message. If put on a field, any generated method assigning + * a value to this field will also produce these nullchecks. + * <p> + * Note that any annotation named {@code NonNull} with any casing and any package will result in nullchecks produced for + * generated methods (and the annotation will be copied to the getter return type and any parameters of generated methods), + * but <em>only</em> this annotation, if present on a parameter, will result in a null check inserted into your otherwise + * handwritten method. * * WARNING: If the java community ever does decide on supporting a single {@code @NonNull} annotation (for example via JSR305), then * this annotation will <strong>be deleted</strong> from the lombok package. If the need to update an import statement scares diff --git a/src/core/lombok/Value.java b/src/core/lombok/Value.java new file mode 100644 index 00000000..2cffe15b --- /dev/null +++ b/src/core/lombok/Value.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2012-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; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Generates a lot of code which fits with a class that is a representation of an immutable entity. + * <p> + * Equivalent to {@code @Getter @FieldDefaults(makeFinal=true, level=AccessLevel.PRIVATE) @RequiredArgsConstructor @ToString @EqualsAndHashCode}. + * <p> + * Complete documentation is found at <a href="http://projectlombok.org/features/experimental/Value.html">the project lombok features page for @Value</a>. + * + * @see lombok.Getter + * @see lombok.experimental.FieldDefaults + * @see lombok.RequiredArgsConstructor + * @see lombok.ToString + * @see lombok.EqualsAndHashCode + * @see lombok.Data + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.SOURCE) +public @interface Value { + /** + * If you specify a static constructor name, then the generated constructor will be private, and + * instead a static factory method is created that other classes can use to create instances. + * We suggest the name: "of", like so: + * + * <pre> + * public @Data(staticConstructor = "of") class Point { final int x, y; } + * </pre> + * + * Default: No static constructor, instead the normal constructor is public. + */ + String staticConstructor() default ""; +} diff --git a/src/core/lombok/bytecode/PreventNullAnalysisRemover.java b/src/core/lombok/bytecode/PreventNullAnalysisRemover.java index a3f18b40..751e691f 100644 --- a/src/core/lombok/bytecode/PreventNullAnalysisRemover.java +++ b/src/core/lombok/bytecode/PreventNullAnalysisRemover.java @@ -44,12 +44,12 @@ public class PreventNullAnalysisRemover implements PostCompilerTransformation { byte[] fixedByteCode = fixJSRInlining(original); ClassReader reader = new ClassReader(fixedByteCode); - ClassWriter writer = new FixedClassWriter(reader, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); + ClassWriter writer = new FixedClassWriter(reader, 0); final AtomicBoolean changesMade = new AtomicBoolean(); - class PreventNullanalysisVisitor extends MethodVisitor { - PreventNullanalysisVisitor(MethodVisitor mv) { + class PreventNullAnalysisVisitor extends MethodVisitor { + PreventNullAnalysisVisitor(MethodVisitor mv) { super(Opcodes.ASM4, mv); } @@ -61,6 +61,7 @@ public class PreventNullAnalysisRemover implements PostCompilerTransformation { if (hit && !"(Ljava/lang/Object;)Ljava/lang/Object;".equals(desc)) hit = false; if (hit) { changesMade.set(true); + if (System.getProperty("lombok.debugAsmOnly", null) != null) super.visitMethodInsn(opcode, owner, name, desc); // DEBUG for issue 470! } else { super.visitMethodInsn(opcode, owner, name, desc); } @@ -69,7 +70,7 @@ public class PreventNullAnalysisRemover implements PostCompilerTransformation { reader.accept(new ClassVisitor(Opcodes.ASM4, writer) { @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { - return new PreventNullanalysisVisitor(super.visitMethod(access, name, desc, signature, exceptions)); + return new PreventNullAnalysisVisitor(super.visitMethod(access, name, desc, signature, exceptions)); } }, 0); return changesMade.get() ? writer.toByteArray() : null; diff --git a/src/core/lombok/bytecode/SneakyThrowsRemover.java b/src/core/lombok/bytecode/SneakyThrowsRemover.java index 8143d860..914c313a 100644 --- a/src/core/lombok/bytecode/SneakyThrowsRemover.java +++ b/src/core/lombok/bytecode/SneakyThrowsRemover.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2012 The Project Lombok Authors. + * 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 @@ -32,19 +32,21 @@ import org.mangosdk.spi.ProviderFor; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Handle; +import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; @ProviderFor(PostCompilerTransformation.class) public class SneakyThrowsRemover implements PostCompilerTransformation { - @Override public byte[] applyTransformations(byte[] original, String fileName, DiagnosticsReceiver diagnostics) { + @Override public byte[] applyTransformations(byte[] original, String fileName, final DiagnosticsReceiver diagnostics) { if (!new ClassFileMetaData(original).usesMethod("lombok/Lombok", "sneakyThrow")) return null; byte[] fixedByteCode = fixJSRInlining(original); ClassReader reader = new ClassReader(fixedByteCode); - ClassWriter writer = new FixedClassWriter(reader, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); + ClassWriter writer = new ClassWriter(reader, 0); final AtomicBoolean changesMade = new AtomicBoolean(); @@ -53,6 +55,8 @@ public class SneakyThrowsRemover implements PostCompilerTransformation { super(Opcodes.ASM4, mv); } + private boolean methodInsnQueued = false; + @Override public void visitMethodInsn(int opcode, String owner, String name, String desc) { if ( opcode == Opcodes.INVOKESTATIC && @@ -60,12 +64,117 @@ public class SneakyThrowsRemover implements PostCompilerTransformation { "lombok/Lombok".equals(owner) && "(Ljava/lang/Throwable;)Ljava/lang/RuntimeException;".equals(desc)) { - changesMade.set(true); - super.visitInsn(Opcodes.ATHROW); + if (System.getProperty("lombok.debugAsmOnly", null) != null) { + super.visitMethodInsn(opcode, owner, name, desc); // DEBUG for issue 470! + } else { + methodInsnQueued = true; + } } else { super.visitMethodInsn(opcode, owner, name, desc); } } + + private void handleQueue() { + if (!methodInsnQueued) return; + super.visitMethodInsn(Opcodes.INVOKESTATIC, "lombok/Lombok", "sneakyThrow", "(Ljava/lang/Throwable;)Ljava/lang/RuntimeException;"); + methodInsnQueued = false; + diagnostics.addWarning("Proper usage is: throw lombok.Lombok.sneakyThrow(someException);. You did not 'throw' it. Because of this, the call to sneakyThrow " + + "remains in your classfile and you will need lombok.jar on the classpath at runtime."); + } + + @Override public void visitInsn(int arg0) { + if (methodInsnQueued && arg0 == Opcodes.ATHROW) { + changesMade.set(true); + // As expected, the required ATHROW. We can now safely 'eat' the previous call. + methodInsnQueued = false; + } + handleQueue(); + super.visitInsn(arg0); + } + @Override public void visitFrame(int arg0, int arg1, Object[] arg2, int arg3, Object[] arg4) { + handleQueue(); + super.visitFrame(arg0, arg1, arg2, arg3, arg4); + } + + @Override public void visitIincInsn(int arg0, int arg1) { + handleQueue(); + super.visitIincInsn(arg0, arg1); + } + + @Override public void visitFieldInsn(int arg0, String arg1, String arg2, String arg3) { + handleQueue(); + super.visitFieldInsn(arg0, arg1, arg2, arg3); + } + + @Override public void visitIntInsn(int arg0, int arg1) { + handleQueue(); + super.visitIntInsn(arg0, arg1); + } + + @Override public void visitEnd() { + handleQueue(); + super.visitEnd(); + } + + @Override public void visitInvokeDynamicInsn(String arg0, String arg1, Handle arg2, Object... arg3) { + handleQueue(); + super.visitInvokeDynamicInsn(arg0, arg1, arg2, arg3); + } + + @Override public void visitLabel(Label arg0) { + handleQueue(); + super.visitLabel(arg0); + } + + @Override public void visitJumpInsn(int arg0, Label arg1) { + handleQueue(); + super.visitJumpInsn(arg0, arg1); + } + + @Override public void visitLdcInsn(Object arg0) { + handleQueue(); + super.visitLdcInsn(arg0); + } + + @Override public void visitLocalVariable(String arg0, String arg1, String arg2, Label arg3, Label arg4, int arg5) { + handleQueue(); + super.visitLocalVariable(arg0, arg1, arg2, arg3, arg4, arg5); + } + + @Override public void visitMaxs(int arg0, int arg1) { + handleQueue(); + super.visitMaxs(arg0, arg1); + } + + @Override public void visitLookupSwitchInsn(Label arg0, int[] arg1, Label[] arg2) { + handleQueue(); + super.visitLookupSwitchInsn(arg0, arg1, arg2); + } + + @Override public void visitMultiANewArrayInsn(String arg0, int arg1) { + handleQueue(); + super.visitMultiANewArrayInsn(arg0, arg1); + } + + @Override public void visitVarInsn(int arg0, int arg1) { + handleQueue(); + super.visitVarInsn(arg0, arg1); + } + + @Override public void visitTryCatchBlock(Label arg0, Label arg1, Label arg2, String arg3) { + handleQueue(); + super.visitTryCatchBlock(arg0, arg1, arg2, arg3); + } + + @Override public void visitTableSwitchInsn(int arg0, int arg1, Label arg2, Label... arg3) { + handleQueue(); + super.visitTableSwitchInsn(arg0, arg1, arg2, arg3); + } + + @Override public void visitTypeInsn(int arg0, String arg1) { + handleQueue(); + super.visitTypeInsn(arg0, arg1); + } } reader.accept(new ClassVisitor(Opcodes.ASM4, writer) { diff --git a/src/core/lombok/core/AST.java b/src/core/lombok/core/AST.java index 68f36412..6fed0252 100644 --- a/src/core/lombok/core/AST.java +++ b/src/core/lombok/core/AST.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009 The Project Lombok Authors. + * Copyright (C) 2009-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 @@ -30,7 +30,6 @@ import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.HashMap; import java.util.IdentityHashMap; import java.util.List; @@ -54,15 +53,15 @@ public abstract class AST<A extends AST<A, L, N>, L extends LombokNode<A, L, N>, private L top; private final String fileName; private final String packageDeclaration; - private final Collection<String> imports; - Map<N, Void> identityDetector = new IdentityHashMap<N, Void>(); + private final ImportList imports; + Map<N, N> identityDetector = new IdentityHashMap<N, N>(); private Map<N, L> nodeMap = new IdentityHashMap<N, L>(); private boolean changed = false; - protected AST(String fileName, String packageDeclaration, Collection<String> imports) { + protected AST(String fileName, String packageDeclaration, ImportList imports) { this.fileName = fileName == null ? "(unknown).java" : fileName; this.packageDeclaration = packageDeclaration; - this.imports = Collections.unmodifiableCollection(new ArrayList<String>(imports)); + this.imports = imports; } public void setChanged() { @@ -96,7 +95,7 @@ public abstract class AST<A extends AST<A, L, N>, L extends LombokNode<A, L, N>, * * Example: "java.util.IOException". */ - public final Collection<String> getImportStatements() { + public final ImportList getImportList() { return imports; } @@ -106,7 +105,7 @@ public abstract class AST<A extends AST<A, L, N>, L extends LombokNode<A, L, N>, */ protected L putInMap(L node) { nodeMap.put(node.get(), node); - identityDetector.put(node.get(), null); + identityDetector.put(node.get(), node.get()); return node; } @@ -118,7 +117,7 @@ public abstract class AST<A extends AST<A, L, N>, L extends LombokNode<A, L, N>, /** Clears the registry that avoids endless loops, and empties the node map. The existing node map * object is left untouched, and instead a new map is created. */ protected void clearState() { - identityDetector = new IdentityHashMap<N, Void>(); + identityDetector = new IdentityHashMap<N, N>(); nodeMap = new IdentityHashMap<N, L>(); } @@ -128,9 +127,7 @@ public abstract class AST<A extends AST<A, L, N>, L extends LombokNode<A, L, N>, * case you should do nothing lest the AST build process loops endlessly. */ protected boolean setAndGetAsHandled(N node) { - if (identityDetector.containsKey(node)) return true; - identityDetector.put(node, null); - return false; + return identityDetector.put(node, node) != null; } public String getFileName() { @@ -147,6 +144,24 @@ public abstract class AST<A extends AST<A, L, N>, L extends LombokNode<A, L, N>, return nodeMap.get(node); } + /** + * Returns the JLS spec version that the compiler uses to parse and compile this AST. + * For example, if -source 1.6 is on the command line, this will return {@code 6}. + */ + public int getSourceVersion() { + return 6; + } + + /** + * Returns the latest version of the java language specification supported by the host compiler. + * For example, if compiling with javac v1.7, this returns {@code 7}. + * + * NB: Even if -source (lower than maximum) is specified, this method still returns the maximum supported number. + */ + public int getLatestJavaSpecSupported() { + return 6; + } + @SuppressWarnings({"unchecked", "rawtypes"}) L replaceNewWithExistingOld(Map<N, L> oldNodes, L newNode) { L oldNode = oldNodes.get(newNode.get()); @@ -159,8 +174,8 @@ public abstract class AST<A extends AST<A, L, N>, L extends LombokNode<A, L, N>, oldChild.parent = targetNode; } - targetNode.children.clear(); - ((List)targetNode.children).addAll(children); + targetNode.children = LombokImmutableList.copyOf(children); + return targetNode; } @@ -217,14 +232,12 @@ public abstract class AST<A extends AST<A, L, N>, L extends LombokNode<A, L, N>, } } - for (Class<?> statementType : getStatementTypes()) { - if (statementType.isAssignableFrom(t)) { - f.setAccessible(true); - fields.add(new FieldAccess(f, dim)); - break; - } + if (shouldDrill(c, t, f.getName())) { + f.setAccessible(true); + fields.add(new FieldAccess(f, dim)); } } + getFields(c.getSuperclass(), fields); } @@ -241,6 +254,14 @@ public abstract class AST<A extends AST<A, L, N>, L extends LombokNode<A, L, N>, * though some platforms (such as Eclipse) group these under one common supertype. */ protected abstract Collection<Class<? extends N>> getStatementTypes(); + protected boolean shouldDrill(Class<?> parentType, Class<?> childType, String fieldName) { + for (Class<?> statementType : getStatementTypes()) { + if (statementType.isAssignableFrom(childType)) return true; + } + + return false; + } + /** * buildTree implementation that uses reflection to find all child nodes by way of inspecting * the fields. */ diff --git a/src/core/lombok/core/AnnotationProcessor.java b/src/core/lombok/core/AnnotationProcessor.java index 8c5f7fba..e9cf3891 100644 --- a/src/core/lombok/core/AnnotationProcessor.java +++ b/src/core/lombok/core/AnnotationProcessor.java @@ -164,12 +164,9 @@ public class AnnotationProcessor extends AbstractProcessor { } } - boolean handled = false; - for (ProcessorDescriptor proc : active) { - handled |= proc.process(annotations, roundEnv); - } + for (ProcessorDescriptor proc : active) proc.process(annotations, roundEnv); - return handled; + return false; } /** diff --git a/src/core/lombok/core/AnnotationValues.java b/src/core/lombok/core/AnnotationValues.java index df056dd4..d04797cb 100644 --- a/src/core/lombok/core/AnnotationValues.java +++ b/src/core/lombok/core/AnnotationValues.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2012 The Project Lombok Authors. + * Copyright (C) 2009-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 @@ -435,23 +435,16 @@ public class AnnotationValues<A extends Annotation> { } /* 2. Walk through non-star imports and search for a match. */ { - for (String im : ast == null ? Collections.<String>emptyList() : ast.getImportStatements()) { - if (im.endsWith(".*")) continue; - int idx = im.lastIndexOf('.'); - String simple = idx == -1 ? im : im.substring(idx+1); - if (simple.equals(prefix)) { - return im + typeName.substring(prefix.length()); - } + if (prefix.equals(typeName)) { + String fqn = ast.getImportList().getFullyQualifiedNameForSimpleName(typeName); + if (fqn != null) return fqn; } } /* 3. Walk through star imports and, if they start with "java.", use Class.forName based resolution. */ { - List<String> imports = ast == null ? Collections.<String>emptyList() : new ArrayList<String>(ast.getImportStatements()); - imports.add("java.lang.*"); - for (String im : imports) { - if (!im.endsWith(".*") || !im.startsWith("java.")) continue; + for (String potential : ast.getImportList().applyNameToStarImports("java", typeName)) { try { - Class<?> c = Class.forName(im.substring(0, im.length()-1) + typeName); + Class<?> c = Class.forName(potential); if (c != null) return c.getName(); } catch (Throwable t) { //Class.forName failed for whatever reason - it most likely does not exist, continue. diff --git a/src/core/lombok/core/ImportList.java b/src/core/lombok/core/ImportList.java new file mode 100644 index 00000000..95e266c4 --- /dev/null +++ b/src/core/lombok/core/ImportList.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2013 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.core; + +import java.util.Collection; + +public interface ImportList { + /** + * If there is an explicit import of the stated unqualified type name, return that. Otherwise, return null. + */ + String getFullyQualifiedNameForSimpleName(String unqualified); + + /** + * Returns true if the package name is explicitly star-imported, OR the packageName refers to this source file's own package name, OR packageName is 'java.lang'. + */ + boolean hasStarImport(String packageName); + + /** + * Takes all explicit non-static star imports whose first element is equal to {@code startsWith}, replaces the star with {@code unqualified}, and returns these. + */ + Collection<String> applyNameToStarImports(String startsWith, String unqualified); + + String applyUnqualifiedNameToPackage(String unqualified); +} diff --git a/src/core/lombok/core/LombokInternalAliasing.java b/src/core/lombok/core/LombokInternalAliasing.java new file mode 100644 index 00000000..4fd7b29d --- /dev/null +++ b/src/core/lombok/core/LombokInternalAliasing.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2013 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.core; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public class LombokInternalAliasing { + public static final Map<String, String> IMPLIED_EXTRA_STAR_IMPORTS; + public static final Map<String, String> ALIASES; + + /** + * Provide a fully qualified name (FQN), and the canonical version of this is returned. + */ + public static String processAliases(String in) { + if (in == null) return null; + for (Map.Entry<String, String> e : ALIASES.entrySet()) { + if (in.equals(e.getKey())) return e.getValue(); + } + return in; + } + + static { + Map<String, String> m = new HashMap<String, String>(); + m.put("lombok.experimental", "lombok"); + IMPLIED_EXTRA_STAR_IMPORTS = Collections.unmodifiableMap(m); + + m = new HashMap<String, String>(); + m.put("lombok.experimental.Value", "lombok.Value"); + ALIASES = Collections.unmodifiableMap(m); + } +} diff --git a/src/core/lombok/core/LombokNode.java b/src/core/lombok/core/LombokNode.java index eac59806..07c62151 100644 --- a/src/core/lombok/core/LombokNode.java +++ b/src/core/lombok/core/LombokNode.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2011 The Project Lombok Authors. + * Copyright (C) 2009-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 @@ -42,7 +42,7 @@ public abstract class LombokNode<A extends AST<A, L, N>, L extends LombokNode<A, protected final A ast; protected final Kind kind; protected final N node; - protected final List<L> children; + protected LombokImmutableList<L> children; protected L parent; /** structurally significant are those nodes that can be annotated in java 1.6 or are method-like toplevels, @@ -62,7 +62,7 @@ public abstract class LombokNode<A extends AST<A, L, N>, L extends LombokNode<A, this.ast = ast; this.kind = kind; this.node = node; - this.children = children == null ? new ArrayList<L>() : children; + this.children = children != null ? LombokImmutableList.copyOf(children) : LombokImmutableList.<L>of(); for (L child : this.children) { child.parent = (L) this; if (!child.isStructurallySignificant) @@ -91,12 +91,12 @@ public abstract class LombokNode<A extends AST<A, L, N>, L extends LombokNode<A, } /** - * Convenient shortcut to the owning ast object's {@code getImportStatements} method. + * Convenient shortcut to the owning ast object's {@code getImportList} method. * - * @see AST#getImportStatements() + * @see AST#getImportList() */ - public Collection<String> getImportStatements() { - return ast.getImportStatements(); + public ImportList getImportList() { + return ast.getImportList(); } /** @@ -120,35 +120,6 @@ public abstract class LombokNode<A extends AST<A, L, N>, L extends LombokNode<A, return node; } - /** - * Replaces the AST node represented by this node object with the provided node. This node must - * have a parent, obviously, for this to work. - * - * Also affects the underlying (Eclipse/javac) AST. - */ - @SuppressWarnings({"rawtypes", "unchecked"}) - public L replaceWith(N newN, Kind newNodeKind) { - ast.setChanged(); - L newNode = ast.buildTree(newN, newNodeKind); - newNode.parent = parent; - for (int i = 0; i < parent.children.size(); i++) { - if (parent.children.get(i) == this) ((List)parent.children).set(i, newNode); - } - - parent.replaceChildNode(get(), newN); - return newNode; - } - - /** - * Replaces the stated node with a new one. The old node must be a direct child of this node. - * - * Also affects the underlying (Eclipse/javac) AST. - */ - public void replaceChildNode(N oldN, N newN) { - ast.setChanged(); - ast.replaceStatementInNode(get(), oldN, newN); - } - public Kind getKind() { return kind; } @@ -204,12 +175,27 @@ public abstract class LombokNode<A extends AST<A, L, N>, L extends LombokNode<A, /** * Returns all children nodes. + */ + public LombokImmutableList<L> down() { + return children; + } + + /** + * Convenient shortcut to the owning ast object's getLatestJavaSpecSupported method. + * + * @see AST#getLatestJavaSpecSupported() + */ + public int getLatestJavaSpecSupported() { + return ast.getLatestJavaSpecSupported(); + } + + /** + * Convenient shortcut to the owning ast object's getSourceVersion method. * - * A copy is created, so changing the list has no effect. Also, while iterating through this list, - * you may add, remove, or replace children without causing {@code ConcurrentModificationException}s. + * @see AST#getSourceVersion() */ - public Collection<L> down() { - return new ArrayList<L>(children); + public int getSourceVersion() { + return ast.getSourceVersion(); } /** @@ -235,13 +221,13 @@ public abstract class LombokNode<A extends AST<A, L, N>, L extends LombokNode<A, * * Does not change the underlying (javac/Eclipse) AST, only the wrapped view. */ - @SuppressWarnings({"rawtypes", "unchecked"}) + @SuppressWarnings({"unchecked"}) public L add(N newChild, Kind newChildKind) { ast.setChanged(); L n = ast.buildTree(newChild, newChildKind); if (n == null) return null; n.parent = (L) this; - ((List)children).add(n); + children = children.append(n); return n; } @@ -267,7 +253,7 @@ public abstract class LombokNode<A extends AST<A, L, N>, L extends LombokNode<A, for (LombokNode child : children) child.gatherAndRemoveChildren(map); ast.identityDetector.remove(get()); map.put(get(), (L) this); - children.clear(); + children = LombokImmutableList.of(); ast.getNodeMap().remove(get()); } @@ -278,7 +264,7 @@ public abstract class LombokNode<A extends AST<A, L, N>, L extends LombokNode<A, */ public void removeChild(L child) { ast.setChanged(); - children.remove(child); + children = children.removeElement(child); } /** diff --git a/src/core/lombok/core/PostCompiler.java b/src/core/lombok/core/PostCompiler.java index 1e40cca2..4a099862 100644 --- a/src/core/lombok/core/PostCompiler.java +++ b/src/core/lombok/core/PostCompiler.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 The Project Lombok Authors. + * 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 @@ -33,6 +33,7 @@ public final class PostCompiler { private static List<PostCompilerTransformation> transformations; public static byte[] applyTransformations(byte[] original, String fileName, DiagnosticsReceiver diagnostics) { + if (System.getProperty("lombok.disablePostCompiler", null) != null) return original; init(diagnostics); byte[] previous = original; for (PostCompilerTransformation transformation : transformations) { @@ -59,6 +60,7 @@ public final class PostCompiler { } 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() { @Override public void close() throws IOException { // no need to call super diff --git a/src/core/lombok/core/TransformationsUtil.java b/src/core/lombok/core/TransformationsUtil.java index 921c27d6..8959ad7a 100644 --- a/src/core/lombok/core/TransformationsUtil.java +++ b/src/core/lombok/core/TransformationsUtil.java @@ -22,13 +22,25 @@ package lombok.core; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.regex.Pattern; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import lombok.ToString; +import lombok.Value; import lombok.experimental.Accessors; +import lombok.experimental.FieldDefaults; +import lombok.experimental.Wither; /** * Container for static utility methods useful for some of the standard lombok transformations, regardless of @@ -39,6 +51,13 @@ public class TransformationsUtil { //Prevent instantiation } + @SuppressWarnings({"all", "unchecked", "deprecation"}) + public static final List<Class<? extends java.lang.annotation.Annotation>> INVALID_ON_BUILDERS = Collections.unmodifiableList( + Arrays.<Class<? extends java.lang.annotation.Annotation>>asList( + Getter.class, Setter.class, Wither.class, ToString.class, EqualsAndHashCode.class, + RequiredArgsConstructor.class, AllArgsConstructor.class, NoArgsConstructor.class, + Data.class, Value.class, lombok.experimental.Value.class, FieldDefaults.class)); + /** * Given the name of a field, return the 'base name' of that field. For example, {@code fFoobar} becomes {@code foobar} if {@code f} is in the prefix list. * For prefixes that end in a letter character, the next character must be a non-lowercase character (i.e. {@code hashCode} is not {@code ashCode} even if @@ -159,12 +178,12 @@ public class TransformationsUtil { if (fieldName.length() == 0) return null; - Accessors ac = accessors.getInstance(); - fieldName = removePrefix(fieldName, ac.prefix()); + Accessors ac = accessors == null ? null : accessors.getInstance(); + fieldName = removePrefix(fieldName, ac == null ? new String[0] : ac.prefix()); if (fieldName == null) return null; String fName = fieldName.toString(); - if (adhereToFluent && ac.fluent()) return fName; + if (adhereToFluent && ac != null && ac.fluent()) return fName; if (isBoolean && fName.startsWith("is") && fieldName.length() > 2 && !Character.isLowerCase(fieldName.charAt(2))) { // The field is for example named 'isRunning'. diff --git a/src/core/lombok/core/TypeLibrary.java b/src/core/lombok/core/TypeLibrary.java index 0e3f7bf4..c0e9dc43 100644 --- a/src/core/lombok/core/TypeLibrary.java +++ b/src/core/lombok/core/TypeLibrary.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2012 The Project Lombok Authors. + * Copyright (C) 2009-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 @@ -21,12 +21,7 @@ */ package lombok.core; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; import java.util.HashMap; -import java.util.List; import java.util.Map; /** @@ -40,24 +35,23 @@ import java.util.Map; * <ul><li>foo.Spork</li><li>Spork</li><li>foo.*</li></ul> */ public class TypeLibrary { - private final Map<String, List<String>> keyToFqnMap; - private final String singletonValue; - private final List<String> singletonKeys; + private final Map<String, String> unqualifiedToQualifiedMap; + private final String unqualified, qualified; public TypeLibrary() { - keyToFqnMap = new HashMap<String, List<String>>(); - singletonKeys = null; - singletonValue = null; + unqualifiedToQualifiedMap = new HashMap<String, String>(); + unqualified = null; + qualified = null; } private TypeLibrary(String fqnSingleton) { - keyToFqnMap = null; - singletonValue = fqnSingleton; + unqualifiedToQualifiedMap = null; + qualified = fqnSingleton; int idx = fqnSingleton.lastIndexOf('.'); if (idx == -1) { - singletonKeys = Collections.singletonList(fqnSingleton); + unqualified = fqnSingleton; } else { - singletonKeys = Arrays.asList(fqnSingleton, fqnSingleton.substring(idx + 1), fqnSingleton.substring(0, idx) + ".*"); + unqualified = fqnSingleton.substring(idx + 1); } } @@ -71,42 +65,34 @@ public class TypeLibrary { * @param fullyQualifiedTypeName the FQN type name, such as 'java.lang.String'. */ public void addType(String fullyQualifiedTypeName) { - if (keyToFqnMap == null) throw new IllegalStateException("SingleType library"); + fullyQualifiedTypeName = fullyQualifiedTypeName.replace("$", "."); int idx = fullyQualifiedTypeName.lastIndexOf('.'); if (idx == -1) throw new IllegalArgumentException( "Only fully qualified types are allowed (and stuff in the default package is not palatable to us either!)"); + String unqualified = fullyQualifiedTypeName.substring(idx + 1); + if (unqualifiedToQualifiedMap == null) throw new IllegalStateException("SingleType library"); - fullyQualifiedTypeName = fullyQualifiedTypeName.replace("$", "."); - final String simpleName = fullyQualifiedTypeName.substring(idx +1); - final String packageName = fullyQualifiedTypeName.substring(0, idx); - - if (keyToFqnMap.put(fullyQualifiedTypeName, Collections.singletonList(fullyQualifiedTypeName)) != null) return; - - addToMap(simpleName, fullyQualifiedTypeName); - addToMap(packageName + ".*", fullyQualifiedTypeName); - } - - private TypeLibrary addToMap(String keyName, String fullyQualifiedTypeName) { - List<String> list = keyToFqnMap.get(keyName); - if (list == null) { - list = new ArrayList<String>(); - keyToFqnMap.put(keyName, list); + unqualifiedToQualifiedMap.put(unqualified, fullyQualifiedTypeName); + unqualifiedToQualifiedMap.put(fullyQualifiedTypeName, fullyQualifiedTypeName); + for (Map.Entry<String, String> e : LombokInternalAliasing.ALIASES.entrySet()) { + if (fullyQualifiedTypeName.equals(e.getValue())) unqualifiedToQualifiedMap.put(e.getKey(), fullyQualifiedTypeName); } - - list.add(fullyQualifiedTypeName); - return this; } /** - * Returns all items in the type library that may be a match to the provided type. + * Translates an unqualified name such as 'String' to 'java.lang.String', _if_ you added 'java.lang.String' to the library via the {@code addType} method. + * Also returns the input if it is equal to a fully qualified name added to this type library. * - * @param typeReference something like 'String', 'java.lang.String', or 'java.lang.*'. - * @return A list of Fully Qualified Names for all types in the library that fit the reference. + * Returns null if it does not match any type in this type library. */ - public Collection<String> findCompatible(String typeReference) { - if (singletonKeys != null) return singletonKeys.contains(typeReference) ? Collections.singletonList(singletonValue) : Collections.<String>emptyList(); - - List<String> result = keyToFqnMap.get(typeReference); - return result == null ? Collections.<String>emptyList() : Collections.unmodifiableList(result); + public String toQualified(String typeReference) { + if (unqualifiedToQualifiedMap == null) { + if (typeReference.equals(unqualified) || typeReference.equals(qualified)) return qualified; + for (Map.Entry<String, String> e : LombokInternalAliasing.ALIASES.entrySet()) { + if (e.getKey().equals(typeReference)) return e.getValue(); + } + return null; + } + return unqualifiedToQualifiedMap.get(typeReference); } } diff --git a/src/core/lombok/core/TypeResolver.java b/src/core/lombok/core/TypeResolver.java index 27f0bfc1..287a085f 100644 --- a/src/core/lombok/core/TypeResolver.java +++ b/src/core/lombok/core/TypeResolver.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2011 The Project Lombok Authors. + * Copyright (C) 2009-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 @@ -21,11 +21,6 @@ */ package lombok.core; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; - import lombok.core.AST.Kind; /** @@ -35,60 +30,47 @@ import lombok.core.AST.Kind; * and this importer also can't find inner types from superclasses/interfaces. */ public class TypeResolver { - private Collection<String> imports; + private ImportList imports; /** * Creates a new TypeResolver that can be used to resolve types in a source file with the given package and import statements. */ - public TypeResolver(String packageString, Collection<String> importStrings) { - this.imports = makeImportList(packageString, importStrings); - } - - private static Collection<String> makeImportList(String packageString, Collection<String> importStrings) { - Set<String> imports = new HashSet<String>(); - if (packageString != null) imports.add(packageString + ".*"); - imports.addAll(importStrings == null ? Collections.<String>emptySet() : importStrings); - imports.add("java.lang.*"); - return imports; + public TypeResolver(ImportList importList) { + this.imports = importList; } public boolean typeMatches(LombokNode<?, ?, ?> context, String fqn, String typeRef) { - return !findTypeMatches(context, TypeLibrary.createLibraryForSingleType(fqn), typeRef).isEmpty(); + return typeRefToFullyQualifiedName(context, TypeLibrary.createLibraryForSingleType(fqn), typeRef) != null; } - /** - * Finds type matches for the stated type reference. The provided context is scanned for local type names - * that shadow type names listed in import statements. If such a shadowing occurs, no matches are returned - * for any shadowed types, as you would expect. - */ - public Collection<String> findTypeMatches(LombokNode<?, ?, ?> context, TypeLibrary library, String typeRef) { + public String typeRefToFullyQualifiedName(LombokNode<?, ?, ?> context, TypeLibrary library, String typeRef) { + typeRef = LombokInternalAliasing.processAliases(typeRef); // When asking if 'Foo' could possibly be referring to 'bar.Baz', the answer is obviously no. - Collection<String> potentialMatches = library.findCompatible(typeRef); - if (potentialMatches.isEmpty()) return Collections.emptyList(); + String qualified = library.toQualified(typeRef); + if (qualified == null) return null; - // If input type appears to be fully qualified, we found a winner. - int idx = typeRef.indexOf('.'); - if (idx > -1) return potentialMatches; + // When asking if 'lombok.Getter' could possibly be referring to 'lombok.Getter', the answer is obviously yes. + if (typeRef.equals(qualified)) return typeRef; - // If there's an import statement that explicitly imports a 'Getter' that isn't any of our potentials, return no matches, - // because if you want to know if 'Foo' could refer to 'bar.Foo' when 'baz.Foo' is explicitly imported, the answer is no. - if (nameConflictInImportList(typeRef, potentialMatches)) return Collections.emptyList(); + // When asking if 'Getter' could possibly be referring to 'lombok.Getter' if 'import lombok.Getter;' is in the source file, the answer is yes. + String fromExplicitImport = imports.getFullyQualifiedNameForSimpleName(typeRef); + if (fromExplicitImport != null) { + // ... and if 'import foobar.Getter;' is in the source file, the answer is no. + return fromExplicitImport.equals(qualified) ? qualified : null; + } - // Check if any of our potentials are even imported in the first place. If not: no matches. - // Note that (ourPackage.*) is added to the imports. - potentialMatches = eliminateImpossibleMatches(potentialMatches, library); - if (potentialMatches.isEmpty()) return Collections.emptyList(); + // When asking if 'Getter' could possibly be referring to 'lombok.Getter' and 'import lombok.*; / package lombok;' isn't in the source file. the answer is no. + String pkgName = qualified.substring(0, qualified.length() - typeRef.length() - 1); + if (!imports.hasStarImport(pkgName)) return null; - // Now the hard part - inner classes or method local classes in our own scope. - // For method locals, this refers to any statements that are 'above' the type reference with the same name. - // For inners, this refers to siblings of us or any parent node that are type declarations. + // Now the hard part: Given that there is a star import, 'Getter' most likely refers to 'lombok.Getter', but type shadowing may occur in which case it doesn't. LombokNode<?, ?, ?> n = context; mainLoop: while (n != null) { if (n.getKind() == Kind.TYPE && typeRef.equals(n.getName())) { // Our own class or one of our outer classes is named 'typeRef' so that's what 'typeRef' is referring to, not one of our type library classes. - return Collections.emptyList(); + return null; } if (n.getKind() == Kind.STATEMENT || n.getKind() == Kind.LOCAL) { @@ -99,7 +81,7 @@ public class TypeResolver { for (LombokNode<?, ?, ?> child : newN.down()) { // We found a method local with the same name above our code. That's the one 'typeRef' is referring to, not // anything in the type library we're trying to find, so, no matches. - if (child.getKind() == Kind.TYPE && typeRef.equals(child.getName())) return Collections.emptyList(); + if (child.getKind() == Kind.TYPE && typeRef.equals(child.getName())) return null; if (child == n) break; } } @@ -110,41 +92,14 @@ public class TypeResolver { if (n.getKind() == Kind.TYPE || n.getKind() == Kind.COMPILATION_UNIT) { for (LombokNode<?, ?, ?> child : n.down()) { // Inner class that's visible to us has 'typeRef' as name, so that's the one being referred to, not one of our type library classes. - if (child.getKind() == Kind.TYPE && typeRef.equals(child.getName())) return Collections.emptyList(); + if (child.getKind() == Kind.TYPE && typeRef.equals(child.getName())) return null; } } n = n.directUp(); } - // No class in this source file is a match, therefore the potential matches found via the import statements must be it. Return those. - return potentialMatches; - } - - private Collection<String> eliminateImpossibleMatches(Collection<String> potentialMatches, TypeLibrary library) { - Set<String> results = new HashSet<String>(); - - for (String importedType : imports) { - Collection<String> reduced = new HashSet<String>(library.findCompatible(importedType)); - reduced.retainAll(potentialMatches); - results.addAll(reduced); - } - - return results; - } - - private boolean nameConflictInImportList(String simpleName, Collection<String> potentialMatches) { - for (String importedType : imports) { - if (!toSimpleName(importedType).equals(simpleName)) continue; - if (potentialMatches.contains(importedType)) continue; - return true; - } - return false; - } - - private static String toSimpleName(String typeName) { - int idx = typeName.lastIndexOf('.'); - return idx == -1 ? typeName : typeName.substring(idx+1); + return qualified; } } diff --git a/src/core/lombok/core/Version.java b/src/core/lombok/core/Version.java index b9239978..bd83c1f8 100644 --- a/src/core/lombok/core/Version.java +++ b/src/core/lombok/core/Version.java @@ -28,8 +28,8 @@ 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 = "0.11.7"; - private static final String RELEASE_NAME = "Dashing Kakapo"; + private static final String VERSION = "0.12.1"; + private static final String RELEASE_NAME = "Angry Butterfy"; private Version() { //Prevent instantiation diff --git a/src/core/lombok/core/handlers/HandlerUtil.java b/src/core/lombok/core/handlers/HandlerUtil.java new file mode 100644 index 00000000..3d386054 --- /dev/null +++ b/src/core/lombok/core/handlers/HandlerUtil.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2013 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.core.handlers; + +import lombok.core.JavaIdentifiers; +import lombok.core.LombokNode; + +public class HandlerUtil { + private HandlerUtil() {} + + /** Checks if the given name is a valid identifier. + * + * If it is, this returns {@code true} and does nothing else. + * If it isn't, this returns {@code false} and adds an error message to the supplied node. + */ + public static boolean checkName(String nameSpec, String identifier, LombokNode<?, ?, ?> errorNode) { + if (identifier.isEmpty()) { + errorNode.addError(nameSpec + " cannot be the empty string."); + return false; + } + + if (!JavaIdentifiers.isValidJavaIdentifier(identifier)) { + errorNode.addError(nameSpec + " must be a valid java identifier."); + return false; + } + + return true; + } +} diff --git a/src/core/lombok/eclipse/EclipseAST.java b/src/core/lombok/eclipse/EclipseAST.java index 8ab42140..370b40fc 100644 --- a/src/core/lombok/eclipse/EclipseAST.java +++ b/src/core/lombok/eclipse/EclipseAST.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2010 The Project Lombok Authors. + * Copyright (C) 2009-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 @@ -24,12 +24,14 @@ package lombok.eclipse; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; +import org.eclipse.jdt.internal.compiler.CompilationResult; import java.util.Collection; import java.util.Collections; import java.util.List; import lombok.Lombok; import lombok.core.AST; +import lombok.core.LombokImmutableList; import org.eclipse.jdt.internal.compiler.ast.ASTNode; import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; @@ -55,7 +57,7 @@ public class EclipseAST extends AST<EclipseAST, EclipseNode, ASTNode> { * @param ast The compilation unit, which serves as the top level node in the tree to be built. */ public EclipseAST(CompilationUnitDeclaration ast) { - super(toFileName(ast), packageDeclaration(ast), imports(ast)); + super(toFileName(ast), packageDeclaration(ast), new EclipseImportList(ast)); this.compilationUnitDeclaration = ast; setTop(buildCompilationUnit(ast)); this.completeParse = isComplete(ast); @@ -67,16 +69,18 @@ public class EclipseAST extends AST<EclipseAST, EclipseNode, ASTNode> { return pkg == null ? null : Eclipse.toQualifiedName(pkg.getImportName()); } - private static Collection<String> imports(CompilationUnitDeclaration cud) { - List<String> imports = new ArrayList<String>(); - if (cud.imports == null) return imports; - for (ImportReference imp : cud.imports) { - if (imp == null) continue; - String qualifiedName = Eclipse.toQualifiedName(imp.getImportName()); - if ((imp.bits & ASTNode.OnDemand) != 0) qualifiedName += ".*"; - imports.add(qualifiedName); - } - return imports; + @Override public int getSourceVersion() { + long sl = compilationUnitDeclaration.problemReporter.options.sourceLevel; + long cl = compilationUnitDeclaration.problemReporter.options.complianceLevel; + sl >>= 16; + cl >>= 16; + if (sl == 0) sl = cl; + if (cl == 0) cl = sl; + return Math.min((int)(sl - 44), (int)(cl - 44)); + } + + @Override public int getLatestJavaSpecSupported() { + return Eclipse.getEcjCompilerVersion(); } /** @@ -88,8 +92,10 @@ public class EclipseAST extends AST<EclipseAST, EclipseNode, ASTNode> { } void traverseChildren(EclipseASTVisitor visitor, EclipseNode node) { - for (EclipseNode child : node.down()) { - child.traverse(visitor); + LombokImmutableList<EclipseNode> children = node.down(); + int len = children.size(); + for (int i = 0; i < len; i++) { + children.get(i).traverse(visitor); } } @@ -118,7 +124,8 @@ public class EclipseAST extends AST<EclipseAST, EclipseNode, ASTNode> { } void addToCompilationResult() { - addProblemToCompilationResult((CompilationUnitDeclaration) top().get(), + CompilationUnitDeclaration cud = (CompilationUnitDeclaration) top().get(); + addProblemToCompilationResult(cud.getFileName(), cud.compilationResult, isWarning, message, sourceStart, sourceEnd); } } @@ -142,11 +149,10 @@ public class EclipseAST extends AST<EclipseAST, EclipseNode, ASTNode> { * Adds a problem to the provided CompilationResult object so that it will show up * in the Problems/Warnings view. */ - public static void addProblemToCompilationResult(CompilationUnitDeclaration ast, + public static void addProblemToCompilationResult(char[] fileNameArray, CompilationResult result, boolean isWarning, String message, int sourceStart, int sourceEnd) { - if (ast.compilationResult == null) return; try { - EcjReflectionCheck.addProblemToCompilationResult.invoke(null, ast, isWarning, message, sourceStart, sourceEnd); + EcjReflectionCheck.addProblemToCompilationResult.invoke(null, fileNameArray, result, isWarning, message, sourceStart, sourceEnd); } catch (NoClassDefFoundError e) { //ignore, we don't have access to the correct ECJ classes, so lombok can't possibly //do anything useful here. @@ -163,7 +169,7 @@ public class EclipseAST extends AST<EclipseAST, EclipseNode, ASTNode> { //do anything useful here. } } - + private final CompilationUnitDeclaration compilationUnitDeclaration; private boolean completeParse; @@ -353,7 +359,7 @@ public class EclipseAST extends AST<EclipseAST, EclipseNode, ASTNode> { } private static class EcjReflectionCheck { - private static final String CUD_TYPE = "org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration"; + private static final String COMPILATIONRESULT_TYPE = "org.eclipse.jdt.internal.compiler.CompilationResult"; public static Method addProblemToCompilationResult; public static final Throwable problem; @@ -362,7 +368,7 @@ public class EclipseAST extends AST<EclipseAST, EclipseNode, ASTNode> { Throwable problem_ = null; Method m = null; try { - m = EclipseAstProblemView.class.getMethod("addProblemToCompilationResult", Class.forName(CUD_TYPE), boolean.class, String.class, int.class, int.class); + m = EclipseAstProblemView.class.getMethod("addProblemToCompilationResult", char[].class, Class.forName(COMPILATIONRESULT_TYPE), boolean.class, String.class, int.class, int.class); } catch (Throwable t) { // That's problematic, but as long as no local classes are used we don't actually need it. // Better fail on local classes than crash altogether. diff --git a/src/core/lombok/eclipse/EclipseAnnotationHandler.java b/src/core/lombok/eclipse/EclipseAnnotationHandler.java index 84304339..ca9965f7 100644 --- a/src/core/lombok/eclipse/EclipseAnnotationHandler.java +++ b/src/core/lombok/eclipse/EclipseAnnotationHandler.java @@ -29,7 +29,7 @@ import lombok.core.SpiLoadUtil; * * You MUST replace 'T' with a specific annotation type, such as: * - * {@code public class HandleGetter implements EclipseAnnotationHandler<Getter>} + * {@code public class HandleGetter extends EclipseAnnotationHandler<Getter>} * * Because this generics parameter is inspected to figure out which class you're interested in. * diff --git a/src/core/lombok/eclipse/EclipseAstProblemView.java b/src/core/lombok/eclipse/EclipseAstProblemView.java index a2d5b833..c1179666 100644 --- a/src/core/lombok/eclipse/EclipseAstProblemView.java +++ b/src/core/lombok/eclipse/EclipseAstProblemView.java @@ -3,7 +3,6 @@ package lombok.eclipse; import org.eclipse.jdt.core.compiler.CategorizedProblem; import org.eclipse.jdt.internal.compiler.CompilationResult; -import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; import org.eclipse.jdt.internal.compiler.problem.DefaultProblem; import org.eclipse.jdt.internal.compiler.problem.ProblemSeverities; import org.eclipse.jdt.internal.compiler.util.Util; @@ -13,14 +12,12 @@ public class EclipseAstProblemView { * Adds a problem to the provided CompilationResult object so that it will show up * in the Problems/Warnings view. */ - public static void addProblemToCompilationResult(CompilationUnitDeclaration ast, + public static void addProblemToCompilationResult(char[] fileNameArray, CompilationResult result, boolean isWarning, String message, int sourceStart, int sourceEnd) { - if (ast.compilationResult == null) return; - char[] fileNameArray = ast.getFileName(); + if (result == null) return; if (fileNameArray == null) fileNameArray = "(unknown).java".toCharArray(); int lineNumber = 0; int columnNumber = 1; - CompilationResult result = ast.compilationResult; int[] lineEnds = null; lineNumber = sourceStart >= 0 ? Util.getLineNumber(sourceStart, lineEnds = result.getLineSeparatorPositions(), 0, lineEnds.length-1) @@ -33,7 +30,7 @@ public class EclipseAstProblemView { fileNameArray, message, 0, new String[0], isWarning ? ProblemSeverities.Warning : ProblemSeverities.Error, sourceStart, sourceEnd, lineNumber, columnNumber); - ast.compilationResult.record(ecProblem, null); + result.record(ecProblem, null); } private static class LombokProblem extends DefaultProblem { diff --git a/src/core/lombok/eclipse/EclipseImportList.java b/src/core/lombok/eclipse/EclipseImportList.java new file mode 100644 index 00000000..69246b3c --- /dev/null +++ b/src/core/lombok/eclipse/EclipseImportList.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2013 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.eclipse; + +import static lombok.eclipse.Eclipse.toQualifiedName; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import lombok.core.ImportList; +import lombok.core.LombokInternalAliasing; + +import org.eclipse.jdt.internal.compiler.ast.ASTNode; +import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; +import org.eclipse.jdt.internal.compiler.ast.ImportReference; + +public class EclipseImportList implements ImportList { + private ImportReference[] imports; + private ImportReference pkg; + + public EclipseImportList(CompilationUnitDeclaration cud) { + this.pkg = cud.currentPackage; + this.imports = cud.imports; + } + + @Override public String getFullyQualifiedNameForSimpleName(String unqualified) { + if (imports != null) { + outer: + for (ImportReference imp : imports) { + if ((imp.bits & ASTNode.OnDemand) != 0) continue; + char[][] tokens = imp.tokens; + char[] token = tokens.length == 0 ? new char[0] : tokens[tokens.length - 1]; + int len = token.length; + if (len != unqualified.length()) continue; + for (int i = 0; i < len; i++) if (token[i] != unqualified.charAt(i)) continue outer; + return LombokInternalAliasing.processAliases(toQualifiedName(tokens)); + } + } + return null; + } + + @Override public boolean hasStarImport(String packageName) { + for (Map.Entry<String, String> e : LombokInternalAliasing.IMPLIED_EXTRA_STAR_IMPORTS.entrySet()) { + if (e.getValue().equals(packageName) && hasStarImport(e.getKey())) return true; + } + if (isEqual(packageName, pkg)) return true; + if ("java.lang".equals(packageName)) return true; + if (imports != null) for (ImportReference imp : imports) { + if ((imp.bits & ASTNode.OnDemand) == 0) continue; + if (imp.isStatic()) continue; + if (isEqual(packageName, imp)) return true; + } + return false; + } + + private static boolean isEqual(String packageName, ImportReference pkgOrStarImport) { + if (pkgOrStarImport == null || pkgOrStarImport.tokens == null || pkgOrStarImport.tokens.length == 0) return packageName.isEmpty(); + int pos = 0; + int len = packageName.length(); + for (int i = 0; i < pkgOrStarImport.tokens.length; i++) { + if (i != 0) { + if (pos >= len) return false; + if (packageName.charAt(pos++) != '.') return false; + } + for (int j = 0; j < pkgOrStarImport.tokens[i].length; j++) { + if (pos >= len) return false; + if (packageName.charAt(pos++) != pkgOrStarImport.tokens[i][j]) return false; + } + } + return true; + } + + @Override public Collection<String> applyNameToStarImports(String startsWith, String name) { + List<String> out = Collections.emptyList(); + + if (pkg != null && pkg.tokens != null && pkg.tokens.length != 0) { + char[] first = pkg.tokens[0]; + int len = first.length; + boolean match = true; + if (startsWith.length() == len) { + for (int i = 0; match && i < len; i++) { + if (startsWith.charAt(i) != first[i]) match = false; + } + if (match) out.add(toQualifiedName(pkg.tokens) + "." + name); + } + } + + if (imports != null) { + outer: + for (ImportReference imp : imports) { + if ((imp.bits & ASTNode.OnDemand) == 0) continue; + if (imp.isStatic()) continue; + if (imp.tokens == null || imp.tokens.length == 0) continue; + char[] firstToken = imp.tokens[0]; + if (firstToken.length != startsWith.length()) continue; + for (int i = 0; i < firstToken.length; i++) if (startsWith.charAt(i) != firstToken[i]) continue outer; + String fqn = toQualifiedName(imp.tokens) + "." + name; + if (out.isEmpty()) out = Collections.singletonList(fqn); + else if (out.size() == 1) { + out = new ArrayList<String>(out); + out.add(fqn); + } else { + out.add(fqn); + } + } + } + return out; + } + + @Override public String applyUnqualifiedNameToPackage(String unqualified) { + if (pkg == null || pkg.tokens == null || pkg.tokens.length == 0) return unqualified; + return toQualifiedName(pkg.tokens) + "." + unqualified; + } +} diff --git a/src/core/lombok/eclipse/HandlerLibrary.java b/src/core/lombok/eclipse/HandlerLibrary.java index 56744793..242e923c 100644 --- a/src/core/lombok/eclipse/HandlerLibrary.java +++ b/src/core/lombok/eclipse/HandlerLibrary.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2012 The Project Lombok Authors. + * Copyright (C) 2009-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 @@ -218,30 +218,27 @@ public class HandlerLibrary { * @param annotation 'node.get()' - convenience parameter. */ public void handleAnnotation(CompilationUnitDeclaration ast, EclipseNode annotationNode, org.eclipse.jdt.internal.compiler.ast.Annotation annotation, long priority) { - String pkgName = annotationNode.getPackageDeclaration(); - Collection<String> imports = annotationNode.getImportStatements(); - - TypeResolver resolver = new TypeResolver(pkgName, imports); + TypeResolver resolver = new TypeResolver(annotationNode.getImportList()); TypeReference rawType = annotation.type; if (rawType == null) return; - for (String fqn : resolver.findTypeMatches(annotationNode, typeLibrary, toQualifiedName(annotation.type.getTypeName()))) { - AnnotationHandlerContainer<?> container = annotationHandlers.get(fqn); - if (container == null) continue; - if (priority != container.getPriority()) continue; - - if (!annotationNode.isCompleteParse() && container.deferUntilPostDiet()) { - if (needsHandling(annotation)) container.preHandle(annotation, annotationNode); - continue; - } - - try { - if (checkAndSetHandled(annotation)) container.handle(annotation, annotationNode); - } catch (AnnotationValueDecodeFail fail) { - fail.owner.setError(fail.getMessage(), fail.idx); - } catch (Throwable t) { - error(ast, String.format("Lombok annotation handler %s failed", container.handler.getClass()), t); - } + String fqn = resolver.typeRefToFullyQualifiedName(annotationNode, typeLibrary, toQualifiedName(annotation.type.getTypeName())); + if (fqn == null) return; + AnnotationHandlerContainer<?> container = annotationHandlers.get(fqn); + if (container == null) return; + if (priority != container.getPriority()) return; + + if (!annotationNode.isCompleteParse() && container.deferUntilPostDiet()) { + if (needsHandling(annotation)) container.preHandle(annotation, annotationNode); + return; + } + + try { + if (checkAndSetHandled(annotation)) container.handle(annotation, annotationNode); + } catch (AnnotationValueDecodeFail fail) { + fail.owner.setError(fail.getMessage(), fail.idx); + } catch (Throwable t) { + error(ast, String.format("Lombok annotation handler %s failed", container.handler.getClass()), t); } } diff --git a/src/core/lombok/eclipse/TransformEclipseAST.java b/src/core/lombok/eclipse/TransformEclipseAST.java index 47e620f6..11caf5c2 100644 --- a/src/core/lombok/eclipse/TransformEclipseAST.java +++ b/src/core/lombok/eclipse/TransformEclipseAST.java @@ -144,7 +144,7 @@ public class TransformEclipseAST { try { String message = "Lombok can't parse this source: " + t.toString(); - EclipseAST.addProblemToCompilationResult(ast, false, message, 0, 0); + EclipseAST.addProblemToCompilationResult(ast.getFileName(), ast.compilationResult, false, message, 0, 0); t.printStackTrace(); } catch (Throwable t2) { try { diff --git a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java index 78780522..9bd634f7 100644 --- a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java +++ b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java @@ -22,6 +22,7 @@ package lombok.eclipse.handlers; import static lombok.eclipse.Eclipse.*; +import static lombok.core.TransformationsUtil.*; import java.lang.reflect.Constructor; import java.lang.reflect.Field; @@ -61,6 +62,7 @@ import org.eclipse.jdt.internal.compiler.ast.Annotation; import org.eclipse.jdt.internal.compiler.ast.ArrayInitializer; import org.eclipse.jdt.internal.compiler.ast.ArrayQualifiedTypeReference; import org.eclipse.jdt.internal.compiler.ast.ArrayTypeReference; +import org.eclipse.jdt.internal.compiler.ast.Block; import org.eclipse.jdt.internal.compiler.ast.CastExpression; import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration; @@ -142,7 +144,7 @@ public class EclipseHandlerUtil { } catch (NoClassDefFoundError e) { //standalone ecj does not jave Platform, ILog, IStatus, and friends. new TerminalLogger().error(message, bundleName, error); } - if (cud != null) EclipseAST.addProblemToCompilationResult(cud, false, message + " - See error log.", 0, 0); + if (cud != null) EclipseAST.addProblemToCompilationResult(cud.getFileName(), cud.compilationResult, false, message + " - See error log.", 0, 0); } /** @@ -290,11 +292,34 @@ public class EclipseHandlerUtil { if (!lastPartA.equals(lastPartB)) return false; String typeName = toQualifiedName(typeRef.getTypeName()); - TypeResolver resolver = new TypeResolver(node.getPackageDeclaration(), node.getImportStatements()); + TypeResolver resolver = new TypeResolver(node.getImportList()); return resolver.typeMatches(node, type.getName(), typeName); } + public static void sanityCheckForMethodGeneratingAnnotationsOnBuilderClass(EclipseNode typeNode, EclipseNode errorNode) { + List<String> disallowed = null; + for (EclipseNode child : typeNode.down()) { + for (Class<? extends java.lang.annotation.Annotation> annType : INVALID_ON_BUILDERS) { + if (annotationTypeMatches(annType, child)) { + if (disallowed == null) disallowed = new ArrayList<String>(); + disallowed.add(annType.getSimpleName()); + } + } + } + + int size = disallowed == null ? 0 : disallowed.size(); + if (size == 0) return; + if (size == 1) { + errorNode.addError("@" + disallowed.get(0) + " is not allowed on builder classes."); + return; + } + StringBuilder out = new StringBuilder(); + for (String a : disallowed) out.append("@").append(a).append(", "); + out.setLength(out.length() - 2); + errorNode.addError(out.append(" are not allowed on builder classes.").toString()); + } + public static Annotation copyAnnotation(Annotation annotation, ASTNode source) { int pS = source.sourceStart, pE = source.sourceEnd; @@ -358,6 +383,20 @@ public class EclipseHandlerUtil { return out; } + public static TypeReference namePlusTypeParamsToTypeReference(char[] typeName, TypeParameter[] params, long p) { + if (params != null && params.length > 0) { + TypeReference[] refs = new TypeReference[params.length]; + int idx = 0; + for (TypeParameter param : params) { + TypeReference typeRef = new SingleTypeReference(param.name, p); + refs[idx++] = typeRef; + } + return new ParameterizedSingleTypeReference(typeName, refs, 0, p); + } + + return new SingleTypeReference(typeName, p); + } + /** * Convenience method that creates a new array and copies each TypeReference in the source array via * {@link #copyType(TypeReference, ASTNode)}. @@ -830,15 +869,20 @@ public class EclipseHandlerUtil { private static final Object MARKER = new Object(); static void registerCreatedLazyGetter(FieldDeclaration field, char[] methodName, TypeReference returnType) { - if (!nameEquals(returnType.getTypeName(), "boolean") || returnType.dimensions() > 0) return; - generatedLazyGettersWithPrimitiveBoolean.put(field, MARKER); + if (isBoolean(returnType)) { + generatedLazyGettersWithPrimitiveBoolean.put(field, MARKER); + } + } + + public static boolean isBoolean(TypeReference typeReference) { + return nameEquals(typeReference.getTypeName(), "boolean") && typeReference.dimensions() == 0; } private static GetterMethod findGetter(EclipseNode field) { FieldDeclaration fieldDeclaration = (FieldDeclaration) field.get(); boolean forceBool = generatedLazyGettersWithPrimitiveBoolean.containsKey(fieldDeclaration); TypeReference fieldType = fieldDeclaration.type; - boolean isBoolean = forceBool || (nameEquals(fieldType.getTypeName(), "boolean") && fieldType.dimensions() == 0); + boolean isBoolean = forceBool || isBoolean(fieldType); EclipseNode typeNode = field.up(); for (String potentialGetterName : toAllGetterNames(field, isBoolean)) { @@ -1207,15 +1251,15 @@ public class EclipseHandlerUtil { * Inserts a field into an existing type. The type must represent a {@code TypeDeclaration}. * The field carries the @{@link SuppressWarnings}("all") annotation. */ - public static void injectFieldSuppressWarnings(EclipseNode type, FieldDeclaration field) { + public static EclipseNode injectFieldSuppressWarnings(EclipseNode type, FieldDeclaration field) { field.annotations = createSuppressWarningsAll(field, field.annotations); - injectField(type, field); + return injectField(type, field); } /** * Inserts a field into an existing type. The type must represent a {@code TypeDeclaration}. */ - public static void injectField(EclipseNode type, FieldDeclaration field) { + public static EclipseNode injectField(EclipseNode type, FieldDeclaration field) { TypeDeclaration parent = (TypeDeclaration) type.get(); if (parent.fields == null) { @@ -1242,7 +1286,7 @@ public class EclipseHandlerUtil { } } - type.add(field, Kind.FIELD); + return type.add(field, Kind.FIELD); } private static boolean isEnumConstant(final FieldDeclaration field) { @@ -1252,7 +1296,7 @@ public class EclipseHandlerUtil { /** * Inserts a method into an existing type. The type must represent a {@code TypeDeclaration}. */ - public static void injectMethod(EclipseNode type, AbstractMethodDeclaration method) { + public static EclipseNode injectMethod(EclipseNode type, AbstractMethodDeclaration method) { method.annotations = createSuppressWarningsAll(method, method.annotations); TypeDeclaration parent = (TypeDeclaration) type.get(); @@ -1285,7 +1329,29 @@ public class EclipseHandlerUtil { parent.methods = newArray; } - type.add(method, Kind.METHOD); + return type.add(method, Kind.METHOD); + } + + /** + * Adds an inner type (class, interface, enum) to the given type. Cannot inject top-level types. + * + * @param typeNode parent type to inject new type into + * @param type New type (class, interface, etc) to inject. + */ + public static EclipseNode injectType(final EclipseNode typeNode, final TypeDeclaration type) { + type.annotations = createSuppressWarningsAll(type, type.annotations); + TypeDeclaration parent = (TypeDeclaration) typeNode.get(); + + if (parent.memberTypes == null) { + parent.memberTypes = new TypeDeclaration[] { type }; + } else { + TypeDeclaration[] newArray = new TypeDeclaration[parent.memberTypes.length + 1]; + System.arraycopy(parent.memberTypes, 0, newArray, 0, parent.memberTypes.length); + newArray[parent.memberTypes.length] = type; + parent.memberTypes = newArray; + } + + return typeNode.add(type, Kind.TYPE); } private static final char[] ALL = "all".toCharArray(); @@ -1334,7 +1400,11 @@ public class EclipseHandlerUtil { EqualExpression equalExpression = new EqualExpression(varName, nullLiteral, OperatorIds.EQUAL_EQUAL); equalExpression.sourceStart = pS; equalExpression.statementEnd = equalExpression.sourceEnd = pE; setGeneratedBy(equalExpression, source); - IfStatement ifStatement = new IfStatement(equalExpression, throwStatement, 0, 0); + Block throwBlock = new Block(0); + throwBlock.statements = new Statement[] {throwStatement}; + throwBlock.sourceStart = pS; throwBlock.sourceEnd = pE; + setGeneratedBy(throwBlock, source); + IfStatement ifStatement = new IfStatement(equalExpression, throwBlock, 0, 0); setGeneratedBy(ifStatement, source); return ifStatement; } diff --git a/src/core/lombok/eclipse/handlers/HandleBuilder.java b/src/core/lombok/eclipse/handlers/HandleBuilder.java new file mode 100644 index 00000000..70110a9c --- /dev/null +++ b/src/core/lombok/eclipse/handlers/HandleBuilder.java @@ -0,0 +1,376 @@ +/* + * Copyright (C) 2013 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.eclipse.handlers; + +import static lombok.eclipse.Eclipse.*; +import static lombok.core.handlers.HandlerUtil.*; +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.ASTNode; +import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.AllocationExpression; +import org.eclipse.jdt.internal.compiler.ast.Annotation; +import org.eclipse.jdt.internal.compiler.ast.Argument; +import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; +import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration; +import org.eclipse.jdt.internal.compiler.ast.Expression; +import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; +import org.eclipse.jdt.internal.compiler.ast.MessageSend; +import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.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.TypeDeclaration; +import org.eclipse.jdt.internal.compiler.ast.TypeParameter; +import org.eclipse.jdt.internal.compiler.ast.TypeReference; +import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; +import org.eclipse.jdt.internal.compiler.lookup.ClassScope; +import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; +import org.mangosdk.spi.ProviderFor; + +import lombok.AccessLevel; +import lombok.core.AST.Kind; +import lombok.core.AnnotationValues; +import lombok.core.HandlerPriority; +import lombok.core.TransformationsUtil; +import lombok.eclipse.Eclipse; +import lombok.eclipse.EclipseAnnotationHandler; +import lombok.eclipse.EclipseNode; +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) { + long p = (long) ast.sourceStart << 32 | ast.sourceEnd; + + Builder builderInstance = annotation.getInstance(); + String builderMethodName = builderInstance.builderMethodName(); + String buildMethodName = builderInstance.buildMethodName(); + String builderClassName = builderInstance.builderClassName(); + + if (builderMethodName == null) builderMethodName = "builder"; + if (buildMethodName == null) builderMethodName = "build"; + if (builderClassName == null) builderClassName = ""; + + if (!checkName("builderMethodName", builderMethodName, annotationNode)) return; + if (!checkName("buildMethodName", buildMethodName, annotationNode)) return; + if (!builderClassName.isEmpty()) { + if (!checkName("builderClassName", builderClassName, annotationNode)) return; + } + + EclipseNode parent = annotationNode.up(); + + List<TypeReference> typesOfParameters = new ArrayList<TypeReference>(); + List<char[]> namesOfParameters = new ArrayList<char[]>(); + TypeReference returnType; + TypeParameter[] typeParams; + TypeReference[] thrownExceptions; + char[] nameOfStaticBuilderMethod; + EclipseNode tdParent; + + AbstractMethodDeclaration fillParametersFrom = null; + + if (parent.get() instanceof TypeDeclaration) { + tdParent = parent; + TypeDeclaration td = (TypeDeclaration) tdParent.get(); + + List<EclipseNode> fields = new ArrayList<EclipseNode>(); + @SuppressWarnings("deprecation") + boolean valuePresent = (hasAnnotation(lombok.Value.class, parent) || hasAnnotation(lombok.experimental.Value.class, parent)); + for (EclipseNode fieldNode : HandleConstructor.findAllFields(tdParent)) { + FieldDeclaration fd = (FieldDeclaration) fieldNode.get(); + // final fields with an initializer cannot be written to, so they can't be 'builderized'. Unfortunately presence of @Value makes + // non-final fields final, but @Value's handler hasn't done this yet, so we have to do this math ourselves. + // Value will only skip making a field final if it has an explicit @NonFinal annotation, so we check for that. + if (fd.initialization != null && valuePresent && !hasAnnotation(NonFinal.class, fieldNode)) continue; + namesOfParameters.add(fd.name); + typesOfParameters.add(fd.type); + fields.add(fieldNode); + } + + new HandleConstructor().generateConstructor(tdParent, AccessLevel.PACKAGE, fields, null, SkipIfConstructorExists.I_AM_BUILDER, true, Collections.<Annotation>emptyList(), ast); + + returnType = namePlusTypeParamsToTypeReference(td.name, td.typeParameters, p); + typeParams = td.typeParameters; + thrownExceptions = null; + nameOfStaticBuilderMethod = null; + if (builderClassName.isEmpty()) builderClassName = new String(td.name) + "Builder"; + } else if (parent.get() instanceof ConstructorDeclaration) { + ConstructorDeclaration cd = (ConstructorDeclaration) parent.get(); + if (cd.typeParameters != null && cd.typeParameters.length > 0) { + annotationNode.addError("@Builder is not supported on constructors with constructor type parameters."); + return; + } + + tdParent = parent.up(); + TypeDeclaration td = (TypeDeclaration) tdParent.get(); + fillParametersFrom = cd; + returnType = namePlusTypeParamsToTypeReference(td.name, td.typeParameters, p); + typeParams = td.typeParameters; + thrownExceptions = cd.thrownExceptions; + nameOfStaticBuilderMethod = null; + if (builderClassName.isEmpty()) builderClassName = new String(cd.selector) + "Builder"; + } else if (parent.get() instanceof MethodDeclaration) { + MethodDeclaration md = (MethodDeclaration) parent.get(); + tdParent = parent.up(); + if (!md.isStatic()) { + annotationNode.addError("@Builder is only supported on types, constructors, and static methods."); + return; + } + fillParametersFrom = md; + returnType = copyType(md.returnType, ast); + typeParams = md.typeParameters; + thrownExceptions = md.thrownExceptions; + nameOfStaticBuilderMethod = md.selector; + if (builderClassName.isEmpty()) { + char[] token; + if (md.returnType instanceof QualifiedTypeReference) { + char[][] tokens = ((QualifiedTypeReference) md.returnType).tokens; + token = tokens[tokens.length - 1]; + } else if (md.returnType instanceof SingleTypeReference) { + token = ((SingleTypeReference) md.returnType).token; + if (!(md.returnType instanceof ParameterizedSingleTypeReference) && typeParams != null) { + for (TypeParameter tp : typeParams) { + if (Arrays.equals(tp.name, token)) { + annotationNode.addError("@Builder requires specifying 'builderClassName' if used on methods with a type parameter as return type."); + return; + } + } + } + } else { + annotationNode.addError("Unexpected kind of return type on annotated method. Specify 'builderClassName' to solve this problem."); + return; + } + + if (Character.isLowerCase(token[0])) { + char[] newToken = new char[token.length]; + System.arraycopy(token, 1, newToken, 1, token.length - 1); + newToken[0] = Character.toTitleCase(token[0]); + token = newToken; + } + + builderClassName = new String(token) + "Builder"; + } + } else { + annotationNode.addError("@Builder is only supported on types, constructors, and static methods."); + return; + } + + if (fillParametersFrom != null) { + if (fillParametersFrom.arguments != null) for (Argument a : fillParametersFrom.arguments) { + namesOfParameters.add(a.name); + typesOfParameters.add(a.type); + } + } + + EclipseNode builderType = findInnerClass(tdParent, builderClassName); + if (builderType == null) { + builderType = makeBuilderClass(tdParent, builderClassName, typeParams, ast); + } else { + sanityCheckForMethodGeneratingAnnotationsOnBuilderClass(builderType, annotationNode); + } + List<EclipseNode> fieldNodes = addFieldsToBuilder(builderType, namesOfParameters, typesOfParameters, ast); + List<AbstractMethodDeclaration> newMethods = new ArrayList<AbstractMethodDeclaration>(); + for (EclipseNode fieldNode : fieldNodes) { + MethodDeclaration newMethod = makeSetterMethodForBuilder(builderType, fieldNode, ast, builderInstance.fluent(), builderInstance.chain()); + if (newMethod != null) newMethods.add(newMethod); + } + + if (constructorExists(builderType) == MemberExistsResult.NOT_EXISTS) { + ConstructorDeclaration cd = HandleConstructor.createConstructor(AccessLevel.PACKAGE, builderType, Collections.<EclipseNode>emptyList(), true, ast, Collections.<Annotation>emptyList()); + if (cd != null) injectMethod(builderType, cd); + } + + for (AbstractMethodDeclaration newMethod : newMethods) injectMethod(builderType, newMethod); + if (methodExists(buildMethodName, builderType, -1) == MemberExistsResult.NOT_EXISTS) { + MethodDeclaration md = generateBuildMethod(buildMethodName, nameOfStaticBuilderMethod, returnType, namesOfParameters, builderType, ast, thrownExceptions); + if (md != null) injectMethod(builderType, md); + } + + if (methodExists("toString", builderType, 0) == MemberExistsResult.NOT_EXISTS) { + MethodDeclaration md = HandleToString.createToString(builderType, fieldNodes, true, false, ast, FieldAccess.ALWAYS_FIELD); + if (md != null) injectMethod(builderType, md); + } + + if (methodExists(builderMethodName, tdParent, -1) == MemberExistsResult.NOT_EXISTS) { + MethodDeclaration md = generateBuilderMethod(builderMethodName, builderClassName, tdParent, typeParams, ast); + if (md != null) injectMethod(tdParent, md); + } + } + + private 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; + + 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), ((TypeDeclaration) type.get()).scope); + return out; + } + + private 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; + + MethodDeclaration out = new MethodDeclaration( + ((CompilationUnitDeclaration) type.top().get()).compilationResult); + + out.modifiers = ClassFileConstants.AccPublic; + TypeDeclaration typeDecl = (TypeDeclaration) type.get(); + out.selector = name.toCharArray(); + out.thrownExceptions = copyTypes(thrownExceptions, source); + 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); + } else { + MessageSend invoke = new MessageSend(); + invoke.selector = staticName; + invoke.receiver = new SingleNameReference(type.up().getName().toCharArray(), p); + TypeParameter[] tps = ((TypeDeclaration) type.get()).typeParameters; + if (tps != null) { + TypeReference[] trs = new TypeReference[tps.length]; + for (int i = 0; i < trs.length; i++) { + trs[i] = new SingleTypeReference(tps[i].name, p); + } + invoke.typeArguments = trs; + } + invoke.arguments = assigns.isEmpty() ? null : assigns.toArray(new Expression[assigns.size()]); + if (returnType instanceof SingleTypeReference && Arrays.equals(TypeBinding.VOID.simpleName, ((SingleTypeReference) returnType).token)) { + statement = invoke; + } else { + statement = new ReturnStatement(invoke, (int)(p >> 32), (int)p); + } + } + + out.statements = new Statement[] { statement }; + + out.traverse(new SetGeneratedByVisitor(source), typeDecl.scope); + return out; + } + + private List<EclipseNode> addFieldsToBuilder(EclipseNode builderType, List<char[]> namesOfParameters, List<TypeReference> typesOfParameters, ASTNode source) { + int len = namesOfParameters.size(); + TypeDeclaration td = (TypeDeclaration) builderType.get(); + FieldDeclaration[] existing = td.fields; + if (existing == null) existing = new FieldDeclaration[0]; + + List<EclipseNode> out = new ArrayList<EclipseNode>(); + + top: + for (int i = len - 1; i >= 0; i--) { + char[] name = namesOfParameters.get(i); + for (FieldDeclaration exists : existing) { + if (Arrays.equals(exists.name, name)) { + out.add(builderType.getNodeFor(exists)); + continue top; + } + } + TypeReference fieldReference = copyType(typesOfParameters.get(i), source); + FieldDeclaration newField = new FieldDeclaration(name, 0, 0); + newField.bits |= Eclipse.ECLIPSE_DO_NOT_TOUCH_FLAG; + newField.modifiers = ClassFileConstants.AccPrivate; + newField.type = fieldReference; + out.add(injectField(builderType, newField)); + } + + Collections.reverse(out); + + return out; + } + + private static final AbstractMethodDeclaration[] EMPTY = {}; + + private MethodDeclaration makeSetterMethodForBuilder(EclipseNode builderType, EclipseNode fieldNode, ASTNode source, boolean fluent, boolean chain) { + TypeDeclaration td = (TypeDeclaration) builderType.get(); + AbstractMethodDeclaration[] existing = td.methods; + if (existing == null) existing = EMPTY; + int len = existing.length; + FieldDeclaration fd = (FieldDeclaration) fieldNode.get(); + char[] name = fd.name; + + for (int i = 0; i < len; i++) { + if (!(existing[i] instanceof MethodDeclaration)) continue; + char[] existingName = existing[i].selector; + if (Arrays.equals(name, existingName)) return null; + } + + boolean isBoolean = isBoolean(fd.type); + String setterName = fluent ? fieldNode.getName() : TransformationsUtil.toSetterName(null, fieldNode.getName(), isBoolean); + + return HandleSetter.createSetter(td, fieldNode, setterName, chain, ClassFileConstants.AccPublic, + source, Collections.<Annotation>emptyList(), Collections.<Annotation>emptyList()); + } + + private EclipseNode findInnerClass(EclipseNode parent, String name) { + char[] c = name.toCharArray(); + for (EclipseNode child : parent.down()) { + if (child.getKind() != Kind.TYPE) continue; + TypeDeclaration td = (TypeDeclaration) child.get(); + if (Arrays.equals(td.name, c)) return child; + } + return null; + } + + private EclipseNode makeBuilderClass(EclipseNode tdParent, String builderClassName, TypeParameter[] typeParams, ASTNode source) { + TypeDeclaration parent = (TypeDeclaration) tdParent.get(); + TypeDeclaration builder = new TypeDeclaration(parent.compilationResult); + builder.bits |= Eclipse.ECLIPSE_DO_NOT_TOUCH_FLAG; + builder.modifiers |= ClassFileConstants.AccPublic | ClassFileConstants.AccStatic; + builder.typeParameters = copyTypeParams(typeParams, source); + builder.name = builderClassName.toCharArray(); + builder.traverse(new SetGeneratedByVisitor(source), (ClassScope) null); + return injectType(tdParent, builder); + } +} diff --git a/src/core/lombok/eclipse/handlers/HandleConstructor.java b/src/core/lombok/eclipse/handlers/HandleConstructor.java index 8ccad77f..1ae680d9 100644 --- a/src/core/lombok/eclipse/handlers/HandleConstructor.java +++ b/src/core/lombok/eclipse/handlers/HandleConstructor.java @@ -39,6 +39,7 @@ import lombok.core.AnnotationValues; import lombok.core.TransformationsUtil; 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; @@ -53,18 +54,14 @@ 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.MethodDeclaration; -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.SingleMemberAnnotation; 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.StringLiteral; import org.eclipse.jdt.internal.compiler.ast.ThisReference; import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; -import org.eclipse.jdt.internal.compiler.ast.TypeParameter; -import org.eclipse.jdt.internal.compiler.ast.TypeReference; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; import org.mangosdk.spi.ProviderFor; @@ -82,7 +79,7 @@ public class HandleConstructor { List<Annotation> onConstructor = unboxAndRemoveAnnotationParameter(ast, "onConstructor", "@NoArgsConstructor(onConstructor=", annotationNode); - new HandleConstructor().generateConstructor(typeNode, level, fields, staticName, false, false, onConstructor, ast); + new HandleConstructor().generateConstructor(typeNode, level, fields, staticName, SkipIfConstructorExists.NO, false, onConstructor, ast); } } @@ -100,7 +97,7 @@ public class HandleConstructor { List<Annotation> onConstructor = unboxAndRemoveAnnotationParameter(ast, "onConstructor", "@RequiredArgsConstructor(onConstructor=", annotationNode); - new HandleConstructor().generateConstructor(typeNode, level, findRequiredFields(typeNode), staticName, false, suppressConstructorProperties, onConstructor, ast); + new HandleConstructor().generateConstructor(typeNode, level, findRequiredFields(typeNode), staticName, SkipIfConstructorExists.NO, suppressConstructorProperties, onConstructor, ast); } } @@ -117,7 +114,7 @@ public class HandleConstructor { return fields; } - private static List<EclipseNode> findAllFields(EclipseNode typeNode) { + static List<EclipseNode> findAllFields(EclipseNode typeNode) { List<EclipseNode> fields = new ArrayList<EclipseNode>(); for (EclipseNode child : typeNode.down()) { if (child.getKind() != Kind.FIELD) continue; @@ -146,7 +143,7 @@ public class HandleConstructor { List<Annotation> onConstructor = unboxAndRemoveAnnotationParameter(ast, "onConstructor", "@AllArgsConstructor(onConstructor=", annotationNode); - new HandleConstructor().generateConstructor(typeNode, level, findAllFields(typeNode), staticName, false, suppressConstructorProperties, onConstructor, ast); + new HandleConstructor().generateConstructor(typeNode, level, findAllFields(typeNode), staticName, SkipIfConstructorExists.NO, suppressConstructorProperties, onConstructor, ast); } } @@ -164,25 +161,34 @@ public class HandleConstructor { return true; } - public void generateRequiredArgsConstructor(EclipseNode typeNode, AccessLevel level, String staticName, boolean skipIfConstructorExists, List<Annotation> onConstructor, ASTNode source) { + public void generateRequiredArgsConstructor(EclipseNode typeNode, AccessLevel level, String staticName, SkipIfConstructorExists skipIfConstructorExists, List<Annotation> onConstructor, ASTNode source) { generateConstructor(typeNode, level, findRequiredFields(typeNode), staticName, skipIfConstructorExists, false, onConstructor, source); } - public void generateAllArgsConstructor(EclipseNode typeNode, AccessLevel level, String staticName, boolean skipIfConstructorExists, List<Annotation> onConstructor, ASTNode source) { + public void generateAllArgsConstructor(EclipseNode typeNode, AccessLevel level, String staticName, SkipIfConstructorExists skipIfConstructorExists, List<Annotation> onConstructor, ASTNode source) { generateConstructor(typeNode, level, findAllFields(typeNode), staticName, skipIfConstructorExists, false, onConstructor, source); } - public void generateConstructor(EclipseNode typeNode, AccessLevel level, List<EclipseNode> fields, String staticName, boolean skipIfConstructorExists, boolean suppressConstructorProperties, List<Annotation> onConstructor, ASTNode source) { + public enum SkipIfConstructorExists { + YES, NO, I_AM_BUILDER; + } + + public void generateConstructor(EclipseNode typeNode, AccessLevel level, List<EclipseNode> fields, String staticName, SkipIfConstructorExists skipIfConstructorExists, boolean suppressConstructorProperties, List<Annotation> onConstructor, ASTNode source) { boolean staticConstrRequired = staticName != null && !staticName.equals(""); - if (skipIfConstructorExists && constructorExists(typeNode) != MemberExistsResult.NOT_EXISTS) return; - if (skipIfConstructorExists) { + if (skipIfConstructorExists != SkipIfConstructorExists.NO && constructorExists(typeNode) != MemberExistsResult.NOT_EXISTS) return; + if (skipIfConstructorExists != SkipIfConstructorExists.NO) { for (EclipseNode child : typeNode.down()) { if (child.getKind() == Kind.ANNOTATION) { - if (annotationTypeMatches(NoArgsConstructor.class, child) || + boolean skipGeneration = (annotationTypeMatches(NoArgsConstructor.class, child) || annotationTypeMatches(AllArgsConstructor.class, child) || - annotationTypeMatches(RequiredArgsConstructor.class, child)) { - + annotationTypeMatches(RequiredArgsConstructor.class, child)); + + if (!skipGeneration && skipIfConstructorExists == SkipIfConstructorExists.YES) { + skipGeneration = annotationTypeMatches(Builder.class, child); + } + + if (skipGeneration) { if (staticConstrRequired) { // @Data has asked us to generate a constructor, but we're going to skip this instruction, as an explicit 'make a constructor' annotation // will take care of it. However, @Data also wants a specific static name; this will be ignored; the appropriate way to do this is to use @@ -235,7 +241,7 @@ public class HandleConstructor { return new Annotation[] { ann }; } - private ConstructorDeclaration createConstructor( + static ConstructorDeclaration createConstructor( AccessLevel level, EclipseNode type, Collection<EclipseNode> fields, boolean suppressConstructorProperties, ASTNode source, List<Annotation> onConstructor) { @@ -307,7 +313,7 @@ public class HandleConstructor { return constructor; } - private boolean isLocalType(EclipseNode type) { + private static boolean isLocalType(EclipseNode type) { Kind kind = type.up().getKind(); if (kind == Kind.COMPILATION_UNIT) return false; if (kind == Kind.TYPE) return isLocalType(type.up()); @@ -321,18 +327,9 @@ public class HandleConstructor { MethodDeclaration constructor = new MethodDeclaration( ((CompilationUnitDeclaration) type.top().get()).compilationResult); - constructor.modifiers = toEclipseModifier(level) | Modifier.STATIC; + constructor.modifiers = toEclipseModifier(level) | ClassFileConstants.AccStatic; TypeDeclaration typeDecl = (TypeDeclaration) type.get(); - if (typeDecl.typeParameters != null && typeDecl.typeParameters.length > 0) { - TypeReference[] refs = new TypeReference[typeDecl.typeParameters.length]; - int idx = 0; - for (TypeParameter param : typeDecl.typeParameters) { - TypeReference typeRef = new SingleTypeReference(param.name, (long)param.sourceStart << 32 | param.sourceEnd); - setGeneratedBy(typeRef, source); - refs[idx++] = typeRef; - } - constructor.returnType = new ParameterizedSingleTypeReference(typeDecl.name, refs, 0, p); - } else constructor.returnType = new SingleTypeReference(((TypeDeclaration)type.get()).name, p); + constructor.returnType = EclipseHandlerUtil.namePlusTypeParamsToTypeReference(typeDecl.name, typeDecl.typeParameters, p); constructor.annotations = null; constructor.selector = name.toCharArray(); constructor.thrownExceptions = null; diff --git a/src/core/lombok/eclipse/handlers/HandleData.java b/src/core/lombok/eclipse/handlers/HandleData.java index 3a43bd3f..aa309489 100644 --- a/src/core/lombok/eclipse/handlers/HandleData.java +++ b/src/core/lombok/eclipse/handlers/HandleData.java @@ -28,6 +28,7 @@ import lombok.Data; import lombok.core.AnnotationValues; import lombok.eclipse.EclipseAnnotationHandler; import lombok.eclipse.EclipseNode; +import lombok.eclipse.handlers.HandleConstructor.SkipIfConstructorExists; import org.eclipse.jdt.internal.compiler.ast.Annotation; import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; @@ -64,6 +65,6 @@ public class HandleData extends EclipseAnnotationHandler<Data> { new HandleSetter().generateSetterForType(typeNode, annotationNode, AccessLevel.PUBLIC, true); new HandleEqualsAndHashCode().generateEqualsAndHashCodeForType(typeNode, annotationNode); new HandleToString().generateToStringForType(typeNode, annotationNode); - new HandleConstructor().generateRequiredArgsConstructor(typeNode, AccessLevel.PUBLIC, ann.staticConstructor(), true, Collections.<Annotation>emptyList(), ast); + new HandleConstructor().generateRequiredArgsConstructor(typeNode, AccessLevel.PUBLIC, ann.staticConstructor(), SkipIfConstructorExists.YES, Collections.<Annotation>emptyList(), ast); } } diff --git a/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java b/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java index 0c82b74c..6990e609 100644 --- a/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java +++ b/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2012 The Project Lombok Authors. + * Copyright (C) 2009-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 @@ -204,18 +204,27 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler<EqualsAndH } boolean isFinal = (typeDecl.modifiers & ClassFileConstants.AccFinal) != 0; - boolean needsCanEqual = !isDirectDescendantOfObject || !isFinal; - java.util.List<MemberExistsResult> existsResults = new ArrayList<MemberExistsResult>(); - existsResults.add(methodExists("equals", typeNode, 1)); - existsResults.add(methodExists("hashCode", typeNode, 0)); - existsResults.add(methodExists("canEqual", typeNode, 1)); - switch (Collections.max(existsResults)) { + boolean needsCanEqual = !isFinal || !isDirectDescendantOfObject; + MemberExistsResult equalsExists = methodExists("equals", typeNode, 1); + MemberExistsResult hashCodeExists = methodExists("hashCode", typeNode, 0); + MemberExistsResult canEqualExists = methodExists("canEqual", typeNode, 1); + switch (Collections.max(Arrays.asList(equalsExists, hashCodeExists, canEqualExists))) { case EXISTS_BY_LOMBOK: return; case EXISTS_BY_USER: if (whineIfExists) { String msg = String.format("Not generating equals%s: A method with one of those names already exists. (Either all or none of these methods will be generated).", needsCanEqual ? ", hashCode and canEquals" : " and hashCode"); errorNode.addWarning(msg); + } else if (equalsExists == MemberExistsResult.NOT_EXISTS || hashCodeExists == MemberExistsResult.NOT_EXISTS) { + // This means equals OR hashCode exists and not both (or neither, but canEqual is there). + // Even though we should suppress the message about not generating these, this is such a weird and surprising situation we should ALWAYS generate a warning. + // The user code couldn't possibly (barring really weird subclassing shenanigans) be in a shippable state anyway; the implementations of these 3 methods are + // all inter-related and should be written by the same entity. + String msg = String.format("Not generating %s: One of equals, hashCode, and canEqual exists. " + + "You should either write all of these are none of these (in the latter case, lombok generates them).", + equalsExists == MemberExistsResult.NOT_EXISTS && hashCodeExists == MemberExistsResult.NOT_EXISTS ? "equals and hashCode" : + equalsExists == MemberExistsResult.NOT_EXISTS ? "equals" : "hashCode"); + errorNode.addWarning(msg); } return; case NOT_EXISTS: diff --git a/src/core/lombok/eclipse/handlers/HandleFieldDefaults.java b/src/core/lombok/eclipse/handlers/HandleFieldDefaults.java index 0d21fc08..d6d839cc 100644 --- a/src/core/lombok/eclipse/handlers/HandleFieldDefaults.java +++ b/src/core/lombok/eclipse/handlers/HandleFieldDefaults.java @@ -43,7 +43,7 @@ import org.mangosdk.spi.ProviderFor; * Handles the {@code lombok.FieldDefaults} annotation for eclipse. */ @ProviderFor(EclipseAnnotationHandler.class) -@HandlerPriority(-512) //-2^9; to ensure @Setter and such pick up on messing with the fields' 'final' state, run earlier. +@HandlerPriority(-2048) //-2^11; to ensure @Value picks up on messing with the fields' 'final' state, run earlier. public class HandleFieldDefaults extends EclipseAnnotationHandler<FieldDefaults> { public boolean generateFieldDefaultsForType(EclipseNode typeNode, EclipseNode pos, AccessLevel level, boolean makeFinal, boolean checkForTypeLevelFieldDefaults) { if (checkForTypeLevelFieldDefaults) { @@ -112,6 +112,14 @@ public class HandleFieldDefaults extends EclipseAnnotationHandler<FieldDefaults> return; } + if (level == AccessLevel.PACKAGE) { + annotationNode.addError("Setting 'level' to PACKAGE does nothing. To force fields as package private, use the @PackagePrivate annotation on the field."); + } + + if (!makeFinal && annotation.isExplicit("makeFinal")) { + annotationNode.addError("Setting 'makeFinal' to false does nothing. To force fields to be non-final, use the @NonFinal annotation on the field."); + } + if (node == null) return; generateFieldDefaultsForType(node, annotationNode, level, makeFinal, false); diff --git a/src/core/lombok/eclipse/handlers/HandleGetter.java b/src/core/lombok/eclipse/handlers/HandleGetter.java index 7f788c5d..787f6f6c 100644 --- a/src/core/lombok/eclipse/handlers/HandleGetter.java +++ b/src/core/lombok/eclipse/handlers/HandleGetter.java @@ -49,6 +49,8 @@ import org.eclipse.jdt.internal.compiler.ast.ArrayTypeReference; 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.CastExpression; +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; @@ -67,6 +69,7 @@ import org.eclipse.jdt.internal.compiler.ast.SynchronizedStatement; import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; import org.eclipse.jdt.internal.compiler.ast.TypeReference; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; +import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; import org.mangosdk.spi.ProviderFor; /** @@ -184,7 +187,7 @@ public class HandleGetter extends EclipseAnnotationHandler<Getter> { } TypeReference fieldType = copyType(field.type, source); - boolean isBoolean = nameEquals(fieldType.getTypeName(), "boolean") && fieldType.dimensions() == 0; + boolean isBoolean = isBoolean(fieldType); String getterName = toGetterName(fieldNode, isBoolean); if (getterName == null) { @@ -286,7 +289,6 @@ public class HandleGetter extends EclipseAnnotationHandler<Getter> { } private static final char[][] AR = fromQualifiedName("java.util.concurrent.atomic.AtomicReference"); - private static final TypeReference[][] AR_PARAMS = new TypeReference[5][]; private static final java.util.Map<String, char[][]> TYPE_MAP; static { @@ -305,41 +307,54 @@ public class HandleGetter extends EclipseAnnotationHandler<Getter> { private static char[] valueName = "value".toCharArray(); private static char[] actualValueName = "actualValue".toCharArray(); + private static final int PARENTHESIZED = (1 << ASTNode.ParenthesizedSHIFT) & ASTNode.ParenthesizedMASK; + private Statement[] createLazyGetterBody(ASTNode source, EclipseNode fieldNode) { /* - java.util.concurrent.atomic.AtomicReference<ValueType> value = this.fieldName.get(); + java.lang.Object value = this.fieldName.get(); if (value == null) { synchronized (this.fieldName) { value = this.fieldName.get(); if (value == null) { - final ValueType actualValue = new ValueType(); - value = new java.util.concurrent.atomic.AtomicReference<ValueType>(actualValue); + final RawValueType actualValue = INITIALIZER_EXPRESSION; + [IF PRIMITIVE] + value = actualValue; + [ELSE] + value = actualValue == null ? this.fieldName : actualValue; + [END IF] this.fieldName.set(value); } } } - return value.get(); + [IF PRIMITIVE] + return (BoxedValueType) value; + [ELSE] + return (BoxedValueType) (value == this.fieldName ? null : value); + [END IF] */ FieldDeclaration field = (FieldDeclaration) fieldNode.get(); int pS = source.sourceStart, pE = source.sourceEnd; long p = (long)pS << 32 | pE; - TypeReference componentType = copyType(field.type, source); + TypeReference rawComponentType = copyType(field.type, source); + TypeReference boxedComponentType = null; + boolean isPrimitive = false; if (field.type instanceof SingleTypeReference && !(field.type instanceof ArrayTypeReference)) { char[][] newType = TYPE_MAP.get(new String(((SingleTypeReference)field.type).token)); if (newType != null) { - componentType = new QualifiedTypeReference(newType, poss(source, 3)); + boxedComponentType = new QualifiedTypeReference(newType, poss(source, 3)); + isPrimitive = true; } } + if (boxedComponentType == null) boxedComponentType = copyType(field.type, source); + boxedComponentType.sourceStart = pS; boxedComponentType.sourceEnd = boxedComponentType.statementEnd = pE; Statement[] statements = new Statement[3]; - /* java.util.concurrent.atomic.AtomicReference<ValueType> value = this.fieldName.get(); */ { + /* java.lang.Object value = this.fieldName.get(); */ { LocalDeclaration valueDecl = new LocalDeclaration(valueName, pS, pE); - TypeReference[][] typeParams = AR_PARAMS.clone(); - typeParams[4] = new TypeReference[] {copyType(componentType, source)}; - valueDecl.type = new ParameterizedQualifiedTypeReference(AR, typeParams, 0, poss(source, 5)); + valueDecl.type = new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, poss(source, 3)); valueDecl.type.sourceStart = pS; valueDecl.type.sourceEnd = valueDecl.type.statementEnd = pE; MessageSend getter = new MessageSend(); @@ -356,8 +371,12 @@ public class HandleGetter extends EclipseAnnotationHandler<Getter> { synchronized (this.fieldName) { value = this.fieldName.get(); if (value == null) { - final ValueType actualValue = new ValueType(); - value = new java.util.concurrent.atomic.AtomicReference<ValueType>(actualValue); + final ValueType actualValue = INITIALIZER_EXPRESSION; + [IF PRIMITIVE] + value = actualValue; + [ELSE] + value = actualValue == null ? this.fieldName : actualValue; + [END IF] this.fieldName.set(value); } } @@ -383,28 +402,37 @@ public class HandleGetter extends EclipseAnnotationHandler<Getter> { EqualExpression innerCond = new EqualExpression( new SingleNameReference(valueName, p), new NullLiteral(pS, pE), BinaryExpression.EQUAL_EQUAL); + innerCond.sourceStart = pS; innerCond.sourceEnd = innerCond.statementEnd = pE; Block innerThen = new Block(0); innerThen.statements = new Statement[3]; - /* final ValueType actualValue = new ValueType(); */ { + /* final ValueType actualValue = INITIALIZER_EXPRESSION */ { LocalDeclaration actualValueDecl = new LocalDeclaration(actualValueName, pS, pE); - actualValueDecl.type = copyType(field.type, source); + actualValueDecl.type = rawComponentType; actualValueDecl.type.sourceStart = pS; actualValueDecl.type.sourceEnd = actualValueDecl.type.statementEnd = pE; actualValueDecl.initialization = field.initialization; actualValueDecl.modifiers = ClassFileConstants.AccFinal; innerThen.statements[0] = actualValueDecl; } - /* value = new java.util.concurrent.atomic.AtomicReference<ValueType>(actualValue); */ { - AllocationExpression create = new AllocationExpression(); - create.sourceStart = pS; create.sourceEnd = create.statementEnd = pE; - TypeReference[][] typeParams = AR_PARAMS.clone(); - typeParams[4] = new TypeReference[] {copyType(componentType, source)}; - create.type = new ParameterizedQualifiedTypeReference(AR, typeParams, 0, poss(source, 5)); - create.type.sourceStart = pS; create.type.sourceEnd = create.type.statementEnd = pE; - create.arguments = new Expression[] {new SingleNameReference(actualValueName, p)}; - Assignment innerAssign = new Assignment(new SingleNameReference(valueName, p), create, pE); - innerAssign.sourceStart = pS; innerAssign.statementEnd = innerAssign.sourceEnd = pE; - - innerThen.statements[1] = innerAssign; + /* [IF PRIMITIVE] value = actualValue; */ { + if (isPrimitive) { + Assignment innerAssign = new Assignment(new SingleNameReference(valueName, p), new SingleNameReference(actualValueName, p), pE); + innerAssign.sourceStart = pS; innerAssign.statementEnd = innerAssign.sourceEnd = pE; + innerThen.statements[1] = innerAssign; + } + } + /* [ELSE] value = actualValue == null ? this.fieldName : actualValue; */ { + if (!isPrimitive) { + EqualExpression avIsNull = new EqualExpression( + new SingleNameReference(actualValueName, p), new NullLiteral(pS, pE), + BinaryExpression.EQUAL_EQUAL); + avIsNull.sourceStart = pS; avIsNull.sourceEnd = avIsNull.statementEnd = pE; + Expression fieldRef = createFieldAccessor(fieldNode, FieldAccess.ALWAYS_FIELD, source); + ConditionalExpression ternary = new ConditionalExpression(avIsNull, fieldRef, new SingleNameReference(actualValueName, p)); + ternary.sourceStart = pS; ternary.sourceEnd = ternary.statementEnd = pE; + Assignment innerAssign = new Assignment(new SingleNameReference(valueName, p), ternary, pE); + innerAssign.sourceStart = pS; innerAssign.statementEnd = innerAssign.sourceEnd = pE; + innerThen.statements[1] = innerAssign; + } } /* this.fieldName.set(value); */ { @@ -428,26 +456,34 @@ public class HandleGetter extends EclipseAnnotationHandler<Getter> { statements[1] = ifStatement; } - /* return value.get(); */ { - MessageSend getter = new MessageSend(); - getter.sourceStart = pS; getter.sourceEnd = getter.statementEnd = pE; - getter.selector = new char[] {'g', 'e', 't'}; - getter.receiver = new SingleNameReference(valueName, p); - - statements[2] = new ReturnStatement(getter, pS, pE); + /* [IF PRIMITIVE] return (BoxedValueType)value; */ { + if (isPrimitive) { + CastExpression cast = makeCastExpression(new SingleNameReference(valueName, p), boxedComponentType, source); + statements[2] = new ReturnStatement(cast, pS, pE); + } + } + /* [ELSE] return (BoxedValueType)(value == this.fieldName ? null : value); */ { + if (!isPrimitive) { + EqualExpression vIsThisFieldName = new EqualExpression( + new SingleNameReference(valueName, p), createFieldAccessor(fieldNode, FieldAccess.ALWAYS_FIELD, source), + BinaryExpression.EQUAL_EQUAL); + vIsThisFieldName.sourceStart = pS; vIsThisFieldName.sourceEnd = vIsThisFieldName.statementEnd = pE; + ConditionalExpression ternary = new ConditionalExpression(vIsThisFieldName, new NullLiteral(pS, pE), new SingleNameReference(valueName, p)); + ternary.sourceStart = pS; ternary.sourceEnd = ternary.statementEnd = pE; + ternary.bits |= PARENTHESIZED; + CastExpression cast = makeCastExpression(ternary, boxedComponentType, source); + statements[2] = new ReturnStatement(cast, pS, pE); + } } - // update the field type and init last - /* private final java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<ValueType> fieldName = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<ValueType>>(); */ { - - LocalDeclaration first = (LocalDeclaration) statements[0]; - TypeReference innerType = copyType(first.type, source); - - TypeReference[][] typeParams = AR_PARAMS.clone(); - typeParams[4] = new TypeReference[] {copyType(innerType, source)}; + /* private final java.util.concurrent.atomic.AtomicReference<java.lang.Object> fieldName = new java.util.concurrent.atomic.AtomicReference<java.lang.Object>(); */ { + TypeReference innerType = new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, poss(source, 3)); + TypeReference[][] typeParams = new TypeReference[5][]; + typeParams[4] = new TypeReference[] {innerType}; TypeReference type = new ParameterizedQualifiedTypeReference(AR, typeParams, 0, poss(source, 5)); + // Some magic here type.sourceStart = -1; type.sourceEnd = -2; diff --git a/src/core/lombok/eclipse/handlers/HandleLog.java b/src/core/lombok/eclipse/handlers/HandleLog.java index bffe2d62..2e7b4475 100644 --- a/src/core/lombok/eclipse/handlers/HandleLog.java +++ b/src/core/lombok/eclipse/handlers/HandleLog.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2012 The Project Lombok Authors. + * 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 @@ -178,6 +178,16 @@ public class HandleLog { } /** + * Handles the {@link lombok.extern.log4j.Log4j2} annotation for Eclipse. + */ + @ProviderFor(EclipseAnnotationHandler.class) + public static class HandleLog4j2Log extends EclipseAnnotationHandler<lombok.extern.log4j.Log4j2> { + @Override public void handle(AnnotationValues<lombok.extern.log4j.Log4j2> annotation, Annotation source, EclipseNode annotationNode) { + processAnnotation(LoggingFramework.LOG4J2, annotation, source, annotationNode); + } + } + + /** * Handles the {@link lombok.extern.slf4j.Slf4j} annotation for Eclipse. */ @ProviderFor(EclipseAnnotationHandler.class) @@ -224,6 +234,9 @@ public class HandleLog { // private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(TargetType.class); LOG4J("org.apache.log4j.Logger", "org.apache.log4j.Logger", "getLogger", "@Log4j"), + // private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(TargetType.class); + LOG4J2("org.apache.logging.log4j.Logger", "org.apache.logging.log4j.LogManager", "getLogger", "@Log4j2"), + // private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TargetType.class); SLF4J("org.slf4j.Logger", "org.slf4j.LoggerFactory", "getLogger", "@Slf4j"), diff --git a/src/core/lombok/eclipse/handlers/HandleSetter.java b/src/core/lombok/eclipse/handlers/HandleSetter.java index 9b46b704..3bfcc51c 100644 --- a/src/core/lombok/eclipse/handlers/HandleSetter.java +++ b/src/core/lombok/eclipse/handlers/HandleSetter.java @@ -159,7 +159,7 @@ public class HandleSetter extends EclipseAnnotationHandler<Setter> { FieldDeclaration field = (FieldDeclaration) fieldNode.get(); TypeReference fieldType = copyType(field.type, source); - boolean isBoolean = nameEquals(fieldType.getTypeName(), "boolean") && fieldType.dimensions() == 0; + boolean isBoolean = isBoolean(fieldType); String setterName = toSetterName(fieldNode, isBoolean); boolean shouldReturnThis = shouldReturnThis(fieldNode); @@ -192,7 +192,7 @@ public class HandleSetter extends EclipseAnnotationHandler<Setter> { injectMethod(fieldNode.up(), method); } - private MethodDeclaration createSetter(TypeDeclaration parent, EclipseNode fieldNode, String name, boolean shouldReturnThis, int modifier, ASTNode source, List<Annotation> onMethod, List<Annotation> onParam) { + static MethodDeclaration createSetter(TypeDeclaration parent, EclipseNode fieldNode, String name, boolean shouldReturnThis, int modifier, ASTNode source, List<Annotation> onMethod, List<Annotation> onParam) { FieldDeclaration field = (FieldDeclaration) fieldNode.get(); int pS = source.sourceStart, pE = source.sourceEnd; long p = (long)pS << 32 | pE; diff --git a/src/core/lombok/eclipse/handlers/HandleSneakyThrows.java b/src/core/lombok/eclipse/handlers/HandleSneakyThrows.java index b7c8a5d8..aa78ca3b 100644 --- a/src/core/lombok/eclipse/handlers/HandleSneakyThrows.java +++ b/src/core/lombok/eclipse/handlers/HandleSneakyThrows.java @@ -40,6 +40,8 @@ import org.eclipse.jdt.internal.compiler.ast.Annotation; import org.eclipse.jdt.internal.compiler.ast.Argument; import org.eclipse.jdt.internal.compiler.ast.ArrayInitializer; import org.eclipse.jdt.internal.compiler.ast.Block; +import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration; +import org.eclipse.jdt.internal.compiler.ast.ExplicitConstructorCall; import org.eclipse.jdt.internal.compiler.ast.Expression; import org.eclipse.jdt.internal.compiler.ast.MemberValuePair; import org.eclipse.jdt.internal.compiler.ast.MessageSend; @@ -147,7 +149,21 @@ public class HandleSneakyThrows extends EclipseAnnotationHandler<SneakyThrows> { return; } - if (method.statements == null) return; + if (method.statements == null || method.statements.length == 0) { + boolean hasConstructorCall = false; + if (method instanceof ConstructorDeclaration) { + ExplicitConstructorCall constructorCall = ((ConstructorDeclaration) method).constructorCall; + hasConstructorCall = constructorCall != null && !constructorCall.isImplicitSuper() && !constructorCall.isImplicitThis(); + } + + if (hasConstructorCall) { + annotation.addWarning("Calls to sibling / super constructors are always excluded from @SneakyThrows; @SneakyThrows has been ignored because there is no other code in this constructor."); + } else { + annotation.addWarning("This method or constructor is empty; @SneakyThrows has been ignored."); + } + + return; + } Statement[] contents = method.statements; @@ -160,9 +176,9 @@ public class HandleSneakyThrows extends EclipseAnnotationHandler<SneakyThrows> { } private Statement buildTryCatchBlock(Statement[] contents, DeclaredException exception, ASTNode source, AbstractMethodDeclaration method) { - int methodStart = method.bodyStart; - int methodEnd = method.bodyEnd; - long methodPosEnd = methodEnd << 32 | (methodEnd & 0xFFFFFFFFL); + int methodStart = method.bodyStart; + int methodEnd = method.bodyEnd; + long methodPosEnd = ((long) methodEnd) << 32 | (methodEnd & 0xFFFFFFFFL); TryStatement tryStatement = new TryStatement(); setGeneratedBy(tryStatement, source); diff --git a/src/core/lombok/eclipse/handlers/HandleToString.java b/src/core/lombok/eclipse/handlers/HandleToString.java index 75d4acef..1193af31 100644 --- a/src/core/lombok/eclipse/handlers/HandleToString.java +++ b/src/core/lombok/eclipse/handlers/HandleToString.java @@ -170,7 +170,7 @@ public class HandleToString extends EclipseAnnotationHandler<ToString> { } } - private MethodDeclaration createToString(EclipseNode type, Collection<EclipseNode> fields, + static MethodDeclaration createToString(EclipseNode type, Collection<EclipseNode> fields, boolean includeFieldNames, boolean callSuper, ASTNode source, FieldAccess fieldAccess) { String typeName = getTypeName(type); char[] suffix = ")".toCharArray(); @@ -209,21 +209,25 @@ public class HandleToString extends EclipseAnnotationHandler<ToString> { } for (EclipseNode field : fields) { - TypeReference fType = getFieldType(field, fieldAccess); + TypeReference fieldType = getFieldType(field, fieldAccess); Expression fieldAccessor = createFieldAccessor(field, fieldAccess, source); + // The distinction between primitive and object will be useful if we ever add a 'hideNulls' option. + boolean fieldBaseTypeIsPrimitive = BUILT_IN_TYPES.contains(new String(fieldType.getLastToken())); + boolean fieldIsPrimitive = fieldType.dimensions() == 0 && fieldBaseTypeIsPrimitive; + boolean fieldIsPrimitiveArray = fieldType.dimensions() == 1 && fieldBaseTypeIsPrimitive; + boolean fieldIsObjectArray = fieldType.dimensions() > 0 && !fieldIsPrimitiveArray; + @SuppressWarnings("unused") + boolean fieldIsObject = !fieldIsPrimitive && !fieldIsPrimitiveArray && !fieldIsObjectArray; + Expression ex; - if (fType.dimensions() > 0) { + if (fieldIsPrimitiveArray || fieldIsObjectArray) { MessageSend arrayToString = new MessageSend(); arrayToString.sourceStart = pS; arrayToString.sourceEnd = pE; arrayToString.receiver = generateQualifiedNameRef(source, TypeConstants.JAVA, TypeConstants.UTIL, "Arrays".toCharArray()); arrayToString.arguments = new Expression[] { fieldAccessor }; setGeneratedBy(arrayToString.arguments[0], source); - if (fType.dimensions() > 1 || !BUILT_IN_TYPES.contains(new String(fType.getLastToken()))) { - arrayToString.selector = "deepToString".toCharArray(); - } else { - arrayToString.selector = "toString".toCharArray(); - } + arrayToString.selector = (fieldIsObjectArray ? "deepToString" : "toString").toCharArray(); ex = arrayToString; } else { ex = fieldAccessor; @@ -278,7 +282,7 @@ public class HandleToString extends EclipseAnnotationHandler<ToString> { return method; } - private String getTypeName(EclipseNode type) { + private static String getTypeName(EclipseNode type) { String typeName = getSingleTypeName(type); EclipseNode upType = type.up(); while (upType.getKind() == Kind.TYPE) { @@ -288,7 +292,7 @@ public class HandleToString extends EclipseAnnotationHandler<ToString> { return typeName; } - private String getSingleTypeName(EclipseNode type) { + private static String getSingleTypeName(EclipseNode type) { TypeDeclaration typeDeclaration = (TypeDeclaration)type.get(); char[] rawTypeName = typeDeclaration.name; return rawTypeName == null ? "" : new String(rawTypeName); @@ -297,7 +301,7 @@ public class HandleToString extends EclipseAnnotationHandler<ToString> { private static final Set<String> BUILT_IN_TYPES = Collections.unmodifiableSet(new HashSet<String>(Arrays.asList( "byte", "short", "int", "long", "char", "boolean", "double", "float"))); - private NameReference generateQualifiedNameRef(ASTNode source, char[]... varNames) { + private static NameReference generateQualifiedNameRef(ASTNode source, char[]... varNames) { int pS = source.sourceStart, pE = source.sourceEnd; long p = (long)pS << 32 | pE; NameReference ref; diff --git a/src/core/lombok/eclipse/handlers/HandleValue.java b/src/core/lombok/eclipse/handlers/HandleValue.java index b69b1669..0607137b 100644 --- a/src/core/lombok/eclipse/handlers/HandleValue.java +++ b/src/core/lombok/eclipse/handlers/HandleValue.java @@ -30,8 +30,9 @@ import lombok.core.AnnotationValues; import lombok.core.HandlerPriority; import lombok.eclipse.EclipseAnnotationHandler; import lombok.eclipse.EclipseNode; +import lombok.eclipse.handlers.HandleConstructor.SkipIfConstructorExists; import lombok.experimental.NonFinal; -import lombok.experimental.Value; +import lombok.Value; import org.eclipse.jdt.internal.compiler.ast.Annotation; import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; @@ -78,6 +79,6 @@ public class HandleValue extends EclipseAnnotationHandler<Value> { new HandleGetter().generateGetterForType(typeNode, annotationNode, AccessLevel.PUBLIC, true); new HandleEqualsAndHashCode().generateEqualsAndHashCodeForType(typeNode, annotationNode); new HandleToString().generateToStringForType(typeNode, annotationNode); - new HandleConstructor().generateAllArgsConstructor(typeNode, AccessLevel.PUBLIC, ann.staticConstructor(), true, Collections.<Annotation>emptyList(), ast); + new HandleConstructor().generateAllArgsConstructor(typeNode, AccessLevel.PUBLIC, ann.staticConstructor(), SkipIfConstructorExists.YES, Collections.<Annotation>emptyList(), ast); } } diff --git a/src/core/lombok/eclipse/handlers/HandleWither.java b/src/core/lombok/eclipse/handlers/HandleWither.java index 9d74cbd1..27fbc635 100644 --- a/src/core/lombok/eclipse/handlers/HandleWither.java +++ b/src/core/lombok/eclipse/handlers/HandleWither.java @@ -160,7 +160,7 @@ public class HandleWither extends EclipseAnnotationHandler<Wither> { FieldDeclaration field = (FieldDeclaration) fieldNode.get(); TypeReference fieldType = copyType(field.type, source); - boolean isBoolean = nameEquals(fieldType.getTypeName(), "boolean") && fieldType.dimensions() == 0; + boolean isBoolean = isBoolean(fieldType); String witherName = toWitherName(fieldNode, isBoolean); if (witherName == null) { diff --git a/src/core/lombok/eclipse/handlers/NonNullHandler.java b/src/core/lombok/eclipse/handlers/NonNullHandler.java new file mode 100644 index 00000000..5c58069c --- /dev/null +++ b/src/core/lombok/eclipse/handlers/NonNullHandler.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2013 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.eclipse.handlers; + +import java.util.Arrays; + +import org.eclipse.jdt.internal.compiler.ast.ASTNode; +import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.AbstractVariableDeclaration; +import org.eclipse.jdt.internal.compiler.ast.Annotation; +import org.eclipse.jdt.internal.compiler.ast.Argument; +import org.eclipse.jdt.internal.compiler.ast.Block; +import org.eclipse.jdt.internal.compiler.ast.EqualExpression; +import org.eclipse.jdt.internal.compiler.ast.Expression; +import org.eclipse.jdt.internal.compiler.ast.IfStatement; +import org.eclipse.jdt.internal.compiler.ast.NullLiteral; +import org.eclipse.jdt.internal.compiler.ast.OperatorIds; +import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; +import org.eclipse.jdt.internal.compiler.ast.Statement; +import org.eclipse.jdt.internal.compiler.ast.ThrowStatement; +import org.mangosdk.spi.ProviderFor; + +import lombok.NonNull; +import lombok.core.AST.Kind; +import lombok.core.AnnotationValues; +import lombok.eclipse.DeferUntilPostDiet; +import lombok.eclipse.EclipseAnnotationHandler; +import lombok.eclipse.EclipseNode; + +import static lombok.eclipse.Eclipse.*; +import static lombok.eclipse.handlers.EclipseHandlerUtil.*; + +@DeferUntilPostDiet +@ProviderFor(EclipseAnnotationHandler.class) +public class NonNullHandler extends EclipseAnnotationHandler<NonNull> { + @Override public void handle(AnnotationValues<NonNull> annotation, Annotation ast, EclipseNode annotationNode) { + if (annotationNode.up().getKind() == Kind.FIELD) { + // This is meaningless unless the field is used to generate a method (@Setter, @RequiredArgsConstructor, etc), + // but in that case those handlers will take care of it. However, we DO check if the annotation is applied to + // a primitive, because those handlers trigger on any annotation named @NonNull and we only want the warning + // behaviour on _OUR_ 'lombok.NonNull'. + + try { + if (isPrimitive(((AbstractVariableDeclaration) annotationNode.up().get()).type)) { + annotationNode.addWarning("@NonNull is meaningless on a primitive."); + } + } catch (Exception ignore) {} + + return; + } + + if (annotationNode.up().getKind() != Kind.ARGUMENT) return; + + Argument arg; + AbstractMethodDeclaration declaration; + + try { + arg = (Argument) annotationNode.up().get(); + declaration = (AbstractMethodDeclaration) annotationNode.up().up().get(); + } catch (Exception e) { + return; + } + + if (isGenerated(declaration)) return; + + // Possibly, if 'declaration instanceof ConstructorDeclaration', fetch declaration.constructorCall, search it for any references to our parameter, + // and if they exist, create a new method in the class: 'private static <T> T lombok$nullCheck(T expr, String msg) {if (expr == null) throw NPE; return expr;}' and + // wrap all references to it in the super/this to a call to this method. + + Statement nullCheck = generateNullCheck(arg, ast); + + if (nullCheck == null) { + // @NonNull applied to a primitive. Kinda pointless. Let's generate a warning. + annotationNode.addWarning("@NonNull is meaningless on a primitive."); + return; + } + + if (declaration.statements == null) { + declaration.statements = new Statement[] {nullCheck}; + } else { + char[] expectedName = arg.name; + for (Statement stat : declaration.statements) { + char[] varNameOfNullCheck = returnVarNameIfNullCheck(stat); + if (varNameOfNullCheck == null) break; + if (Arrays.equals(expectedName, varNameOfNullCheck)) return; + } + + Statement[] newStatements = new Statement[declaration.statements.length + 1]; + int skipOver = 0; + for (Statement stat : declaration.statements) { + if (isGenerated(stat)) skipOver++; + else break; + } + System.arraycopy(declaration.statements, 0, newStatements, 0, skipOver); + System.arraycopy(declaration.statements, skipOver, newStatements, skipOver + 1, declaration.statements.length - skipOver); + newStatements[skipOver] = nullCheck; + declaration.statements = newStatements; + } + annotationNode.up().up().rebuild(); + } + + private char[] returnVarNameIfNullCheck(Statement stat) { + if (!(stat instanceof IfStatement)) return null; + + /* Check that the if's statement is a throw statement, possibly in a block. */ { + Statement then = ((IfStatement) stat).thenStatement; + if (then instanceof Block) { + Statement[] blockStatements = ((Block) then).statements; + if (blockStatements == null || blockStatements.length == 0) return null; + then = blockStatements[0]; + } + + if (!(then instanceof ThrowStatement)) return null; + } + + /* Check that the if's conditional is like 'x == null'. Return from this method (don't generate + a nullcheck) if 'x' is equal to our own variable's name: There's already a nullcheck here. */ { + Expression cond = ((IfStatement) stat).condition; + if (!(cond instanceof EqualExpression)) return null; + EqualExpression bin = (EqualExpression) cond; + int operatorId = ((bin.bits & ASTNode.OperatorMASK) >> ASTNode.OperatorSHIFT); + if (operatorId != OperatorIds.EQUAL_EQUAL) return null; + if (!(bin.left instanceof SingleNameReference)) return null; + if (!(bin.right instanceof NullLiteral)) return null; + return ((SingleNameReference) bin.left).token; + } + } +} diff --git a/src/core/lombok/experimental/Builder.java b/src/core/lombok/experimental/Builder.java new file mode 100644 index 00000000..1300e7d3 --- /dev/null +++ b/src/core/lombok/experimental/Builder.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2013 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.experimental; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.*; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * The builder annotation creates a so-called 'builder' aspect to the class that is annotated or the class + * that contains a member which is annotated with {@code @Builder}. + * <p> + * If a member is annotated, it must be either a constructor or a 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 ""; + + /** + * Normally the builder's 'set' methods are fluent, meaning, they have the same name as the field. Set this + * to {@code false} to name the setter method for field {@code someField}: {@code setSomeField}. + * <p> + * <strong>Default: true</strong> + */ + boolean fluent() default true; + + /** + * Normally the builder's 'set' methods are chaining, meaning, they return the builder so that you can chain + * calls to set methods. Set this to {@code false} to have these 'set' methods return {@code void} instead. + * <p> + * <strong>Default: true</strong> + */ + boolean chain() default true; +} diff --git a/src/core/lombok/experimental/Value.java b/src/core/lombok/experimental/Value.java index 048066df..b7700bb5 100644 --- a/src/core/lombok/experimental/Value.java +++ b/src/core/lombok/experimental/Value.java @@ -39,9 +39,11 @@ import java.lang.annotation.Target; * @see lombok.ToString * @see lombok.EqualsAndHashCode * @see lombok.Data + * @deprecated {@link lombok.Value} has been promoted to the main package, so use that one instead. */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.SOURCE) +@Deprecated public @interface Value { /** * If you specify a static constructor name, then the generated constructor will be private, and diff --git a/src/core/lombok/extern/apachecommons/CommonsLog.java b/src/core/lombok/extern/apachecommons/CommonsLog.java index f178ae05..024e3744 100644 --- a/src/core/lombok/extern/apachecommons/CommonsLog.java +++ b/src/core/lombok/extern/apachecommons/CommonsLog.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2012 The Project Lombok Authors. + * 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 @@ -52,6 +52,7 @@ import java.lang.annotation.Target; * @see org.apache.commons.logging.LogFactory#getLog(java.lang.Class) org.apache.commons.logging.LogFactory.getLog(Class target) * @see lombok.extern.java.Log @Log * @see lombok.extern.log4j.Log4j @Log4j + * @see lombok.extern.log4j.Log4j2 @Log4j2 * @see lombok.extern.slf4j.Slf4j @Slf4j * @see lombok.extern.slf4j.XSlf4j @XSlf4j */ diff --git a/src/core/lombok/extern/java/Log.java b/src/core/lombok/extern/java/Log.java index 90c62956..7ae4e07b 100644 --- a/src/core/lombok/extern/java/Log.java +++ b/src/core/lombok/extern/java/Log.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2012 The Project Lombok Authors. + * 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 @@ -51,6 +51,7 @@ import java.lang.annotation.Target; * @see java.util.logging.Logger#getLogger(java.lang.String) java.util.logging.Logger.getLogger(String name) * @see lombok.extern.apachecommons.CommonsLog @CommonsLog * @see lombok.extern.log4j.Log4j @Log4j + * @see lombok.extern.log4j.Log4j2 @Log4j2 * @see lombok.extern.slf4j.Slf4j @Slf4j * @see lombok.extern.slf4j.XSlf4j @XSlf4j */ diff --git a/src/core/lombok/extern/log4j/Log4j.java b/src/core/lombok/extern/log4j/Log4j.java index 9cfc5839..29e1b27c 100644 --- a/src/core/lombok/extern/log4j/Log4j.java +++ b/src/core/lombok/extern/log4j/Log4j.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2012 The Project Lombok Authors. + * 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 @@ -50,6 +50,7 @@ import java.lang.annotation.Target; * * @see org.apache.log4j.Logger org.apache.log4j.Logger * @see org.apache.log4j.Logger#getLogger(java.lang.Class) org.apache.log4j.Logger.getLogger(Class target) + * @see lombok.extern.log4j.Log4j2 @Log4j2 * @see lombok.extern.apachecommons.CommonsLog @CommonsLog * @see lombok.extern.java.Log @Log * @see lombok.extern.slf4j.Slf4j @Slf4j diff --git a/src/core/lombok/extern/log4j/Log4j2.java b/src/core/lombok/extern/log4j/Log4j2.java new file mode 100644 index 00000000..2a0f09e1 --- /dev/null +++ b/src/core/lombok/extern/log4j/Log4j2.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2013 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.extern.log4j; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Causes lombok to generate a logger field. + * <p> + * Complete documentation is found at <a href="http://projectlombok.org/features/Log.html">the project lombok features page for lombok log annotations</a>. + * <p> + * Example: + * <pre> + * @Log4j2 + * public class LogExample { + * } + * </pre> + * + * will generate: + * + * <pre> + * public class LogExample { + * private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.Logger.getLogger(LogExample.class); + * } + * </pre> + * + * This annotation is valid for classes and enumerations.<br /> + * + * @see org.apache.logging.log4j.Logger org.apache.logging.log4j.Logger + * @see org.apache.logging.log4j.LogManager#getLogger(java.lang.Class) org.apache.logging.log4j.LogManager.getLogger(Class target) + * @see lombok.extern.log4j.Log4j @Log4j + * @see lombok.extern.apachecommons.CommonsLog @CommonsLog + * @see lombok.extern.java.Log @Log + * @see lombok.extern.slf4j.Slf4j @Slf4j + * @see lombok.extern.slf4j.XSlf4j @XSlf4j + */ +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.TYPE) +public @interface Log4j2 { +}
\ No newline at end of file diff --git a/src/core/lombok/extern/slf4j/Slf4j.java b/src/core/lombok/extern/slf4j/Slf4j.java index 14dbcba6..45942971 100644 --- a/src/core/lombok/extern/slf4j/Slf4j.java +++ b/src/core/lombok/extern/slf4j/Slf4j.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2012 The Project Lombok Authors. + * 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 @@ -51,6 +51,7 @@ import java.lang.annotation.Target; * @see lombok.extern.apachecommons.CommonsLog @CommonsLog * @see lombok.extern.java.Log @Log * @see lombok.extern.log4j.Log4j @Log4j + * @see lombok.extern.log4j.Log4j2 @Log4j2 */ @Retention(RetentionPolicy.SOURCE) @Target(ElementType.TYPE) diff --git a/src/core/lombok/extern/slf4j/XSlf4j.java b/src/core/lombok/extern/slf4j/XSlf4j.java index bdf8a62c..599c68ab 100644 --- a/src/core/lombok/extern/slf4j/XSlf4j.java +++ b/src/core/lombok/extern/slf4j/XSlf4j.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 The Project Lombok Authors. + * Copyright (C) 2012-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 @@ -51,6 +51,7 @@ import java.lang.annotation.Target; * @see lombok.extern.apachecommons.CommonsLog @CommonsLog * @see lombok.extern.java.Log @Log * @see lombok.extern.log4j.Log4j @Log4j + * @see lombok.extern.log4j.Log4j2 @Log4j2 * @see lombok.extern.slf4j.Slf4j @Slf4j */ @Retention(RetentionPolicy.SOURCE) diff --git a/src/core/lombok/javac/FindTypeVarScanner.java b/src/core/lombok/javac/FindTypeVarScanner.java index b1b8e525..7c7d9d50 100644 --- a/src/core/lombok/javac/FindTypeVarScanner.java +++ b/src/core/lombok/javac/FindTypeVarScanner.java @@ -88,7 +88,7 @@ public class FindTypeVarScanner extends AbstractTypeVisitor6<Void, Void> { @Override public Void visitTypeVariable(TypeVariable t, Void p) { Name name = null; try { - name = ((Type)t).tsym.name; + name = ((Type) t).tsym.name; } catch (NullPointerException e) {} if (name != null) typeVariables.add(name.toString()); subVisit(t.getLowerBound()); diff --git a/src/core/lombok/javac/HandlerLibrary.java b/src/core/lombok/javac/HandlerLibrary.java index 2be84355..4306b5f2 100644 --- a/src/core/lombok/javac/HandlerLibrary.java +++ b/src/core/lombok/javac/HandlerLibrary.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2012 The Project Lombok Authors. + * Copyright (C) 2009-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 @@ -231,23 +231,23 @@ public class HandlerLibrary { * @param annotation 'node.get()' - convenience parameter. */ public void handleAnnotation(JCCompilationUnit unit, JavacNode node, JCAnnotation annotation, long priority) { - TypeResolver resolver = new TypeResolver(node.getPackageDeclaration(), node.getImportStatements()); + TypeResolver resolver = new TypeResolver(node.getImportList()); String rawType = annotation.annotationType.toString(); - for (String fqn : resolver.findTypeMatches(node, typeLibrary, rawType)) { - AnnotationHandlerContainer<?> container = annotationHandlers.get(fqn); - if (container == null) continue; - - try { - if (container.getPriority() == priority) { - if (checkAndSetHandled(annotation)) container.handle(node); - } - } catch (AnnotationValueDecodeFail fail) { - fail.owner.setError(fail.getMessage(), fail.idx); - } catch (Throwable t) { - String sourceName = "(unknown).java"; - if (unit != null && unit.sourcefile != null) sourceName = unit.sourcefile.getName(); - javacError(String.format("Lombok annotation handler %s failed on " + sourceName, container.handler.getClass()), t); + String fqn = resolver.typeRefToFullyQualifiedName(node, typeLibrary, rawType); + if (fqn == null) return; + AnnotationHandlerContainer<?> container = annotationHandlers.get(fqn); + if (container == null) return; + + try { + if (container.getPriority() == priority) { + if (checkAndSetHandled(annotation)) container.handle(node); } + } catch (AnnotationValueDecodeFail fail) { + fail.owner.setError(fail.getMessage(), fail.idx); + } catch (Throwable t) { + String sourceName = "(unknown).java"; + if (unit != null && unit.sourcefile != null) sourceName = unit.sourcefile.getName(); + javacError(String.format("Lombok annotation handler %s failed on " + sourceName, container.handler.getClass()), t); } } diff --git a/src/core/lombok/javac/Javac6BasedLombokOptions.java b/src/core/lombok/javac/Javac6BasedLombokOptions.java index 4bb2bdc3..871e41c4 100644 --- a/src/core/lombok/javac/Javac6BasedLombokOptions.java +++ b/src/core/lombok/javac/Javac6BasedLombokOptions.java @@ -26,7 +26,6 @@ import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.Options; public class Javac6BasedLombokOptions extends LombokOptions { - public static Javac6BasedLombokOptions replaceWithDelombokOptions(Context context) { Options options = Options.instance(context); context.put(optionsKey, (Options)null); @@ -38,7 +37,7 @@ public class Javac6BasedLombokOptions extends LombokOptions { private Javac6BasedLombokOptions(Context context) { super(context); } - + @Override public void putJavacOption(String optionName, String value) { put(OptionName.valueOf(optionName), value); } diff --git a/src/core/lombok/javac/JavacAST.java b/src/core/lombok/javac/JavacAST.java index 71c17538..36c51210 100644 --- a/src/core/lombok/javac/JavacAST.java +++ b/src/core/lombok/javac/JavacAST.java @@ -24,6 +24,7 @@ package lombok.javac; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.List; import javax.annotation.processing.Messager; @@ -34,10 +35,12 @@ import javax.tools.JavaFileObject; import lombok.core.AST; import com.sun.tools.javac.code.Symtab; +import com.sun.tools.javac.code.Source; import com.sun.tools.javac.model.JavacElements; import com.sun.tools.javac.model.JavacTypes; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.JCCatch; +import com.sun.tools.javac.tree.JCTree.JCTry; import com.sun.tools.javac.tree.TreeMaker; import com.sun.tools.javac.tree.JCTree.JCAnnotation; import com.sun.tools.javac.tree.JCTree.JCBlock; @@ -46,7 +49,6 @@ import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; import com.sun.tools.javac.tree.JCTree.JCExpression; import com.sun.tools.javac.tree.JCTree.JCFieldAccess; import com.sun.tools.javac.tree.JCTree.JCIdent; -import com.sun.tools.javac.tree.JCTree.JCImport; import com.sun.tools.javac.tree.JCTree.JCMethodDecl; import com.sun.tools.javac.tree.JCTree.JCStatement; import com.sun.tools.javac.tree.JCTree.JCVariableDecl; @@ -78,7 +80,7 @@ public class JavacAST extends AST<JavacAST, JavacNode, JCTree> { * @param top The compilation unit, which serves as the top level node in the tree to be built. */ public JavacAST(Messager messager, Context context, JCCompilationUnit top) { - super(sourceName(top), packageDeclaration(top), imports(top)); + super(sourceName(top), packageDeclaration(top), new JavacImportList(top)); setTop(buildCompilationUnit(top)); this.context = context; this.messager = messager; @@ -98,16 +100,6 @@ public class JavacAST extends AST<JavacAST, JavacNode, JCTree> { return (cu.pid instanceof JCFieldAccess || cu.pid instanceof JCIdent) ? cu.pid.toString() : null; } - private static Collection<String> imports(JCCompilationUnit cu) { - List<String> imports = new ArrayList<String>(); - for (JCTree def : cu.defs) { - if (def instanceof JCImport) { - imports.add(((JCImport)def).qualid.toString()); - } - } - return imports; - } - public Context getContext() { return context; } @@ -121,9 +113,20 @@ public class JavacAST extends AST<JavacAST, JavacNode, JCTree> { } void traverseChildren(JavacASTVisitor visitor, JavacNode node) { - for (JavacNode child : new ArrayList<JavacNode>(node.down())) { - child.traverse(visitor); - } + for (JavacNode child : node.down()) child.traverse(visitor); + } + + @Override public int getSourceVersion() { + try { + String nm = Source.instance(context).name(); + int underscoreIdx = nm.indexOf('_'); + if (underscoreIdx > -1) return Integer.parseInt(nm.substring(underscoreIdx + 1)); + } catch (Exception ignore) {} + return 6; + } + + @Override public int getLatestJavaSpecSupported() { + return Javac.getJavaCompilerVersion(); } /** @return A Name object generated for the proper name table belonging to this AST. */ @@ -223,6 +226,46 @@ public class JavacAST extends AST<JavacAST, JavacNode, JCTree> { return putInMap(new JavacNode(this, local, childNodes, kind)); } + private static boolean JCTRY_RESOURCES_FIELD_INITIALIZED; + private static Field JCTRY_RESOURCES_FIELD; + + @SuppressWarnings("unchecked") + private static List<JCTree> getResourcesForTryNode(JCTry tryNode) { + if (!JCTRY_RESOURCES_FIELD_INITIALIZED) { + try { + JCTRY_RESOURCES_FIELD = JCTry.class.getField("resources"); + } catch (NoSuchFieldException ignore) { + // Java 1.6 or lower won't have this at all. + } catch (Exception ignore) { + // Shouldn't happen. Best thing we can do is just carry on and break on try/catch. + } + JCTRY_RESOURCES_FIELD_INITIALIZED = true; + } + + if (JCTRY_RESOURCES_FIELD == null) return Collections.emptyList(); + Object rv = null; + try { + rv = JCTRY_RESOURCES_FIELD.get(tryNode); + } catch (Exception ignore) {} + + if (rv instanceof List) return (List<JCTree>) rv; + return Collections.emptyList(); + } + + private JavacNode buildTry(JCTry tryNode) { + if (setAndGetAsHandled(tryNode)) return null; + List<JavacNode> childNodes = new ArrayList<JavacNode>(); + for (JCTree varDecl : getResourcesForTryNode(tryNode)) { + if (varDecl instanceof JCVariableDecl) { + addIfNotNull(childNodes, buildLocalVar((JCVariableDecl) varDecl, Kind.LOCAL)); + } + } + addIfNotNull(childNodes, buildStatement(tryNode.body)); + for (JCCatch jcc : tryNode.catchers) addIfNotNull(childNodes, buildTree(jcc, Kind.STATEMENT)); + addIfNotNull(childNodes, buildStatement(tryNode.finalizer)); + return putInMap(new JavacNode(this, tryNode, childNodes, Kind.STATEMENT)); + } + private JavacNode buildInitializer(JCBlock initializer) { if (setAndGetAsHandled(initializer)) return null; List<JavacNode> childNodes = new ArrayList<JavacNode>(); @@ -264,6 +307,7 @@ public class JavacAST extends AST<JavacAST, JavacNode, JCTree> { if (statement instanceof JCAnnotation) return null; if (statement instanceof JCClassDecl) return buildType((JCClassDecl)statement); if (statement instanceof JCVariableDecl) return buildLocalVar((JCVariableDecl)statement, Kind.LOCAL); + if (statement instanceof JCTry) return buildTry((JCTry) statement); if (setAndGetAsHandled(statement)) return null; @@ -271,9 +315,18 @@ public class JavacAST extends AST<JavacAST, JavacNode, JCTree> { } private JavacNode drill(JCTree statement) { - List<JavacNode> childNodes = new ArrayList<JavacNode>(); - for (FieldAccess fa : fieldsOf(statement.getClass())) childNodes.addAll(buildWithField(JavacNode.class, statement, fa)); - return putInMap(new JavacNode(this, statement, childNodes, Kind.STATEMENT)); + try { + List<JavacNode> childNodes = new ArrayList<JavacNode>(); + for (FieldAccess fa : fieldsOf(statement.getClass())) childNodes.addAll(buildWithField(JavacNode.class, statement, fa)); + return putInMap(new JavacNode(this, statement, childNodes, Kind.STATEMENT)); + } catch (OutOfMemoryError oome) { + String msg = oome.getMessage(); + if (msg == null) msg = "(no original message)"; + OutOfMemoryError newError = new OutOfMemoryError(getFileName() + "@pos" + statement.getPreferredPosition() + ": " + msg); + // We could try to set the stack trace of the new exception to the same one as the old exception, but this costs memory, + // and we're already in an extremely fragile situation in regards to remaining heap space, so let's not do that. + throw newError; + } } /** For javac, both JCExpression and JCStatement are considered as valid children types. */ diff --git a/src/core/lombok/javac/JavacAnnotationHandler.java b/src/core/lombok/javac/JavacAnnotationHandler.java index 169e2026..a86aa6c6 100644 --- a/src/core/lombok/javac/JavacAnnotationHandler.java +++ b/src/core/lombok/javac/JavacAnnotationHandler.java @@ -33,7 +33,7 @@ import com.sun.tools.javac.tree.JCTree.JCAnnotation; * * You MUST replace 'T' with a specific annotation type, such as: * - * {@code public class HandleGetter implements JavacAnnotationHandler<Getter>} + * {@code public class HandleGetter extends JavacAnnotationHandler<Getter>} * * Because this generics parameter is inspected to figure out which class you're interested in. * diff --git a/src/core/lombok/javac/JavacImportList.java b/src/core/lombok/javac/JavacImportList.java new file mode 100644 index 00000000..d5d7460a --- /dev/null +++ b/src/core/lombok/javac/JavacImportList.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2013 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.javac; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; + +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; +import com.sun.tools.javac.tree.JCTree.JCExpression; +import com.sun.tools.javac.tree.JCTree.JCFieldAccess; +import com.sun.tools.javac.tree.JCTree.JCImport; +import com.sun.tools.javac.util.List; + +import lombok.core.ImportList; +import lombok.core.LombokInternalAliasing; + +public class JavacImportList implements ImportList { + private final JCExpression pkg; + private final List<JCTree> defs; + + public JavacImportList(JCCompilationUnit cud) { + this.pkg = cud.pid; + this.defs = cud.defs; + } + + @Override public String getFullyQualifiedNameForSimpleName(String unqualified) { + for (JCTree def : defs) { + if (!(def instanceof JCImport)) continue; + JCTree qual = ((JCImport) def).qualid; + if (!(qual instanceof JCFieldAccess)) continue; + String simpleName = ((JCFieldAccess) qual).name.toString(); + if (simpleName.equals(unqualified)) { + return LombokInternalAliasing.processAliases(qual.toString()); + } + } + + return null; + } + + @Override public boolean hasStarImport(String packageName) { + for (Map.Entry<String, String> e : LombokInternalAliasing.IMPLIED_EXTRA_STAR_IMPORTS.entrySet()) { + if (e.getValue().equals(packageName) && hasStarImport(e.getKey())) return true; + } + if (pkg != null && pkg.toString().equals(packageName)) return true; + if ("java.lang".equals(packageName)) return true; + + for (JCTree def : defs) { + if (!(def instanceof JCImport)) continue; + if (((JCImport) def).staticImport) continue; + JCTree qual = ((JCImport) def).qualid; + if (!(qual instanceof JCFieldAccess)) continue; + String simpleName = ((JCFieldAccess) qual).name.toString(); + if (!"*".equals(simpleName)) continue; + if (packageName.equals(((JCFieldAccess) qual).selected.toString())) return true; + } + + return false; + } + + @Override public Collection<String> applyNameToStarImports(String startsWith, String name) { + ArrayList<String> out = new ArrayList<String>(); + + if (pkg != null && topLevelName(pkg).equals(startsWith)) out.add(pkg.toString() + "." + name); + + for (JCTree def : defs) { + if (!(def instanceof JCImport)) continue; + if (((JCImport) def).staticImport) continue; + JCTree qual = ((JCImport) def).qualid; + if (!(qual instanceof JCFieldAccess)) continue; + String simpleName = ((JCFieldAccess) qual).name.toString(); + if (!"*".equals(simpleName)) continue; + + String topLevelName = topLevelName(qual); + if (topLevelName.equals(startsWith)) { + out.add(((JCFieldAccess) qual).selected.toString() + "." + name); + } + } + + return out; + } + + private String topLevelName(JCTree tree) { + while (tree instanceof JCFieldAccess) tree = ((JCFieldAccess) tree).selected; + return tree.toString(); + } + + @Override public String applyUnqualifiedNameToPackage(String unqualified) { + if (pkg == null) return unqualified; + return pkg.toString() + "." + unqualified; + } +} diff --git a/src/core/lombok/javac/JavacResolution.java b/src/core/lombok/javac/JavacResolution.java index e5d8ed38..82ce0cb8 100644 --- a/src/core/lombok/javac/JavacResolution.java +++ b/src/core/lombok/javac/JavacResolution.java @@ -433,8 +433,8 @@ public class JavacResolution { if (symbol.name.length() == 0) { // Anonymous inner class if (type instanceof ClassType) { - List<Type> ifaces = ((ClassType)type).interfaces_field; - Type supertype = ((ClassType)type).supertype_field; + List<Type> ifaces = ((ClassType) type).interfaces_field; + Type supertype = ((ClassType) type).supertype_field; if (ifaces != null && ifaces.length() == 1) { return typeToJCTree(ifaces.get(0), ast, allowCompound, allowVoid); } diff --git a/src/core/lombok/javac/LombokOptions.java b/src/core/lombok/javac/LombokOptions.java index 66e42fe7..f1567e9d 100644 --- a/src/core/lombok/javac/LombokOptions.java +++ b/src/core/lombok/javac/LombokOptions.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 The Project Lombok Authors. + * 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 @@ -49,6 +49,6 @@ public abstract class LombokOptions extends Options { protected LombokOptions(Context context) { super(context); } - + public abstract void putJavacOption(String optionName, String value); } diff --git a/src/core/lombok/javac/apt/Processor.java b/src/core/lombok/javac/apt/Processor.java index 96150b06..110acaad 100644 --- a/src/core/lombok/javac/apt/Processor.java +++ b/src/core/lombok/javac/apt/Processor.java @@ -62,10 +62,6 @@ import com.sun.tools.javac.util.Context; /** * This Annotation Processor is the standard injection mechanism for lombok-enabling the javac compiler. * - * Due to lots of changes in the core javac code, as well as lombok's heavy usage of non-public API, this - * code only works for the javac v1.6 compiler; it definitely won't work for javac v1.5, and it probably - * won't work for javac v1.7 without modifications. - * * To actually enable lombok in a javac compilation run, this class should be in the classpath when * running javac; that's the only requirement. */ diff --git a/src/core/lombok/javac/handlers/HandleBuilder.java b/src/core/lombok/javac/handlers/HandleBuilder.java new file mode 100644 index 00000000..e60819da --- /dev/null +++ b/src/core/lombok/javac/handlers/HandleBuilder.java @@ -0,0 +1,330 @@ +/* + * Copyright (C) 2013 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.javac.handlers; + +import java.util.ArrayList; +import java.util.Collections; + +import org.mangosdk.spi.ProviderFor; + +import com.sun.tools.javac.code.Flags; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.JCAnnotation; +import com.sun.tools.javac.tree.JCTree.JCBlock; +import com.sun.tools.javac.tree.JCTree.JCClassDecl; +import com.sun.tools.javac.tree.JCTree.JCExpression; +import com.sun.tools.javac.tree.JCTree.JCFieldAccess; +import com.sun.tools.javac.tree.JCTree.JCIdent; +import com.sun.tools.javac.tree.JCTree.JCMethodDecl; +import com.sun.tools.javac.tree.JCTree.JCModifiers; +import com.sun.tools.javac.tree.JCTree.JCPrimitiveTypeTree; +import com.sun.tools.javac.tree.JCTree.JCStatement; +import com.sun.tools.javac.tree.JCTree.JCTypeApply; +import com.sun.tools.javac.tree.JCTree.JCTypeParameter; +import com.sun.tools.javac.tree.JCTree.JCVariableDecl; +import com.sun.tools.javac.tree.TreeMaker; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.util.ListBuffer; +import com.sun.tools.javac.util.Name; + +import lombok.AccessLevel; +import lombok.core.AST.Kind; +import lombok.core.AnnotationValues; +import lombok.core.HandlerPriority; +import lombok.core.TransformationsUtil; +import lombok.experimental.Builder; +import lombok.experimental.NonFinal; +import lombok.javac.JavacAnnotationHandler; +import lombok.javac.JavacNode; +import lombok.javac.handlers.HandleConstructor.SkipIfConstructorExists; +import static lombok.javac.Javac.*; +import static lombok.core.handlers.HandlerUtil.*; +import static lombok.javac.handlers.JavacHandlerUtil.*; + +@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) { + Builder builderInstance = annotation.getInstance(); + String builderMethodName = builderInstance.builderMethodName(); + String buildMethodName = builderInstance.buildMethodName(); + String builderClassName = builderInstance.builderClassName(); + + if (builderMethodName == null) builderMethodName = "builder"; + if (buildMethodName == null) buildMethodName = "build"; + if (builderClassName == null) builderClassName = ""; + + if (!checkName("builderMethodName", builderMethodName, annotationNode)) return; + if (!checkName("buildMethodName", buildMethodName, annotationNode)) return; + if (!builderClassName.isEmpty()) { + if (!checkName("builderClassName", builderClassName, annotationNode)) return; + } + + deleteAnnotationIfNeccessary(annotationNode, Builder.class); + deleteImportFromCompilationUnit(annotationNode, "lombok.experimental.Builder"); + + JavacNode parent = annotationNode.up(); + + java.util.List<JCExpression> typesOfParameters = new ArrayList<JCExpression>(); + java.util.List<Name> namesOfParameters = new ArrayList<Name>(); + 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; + + if (parent.get() instanceof JCClassDecl) { + tdParent = parent; + JCClassDecl td = (JCClassDecl) tdParent.get(); + ListBuffer<JavacNode> allFields = ListBuffer.lb(); + @SuppressWarnings("deprecation") + boolean valuePresent = (hasAnnotation(lombok.Value.class, parent) || hasAnnotation(lombok.experimental.Value.class, parent)); + for (JavacNode fieldNode : HandleConstructor.findAllFields(tdParent)) { + JCVariableDecl fd = (JCVariableDecl) fieldNode.get(); + // final fields with an initializer cannot be written to, so they can't be 'builderized'. Unfortunately presence of @Value makes + // non-final fields final, but @Value's handler hasn't done this yet, so we have to do this math ourselves. + // Value will only skip making a field final if it has an explicit @NonFinal annotation, so we check for that. + if (fd.init != null && valuePresent && !hasAnnotation(NonFinal.class, fieldNode)) continue; + namesOfParameters.add(fd.name); + typesOfParameters.add(fd.vartype); + allFields.append(fieldNode); + } + + new HandleConstructor().generateConstructor(tdParent, AccessLevel.PACKAGE, List.<JCAnnotation>nil(), allFields.toList(), null, SkipIfConstructorExists.I_AM_BUILDER, true, annotationNode); + + returnType = namePlusTypeParamsToTypeReference(tdParent.getTreeMaker(), td.name, td.typarams); + typeParams = td.typarams; + thrownExceptions = List.nil(); + nameOfStaticBuilderMethod = null; + if (builderClassName.isEmpty()) builderClassName = td.name.toString() + "Builder"; + } else if (fillParametersFrom != null && fillParametersFrom.getName().toString().equals("<init>")) { + if (!fillParametersFrom.typarams.isEmpty()) { + annotationNode.addError("@Builder is not supported on constructors with constructor type parameters."); + return; + } + tdParent = parent.up(); + JCClassDecl td = (JCClassDecl) tdParent.get(); + returnType = namePlusTypeParamsToTypeReference(tdParent.getTreeMaker(), td.name, td.typarams); + typeParams = td.typarams; + thrownExceptions = fillParametersFrom.thrown; + nameOfStaticBuilderMethod = null; + if (builderClassName.isEmpty()) builderClassName = td.name.toString() + "Builder"; + } else if (fillParametersFrom != null) { + tdParent = parent.up(); + JCClassDecl td = (JCClassDecl) tdParent.get(); + if ((fillParametersFrom.mods.flags & Flags.STATIC) == 0) { + annotationNode.addError("@Builder is only supported on types, constructors, and static methods."); + return; + } + returnType = fillParametersFrom.restype; + typeParams = fillParametersFrom.typarams; + thrownExceptions = fillParametersFrom.thrown; + nameOfStaticBuilderMethod = fillParametersFrom.name; + if (builderClassName.isEmpty()) { + if (returnType instanceof JCTypeApply) { + returnType = ((JCTypeApply) returnType).clazz; + } + if (returnType instanceof JCFieldAccess) { + builderClassName = ((JCFieldAccess) returnType).name.toString() + "Builder"; + } else if (returnType instanceof JCIdent) { + Name n = ((JCIdent) returnType).name; + + for (JCTypeParameter tp : typeParams) { + if (tp.name.equals(n)) { + annotationNode.addError("@Builder requires specifying 'builderClassName' if used on methods with a type parameter as return type."); + return; + } + } + builderClassName = n.toString() + "Builder"; + } else if (returnType instanceof JCPrimitiveTypeTree) { + builderClassName = returnType.toString() + "Builder"; + if (Character.isLowerCase(builderClassName.charAt(0))) { + builderClassName = Character.toTitleCase(builderClassName.charAt(0)) + builderClassName.substring(1); + } + + } else { + // This shouldn't happen. + System.err.println("Lombok bug ID#20140614-1651: javac HandleBuilder: return type to name conversion failed: " + returnType.getClass()); + builderClassName = td.name.toString() + "Builder"; + } + } + } else { + annotationNode.addError("@Builder is only supported on types, constructors, and static methods."); + return; + } + + if (fillParametersFrom != null) { + for (JCVariableDecl param : fillParametersFrom.params) { + namesOfParameters.add(param.name); + typesOfParameters.add(param.vartype); + } + } + + JavacNode builderType = findInnerClass(tdParent, builderClassName); + if (builderType == null) { + builderType = makeBuilderClass(tdParent, builderClassName, typeParams, ast); + } else { + sanityCheckForMethodGeneratingAnnotationsOnBuilderClass(builderType, annotationNode); + } + 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, ast, builderInstance.fluent(), builderInstance.chain()); + if (newMethod != null) newMethods.add(newMethod); + } + + if (constructorExists(builderType) == MemberExistsResult.NOT_EXISTS) { + JCMethodDecl cd = HandleConstructor.createConstructor(AccessLevel.PACKAGE, List.<JCAnnotation>nil(), builderType, List.<JavacNode>nil(), true, ast); + if (cd != null) injectMethod(builderType, cd); + } + + for (JCMethodDecl newMethod : newMethods) injectMethod(builderType, newMethod); + + if (methodExists(buildMethodName, builderType, -1) == MemberExistsResult.NOT_EXISTS) { + JCMethodDecl md = generateBuildMethod(buildMethodName, nameOfStaticBuilderMethod, returnType, namesOfParameters, builderType, thrownExceptions); + if (md != null) injectMethod(builderType, md); + } + + if (methodExists("toString", builderType, 0) == MemberExistsResult.NOT_EXISTS) { + JCMethodDecl md = HandleToString.createToString(builderType, fieldNodes, true, false, FieldAccess.ALWAYS_FIELD, ast); + if (md != null) injectMethod(builderType, md); + } + + if (methodExists(builderMethodName, tdParent, -1) == MemberExistsResult.NOT_EXISTS) { + JCMethodDecl md = generateBuilderMethod(builderMethodName, builderClassName, tdParent, typeParams); + if (md != null) injectMethod(tdParent, md); + } + } + + private JCMethodDecl generateBuildMethod(String name, Name staticName, JCExpression returnType, java.util.List<Name> fieldNames, JavacNode type, List<JCExpression> thrownExceptions) { + TreeMaker maker = type.getTreeMaker(); + + JCExpression call; + JCStatement statement; + + ListBuffer<JCExpression> args = ListBuffer.lb(); + for (Name n : fieldNames) { + args.append(maker.Ident(n)); + } + + if (staticName == null) { + call = maker.NewClass(null, List.<JCExpression>nil(), returnType, args.toList(), null); + statement = maker.Return(call); + } else { + ListBuffer<JCExpression> typeParams = ListBuffer.lb(); + for (JCTypeParameter tp : ((JCClassDecl) type.get()).typarams) { + typeParams.append(maker.Ident(tp.name)); + } + + JCExpression fn = maker.Select(maker.Ident(((JCClassDecl) type.up().get()).name), staticName); + call = maker.Apply(typeParams.toList(), fn, args.toList()); + if (returnType instanceof JCPrimitiveTypeTree && compareCTC(getTypeTag((JCPrimitiveTypeTree) returnType), CTC_VOID)) { + statement = maker.Exec(call); + } else { + statement = maker.Return(call); + } + } + + JCBlock body = maker.Block(0, List.<JCStatement>of(statement)); + + return maker.MethodDef(maker.Modifiers(Flags.PUBLIC), type.toName(name), returnType, List.<JCTypeParameter>nil(), List.<JCVariableDecl>nil(), thrownExceptions, body, null); + } + + private JCMethodDecl generateBuilderMethod(String builderMethodName, String builderClassName, JavacNode type, List<JCTypeParameter> typeParams) { + TreeMaker maker = type.getTreeMaker(); + + ListBuffer<JCExpression> typeArgs = ListBuffer.lb(); + for (JCTypeParameter typeParam : typeParams) { + typeArgs.append(maker.Ident(typeParam.name)); + } + + JCExpression call = maker.NewClass(null, List.<JCExpression>nil(), namePlusTypeParamsToTypeReference(maker, type.toName(builderClassName), typeParams), List.<JCExpression>nil(), null); + JCStatement statement = maker.Return(call); + + JCBlock body = maker.Block(0, List.<JCStatement>of(statement)); + return maker.MethodDef(maker.Modifiers(Flags.STATIC | Flags.PUBLIC), type.toName(builderMethodName), namePlusTypeParamsToTypeReference(maker, type.toName(builderClassName), typeParams), copyTypeParams(maker, typeParams), List.<JCVariableDecl>nil(), List.<JCExpression>nil(), body, null); + } + + private java.util.List<JavacNode> addFieldsToBuilder(JavacNode builderType, java.util.List<Name> namesOfParameters, java.util.List<JCExpression> typesOfParameters, JCTree source) { + int len = namesOfParameters.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; + } + } + TreeMaker maker = builderType.getTreeMaker(); + JCModifiers mods = maker.Modifiers(Flags.PRIVATE); + JCVariableDecl newField = maker.VarDef(mods, name, cloneType(maker, typesOfParameters.get(i), source), null); + out.add(injectField(builderType, newField)); + } + + Collections.reverse(out); + return out; + } + + + private JCMethodDecl makeSetterMethodForBuilder(JavacNode builderType, JavacNode fieldNode, JCTree 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; + } + + boolean isBoolean = isBoolean(fieldNode); + String setterName = fluent ? fieldNode.getName() : TransformationsUtil.toSetterName(null, fieldNode.getName(), isBoolean); + + TreeMaker maker = builderType.getTreeMaker(); + return HandleSetter.createSetter(Flags.PUBLIC, fieldNode, maker, setterName, chain, source, List.<JCAnnotation>nil(), List.<JCAnnotation>nil()); + } + + private JavacNode findInnerClass(JavacNode parent, String name) { + for (JavacNode child : parent.down()) { + if (child.getKind() != Kind.TYPE) continue; + JCClassDecl td = (JCClassDecl) child.get(); + if (td.name.contentEquals(name)) return child; + } + return null; + } + + private JavacNode makeBuilderClass(JavacNode tdParent, String builderClassName, List<JCTypeParameter> typeParams, JCAnnotation ast) { + TreeMaker maker = tdParent.getTreeMaker(); + JCModifiers mods = maker.Modifiers(Flags.PUBLIC | Flags.STATIC); + JCClassDecl builder = ClassDef(maker, mods, tdParent.toName(builderClassName), copyTypeParams(maker, typeParams), null, List.<JCExpression>nil(), List.<JCTree>nil()); + return injectType(tdParent, builder); + } +} diff --git a/src/core/lombok/javac/handlers/HandleCleanup.java b/src/core/lombok/javac/handlers/HandleCleanup.java index c75256a5..790d8964 100644 --- a/src/core/lombok/javac/handlers/HandleCleanup.java +++ b/src/core/lombok/javac/handlers/HandleCleanup.java @@ -32,7 +32,6 @@ import lombok.javac.JavacNode; import org.mangosdk.spi.ProviderFor; -import com.sun.tools.javac.code.TypeTags; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.JCAnnotation; import com.sun.tools.javac.tree.JCTree.JCAssign; diff --git a/src/core/lombok/javac/handlers/HandleConstructor.java b/src/core/lombok/javac/handlers/HandleConstructor.java index bb883ca4..ecd982e9 100644 --- a/src/core/lombok/javac/handlers/HandleConstructor.java +++ b/src/core/lombok/javac/handlers/HandleConstructor.java @@ -29,6 +29,7 @@ import lombok.RequiredArgsConstructor; import lombok.core.AnnotationValues; import lombok.core.TransformationsUtil; import lombok.core.AST.Kind; +import lombok.experimental.Builder; import lombok.javac.JavacAnnotationHandler; import lombok.javac.JavacNode; @@ -66,7 +67,7 @@ public class HandleConstructor { String staticName = ann.staticName(); if (level == AccessLevel.NONE) return; List<JavacNode> fields = List.nil(); - new HandleConstructor().generateConstructor(typeNode, level, onConstructor, fields, staticName, false, false, annotationNode); + new HandleConstructor().generateConstructor(typeNode, level, onConstructor, fields, staticName, SkipIfConstructorExists.NO, false, annotationNode); } } @@ -84,7 +85,7 @@ public class HandleConstructor { @SuppressWarnings("deprecation") boolean suppressConstructorProperties = ann.suppressConstructorProperties(); if (level == AccessLevel.NONE) return; - new HandleConstructor().generateConstructor(typeNode, level, onConstructor, findRequiredFields(typeNode), staticName, false, suppressConstructorProperties, annotationNode); + new HandleConstructor().generateConstructor(typeNode, level, onConstructor, findRequiredFields(typeNode), staticName, SkipIfConstructorExists.NO, suppressConstructorProperties, annotationNode); } } @@ -119,11 +120,11 @@ public class HandleConstructor { @SuppressWarnings("deprecation") boolean suppressConstructorProperties = ann.suppressConstructorProperties(); if (level == AccessLevel.NONE) return; - new HandleConstructor().generateConstructor(typeNode, level, onConstructor, findAllFields(typeNode), staticName, false, suppressConstructorProperties, annotationNode); + new HandleConstructor().generateConstructor(typeNode, level, onConstructor, findAllFields(typeNode), staticName, SkipIfConstructorExists.NO, suppressConstructorProperties, annotationNode); } } - private static List<JavacNode> findAllFields(JavacNode typeNode) { + static List<JavacNode> findAllFields(JavacNode typeNode) { ListBuffer<JavacNode> fields = ListBuffer.lb(); for (JavacNode child : typeNode.down()) { if (child.getKind() != Kind.FIELD) continue; @@ -154,25 +155,34 @@ public class HandleConstructor { return true; } - public void generateRequiredArgsConstructor(JavacNode typeNode, AccessLevel level, String staticName, boolean skipIfConstructorExists, JavacNode source) { + public void generateRequiredArgsConstructor(JavacNode typeNode, AccessLevel level, String staticName, SkipIfConstructorExists skipIfConstructorExists, JavacNode source) { generateConstructor(typeNode, level, List.<JCAnnotation>nil(), findRequiredFields(typeNode), staticName, skipIfConstructorExists, false, source); } - public void generateAllArgsConstructor(JavacNode typeNode, AccessLevel level, String staticName, boolean skipIfConstructorExists, JavacNode source) { + public enum SkipIfConstructorExists { + YES, NO, I_AM_BUILDER; + } + + public void generateAllArgsConstructor(JavacNode typeNode, AccessLevel level, String staticName, SkipIfConstructorExists skipIfConstructorExists, JavacNode source) { generateConstructor(typeNode, level, List.<JCAnnotation>nil(), findAllFields(typeNode), staticName, skipIfConstructorExists, false, source); } - public void generateConstructor(JavacNode typeNode, AccessLevel level, List<JCAnnotation> onConstructor, List<JavacNode> fields, String staticName, boolean skipIfConstructorExists, boolean suppressConstructorProperties, JavacNode source) { + public void generateConstructor(JavacNode typeNode, AccessLevel level, List<JCAnnotation> onConstructor, List<JavacNode> fields, String staticName, SkipIfConstructorExists skipIfConstructorExists, boolean suppressConstructorProperties, JavacNode source) { boolean staticConstrRequired = staticName != null && !staticName.equals(""); - if (skipIfConstructorExists && constructorExists(typeNode) != MemberExistsResult.NOT_EXISTS) return; - if (skipIfConstructorExists) { + if (skipIfConstructorExists != SkipIfConstructorExists.NO && constructorExists(typeNode) != MemberExistsResult.NOT_EXISTS) return; + if (skipIfConstructorExists != SkipIfConstructorExists.NO) { for (JavacNode child : typeNode.down()) { if (child.getKind() == Kind.ANNOTATION) { - if (annotationTypeMatches(NoArgsConstructor.class, child) || + boolean skipGeneration = annotationTypeMatches(NoArgsConstructor.class, child) || annotationTypeMatches(AllArgsConstructor.class, child) || - annotationTypeMatches(RequiredArgsConstructor.class, child)) { - + annotationTypeMatches(RequiredArgsConstructor.class, child); + + if (!skipGeneration && skipIfConstructorExists == SkipIfConstructorExists.YES) { + skipGeneration = annotationTypeMatches(Builder.class, child); + } + + if (skipGeneration) { if (staticConstrRequired) { // @Data has asked us to generate a constructor, but we're going to skip this instruction, as an explicit 'make a constructor' annotation // will take care of it. However, @Data also wants a specific static name; this will be ignored; the appropriate way to do this is to use @@ -207,7 +217,7 @@ public class HandleConstructor { mods.annotations = mods.annotations.append(annotation); } - private JCMethodDecl createConstructor(AccessLevel level, List<JCAnnotation> onConstructor, JavacNode typeNode, List<JavacNode> fields, boolean suppressConstructorProperties, JCTree source) { + static JCMethodDecl createConstructor(AccessLevel level, List<JCAnnotation> onConstructor, JavacNode typeNode, List<JavacNode> fields, boolean suppressConstructorProperties, JCTree source) { TreeMaker maker = typeNode.getTreeMaker(); boolean isEnum = (((JCClassDecl) typeNode.get()).mods.flags & Flags.ENUM) != 0; @@ -243,7 +253,7 @@ public class HandleConstructor { null, List.<JCTypeParameter>nil(), params.toList(), List.<JCExpression>nil(), maker.Block(0L, nullChecks.appendList(assigns).toList()), null), source); } - private boolean isLocalType(JavacNode type) { + private static boolean isLocalType(JavacNode type) { Kind kind = type.up().getKind(); if (kind == Kind.COMPILATION_UNIT) return false; if (kind == Kind.TYPE) return isLocalType(type.up()); diff --git a/src/core/lombok/javac/handlers/HandleData.java b/src/core/lombok/javac/handlers/HandleData.java index 62183a15..858fb543 100644 --- a/src/core/lombok/javac/handlers/HandleData.java +++ b/src/core/lombok/javac/handlers/HandleData.java @@ -27,6 +27,7 @@ import lombok.Data; import lombok.core.AnnotationValues; import lombok.javac.JavacAnnotationHandler; import lombok.javac.JavacNode; +import lombok.javac.handlers.HandleConstructor.SkipIfConstructorExists; import org.mangosdk.spi.ProviderFor; @@ -50,7 +51,7 @@ public class HandleData extends JavacAnnotationHandler<Data> { String staticConstructorName = annotation.getInstance().staticConstructor(); // TODO move this to the end OR move it to the top in eclipse. - new HandleConstructor().generateRequiredArgsConstructor(typeNode, AccessLevel.PUBLIC, staticConstructorName, true, annotationNode); + new HandleConstructor().generateRequiredArgsConstructor(typeNode, AccessLevel.PUBLIC, staticConstructorName, SkipIfConstructorExists.YES, annotationNode); new HandleGetter().generateGetterForType(typeNode, annotationNode, AccessLevel.PUBLIC, true); new HandleSetter().generateSetterForType(typeNode, annotationNode, AccessLevel.PUBLIC, true); new HandleEqualsAndHashCode().generateEqualsAndHashCodeForType(typeNode, annotationNode); diff --git a/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java b/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java index 39edb143..741e7e21 100644 --- a/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java +++ b/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2012 The Project Lombok Authors. + * Copyright (C) 2009-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 @@ -25,6 +25,7 @@ import static lombok.javac.Javac.*; import static lombok.javac.handlers.JavacHandlerUtil.*; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import lombok.EqualsAndHashCode; @@ -178,17 +179,26 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler<EqualsAndHas boolean isFinal = (((JCClassDecl)typeNode.get()).mods.flags & Flags.FINAL) != 0; boolean needsCanEqual = !isFinal || !isDirectDescendantOfObject; - java.util.List<MemberExistsResult> existsResults = new ArrayList<MemberExistsResult>(); - existsResults.add(methodExists("equals", typeNode, 1)); - existsResults.add(methodExists("hashCode", typeNode, 0)); - existsResults.add(methodExists("canEqual", typeNode, 1)); - switch (Collections.max(existsResults)) { + MemberExistsResult equalsExists = methodExists("equals", typeNode, 1); + MemberExistsResult hashCodeExists = methodExists("hashCode", typeNode, 0); + MemberExistsResult canEqualExists = methodExists("canEqual", typeNode, 1); + switch (Collections.max(Arrays.asList(equalsExists, hashCodeExists, canEqualExists))) { case EXISTS_BY_LOMBOK: return; case EXISTS_BY_USER: if (whineIfExists) { String msg = String.format("Not generating equals%s: A method with one of those names already exists. (Either all or none of these methods will be generated).", needsCanEqual ? ", hashCode and canEquals" : " and hashCode"); source.addWarning(msg); + } else if (equalsExists == MemberExistsResult.NOT_EXISTS || hashCodeExists == MemberExistsResult.NOT_EXISTS) { + // This means equals OR hashCode exists and not both (or neither, but canEqual is there). + // Even though we should suppress the message about not generating these, this is such a weird and surprising situation we should ALWAYS generate a warning. + // The user code couldn't possibly (barring really weird subclassing shenanigans) be in a shippable state anyway; the implementations of these 3 methods are + // all inter-related and should be written by the same entity. + String msg = String.format("Not generating %s: One of equals, hashCode, and canEqual exists. " + + "You should either write all of these are none of these (in the latter case, lombok generates them).", + equalsExists == MemberExistsResult.NOT_EXISTS && hashCodeExists == MemberExistsResult.NOT_EXISTS ? "equals and hashCode" : + equalsExists == MemberExistsResult.NOT_EXISTS ? "equals" : "hashCode"); + source.addWarning(msg); } return; case NOT_EXISTS: diff --git a/src/core/lombok/javac/handlers/HandleFieldDefaults.java b/src/core/lombok/javac/handlers/HandleFieldDefaults.java index d32446c3..038f3e3f 100644 --- a/src/core/lombok/javac/handlers/HandleFieldDefaults.java +++ b/src/core/lombok/javac/handlers/HandleFieldDefaults.java @@ -44,7 +44,7 @@ import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; * Handles the {@code lombok.FieldDefaults} annotation for eclipse. */ @ProviderFor(JavacAnnotationHandler.class) -@HandlerPriority(-512) //-2^9; to ensure @Setter and such pick up on messing with the fields' 'final' state, run earlier. +@HandlerPriority(-2048) //-2^11; to ensure @Value picks up on messing with the fields' 'final' state, run earlier. public class HandleFieldDefaults extends JavacAnnotationHandler<FieldDefaults> { public boolean generateFieldDefaultsForType(JavacNode typeNode, JavacNode errorNode, AccessLevel level, boolean makeFinal, boolean checkForTypeLevelFieldDefaults) { if (checkForTypeLevelFieldDefaults) { @@ -108,6 +108,14 @@ public class HandleFieldDefaults extends JavacAnnotationHandler<FieldDefaults> { return; } + if (level == AccessLevel.PACKAGE) { + annotationNode.addError("Setting 'level' to PACKAGE does nothing. To force fields as package private, use the @PackagePrivate annotation on the field."); + } + + if (!makeFinal && annotation.isExplicit("makeFinal")) { + annotationNode.addError("Setting 'makeFinal' to false does nothing. To force fields to be non-final, use the @NonFinal annotation on the field."); + } + if (node == null) return; generateFieldDefaultsForType(node, annotationNode, level, makeFinal, false); diff --git a/src/core/lombok/javac/handlers/HandleGetter.java b/src/core/lombok/javac/handlers/HandleGetter.java index 2fa2a755..c5ec6f60 100644 --- a/src/core/lombok/javac/handlers/HandleGetter.java +++ b/src/core/lombok/javac/handlers/HandleGetter.java @@ -53,11 +53,9 @@ import com.sun.tools.javac.tree.JCTree.JCExpressionStatement; import com.sun.tools.javac.tree.JCTree.JCIf; import com.sun.tools.javac.tree.JCTree.JCMethodDecl; import com.sun.tools.javac.tree.JCTree.JCMethodInvocation; -import com.sun.tools.javac.tree.JCTree.JCNewClass; import com.sun.tools.javac.tree.JCTree.JCPrimitiveTypeTree; import com.sun.tools.javac.tree.JCTree.JCStatement; import com.sun.tools.javac.tree.JCTree.JCSynchronized; -import com.sun.tools.javac.tree.JCTree.JCTypeApply; import com.sun.tools.javac.tree.JCTree.JCTypeParameter; import com.sun.tools.javac.tree.JCTree.JCVariableDecl; import com.sun.tools.javac.tree.TreeMaker; @@ -256,6 +254,7 @@ public class HandleGetter extends JavacAnnotationHandler<Getter> { if (toClearOfMarkers != null) recursiveSetGeneratedBy(toClearOfMarkers, null); decl.mods.annotations = decl.mods.annotations.appendList(delegates); + copyJavadoc(field, decl, CopyJavadoc.GETTER); return decl; } @@ -287,6 +286,7 @@ public class HandleGetter extends JavacAnnotationHandler<Getter> { } private static final String AR = "java.util.concurrent.atomic.AtomicReference"; + private static final String JLO = "java.lang.Object"; private static final List<JCExpression> NIL_EXPRESSION = List.nil(); private static final java.util.Map<Object, String> TYPE_MAP; @@ -305,37 +305,50 @@ public class HandleGetter extends JavacAnnotationHandler<Getter> { private List<JCStatement> createLazyGetterBody(TreeMaker maker, JavacNode fieldNode, JCTree source) { /* - java.util.concurrent.atomic.AtomicReference<ValueType> value = this.fieldName.get(); + java.lang.Object value = this.fieldName.get(); if (value == null) { synchronized (this.fieldName) { value = this.fieldName.get(); - if (value == null) { - final ValueType actualValue = new ValueType(); - value = new java.util.concurrent.atomic.AtomicReference<ValueType>(actualValue); + if (value == null) { + final RawValueType actualValue = INITIALIZER_EXPRESSION; + [IF PRIMITIVE] + value = actualValue; + [ELSE] + value = actualValue == null ? this.fieldName : actualValue; + [END IF] this.fieldName.set(value); } } } - return value.get(); + [IF PRIMITIVE] + return (BoxedValueType) value; + [ELSE] + return (BoxedValueType) (value == this.fieldName ? null : value); + [END IF] */ ListBuffer<JCStatement> statements = ListBuffer.lb(); JCVariableDecl field = (JCVariableDecl) fieldNode.get(); JCExpression copyOfRawFieldType = copyType(maker, field); + JCExpression copyOfBoxedFieldType = null; field.type = null; + boolean isPrimitive = false; if (field.vartype instanceof JCPrimitiveTypeTree) { String boxed = TYPE_MAP.get(((JCPrimitiveTypeTree)field.vartype).typetag); if (boxed != null) { + isPrimitive = true; field.vartype = chainDotsString(fieldNode, boxed); + copyOfBoxedFieldType = chainDotsString(fieldNode, boxed); } } + if (copyOfBoxedFieldType == null) copyOfBoxedFieldType = copyType(maker, field); Name valueName = fieldNode.toName("value"); Name actualValueName = fieldNode.toName("actualValue"); - /* java.util.concurrent.atomic.AtomicReference<ValueType> value = this.fieldName.get();*/ { - JCTypeApply valueVarType = maker.TypeApply(chainDotsString(fieldNode, AR), List.of(copyType(maker, field))); + /* java.lang.Object value = this.fieldName.get();*/ { + JCExpression valueVarType = chainDotsString(fieldNode, JLO); statements.append(maker.VarDef(maker.Modifiers(0), valueName, valueVarType, callGet(fieldNode, createFieldAccessor(maker, fieldNode, FieldAccess.ALWAYS_FIELD)))); } @@ -350,15 +363,23 @@ public class HandleGetter extends JavacAnnotationHandler<Getter> { /* if (value == null) { */ { ListBuffer<JCStatement> innerIfStatements = ListBuffer.lb(); - /* ValueType actualValue = new ValueType(); */ { + /* final RawValueType actualValue = INITIALIZER_EXPRESSION; */ { innerIfStatements.append(maker.VarDef(maker.Modifiers(Flags.FINAL), actualValueName, copyOfRawFieldType, field.init)); } - /* value = new java.util.concurrent.atomic.AtomicReference<ValueType>(actualValue);*/ { - JCTypeApply valueVarType = maker.TypeApply(chainDotsString(fieldNode, AR), List.of(copyType(maker, field))); - JCNewClass newInstance = maker.NewClass(null, NIL_EXPRESSION, valueVarType, List.<JCExpression>of(maker.Ident(actualValueName)), null); - - JCStatement statement = maker.Exec(maker.Assign(maker.Ident(valueName), newInstance)); - innerIfStatements.append(statement); + /* [IF primitive] value = actualValue; */ { + if (isPrimitive) { + JCStatement statement = maker.Exec(maker.Assign(maker.Ident(valueName), maker.Ident(actualValueName))); + innerIfStatements.append(statement); + } + } + /* [ELSE] value = actualValue == null ? this.fieldName : actualValue; */ { + if (!isPrimitive) { + JCExpression actualValueIsNull = Javac.makeBinary(maker, CTC_EQUAL, maker.Ident(actualValueName), Javac.makeLiteral(maker, CTC_BOT, null)); + JCExpression thisDotFieldName = createFieldAccessor(maker, fieldNode, FieldAccess.ALWAYS_FIELD); + JCExpression ternary = maker.Conditional(actualValueIsNull, thisDotFieldName, maker.Ident(actualValueName)); + JCStatement statement = maker.Exec(maker.Assign(maker.Ident(valueName), ternary)); + innerIfStatements.append(statement); + } } /* this.fieldName.set(value); */ { JCStatement statement = callSet(fieldNode, createFieldAccessor(maker, fieldNode, FieldAccess.ALWAYS_FIELD), maker.Ident(valueName)); @@ -377,15 +398,25 @@ public class HandleGetter extends JavacAnnotationHandler<Getter> { JCIf ifStatement = maker.If(isNull, maker.Block(0, List.<JCStatement>of(synchronizedStatement)), null); statements.append(ifStatement); } - /* return value.get(); */ - statements.append(maker.Return(callGet(fieldNode, maker.Ident(valueName)))); + /* [IF PRIMITIVE] return (BoxedValueType) value; */ { + if (isPrimitive) { + statements.append(maker.Return(maker.TypeCast(copyOfBoxedFieldType, maker.Ident(valueName)))); + } + } + /* [ELSE] return (BoxedValueType) (value == this.fieldName ? null : value); */ { + if (!isPrimitive) { + JCExpression valueEqualsSelf = Javac.makeBinary(maker, CTC_EQUAL, maker.Ident(valueName), createFieldAccessor(maker, fieldNode, FieldAccess.ALWAYS_FIELD)); + JCExpression ternary = maker.Conditional(valueEqualsSelf, Javac.makeLiteral(maker, CTC_BOT, null), maker.Ident(valueName)); + JCExpression typeCast = maker.TypeCast(copyOfBoxedFieldType, maker.Parens(ternary)); + statements.append(maker.Return(typeCast)); + } + } // update the field type and init last - /* private final java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<ValueType> fieldName = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<ValueType>>(); */ { + /* private final java.util.concurrent.atomic.AtomicReference<Object> fieldName = new java.util.concurrent.atomic.AtomicReference<Object>(); */ { field.vartype = recursiveSetGeneratedBy( - maker.TypeApply(chainDotsString(fieldNode, AR), List.<JCExpression>of(maker.TypeApply(chainDotsString(fieldNode, AR), List.of(copyType(maker, field))))), - source); + maker.TypeApply(chainDotsString(fieldNode, AR), List.<JCExpression>of(chainDotsString(fieldNode, JLO))), source); field.init = recursiveSetGeneratedBy(maker.NewClass(null, NIL_EXPRESSION, copyType(maker, field), NIL_EXPRESSION, null), source); } diff --git a/src/core/lombok/javac/handlers/HandleLog.java b/src/core/lombok/javac/handlers/HandleLog.java index 62a55c44..35a32be5 100644 --- a/src/core/lombok/javac/handlers/HandleLog.java +++ b/src/core/lombok/javac/handlers/HandleLog.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2012 The Project Lombok Authors. + * 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 @@ -82,7 +82,7 @@ public class HandleLog { private static boolean createField(LoggingFramework framework, JavacNode typeNode, JCFieldAccess loggingType, JCTree source) { TreeMaker maker = typeNode.getTreeMaker(); - // private static final <loggerType> log = <factoryMethod>(<parameter>); + // private static final <loggerType> log = <factoryMethod>(<parameter>); JCExpression loggerType = chainDotsString(typeNode, framework.getLoggerTypeName()); JCExpression factoryMethod = chainDotsString(typeNode, framework.getLoggerFactoryMethodName()); @@ -128,6 +128,16 @@ public class HandleLog { } /** + * Handles the {@link lombok.extern.log4j.Log4j2} annotation for javac. + */ + @ProviderFor(JavacAnnotationHandler.class) + public static class HandleLog4j2Log extends JavacAnnotationHandler<lombok.extern.log4j.Log4j2> { + @Override public void handle(AnnotationValues<lombok.extern.log4j.Log4j2> annotation, JCAnnotation ast, JavacNode annotationNode) { + processAnnotation(LoggingFramework.LOG4J2, annotation, annotationNode); + } + } + + /** * Handles the {@link lombok.extern.slf4j.Slf4j} annotation for javac. */ @ProviderFor(JavacAnnotationHandler.class) @@ -163,6 +173,9 @@ public class HandleLog { // private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(TargetType.class); LOG4J(lombok.extern.log4j.Log4j.class, "org.apache.log4j.Logger", "org.apache.log4j.Logger.getLogger"), + // private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(TargetType.class); + LOG4J2(lombok.extern.log4j.Log4j2.class, "org.apache.logging.log4j.Logger", "org.apache.logging.log4j.LogManager.getLogger"), + // private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TargetType.class); SLF4J(lombok.extern.slf4j.Slf4j.class, "org.slf4j.Logger", "org.slf4j.LoggerFactory.getLogger"), diff --git a/src/core/lombok/javac/handlers/HandleSetter.java b/src/core/lombok/javac/handlers/HandleSetter.java index 2136024e..c3ee10a3 100644 --- a/src/core/lombok/javac/handlers/HandleSetter.java +++ b/src/core/lombok/javac/handlers/HandleSetter.java @@ -26,10 +26,6 @@ import static lombok.javac.handlers.JavacHandlerUtil.*; import java.util.Collection; -import javax.lang.model.type.NoType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeVisitor; - import lombok.AccessLevel; import lombok.Setter; import lombok.core.AST.Kind; @@ -43,7 +39,6 @@ import lombok.javac.handlers.JavacHandlerUtil.FieldAccess; import org.mangosdk.spi.ProviderFor; import com.sun.tools.javac.code.Flags; -import com.sun.tools.javac.code.Type; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.JCAnnotation; import com.sun.tools.javac.tree.JCTree.JCAssign; @@ -195,9 +190,13 @@ public class HandleSetter extends JavacAnnotationHandler<Setter> { injectMethod(fieldNode.up(), createdSetter); } - private JCMethodDecl createSetter(long access, JavacNode field, TreeMaker treeMaker, JCTree source, List<JCAnnotation> onMethod, List<JCAnnotation> onParam) { + static JCMethodDecl createSetter(long access, JavacNode field, TreeMaker treeMaker, JCTree source, List<JCAnnotation> onMethod, List<JCAnnotation> onParam) { String setterName = toSetterName(field); boolean returnThis = shouldReturnThis(field); + return createSetter(access, field, treeMaker, setterName, returnThis, source, onMethod, onParam); + } + + static JCMethodDecl createSetter(long access, JavacNode field, TreeMaker treeMaker, String setterName, boolean shouldReturnThis, JCTree source, List<JCAnnotation> onMethod, List<JCAnnotation> onParam) { if (setterName == null) return null; JCVariableDecl fieldDecl = (JCVariableDecl) field.get(); @@ -223,17 +222,17 @@ public class HandleSetter extends JavacAnnotationHandler<Setter> { } JCExpression methodType = null; - if (returnThis) { + 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(new JCNoType(CTC_VOID)); - returnThis = false; + methodType = treeMaker.Type(Javac.createVoidType(treeMaker, CTC_VOID)); + shouldReturnThis = false; } - if (returnThis) { + if (shouldReturnThis) { JCReturn returnStatement = treeMaker.Return(treeMaker.Ident(field.toName("this"))); statements.append(returnStatement); } @@ -249,26 +248,9 @@ public class HandleSetter extends JavacAnnotationHandler<Setter> { annsOnMethod = annsOnMethod.prepend(treeMaker.Annotation(chainDots(field, "java", "lang", "Deprecated"), List.<JCExpression>nil())); } - return recursiveSetGeneratedBy(treeMaker.MethodDef(treeMaker.Modifiers(access, annsOnMethod), methodName, methodType, + JCMethodDecl decl = recursiveSetGeneratedBy(treeMaker.MethodDef(treeMaker.Modifiers(access, annsOnMethod), methodName, methodType, methodGenericParams, parameters, throwsClauses, methodBody, annotationMethodDefaultValue), source); - } - - private static class JCNoType extends Type implements NoType { - public JCNoType(Object tag) { - //FIX - super(1, null); - } - - @Override - public TypeKind getKind() { - if (Javac.compareCTC(tag, CTC_VOID)) return TypeKind.VOID; - if (Javac.compareCTC(tag, CTC_NONE)) return TypeKind.NONE; - throw new AssertionError("Unexpected tag: " + tag); - } - - @Override - public <R, P> R accept(TypeVisitor<R, P> v, P p) { - return v.visitNoType(this, p); - } + copyJavadoc(field, decl, CopyJavadoc.SETTER); + return decl; } } diff --git a/src/core/lombok/javac/handlers/HandleSneakyThrows.java b/src/core/lombok/javac/handlers/HandleSneakyThrows.java index a5bd74e7..c818f630 100644 --- a/src/core/lombok/javac/handlers/HandleSneakyThrows.java +++ b/src/core/lombok/javac/handlers/HandleSneakyThrows.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2011 The Project Lombok Authors. + * Copyright (C) 2009-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 @@ -36,8 +36,6 @@ 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.JCExpressionStatement; -import com.sun.tools.javac.tree.JCTree.JCMethodInvocation; import com.sun.tools.javac.tree.TreeMaker; import com.sun.tools.javac.tree.JCTree.JCAnnotation; import com.sun.tools.javac.tree.JCTree.JCBlock; @@ -84,13 +82,20 @@ public class HandleSneakyThrows extends JavacAnnotationHandler<SneakyThrows> { return; } - if (method.body == null) return; - if (method.body.stats.isEmpty()) return; + if (method.body == null || method.body.stats.isEmpty()) { + generateEmptyBlockWarning(methodNode, annotation, false); + return; + } final JCStatement constructorCall = method.body.stats.get(0); final boolean isConstructorCall = isConstructorCall(constructorCall); List<JCStatement> contents = isConstructorCall ? method.body.stats.tail : method.body.stats; + if (contents == null || contents.isEmpty()) { + generateEmptyBlockWarning(methodNode, annotation, true); + return; + } + for (String exception : exceptions) { contents = List.of(buildTryCatchBlock(methodNode, contents, exception, annotation.get())); } @@ -99,14 +104,14 @@ public class HandleSneakyThrows extends JavacAnnotationHandler<SneakyThrows> { methodNode.rebuild(); } - private boolean isConstructorCall(final JCStatement supect) { - if (!(supect instanceof JCExpressionStatement)) return false; - final JCExpression supectExpression = ((JCExpressionStatement) supect).expr; - if (!(supectExpression instanceof JCMethodInvocation)) return false; - final String methodName = ((JCMethodInvocation) supectExpression).meth.toString(); - return "super".equals(methodName) || "this".equals(methodName); + private void generateEmptyBlockWarning(JavacNode methodNode, JavacNode annotation, boolean hasConstructorCall) { + if (hasConstructorCall) { + annotation.addWarning("Calls to sibling / super constructors are always excluded from @SneakyThrows; @SneakyThrows has been ignored because there is no other code in this constructor."); + } else { + annotation.addWarning("This method or constructor is empty; @SneakyThrows has been ignored."); + } } - + private JCStatement buildTryCatchBlock(JavacNode node, List<JCStatement> contents, String exception, JCTree source) { TreeMaker maker = node.getTreeMaker(); diff --git a/src/core/lombok/javac/handlers/HandleToString.java b/src/core/lombok/javac/handlers/HandleToString.java index c37fe149..8297a4db 100644 --- a/src/core/lombok/javac/handlers/HandleToString.java +++ b/src/core/lombok/javac/handlers/HandleToString.java @@ -24,6 +24,8 @@ package lombok.javac.handlers; import static lombok.javac.handlers.JavacHandlerUtil.*; import static lombok.javac.Javac.*; +import java.util.Collection; + import lombok.ToString; import lombok.core.AnnotationValues; import lombok.core.AST.Kind; @@ -165,7 +167,7 @@ public class HandleToString extends JavacAnnotationHandler<ToString> { } } - private JCMethodDecl createToString(JavacNode typeNode, List<JavacNode> fields, boolean includeFieldNames, boolean callSuper, FieldAccess fieldAccess, JCTree source) { + static JCMethodDecl createToString(JavacNode typeNode, Collection<JavacNode> fields, boolean includeFieldNames, boolean callSuper, FieldAccess fieldAccess, JCTree source) { TreeMaker maker = typeNode.getTreeMaker(); JCAnnotation overrideAnnotation = maker.Annotation(chainDots(typeNode, "java", "lang", "Override"), List.<JCExpression>nil()); @@ -199,18 +201,22 @@ public class HandleToString extends JavacAnnotationHandler<ToString> { } for (JavacNode fieldNode : fields) { - JCVariableDecl field = (JCVariableDecl) fieldNode.get(); JCExpression expr; JCExpression fieldAccessor = createFieldAccessor(maker, fieldNode, fieldAccess); - if (getFieldType(fieldNode, fieldAccess) instanceof JCArrayTypeTree) { - boolean multiDim = ((JCArrayTypeTree)field.vartype).elemtype instanceof JCArrayTypeTree; - boolean primitiveArray = ((JCArrayTypeTree)field.vartype).elemtype instanceof JCPrimitiveTypeTree; - boolean useDeepTS = multiDim || !primitiveArray; - - JCExpression hcMethod = chainDots(typeNode, "java", "util", "Arrays", useDeepTS ? "deepToString" : "toString"); - expr = maker.Apply(List.<JCExpression>nil(), hcMethod, List.<JCExpression>of(fieldAccessor)); + JCExpression fieldType = getFieldType(fieldNode, fieldAccess); + + // The distinction between primitive and object will be useful if we ever add a 'hideNulls' option. + boolean fieldIsPrimitive = fieldType instanceof JCPrimitiveTypeTree; + boolean fieldIsPrimitiveArray = fieldType instanceof JCArrayTypeTree && ((JCArrayTypeTree) fieldType).elemtype instanceof JCPrimitiveTypeTree; + boolean fieldIsObjectArray = !fieldIsPrimitiveArray && fieldType instanceof JCArrayTypeTree; + @SuppressWarnings("unused") + boolean fieldIsObject = !fieldIsPrimitive && !fieldIsPrimitiveArray && !fieldIsObjectArray; + + if (fieldIsPrimitiveArray || fieldIsObjectArray) { + JCExpression tsMethod = chainDots(typeNode, "java", "util", "Arrays", fieldIsObjectArray ? "deepToString" : "toString"); + expr = maker.Apply(List.<JCExpression>nil(), tsMethod, List.<JCExpression>of(fieldAccessor)); } else expr = fieldAccessor; if (first) { @@ -238,7 +244,7 @@ public class HandleToString extends JavacAnnotationHandler<ToString> { List.<JCTypeParameter>nil(), List.<JCVariableDecl>nil(), List.<JCExpression>nil(), body, null), source); } - private String getTypeName(JavacNode typeNode) { + private static String getTypeName(JavacNode typeNode) { String typeName = ((JCClassDecl) typeNode.get()).name.toString(); JavacNode upType = typeNode.up(); while (upType.getKind() == Kind.TYPE) { diff --git a/src/core/lombok/javac/handlers/HandleValue.java b/src/core/lombok/javac/handlers/HandleValue.java index f5b10bc1..15fb4781 100644 --- a/src/core/lombok/javac/handlers/HandleValue.java +++ b/src/core/lombok/javac/handlers/HandleValue.java @@ -22,13 +22,17 @@ package lombok.javac.handlers; import static lombok.javac.handlers.JavacHandlerUtil.*; + +import java.lang.annotation.Annotation; + import lombok.AccessLevel; import lombok.core.AnnotationValues; import lombok.core.HandlerPriority; import lombok.experimental.NonFinal; -import lombok.experimental.Value; +import lombok.Value; import lombok.javac.JavacAnnotationHandler; import lombok.javac.JavacNode; +import lombok.javac.handlers.HandleConstructor.SkipIfConstructorExists; import org.mangosdk.spi.ProviderFor; @@ -44,7 +48,9 @@ import com.sun.tools.javac.tree.JCTree.JCModifiers; @HandlerPriority(-512) //-2^9; to ensure @EqualsAndHashCode and such pick up on this handler making the class final and messing with the fields' access levels, run earlier. public class HandleValue extends JavacAnnotationHandler<Value> { @Override public void handle(AnnotationValues<Value> annotation, JCAnnotation ast, JavacNode annotationNode) { - deleteAnnotationIfNeccessary(annotationNode, Value.class); + @SuppressWarnings("deprecation") + Class<? extends Annotation> oldExperimentalValue = lombok.experimental.Value.class; + deleteAnnotationIfNeccessary(annotationNode, Value.class, oldExperimentalValue); JavacNode typeNode = annotationNode.up(); boolean notAClass = !isClass(typeNode); @@ -65,7 +71,7 @@ public class HandleValue extends JavacAnnotationHandler<Value> { new HandleFieldDefaults().generateFieldDefaultsForType(typeNode, annotationNode, AccessLevel.PRIVATE, true, true); // TODO move this to the end OR move it to the top in eclipse. - new HandleConstructor().generateAllArgsConstructor(typeNode, AccessLevel.PUBLIC, staticConstructorName, true, annotationNode); + new HandleConstructor().generateAllArgsConstructor(typeNode, AccessLevel.PUBLIC, staticConstructorName, SkipIfConstructorExists.YES, annotationNode); new HandleGetter().generateGetterForType(typeNode, annotationNode, AccessLevel.PUBLIC, true); new HandleEqualsAndHashCode().generateEqualsAndHashCodeForType(typeNode, annotationNode); new HandleToString().generateToStringForType(typeNode, annotationNode); diff --git a/src/core/lombok/javac/handlers/HandleWither.java b/src/core/lombok/javac/handlers/HandleWither.java index 62ed63f1..514e27dd 100644 --- a/src/core/lombok/javac/handlers/HandleWither.java +++ b/src/core/lombok/javac/handlers/HandleWither.java @@ -34,6 +34,7 @@ import lombok.experimental.Wither; import lombok.javac.Javac; import lombok.javac.JavacAnnotationHandler; import lombok.javac.JavacNode; +import lombok.javac.handlers.JavacHandlerUtil.CopyJavadoc; import lombok.javac.handlers.JavacHandlerUtil.FieldAccess; import org.mangosdk.spi.ProviderFor; @@ -265,7 +266,9 @@ public class HandleWither extends JavacAnnotationHandler<Wither> { if (isFieldDeprecated(field)) { annsOnMethod = annsOnMethod.prepend(treeMaker.Annotation(chainDots(field, "java", "lang", "Deprecated"), List.<JCExpression>nil())); } - return recursiveSetGeneratedBy(treeMaker.MethodDef(treeMaker.Modifiers(access, annsOnMethod), methodName, returnType, + JCMethodDecl decl = recursiveSetGeneratedBy(treeMaker.MethodDef(treeMaker.Modifiers(access, annsOnMethod), methodName, returnType, methodGenericParams, parameters, throwsClauses, methodBody, annotationMethodDefaultValue), source); + copyJavadoc(field, decl, CopyJavadoc.WITHER); + return decl; } } diff --git a/src/core/lombok/javac/handlers/JavacHandlerUtil.java b/src/core/lombok/javac/handlers/JavacHandlerUtil.java index 178b82a2..211f5d36 100644 --- a/src/core/lombok/javac/handlers/JavacHandlerUtil.java +++ b/src/core/lombok/javac/handlers/JavacHandlerUtil.java @@ -21,6 +21,7 @@ */ package lombok.javac.handlers; +import static lombok.core.TransformationsUtil.INVALID_ON_BUILDERS; import static lombok.javac.Javac.*; import java.lang.annotation.Annotation; @@ -31,6 +32,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.WeakHashMap; +import java.util.regex.Matcher; import java.util.regex.Pattern; import lombok.AccessLevel; @@ -51,9 +53,11 @@ import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.JCAnnotation; import com.sun.tools.javac.tree.JCTree.JCArrayTypeTree; import com.sun.tools.javac.tree.JCTree.JCAssign; +import com.sun.tools.javac.tree.JCTree.JCBlock; import com.sun.tools.javac.tree.JCTree.JCClassDecl; import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; import com.sun.tools.javac.tree.JCTree.JCExpression; +import com.sun.tools.javac.tree.JCTree.JCExpressionStatement; import com.sun.tools.javac.tree.JCTree.JCFieldAccess; import com.sun.tools.javac.tree.JCTree.JCIdent; import com.sun.tools.javac.tree.JCTree.JCImport; @@ -122,6 +126,7 @@ public class JavacHandlerUtil { } public static <T extends JCTree> T recursiveSetGeneratedBy(T node, JCTree source) { + if (node == null) return null; setGeneratedBy(node, source); node.accept(new MarkingScanner(source)); @@ -186,7 +191,7 @@ public class JavacHandlerUtil { public static boolean typeMatches(Class<?> type, JavacNode node, JCTree typeNode) { String typeName = typeNode.toString(); - TypeResolver resolver = new TypeResolver(node.getPackageDeclaration(), node.getImportStatements()); + TypeResolver resolver = new TypeResolver(node.getImportList()); return resolver.typeMatches(node, type.getName(), typeName); } @@ -278,7 +283,22 @@ public class JavacHandlerUtil { * then removes any import statement that imports this exact annotation (not star imports). * Only does this if the DeleteLombokAnnotations class is in the context. */ + @SuppressWarnings("unchecked") public static void deleteAnnotationIfNeccessary(JavacNode annotation, Class<? extends Annotation> annotationType) { + deleteAnnotationIfNeccessary0(annotation, annotationType); + } + + /** + * Removes the annotation from javac's AST (it remains in lombok's AST), + * then removes any import statement that imports this exact annotation (not star imports). + * Only does this if the DeleteLombokAnnotations class is in the context. + */ + @SuppressWarnings("unchecked") + public static void deleteAnnotationIfNeccessary(JavacNode annotation, Class<? extends Annotation> annotationType1, Class<? extends Annotation> annotationType2) { + deleteAnnotationIfNeccessary0(annotation, annotationType1, annotationType2); + } + + private static void deleteAnnotationIfNeccessary0(JavacNode annotation, Class<? extends Annotation>... annotationTypes) { if (inNetbeansEditor(annotation)) return; if (!annotation.shouldDeleteLombokAnnotations()) return; JavacNode parentNode = annotation.directUp(); @@ -306,7 +326,10 @@ public class JavacHandlerUtil { return; } - deleteImportFromCompilationUnit(annotation, annotationType.getName()); + parentNode.getAst().setChanged(); + for (Class<?> annotationType : annotationTypes) { + deleteImportFromCompilationUnit(annotation, annotationType.getName()); + } } public static void deleteImportFromCompilationUnit(JavacNode node, String name) { @@ -425,8 +448,12 @@ public class JavacHandlerUtil { } } - private static boolean isBoolean(JavacNode field) { + public static boolean isBoolean(JavacNode field) { JCExpression varType = ((JCVariableDecl) field.get()).vartype; + return isBoolean(varType); + } + + public static boolean isBoolean(JCExpression varType) { return varType != null && varType.toString().equals("boolean"); } @@ -543,6 +570,23 @@ public class JavacHandlerUtil { return MemberExistsResult.NOT_EXISTS; } + public static boolean isConstructorCall(final JCStatement statement) { + if (!(statement instanceof JCExpressionStatement)) return false; + JCExpression expr = ((JCExpressionStatement) statement).expr; + if (!(expr instanceof JCMethodInvocation)) return false; + JCExpression invocation = ((JCMethodInvocation) expr).meth; + String name; + if (invocation instanceof JCFieldAccess) { + name = ((JCFieldAccess) invocation).name.toString(); + } else if (invocation instanceof JCIdent) { + name = ((JCIdent) invocation).name.toString(); + } else { + name = ""; + } + + return "super".equals(name) || "this".equals(name); + } + /** * Turns an {@code AccessLevel} instance into the flag bit used by javac. */ @@ -710,11 +754,11 @@ public class JavacHandlerUtil { * * Also takes care of updating the JavacAST. */ - public static void injectField(JavacNode typeNode, JCVariableDecl field) { - injectField(typeNode, field, false); + public static JavacNode injectField(JavacNode typeNode, JCVariableDecl field) { + return injectField(typeNode, field, false); } - private static void injectField(JavacNode typeNode, JCVariableDecl field, boolean addSuppressWarnings) { + private static JavacNode injectField(JavacNode typeNode, JCVariableDecl field, boolean addSuppressWarnings) { JCClassDecl type = (JCClassDecl) typeNode.get(); if (addSuppressWarnings) addSuppressWarningsAll(field.mods, typeNode, field.pos, getGeneratedBy(field)); @@ -740,7 +784,7 @@ public class JavacHandlerUtil { insertAfter.tail = fieldEntry; } - typeNode.add(field, Kind.FIELD); + return typeNode.add(field, Kind.FIELD); } private static boolean isEnumConstant(final JCVariableDecl field) { @@ -781,6 +825,20 @@ public class JavacHandlerUtil { typeNode.add(method, Kind.METHOD); } + /** + * Adds an inner type (class, interface, enum) to the given type. Cannot inject top-level types. + * + * @param typeNode parent type to inject new type into + * @param type New type (class, interface, etc) to inject. + * @return + */ + public static JavacNode injectType(final JavacNode typeNode, final JCClassDecl type) { + JCClassDecl typeDecl = (JCClassDecl) typeNode.get(); + addSuppressWarningsAll(type.mods, typeNode, type.pos, getGeneratedBy(type)); + typeDecl.defs = typeDecl.defs.append(type); + return typeNode.add(type, Kind.TYPE); + } + private static void addSuppressWarningsAll(JCModifiers mods, JavacNode node, int pos, JCTree source) { TreeMaker maker = node.getTreeMaker(); JCExpression suppressWarningsType = chainDots(node, "java", "lang", "SuppressWarnings"); @@ -890,7 +948,8 @@ public class JavacHandlerUtil { JCExpression npe = chainDots(variable, "java", "lang", "NullPointerException"); JCTree exception = maker.NewClass(null, List.<JCExpression>nil(), npe, List.<JCExpression>of(maker.Literal(fieldName.toString())), null); JCStatement throwStatement = maker.Throw(exception); - return maker.If(Javac.makeBinary(maker, CTC_EQUAL, maker.Ident(fieldName), Javac.makeLiteral(maker, CTC_BOT, null)), throwStatement, null); + JCBlock throwBlock = maker.Block(0, List.of(throwStatement)); + return maker.If(Javac.makeBinary(maker, CTC_EQUAL, maker.Ident(fieldName), Javac.makeLiteral(maker, CTC_BOT, null)), throwBlock, null); } /** @@ -991,6 +1050,49 @@ public class JavacHandlerUtil { return result.toList(); } + public static List<JCTypeParameter> copyTypeParams(TreeMaker maker, List<JCTypeParameter> params) { + if (params == null || params.isEmpty()) return params; + ListBuffer<JCTypeParameter> out = ListBuffer.lb(); + for (JCTypeParameter tp : params) out.append(maker.TypeParameter(tp.name, tp.bounds)); + return out.toList(); + } + + public static JCExpression namePlusTypeParamsToTypeReference(TreeMaker maker, Name typeName, List<JCTypeParameter> params) { + ListBuffer<JCExpression> typeArgs = ListBuffer.lb(); + + if (!params.isEmpty()) { + for (JCTypeParameter param : params) { + typeArgs.append(maker.Ident(param.name)); + } + + return maker.TypeApply(maker.Ident(typeName), typeArgs.toList()); + } + + return maker.Ident(typeName); + } + + public static void sanityCheckForMethodGeneratingAnnotationsOnBuilderClass(JavacNode typeNode, JavacNode errorNode) { + List<String> disallowed = List.nil(); + for (JavacNode child : typeNode.down()) { + for (Class<? extends java.lang.annotation.Annotation> annType : INVALID_ON_BUILDERS) { + if (annotationTypeMatches(annType, child)) { + disallowed = disallowed.append(annType.getSimpleName()); + } + } + } + + int size = disallowed.size(); + if (size == 0) return; + if (size == 1) { + errorNode.addError("@" + disallowed.head + " is not allowed on builder classes."); + return; + } + StringBuilder out = new StringBuilder(); + for (String a : disallowed) out.append("@").append(a).append(", "); + out.setLength(out.length() - 2); + errorNode.addError(out.append(" are not allowed on builder classes.").toString()); + } + static List<JCAnnotation> copyAnnotations(List<? extends JCExpression> in) { ListBuffer<JCAnnotation> out = ListBuffer.lb(); for (JCExpression expr : in) { @@ -1090,4 +1192,103 @@ public class JavacHandlerUtil { // This is somewhat unsafe, but it's better than outright throwing an exception here. Returning null will just cause an exception down the pipeline. return (JCExpression) in; } + + private static final Pattern SECTION_FINDER = Pattern.compile("^\\s*\\**\\s*[-*][-*]+\\s*([GS]ETTER|WITHER)\\s*[-*][-*]+\\s*\\**\\s*$", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE); + + private static String stripLinesWithTagFromJavadoc(String javadoc, String regexpFragment) { + Pattern p = Pattern.compile("^\\s*\\**\\s*" + regexpFragment + "\\s*\\**\\s*$", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE); + Matcher m = p.matcher(javadoc); + return m.replaceAll(""); + } + + private static String[] splitJavadocOnSectionIfPresent(String javadoc, String sectionName) { + Matcher m = SECTION_FINDER.matcher(javadoc); + int getterSectionHeaderStart = -1; + int getterSectionStart = -1; + int getterSectionEnd = -1; + while (m.find()) { + if (m.group(1).equalsIgnoreCase(sectionName)) { + getterSectionStart = m.end() + 1; + getterSectionHeaderStart = m.start(); + } else if (getterSectionStart != -1) { + getterSectionEnd = m.start(); + } + } + + if (getterSectionStart != -1) { + if (getterSectionEnd != -1) { + return new String[] {javadoc.substring(getterSectionStart, getterSectionEnd), javadoc.substring(0, getterSectionHeaderStart) + javadoc.substring(getterSectionEnd)}; + } else { + return new String[] {javadoc.substring(getterSectionStart), javadoc.substring(0, getterSectionHeaderStart)}; + } + } + + return null; + } + + public static enum CopyJavadoc { + VERBATIM, GETTER { + @Override public String[] split(String javadoc) { + // step 1: Check if there is a 'GETTER' section. If yes, that becomes the new method's javadoc and we strip that from the original. + String[] out = splitJavadocOnSectionIfPresent(javadoc, "GETTER"); + if (out != null) return out; + // failing that, create a copy, but strip @return from the original and @param from the copy. + String copy = javadoc; + javadoc = stripLinesWithTagFromJavadoc(javadoc, "@returns?\\s+.*"); + copy = stripLinesWithTagFromJavadoc(copy, "@param(?:eter)?\\s+.*"); + return new String[] {copy, javadoc}; + } + }, + SETTER { + @Override public String[] split(String javadoc) { + return splitForSetters(javadoc, "SETTER"); + } + }, + WITHER { + @Override public String[] split(String javadoc) { + return splitForSetters(javadoc, "WITHER"); + } + }; + + private static String[] splitForSetters(String javadoc, String sectionName) { + // step 1: Check if there is a 'SETTER' section. If yes, that becomes the new one and we strip that from the original. + String[] out = splitJavadocOnSectionIfPresent(javadoc, sectionName); + if (out != null) return out; + // failing that, create a copy, but strip @param from the original and @return from the copy. + String copy = javadoc; + javadoc = stripLinesWithTagFromJavadoc(javadoc, "@param(?:eter)?\\s+.*"); + copy = stripLinesWithTagFromJavadoc(copy, "@returns?\\s+.*"); + return new String[] {copy, javadoc}; + } + + /** Splits the javadoc into the section to be copied (ret[0]) and the section to replace the original with (ret[1]) */ + public String[] split(String javadoc) { + return new String[] {javadoc, javadoc}; + } + } + + /** + * Copies javadoc on one node to the other. + * + * in 'GETTER' copyMode, first a 'GETTER' segment is searched for. If it exists, that will become the javadoc for the 'to' node, and this section is + * stripped out of the 'from' node. If no 'GETTER' segment is found, then the entire javadoc is taken minus any {@code @param} lines. any {@code @return} lines + * are stripped from 'from'. + * + * in 'SETTER' mode, stripping works similarly to 'GETTER' mode, except {@code param} are copied and stripped from the original and {@code @return} are skipped. + */ + public static void copyJavadoc(JavacNode from, JCTree to, CopyJavadoc copyMode) { + if (copyMode == null) copyMode = CopyJavadoc.VERBATIM; + try { + JCCompilationUnit cu = ((JCCompilationUnit) from.top().get()); + if (cu.docComments != null) { + String javadoc = cu.docComments.get(from.get()); + + if (javadoc != null) { + String[] filtered = copyMode.split(javadoc); + cu.docComments.put(to, filtered[0]); + cu.docComments.put(from.get(), filtered[1]); + } + } + } catch (Exception ignore) {} + } } diff --git a/src/core/lombok/javac/handlers/NonNullHandler.java b/src/core/lombok/javac/handlers/NonNullHandler.java new file mode 100644 index 00000000..d74fb55d --- /dev/null +++ b/src/core/lombok/javac/handlers/NonNullHandler.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2013 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.javac.handlers; + +import static lombok.javac.Javac.*; +import static lombok.javac.handlers.JavacHandlerUtil.*; + +import org.mangosdk.spi.ProviderFor; + +import com.sun.tools.javac.tree.JCTree.JCAnnotation; +import com.sun.tools.javac.tree.JCTree.JCBinary; +import com.sun.tools.javac.tree.JCTree.JCBlock; +import com.sun.tools.javac.tree.JCTree.JCExpression; +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.JCParens; +import com.sun.tools.javac.tree.JCTree.JCStatement; +import com.sun.tools.javac.tree.JCTree.JCThrow; +import com.sun.tools.javac.tree.JCTree.JCVariableDecl; +import com.sun.tools.javac.util.List; + +import lombok.NonNull; +import lombok.core.AnnotationValues; +import lombok.core.AST.Kind; +import lombok.javac.JavacAnnotationHandler; +import lombok.javac.JavacNode; + +@ProviderFor(JavacAnnotationHandler.class) +public class NonNullHandler extends JavacAnnotationHandler<NonNull> { + @Override public void handle(AnnotationValues<NonNull> annotation, JCAnnotation ast, JavacNode annotationNode) { + if (annotationNode.up().getKind() == Kind.FIELD) { + // This is meaningless unless the field is used to generate a method (@Setter, @RequiredArgsConstructor, etc), + // but in that case those handlers will take care of it. However, we DO check if the annotation is applied to + // a primitive, because those handlers trigger on any annotation named @NonNull and we only want the warning + // behaviour on _OUR_ 'lombok.NonNull'. + + try { + if (isPrimitive(((JCVariableDecl) annotationNode.up().get()).vartype)) { + annotationNode.addWarning("@NonNull is meaningless on a primitive."); + } + } catch (Exception ignore) {} + + return; + } + + if (annotationNode.up().getKind() != Kind.ARGUMENT) return; + + JCMethodDecl declaration; + + try { + declaration = (JCMethodDecl) annotationNode.up().up().get(); + } catch (Exception e) { + return; + } + + if (JavacHandlerUtil.isGenerated(declaration)) return; + + // Possibly, if 'declaration instanceof ConstructorDeclaration', fetch declaration.constructorCall, search it for any references to our parameter, + // and if they exist, create a new method in the class: 'private static <T> T lombok$nullCheck(T expr, String msg) {if (expr == null) throw NPE; return expr;}' and + // wrap all references to it in the super/this to a call to this method. + + JCStatement nullCheck = recursiveSetGeneratedBy(generateNullCheck(annotationNode.getTreeMaker(), annotationNode.up()), ast); + + if (nullCheck == null) { + // @NonNull applied to a primitive. Kinda pointless. Let's generate a warning. + annotationNode.addWarning("@NonNull is meaningless on a primitive."); + return; + } + + List<JCStatement> statements = declaration.body.stats; + + String expectedName = annotationNode.up().getName(); + for (JCStatement stat : statements) { + if (JavacHandlerUtil.isConstructorCall(stat)) continue; + String varNameOfNullCheck = returnVarNameIfNullCheck(stat); + if (varNameOfNullCheck == null) break; + if (varNameOfNullCheck.equals(expectedName)) return; + } + + List<JCStatement> tail = statements; + List<JCStatement> head = List.nil(); + for (JCStatement stat : statements) { + if (JavacHandlerUtil.isConstructorCall(stat) || JavacHandlerUtil.isGenerated(stat)) { + tail = tail.tail; + head = head.prepend(stat); + continue; + } + break; + } + + List<JCStatement> newList = tail.prepend(nullCheck); + for (JCStatement stat : head) newList = newList.prepend(stat); + declaration.body.stats = newList; + } + + /** + * Checks if the statement is of the form 'if (x == null) {throw WHATEVER;}, + * where the block braces are optional. If it is of this form, returns "x". + * If it is not of this form, returns null. + */ + private String returnVarNameIfNullCheck(JCStatement stat) { + if (!(stat instanceof JCIf)) return null; + + /* Check that the if's statement is a throw statement, possibly in a block. */ { + JCStatement then = ((JCIf) stat).thenpart; + if (then instanceof JCBlock) { + List<JCStatement> stats = ((JCBlock) then).stats; + if (stats.length() == 0) return null; + then = stats.get(0); + } + if (!(then instanceof JCThrow)) return null; + } + + /* Check that the if's conditional is like 'x == null'. Return from this method (don't generate + a nullcheck) if 'x' is equal to our own variable's name: There's already a nullcheck here. */ { + JCExpression cond = ((JCIf) stat).cond; + while (cond instanceof JCParens) cond = ((JCParens) cond).expr; + if (!(cond instanceof JCBinary)) return null; + JCBinary bin = (JCBinary) cond; + if (compareCTC(getTag(bin), CTC_EQUAL)) return null; + if (!(bin.lhs instanceof JCIdent)) return null; + if (!(bin.rhs instanceof JCLiteral)) return null; + if (compareCTC(getTypeTag((JCLiteral) bin.rhs), CTC_BOT)) return null; + return ((JCIdent) bin.lhs).name.toString(); + } + } +} diff --git a/src/delombok/lombok/delombok/LombokOptionsFactory.java b/src/delombok/lombok/delombok/LombokOptionsFactory.java index 8d3b2528..e581593f 100644 --- a/src/delombok/lombok/delombok/LombokOptionsFactory.java +++ b/src/delombok/lombok/delombok/LombokOptionsFactory.java @@ -21,17 +21,16 @@ */ package lombok.delombok; +import lombok.javac.Javac; import lombok.javac.Javac6BasedLombokOptions; import lombok.javac.Javac8BasedLombokOptions; import lombok.javac.LombokOptions; -import com.sun.tools.javac.main.JavaCompiler; import com.sun.tools.javac.util.Context; public class LombokOptionsFactory { - enum LombokOptionCompilerVersion { - JDK6_7 { + JDK7_AND_LOWER { @Override LombokOptions createAndRegisterOptions(Context context) { return Javac6BasedLombokOptions.replaceWithDelombokOptions(context); } @@ -46,17 +45,13 @@ public class LombokOptionsFactory { abstract LombokOptions createAndRegisterOptions(Context context); } - public static LombokOptions getDelombokOptions(Context context) { LombokOptions options; - if (JavaCompiler.version().startsWith("1.6") || JavaCompiler.version().startsWith("1.7")) { - options = LombokOptionCompilerVersion.JDK6_7.createAndRegisterOptions(context); - } else if (JavaCompiler.version().startsWith("1.8")) { - options = LombokOptionCompilerVersion.JDK8.createAndRegisterOptions(context); + if (Javac.getJavaCompilerVersion() < 8) { + options = LombokOptionCompilerVersion.JDK7_AND_LOWER.createAndRegisterOptions(context); } else { - throw new IllegalStateException("No support for compiler version " + JavaCompiler.version() + " for delombok"); + options = LombokOptionCompilerVersion.JDK8.createAndRegisterOptions(context); } return options; - } } diff --git a/src/delombok/lombok/delombok/PrettyCommentsPrinter.java b/src/delombok/lombok/delombok/PrettyCommentsPrinter.java index 9817f050..075cc64f 100644 --- a/src/delombok/lombok/delombok/PrettyCommentsPrinter.java +++ b/src/delombok/lombok/delombok/PrettyCommentsPrinter.java @@ -22,6 +22,11 @@ * CA 95054 USA or visit www.sun.com if you need additional information or * have any questions. */ + +/* + * Code derived from com.sun.tools.javac.tree.Pretty, from the langtools project. + * A version can be found at, for example, http://hg.openjdk.java.net/jdk7/build/langtools + */ package lombok.delombok; import static com.sun.tools.javac.code.Flags.ANNOTATION; @@ -220,12 +225,21 @@ public class PrettyCommentsPrinter extends JCTree.Visitor { return tree.getEndPosition(cu.endPositions); } - private void consumeComments(int till) throws IOException { + private void consumeComments(int until) throws IOException { + consumeComments(until, null); + } + private void consumeComments(int until, JCTree tree) throws IOException { boolean prevNewLine = onNewLine; boolean found = false; CommentInfo head = comments.head; - while (comments.nonEmpty() && head.pos < till) { - printComment(head); + while (comments.nonEmpty() && head.pos < until) { + if (tree != null && docComments != null && docComments.containsKey(tree) && head.isJavadoc() && noFurtherJavadocForthcoming(until)) { + // This is (presumably) the exact same javadoc that has already been associated with the node that we're just about to + // print. These javadoc can be modified by lombok handlers, and as such we should NOT print them from the consumed comments db, + // and instead print the actual javadoc associated with the upcoming node (which the visit method for that node will take care of). + } else { + printComment(head); + } comments = comments.tail; head = comments.head; } @@ -233,6 +247,17 @@ public class PrettyCommentsPrinter extends JCTree.Visitor { println(); } } + + private boolean noFurtherJavadocForthcoming(int until) { + List<CommentInfo> c = comments; + if (c.nonEmpty()) c = c.tail; + while (c.nonEmpty()) { + if (c.head.pos >= until) return true; + if (c.head.isJavadoc()) return false; + c = c.tail; + } + return true; + } private void consumeTrailingComments(int from) throws IOException { boolean prevNewLine = onNewLine; @@ -404,7 +429,7 @@ public class PrettyCommentsPrinter extends JCTree.Visitor { this.prec = prec; if (tree == null) print("/*missing*/"); else { - consumeComments(tree.pos); + consumeComments(tree.pos, tree); tree.accept(this); int endPos = endPos(tree); consumeTrailingComments(endPos); @@ -506,7 +531,14 @@ public class PrettyCommentsPrinter extends JCTree.Visitor { print("/**"); println(); int pos = 0; int endpos = lineEndPos(dc, pos); + boolean atStart = true; while (pos < dc.length()) { + String line = dc.substring(pos, endpos); + if (line.trim().isEmpty() && atStart) { + atStart = false; + continue; + } + atStart = false; align(); print(" *"); if (pos < dc.length() && dc.charAt(pos) > ' ') print(" "); @@ -616,7 +648,7 @@ public class PrettyCommentsPrinter extends JCTree.Visitor { docComments = tree.docComments; printDocComment(tree); if (tree.pid != null) { - consumeComments(tree.pos); + consumeComments(tree.pos, tree); print("package "); printExpr(tree.pid); print(";"); @@ -690,7 +722,7 @@ public class PrettyCommentsPrinter extends JCTree.Visitor { public void visitClassDef(JCClassDecl tree) { try { - consumeComments(tree.pos); + consumeComments(tree.pos, tree); println(); align(); printDocComment(tree); printAnnotations(tree.mods.annotations); @@ -982,6 +1014,29 @@ public class PrettyCommentsPrinter extends JCTree.Visitor { public void visitTry(JCTry tree) { try { print("try "); + List<?> resources = null; + try { + Field f = JCTry.class.getField("resources"); + resources = (List<?>) f.get(tree); + } catch (Exception ignore) { + // In JDK6 and down this field does not exist; resources will retain its initializer value which is what we want. + } + + if (resources != null && resources.nonEmpty()) { + boolean first = true; + print("("); + for (Object var0 : resources) { + JCTree var = (JCTree) var0; + if (!first) { + println(); + indent(); + } + printStat(var); + first = false; + } + print(") "); + } + printStat(tree.body); for (List<JCCatch> l = tree.catchers; l.nonEmpty(); l = l.tail) { printStat(l.head); diff --git a/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java b/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java index f9b53e68..8c6011bc 100644 --- a/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java +++ b/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java @@ -267,7 +267,7 @@ public class EclipsePatcher extends Agent { .build()); } - + private static void patchPostCompileHookEclipse(ScriptManager sm) { sm.addScript(ScriptBuilder.wrapMethodCall() .target(new MethodTarget("org.eclipse.jdt.internal.core.builder.IncrementalImageBuilder", "writeClassFileContents")) diff --git a/src/eclipseAgent/lombok/eclipse/agent/PatchExtensionMethod.java b/src/eclipseAgent/lombok/eclipse/agent/PatchExtensionMethod.java index 008e722a..a3d77055 100644 --- a/src/eclipseAgent/lombok/eclipse/agent/PatchExtensionMethod.java +++ b/src/eclipseAgent/lombok/eclipse/agent/PatchExtensionMethod.java @@ -236,10 +236,8 @@ public class PatchExtensionMethod { for (int i = 0, iend = arguments.size(); i < iend; i++) { Expression arg = arguments.get(i); if (fixedBinding.parameters[i].isArrayType() != arg.resolvedType.isArrayType()) break; - if (arg.resolvedType.isArrayType()) { - if (arg instanceof MessageSend) { - ((MessageSend) arg).valueCast = arg.resolvedType; - } + if (arg instanceof MessageSend) { + ((MessageSend) arg).valueCast = arg.resolvedType; } if (!fixedBinding.parameters[i].isBaseType() && arg.resolvedType.isBaseType()) { int id = arg.resolvedType.id; diff --git a/src/eclipseAgent/lombok/eclipse/agent/PatchFixes.java b/src/eclipseAgent/lombok/eclipse/agent/PatchFixes.java index cb91fd25..0002e26e 100644 --- a/src/eclipseAgent/lombok/eclipse/agent/PatchFixes.java +++ b/src/eclipseAgent/lombok/eclipse/agent/PatchFixes.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2012 The Project Lombok Authors. + * 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 @@ -329,7 +329,7 @@ public class PatchFixes { return newSimpleNames; } - public static byte[] runPostCompiler(byte[] bytes, String fileName) { + public static byte[] runPostCompiler(byte[] bytes, String fileName) { byte[] transformed = PostCompiler.applyTransformations(bytes, fileName, DiagnosticsReceiver.CONSOLE); return transformed == null ? bytes : transformed; } diff --git a/src/installer/lombok/installer/IdeLocation.java b/src/installer/lombok/installer/IdeLocation.java index a15f2ac9..4d28fb90 100644 --- a/src/installer/lombok/installer/IdeLocation.java +++ b/src/installer/lombok/installer/IdeLocation.java @@ -25,6 +25,7 @@ import java.io.File; import java.io.IOException; import java.net.URL; +import lombok.installer.eclipse.EclipseFinder; import lombok.patcher.inject.LiveInjector; /** @@ -66,11 +67,12 @@ public abstract class IdeLocation { } private static final String LEGAL_PATH_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_/"; + private static final String LEGAL_PATH_CHARS_WINDOWS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_/:\\ "; public static String escapePath(String path) { StringBuilder out = new StringBuilder(); - + String legalChars = IdeFinder.getOS() == EclipseFinder.OS.UNIX ? LEGAL_PATH_CHARS : LEGAL_PATH_CHARS_WINDOWS; for (char c : path.toCharArray()) { - if (LEGAL_PATH_CHARS.indexOf(c) == -1) out.append('\\'); + if (legalChars.indexOf(c) == -1) out.append('\\'); out.append(c); } return out.toString(); diff --git a/src/installer/lombok/installer/eclipse/EclipseFinder.java b/src/installer/lombok/installer/eclipse/EclipseFinder.java index 9fbcabbc..8a1a689a 100644 --- a/src/installer/lombok/installer/eclipse/EclipseFinder.java +++ b/src/installer/lombok/installer/eclipse/EclipseFinder.java @@ -64,14 +64,10 @@ public class EclipseFinder extends IdeFinder { /** * Returns a list of paths of Eclipse installations. - * Eclipse installations are found by checking for the existence of 'eclipse.exe' in the following locations: - * <ul> - * <li>X:\*Program Files*\*Eclipse*</li> - * <li>X:\*Eclipse*</li> - * </ul> * - * Where 'X' is tried for all local disk drives, unless there's a problem calling fsutil, in which case only - * C: is tried. + * The search process works by scanning for each 'source dir' for either an eclipse installation or a folder containing the text returned + * by getDirName(). If such a folder is found, this process is applied recursively. On windows, this process is run on each drive letter + * which represents a physical hard disk. If the native windows API call to determine these drive letters fails, only 'C:' is checked. */ private List<String> getSourceDirsOnWindowsWithDriveLetters() { List<String> driveLetters = asList("C"); @@ -83,12 +79,22 @@ public class EclipseFinder extends IdeFinder { List<String> sourceDirs = new ArrayList<String>(); for (String letter : driveLetters) { for (String possibleSource : getSourceDirsOnWindows()) { - sourceDirs.add(letter + ":" + possibleSource); + if (!isDriveSpecificOnWindows(possibleSource)) { + sourceDirs.add(letter + ":" + possibleSource); + } } } + for (String possibleSource : getSourceDirsOnWindows()) { + if (isDriveSpecificOnWindows(possibleSource)) sourceDirs.add(possibleSource); + } + return sourceDirs; } + public boolean isDriveSpecificOnWindows(String path) { + return path.length() > 1 && path.charAt(1) == ':'; + } + protected List<String> getSourceDirsOnMac() { return Arrays.asList("/Applications", System.getProperty("user.home", ".")); } diff --git a/src/installer/lombok/installer/eclipse/JbdsFinder.java b/src/installer/lombok/installer/eclipse/JbdsFinder.java new file mode 100644 index 00000000..2dfaacba --- /dev/null +++ b/src/installer/lombok/installer/eclipse/JbdsFinder.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2013 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.installer.eclipse; + +import java.util.Arrays; +import java.util.List; + +import lombok.installer.CorruptedIdeLocationException; +import lombok.installer.IdeFinder; +import lombok.installer.IdeLocation; + +import org.mangosdk.spi.ProviderFor; + +/** + * JBDS (JBoss Developer Studio) is an eclipse variant. + * Other than different executable names, it's the same as eclipse, as far as lombok support goes. + */ +@ProviderFor(IdeFinder.class) +public class JbdsFinder extends EclipseFinder { + @Override protected IdeLocation createLocation(String guess) throws CorruptedIdeLocationException { + return new JbdsLocationProvider().create0(guess); + } + + @Override protected String getDirName() { + return "studio"; + } + + @Override protected String getMacExecutableName() { + return "jbdevstudio.app"; + } + + @Override protected String getUnixExecutableName() { + return "jbdevstudio"; + } + + @Override protected String getWindowsExecutableName() { + return "jbdevstudio.exe"; + } + + @Override protected List<String> getSourceDirsOnWindows() { + return Arrays.asList("\\", "\\Program Files", "\\Program Files (x86)", System.getProperty("user.home", ".")); + } + + @Override protected List<String> getSourceDirsOnMac() { + return Arrays.asList("/Applications", System.getProperty("user.home", ".")); + } + + @Override protected List<String> getSourceDirsOnUnix() { + return Arrays.asList(System.getProperty("user.home", ".")); + } +} diff --git a/src/installer/lombok/installer/eclipse/JbdsLocation.java b/src/installer/lombok/installer/eclipse/JbdsLocation.java new file mode 100644 index 00000000..81fb5261 --- /dev/null +++ b/src/installer/lombok/installer/eclipse/JbdsLocation.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2013 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.installer.eclipse; + +import java.io.File; +import java.net.URL; + +import lombok.installer.CorruptedIdeLocationException; + +public class JbdsLocation extends EclipseLocation { + public JbdsLocation(String nameOfLocation, File pathToEclipseIni) throws CorruptedIdeLocationException { + super(nameOfLocation, pathToEclipseIni); + } + + @Override public URL getIdeIcon() { + return JbdsLocation.class.getResource("jbds.png"); + } + + @Override protected String getIniFileName() { + return "jbdevstudio.ini"; + } + + @Override protected String getTypeName() { + return "JBoss Developer Studio"; + } +} diff --git a/src/installer/lombok/installer/eclipse/JbdsLocationProvider.java b/src/installer/lombok/installer/eclipse/JbdsLocationProvider.java new file mode 100644 index 00000000..e6df0e43 --- /dev/null +++ b/src/installer/lombok/installer/eclipse/JbdsLocationProvider.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2013 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.installer.eclipse; + +import java.io.File; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Pattern; + +import lombok.installer.CorruptedIdeLocationException; +import lombok.installer.IdeLocation; +import lombok.installer.IdeLocationProvider; +import lombok.installer.IdeFinder.OS; + +import org.mangosdk.spi.ProviderFor; + +@ProviderFor(IdeLocationProvider.class) +public class JbdsLocationProvider extends EclipseLocationProvider { + @Override protected List<String> getEclipseExecutableNames() { + return Arrays.asList("jbdevstudio.app", "jbdevstudio.exe", "jbdevstudioc.exe", "jbdevstudio"); + } + + @Override protected String getIniName() { + return "jbdevstudio.ini"; + } + + @Override protected IdeLocation makeLocation(String name, File ini) throws CorruptedIdeLocationException { + return new JbdsLocation(name, ini); + } + + @Override protected String getMacAppName() { + return "jbdevstudio.app"; + } + + @Override protected String getUnixAppName() { + return "jbdevstudio"; + } + + @Override public Pattern getLocationSelectors(OS os) { + switch (os) { + case MAC_OS_X: + return Pattern.compile("^(jbdevstudio|jbdevstudio\\.ini|jbdevstudio\\.app)$", Pattern.CASE_INSENSITIVE); + case WINDOWS: + return Pattern.compile("^(jbdevstudioc?\\.exe|jbdevstudio\\.ini)$", Pattern.CASE_INSENSITIVE); + default: + case UNIX: + return Pattern.compile("^(jbdevstudio|jbdevstudio\\.ini)$", Pattern.CASE_INSENSITIVE); + } + } +} diff --git a/src/installer/lombok/installer/eclipse/STSFinder.java b/src/installer/lombok/installer/eclipse/STSFinder.java index 74b8ed34..82bc9b80 100644 --- a/src/installer/lombok/installer/eclipse/STSFinder.java +++ b/src/installer/lombok/installer/eclipse/STSFinder.java @@ -57,7 +57,7 @@ public class STSFinder extends EclipseFinder { } @Override protected List<String> getSourceDirsOnWindows() { - return Arrays.asList("\\", "\\springsource", "\\Program Files", "\\Program Files\\springsource", System.getProperty("user.home", "."), System.getProperty("user.home", ".") + "\\springsource"); + return Arrays.asList("\\", "\\springsource", "\\Program Files", "\\Program Files (x86)", "\\Program Files\\springsource", "\\Program Files (x86)\\springsource", System.getProperty("user.home", "."), System.getProperty("user.home", ".") + "\\springsource"); } @Override protected List<String> getSourceDirsOnMac() { diff --git a/src/installer/lombok/installer/eclipse/jbds.png b/src/installer/lombok/installer/eclipse/jbds.png Binary files differnew file mode 100644 index 00000000..ca7738e6 --- /dev/null +++ b/src/installer/lombok/installer/eclipse/jbds.png diff --git a/src/stubs/com/sun/tools/javac/util/Context.java b/src/javac-only-stubs/com/sun/tools/javac/util/Context.java index 06b8ff4d..06b8ff4d 100644 --- a/src/stubs/com/sun/tools/javac/util/Context.java +++ b/src/javac-only-stubs/com/sun/tools/javac/util/Context.java diff --git a/src/stubs/com/sun/tools/javac/parser/DocCommentScanner.java b/src/stubs/com/sun/tools/javac/parser/DocCommentScanner.java new file mode 100644 index 00000000..d461d54b --- /dev/null +++ b/src/stubs/com/sun/tools/javac/parser/DocCommentScanner.java @@ -0,0 +1,25 @@ +/* + * These are stub versions of various bits of javac-internal API (for various different versions of javac). Lombok is compiled against these. + */ +package com.sun.tools.javac.parser; + +import java.nio.CharBuffer; + +public class DocCommentScanner extends Scanner { + protected DocCommentScanner(Factory fac, CharBuffer buffer) { + super(fac, buffer); + } + + protected DocCommentScanner(Factory fac, char[] input, int inputLength) { + super(fac, input, inputLength); + } + + protected DocCommentScanner(ScannerFactory fac, CharBuffer buffer) { + super(fac, buffer); + } + + protected DocCommentScanner(ScannerFactory fac, char[] input, int inputLength) { + super(fac, input, inputLength); + } + +} diff --git a/src/stubs/com/sun/tools/javac/parser/Scanner.java b/src/stubs/com/sun/tools/javac/parser/Scanner.java index af94bacc..266208e5 100644 --- a/src/stubs/com/sun/tools/javac/parser/Scanner.java +++ b/src/stubs/com/sun/tools/javac/parser/Scanner.java @@ -59,4 +59,11 @@ public class Scanner implements Lexer { public char[] getRawCharacters(int beginIndex, int endIndex) { return null; } + + public void nextToken() { + } + + public char[] getRawCharacters() { + return new char[0]; + } } diff --git a/src/stubs/com/sun/tools/javadoc/DocCommentScanner.java b/src/stubs/com/sun/tools/javadoc/DocCommentScanner.java new file mode 100644 index 00000000..e2ea9614 --- /dev/null +++ b/src/stubs/com/sun/tools/javadoc/DocCommentScanner.java @@ -0,0 +1,25 @@ +/* + * These are stub versions of various bits of javac-internal API (for various different versions of javac). Lombok is compiled against these. + */ +package com.sun.tools.javadoc; + +import java.nio.CharBuffer; + +import com.sun.tools.javac.parser.Scanner; +import com.sun.tools.javac.util.Context; + +public class DocCommentScanner extends Scanner { + protected DocCommentScanner(com.sun.tools.javadoc.DocCommentScanner.Factory fac, CharBuffer buffer) { + super(fac, buffer); + } + + protected DocCommentScanner(com.sun.tools.javadoc.DocCommentScanner.Factory fac, char[] input, int inputLength) { + super(fac, input, inputLength); + } + + public static class Factory extends Scanner.Factory { + protected Factory(Context context) { + super(context); + } + } +} diff --git a/src/utils/lombok/core/JavaIdentifiers.java b/src/utils/lombok/core/JavaIdentifiers.java new file mode 100644 index 00000000..cbe90eed --- /dev/null +++ b/src/utils/lombok/core/JavaIdentifiers.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2013 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.core; + +/** + * Utility functions for validating potential java verifiers. + */ +public class JavaIdentifiers { + private JavaIdentifiers() {} + + private static final LombokImmutableList<String> KEYWORDS = LombokImmutableList.of( + "public", "private", "protected", + "default", "switch", "case", + "for", "do", "goto", "const", "strictfp", "while", "if", "else", + "byte", "short", "int", "long", "float", "double", "void", "boolean", "char", + "null", "false", "true", + "continue", "break", "return", "instanceof", + "synchronized", "volatile", "transient", "final", "static", + "interface", "class", "extends", "implements", "throws", + "throw", "catch", "try", "finally", "abstract", "assert", + "enum", "import", "package", "native", "new", "super", "this"); + + public static boolean isValidJavaIdentifier(String identifier) { + if (identifier == null) return false; + if (identifier.isEmpty()) return false; + + if (!Character.isJavaIdentifierStart(identifier.charAt(0))) return false; + for (int i = 1; i < identifier.length(); i++) { + if (!Character.isJavaIdentifierPart(identifier.charAt(i))) return false; + } + + return !isKeyword(identifier); + } + + public static boolean isKeyword(String keyword) { + return KEYWORDS.contains(keyword); + } +} diff --git a/src/utils/lombok/core/LombokImmutableList.java b/src/utils/lombok/core/LombokImmutableList.java new file mode 100644 index 00000000..e0e1136c --- /dev/null +++ b/src/utils/lombok/core/LombokImmutableList.java @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2013 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.core; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; + +public final class LombokImmutableList<T> implements Iterable<T> { + private Object[] content; + private static final LombokImmutableList<?> EMPTY = new LombokImmutableList<Object>(new Object[0]); + + @SuppressWarnings("unchecked") + public static <T> LombokImmutableList<T> of() { + return (LombokImmutableList<T>) EMPTY; + } + + public static <T> LombokImmutableList<T> of(T a) { + return new LombokImmutableList<T>(new Object[] {a}); + } + + public static <T> LombokImmutableList<T> of(T a, T b) { + return new LombokImmutableList<T>(new Object[] {a, b}); + } + + public static <T> LombokImmutableList<T> of(T a, T b, T c) { + return new LombokImmutableList<T>(new Object[] {a, b, c}); + } + + public static <T> LombokImmutableList<T> of(T a, T b, T c, T d) { + return new LombokImmutableList<T>(new Object[] {a, b, c, d}); + } + + public static <T> LombokImmutableList<T> of(T a, T b, T c, T d, T e) { + return new LombokImmutableList<T>(new Object[] {a, b, c, d, e}); + } + + public static <T> LombokImmutableList<T> of(T a, T b, T c, T d, T e, T f, T... g) { + Object[] rest = g == null ? new Object[] {null} : g; + Object[] val = new Object[rest.length + 6]; + System.arraycopy(rest, 0, val, 6, rest.length); + val[0] = a; + val[1] = b; + val[2] = c; + val[3] = d; + val[4] = e; + val[5] = f; + return new LombokImmutableList<T>(val); + } + + public static <T> LombokImmutableList<T> copyOf(Collection<? extends T> list) { + return new LombokImmutableList<T>(list.toArray()); + } + + public static <T> LombokImmutableList<T> copyOf(Iterable<? extends T> iterable) { + List<T> list = new ArrayList<T>(); + for (T o : iterable) list.add(o); + return copyOf(list); + } + + private LombokImmutableList(Object[] content) { + this.content = content; + } + + public LombokImmutableList<T> replaceElementAt(int idx, T newValue) { + Object[] newContent = content.clone(); + newContent[idx] = newValue; + return new LombokImmutableList<T>(newContent); + } + + public LombokImmutableList<T> append(T newValue) { + int len = content.length; + Object[] newContent = new Object[len + 1]; + System.arraycopy(content, 0, newContent, 0, len); + newContent[len] = newValue; + return new LombokImmutableList<T>(newContent); + } + + public LombokImmutableList<T> prepend(T newValue) { + int len = content.length; + Object[] newContent = new Object[len + 1]; + System.arraycopy(content, 0, newContent, 1, len); + newContent[0] = newValue; + return new LombokImmutableList<T>(newContent); + } + + public int indexOf(T val) { + int len = content.length; + if (val == null) { + for (int i = 0; i < len; i++) if (content[i] == null) return i; + return -1; + } + + for (int i = 0; i < len; i++) if (val.equals(content[i])) return i; + return -1; + } + + public LombokImmutableList<T> removeElement(T val) { + int idx = indexOf(val); + return idx == -1 ? this : removeElementAt(idx); + } + + public LombokImmutableList<T> removeElementAt(int idx) { + int len = content.length; + Object[] newContent = new Object[len - 1]; + if (idx > 0) System.arraycopy(content, 0, newContent, 0, idx); + if (idx < len - 1) System.arraycopy(content, idx + 1, newContent, idx, len - idx - 1); + return new LombokImmutableList<T>(newContent); + } + + public boolean isEmpty() { + return content.length == 0; + } + + public int size() { + return content.length; + } + + @SuppressWarnings("unchecked") + public T get(int idx) { + return (T) content[idx]; + } + + public boolean contains(T in) { + if (in == null) { + for (Object e : content) if (e == null) return true; + return false; + } + + for (Object e : content) if (in.equals(e)) return true; + return false; + } + + public Iterator<T> iterator() { + return new Iterator<T>() { + private int idx = 0; + @Override public boolean hasNext() { + return idx < content.length; + } + + @SuppressWarnings("unchecked") + @Override public T next() { + if (idx < content.length) return (T) content[idx++]; + throw new NoSuchElementException(); + } + + @Override public void remove() { + throw new UnsupportedOperationException("List is immutable"); + } + }; + } + + @Override public String toString() { + return Arrays.toString(content); + } + + @Override public boolean equals(Object obj) { + if (!(obj instanceof LombokImmutableList)) return false; + if (obj == this) return true; + return Arrays.equals(content, ((LombokImmutableList<?>) obj).content); + } + + @Override public int hashCode() { + return Arrays.hashCode(content); + } +} diff --git a/src/utils/lombok/eclipse/Eclipse.java b/src/utils/lombok/eclipse/Eclipse.java index 301925d1..c2a863d5 100644 --- a/src/utils/lombok/eclipse/Eclipse.java +++ b/src/utils/lombok/eclipse/Eclipse.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2011 The Project Lombok Authors. + * Copyright (C) 2009-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 @@ -21,6 +21,7 @@ */ package lombok.eclipse; +import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -36,8 +37,11 @@ import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; import org.eclipse.jdt.internal.compiler.ast.Literal; import org.eclipse.jdt.internal.compiler.ast.QualifiedNameReference; import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; +import org.eclipse.jdt.internal.compiler.ast.TryStatement; import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; import org.eclipse.jdt.internal.compiler.ast.TypeReference; +import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; +import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; import org.eclipse.jdt.internal.compiler.lookup.TypeIds; public class Eclipse { @@ -58,7 +62,9 @@ public class Eclipse { * but we need to deal with it. This turns [[java][lang][String]] into "java.lang.String". */ public static String toQualifiedName(char[][] typeName) { - StringBuilder sb = new StringBuilder(); + int len = typeName.length - 1; + for (char[] c : typeName) len += c.length; + StringBuilder sb = new StringBuilder(len); boolean first = true; for (char[] c : typeName) { sb.append(first ? "" : ".").append(c); @@ -175,4 +181,58 @@ public class Eclipse { return null; } + + private static long latestEcjCompilerVersionConstantCached = 0; + + public static long getLatestEcjCompilerVersionConstant() { + if (latestEcjCompilerVersionConstantCached != 0) return latestEcjCompilerVersionConstantCached; + + int highestVersionSoFar = 0; + for (Field f : ClassFileConstants.class.getDeclaredFields()) { + try { + if (f.getName().startsWith("JDK1_")) { + int thisVersion = Integer.parseInt(f.getName().substring("JDK1_".length())); + if (thisVersion > highestVersionSoFar) { + highestVersionSoFar = thisVersion; + latestEcjCompilerVersionConstantCached = (Long) f.get(null); + } + } + } catch (Exception ignore) {} + } + + if (highestVersionSoFar > 6 && !ecjSupportsJava7Features()) { + latestEcjCompilerVersionConstantCached = ClassFileConstants.JDK1_6; + } + return latestEcjCompilerVersionConstantCached; + } + + private static int ecjCompilerVersionCached = -1; + public static int getEcjCompilerVersion() { + if (ecjCompilerVersionCached >= 0) return ecjCompilerVersionCached; + + for (Field f : CompilerOptions.class.getDeclaredFields()) { + try { + if (f.getName().startsWith("VERSION_1_")) { + ecjCompilerVersionCached = Math.max(ecjCompilerVersionCached, Integer.parseInt(f.getName().substring("VERSION_1_".length()))); + } + } catch (Exception ignore) {} + } + + if (ecjCompilerVersionCached < 5) ecjCompilerVersionCached = 5; + if (!ecjSupportsJava7Features()) ecjCompilerVersionCached = Math.min(6, ecjCompilerVersionCached); + return ecjCompilerVersionCached; + } + + /** + * Certain ECJ versions that only go up to -source 6 report that they support -source 7 and even fail to error when -source 7 is applied. + * We detect this and correctly say that no more than -source 6 is supported. (when this is the case, this method returns false). + */ + private static boolean ecjSupportsJava7Features() { + try { + TryStatement.class.getDeclaredField("resources"); + return true; + } catch (NoSuchFieldException e) { + return false; + } + } } diff --git a/src/utils/lombok/javac/CommentCatcher.java b/src/utils/lombok/javac/CommentCatcher.java index e3754627..8d1e71c0 100644 --- a/src/utils/lombok/javac/CommentCatcher.java +++ b/src/utils/lombok/javac/CommentCatcher.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011 The Project Lombok Authors. + * Copyright (C) 2011-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 @@ -21,9 +21,12 @@ */ package lombok.javac; +import java.lang.reflect.InvocationTargetException; import java.util.Map; import java.util.WeakHashMap; +import lombok.Lombok; + import com.sun.tools.javac.main.JavaCompiler; import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; import com.sun.tools.javac.util.Context; @@ -62,36 +65,33 @@ public class CommentCatcher { private static void registerCommentsCollectingScannerFactory(Context context) { try { - if (JavaCompiler.version().startsWith("1.6")) { - Class.forName("lombok.javac.java6.CommentCollectingScannerFactory").getMethod("preRegister", Context.class).invoke(null, context); - } else if (JavaCompiler.version().startsWith("1.7") || JavaCompiler.version().startsWith("1.8")) { - Class.forName("lombok.javac.java7.CommentCollectingScannerFactory").getMethod("preRegister", Context.class).invoke(null, context); + Class<?> scannerFactory; + if (Javac.getJavaCompilerVersion() <= 6) { + scannerFactory = Class.forName("lombok.javac.java6.CommentCollectingScannerFactory"); } else { - throw new IllegalStateException("No comments parser for compiler version " + JavaCompiler.version()); + scannerFactory = Class.forName("lombok.javac.java7.CommentCollectingScannerFactory"); } + scannerFactory.getMethod("preRegister", Context.class).invoke(null, context); + } catch (InvocationTargetException e) { + throw Lombok.sneakyThrow(e.getCause()); } catch (Exception e) { - if (e instanceof RuntimeException) throw (RuntimeException)e; - throw new RuntimeException(e); + throw Lombok.sneakyThrow(e); } } private static void setInCompiler(JavaCompiler compiler, Context context, Map<JCCompilationUnit, List<CommentInfo>> commentsMap) { - try { - if (JavaCompiler.version().startsWith("1.6")) { - Class<?> parserFactory = Class.forName("lombok.javac.java6.CommentCollectingParserFactory"); - parserFactory.getMethod("setInCompiler",JavaCompiler.class, Context.class, Map.class).invoke(null, compiler, context, commentsMap); - } else if (JavaCompiler.version().startsWith("1.7") || JavaCompiler.version().startsWith("1.8")) { - Class<?> parserFactory = Class.forName("lombok.javac.java7.CommentCollectingParserFactory"); - parserFactory.getMethod("setInCompiler",JavaCompiler.class, Context.class, Map.class).invoke(null, compiler, context, commentsMap); + Class<?> parserFactory; + if (Javac.getJavaCompilerVersion() <= 6) { + parserFactory = Class.forName("lombok.javac.java6.CommentCollectingParserFactory"); } else { - throw new IllegalStateException("No comments parser for compiler version " + JavaCompiler.version()); + parserFactory = Class.forName("lombok.javac.java7.CommentCollectingParserFactory"); } - + parserFactory.getMethod("setInCompiler", JavaCompiler.class, Context.class, Map.class).invoke(null, compiler, context, commentsMap); + } catch (InvocationTargetException e) { + throw Lombok.sneakyThrow(e.getCause()); } catch (Exception e) { - if (e instanceof RuntimeException) throw (RuntimeException)e; - throw new RuntimeException(e); + throw Lombok.sneakyThrow(e); } } - } diff --git a/src/utils/lombok/javac/CommentInfo.java b/src/utils/lombok/javac/CommentInfo.java index 7375d51a..afdd8469 100644 --- a/src/utils/lombok/javac/CommentInfo.java +++ b/src/utils/lombok/javac/CommentInfo.java @@ -66,7 +66,7 @@ public final class CommentInfo { } public boolean isJavadoc() { - return content.startsWith("/**"); + return content.startsWith("/**") && content.length() > 4; } @Override diff --git a/src/utils/lombok/javac/JCNoTypeFactory.java b/src/utils/lombok/javac/JCNoTypeFactory.java deleted file mode 100644 index e7be7665..00000000 --- a/src/utils/lombok/javac/JCNoTypeFactory.java +++ /dev/null @@ -1,11 +0,0 @@ -package lombok.javac; - -import com.sun.tools.javac.code.Symbol.TypeSymbol; -import com.sun.tools.javac.code.Type; - - -public class JCNoTypeFactory { - public static final Type getJCNotType(Object typeTag, TypeSymbol tsym) { - - } -} diff --git a/src/utils/lombok/javac/Javac.java b/src/utils/lombok/javac/Javac.java index 9f2936a8..dfa51e00 100644 --- a/src/utils/lombok/javac/Javac.java +++ b/src/utils/lombok/javac/Javac.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2012 The Project Lombok Authors. + * Copyright (C) 2009-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 @@ -21,19 +21,38 @@ */ package lombok.javac; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.Objects; +import java.lang.reflect.Modifier; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.regex.Matcher; import java.util.regex.Pattern; +import javax.lang.model.type.NoType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeVisitor; + +import lombok.Lombok; + +import com.sun.tools.javac.code.Type; import com.sun.tools.javac.main.JavaCompiler; +import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.JCBinary; +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.JCLiteral; +import com.sun.tools.javac.tree.JCTree.JCModifiers; import com.sun.tools.javac.tree.JCTree.JCPrimitiveTypeTree; +import com.sun.tools.javac.tree.JCTree.JCTypeParameter; import com.sun.tools.javac.tree.JCTree.JCUnary; import com.sun.tools.javac.tree.TreeMaker; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.util.Name; /** * Container for static utility methods relevant to lombok's operation on javac. @@ -46,6 +65,30 @@ public class Javac { /** Matches any of the 8 primitive names, such as {@code boolean}. */ private static final Pattern PRIMITIVE_TYPE_NAME_PATTERN = Pattern.compile("^(boolean|byte|short|int|long|float|double|char)$"); + private static final Pattern VERSION_PARSER = Pattern.compile("^(\\d{1,6})\\.(\\d{1,6}).*$"); + + private static final AtomicInteger compilerVersion = new AtomicInteger(-1); + + /** + * Returns the version of this java compiler, i.e. the JDK that it shipped in. For example, for javac v1.7, this returns {@code 7}. + */ + public static int getJavaCompilerVersion() { + int cv = compilerVersion.get(); + if (cv != -1) return cv; + Matcher m = VERSION_PARSER.matcher(JavaCompiler.version()); + if (m.matches()) { + int major = Integer.parseInt(m.group(1)); + int minor = Integer.parseInt(m.group(2)); + if (major == 1) { + compilerVersion.set(minor); + return minor; + } + } + + compilerVersion.set(6); + return 6; + } + /** * Checks if the given expression (that really ought to refer to a type * expression) represents a primitive type. @@ -102,56 +145,53 @@ public class Javac { public static final Object CTC_EQUAL = getTreeTag("EQ"); public static boolean compareCTC(Object ctc1, Object ctc2) { - return Objects.equals(ctc1, ctc2); + return ctc1 == null ? ctc2 == null : ctc1.equals(ctc2); } + private static final ConcurrentMap<String, Object> TYPE_TAG_CACHE = new ConcurrentHashMap<String, Object>(); + private static final ConcurrentMap<String, Object> TREE_TAG_CACHE = new ConcurrentHashMap<String, Object>(); + + /** - * Retrieves the current type tag. The actual type object differs depending on the Compiler version - * - * For JDK 8 this is an enum value of type <code>com.sun.tools.javac.code.TypeTag</code> - * for JDK 7 and lower, this is the value of the constant within <code>com.sun.tools.javac.code.TypeTags</code> + * Retrieves the provided TypeTag value, in a compiler version independent manner. * + * The actual type object differs depending on the Compiler version: + * <ul> + * <li>For JDK 8 this is an enum value of type <code>com.sun.tools.javac.code.TypeTag</code> + * <li>for JDK 7 and lower, this is the value of the constant within <code>com.sun.tools.javac.code.TypeTags</code> + * </ul> * Solves the problem of compile time constant inlining, resulting in lombok * having the wrong value (javac compiler changes private api constants from * time to time). * - * @param identifier - * @return the ordinal value of the typetag constant + * @param identifier Identifier to turn into a TypeTag. + * @return the value of the typetag constant (either enum instance or an Integer object). */ public static Object getTypeTag(String identifier) { - try { - if (JavaCompiler.version().startsWith("1.8")) { - return Class.forName("com.sun.tools.javac.code.TypeTag").getField(identifier).get(null); - } else { - return Class.forName("com.sun.tools.javac.code.TypeTags").getField(identifier).get(null); - } - } catch (NoSuchFieldException e) { - throw new RuntimeException(e); - } catch (IllegalAccessException e) { - throw new RuntimeException(e); - } catch (Exception e) { - if (e instanceof RuntimeException) throw (RuntimeException) e; - throw new RuntimeException(e); - } + return getFieldCached(TYPE_TAG_CACHE, getJavaCompilerVersion() < 8 ? "com.sun.tools.javac.code.TypeTag" : "com.sun.tools.javac.code.TypeTags", identifier); } public static Object getTreeTag(String identifier) { - try { - if (JavaCompiler.version().startsWith("1.8")) { - return Class.forName("com.sun.tools.javac.tree.JCTree$Tag").getField(identifier).get(null); - } else { - return Class.forName("com.sun.tools.javac.tree.JCTree").getField(identifier).get(null); - } + return getFieldCached(TREE_TAG_CACHE, getJavaCompilerVersion() < 8 ? "com.sun.tools.javac.tree.JCTree" : "com.sun.tools.javac.tree.JCTree$Tag", identifier); + } + + private static Object getFieldCached(ConcurrentMap<String, Object> cache, String className, String fieldName) { + Object value = cache.get(fieldName); + if (value != null) return value; + try { + value = Class.forName(className).getField(fieldName).get(null); } catch (NoSuchFieldException e) { - throw new RuntimeException(e); + throw Lombok.sneakyThrow(e); } catch (IllegalAccessException e) { - throw new RuntimeException(e); - } catch (Exception e) { - if (e instanceof RuntimeException) throw (RuntimeException) e; - throw new RuntimeException(e); + throw Lombok.sneakyThrow(e); + } catch (ClassNotFoundException e) { + throw Lombok.sneakyThrow(e); } + + cache.putIfAbsent(fieldName, value); + return value; } - + public static Object getTreeTypeTag(JCPrimitiveTypeTree tree) { return tree.typetag; } @@ -159,82 +199,239 @@ public class Javac { public static Object getTreeTypeTag(JCLiteral tree) { return tree.typetag; } + + private static final Method createIdent, createLiteral, createUnary, createBinary; + static { + if (getJavaCompilerVersion() < 8) { + createIdent = getMethod(TreeMaker.class, "TypeIdent", int.class); + } else { + createIdent = getMethod(TreeMaker.class, "TypeIdent", "com.sun.tools.javac.code.TypeTag"); + } + createIdent.setAccessible(true); + + if (getJavaCompilerVersion() < 8) { + createLiteral = getMethod(TreeMaker.class, "Literal", int.class, Object.class); + } else { + createLiteral = getMethod(TreeMaker.class, "Literal", "com.sun.tools.javac.code.TypeTag", "java.lang.Object"); + } + createLiteral.setAccessible(true); + + if (getJavaCompilerVersion() < 8) { + createUnary = getMethod(TreeMaker.class, "Unary", int.class, JCExpression.class); + } else { + createUnary = getMethod(TreeMaker.class, "Unary", "com.sun.tools.javac.code.TypeTag", JCExpression.class.getName()); + } + createUnary.setAccessible(true); + + if (getJavaCompilerVersion() < 8) { + createBinary = getMethod(TreeMaker.class, "Binary", Integer.TYPE, JCExpression.class, JCExpression.class); + } else { + createBinary = getMethod(TreeMaker.class, "Binary", "com.sun.tools.javac.code.TypeTag", JCExpression.class.getName(), JCExpression.class.getName()); + } + createBinary.setAccessible(true); + } + + private static Method getMethod(Class<?> clazz, String name, Class<?>... paramTypes) { + try { + return clazz.getMethod(name, paramTypes); + } catch (NoSuchMethodException e) { + throw Lombok.sneakyThrow(e); + } + } + + private static Method getMethod(Class<?> clazz, String name, String... paramTypes) { + try { + Class<?>[] c = new Class[paramTypes.length]; + for (int i = 0; i < paramTypes.length; i++) c[i] = Class.forName(paramTypes[i]); + return clazz.getMethod(name, c); + } catch (NoSuchMethodException e) { + throw Lombok.sneakyThrow(e); + } catch (ClassNotFoundException e) { + throw Lombok.sneakyThrow(e); + } + } + public static JCExpression makeTypeIdent(TreeMaker maker, Object ctc) { try { - Method createIdent; - if (JavaCompiler.version().startsWith("1.8")) { - createIdent = TreeMaker.class.getMethod("TypeIdent", Class.forName("com.sun.tools.javac.code.TypeTag")); - } else { - createIdent = TreeMaker.class.getMethod("TypeIdent", Integer.TYPE); - } return (JCExpression) createIdent.invoke(maker, ctc); - } catch (NoSuchMethodException e) { - throw new RuntimeException(e); } catch (IllegalAccessException e) { - throw new RuntimeException(e); - } catch (Exception e) { - if (e instanceof RuntimeException) throw (RuntimeException) e; - throw new RuntimeException(e); + throw Lombok.sneakyThrow(e); + } catch (InvocationTargetException e) { + throw Lombok.sneakyThrow(e.getCause()); } } public static JCLiteral makeLiteral(TreeMaker maker, Object ctc, Object argument) { try { - Method createLiteral; - if (JavaCompiler.version().startsWith("1.8")) { - createLiteral = TreeMaker.class.getMethod("Literal", Class.forName("com.sun.tools.javac.code.TypeTag"), Object.class); - } else { - createLiteral = TreeMaker.class.getMethod("Literal", Integer.TYPE, Object.class); - } return (JCLiteral) createLiteral.invoke(maker, ctc, argument); - } catch (NoSuchMethodException e) { - throw new RuntimeException(e); } catch (IllegalAccessException e) { - throw new RuntimeException(e); - } catch (Exception e) { - if (e instanceof RuntimeException) throw (RuntimeException) e; - throw new RuntimeException(e); + throw Lombok.sneakyThrow(e); + } catch (InvocationTargetException e) { + throw Lombok.sneakyThrow(e.getCause()); } } public static JCUnary makeUnary(TreeMaker maker, Object ctc, JCExpression argument) { try { - Method createUnary; - if (JavaCompiler.version().startsWith("1.8")) { - createUnary = TreeMaker.class.getMethod("Unary", Class.forName("com.sun.tools.javac.code.TypeTag"), JCExpression.class); - } else { - createUnary = TreeMaker.class.getMethod("Unary", Integer.TYPE, JCExpression.class); - } return (JCUnary) createUnary.invoke(maker, ctc, argument); - } catch (NoSuchMethodException e) { - throw new RuntimeException(e); } catch (IllegalAccessException e) { - throw new RuntimeException(e); - } catch (Exception e) { - if (e instanceof RuntimeException) throw (RuntimeException) e; - throw new RuntimeException(e); + throw Lombok.sneakyThrow(e); + } catch (InvocationTargetException e) { + throw Lombok.sneakyThrow(e.getCause()); } } - public static JCBinary makeBinary(TreeMaker maker, Object ctc, JCExpression rhsArgument, JCExpression lhsArgument) { + public static JCBinary makeBinary(TreeMaker maker, Object ctc, JCExpression lhsArgument, JCExpression rhsArgument) { try { - Method createUnary; - if (JavaCompiler.version().startsWith("1.8")) { - createUnary = TreeMaker.class.getMethod("Binary", Class.forName("com.sun.tools.javac.code.TypeTag"), JCExpression.class, JCExpression.class); - } else { - createUnary = TreeMaker.class.getMethod("Binary", Integer.TYPE, JCExpression.class, JCExpression.class); - } - return (JCBinary) createUnary.invoke(maker, ctc, rhsArgument, lhsArgument); - } catch (NoSuchMethodException e) { - throw new RuntimeException(e); + return (JCBinary) createBinary.invoke(maker, ctc, lhsArgument, rhsArgument); } catch (IllegalAccessException e) { - throw new RuntimeException(e); + throw Lombok.sneakyThrow(e); + } catch (InvocationTargetException e) { + throw Lombok.sneakyThrow(e.getCause()); + } + } + + private static final Class<?> JC_VOID_TYPE, JC_NO_TYPE; + + static { + Class<?> c = null; + try { + c = Class.forName("com.sun.tools.javac.code.Type$JCVoidType"); + } catch (Exception ignore) {} + JC_VOID_TYPE = c; + c = null; + try { + c = Class.forName("com.sun.tools.javac.code.Type$JCNoType"); + } catch (Exception ignore) {} + JC_NO_TYPE = c; + } + + public static Type createVoidType(TreeMaker maker, Object tag) { + if (Javac.getJavaCompilerVersion() < 8) { + return new JCNoType(((Integer) tag).intValue()); + } else { + try { + if (compareCTC(tag, CTC_VOID)) { + return (Type) JC_VOID_TYPE.newInstance(); + } else { + return (Type) JC_NO_TYPE.newInstance(); + } + } catch (IllegalAccessException e) { + throw Lombok.sneakyThrow(e); + } catch (InstantiationException e) { + throw Lombok.sneakyThrow(e); + } + } + } + + private static class JCNoType extends Type implements NoType { + public JCNoType(int tag) { + super(tag, null); + } + + @Override + public TypeKind getKind() { + if (Javac.compareCTC(tag, CTC_VOID)) return TypeKind.VOID; + if (Javac.compareCTC(tag, CTC_NONE)) return TypeKind.NONE; + throw new AssertionError("Unexpected tag: " + tag); + } + + @Override + public <R, P> R accept(TypeVisitor<R, P> v, P p) { + return v.visitNoType(this, p); + } + } + + private static final Field JCTREE_TAG, JCLITERAL_TYPETAG, JCPRIMITIVETYPETREE_TYPETAG; + private static final Method JCTREE_GETTAG; + static { + Field f = null; + try { + f = JCTree.class.getDeclaredField("tag"); + } catch (NoSuchFieldException e) {} + JCTREE_TAG = f; + + f = null; + try { + f = JCLiteral.class.getDeclaredField("typetag"); + } catch (NoSuchFieldException e) {} + JCLITERAL_TYPETAG = f; + + f = null; + try { + f = JCPrimitiveTypeTree.class.getDeclaredField("typetag"); + } catch (NoSuchFieldException e) {} + JCPRIMITIVETYPETREE_TYPETAG = f; + + Method m = null; + try { + m = JCTree.class.getDeclaredMethod("getTag"); + } catch (NoSuchMethodException e) {} + JCTREE_GETTAG = m; + } + + public static Object getTag(JCTree node) { + if (JCTREE_GETTAG != null) { + try { + return JCTREE_GETTAG.invoke(node); + } catch (Exception e) {} + } + try { + return JCTREE_TAG.get(node); } catch (Exception e) { - if (e instanceof RuntimeException) throw (RuntimeException) e; - throw new RuntimeException(e); + throw new IllegalStateException("Can't get node tag"); } } - + public static Object getTypeTag(JCLiteral node) { + try { + return JCLITERAL_TYPETAG.get(node); + } catch (Exception e) { + throw new IllegalStateException("Can't get JCLiteral typetag"); + } + } + + public static Object getTypeTag(JCPrimitiveTypeTree node) { + try { + return JCPRIMITIVETYPETREE_TYPETAG.get(node); + } catch (Exception e) { + throw new IllegalStateException("Can't get JCPrimitiveTypeTree typetag"); + } + } + + private static Method classDef; + + public static JCClassDecl ClassDef(TreeMaker maker, JCModifiers mods, Name name, List<JCTypeParameter> typarams, JCExpression extending, List<JCExpression> implementing, List<JCTree> defs) { + if (classDef == null) try { + classDef = TreeMaker.class.getDeclaredMethod("ClassDef", JCModifiers.class, Name.class, List.class, JCExpression.class, List.class, List.class); + } catch (NoSuchMethodException ignore) {} + if (classDef == null) try { + classDef = TreeMaker.class.getDeclaredMethod("ClassDef", JCModifiers.class, Name.class, List.class, JCTree.class, List.class, List.class); + } catch (NoSuchMethodException ignore) {} + + if (classDef == null) throw new IllegalStateException("Lombok bug #20130617-1310: ClassDef doesn't look like anything we thought it would look like."); + if (!Modifier.isPublic(classDef.getModifiers()) && !classDef.isAccessible()) { + classDef.setAccessible(true); + } + + try { + return (JCClassDecl) classDef.invoke(maker, mods, name, typarams, extending, implementing, defs); + } catch (InvocationTargetException e) { + throw sneakyThrow(e.getCause()); + } catch (IllegalAccessException e) { + throw sneakyThrow(e.getCause()); + } + } + + private static RuntimeException sneakyThrow(Throwable t) { + if (t == null) throw new NullPointerException("t"); + Javac.<RuntimeException>sneakyThrow0(t); + return null; + } + + @SuppressWarnings("unchecked") + private static <T extends Throwable> void sneakyThrow0(Throwable t) throws T { + throw (T)t; + } } diff --git a/src/utils/lombok/javac/java6/CommentCollectingParserFactory.java b/src/utils/lombok/javac/java6/CommentCollectingParserFactory.java index b2a248c8..7e34b723 100644 --- a/src/utils/lombok/javac/java6/CommentCollectingParserFactory.java +++ b/src/utils/lombok/javac/java6/CommentCollectingParserFactory.java @@ -25,7 +25,7 @@ public class CommentCollectingParserFactory extends Parser.Factory { } @Override public Parser newParser(Lexer S, boolean keepDocComments, boolean genEndPos) { - Object x = new CommentCollectingParser(this, S, keepDocComments, commentsMap); + Object x = new CommentCollectingParser(this, S, true, commentsMap); return (Parser) x; // CCP is based on a stub which extends nothing, but at runtime the stub is replaced with either //javac6's EndPosParser which extends Parser, or javac7's EndPosParser which implements Parser. diff --git a/src/utils/lombok/javac/java6/CommentCollectingScanner.java b/src/utils/lombok/javac/java6/CommentCollectingScanner.java index 66e1514d..b584ec16 100644 --- a/src/utils/lombok/javac/java6/CommentCollectingScanner.java +++ b/src/utils/lombok/javac/java6/CommentCollectingScanner.java @@ -27,12 +27,10 @@ import lombok.javac.CommentInfo; import lombok.javac.CommentInfo.EndConnection; import lombok.javac.CommentInfo.StartConnection; -import com.sun.tools.javac.parser.Scanner; import com.sun.tools.javac.util.List; import com.sun.tools.javac.util.ListBuffer; - -public class CommentCollectingScanner extends Scanner { +public class CommentCollectingScanner extends DocCommentScanner { private final ListBuffer<CommentInfo> comments = ListBuffer.lb(); private int endComment = 0; @@ -56,6 +54,7 @@ public class CommentCollectingScanner extends Scanner { CommentInfo comment = new CommentInfo(prevEndPos, pos, endPos, content, start, end); comments.append(comment); + super.processComment(style); } private EndConnection determineEndConnection(int pos) { diff --git a/src/utils/lombok/javac/java6/CommentCollectingScannerFactory.java b/src/utils/lombok/javac/java6/CommentCollectingScannerFactory.java index 30acbd5a..f3d6bd72 100644 --- a/src/utils/lombok/javac/java6/CommentCollectingScannerFactory.java +++ b/src/utils/lombok/javac/java6/CommentCollectingScannerFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011 The Project Lombok Authors. + * Copyright (C) 2011-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 @@ -26,19 +26,44 @@ import java.nio.CharBuffer; import com.sun.tools.javac.parser.Scanner; import com.sun.tools.javac.util.Context; -public class CommentCollectingScannerFactory extends Scanner.Factory { +public class CommentCollectingScannerFactory extends DocCommentScanner.Factory { + @SuppressWarnings("all") public static void preRegister(final Context context) { if (context.get(scannerFactoryKey) == null) { - context.put(scannerFactoryKey, new Context.Factory<Scanner.Factory>() { - public CommentCollectingScanner.Factory make() { + // Careful! There is voodoo magic here! + // + // Context.Factory is parameterized. make() is for javac6 and below; make(Context) is for javac7 and up. + // this anonymous inner class definition is intentionally 'raw' - the return type of both 'make' methods is 'T', + // which means the compiler will only generate the correct "real" override method (with returntype Object, which is + // the lower bound for T, as a synthetic accessor for the make with returntype ScannerFactory) for that make method which + // is actually on the classpath (either make() for javac6-, or make(Context) for javac7+). + // + // We normally solve this issue via src/stubs, with BOTH make methods listed, but for some reason the presence of a stubbed out + // Context (or even a complete copy, it doesn't matter) results in a really strange eclipse bug, where any mention of any kind + // of com.sun.tools.javac.tree.TreeMaker in a source file disables ALL usage of 'go to declaration' and auto-complete in the entire + // source file. + // + // Thus, in short: + // * Do NOT parameterize the anonymous inner class literal. + // * Leave the return types as 'j.l.Object'. + // * Leave both make methods intact; deleting one has no effect on javac6- / javac7+, but breaks the other. Hard to test for. + // * Do not stub com.sun.tools.javac.util.Context or any of its inner types, like Factory. + @SuppressWarnings("all") + class MyFactory implements Context.Factory { + // This overrides the javac6- version of make. + public Object make() { return new CommentCollectingScannerFactory(context); } - public CommentCollectingScanner.Factory make(Context c) { + // This overrides the javac7+ version of make. + public Object make(Context c) { return new CommentCollectingScannerFactory(c); } - }); + } + + @SuppressWarnings("unchecked") Context.Factory<Scanner.Factory> factory = new MyFactory(); + context.put(scannerFactoryKey, factory); } } diff --git a/src/utils/lombok/javac/java6/DocCommentScanner.java b/src/utils/lombok/javac/java6/DocCommentScanner.java new file mode 100644 index 00000000..ff3eadd4 --- /dev/null +++ b/src/utils/lombok/javac/java6/DocCommentScanner.java @@ -0,0 +1,461 @@ +package lombok.javac.java6; + +/* + * Copyright 2004-2006 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +import static com.sun.tools.javac.util.LayoutCharacters.*; + +import java.nio.CharBuffer; + +import com.sun.tools.javac.parser.Scanner; +import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.Position; + +/** An extension to the base lexical analyzer that captures + * and processes the contents of doc comments. It does so by + * translating Unicode escape sequences and by stripping the + * leading whitespace and starts from each line of the comment. + * + * <p><b>This is NOT part of any API supported by Sun Microsystems. If + * you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice.</b> + */ +public class DocCommentScanner extends Scanner { + + /** A factory for creating scanners. */ + public static class Factory extends Scanner.Factory { + + @SuppressWarnings({"unchecked", "all"}) + public static void preRegister(final Context context) { + context.put(scannerFactoryKey, new Context.Factory() { + public Object make() { + return new Factory(context); + } + + public Object make(Context c) { + return new Factory(c); + } + }); + } + + /** Create a new scanner factory. */ + protected Factory(Context context) { + super(context); + } + + @Override + public Scanner newScanner(CharSequence input) { + if (input instanceof CharBuffer) { + return new DocCommentScanner(this, (CharBuffer)input); + } else { + char[] array = input.toString().toCharArray(); + return newScanner(array, array.length); + } + } + + @Override + public Scanner newScanner(char[] input, int inputLength) { + return new DocCommentScanner(this, input, inputLength); + } + } + + + /** Create a scanner from the input buffer. buffer must implement + * array() and compact(), and remaining() must be less than limit(). + */ + protected DocCommentScanner(Factory fac, CharBuffer buffer) { + super(fac, buffer); + } + + /** Create a scanner from the input array. The array must have at + * least a single character of extra space. + */ + protected DocCommentScanner(Factory fac, char[] input, int inputLength) { + super(fac, input, inputLength); + } + + /** Starting position of the comment in original source + */ + private int pos; + + /** The comment input buffer, index of next chacter to be read, + * index of one past last character in buffer. + */ + private char[] buf; + private int bp; + private int buflen; + + /** The current character. + */ + private char ch; + + /** The column number position of the current character. + */ + private int col; + + /** The buffer index of the last converted Unicode character + */ + private int unicodeConversionBp = 0; + + /** + * Buffer for doc comment. + */ + private char[] docCommentBuffer = new char[1024]; + + /** + * Number of characters in doc comment buffer. + */ + private int docCommentCount; + + /** + * Translated and stripped contents of doc comment + */ + private String docComment = null; + + + /** Unconditionally expand the comment buffer. + */ + private void expandCommentBuffer() { + char[] newBuffer = new char[docCommentBuffer.length * 2]; + System.arraycopy(docCommentBuffer, 0, newBuffer, + 0, docCommentBuffer.length); + docCommentBuffer = newBuffer; + } + + /** Convert an ASCII digit from its base (8, 10, or 16) + * to its value. + */ + private int digit(int base) { + char c = ch; + int result = Character.digit(c, base); + if (result >= 0 && c > 0x7f) { + ch = "0123456789abcdef".charAt(result); + } + return result; + } + + /** Convert Unicode escape; bp points to initial '\' character + * (Spec 3.3). + */ + private void convertUnicode() { + if (ch == '\\' && unicodeConversionBp != bp) { + bp++; ch = buf[bp]; col++; + if (ch == 'u') { + do { + bp++; ch = buf[bp]; col++; + } while (ch == 'u'); + int limit = bp + 3; + if (limit < buflen) { + int d = digit(16); + int code = d; + while (bp < limit && d >= 0) { + bp++; ch = buf[bp]; col++; + d = digit(16); + code = (code << 4) + d; + } + if (d >= 0) { + ch = (char)code; + unicodeConversionBp = bp; + return; + } + } + // "illegal.Unicode.esc", reported by base scanner + } else { + bp--; + ch = '\\'; + col--; + } + } + } + + + /** Read next character. + */ + private void scanChar() { + bp++; + ch = buf[bp]; + switch (ch) { + case '\r': // return + col = 0; + break; + case '\n': // newline + if (bp == 0 || buf[bp-1] != '\r') { + col = 0; + } + break; + case '\t': // tab + col = (col / TabInc * TabInc) + TabInc; + break; + case '\\': // possible Unicode + col++; + convertUnicode(); + break; + default: + col++; + break; + } + } + + /** + * Read next character in doc comment, skipping over double '\' characters. + * If a double '\' is skipped, put in the buffer and update buffer count. + */ + private void scanDocCommentChar() { + scanChar(); + if (ch == '\\') { + if (buf[bp+1] == '\\' && unicodeConversionBp != bp) { + if (docCommentCount == docCommentBuffer.length) + expandCommentBuffer(); + docCommentBuffer[docCommentCount++] = ch; + bp++; col++; + } else { + convertUnicode(); + } + } + } + + /* Reset doc comment before reading each new token + */ + public void nextToken() { + docComment = null; + super.nextToken(); + } + + /** + * Returns the documentation string of the current token. + */ + public String docComment() { + return docComment; + } + + /** + * Process a doc comment and make the string content available. + * Strips leading whitespace and stars. + */ + @SuppressWarnings("fallthrough") + protected void processComment(CommentStyle style) { + if (style != CommentStyle.JAVADOC) { + return; + } + + pos = pos(); + buf = getRawCharacters(pos, endPos()); + buflen = buf.length; + bp = 0; + col = 0; + + docCommentCount = 0; + + boolean firstLine = true; + + // Skip over first slash + scanDocCommentChar(); + // Skip over first star + scanDocCommentChar(); + + // consume any number of stars + while (bp < buflen && ch == '*') { + scanDocCommentChar(); + } + // is the comment in the form /**/, /***/, /****/, etc. ? + if (bp < buflen && ch == '/') { + docComment = ""; + return; + } + + // skip a newline on the first line of the comment. + if (bp < buflen) { + if (ch == LF) { + scanDocCommentChar(); + firstLine = false; + } else if (ch == CR) { + scanDocCommentChar(); + if (ch == LF) { + scanDocCommentChar(); + firstLine = false; + } + } + } + + outerLoop: + + // The outerLoop processes the doc comment, looping once + // for each line. For each line, it first strips off + // whitespace, then it consumes any stars, then it + // puts the rest of the line into our buffer. + while (bp < buflen) { + + // The wsLoop consumes whitespace from the beginning + // of each line. + wsLoop: + + while (bp < buflen) { + switch(ch) { + case ' ': + scanDocCommentChar(); + break; + case '\t': + col = ((col - 1) / TabInc * TabInc) + TabInc; + scanDocCommentChar(); + break; + case FF: + col = 0; + scanDocCommentChar(); + break; +// Treat newline at beginning of line (blank line, no star) +// as comment text. Old Javadoc compatibility requires this. +/*---------------------------------* + case CR: // (Spec 3.4) + scanDocCommentChar(); + if (ch == LF) { + col = 0; + scanDocCommentChar(); + } + break; + case LF: // (Spec 3.4) + scanDocCommentChar(); + break; +*---------------------------------*/ + default: + // we've seen something that isn't whitespace; + // jump out. + break wsLoop; + } + } + + // Are there stars here? If so, consume them all + // and check for the end of comment. + if (ch == '*') { + // skip all of the stars + do { + scanDocCommentChar(); + } while (ch == '*'); + + // check for the closing slash. + if (ch == '/') { + // We're done with the doc comment + // scanChar() and breakout. + break outerLoop; + } + } else if (! firstLine) { + //The current line does not begin with a '*' so we will indent it. + for (int i = 1; i < col; i++) { + if (docCommentCount == docCommentBuffer.length) + expandCommentBuffer(); + docCommentBuffer[docCommentCount++] = ' '; + } + } + + // The textLoop processes the rest of the characters + // on the line, adding them to our buffer. + textLoop: + while (bp < buflen) { + switch (ch) { + case '*': + // Is this just a star? Or is this the + // end of a comment? + scanDocCommentChar(); + if (ch == '/') { + // This is the end of the comment, + // set ch and return our buffer. + break outerLoop; + } + // This is just an ordinary star. Add it to + // the buffer. + if (docCommentCount == docCommentBuffer.length) + expandCommentBuffer(); + docCommentBuffer[docCommentCount++] = '*'; + break; + case ' ': + case '\t': + if (docCommentCount == docCommentBuffer.length) + expandCommentBuffer(); + docCommentBuffer[docCommentCount++] = ch; + scanDocCommentChar(); + break; + case FF: + scanDocCommentChar(); + break textLoop; // treat as end of line + case CR: // (Spec 3.4) + scanDocCommentChar(); + if (ch != LF) { + // Canonicalize CR-only line terminator to LF + if (docCommentCount == docCommentBuffer.length) + expandCommentBuffer(); + docCommentBuffer[docCommentCount++] = (char)LF; + break textLoop; + } + /* fall through to LF case */ + case LF: // (Spec 3.4) + // We've seen a newline. Add it to our + // buffer and break out of this loop, + // starting fresh on a new line. + if (docCommentCount == docCommentBuffer.length) + expandCommentBuffer(); + docCommentBuffer[docCommentCount++] = ch; + scanDocCommentChar(); + break textLoop; + default: + // Add the character to our buffer. + if (docCommentCount == docCommentBuffer.length) + expandCommentBuffer(); + docCommentBuffer[docCommentCount++] = ch; + scanDocCommentChar(); + } + } // end textLoop + firstLine = false; + } // end outerLoop + + if (docCommentCount > 0) { + int i = docCommentCount - 1; + trailLoop: + while (i > -1) { + switch (docCommentBuffer[i]) { + case '*': + i--; + break; + default: + break trailLoop; + } + } + docCommentCount = i + 1; + + // Store the text of the doc comment + docComment = new String(docCommentBuffer, 0 , docCommentCount); + } else { + docComment = ""; + } + } + + /** Build a map for translating between line numbers and + * positions in the input. + * + * @return a LineMap */ + public Position.LineMap getLineMap() { + char[] buf = getRawCharacters(); + return Position.makeLineMap(buf, buf.length, true); + } +} diff --git a/src/utils/lombok/javac/java7/CommentCollectingParserFactory.java b/src/utils/lombok/javac/java7/CommentCollectingParserFactory.java index e361a5bd..e9575c14 100644 --- a/src/utils/lombok/javac/java7/CommentCollectingParserFactory.java +++ b/src/utils/lombok/javac/java7/CommentCollectingParserFactory.java @@ -30,8 +30,8 @@ public class CommentCollectingParserFactory extends ParserFactory { public Parser newParser(CharSequence input, boolean keepDocComments, boolean keepEndPos, boolean keepLineMap) { ScannerFactory scannerFactory = ScannerFactory.instance(context); - Lexer lexer = scannerFactory.newScanner(input, keepDocComments); - Object x = new CommentCollectingParser(this, lexer, keepDocComments, keepLineMap, commentsMap); + Lexer lexer = scannerFactory.newScanner(input, true); + Object x = new CommentCollectingParser(this, lexer, true, keepLineMap, commentsMap); return (Parser) x; // CCP is based on a stub which extends nothing, but at runtime the stub is replaced with either //javac6's EndPosParser which extends Parser, or javac7's EndPosParser which implements Parser. diff --git a/src/utils/lombok/javac/java7/CommentCollectingScanner.java b/src/utils/lombok/javac/java7/CommentCollectingScanner.java index e2d040f2..6ebd3ac1 100644 --- a/src/utils/lombok/javac/java7/CommentCollectingScanner.java +++ b/src/utils/lombok/javac/java7/CommentCollectingScanner.java @@ -26,12 +26,12 @@ import java.nio.CharBuffer; import lombok.javac.CommentInfo; import lombok.javac.CommentInfo.EndConnection; import lombok.javac.CommentInfo.StartConnection; + import com.sun.tools.javac.util.List; import com.sun.tools.javac.util.ListBuffer; +import com.sun.tools.javac.parser.DocCommentScanner; -import com.sun.tools.javac.parser.Scanner; - -public class CommentCollectingScanner extends Scanner { +public class CommentCollectingScanner extends DocCommentScanner { private final ListBuffer<CommentInfo> comments = ListBuffer.lb(); private int endComment = 0; @@ -55,6 +55,7 @@ public class CommentCollectingScanner extends Scanner { CommentInfo comment = new CommentInfo(prevEndPos, pos, endPos, content, start, end); comments.append(comment); + super.processComment(style); } private EndConnection determineEndConnection(int pos) { diff --git a/src/utils/lombok/javac/java7/CommentCollectingScannerFactory.java b/src/utils/lombok/javac/java7/CommentCollectingScannerFactory.java index 9a29528e..626d3d63 100644 --- a/src/utils/lombok/javac/java7/CommentCollectingScannerFactory.java +++ b/src/utils/lombok/javac/java7/CommentCollectingScannerFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011 The Project Lombok Authors. + * Copyright (C) 2011-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 @@ -29,17 +29,41 @@ import com.sun.tools.javac.util.Context; public class CommentCollectingScannerFactory extends ScannerFactory { + @SuppressWarnings("all") public static void preRegister(final Context context) { if (context.get(scannerFactoryKey) == null) { - context.put(scannerFactoryKey, new Context.Factory<ScannerFactory>() { - public ScannerFactory make() { + // Careful! There is voodoo magic here! + // + // Context.Factory is parameterized. make() is for javac6 and below; make(Context) is for javac7 and up. + // this anonymous inner class definition is intentionally 'raw' - the return type of both 'make' methods is 'T', + // which means the compiler will only generate the correct "real" override method (with returntype Object, which is + // the lower bound for T, as a synthetic accessor for the make with returntype ScannerFactory) for that make method which + // is actually on the classpath (either make() for javac6-, or make(Context) for javac7+). + // + // We normally solve this issue via src/stubs, with BOTH make methods listed, but for some reason the presence of a stubbed out + // Context (or even a complete copy, it doesn't matter) results in a really strange eclipse bug, where any mention of any kind + // of com.sun.tools.javac.tree.TreeMaker in a source file disables ALL usage of 'go to declaration' and auto-complete in the entire + // source file. + // + // Thus, in short: + // * Do NOT parameterize the anonymous inner class literal. + // * Leave the return types as 'j.l.Object'. + // * Leave both make methods intact; deleting one has no effect on javac6- / javac7+, but breaks the other. Hard to test for. + // * Do not stub com.sun.tools.javac.util.Context or any of its inner types, like Factory. + @SuppressWarnings("all") + class MyFactory implements Context.Factory { + // This overrides the javac6- version of make. + public Object make() { return new CommentCollectingScannerFactory(context); } - @Override public ScannerFactory make(Context c) { + // This overrides the javac7+ version. + public Object make(Context c) { return new CommentCollectingScannerFactory(c); } - }); + } + @SuppressWarnings("unchecked") Context.Factory<ScannerFactory> factory = new MyFactory(); + context.put(scannerFactoryKey, factory); } } diff --git a/test/core/src/lombok/AbstractRunTests.java b/test/core/src/lombok/AbstractRunTests.java index a3f52cdd..2f3f0988 100644 --- a/test/core/src/lombok/AbstractRunTests.java +++ b/test/core/src/lombok/AbstractRunTests.java @@ -43,7 +43,6 @@ import java.util.List; import lombok.javac.CapturingDiagnosticListener.CompilerMessage; public abstract class AbstractRunTests { - protected static final String LINE_SEPARATOR = System.getProperty("line.separator"); private final File dumpActualFilesHere; public AbstractRunTests() { @@ -69,10 +68,12 @@ public abstract class AbstractRunTests { } } - StringReader r = new StringReader(expectedFile); - BufferedReader br = new BufferedReader(r); - String firstLine = br.readLine(); - if (firstLine != null && firstLine.startsWith("//ignore")) return false; + if (expectedFile != null) { + StringReader r = new StringReader(expectedFile); + BufferedReader br = new BufferedReader(r); + String firstLine = br.readLine(); + if (firstLine != null && (firstLine.startsWith("//ignore") || params.shouldIgnoreBasedOnVersion(firstLine))) return false; + } compare( file.getName(), @@ -92,20 +93,20 @@ public abstract class AbstractRunTests { try { reader = new BufferedReader(new FileReader(file)); } catch (FileNotFoundException e) { - return ""; + return null; } StringBuilder result = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { result.append(line); - result.append(LINE_SEPARATOR); + result.append("\n"); } reader.close(); return result.toString(); } private String readFile(File dir, File file, boolean messages) throws IOException { - if (dir == null) return ""; + if (dir == null) return null; return readFile(new File(dir, file.getName() + (messages ? ".messages" : ""))); } @@ -141,7 +142,9 @@ public abstract class AbstractRunTests { } private void compare(String name, String expectedFile, String actualFile, List<CompilerMessageMatcher> expectedMessages, LinkedHashSet<CompilerMessage> actualMessages, boolean printErrors) throws Throwable { - try { + if (expectedFile == null && expectedMessages.isEmpty()) expectedFile = ""; + + if (expectedFile != null) try { compareContent(name, expectedFile, actualFile); } catch (Throwable e) { if (printErrors) { diff --git a/test/core/src/lombok/DirectoryRunner.java b/test/core/src/lombok/DirectoryRunner.java index 191a7b63..5325c1e3 100644 --- a/test/core/src/lombok/DirectoryRunner.java +++ b/test/core/src/lombok/DirectoryRunner.java @@ -29,6 +29,11 @@ import java.io.FileReader; import java.io.IOException; import java.util.Map; import java.util.TreeMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import lombok.eclipse.Eclipse; +import lombok.javac.Javac; import org.junit.runner.Description; import org.junit.runner.Runner; @@ -37,7 +42,23 @@ import org.junit.runner.notification.RunNotifier; public class DirectoryRunner extends Runner { public enum Compiler { - DELOMBOK, JAVAC, ECJ; + DELOMBOK { + @Override public int getVersion() { + return Javac.getJavaCompilerVersion(); + } + }, + JAVAC { + @Override public int getVersion() { + return DELOMBOK.getVersion(); + } + }, + ECJ { + @Override public int getVersion() { + return Eclipse.getEcjCompilerVersion(); + } + }; + + public abstract int getVersion(); } public static abstract class TestParams { @@ -46,10 +67,52 @@ public class DirectoryRunner extends Runner { public abstract File getBeforeDirectory(); public abstract File getAfterDirectory(); public abstract File getMessagesDirectory(); + /** Version of the JDK dialect that the compiler can understand; for example, if you return '7', you should know what try-with-resources is. */ + public int getVersion() { + return getCompiler().getVersion(); + } public boolean accept(File file) { return true; } + + private static final Pattern P1 = Pattern.compile("^(\\d+)$"); + private static final Pattern P2 = Pattern.compile("^\\:(\\d+)$"); + private static final Pattern P3 = Pattern.compile("^(\\d+):$"); + private static final Pattern P4 = Pattern.compile("^(\\d+):(\\d+)$"); + + public boolean shouldIgnoreBasedOnVersion(String firstLine) { + int thisVersion = getVersion(); + if (!firstLine.startsWith("//version ")) return false; + + String spec = firstLine.substring("//version ".length()); + + /* Single version: '5' */ { + Matcher m = P1.matcher(spec); + if (m.matches()) return Integer.parseInt(m.group(1)) != thisVersion; + } + + /* Upper bound: ':5' (inclusive) */ { + Matcher m = P2.matcher(spec); + if (m.matches()) return Integer.parseInt(m.group(1)) < thisVersion; + } + + /* Lower bound '5:' (inclusive) */ { + Matcher m = P3.matcher(spec); + if (m.matches()) return Integer.parseInt(m.group(1)) > thisVersion; + } + + /* Range '7:8' (inclusive) */ { + Matcher m = P4.matcher(spec); + if (m.matches()) { + if (Integer.parseInt(m.group(1)) < thisVersion) return true; + if (Integer.parseInt(m.group(2)) > thisVersion) return true; + return false; + } + } + + throw new IllegalArgumentException("Version validity spec not valid: " + spec); + } } private static final FileFilter JAVA_FILE_FILTER = new FileFilter() { @@ -135,6 +198,6 @@ public class DirectoryRunner extends Runner { BufferedReader reader = new BufferedReader(new FileReader(file)); String line = reader.readLine(); reader.close(); - return line != null && line.startsWith("//ignore"); + return line != null && (line.startsWith("//ignore") || params.shouldIgnoreBasedOnVersion(line)); } } diff --git a/test/core/src/lombok/RunTestsViaEcj.java b/test/core/src/lombok/RunTestsViaEcj.java index 0bf97213..f7294f1f 100644 --- a/test/core/src/lombok/RunTestsViaEcj.java +++ b/test/core/src/lombok/RunTestsViaEcj.java @@ -33,6 +33,7 @@ import java.util.Locale; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; +import lombok.eclipse.Eclipse; import lombok.javac.CapturingDiagnosticListener.CompilerMessage; import org.eclipse.jdt.core.compiler.CategorizedProblem; @@ -43,7 +44,6 @@ import org.eclipse.jdt.internal.compiler.IErrorHandlingPolicy; import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; import org.eclipse.jdt.internal.compiler.batch.CompilationUnit; import org.eclipse.jdt.internal.compiler.batch.FileSystem; -import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; import org.eclipse.jdt.internal.compiler.env.ICompilationUnit; import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory; @@ -51,9 +51,10 @@ import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory; public class RunTestsViaEcj extends AbstractRunTests { protected CompilerOptions ecjCompilerOptions() { CompilerOptions options = new CompilerOptions(); - options.complianceLevel = ClassFileConstants.JDK1_6; - options.sourceLevel = ClassFileConstants.JDK1_6; - options.targetJDK = ClassFileConstants.JDK1_6; + options.complianceLevel = Eclipse.getLatestEcjCompilerVersionConstant(); + options.sourceLevel = Eclipse.getLatestEcjCompilerVersionConstant(); + options.targetJDK = Eclipse.getLatestEcjCompilerVersionConstant(); + options.docCommentSupport = false; options.parseLiteralExpressionsAsConstants = true; options.inlineJsrBytecode = true; options.reportUnusedDeclaredThrownExceptionExemptExceptionAndThrowable = false; @@ -64,7 +65,6 @@ public class RunTestsViaEcj extends AbstractRunTests { options.reportUnusedParameterWhenOverridingConcrete = false; options.reportDeadCodeInTrivialIfStatement = false; options.generateClassFiles = false; - options.docCommentSupport = false; Map<String, String> warnings = new HashMap<String, String>(); warnings.put(CompilerOptions.OPTION_ReportUnusedLocal, "ignore"); warnings.put(CompilerOptions.OPTION_ReportUnusedLabel, "ignore"); @@ -129,10 +129,11 @@ public class RunTestsViaEcj extends AbstractRunTests { } } classpath.add("dist/lombok.jar"); - classpath.add("lib/test/commons-logging.jar"); - classpath.add("lib/test/slf4j-api.jar"); - classpath.add("lib/test/slf4j-ext.jar"); - classpath.add("lib/test/log4j.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"); return new FileSystem(classpath.toArray(new String[0]), new String[] {file.getAbsolutePath()}, "UTF-8"); } } diff --git a/test/pretty/resource/after/TryWithResources.java b/test/pretty/resource/after/TryWithResources.java new file mode 100644 index 00000000..1a8b82e2 --- /dev/null +++ b/test/pretty/resource/after/TryWithResources.java @@ -0,0 +1,9 @@ +//version 7: +import java.io.PrintWriter; +public class TryWithResources { + { + try (final PrintWriter pw = new PrintWriter(System.out);) { + pw.println(); + } + } +} diff --git a/test/pretty/resource/before/TryWithResources.java b/test/pretty/resource/before/TryWithResources.java new file mode 100644 index 00000000..eb622f2c --- /dev/null +++ b/test/pretty/resource/before/TryWithResources.java @@ -0,0 +1,9 @@ +//version 7: +import java.io.PrintWriter; +public class TryWithResources { + { + try (PrintWriter pw = new PrintWriter(System.out)) { + pw.println(); + } + } +} diff --git a/test/transform/resource/after-delombok/BuilderChainAndFluent.java b/test/transform/resource/after-delombok/BuilderChainAndFluent.java new file mode 100644 index 00000000..d4975bff --- /dev/null +++ b/test/transform/resource/after-delombok/BuilderChainAndFluent.java @@ -0,0 +1,31 @@ +class BuilderChainAndFluent { + private final int yes; + @java.lang.SuppressWarnings("all") + BuilderChainAndFluent(final int yes) { + this.yes = yes; + } + @java.lang.SuppressWarnings("all") + public static class BuilderChainAndFluentBuilder { + private int yes; + @java.lang.SuppressWarnings("all") + BuilderChainAndFluentBuilder() { + } + @java.lang.SuppressWarnings("all") + public void setYes(final int yes) { + this.yes = yes; + } + @java.lang.SuppressWarnings("all") + public BuilderChainAndFluent build() { + return new BuilderChainAndFluent(yes); + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public java.lang.String toString() { + return "BuilderChainAndFluent.BuilderChainAndFluentBuilder(yes=" + this.yes + ")"; + } + } + @java.lang.SuppressWarnings("all") + public static BuilderChainAndFluentBuilder builder() { + return new BuilderChainAndFluentBuilder(); + } +}
\ No newline at end of file diff --git a/test/transform/resource/after-delombok/BuilderComplex.java b/test/transform/resource/after-delombok/BuilderComplex.java new file mode 100644 index 00000000..3c97f92a --- /dev/null +++ b/test/transform/resource/after-delombok/BuilderComplex.java @@ -0,0 +1,48 @@ +import java.util.List; +class BuilderComplex { + private static <T extends Number> void testVoidWithGenerics(T number, int arg2, String arg3, BuilderComplex selfRef) { + } + @java.lang.SuppressWarnings("all") + public static class VoidBuilder<T extends Number> { + private T number; + private int arg2; + private String arg3; + private BuilderComplex selfRef; + @java.lang.SuppressWarnings("all") + VoidBuilder() { + } + @java.lang.SuppressWarnings("all") + public VoidBuilder<T> number(final T number) { + this.number = number; + return this; + } + @java.lang.SuppressWarnings("all") + public VoidBuilder<T> arg2(final int arg2) { + this.arg2 = arg2; + return this; + } + @java.lang.SuppressWarnings("all") + public VoidBuilder<T> arg3(final String arg3) { + this.arg3 = arg3; + return this; + } + @java.lang.SuppressWarnings("all") + public VoidBuilder<T> selfRef(final BuilderComplex selfRef) { + this.selfRef = selfRef; + return this; + } + @java.lang.SuppressWarnings("all") + public void execute() { + BuilderComplex.<T>testVoidWithGenerics(number, arg2, arg3, selfRef); + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public java.lang.String toString() { + return "BuilderComplex.VoidBuilder(number=" + this.number + ", arg2=" + this.arg2 + ", arg3=" + this.arg3 + ", selfRef=" + this.selfRef + ")"; + } + } + @java.lang.SuppressWarnings("all") + public static <T extends Number> VoidBuilder<T> builder() { + return new VoidBuilder<T>(); + } +}
\ No newline at end of file diff --git a/test/transform/resource/after-delombok/BuilderSimple.java b/test/transform/resource/after-delombok/BuilderSimple.java new file mode 100644 index 00000000..11c0e58c --- /dev/null +++ b/test/transform/resource/after-delombok/BuilderSimple.java @@ -0,0 +1,43 @@ +import java.util.List; +class BuilderSimple<T> { + private final int noshow = 0; + private final int yes; + private List<T> also; + private int $butNotMe; + @java.lang.SuppressWarnings("all") + BuilderSimple(final int yes, final List<T> also) { + this.yes = yes; + this.also = also; + } + @java.lang.SuppressWarnings("all") + public static class BuilderSimpleBuilder<T> { + private int yes; + private List<T> also; + @java.lang.SuppressWarnings("all") + BuilderSimpleBuilder() { + } + @java.lang.SuppressWarnings("all") + public BuilderSimpleBuilder<T> yes(final int yes) { + this.yes = yes; + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderSimpleBuilder<T> also(final List<T> also) { + this.also = also; + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderSimple<T> build() { + return new BuilderSimple<T>(yes, also); + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public java.lang.String toString() { + return "BuilderSimple.BuilderSimpleBuilder(yes=" + this.yes + ", also=" + this.also + ")"; + } + } + @java.lang.SuppressWarnings("all") + public static <T> BuilderSimpleBuilder<T> builder() { + return new BuilderSimpleBuilder<T>(); + } +} diff --git a/test/transform/resource/after-delombok/BuilderWithExistingBuilderClass.java b/test/transform/resource/after-delombok/BuilderWithExistingBuilderClass.java new file mode 100644 index 00000000..1d40dbfa --- /dev/null +++ b/test/transform/resource/after-delombok/BuilderWithExistingBuilderClass.java @@ -0,0 +1,38 @@ +class BuilderWithExistingBuilderClass<T, K extends Number> { + public static <Z extends Number> BuilderWithExistingBuilderClass<String, Z> staticMethod(Z arg1, boolean arg2, String arg3) { + return null; + } + public static class BuilderWithExistingBuilderClassBuilder<Z extends Number> { + private boolean arg2; + private String arg3; + private Z arg1; + public void arg2(boolean arg) { + } + @java.lang.SuppressWarnings("all") + BuilderWithExistingBuilderClassBuilder() { + } + @java.lang.SuppressWarnings("all") + public BuilderWithExistingBuilderClassBuilder<Z> arg1(final Z arg1) { + this.arg1 = arg1; + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderWithExistingBuilderClassBuilder<Z> arg3(final String arg3) { + this.arg3 = arg3; + return this; + } + @java.lang.SuppressWarnings("all") + public BuilderWithExistingBuilderClass build() { + return BuilderWithExistingBuilderClass.<Z>staticMethod(arg1, arg2, arg3); + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public java.lang.String toString() { + return "BuilderWithExistingBuilderClass.BuilderWithExistingBuilderClassBuilder(arg1=" + this.arg1 + ", arg2=" + this.arg2 + ", arg3=" + this.arg3 + ")"; + } + } + @java.lang.SuppressWarnings("all") + public static <Z extends Number> BuilderWithExistingBuilderClassBuilder<Z> builder() { + return new BuilderWithExistingBuilderClassBuilder<Z>(); + } +}
\ No newline at end of file diff --git a/test/transform/resource/after-delombok/CommentsInterspersed.java b/test/transform/resource/after-delombok/CommentsInterspersed.java index 5aaafe42..833f2ce0 100644 --- a/test/transform/resource/after-delombok/CommentsInterspersed.java +++ b/test/transform/resource/after-delombok/CommentsInterspersed.java @@ -1,9 +1,13 @@ /* cmt *//* cmt2 */ /* cmt3 */ /*bla */ public class CommentsInterspersed { - /** javadoc for field */ + /** + * javadoc for field + */ private int x; /* bla2 */ private String test = "foo"; //$NON-NLS-1$ - /** Javadoc on method */ + /** + * Javadoc on method + */ public native void gwtTest(); /*-{ javascript; }-*/ diff --git a/test/transform/resource/after-delombok/DataOnLocalClass.java b/test/transform/resource/after-delombok/DataOnLocalClass.java index ed4d30ca..abe2757b 100644 --- a/test/transform/resource/after-delombok/DataOnLocalClass.java +++ b/test/transform/resource/after-delombok/DataOnLocalClass.java @@ -63,7 +63,9 @@ class DataOnLocalClass2 { String name; @java.lang.SuppressWarnings("all") public InnerLocal(@lombok.NonNull final String name) { - if (name == null) throw new java.lang.NullPointerException("name"); + if (name == null) { + throw new java.lang.NullPointerException("name"); + } this.name = name; } @lombok.NonNull @@ -73,7 +75,9 @@ class DataOnLocalClass2 { } @java.lang.SuppressWarnings("all") public void setName(@lombok.NonNull final String name) { - if (name == null) throw new java.lang.NullPointerException("name"); + if (name == null) { + throw new java.lang.NullPointerException("name"); + } this.name = name; } @java.lang.Override diff --git a/test/transform/resource/after-delombok/DelegateOnGetter.java b/test/transform/resource/after-delombok/DelegateOnGetter.java index 08d682a2..e8d01a49 100644 --- a/test/transform/resource/after-delombok/DelegateOnGetter.java +++ b/test/transform/resource/after-delombok/DelegateOnGetter.java @@ -1,12 +1,12 @@ class DelegateOnGetter { - private final java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<Bar>> bar = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<Bar>>(); + private final java.util.concurrent.atomic.AtomicReference<java.lang.Object> bar = new java.util.concurrent.atomic.AtomicReference<java.lang.Object>(); private interface Bar { void setList(java.util.ArrayList<java.lang.String> list); int getInt(); } @java.lang.SuppressWarnings("all") public Bar getBar() { - java.util.concurrent.atomic.AtomicReference<Bar> value = this.bar.get(); + java.lang.Object value = this.bar.get(); if (value == null) { synchronized (this.bar) { value = this.bar.get(); @@ -18,12 +18,12 @@ class DelegateOnGetter { return 42; } }; - value = new java.util.concurrent.atomic.AtomicReference<Bar>(actualValue); + value = actualValue == null ? this.bar : actualValue; this.bar.set(value); } } } - return value.get(); + return (Bar)(value == this.bar ? null : value); } @java.lang.SuppressWarnings("all") public void setList(final java.util.ArrayList<java.lang.String> list) { diff --git a/test/transform/resource/after-delombok/DelegateWithDeprecated.java b/test/transform/resource/after-delombok/DelegateWithDeprecated.java index 04e12160..f7bd1e6d 100644 --- a/test/transform/resource/after-delombok/DelegateWithDeprecated.java +++ b/test/transform/resource/after-delombok/DelegateWithDeprecated.java @@ -3,7 +3,9 @@ class DelegateWithDeprecated { private interface Bar { @Deprecated void deprecatedAnnotation(); - /** @deprecated */ + /** + * @deprecated + */ void deprecatedComment(); void notDeprecated(); } diff --git a/test/transform/resource/after-delombok/EqualsAndHashCodeWithSomeExistingMethods.java b/test/transform/resource/after-delombok/EqualsAndHashCodeWithSomeExistingMethods.java new file mode 100644 index 00000000..0a6b1e7f --- /dev/null +++ b/test/transform/resource/after-delombok/EqualsAndHashCodeWithSomeExistingMethods.java @@ -0,0 +1,82 @@ +import lombok.*; +import static lombok.AccessLevel.NONE; +class EqualsAndHashCodeWithSomeExistingMethods { + int x; + public int hashCode() { + return 42; + } + @java.lang.SuppressWarnings("all") + public EqualsAndHashCodeWithSomeExistingMethods() { + + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public java.lang.String toString() { + return "EqualsAndHashCodeWithSomeExistingMethods(x=" + this.x + ")"; + } +} +class EqualsAndHashCodeWithSomeExistingMethods2 { + int x; + public boolean canEqual(Object other) { + return false; + } + @java.lang.SuppressWarnings("all") + public EqualsAndHashCodeWithSomeExistingMethods2() { + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public java.lang.String toString() { + return "EqualsAndHashCodeWithSomeExistingMethods2(x=" + this.x + ")"; + } +} +class EqualsAndHashCodeWithAllExistingMethods { + int x; + public int hashCode() { + return 42; + } + public boolean equals(Object other) { + return false; + } + @java.lang.SuppressWarnings("all") + public EqualsAndHashCodeWithAllExistingMethods() { + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public java.lang.String toString() { + return "EqualsAndHashCodeWithAllExistingMethods(x=" + this.x + ")"; + } +} +class EqualsAndHashCodeWithNoExistingMethods { + int x; + @java.lang.SuppressWarnings("all") + public EqualsAndHashCodeWithNoExistingMethods() { + + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public boolean equals(final java.lang.Object o) { + if (o == this) return true; + if (!(o instanceof EqualsAndHashCodeWithNoExistingMethods)) return false; + final EqualsAndHashCodeWithNoExistingMethods other = (EqualsAndHashCodeWithNoExistingMethods)o; + if (!other.canEqual((java.lang.Object)this)) return false; + if (this.x != other.x) return false; + return true; + } + @java.lang.SuppressWarnings("all") + public boolean canEqual(final java.lang.Object other) { + return other instanceof EqualsAndHashCodeWithNoExistingMethods; + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = result * PRIME + this.x; + return result; + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public java.lang.String toString() { + return "EqualsAndHashCodeWithNoExistingMethods(x=" + this.x + ")"; + } +} diff --git a/test/transform/resource/after-delombok/GetterDeprecated.java b/test/transform/resource/after-delombok/GetterDeprecated.java index 3387540f..09ea9929 100644 --- a/test/transform/resource/after-delombok/GetterDeprecated.java +++ b/test/transform/resource/after-delombok/GetterDeprecated.java @@ -10,6 +10,9 @@ class GetterDeprecated { public int getAnnotation() { return this.annotation; } + /** + * @deprecated + */ @java.lang.Deprecated @java.lang.SuppressWarnings("all") public int getJavadoc() { diff --git a/test/transform/resource/after-delombok/GetterLazy.java b/test/transform/resource/after-delombok/GetterLazy.java index 95be39e3..4f6f2c03 100644 --- a/test/transform/resource/after-delombok/GetterLazy.java +++ b/test/transform/resource/after-delombok/GetterLazy.java @@ -1,20 +1,20 @@ class GetterLazy { static class ValueType { } - private final java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<ValueType>> fieldName = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<ValueType>>(); + private final java.util.concurrent.atomic.AtomicReference<java.lang.Object> fieldName = new java.util.concurrent.atomic.AtomicReference<java.lang.Object>(); @java.lang.SuppressWarnings("all") public ValueType getFieldName() { - java.util.concurrent.atomic.AtomicReference<ValueType> value = this.fieldName.get(); + java.lang.Object value = this.fieldName.get(); if (value == null) { synchronized (this.fieldName) { value = this.fieldName.get(); if (value == null) { final ValueType actualValue = new ValueType(); - value = new java.util.concurrent.atomic.AtomicReference<ValueType>(actualValue); + value = actualValue == null ? this.fieldName : actualValue; this.fieldName.set(value); } } } - return value.get(); + return (ValueType)(value == this.fieldName ? null : value); } } diff --git a/test/transform/resource/after-delombok/GetterLazyBoolean.java b/test/transform/resource/after-delombok/GetterLazyBoolean.java index caab2803..08d32013 100644 --- a/test/transform/resource/after-delombok/GetterLazyBoolean.java +++ b/test/transform/resource/after-delombok/GetterLazyBoolean.java @@ -1,6 +1,6 @@ class GetterLazyBoolean { - private final java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Boolean>> booleanValue = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Boolean>>(); - private final java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Boolean>> otherBooleanValue = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Boolean>>(); + private final java.util.concurrent.atomic.AtomicReference<java.lang.Object> booleanValue = new java.util.concurrent.atomic.AtomicReference<java.lang.Object>(); + private final java.util.concurrent.atomic.AtomicReference<java.lang.Object> otherBooleanValue = new java.util.concurrent.atomic.AtomicReference<java.lang.Object>(); private static boolean calculateBoolean() { return true; } @@ -33,32 +33,32 @@ class GetterLazyBoolean { } @java.lang.SuppressWarnings("all") public boolean isBooleanValue() { - java.util.concurrent.atomic.AtomicReference<java.lang.Boolean> value = this.booleanValue.get(); + java.lang.Object value = this.booleanValue.get(); if (value == null) { synchronized (this.booleanValue) { value = this.booleanValue.get(); if (value == null) { final boolean actualValue = calculateBoolean(); - value = new java.util.concurrent.atomic.AtomicReference<java.lang.Boolean>(actualValue); + value = actualValue; this.booleanValue.set(value); } } } - return value.get(); + return (java.lang.Boolean)value; } @java.lang.SuppressWarnings("all") public boolean isOtherBooleanValue() { - java.util.concurrent.atomic.AtomicReference<java.lang.Boolean> value = this.otherBooleanValue.get(); + java.lang.Object value = this.otherBooleanValue.get(); if (value == null) { synchronized (this.otherBooleanValue) { value = this.otherBooleanValue.get(); if (value == null) { final boolean actualValue = !calculateBoolean(); - value = new java.util.concurrent.atomic.AtomicReference<java.lang.Boolean>(actualValue); + value = actualValue; this.otherBooleanValue.set(value); } } } - return value.get(); + return (java.lang.Boolean)value; } } diff --git a/test/transform/resource/after-delombok/GetterLazyEahcToString.java b/test/transform/resource/after-delombok/GetterLazyEahcToString.java index ce3555fe..1358e536 100644 --- a/test/transform/resource/after-delombok/GetterLazyEahcToString.java +++ b/test/transform/resource/after-delombok/GetterLazyEahcToString.java @@ -1,6 +1,6 @@ class GetterLazyEahcToString { - private final java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<String>> value = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<String>>(); + private final java.util.concurrent.atomic.AtomicReference<java.lang.Object> value = new java.util.concurrent.atomic.AtomicReference<java.lang.Object>(); private final String value2 = ""; @java.lang.Override @@ -44,18 +44,18 @@ class GetterLazyEahcToString { @java.lang.SuppressWarnings("all") public String getValue() { - java.util.concurrent.atomic.AtomicReference<String> value = this.value.get(); + java.lang.Object value = this.value.get(); if (value == null) { synchronized (this.value) { value = this.value.get(); if (value == null) { final String actualValue = ""; - value = new java.util.concurrent.atomic.AtomicReference<String>(actualValue); + value = actualValue == null ? this.value : actualValue; this.value.set(value); } } } - return value.get(); + return (String)(value == this.value ? null : value); } @java.lang.SuppressWarnings("all") diff --git a/test/transform/resource/after-delombok/GetterLazyNative.java b/test/transform/resource/after-delombok/GetterLazyNative.java index a10075ba..eb31991a 100644 --- a/test/transform/resource/after-delombok/GetterLazyNative.java +++ b/test/transform/resource/after-delombok/GetterLazyNative.java @@ -1,146 +1,146 @@ class GetterLazyNative { - private final java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Boolean>> booleanField = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Boolean>>(); - private final java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Byte>> byteField = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Byte>>(); - private final java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Short>> shortField = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Short>>(); - private final java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Integer>> intField = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Integer>>(); - private final java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Long>> longField = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Long>>(); - private final java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Float>> floatField = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Float>>(); - private final java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Double>> doubleField = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Double>>(); - private final java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Character>> charField = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Character>>(); - private final java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<int[]>> intArrayField = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<int[]>>(); + private final java.util.concurrent.atomic.AtomicReference<java.lang.Object> booleanField = new java.util.concurrent.atomic.AtomicReference<java.lang.Object>(); + private final java.util.concurrent.atomic.AtomicReference<java.lang.Object> byteField = new java.util.concurrent.atomic.AtomicReference<java.lang.Object>(); + private final java.util.concurrent.atomic.AtomicReference<java.lang.Object> shortField = new java.util.concurrent.atomic.AtomicReference<java.lang.Object>(); + private final java.util.concurrent.atomic.AtomicReference<java.lang.Object> intField = new java.util.concurrent.atomic.AtomicReference<java.lang.Object>(); + private final java.util.concurrent.atomic.AtomicReference<java.lang.Object> longField = new java.util.concurrent.atomic.AtomicReference<java.lang.Object>(); + private final java.util.concurrent.atomic.AtomicReference<java.lang.Object> floatField = new java.util.concurrent.atomic.AtomicReference<java.lang.Object>(); + private final java.util.concurrent.atomic.AtomicReference<java.lang.Object> doubleField = new java.util.concurrent.atomic.AtomicReference<java.lang.Object>(); + private final java.util.concurrent.atomic.AtomicReference<java.lang.Object> charField = new java.util.concurrent.atomic.AtomicReference<java.lang.Object>(); + private final java.util.concurrent.atomic.AtomicReference<java.lang.Object> intArrayField = new java.util.concurrent.atomic.AtomicReference<java.lang.Object>(); @java.lang.SuppressWarnings("all") public boolean isBooleanField() { - java.util.concurrent.atomic.AtomicReference<java.lang.Boolean> value = this.booleanField.get(); + java.lang.Object value = this.booleanField.get(); if (value == null) { synchronized (this.booleanField) { value = this.booleanField.get(); if (value == null) { final boolean actualValue = true; - value = new java.util.concurrent.atomic.AtomicReference<java.lang.Boolean>(actualValue); + value = actualValue; this.booleanField.set(value); } } } - return value.get(); + return (java.lang.Boolean)value; } @java.lang.SuppressWarnings("all") public byte getByteField() { - java.util.concurrent.atomic.AtomicReference<java.lang.Byte> value = this.byteField.get(); + java.lang.Object value = this.byteField.get(); if (value == null) { synchronized (this.byteField) { value = this.byteField.get(); if (value == null) { final byte actualValue = 1; - value = new java.util.concurrent.atomic.AtomicReference<java.lang.Byte>(actualValue); + value = actualValue; this.byteField.set(value); } } } - return value.get(); + return (java.lang.Byte)value; } @java.lang.SuppressWarnings("all") public short getShortField() { - java.util.concurrent.atomic.AtomicReference<java.lang.Short> value = this.shortField.get(); + java.lang.Object value = this.shortField.get(); if (value == null) { synchronized (this.shortField) { value = this.shortField.get(); if (value == null) { final short actualValue = 1; - value = new java.util.concurrent.atomic.AtomicReference<java.lang.Short>(actualValue); + value = actualValue; this.shortField.set(value); } } } - return value.get(); + return (java.lang.Short)value; } @java.lang.SuppressWarnings("all") public int getIntField() { - java.util.concurrent.atomic.AtomicReference<java.lang.Integer> value = this.intField.get(); + java.lang.Object value = this.intField.get(); if (value == null) { synchronized (this.intField) { value = this.intField.get(); if (value == null) { final int actualValue = 1; - value = new java.util.concurrent.atomic.AtomicReference<java.lang.Integer>(actualValue); + value = actualValue; this.intField.set(value); } } } - return value.get(); + return (java.lang.Integer)value; } @java.lang.SuppressWarnings("all") public long getLongField() { - java.util.concurrent.atomic.AtomicReference<java.lang.Long> value = this.longField.get(); + java.lang.Object value = this.longField.get(); if (value == null) { synchronized (this.longField) { value = this.longField.get(); if (value == null) { final long actualValue = 1; - value = new java.util.concurrent.atomic.AtomicReference<java.lang.Long>(actualValue); + value = actualValue; this.longField.set(value); } } } - return value.get(); + return (java.lang.Long)value; } @java.lang.SuppressWarnings("all") public float getFloatField() { - java.util.concurrent.atomic.AtomicReference<java.lang.Float> value = this.floatField.get(); + java.lang.Object value = this.floatField.get(); if (value == null) { synchronized (this.floatField) { value = this.floatField.get(); if (value == null) { final float actualValue = 1.0F; - value = new java.util.concurrent.atomic.AtomicReference<java.lang.Float>(actualValue); + value = actualValue; this.floatField.set(value); } } } - return value.get(); + return (java.lang.Float)value; } @java.lang.SuppressWarnings("all") public double getDoubleField() { - java.util.concurrent.atomic.AtomicReference<java.lang.Double> value = this.doubleField.get(); + java.lang.Object value = this.doubleField.get(); if (value == null) { synchronized (this.doubleField) { value = this.doubleField.get(); if (value == null) { final double actualValue = 1.0; - value = new java.util.concurrent.atomic.AtomicReference<java.lang.Double>(actualValue); + value = actualValue; this.doubleField.set(value); } } } - return value.get(); + return (java.lang.Double)value; } @java.lang.SuppressWarnings("all") public char getCharField() { - java.util.concurrent.atomic.AtomicReference<java.lang.Character> value = this.charField.get(); + java.lang.Object value = this.charField.get(); if (value == null) { synchronized (this.charField) { value = this.charField.get(); if (value == null) { final char actualValue = '1'; - value = new java.util.concurrent.atomic.AtomicReference<java.lang.Character>(actualValue); + value = actualValue; this.charField.set(value); } } } - return value.get(); + return (java.lang.Character)value; } @java.lang.SuppressWarnings("all") public int[] getIntArrayField() { - java.util.concurrent.atomic.AtomicReference<int[]> value = this.intArrayField.get(); + java.lang.Object value = this.intArrayField.get(); if (value == null) { synchronized (this.intArrayField) { value = this.intArrayField.get(); if (value == null) { final int[] actualValue = new int[]{1}; - value = new java.util.concurrent.atomic.AtomicReference<int[]>(actualValue); + value = actualValue == null ? this.intArrayField : actualValue; this.intArrayField.set(value); } } } - return value.get(); + return (int[])(value == this.intArrayField ? null : value); } -}
\ No newline at end of file +} diff --git a/test/transform/resource/after-delombok/GetterSetterJavadoc.java b/test/transform/resource/after-delombok/GetterSetterJavadoc.java new file mode 100644 index 00000000..7bf92d1f --- /dev/null +++ b/test/transform/resource/after-delombok/GetterSetterJavadoc.java @@ -0,0 +1,100 @@ +class GetterSetterJavadoc1 { + /** + * Some text + */ + private int fieldName; + @java.lang.SuppressWarnings("all") + public GetterSetterJavadoc1() { + } + /** + * Getter section + * + * @return Sky is blue + */ + @java.lang.SuppressWarnings("all") + public int getFieldName() { + return this.fieldName; + } + /** + * Some text + * + * @param fieldName Hello, World + */ + @java.lang.SuppressWarnings("all") + public void setFieldName(final int fieldName) { + this.fieldName = fieldName; + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public boolean equals(final java.lang.Object o) { + if (o == this) return true; + if (!(o instanceof GetterSetterJavadoc1)) return false; + final GetterSetterJavadoc1 other = (GetterSetterJavadoc1)o; + if (!other.canEqual((java.lang.Object)this)) return false; + if (this.getFieldName() != other.getFieldName()) return false; + return true; + } + @java.lang.SuppressWarnings("all") + public boolean canEqual(final java.lang.Object other) { + return other instanceof GetterSetterJavadoc1; + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = result * PRIME + this.getFieldName(); + return result; + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public java.lang.String toString() { + return "GetterSetterJavadoc1(fieldName=" + this.getFieldName() + ")"; + } +} +class GetterSetterJavadoc2 { + /** + * Some text + */ + private int fieldName; + /** + * Some text + * + * @return Sky is blue + */ + @java.lang.SuppressWarnings("all") + public int getFieldName() { + return this.fieldName; + } + /** + * Some text + * + * @param fieldName Hello, World + */ + @java.lang.SuppressWarnings("all") + public void setFieldName(final int fieldName) { + this.fieldName = fieldName; + } +} +class GetterSetterJavadoc3 { + /** + * Some text + */ + private int fieldName; + /** + * Getter section + * @return Sky is blue + */ + @java.lang.SuppressWarnings("all") + public int getFieldName() { + return this.fieldName; + } + /** + * Setter section + * @param fieldName Hello, World + */ + @java.lang.SuppressWarnings("all") + public void setFieldName(final int fieldName) { + this.fieldName = fieldName; + } +}
\ No newline at end of file diff --git a/test/transform/resource/after-delombok/JavadocGenerally.java b/test/transform/resource/after-delombok/JavadocGenerally.java new file mode 100644 index 00000000..729cdce3 --- /dev/null +++ b/test/transform/resource/after-delombok/JavadocGenerally.java @@ -0,0 +1,24 @@ +/** + * Doc on package + */ +package testPackage; +/** Weird doc */ +/** + * Doc on class + */ +class JavadocGenerally { + /** + * Doc on field + */ + private int someField; + /** + * Doc on method + */ + public void test() { + } + /** + * Doc on inner + */ + public interface TestingInner { + } +} diff --git a/test/transform/resource/after-delombok/LoggerCommons.java b/test/transform/resource/after-delombok/LoggerCommons.java index c2a03815..dfe3e88d 100644 --- a/test/transform/resource/after-delombok/LoggerCommons.java +++ b/test/transform/resource/after-delombok/LoggerCommons.java @@ -1,7 +1,6 @@ class LoggerCommons { private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(LoggerCommons.class); } - class LoggerCommonsWithImport { private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(LoggerCommonsWithImport.class); }
\ No newline at end of file diff --git a/test/transform/resource/after-delombok/LoggerJul.java b/test/transform/resource/after-delombok/LoggerJul.java index 39cb2aac..b020c540 100644 --- a/test/transform/resource/after-delombok/LoggerJul.java +++ b/test/transform/resource/after-delombok/LoggerJul.java @@ -1,7 +1,6 @@ class LoggerJul { private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(LoggerJul.class.getName()); } - class LoggerJulWithImport { private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(LoggerJulWithImport.class.getName()); }
\ No newline at end of file diff --git a/test/transform/resource/after-delombok/LoggerLog4j.java b/test/transform/resource/after-delombok/LoggerLog4j.java index 6892a7d8..dfbad89a 100644 --- a/test/transform/resource/after-delombok/LoggerLog4j.java +++ b/test/transform/resource/after-delombok/LoggerLog4j.java @@ -1,7 +1,6 @@ class LoggerLog4j { private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(LoggerLog4j.class); } - class LoggerLog4jWithImport { private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(LoggerLog4jWithImport.class); }
\ No newline at end of file diff --git a/test/transform/resource/after-delombok/LoggerLog4j2.java b/test/transform/resource/after-delombok/LoggerLog4j2.java new file mode 100644 index 00000000..3447a9a5 --- /dev/null +++ b/test/transform/resource/after-delombok/LoggerLog4j2.java @@ -0,0 +1,6 @@ +class LoggerLog4j2 { + private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(LoggerLog4j2.class); +} +class LoggerLog4j2WithImport { + private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(LoggerLog4j2WithImport.class); +}
\ No newline at end of file diff --git a/test/transform/resource/after-delombok/LoggerSlf4j.java b/test/transform/resource/after-delombok/LoggerSlf4j.java index cb1486ba..4cc7c107 100644 --- a/test/transform/resource/after-delombok/LoggerSlf4j.java +++ b/test/transform/resource/after-delombok/LoggerSlf4j.java @@ -1,11 +1,9 @@ class LoggerSlf4j { private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LoggerSlf4j.class); } - class LoggerSlf4jWithImport { private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LoggerSlf4jWithImport.class); } - class LoggerSlf4jOuter { static class Inner { private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Inner.class); diff --git a/test/transform/resource/after-delombok/LoggerXSlf4j.java b/test/transform/resource/after-delombok/LoggerXSlf4j.java index 0239c60b..7d8f3236 100644 --- a/test/transform/resource/after-delombok/LoggerXSlf4j.java +++ b/test/transform/resource/after-delombok/LoggerXSlf4j.java @@ -1,7 +1,6 @@ class LoggerXSlf4j { private static final org.slf4j.ext.XLogger log = org.slf4j.ext.XLoggerFactory.getXLogger(LoggerXSlf4j.class); } - class LoggerXSlf4jWithImport { private static final org.slf4j.ext.XLogger log = org.slf4j.ext.XLoggerFactory.getXLogger(LoggerXSlf4jWithImport.class); }
\ No newline at end of file diff --git a/test/transform/resource/after-delombok/NonNullOnParameter.java b/test/transform/resource/after-delombok/NonNullOnParameter.java new file mode 100644 index 00000000..a27d19c9 --- /dev/null +++ b/test/transform/resource/after-delombok/NonNullOnParameter.java @@ -0,0 +1,48 @@ +class NonNullOnParameter extends Thread { + NonNullOnParameter(@lombok.NonNull String arg) { + this(arg, ""); + if (arg == null) { + throw new java.lang.NullPointerException("arg"); + } + } + NonNullOnParameter(@lombok.NonNull String arg, @lombok.NonNull String arg2) { + super(arg); + if (arg2 == null) { + throw new java.lang.NullPointerException("arg2"); + } + if (arg == null) throw new NullPointerException(); + } + public void test2(@lombok.NonNull String arg, @lombok.NonNull String arg2, @lombok.NonNull String arg3) { + if (arg == null) { + throw new java.lang.NullPointerException("arg"); + } + if (arg3 == null) { + throw new java.lang.NullPointerException("arg3"); + } + if (arg2 == null) { + throw new NullPointerException("arg2"); + } + if (arg == null) System.out.println("Hello"); + } + public void test3(@lombok.NonNull String arg) { + if (arg == null) { + throw new java.lang.NullPointerException("arg"); + } + if (arg != null) throw new IllegalStateException(); + } + public void test(@lombok.NonNull String stringArg, @lombok.NonNull String arg2, @lombok.NonNull int primitiveArg) { + if (stringArg == null) { + throw new java.lang.NullPointerException("stringArg"); + } + if (arg2 == null) { + throw new java.lang.NullPointerException("arg2"); + } + } + public void test(@lombok.NonNull String arg) { + if (arg == null) { + throw new java.lang.NullPointerException("arg"); + } + System.out.println("Hey"); + if (arg == null) throw new NullPointerException(); + } +} diff --git a/test/transform/resource/after-delombok/NonNullPlain.java b/test/transform/resource/after-delombok/NonNullPlain.java index 064e00b9..6b85cbf7 100644 --- a/test/transform/resource/after-delombok/NonNullPlain.java +++ b/test/transform/resource/after-delombok/NonNullPlain.java @@ -16,7 +16,9 @@ class NonNullPlain { @java.beans.ConstructorProperties({"i", "s"}) @java.lang.SuppressWarnings("all") public NonNullPlain(@lombok.NonNull final int i, @lombok.NonNull final String s) { - if (s == null) throw new java.lang.NullPointerException("s"); + if (s == null) { + throw new java.lang.NullPointerException("s"); + } this.i = i; this.s = s; } @@ -45,7 +47,9 @@ class NonNullPlain { @java.lang.SuppressWarnings("all") public void setS(@lombok.NonNull final String s) { - if (s == null) throw new java.lang.NullPointerException("s"); + if (s == null) { + throw new java.lang.NullPointerException("s"); + } this.s = s; } diff --git a/test/transform/resource/after-delombok/SetterDeprecated.java b/test/transform/resource/after-delombok/SetterDeprecated.java index 5a6cf9f3..72a609ad 100644 --- a/test/transform/resource/after-delombok/SetterDeprecated.java +++ b/test/transform/resource/after-delombok/SetterDeprecated.java @@ -10,6 +10,9 @@ class SetterDeprecated { public void setAnnotation(final int annotation) { this.annotation = annotation; } + /** + * @deprecated + */ @java.lang.Deprecated @java.lang.SuppressWarnings("all") public void setJavadoc(final int javadoc) { diff --git a/test/transform/resource/after-delombok/SetterOnClass.java b/test/transform/resource/after-delombok/SetterOnClass.java index 151bc179..7077c492 100644 --- a/test/transform/resource/after-delombok/SetterOnClass.java +++ b/test/transform/resource/after-delombok/SetterOnClass.java @@ -53,7 +53,9 @@ class SetterOnClass6 { } @java.lang.SuppressWarnings("all") public void setNonNull(@lombok.NonNull final String nonNull) { - if (nonNull == null) throw new java.lang.NullPointerException("nonNull"); + if (nonNull == null) { + throw new java.lang.NullPointerException("nonNull"); + } this.nonNull = nonNull; } }
\ No newline at end of file diff --git a/test/transform/resource/after-delombok/ValInTryWithResources.java b/test/transform/resource/after-delombok/ValInTryWithResources.java new file mode 100644 index 00000000..73f8d1a2 --- /dev/null +++ b/test/transform/resource/after-delombok/ValInTryWithResources.java @@ -0,0 +1,10 @@ +//version 7 +import java.io.IOException; +public class ValInTryWithResources { + public void whyTryInsteadOfCleanup() throws IOException { + try (final java.io.InputStream in = getClass().getResourceAsStream("ValInTryWithResources.class");) { + final java.io.InputStream i = in; + final int j = in.read(); + } + } +} diff --git a/test/transform/resource/after-delombok/ValueExperimental.java b/test/transform/resource/after-delombok/ValueExperimental.java new file mode 100644 index 00000000..77a48ec9 --- /dev/null +++ b/test/transform/resource/after-delombok/ValueExperimental.java @@ -0,0 +1,46 @@ +final class ValueExperimental1 { + @java.lang.SuppressWarnings("all") + public ValueExperimental1() { + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public boolean equals(final java.lang.Object o) { + if (o == this) return true; + if (!(o instanceof ValueExperimental1)) return false; + return true; + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public int hashCode() { + int result = 1; + return result; + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public java.lang.String toString() { + return "ValueExperimental1()"; + } +} +final class ValueExperimental2 { + @java.lang.SuppressWarnings("all") + public ValueExperimental2() { + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public boolean equals(final java.lang.Object o) { + if (o == this) return true; + if (!(o instanceof ValueExperimental2)) return false; + return true; + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public int hashCode() { + int result = 1; + return result; + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public java.lang.String toString() { + return "ValueExperimental2()"; + } +}
\ No newline at end of file diff --git a/test/transform/resource/after-delombok/ValueExperimentalStarImport.java b/test/transform/resource/after-delombok/ValueExperimentalStarImport.java new file mode 100644 index 00000000..6911f260 --- /dev/null +++ b/test/transform/resource/after-delombok/ValueExperimentalStarImport.java @@ -0,0 +1,25 @@ +import lombok.experimental.*; +final class ValueExperimentalStarImport { + @java.lang.SuppressWarnings("all") + public ValueExperimentalStarImport() { + + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public boolean equals(final java.lang.Object o) { + if (o == this) return true; + if (!(o instanceof ValueExperimentalStarImport)) return false; + return true; + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public int hashCode() { + int result = 1; + return result; + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public java.lang.String toString() { + return "ValueExperimentalStarImport()"; + } +}
\ No newline at end of file diff --git a/test/transform/resource/after-delombok/WitherDeprecated.java b/test/transform/resource/after-delombok/WitherDeprecated.java index f076d90e..29192012 100644 --- a/test/transform/resource/after-delombok/WitherDeprecated.java +++ b/test/transform/resource/after-delombok/WitherDeprecated.java @@ -12,6 +12,9 @@ class WitherDeprecated { public WitherDeprecated withAnnotation(final int annotation) { return this.annotation == annotation ? this : new WitherDeprecated(annotation, this.javadoc); } + /** + * @deprecated + */ @java.lang.Deprecated @java.lang.SuppressWarnings("all") public WitherDeprecated withJavadoc(final int javadoc) { diff --git a/test/transform/resource/after-delombok/WitherOnClass.java b/test/transform/resource/after-delombok/WitherOnClass.java index 783fede1..45d0c4b5 100644 --- a/test/transform/resource/after-delombok/WitherOnClass.java +++ b/test/transform/resource/after-delombok/WitherOnClass.java @@ -35,7 +35,9 @@ class WitherOnClass3 { } @java.lang.SuppressWarnings("all") public WitherOnClass3 withNonNull(@lombok.NonNull final String nonNull) { - if (nonNull == null) throw new java.lang.NullPointerException("nonNull"); + if (nonNull == null) { + throw new java.lang.NullPointerException("nonNull"); + } return this.nonNull == nonNull ? this : new WitherOnClass3(this.couldBeNull, nonNull); } } diff --git a/test/transform/resource/after-ecj/BuilderChainAndFluent.java b/test/transform/resource/after-ecj/BuilderChainAndFluent.java new file mode 100644 index 00000000..6a307105 --- /dev/null +++ b/test/transform/resource/after-ecj/BuilderChainAndFluent.java @@ -0,0 +1,25 @@ +@lombok.experimental.Builder(fluent = false,chain = false) class BuilderChainAndFluent { + public static @java.lang.SuppressWarnings("all") class BuilderChainAndFluentBuilder { + private int yes; + @java.lang.SuppressWarnings("all") BuilderChainAndFluentBuilder() { + super(); + } + public @java.lang.SuppressWarnings("all") void setYes(final int yes) { + this.yes = yes; + } + public @java.lang.SuppressWarnings("all") BuilderChainAndFluent build() { + return new BuilderChainAndFluent(yes); + } + public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() { + return (("BuilderChainAndFluent.BuilderChainAndFluentBuilder(yes=" + this.yes) + ")"); + } + } + private final int yes; + @java.lang.SuppressWarnings("all") BuilderChainAndFluent(final int yes) { + super(); + this.yes = yes; + } + public static @java.lang.SuppressWarnings("all") BuilderChainAndFluentBuilder builder() { + return new BuilderChainAndFluentBuilder(); + } +} diff --git a/test/transform/resource/after-ecj/BuilderComplex.java b/test/transform/resource/after-ecj/BuilderComplex.java new file mode 100644 index 00000000..19aeb043 --- /dev/null +++ b/test/transform/resource/after-ecj/BuilderComplex.java @@ -0,0 +1,43 @@ +import java.util.List; +import lombok.experimental.Builder; +class BuilderComplex { + public static @java.lang.SuppressWarnings("all") class VoidBuilder<T extends Number> { + private T number; + private int arg2; + private String arg3; + private BuilderComplex selfRef; + @java.lang.SuppressWarnings("all") VoidBuilder() { + super(); + } + public @java.lang.SuppressWarnings("all") VoidBuilder<T> number(final T number) { + this.number = number; + return this; + } + public @java.lang.SuppressWarnings("all") VoidBuilder<T> arg2(final int arg2) { + this.arg2 = arg2; + return this; + } + public @java.lang.SuppressWarnings("all") VoidBuilder<T> arg3(final String arg3) { + this.arg3 = arg3; + return this; + } + public @java.lang.SuppressWarnings("all") VoidBuilder<T> selfRef(final BuilderComplex selfRef) { + this.selfRef = selfRef; + return this; + } + public @java.lang.SuppressWarnings("all") void execute() { + BuilderComplex.<T>testVoidWithGenerics(number, arg2, arg3, selfRef); + } + public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() { + return (((((((("BuilderComplex.VoidBuilder(number=" + this.number) + ", arg2=") + this.arg2) + ", arg3=") + this.arg3) + ", selfRef=") + this.selfRef) + ")"); + } + } + BuilderComplex() { + super(); + } + private static @Builder(buildMethodName = "execute") <T extends Number>void testVoidWithGenerics(T number, int arg2, String arg3, BuilderComplex selfRef) { + } + public static @java.lang.SuppressWarnings("all") <T extends Number>VoidBuilder<T> builder() { + return new VoidBuilder<T>(); + } +} diff --git a/test/transform/resource/after-ecj/BuilderSimple.java b/test/transform/resource/after-ecj/BuilderSimple.java new file mode 100644 index 00000000..85db360d --- /dev/null +++ b/test/transform/resource/after-ecj/BuilderSimple.java @@ -0,0 +1,36 @@ +import java.util.List; +@lombok.experimental.Builder class BuilderSimple<T> { + public static @java.lang.SuppressWarnings("all") class BuilderSimpleBuilder<T> { + private int yes; + private List<T> also; + @java.lang.SuppressWarnings("all") BuilderSimpleBuilder() { + super(); + } + public @java.lang.SuppressWarnings("all") BuilderSimpleBuilder<T> yes(final int yes) { + this.yes = yes; + return this; + } + public @java.lang.SuppressWarnings("all") BuilderSimpleBuilder<T> also(final List<T> also) { + this.also = also; + return this; + } + public @java.lang.SuppressWarnings("all") BuilderSimple<T> build() { + return new BuilderSimple<T>(yes, also); + } + public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() { + return (((("BuilderSimple.BuilderSimpleBuilder(yes=" + this.yes) + ", also=") + this.also) + ")"); + } + } + private final int noshow = 0; + private final int yes; + private List<T> also; + private int $butNotMe; + @java.lang.SuppressWarnings("all") BuilderSimple(final int yes, final List<T> also) { + super(); + this.yes = yes; + this.also = also; + } + public static @java.lang.SuppressWarnings("all") <T>BuilderSimpleBuilder<T> builder() { + return new BuilderSimpleBuilder<T>(); + } +} diff --git a/test/transform/resource/after-ecj/BuilderWithExistingBuilderClass.java b/test/transform/resource/after-ecj/BuilderWithExistingBuilderClass.java new file mode 100644 index 00000000..38cb0038 --- /dev/null +++ b/test/transform/resource/after-ecj/BuilderWithExistingBuilderClass.java @@ -0,0 +1,36 @@ +import lombok.experimental.Builder; +class BuilderWithExistingBuilderClass<T, K extends Number> { + public static class BuilderWithExistingBuilderClassBuilder<Z extends Number> { + private boolean arg2; + private String arg3; + private Z arg1; + public void arg2(boolean arg) { + } + @java.lang.SuppressWarnings("all") BuilderWithExistingBuilderClassBuilder() { + super(); + } + public @java.lang.SuppressWarnings("all") BuilderWithExistingBuilderClassBuilder<Z> arg1(final Z arg1) { + this.arg1 = arg1; + return this; + } + public @java.lang.SuppressWarnings("all") BuilderWithExistingBuilderClassBuilder<Z> arg3(final String arg3) { + this.arg3 = arg3; + return this; + } + public @java.lang.SuppressWarnings("all") BuilderWithExistingBuilderClass<String, Z> build() { + return BuilderWithExistingBuilderClass.<Z>staticMethod(arg1, arg2, arg3); + } + public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() { + return (((((("BuilderWithExistingBuilderClass.BuilderWithExistingBuilderClassBuilder(arg1=" + this.arg1) + ", arg2=") + this.arg2) + ", arg3=") + this.arg3) + ")"); + } + } + BuilderWithExistingBuilderClass() { + super(); + } + public static @Builder <Z extends Number>BuilderWithExistingBuilderClass<String, Z> staticMethod(Z arg1, boolean arg2, String arg3) { + return null; + } + public static @java.lang.SuppressWarnings("all") <Z extends Number>BuilderWithExistingBuilderClassBuilder<Z> builder() { + return new BuilderWithExistingBuilderClassBuilder<Z>(); + } +}
\ No newline at end of file diff --git a/test/transform/resource/after-ecj/DataOnLocalClass.java b/test/transform/resource/after-ecj/DataOnLocalClass.java index 137edf50..2f8dcca1 100644 --- a/test/transform/resource/after-ecj/DataOnLocalClass.java +++ b/test/transform/resource/after-ecj/DataOnLocalClass.java @@ -63,7 +63,9 @@ class DataOnLocalClass2 { } public @java.lang.SuppressWarnings("all") void setName(final @lombok.NonNull String name) { if ((name == null)) - throw new java.lang.NullPointerException("name"); + { + throw new java.lang.NullPointerException("name"); + } this.name = name; } public @java.lang.Override @java.lang.SuppressWarnings("all") boolean equals(final java.lang.Object o) { @@ -96,7 +98,9 @@ class DataOnLocalClass2 { public @java.lang.SuppressWarnings("all") InnerLocal(final @lombok.NonNull String name) { super(); if ((name == null)) - throw new java.lang.NullPointerException("name"); + { + throw new java.lang.NullPointerException("name"); + } this.name = name; } } diff --git a/test/transform/resource/after-ecj/DelegateOnGetter.java b/test/transform/resource/after-ecj/DelegateOnGetter.java index 4eab3791..59f6a3b8 100644 --- a/test/transform/resource/after-ecj/DelegateOnGetter.java +++ b/test/transform/resource/after-ecj/DelegateOnGetter.java @@ -5,12 +5,12 @@ class DelegateOnGetter { void setList(java.util.ArrayList<java.lang.String> list); int getInt(); } - private final @Delegate @Getter(lazy = true) java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<Bar>> bar = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<Bar>>(); + private final @Delegate @Getter(lazy = true) java.util.concurrent.atomic.AtomicReference<java.lang.Object> bar = new java.util.concurrent.atomic.AtomicReference<java.lang.Object>(); DelegateOnGetter() { super(); } public @Delegate @java.lang.SuppressWarnings("all") Bar getBar() { - java.util.concurrent.atomic.AtomicReference<Bar> value = this.bar.get(); + java.lang.Object value = this.bar.get(); if ((value == null)) { synchronized (this.bar) @@ -28,12 +28,12 @@ class DelegateOnGetter { return 42; } }; - value = new java.util.concurrent.atomic.AtomicReference<Bar>(actualValue); + value = ((actualValue == null) ? this.bar : actualValue); this.bar.set(value); } } } - return value.get(); + return (Bar) ((value == this.bar) ? null : value); } public @java.lang.SuppressWarnings("all") int getInt() { return this.getBar().getInt(); diff --git a/test/transform/resource/after-ecj/EqualsAndHashCodeWithSomeExistingMethods.java b/test/transform/resource/after-ecj/EqualsAndHashCodeWithSomeExistingMethods.java new file mode 100644 index 00000000..cdd771a4 --- /dev/null +++ b/test/transform/resource/after-ecj/EqualsAndHashCodeWithSomeExistingMethods.java @@ -0,0 +1,71 @@ +import lombok.*; +import static lombok.AccessLevel.NONE; +@Data @Getter(NONE) @Setter(NONE) class EqualsAndHashCodeWithSomeExistingMethods { + int x; + public int hashCode() { + return 42; + } + public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() { + return (("EqualsAndHashCodeWithSomeExistingMethods(x=" + this.x) + ")"); + } + public @java.lang.SuppressWarnings("all") EqualsAndHashCodeWithSomeExistingMethods() { + super(); + } +} +@Data @Getter(NONE) @Setter(NONE) class EqualsAndHashCodeWithSomeExistingMethods2 { + int x; + public boolean canEqual(Object other) { + return false; + } + public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() { + return (("EqualsAndHashCodeWithSomeExistingMethods2(x=" + this.x) + ")"); + } + public @java.lang.SuppressWarnings("all") EqualsAndHashCodeWithSomeExistingMethods2() { + super(); + } +} +@Data @Getter(NONE) @Setter(NONE) class EqualsAndHashCodeWithAllExistingMethods { + int x; + public int hashCode() { + return 42; + } + public boolean equals(Object other) { + return false; + } + public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() { + return (("EqualsAndHashCodeWithAllExistingMethods(x=" + this.x) + ")"); + } + public @java.lang.SuppressWarnings("all") EqualsAndHashCodeWithAllExistingMethods() { + super(); + } +} +@Data @Getter(AccessLevel.NONE) @Setter(lombok.AccessLevel.NONE) class EqualsAndHashCodeWithNoExistingMethods { + int x; + public @java.lang.Override @java.lang.SuppressWarnings("all") boolean equals(final java.lang.Object o) { + if ((o == this)) + return true; + if ((! (o instanceof EqualsAndHashCodeWithNoExistingMethods))) + return false; + final @java.lang.SuppressWarnings("all") EqualsAndHashCodeWithNoExistingMethods other = (EqualsAndHashCodeWithNoExistingMethods) o; + if ((! other.canEqual((java.lang.Object) this))) + return false; + if ((this.x != other.x)) + return false; + return true; + } + public @java.lang.SuppressWarnings("all") boolean canEqual(final java.lang.Object other) { + return (other instanceof EqualsAndHashCodeWithNoExistingMethods); + } + public @java.lang.Override @java.lang.SuppressWarnings("all") int hashCode() { + final int PRIME = 31; + int result = 1; + result = ((result * PRIME) + this.x); + return result; + } + public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() { + return (("EqualsAndHashCodeWithNoExistingMethods(x=" + this.x) + ")"); + } + public @java.lang.SuppressWarnings("all") EqualsAndHashCodeWithNoExistingMethods() { + super(); + } +} diff --git a/test/transform/resource/after-ecj/GetterLazy.java b/test/transform/resource/after-ecj/GetterLazy.java index 0f5027b9..8394f58d 100644 --- a/test/transform/resource/after-ecj/GetterLazy.java +++ b/test/transform/resource/after-ecj/GetterLazy.java @@ -4,12 +4,12 @@ class GetterLazy { super(); } } - private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<ValueType>> fieldName = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<ValueType>>(); + private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference<java.lang.Object> fieldName = new java.util.concurrent.atomic.AtomicReference<java.lang.Object>(); GetterLazy() { super(); } public @java.lang.SuppressWarnings("all") ValueType getFieldName() { - java.util.concurrent.atomic.AtomicReference<ValueType> value = this.fieldName.get(); + java.lang.Object value = this.fieldName.get(); if ((value == null)) { synchronized (this.fieldName) @@ -18,11 +18,11 @@ class GetterLazy { if ((value == null)) { final ValueType actualValue = new ValueType(); - value = new java.util.concurrent.atomic.AtomicReference<ValueType>(actualValue); + value = ((actualValue == null) ? this.fieldName : actualValue); this.fieldName.set(value); } } } - return value.get(); + return (ValueType) ((value == this.fieldName) ? null : value); } } diff --git a/test/transform/resource/after-ecj/GetterLazyBoolean.java b/test/transform/resource/after-ecj/GetterLazyBoolean.java index 8c890827..a51b164f 100644 --- a/test/transform/resource/after-ecj/GetterLazyBoolean.java +++ b/test/transform/resource/after-ecj/GetterLazyBoolean.java @@ -1,6 +1,6 @@ @lombok.EqualsAndHashCode(of = "booleanValue") @lombok.ToString(of = "booleanValue") class GetterLazyBoolean { - private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Boolean>> booleanValue = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Boolean>>(); - private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Boolean>> otherBooleanValue = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Boolean>>(); + private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference<java.lang.Object> booleanValue = new java.util.concurrent.atomic.AtomicReference<java.lang.Object>(); + private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference<java.lang.Object> otherBooleanValue = new java.util.concurrent.atomic.AtomicReference<java.lang.Object>(); GetterLazyBoolean() { super(); } @@ -8,7 +8,7 @@ return true; } public @java.lang.SuppressWarnings("all") boolean isBooleanValue() { - java.util.concurrent.atomic.AtomicReference<java.lang.Boolean> value = this.booleanValue.get(); + java.lang.Object value = this.booleanValue.get(); if ((value == null)) { synchronized (this.booleanValue) @@ -17,15 +17,15 @@ if ((value == null)) { final boolean actualValue = calculateBoolean(); - value = new java.util.concurrent.atomic.AtomicReference<java.lang.Boolean>(actualValue); + value = actualValue; this.booleanValue.set(value); } } } - return value.get(); + return (java.lang.Boolean) value; } public @java.lang.SuppressWarnings("all") boolean isOtherBooleanValue() { - java.util.concurrent.atomic.AtomicReference<java.lang.Boolean> value = this.otherBooleanValue.get(); + java.lang.Object value = this.otherBooleanValue.get(); if ((value == null)) { synchronized (this.otherBooleanValue) @@ -34,12 +34,12 @@ if ((value == null)) { final boolean actualValue = (! calculateBoolean()); - value = new java.util.concurrent.atomic.AtomicReference<java.lang.Boolean>(actualValue); + value = actualValue; this.otherBooleanValue.set(value); } } } - return value.get(); + return (java.lang.Boolean) value; } public @java.lang.Override @java.lang.SuppressWarnings("all") boolean equals(final java.lang.Object o) { if ((o == this)) diff --git a/test/transform/resource/after-ecj/GetterLazyEahcToString.java b/test/transform/resource/after-ecj/GetterLazyEahcToString.java index 1ca848af..db2d4fcf 100644 --- a/test/transform/resource/after-ecj/GetterLazyEahcToString.java +++ b/test/transform/resource/after-ecj/GetterLazyEahcToString.java @@ -1,11 +1,11 @@ @lombok.EqualsAndHashCode(doNotUseGetters = true) @lombok.ToString(doNotUseGetters = true) class GetterLazyEahcToString { - private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<String>> value = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<String>>(); + private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference<java.lang.Object> value = new java.util.concurrent.atomic.AtomicReference<java.lang.Object>(); private final @lombok.Getter String value2 = ""; GetterLazyEahcToString() { super(); } public @java.lang.SuppressWarnings("all") String getValue() { - java.util.concurrent.atomic.AtomicReference<String> value = this.value.get(); + java.lang.Object value = this.value.get(); if ((value == null)) { synchronized (this.value) @@ -14,12 +14,12 @@ if ((value == null)) { final String actualValue = ""; - value = new java.util.concurrent.atomic.AtomicReference<String>(actualValue); + value = ((actualValue == null) ? this.value : actualValue); this.value.set(value); } } } - return value.get(); + return (String) ((value == this.value) ? null : value); } public @java.lang.SuppressWarnings("all") String getValue2() { return this.value2; diff --git a/test/transform/resource/after-ecj/GetterLazyNative.java b/test/transform/resource/after-ecj/GetterLazyNative.java index b67abfc9..db70f2f4 100644 --- a/test/transform/resource/after-ecj/GetterLazyNative.java +++ b/test/transform/resource/after-ecj/GetterLazyNative.java @@ -1,18 +1,18 @@ class GetterLazyNative { - private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Boolean>> booleanField = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Boolean>>(); - private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Byte>> byteField = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Byte>>(); - private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Short>> shortField = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Short>>(); - private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Integer>> intField = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Integer>>(); - private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Long>> longField = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Long>>(); - private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Float>> floatField = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Float>>(); - private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Double>> doubleField = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Double>>(); - private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Character>> charField = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Character>>(); - private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<int[]>> intArrayField = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<int[]>>(); + private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference<java.lang.Object> booleanField = new java.util.concurrent.atomic.AtomicReference<java.lang.Object>(); + private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference<java.lang.Object> byteField = new java.util.concurrent.atomic.AtomicReference<java.lang.Object>(); + private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference<java.lang.Object> shortField = new java.util.concurrent.atomic.AtomicReference<java.lang.Object>(); + private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference<java.lang.Object> intField = new java.util.concurrent.atomic.AtomicReference<java.lang.Object>(); + private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference<java.lang.Object> longField = new java.util.concurrent.atomic.AtomicReference<java.lang.Object>(); + private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference<java.lang.Object> floatField = new java.util.concurrent.atomic.AtomicReference<java.lang.Object>(); + private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference<java.lang.Object> doubleField = new java.util.concurrent.atomic.AtomicReference<java.lang.Object>(); + private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference<java.lang.Object> charField = new java.util.concurrent.atomic.AtomicReference<java.lang.Object>(); + private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference<java.lang.Object> intArrayField = new java.util.concurrent.atomic.AtomicReference<java.lang.Object>(); GetterLazyNative() { super(); } public @java.lang.SuppressWarnings("all") boolean isBooleanField() { - java.util.concurrent.atomic.AtomicReference<java.lang.Boolean> value = this.booleanField.get(); + java.lang.Object value = this.booleanField.get(); if ((value == null)) { synchronized (this.booleanField) @@ -21,15 +21,15 @@ class GetterLazyNative { if ((value == null)) { final boolean actualValue = true; - value = new java.util.concurrent.atomic.AtomicReference<java.lang.Boolean>(actualValue); + value = actualValue; this.booleanField.set(value); } } } - return value.get(); + return (java.lang.Boolean) value; } public @java.lang.SuppressWarnings("all") byte getByteField() { - java.util.concurrent.atomic.AtomicReference<java.lang.Byte> value = this.byteField.get(); + java.lang.Object value = this.byteField.get(); if ((value == null)) { synchronized (this.byteField) @@ -38,15 +38,15 @@ class GetterLazyNative { if ((value == null)) { final byte actualValue = 1; - value = new java.util.concurrent.atomic.AtomicReference<java.lang.Byte>(actualValue); + value = actualValue; this.byteField.set(value); } } } - return value.get(); + return (java.lang.Byte) value; } public @java.lang.SuppressWarnings("all") short getShortField() { - java.util.concurrent.atomic.AtomicReference<java.lang.Short> value = this.shortField.get(); + java.lang.Object value = this.shortField.get(); if ((value == null)) { synchronized (this.shortField) @@ -55,15 +55,15 @@ class GetterLazyNative { if ((value == null)) { final short actualValue = 1; - value = new java.util.concurrent.atomic.AtomicReference<java.lang.Short>(actualValue); + value = actualValue; this.shortField.set(value); } } } - return value.get(); + return (java.lang.Short) value; } public @java.lang.SuppressWarnings("all") int getIntField() { - java.util.concurrent.atomic.AtomicReference<java.lang.Integer> value = this.intField.get(); + java.lang.Object value = this.intField.get(); if ((value == null)) { synchronized (this.intField) @@ -72,15 +72,15 @@ class GetterLazyNative { if ((value == null)) { final int actualValue = 1; - value = new java.util.concurrent.atomic.AtomicReference<java.lang.Integer>(actualValue); + value = actualValue; this.intField.set(value); } } } - return value.get(); + return (java.lang.Integer) value; } public @java.lang.SuppressWarnings("all") long getLongField() { - java.util.concurrent.atomic.AtomicReference<java.lang.Long> value = this.longField.get(); + java.lang.Object value = this.longField.get(); if ((value == null)) { synchronized (this.longField) @@ -89,15 +89,15 @@ class GetterLazyNative { if ((value == null)) { final long actualValue = 1; - value = new java.util.concurrent.atomic.AtomicReference<java.lang.Long>(actualValue); + value = actualValue; this.longField.set(value); } } } - return value.get(); + return (java.lang.Long) value; } public @java.lang.SuppressWarnings("all") float getFloatField() { - java.util.concurrent.atomic.AtomicReference<java.lang.Float> value = this.floatField.get(); + java.lang.Object value = this.floatField.get(); if ((value == null)) { synchronized (this.floatField) @@ -106,15 +106,15 @@ class GetterLazyNative { if ((value == null)) { final float actualValue = 1.0f; - value = new java.util.concurrent.atomic.AtomicReference<java.lang.Float>(actualValue); + value = actualValue; this.floatField.set(value); } } } - return value.get(); + return (java.lang.Float) value; } public @java.lang.SuppressWarnings("all") double getDoubleField() { - java.util.concurrent.atomic.AtomicReference<java.lang.Double> value = this.doubleField.get(); + java.lang.Object value = this.doubleField.get(); if ((value == null)) { synchronized (this.doubleField) @@ -123,15 +123,15 @@ class GetterLazyNative { if ((value == null)) { final double actualValue = 1.0; - value = new java.util.concurrent.atomic.AtomicReference<java.lang.Double>(actualValue); + value = actualValue; this.doubleField.set(value); } } } - return value.get(); + return (java.lang.Double) value; } public @java.lang.SuppressWarnings("all") char getCharField() { - java.util.concurrent.atomic.AtomicReference<java.lang.Character> value = this.charField.get(); + java.lang.Object value = this.charField.get(); if ((value == null)) { synchronized (this.charField) @@ -140,15 +140,15 @@ class GetterLazyNative { if ((value == null)) { final char actualValue = '1'; - value = new java.util.concurrent.atomic.AtomicReference<java.lang.Character>(actualValue); + value = actualValue; this.charField.set(value); } } } - return value.get(); + return (java.lang.Character) value; } public @java.lang.SuppressWarnings("all") int[] getIntArrayField() { - java.util.concurrent.atomic.AtomicReference<int[]> value = this.intArrayField.get(); + java.lang.Object value = this.intArrayField.get(); if ((value == null)) { synchronized (this.intArrayField) @@ -157,11 +157,11 @@ class GetterLazyNative { if ((value == null)) { final int[] actualValue = new int[]{1}; - value = new java.util.concurrent.atomic.AtomicReference<int[]>(actualValue); + value = ((actualValue == null) ? this.intArrayField : actualValue); this.intArrayField.set(value); } } } - return value.get(); + return (int[]) ((value == this.intArrayField) ? null : value); } } diff --git a/test/transform/resource/after-ecj/GetterSetterJavadoc.java b/test/transform/resource/after-ecj/GetterSetterJavadoc.java new file mode 100644 index 00000000..73f26180 --- /dev/null +++ b/test/transform/resource/after-ecj/GetterSetterJavadoc.java @@ -0,0 +1,60 @@ +@lombok.Data class GetterSetterJavadoc1 { + private int fieldName; + public @java.lang.SuppressWarnings("all") int getFieldName() { + return this.fieldName; + } + public @java.lang.SuppressWarnings("all") void setFieldName(final int fieldName) { + this.fieldName = fieldName; + } + public @java.lang.Override @java.lang.SuppressWarnings("all") boolean equals(final java.lang.Object o) { + if ((o == this)) + return true; + if ((! (o instanceof GetterSetterJavadoc1))) + return false; + final @java.lang.SuppressWarnings("all") GetterSetterJavadoc1 other = (GetterSetterJavadoc1) o; + if ((! other.canEqual((java.lang.Object) this))) + return false; + if ((this.getFieldName() != other.getFieldName())) + return false; + return true; + } + public @java.lang.SuppressWarnings("all") boolean canEqual(final java.lang.Object other) { + return (other instanceof GetterSetterJavadoc1); + } + public @java.lang.Override @java.lang.SuppressWarnings("all") int hashCode() { + final int PRIME = 31; + int result = 1; + result = ((result * PRIME) + this.getFieldName()); + return result; + } + public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() { + return (("GetterSetterJavadoc1(fieldName=" + this.getFieldName()) + ")"); + } + public @java.lang.SuppressWarnings("all") GetterSetterJavadoc1() { + super(); + } +} +class GetterSetterJavadoc2 { + private @lombok.Getter @lombok.Setter int fieldName; + GetterSetterJavadoc2() { + super(); + } + public @java.lang.SuppressWarnings("all") int getFieldName() { + return this.fieldName; + } + public @java.lang.SuppressWarnings("all") void setFieldName(final int fieldName) { + this.fieldName = fieldName; + } +} +class GetterSetterJavadoc3 { + private @lombok.Getter @lombok.Setter int fieldName; + GetterSetterJavadoc3() { + super(); + } + public @java.lang.SuppressWarnings("all") int getFieldName() { + return this.fieldName; + } + public @java.lang.SuppressWarnings("all") void setFieldName(final int fieldName) { + this.fieldName = fieldName; + } +}
\ No newline at end of file diff --git a/test/transform/resource/after-ecj/JavadocGenerally.java b/test/transform/resource/after-ecj/JavadocGenerally.java new file mode 100644 index 00000000..be9ed756 --- /dev/null +++ b/test/transform/resource/after-ecj/JavadocGenerally.java @@ -0,0 +1,11 @@ +package testPackage; +class JavadocGenerally { + public interface TestingInner { + } + private int someField; + JavadocGenerally() { + super(); + } + public void test() { + } +}
\ No newline at end of file diff --git a/test/transform/resource/after-ecj/LoggerLog4j2.java b/test/transform/resource/after-ecj/LoggerLog4j2.java new file mode 100644 index 00000000..c1368e5d --- /dev/null +++ b/test/transform/resource/after-ecj/LoggerLog4j2.java @@ -0,0 +1,17 @@ +import lombok.extern.log4j.Log4j2; +@lombok.extern.log4j.Log4j2 class LoggerLog4j2 { + private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(LoggerLog4j2.class); + <clinit>() { + } + LoggerLog4j2() { + super(); + } +} +@Log4j2 class LoggerLog4j2WithImport { + private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(LoggerLog4j2WithImport.class); + <clinit>() { + } + LoggerLog4j2WithImport() { + super(); + } +}
\ No newline at end of file diff --git a/test/transform/resource/after-ecj/NonNullOnParameter.java b/test/transform/resource/after-ecj/NonNullOnParameter.java new file mode 100644 index 00000000..bbceb153 --- /dev/null +++ b/test/transform/resource/after-ecj/NonNullOnParameter.java @@ -0,0 +1,61 @@ +class NonNullOnParameter extends Thread { + NonNullOnParameter(@lombok.NonNull String arg) { + this(arg, ""); + if ((arg == null)) + { + throw new java.lang.NullPointerException("arg"); + } + } + NonNullOnParameter(@lombok.NonNull String arg, @lombok.NonNull String arg2) { + super(arg); + if ((arg2 == null)) + { + throw new java.lang.NullPointerException("arg2"); + } + if ((arg == null)) + throw new NullPointerException(); + } + public void test2(@lombok.NonNull String arg, @lombok.NonNull String arg2, @lombok.NonNull String arg3) { + if ((arg == null)) + { + throw new java.lang.NullPointerException("arg"); + } + if ((arg3 == null)) + { + throw new java.lang.NullPointerException("arg3"); + } + if ((arg2 == null)) + { + throw new NullPointerException("arg2"); + } + if ((arg == null)) + System.out.println("Hello"); + } + public void test3(@lombok.NonNull String arg) { + if ((arg == null)) + { + throw new java.lang.NullPointerException("arg"); + } + if ((arg != null)) + throw new IllegalStateException(); + } + public void test(@lombok.NonNull String stringArg, @lombok.NonNull String arg2, @lombok.NonNull int primitiveArg) { + if ((stringArg == null)) + { + throw new java.lang.NullPointerException("stringArg"); + } + if ((arg2 == null)) + { + throw new java.lang.NullPointerException("arg2"); + } + } + public void test(@lombok.NonNull String arg) { + if ((arg == null)) + { + throw new java.lang.NullPointerException("arg"); + } + System.out.println("Hey"); + if ((arg == null)) + throw new NullPointerException(); + } +}
\ No newline at end of file diff --git a/test/transform/resource/after-ecj/NonNullPlain.java b/test/transform/resource/after-ecj/NonNullPlain.java index c9c96d0a..6e937f6a 100644 --- a/test/transform/resource/after-ecj/NonNullPlain.java +++ b/test/transform/resource/after-ecj/NonNullPlain.java @@ -8,7 +8,9 @@ import java.lang.annotation.*; public @java.beans.ConstructorProperties({"i", "s"}) @java.lang.SuppressWarnings("all") NonNullPlain(final @lombok.NonNull int i, final @lombok.NonNull String s) { super(); if ((s == null)) - throw new java.lang.NullPointerException("s"); + { + throw new java.lang.NullPointerException("s"); + } this.i = i; this.s = s; } @@ -26,7 +28,9 @@ import java.lang.annotation.*; } public @java.lang.SuppressWarnings("all") void setS(final @lombok.NonNull String s) { if ((s == null)) - throw new java.lang.NullPointerException("s"); + { + throw new java.lang.NullPointerException("s"); + } this.s = s; } public @java.lang.SuppressWarnings("all") void setO(final Object o) { diff --git a/test/transform/resource/after-ecj/SetterOnClass.java b/test/transform/resource/after-ecj/SetterOnClass.java index da928f24..aa3459bb 100644 --- a/test/transform/resource/after-ecj/SetterOnClass.java +++ b/test/transform/resource/after-ecj/SetterOnClass.java @@ -63,7 +63,9 @@ } public @java.lang.SuppressWarnings("all") void setNonNull(final @lombok.NonNull String nonNull) { if ((nonNull == null)) - throw new java.lang.NullPointerException("nonNull"); + { + throw new java.lang.NullPointerException("nonNull"); + } this.nonNull = nonNull; } } diff --git a/test/transform/resource/after-ecj/ValueExperimental.java b/test/transform/resource/after-ecj/ValueExperimental.java new file mode 100644 index 00000000..dd13574a --- /dev/null +++ b/test/transform/resource/after-ecj/ValueExperimental.java @@ -0,0 +1,39 @@ +import lombok.experimental.Value; +final @Value class ValueExperimental1 { + public @java.lang.Override @java.lang.SuppressWarnings("all") boolean equals(final java.lang.Object o) { + if ((o == this)) + return true; + if ((! (o instanceof ValueExperimental1))) + return false; + return true; + } + public @java.lang.Override @java.lang.SuppressWarnings("all") int hashCode() { + int result = 1; + return result; + } + public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() { + return "ValueExperimental1()"; + } + public @java.lang.SuppressWarnings("all") ValueExperimental1() { + super(); + } +} +final @lombok.experimental.Value class ValueExperimental2 { + public @java.lang.Override @java.lang.SuppressWarnings("all") boolean equals(final java.lang.Object o) { + if ((o == this)) + return true; + if ((! (o instanceof ValueExperimental2))) + return false; + return true; + } + public @java.lang.Override @java.lang.SuppressWarnings("all") int hashCode() { + int result = 1; + return result; + } + public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() { + return "ValueExperimental2()"; + } + public @java.lang.SuppressWarnings("all") ValueExperimental2() { + super(); + } +}
\ No newline at end of file diff --git a/test/transform/resource/after-ecj/ValueExperimentalStarImport.java b/test/transform/resource/after-ecj/ValueExperimentalStarImport.java new file mode 100644 index 00000000..b69e85d9 --- /dev/null +++ b/test/transform/resource/after-ecj/ValueExperimentalStarImport.java @@ -0,0 +1,20 @@ +import lombok.experimental.*; +final @Value class ValueExperimentalStarImport { + public @java.lang.Override @java.lang.SuppressWarnings("all") boolean equals(final java.lang.Object o) { + if ((o == this)) + return true; + if ((! (o instanceof ValueExperimentalStarImport))) + return false; + return true; + } + public @java.lang.Override @java.lang.SuppressWarnings("all") int hashCode() { + int result = 1; + return result; + } + public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() { + return "ValueExperimentalStarImport()"; + } + public @java.lang.SuppressWarnings("all") ValueExperimentalStarImport() { + super(); + } +} diff --git a/test/transform/resource/after-ecj/ValuePlain.java b/test/transform/resource/after-ecj/ValuePlain.java index b798b308..d095913f 100644 --- a/test/transform/resource/after-ecj/ValuePlain.java +++ b/test/transform/resource/after-ecj/ValuePlain.java @@ -1,5 +1,5 @@ -import lombok.experimental.Value; -final @lombok.experimental.Value class Value1 { +import lombok.Value; +final @lombok.Value class Value1 { private final int x; private final String name; public @java.lang.SuppressWarnings("all") int getX() { diff --git a/test/transform/resource/after-ecj/WitherOnClass.java b/test/transform/resource/after-ecj/WitherOnClass.java index ff4566e5..82132e87 100644 --- a/test/transform/resource/after-ecj/WitherOnClass.java +++ b/test/transform/resource/after-ecj/WitherOnClass.java @@ -33,7 +33,9 @@ } public @java.lang.SuppressWarnings("all") WitherOnClass3 withNonNull(final @lombok.NonNull String nonNull) { if ((nonNull == null)) - throw new java.lang.NullPointerException("nonNull"); + { + throw new java.lang.NullPointerException("nonNull"); + } return ((this.nonNull == nonNull) ? this : new WitherOnClass3(this.couldBeNull, nonNull)); } } diff --git a/test/transform/resource/before/BuilderChainAndFluent.java b/test/transform/resource/before/BuilderChainAndFluent.java new file mode 100644 index 00000000..4d08741b --- /dev/null +++ b/test/transform/resource/before/BuilderChainAndFluent.java @@ -0,0 +1,4 @@ +@lombok.experimental.Builder(fluent = false, chain = false) +class BuilderChainAndFluent { + private final int yes; +} diff --git a/test/transform/resource/before/BuilderComplex.java b/test/transform/resource/before/BuilderComplex.java new file mode 100644 index 00000000..590a2723 --- /dev/null +++ b/test/transform/resource/before/BuilderComplex.java @@ -0,0 +1,7 @@ +import java.util.List; +import lombok.experimental.Builder; + +class BuilderComplex { + @Builder(buildMethodName = "execute") + private static <T extends Number> void testVoidWithGenerics(T number, int arg2, String arg3, BuilderComplex selfRef) {} +} diff --git a/test/transform/resource/before/BuilderInvalidUse.java b/test/transform/resource/before/BuilderInvalidUse.java new file mode 100644 index 00000000..07f37d3d --- /dev/null +++ b/test/transform/resource/before/BuilderInvalidUse.java @@ -0,0 +1,18 @@ +@lombok.experimental.Builder +class BuilderInvalidUse { + private int something; + + @lombok.Getter @lombok.Setter @lombok.experimental.FieldDefaults(makeFinal = true) @lombok.experimental.Wither @lombok.Data @lombok.ToString @lombok.EqualsAndHashCode + @lombok.AllArgsConstructor + public static class BuilderInvalidUseBuilder { + + } +} + +@lombok.experimental.Builder +class AlsoInvalid { + @lombok.Value + public static class AlsoInvalidBuilder { + + } +}
\ No newline at end of file diff --git a/test/transform/resource/before/BuilderSimple.java b/test/transform/resource/before/BuilderSimple.java new file mode 100644 index 00000000..c749bb6c --- /dev/null +++ b/test/transform/resource/before/BuilderSimple.java @@ -0,0 +1,9 @@ +import java.util.List; + +@lombok.experimental.Builder +class BuilderSimple<T> { + private final int noshow = 0; + private final int yes; + private List<T> also; + private int $butNotMe; +} diff --git a/test/transform/resource/before/BuilderWithExistingBuilderClass.java b/test/transform/resource/before/BuilderWithExistingBuilderClass.java new file mode 100644 index 00000000..262e3b85 --- /dev/null +++ b/test/transform/resource/before/BuilderWithExistingBuilderClass.java @@ -0,0 +1,15 @@ +import lombok.experimental.Builder; + +class BuilderWithExistingBuilderClass<T, K extends Number> { + @Builder + public static <Z extends Number> BuilderWithExistingBuilderClass<String, Z> staticMethod(Z arg1, boolean arg2, String arg3) { + return null; + } + + public static class BuilderWithExistingBuilderClassBuilder<Z extends Number> { + private Z arg1; + + public void arg2(boolean arg) { + } + } +}
\ No newline at end of file diff --git a/test/transform/resource/before/CommentsInterspersed.java b/test/transform/resource/before/CommentsInterspersed.java index e7898aaa..23a1060b 100644 --- a/test/transform/resource/before/CommentsInterspersed.java +++ b/test/transform/resource/before/CommentsInterspersed.java @@ -1,12 +1,16 @@ import /* cmt */ lombok./* cmt2 */Getter /* cmt3 */ ; public /*bla */ class CommentsInterspersed { - /** javadoc for field */ + /** + * javadoc for field + */ private int x; private /* bla2 */ @Getter String test = "foo"; //$NON-NLS-1$ - /** Javadoc on method */ + /** + * Javadoc on method + */ public native void gwtTest(); /*-{ javascript; }-*/ diff --git a/test/transform/resource/before/DelegateWithDeprecated.java b/test/transform/resource/before/DelegateWithDeprecated.java index b748c6ec..064e951d 100644 --- a/test/transform/resource/before/DelegateWithDeprecated.java +++ b/test/transform/resource/before/DelegateWithDeprecated.java @@ -6,7 +6,9 @@ class DelegateWithDeprecated { private interface Bar { @Deprecated void deprecatedAnnotation(); - /** @deprecated */ + /** + * @deprecated + */ void deprecatedComment(); void notDeprecated(); } diff --git a/test/transform/resource/before/EqualsAndHashCodeWithSomeExistingMethods.java b/test/transform/resource/before/EqualsAndHashCodeWithSomeExistingMethods.java new file mode 100644 index 00000000..784e3b3f --- /dev/null +++ b/test/transform/resource/before/EqualsAndHashCodeWithSomeExistingMethods.java @@ -0,0 +1,47 @@ +import lombok.*; +import static lombok.AccessLevel.NONE; + +@Data +@Getter(NONE) +@Setter(NONE) +class EqualsAndHashCodeWithSomeExistingMethods { + int x; + + public int hashCode() { + return 42; + } +} + +@Data +@Getter(NONE) +@Setter(NONE) +class EqualsAndHashCodeWithSomeExistingMethods2 { + int x; + + public boolean canEqual(Object other) { + return false; + } +} + +@Data +@Getter(NONE) +@Setter(NONE) +class EqualsAndHashCodeWithAllExistingMethods { + int x; + + public int hashCode() { + return 42; + } + + public boolean equals(Object other) { + return false; + } +} + +@Data +@Getter(AccessLevel.NONE) +@Setter(lombok.AccessLevel.NONE) +class EqualsAndHashCodeWithNoExistingMethods { + int x; +} + diff --git a/test/transform/resource/before/GetterSetterJavadoc.java b/test/transform/resource/before/GetterSetterJavadoc.java new file mode 100644 index 00000000..e3ae0aac --- /dev/null +++ b/test/transform/resource/before/GetterSetterJavadoc.java @@ -0,0 +1,37 @@ +@lombok.Data +class GetterSetterJavadoc1 { + /** + * Some text + * + * @param fieldName Hello, World + * --- GETTER --- + * Getter section + * + * @return Sky is blue + */ + private int fieldName; +} + +class GetterSetterJavadoc2 { + /** + * Some text + * + * @param fieldName Hello, World + * @return Sky is blue + */ + @lombok.Getter @lombok.Setter private int fieldName; +} + +class GetterSetterJavadoc3 { + /** + * Some text + * + * **SETTER** + * Setter section + * @param fieldName Hello, World + * **GETTER** + * Getter section + * @return Sky is blue + */ + @lombok.Getter @lombok.Setter private int fieldName; +} diff --git a/test/transform/resource/before/JavadocGenerally.java b/test/transform/resource/before/JavadocGenerally.java new file mode 100644 index 00000000..ee015acf --- /dev/null +++ b/test/transform/resource/before/JavadocGenerally.java @@ -0,0 +1,27 @@ +/** + * Doc on package + */ +package testPackage; + +/** Weird doc */ +/** + * Doc on class + */ +class JavadocGenerally { + /** + * Doc on field + */ + private int someField; + + /** + * Doc on method + */ + public void test() { + } + + /** + * Doc on inner + */ + public interface TestingInner { + } +} diff --git a/test/transform/resource/before/LoggerLog4j2.java b/test/transform/resource/before/LoggerLog4j2.java new file mode 100644 index 00000000..b7ea99ee --- /dev/null +++ b/test/transform/resource/before/LoggerLog4j2.java @@ -0,0 +1,9 @@ +import lombok.extern.log4j.Log4j2; + +@lombok.extern.log4j.Log4j2 +class LoggerLog4j2 { +} + +@Log4j2 +class LoggerLog4j2WithImport { +}
\ No newline at end of file diff --git a/test/transform/resource/before/NonNullOnParameter.java b/test/transform/resource/before/NonNullOnParameter.java new file mode 100644 index 00000000..7eb4c565 --- /dev/null +++ b/test/transform/resource/before/NonNullOnParameter.java @@ -0,0 +1,30 @@ +class NonNullOnParameter extends Thread { + NonNullOnParameter(@lombok.NonNull String arg) { + this(arg, ""); + } + + NonNullOnParameter(@lombok.NonNull String arg, @lombok.NonNull String arg2) { + super(arg); + if (arg == null) throw new NullPointerException(); + } + + public void test2(@lombok.NonNull String arg, @lombok.NonNull String arg2, @lombok.NonNull String arg3) { + if (arg2 == null) { + throw new NullPointerException("arg2"); + } + if (arg == null) System.out.println("Hello"); + } + + public void test3(@lombok.NonNull String arg) { + if (arg != null) throw new IllegalStateException(); + } + + public void test(@lombok.NonNull String stringArg, @lombok.NonNull String arg2, @lombok.NonNull int primitiveArg) { + + } + + public void test(@lombok.NonNull String arg) { + System.out.println("Hey"); + if (arg == null) throw new NullPointerException(); + } +}
\ No newline at end of file diff --git a/test/transform/resource/before/ValInTryWithResources.java b/test/transform/resource/before/ValInTryWithResources.java new file mode 100644 index 00000000..780e1c71 --- /dev/null +++ b/test/transform/resource/before/ValInTryWithResources.java @@ -0,0 +1,12 @@ +//version 7 +import lombok.val; +import java.io.IOException; + +public class ValInTryWithResources { + public void whyTryInsteadOfCleanup() throws IOException { + try (val in = getClass().getResourceAsStream("ValInTryWithResources.class")) { + val i = in; + val j = in.read(); + } + } +}
\ No newline at end of file diff --git a/test/transform/resource/before/ValueExperimental.java b/test/transform/resource/before/ValueExperimental.java new file mode 100644 index 00000000..6bae26a0 --- /dev/null +++ b/test/transform/resource/before/ValueExperimental.java @@ -0,0 +1,9 @@ +import lombok.experimental.Value; + +@Value +class ValueExperimental1 { +} + +@lombok.experimental.Value +class ValueExperimental2 { +}
\ No newline at end of file diff --git a/test/transform/resource/before/ValueExperimentalStarImport.java b/test/transform/resource/before/ValueExperimentalStarImport.java new file mode 100644 index 00000000..5f18cffe --- /dev/null +++ b/test/transform/resource/before/ValueExperimentalStarImport.java @@ -0,0 +1,5 @@ +import lombok.experimental.*; + +@Value +class ValueExperimentalStarImport { +}
\ No newline at end of file diff --git a/test/transform/resource/before/ValuePlain.java b/test/transform/resource/before/ValuePlain.java index 39c583cc..3fe33705 100644 --- a/test/transform/resource/before/ValuePlain.java +++ b/test/transform/resource/before/ValuePlain.java @@ -1,5 +1,5 @@ -import lombok.experimental.Value; -@lombok.experimental.Value class Value1 { +import lombok.Value; +@lombok.Value class Value1 { final int x; String name; } diff --git a/test/transform/resource/messages-delombok/BuilderInvalidUse.java.messages b/test/transform/resource/messages-delombok/BuilderInvalidUse.java.messages new file mode 100644 index 00000000..aeeb0c86 --- /dev/null +++ b/test/transform/resource/messages-delombok/BuilderInvalidUse.java.messages @@ -0,0 +1,2 @@ +1:1 @Getter, @Setter, @Wither, @Data, @ToString, @EqualsAndHashCode, @AllArgsConstructor are not allowed on builder classes. +12:1 @Value is not allowed on builder classes.
\ No newline at end of file diff --git a/test/transform/resource/messages-delombok/EqualsAndHashCodeWithSomeExistingMethods.java.messages b/test/transform/resource/messages-delombok/EqualsAndHashCodeWithSomeExistingMethods.java.messages new file mode 100644 index 00000000..9a0b29f3 --- /dev/null +++ b/test/transform/resource/messages-delombok/EqualsAndHashCodeWithSomeExistingMethods.java.messages @@ -0,0 +1,2 @@ +4:1 Not generating equals: One of equals, hashCode, and canEqual exists. You should either write all of these are none of these (in the latter case, lombok generates them). +15:1 Not generating equals and hashCode: One of equals, hashCode, and canEqual exists. You should either write all of these are none of these (in the latter case, lombok generates them). diff --git a/test/transform/resource/messages-delombok/NonNullOnParameter.java.messages b/test/transform/resource/messages-delombok/NonNullOnParameter.java.messages new file mode 100644 index 00000000..ac87adcd --- /dev/null +++ b/test/transform/resource/messages-delombok/NonNullOnParameter.java.messages @@ -0,0 +1 @@ +22:89 @NonNull is meaningless on a primitive. diff --git a/test/transform/resource/messages-delombok/NonNullPlain.java.messages b/test/transform/resource/messages-delombok/NonNullPlain.java.messages new file mode 100644 index 00000000..67eb8abe --- /dev/null +++ b/test/transform/resource/messages-delombok/NonNullPlain.java.messages @@ -0,0 +1 @@ +7:9 @NonNull is meaningless on a primitive.
\ No newline at end of file diff --git a/test/transform/resource/messages-ecj/BuilderInvalidUse.java.messages b/test/transform/resource/messages-ecj/BuilderInvalidUse.java.messages new file mode 100644 index 00000000..8ffc6e26 --- /dev/null +++ b/test/transform/resource/messages-ecj/BuilderInvalidUse.java.messages @@ -0,0 +1,2 @@ +1:0 @Getter, @Setter, @FieldDefaults, @Wither, @Data, @ToString, @EqualsAndHashCode, @AllArgsConstructor are not allowed on builder classes. +12:331 @Value is not allowed on builder classes.
\ No newline at end of file diff --git a/test/transform/resource/messages-ecj/EqualsAndHashCodeWithSomeExistingMethods.java.messages b/test/transform/resource/messages-ecj/EqualsAndHashCodeWithSomeExistingMethods.java.messages new file mode 100644 index 00000000..cf6ebea2 --- /dev/null +++ b/test/transform/resource/messages-ecj/EqualsAndHashCodeWithSomeExistingMethods.java.messages @@ -0,0 +1,2 @@ +4:57 Not generating equals: One of equals, hashCode, and canEqual exists. You should either write all of these are none of these (in the latter case, lombok generates them). +15:194 Not generating equals and hashCode: One of equals, hashCode, and canEqual exists. You should either write all of these are none of these (in the latter case, lombok generates them). diff --git a/test/transform/resource/messages-ecj/NonNullOnParameter.java.messages b/test/transform/resource/messages-ecj/NonNullOnParameter.java.messages new file mode 100644 index 00000000..1539929b --- /dev/null +++ b/test/transform/resource/messages-ecj/NonNullOnParameter.java.messages @@ -0,0 +1,3 @@ +15:460 Dead code +22:683 @NonNull is meaningless on a primitive. +28:823 Dead code
\ No newline at end of file diff --git a/test/transform/resource/messages-ecj/NonNullPlain.java.messages b/test/transform/resource/messages-ecj/NonNullPlain.java.messages new file mode 100644 index 00000000..96eed252 --- /dev/null +++ b/test/transform/resource/messages-ecj/NonNullPlain.java.messages @@ -0,0 +1 @@ +7:116 @NonNull is meaningless on a primitive.
\ No newline at end of file diff --git a/test/transform/resource/messages-ecj/ValueExperimental.java.messages b/test/transform/resource/messages-ecj/ValueExperimental.java.messages new file mode 100644 index 00000000..db4520ca --- /dev/null +++ b/test/transform/resource/messages-ecj/ValueExperimental.java.messages @@ -0,0 +1 @@ +1:7 The type Value is deprecated
\ No newline at end of file diff --git a/test/transform/resource/messages-idempotent/NonNullOnParameter.java.messages b/test/transform/resource/messages-idempotent/NonNullOnParameter.java.messages new file mode 100644 index 00000000..fd23a32a --- /dev/null +++ b/test/transform/resource/messages-idempotent/NonNullOnParameter.java.messages @@ -0,0 +1 @@ +33:89 @NonNull is meaningless on a primitive.
\ No newline at end of file diff --git a/test/transform/resource/messages-idempotent/NonNullPlain.java.messages b/test/transform/resource/messages-idempotent/NonNullPlain.java.messages new file mode 100644 index 00000000..c48da311 --- /dev/null +++ b/test/transform/resource/messages-idempotent/NonNullPlain.java.messages @@ -0,0 +1,3 @@ +4:9 @NonNull is meaningless on a primitive. +18:29 @NonNull is meaningless on a primitive. +44:26 @NonNull is meaningless on a primitive.
\ No newline at end of file diff --git a/usage_examples/GetterLazyExample_post.jpage b/usage_examples/GetterLazyExample_post.jpage index 9f4b1ba3..afed1748 100644 --- a/usage_examples/GetterLazyExample_post.jpage +++ b/usage_examples/GetterLazyExample_post.jpage @@ -1,18 +1,19 @@ public class GetterLazyExample { - private double[] $lombok$lazy1v; - private volatile boolean $lombok$lazy1i; - private final Object $lombok$lazyLock = new Object[0]; + private final java.util.concurrent.AtomicReference<java.lang.Object> cached = new java.util.concurrent.AtomicReference<java.lang.Object>(); public double[] getCached() { - if (!this.$lombok$lazy1i) { - synchronized (this.$lombok$lazyLock) { - if (!this.$lombok$lazy1i) { - this.$lombok$lazy1v = expensive(); - this.$lombok$lazy1i = true; + java.lang.Object value = this.cached.get(); + if (value == null) { + synchronized(value) { + value = this.cached.get(); + if (value == null) { + final double[] actualValue = expensive(); + value = actualValue == null ? this.cached : actualValue; + this.cached.set(value); } } } - return this.$lombok$lazy1v; + return (double[])(value == this.cached ? null : value); } private double[] expensive() { diff --git a/usage_examples/GetterSetterExample_post.jpage b/usage_examples/GetterSetterExample_post.jpage index b99ba7e1..241a3a4e 100644 --- a/usage_examples/GetterSetterExample_post.jpage +++ b/usage_examples/GetterSetterExample_post.jpage @@ -1,19 +1,41 @@ public class GetterSetterExample { + /** + * Age of the person. Water is wet. + */ private int age = 10; + + /** + * Name of the person. + */ private String name; @Override public String toString() { return String.format("%s (age: %d)", name, age); } + /** + * Age of the person. Water is wet. + * + * @return The current value of this person's age. Circles are round. + */ public int getAge() { return age; } + /** + * Age of the person. Water is wet. + * + * @param age New value for this person's age. Sky is blue. + */ public void setAge(int age) { this.age = age; } + /** + * Changes the name of this person. + * + * @param name The new value. + */ protected void setName(String name) { this.name = name; } diff --git a/usage_examples/GetterSetterExample_pre.jpage b/usage_examples/GetterSetterExample_pre.jpage index 9ef0532c..4183aa5d 100644 --- a/usage_examples/GetterSetterExample_pre.jpage +++ b/usage_examples/GetterSetterExample_pre.jpage @@ -3,7 +3,21 @@ import lombok.Getter; import lombok.Setter; public class GetterSetterExample { + /** + * Age of the person. Water is wet. + * + * @param age New value for this person's age. Sky is blue. + * @return The current value of this person's age. Circles are round. + */ @Getter @Setter private int age = 10; + + /** + * Name of the person. + * -- SETTER -- + * Changes the name of this person. + * + * @param name The new value. + */ @Setter(AccessLevel.PROTECTED) private String name; @Override public String toString() { diff --git a/usage_examples/NonNullExample_post.jpage b/usage_examples/NonNullExample_post.jpage new file mode 100644 index 00000000..24175e06 --- /dev/null +++ b/usage_examples/NonNullExample_post.jpage @@ -0,0 +1,13 @@ +import lombok.NonNull; + +public class NonNullExample extends Something { + private String name; + + public NonNullExample(@NonNull Person person) { + super("Hello"); + if (person == null) { + throw new NullPointerException("person"); + } + this.name = person.getName(); + } +} diff --git a/usage_examples/NonNullExample_pre.jpage b/usage_examples/NonNullExample_pre.jpage new file mode 100644 index 00000000..47556ce7 --- /dev/null +++ b/usage_examples/NonNullExample_pre.jpage @@ -0,0 +1,10 @@ +import lombok.NonNull; + +public class NonNullExample extends Something { + private String name; + + public NonNullExample(@NonNull Person person) { + super("Hello"); + this.name = person.getName(); + } +} diff --git a/usage_examples/experimental/ValueExample_post.jpage b/usage_examples/ValueExample_post.jpage index ac9b64d1..ac9b64d1 100644 --- a/usage_examples/experimental/ValueExample_post.jpage +++ b/usage_examples/ValueExample_post.jpage diff --git a/usage_examples/experimental/ValueExample_pre.jpage b/usage_examples/ValueExample_pre.jpage index d9550c25..d9550c25 100644 --- a/usage_examples/experimental/ValueExample_pre.jpage +++ b/usage_examples/ValueExample_pre.jpage diff --git a/usage_examples/experimental/BuilderExample_post.jpage b/usage_examples/experimental/BuilderExample_post.jpage new file mode 100644 index 00000000..624b236b --- /dev/null +++ b/usage_examples/experimental/BuilderExample_post.jpage @@ -0,0 +1,40 @@ +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 new file mode 100644 index 00000000..9c754352 --- /dev/null +++ b/usage_examples/experimental/BuilderExample_pre.jpage @@ -0,0 +1,7 @@ +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 bb354c8f..15fcec7a 100644 --- a/website/download.html +++ b/website/download.html @@ -44,7 +44,7 @@ <div class="edgeLink">Feeling adventurous? Download the latest <a href="download-edge.html">snapshot</a> release.</div> <table cellspacing="0" cellpadding="0"> - <tr><td class="platform">Maven or Ivy</td> + <tr><td class="platform">Maven or Ivy</td> <td class="instruction">Lombok is in maven central. <a href="mavenrepo/index.html">More…</a></td></tr> <tr><td class="platform">Javac</td> @@ -53,11 +53,11 @@ <tr><td class="platform">NetBeans</td> <td class="instruction">Just put <code>lombok.jar</code> on the classpath and enable annotation processing. <a href="setup/netbeans.html">More…</a></td></tr> - <tr><td class="platform">Eclipse and STS</td> - <td class="instruction">Run <code>lombok.jar</code> as a java app (i.e. doubleclick it, usually) to install. Also add lombok.jar to your project.</td></tr> + <tr><td class="platform">Eclipse and variants</td> + <td class="instruction">Run <code>lombok.jar</code> as a java app (i.e. doubleclick it, usually) to install. Also add lombok.jar to your project. <span style="font-size: 0.8em;"><em>Supported variants: Springsource Tool Suite, JBoss Developer Studio</em></span></td></tr> - <tr><td class="platform">IDEA IntelliJ</td> - <td class="instruction">lombok doesn't (yet) work under IntelliJ. We're working on it though!</td></tr> + <tr><td class="platform">IDEA IntelliJ</td> + <td class="instruction"><a href="https://code.google.com/p/lombok-intellij-plugin/">A plugin developed by Michael Plushnikov</a> adds support for most features.</td></tr> <tr><td class="platform">Javadoc</td> <td class="instruction">First delombok your code then run javadoc on the result. <a href="features/delombok.html">More…</a></td></tr> @@ -68,7 +68,7 @@ <tr><td class="platform">GWT</td> <td class="instruction">Lombok works with GWT. <a href="setup/gwt.html">More…</a></td></tr> - <tr><td class="platform">Play! Framework</td> + <tr><td class="platform">Play! Framework</td> <td class="instruction">Use Aaron Freeman's <a href="https://github.com/aaronfreeman/play-lombok#readme">lombok play plugin</a>.</td></tr> <tr><td class="platform">ecj</td> diff --git a/website/features/Cleanup.html b/website/features/Cleanup.html index d1637dd4..a6e41f39 100644 --- a/website/features/Cleanup.html +++ b/website/features/Cleanup.html @@ -60,7 +60,7 @@ </div> </div> <div class="footer"> - <a href="index.html">Back to features</a> | <a href="Data.html">Previous feature (@Data)</a> | <a href="Synchronized.html">Next feature (@Synchronized)</a><br /> + <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-2013 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> diff --git a/website/features/Data.html b/website/features/Data.html index 8ace96cb..ad3aa892 100644 --- a/website/features/Data.html +++ b/website/features/Data.html @@ -75,7 +75,7 @@ </div> </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="Cleanup.html">Next feature (@Cleanup)</a><br /> + <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-2013 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> diff --git a/website/features/Delegate.html b/website/features/Delegate.html index 532f3f54..02cdf290 100644 --- a/website/features/Delegate.html +++ b/website/features/Delegate.html @@ -55,16 +55,15 @@ When passing classes to the annotation's <code>types</code> or <code>excludes</code> parameter, you cannot include generics. This is a limitation of java. Use private inner interfaces or classes that extend the intended type including the generics parameter to work around this problem. - </p> - <p> + </p><p> When passing classes to the annotation, these classes do not need to be supertypes of the field. See the example. - </p> - <p> + </p><p> <code>@Delegate</code> cannot be used on static fields or methods. - <div> + </p> + </div> </div> <div class="footer"> - <a href="index.html">Back to features</a> | <a href="val.html">Previous feature (val)</a> | <span class="disabled">Next feature</span><br /> + <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 © 2010-2013 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> diff --git a/website/features/GetterLazy.html b/website/features/GetterLazy.html index bc5ecb0c..c6d21f01 100644 --- a/website/features/GetterLazy.html +++ b/website/features/GetterLazy.html @@ -15,12 +15,7 @@ <div class="overview"> <h3>Overview</h3> <p> - <em>NEW IN Lombok 0.10: </em>You can let lombok generate a getter which will calculate a value once, the first time this getter is called, and cache it from then on. This can be useful - if calculating the value takes a lot of CPU, or the value takes a lot of memory. To use this feature, create a <code>private final</code> variable, - initialize it with the expression that's expensive to run, and annotate your field with <code>@Getter(lazy=true)</code>. The field will be hidden from the - rest of your code, and the expression will be evaluated no more than once, when the getter is first called. There are no magic marker values (i.e. even - if the result of your expensive calculation is <code>null</code>, the result is cached) and your expensive calculation need not be thread-safe, as lombok - takes care of locking. + <em>NEW IN Lombok 0.10: </em>You can let lombok generate a getter which will calculate a value once, the first time this getter is called, and cache it from then on. This can be useful if calculating the value takes a lot of CPU, or the value takes a lot of memory. To use this feature, create a <code>private final</code> variable, initialize it with the expression that's expensive to run, and annotate your field with <code>@Getter(lazy=true)</code>. The field will be hidden from the rest of your code, and the expression will be evaluated no more than once, when the getter is first called. There are no magic marker values (i.e. even if the result of your expensive calculation is <code>null</code>, the result is cached) and your expensive calculation need not be thread-safe, as lombok takes care of locking. </p> </div> <div class="snippets"> @@ -38,15 +33,14 @@ <div class="overview"> <h3>Small print</h3><div class="smallprint"> <p> - Lombok actually creates a few fields all prefixed with <code>$lombok$</code> to cache the value. You should not rely on the exact type, name, and structure - of these fields as future implementations may change them. To access the lazily initialized value, <em>always</em> use the generated getter. + You should never refer to the field directly, always use the getter generated by lombok, because the type of the field will be mangled into an <code>AtomicReference</code>. Do not try to directly access this <code>AtomicReference</code>; if it points to itself, the value has been calculated, and it is <code>null</code>. If the reference points to <code>null</code>, then the value has not been calculated. This behaviour may change in future versions. Therefore, <em>always</em> use the generated getter to access your field! </p><p> Other Lombok annotations such as <code>@ToString</code> always call the getter even if you use <code>doNotUseGetters=true</code>. </p> </div> </div> <div class="footer"> - <a href="index.html">Back to features</a> | <a href="GetterSetter.html">Previous feature (@Getter / @Setter)</a> | <a href="ToString.html">Next feature (@ToString)</a><br /> + <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-2013 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> diff --git a/website/features/GetterSetter.html b/website/features/GetterSetter.html index 03704119..7e2ff226 100644 --- a/website/features/GetterSetter.html +++ b/website/features/GetterSetter.html @@ -30,6 +30,8 @@ behaviour of a <code>@Getter</code>, <code>@Setter</code> or <code>@Data</code> annotation on a class. </p><p> To put annotations on the generated method, you can use <code>onMethod=@_({@AnnotationsHere})</code>; to put annotations on the only parameter of a generated setter method, you can use <code>onParam=@_({@AnnotationsHere})</code>. Be careful though! This is an experimental feature. For more details see the documentation on the <a href="experimental/onX.html">onX</a> feature. + </p><p> + <em>NEW in lombok v1.12.0:</em> javadoc on the field will now be copied to generated getters and setters. Normally, all text is copied, and <code>@return</code> is <em>moved</em> to the getter, whilst <code>@param</code> lines are <em>moved</em> to the setter. Moved means: Deleted from the field's javadoc. It is also possible to define unique text for each getter/setter. To do that, you create a 'section' named <code>GETTER</code> and/or <code>SETTER</code>. A section is a line in your javadoc containing 2 or more dashes, then the text 'GETTER' or 'SETTER', followed by 2 or more dashes, and nothing else on the line. If you use sections, <code>@return</code> and <code>@param</code> stripping for that section is no longer done (move the <code>@return</code> or <code>@param</code> line into the section). </p> </div> <div class="snippets"> @@ -75,7 +77,7 @@ </div> </div> <div class="footer"> - <a href="index.html">Back to features</a> | <span class="disabled">Previous feature</span> | <a href="GetterLazy.html">Next feature (@Getter(lazy=true))</a><br /> + <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-2013 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> diff --git a/website/features/Log.html b/website/features/Log.html index fb529426..2d4fa375 100644 --- a/website/features/Log.html +++ b/website/features/Log.html @@ -16,9 +16,9 @@ <h3>Overview</h3> <p> <em>NEW in lombok 0.10: </em>You can annotate any class with a log annotation to let lombok generate a logger field.<br /> - The logger is named <code>log</code> and field's type depends on which logger you have selected. + The logger is named <code>log</code> and the field's type depends on which logger you have selected. </p><p> - There are four choices available:<br /> + There are six choices available:<br /> <dl> <dt><code>@CommonsLog</code></dt> <dd>Creates <code><span class="keyword">private static final </span><a href="http://commons.apache.org/logging/apidocs/org/apache/commons/logging/Log.html">org.apache.commons.logging.Log</a> <span class="staticfield">log</span> = <a href="http://commons.apache.org/logging/apidocs/org/apache/commons/logging/LogFactory.html#getLog(java.lang.Class)">org.apache.commons.logging.LogFactory.getLog</a>(LogExample.<span class="keyword">class</span>);</code></dd> @@ -26,6 +26,8 @@ <dd>Creates <code><span class="keyword">private static final </span><a href="http://download.oracle.com/javase/6/docs/api/java/util/logging/Logger.html">java.util.logging.Logger</a> <span class="staticfield">log</span> = <a href="http://download.oracle.com/javase/6/docs/api/java/util/logging/Logger.html#getLogger(java.lang.String)">java.util.logging.Logger.getLogger</a>(LogExample.<span class="keyword">class</span>.getName());</code></dd> <dt><code>@Log4j</code></dt> <dd>Creates <code><span class="keyword">private static final </span><a href="http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/Logger.html">org.apache.log4j.Logger</a> <span class="staticfield">log</span> = <a href="http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/Logger.html#getLogger(java.lang.Class)">org.apache.log4j.Logger.getLogger</a>(LogExample.<span class="keyword">class</span>);</code></dd> + <dt><code>@Log4j2</code></dt> + <dd>Creates <code><span class="keyword">private static final </span><a href="http://logging.apache.org/log4j/2.0/log4j-api/apidocs/org/apache/logging/log4j/Logger.html">org.apache.logging.log4j.Logger</a> <span class="staticfield">log</span> = <a href="http://logging.apache.org/log4j/2.0/log4j-api/apidocs/org/apache/logging/log4j/LogManager.html#getLogger(java.lang.Class)">org.apache.logging.log4j.LogManager.getLogger</a>(LogExample.<span class="keyword">class</span>);</code></dd> <dt><code>@Slf4j</code></dt> <dd>Creates <code><span class="keyword">private static final </span><a href="http://www.slf4j.org/api/org/slf4j/Logger.html">org.slf4j.Logger</a> <span class="staticfield">log</span> = <a href="http://www.slf4j.org/apidocs/org/slf4j/LoggerFactory.html#getLogger(java.lang.Class)">org.slf4j.LoggerFactory.getLogger</a>(LogExample.<span class="keyword">class</span>);</code></dd> <dt><code>@XSlf4j</code></dt> @@ -58,7 +60,7 @@ </div> </div> <div class="footer"> - <a href="index.html">Back to features</a> | <a href="SneakyThrows.html">Previous feature (@SneakyThrows)</a> | <a href="val.html">Next feature (val)</a><br /> + <a href="index.html">Back to features</a> | <a href="GetterLazy.html">Previous feature (@Getter(lazy=true))</a> | <a href="Delegate.html">Next feature (@Delegate)</a><br /> <a href="../credits.html" class="creditsLink">credits</a> | <span class="copyright">Copyright © 2009-2013 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> diff --git a/website/features/NonNull.html b/website/features/NonNull.html new file mode 100644 index 00000000..9faad502 --- /dev/null +++ b/website/features/NonNull.html @@ -0,0 +1,73 @@ +<!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>val</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>@NonNull</h1> + <div class="byline">or: How I learned to stop worrying and love the NullPointerException.</div> + <div class="overview"> + <h3>Overview</h3> + <p> + <em>NEW in Lombok 0.11.10: </em>You can use <code>@NonNull</code> on the parameter of a method or constructor to have lombok generate a null-check statement for you. + </p><p> + Lombok has always treated any annotation named <code>@NonNull</code> on a field as a signal to generate a null-check if lombok generates an entire method or constructor for you, via + for example <a href="Data.html"><code>@Data</code></a>. Now, however, using lombok's own <code>@lombok.NonNull</code> on a parameter results in the insertion of just the null-check + statement inside your own method or constructor. + </p><p> + The null-check looks like <code>if (param == null) throw new NullPointerException("param");</code> and will be inserted at the very top of your method. For constructors, the null-check + will be inserted immediately following any explicit <code>this()</code> or <code>super()</code> calls. + </p><p> + If a null-check is already present at the top, no additional null-check will be generated. + </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"> + <h3>Small print</h3><div class="smallprint"> + <p> + Lombok's detection scheme for already existing null-checks consists of scanning for if statements that look just like lombok's own. Any 'throws' statement as + the 'then' part of the if statement, whether in braces or not, counts. The conditional of the if statement <em>must</em> look exactly like <code>PARAMNAME == null</code>. + The first statement in your method that is not such a null-check stops the process of inspecting for null-checks. + </p><p> + While <code>@Data</code> and other method-generating lombok annotations will trigger on any annotation named <code>@NonNull</code> regardless of casing or package name, + this feature only triggers on lombok's own <code>@NonNull</code> annotation from the <code>lombok</code> package. + </p><p> + A <code>@NonNull</code> on a primitive parameter results in a warning. No null-check will be generated. + </p> + </div> + </div> + <div class="footer"> + <a href="index.html">Back to features</a> | <a href="val.html">Previous feature (val)</a> | <a href="Cleanup.html">Next feature (@Cleanup)</a><br /> + <a href="../credits.html" class="creditsLink">credits</a> | <span class="copyright">Copyright © 2013 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 0f04b7d9..3b3987e4 100644 --- a/website/features/SneakyThrows.html +++ b/website/features/SneakyThrows.html @@ -54,8 +54,6 @@ <div class="overview"> <h3>Small print</h3><div class="smallprint"> <p> - <code>@SneakyThrows</code> is an implementation of this feature request: <a href="http://bugs.sun.com/view_bug.do?bug_id=6534270">http://bugs.sun.com/view_bug.do?bug_id=6534270</a>. - </p><p> Because <code>@SneakyThrows</code> is an implementation detail and not part of your method signature, it is an error if you try to declare a checked exception as sneakily thrown when you don't call any methods that throw this exception. (Doing so is perfectly legal for <code>throws</code> statements to accommodate subclasses). Similarly, <code>@SneakyThrows</code> does not inherit. @@ -64,11 +62,15 @@ statement in a try/catch block with just <code>e.printStackTrace()</code> in the catch block. This is so spectacularly non-productive compared to just sneakily throwing the exception onwards, that Roel and Reinier feel more than justified in claiming that the checked exception system is far from perfect, and thus an opt-out mechanism is warranted. - </p> + </p><p> + If you put <code>@SneakyThrows</code> on a constructor, any call to a sibling or super constructor is <em>excluded</em> from the <code>@SneakyThrows</code> treatment. This is a + java restriction we cannot work around: Calls to sibling/super constructors MUST be the first statement in the constructor; they cannot be placed inside try/catch blocks. + </p><p> + <code>@SneakyThrows</code> on an empty method, or a constructor that is empty or only has a call to a sibling / super constructor results in no try/catch block and a warning. </div> </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="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-2013 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> diff --git a/website/features/Synchronized.html b/website/features/Synchronized.html index 4b6ef251..9ab6c87f 100644 --- a/website/features/Synchronized.html +++ b/website/features/Synchronized.html @@ -59,7 +59,7 @@ </div> </div> <div class="footer"> - <a href="index.html">Back to features</a> | <a href="Cleanup.html">Previous feature (@Cleanup)</a> | <a href="SneakyThrows.html">Next feature (@SneakyThrows)</a><br /> + <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-2013 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> diff --git a/website/features/ToString.html b/website/features/ToString.html index c3b389ba..585dc72b 100644 --- a/website/features/ToString.html +++ b/website/features/ToString.html @@ -68,7 +68,7 @@ </div> </div> <div class="footer"> - <a href="index.html">Back to features</a> | <a href="GetterLazy.html">Previous feature (@Getter(lazy=true))</a> | <a href="EqualsAndHashCode.html">Next feature (@EqualsAndHashCode)</a><br /> + <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-2013 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> diff --git a/website/features/experimental/Value.html b/website/features/Value.html index d2acfee4..6bbd6f2a 100644 --- a/website/features/experimental/Value.html +++ b/website/features/Value.html @@ -1,15 +1,15 @@ <!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" /> + <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 - @ExtensionMethod</title> + <title>@ExtensionMethod</title> </head><body><div id="pepper"> <div class="minimumHeight"></div> <div class="meat"> - <div class="header"><a href="../../index.html">Project Lombok</a></div> + <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="since"> @@ -18,27 +18,20 @@ <code>@Value</code> was introduced as experimental feature in lombok v0.11.4. </p><p> <code>@Value</code> no longer implies <code>@Wither</code> since lombok v0.11.8. - </div> - <div class="experimental"> - <h3>Experimental</h3> - <p> - Experimental because: - <ul> - <li>Various choices still have to be vetted as being the correct 'least surprise' choice: Should the class be made final by default, etc.</li> - </ul> - Current status: <em>positive</em> - Currently we feel this feature may move out of experimental status with no or minor changes soon. + </p><p> + <code>@Value</code> promoted to the main <code>lombok</code> package since lombok v0.12.0. </div> <div class="overview"> <h3>Overview</h3> <p> - <code>@Value</code> is the immutable variant of <a href="../Data.html"><code>@Data</code></a>; all fields are made <code>private</code> and <code>final</code> by default, and setters are not generated. The class itself is also made <code>final</code> by default, because immutability is not something that can be forced onto a subclass. Like <code>@Data</code>, useful <code>toString()</code>, <code>equals()</code> and <code>hashCode()</code> methods are also generated, each field gets a getter method, and a constructor that covers every + <code>@Value</code> is the immutable variant of <a href="Data.html"><code>@Data</code></a>; all fields are made <code>private</code> and <code>final</code> by default, and setters are not generated. The class itself is also made <code>final</code> by default, because immutability is not something that can be forced onto a subclass. Like <code>@Data</code>, useful <code>toString()</code>, <code>equals()</code> and <code>hashCode()</code> methods are also generated, each field gets a getter method, and a constructor that covers every argument (except <code>final</code> fields that are initialized in the field declaration) is also generated. </p><p> In practice, <code>@Value</code> is shorthand for: <code>final @ToString @EqualsAndHashCode @AllArgsConstructor @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) @Getter</code>. </p><p> It is possible to override the final-by-default and private-by-default behaviour using either an explicit access level on a field, or by using the <code>@NonFinal</code> or <code>@PackagePrivate</code> annotations.<br /> It is possible to override any default behaviour for any of the 'parts' that make up <code>@Value</code> by explicitly using that annotation. - </p> + </p> </div> <div class="snippets"> <div class="pre"> @@ -55,15 +48,20 @@ <div class="overview"> <h3>Small print</h3><div class="smallprint"> <p> - Look for the documentation on the 'parts' of <code>@Value</code>: <a href="../ToString.html"><code>@ToString</code></a>, <a href="../EqualsAndHashCode.html"><code>@EqualsAndHashCode</code></a>, <a href="../Constructor.html"><code>@AllArgsConstructor</code></a>, <a href="FieldDefaults.html"><code>@FieldDefaults</code></a>, and <a href="../GetterSetter.html"><code>@Getter</code></a>. + Look for the documentation on the 'parts' of <code>@Value</code>: <a href="ToString.html"><code>@ToString</code></a>, <a href="EqualsAndHashCode.html"><code>@EqualsAndHashCode</code></a>, <a href="Constructor.html"><code>@AllArgsConstructor</code></a>, <a href="experimental/FieldDefaults.html"><code>@FieldDefaults</code></a>, and <a href="GetterSetter.html"><code>@Getter</code></a>. </p><p> For classes with generics, it's useful to have a static method which serves as a constructor, because inference of generic parameters via static methods works in java6 and avoids having to use the diamond operator. While you can force this by applying an explicit <code>@AllArgsConstructor(staticConstructor="of")</code> annotation, there's also the <code>@Value(staticConstructor="of")</code> feature, which will make the generated all-arguments constructor private, and generates a public static method named <code>of</code> which is a wrapper around this private constructor. + </p><p> + <code>@Value</code> was an experimental feature from v0.11.4 to v0.11.9 (as <code>@lombok.experimental.Value</code>). It has since been moved into the core package. The old annotation is still + around (and is an alias). It will eventually be removed in a future version, though. + </p><p> + It is not possible to use <code>@FieldDefaults</code> to 'undo' the private-by-default and final-by-default aspect of fields in the annotated class. Use <code>@NonFinal</code> and <code>@PackagePrivate</code> on the fields in the class to override this behaviour. </p> </div> </div> <div class="footer"> - <a href="index.html">Back to experimental features</a> | <a href="Wither.html">Previous feature (@Wither)</a> | <a href="onX.html">Next feature (onX)</a><br /> - <a href="../../credits.html" class="creditsLink">credits</a> | <span class="copyright">Copyright © 2009-2013 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="SneakyThrows.html">Next feature (@SneakyThrows)</a><br /> + <a href="../credits.html" class="creditsLink">credits</a> | <span class="copyright">Copyright © 2009-2013 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 dce77d32..3ca79de5 100644 --- a/website/features/experimental/Accessors.html +++ b/website/features/experimental/Accessors.html @@ -84,7 +84,7 @@ </div> </div> <div class="footer"> - <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="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-2013 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> diff --git a/website/features/experimental/Builder.html b/website/features/experimental/Builder.html new file mode 100644 index 00000000..0616afc9 --- /dev/null +++ b/website/features/experimental/Builder.html @@ -0,0 +1,134 @@ +<!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 - @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> + @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"> + <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> + <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-2013 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 da2156cd..b334cd7c 100644 --- a/website/features/experimental/Wither.html +++ b/website/features/experimental/Wither.html @@ -46,6 +46,8 @@ a 'wither' is generated for each field (even non-final fields). </p><p> To put annotations on the generated method, you can use <code>onMethod=@_({@AnnotationsHere})</code>; to put annotations on the only parameter of a generated wither method, you can use <code>onParam=@_({@AnnotationsHere})</code>. Be careful though! This is an experimental feature. For more details see the documentation on the <a href="onX.html">onX</a> feature. + </p><p> + <em>NEW in lombok v1.12.0:</em> javadoc on the field will now be copied to generated withers. Normally, all text is copied, and <code>@param</code> is <em>moved</em> to the wither, whilst <code>@return</code> lines are stripped from the wither's javadoc. Moved means: Deleted from the field's javadoc. It is also possible to define unique text for the wither's javadoc. To do that, you create a 'section' named <code>WITHER</code>. A section is a line in your javadoc containing 2 or more dashes, then the text 'WITHER', followed by 2 or more dashes, and nothing else on the line. If you use sections, <code>@return</code> and <code>@param</code> stripping / copying for that section is no longer done (move the <code>@param</code> line into the section). </p> </div> <div class="snippets"> @@ -83,7 +85,7 @@ </div> </div> <div class="footer"> - <a href="index.html">Back to experimental features</a> | <a href="FieldDefaults.html">Previous feature (@FieldDefaults)</a> | <a href="Value.html">Next feature (@Value)</a><br /> + <a href="index.html">Back to experimental features</a> | <a href="FieldDefaults.html">Previous feature (@FieldDefaults)</a> | <a href="onX.html">Next feature (onX)</a><br /> <a href="../../credits.html" class="creditsLink">credits</a> | <span class="copyright">Copyright © 2009-2013 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> diff --git a/website/features/experimental/index.html b/website/features/experimental/index.html index 24fbb541..16d58050 100644 --- a/website/features/experimental/index.html +++ b/website/features/experimental/index.html @@ -22,6 +22,8 @@ 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> @@ -30,12 +32,17 @@ <dd>New default field modifiers for the 21st century.</dd> <dt><a href="Wither.html"><code>@Wither</code></a></dt> <dd>Immutable 'setters' - methods that create a clone but with one changed field.</dd> - <dt><a href="Value.html"><code>@Value</code></a></dt> - <dd>Immutable classes made very easy.</dd> <dt><a href="onX.html"><code>onMethod= / onConstructor= / onParam</code></a></dt> <dd>Sup dawg, we heard you like annotations, so we put annotations in your annotations so you can annotate while you're annotating.</dd> </dl> </div> + <div class="index overview"> + <h3>Putting the "Ex" in "Experimental": promoted or deleted experimental features.</h3> + <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> + </dl> + </div> <div class="footer"> <a href="../../credits.html" class="creditsLink">credits</a> | <span class="copyright">Copyright © 2009-2013 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>.</span> </div> diff --git a/website/features/experimental/onX.html b/website/features/experimental/onX.html index 99365deb..66b0164f 100644 --- a/website/features/experimental/onX.html +++ b/website/features/experimental/onX.html @@ -69,7 +69,7 @@ </div> </div> <div class="footer"> - <a href="index.html">Back to experimental features</a> | <a href="Value.html">Previous feature (@Value)</a> | <span class="disabled">Next feature</span><br /> + <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-2013 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> diff --git a/website/features/index.html b/website/features/index.html index 37038ddb..f9b8cdfa 100644 --- a/website/features/index.html +++ b/website/features/index.html @@ -13,10 +13,14 @@ <h1>Lombok features</h1> <div class="index overview"> <dl> + <dt><a href="val.html"><code>val</code></a></dt> + <dd>Finally! Hassle-free final local variables.</dd> + <dt><a href="NonNull.html"><code>@NonNull</code></a></dt> + <dd>or: How I learned to stop worrying and love the NullPointerException.</dd> + <dt><a href="Cleanup.html"><code>@Cleanup</code></a></dt> + <dd>Automatic resource management: Call your <code>close()</code> methods safely with no hassle.</dd> <dt><a href="GetterSetter.html"><code>@Getter</code> / <code>@Setter</code></a></dt> <dd>Never write <code>public int getFoo() {return foo;}</code> again.</dd> - <dt><a href="GetterLazy.html"><code>@Getter(lazy=true)</code></a></dt> - <dd>Laziness is a virtue!</dd> <dt><a href="ToString.html"><code>@ToString</code></a></dt> <dd>No need to start a debugger to see your fields: Just let lombok generate a <code>toString</code> for you!</dd> <dt><a href="EqualsAndHashCode.html"><code>@EqualsAndHashCode</code></a></dt> @@ -26,16 +30,16 @@ <dt><a href="Data.html"><code>@Data</code></a></dt> <dd>All together now: A shortcut for <code>@ToString</code>, <code>@EqualsAndHashCode</code>, <code>@Getter</code> on all fields, and <code>@Setter</code> on all non-final fields, and <code>@RequiredArgsConstructor</code>!</dd> - <dt><a href="Cleanup.html"><code>@Cleanup</code></a></dt> - <dd>Automatic resource management: Call your <code>close()</code> methods safely with no hassle.</dd> - <dt><a href="Synchronized.html"><code>@Synchronized</code></a></dt> - <dd><code>synchronized</code> done right: Don't expose your locks.</dd> + <dt><a href="Value.html"><code>@Value</code></a></dt> + <dd>Immutable classes made very easy.</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> + <dd><code>synchronized</code> done right: Don't expose your locks.</dd> + <dt><a href="GetterLazy.html"><code>@Getter(lazy=true)</code></a></dt> + <dd>Laziness is a virtue!</dd> <dt><a href="Log.html"><code>@Log</code></a></dt> <dd>Captain's Log, stardate 24435.7: "What was that line again?"</dd> - <dt><a href="val.html"><code>val</code></a></dt> - <dd>Finally! Hassle-free final local variables.</dd> <dt><a href="Delegate.html"><code>@Delegate</code></a></dt> <dd>Don't lose your composition.</dd> <dt><a href="experimental/index.html">experimental features</a></dt> @@ -43,6 +47,9 @@ </dl> </div> <div class="pointer"> + If you'd rather read a longer tutorial, <a href="http://jnb.ociweb.com/jnb/jnbJan2010.html">here is a great tutorial (including a walkthrough through some of the above features)</a> written by Michael Kimberlin. + </div> + <div class="pointer"> The documentation above is a lot easier to follow, but if you want to build your own transformations, or you want to add javadoc to <code>lombok.jar</code> in your IDE, you can also check out <a href="../api/index.html">the javadoc</a>. </div> diff --git a/website/features/val.html b/website/features/val.html index cec799e9..c4c8ad27 100644 --- a/website/features/val.html +++ b/website/features/val.html @@ -50,7 +50,7 @@ </div> </div> <div class="footer"> - <a href="index.html">Back to features</a> | <a href="Log.html">Previous feature (@Log)</a> | <a href="Delegate.html">Next feature (@Delegate)</a><br /> + <a href="index.html">Back to features</a> | <span class="disabled">Previous feature</span> | <a href="NonNull.html">Next feature (@NonNull)</a><br /> <a href="../credits.html" class="creditsLink">credits</a> | <span class="copyright">Copyright © 2010-2013 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> diff --git a/website/index.html b/website/index.html index edce2a2d..97b51114 100644 --- a/website/index.html +++ b/website/index.html @@ -87,7 +87,7 @@ </video> </div> <div class="endBar"> - <a href="novideo.html">I can't see the video</a> | <a href="slideshow.html">I don't want to see the video - show me a slideshow instead!</a> + <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-2013 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>. |