Package com.google.gwt.dev.jjs.impl

Source Code of com.google.gwt.dev.jjs.impl.GenerateJavaScriptAST$GenerateJavaScriptVisitor

/*
* Copyright 2007 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.gwt.dev.jjs.impl;

import com.google.gwt.dev.jjs.InternalCompilerException;
import com.google.gwt.dev.jjs.ast.Context;
import com.google.gwt.dev.jjs.ast.HasEnclosingType;
import com.google.gwt.dev.jjs.ast.HasName;
import com.google.gwt.dev.jjs.ast.JAbsentArrayDimension;
import com.google.gwt.dev.jjs.ast.JArrayRef;
import com.google.gwt.dev.jjs.ast.JArrayType;
import com.google.gwt.dev.jjs.ast.JAssertStatement;
import com.google.gwt.dev.jjs.ast.JBinaryOperation;
import com.google.gwt.dev.jjs.ast.JBinaryOperator;
import com.google.gwt.dev.jjs.ast.JBlock;
import com.google.gwt.dev.jjs.ast.JBooleanLiteral;
import com.google.gwt.dev.jjs.ast.JBreakStatement;
import com.google.gwt.dev.jjs.ast.JCaseStatement;
import com.google.gwt.dev.jjs.ast.JCastOperation;
import com.google.gwt.dev.jjs.ast.JCharLiteral;
import com.google.gwt.dev.jjs.ast.JClassLiteral;
import com.google.gwt.dev.jjs.ast.JClassType;
import com.google.gwt.dev.jjs.ast.JConditional;
import com.google.gwt.dev.jjs.ast.JContinueStatement;
import com.google.gwt.dev.jjs.ast.JDoStatement;
import com.google.gwt.dev.jjs.ast.JDoubleLiteral;
import com.google.gwt.dev.jjs.ast.JExpressionStatement;
import com.google.gwt.dev.jjs.ast.JField;
import com.google.gwt.dev.jjs.ast.JFieldRef;
import com.google.gwt.dev.jjs.ast.JFloatLiteral;
import com.google.gwt.dev.jjs.ast.JForStatement;
import com.google.gwt.dev.jjs.ast.JIfStatement;
import com.google.gwt.dev.jjs.ast.JInstanceOf;
import com.google.gwt.dev.jjs.ast.JIntLiteral;
import com.google.gwt.dev.jjs.ast.JInterfaceType;
import com.google.gwt.dev.jjs.ast.JLabel;
import com.google.gwt.dev.jjs.ast.JLabeledStatement;
import com.google.gwt.dev.jjs.ast.JLocal;
import com.google.gwt.dev.jjs.ast.JLocalDeclarationStatement;
import com.google.gwt.dev.jjs.ast.JLocalRef;
import com.google.gwt.dev.jjs.ast.JLongLiteral;
import com.google.gwt.dev.jjs.ast.JMethod;
import com.google.gwt.dev.jjs.ast.JMethodCall;
import com.google.gwt.dev.jjs.ast.JNewArray;
import com.google.gwt.dev.jjs.ast.JNewInstance;
import com.google.gwt.dev.jjs.ast.JNullLiteral;
import com.google.gwt.dev.jjs.ast.JParameter;
import com.google.gwt.dev.jjs.ast.JParameterRef;
import com.google.gwt.dev.jjs.ast.JPostfixOperation;
import com.google.gwt.dev.jjs.ast.JPrefixOperation;
import com.google.gwt.dev.jjs.ast.JProgram;
import com.google.gwt.dev.jjs.ast.JReferenceType;
import com.google.gwt.dev.jjs.ast.JReturnStatement;
import com.google.gwt.dev.jjs.ast.JStatement;
import com.google.gwt.dev.jjs.ast.JStringLiteral;
import com.google.gwt.dev.jjs.ast.JSwitchStatement;
import com.google.gwt.dev.jjs.ast.JThisRef;
import com.google.gwt.dev.jjs.ast.JThrowStatement;
import com.google.gwt.dev.jjs.ast.JTryStatement;
import com.google.gwt.dev.jjs.ast.JType;
import com.google.gwt.dev.jjs.ast.JTypeOracle;
import com.google.gwt.dev.jjs.ast.JUnaryOperator;
import com.google.gwt.dev.jjs.ast.JVisitor;
import com.google.gwt.dev.jjs.ast.JWhileStatement;
import com.google.gwt.dev.jjs.ast.js.JClassSeed;
import com.google.gwt.dev.jjs.ast.js.JMultiExpression;
import com.google.gwt.dev.jjs.ast.js.JsniMethod;
import com.google.gwt.dev.jjs.ast.js.JsonArray;
import com.google.gwt.dev.jjs.ast.js.JsonObject;
import com.google.gwt.dev.jjs.ast.js.JsonObject.JsonPropInit;
import com.google.gwt.dev.js.ast.JsArrayAccess;
import com.google.gwt.dev.js.ast.JsArrayLiteral;
import com.google.gwt.dev.js.ast.JsBinaryOperation;
import com.google.gwt.dev.js.ast.JsBinaryOperator;
import com.google.gwt.dev.js.ast.JsBlock;
import com.google.gwt.dev.js.ast.JsBreak;
import com.google.gwt.dev.js.ast.JsCase;
import com.google.gwt.dev.js.ast.JsCatch;
import com.google.gwt.dev.js.ast.JsCollection;
import com.google.gwt.dev.js.ast.JsConditional;
import com.google.gwt.dev.js.ast.JsContext;
import com.google.gwt.dev.js.ast.JsContinue;
import com.google.gwt.dev.js.ast.JsDefault;
import com.google.gwt.dev.js.ast.JsDoWhile;
import com.google.gwt.dev.js.ast.JsExprStmt;
import com.google.gwt.dev.js.ast.JsExpression;
import com.google.gwt.dev.js.ast.JsFor;
import com.google.gwt.dev.js.ast.JsFunction;
import com.google.gwt.dev.js.ast.JsIf;
import com.google.gwt.dev.js.ast.JsIntegralLiteral;
import com.google.gwt.dev.js.ast.JsInvocation;
import com.google.gwt.dev.js.ast.JsLabel;
import com.google.gwt.dev.js.ast.JsModVisitor;
import com.google.gwt.dev.js.ast.JsName;
import com.google.gwt.dev.js.ast.JsNameRef;
import com.google.gwt.dev.js.ast.JsNew;
import com.google.gwt.dev.js.ast.JsNode;
import com.google.gwt.dev.js.ast.JsObjectLiteral;
import com.google.gwt.dev.js.ast.JsParameter;
import com.google.gwt.dev.js.ast.JsParameters;
import com.google.gwt.dev.js.ast.JsPostfixOperation;
import com.google.gwt.dev.js.ast.JsPrefixOperation;
import com.google.gwt.dev.js.ast.JsProgram;
import com.google.gwt.dev.js.ast.JsPropertyInitializer;
import com.google.gwt.dev.js.ast.JsReturn;
import com.google.gwt.dev.js.ast.JsScope;
import com.google.gwt.dev.js.ast.JsStatement;
import com.google.gwt.dev.js.ast.JsStatements;
import com.google.gwt.dev.js.ast.JsStringLiteral;
import com.google.gwt.dev.js.ast.JsSwitch;
import com.google.gwt.dev.js.ast.JsSwitchMember;
import com.google.gwt.dev.js.ast.JsThisRef;
import com.google.gwt.dev.js.ast.JsThrow;
import com.google.gwt.dev.js.ast.JsTry;
import com.google.gwt.dev.js.ast.JsUnaryOperation;
import com.google.gwt.dev.js.ast.JsUnaryOperator;
import com.google.gwt.dev.js.ast.JsVars;
import com.google.gwt.dev.js.ast.JsWhile;
import com.google.gwt.dev.js.ast.JsVars.JsVar;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.TreeMap;
import java.util.Map.Entry;

/**
* Creates a JavaScript AST from a <code>JProgram</code> node.
*/
public class GenerateJavaScriptAST {

