http://git-wip-us.apache.org/repos/asf/groovy/blob/0ad8c07c/src/main/groovy/groovy/transform/Trait.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/transform/Trait.java b/src/main/groovy/groovy/transform/Trait.java new file mode 100644 index 0000000..de88180 --- /dev/null +++ b/src/main/groovy/groovy/transform/Trait.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package groovy.transform; + +import org.codehaus.groovy.transform.GroovyASTTransformationClass; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Used to mark a class as being a trait. + * + * @since 2.3.0 + */ [email protected] +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +@GroovyASTTransformationClass("org.codehaus.groovy.transform.trait.TraitASTTransformation") +public @interface Trait { +}
http://git-wip-us.apache.org/repos/asf/groovy/blob/0ad8c07c/src/main/groovy/groovy/transform/TupleConstructor.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/transform/TupleConstructor.java b/src/main/groovy/groovy/transform/TupleConstructor.java new file mode 100644 index 0000000..2cd2be7 --- /dev/null +++ b/src/main/groovy/groovy/transform/TupleConstructor.java @@ -0,0 +1,278 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package groovy.transform; + +import org.codehaus.groovy.transform.GroovyASTTransformationClass; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Class annotation used to assist in the creation of tuple constructors in classes. + * Should be used with care with other annotations which create constructors - see "Known + * Limitations" for more details. + * <p> + * It allows you to write classes in this shortened form: + * <pre class="groovyTestCase"> + * {@code @groovy.transform.TupleConstructor} class Customer { + * String first, last + * int age + * Date since + * Collection favItems + * } + * def c1 = new Customer(first:'Tom', last:'Jones', age:21, since:new Date(), favItems:['Books', 'Games']) + * def c2 = new Customer('Tom', 'Jones', 21, new Date(), ['Books', 'Games']) + * def c3 = new Customer('Tom', 'Jones') + * </pre> + * The {@code @TupleConstructor} annotation instructs the compiler to execute an + * AST transformation which adds the necessary constructor method to your class. + * <p> + * A tuple constructor is created with a parameter for each property (and optionally field and + * super properties). + * A default value is provided (using Java's default values) for all parameters in the constructor. + * Groovy's normal conventions then allows any number of parameters to be left off the end of the parameter list + * including all of the parameters - giving a no-arg constructor which can be used with the map-style naming conventions. + * <p> + * The order of parameters is given by the properties of any super classes with most super first + * (if {@code includeSuperProperties} is set) followed by the properties of the class followed + * by the fields of the class (if {@code includeFields} is set). Within each grouping the order + * is as attributes appear within the respective class. + * <p>More examples:</p> + * <pre class="groovyTestCase"> + * //-------------------------------------------------------------------------- + * import groovy.transform.TupleConstructor + * + * @TupleConstructor() + * class Person { + * String name + * List likes + * private boolean active = false + * } + * + * def person = new Person('mrhaki', ['Groovy', 'Java']) + * + * assert person.name == 'mrhaki' + * assert person.likes == ['Groovy', 'Java'] + * + * person = new Person('mrhaki') + * + * assert person.name == 'mrhaki' + * assert !person.likes + * </pre> + * <pre class="groovyTestCase"> + * //-------------------------------------------------------------------------- + * // includeFields in the constructor creation. + * import groovy.transform.TupleConstructor + * + * @TupleConstructor(includeFields=true) + * class Person { + * String name + * List likes + * private boolean active = false + * + * boolean isActivated() { active } + * } + * + * def person = new Person('mrhaki', ['Groovy', 'Java'], true) + * + * assert person.name == 'mrhaki' + * assert person.likes == ['Groovy', 'Java'] + * assert person.activated + * </pre> + * <pre class="groovyTestCase"> + * //-------------------------------------------------------------------------- + * // use force attribute to force creation of constructor + * // even if we define our own constructors. + * import groovy.transform.TupleConstructor + * + * @TupleConstructor(force=true) + * class Person { + * String name + * List likes + * private boolean active = false + * + * Person(boolean active) { + * this.active = active + * } + * + * boolean isActivated() { active } + * } + * + * def person = new Person('mrhaki', ['Groovy', 'Java']) + * + * assert person.name == 'mrhaki' + * assert person.likes == ['Groovy', 'Java'] + * assert !person.activated + * + * person = new Person(true) + * + * assert person.activated + * </pre> + * <pre class="groovyTestCase"> + * //-------------------------------------------------------------------------- + * // include properties and fields from super class. + * import groovy.transform.TupleConstructor + * + * @TupleConstructor(includeFields=true) + * class Person { + * String name + * List likes + * private boolean active = false + * + * boolean isActivated() { active } + * } + * + * @TupleConstructor(callSuper=true, includeSuperProperties=true, includeSuperFields=true) + * class Student extends Person { + * List courses + * } + * + * def student = new Student('mrhaki', ['Groovy', 'Java'], true, ['IT']) + * + * assert student.name == 'mrhaki' + * assert student.likes == ['Groovy', 'Java'] + * assert student.activated + * assert student.courses == ['IT'] + * </pre> + * <p> + * Known Limitations: + * <ul> + * <li>This AST transform might become a no-op if you are defining your own constructors or + * combining with other AST transforms which create constructors (e.g. {@code @InheritConstructors}); + * the order in which the particular transforms are processed becomes important in that case. + * See the {@code force} attribute for further details about customizing this behavior.</li> + * <li>This AST transform normally uses default parameter values which creates multiple constructors under + * the covers. You should use with care if you are defining your own constructors or + * combining with other AST transforms which create constructors (e.g. {@code @InheritConstructors}); + * the order in which the particular transforms are processed becomes important in that case. + * See the {@code defaults} attribute for further details about customizing this behavior.</li> + * <li>Groovy's normal map-style naming conventions will not be available if the first property (or field) + * has type {@code LinkedHashMap} or if there is a single Map, AbstractMap or HashMap property (or field)</li> + * </ul> + * + * @since 1.8.0 + */ [email protected] +@Retention(RetentionPolicy.SOURCE) +@Target({ElementType.TYPE}) +@GroovyASTTransformationClass("org.codehaus.groovy.transform.TupleConstructorASTTransformation") +public @interface TupleConstructor { + /** + * List of field and/or property names to exclude from the constructor. + * Must not be used if 'includes' is used. For convenience, a String with comma separated names + * can be used in addition to an array (using Groovy's literal list notation) of String values. + */ + String[] excludes() default {}; + + /** + * List of field and/or property names to include within the constructor. + * Must not be used if 'excludes' is used. For convenience, a String with comma separated names + * can be used in addition to an array (using Groovy's literal list notation) of String values. + * The default value is a special marker value indicating that no includes are defined; + * all fields are included if includes remains undefined and excludes is explicitly or implicitly + * an empty list. + */ + String[] includes() default {Undefined.STRING}; + + /** + * Include fields in the constructor. + */ + boolean includeFields() default false; + + /** + * Include properties in the constructor. + */ + boolean includeProperties() default true; + + /** + * Include fields from super classes in the constructor. + */ + boolean includeSuperFields() default false; + + /** + * Include properties from super classes in the constructor. + */ + boolean includeSuperProperties() default false; + + /** + * Should super properties be called within a call to the parent constructor + * rather than set as properties. Typically used in combination with {@code includeSuperProperties}. + * Can't be true if using {@code pre} with a {@code super} first statement. + */ + boolean callSuper() default false; + + /** + * By default, this annotation becomes a no-op if you provide your own constructor. + * By setting {@code force=true} then the tuple constructor(s) will be added regardless of + * whether existing constructors exist. It is up to you to avoid creating duplicate constructors. + */ + boolean force() default false; + + /** + * Used to set whether default value processing is enabled (the default) or disabled. + * + * By default, every constructor parameter is given a default value. This value will + * be Java's default for primitive types (zero or false) and null for Objects, unless + * an initial value is given when declaring the property or field. A consequence of + * this design is that you can leave off parameters from the right if the default + * value will suffice. As far as Java interoperability is concerned, Groovy will + * create additional constructors under the covers representing the constructors + * with parameters left off, all the way from the constructor with all arguments + * to the no-arg constructor. + * + * However, when set to false, default values are not allowed for properties and fields. + * Only the constructor containing all arguments will be provided. + * In particular, a no-arg constructor won't be provided and since this is currently + * used by Groovy when using named-arguments, the named-argument style won't be available. + */ + boolean defaults() default true; + + /** + * By default, properties are set directly using their respective field. + * By setting {@code useSetters=true} then a writable property will be set using its setter. + * If turning on this flag we recommend that setters that might be called are + * made null-safe wrt the parameter. + */ + boolean useSetters() default false; + + /** + * Whether to include all fields and/or properties within the constructor, including those with names that are + * considered internal. + * + * @since 2.5.0 + */ + boolean allNames() default false; + + /** + * A Closure containing statements which will be prepended to the generated constructor. The first statement + * within the Closure may be {@code super(someArgs)} in which case the no-arg super constructor won't be called. + * + * @since 2.5.0 + */ + Class pre() default Undefined.CLASS.class; + + /** + * A Closure containing statements which will be appended to the end of the generated constructor. Useful for validation steps or tweaking the populated fields/properties. + * + * @since 2.5.0 + */ + Class post() default Undefined.CLASS.class; +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0ad8c07c/src/main/groovy/groovy/transform/TypeChecked.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/transform/TypeChecked.java b/src/main/groovy/groovy/transform/TypeChecked.java new file mode 100644 index 0000000..b902f3f --- /dev/null +++ b/src/main/groovy/groovy/transform/TypeChecked.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package groovy.transform; + +import org.codehaus.groovy.transform.GroovyASTTransformationClass; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This will let the Groovy compiler use compile time checks in the style of Java. + * @author <a href="mailto:[email protected]">Jochen "blackdrag" Theodorou</a> + */ [email protected] +@Retention(RetentionPolicy.SOURCE) +@Target({ ElementType.METHOD, ElementType.TYPE, + ElementType.CONSTRUCTOR +}) +@GroovyASTTransformationClass("org.codehaus.groovy.transform.StaticTypesTransformation") +public @interface TypeChecked { + TypeCheckingMode value() default TypeCheckingMode.PASS; + + /** + * The list of (classpath resources) paths to type checking DSL scripts, also known + * as type checking extensions. + * @return an array of paths to groovy scripts that must be on compile classpath + */ + String[] extensions() default {}; + + /** + * This annotation is added by @TypeChecked on methods which have type checking turned on. + * It is used to embed type information into binary, so that the type checker can use this information, + * if available, for precompiled classes. + */ + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface TypeCheckingInfo { + /** + * Returns the type checker information protocol number. This is used if the format of the + * string used in {@link #inferredType()} changes. + * @return the protocol version + */ + int version() default 0; + + /** + * An encoded type information. + * @return the inferred type + */ + String inferredType(); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/groovy/blob/0ad8c07c/src/main/groovy/groovy/transform/TypeCheckingMode.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/transform/TypeCheckingMode.java b/src/main/groovy/groovy/transform/TypeCheckingMode.java new file mode 100644 index 0000000..075bd71 --- /dev/null +++ b/src/main/groovy/groovy/transform/TypeCheckingMode.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package groovy.transform; + +/** + * This enumeration can be used whenever it is preferred to annotate a class as + * {@link TypeChecked} in general, but where only one or more methods are "dynamic". This allows the user + * to annotate the class itself then annotate only the methods which require exclusion. + * + * @author Cedric Champeau + */ +public enum TypeCheckingMode { + PASS, + SKIP +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0ad8c07c/src/main/groovy/groovy/transform/Undefined.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/transform/Undefined.java b/src/main/groovy/groovy/transform/Undefined.java new file mode 100644 index 0000000..35b360d --- /dev/null +++ b/src/main/groovy/groovy/transform/Undefined.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package groovy.transform; + +import org.codehaus.groovy.ast.ClassNode; + +/** + * Java doesn't allow you to have null as an attribute value. It wants you to indicate what you really + * mean by null, so that is what we do here - as ugly as it is. + */ +public final class Undefined { + private Undefined() {} + public static final String STRING = "<DummyUndefinedMarkerString-DoNotUse>"; + public static final class CLASS {} + public static final class EXCEPTION extends RuntimeException { + private static final long serialVersionUID = -3960500360386581172L; + } + public static boolean isUndefined(String other) { return STRING.equals(other); } + public static boolean isUndefined(ClassNode other) { return CLASS.class.getName().equals(other.getName()); } + public static boolean isUndefinedException(ClassNode other) { return EXCEPTION.class.getName().equals(other.getName()); } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0ad8c07c/src/main/groovy/groovy/transform/WithReadLock.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/transform/WithReadLock.java b/src/main/groovy/groovy/transform/WithReadLock.java new file mode 100644 index 0000000..475786a --- /dev/null +++ b/src/main/groovy/groovy/transform/WithReadLock.java @@ -0,0 +1,107 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package groovy.transform; + +import org.codehaus.groovy.transform.GroovyASTTransformationClass; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation is used in conjunction with {@link WithWriteLock} to support read and write synchronization on a method. + * <p> + * To use this annotation, declare {@code @WithReadLock} on your method. The method may be either an instance method or + * a static method. The resulting method will allow multiple threads to read the information at the same time. + * However, if some other method obtains a write lock, then this method will force callers to wait until the write is complete. + * <p> + * This annotation is a declarative wrapper around the JDK's <code>java.util.concurrent.locks.ReentrantReadWriteLock</code>. + * Objects containing this annotation will have a ReentrantReadWriteLock field named <code>$reentrantLock</code> added to the class, + * and method access is protected by the lock. If the method is static then the field is static and named <code>$REENTRANTLOCK</code>. + * <p> + * The annotation takes an optional parameter for the name of the field. This field must exist on the class and must be + * of type ReentrantReadWriteLock. + * <p> + * To understand how this annotation works, it is convenient to think in terms of the source code it replaces. The following + * is a typical usage of this annotation from Groovy: + * <pre> + * import groovy.transform.*; + * + * public class ResourceProvider { + * + * private final Map<String, String> data = new HashMap<String, String>(); + * + * {@code @WithReadLock} + * public String getResource(String key) throws Exception { + * return data.get(key); + * } + * + * {@code @WithWriteLock} + * public void refresh() throws Exception { + * //reload the resources into memory + * } + * } + * </pre> + * As part of the Groovy compiler, code resembling this is produced: + * <pre> + * import java.util.concurrent.locks.ReentrantReadWriteLock; + * import java.util.concurrent.locks.ReadWriteLock; + * + * public class ResourceProvider { + * + * private final ReadWriteLock $reentrantlock = new ReentrantReadWriteLock(); + * private final Map<String, String> data = new HashMap<String, String>(); + * + * public String getResource(String key) throws Exception { + * $reentrantlock.readLock().lock(); + * try { + * return data.get(key); + * } finally { + * $reentrantlock.readLock().unlock(); + * } + * } + * + * public void refresh() throws Exception { + * $reentrantlock.writeLock().lock(); + * try { + * //reload the resources into memory + * } finally { + * $reentrantlock.writeLock().unlock(); + * } + * } + * } + * </pre> + * + * @author Hamlet D'Arcy + * @since 1.8.0 + */ [email protected] +@Retention(RetentionPolicy.SOURCE) +@Target({ElementType.METHOD}) +@GroovyASTTransformationClass("org.codehaus.groovy.transform.ReadWriteLockASTTransformation") +public @interface WithReadLock { + /** + * @return if a user specified lock object with the given name should be used + * the lock object must exist. If the annotated method is static then the + * lock object must be static. If the annotated method is not static then + * the lock object must not be static. + */ + String value () default ""; +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0ad8c07c/src/main/groovy/groovy/transform/WithWriteLock.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/transform/WithWriteLock.java b/src/main/groovy/groovy/transform/WithWriteLock.java new file mode 100644 index 0000000..1eeb7f0 --- /dev/null +++ b/src/main/groovy/groovy/transform/WithWriteLock.java @@ -0,0 +1,107 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package groovy.transform; + +import org.codehaus.groovy.transform.GroovyASTTransformationClass; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation is used in conjunction with {@link WithReadLock} to support read and write synchronization on a method. + * <p> + * To use this annotation, declare {@code @WithWriteLock} on your method. The method may be either an instance method or + * a static method. The resulting method will allow only one thread access to the method at a time, and will wait to access + * the method until any other read locks have been released. + * <p> + * This annotation is a declarative wrapper around the JDK's <code>java.util.concurrent.locks.ReentrantReadWriteLock</code>. + * Objects containing this annotation will have a ReentrantReadWriteLock field named <code>$reentrantLock</code> added to the class, + * and method access is protected by the lock. If the method is static then the field is static and named <code>$REENTRANTLOCK</code>. + * <p> + * The annotation takes an optional parameter for the name of the field. This field must exist on the class and must be + * of type ReentrantReadWriteLock. + * <p> + * To understand how this annotation works, it is convenient to think in terms of the source code it replaces. The following + * is a typical usage of this annotation from Groovy: + * <pre> + * import groovy.transform.*; + * + * public class ResourceProvider { + * + * private final Map<String, String> data = new HashMap<String, String>(); + * + * {@code @WithReadLock} + * public String getResource(String key) throws Exception { + * return data.get(key); + * } + * + * {@code @WithWriteLock} + * public void refresh() throws Exception { + * //reload the resources into memory + * } + * } + * </pre> + * As part of the Groovy compiler, code resembling this is produced: + * <pre> + * import java.util.concurrent.locks.ReentrantReadWriteLock; + * import java.util.concurrent.locks.ReadWriteLock; + * + * public class ResourceProvider { + * + * private final ReadWriteLock $reentrantlock = new ReentrantReadWriteLock(); + * private final Map<String, String> data = new HashMap<String, String>(); + * + * public String getResource(String key) throws Exception { + * $reentrantlock.readLock().lock(); + * try { + * return data.get(key); + * } finally { + * $reentrantlock.readLock().unlock(); + * } + * } + * + * public void refresh() throws Exception { + * $reentrantlock.writeLock().lock(); + * try { + * //reload the resources into memory + * } finally { + * $reentrantlock.writeLock().unlock(); + * } + * } + * } + * </pre> + * + * @author Hamlet D'Arcy + * @since 1.8.0 + */ [email protected] +@Retention(RetentionPolicy.SOURCE) +@Target({ElementType.METHOD}) +@GroovyASTTransformationClass("org.codehaus.groovy.transform.ReadWriteLockASTTransformation") +public @interface WithWriteLock { + /** + * @return if a user specified lock object with the given name should be used + * the lock object must exist. If the annotated method is static then the + * lock object must be static. If the annotated method is not static then + * the lock object must not be static. + */ + String value () default ""; +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0ad8c07c/src/main/groovy/groovy/transform/builder/Builder.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/transform/builder/Builder.java b/src/main/groovy/groovy/transform/builder/Builder.java new file mode 100644 index 0000000..93b6090 --- /dev/null +++ b/src/main/groovy/groovy/transform/builder/Builder.java @@ -0,0 +1,160 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package groovy.transform.builder; + +import groovy.transform.Undefined; +import org.codehaus.groovy.transform.GroovyASTTransformationClass; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import static org.codehaus.groovy.transform.BuilderASTTransformation.BuilderStrategy; + +/** + * The {@code @Builder} AST transformation is used to help write classes that can be created using <em>fluent</em> api calls.<!-- --> + * The transform supports multiple building strategies to cover a range of cases and there are a number + * of configuration options to customize the building process. + * + * In addition, a number of annotation attributes let you customise the building process. Not all annotation attributes + * are supported by all strategies. See the individual strategy documentation for more details. + * If you're an AST hacker, you can also define your own strategy class. + * + * The following strategies are bundled with Groovy: + * <ul> + * <li>{@link SimpleStrategy} for creating chained setters</li> + * <li>{@link ExternalStrategy} where you annotate an explicit builder class while leaving some buildee class being built untouched</li> + * <li>{@link DefaultStrategy} which creates a nested helper class for instance creation</li> + * <li>{@link InitializerStrategy} which creates a nested helper class for instance creation which when used with {@code @CompileStatic} allows type-safe object creation</li> + * </ul> + * + * Note that Groovy provides other built-in mechanisms for easy creation of objects, e.g. the named-args constructor: + * <pre> + * new Person(firstName: "Robert", lastName: "Lewandowski", age: 21) + * </pre> + * or the with statement: + * <pre> + * new Person().with { + * firstName = "Robert" + * lastName = "Lewandowski" + * age = 21 + * } + * </pre> + * so you might not find value in using the builder transform at all. But if you need Java integration or in some cases improved type safety, the {@code @Builder} transform might prove very useful. + * + * @see groovy.transform.builder.SimpleStrategy + * @see groovy.transform.builder.ExternalStrategy + * @see groovy.transform.builder.DefaultStrategy + * @see groovy.transform.builder.InitializerStrategy + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.CONSTRUCTOR, ElementType.METHOD}) +@GroovyASTTransformationClass("org.codehaus.groovy.transform.BuilderASTTransformation") +public @interface Builder { + + /** + * A class for which builder methods should be created. It will be an error to leave + * this attribute with its default value for some strategies. + */ + Class forClass() default Undefined.CLASS.class; + + /** + * A class capturing the builder strategy + */ + Class<? extends BuilderStrategy> builderStrategy() default DefaultStrategy.class; + + /** + * The prefix to use when creating the setter methods. + * Default is determined by the strategy which might use "" or "set" but you can choose your own, e.g. "with". + * If non-empty the first letter of the property will be capitalized before being appended to the prefix. + */ + String prefix() default Undefined.STRING; + + /** + * For strategies which create a builder helper class, the class name to use for the helper class. + * Not used if using {@code forClass} since in such cases the builder class is explicitly supplied. + * Default is determined by the strategy, e.g. <em>TargetClass</em> + "Builder" or <em>TargetClass</em> + "Initializer". + */ + String builderClassName() default Undefined.STRING; + + /** + * For strategies which create a builder helper class that creates the instance, the method name to call to create the instance. + * Default is determined by the strategy, e.g. <em>build</em> or <em>create</em>. + */ + String buildMethodName() default Undefined.STRING; + + /** + * The method name to use for a builder factory method in the source class for easy access of the + * builder helper class for strategies which create such a helper class. + * Must not be used if using {@code forClass}. + * Default is determined by the strategy, e.g. <em>builder</em> or <em>createInitializer</em>. + */ + String builderMethodName() default Undefined.STRING; + + /** + * List of field and/or property names to exclude from generated builder methods. + * Must not be used if 'includes' is used. For convenience, a String with comma separated names + * can be used in addition to an array (using Groovy's literal list notation) of String values. + */ + String[] excludes() default {}; + + /** + * List of field and/or property names to include within the generated builder methods. + * Must not be used if 'excludes' is used. For convenience, a String with comma separated names + * can be used in addition to an array (using Groovy's literal list notation) of String values. + * The default value is a special marker value indicating that no includes are defined; all fields + * are included if includes remains undefined and excludes is explicitly or implicitly an empty list. + */ + String[] includes() default {Undefined.STRING}; + + /** + * By default, properties are set directly using their respective field. + * By setting {@code useSetters=true} then a writable property will be set using its setter. + * If turning on this flag we recommend that setters that might be called are + * made null-safe wrt the parameter. + */ + boolean useSetters() default false; + + /** + * Generate builder methods for properties from super classes. + */ + boolean includeSuperProperties() default false; + + /** + * Whether the generated builder should support all properties, including those with names that are considered internal. + * + * @since 2.5.0 + */ + boolean allNames() default false; + + /** + * Whether to include all properties (as per the JavaBean spec) in the generated builder. + * Groovy recognizes any field-like definitions with no explicit visibility as property definitions + * and always includes them in the {@code @Builder} generated classes. Groovy also treats any explicitly created getXxx() or isYyy() + * methods as property getters as per the JavaBean specification. Old versions of Groovy did not. + * So set this flag to false for the old behavior or if you want to explicitly exclude such properties. + * Currently only supported by DefaultStrategy and ExternalStrategy. + * + * @since 2.5.0 + */ + boolean allProperties() default true; +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0ad8c07c/src/main/groovy/groovy/transform/builder/DefaultStrategy.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/transform/builder/DefaultStrategy.java b/src/main/groovy/groovy/transform/builder/DefaultStrategy.java new file mode 100644 index 0000000..65d90e3 --- /dev/null +++ b/src/main/groovy/groovy/transform/builder/DefaultStrategy.java @@ -0,0 +1,293 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package groovy.transform.builder; + +import groovy.transform.Undefined; +import org.codehaus.groovy.ast.AnnotatedNode; +import org.codehaus.groovy.ast.AnnotationNode; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.ConstructorNode; +import org.codehaus.groovy.ast.FieldNode; +import org.codehaus.groovy.ast.InnerClassNode; +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.ast.Parameter; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.stmt.BlockStatement; +import org.codehaus.groovy.transform.BuilderASTTransformation; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static org.codehaus.groovy.ast.ClassHelper.OBJECT_TYPE; +import static org.codehaus.groovy.ast.tools.GeneralUtils.args; +import static org.codehaus.groovy.ast.tools.GeneralUtils.assignX; +import static org.codehaus.groovy.ast.tools.GeneralUtils.block; +import static org.codehaus.groovy.ast.tools.GeneralUtils.callX; +import static org.codehaus.groovy.ast.tools.GeneralUtils.constX; +import static org.codehaus.groovy.ast.tools.GeneralUtils.ctorX; +import static org.codehaus.groovy.ast.tools.GeneralUtils.declS; +import static org.codehaus.groovy.ast.tools.GeneralUtils.param; +import static org.codehaus.groovy.ast.tools.GeneralUtils.params; +import static org.codehaus.groovy.ast.tools.GeneralUtils.propX; +import static org.codehaus.groovy.ast.tools.GeneralUtils.returnS; +import static org.codehaus.groovy.ast.tools.GeneralUtils.stmt; +import static org.codehaus.groovy.ast.tools.GeneralUtils.varX; +import static org.codehaus.groovy.ast.tools.GenericsUtils.correctToGenericsSpecRecurse; +import static org.codehaus.groovy.ast.tools.GenericsUtils.createGenericsSpec; +import static org.codehaus.groovy.ast.tools.GenericsUtils.extractSuperClassGenerics; +import static org.codehaus.groovy.ast.tools.GenericsUtils.newClass; +import static org.codehaus.groovy.transform.AbstractASTTransformation.getMemberStringValue; +import static org.codehaus.groovy.transform.BuilderASTTransformation.NO_EXCEPTIONS; +import static org.codehaus.groovy.transform.BuilderASTTransformation.NO_PARAMS; +import static org.objectweb.asm.Opcodes.ACC_PRIVATE; +import static org.objectweb.asm.Opcodes.ACC_PUBLIC; +import static org.objectweb.asm.Opcodes.ACC_STATIC; + +/** + * This strategy is used with the {@link Builder} AST transform to create a builder helper class + * for the fluent creation of instances of a specified class. It can be used at the class, + * static method or constructor levels. + * + * You use it as follows: + * <pre class="groovyTestCase"> + * import groovy.transform.builder.* + * + * {@code @Builder} + * class Person { + * String firstName + * String lastName + * int age + * } + * def person = Person.builder().firstName("Robert").lastName("Lewandowski").age(21).build() + * assert person.firstName == "Robert" + * assert person.lastName == "Lewandowski" + * assert person.age == 21 + * </pre> + * The {@code prefix} annotation attribute can be used to create setters with a different naming convention. The default is the + * empty string but you could change that to "set" as follows: + * <pre class="groovyTestCase"> + * {@code @groovy.transform.builder.Builder}(prefix='set') + * class Person { + * String firstName + * String lastName + * int age + * } + * def p2 = Person.builder().setFirstName("Robert").setLastName("Lewandowski").setAge(21).build() + * </pre> + * or using a prefix of 'with' would result in usage like this: + * <pre> + * def p3 = Person.builder().withFirstName("Robert").withLastName("Lewandowski").withAge(21).build() + * </pre> + * + * You can also use the {@code @Builder} annotation in combination with this strategy on one or more constructor or + * static method instead of or in addition to using it at the class level. An example with a constructor follows: + * <pre class="groovyTestCase"> + * import groovy.transform.ToString + * import groovy.transform.builder.Builder + * + * {@code @ToString} + * class Person { + * String first, last + * int born + * + * {@code @Builder} + * Person(String roleName) { + * if (roleName == 'Jack Sparrow') { + * first = 'Johnny'; last = 'Depp'; born = 1963 + * } + * } + * } + * assert Person.builder().roleName("Jack Sparrow").build().toString() == 'Person(Johnny, Depp, 1963)' + * </pre> + * In this case, the parameter(s) for the constructor or static method become the properties available + * in the builder. For the case of a static method, the return type of the static method becomes the + * class of the instance being created. For static factory methods, this is normally the class containing the + * static method but in general it can be any class. + * + * Note: if using more than one {@code @Builder} annotation, which is only possible when using static method + * or constructor variants, it is up to you to ensure that any generated helper classes or builder methods + * have unique names. E.g. we can modify the previous example to have three builders. At least two of the builders + * in our case will need to set the 'builderClassName' and 'builderMethodName' annotation attributes to ensure + * we have unique names. This is shown in the following example: + * <pre class="groovyTestCase"> + * import groovy.transform.builder.* + * import groovy.transform.* + * + * {@code @ToString} + * {@code @Builder} + * class Person { + * String first, last + * int born + * + * Person(){} // required to retain no-arg constructor + * + * {@code @Builder}(builderClassName='MovieBuilder', builderMethodName='byRoleBuilder') + * Person(String roleName) { + * if (roleName == 'Jack Sparrow') { + * this.first = 'Johnny'; this.last = 'Depp'; this.born = 1963 + * } + * } + * + * {@code @Builder}(builderClassName='SplitBuilder', builderMethodName='splitBuilder') + * static Person split(String name, int year) { + * def parts = name.split(' ') + * new Person(first: parts[0], last: parts[1], born: year) + * } + * } + * + * assert Person.splitBuilder().name("Johnny Depp").year(1963).build().toString() == 'Person(Johnny, Depp, 1963)' + * assert Person.byRoleBuilder().roleName("Jack Sparrow").build().toString() == 'Person(Johnny, Depp, 1963)' + * assert Person.builder().first("Johnny").last('Depp').born(1963).build().toString() == 'Person(Johnny, Depp, 1963)' + * </pre> + * + * The 'forClass' annotation attribute for the {@code @Builder} transform isn't applicable for this strategy. + * The 'useSetters' annotation attribute for the {@code @Builder} transform is ignored by this strategy which always uses setters. + */ +public class DefaultStrategy extends BuilderASTTransformation.AbstractBuilderStrategy { + private static final Expression DEFAULT_INITIAL_VALUE = null; + private static final int PUBLIC_STATIC = ACC_PUBLIC | ACC_STATIC; + + public void build(BuilderASTTransformation transform, AnnotatedNode annotatedNode, AnnotationNode anno) { + if (unsupportedAttribute(transform, anno, "forClass")) return; + if (annotatedNode instanceof ClassNode) { + buildClass(transform, (ClassNode) annotatedNode, anno); + } else if (annotatedNode instanceof MethodNode) { + buildMethod(transform, (MethodNode) annotatedNode, anno); + } + } + + public void buildMethod(BuilderASTTransformation transform, MethodNode mNode, AnnotationNode anno) { + if (transform.getMemberValue(anno, "includes") != null || transform.getMemberValue(anno, "excludes") != null) { + transform.addError("Error during " + BuilderASTTransformation.MY_TYPE_NAME + + " processing: includes/excludes only allowed on classes", anno); + } + ClassNode buildee = mNode.getDeclaringClass(); + ClassNode builder = createBuilder(anno, buildee); + createBuilderFactoryMethod(anno, buildee, builder); + for (Parameter parameter : mNode.getParameters()) { + builder.addField(createFieldCopy(buildee, parameter)); + builder.addMethod(createBuilderMethodForProp(builder, new PropertyInfo(parameter.getName(), parameter.getType()), getPrefix(anno))); + } + builder.addMethod(createBuildMethodForMethod(anno, buildee, mNode, mNode.getParameters())); + } + + public void buildClass(BuilderASTTransformation transform, ClassNode buildee, AnnotationNode anno) { + List<String> excludes = new ArrayList<String>(); + List<String> includes = new ArrayList<String>(); + includes.add(Undefined.STRING); + if (!getIncludeExclude(transform, anno, buildee, excludes, includes)) return; + if (includes.size() == 1 && Undefined.isUndefined(includes.get(0))) includes = null; + ClassNode builder = createBuilder(anno, buildee); + createBuilderFactoryMethod(anno, buildee, builder); +// List<FieldNode> fields = getFields(transform, anno, buildee); + boolean allNames = transform.memberHasValue(anno, "allNames", true); + boolean allProperties = !transform.memberHasValue(anno, "allProperties", false); + List<PropertyInfo> props = getPropertyInfos(transform, anno, buildee, excludes, includes, allNames, allProperties); + for (PropertyInfo pi : props) { + ClassNode correctedType = getCorrectedType(buildee, pi.getType(), builder); + String fieldName = pi.getName(); + builder.addField(createFieldCopy(buildee, fieldName, correctedType)); + builder.addMethod(createBuilderMethodForProp(builder, new PropertyInfo(fieldName, correctedType), getPrefix(anno))); + } + builder.addMethod(createBuildMethod(anno, buildee, props)); + } + + private static ClassNode getCorrectedType(ClassNode buildee, ClassNode fieldType, ClassNode declaringClass) { + Map<String,ClassNode> genericsSpec = createGenericsSpec(declaringClass); + extractSuperClassGenerics(fieldType, buildee, genericsSpec); + return correctToGenericsSpecRecurse(genericsSpec, fieldType); + } + + private static void createBuilderFactoryMethod(AnnotationNode anno, ClassNode buildee, ClassNode builder) { + buildee.getModule().addClass(builder); + buildee.addMethod(createBuilderMethod(anno, builder)); + } + + private static ClassNode createBuilder(AnnotationNode anno, ClassNode buildee) { + return new InnerClassNode(buildee, getFullName(anno, buildee), PUBLIC_STATIC, OBJECT_TYPE); + } + + private static String getFullName(AnnotationNode anno, ClassNode buildee) { + String builderClassName = getMemberStringValue(anno, "builderClassName", buildee.getNameWithoutPackage() + "Builder"); + return buildee.getName() + "$" + builderClassName; + } + + private static String getPrefix(AnnotationNode anno) { + return getMemberStringValue(anno, "prefix", ""); + } + + private static MethodNode createBuildMethodForMethod(AnnotationNode anno, ClassNode buildee, MethodNode mNode, Parameter[] params) { + String buildMethodName = getMemberStringValue(anno, "buildMethodName", "build"); + final BlockStatement body = new BlockStatement(); + ClassNode returnType; + if (mNode instanceof ConstructorNode) { + returnType = newClass(buildee); + body.addStatement(returnS(ctorX(newClass(mNode.getDeclaringClass()), args(params)))); + } else { + body.addStatement(returnS(callX(newClass(mNode.getDeclaringClass()), mNode.getName(), args(params)))); + returnType = newClass(mNode.getReturnType()); + } + return new MethodNode(buildMethodName, ACC_PUBLIC, returnType, NO_PARAMS, NO_EXCEPTIONS, body); + } + + private static MethodNode createBuilderMethod(AnnotationNode anno, ClassNode builder) { + String builderMethodName = getMemberStringValue(anno, "builderMethodName", "builder"); + final BlockStatement body = new BlockStatement(); + body.addStatement(returnS(ctorX(builder))); + return new MethodNode(builderMethodName, PUBLIC_STATIC, builder, NO_PARAMS, NO_EXCEPTIONS, body); + } + + private static MethodNode createBuildMethod(AnnotationNode anno, ClassNode buildee, List<PropertyInfo> props) { + String buildMethodName = getMemberStringValue(anno, "buildMethodName", "build"); + final BlockStatement body = new BlockStatement(); + body.addStatement(returnS(initializeInstance(buildee, props, body))); + return new MethodNode(buildMethodName, ACC_PUBLIC, newClass(buildee), NO_PARAMS, NO_EXCEPTIONS, body); + } + + private MethodNode createBuilderMethodForProp(ClassNode builder, PropertyInfo pinfo, String prefix) { + ClassNode fieldType = pinfo.getType(); + String fieldName = pinfo.getName(); + String setterName = getSetterName(prefix, fieldName); + return new MethodNode(setterName, ACC_PUBLIC, newClass(builder), params(param(fieldType, fieldName)), NO_EXCEPTIONS, block( + stmt(assignX(propX(varX("this"), constX(fieldName)), varX(fieldName, fieldType))), + returnS(varX("this", builder)) + )); + } + + private static FieldNode createFieldCopy(ClassNode buildee, Parameter param) { + Map<String,ClassNode> genericsSpec = createGenericsSpec(buildee); + extractSuperClassGenerics(param.getType(), buildee, genericsSpec); + ClassNode correctedParamType = correctToGenericsSpecRecurse(genericsSpec, param.getType()); + return new FieldNode(param.getName(), ACC_PRIVATE, correctedParamType, buildee, param.getInitialExpression()); + } + + private static FieldNode createFieldCopy(ClassNode buildee, String fieldName, ClassNode fieldType) { + return new FieldNode(fieldName, ACC_PRIVATE, fieldType, buildee, DEFAULT_INITIAL_VALUE); + } + + private static Expression initializeInstance(ClassNode buildee, List<PropertyInfo> props, BlockStatement body) { + Expression instance = varX("_the" + buildee.getNameWithoutPackage(), buildee); + body.addStatement(declS(instance, ctorX(buildee))); + for (PropertyInfo pi : props) { + body.addStatement(stmt(assignX(propX(instance, pi.getName()), varX(pi.getName(), pi.getType())))); + } + return instance; + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0ad8c07c/src/main/groovy/groovy/transform/builder/ExternalStrategy.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/transform/builder/ExternalStrategy.java b/src/main/groovy/groovy/transform/builder/ExternalStrategy.java new file mode 100644 index 0000000..c482bef --- /dev/null +++ b/src/main/groovy/groovy/transform/builder/ExternalStrategy.java @@ -0,0 +1,158 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package groovy.transform.builder; + +import groovy.transform.Undefined; +import org.codehaus.groovy.ast.AnnotatedNode; +import org.codehaus.groovy.ast.AnnotationNode; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.FieldNode; +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.stmt.BlockStatement; +import org.codehaus.groovy.transform.BuilderASTTransformation; + +import java.util.ArrayList; +import java.util.List; + +import static org.codehaus.groovy.ast.tools.GeneralUtils.assignX; +import static org.codehaus.groovy.ast.tools.GeneralUtils.block; +import static org.codehaus.groovy.ast.tools.GeneralUtils.constX; +import static org.codehaus.groovy.ast.tools.GeneralUtils.ctorX; +import static org.codehaus.groovy.ast.tools.GeneralUtils.declS; +import static org.codehaus.groovy.ast.tools.GeneralUtils.param; +import static org.codehaus.groovy.ast.tools.GeneralUtils.params; +import static org.codehaus.groovy.ast.tools.GeneralUtils.propX; +import static org.codehaus.groovy.ast.tools.GeneralUtils.returnS; +import static org.codehaus.groovy.ast.tools.GeneralUtils.stmt; +import static org.codehaus.groovy.ast.tools.GeneralUtils.varX; +import static org.codehaus.groovy.ast.tools.GenericsUtils.newClass; +import static org.codehaus.groovy.transform.BuilderASTTransformation.MY_TYPE_NAME; +import static org.codehaus.groovy.transform.BuilderASTTransformation.NO_EXCEPTIONS; +import static org.codehaus.groovy.transform.BuilderASTTransformation.NO_PARAMS; +import static org.objectweb.asm.Opcodes.ACC_PRIVATE; +import static org.objectweb.asm.Opcodes.ACC_PUBLIC; + +/** + * This strategy is used with the {@link Builder} AST transform to populate a builder helper class + * so that it can be used for the fluent creation of instances of a specified class. The specified class is not modified in any way and may be a Java class. + * + * You use it by creating and annotating an explicit builder class which will be filled in by during + * annotation processing with the appropriate build method and setters. An example is shown here: + * <pre class="groovyTestCase"> + * import groovy.transform.builder.* + * + * class Person { + * String firstName + * String lastName + * } + * + * {@code @Builder}(builderStrategy=ExternalStrategy, forClass=Person) + * class PersonBuilder { } + * + * def person = new PersonBuilder().firstName("Robert").lastName("Lewandowski").build() + * assert person.firstName == "Robert" + * assert person.lastName == "Lewandowski" + * </pre> + * The {@code prefix} annotation attribute, which defaults to the empty String for this strategy, can be used to create setters with a different naming convention, e.g. with + * the {@code prefix} changed to 'set', you would use your setters as follows: + * <pre> + * def p1 = new PersonBuilder().setFirstName("Robert").setLastName("Lewandowski").setAge(21).build() + * </pre> + * or using a prefix of 'with': + * <pre> + * def p2 = new PersonBuilder().withFirstName("Robert").withLastName("Lewandowski").withAge(21).build() + * </pre> + * + * The properties to use can be filtered using either the 'includes' or 'excludes' annotation attributes for {@code @Builder}. + * The {@code @Builder} 'buildMethodName' annotation attribute can be used for configuring the build method's name, default "build". + * + * The {@code @Builder} 'builderMethodName' and 'builderClassName' annotation attributes aren't applicable for this strategy. + * The {@code @Builder} 'useSetters' annotation attribute is ignored by this strategy which always uses setters. + */ +public class ExternalStrategy extends BuilderASTTransformation.AbstractBuilderStrategy { + private static final Expression DEFAULT_INITIAL_VALUE = null; + + public void build(BuilderASTTransformation transform, AnnotatedNode annotatedNode, AnnotationNode anno) { + if (!(annotatedNode instanceof ClassNode)) { + transform.addError("Error during " + BuilderASTTransformation.MY_TYPE_NAME + " processing: building for " + + annotatedNode.getClass().getSimpleName() + " not supported by " + getClass().getSimpleName(), annotatedNode); + return; + } + ClassNode builder = (ClassNode) annotatedNode; + String prefix = transform.getMemberStringValue(anno, "prefix", ""); + ClassNode buildee = transform.getMemberClassValue(anno, "forClass"); + if (buildee == null) { + transform.addError("Error during " + MY_TYPE_NAME + " processing: 'forClass' must be specified for " + getClass().getName(), anno); + return; + } + List<String> excludes = new ArrayList<String>(); + List<String> includes = new ArrayList<String>(); + includes.add(Undefined.STRING); + if (!getIncludeExclude(transform, anno, buildee, excludes, includes)) return; + if (includes.size() == 1 && Undefined.isUndefined(includes.get(0))) includes = null; + if (unsupportedAttribute(transform, anno, "builderClassName")) return; + if (unsupportedAttribute(transform, anno, "builderMethodName")) return; + boolean allNames = transform.memberHasValue(anno, "allNames", true); + boolean allProperties = !transform.memberHasValue(anno, "allProperties", false); + List<PropertyInfo> props = getPropertyInfos(transform, anno, buildee, excludes, includes, allNames, allProperties); + if (includes != null) { + for (String name : includes) { + checkKnownProperty(transform, anno, name, props); + } + } + for (PropertyInfo prop : props) { + builder.addField(createFieldCopy(builder, prop)); + builder.addMethod(createBuilderMethodForField(builder, prop, prefix)); + } + builder.addMethod(createBuildMethod(transform, anno, buildee, props)); + } + + private static MethodNode createBuildMethod(BuilderASTTransformation transform, AnnotationNode anno, ClassNode sourceClass, List<PropertyInfo> fields) { + String buildMethodName = transform.getMemberStringValue(anno, "buildMethodName", "build"); + final BlockStatement body = new BlockStatement(); + Expression sourceClassInstance = initializeInstance(sourceClass, fields, body); + body.addStatement(returnS(sourceClassInstance)); + return new MethodNode(buildMethodName, ACC_PUBLIC, sourceClass, NO_PARAMS, NO_EXCEPTIONS, body); + } + + private MethodNode createBuilderMethodForField(ClassNode builderClass, PropertyInfo prop, String prefix) { + String propName = prop.getName().equals("class") ? "clazz" : prop.getName(); + String setterName = getSetterName(prefix, prop.getName()); + return new MethodNode(setterName, ACC_PUBLIC, newClass(builderClass), params(param(newClass(prop.getType()), propName)), NO_EXCEPTIONS, block( + stmt(assignX(propX(varX("this"), constX(propName)), varX(propName))), + returnS(varX("this", newClass(builderClass))) + )); + } + + private static FieldNode createFieldCopy(ClassNode builderClass, PropertyInfo prop) { + String propName = prop.getName(); + return new FieldNode(propName.equals("class") ? "clazz" : propName, ACC_PRIVATE, newClass(prop.getType()), builderClass, DEFAULT_INITIAL_VALUE); + } + + private static Expression initializeInstance(ClassNode sourceClass, List<PropertyInfo> props, BlockStatement body) { + Expression instance = varX("_the" + sourceClass.getNameWithoutPackage(), sourceClass); + body.addStatement(declS(instance, ctorX(sourceClass))); + for (PropertyInfo prop : props) { + body.addStatement(stmt(assignX(propX(instance, prop.getName()), varX(prop.getName().equals("class") ? "clazz" : prop.getName(), newClass(prop.getType()))))); + } + return instance; + } + +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0ad8c07c/src/main/groovy/groovy/transform/builder/InitializerStrategy.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/transform/builder/InitializerStrategy.java b/src/main/groovy/groovy/transform/builder/InitializerStrategy.java new file mode 100644 index 0000000..c678c38 --- /dev/null +++ b/src/main/groovy/groovy/transform/builder/InitializerStrategy.java @@ -0,0 +1,388 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package groovy.transform.builder; + +import groovy.transform.Undefined; +import org.codehaus.groovy.ast.AnnotatedNode; +import org.codehaus.groovy.ast.AnnotationNode; +import org.codehaus.groovy.ast.ClassHelper; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.ConstructorNode; +import org.codehaus.groovy.ast.FieldNode; +import org.codehaus.groovy.ast.GenericsType; +import org.codehaus.groovy.ast.InnerClassNode; +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.ast.Parameter; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.stmt.BlockStatement; +import org.codehaus.groovy.classgen.Verifier; +import org.codehaus.groovy.transform.AbstractASTTransformation; +import org.codehaus.groovy.transform.BuilderASTTransformation; +import org.codehaus.groovy.transform.ImmutableASTTransformation; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static org.codehaus.groovy.ast.ClassHelper.OBJECT_TYPE; +import static org.codehaus.groovy.ast.tools.GeneralUtils.args; +import static org.codehaus.groovy.ast.tools.GeneralUtils.assignX; +import static org.codehaus.groovy.ast.tools.GeneralUtils.block; +import static org.codehaus.groovy.ast.tools.GeneralUtils.callThisX; +import static org.codehaus.groovy.ast.tools.GeneralUtils.callX; +import static org.codehaus.groovy.ast.tools.GeneralUtils.constX; +import static org.codehaus.groovy.ast.tools.GeneralUtils.ctorSuperS; +import static org.codehaus.groovy.ast.tools.GeneralUtils.ctorThisS; +import static org.codehaus.groovy.ast.tools.GeneralUtils.ctorX; +import static org.codehaus.groovy.ast.tools.GeneralUtils.param; +import static org.codehaus.groovy.ast.tools.GeneralUtils.params; +import static org.codehaus.groovy.ast.tools.GeneralUtils.propX; +import static org.codehaus.groovy.ast.tools.GeneralUtils.returnS; +import static org.codehaus.groovy.ast.tools.GeneralUtils.stmt; +import static org.codehaus.groovy.ast.tools.GeneralUtils.varX; +import static org.codehaus.groovy.ast.tools.GenericsUtils.correctToGenericsSpecRecurse; +import static org.codehaus.groovy.ast.tools.GenericsUtils.createGenericsSpec; +import static org.codehaus.groovy.ast.tools.GenericsUtils.extractSuperClassGenerics; +import static org.codehaus.groovy.ast.tools.GenericsUtils.makeClassSafeWithGenerics; +import static org.codehaus.groovy.transform.AbstractASTTransformation.getMemberStringValue; +import static org.codehaus.groovy.transform.BuilderASTTransformation.NO_EXCEPTIONS; +import static org.codehaus.groovy.transform.BuilderASTTransformation.NO_PARAMS; +import static org.objectweb.asm.Opcodes.ACC_PRIVATE; +import static org.objectweb.asm.Opcodes.ACC_PUBLIC; +import static org.objectweb.asm.Opcodes.ACC_STATIC; +import static org.objectweb.asm.Opcodes.ACC_SYNTHETIC; + +/** + * This strategy is used with the {@link Builder} AST transform to create a builder helper class + * for the fluent and type-safe creation of instances of a specified class. + * + * It is modelled roughly on the design outlined here: + * http://michid.wordpress.com/2008/08/13/type-safe-builder-pattern-in-java/ + * + * You define classes which use the type-safe initializer pattern as follows: + * <pre> + * import groovy.transform.builder.* + * import groovy.transform.* + * + * {@code @ToString} + * {@code @Builder}(builderStrategy=InitializerStrategy) class Person { + * String firstName + * String lastName + * int age + * } + * </pre> + * While it isn't required to do so, the benefit of this builder strategy comes in conjunction with static type-checking or static compilation. Typical usage is as follows: + * <pre> + * {@code @CompileStatic} + * def main() { + * println new Person(Person.createInitializer().firstName("John").lastName("Smith").age(21)) + * } + * </pre> + * which prints: + * <pre> + * Person(John, Smith, 21) + * </pre> + * If you don't initialise some of the properties, your code won't compile, e.g. if the method body above was changed to this: + * <pre> + * println new Person(Person.createInitializer().firstName("John").lastName("Smith")) + * </pre> + * then the following compile-time error would result: + * <pre> + * [Static type checking] - Cannot find matching method Person#<init>(Person$PersonInitializer <groovy.transform.builder.InitializerStrategy$SET, groovy.transform.builder.InitializerStrategy$SET, groovy.transform.builder.InitializerStrategy$UNSET>). Please check if the declared type is right and if the method exists. + * </pre> + * The message is a little cryptic, but it is basically the static compiler telling us that the third parameter, {@code age} in our case, is unset. + * + * You can also add this annotation to your predefined constructors. These will be made private and an initializer will be set up + * to call your constructor. Any parameters to your constructor become the properties expected by the initializer. + * If you use such a builder on a constructor as well as on the class or on more than one constructor, then it is up to you + * to define unique values for 'builderClassName' and 'builderMethodName' for each annotation. + */ +public class InitializerStrategy extends BuilderASTTransformation.AbstractBuilderStrategy { + + /** + * Internal phantom type used by the {@code InitializerStrategy} to indicate that a property has been set. It is used in conjunction with the generated parameterized type helper class. + */ + public abstract static class SET { + } + + /** + * Internal phantom type used by the {@code InitializerStrategy} to indicate that a property remains unset. It is used in conjunction with the generated parameterized type helper class. + */ + public abstract static class UNSET { + } + + private static final int PUBLIC_STATIC = ACC_PUBLIC | ACC_STATIC; + private static final Expression DEFAULT_INITIAL_VALUE = null; + + public void build(BuilderASTTransformation transform, AnnotatedNode annotatedNode, AnnotationNode anno) { + if (unsupportedAttribute(transform, anno, "forClass")) return; + if (unsupportedAttribute(transform, anno, "allProperties")) return; + boolean useSetters = transform.memberHasValue(anno, "useSetters", true); + boolean allNames = transform.memberHasValue(anno, "allNames", true); + if (annotatedNode instanceof ClassNode) { + createBuilderForAnnotatedClass(transform, (ClassNode) annotatedNode, anno, useSetters, allNames); + } else if (annotatedNode instanceof MethodNode) { + createBuilderForAnnotatedMethod(transform, (MethodNode) annotatedNode, anno, useSetters); + } + } + + private void createBuilderForAnnotatedClass(BuilderASTTransformation transform, ClassNode buildee, AnnotationNode anno, boolean useSetters, boolean allNames) { + List<String> excludes = new ArrayList<String>(); + List<String> includes = new ArrayList<String>(); + includes.add(Undefined.STRING); + if (!getIncludeExclude(transform, anno, buildee, excludes, includes)) return; + if (includes.size() == 1 && Undefined.isUndefined(includes.get(0))) includes = null; + List<FieldNode> fields = getFields(transform, anno, buildee); + List<FieldNode> filteredFields = filterFields(fields, includes, excludes, allNames); + if (filteredFields.isEmpty()) { + transform.addError("Error during " + BuilderASTTransformation.MY_TYPE_NAME + + " processing: at least one property is required for this strategy", anno); + } + ClassNode builder = createInnerHelperClass(buildee, getBuilderClassName(buildee, anno), filteredFields.size()); + addFields(buildee, filteredFields, builder); + + buildCommon(buildee, anno, filteredFields, builder); + createBuildeeConstructors(transform, buildee, builder, filteredFields, true, useSetters); + } + + private void createBuilderForAnnotatedMethod(BuilderASTTransformation transform, MethodNode mNode, AnnotationNode anno, boolean useSetters) { + if (transform.getMemberValue(anno, "includes") != null || transform.getMemberValue(anno, "excludes") != null) { + transform.addError("Error during " + BuilderASTTransformation.MY_TYPE_NAME + + " processing: includes/excludes only allowed on classes", anno); + } + if (mNode instanceof ConstructorNode) { + mNode.setModifiers(ACC_PRIVATE | ACC_SYNTHETIC); + } else { + if ((mNode.getModifiers() & ACC_STATIC) == 0) { + transform.addError("Error during " + BuilderASTTransformation.MY_TYPE_NAME + + " processing: method builders only allowed on static methods", anno); + } + mNode.setModifiers(ACC_PRIVATE | ACC_SYNTHETIC | ACC_STATIC); + } + ClassNode buildee = mNode.getDeclaringClass(); + Parameter[] parameters = mNode.getParameters(); + if (parameters.length == 0) { + transform.addError("Error during " + BuilderASTTransformation.MY_TYPE_NAME + + " processing: at least one parameter is required for this strategy", anno); + } + ClassNode builder = createInnerHelperClass(buildee, getBuilderClassName(buildee, anno), parameters.length); + List<FieldNode> convertedFields = convertParamsToFields(builder, parameters); + + buildCommon(buildee, anno, convertedFields, builder); + if (mNode instanceof ConstructorNode) { + createBuildeeConstructors(transform, buildee, builder, convertedFields, false, useSetters); + } else { + createBuildeeMethods(buildee, mNode, builder, convertedFields); + } + } + + private static String getBuilderClassName(ClassNode buildee, AnnotationNode anno) { + return getMemberStringValue(anno, "builderClassName", buildee.getNameWithoutPackage() + "Initializer"); + } + + private static void addFields(ClassNode buildee, List<FieldNode> filteredFields, ClassNode builder) { + for (FieldNode filteredField : filteredFields) { + builder.addField(createFieldCopy(buildee, filteredField)); + } + } + + private void buildCommon(ClassNode buildee, AnnotationNode anno, List<FieldNode> fieldNodes, ClassNode builder) { + String prefix = getMemberStringValue(anno, "prefix", ""); + String buildMethodName = getMemberStringValue(anno, "buildMethodName", "create"); + createBuilderConstructors(builder, buildee, fieldNodes); + buildee.getModule().addClass(builder); + String builderMethodName = getMemberStringValue(anno, "builderMethodName", "createInitializer"); + buildee.addMethod(createBuilderMethod(buildMethodName, builder, fieldNodes.size(), builderMethodName)); + for (int i = 0; i < fieldNodes.size(); i++) { + builder.addMethod(createBuilderMethodForField(builder, fieldNodes, prefix, i)); + } + builder.addMethod(createBuildMethod(builder, buildMethodName, fieldNodes)); + } + + private static List<FieldNode> convertParamsToFields(ClassNode builder, Parameter[] parameters) { + List<FieldNode> fieldNodes = new ArrayList<FieldNode>(); + for(Parameter parameter: parameters) { + Map<String,ClassNode> genericsSpec = createGenericsSpec(builder); + ClassNode correctedType = correctToGenericsSpecRecurse(genericsSpec, parameter.getType()); + FieldNode fieldNode = new FieldNode(parameter.getName(), parameter.getModifiers(), correctedType, builder, DEFAULT_INITIAL_VALUE); + fieldNodes.add(fieldNode); + builder.addField(fieldNode); + } + return fieldNodes; + } + + private static ClassNode createInnerHelperClass(ClassNode buildee, String builderClassName, int fieldsSize) { + final String fullName = buildee.getName() + "$" + builderClassName; + ClassNode builder = new InnerClassNode(buildee, fullName, PUBLIC_STATIC, OBJECT_TYPE); + GenericsType[] gtypes = new GenericsType[fieldsSize]; + for (int i = 0; i < gtypes.length; i++) { + gtypes[i] = makePlaceholder(i); + } + builder.setGenericsTypes(gtypes); + return builder; + } + + private static MethodNode createBuilderMethod(String buildMethodName, ClassNode builder, int numFields, String builderMethodName) { + final BlockStatement body = new BlockStatement(); + body.addStatement(returnS(callX(builder, buildMethodName))); + ClassNode returnType = makeClassSafeWithGenerics(builder, unsetGenTypes(numFields)); + return new MethodNode(builderMethodName, PUBLIC_STATIC, returnType, NO_PARAMS, NO_EXCEPTIONS, body); + } + + private static GenericsType[] unsetGenTypes(int numFields) { + GenericsType[] gtypes = new GenericsType[numFields]; + for (int i = 0; i < gtypes.length; i++) { + gtypes[i] = new GenericsType(ClassHelper.make(UNSET.class)); + } + return gtypes; + } + + private static GenericsType[] setGenTypes(int numFields) { + GenericsType[] gtypes = new GenericsType[numFields]; + for (int i = 0; i < gtypes.length; i++) { + gtypes[i] = new GenericsType(ClassHelper.make(SET.class)); + } + return gtypes; + } + + private static void createBuilderConstructors(ClassNode builder, ClassNode buildee, List<FieldNode> fields) { + builder.addConstructor(ACC_PRIVATE, NO_PARAMS, NO_EXCEPTIONS, block(ctorSuperS())); + final BlockStatement body = new BlockStatement(); + body.addStatement(ctorSuperS()); + initializeFields(fields, body, false); + builder.addConstructor(ACC_PRIVATE, getParams(fields, buildee), NO_EXCEPTIONS, body); + } + + private static void createBuildeeConstructors(BuilderASTTransformation transform, ClassNode buildee, ClassNode builder, List<FieldNode> fields, boolean needsConstructor, boolean useSetters) { + ConstructorNode initializer = createInitializerConstructor(buildee, builder, fields); + if (transform.hasAnnotation(buildee, ImmutableASTTransformation.MY_TYPE)) { + initializer.putNodeMetaData(ImmutableASTTransformation.IMMUTABLE_SAFE_FLAG, Boolean.TRUE); + } else if (needsConstructor) { + final BlockStatement body = new BlockStatement(); + body.addStatement(ctorSuperS()); + initializeFields(fields, body, useSetters); + buildee.addConstructor(ACC_PRIVATE | ACC_SYNTHETIC, getParams(fields, buildee), NO_EXCEPTIONS, body); + } + } + + private static void createBuildeeMethods(ClassNode buildee, MethodNode mNode, ClassNode builder, List<FieldNode> fields) { + ClassNode paramType = makeClassSafeWithGenerics(builder, setGenTypes(fields.size())); + List<Expression> argsList = new ArrayList<Expression>(); + Parameter initParam = param(paramType, "initializer"); + for (FieldNode fieldNode : fields) { + argsList.add(propX(varX(initParam), fieldNode.getName())); + } + String newName = "$" + mNode.getName(); // can't have private and public methods of the same name, so rename original + buildee.addMethod(mNode.getName(), PUBLIC_STATIC, mNode.getReturnType(), params(param(paramType, "initializer")), NO_EXCEPTIONS, + block(stmt(callX(buildee, newName, args(argsList))))); + renameMethod(buildee, mNode, newName); + } + + // no rename so delete and add + private static void renameMethod(ClassNode buildee, MethodNode mNode, String newName) { + buildee.addMethod(newName, mNode.getModifiers(), mNode.getReturnType(), mNode.getParameters(), mNode.getExceptions(), mNode.getCode()); + buildee.removeMethod(mNode); + } + + private static Parameter[] getParams(List<FieldNode> fields, ClassNode cNode) { + Parameter[] parameters = new Parameter[fields.size()]; + for (int i = 0; i < parameters.length; i++) { + FieldNode fNode = fields.get(i); + Map<String,ClassNode> genericsSpec = createGenericsSpec(fNode.getDeclaringClass()); + extractSuperClassGenerics(fNode.getType(), cNode, genericsSpec); + ClassNode correctedType = correctToGenericsSpecRecurse(genericsSpec, fNode.getType()); + parameters[i] = new Parameter(correctedType, fNode.getName()); + } + return parameters; + } + + private static ConstructorNode createInitializerConstructor(ClassNode buildee, ClassNode builder, List<FieldNode> fields) { + ClassNode paramType = makeClassSafeWithGenerics(builder, setGenTypes(fields.size())); + List<Expression> argsList = new ArrayList<Expression>(); + Parameter initParam = param(paramType, "initializer"); + for (FieldNode fieldNode : fields) { + argsList.add(propX(varX(initParam), fieldNode.getName())); + } + return buildee.addConstructor(ACC_PUBLIC, params(param(paramType, "initializer")), NO_EXCEPTIONS, block(ctorThisS(args(argsList)))); + } + + private static MethodNode createBuildMethod(ClassNode builder, String buildMethodName, List<FieldNode> fields) { + ClassNode returnType = makeClassSafeWithGenerics(builder, unsetGenTypes(fields.size())); + return new MethodNode(buildMethodName, PUBLIC_STATIC, returnType, NO_PARAMS, NO_EXCEPTIONS, block(returnS(ctorX(returnType)))); + } + + private MethodNode createBuilderMethodForField(ClassNode builder, List<FieldNode> fields, String prefix, int fieldPos) { + String fieldName = fields.get(fieldPos).getName(); + String setterName = getSetterName(prefix, fieldName); + GenericsType[] gtypes = new GenericsType[fields.size()]; + List<Expression> argList = new ArrayList<Expression>(); + for (int i = 0; i < fields.size(); i++) { + gtypes[i] = i == fieldPos ? new GenericsType(ClassHelper.make(SET.class)) : makePlaceholder(i); + argList.add(i == fieldPos ? propX(varX("this"), constX(fieldName)) : varX(fields.get(i).getName())); + } + ClassNode returnType = makeClassSafeWithGenerics(builder, gtypes); + FieldNode fNode = fields.get(fieldPos); + Map<String,ClassNode> genericsSpec = createGenericsSpec(fNode.getDeclaringClass()); + extractSuperClassGenerics(fNode.getType(), builder, genericsSpec); + ClassNode correctedType = correctToGenericsSpecRecurse(genericsSpec, fNode.getType()); + return new MethodNode(setterName, ACC_PUBLIC, returnType, params(param(correctedType, fieldName)), NO_EXCEPTIONS, block( + stmt(assignX(propX(varX("this"), constX(fieldName)), varX(fieldName, correctedType))), + returnS(ctorX(returnType, args(argList))) + )); + } + + private static GenericsType makePlaceholder(int i) { + ClassNode type = ClassHelper.makeWithoutCaching("T" + i); + type.setRedirect(OBJECT_TYPE); + type.setGenericsPlaceHolder(true); + return new GenericsType(type); + } + + private static FieldNode createFieldCopy(ClassNode buildee, FieldNode fNode) { + Map<String,ClassNode> genericsSpec = createGenericsSpec(fNode.getDeclaringClass()); + extractSuperClassGenerics(fNode.getType(), buildee, genericsSpec); + ClassNode correctedType = correctToGenericsSpecRecurse(genericsSpec, fNode.getType()); + return new FieldNode(fNode.getName(), fNode.getModifiers(), correctedType, buildee, DEFAULT_INITIAL_VALUE); + } + + private static List<FieldNode> filterFields(List<FieldNode> fieldNodes, List<String> includes, List<String> excludes, boolean allNames) { + List<FieldNode> fields = new ArrayList<FieldNode>(); + for (FieldNode fNode : fieldNodes) { + if (AbstractASTTransformation.shouldSkipUndefinedAware(fNode.getName(), excludes, includes, allNames)) continue; + fields.add(fNode); + } + return fields; + } + + private static void initializeFields(List<FieldNode> fields, BlockStatement body, boolean useSetters) { + for (FieldNode field : fields) { + String name = field.getName(); + body.addStatement( + stmt(useSetters && !field.isFinal() + ? callThisX(getSetterName(name), varX(param(field.getType(), name))) + : assignX(propX(varX("this"), field.getName()), varX(param(field.getType(), name))) + ) + ); + } + } + + private static String getSetterName(String name) { + return "set" + Verifier.capitalize(name); + } +}
