/* * Copyright 2013 Guidewire Software, Inc. */ package gw.internal.gosu.parser.java.classinfo; import gw.internal.ext.org.antlr.runtime.ANTLRStringStream; import gw.internal.ext.org.antlr.runtime.CharStream; import gw.internal.ext.org.antlr.runtime.Token; import gw.internal.gosu.parser.AnnotationInfoFactoryImpl; import gw.internal.gosu.parser.FieldJavaClassField; import gw.internal.gosu.parser.GosuParser; import gw.internal.gosu.parser.Symbol; import gw.internal.gosu.parser.TypeLord; import gw.internal.gosu.parser.TypeUsesMap; import gw.internal.gosu.parser.java.JavaLexer; import gw.lang.parser.GosuParserFactory; import gw.lang.parser.IExpression; import gw.lang.parser.ISymbol; import gw.lang.parser.ISymbolTable; import gw.lang.parser.ITypeUsesMap; import gw.lang.parser.TypelessScriptPartId; import gw.lang.parser.exceptions.ParseResultsException; import gw.lang.reflect.IFeatureInfo; import gw.lang.reflect.IPropertyInfo; import gw.lang.reflect.IType; import gw.lang.reflect.TypeSystem; import gw.lang.reflect.java.ICompileTimeConstantValue; import gw.lang.reflect.java.IJavaClassField; import gw.lang.reflect.java.IJavaClassInfo; import gw.util.GosuExceptionUtil; import java.lang.annotation.Annotation; import java.lang.reflect.Array; import java.lang.reflect.Modifier; import java.util.Collections; import java.util.List; /** */ public class CompileTimeExpressionParser { public static IExpression parse(String text, IJavaClassInfo enclosingType, IType resultType) { if( text.endsWith( ".class" ) ) { text = text.substring( 0, text.lastIndexOf( ".class" ) ); } ITypeUsesMap usesMap = null; List<String> staticImports = null; IJavaClassInfo outerMostEnclosingType = TypeLord.getOuterMostEnclosingClass(enclosingType); if (outerMostEnclosingType instanceof JavaSourceType) { usesMap = ((JavaSourceType) outerMostEnclosingType).getTypeUsesMap().copy(); staticImports = ((JavaSourceType)outerMostEnclosingType).getStaticImports(); addInnerClassNames( enclosingType, usesMap ); } else { usesMap = new TypeUsesMap(); staticImports = Collections.emptyList(); } usesMap.addToDefaultTypeUses("gw.lang."); TypeSystem.pushIncludeAll(); addEnclosingPackages(usesMap, enclosingType ); try { text = Java7ToGosuLexicalConversion(text); GosuParser scriptParser = (GosuParser)GosuParserFactory.createParser( text ); maybePushEnumTypes( scriptParser.getSymbolTable(), resultType ); pushLocalConstants( scriptParser.getSymbolTable(), enclosingType ); pushStaticImports( scriptParser.getSymbolTable(), staticImports, enclosingType ); scriptParser.setTypeUsesMap( usesMap ); IExpression expr = scriptParser.parseExpOrProgram( new TypelessScriptPartId("compile-time annotation eval" ), resultType, false, false ); return expr; } catch (ParseResultsException e) { throw GosuExceptionUtil.forceThrow( e ); } finally { TypeSystem.popIncludeAll(); } } private static void addInnerClassNames( IJavaClassInfo enclosingType, ITypeUsesMap usesMap ) { if( enclosingType == null ) { return; } usesMap.addToTypeUses( enclosingType.getName() ); for( IJavaClassInfo jci : enclosingType.getDeclaredClasses() ) { usesMap.addToTypeUses( jci.getName() ); } addInnerClassNames( enclosingType.getEnclosingClass(), usesMap ); } private static String Java7ToGosuLexicalConversion(String src) { CharStream cs = new ANTLRStringStream(src); JavaLexer lexer = new JavaLexer(cs); Token t = lexer.nextToken(); StringBuilder sb = new StringBuilder(); while(t.getType() != Token.EOF) { String text = t.getText(); if(t.getType() != JavaLexer.IDENTIFIER && t.getType() != JavaLexer.STRINGLITERAL && t.getType() != JavaLexer.CHARLITERAL) { text = text.replaceAll("_", ""); } if(t.getType() == JavaLexer.BINLITERAL) { String binaryString = text.toLowerCase(); int s = 2; int e = binaryString.length(); if(binaryString.charAt(e-1) == 'l') { e--; } binaryString = binaryString.substring(s, e); text = Long.valueOf(binaryString, 2).toString(); } if( t.getType() == JavaLexer.LONGLITERAL || t.getType() == JavaLexer.INTLITERAL) { String number = text.toLowerCase(); int s = 0; int e = number.length(); if(e >= 2 && number.charAt(0) == '0' && number.charAt(1) != 'x') { s = 1; if(number.charAt(e-1) == 'l') { e--; } if( s < e ) { number = number.substring(s, e); text = Long.valueOf(number, 8).toString(); } } } sb.append(" "); sb.append(text); t = lexer.nextToken(); } src = sb.toString(); return src; } private static void pushLocalConstants(ISymbolTable symbolTable, IJavaClassInfo enclosingClass) { for(IJavaClassField field : enclosingClass.getDeclaredFields() ) { symbolTable.putSymbol( new CompileTimeFieldSymbol( field ) ); } } private static void pushStaticImports(ISymbolTable symbolTable, List<String> staticImports, IJavaClassInfo enclosingType) { for( String imp : staticImports ) { IJavaClassInfo javaClassInfo = TypeSystem.getJavaClassInfo(imp, enclosingType.getModule()); if (javaClassInfo != null) { for(IJavaClassField field : javaClassInfo.getFields() ) { symbolTable.putSymbol( new CompileTimeFieldSymbol( field ) ); } } else { int endIndex = imp.lastIndexOf('.'); String typeName = imp.substring(0, endIndex); String fieldName = imp.substring(endIndex + 1); javaClassInfo = TypeSystem.getJavaClassInfo(typeName, enclosingType.getModule()); for(IJavaClassField field : javaClassInfo.getFields() ) { if (field.getName().equals(fieldName)) { symbolTable.putSymbol( new CompileTimeFieldSymbol( field ) ); } } } } } private static void maybePushEnumTypes(ISymbolTable symbolTable, IType returnType) { if( !returnType.isEnum() ) { return; } for(IPropertyInfo pi : returnType.getTypeInfo().getProperties() ) { if( pi.isStatic() && pi.isPublic() ) { symbolTable.putSymbol( new Symbol(pi.getName(), pi.getFeatureType(), null) ); } } } private static void addEnclosingPackages(ITypeUsesMap map, IJavaClassInfo type) { map.addToDefaultTypeUses( type.getNamespace() + "."); if (type.getEnclosingType() != null) { addEnclosingPackages(map, type.getEnclosingClass()); } } public static class CompileTimeFieldSymbol extends Symbol implements ICompileTimeConstantValue { private IJavaClassField _field; public CompileTimeFieldSymbol( IJavaClassField field ) { super( field.getName(), field.getType().getJavaType(), null ); _field = field; } public IJavaClassField getField() { return _field; } public boolean isCompileTimeConstantValue() { return Modifier.isStatic( getField().getModifiers() ) && Modifier.isFinal( getField().getModifiers() ); } @Override public ISymbol getLightWeightReference() { return this; } @Override public Object doCompileTimeEvaluation() { IJavaClassField field = getField(); if( field instanceof JavaSourceField ) { String rhs = ((JavaSourceField)field).getRhs(); IExpression expr = CompileTimeExpressionParser.parse(rhs, field.getEnclosingClass(), field.getType().getJavaType()); return expr.evaluate(); } else if( field instanceof FieldJavaClassField ) { try { Object value = ((FieldJavaClassField)field).get( null ); return convertValueToInfoFriendlyValue( value, _field.getEnclosingClass().getJavaType().getTypeInfo() ); } catch( IllegalAccessException e ) { throw new RuntimeException( e ); } } else { throw new IllegalStateException( "Unexpected field type: " + field ); } } } public static Object convertValueToInfoFriendlyValue( Object value, IFeatureInfo enclosingType ) { if( value == null ) { return null; } if( value instanceof Enum ) { return ((Enum)value).name(); } if( value.getClass().isArray() && !IType.class.isAssignableFrom( value.getClass().getComponentType() ) ) { Object arrayValue = null; for( int i = 0; i < Array.getLength( value ); i++ ) { //noinspection RedundantCast Object elemValue = convertValueToInfoFriendlyValue( Array.get( value, i ), enclosingType ); if( arrayValue == null ) { arrayValue = Array.newInstance( elemValue.getClass(), Array.getLength( value ) ); } Array.set( arrayValue, i, elemValue ); } if( arrayValue == null ) { arrayValue = value; } return arrayValue; } if( value instanceof Annotation ) { return AnnotationInfoFactoryImpl.instance().createJavaAnnotation( (Annotation)value, enclosingType ); } return value; } }