  private class CreateNamesAndScopesVisitor extends JVisitor {

    private final Stack/* <JsScope> */scopeStack = new Stack();

    // @Override
    public void endVisit(JClassType x, Context ctx) {
      pop();
    }

    // @Override
    public void endVisit(JField x, Context ctx) {
      String name = x.getName();
      String mangleName = mangleName(x);
      if (x.isStatic()) {
        names.put(x, topScope.declareName(mangleName, name));
      } else {
        names.put(x, peek().declareName(mangleName, name));
      }
    }

    // @Override
    public void endVisit(JInterfaceType x, Context ctx) {
      pop();
    }

    // @Override
    public void endVisit(JLabel x, Context ctx) {
      if (getName(x) != null) {
        return;
      }
      names.put(x, peek().declareName(x.getName()));
    }

    // @Override
    public void endVisit(JLocal x, Context ctx) {
      // locals can conflict, that's okay just reuse the same variable
      JsScope scope = peek();
      JsName jsName = scope.declareName(x.getName());
      names.put(x, jsName);
    }

    // @Override
    public void endVisit(JMethod x, Context ctx) {
      pop();
    }

    // @Override
    public void endVisit(JParameter x, Context ctx) {
      names.put(x, peek().declareName(x.getName()));
    }

    // @Override
    public void endVisit(JProgram x, Context ctx) {
      // visit special things that may have been culled
      JField field = x.getSpecialField("Object.typeId");
      names.put(field, objectScope.declareName(mangleName(field),
          field.getName()));

      field = x.getSpecialField("Object.typeName");
      names.put(field, objectScope.declareName(mangleName(field),
          field.getName()));

      field = x.getSpecialField("Cast.typeIdArray");
      names.put(field, topScope.declareName(mangleName(field), field.getName()));

      /*
       * put the null method and field into objectScope since they can be
       * referenced as instance on null-types (as determined by type flow)
       */
      JMethod nullMethod = x.getNullMethod();
      polymorphicNames.put(nullMethod,
          objectScope.declareName(nullMethod.getName()));
      JField nullField = x.getNullField();
      JsName nullFieldName = objectScope.declareName(nullField.getName());
      polymorphicNames.put(nullField, nullFieldName);
      names.put(nullField, nullFieldName);

      /*
       * put nullMethod in the global scope, too; it's the replacer for clinits
       */
      nullMethodName = topScope.declareName(nullMethod.getName());
      names.put(nullMethod, nullMethodName);
    }

    // @Override
    public void endVisit(JsniMethod x, Context ctx) {
      // didn't push anything
    }

    // @Override
    public boolean visit(JClassType x, Context ctx) {
      // have I already been visited as a super type?
      JsScope myScope = (JsScope) classScopes.get(x);
      if (myScope != null) {
        push(myScope);
        return false;
      }

      // My seed function name
      names.put(x, topScope.declareName(getNameString(x), x.getShortName()));

      // My class scope
      if (x.extnds == null) {
        myScope = objectScope;
      } else {
        JsScope parentScope = (JsScope) classScopes.get(x.extnds);
        // Run my superclass first!
        if (parentScope == null) {
          accept(x.extnds);
        }
        parentScope = (JsScope) classScopes.get(x.extnds);
        assert (parentScope != null);
        /*
         * WEIRD: we wedge the global interface scope in between object and all
         * of its subclasses; this ensures that interface method names trump all
         * (except Object method names)
         */
        if (parentScope == objectScope) {
          parentScope = interfaceScope;
        }
        myScope = new JsScope(parentScope, "class " + x.getShortName());
      }
      classScopes.put(x, myScope);

      // Make a name for my package if it doesn't already exist
      String packageName = getPackageName(x.getName());
      if (packageName != null && packageName.length() > 0) {
        if (packageNames.get(packageName) == null) {
          String ident = "package_" + packageName.replace('.', '_');
          JsName jsName = topScope.declareName(ident);
          packageNames.put(packageName, jsName);
        }
      }

      push(myScope);
      return true;
    }

    // @Override
    public boolean visit(JInterfaceType x, Context ctx) {
      // interfaces have no name at run time
      push(interfaceScope);
      return true;
    }

    // @Override
    public boolean visit(JMethod x, Context ctx) {

      // my polymorphic name
      String name = x.getName();
      if (!x.isStatic()) {
        if (getPolyName(x) == null) {
          String mangleName = mangleNameForPoly(x);
          JsName polyName = interfaceScope.declareName(mangleName, name);
          polymorphicNames.put(x, polyName);
        }
      }

      if (x.isAbstract()) {
        // just push a dummy scope that we can pop in endVisit
        push(null);
        return false;
      }

      // my global name
      JsName globalName;
      if (x.getEnclosingType() == null) {
        globalName = topScope.declareName(name);
      } else {
        String mangleName = mangleNameForGlobal(x);
        globalName = topScope.declareName(mangleName, name);
      }
      names.put(x, globalName);

      // create my peer JsFunction
      JsFunction jsFunction = new JsFunction(topScope, globalName);
      methodMap.put(x, jsFunction);

      push(jsFunction.getScope());
      return true;
    }

    // @Override
    public boolean visit(JsniMethod x, Context ctx) {
      // my polymorphic name
      String name = x.getName();
      if (!x.isStatic()) {
        if (getPolyName(x) == null) {
          String mangleName = mangleNameForPoly(x);
          JsName polyName = interfaceScope.declareName(mangleName, name);
          polymorphicNames.put(x, polyName);
        }
      }

      // set my global name now that we have a name allocator
      String fnName = mangleNameForGlobal(x);
      JsName globalName = topScope.declareName(fnName, name);
      x.getFunc().setName(globalName);
      names.put(x, globalName);

      return false;
    }

    public boolean visit(JTryStatement x, Context ctx) {
      accept(x.getTryBlock());

      List catchArgs = x.getCatchArgs();
      List catchBlocks = x.getCatchBlocks();
      for (int i = 0, c = catchArgs.size(); i < c; ++i) {
        JLocalRef arg = (JLocalRef) catchArgs.get(i);
        JBlock catchBlock = (JBlock) catchBlocks.get(i);
        JsCatch jsCatch = new JsCatch(peek(), arg.getTarget().getName());
        JsParameter jsParam = jsCatch.getParameter();
        names.put(arg.getTarget(), jsParam.getName());
        catchMap.put(catchBlock, jsCatch);

        push(jsCatch.getScope());
        accept(catchBlock);
        pop();
      }

      // TODO: normalize this so it's never null?
      if (x.getFinallyBlock() != null) {
        accept(x.getFinallyBlock());
      }
      return false;
    }

    private JsScope peek() {
      return (JsScope) scopeStack.peek();
    }

    private void pop() {
      scopeStack.pop();
    }

    private void push(JsScope scope) {
      scopeStack.push(scope);
    }
  }

  private class GenerateJavaScriptVisitor extends JVisitor {

    private final Set/* <JClassType> */alreadyRan = new HashSet/* <JClassType> */();

    private JMethod currentMethod = null;

    private final JsName globalTemp = topScope.declareName("_");

    private final Stack/* <JsNode> */nodeStack = new Stack/* <JsNode> */();

    private final JsName prototype = objectScope.declareName("prototype");

    {
      globalTemp.setObfuscatable(false);
      prototype.setObfuscatable(false);
    }

    // @Override
    public void endVisit(JAbsentArrayDimension x, Context ctx) {
      throw new InternalCompilerException("Should not get here.");
    }

    // @Override
    public void endVisit(JArrayRef x, Context ctx) {
      JsArrayAccess jsArrayAccess = new JsArrayAccess();
      jsArrayAccess.setIndexExpr((JsExpression) pop());
      jsArrayAccess.setArrayExpr((JsExpression) pop());
      push(jsArrayAccess);
    }

    // @Override
    public void endVisit(JAssertStatement x, Context ctx) {
      throw new InternalCompilerException("Should not get here.");
    }

