Package org.dtk.resources.dependencies

Source Code of org.dtk.resources.dependencies.ScriptParser

package org.dtk.resources.dependencies;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.dtk.analysis.script.ScriptParserErrorReporter;
import org.mozilla.javascript.CompilerEnvirons;
import org.mozilla.javascript.ContextFactory;
import org.mozilla.javascript.ErrorReporter;
import org.mozilla.javascript.EvaluatorException;
import org.mozilla.javascript.FunctionNode;
import org.mozilla.javascript.Node;
import org.mozilla.javascript.Parser;
import org.mozilla.javascript.ScriptOrFnNode;
import org.mozilla.javascript.Token;

public class ScriptParser {
  // Source code for JavaScript to be parsed
  String scriptSource;
 
  // Store indication that we've already parsed this script.
  boolean scriptParsed = false;
 
  // Store modules referenced in any dojo.provide/declare/require function calls,
  // keyed on the function name.
  Map<String, List<String>> moduleLists = new HashMap<String, List<String>>() {{
    put("require", new ArrayList<String>());
    put("provide", new ArrayList<String>());
    put("declare", new ArrayList<String>());
  }};
 
  // Collection of any custom module paths defined, through dojo.registerModulePath
  // or djConfig.
  Map<String, String> modulePaths = new HashMap<String, String>();
 
  Set<Node> parsedNodes = new HashSet<Node>();
 
  public ScriptParser(String scriptSource) {
    this.scriptSource = scriptSource;
  }
 
 
  protected void parse() throws EvaluatorException {
    // Parse script source
    CompilerEnvirons ce = new CompilerEnvirons();
    ScriptParserErrorReporter errorReporter = new ScriptParserErrorReporter();
   
    ce.setGenerateDebugInfo(true);   
    ce.initFromContext(ContextFactory.getGlobal().enterContext());
    ce.setErrorReporter(errorReporter);
   
    Parser p = new Parser(ce, errorReporter);
    ScriptOrFnNode ast = p.parse(this.scriptSource, "script", 0);
   
    recursiveParse(ast);
   
    this.scriptParsed = true;
  }
 
  protected void recursiveParse(Node node) {
    // Ignore null nodes or those we have seen before!
    if (node == null || parsedNodes.contains(node)) {
      return;
    }
   
    // Script level nodes have global list of function definitions.
    // Parse inner tokens for function definitions here.
    if (node instanceof ScriptOrFnNode) {
      ScriptOrFnNode sofn = (ScriptOrFnNode) node;
      int count = sofn.getFunctionCount() - 1;
      while (count > -1) {
        FunctionNode fn = sofn.getFunctionNode(count);
        recursiveParse(fn);
        count--;
      }
    }
   
    // Parse children from left to right.
    recursiveParse(node.getFirstChild());
    recursiveParse(node.getLastChild());
   
    // Parse this node and then move along to next
    parseNode(node);
    parsedNodes.add(node);
   
    recursiveParse(node.getNext());   
  }
 
  protected void parseNode(Node node) {
    switch(node.getType()) {
    // Function call token
    case Token.CALL:
      if (isDojoCall(node)) {
        // We are looking for dojo.some_function(...) calls, this means first
        // child of function call is property resolution of function in the
        // dojo object.
        Node propertyResolution = node.getFirstChild();
       
        if (propertyResolution != null) {
          // Find property label accessing object
          String propertyName = functionPropertyName(propertyResolution);
          // Single argument module functions, put out value and store in appropriate list.
          if ("require".equals(propertyName) || "provide".equals(propertyName) || "declare".equals(propertyName)) {
            String firstArg = functionCallArgument(propertyResolution, 0);
            List<String> moduleList = moduleLists.get(propertyName);
           
            if (!moduleList.contains(firstArg)) {
              moduleList.add(firstArg);
            }
          } else if ("registerModulePath".equals(propertyName)) {
            // Extract module identifier and module path, first two arguments to
            // dojo.registerModulePath();
            String modulePrefix = functionCallArgument(propertyResolution, 0);
            String modulePath = functionCallArgument(propertyResolution, 1);
           
            modulePaths.put(modulePrefix, modulePath);
          }
        }
       
      }
      break;
    // Global identifier token, check for djConfig assignment, djConfig = {...}
    case Token.SETNAME:
      // LHS is global name binding.
      Node bindName = node.getFirstChild();
     
      // LHS should be djConfig global assignment.
      if (bindName.getType() == Token.BINDNAME && "djConfig".equals(bindName.getString())) {
        // RHS of statement should be literal assignment.
        Node assignmentExpr = node.getLastChild();
        parseModulePathsExpr(assignmentExpr);
      }
     
      break;
    // Local identifier token, check for djConfig assignment, var djConfig = {...}
    case Token.NAME:
      // LHS is name token
      if ("djConfig".equals(node.getString())) {
        // Check for object literal assignment
        Node assignmentExpr = node.getFirstChild();
        parseModulePathsExpr(assignmentExpr);
      }
     
      break;
    }
  }
 
