/* * Copyright 2008-2009 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, * CA 95054 USA or visit www.sun.com if you need additional information or * have any questions. */ package org.visage.tools.comp; import java.net.URI; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.tools.FileObject; import org.visage.api.VisageBindStatus; import org.visage.api.tree.SyntheticTree.SynthType; import org.visage.api.tree.TypeTree; import org.visage.api.tree.TypeTree.Cardinality; import com.sun.tools.mjavac.code.Flags; import static com.sun.tools.mjavac.code.Flags.*; import com.sun.tools.mjavac.tree.JCTree; import com.sun.tools.mjavac.util.*; import com.sun.tools.mjavac.util.JCDiagnostic.DiagnosticPosition; import com.sun.tools.mjavac.util.Name.Table; import org.visage.tools.code.VisageFlags; import static org.visage.tools.code.VisageFlags.SCRIPT_LEVEL_SYNTH_STATIC; import org.visage.tools.code.VisageSymtab; import org.visage.tools.tree.*; import org.visage.tools.util.MsgSym; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; public class VisageScriptClassBuilder { protected static final Context.Key<VisageScriptClassBuilder> visageModuleBuilderKey = new Context.Key<VisageScriptClassBuilder>(); private final VisageDefs defs; private Table names; private VisageTreeMaker visagemake; private JCDiagnostic.Factory diags; private Log log; private VisageSymtab syms; private Set<Name> reservedTopLevelNamesSet; private Name pseudoSourceFile; private Name pseudoFile; private Name pseudoDir; private Name pseudoProfile; private Name defaultRunArgName; public boolean scriptingMode; private static final boolean debugBadPositions = Boolean.getBoolean("VisageModuleBuilder.debugBadPositions"); public static VisageScriptClassBuilder instance(Context context) { VisageScriptClassBuilder instance = context.get(visageModuleBuilderKey); if (instance == null) instance = new VisageScriptClassBuilder(context); return instance; } protected VisageScriptClassBuilder(Context context) { context.put(visageModuleBuilderKey, this); defs = VisageDefs.instance(context); names = Table.instance(context); visagemake = (VisageTreeMaker)VisageTreeMaker.instance(context); diags = JCDiagnostic.Factory.instance(context); log = Log.instance(context); syms = (VisageSymtab)VisageSymtab.instance(context); pseudoSourceFile = names.fromString("__SOURCE_FILE__"); pseudoFile = names.fromString("__FILE__"); pseudoDir = names.fromString("__DIR__"); pseudoProfile = names.fromString("__PROFILE__"); defaultRunArgName = names.fromString("_$UNUSED$_$ARGS$_"); } public void convertAccessFlags(VisageScript script) { new VisageTreeScanner() { void convertFlags(VisageModifiers mods) { long flags = mods.flags; long access = flags & (Flags.AccessFlags | VisageFlags.PACKAGE_ACCESS); if (access == 0L) { flags |= VisageFlags.SCRIPT_PRIVATE; } mods.flags = flags; } @Override public void visitClassDeclaration(VisageClassDeclaration tree) { super.visitClassDeclaration(tree); convertFlags(tree.getModifiers()); } @Override public void visitFunctionDefinition(VisageFunctionDefinition tree) { super.visitFunctionDefinition(tree); convertFlags(tree.getModifiers()); } @Override public void visitVar(VisageVar tree) { super.visitVar(tree); convertFlags(tree.getModifiers()); } }.scan(script); } private void checkAndNormalizeUserRunFunction(VisageFunctionDefinition runFunc) { VisageFunctionValue fval = runFunc.operation; List<VisageVar> params = fval.funParams; switch (params.size()) { case 0: { // no parameter specified, fill it in fval.funParams = makeRunFunctionArgs(defaultRunArgName); break; } case 1: { VisageType paramType = params.head.getVisageType(); if (paramType.getCardinality() == Cardinality.ANY && paramType instanceof VisageTypeClass) { VisageExpression cnExp = ((VisageTypeClass) paramType).getClassName(); if (cnExp instanceof VisageIdent) { Name cName = ((VisageIdent)cnExp).getName(); if (cName == syms.stringTypeName) { break; } } } // not well-formed, fall-through } default: { // bad arguments log.error(runFunc.pos(), MsgSym.MESSAGE_VISAGE_RUN_FUNCTION_PARAM); fval.funParams = makeRunFunctionArgs(defaultRunArgName); } } //TODO: check specified return type // set return type fval.rettype = makeRunFunctionType(); } public VisageClassDeclaration preProcessVisageTopLevel(VisageScript module) { Name moduleClassName = scriptName(module); if (debugBadPositions) { checkForBadPositions(module); } if (scriptingMode && module.pid != null) log.error(module.pos(), MsgSym.MESSAGE_VISAGE_PACKAGE_IN_SCRIPT_EVAL_MODE); // check for references to pseudo variables and if found, declare them class PseudoIdentScanner extends VisageTreeScanner { public boolean usesSourceFile; public boolean usesFile; public boolean usesDir; public boolean usesProfile; public DiagnosticPosition diagPos; @Override public void visitIdent(VisageIdent id) { super.visitIdent(id); if (id.getName().equals(pseudoSourceFile)) { usesSourceFile = true; markPosition(id); } if (id.getName().equals(pseudoFile)) { usesFile = true; markPosition(id); } if (id.getName().equals(pseudoDir)) { usesDir = true; markPosition(id); } if (id.getName().equals(pseudoProfile)) { usesProfile = true; markPosition(id); } } void markPosition(VisageTree tree) { if (diagPos == null) { // want the first only diagPos = tree.pos(); } } } PseudoIdentScanner pseudoScanner = new PseudoIdentScanner(); pseudoScanner.scan(module.defs); //debugPositions(module); ListBuffer<VisageTree> scriptTops = ListBuffer.<VisageTree>lb(); final List<VisageTree> pseudoVars = pseudoVariables(module.pos(), moduleClassName, module, pseudoScanner.usesSourceFile, pseudoScanner.usesFile, pseudoScanner.usesDir, pseudoScanner.usesProfile); scriptTops.appendList(pseudoVars); scriptTops.appendList(module.defs); // Determine if this is a library script boolean externalAccessFound = false; VisageFunctionDefinition userRunFunction = null; final long EXTERNALIZING_FLAGS = Flags.PUBLIC | Flags.PROTECTED | VisageFlags.PACKAGE_ACCESS | VisageFlags.PUBLIC_READ | VisageFlags.PUBLIC_INIT; for (VisageTree tree : scriptTops) { // Protect against erroneous scripts being attributed by IDE plugin // if (tree == null ) continue; switch (tree.getVisageTag()) { case CLASS_DEF: { VisageClassDeclaration decl = (VisageClassDeclaration) tree; if ((decl.getModifiers().flags & EXTERNALIZING_FLAGS) != 0) { externalAccessFound = true; } break; } case FUNCTION_DEF: { VisageFunctionDefinition decl = (VisageFunctionDefinition) tree; Name name = decl.name; if (name == defs.userRunFunctionName) { if (userRunFunction == null) { checkAndNormalizeUserRunFunction(decl); userRunFunction = decl; } else { log.error(decl.pos(), MsgSym.MESSAGE_VISAGE_RUN_FUNCTION_SINGLE); } } if ((decl.getModifiers().flags & EXTERNALIZING_FLAGS) != 0) { externalAccessFound = true; } break; } case VAR_DEF: { VisageVar decl = (VisageVar) tree; if ((decl.getModifiers().flags & EXTERNALIZING_FLAGS) != 0) { externalAccessFound = true; } break; } } } final boolean isLibrary = externalAccessFound || (userRunFunction != null); module.isLibrary = isLibrary; ListBuffer<VisageTree> scriptClassDefs = new ListBuffer<VisageTree>(); ListBuffer<VisageExpression> stats = new ListBuffer<VisageExpression>(); VisageExpression value = null; // Divide module defs between internsl run function body, Java compilation unit, and module class ListBuffer<VisageTree> topLevelDefs = new ListBuffer<VisageTree>(); VisageClassDeclaration moduleClass = null; boolean looseExpressionsSeen = false; for (VisageTree tree : scriptTops) { // Protect against errneous script trees being attributed by // IDE plugins. // if (tree == null) continue; if (value != null) { stats.append(value); value = null; } switch (tree.getVisageTag()) { case IMPORT: topLevelDefs.append(tree); break; case CLASS_DEF: { VisageClassDeclaration decl = (VisageClassDeclaration) tree; Name name = decl.getName(); checkName(tree.pos, name); if (name == moduleClassName) { moduleClass = decl; // script-class added to topLevelDefs below } else { // classes other than the script-class become nested static classes decl.mods.flags |= STATIC | SCRIPT_LEVEL_SYNTH_STATIC; scriptClassDefs.append(tree); } break; } case FUNCTION_DEF: { // turn script-level functions into script-class static functions VisageFunctionDefinition decl = (VisageFunctionDefinition) tree; decl.mods.flags |= STATIC | SCRIPT_LEVEL_SYNTH_STATIC; Name name = decl.name; checkName(tree.pos, name); // User run function isn't used directly. // Guts will be added to internal run function. // Other functions added to the script-class if (name != defs.userRunFunctionName) { scriptClassDefs.append(tree); } break; } case VAR_DEF: { // turn script-level variables into script-class static variables VisageVar decl = (VisageVar) tree; if ( (decl.mods.flags & SCRIPT_LEVEL_SYNTH_STATIC) == 0) { // if this wasn't already created as a synthetic checkName(tree.pos, decl.getName()); } decl.mods.flags |= STATIC | SCRIPT_LEVEL_SYNTH_STATIC; scriptClassDefs.append(decl); // declare variable as a static in the script class if (!isLibrary) { // This is a simple-form script where the main-code is just loose at the script-level. // The main-code will go into the run method. The variable initializations should // be in-place inline. Place the variable initialization in 'value' so that // it will wind up in the code of the run method. value = visagemake.VarInit(decl); } break; } default: { // loose expressions, if allowed, get added to the statements/value if (isLibrary && !looseExpressionsSeen) { JCDiagnostic reason = externalAccessFound ? diags.fragment(MsgSym.MESSAGE_VISAGE_LOOSE_IN_LIB) : diags.fragment(MsgSym.MESSAGE_VISAGE_LOOSE_IN_RUN); log.error(tree.pos(), MsgSym.MESSAGE_VISAGE_LOOSE_EXPRESSIONS, reason); } looseExpressionsSeen = true; value = (VisageExpression) tree; break; } } } { // Create the internal run function, take as much as // possible from the user run function (if it exists) // // If there was no user supplied run function then we mark the // funcitonas synthetic and make sense of the start and endpos // for the node. If the user supplied a run function, then // we use the information it gives us and neither flag it as // synthetic nor change the node postions. // SynthType sType = SynthType.SYNTHETIC; VisageFunctionDefinition internalRunFunction = null; Name commandLineArgs = defaultRunArgName; if (userRunFunction != null) { List<VisageVar> params = userRunFunction.operation.getParams(); // Protect IDE plugin against partially typed run function // returning null for the parameters, statements or body, by // null checking for each of those elements. // if (params != null && params.size() == 1) { commandLineArgs = params.head.getName(); } // a run function was specified, start the statement, protecting // against IDE generated errors. // VisageBlock body = userRunFunction.getBodyExpression(); if (body != null) { int sSize = 0; List<VisageExpression> statements = body.getStmts(); if (statements != null) { sSize = statements.size(); } if (sSize > 0 || body.getValue() != null) { if (value != null) { stats.append(value); } if (sSize > 0) { stats.appendList(body.getStmts()); } if (body.getValue() != null) { value = body.getValue(); } } } } // If there is a user supplied run function, use content and position from it. // Otherwise, unless this is a pure library, create a run function from the loose expressions, // with no position. if (userRunFunction != null || !isLibrary || looseExpressionsSeen) { internalRunFunction = makeInternalRunFunction(module, commandLineArgs, userRunFunction, stats.toList(), value); scriptClassDefs.prepend(internalRunFunction); module.isRunnable = true; } } if (moduleClass == null) { // Synthesize a Main class definition and flag it as // such. // VisageModifiers cMods = visagemake.Modifiers(PUBLIC); cMods.setGenType(SynthType.SYNTHETIC); moduleClass = visagemake.ClassDeclaration( cMods, //public access needed for applet initialization moduleClassName, List.<VisageExpression>nil(), // no supertypes scriptClassDefs.toList()); moduleClass.setGenType(SynthType.SYNTHETIC); moduleClass.setPos(module.getStartPosition()); } else { moduleClass.setMembers(scriptClassDefs.appendList(moduleClass.getMembers()).toList()); } // Check endpos for IDE // setEndPos(module, moduleClass, module); moduleClass.isScriptClass = true; if (scriptingMode) moduleClass.setScriptingModeScript(); moduleClass.runMethod = userRunFunction; topLevelDefs.append(moduleClass); module.defs = topLevelDefs.toList(); // Sort the list into startPosition order for IDEs // ArrayList<VisageTree> sortL = new ArrayList<VisageTree>(moduleClass.getMembers()); Collections.sort(sortL, new Comparator<VisageTree>() { public int compare(VisageTree t1, VisageTree t2) { if (pseudoVars.contains(t1)) { return -1; } else if (pseudoVars.contains(t2)) { return +1; } else { return t1.getStartPosition() - t2.getStartPosition(); } } }); /***** This is part of the fix for VSGC-3416. But, it causes incorrect compile time errors in the ShoppingService sample so we disable this. The error msgs in functional/should-fail/AccessModifiersTest.visage.EXPECTED are sensitive to this so if you fix the problem, you will have to fix that file too. // This a hokey way to do this, but we are using mjavac.util.List and it doesn't // support much. Fortunately, there won't be thousands of entries in the member lists // scriptClassDefs.clear(); for (VisageTree e : sortL) { scriptClassDefs.append(e); } moduleClass.setMembers(scriptClassDefs.toList()); *****/ convertAccessFlags(module); reservedTopLevelNamesSet = null; return moduleClass; } /** * Helper method that checks to see if we can/need to record the correct end * position for any synthesized nodes, based upon the end position of some * supplied node that makes sense to use the end position of. * * @param module The top level script node * @param built The AST we are synthesizing * @param copy The AST we are copying information from */ protected void setEndPos(final VisageScript module, final VisageTree build, final VisageTree copy) { // We can only calculate end position spans if we have an // end position map, for debugging, or for the IDE. // if (module.endPositions != null) { module.endPositions.put(build, copy.getEndPosition(module.endPositions)); } } private void debugPositions(final VisageScript module) { new VisageTreeScanner() { @Override public void scan(VisageTree tree) { super.scan(tree); if (tree != null) { System.out.println("[" + tree.getStartPosition() + "," + tree.getEndPosition(module.endPositions) + "] " + tree.toString()); } } }.scan(module); } private List<VisageTree> pseudoVariables(DiagnosticPosition diagPos, Name moduleClassName, VisageScript module, boolean usesSourceFile, boolean usesFile, boolean usesDir, boolean usesProfile) { ListBuffer<VisageTree> pseudoDefs = ListBuffer.<VisageTree>lb(); if (usesSourceFile) { String sourceName = module.getSourceFile().toUri().toString(); VisageExpression sourceFileVar = visagemake.at(diagPos).Var(pseudoSourceFile, getPseudoVarType(diagPos), visagemake.at(diagPos).Modifiers(FINAL|STATIC|SCRIPT_LEVEL_SYNTH_STATIC|VisageFlags.IS_DEF), visagemake.Literal(sourceName), VisageBindStatus.UNBOUND, null, null); pseudoDefs.append(sourceFileVar); } if (usesFile || usesDir) { VisageExpression moduleClassFQN = module.pid != null ? visagemake.at(diagPos).Select(module.pid, moduleClassName, false) : visagemake.at(diagPos).Ident(moduleClassName); VisageExpression getFile = visagemake.at(diagPos).Identifier("org.visage.runtime.PseudoVariables.get__FILE__"); VisageExpression forName = visagemake.at(diagPos).Identifier("java.lang.Class.forName"); List<VisageExpression> args = List.<VisageExpression>of(visagemake.at(diagPos).Literal(moduleClassFQN.toString())); VisageExpression loaderCall = visagemake.at(diagPos).Apply(List.<VisageExpression>nil(), forName, args); args = List.<VisageExpression>of(loaderCall); VisageExpression getFileURL = visagemake.at(diagPos).Apply(List.<VisageExpression>nil(), getFile, args); VisageExpression fileVar = visagemake.at(diagPos).Var(pseudoFile, getPseudoVarType(diagPos), visagemake.at(diagPos).Modifiers(FINAL|STATIC|SCRIPT_LEVEL_SYNTH_STATIC|VisageFlags.IS_DEF), getFileURL, VisageBindStatus.UNBOUND, null, null); pseudoDefs.append(fileVar); if (usesDir) { VisageExpression getDir = visagemake.at(diagPos).Identifier("org.visage.runtime.PseudoVariables.get__DIR__"); args = List.<VisageExpression>of(visagemake.at(diagPos).Ident(pseudoFile)); VisageExpression getDirURL = visagemake.at(diagPos).Apply(List.<VisageExpression>nil(), getDir, args); pseudoDefs.append( visagemake.at(diagPos).Var(pseudoDir, getPseudoVarType(diagPos), visagemake.at(diagPos).Modifiers(FINAL|STATIC|SCRIPT_LEVEL_SYNTH_STATIC|VisageFlags.IS_DEF), getDirURL, VisageBindStatus.UNBOUND, null, null)); } } if (usesProfile) { VisageExpression getProfile = visagemake.at(diagPos).Identifier("org.visage.runtime.PseudoVariables.get__PROFILE__"); VisageExpression getProfileString = visagemake.at(diagPos).Apply(List.<VisageExpression>nil(), getProfile, List.<VisageExpression>nil()); VisageExpression profileVar = visagemake.at(diagPos).Var(pseudoProfile, getPseudoVarType(diagPos), visagemake.at(diagPos).Modifiers(FINAL|STATIC|SCRIPT_LEVEL_SYNTH_STATIC|VisageFlags.IS_DEF), getProfileString, VisageBindStatus.UNBOUND, null, null); pseudoDefs.append(profileVar); } return pseudoDefs.toList(); } private VisageType getPseudoVarType(DiagnosticPosition diagPos) { VisageExpression fqn = visagemake.at(diagPos).Identifier("java.lang.String"); return visagemake.at(diagPos).TypeClass(fqn, TypeTree.Cardinality.SINGLETON); } private List<VisageVar> makeRunFunctionArgs(Name argName) { VisageVar mainArgs = visagemake.Param(argName, visagemake.TypeClass( visagemake.Ident(syms.stringTypeName), TypeTree.Cardinality.ANY)); return List.<VisageVar>of(mainArgs); } private VisageType makeRunFunctionType() { VisageExpression rettree = visagemake.Type(syms.objectType); rettree.type = syms.objectType; return visagemake.TypeClass(rettree, VisageType.Cardinality.SINGLETON); } /** * Constructs the internal static run function when the user has explicitly supplied a * declaration and body for that function. * * TODO: Review whether the caller even needs to copy the statements from the existing * body into stats, or can just use it. This change to code positions was done as * an emergency patch (VSGC-2291) for release 1.0 and I thought * it best to perform minimal surgery on the existing mechanism - Jim Idle. * * @param module The Script level node * @param argName The symbol table name of the args array * @param userRunFunction The user written run function (if there is one) * @param value The value of the function * @return The run function we have constructed */ private VisageFunctionDefinition makeInternalRunFunction(VisageScript module, Name argName, VisageFunctionDefinition userRunFunction, List<VisageExpression> stats, VisageExpression value) { VisageBlock existingBody = null; VisageBlock body = visagemake.at(null).Block(module.getStartPosition(), stats, value); int sPos = module.getStartPosition(); // First assume that this is synthetic // setEndPos(module, body, module); body.setGenType(SynthType.SYNTHETIC); // Now, override if it is not synthetic and there is a function body // - there will only not be a body if this is coming from the IDE and the // script is in error at this point. // if (userRunFunction != null) { existingBody = userRunFunction.getBodyExpression(); body.setGenType(SynthType.COMPILED); if (existingBody != null) { body.setPos(existingBody.getStartPosition()); setEndPos(module, body, existingBody); sPos = userRunFunction.getStartPosition(); } } // Make the static run function // VisageFunctionDefinition func = visagemake.at(sPos).FunctionDefinition( visagemake.Modifiers(PUBLIC | STATIC | SCRIPT_LEVEL_SYNTH_STATIC | SYNTHETIC), defs.internalRunFunctionName, makeRunFunctionType(), makeRunFunctionArgs(argName), body); // Construct the source code end position from either the existing // function, or the module level. // if (userRunFunction != null) { setEndPos(module, func, userRunFunction); func.operation.setPos(body.getStartPosition()); VisageVar param = func.getParams().head; VisageVar existingParam = userRunFunction.getParams().head; if (existingParam != null) { param.setPos(existingParam.getStartPosition()); setEndPos(module, param, existingParam); } } else { setEndPos(module, func, module); func.setGenType(SynthType.SYNTHETIC); } setEndPos(module, func.operation, body); return func; } private Name scriptName(VisageScript tree) { String fileObjName = null; FileObject fo = tree.getSourceFile(); URI uri = fo.toUri(); String path = uri.toString(); int i = path.lastIndexOf('/') + 1; fileObjName = path.substring(i); int lastDotIdx = fileObjName.lastIndexOf('.'); if (lastDotIdx != -1) { fileObjName = fileObjName.substring(0, lastDotIdx); } return names.fromString(fileObjName); } private void checkName(int pos, Name name) { if (reservedTopLevelNamesSet == null) { reservedTopLevelNamesSet = new HashSet<Name>(); // make sure no one tries to declare these reserved names reservedTopLevelNamesSet.add(pseudoFile); reservedTopLevelNamesSet.add(pseudoDir); } if (reservedTopLevelNamesSet.contains(name)) { log.error(pos, MsgSym.MESSAGE_VISAGE_RESERVED_TOP_LEVEL_SCRIPT_MEMBER, name.toString()); } } private void checkForBadPositions(VisageScript testTree) { final Map<JCTree, Integer> endPositions = testTree.endPositions; new VisageTreeScanner() { @Override public void scan(VisageTree tree) { super.scan(tree); // A Modifiers instance with no modifier tokens and no annotations // is defined as having no position. if (tree instanceof VisageModifiers) { VisageModifiers mods = (VisageModifiers)tree; if (mods.getFlags().isEmpty() || (mods.flags & Flags.SYNTHETIC) != 0) return; } // TypeUnknown trees have no associated tokens. if (tree instanceof VisageTypeUnknown) return; if (tree != null) { if (tree.pos <= 0) { String where = tree.getClass().getSimpleName(); try { where = where + ": " + tree.toString(); } catch (Throwable exc) { //ignore } System.err.println("Position of " + tree.pos + " in ---" + where); } if (tree.getEndPosition(endPositions) <= 0) { String where = tree.getClass().getSimpleName(); try { where = where + ": " + tree.toString(); } catch (Throwable exc) { //ignore } System.err.println("End position of " + tree.getEndPosition(endPositions) + " in ---" + where); } } } }.scan(testTree); } }