    // @Override
    public void endVisit(JBinaryOperation x, Context ctx) {
      JsExpression rhs = (JsExpression) pop(); // rhs
      JsExpression lhs = (JsExpression) pop(); // lhs
      JsBinaryOperator myOp = JavaToJsOperatorMap.get(x.getOp());

      /*
       * Use === and !== on reference types, or else you can get wrong answers
       * when Object.toString() == 'some string'.
       */
      if (myOp == JsBinaryOperator.EQ
          && x.getLhs().getType() instanceof JReferenceType
          && x.getRhs().getType() instanceof JReferenceType) {
        myOp = JsBinaryOperator.REF_EQ;
      } else if (myOp == JsBinaryOperator.NEQ
          && x.getLhs().getType() instanceof JReferenceType
          && x.getRhs().getType() instanceof JReferenceType) {
        myOp = JsBinaryOperator.REF_NEQ;
      }

      push(new JsBinaryOperation(myOp, lhs, rhs));
    }

    // @Override
    public void endVisit(JBlock x, Context ctx) {
      JsBlock jsBlock = new JsBlock();
      JsStatements stmts = jsBlock.getStatements();
      popList(stmts, x.statements.size()); // stmts
      Iterator iterator = stmts.iterator();
      while (iterator.hasNext()) {
        JsStatement stmt = (JsStatement) iterator.next();
        if (stmt == jsProgram.getEmptyStmt()) {
          iterator.remove();
        }
      }
      push(jsBlock);
    }

    // @Override
    public void endVisit(JBooleanLiteral x, Context ctx) {
      push(x.getValue() ? jsProgram.getTrueLiteral()
          : jsProgram.getFalseLiteral());
    }

    // @Override
    public void endVisit(JBreakStatement x, Context ctx) {
      JsNameRef labelRef = null;
      if (x.getLabel() != null) {
        JsLabel label = (JsLabel) pop(); // label
        labelRef = label.getName().makeRef();
      }
      push(new JsBreak(labelRef));
    }

    // @Override
    public void endVisit(JCaseStatement x, Context ctx) {
      if (x.getExpr() == null) {
        push(new JsDefault());
      } else {
        JsCase jsCase = new JsCase();
        jsCase.setCaseExpr((JsExpression) pop()); // expr
        push(jsCase);
      }
    }

    // @Override
    public void endVisit(JCastOperation x, Context ctx) {
      throw new InternalCompilerException("Should not get here.");
    }

    // @Override
    public void endVisit(JCharLiteral x, Context ctx) {
      push(jsProgram.getIntegralLiteral(BigInteger.valueOf(x.getValue())));
    }

    // @Override
    public void endVisit(JClassLiteral x, Context ctx) {
      // My seed function name
      String nameString = x.getRefType().getJavahSignatureName() + "_classlit";
      JsName classLit = topScope.declareName(nameString);
      classLits.put(x.getRefType(), classLit);
      push(classLit.makeRef());
    }

    // @Override
    public void endVisit(JClassSeed x, Context ctx) {
      push(getName(x.getRefType()).makeRef());
    }

    // @Override
    public void endVisit(JClassType x, Context ctx) {
      if (alreadyRan.contains(x)) {
        return;
      }

      alreadyRan.add(x);

      List/* <JsFunction> */jsFuncs = popList(x.methods.size()); // methods
      List/* <JsStatement> */jsFields = popList(x.fields.size()); // fields

      if (typeOracle.hasDirectClinit(x)) {
        // see if there's a super class we need to chain to
        JClassType superType = x.extnds;
        while (superType != null && !typeOracle.hasDirectClinit(superType)) {
          superType = superType.extnds;
        }
        handleClinit((JsFunction) jsFuncs.get(0), superType);
      } else {
        jsFuncs.set(0, null);
      }

      JsStatements globalStmts = jsProgram.getGlobalBlock().getStatements();

      // declare all methods into the global scope
      for (int i = 0; i < jsFuncs.size(); ++i) {
        JsFunction func = (JsFunction) jsFuncs.get(i);
        if (func != null) {
          globalStmts.add(func.makeStmt());
        }
      }

      if (typeOracle.isInstantiatedType(x)) {
        generateClassSetup(x, globalStmts);
      }

      // setup fields
      JsVars vars = new JsVars();
      for (int i = 0; i < jsFields.size(); ++i) {
        JsNode node = (JsNode) jsFields.get(i);
        if (node instanceof JsVar) {
          vars.add((JsVar) node);
        } else {
          assert (node instanceof JsStatement);
          JsStatement stmt = (JsStatement) jsFields.get(i);
          globalStmts.add(stmt);
        }
      }
      if (!vars.isEmpty()) {
        globalStmts.add(vars);
      }
    }

    // @Override
    public void endVisit(JConditional x, Context ctx) {
      JsExpression elseExpr = (JsExpression) pop(); // elseExpr
      JsExpression thenExpr = (JsExpression) pop(); // thenExpr
      JsExpression ifTest = (JsExpression) pop(); // ifTest
      push(new JsConditional(ifTest, thenExpr, elseExpr));
    }

    // @Override
    public void endVisit(JContinueStatement x, Context ctx) {
      JsNameRef labelRef = null;
      if (x.getLabel() != null) {
        JsLabel label = (JsLabel) pop(); // label
        labelRef = label.getName().makeRef();
      }
      push(new JsContinue(labelRef));
    }

    // @Override
    public void endVisit(JDoStatement x, Context ctx) {
      JsDoWhile stmt = new JsDoWhile();
      if (x.getBody() != null) {
        stmt.setBody((JsStatement) pop()); // body
      } else {
        stmt.setBody(jsProgram.getEmptyStmt());
      }
      stmt.setCondition((JsExpression) pop()); // testExpr
      push(stmt);
    }

    // @Override
    public void endVisit(JDoubleLiteral x, Context ctx) {
      push(jsProgram.getDecimalLiteral(String.valueOf(x.getValue())));
    }

    // @Override
    public void endVisit(JExpressionStatement x, Context ctx) {
      JsExpression expr = (JsExpression) pop(); // expr
      push(expr.makeStmt());
    }

    // @Override
    public void endVisit(JField x, Context ctx) {
      // if we need an initial value, create an assignment
      if (x.constInitializer != null) {
        // setup the constant value
        accept(x.constInitializer);
      } else if (!x.hasInitializer()) {
        // setup a default value
        accept(x.getType().getDefaultValue());
      } else {
        // the variable is setup during clinit, no need to initialize here
        push(null);
      }
      JsExpression rhs = (JsExpression) pop();
      JsName name = getName(x);

      if (x.isStatic()) {
        // setup a var for the static
        JsVar var = new JsVar(name);
        var.setInitExpr(rhs);
        push(var);
      } else {
        // for non-statics, only setup an assignment if needed
        if (rhs != null) {
          JsNameRef fieldRef = name.makeRef();
          fieldRef.setQualifier(globalTemp.makeRef());
          JsExpression asg = createAssignment(fieldRef, rhs);
          push(new JsExprStmt(asg));
        } else {
          push(null);
        }
      }
    }

    // @Override
    public void endVisit(JFieldRef x, Context ctx) {
      JField field = x.getField();
      JsName jsFieldName = getName(field);
      JsNameRef nameRef = jsFieldName.makeRef();
      JsExpression curExpr = nameRef;

      /*
       * Note: the comma expressions here would cause an illegal tree state if
       * the result expression ended up on the lhs of an assignment. A hack in
       * in endVisit(JBinaryOperation) rectifies the situation.
       */

      // See if we need a clinit
      JsInvocation jsInvocation = maybeCreateClinitCall(field);
      if (jsInvocation != null) {
        curExpr = createCommaExpression(jsInvocation, curExpr);
      }

      if (x.getInstance() != null) {
        JsExpression qualifier = (JsExpression) pop();
        if (field.isStatic()) {
          // unnecessary qualifier, create a comma expression
          curExpr = createCommaExpression(qualifier, curExpr);
        } else {
          // necessary qualifier, qualify the name ref
          nameRef.setQualifier(qualifier);
        }
      }

      push(curExpr);
    }

