/* * Copyright 2013 Guidewire Software, Inc. */ package gw.internal.gosu.template; import gw.internal.gosu.parser.CommonSymbolsScope; import gw.internal.gosu.parser.CompiledGosuClassSymbolTable; import gw.internal.gosu.parser.ContextInferenceManager; import gw.internal.gosu.parser.DynamicFunctionSymbol; import gw.internal.gosu.parser.GosuParser; import gw.internal.gosu.parser.IGosuClassInternal; import gw.internal.gosu.parser.IGosuEnhancementInternal; import gw.internal.gosu.parser.Symbol; import gw.internal.gosu.parser.TypeLoaderAccess; import gw.internal.gosu.parser.expressions.BlockExpression; import gw.internal.gosu.parser.expressions.Identifier; import gw.internal.gosu.parser.expressions.Program; import gw.lang.parser.ExternalSymbolMapForMap; import gw.lang.parser.GosuParserFactory; import gw.lang.parser.GosuParserTypes; import gw.lang.parser.IExpression; import gw.lang.parser.IFunctionSymbol; import gw.lang.parser.IGosuParser; import gw.lang.parser.IParseTree; import gw.lang.parser.IScriptPartId; import gw.lang.parser.ISymbol; import gw.lang.parser.ISymbolTable; import gw.lang.parser.ITypeUsesMap; import gw.lang.parser.Keyword; import gw.lang.parser.ScriptPartId; import gw.lang.parser.ScriptabilityModifiers; import gw.lang.parser.TypelessScriptPartId; import gw.lang.parser.exceptions.ParseResultsException; import gw.lang.parser.expressions.IProgram; import gw.lang.parser.resources.Res; import gw.lang.parser.template.IEscapesAllContent; import gw.lang.parser.template.ITemplateGenerator; import gw.lang.parser.template.StringEscaper; import gw.lang.parser.template.TemplateParseException; import gw.lang.reflect.FunctionType; import gw.lang.reflect.IType; import gw.lang.reflect.TypeSystem; import gw.lang.reflect.TypeSystemShutdownListener; import gw.lang.reflect.gs.GosuClassTypeLoader; import gw.lang.reflect.gs.IExternalSymbolMap; import gw.lang.reflect.gs.IGosuClass; import gw.lang.reflect.gs.IGosuEnhancement; import gw.lang.reflect.java.JavaTypes; import gw.util.GosuClassUtil; import gw.util.GosuEscapeUtil; import gw.util.GosuStringUtil; import gw.util.Stack; import gw.util.StreamUtil; import gw.util.concurrent.LockingLazyVar; import java.io.IOException; import java.io.Reader; import java.io.Writer; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; /** * A template generator employing Gosu. * <p/> * Works much like JSP -- uses <% script %> for scriptlets and <%= expr %> for expressions. * Also supports JSP comments like this: <%-- comment here --%> * <p/> * Templates can be any type e.g., XML, HTML, text, whatever. */ public class TemplateGenerator implements ITemplateGenerator { public static final String GS_TEMPLATE = "GsTemplate"; public static final String GS_TEMPLATE_PARSED = "GsTemplateParsed"; public static final String SCRIPTLET_BEGIN = "<%"; public static final String SCRIPTLET_END = "%>"; public static final char EXPRESSION_SUFFIX = '='; public static final char DECLARATION_SUFFIX = '!'; public static final char DIRECTIVE_SUFFIX = '@'; public static final String COMMENT_BEGIN = "<%--"; public static final String COMMENT_END = "--%>"; public static final String ALTERNATE_EXPRESSION_BEGIN = "${"; public static final String ALTERNATE_EXPRESSION_END = "}"; public static final char ESCAPED_SCRIPTLET_MARKER = '\uffe0'; public static final char ESCAPED_SCRIPTLET_BEGIN_CHAR = '\uffe1'; public static final char ESCAPED_ALTERNATE_EXPRESSION_BEGIN_CHAR = '\uffe2'; public static final int SCRIPTLET_BEGIN_LEN = SCRIPTLET_BEGIN.length(); public static final int SCRIPTLET_END_LEN = SCRIPTLET_END.length(); public static final int COMMENT_BEGIN_LEN = COMMENT_BEGIN.length(); public static final int COMMENT_END_LEN = COMMENT_END.length(); public static final int ALTERNATE_EXPRESSION_BEGIN_LEN = ALTERNATE_EXPRESSION_BEGIN.length(); public static final int ALTERNATE_EXPRESSION_END_LEN = ALTERNATE_EXPRESSION_END.length(); public static final LockingLazyVar<ISymbol> PRINT_CONTENT_SYMBOL = new LockingLazyVar<ISymbol>() { protected ISymbol init() { try { return new Symbol( PRINT_METHOD, new FunctionType( PRINT_METHOD, GosuParserTypes.NULL_TYPE(), new IType[]{GosuParserTypes.STRING_TYPE(), GosuParserTypes.BOOLEAN_TYPE()} ), TemplateGenerator.class.getMethod( PRINT_METHOD, String.class, Boolean.TYPE ) ); } catch (NoSuchMethodException e) { throw new RuntimeException( e ); } } }; public static final LockingLazyVar<ISymbol> PRINT_RANGE_SYMBOL = new LockingLazyVar<ISymbol>() { protected ISymbol init() { try { return new Symbol( PRINT_RANGE_METHOD, new FunctionType( PRINT_RANGE_METHOD, GosuParserTypes.NULL_TYPE(), new IType[]{JavaTypes.pINT(), JavaTypes.pINT()} ), TemplateGenerator.class.getMethod( PRINT_RANGE_METHOD, int.class, int.class ) ); } catch (NoSuchMethodException e) { throw new RuntimeException( e ); } } }; static { TypeSystem.addShutdownListener(new TypeSystemShutdownListener() { public void shutdown() { PRINT_CONTENT_SYMBOL.clear(); PRINT_RANGE_SYMBOL.clear(); } }); } private static ThreadLocal<Stack<RuntimeData>> g_runtimeData = new ThreadLocal<Stack<RuntimeData>>(); private String _fqn; private String _scriptStr; private List<ISymbol> _params = new ArrayList<ISymbol>(); private Program _program; private ISymbolTable _compileTimeSymbolTable; private IType _supertype; private boolean _useStudioEditorParser; private boolean _disableAlternative; private boolean _hasOwnSymbolScope; private ContextInferenceManager _ctxInferenceMgr; private boolean _bStringLiteralTemplate; /** * Generates a template of any format having embedded Gosu. * * @param readerTemplate The source of the template. * @param writerOut Where the output should go. * @param symTable The symbol table to use. * @throws TemplateParseException on execution exception */ public static void generateTemplate( Reader readerTemplate, Writer writerOut, ISymbolTable symTable) throws TemplateParseException { generateTemplate(readerTemplate, writerOut, symTable, false); } /** * Generates a template of any format having embedded Gosu. * * @param readerTemplate The source of the template. * @param writerOut Where the output should go. * @param symTable The symbol table to use. * @param strict whether to allow althernative template * @throws TemplateParseException on execution exception */ public static void generateTemplate( Reader readerTemplate, Writer writerOut, ISymbolTable symTable, boolean strict) throws TemplateParseException { TemplateGenerator te = new TemplateGenerator( readerTemplate); te.setDisableAlternative(strict); te.execute( writerOut, symTable); } public static TemplateGenerator getTemplate( Reader readerTemplate ) { return new TemplateGenerator( readerTemplate ); } public static TemplateGenerator getTemplate(Reader readerTemplate, String fullyQualifiedName) { TemplateGenerator template = getTemplate(readerTemplate); template.setFqn(fullyQualifiedName); template.setHasOwnSymbolScope(true); return template; } private void setHasOwnSymbolScope(boolean hasSymbolScope) { _hasOwnSymbolScope = hasSymbolScope; } public Program getProgram() { return _program; } private static RuntimeData getRuntimeData() { Stack stack = g_runtimeData.get(); if( stack != null && stack.size() > 0 ) { return (RuntimeData)stack.peek(); } return null; } private static void pushRuntimeData( RuntimeData pair ) { Stack<RuntimeData> stack = g_runtimeData.get(); if( stack == null ) { g_runtimeData.set( stack = new Stack<RuntimeData>() ); } stack.push( pair ); } private static void popRuntimeData() { Stack<RuntimeData> stack = g_runtimeData.get(); stack.pop(); } /** * WARNING: This will consume the reader and close it! * @param reader the reader containing the template */ private TemplateGenerator( Reader reader ) { try { _scriptStr = StreamUtil.getContent(reader); } catch (IOException e) { throw new RuntimeException(e); } } /** * @throws TemplateParseException */ public void execute( Writer writer, ISymbolTable symbolTable) throws TemplateParseException { execute(writer, null, symbolTable); } /** * @throws TemplateParseException */ public void execute( Writer writer, StringEscaper escaper, ISymbolTable symTable) throws TemplateParseException { symTable.pushScope(); String strCompiledSource = null; try { symTable.putSymbol( PRINT_CONTENT_SYMBOL.get() ); symTable.putSymbol( PRINT_RANGE_SYMBOL.get() ); if(_supertype != null) { symTable.putSymbol(new Symbol("this", _supertype, null)); } pushRuntimeData( new RuntimeData( writer, escaper ) ); try { if( _program == null ) { TypeSystem.lock(); try { if( _program == null ) { List<TemplateParseException> exceptions = new ArrayList<TemplateParseException>(); strCompiledSource = transformTemplate(_scriptStr, exceptions); if (!exceptions.isEmpty()) { throw exceptions.get(0); } _program = compile( new java.util.Stack<IScriptPartId>(), strCompiledSource, symTable, new HashMap<String, Set<IFunctionSymbol>>(), null, null, null ); _compileTimeSymbolTable = symTable.copy(); } if( _fqn == null ) { _program.getGosuProgram().setThrowaway( true ); } } finally { TypeSystem.unlock(); } } _program.evaluate(extractExternalSymbols( _compileTimeSymbolTable, symTable )); } finally { popRuntimeData(); } } catch( ParseResultsException e ) { throw new TemplateParseException( e, strCompiledSource ); } finally { symTable.popScope(); } } public void compile(ISymbolTable symTable) throws TemplateParseException { compile(new java.util.Stack<IScriptPartId>(), symTable, new HashMap<String, Set<IFunctionSymbol>>(), null, null, _ctxInferenceMgr); } public void compile( java.util.Stack<IScriptPartId> scriptPartIdStack, ISymbolTable symTable, Map<String, Set<IFunctionSymbol>> dfsDeclByName, ITypeUsesMap typeUsesMap, Stack<BlockExpression> blocks, ContextInferenceManager ctxInferenceMgr) throws TemplateParseException { symTable.pushScope(); if (ctxInferenceMgr != null) { ctxInferenceMgr.suspendRefCollection(); } String strCompiledSource = null; try { symTable.putSymbol( PRINT_CONTENT_SYMBOL.get() ); symTable.putSymbol( PRINT_RANGE_SYMBOL.get() ); if( _program == null ) { TypeSystem.lock(); try { if( _program == null ) { List<TemplateParseException> exceptions = new ArrayList<TemplateParseException>(); strCompiledSource = transformTemplate(_scriptStr, exceptions); if (!exceptions.isEmpty()) { throw exceptions.get(0); } for (ISymbol param : _params) { Symbol s = new Symbol( param.getName(), param.getType(), null ); symTable.putSymbol(s); } _program = compile( scriptPartIdStack, strCompiledSource, symTable, dfsDeclByName, typeUsesMap, blocks, ctxInferenceMgr ); _compileTimeSymbolTable = symTable.copy(); } } finally { TypeSystem.unlock(); } } } catch( ParseResultsException e ) { throw new TemplateParseException( e, strCompiledSource ); } finally { symTable.popScope(); if (ctxInferenceMgr != null) { ctxInferenceMgr.resumeRefCollection(); } } } /** * @return the program to execute * @throws gw.lang.parser.exceptions.ParseException * */ private Program compile( java.util.Stack<IScriptPartId> scriptPartIdStack, String strCompiledSource, ISymbolTable symbolTable, Map<String, Set<IFunctionSymbol>> dfsDeclByName, ITypeUsesMap typeUsesMap, Stack<BlockExpression> blocks, ContextInferenceManager ctxInferenceMgr) throws ParseResultsException { IGosuParser parser = GosuParserFactory.createParser(symbolTable, ScriptabilityModifiers.SCRIPTABLE); for( IScriptPartId id: scriptPartIdStack ) { ((GosuParser)parser).pushScriptPart( id ); } parser.setScript( strCompiledSource ); if(parser instanceof GosuParser) { parser.setDfsDeclInSetByName(dfsDeclByName); if (ctxInferenceMgr != null) { ((GosuParser) parser).setContextInferenceManager(ctxInferenceMgr); } } if (typeUsesMap == null) { typeUsesMap = parser.getTypeUsesMap(); } else { parser.setTypeUsesMap(typeUsesMap); } if(_fqn != null) { typeUsesMap.addToTypeUses(GosuClassUtil.getPackage(_fqn) + ".*"); } if( blocks != null ) { ((GosuParser) parser).setBlocks( blocks ); } if(_supertype != null && _supertype instanceof IGosuClassInternal ) { IGosuClassInternal supertype = (IGosuClassInternal) _supertype; addStaticSymbols(symbolTable, (GosuParser) parser, supertype); List<? extends GosuClassTypeLoader> typeLoaders = TypeSystem.getCurrentModule().getTypeLoaders(GosuClassTypeLoader.class); for (GosuClassTypeLoader typeLoader : typeLoaders) { List<? extends IGosuEnhancement> enhancementsForType = typeLoader.getEnhancementIndex().getEnhancementsForType(supertype); for (IGosuEnhancement enhancement : enhancementsForType) { if (enhancement instanceof IGosuEnhancementInternal) { addStaticSymbols(symbolTable, (GosuParser) parser, (IGosuEnhancementInternal) enhancement); } } } // clear out private DFS that may have made their way into the dfsDecldsByName (jove this is ugly) for (Entry<String, Set<IFunctionSymbol>> dfsDecls : parser.getDfsDecls().entrySet()) { for (Iterator<IFunctionSymbol> it = dfsDecls.getValue().iterator(); it.hasNext(); ) { IFunctionSymbol fs = it.next(); if (fs instanceof Symbol && fs.isPrivate()) { it.remove(); } } } } IScriptPartId scriptPart; ISymbol thisSymbol = symbolTable.getSymbol( Keyword.KW_this.getName() ); if( thisSymbol != null && thisSymbol.getType() instanceof IGosuClass ) { scriptPart = new ScriptPartId( thisSymbol.getType(), GS_TEMPLATE_PARSED ); } else { scriptPart = new TypelessScriptPartId( GS_TEMPLATE_PARSED ); } Program program = (Program)parser.parseProgram( scriptPart, null, null, true ); program.clearParseTreeInformation(); return program; } private void addStaticSymbols(ISymbolTable symbolTable, GosuParser parser, IGosuClassInternal supertype) { supertype.putClassMembers( parser, symbolTable, supertype, true ); for(Object entryObj : symbolTable.getSymbols().entrySet()) { @SuppressWarnings({"unchecked"}) Entry<CharSequence, ISymbol> entry = (Entry<CharSequence, ISymbol>) entryObj; if((entry.getValue()).isPrivate()) { symbolTable.removeSymbol(entry.getKey()); } } } public void verify( IGosuParser parser, Map<String, Set<IFunctionSymbol>> dfsDeclByName, ITypeUsesMap typeUsesMap) throws ParseResultsException { verify( parser, dfsDeclByName, typeUsesMap, false ); } private IProgram verify( IGosuParser parser, Map<String, Set<IFunctionSymbol>> dfsDeclByName, ITypeUsesMap typeUsesMap, boolean bDoNotThrowParseResultsException ) throws ParseResultsException { assert _scriptStr != null : "Cannot verify a template after it has been compiled"; ISymbolTable symTable = parser.getSymbolTable(); symTable.pushScope(); try { parser.setScript( _scriptStr ); if (parser instanceof GosuParser) { parser.setTokenizerInstructor( new TemplateTokenizerInstructor(((GosuParser)parser).getTokenizer()) ); parser.setDfsDeclInSetByName(dfsDeclByName); } if (typeUsesMap == null) { typeUsesMap = parser.getTypeUsesMap(); } else { parser.setTypeUsesMap(typeUsesMap); } if(_fqn != null) { typeUsesMap.addToTypeUses(GosuClassUtil.getPackage(_fqn) + ".*"); } parser.setTypeUsesMap(typeUsesMap); IScriptPartId scriptPart = parser.getScriptPart() == null ? new TypelessScriptPartId( GS_TEMPLATE ) : new ScriptPartId( parser.getScriptPart().getContainingType(), GS_TEMPLATE ); return parser.parseProgram( scriptPart, true, false, null, null, false, bDoNotThrowParseResultsException ); } finally { symTable.popScope(); for (IParseTree parseTree : parser.getLocations()) { parseTree.setLength(Math.min(parseTree.getLength(), _scriptStr.length() - parseTree.getOffset())); } } } public void verify( IGosuParser parser ) throws ParseResultsException { verify( parser, false ); } private IProgram verify( IGosuParser parser, boolean bDoNotThrowParseResultException ) throws ParseResultsException { HashMap<String, Set<IFunctionSymbol>> dfsMap = new HashMap<String, Set<IFunctionSymbol>>(); dfsMap.put( PRINT_METHOD, Collections.<IFunctionSymbol>singleton(new DynamicFunctionSymbol(parser.getSymbolTable(), PRINT_METHOD, new FunctionType(PRINT_METHOD, GosuParserTypes.NULL_TYPE(), new IType[]{GosuParserTypes.STRING_TYPE(), GosuParserTypes.BOOLEAN_TYPE()}), Arrays.<ISymbol>asList(new Symbol("content", GosuParserTypes.STRING_TYPE(), null), new Symbol("escape", GosuParserTypes.BOOLEAN_TYPE(), null)), (IExpression)null))); dfsMap.put(PRINT_RANGE_METHOD, Collections.<IFunctionSymbol>singleton(new DynamicFunctionSymbol(parser.getSymbolTable(), PRINT_RANGE_METHOD, new FunctionType(PRINT_RANGE_METHOD, GosuParserTypes.NULL_TYPE(), new IType[]{GosuParserTypes.STRING_TYPE(), JavaTypes.pINT(), JavaTypes.pINT()}), Arrays.<ISymbol>asList(new Symbol("start", JavaTypes.pINT(), null), new Symbol("end", JavaTypes.pINT(), null)), (IExpression) null))); return verify( parser, dfsMap, null, bDoNotThrowParseResultException ); } public List<TemplateParseException> getTemplateSyntaxProblems() { List<TemplateParseException> exceptions = new ArrayList<TemplateParseException>(); transformTemplate(_scriptStr, exceptions); return exceptions; } @SuppressWarnings({"ThrowableInstanceNeverThrown", "ConstantConditions"}) private String transformTemplate(String strSource, List<TemplateParseException> exceptions) { _params.clear(); StringBuilder sbTarget = new StringBuilder( strSource.length() ); int iIndex = 0; while( true ) { int iIndex2 = strSource.indexOf( SCRIPTLET_BEGIN, iIndex ); int altIndex2 = strSource.indexOf( ALTERNATE_EXPRESSION_BEGIN, iIndex ); if( iIndex2 >= 0 || altIndex2 >= 0 ) { if( iIndex2 >= 0 && (altIndex2 < 0 || iIndex2 < altIndex2) ) { if( iIndex2 > 0 && strSource.charAt( iIndex2 - 1 ) == '\\' ) { // Handle escaped "\<%" addRefText( sbTarget, iIndex, iIndex2 - 1 ); addText( sbTarget, SCRIPTLET_BEGIN.substring( 0, 1 ) ); iIndex = iIndex2 + 1; } else { boolean bPrecedingContentEndsWithNewLine = iIndex2-iIndex <= 0 || strSource.charAt( iIndex2-1 ) == '\n'; addRefText( sbTarget, iIndex, iIndex2 ); iIndex = iIndex2 + SCRIPTLET_BEGIN_LEN; boolean bExpression = false; if( iIndex < strSource.length() ) { if( strSource.indexOf( "--", iIndex ) == iIndex ) { int iEndComment = strSource.indexOf( COMMENT_END, iIndex+2 ); if( iEndComment > 0 ) { iIndex = iEndComment + COMMENT_END_LEN; continue; } else { return sbTarget.toString(); } } bExpression = strSource.charAt( iIndex ) == EXPRESSION_SUFFIX; boolean bDeclaration = strSource.charAt( iIndex ) == DECLARATION_SUFFIX; boolean bDirective = strSource.charAt( iIndex ) == DIRECTIVE_SUFFIX; iIndex += ((bExpression || bDeclaration || bDirective) ? 1 : 0); iIndex2 = strSource.indexOf( SCRIPTLET_END, iIndex ); if( iIndex2 < 0 ) { int iLineNumber = GosuStringUtil.getLineNumberForIndex( strSource, iIndex ); int iColumn = getColumnForIndex( strSource, iIndex ); exceptions.add( new TemplateParseException( bExpression ? Res.MSG_TEMPLATE_MISSING_END_TAG_EXPRESSION : Res.MSG_TEMPLATE_MISSING_END_TAG_SCRIPTLET, iLineNumber, iColumn, iIndex ) ); return sbTarget.toString(); } String strScript = strSource.substring( iIndex, iIndex2 ); if( bExpression ) { addExpression( sbTarget, strScript ); } else if( bDirective ) { try { int iLineNumber = GosuStringUtil.getLineNumberForIndex( strSource, iIndex ); int iColumn = getColumnForIndex( strSource, iIndex ); processDirective( strScript, iLineNumber, iColumn, iIndex ); } catch( TemplateParseException e ) { exceptions.add( e ); } } else { addScriptlet( sbTarget, strScript ); } } iIndex = iIndex2 + SCRIPTLET_END_LEN; if( !bExpression && bPrecedingContentEndsWithNewLine ) { iIndex = ignoreTrailingLineSeparator( strSource, iIndex ); } } } else if( _disableAlternative && altIndex2 > 0 && strSource.charAt( altIndex2 - 1 ) != '\\' ) { addRefText( sbTarget, iIndex, altIndex2 - 1 ); addText( sbTarget, "\\" + ALTERNATE_EXPRESSION_BEGIN.substring( 0, 1 ) ); iIndex = altIndex2 + 1; } else { if( altIndex2 > 0 && strSource.charAt( altIndex2 - 1 ) == '\\' ) { addRefText( sbTarget, iIndex, altIndex2 - 1 ); addText( sbTarget, ALTERNATE_EXPRESSION_BEGIN.substring( 0, 1 ) ); iIndex = altIndex2 + 1; } else { addRefText( sbTarget, iIndex, altIndex2 ); iIndex = altIndex2 + ALTERNATE_EXPRESSION_BEGIN_LEN; altIndex2 = strSource.indexOf( ALTERNATE_EXPRESSION_END, iIndex ); int nextOpen = strSource.indexOf( "{", iIndex ); if( nextOpen != -1 && nextOpen < altIndex2 ) { int numOpen = 2; while( numOpen > 1 && altIndex2 != -1 ) { nextOpen = strSource.indexOf( "{", Math.min( nextOpen, altIndex2 ) + 1 ); if( nextOpen != -1 && nextOpen < altIndex2 ) { numOpen++; } else { numOpen--; altIndex2 = strSource.indexOf( "}", altIndex2 + ALTERNATE_EXPRESSION_END_LEN ); } } } if( altIndex2 < 0 ) { int iLineNumber = GosuStringUtil.getLineNumberForIndex( strSource, iIndex ); int iColumn = getColumnForIndex( strSource, iIndex ); exceptions.add( new TemplateParseException( Res.MSG_TEMPLATE_MISSING_END_TAG_EXPRESSION_ALT, iLineNumber, iColumn, iIndex ) ); return sbTarget.toString(); } String strScript = strSource.substring( iIndex, altIndex2 ); addExpression( sbTarget, strScript ); iIndex = altIndex2 + ALTERNATE_EXPRESSION_END_LEN; } } } else { addRefText( sbTarget, iIndex, strSource.length() ); break; } } return sbTarget.toString(); } private int ignoreTrailingLineSeparator( String strSource, int iIndex ) { if( iIndex < strSource.length() ) { if( strSource.charAt( iIndex ) == '\n' ) { iIndex++; } else if( strSource.startsWith( System.lineSeparator(), iIndex ) ) { iIndex += System.lineSeparator().length(); } } return iIndex; } private void processDirective(String strScript, int lineNumber, int column, int offset) throws TemplateParseException { strScript = strScript.trim(); if(strScript.startsWith("params")) { if(!_params.isEmpty()) { throw new TemplateParseException(Res.MSG_TEMPLATE_MULTIPLE_PARAMS, lineNumber, column, offset); } int iOpeningParen = strScript.indexOf( "(" ); int iClosingParen = strScript.lastIndexOf( ")" ); String strSignature = ""; if( iOpeningParen > 0 && iClosingParen > 0 ) { strSignature = strScript.substring( iOpeningParen + 1, iClosingParen ).trim(); } if (strSignature.length() > 0) { // get type uses map first GosuParser usesParser = (GosuParser) GosuParserFactory.createParser(_scriptStr); usesParser.setTokenizerInstructor( new TemplateTokenizerInstructor(usesParser.getTokenizer()) ); if( _fqn != null ) { ITypeUsesMap typeUsesMap = usesParser.getTypeUsesMap(); if( typeUsesMap != null ) { typeUsesMap.addToTypeUses(GosuClassUtil.getPackage(_fqn) + ".*"); } } try { usesParser.parseProgram( new TypelessScriptPartId( GS_TEMPLATE ) ); } catch (ParseResultsException e) { // there are going to be errors, ignore them } // now parse signature GosuParser parser = (GosuParser) GosuParserFactory.createParser(strSignature); parser.setEditorParser(_useStudioEditorParser); parser.setTypeUsesMap(usesParser.getTypeUsesMap()); parser.getTokenizer().nextToken(); Identifier pe = new Identifier(); _params.addAll(parser.parseParameterDeclarationList(pe, false, null)); if (pe.hasParseExceptions()) { throw new TemplateParseException(Res.MSG_TEMPLATE_INVALID_PARAMS, lineNumber, column, offset, strScript + ": " + pe.getParseExceptions().get(0).getConsoleMessage()); } } } else if (strScript.startsWith("extends")) { String typeName = strScript.substring(strScript.indexOf("extends") + "extends".length()).trim(); _supertype = TypeLoaderAccess.instance().getByFullNameIfValid(typeName); if(_supertype == null) { throw new TemplateParseException(Res.MSG_INVALID_TYPE, lineNumber, column, offset, typeName); } } else { throw new TemplateParseException(Res.MSG_TEMPLATE_UNKNOWN_DIRECTIVE, lineNumber, column, offset, strScript); } } private void addText( StringBuilder strTarget, String strText ) { if( !GosuStringUtil.isEmpty( strText ) ) { strText = escapeForGosuStringLiteral( strText ); strTarget.append( PRINT_METHOD ).append( "(\"" ).append( strText ).append( "\", false)\r\n" ); } } private void addRefText( StringBuilder sbTarget, int iStart, int iEnd ) { if( isForStringLiteral() ) { addText( sbTarget, _scriptStr.substring( iStart, iEnd ) ); } else if( iEnd - iStart > 0 ) { sbTarget.append( PRINT_RANGE_METHOD ) .append( "(" ) .append( iStart ).append( "," ) .append( iEnd ) .append( ")" ); } } private void addExpression( StringBuilder strTarget, String strExpression ) { if( strExpression.trim().length() != 0 ) { strTarget.append( PRINT_METHOD ).append( "((" ).append( strExpression ).append( ") as String, true)\r\n" ); } } private void addScriptlet( StringBuilder strTarget, String strScript ) { strTarget.append( strScript ).append( "\r\n" ); } private String escapeForGosuStringLiteral( String strText ) { if( strText == null ) { return null; } strText = GosuEscapeUtil.escapeForGosuStringLiteral( strText ); return strText; } private int getColumnForIndex( String strSource, int iIndex ) { int lastLineBreak = 0; for( int i = 0; i <= iIndex && i < strSource.length(); i++ ) { char c = strSource.charAt( i ); if( c == '\n' ) { lastLineBreak = i; } } return iIndex - lastLineBreak; } /** * For internal use only!! */ public static void printContent( String strContent , boolean escape ) { try { RuntimeData pair = getRuntimeData(); Writer writer = pair._writer; if (escape && pair._esc != null) { strContent = pair._esc.escape(strContent); } else if (pair._esc instanceof IEscapesAllContent) { strContent = ((IEscapesAllContent)pair._esc).escapeBody(strContent); } writer.write( strContent == null ? "null" : strContent ); } catch( IOException e ) { throw new RuntimeException( e ); } } @SuppressWarnings("UnusedDeclaration") public static void printRange( int iStart, int iEnd ) { try { RuntimeData runtimeData = getRuntimeData(); String strContent = runtimeData._templateSource.substring( iStart, iEnd ); Writer writer = runtimeData._writer; writer.write( strContent ); } catch( IOException e ) { throw new RuntimeException( e ); } } public void setDisableAlternative(boolean disableAlternative) { _disableAlternative = disableAlternative; } public void setContextInferenceManager( ContextInferenceManager ctxInferenceMgr ) { _ctxInferenceMgr = ctxInferenceMgr.copy(); } public void setForStringLiteral( boolean bForStringLiteralTemplate ) { _bStringLiteralTemplate = bForStringLiteralTemplate; } public boolean isForStringLiteral() { return _bStringLiteralTemplate; } private class RuntimeData { Writer _writer; StringEscaper _esc; String _templateSource; public RuntimeData( Writer writer, StringEscaper esc ) { _writer = writer; _esc = esc; _templateSource = getSource(); } } public boolean isValid() { compileIfNotCompiled(); return _program != null && !_program.hasParseExceptions(); } private void setFqn(String fullyQualifiedName) { _fqn = fullyQualifiedName; } public String getFullyQualifiedTypeName() { return _fqn; } public List<ISymbol> getParameters() { compileIfNotCompiled(); return _params; } private void compileIfNotCompiled() { if(_program == null) { if(_hasOwnSymbolScope) { CompiledGosuClassSymbolTable.instance().pushCompileTimeSymbolTable(); } try { compile(CompiledGosuClassSymbolTable.instance()); } catch (TemplateParseException e) { // ignore? } finally { if (_hasOwnSymbolScope) { CompiledGosuClassSymbolTable.instance().popCompileTimeSymbolTable(); } } } } public IType getSuperType() { return _supertype; } public void setUseStudioEditorParser(boolean useStudioEditorParser) { _useStudioEditorParser = useStudioEditorParser; } @Override public String toString() { if (_scriptStr != null) { return _scriptStr; } else { return _program.toString(); } } public String getSource() { return _scriptStr; } private IExternalSymbolMap extractExternalSymbols( ISymbolTable compileTimeSymbolTable, ISymbolTable runtimeSymbolTable ) { HashMap<String, ISymbol> externalSymbolsMap = new HashMap<String, ISymbol>( 8 ); putSymbols( compileTimeSymbolTable, externalSymbolsMap ); putSymbols( runtimeSymbolTable, externalSymbolsMap ); // overwrite compile time symbols with the runtime stuff. yeesh return new ExternalSymbolMapForMap(externalSymbolsMap); } private void putSymbols( ISymbolTable symTable, HashMap<String, ISymbol> externalSymbolsMap ) { Map symbols = symTable.getSymbols(); if( symbols != null ) { //noinspection unchecked for( ISymbol sym : (Collection<ISymbol>)symbols.values() ) { if( !(sym instanceof CommonSymbolsScope.LockedDownSymbol) && sym != null ) { externalSymbolsMap.put( (String)sym.getName(), sym ); } } } } }