├── .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 | [![Build Status](https://travis-ci.org/ltearno/builder-generator.svg?branch=master)](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 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 | --------------------------------------------------------------------------------