    // @Override
    public void endVisit(JFloatLiteral x, Context ctx) {
      push(jsProgram.getDecimalLiteral(String.valueOf(x.getValue())));
    }

    // @Override
    public void endVisit(JForStatement x, Context ctx) {
      JsFor jsFor = new JsFor();

      // body
      if (x.getBody() != null) {
        jsFor.setBody((JsStatement) pop());
      } else {
        jsFor.setBody(jsProgram.getEmptyStmt());
      }

      // increments
      {
        JsExpression incrExpr = null;
        List/* <JsExprStmt> */exprStmts = popList(x.getIncrements().size());
        for (int i = 0; i < exprStmts.size(); ++i) {
          JsExprStmt exprStmt = (JsExprStmt) exprStmts.get(i);
          incrExpr = createCommaExpression(incrExpr, exprStmt.getExpression());
        }
        jsFor.setIncrExpr(incrExpr);
      }

      // condition
      if (x.getTestExpr() != null) {
        jsFor.setCondition((JsExpression) pop());
      }

      // initializers
      JsExpression initExpr = null;
      List/* <JsExprStmt> */initStmts = popList(x.getInitializers().size());
      for (int i = 0; i < initStmts.size(); ++i) {
        JsExprStmt initStmt = (JsExprStmt) initStmts.get(i);
        if (initStmt != null) {
          initExpr = createCommaExpression(initExpr, initStmt.getExpression());
        }
      }
      jsFor.setInitExpr(initExpr);

      push(jsFor);
    }

    // @Override
    public void endVisit(JIfStatement x, Context ctx) {
      JsIf stmt = new JsIf();

      if (x.getElseStmt() != null) {
        stmt.setElseStmt((JsStatement) pop()); // elseStmt
      }

      if (x.getThenStmt() != null) {
        stmt.setThenStmt((JsStatement) pop()); // thenStmt
      } else {
        stmt.setThenStmt(jsProgram.getEmptyStmt());
      }

      stmt.setIfExpr((JsExpression) pop()); // ifExpr
      push(stmt);
    }

    // @Override
    public void endVisit(JInstanceOf x, Context ctx) {
      throw new InternalCompilerException("Should not get here.");
    }

    // @Override
    public void endVisit(JInterfaceType x, Context ctx) {
      List/* <JsFunction> */jsFuncs = popList(x.methods.size()); // methods
      List/* <JsStatement> */jsFields = popList(x.fields.size()); // fields

      JsStatements globalStmts = jsProgram.getGlobalBlock().getStatements();

      if (typeOracle.hasDirectClinit(x)) {
        JsFunction clinitFunc = (JsFunction) jsFuncs.get(0);
        handleClinit(clinitFunc, null);
        globalStmts.add(clinitFunc.makeStmt());
      }

      // setup fields
      JsVars vars = new JsVars();
      for (int i = 0; i < jsFields.size(); ++i) {
        JsNode node = (JsNode) jsFields.get(i);
        assert (node instanceof JsVar);
        vars.add((JsVar) node);
      }
      if (!vars.isEmpty()) {
        globalStmts.add(vars);
      }
    }

    // @Override
    public void endVisit(JIntLiteral x, Context ctx) {
      push(jsProgram.getIntegralLiteral(BigInteger.valueOf(x.getValue())));
    }

    // @Override
    public void endVisit(JLabel x, Context ctx) {
      push(new JsLabel(getName(x)));
    }

    // @Override
    public void endVisit(JLabeledStatement x, Context ctx) {
      JsStatement body = (JsStatement) pop(); // body
      JsLabel label = (JsLabel) pop(); // label
      label.setStmt(body);
      push(label);
    }

    // @Override
    public void endVisit(JLocal x, Context ctx) {
      push(getName(x).makeRef());
    }

    // @Override
    public void endVisit(JLocalDeclarationStatement x, Context ctx) {

      if (x.getInitializer() == null) {
        pop(); // localRef
        /*
         * local decls can only appear in blocks, so it's okay to push null
         * instead of an empty statement
         */
        push(null);
        return;
      }

      JsExpression initializer = (JsExpression) pop(); // initializer
      JsNameRef localRef = (JsNameRef) pop(); // localRef

      JsBinaryOperation binOp = new JsBinaryOperation(JsBinaryOperator.ASG,
          localRef, initializer);

      push(binOp.makeStmt());
    }

    // @Override
    public void endVisit(JLocalRef x, Context ctx) {
      push(getName(x.getTarget()).makeRef());
    }

    // @Override
    public void endVisit(JLongLiteral x, Context ctx) {
      push(jsProgram.getIntegralLiteral(BigInteger.valueOf(x.getValue())));
    }

    // @Override
    public void endVisit(JMethod x, Context ctx) {

      JsBlock body = (JsBlock) pop();
      List/* <JsNameRef> */locals = popList(x.locals.size()); // locals
      List/* <JsParameter> */params = popList(x.params.size()); // params

      if (x.isAbstract()) {
        push(null);
        return;
      }

      JsFunction jsFunc = (JsFunction) methodMap.get(x);
      jsFunc.setBody(body); // body

      JsParameters jsParams = jsFunc.getParameters();
      for (int i = 0; i < params.size(); ++i) {
        JsParameter param = (JsParameter) params.get(i);
        jsParams.add(param);
      }

      /*
       * Emit a statement to declare the method's complete set of local
       * variables. JavaScript doesn't have the same concept of lexical scoping
       * as Java, so it's okay to just predeclare all local vars at the top of
       * the function, which saves us having to use the "var" keyword over and
       * over.
       *
       * Note: it's fine to use the same JS ident to represent two different
       * Java locals of the same name since they could never conflict with each
       * other in Java. We use the alreadySeen set to make sure we don't declare
       * the same-named local var twice.
       */
      JsVars vars = new JsVars();
      Set alreadySeen = new HashSet();
      for (int i = 0; i < locals.size(); ++i) {
        JsName name = (JsName) names.get(x.locals.get(i));
        String ident = name.getIdent();
        if (!alreadySeen.contains(ident)) {
          alreadySeen.add(ident);
          vars.add(new JsVar(name));
        }
      }

      if (!vars.isEmpty()) {
        jsFunc.getBody().getStatements().add(0, vars);
      }

      JsInvocation jsInvocation = maybeCreateClinitCall(x);
      if (jsInvocation != null) {
        jsFunc.getBody().getStatements().add(0, jsInvocation.makeStmt());
      }

      push(jsFunc);
      currentMethod = null;
    }

    // @Override
    public void endVisit(JMethodCall x, Context ctx) {
      JMethod method = x.getTarget();
      JsInvocation jsInvocation = new JsInvocation();

      popList(jsInvocation.getArguments(), x.getArgs().size()); // args

      JsNameRef qualifier;
      JsExpression unnecessaryQualifier = null;
      if (method.isStatic()) {
        if (x.getInstance() != null) {
          unnecessaryQualifier = (JsExpression) pop(); // instance
        }
        qualifier = getName(method).makeRef();
      } else {
        if (x.isStaticDispatchOnly()) {
          /*
           * Dispatch statically (odd case). This happens when a call that must
           * be static is targeting an instance method that could not be
           * transformed into a static. For example, making a super call into a
           * native method currently causes this, because we cannot currently
           * staticify native methods.
           *
           * Have to use a "call" construct.
           */
          JsName callName = objectScope.declareName("call");
          callName.setObfuscatable(false);
          qualifier = callName.makeRef();
          qualifier.setQualifier(getName(method).makeRef());
          jsInvocation.getArguments().add(0, (JsExpression) pop()); // instance
        } else {
          // Dispatch polymorphically (normal case).
          qualifier = getPolyName(method).makeRef();
          qualifier.setQualifier((JsExpression) pop()); // instance
        }
      }
      jsInvocation.setQualifier(qualifier);
      push(createCommaExpression(unnecessaryQualifier, jsInvocation));
    }

