/*
* Copyright 2013 Guidewire Software, Inc.
*/
package gw.internal.gosu.parser;
import gw.config.CommonServices;
import gw.internal.gosu.annotations.AnnotationMap;
import gw.internal.gosu.parser.expressions.BeanMethodCallExpression;
import gw.internal.gosu.parser.expressions.Identifier;
import gw.internal.gosu.parser.expressions.ImplicitTypeAsExpression;
import gw.internal.gosu.parser.expressions.NewExpression;
import gw.internal.gosu.parser.expressions.NotAWordExpression;
import gw.internal.gosu.parser.expressions.StringLiteral;
import gw.internal.gosu.parser.expressions.TypeLiteral;
import gw.internal.gosu.parser.statements.BeanMethodCallStatement;
import gw.internal.gosu.parser.statements.ReturnStatement;
import gw.internal.gosu.parser.statements.StatementList;
import gw.internal.gosu.parser.statements.VarStatement;
import gw.lang.parser.StandardSymbolTable;
import gw.lang.parser.expressions.IVarStatement;
import gw.lang.reflect.IConstructorInfo;
import gw.lang.reflect.IErrorType;
import gw.lang.reflect.IMethodInfo;
import gw.lang.reflect.IParameterInfo;
import gw.lang.reflect.IPropertyInfo;
import gw.lang.reflect.IRelativeTypeInfo;
import gw.lang.reflect.IType;
import gw.lang.reflect.TypeSystem;
import gw.lang.reflect.TypeSystemShutdownListener;
import gw.lang.reflect.gs.IGosuClass;
import gw.lang.reflect.java.IJavaType;
import gw.lang.reflect.java.JavaTypes;
import gw.util.concurrent.LockingLazyVar;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
public class AnnotationBuilder
{
private static final int MAX_STMTS_PER_EVAL_METHOD = 1000;
private static final String ANNOTATION_MAP_BUILDER_VAR_NAME = "builder";
public static final LockingLazyVar<Symbol> BUILDER_SYMBOL = new LockingLazyVar<Symbol>() {
protected Symbol init() {
return new Symbol(ANNOTATION_MAP_BUILDER_VAR_NAME, annotationMapType(), null) {
@Override
public int getIndex() {
return 42; // hack to make this look like a local symbol
}
};
}
};
static {
TypeSystem.addShutdownListener(new TypeSystemShutdownListener() {
public void shutdown() {
BUILDER_SYMBOL.clear();
}
});
}
public static List<StatementList> buildAnnotationInitMethodBody( ICompilableTypeInternal gosuClass, boolean bClearOnly )
{
if( bClearOnly && !((IGosuClass)gosuClass).isDeclarationsCompiled() )
{
// Don't parse unparsed class if we're clearing
return null;
}
if( gosuClass.getName().startsWith( IGosuClass.PROXY_PREFIX ) )
{
return Collections.emptyList();
}
List<Statement> stmts = new ArrayList<Statement>();
if( !bClearOnly )
{
stmts.add( initAnnotationMap() );
}
// class annotations
List<? extends IGosuAnnotation> annotations = gosuClass.getGosuAnnotations();
addAnnotations( annotations, GosuClass.CLASS_ANNOTATION_SLOT, stmts, bClearOnly );
// field annotations
for( IVarStatement varStmt : gosuClass.getMemberFields() )
{
annotations = ((VarStatement)varStmt).getAnnotations();
addAnnotations( annotations, varStmt.getSymbol().getName(), stmts, bClearOnly );
}
// static field annotations
for( IVarStatement varStmt : gosuClass.getStaticFields() )
{
annotations = ((VarStatement)varStmt).getAnnotations();
addAnnotations( annotations, varStmt.getSymbol().getName(), stmts, bClearOnly );
}
// constructor annotations
for( IConstructorInfo constructorInfo : gosuClass.getTypeInfo().getDeclaredConstructors() )
{
if( constructorInfo instanceof GosuConstructorInfo )
{
annotations = ((GosuConstructorInfo)constructorInfo).getGosuAnnotations();
addAnnotations( annotations, constructorInfo.getName(), stmts, bClearOnly);
}
}
// method annotations
for( IMethodInfo methodInfo : gosuClass.getTypeInfo().getDeclaredMethods() )
{
if( methodInfo instanceof GosuMethodInfo )
{
annotations = ((GosuMethodInfo)methodInfo).getGosuAnnotations();
addAnnotations( annotations, methodInfo.getName(), stmts, bClearOnly );
}
}
// property annotations
for( IPropertyInfo propertyInfo : gosuClass.getTypeInfo().getDeclaredProperties() )
{
if( propertyInfo instanceof GosuPropertyInfo )
{
annotations = ((GosuPropertyInfo)propertyInfo).getGosuAnnotations();
addAnnotations( annotations, propertyInfo.getName(), stmts, bClearOnly );
}
}
if( !bClearOnly )
{
//assign to the static class variable
stmts.add( makeReturnStatement( gosuClass ) );
return makeStmtList( gosuClass, stmts );
}
else
{
return null;
}
}
private static Statement makeReturnStatement( ICompilableTypeInternal gosuClass )
{
ReturnStatement statement = initLocation( new ReturnStatement() );
statement.setValue( invokeExpr( "getAnnotations", new Class[0], JavaTypes.MAP() ) );
return statement;
}
private static VarStatement initAnnotationMap()
{
VarStatement varStatement = initLocation( new VarStatement() );
varStatement.setSymbol( BUILDER_SYMBOL.get() );
varStatement.setType( annotationMapType() );
NewExpression newMap = initLocation( new NewExpression() );
newMap.setConstructor( annotationMapType().getTypeInfo().getConstructor() );
newMap.setType( annotationMapType() );
varStatement.setAsExpression( newMap );
return varStatement;
}
private static List<StatementList> makeStmtList( ICompilableTypeInternal gosuClass, List<Statement> statements )
{
if( statements.isEmpty() )
{
return Collections.emptyList();
}
else
{
ArrayList<StatementList> lst = new ArrayList<StatementList>();
int i = 0;
while( i < statements.size() )
{
StatementList stmt = new StatementList( new StandardSymbolTable() );
int length = Math.min( MAX_STMTS_PER_EVAL_METHOD, statements.size() - i );
List<Statement> statementList = statements.subList( i, i + length );
lst.add( stmt );
i+= MAX_STMTS_PER_EVAL_METHOD;
stmt.setStatements( statementList );
}
return lst;
}
}
private static Expression box( Expression value )
{
ImplicitTypeAsExpression cast = initLocation( new ImplicitTypeAsExpression() );
cast.setLHS( value );
cast.setCoercer( CommonServices.getCoercionManager().resolveCoercerStatically( JavaTypes.OBJECT(), value.getType() ) );
cast.setType( TypeSystem.getBoxType( value.getType() ) );
value = cast;
return value;
}
private static LinkedHashMap<String, Expression> createArgMap( IGosuAnnotation annotation )
{
LinkedHashMap<String, Expression> args = new LinkedHashMap<String, Expression>();
if (annotation.getExpression() instanceof NotAWordExpression) {
System.out.println("XXXDEBUG: " + annotation.getName());
System.out.println("XXXDEBUG: " + annotation.getOwnersType());
System.out.println("XXXDEBUG: " + annotation.getType());
}
NewExpression newExpression = (NewExpression)annotation.getExpression();
IParameterInfo[] parameters = newExpression.getConstructor().getParameters();
Expression[] argArr = newExpression.getArgs();
if( argArr != null )
{
for( int i = 0; i < argArr.length; i++ )
{
String name = parameters[i].getName();
Expression arg = argArr[i];
args.put( name, arg );
}
}
return args;
}
private static String[] getNamesForJavaAnnotation( IType type )
{
IRelativeTypeInfo rTypeInfo = (IRelativeTypeInfo)type.getTypeInfo();
List<IMethodInfo> methodInfos = new ArrayList<IMethodInfo>( rTypeInfo.getDeclaredMethods() );
Collections.sort( methodInfos, new Comparator<IMethodInfo>()
{
@Override
public int compare( IMethodInfo o1, IMethodInfo o2 )
{
return o1.getName().compareTo( o2.getName() );
}
} );
String[] strings = new String[methodInfos.size()];
for( int i = 0; i < methodInfos.size(); i++ )
{
IMethodInfo methodInfo = methodInfos.get( i );
strings[i] = methodInfo.getDisplayName();
}
return strings;
}
private static <T extends ParsedElement> T initLocation( T pe )
{
pe.setLocation( new ParseTree( pe, 0, 0, null ) );
return pe;
}
private static void debug( String s )
{
System.out.println( s );
}
private static void addAnnotations( List<? extends IGosuAnnotation> annotations, String annotationSlot, List<Statement> stmts, boolean bClearOnly )
{
if( annotations.isEmpty() )
{
return;
}
stmts.add( invoke( "startAnnotationInfoForFeature", new Class[]{String.class}, new StringLiteral( annotationSlot ) ) );
for( IGosuAnnotation annotation : annotations )
{
//java annotation
IType type = annotation.getType();
// Skip this type entirely if it's an error type
if (type instanceof IErrorType) {
continue;
}
if( type != null && !JavaTypes.IANNOTATION().isAssignableFrom(type) )
{
// Java Annotations
if( !bClearOnly )
{
stmts.add( invoke( "startJavaAnnotation", new Class[]{IType.class}, initLocation( new TypeLiteral( type ) ) ) );
LinkedHashMap<String, Expression> argMap = createArgMap( annotation );
for( String name : argMap.keySet() )
{
Expression value = argMap.get( name );
if( value.getType().isPrimitive() )
{
value = box( value );
}
stmts.add( invoke( "withArg", new Class[]{String.class, Object.class}, initLocation( new StringLiteral( name ) ), value ) );
}
stmts.add( invoke( "finishJavaAnnotation", new Class[0] ) );
}
}
else
{
// Gosu Annotations
Expression expression = (Expression) annotation.getExpression();
if( expression == null )
{
throw new IllegalStateException( "The expression cannot be null when generating the annotation map.\n" +
"This is probably caused by a type not being compiled fully or the " +
"expression was cleared prematurely." );
}
else
{
stmts.add( invoke( "addGosuAnnotation", new Class[]{Object.class}, expression ) );
}
}
}
}
private static Statement invoke( String name, Class[] argTypes, Expression... args )
{
BeanMethodCallStatement bmcs = initLocation( new BeanMethodCallStatement() );
bmcs.setBeanMethodCall( invokeExpr( name, argTypes, annotationMapType(), args ) );
initLocation( bmcs );
return bmcs;
}
private static BeanMethodCallExpression invokeExpr( String name, Class[] argTypes, IJavaType returnType, Expression... args )
{
BeanMethodCallExpression bmce = initLocation( new BeanMethodCallExpression() );
bmce.setArgs( args );
bmce.setType( returnType );
IRelativeTypeInfo typeInfo = (IRelativeTypeInfo)((IJavaType)annotationMapType()).getTypeInfo();
IType[] types = new IType[argTypes.length];
for( int i = 0; i < argTypes.length; i++ )
{
types[i] = TypeSystem.get( argTypes[i] );
}
bmce.setMethodDescriptor( typeInfo.getMethod( annotationMapType(), name, types ) );
Identifier builderSymbol = initLocation( new Identifier() );
builderSymbol.setSymbol( BUILDER_SYMBOL.get(), new StandardSymbolTable() );
builderSymbol.setType( annotationMapType() );
bmce.setRootExpression( builderSymbol );
return bmce;
}
private static IJavaType annotationMapType()
{
return JavaTypes.getGosuType( AnnotationMap.class );
}
}