├── .travis.yml
├── src
└── main
│ ├── resources
│ └── META-INF
│ │ └── services
│ │ └── javax.annotation.processing.Processor
│ └── java
│ └── fr
│ └── lteconsulting
│ ├── Parameter.java
│ ├── Mandatory.java
│ ├── UseBuilderGenerator.java
│ └── UseBuilderGeneratorProcessor.java
├── .gitignore
├── sample
├── src
│ └── main
│ │ └── java
│ │ └── fr
│ │ └── lteconsulting
│ │ ├── Example.java
│ │ ├── ApiClass.java
│ │ ├── Demonstration.java
│ │ ├── PeteBurne.java
│ │ ├── ComplexClass.java
│ │ └── SuperRelou.java
└── pom.xml
├── pom.xml
└── README.md
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: java
2 | jdk:
3 | - oraclejdk8
4 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/services/javax.annotation.processing.Processor:
--------------------------------------------------------------------------------
1 | fr.lteconsulting.UseBuilderGeneratorProcessor
--------------------------------------------------------------------------------
/src/main/java/fr/lteconsulting/Parameter.java:
--------------------------------------------------------------------------------
1 | package fr.lteconsulting;
2 |
3 | public @interface Parameter
4 | {
5 | boolean mandatory() default false;
6 | String name() default "";
7 | }
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .classpath
2 | .factorypath
3 | .project
4 | .settings
5 | eclipsebin
6 |
7 | bin
8 | gen
9 | build
10 | out
11 | lib
12 |
13 | target
14 | /target
15 | pom.xml.*
16 | release.properties
17 | log
18 | settings.xml
19 | *.versionsBackup
20 |
21 | .idea
22 | *.iml
23 | *.iws
24 | classes
25 |
26 | obj
27 |
28 | .DS_Store
29 | *.DS_Store
30 |
--------------------------------------------------------------------------------
/src/main/java/fr/lteconsulting/Mandatory.java:
--------------------------------------------------------------------------------
1 | package fr.lteconsulting;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | /**
9 | * Specifies that this constructor parameter is mandatory
10 | */
11 | @Retention(RetentionPolicy.SOURCE)
12 | @Target(ElementType.PARAMETER)
13 | public @interface Mandatory
14 | {
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/fr/lteconsulting/UseBuilderGenerator.java:
--------------------------------------------------------------------------------
1 | package fr.lteconsulting;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | /**
9 | * Ask for automatic generation of a builder for a constructor
10 | */
11 | @Retention( RetentionPolicy.SOURCE )
12 | @Target( { ElementType.METHOD, ElementType.CONSTRUCTOR } )
13 | public @interface UseBuilderGenerator
14 | {
15 | String builderName() default "";
16 |
17 | String builderPackage() default "";
18 |
19 | String finalMethodName() default "";
20 | }
21 |
--------------------------------------------------------------------------------
/sample/src/main/java/fr/lteconsulting/Example.java:
--------------------------------------------------------------------------------
1 | package fr.lteconsulting;
2 |
3 | import fr.lteconsulting.Mandatory;
4 | import fr.lteconsulting.UseBuilderGenerator;
5 |
6 | public class Example
7 | {
8 | private final String a;
9 |
10 | private final String b;
11 |
12 | private String c;
13 |
14 | private String d;
15 |
16 | @UseBuilderGenerator
17 | public Example(@Mandatory String a, @Mandatory String b, String c, String d)
18 | {
19 | super();
20 | this.a = a;
21 | this.b = b;
22 | this.c = c;
23 | this.d = d;
24 | }
25 |
26 | @Override
27 | public String toString()
28 | {
29 | return "Example [a=" + a + ", b=" + b + ", c=" + c + ", d=" + d + "]";
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/sample/src/main/java/fr/lteconsulting/ApiClass.java:
--------------------------------------------------------------------------------
1 | package fr.lteconsulting;
2 |
3 | /**
4 | * Construction of an expression tree
5 | */
6 | public class ApiClass
7 | {
8 | static abstract class Node
9 | {
10 | }
11 |
12 | public static class Operation extends Node
13 | {
14 | Node left;
15 | Node right;
16 | String operation;
17 |
18 | @UseBuilderGenerator
19 | public Operation( @Mandatory Node left, @Mandatory String operation, @Mandatory Node right )
20 | {
21 | this.left = left;
22 | this.right = right;
23 | this.operation = operation;
24 | }
25 |
26 | @Override
27 | public String toString()
28 | {
29 | return "(" + left + " " + operation + " " + right + ")";
30 | }
31 | }
32 |
33 | public static class Value extends Node
34 | {
35 | int value;
36 |
37 | @UseBuilderGenerator
38 | public Value( @Mandatory int value )
39 | {
40 | this.value = value;
41 | }
42 |
43 | @Override
44 | public String toString()
45 | {
46 | return String.valueOf( value );
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/sample/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | fr.lteconsulting
7 | builder-generator-sample
8 | 1.0-SNAPSHOT
9 |
10 | UTF-8
11 | 1.8
12 | 1.8
13 |
14 |
15 |
16 | fr.lteconsulting
17 | builder-generator
18 | 1.0-SNAPSHOT
19 | provided
20 |
21 |
22 |
23 |
24 |
25 | org.apache.maven.plugins
26 | maven-compiler-plugin
27 | 3.3
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/sample/src/main/java/fr/lteconsulting/Demonstration.java:
--------------------------------------------------------------------------------
1 | package fr.lteconsulting;
2 |
3 | import fr.lteconsulting.ApiClass.Operation;
4 | import fr.lteconsulting.builders.ComplexClassBuilder;
5 |
6 | public class Demonstration
7 | {
8 | public static void main( String[] args )
9 | {
10 | ComplexClass instance = ComplexClassBuilder
11 | .withA( "this one is mandatory" )
12 | .bonjour( "monsieur" )
13 | .withC( "this one too" )
14 | .withE( "all this is generated !" )
15 | .withD( "an optional parameter" )
16 | .build();
17 |
18 | ComplexClassBuilder.prepare().withA( null ).bonjour( null ).withC( null ).withE( null ).build();
19 |
20 | SomeMethodeCaller.prepare().withImportantNote( 52 ).call();
21 |
22 | GetValeurCaller.prepare( instance ).withX( 12 ).withToto( 'a' ).call();
23 |
24 | // TODO : maybe since withValue() is a terminal method here, we may directly return the built instance instead of calling build()
25 |
26 | Operation op = OperationBuilder
27 | .withLeft( ValueBuilder.withValue( 5 ).build() )
28 | .withOperation( "+" )
29 | .withRight( ValueBuilder.withValue( 5 ).build() )
30 | .build();
31 | System.out.println( op.toString() );
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/sample/src/main/java/fr/lteconsulting/PeteBurne.java:
--------------------------------------------------------------------------------
1 | package fr.lteconsulting;
2 |
3 | public class PeteBurne
4 | {
5 | private String a;
6 |
7 | private String b;
8 |
9 | private String c;
10 |
11 | private String d;
12 |
13 | private String e;
14 |
15 | private String f;
16 |
17 | @UseBuilderGenerator(finalMethodName = "call")
18 | public PeteBurne(@Mandatory String a, @Mandatory String b, String c, String d, String e, String f)
19 | {
20 | this.a = a;
21 | this.b = b;
22 | this.c = c;
23 | this.d = d;
24 | this.e = e;
25 | this.f = f;
26 | }
27 |
28 | public String getA()
29 | {
30 | return a;
31 | }
32 |
33 | public void setA(String a)
34 | {
35 | this.a = a;
36 | }
37 |
38 | public String getB()
39 | {
40 | return b;
41 | }
42 |
43 | public void setB(String b)
44 | {
45 | this.b = b;
46 | }
47 |
48 | public String getC()
49 | {
50 | return c;
51 | }
52 |
53 | public void setC(String c)
54 | {
55 | this.c = c;
56 | }
57 |
58 | public String getD()
59 | {
60 | return d;
61 | }
62 |
63 | public void setD(String d)
64 | {
65 | this.d = d;
66 | }
67 |
68 | public String getE()
69 | {
70 | return e;
71 | }
72 |
73 | public void setE(String e)
74 | {
75 | this.e = e;
76 | }
77 |
78 | public String getF()
79 | {
80 | return f;
81 | }
82 |
83 | public void setF(String f)
84 | {
85 | this.f = f;
86 | }
87 |
88 | }
89 |
--------------------------------------------------------------------------------
/sample/src/main/java/fr/lteconsulting/ComplexClass.java:
--------------------------------------------------------------------------------
1 | package fr.lteconsulting;
2 |
3 | public class ComplexClass
4 | {
5 | private String a;
6 |
7 | private String b;
8 |
9 | private String c;
10 |
11 | private String d;
12 |
13 | private String e;
14 |
15 | @UseBuilderGenerator(builderPackage = "fr.lteconsulting.builders")
16 | public ComplexClass(@Mandatory String a, @Parameter(mandatory = true, name = "bonjour") String b,
17 | @Mandatory String c, String d, @Mandatory String e)
18 | {
19 | this.a = a;
20 | this.b = b;
21 | this.c = c;
22 | this.d = d;
23 | this.e = e;
24 | }
25 |
26 | @UseBuilderGenerator
27 | public static String someMethode(@Parameter(mandatory = true, name = "withImportantNote") int a, int b)
28 | {
29 | return null;
30 | }
31 |
32 | @UseBuilderGenerator
33 | public Integer getValeur(int p1, int c2, @Mandatory int x, int y, int z, char toto)
34 | {
35 | return 5;
36 | }
37 |
38 | public String getA()
39 | {
40 | return a;
41 | }
42 |
43 | public void setA(String a)
44 | {
45 | this.a = a;
46 | }
47 |
48 | public String getB()
49 | {
50 | return b;
51 | }
52 |
53 | public void setB(String b)
54 | {
55 | this.b = b;
56 | }
57 |
58 | public String getC()
59 | {
60 | return c;
61 | }
62 |
63 | public void setC(String c)
64 | {
65 | this.c = c;
66 | }
67 |
68 | public String getD()
69 | {
70 | return d;
71 | }
72 |
73 | public void setD(String d)
74 | {
75 | this.d = d;
76 | }
77 |
78 | public String getE()
79 | {
80 | return e;
81 | }
82 |
83 | public void setE(String e)
84 | {
85 | this.e = e;
86 | }
87 |
88 | }
89 |
--------------------------------------------------------------------------------
/sample/src/main/java/fr/lteconsulting/SuperRelou.java:
--------------------------------------------------------------------------------
1 | package fr.lteconsulting;
2 |
3 | import fr.lteconsulting.Mandatory;
4 | import fr.lteconsulting.UseBuilderGenerator;
5 |
6 | public class SuperRelou
7 | {
8 | private String nom;
9 |
10 | private String prenom;
11 |
12 | private String adresse;
13 |
14 | private String nationalite;
15 |
16 | private String cursus;
17 |
18 | private String description;
19 |
20 | @UseBuilderGenerator
21 | public SuperRelou(@Mandatory String nom, @Mandatory String prenom, String adresse, String nationalite,
22 | String cursus, String description)
23 | {
24 | this.nom = nom;
25 | this.prenom = prenom;
26 | this.adresse = adresse;
27 | this.nationalite = nationalite;
28 | this.cursus = cursus;
29 | this.description = description;
30 | }
31 |
32 | public String getNom()
33 | {
34 | return nom;
35 | }
36 |
37 | public void setNom(String nom)
38 | {
39 | this.nom = nom;
40 | }
41 |
42 | public String getPrenom()
43 | {
44 | return prenom;
45 | }
46 |
47 | public void setPrenom(String prenom)
48 | {
49 | this.prenom = prenom;
50 | }
51 |
52 | public String getAdresse()
53 | {
54 | return adresse;
55 | }
56 |
57 | public void setAdresse(String adresse)
58 | {
59 | this.adresse = adresse;
60 | }
61 |
62 | public String getNationalite()
63 | {
64 | return nationalite;
65 | }
66 |
67 | public void setNationalite(String nationalite)
68 | {
69 | this.nationalite = nationalite;
70 | }
71 |
72 | public String getCursus()
73 | {
74 | return cursus;
75 | }
76 |
77 | public void setCursus(String cursus)
78 | {
79 | this.cursus = cursus;
80 | }
81 |
82 | public String getDescription()
83 | {
84 | return description;
85 | }
86 |
87 | public void setDescription(String description)
88 | {
89 | this.description = description;
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 |
7 | fr.lteconsulting
8 | hexa.tools
9 | 1.3
10 |
11 |
12 | builder-generator
13 | 1.1-SNAPSHOT
14 |
15 |
16 | UTF-8
17 | 1.8
18 | 1.8
19 |
20 |
21 | http://www.lteconsulting.fr
22 |
23 |
24 | scm:git:git@github.com:ltearno/builder-generator
25 | scm:git:git@github.com:ltearno/builder-generator
26 | git@github.com:ltearno/builder-generator
27 |
28 |
29 |
30 |
31 | junit
32 | junit
33 | test
34 |
35 |
36 |
37 |
38 |
39 |
40 | org.apache.maven.plugins
41 | maven-compiler-plugin
42 |
43 | -proc:none
44 |
45 |
46 |
47 | org.apache.maven.plugins
48 | maven-source-plugin
49 | 2.4
50 |
51 |
52 | attach-sources
53 |
54 | jar
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Builder Generator
2 |
3 | [](https://travis-ci.org/ltearno/builder-generator)
4 |
5 | Builder Generator is a Java library allowing you to automatically generate builder classes for your constructors.
6 |
7 | It uses the builder pattern described [here](http://www.jayway.com/2012/02/07/builder-pattern-with-a-twist/). An advantage to this pattern is that it manages **mandatory** parameters in a clean and elegant way.
8 |
9 | Builders are generated by adding the `@UseBuilderGenerator` annotation on the constructor you choose to be the target of the builder. Note that the generated builder class will not be part of your classes (although generated in the same package), but will call the targeted constructor of your class to build instances. Hence you have to write such a constructor in your class, and it has to be at least package protected.
10 |
11 | On the other side, generating and maintaining code implementing the builder pattern is useful but painful and not productive.
12 |
13 | To use the library in maven projects, declare :
14 |
15 |
16 | fr.lteconsulting
17 | builder-generator
18 | 0.9
19 | provided
20 |
21 |
22 | To use the latest uploaded snapshot version, use `1.0-SNAPSHOT`.
23 |
24 | This is a sample of how to use `Builder Generator` :
25 |
26 | Imagine you have a class with a terrible amount of parameters. Let's say `a`, `c`, and `e` are mandatory parameters, but the others are considered optional (meaning you probably provide an overloaded constructor falling back to a default value). It may look like this :
27 |
28 | public class ComplexClass
29 | {
30 | private String a;
31 | private String b;
32 | private String c;
33 | private String d;
34 | private String e;
35 |
36 | public ComplexClass(String a, String b, String c, String d, String e)
37 | {
38 | ...
39 | }
40 |
41 | public ComplexClass(String a, String c, String e) ...
42 | public ComplexClass(String a, String b, String c, String e) ...
43 | public ComplexClass(String a, String c, String d, String e) ...
44 | }
45 |
46 | `Builder generator` allows you to instead generate a builder with the `UseBuilderGenerator` annotation. (You can by the way certainly remove your overloaded constructors...) :
47 |
48 | public class ComplexClass
49 | {
50 | private String a;
51 | private String b;
52 | private String c;
53 | private String d;
54 | private String e;
55 |
56 | @UseBuilderGenerator
57 | public ComplexClass(@Mandatory String a, String b, @Mandatory String c, String d, @Mandatory String e)
58 | {
59 | ... // your normal constructor
60 | }
61 | }
62 |
63 | When you want to create an instance of this class, you use the generated builder :
64 |
65 | ComplexClass instance = ComplexClassBuilder
66 | .withA("this one is mandatory")
67 | .withC("this one too")
68 | .withE("all this is generated !")
69 | .withD("an optional parameter")
70 | .build();
71 |
72 | Since the code is generated, you can even copy it in your own source and remove the annotations, but it won't be automatically maintained...
73 |
74 | All kinds of possibilities are offered here, so may this be useful !
75 |
76 | ## Note about IDE integration
77 |
78 | This library is based on the pluggable annotation processor api (jsr-269) which is completely standard and will run with maven out of the box (although you may have to disable the incremental compilation sometimes). On the Eclipse IDE, you may need to download and activate the m2e-apt connector.
79 |
--------------------------------------------------------------------------------
/src/main/java/fr/lteconsulting/UseBuilderGeneratorProcessor.java:
--------------------------------------------------------------------------------
1 | package fr.lteconsulting;
2 |
3 | import java.io.IOException;
4 | import java.io.OutputStream;
5 | import java.io.PrintWriter;
6 | import java.util.ArrayList;
7 | import java.util.List;
8 | import java.util.Set;
9 |
10 | import javax.annotation.processing.AbstractProcessor;
11 | import javax.annotation.processing.RoundEnvironment;
12 | import javax.annotation.processing.SupportedAnnotationTypes;
13 | import javax.annotation.processing.SupportedSourceVersion;
14 | import javax.lang.model.SourceVersion;
15 | import javax.lang.model.element.Element;
16 | import javax.lang.model.element.ElementKind;
17 | import javax.lang.model.element.ExecutableElement;
18 | import javax.lang.model.element.Modifier;
19 | import javax.lang.model.element.PackageElement;
20 | import javax.lang.model.element.TypeElement;
21 | import javax.lang.model.element.VariableElement;
22 | import javax.lang.model.type.TypeMirror;
23 | import javax.tools.Diagnostic.Kind;
24 | import javax.tools.JavaFileObject;
25 |
26 | /**
27 | * Generates a Builder class for constructors annotated with {@link UseBuilderGenerator}.
28 | *
29 | *
30 | * The generated builder supports mandatory parameters, which should be annotated with {@link Mandatory}.
31 | *
32 | *
33 | * For more detailed customization, use the {@link Parameter} annotation.
34 | *
35 | *
36 | * The generated builders conform to the pattern described here : http://www.jayway.com/2012/02/07/builder-pattern-with-a-twist/.
37 | *
38 | * @author Arnaud Tournier www.lteconsulting.fr github.com/ltearno @ltearno
39 | *
40 | */
41 | @SupportedAnnotationTypes( UseBuilderGeneratorProcessor.AnnotationFqn )
42 | @SupportedSourceVersion( SourceVersion.RELEASE_8 )
43 | public class UseBuilderGeneratorProcessor extends AbstractProcessor
44 | {
45 | private final static String tab = " ";
46 | public final static String AnnotationFqn = "fr.lteconsulting.UseBuilderGenerator";
47 |
48 | @Override
49 | public boolean process( Set extends TypeElement> annotations, RoundEnvironment roundEnv )
50 | {
51 | for( Element e : roundEnv.getElementsAnnotatedWith( UseBuilderGenerator.class ) )
52 | {
53 | if( e.getKind() != ElementKind.CONSTRUCTOR && e.getKind() != ElementKind.METHOD )
54 | continue;
55 | processExecutableElement( (ExecutableElement) e );
56 | }
57 |
58 | roundEnv.errorRaised();
59 |
60 | return true;
61 | }
62 |
63 | private void processExecutableElement( ExecutableElement element )
64 | {
65 | // prepare lists of mandatory and optional parameters
66 | List mandatoryParameters = new ArrayList<>();
67 | List optionalParameters = new ArrayList<>();
68 |
69 | // split the constructor parameters into mandatory and optional
70 | analyzeParametersAndFeedLists( element, mandatoryParameters, optionalParameters );
71 |
72 | boolean staticCall = true;
73 | String returnTypeFqn;
74 | String finalCallText;
75 | String defaultFinalMethodName;
76 | String defaultBuilderClassName;
77 | if( element.getKind() == ElementKind.CONSTRUCTOR )
78 | {
79 | returnTypeFqn = getEnclosingTypeElement( element ).getQualifiedName().toString();
80 | finalCallText = "new " + getEnclosingTypeElement( element ).getQualifiedName().toString();
81 | defaultFinalMethodName = "build";
82 | defaultBuilderClassName = getEnclosingTypeElement( element ).getSimpleName().toString() + "Builder";
83 | }
84 | else if( element.getKind() == ElementKind.METHOD )
85 | {
86 | returnTypeFqn = element.getReturnType().toString();
87 |
88 | if( !element.getModifiers().contains( Modifier.STATIC ) )
89 | {
90 | finalCallText = "calledInstance" + "." + element.getSimpleName();
91 | staticCall = false;
92 | }
93 | else
94 | {
95 | finalCallText = getEnclosingTypeElement( element ).getQualifiedName() + "." + element.getSimpleName();
96 | }
97 |
98 | defaultFinalMethodName = "call";
99 | defaultBuilderClassName = capitalize( element.getSimpleName().toString() ) + "Caller";
100 | }
101 | else
102 | {
103 | processingEnv.getMessager().printMessage( Kind.ERROR, "This element is not supported by builder generator !", element );
104 | return;
105 | }
106 |
107 | // prepare and do code generation
108 | UseBuilderGenerator useBuilderGeneratorAnnotation = element.getAnnotation( UseBuilderGenerator.class );
109 | String finalMethodName = defaultFinalMethodName;
110 | if( !useBuilderGeneratorAnnotation.finalMethodName().isEmpty() )
111 | finalMethodName = useBuilderGeneratorAnnotation.finalMethodName();
112 | String packageName = getPackageName( element );
113 | if( !useBuilderGeneratorAnnotation.builderPackage().isEmpty() )
114 | packageName = useBuilderGeneratorAnnotation.builderPackage();
115 | String builderClassName = defaultBuilderClassName;
116 | if( !useBuilderGeneratorAnnotation.builderName().isEmpty() )
117 | builderClassName = useBuilderGeneratorAnnotation.builderName();
118 | String builderClassFqn = packageName + "." + builderClassName;
119 |
120 | GeneratorContext ctx = new GeneratorContext( element, staticCall, packageName, builderClassName, finalMethodName, returnTypeFqn, finalCallText, mandatoryParameters, optionalParameters, builderClassFqn );
121 | StringBuilder sb = new StringBuilder();
122 |
123 | generateBuilderClassCode( ctx, sb );
124 |
125 | saveBuilderClass( ctx, sb );
126 | }
127 |
128 | private static class GeneratorContext
129 | {
130 | final ExecutableElement element;
131 | final boolean staticCall;
132 | final String packageName;
133 | final String builderClassName;
134 | final String finalMethodName;
135 | final String returnTypeFqn;
136 | final String finalCallText;
137 | final List mandatoryParameters;
138 | final List optionalParameters;
139 | final String builderClassFqn;
140 |
141 | public GeneratorContext( ExecutableElement element, boolean staticCall, String packageName, String builderClassName, String finalMethodName, String returnTypeFqn, String finalCallText, List mandatoryParameters,
142 | List optionalParameters, String builderClassFqn )
143 | {
144 | this.element = element;
145 | this.staticCall = staticCall;
146 | this.packageName = packageName;
147 | this.builderClassName = builderClassName;
148 | this.finalMethodName = finalMethodName;
149 | this.returnTypeFqn = returnTypeFqn;
150 | this.finalCallText = finalCallText;
151 | this.mandatoryParameters = mandatoryParameters;
152 | this.optionalParameters = optionalParameters;
153 | this.builderClassFqn = builderClassFqn;
154 | }
155 | }
156 |
157 | private void analyzeParametersAndFeedLists( ExecutableElement element, List mandatoryParameters, List optionalParameters )
158 | {
159 | for( VariableElement parameter : element.getParameters() )
160 | {
161 | String parameterName = parameter.getSimpleName().toString();
162 | TypeMirror parameterType = parameter.asType();
163 | Mandatory mandatoryAnnotation = parameter.getAnnotation( Mandatory.class );
164 | Parameter parameterAnnotation = parameter.getAnnotation( Parameter.class );
165 |
166 | String setterName = "with" + capitalize( parameterName );
167 | if( parameterAnnotation != null && !parameterAnnotation.name().isEmpty() )
168 | setterName = parameterAnnotation.name();
169 |
170 | ParameterInformation paramInfo = new ParameterInformation( parameterName, parameterType, setterName );
171 |
172 | List list = optionalParameters;
173 | if( mandatoryAnnotation != null || (parameterAnnotation != null && parameterAnnotation.mandatory()) )
174 | list = mandatoryParameters;
175 |
176 | list.add( paramInfo );
177 | }
178 | }
179 |
180 | private void generateBuilderClassCode( GeneratorContext ctx, StringBuilder sb )
181 | {
182 | sb.append( "package " + ctx.packageName + ";\r\n" );
183 | sb.append( "\r\n" );
184 | sb.append( "public class " + ctx.builderClassName + " {\r\n" );
185 |
186 | generateMandatoryParametersInterfaces( ctx, sb );
187 | generateOptionalParametersInterface( ctx, sb );
188 | generateBuilderImplementation( ctx, sb );
189 | generateBootstrapMethod( ctx, sb );
190 |
191 | sb.append( "}\r\n" );
192 | }
193 |
194 | private void generateMandatoryParametersInterfaces( GeneratorContext ctx, StringBuilder sb )
195 | {
196 | for( int i = 0; i < ctx.mandatoryParameters.size(); i++ )
197 | {
198 | ParameterInformation paramInfo = ctx.mandatoryParameters.get( i );
199 | String nextInterfaceName = i < ctx.mandatoryParameters.size() - 1 ? ctx.mandatoryParameters.get( i + 1 ).interfaceName : "OptionalParameters";
200 |
201 | sb.append( tab + "public interface " + paramInfo.interfaceName + " {\r\n" );
202 | sb.append( tab + tab + nextInterfaceName + " " + paramInfo.setterName + "(" + paramInfo.parameterType + " " + paramInfo.parameterName + ");\r\n" );
203 | sb.append( tab + "}\r\n" );
204 | sb.append( "\r\n" );
205 | }
206 | }
207 |
208 | private void generateOptionalParametersInterface( GeneratorContext ctx, StringBuilder sb )
209 | {
210 | sb.append( tab + "public interface OptionalParameters {\r\n" );
211 | sb.append( tab + tab + ctx.returnTypeFqn + " " + ctx.finalMethodName + "();\r\n" );
212 | for( ParameterInformation info : ctx.optionalParameters )
213 | {
214 | sb.append( tab + tab + "OptionalParameters " + info.setterName + "(" + info.parameterType + " " + info.parameterName + ");\r\n" );
215 | }
216 | sb.append( tab + "}\r\n" );
217 | sb.append( "\r\n" );
218 | }
219 |
220 | private void generateBuilderImplementation( GeneratorContext ctx, StringBuilder sb )
221 | {
222 | sb.append( tab + "private static class BuilderInternal implements OptionalParameters" );
223 | for( ParameterInformation info : ctx.mandatoryParameters )
224 | sb.append( ", " + info.interfaceName );
225 | sb.append( " {\r\n" );
226 |
227 | generatePrivateFields( ctx, sb );
228 | generateConstructor( ctx, sb );
229 | generateBuildMethod( ctx, sb );
230 | generateMandatorySetters( ctx, sb );
231 | generateOptionalSetters( ctx, sb );
232 |
233 | sb.append( tab + "}\r\n" );
234 | sb.append( "\r\n" );
235 | }
236 |
237 | private void generateConstructor( GeneratorContext ctx, StringBuilder sb )
238 | {
239 | if( ctx.staticCall )
240 | return;
241 |
242 | sb.append( tab + tab + "private BuilderInternal(" + getEnclosingTypeElement( ctx.element ).getQualifiedName() + " calledInstance) {\r\n" );
243 | sb.append( tab + tab + tab + "this.calledInstance = calledInstance;\r\n" );
244 | sb.append( tab + tab + "}\r\n" );
245 | sb.append( tab + tab + "\r\n" );
246 | sb.append( tab + tab + "\r\n" );
247 | }
248 |
249 | private void generatePrivateFields( GeneratorContext ctx, StringBuilder sb )
250 | {
251 | if( !ctx.staticCall )
252 | sb.append( tab + tab + "private " + getEnclosingTypeElement( ctx.element ).getQualifiedName() + " calledInstance;\r\n" );
253 |
254 | for( VariableElement parameter : ctx.element.getParameters() )
255 | sb.append( tab + tab + "private " + parameter.asType() + " " + parameter.getSimpleName().toString() + ";\r\n" );
256 |
257 | sb.append( "\r\n" );
258 | }
259 |
260 | private void generateBuildMethod( GeneratorContext ctx, StringBuilder sb )
261 | {
262 | sb.append( tab + tab + "@Override public " + ctx.returnTypeFqn + " " + ctx.finalMethodName + "() {\r\n" );
263 | sb.append( tab + tab + tab );
264 | if( !"void".equals( ctx.returnTypeFqn ) )
265 | sb.append( "return " );
266 | sb.append( ctx.finalCallText + "(" );
267 | boolean first = true;
268 | for( VariableElement parameter : ctx.element.getParameters() )
269 | {
270 | if( !first )
271 | sb.append( ", " );
272 | else
273 | first = false;
274 | sb.append( parameter.getSimpleName().toString() );
275 | }
276 | sb.append( ");\r\n" );
277 | sb.append( tab + tab + "}\r\n" );
278 | sb.append( "\r\n" );
279 | }
280 |
281 | private void generateMandatorySetters( GeneratorContext ctx, StringBuilder sb )
282 | {
283 | for( int i = 0; i < ctx.mandatoryParameters.size(); i++ )
284 | {
285 | ParameterInformation paramInfo = ctx.mandatoryParameters.get( i );
286 | String nextInterfaceName = i < ctx.mandatoryParameters.size() - 1 ? ctx.mandatoryParameters.get( i + 1 ).interfaceName : "OptionalParameters";
287 |
288 | sb.append( tab + tab + "@Override public " + nextInterfaceName + " " + paramInfo.setterName + "(" + paramInfo.parameterType + " " + paramInfo.parameterName + ") {\r\n" );
289 | sb.append( tab + tab + tab + "this." + paramInfo.parameterName + " = " + paramInfo.parameterName + ";\r\n" );
290 | sb.append( tab + tab + tab + "return this;\r\n" );
291 | sb.append( tab + tab + "}\r\n" );
292 | sb.append( "\r\n" );
293 | }
294 | }
295 |
296 | private void generateOptionalSetters( GeneratorContext ctx, StringBuilder sb )
297 | {
298 | for( ParameterInformation info : ctx.optionalParameters )
299 | {
300 | sb.append( tab + tab + "@Override public OptionalParameters " + info.setterName + "(" + info.parameterType + " " + info.parameterName + ") {\r\n" );
301 | sb.append( tab + tab + tab + "this." + info.parameterName + " = " + info.parameterName + ";\r\n" );
302 | sb.append( tab + tab + tab + "return this;\r\n" );
303 | sb.append( tab + tab + "}\r\n" );
304 | sb.append( "\r\n" );
305 | }
306 | }
307 |
308 | private void generateBootstrapMethod( GeneratorContext ctx, StringBuilder sb )
309 | {
310 | if( !ctx.mandatoryParameters.isEmpty() )
311 | {
312 | ParameterInformation info = ctx.mandatoryParameters.get( 0 );
313 | String nextInterfaceName = ctx.mandatoryParameters.size() > 1 ? ctx.mandatoryParameters.get( 1 ).interfaceName : "OptionalParameters";
314 |
315 | if( ctx.staticCall )
316 | {
317 | sb.append( tab + "public static " + nextInterfaceName + " " + info.setterName + "(" + info.parameterType + " " + info.parameterName + ") {\r\n" );
318 | sb.append( tab + tab + "return new BuilderInternal()." + info.setterName + "(" + info.parameterName + ");\r\n" );
319 | sb.append( tab + "}\r\n" );
320 | }
321 |
322 | generatePrepareMethod( ctx, info.interfaceName, sb );
323 | }
324 | else
325 | {
326 | generatePrepareMethod( ctx, "OptionalParameters", sb );
327 | }
328 | }
329 |
330 | private void generatePrepareMethod( GeneratorContext ctx, String shellInterfaceName, StringBuilder sb )
331 | {
332 | if( ctx.staticCall )
333 | {
334 | sb.append( tab + "public static " + shellInterfaceName + " prepare() {\r\n" );
335 | sb.append( tab + tab + "return new BuilderInternal();\r\n" );
336 | sb.append( tab + "}\r\n" );
337 | }
338 | else
339 | {
340 | sb.append( tab + "public static " + shellInterfaceName + " prepare(" + getEnclosingTypeElement( ctx.element ).getQualifiedName() + " instance) {\r\n" );
341 | sb.append( tab + tab + "return new BuilderInternal(instance);\r\n" );
342 | sb.append( tab + "}\r\n" );
343 | }
344 | }
345 |
346 | private void saveBuilderClass( GeneratorContext ctx, StringBuilder sb )
347 | {
348 | try
349 | {
350 | JavaFileObject jfo = processingEnv.getFiler().createSourceFile( ctx.builderClassFqn, ctx.element );
351 |
352 | OutputStream os = jfo.openOutputStream();
353 | PrintWriter pw = new PrintWriter( os );
354 | pw.print( sb.toString() );
355 | pw.close();
356 | os.close();
357 |
358 | processingEnv.getMessager().printMessage( Kind.NOTE, "Builder generated for this constructor: " + ctx.builderClassFqn, ctx.element );
359 | }
360 | catch( IOException e )
361 | {
362 | e.printStackTrace();
363 | processingEnv.getMessager().printMessage( Kind.ERROR, "Error generating builder, a builder may already exist (" + ctx.builderClassFqn + ") !" + e, ctx.element );
364 | }
365 | }
366 |
367 | private static class ParameterInformation
368 | {
369 | String parameterName;
370 | TypeMirror parameterType;
371 | String interfaceName;
372 | String setterName;
373 |
374 | public ParameterInformation( String parameterName, TypeMirror parameterType, String setterName )
375 | {
376 | this.parameterName = parameterName;
377 | this.parameterType = parameterType;
378 | this.interfaceName = "MandatoryParameter" + capitalize( parameterName );
379 | this.setterName = setterName;
380 | }
381 | }
382 |
383 | private static String getPackageName( Element element )
384 | {
385 | while( element != null && !(element instanceof PackageElement) )
386 | element = element.getEnclosingElement();
387 | if( element == null )
388 | return null;
389 | return ((PackageElement) element).getQualifiedName().toString();
390 | }
391 |
392 | private static TypeElement getEnclosingTypeElement( Element element )
393 | {
394 | while( element != null && !(element instanceof TypeElement) )
395 | element = element.getEnclosingElement();
396 | if( element == null )
397 | return null;
398 | return (TypeElement) element;
399 | }
400 |
401 | private static String capitalize( String value )
402 | {
403 | return value.substring( 0, 1 ).toUpperCase() + value.substring( 1 );
404 | }
405 | }
406 |
--------------------------------------------------------------------------------