    // @Override
    public void endVisit(JMultiExpression x, Context ctx) {
      List/* <JsExpression> */exprs = popList(x.exprs.size());
      JsExpression cur = null;
      for (int i = 0; i < exprs.size(); ++i) {
        JsExpression next = (JsExpression) exprs.get(i);
        cur = createCommaExpression(cur, next);
      }
      push(cur);
    }

    // @Override
    public void endVisit(JNewArray x, Context ctx) {
      throw new InternalCompilerException("Should not get here.");
    }

    // @Override
    public void endVisit(JNewInstance x, Context ctx) {
      JsNew newOp = new JsNew();
      JsNameRef nameRef = getName(x.getType()).makeRef();
      newOp.setConstructorExpression(nameRef);
      push(newOp);
    }

    // @Override
    public void endVisit(JNullLiteral x, Context ctx) {
      push(jsProgram.getNullLiteral());
    }

    // @Override
    public void endVisit(JParameter x, Context ctx) {
      push(new JsParameter(getName(x)));
    }

    // @Override
    public void endVisit(JParameterRef x, Context ctx) {
      push(getName(x.getTarget()).makeRef());
    }

    // @Override
    public void endVisit(JPostfixOperation x, Context ctx) {
      JsUnaryOperation op = new JsPostfixOperation(
          JavaToJsOperatorMap.get(x.getOp()), ((JsExpression) pop())); // arg
      push(op);
    }

    // @Override
    public void endVisit(JPrefixOperation x, Context ctx) {
      JsUnaryOperation op = new JsPrefixOperation(
          JavaToJsOperatorMap.get(x.getOp()), ((JsExpression) pop())); // arg
      push(op);
    }

    // @Override
    public void endVisit(JProgram x, Context ctx) {
      JsStatements globalStmts = jsProgram.getGlobalBlock().getStatements();

      // types don't push

      // Generate entry methods
      List/* <JsFunction> */entryFuncs = popList(x.entryMethods.size()); // entryMethods
      for (int i = 0; i < entryFuncs.size(); ++i) {
        JsFunction func = (JsFunction) entryFuncs.get(i);
        if (func != null) {
          globalStmts.add(func.makeStmt());
        }
      }

      generateGwtOnLoad(entryFuncs, globalStmts);

      // a few more vars on the very end
      JsVars vars = new JsVars();
      generateTypeTable(vars);
      generateClassLiterals(vars);
      globalStmts.add(vars);
    }

    // @Override
    public void endVisit(JReturnStatement x, Context ctx) {
      if (x.getExpr() != null) {
        push(new JsReturn((JsExpression) pop())); // expr
      } else {
        push(new JsReturn());
      }
    }

    // @Override
    public void endVisit(JsniMethod x, Context ctx) {
      JsFunction jsFunc = x.getFunc();

      // replace all JSNI idents with a real JsName now that we know it
      new JsModVisitor() {
        // @Override
        public void endVisit(JsNameRef x, JsContext ctx) {
          String ident = x.getIdent();
          if (ident.charAt(0) == '@') {
            HasEnclosingType node = (HasEnclosingType) program.jsniMap.get(ident);
            assert (node != null);
            if (node instanceof JField) {
              JField field = (JField) node;
              JsName jsName = getName(field);
              assert (jsName != null);
              x.resolve(jsName);

              // See if we need to add a clinit call to a static field ref
              JsInvocation clinitCall = maybeCreateClinitCall(field);
              if (clinitCall != null) {
                JsExpression commaExpr = createCommaExpression(clinitCall, x);
                ctx.replaceMe(commaExpr);
              }
            } else {
              JMethod method = (JMethod) node;
              if (x.getQualifier() == null) {
                JsName jsName = getName(method);
                assert (jsName != null);
                x.resolve(jsName);
              } else {
                JsName jsName = getPolyName(method);
                if (jsName == null) {
                  // this can occur when JSNI references an instance method on a
                  // type that was never actually instantiated.
                  jsName = nullMethodName;
                }
                x.resolve(jsName);
              }
            }
          }
        }
      }.accept(jsFunc);

      JsInvocation jsInvocation = maybeCreateClinitCall(x);
      if (jsInvocation != null) {
        jsFunc.getBody().getStatements().add(0, jsInvocation.makeStmt());
      }

      push(jsFunc);
      currentMethod = null;
    }

    // @Override
    public void endVisit(JsonArray x, Context ctx) {
      JsArrayLiteral jsArrayLiteral = new JsArrayLiteral();
      popList(jsArrayLiteral.getExpressions(), x.exprs.size());
      push(jsArrayLiteral);
    }

    // @Override
    public void endVisit(JsonObject x, Context ctx) {
      JsObjectLiteral jsObjectLiteral = new JsObjectLiteral();
      popList(jsObjectLiteral.getPropertyInitializers(), x.propInits.size());
      push(jsObjectLiteral);
    }

    // @Override
    public void endVisit(JsonPropInit init, Context ctx) {
      JsExpression valueExpr = (JsExpression) pop();
      JsExpression labelExpr = (JsExpression) pop();
      push(new JsPropertyInitializer(labelExpr, valueExpr));
    }

    // @Override
    public void endVisit(JStringLiteral x, Context ctx) {
      push(jsProgram.getStringLiteral(x.getValue()));
    }

    // @Override
    public void endVisit(JThisRef x, Context ctx) {
      push(new JsThisRef());
    }

    // @Override
    public void endVisit(JThrowStatement x, Context ctx) {
      push(new JsThrow((JsExpression) pop())); // expr
    }

    // @Override
    public void endVisit(JTryStatement x, Context ctx) {
      JsTry jsTry = new JsTry();

      if (x.getFinallyBlock() != null) {
        JsBlock finallyBlock = (JsBlock) pop(); // finallyBlock
        if (finallyBlock.getStatements().size() > 0) {
          jsTry.setFinallyBlock(finallyBlock);
        }
      }

      int size = x.getCatchArgs().size();
      assert (size < 2 && size == x.getCatchBlocks().size());
      if (size == 1) {
        JsBlock catchBlock = (JsBlock) pop(); // catchBlocks
        pop(); // catchArgs
        JsCatch jsCatch = (JsCatch) catchMap.get(x.getCatchBlocks().get(0));
        jsCatch.setBody(catchBlock);
        jsTry.getCatches().add(jsCatch);
      }

      jsTry.setTryBlock((JsBlock) pop()); // tryBlock

      push(jsTry);
    }

    // @Override
    public void endVisit(JWhileStatement x, Context ctx) {
      JsWhile stmt = new JsWhile();
      if (x.getBody() != null) {
        stmt.setBody((JsStatement) pop()); // body
      } else {
        stmt.setBody(jsProgram.getEmptyStmt());
      }
      stmt.setCondition((JsExpression) pop()); // testExpr
      push(stmt);
    }

    // @Override
    public boolean visit(JClassType x, Context ctx) {
      if (alreadyRan.contains(x)) {
        return false;
      }

      // force super type to generate code first, this is required for prototype
      // chaining to work properly
      if (x.extnds != null && !alreadyRan.contains(x)) {
        accept(x.extnds);
      }

      return true;
    }

    // @Override
    public boolean visit(JMethod x, Context ctx) {
      currentMethod = x;
      return true;
    }

    // @Override
    public boolean visit(JProgram x, Context ctx) {
      JsStatements globalStatements = jsProgram.getGlobalBlock().getStatements();

      // declare some global vars
      JsVars vars = new JsVars();

      // reserve the "_" identifier
      vars.add(new JsVar(globalTemp));
      generatePackageNames(vars);
      globalStatements.add(vars);

      generateNullFunc(globalStatements);
      return true;
    }

    // @Override
    public boolean visit(JsniMethod x, Context ctx) {
      currentMethod = x;
      return false;
    }

