![JAR search and dependency download from the Maven repository](/logo.png)
fr.lteconsulting.UseBuilderGeneratorProcessor Maven / Gradle / Ivy
package fr.lteconsulting;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic.Kind;
import javax.tools.JavaFileObject;
/**
* Generates a Builder class for constructors annotated with {@link UseBuilderGenerator}.
*
*
* The generated builder supports mandatory parameters, which should be annotated with {@link Mandatory}.
*
*
* For more detailed customization, use the {@link Parameter} annotation.
*
*
* The generated builders conform to the pattern described here : http://www.jayway.com/2012/02/07/builder-pattern-with-a-twist/.
*
* @author Arnaud Tournier www.lteconsulting.fr github.com/ltearno @ltearno
*
*/
@SupportedAnnotationTypes( UseBuilderGeneratorProcessor.AnnotationFqn )
@SupportedSourceVersion( SourceVersion.RELEASE_8 )
public class UseBuilderGeneratorProcessor extends AbstractProcessor
{
private final static String tab = " ";
public final static String AnnotationFqn = "fr.lteconsulting.UseBuilderGenerator";
@Override
public boolean process( Set extends TypeElement> annotations, RoundEnvironment roundEnv )
{
for( Element e : roundEnv.getElementsAnnotatedWith( UseBuilderGenerator.class ) )
{
if( e.getKind() != ElementKind.CONSTRUCTOR && e.getKind() != ElementKind.METHOD )
continue;
processExecutableElement( (ExecutableElement) e );
}
roundEnv.errorRaised();
return true;
}
private void processExecutableElement( ExecutableElement element )
{
// prepare lists of mandatory and optional parameters
List mandatoryParameters = new ArrayList<>();
List optionalParameters = new ArrayList<>();
// split the constructor parameters into mandatory and optional
analyzeParametersAndFeedLists( element, mandatoryParameters, optionalParameters );
boolean staticCall = true;
String returnTypeFqn;
String finalCallText;
String defaultFinalMethodName;
String defaultBuilderClassName;
if( element.getKind() == ElementKind.CONSTRUCTOR )
{
returnTypeFqn = getEnclosingTypeElement( element ).getQualifiedName().toString();
finalCallText = "new " + getEnclosingTypeElement( element ).getQualifiedName().toString();
defaultFinalMethodName = "build";
defaultBuilderClassName = getEnclosingTypeElement( element ).getSimpleName().toString() + "Builder";
}
else if( element.getKind() == ElementKind.METHOD )
{
returnTypeFqn = element.getReturnType().toString();
if( !element.getModifiers().contains( Modifier.STATIC ) )
{
finalCallText = "calledInstance" + "." + element.getSimpleName();
staticCall = false;
}
else
{
finalCallText = getEnclosingTypeElement( element ).getQualifiedName() + "." + element.getSimpleName();
}
defaultFinalMethodName = "call";
defaultBuilderClassName = capitalize( element.getSimpleName().toString() ) + "Caller";
}
else
{
processingEnv.getMessager().printMessage( Kind.ERROR, "This element is not supported by builder generator !", element );
return;
}
// prepare and do code generation
UseBuilderGenerator useBuilderGeneratorAnnotation = element.getAnnotation( UseBuilderGenerator.class );
String finalMethodName = defaultFinalMethodName;
if( !useBuilderGeneratorAnnotation.finalMethodName().isEmpty() )
finalMethodName = useBuilderGeneratorAnnotation.finalMethodName();
String packageName = getPackageName( element );
if( !useBuilderGeneratorAnnotation.builderPackage().isEmpty() )
packageName = useBuilderGeneratorAnnotation.builderPackage();
String builderClassName = defaultBuilderClassName;
if( !useBuilderGeneratorAnnotation.builderName().isEmpty() )
builderClassName = useBuilderGeneratorAnnotation.builderName();
String builderClassFqn = packageName + "." + builderClassName;
GeneratorContext ctx = new GeneratorContext( element, staticCall, packageName, builderClassName, finalMethodName, returnTypeFqn, finalCallText, mandatoryParameters, optionalParameters, builderClassFqn );
StringBuilder sb = new StringBuilder();
generateBuilderClassCode( ctx, sb );
saveBuilderClass( ctx, sb );
}
private static class GeneratorContext
{
final ExecutableElement element;
final boolean staticCall;
final String packageName;
final String builderClassName;
final String finalMethodName;
final String returnTypeFqn;
final String finalCallText;
final List mandatoryParameters;
final List optionalParameters;
final String builderClassFqn;
public GeneratorContext( ExecutableElement element, boolean staticCall, String packageName, String builderClassName, String finalMethodName, String returnTypeFqn, String finalCallText, List mandatoryParameters,
List optionalParameters, String builderClassFqn )
{
this.element = element;
this.staticCall = staticCall;
this.packageName = packageName;
this.builderClassName = builderClassName;
this.finalMethodName = finalMethodName;
this.returnTypeFqn = returnTypeFqn;
this.finalCallText = finalCallText;
this.mandatoryParameters = mandatoryParameters;
this.optionalParameters = optionalParameters;
this.builderClassFqn = builderClassFqn;
}
}
private void analyzeParametersAndFeedLists( ExecutableElement element, List mandatoryParameters, List optionalParameters )
{
for( VariableElement parameter : element.getParameters() )
{
String parameterName = parameter.getSimpleName().toString();
TypeMirror parameterType = parameter.asType();
Mandatory mandatoryAnnotation = parameter.getAnnotation( Mandatory.class );
Parameter parameterAnnotation = parameter.getAnnotation( Parameter.class );
String setterName = "with" + capitalize( parameterName );
if( parameterAnnotation != null && !parameterAnnotation.name().isEmpty() )
setterName = parameterAnnotation.name();
ParameterInformation paramInfo = new ParameterInformation( parameterName, parameterType, setterName );
List list = optionalParameters;
if( mandatoryAnnotation != null || (parameterAnnotation != null && parameterAnnotation.mandatory()) )
list = mandatoryParameters;
list.add( paramInfo );
}
}
private void generateBuilderClassCode( GeneratorContext ctx, StringBuilder sb )
{
sb.append( "package " + ctx.packageName + ";\r\n" );
sb.append( "\r\n" );
sb.append( "public class " + ctx.builderClassName + " {\r\n" );
generateMandatoryParametersInterfaces( ctx, sb );
generateOptionalParametersInterface( ctx, sb );
generateBuilderImplementation( ctx, sb );
generateBootstrapMethod( ctx, sb );
sb.append( "}\r\n" );
}
private void generateMandatoryParametersInterfaces( GeneratorContext ctx, StringBuilder sb )
{
for( int i = 0; i < ctx.mandatoryParameters.size(); i++ )
{
ParameterInformation paramInfo = ctx.mandatoryParameters.get( i );
String nextInterfaceName = i < ctx.mandatoryParameters.size() - 1 ? ctx.mandatoryParameters.get( i + 1 ).interfaceName : "OptionalParameters";
sb.append( tab + "public interface " + paramInfo.interfaceName + " {\r\n" );
sb.append( tab + tab + nextInterfaceName + " " + paramInfo.setterName + "(" + paramInfo.parameterType + " " + paramInfo.parameterName + ");\r\n" );
sb.append( tab + "}\r\n" );
sb.append( "\r\n" );
}
}
private void generateOptionalParametersInterface( GeneratorContext ctx, StringBuilder sb )
{
sb.append( tab + "public interface OptionalParameters {\r\n" );
sb.append( tab + tab + ctx.returnTypeFqn + " " + ctx.finalMethodName + "();\r\n" );
for( ParameterInformation info : ctx.optionalParameters )
{
sb.append( tab + tab + "OptionalParameters " + info.setterName + "(" + info.parameterType + " " + info.parameterName + ");\r\n" );
}
sb.append( tab + "}\r\n" );
sb.append( "\r\n" );
}
private void generateBuilderImplementation( GeneratorContext ctx, StringBuilder sb )
{
sb.append( tab + "private static class BuilderInternal implements OptionalParameters" );
for( ParameterInformation info : ctx.mandatoryParameters )
sb.append( ", " + info.interfaceName );
sb.append( " {\r\n" );
generatePrivateFields( ctx, sb );
generateConstructor( ctx, sb );
generateBuildMethod( ctx, sb );
generateMandatorySetters( ctx, sb );
generateOptionalSetters( ctx, sb );
sb.append( tab + "}\r\n" );
sb.append( "\r\n" );
}
private void generateConstructor( GeneratorContext ctx, StringBuilder sb )
{
if( ctx.staticCall )
return;
sb.append( tab + tab + "private BuilderInternal(" + getEnclosingTypeElement( ctx.element ).getQualifiedName() + " calledInstance) {\r\n" );
sb.append( tab + tab + tab + "this.calledInstance = calledInstance;\r\n" );
sb.append( tab + tab + "}\r\n" );
sb.append( tab + tab + "\r\n" );
sb.append( tab + tab + "\r\n" );
}
private void generatePrivateFields( GeneratorContext ctx, StringBuilder sb )
{
if( !ctx.staticCall )
sb.append( tab + tab + "private " + getEnclosingTypeElement( ctx.element ).getQualifiedName() + " calledInstance;\r\n" );
for( VariableElement parameter : ctx.element.getParameters() )
sb.append( tab + tab + "private " + parameter.asType() + " " + parameter.getSimpleName().toString() + ";\r\n" );
sb.append( "\r\n" );
}
private void generateBuildMethod( GeneratorContext ctx, StringBuilder sb )
{
sb.append( tab + tab + "@Override public " + ctx.returnTypeFqn + " " + ctx.finalMethodName + "() {\r\n" );
sb.append( tab + tab + tab );
if( !"void".equals( ctx.returnTypeFqn ) )
sb.append( "return " );
sb.append( ctx.finalCallText + "(" );
boolean first = true;
for( VariableElement parameter : ctx.element.getParameters() )
{
if( !first )
sb.append( ", " );
else
first = false;
sb.append( parameter.getSimpleName().toString() );
}
sb.append( ");\r\n" );
sb.append( tab + tab + "}\r\n" );
sb.append( "\r\n" );
}
private void generateMandatorySetters( GeneratorContext ctx, StringBuilder sb )
{
for( int i = 0; i < ctx.mandatoryParameters.size(); i++ )
{
ParameterInformation paramInfo = ctx.mandatoryParameters.get( i );
String nextInterfaceName = i < ctx.mandatoryParameters.size() - 1 ? ctx.mandatoryParameters.get( i + 1 ).interfaceName : "OptionalParameters";
sb.append( tab + tab + "@Override public " + nextInterfaceName + " " + paramInfo.setterName + "(" + paramInfo.parameterType + " " + paramInfo.parameterName + ") {\r\n" );
sb.append( tab + tab + tab + "this." + paramInfo.parameterName + " = " + paramInfo.parameterName + ";\r\n" );
sb.append( tab + tab + tab + "return this;\r\n" );
sb.append( tab + tab + "}\r\n" );
sb.append( "\r\n" );
}
}
private void generateOptionalSetters( GeneratorContext ctx, StringBuilder sb )
{
for( ParameterInformation info : ctx.optionalParameters )
{
sb.append( tab + tab + "@Override public OptionalParameters " + info.setterName + "(" + info.parameterType + " " + info.parameterName + ") {\r\n" );
sb.append( tab + tab + tab + "this." + info.parameterName + " = " + info.parameterName + ";\r\n" );
sb.append( tab + tab + tab + "return this;\r\n" );
sb.append( tab + tab + "}\r\n" );
sb.append( "\r\n" );
}
}
private void generateBootstrapMethod( GeneratorContext ctx, StringBuilder sb )
{
if( !ctx.mandatoryParameters.isEmpty() )
{
ParameterInformation info = ctx.mandatoryParameters.get( 0 );
String nextInterfaceName = ctx.mandatoryParameters.size() > 1 ? ctx.mandatoryParameters.get( 1 ).interfaceName : "OptionalParameters";
if( ctx.staticCall )
{
sb.append( tab + "public static " + nextInterfaceName + " " + info.setterName + "(" + info.parameterType + " " + info.parameterName + ") {\r\n" );
sb.append( tab + tab + "return new BuilderInternal()." + info.setterName + "(" + info.parameterName + ");\r\n" );
sb.append( tab + "}\r\n" );
}
generatePrepareMethod( ctx, info.interfaceName, sb );
}
else
{
generatePrepareMethod( ctx, "OptionalParameters", sb );
}
}
private void generatePrepareMethod( GeneratorContext ctx, String shellInterfaceName, StringBuilder sb )
{
if( ctx.staticCall )
{
sb.append( tab + "public static " + shellInterfaceName + " prepare() {\r\n" );
sb.append( tab + tab + "return new BuilderInternal();\r\n" );
sb.append( tab + "}\r\n" );
}
else
{
sb.append( tab + "public static " + shellInterfaceName + " prepare(" + getEnclosingTypeElement( ctx.element ).getQualifiedName() + " instance) {\r\n" );
sb.append( tab + tab + "return new BuilderInternal(instance);\r\n" );
sb.append( tab + "}\r\n" );
}
}
private void saveBuilderClass( GeneratorContext ctx, StringBuilder sb )
{
try
{
JavaFileObject jfo = processingEnv.getFiler().createSourceFile( ctx.builderClassFqn, ctx.element );
OutputStream os = jfo.openOutputStream();
PrintWriter pw = new PrintWriter( os );
pw.print( sb.toString() );
pw.close();
os.close();
processingEnv.getMessager().printMessage( Kind.NOTE, "Builder generated for this constructor: " + ctx.builderClassFqn, ctx.element );
}
catch( IOException e )
{
e.printStackTrace();
processingEnv.getMessager().printMessage( Kind.ERROR, "Error generating builder, a builder may already exist (" + ctx.builderClassFqn + ") !" + e, ctx.element );
}
}
private static class ParameterInformation
{
String parameterName;
TypeMirror parameterType;
String interfaceName;
String setterName;
public ParameterInformation( String parameterName, TypeMirror parameterType, String setterName )
{
this.parameterName = parameterName;
this.parameterType = parameterType;
this.interfaceName = "MandatoryParameter" + capitalize( parameterName );
this.setterName = setterName;
}
}
private static String getPackageName( Element element )
{
while( element != null && !(element instanceof PackageElement) )
element = element.getEnclosingElement();
if( element == null )
return null;
return ((PackageElement) element).getQualifiedName().toString();
}
private static TypeElement getEnclosingTypeElement( Element element )
{
while( element != null && !(element instanceof TypeElement) )
element = element.getEnclosingElement();
if( element == null )
return null;
return (TypeElement) element;
}
private static String capitalize( String value )
{
return value.substring( 0, 1 ).toUpperCase() + value.substring( 1 );
}
}