From 2d76b1d22dea1e78326ebafdb48967512183cede Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Thu, 23 May 2013 21:12:01 +0200 Subject: First steps Builder support --- .../lombok/eclipse/handlers/HandleBuilder.java | 71 +++++++++++++ src/core/lombok/experimental/Builder.java | 112 +++++++++++++++++++++ src/utils/lombok/core/JavaIdentifiers.java | 57 +++++++++++ 3 files changed, 240 insertions(+) create mode 100644 src/core/lombok/eclipse/handlers/HandleBuilder.java create mode 100644 src/core/lombok/experimental/Builder.java create mode 100644 src/utils/lombok/core/JavaIdentifiers.java diff --git a/src/core/lombok/eclipse/handlers/HandleBuilder.java b/src/core/lombok/eclipse/handlers/HandleBuilder.java new file mode 100644 index 00000000..13271165 --- /dev/null +++ b/src/core/lombok/eclipse/handlers/HandleBuilder.java @@ -0,0 +1,71 @@ +/* + * 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.handlers.EclipseHandlerUtil.*; + +import java.util.Collections; +import java.util.List; + +import org.eclipse.jdt.internal.compiler.ast.Annotation; +import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration; +import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; + +import lombok.AccessLevel; +import lombok.core.AnnotationValues; +import lombok.core.ImmutableList; +import lombok.core.JavaIdentifiers; +import lombok.eclipse.EclipseAnnotationHandler; +import lombok.eclipse.EclipseNode; +import lombok.experimental.Builder; + +public class HandleBuilder extends EclipseAnnotationHandler { + @Override public void handle(AnnotationValues annotation, Annotation ast, EclipseNode annotationNode) { + String builderMethodName = annotation.getInstance().builderMethodName(); + if (builderMethodName == null) builderMethodName = "builder"; + if (builderMethodName.length() == 0) { + annotationNode.addError("builderMethodName cannot be the empty string."); + return; + } + + if (!JavaIdentifiers.isValidJavaIdentifier(builderMethodName)) { + annotationNode.addError("builderMethodName must be a valid java method name."); + return; + } + + EclipseNode parent = annotationNode.up(); + + if (parent.get() instanceof ConstructorDeclaration) { + + } + + if (parent.get() instanceof MethodDeclaration) { + + } + + if (parent.get() instanceof TypeDeclaration) { + // TODO: How do we ensure this one will 'win' over the implicit constructors generated by @Data and @Value. + new HandleConstructor().generateAllArgsConstructor(parent, AccessLevel.PRIVATE, null, true, Collections.emptyList(), ast); + } + } +} diff --git a/src/core/lombok/experimental/Builder.java b/src/core/lombok/experimental/Builder.java new file mode 100644 index 00000000..b6667462 --- /dev/null +++ b/src/core/lombok/experimental/Builder.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.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}. + *

+ * 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. + *

+ * The effect of {@code @Builder} is that an inner class is generated named TBuilder, + * with a private constructor. Instances of TBuilder are made with the static + * method named {@code builder()} which is also generated for you in the class itself (not in the builder class). + *

+ * The TBuilder 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 build() 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. + *

+ * Complete documentation is found at the project lombok features page for @Builder. + *

+ *

+ * Before: + * + *

+ * @Builder
+ * class Example {
+ * 	private int foo;
+ * 	private final String bar;
+ * }
+ * 
+ * + * After: + * + *
+ * 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);
+ * 		}
+ * 	}
+ * }
+ * 
+ */ +@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"; +} diff --git a/src/utils/lombok/core/JavaIdentifiers.java b/src/utils/lombok/core/JavaIdentifiers.java new file mode 100644 index 00000000..dfec8815 --- /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 ImmutableList KEYWORDS = ImmutableList.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); + } +} -- cgit