    public boolean visit(JSwitchStatement x, Context ctx) {
      /*
       * What a pain.. JSwitchStatement and JsSwitch are modeled completely
       * differently. Here we try to resolve those differences.
       */
      JsSwitch jsSwitch = new JsSwitch();
      accept(x.getExpr());
      jsSwitch.setExpr((JsExpression) pop()); // expr

      List/* <JStatement> */bodyStmts = x.getBody().statements;
      if (bodyStmts.size() > 0) {
        JsStatements curStatements = null;
        for (int i = 0; i < bodyStmts.size(); ++i) {
          JStatement stmt = (JStatement) bodyStmts.get(i);
          accept(stmt);
          if (stmt instanceof JCaseStatement) {
            // create a new switch member
            JsSwitchMember switchMember = (JsSwitchMember) pop(); // stmt
            jsSwitch.getCases().add(switchMember);
            curStatements = switchMember.getStmts();
          } else {
            // add to statements for current case
            assert (curStatements != null);
            JsStatement newStmt = (JsStatement) pop(); // stmt
            if (newStmt != null) {
              // Empty JLocalDeclarationStatement produces a null
              curStatements.add(newStmt);
            }
          }
        }
      }

      push(jsSwitch);
      return false;
    }

    private JsExpression createAssignment(JsExpression lhs, JsExpression rhs) {
      return new JsBinaryOperation(JsBinaryOperator.ASG, lhs, rhs);
    }

    private JsExpression createCommaExpression(JsExpression lhs,
        JsExpression rhs) {
      if (lhs == null) {
        return rhs;
      } else if (rhs == null) {
        return lhs;
      }
      return new JsBinaryOperation(JsBinaryOperator.COMMA, lhs, rhs);
    }

    private void generateClassLiterals(JsVars vars) {
      /*
       * Class literals are useless right now, just use String literals and the
       * Object methods will basically work.
       */
      for (Iterator itType = classLits.keySet().iterator(); itType.hasNext();) {
        JType type = (JType) itType.next();
        JsName jsName = (JsName) classLits.get(type);
        String string;
        if (type instanceof JArrayType) {
          string = "class " + type.getJsniSignatureName().replace('/', '.');
        } else if (type instanceof JClassType) {
          string = "class " + type.getName();
        } else if (type instanceof JInterfaceType) {
          string = "interface " + type.getName();
        } else {
          string = type.getName();
        }
        JsStringLiteral stringLiteral = jsProgram.getStringLiteral(string);
        JsVar var = new JsVar(jsName);
        var.setInitExpr(stringLiteral);
        vars.add(var);
      }
    }

    private void generateClassSetup(JClassType x, JsStatements globalStmts) {
      generateSeedFuncAndPrototype(x, globalStmts);
      generateVTables(x, globalStmts);

      if (x == program.getTypeJavaLangObject()) {
        // special: setup a "toString" alias for java.lang.Object.toString()
        generateToStringAlias(x, globalStmts);
      }

      generateTypeName(x, globalStmts);
      generateTypeId(x, globalStmts);
    }

    private void generateGwtOnLoad(List entryFuncs, JsStatements globalStmts) {
      /**
       * <pre>
       * function gwtOnLoad(errFn, modName, modBase){
       *   $moduleName = modName;
       *   $moduleBase = modBase;
       *   if (errFn) {
       *     try {
       *       init();
       *     } catch(e) {
       *       errFn(modName);
       *     }
       *   } else {
       *     init();
       *   }
       * }
       * </pre>
       */
      JsFunction gwtOnLoad = new JsFunction(topScope);
      globalStmts.add(gwtOnLoad.makeStmt());
      JsName gwtOnLoadName = topScope.declareName("gwtOnLoad");
      gwtOnLoadName.setObfuscatable(false);
      gwtOnLoad.setName(gwtOnLoadName);
      JsBlock body = new JsBlock();
      gwtOnLoad.setBody(body);
      JsScope fnScope = gwtOnLoad.getScope();
      JsParameters params = gwtOnLoad.getParameters();
      JsName errFn = fnScope.declareName("errFn");
      JsName modName = fnScope.declareName("modName");
      JsName modBase = fnScope.declareName("modBase");
      params.add(new JsParameter(errFn));
      params.add(new JsParameter(modName));
      params.add(new JsParameter(modBase));
      JsExpression asg = createAssignment(
          topScope.findExistingUnobfuscatableName("$moduleName").makeRef(),
          modName.makeRef());
      body.getStatements().add(asg.makeStmt());
      asg = createAssignment(topScope.findExistingUnobfuscatableName(
          "$moduleBase").makeRef(), modBase.makeRef());
      body.getStatements().add(asg.makeStmt());
      JsIf jsIf = new JsIf();
      body.getStatements().add(jsIf);
      jsIf.setIfExpr(errFn.makeRef());
      JsTry jsTry = new JsTry();
      jsIf.setThenStmt(jsTry);
      JsBlock callBlock = new JsBlock();
      jsIf.setElseStmt(callBlock);
      jsTry.setTryBlock(callBlock);
      for (int i = 0; i < entryFuncs.size(); ++i) {
        JsFunction func = (JsFunction) entryFuncs.get(i);
        if (func != null) {
          JsInvocation call = new JsInvocation();
          call.setQualifier(func.getName().makeRef());
          callBlock.getStatements().add(call.makeStmt());
        }
      }
      JsCatch jsCatch = new JsCatch(fnScope, "e");
      jsTry.getCatches().add(jsCatch);
      JsBlock catchBlock = new JsBlock();
      jsCatch.setBody(catchBlock);
      JsInvocation errCall = new JsInvocation();
      catchBlock.getStatements().add(errCall.makeStmt());
      errCall.setQualifier(errFn.makeRef());
      errCall.getArguments().add(modName.makeRef());
    }

    private void generateNullFunc(JsStatements globalStatements) {
      // handle null method
      JsFunction nullFunc = new JsFunction(topScope, nullMethodName);
      nullFunc.setBody(new JsBlock());
      globalStatements.add(nullFunc.makeStmt());
    }

    private void generatePackageNames(JsVars vars) {
      for (Iterator it = packageNames.entrySet().iterator(); it.hasNext();) {
        Map.Entry entry = (Entry) it.next();
        String packageName = (String) entry.getKey();
        JsName name = (JsName) entry.getValue();
        JsVar jsVar = new JsVar(name);
        jsVar.setInitExpr(jsProgram.getStringLiteral(packageName));
        vars.add(jsVar);
      }
    }

    private void generateSeedFuncAndPrototype(JClassType x,
        JsStatements globalStmts) {
      if (x != program.getTypeJavaLangString()) {
        JsName seedFuncName = getName(x);

        // seed function
        // function com_example_foo_Foo() { }
        JsFunction seedFunc = new JsFunction(topScope, seedFuncName);
        JsBlock body = new JsBlock();
        seedFunc.setBody(body);
        globalStmts.add(seedFunc.makeStmt());

        // setup prototype, assign to temp
        // _ = com_example_foo_Foo.prototype = new com_example_foo_FooSuper();
        JsNameRef lhs = prototype.makeRef();
        lhs.setQualifier(seedFuncName.makeRef());
        JsExpression rhs;
        if (x.extnds != null) {
          JsNew newExpr = new JsNew();
          newExpr.setConstructorExpression(getName(x.extnds).makeRef());
          rhs = newExpr;
        } else {
          rhs = new JsObjectLiteral();
        }
        JsExpression protoAsg = createAssignment(lhs, rhs);
        JsExpression tmpAsg = createAssignment(globalTemp.makeRef(), protoAsg);
        globalStmts.add(tmpAsg.makeStmt());
      } else {
        /*
         * MAGIC: java.lang.String is implemented as a JavaScript String
         * primitive with a modified prototype.
         */
        JsNameRef rhs = prototype.makeRef();
        rhs.setQualifier(jsProgram.getRootScope().declareName("String").makeRef());
        JsExpression tmpAsg = createAssignment(globalTemp.makeRef(), rhs);
        globalStmts.add(tmpAsg.makeStmt());
      }
    }

