package org.andork.codegen.builder;
import static org.andork.codegen.NameUtils.a;
import static org.andork.codegen.NameUtils.cap;
import static org.andork.codegen.NameUtils.getElementPluralName;
import static org.andork.codegen.NameUtils.getElementSingularName;
import japa.parser.JavaParser;
import japa.parser.ast.CompilationUnit;
import japa.parser.ast.Node;
import japa.parser.ast.body.BodyDeclaration;
import japa.parser.ast.body.MethodDeclaration;
import japa.parser.ast.body.Parameter;
import japa.parser.ast.body.TypeDeclaration;
import japa.parser.ast.expr.AnnotationExpr;
import japa.parser.ast.expr.SingleMemberAnnotationExpr;
import japa.parser.ast.expr.StringLiteralExpr;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import org.andork.codegen.CodeBuilder;
import org.andork.codegen.CodeBuilder.Block;
import org.andork.codegen.LineRegion;
/**
* Does some Judo to generate builders for interrelated classes.<br>
* To use it, just toss all the classes you want to generate builders for into {@link #generateBuilders(Class...)}, and see if it works!
*
* @author james.a.edwards
*/
public class BuilderGenerator
{
public static final boolean DRY_RUN = false;
public static void main( String[ ] args ) throws Exception
{
// String[] builders = generateBuilders(
// AbstractOrderActionMessage.class,
// AbstractEnterOrChangeOrderMessage.class,
// AbstractEnterOrChangeOrderMessage.Leg.class,
// AbstractEnterOrChangeOrderMessage.VSPInst.class,
// OrdEnterMessage.class,
// OrdCancelMessage.class,
// OrdChangeMessage.class,
// OrdStateMessage.class);
String[ ] builders = new String[ 0 ];
// String[] builders = generateBuilders(
// MultiLegRequest.class,
// MultiLegRequestWithEcho.class,
// MultiLegChainRequest.class,
// MultiLegChainRequest.Leg.class,
// MultiLegCancelRequest.class);
// String[] builders = generateBuilders(
// MultiLegResponse.class,
// LegQuoteResponse.class,
// DerivedQuoteResponse.class);
// String[] builders = generateBuilders(
// // LegQuoteResponse.class,
// DerivedQuoteResponse.class,
// MultiLegResponse.class,
// MultiLegErrorResponse.class,
// // MultiLegResponseWithServerSideId.class,
// DefaultStrategyResponse.class,
// DefaultStrategyResponse.Leg.class);
// // MultiLegChainResponse.class,
// // MultiLegChainResponse.ChainRoot.class,
// // MultiLegChainResponse.Leg.class,
// // MultiLegChainResponse.Menu.class,
// // MultiLegChainResponse.RootGroup.class,
// // MultiLegChainResponse.StrategyInstance.class,
// // MultiLegChainResponse.ExpirationGroup.class);
// String[] builders = generateBuilders(
// MultiLegResponse.class,
// DefaultStrategyResponse.class,
// MultiLegResponseWithServerSideId.class,
// MultiLegChainResponse.class);
// String[] builders = generateBuilders(
// MultiLegResponse.class,
// MultiLegErrorResponse.class);
// String[] builders = generateBuilders(
// MultiLegChainResponse.Menu.class,
// MultiLegChainResponse.StrikesPerExpirationMenu.class,
// MultiLegMessages.PreparsedMenu.class);
// String[] builders = generateBuilders(
// MultiLegChainResponse.Menu.class,
// MultiLegChainResponse.ExpirationGroupListMenu.class,
// MultiLegMessages.PreparsedMenu.class);
// String[] builders = generateBuilders(
// SpecifiedLot.class);
// String[] builders = generateBuilders(LegQuoteResponse.class);
for( String builder : builders )
{
if( DRY_RUN )
{
System.out.println( builder );
}
else
{
try
{
writeBuilder( builder );
}
catch( Exception ex )
{
ex.printStackTrace( );
}
}
}
}
private static String toString( Type type )
{
if( type instanceof ParameterizedType )
{
ParameterizedType paramType = ( ParameterizedType ) type;
Type[ ] typeArgs = paramType.getActualTypeArguments( );
StringBuffer sb = new StringBuffer( );
sb.append( ( ( Class<?> ) paramType.getRawType( ) ).getSimpleName( ) );
sb.append( '<' );
for( int i = 0 ; i < typeArgs.length ; i++ )
{
if( i > 0 )
{
sb.append( ',' );
}
sb.append( toString( typeArgs[ i ] ) );
}
sb.append( '>' );
return sb.toString( );
}
else if( type instanceof Class )
{
return ( ( Class<?> ) type ).getSimpleName( );
}
return type.toString( );
}
private static String getTypeName( Field field )
{
if( field.getGenericType( ) != null )
{
return toString( field.getGenericType( ) );
}
else
{
return field.getType( ).getSimpleName( );
}
}
private static String getListElementTypeName( Field field )
{
if( field.getGenericType( ) != null && field.getGenericType( ) instanceof ParameterizedType )
{
return toString( ( ( ParameterizedType ) field.getGenericType( ) ).getActualTypeArguments( )[ 0 ] );
}
return "java.lang.Object";
}
private static String getMapKeyTypeName( Field field )
{
if( field.getGenericType( ) != null && field.getGenericType( ) instanceof ParameterizedType )
{
return toString( ( ( ParameterizedType ) field.getGenericType( ) ).getActualTypeArguments( )[ 0 ] );
}
return "java.lang.Object";
}
private static String getMapValueTypeName( Field field )
{
if( field.getGenericType( ) != null && field.getGenericType( ) instanceof ParameterizedType )
{
return toString( ( ( ParameterizedType ) field.getGenericType( ) ).getActualTypeArguments( )[ 1 ] );
}
return "java.lang.Object";
}
/**
* Generates builder code for a group of types using reflection. <br>
* <br>
* <b>Features</b>:
* <ul>
* <li>A builder will have setters for each field in its target class/superclasses except fields annotated with {@link BuilderIgnore}.
* <li>A builder will have {@code add} methods for {@link List} fields in its target class/superclasses. The {@code List}s must have a non-wildcard generic
* type argument.
* <li>A builder's {@code create()} method will throw an exception if any field was not set (except for fields/fields of classes annotated with
* {@link BuilderAllowNull}).
* <li>A builder's {@code create()} method will call any methods in its target class/superclasses that are annotated with
* <li>A builder interface will be generated for each class for which a subclass is also provided in {@code types}. It will extend any builder interfaces
* generated for superclasses.
* <li>A concrete builder class will be generated for each concrete class. It will implement any builder interfaces generated for superclasses.
* {@link BuilderValidator}.
* <li>
* If two types share a common ancestor, and the ancestor is provided, a common builder interface for those types will be generated.
* </ul>
*
* @param types
* the types to generate builder code for. If you want common interfaces for builders for types with a common ancestor, call this method with
* those types and the common ancestors together.
* @return an array of {@code String}s, each containing builder code for the corresponding type in {@code types}.
*/
public static String[ ] generateBuilders( Class<?> ... types )
{
String[ ] result = new String[ types.length ];
String bgLink = "{@link " + BuilderGenerator.class.getName( ) + " " + BuilderGenerator.class.getSimpleName( ) + "}";
Class<?>[ ] superclasses = new Class<?>[ types.length ];
boolean[ ] hasSubclasses = new boolean[ types.length ];
for( int i = 0 ; i < types.length ; i++ )
{
for( int j = i + 1 ; j < types.length ; j++ )
{
if( types[ i ].isAssignableFrom( types[ j ] ) )
{
if( superclasses[ j ] == null || superclasses[ j ].isAssignableFrom( types[ i ] ) )
{
superclasses[ j ] = types[ i ];
hasSubclasses[ i ] = true;
}
}
}
}
for( int i = 0 ; i < types.length ; i++ )
{
Block topBlock = CodeBuilder.newBlock( );
Class<?> type = types[ i ];
Class<?> superclass = superclasses[ i ];
String typeName = type.getSimpleName( );
String superclassName = superclass == null ? null : superclass.getSimpleName( );
boolean isAbstract = Modifier.isAbstract( type.getModifiers( ) );
if( i > 0 )
{
topBlock.addLine( );
topBlock.addLine( );
}
topBlock.addLine( "@fullname(\"" + type.getName( ) + "\")" );
Block typeBlock = topBlock.newJavaBlock( "class " + typeName );
if( hasSubclasses[ i ] )
{
Block ifaceJavadoc = typeBlock.newJavadocBlock( );
StringBuffer line = ifaceJavadoc.addLine( "Interface for builders for" );
if( isAbstract )
{
line.append( " subclasses of {@code " ).append( typeName ).append( "}." );
}
else
{
line.append( " {@code " ).append( typeName ).append( "} and subclasses." );
}
ifaceJavadoc.addLine( "<br><br>" );
ifaceJavadoc.addLine( "<i>This interface was auto-generated by " + bgLink + ".</i>" );
String interfaceHeader = "public static interface IBuilder";
if( superclass != null )
{
interfaceHeader += " extends " + superclassName + ".IBuilder";
}
Block ifaceBlock = typeBlock.newJavaBlock( interfaceHeader );
for( Field field : type.getDeclaredFields( ) )
{
if( field.getAnnotation( BuilderIgnore.class ) != null )
{
continue;
}
if( Modifier.isStatic( field.getModifiers( ) ) )
{
continue;
}
Class<?> ftype = field.getType( );
String fieldName = field.getName( );
String ftypeName = getTypeName( field );
if( ftype.isAssignableFrom( ArrayList.class ) )
{
if( !( field.getGenericType( ) instanceof ParameterizedType ) )
{
continue;
}
String elemName = getElementSingularName( field );
String elemsName = getElementPluralName( field );
String listTypeName = getListElementTypeName( field );
Block setJavadoc = ifaceBlock.newJavadocBlock( );
setJavadoc.addLine( "Sets the " + elemsName + " of the result." );
setJavadoc.addLine( );
setJavadoc.addLine( "@param " + elemsName );
setJavadoc.addLine( "\t\tThe " + elemsName + " to set." );
setJavadoc.addLine( "@return" );
setJavadoc.addLine( "\t\tThis {@code IBuilder}, for chaining." );
ifaceBlock.addLine( "public abstract IBuilder set" + cap( elemsName ) + "(Collection<? extends " + listTypeName + "> " + elemsName + ");" );
Block addJavadoc = ifaceBlock.newJavadocBlock( );
addJavadoc.addLine( "Adds " + a( elemName ) + " to the result." );
addJavadoc.addLine( );
addJavadoc.addLine( "@param " + elemName );
addJavadoc.addLine( "\t\tThe " + elemName + " to add." );
addJavadoc.addLine( "@return" );
addJavadoc.addLine( "\t\tThis {@code IBuilder}, for chaining." );
ifaceBlock.addLine( "public abstract IBuilder add" + cap( elemName ) + "(" + listTypeName + " " + elemName + ");" );
Block addAllJavadoc = ifaceBlock.newJavadocBlock( );
addAllJavadoc.addLine( "Adds multiple " + elemsName + " to the result." );
addAllJavadoc.addLine( );
addAllJavadoc.addLine( "@param " + elemsName );
addAllJavadoc.addLine( "\t\tThe " + elemsName + " to add." );
addAllJavadoc.addLine( "@return" );
addAllJavadoc.addLine( "\t\tThis {@code IBuilder}, for chaining." );
ifaceBlock.addLine( "public abstract IBuilder add" + cap( elemsName ) + "(Collection<? extends " + listTypeName + "> " + elemsName + ");" );
Block addAll2Javadoc = ifaceBlock.newJavadocBlock( );
addAll2Javadoc.addLine( "Adds multiple " + elemsName + " to the result." );
addAll2Javadoc.addLine( );
addAll2Javadoc.addLine( "@param " + elemsName );
addAll2Javadoc.addLine( "\t\tThe " + elemsName + " to add." );
addAll2Javadoc.addLine( "@return" );
addAll2Javadoc.addLine( "\t\tThis {@code IBuilder}, for chaining." );
ifaceBlock.addLine( "public abstract IBuilder add" + cap( elemsName ) + "(" + listTypeName + "... " + elemsName + ");" );
}
else if( ftype.isAssignableFrom( HashMap.class ) )
{
if( !( field.getGenericType( ) instanceof ParameterizedType ) )
{
continue;
}
String mapKeyTypeName = getMapKeyTypeName( field );
String mapValueTypeName = getMapValueTypeName( field );
String entryName = getElementSingularName( field );
Block addJavadoc = ifaceBlock.newJavadocBlock( );
addJavadoc.addLine( "Puts " + a( entryName ) + " entry into the result." );
addJavadoc.addLine( );
addJavadoc.addLine( "@param key" );
addJavadoc.addLine( "\t\tThe key for the entry." );
addJavadoc.addLine( "@param value" );
addJavadoc.addLine( "\t\tThe value for the entry." );
addJavadoc.addLine( "@return" );
addJavadoc.addLine( "\t\tThis {@code Builder}, for chaining." );
ifaceBlock.addLine( "public abstract IBuilder put" + cap( entryName ) + "(" + mapKeyTypeName + " key, " + mapValueTypeName + " value)" );
}
else
{
Block setJavadoc = ifaceBlock.newJavadocBlock( );
String getter = "get";
if( boolean.class.equals( field.getType( ) ) )
{
getter = "is";
}
setJavadoc.addLine( "Sets the {@link " + typeName + "#" + getter + cap( fieldName ) + "() " + fieldName + "} of the result." );
setJavadoc.addLine( );
setJavadoc.addLine( "@param " + fieldName );
setJavadoc.addLine( "\t\tThe value for the {@code " + fieldName + "}." );
setJavadoc.addLine( "@return" );
setJavadoc.addLine( "\t\tThis {@code IBuilder}, for chaining." );
String paramTypeName = ftypeName;
String returnTypeName = ftypeName;
if( ftype.isArray( ) )
{
paramTypeName = toString( ftype.getComponentType( ) ) + "...";
returnTypeName = toString( ftype.getComponentType( ) ) + "[]";
}
ifaceBlock.addLine( "public abstract IBuilder " + fieldName + "(" + paramTypeName + " " + fieldName + ");" );
Block getJavadoc = ifaceBlock.newJavadocBlock( );
getJavadoc.addLine( "Gets the current {@link " + typeName + "#" + fieldName + " " + fieldName + "} of the result." );
getJavadoc.addLine( );
getJavadoc.addLine( "@return" );
getJavadoc.addLine( "\t\tthe current {@link " + typeName + "#" + fieldName + " " + fieldName + "} of the result." );
ifaceBlock.addLine( "public abstract " + returnTypeName + " " + fieldName + "();" );
}
ifaceBlock.addLine( );
}
Block createJavadoc = ifaceBlock.newJavadocBlock( );
createJavadoc.addLine( "Creates a new {@code " + typeName + "} with the properties that have been set on this {@code IBuilder}." );
createJavadoc.addLine( "<br>" );
createJavadoc.addLine( "<b>Note</b>: this method may only be called once. Afterward it will return {@code null}." );
createJavadoc.addLine( );
createJavadoc.addLine( "@return" );
createJavadoc.addLine( "\tA new {@code " + typeName + "} with the properties that have been set on this {@code IBuilder}." );
ifaceBlock.addLine( "public abstract " + typeName + " create();" );
}
typeBlock.addLine( );
if( !isAbstract )
{
Block newBuilderJavadoc = typeBlock.newJavadocBlock( );
newBuilderJavadoc.addLine( "@return" );
newBuilderJavadoc.addLine( "\ta new {@link Builder}." );
newBuilderJavadoc.addLine( "@see" );
newBuilderJavadoc.addLine( "\t{@link Builder#create()}" );
Block newBuilderBlock = typeBlock.newJavaBlock( "public static Builder newBuilder()" );
newBuilderBlock.addLine( "return new Builder();" );
typeBlock.addLine( );
Block builderJavadoc = typeBlock.newJavadocBlock( );
builderJavadoc.addLine( "Builds " + a( "{@link " + typeName + "}" ) + "." );
builderJavadoc.addLine( "<br>" );
builderJavadoc.addLine( "If you are not familiar with the builder pattern, see <a href=\"http://en.wikipedia.org/wiki/Builder_pattern\">http://en.wikipedia.org/wiki/Builder_pattern</a>." );
builderJavadoc.addLine( "<br>" );
builderJavadoc.addLine( "{@code Builder} has no public constructor; use {@link #newBuilder()} to create a {@code Builder}." );
builderJavadoc.addLine( "<br><br>" );
builderJavadoc.addLine( "<i>This class was auto-generated by " + bgLink + ".</i>" );
String builderHeader = "public static class Builder";
if( superclass != null )
{
builderHeader += " implements IBuilder";
}
Block builderBlock = typeBlock.newJavaBlock( builderHeader );
builderBlock.addLine( "private " + typeName + " result;" );
builderBlock.addLine( );
Block constructor = builderBlock.newJavaBlock( "private Builder()" );
constructor.addLine( "result = new " + typeName + "();" );
builderBlock.addLine( );
List<Field> fields = new ArrayList<Field>( );
List<Method> validators = new ArrayList<Method>( );
Class<?> c = type;
while( !c.equals( Object.class ) )
{
fields.addAll( Arrays.asList( c.getDeclaredFields( ) ) );
for( Method m : c.getDeclaredMethods( ) )
{
if( Modifier.isStatic( m.getModifiers( ) ) )
{
continue;
}
if( !m.getReturnType( ).equals( void.class ) )
{
continue;
}
Class<?>[ ] paramTypes = m.getParameterTypes( );
if( paramTypes != null && paramTypes.length > 0 )
{
continue;
}
if( c != type && Modifier.isPrivate( m.getModifiers( ) ) )
{
continue;
}
validators.add( m );
}
c = c.getSuperclass( );
}
for( Field field : fields )
{
if( field.getAnnotation( BuilderIgnore.class ) != null )
{
continue;
}
if( Modifier.isStatic( field.getModifiers( ) ) )
{
continue;
}
Class<?> ftype = field.getType( );
String fieldName = field.getName( );
String ftypeName = getTypeName( field );
if( ftype.isAssignableFrom( ArrayList.class ) )
{
if( !( field.getGenericType( ) instanceof ParameterizedType ) )
{
continue;
}
String listTypeName = getListElementTypeName( field );
String elemName = getElementSingularName( field );
String elemsName = getElementPluralName( field );
boolean lazyInit = field.getAnnotation( BuilderLazyInitialize.class ) != null;
Block setJavadoc = builderBlock.newJavadocBlock( );
setJavadoc.addLine( "Sets the " + elemsName + " of the result." );
setJavadoc.addLine( );
setJavadoc.addLine( "@param " + elemsName );
setJavadoc.addLine( "\t\tThe " + elemsName + " to set." );
setJavadoc.addLine( "@return" );
setJavadoc.addLine( "\t\tThis {@code Builder}, for chaining." );
Block setBlock = builderBlock.newJavaBlock( "public Builder set" + cap( elemsName ) + "(Collection<? extends " + listTypeName + "> " + elemsName + ")" );
builderBlock.addLine( );
Block addJavadoc = builderBlock.newJavadocBlock( );
addJavadoc.addLine( "Adds " + a( elemName ) + " to the result." );
addJavadoc.addLine( );
addJavadoc.addLine( "@param " + elemName );
addJavadoc.addLine( "\t\tThe " + elemName + " to add." );
addJavadoc.addLine( "@return" );
addJavadoc.addLine( "\t\tThis {@code Builder}, for chaining." );
Block addBlock = builderBlock.newJavaBlock( "public Builder add" + cap( elemName ) + "(" + listTypeName + " " + elemName + ")" );
builderBlock.addLine( );
Block addAllJavadoc = builderBlock.newJavadocBlock( );
addAllJavadoc.addLine( "Adds multiple " + elemsName + " to the result." );
addAllJavadoc.addLine( );
addAllJavadoc.addLine( "@param " + elemsName );
addAllJavadoc.addLine( "\t\tThe " + elemsName + " to add." );
addAllJavadoc.addLine( "@return" );
addAllJavadoc.addLine( "\t\tThis {@code Builder}, for chaining." );
Block addAllBlock = builderBlock.newJavaBlock( "public Builder add" + cap( elemsName ) + "(Collection<? extends " + listTypeName + "> " + elemsName + ")" );
builderBlock.addLine( );
Block addAll2Javadoc = builderBlock.newJavadocBlock( );
addAll2Javadoc.addLine( "Adds multiple " + elemsName + " to the result." );
addAll2Javadoc.addLine( );
addAll2Javadoc.addLine( "@param " + elemsName );
addAll2Javadoc.addLine( "\t\tThe " + elemsName + " to add." );
addAll2Javadoc.addLine( "@return" );
addAll2Javadoc.addLine( "\t\tThis {@code Builder}, for chaining." );
Block addAll2Block = builderBlock.newJavaBlock( "public Builder add" + cap( elemsName ) + "(" + listTypeName + "... " + elemsName + ")" );
addAll2Block.addLine( "return add" + cap( elemsName ) + "(Arrays.asList(" + elemsName + "));" );
builderBlock.addLine( );
if( lazyInit )
{
Block constructBlock;
constructBlock = setBlock.newJavaBlock( "if (result." + fieldName + " == null)" );
constructBlock.addLine( "result." + fieldName + " = new ArrayList<" + listTypeName + ">();" );
constructBlock = addBlock.newJavaBlock( "if (result." + fieldName + " == null)" );
constructBlock.addLine( "result." + fieldName + " = new ArrayList<" + listTypeName + ">();" );
constructBlock = addAllBlock.newJavaBlock( "if (result." + fieldName + " == null)" );
constructBlock.addLine( "result." + fieldName + " = new ArrayList<" + listTypeName + ">();" );
}
else
{
constructor.addLine( "result." + fieldName + " = new ArrayList<" + listTypeName + ">();" );
}
setBlock.addLine( "result." + fieldName + ".clear();" );
setBlock.addLine( "result." + fieldName + ".addAll(" + elemsName + ");" );
setBlock.addLine( "return this;" );
addBlock.addLine( "result." + fieldName + ".add(" + elemName + ");" );
addBlock.addLine( "return this;" );
addAllBlock.addLine( "result." + fieldName + ".addAll(" + elemsName + ");" );
addAllBlock.addLine( "return this;" );
}
else if( ftype.isAssignableFrom( HashMap.class ) )
{
if( !( field.getGenericType( ) instanceof ParameterizedType ) )
{
continue;
}
String mapKeyTypeName = getMapKeyTypeName( field );
String mapValueTypeName = getMapValueTypeName( field );
String entryName = getElementSingularName( field );
boolean lazyInit = field.getAnnotation( BuilderLazyInitialize.class ) != null;
Block addJavadoc = builderBlock.newJavadocBlock( );
addJavadoc.addLine( "Puts " + a( entryName ) + " entry into the result." );
addJavadoc.addLine( );
addJavadoc.addLine( "@param key" );
addJavadoc.addLine( "\t\tThe key for the entry." );
addJavadoc.addLine( "@param value" );
addJavadoc.addLine( "\t\tThe value for the entry." );
addJavadoc.addLine( "@return" );
addJavadoc.addLine( "\t\tThis {@code Builder}, for chaining." );
Block addBlock = builderBlock.newJavaBlock( "public Builder put" + cap( entryName ) + "(" + mapKeyTypeName + " key, " + mapValueTypeName + " value)" );
builderBlock.addLine( );
if( lazyInit )
{
Block constructBlock = addBlock.newJavaBlock( "if (result." + fieldName + " == null)" );
constructBlock.addLine( "result." + fieldName + " = new HashMap<" + mapKeyTypeName + ", " + mapValueTypeName + ">();" );
}
else
{
constructor.addLine( "result." + fieldName + " = new HashMap<" + mapKeyTypeName + ", " + mapValueTypeName + ">();" );
}
addBlock.addLine( "result." + fieldName + ".put(key, value);" );
addBlock.addLine( "return this;" );
}
else
{
Block setJavadoc = builderBlock.newJavadocBlock( );
String getter = "get";
if( boolean.class.equals( field.getType( ) ) )
{
getter = "is";
}
setJavadoc.addLine( "Sets the {@link " + typeName + "#" + getter + cap( fieldName ) + "() " + fieldName + "} of the result." );
setJavadoc.addLine( );
setJavadoc.addLine( "@param " + fieldName );
setJavadoc.addLine( "\t\tThe value for the {@code " + fieldName + "}." );
setJavadoc.addLine( "@return" );
setJavadoc.addLine( "\t\tThis {@code Builder}, for chaining." );
String paramTypeName = ftypeName;
String returnTypeName = ftypeName;
if( ftype.isArray( ) )
{
paramTypeName = toString( ftype.getComponentType( ) ) + "...";
returnTypeName = toString( ftype.getComponentType( ) ) + "[]";
}
Block setBlock = builderBlock.newJavaBlock( "public Builder " + fieldName + "(" + paramTypeName + " " + fieldName + ")" );
setBlock.addLine( "result." + fieldName + " = " + fieldName + ";" );
setBlock.addLine( "return this;" );
builderBlock.addLine( );
Block getJavadoc = builderBlock.newJavadocBlock( );
getJavadoc.addLine( "Gets the current {@link " + typeName + "#" + fieldName + " " + fieldName + "} of the result." );
getJavadoc.addLine( );
getJavadoc.addLine( "@return" );
getJavadoc.addLine( "\t\tThe current {@link " + typeName + "#" + fieldName + " " + fieldName + "} of the result." );
Block getBlock = builderBlock.newJavaBlock( "public " + returnTypeName + " " + fieldName + "()" );
getBlock.addLine( "return result." + fieldName + ";" );
builderBlock.addLine( );
}
}
Block createJavadoc = builderBlock.newJavadocBlock( );
createJavadoc.addLine( "Creates a new {@code " + typeName + "} with the properties that have been set on this {@code Builder}." );
createJavadoc.addLine( "<br>" );
createJavadoc.addLine( "<b>Note</b>: this method may only be called once. Afterward it will return {@code null}." );
createJavadoc.addLine( );
createJavadoc.addLine( "@return" );
createJavadoc.addLine( "\tA new {@code " + typeName + "} with the properties that have been set on this {@code Builder}." );
Block createBlock = builderBlock.newJavaBlock( "public " + typeName + " create()" );
if( type.getAnnotation( BuilderAllowNull.class ) == null )
{
for( Field field : fields )
{
if( field.getAnnotation( BuilderIgnore.class ) != null )
{
continue;
}
if( field.getAnnotation( BuilderAllowNull.class ) != null )
{
continue;
}
if( Modifier.isStatic( field.getModifiers( ) ) )
{
continue;
}
if( field.getType( ).equals( List.class ) )
{
continue;
}
if( field.getType( ).isPrimitive( ) )
{
continue;
}
String fieldName = field.getName( );
Block nullBlock = createBlock.newJavaBlock( "if (result." + fieldName + " == null)" );
nullBlock.addLine( "throw new IllegalStateException(\"" + fieldName + " must be non-null\");" );
}
}
for( Method m : validators )
{
createBlock.addLine( "result." + m.getName( ) + "();" );
}
createBlock.addLine( typeName + " result = this.result;" );
createBlock.addLine( "this.result = null;" );
createBlock.addLine( "return result;" );
}
result[ i ] = topBlock.toString( );
}
return result;
}
/**
* Finds the java file a generated builder belongs in, and attempts to insert the builder code, or replace the existing auto-generated types and methods if
* necessary.<br>
* <i>Requres JavaParser 1.0.8 or greater.</i><br>
* <br>
* This method is crazy. Finding and modifying the file is complicated, and this method makes a backup, but who knows what could go wrong. USE AT YOUR OWN
* RISK!
*
* @param builder
* a builder generated by {@code #generateBuilders(Class...)}.
* @throws Exception
* for any number of reasons...
*/
public static void writeBuilder( String builder ) throws Exception
{
CompilationUnit compunit = JavaParser.parse( new ByteArrayInputStream( builder.getBytes( ) ) );
for( TypeDeclaration typeDecl : compunit.getTypes( ) )
{
File javaFile = findJavaFile( typeDecl );
if( javaFile != null )
{
List<String> lines = insertBuilder( typeDecl , javaFile );
File backup = new File( javaFile + "_backup" );
File newFile = new File( javaFile + "_new" );
System.out.println( "Copying " + javaFile + " to " + backup );
copy( javaFile , backup );
System.out.println( "Writing " + newFile + "..." );
write( lines , newFile );
System.out.println( "Writing " + javaFile + "..." );
write( lines , javaFile );
System.out.println( "Deleting " + backup + "..." );
backup.delete( );
System.out.println( "Deleting " + newFile + "..." );
newFile.delete( );
}
}
}
private static void copy( File src , File dest ) throws Exception
{
InputStream in = new FileInputStream( src );
OutputStream out = new FileOutputStream( dest );
byte[ ] bytes = new byte[ 1024 ];
int count;
while( ( count = in.read( bytes ) ) >= 0 )
{
out.write( bytes , 0 , count );
}
out.flush( );
out.close( );
in.close( );
}
private static void write( List<String> lines , Writer writer ) throws Exception
{
for( String line : lines )
{
writer.write( line );
writer.write( "\n" );
}
}
private static void write( List<String> lines , File dest ) throws Exception
{
BufferedWriter writer = new BufferedWriter( new FileWriter( dest ) );
write( lines , writer );
writer.flush( );
writer.close( );
}
private static String[ ] getNestedClassNames( TypeDeclaration decl )
{
SingleMemberAnnotationExpr fullNameAnnot = ( SingleMemberAnnotationExpr ) decl.getAnnotations( ).get( 0 );
StringLiteralExpr fullNameExpr = ( StringLiteralExpr ) fullNameAnnot.getMemberValue( );
String fullName = fullNameExpr.getValue( );
int lastDot = fullName.lastIndexOf( '.' );
return fullName.substring( lastDot + 1 ).split( "\\$" );
}
private static File findJavaFile( TypeDeclaration builderParentDecl )
{
SingleMemberAnnotationExpr fullNameAnnot = ( SingleMemberAnnotationExpr ) builderParentDecl.getAnnotations( ).get( 0 );
StringLiteralExpr fullNameExpr = ( StringLiteralExpr ) fullNameAnnot.getMemberValue( );
String fullName = fullNameExpr.getValue( );
int lastDot = fullName.lastIndexOf( '.' );
String path = fullName.substring( 0 , lastDot ).replace( '.' , File.separatorChar );
String[ ] classNames = fullName.substring( lastDot + 1 ).split( "\\$" );
String filePath = path + File.separator + classNames[ 0 ] + ".java";
return FileFinder.findFile( filePath , new File( "applet/src" ) , 2 );
}
private static List<String> readLines( File file ) throws IOException
{
List<String> lines = new ArrayList<String>( );
BufferedReader reader = new BufferedReader( new FileReader( file ) );
String line;
while( ( line = reader.readLine( ) ) != null )
{
lines.add( line );
}
reader.close( );
return lines;
}
private static CompilationUnit parse( List<String> lines ) throws Exception
{
StringWriter writer = new StringWriter( );
write( lines , writer );
CompilationUnit result = JavaParser.parse( new ByteArrayInputStream( writer.getBuffer( ).toString( ).getBytes( ) ) );
return result;
}
private static List<String> insertBuilder( TypeDeclaration builderParentDecl , File javaFile ) throws Exception
{
List<String> lines = readLines( javaFile );
String[ ] classNames = getNestedClassNames( builderParentDecl );
List<BodyDeclaration> members = builderParentDecl.getMembers( );
for( int m = members.size( ) - 1 ; m >= 0 ; m-- )
{
BodyDeclaration decl = members.get( m );
// re-parse after every insertion, since the end line of the
// destination type will have changed
CompilationUnit compunit = parse( lines );
TypeDeclaration destType = find( compunit , classNames );
BodyDeclaration toReplace = null;
if( decl instanceof MethodDeclaration )
{
toReplace = find( destType , ( MethodDeclaration ) decl );
}
else if( decl instanceof TypeDeclaration )
{
toReplace = find( destType , ( ( TypeDeclaration ) decl ).getName( ) );
}
if( toReplace != null )
{
replace( lines , getTotalLineRegion( toReplace ) , decl.toString( ) );
}
else
{
insert( lines , destType.getEndLine( ) - 1 , destType.getEndColumn( ) - 1 , decl.toString( ) );
}
}
return lines;
}
private static TypeDeclaration find( CompilationUnit compunit , String[ ] nestedTypeNames )
{
for( TypeDeclaration decl : compunit.getTypes( ) )
{
if( decl.getName( ).equals( nestedTypeNames[ 0 ] ) )
{
if( nestedTypeNames.length == 1 )
{
return decl;
}
else
{
return find( decl , nestedTypeNames , 1 );
}
}
}
return null;
}
private static TypeDeclaration find( TypeDeclaration decl , String[ ] nestedTypeNames , int start )
{
TypeDeclaration nested = find( decl , nestedTypeNames[ start ] );
if( nested != null )
{
if( start == nestedTypeNames.length - 1 )
{
return nested;
}
else
{
return find( nested , nestedTypeNames , start + 1 );
}
}
return null;
}
private static TypeDeclaration find( TypeDeclaration decl , String nestedTypeName )
{
for( BodyDeclaration bodyDecl : decl.getMembers( ) )
{
if( bodyDecl instanceof TypeDeclaration && ( ( TypeDeclaration ) bodyDecl ).getName( ).equals( nestedTypeName ) )
{
return ( TypeDeclaration ) bodyDecl;
}
}
return null;
}
private static MethodDeclaration find( TypeDeclaration decl , MethodDeclaration analogous )
{
for( BodyDeclaration bodyDecl : decl.getMembers( ) )
{
if( bodyDecl instanceof MethodDeclaration )
{
MethodDeclaration methodDecl = ( MethodDeclaration ) bodyDecl;
if( methodDecl.getName( ).equals( analogous.getName( ) )
&& methodDecl.getModifiers( ) == analogous.getModifiers( )
&& sameTypes( methodDecl.getParameters( ) , analogous.getParameters( ) ) )
{
return methodDecl;
}
}
}
return null;
}
private static boolean sameTypes( List<Parameter> l1 , List<Parameter> l2 )
{
if( l1 == null && l2 == null )
{
return true;
}
if( l1.size( ) != l2.size( ) )
{
return false;
}
for( int i = 0 ; i < l1.size( ) ; i++ )
{
Parameter p1 = l1.get( i );
Parameter p2 = l2.get( i );
if( !p1.getType( ).toString( ).equals( p2.getType( ).toString( ) ) )
{
return false;
}
}
return true;
}
private static void replace( List<String> lines , LineRegion region , String replacement )
{
StringBuffer sb = new StringBuffer( );
String startLine = lines.get( region.startLine );
int startIndex = indexOfColumn( startLine , region.startColumn );
String endLine = lines.get( region.endLine );
int endIndex = indexOfColumn( endLine , region.endColumn );
sb.append( startLine.substring( 0 , startIndex ) );
sb.append( replacement );
sb.append( endLine.substring( endIndex + 1 ) );
for( int line = region.endLine ; line >= region.startLine ; line-- )
{
lines.remove( line );
}
String[ ] newLines = sb.toString( ).split( "(\r\n|\n\r|\n|\r)" );
for( int line = newLines.length - 1 ; line >= 0 ; line-- )
{
lines.add( region.startLine , newLines[ line ] );
}
}
private static void insert( List<String> lines , int line , int column , String replacement )
{
StringBuffer sb = new StringBuffer( );
String lineStr = lines.get( line );
int index = indexOfColumn( lineStr , column );
sb.append( lineStr.substring( 0 , index ) );
sb.append( replacement );
sb.append( lineStr.substring( index ) );
lines.remove( line );
String[ ] newLines = sb.toString( ).split( "(\r\n|\n\r|\n|\r)" );
for( int newLine = newLines.length - 1 ; newLine >= 0 ; newLine-- )
{
lines.add( line , newLines[ newLine ] );
}
}
private static int indexOfColumn( String s , int column )
{
int curColumn = 0;
int i;
for( i = 0 ; i < s.length( ) && curColumn < column ; i++ )
{
if( s.charAt( i ) == '\t' )
{
curColumn += 8;
}
else
{
curColumn++ ;
}
}
return i;
}
private static LineRegion getLineRegion( Node node )
{
return new LineRegion( node.getBeginLine( ) - 1 , node.getBeginColumn( ) - 1 , node.getEndLine( ) - 1 , node.getEndColumn( ) - 1 );
}
private static LineRegion getTotalLineRegion( BodyDeclaration decl )
{
LineRegion region = getLineRegion( decl );
if( decl.getJavaDoc( ) != null )
{
region = region.union( getLineRegion( decl.getJavaDoc( ) ) );
}
if( decl.getAnnotations( ) != null )
{
for( AnnotationExpr annotation : decl.getAnnotations( ) )
{
region = region.union( getLineRegion( annotation ) );
}
}
return region;
}
}