  protected boolean isDojoCall (Node functionCall) {
    boolean isDojoCall = false;
   
    Node propertyLookup = functionCall.getFirstChild();
   
    // Check node is a property name lookup from the "dojo" object.
    // Property Name Lookup -> Source, Property
    if (propertyLookup.getType() == Token.GETPROP && propertyLookup.getFirstChild().getType() == Token.NAME) {
        String functionName = propertyLookup.getFirstChild().getString();
        if ("dojo".equals(functionName)) {
          isDojoCall = true;
      }
    }
   
    return isDojoCall;
  }
 
  // Try to find module paths expression in djConfig object literal. Merge results
  // into existing module paths map.
  protected void parseModulePathsExpr(Node objectLiteral) {
    if (objectLiteral.getType() == Token.OBJECTLIT) {
      Node modulePathsLiteral = findLiteralValue(objectLiteral, "modulePaths");
      if (modulePathsLiteral != null && modulePathsLiteral.getType() == Token.OBJECTLIT) {
        // Put out all String module path values and merge into existing map.
        Map<String, String> modulePaths = findLiteralStringValues(modulePathsLiteral);
        this.modulePaths.putAll(modulePaths);
      }
    }
  }
 
  protected Map<String, String> findLiteralStringValues(Node objLiteral) {
    Map<String, String> literalValues = new HashMap<String, String>();
   
    // Find object literal keys from property list.
    Object[] propertyList = (Object[]) objLiteral.getProp(Token.EQ);
   
    // Iterate through each literal key, extracting associated value. Ignore
    // any non-string values, we aren't attempting inner-resolution of references.
    if (propertyList != null) {
      int numArgs = 0;
      // Child nodes are linked list of object values
      Node propertyValue = objLiteral.getFirstChild();
     
      while (numArgs < propertyList.length) {
        // Convert key/value pairs into a handy format
        if (propertyValue.getType() == Token.STRING) {
          literalValues.put((String) propertyList[numArgs], propertyValue.getString());
        }
       
        propertyValue = propertyValue.getNext();
        numArgs++;
      }
    }
   
    return literalValues;
  }
 
  // Find an object literal's value for a given key. If not found,
  // return null.
  protected Node findLiteralValue(Node objLiteral, String key) {
    Node literalValueNode = null;
   
    // Get list of all properties
    Object[] propertyList = (Object[]) objLiteral.getProp(Token.EQ);
   
    // Iterate through key/values looking for a match
    if (propertyList != null) {
      Node propertyValue = objLiteral.getFirstChild();
      for(int index = 0; index < propertyList.length; index++) {
        if (key.equals(propertyList[index])) {
          literalValueNode = propertyValue;
          break;
        }
        propertyValue = propertyValue.getNext();
      }
    }
   
    return literalValueNode;
  }
 
  // Extract property name accessed. Will have two child nodes,
  // object reference and property name. Ignore any further
  // variable resolution.
  protected String functionPropertyName(Node propertyLookup) {
    String propertyName = null;
   
    // First argument is an object reference that lookup should be
    // performed against.
    Node objectReference = propertyLookup.getFirstChild();
   
    if (objectReference != null) {
      // Find property identifier we are retrieving...
      Node propertyNameNode = objectReference.getNext();
     
      // Only handle explicit string properties, we cannot resolve variables.
      if (propertyNameNode != null && propertyNameNode.getType() == Token.STRING) {
        propertyName = propertyNameNode.getString();
      }
    }
   
    return propertyName;
  }
 
  // Given child nodes for a function call, object ref and arguments, return
  // the string value for the first argument. Ignore any complicated resolution.
  protected String functionCallArgument(Node functionCallNodes, int argIndex) {
    String fnCallArg = null;
   
    // Traverse argument nodes until we reach desired one.
    Node argument = functionCallNodes.getNext();
    while (argument != null && argIndex > 0) {
      argument = argument.getNext();
      argIndex--;
    };
   
    // Check we have a simple type value.
    if (argument != null && argument.getType() == Token.STRING) {
      fnCallArg = argument.getString();
    }
   
    return fnCallArg;
  }
 
  public Map<String, String> retrieveModulePaths() throws EvaluatorException {
    if (!scriptParsed) {
      parse();
    }
   
    return this.modulePaths;
  }
 
  public List<String> retrieveModuleRequires() throws EvaluatorException {
    return retrieveModuleList("require");
  }
 
  public List<String> retrieveModuleDeclares() throws EvaluatorException {
    return retrieveModuleList("declare");
  }
 
  public List<String> retrieveModuleProvides() throws EvaluatorException {
    return retrieveModuleList("provide");
  }
 
  protected List<String> retrieveModuleList(String identifier) throws EvaluatorException {
    if (!scriptParsed) {
      parse();
    }
   
    return this.moduleLists.get(identifier);
  }
}
TOP

Related Classes of org.dtk.resources.dependencies.ScriptParser

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.