    private void generateToStringAlias(JClassType x, JsStatements globalStmts) {
      JMethod toStringMeth = program.getSpecialMethod("Object.toString");
      if (x.methods.contains(toStringMeth)) {
        // _.toString = function(){return this.java_lang_Object_toString();}

        // lhs
        JsName lhsName = objectScope.declareName("toString");
        lhsName.setObfuscatable(false);
        JsNameRef lhs = lhsName.makeRef();
        lhs.setQualifier(globalTemp.makeRef());

        // rhs
        JsInvocation call = new JsInvocation();
        JsNameRef toStringRef = new JsNameRef(getPolyName(toStringMeth));
        toStringRef.setQualifier(new JsThisRef());
        call.setQualifier(toStringRef);
        JsReturn jsReturn = new JsReturn(call);
        JsFunction rhs = new JsFunction(topScope);
        JsBlock body = new JsBlock();
        body.getStatements().add(jsReturn);
        rhs.setBody(body);

        // asg
        JsExpression asg = createAssignment(lhs, rhs);
        globalStmts.add(new JsExprStmt(asg));
      }
    }

    private void generateTypeId(JClassType x, JsStatements globalStmts) {
      int typeId = program.getTypeId(x);
      if (typeId >= 0) {
        JField typeIdField = program.getSpecialField("Object.typeId");
        JsNameRef fieldRef = getName(typeIdField).makeRef();
        fieldRef.setQualifier(globalTemp.makeRef());
        JsIntegralLiteral typeIdLit = jsProgram.getIntegralLiteral(BigInteger.valueOf(typeId));
        JsExpression asg = createAssignment(fieldRef, typeIdLit);
        globalStmts.add(new JsExprStmt(asg));
      }
    }

    private void generateTypeName(JClassType x, JsStatements globalStmts) {
      JField typeIdField = program.getSpecialField("Object.typeName");
      JsNameRef lhs = getName(typeIdField).makeRef();
      lhs.setQualifier(globalTemp.makeRef());

      // Split the full class name into package + class so package strings
      // can be merged.
      String className = getClassName(x.getName());
      String packageName = getPackageName(x.getName());
      JsExpression rhs;
      if (packageName != null) {
        // use "com.example.foo." + "Foo"
        JsName name = (JsName) packageNames.get(packageName);
        rhs = new JsBinaryOperation(JsBinaryOperator.ADD, name.makeRef(),
            jsProgram.getStringLiteral(className));
      } else {
        // no package name could be split, just use the full name
        rhs = jsProgram.getStringLiteral(x.getName());
      }
      JsExpression asg = createAssignment(lhs, rhs);
      globalStmts.add(new JsExprStmt(asg));
    }

    private void generateTypeTable(JsVars vars) {
      JField typeIdArray = program.getSpecialField("Cast.typeIdArray");
      JsName jsName = getName(typeIdArray);
      JsArrayLiteral arrayLit = new JsArrayLiteral();
      for (int i = 0; i < program.getJsonTypeTable().size(); ++i) {
        JsonObject jsonObject = (JsonObject) program.getJsonTypeTable().get(i);
        accept(jsonObject);
        arrayLit.getExpressions().add((JsExpression) pop());
      }
      JsVar var = new JsVar(jsName);
      var.setInitExpr(arrayLit);
      vars.add(var);
    }

    private void generateVTables(JClassType x, JsStatements globalStmts) {
      for (int i = 0; i < x.methods.size(); ++i) {
        JMethod method = (JMethod) x.methods.get(i);
        if (!method.isStatic() && !method.isAbstract()) {
          JsNameRef lhs = getPolyName(method).makeRef();
          lhs.setQualifier(globalTemp.makeRef());
          JsNameRef rhs = getName(method).makeRef();
          JsExpression asg = createAssignment(lhs, rhs);
          globalStmts.add(new JsExprStmt(asg));
        }
      }
    }

    private void handleClinit(JsFunction clinitFunc, JReferenceType chainTo) {
      JsStatements statements = clinitFunc.getBody().getStatements();
      // self-assign to the null method immediately (to prevent reentrancy)
      JsExpression asg = createAssignment(clinitFunc.getName().makeRef(),
          nullMethodName.makeRef());
      statements.add(0, asg.makeStmt());
      if (chainTo != null) {
        JMethod chainToMeth = (JMethod) chainTo.methods.get(0);
        JsInvocation jsInvocation = new JsInvocation();
        JsNameRef qualifier = getName(chainToMeth).makeRef();
        jsInvocation.setQualifier(qualifier);
        statements.add(1, jsInvocation.makeStmt());
      }
    }

    private JsInvocation maybeCreateClinitCall(JField x) {
      if (!x.isStatic()) {
        return null;
      }

      JReferenceType enclosingType = x.getEnclosingType();
      if (!typeOracle.checkClinit(currentMethod.getEnclosingType(),
          enclosingType)) {
        return null;
      }

      // The actual target class might not have a direct clinit; find the super
      // class that does.
      while (!typeOracle.hasDirectClinit(enclosingType)) {
        enclosingType = enclosingType.extnds;
      }
      JMethod clinitMethod = (JMethod) enclosingType.methods.get(0);
      JsInvocation jsInvocation = new JsInvocation();
      jsInvocation.setQualifier(getName(clinitMethod).makeRef());
      return jsInvocation;
    }

    private JsInvocation maybeCreateClinitCall(JMethod x) {
      if (!x.isStatic()) {
        return null;
      }
      JReferenceType enclosingType = x.getEnclosingType();
      if (!typeOracle.hasClinit(enclosingType)) {
        return null;
      }
      if (program.isStaticImpl(x)) {
        return null;
      }
      // avoid recursion sickness
      if (x == enclosingType.methods.get(0)) {
        return null;
      }

      // The actual target class might not have a direct clinit; find the super
      // class that does.
      while (!typeOracle.hasDirectClinit(enclosingType)) {
        enclosingType = enclosingType.extnds;
      }
      JMethod clinitMethod = (JMethod) enclosingType.methods.get(0);
      JsInvocation jsInvocation = new JsInvocation();
      jsInvocation.setQualifier(getName(clinitMethod).makeRef());
      return jsInvocation;
    }

    private JsNode pop() {
      return (JsNode) nodeStack.pop();
    }

    private/* <T extends JsNode> */List/* <T> */popList(int count) {
      JsNode[] array = new JsNode[count];
      while (count > 0) {
        array[--count] = pop();
      }

      List/* <T> */list = new ArrayList/* <T> */();
      for (int i = 0; i < array.length; i++) {
        JsNode item = array[i];
        if (item != null) {
          list.add(/* (T) */item);
        }
      }
      return list;
    }

    private void popList(JsCollection collection, int count) {
      JsNode[] array = new JsNode[count];
      while (count > 0) {
        array[--count] = pop();
      }

      for (int i = 0; i < array.length; i++) {
        JsNode item = array[i];
        if (item != null) {
          collection.addNode(item);
        }
      }
    }

    private void push(JsNode node) {
      nodeStack.push(node);
    }
  }

  private static class JavaToJsOperatorMap {
    private static final Map/* <JBinaryOperator, JsBinaryOperator> */bOpMap = new IdentityHashMap();
    private static final Map/* <JUnaryOperator, JsUnaryOperator> */uOpMap = new IdentityHashMap();

    static {
      bOpMap.put(JBinaryOperator.MUL, JsBinaryOperator.MUL);
      bOpMap.put(JBinaryOperator.DIV, JsBinaryOperator.DIV);
      bOpMap.put(JBinaryOperator.MOD, JsBinaryOperator.MOD);
      bOpMap.put(JBinaryOperator.ADD, JsBinaryOperator.ADD);
      bOpMap.put(JBinaryOperator.SUB, JsBinaryOperator.SUB);
      bOpMap.put(JBinaryOperator.SHL, JsBinaryOperator.SHL);
      bOpMap.put(JBinaryOperator.SHR, JsBinaryOperator.SHR);
      bOpMap.put(JBinaryOperator.SHRU, JsBinaryOperator.SHRU);
      bOpMap.put(JBinaryOperator.LT, JsBinaryOperator.LT);
      bOpMap.put(JBinaryOperator.LTE, JsBinaryOperator.LTE);
      bOpMap.put(JBinaryOperator.GT, JsBinaryOperator.GT);
      bOpMap.put(JBinaryOperator.GTE, JsBinaryOperator.GTE);
      bOpMap.put(JBinaryOperator.EQ, JsBinaryOperator.EQ);
      bOpMap.put(JBinaryOperator.NEQ, JsBinaryOperator.NEQ);
      bOpMap.put(JBinaryOperator.BIT_AND, JsBinaryOperator.BIT_AND);
      bOpMap.put(JBinaryOperator.BIT_XOR, JsBinaryOperator.BIT_XOR);
      bOpMap.put(JBinaryOperator.BIT_OR, JsBinaryOperator.BIT_OR);
      bOpMap.put(JBinaryOperator.AND, JsBinaryOperator.AND);
      bOpMap.put(JBinaryOperator.OR, JsBinaryOperator.OR);
      bOpMap.put(JBinaryOperator.ASG, JsBinaryOperator.ASG);
      bOpMap.put(JBinaryOperator.ASG_ADD, JsBinaryOperator.ASG_ADD);
      bOpMap.put(JBinaryOperator.ASG_SUB, JsBinaryOperator.ASG_SUB);
      bOpMap.put(JBinaryOperator.ASG_MUL, JsBinaryOperator.ASG_MUL);
      bOpMap.put(JBinaryOperator.ASG_DIV, JsBinaryOperator.ASG_DIV);
      bOpMap.put(JBinaryOperator.ASG_MOD, JsBinaryOperator.ASG_MOD);
      bOpMap.put(JBinaryOperator.ASG_SHL, JsBinaryOperator.ASG_SHL);
      bOpMap.put(JBinaryOperator.ASG_SHR, JsBinaryOperator.ASG_SHR);
      bOpMap.put(JBinaryOperator.ASG_SHRU, JsBinaryOperator.ASG_SHRU);
      bOpMap.put(JBinaryOperator.ASG_BIT_AND, JsBinaryOperator.ASG_BIT_AND);
      bOpMap.put(JBinaryOperator.ASG_BIT_OR, JsBinaryOperator.ASG_BIT_OR);
      bOpMap.put(JBinaryOperator.ASG_BIT_XOR, JsBinaryOperator.ASG_BIT_XOR);

      uOpMap.put(JUnaryOperator.INC, JsUnaryOperator.INC);
      uOpMap.put(JUnaryOperator.DEC, JsUnaryOperator.DEC);
      uOpMap.put(JUnaryOperator.NEG, JsUnaryOperator.NEG);
      uOpMap.put(JUnaryOperator.NOT, JsUnaryOperator.NOT);
      uOpMap.put(JUnaryOperator.BIT_NOT, JsUnaryOperator.BIT_NOT);
    }

    public static JsBinaryOperator get(JBinaryOperator op) {
      return (JsBinaryOperator) bOpMap.get(op);
    }

    public static JsUnaryOperator get(JUnaryOperator op) {
      return (JsUnaryOperator) uOpMap.get(op);
    }
  }

  private class SortVisitor extends JVisitor {

    private final HasNameSort hasNameSort = new HasNameSort();

    public void endVisit(JClassType x, Context ctx) {
      Collections.sort(x.fields, hasNameSort);

      // Sort the methods manually to avoid sorting clinit out of place!
      List methods = x.methods;
      Object a[] = methods.toArray();
      Arrays.sort(a, 1, a.length, hasNameSort);
      for (int i = 1; i < a.length; i++) {
        methods.set(i, a[i]);
      }
    }

    public void endVisit(JInterfaceType x, Context ctx) {
      Collections.sort(x.fields, hasNameSort);
      Collections.sort(x.methods, hasNameSort);
    }

    public void endVisit(JMethod x, Context ctx) {
      Collections.sort(x.locals, hasNameSort);
    }

    public void endVisit(JProgram x, Context ctx) {
      Collections.sort(x.entryMethods, hasNameSort);
      Collections.sort(x.getDeclaredTypes(), hasNameSort);
    }
  }

  public static void exec(JProgram program, JsProgram jsProgram) {
    GenerateJavaScriptAST generateJavaScriptAST = new GenerateJavaScriptAST(
        program, jsProgram);
    generateJavaScriptAST.execImpl();
  }

  private static String getClassName(String fullName) {
    int pos = fullName.lastIndexOf(".");
    return fullName.substring(pos + 1);
  }

  private static String getPackageName(String fullName) {
    int pos = fullName.lastIndexOf(".");
    return fullName.substring(0, pos + 1);
  }

  private final Map/* <JBlock, JsCatch> */catchMap = new IdentityHashMap();
  private final Map/* <JType, JsName> */classLits = new IdentityHashMap();
  private final Map/* <JClassType, JsScope> */classScopes = new IdentityHashMap();

  /**
   * Contains JsNames for all interface methods. A special scope is needed so
   * that independent classes will obfuscate their interface implementation
   * methods the same way.
   */
  private final JsScope interfaceScope;

  private final JsProgram jsProgram;
  private final Map/* <JMethod, JsFunction> */methodMap = new IdentityHashMap();
  private final Map/* <HasName, JsName> */names = new IdentityHashMap();
  private JsName nullMethodName;

  /**
   * Contains JsNames for the Object instance methods, such as equals, hashCode,
   * and toString. All other class scopes have this scope as an ultimate parent.
   */
  private final JsScope objectScope;
  private final Map/* <String, JsName> */packageNames = new TreeMap();
  private final Map/* <JMethod, JsName> */polymorphicNames = new IdentityHashMap();
  private final JProgram program;

  /**
   * Contains JsNames for all globals, such as static fields and methods.
   */
  private final JsScope topScope;

  private final JTypeOracle typeOracle;

  private GenerateJavaScriptAST(JProgram program, JsProgram jsProgram) {
    this.program = program;
    typeOracle = program.typeOracle;
    this.jsProgram = jsProgram;
    topScope = jsProgram.getScope();
    objectScope = jsProgram.getObjectScope();
    interfaceScope = new JsScope(objectScope, "Interfaces");
  }

  String getNameString(HasName hasName) {
    String s = hasName.getName().replaceAll("_", "_1").replace('.', '_');
    return s;
  }

  String mangleName(JField x) {
    String s = getNameString(x.getEnclosingType()) + '_' + getNameString(x);
    return s;
  }

  String mangleNameForGlobal(JMethod x) {
    String s = getNameString(x.getEnclosingType()) + '_' + getNameString(x)
        + "__";
    for (int i = 0; i < x.getOriginalParamTypes().size(); ++i) {
      JType type = (JType) x.getOriginalParamTypes().get(i);
      s += type.getJavahSignatureName();
    }
    return s;
  }

  String mangleNameForPoly(JMethod x) {
    String s = getNameString(x) + "__";
    for (int i = 0; i < x.getOriginalParamTypes().size(); ++i) {
      JType type = (JType) x.getOriginalParamTypes().get(i);
      s += type.getJavahSignatureName();
    }
    return s;
  }

  private void execImpl() {
    SortVisitor sorter = new SortVisitor();
    sorter.accept(program);
    CreateNamesAndScopesVisitor creator = new CreateNamesAndScopesVisitor();
    creator.accept(program);
    GenerateJavaScriptVisitor generator = new GenerateJavaScriptVisitor();
    generator.accept(program);
  }

  private JsName getName(HasName x) {
    return (JsName) names.get(x);
  }

  private JsName getPolyName(HasName x) {
    return (JsName) polymorphicNames.get(x);
  }

}
TOP

Related Classes of com.google.gwt.dev.jjs.impl.GenerateJavaScriptAST$GenerateJavaScriptVisitor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.