/*******************************************************************************
* Copyright (c) 2009, 2010 Innovation Gate GmbH.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Innovation Gate GmbH - initial API and implementation
******************************************************************************/
package de.innovationgate.eclipse.editors.tmlscript.parsing;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.content.IContentType;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.mozilla.javascript.CompilerEnvirons;
import org.mozilla.javascript.ErrorReporter;
import org.mozilla.javascript.EvaluatorException;
import org.mozilla.javascript.Parser;
import org.mozilla.javascript.ast.ArrayLiteral;
import org.mozilla.javascript.ast.AstNode;
import org.mozilla.javascript.ast.AstRoot;
import org.mozilla.javascript.ast.ElementGet;
import org.mozilla.javascript.ast.ExpressionStatement;
import org.mozilla.javascript.ast.FunctionCall;
import org.mozilla.javascript.ast.Name;
import org.mozilla.javascript.ast.NodeVisitor;
import org.mozilla.javascript.ast.Scope;
import org.mozilla.javascript.ast.StringLiteral;
import org.mozilla.javascript.ast.VariableDeclaration;
import de.innovationgate.eclipse.editors.Plugin;
import de.innovationgate.eclipse.editors.ResourceIDs;
import de.innovationgate.eclipse.editors.tml.TMLPartitionScanner;
import de.innovationgate.eclipse.editors.tml.TMLRegion;
import de.innovationgate.eclipse.editors.tmlscript.CodeCompletionLRUManager;
import de.innovationgate.eclipse.editors.tmlscript.TMLScriptCompletionProposal;
import de.innovationgate.utils.WGUtils;
import de.innovationgate.wga.model.VersionCompliance;
public class TMLScriptRegion {
private static final String PACKAGE_PREFIX = "$P$";
private static final String CLASS_PREFIX = "$C$";
private IRegion _region;
private IDocument _document;
private int _cursorInDocument;
private Map<String, TMLScriptScope> _scopes = new LinkedHashMap<String, TMLScriptScope>();
private String _typed;
private String _env;
private String _prefix;
private VersionCompliance _versionCompliance;
private int _envFlags = ReflectionManager.ENV_DEFAULT;
public static TMLScriptRegion parse(IRegion region, IDocument document, int cursorInDocument, VersionCompliance versionCompliance) throws BadLocationException {
TMLScriptRegion scriptRegion = new TMLScriptRegion(region, document, cursorInDocument, versionCompliance);
scriptRegion.parse();
return scriptRegion;
}
private TMLScriptRegion(IRegion region, IDocument document, int cursorInDocument, VersionCompliance versionCompliance) {
_region = region;
_document = document;
_cursorInDocument = cursorInDocument;
_versionCompliance = versionCompliance;
}
private void parse() throws BadLocationException {
// compute environment flags
IContentType contentType;
try {
contentType = Plugin.getDefault().getActiveFile().getContentDescription().getContentType();
if (contentType.getId().equals(ResourceIDs.CONTENT_TYPE_TML)) {
_envFlags = _envFlags | ReflectionManager.ENV_BLOCK;
} else if (contentType.getId().equals(ResourceIDs.CONTENT_TYPE_TMLSCRIPT)) {
_envFlags = _envFlags | ReflectionManager.ENV_MODULE;
}
}
catch (CoreException e1) {
Plugin.getDefault().logWarning("Unable to define environment flags for tml script region", e1);
}
if (_region.getOffset() > 0) {
if (_document.getContentType(_region.getOffset() - 1).equals(TMLPartitionScanner.TML_TAG_START)) {
try {
TMLRegion region = TMLRegion.parse(_document.getPartition(_region.getOffset() - 1), _document);
if (region.getTagName().equals("action")) {
_envFlags = _envFlags | ReflectionManager.ENV_ACTION;
} else if (region.getTagName().equals("eventscript")) {
_envFlags = _envFlags | ReflectionManager.ENV_EVENTSCRIPT;
}
}
catch (ParseException e) {
Plugin.getDefault().logWarning("Unable to define environment flags for tml script region", e);
}
}
}
// first time parsing - compute scopes and variable declarations
AstRoot root = createParser().parse(_document.get(_region.getOffset(), _region.getLength()), null, 0);
root.visit(new NodeVisitor() {
public boolean visit(AstNode node) {
Scope scope = node.getEnclosingScope();
List<Integer> scopePath = new ArrayList<Integer>();
while (scope != null) {
scopePath.add(scope.getAbsolutePosition());
scope = scope.getParentScope();
}
Collections.reverse(scopePath);
String path = WGUtils.serializeCollection(scopePath, ".");
TMLScriptScope tmlScriptScope = _scopes.get(path);
if (tmlScriptScope == null && node.getEnclosingScope() != null) {
tmlScriptScope = new TMLScriptScope(path, node.getEnclosingScope().getAbsolutePosition(), node.getEnclosingScope().getLength());
_scopes.put(path, tmlScriptScope);
}
if (node instanceof VariableDeclaration || node instanceof ExpressionStatement) {
if (node instanceof VariableDeclaration) {
VariableDeclaration declaration = (VariableDeclaration) node;
String source = declaration.toSource();
String name = source.substring(0, source.indexOf("=")).trim();
name = name.substring(name.indexOf("var") + 3).trim();
String value = source.substring(source.indexOf("=") + 1).trim();
TMLScriptVariableDeclaration tmlScriptVarDec = new TMLScriptVariableDeclaration(tmlScriptScope, name);
tmlScriptVarDec.setValue(value);
tmlScriptVarDec.setOffset(declaration.getAbsolutePosition());
tmlScriptScope.getVariableDeclarations().add(tmlScriptVarDec);
} else if (node instanceof ExpressionStatement) {
ExpressionStatement expressionStatement = (ExpressionStatement) node;
String source = expressionStatement.getExpression().toSource();
if (source != null && source.contains("=")) {
String name = source.substring(0, source.indexOf("=")).trim();
String value = source.substring(source.indexOf("=") + 1).trim();
TMLScriptVariableDeclaration tmlScriptVarDec = new TMLScriptVariableDeclaration(tmlScriptScope, name);
tmlScriptVarDec.setValue(value);
tmlScriptVarDec.setOffset(expressionStatement.getAbsolutePosition());
tmlScriptVarDec.setTmlVariable(true);
tmlScriptScope.getVariableDeclarations().add(tmlScriptVarDec);
}
}
}
return true;
}
});
// second parsing run - compute type of variables
Iterator<TMLScriptScope> scopes = _scopes.values().iterator();
while (scopes.hasNext()) {
TMLScriptScope scope = scopes.next();
Iterator<TMLScriptVariableDeclaration> vars = scope.getVariableDeclarations().iterator();
while (vars.hasNext()) {
final TMLScriptVariableDeclaration fVar = vars.next();
Map<String,TMLScriptVariableDeclaration> visibleVars = retrieveVisibleVars(fVar);
String currentEnvironmentObject = ReflectionManager.GLOBAL_SCOPE_CLASSNAME;
String[] varValues = splitVariableValue(fVar.getValue(), false);
if (fVar.getValue().startsWith("Packages") || (fVar.getValue().startsWith("new") && fVar.getValue().contains("Packages"))) {
fVar.setType(resolvePackageOrConstructorCall(fVar.getValue(), false));
} else {
for (String value : varValues) {
AstRoot valueAst = createParser().parse(value, "", 0);
TypeVisitor visitor = new TypeVisitor(currentEnvironmentObject, visibleVars, _versionCompliance, _envFlags);
valueAst.visit(visitor);
currentEnvironmentObject = visitor.getType();
if (currentEnvironmentObject == null) {
break;
}
}
//fVar.setType(ReflectionManager.jsWrap(currentEnvironmentObject));
fVar.setType(currentEnvironmentObject);
}
}
}
// compute user input
IRegion lineRegion = _document.getLineInformationOfOffset(_cursorInDocument);
if (lineRegion.getOffset() < _region.getOffset()) {
// stay in parsion region
lineRegion = _region;
}
// read backwards until open bracket / line start or control character
int numClosedBrackets = 0;
int pos =_cursorInDocument -1;
while (pos >= lineRegion.getOffset()) {
char c = _document.getChar(pos);
if (c == ')') {
numClosedBrackets++;
}
if (c =='(') {
if (numClosedBrackets == 0) {
break;
} else {
numClosedBrackets--;
}
}
if (c == '=' || c==';' || c=='>' || c=='<' || c == '|') {
break;
}
if (c == ',' && numClosedBrackets == 0) {
break;
}
pos--;
}
_typed = _document.get(pos + 1, _cursorInDocument - pos - 1).trim();
// compute object in context
_env = ReflectionManager.GLOBAL_SCOPE_CLASSNAME;
if (_typed != null) {
String[] tokens = splitVariableValue(_typed, true);
if (_typed.startsWith("Packages") || (_typed.startsWith("new") && _typed.contains("Packages"))) {
_env = resolvePackageOrConstructorCall(_typed, true);
} else {
for (int i = 0; i < tokens.length; i++) {
String value = tokens[i];
if (!value.equals(".") && (i < tokens.length - 1)) {
AstRoot valueAst = createParser().parse(value, "", 0);
TypeVisitor visitor = new TypeVisitor(_env, retrieveVisibleVars(), _versionCompliance, _envFlags);
valueAst.visit(visitor);
_env = visitor.getType();
// if ((i == tokens.length - 3) || (i == tokens.length - 2)) {
// // last token - check if we have to wrap
// if (!visitor.skipJSWrapping()) {
// _env = ReflectionManager.jsWrap(_env);
// }
// }
if (_env == null) {
break;
}
}
}
}
}
// compute prefix
if (_typed != null && _typed.indexOf(".") != -1) {
_prefix = _typed.substring(_typed.lastIndexOf(".") + 1);
} else {
_prefix = _typed;
}
}
private String resolvePackageOrConstructorCall(String input, boolean skipLastToken) {
String[] tokens = splitVariableValue(input, true);
String type = "";
if (tokens[0].contains("Packages")) {
if (tokens.length == 1 || (tokens.length <= 3 && tokens[1].equals("."))) {
return PACKAGE_PREFIX;
}
}
boolean isConstructorCall = false;
boolean isStaticMethod = false;
int tokenIndex = 0;
for (int i = 0; i < tokens.length; i++) {
String value = tokens[i];
tokenIndex++;
if (!value.equals(".")) {
if (i < tokens.length - 1 || !skipLastToken) {
if (value.contains("(") && value.charAt(0) == value.toUpperCase().charAt(0)) {
isConstructorCall = true;
value = value.substring(0, value.indexOf("("));
type += value;
break;
} else if (value.contains("(")){
isStaticMethod = true;
tokenIndex--;
break;
}
// } else if (!value.contains("Packages") && !value.equals(value.toLowerCase())) {
// isConstructorCall = true;
// type += value;
// break;
// }
if (!value.contains("Packages")) {
type += value;
type += ".";
}
}
}
}
if (!type.equals("")) {
if (isConstructorCall || isStaticMethod) {
if (type.endsWith(".")) {
type = type.substring(0, type.length() - 1);
}
// check if we have to resolve further method calls after constructor
int length = tokens.length;
if (skipLastToken) {
length--;
}
String currentEnvironmentObject = "$N$" + type;
for (int i = tokenIndex; i < length; i++) {
if (!tokens[i].equals(".")) {
// further tokens to parse for method calls
AstRoot valueAst = createParser().parse(tokens[i], "", 0);
TypeVisitor visitor = new TypeVisitor(currentEnvironmentObject, new HashMap<String, TMLScriptVariableDeclaration>(), _versionCompliance, _envFlags);
valueAst.visit(visitor);
currentEnvironmentObject = visitor.getType();
if (currentEnvironmentObject == null) {
break;
}
}
}
//return "$N$" + currentEnvironmentObject;
return currentEnvironmentObject;
} else {
if (type.equals(type.toLowerCase())) {
return PACKAGE_PREFIX + type.substring(0, type.length() - 1);
} else {
return CLASS_PREFIX + type.substring(0, type.length() - 1);
}
}
}
return null;
}
private static String[] splitVariableValue(String value, boolean includeDelimiter) {
List<String> varValues = new ArrayList<String>();
StringBuffer read = new StringBuffer();
int openBrakets = 0;
for (int i=0; i < value.length(); i++) {
char c = value.charAt(i);
if (c == '(') {
openBrakets++;
} else if (c == ')') {
openBrakets--;
}
if (c != '.' || openBrakets > 0) {
read.append(c);
} else if (openBrakets == 0){
varValues.add(read.toString());
read = new StringBuffer();
if (includeDelimiter) {
varValues.add(".");
}
}
}
if (!read.toString().equals("")) {
varValues.add(read.toString());
}
return varValues.toArray(new String[0]);
}
public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append("Typed: " + _typed + " - " + _prefix + " - " + _env + "\n");
Iterator<TMLScriptScope> scopes = _scopes.values().iterator();
while (scopes.hasNext()) {
TMLScriptScope scope = scopes.next();
buffer.append(scope.toString());
}
return buffer.toString();
}
private static Parser createParser() {
CompilerEnvirons env = new CompilerEnvirons();
env.setIdeMode(true);
env.setRecoverFromErrors(true);
env.setStrictMode(false);
env.setErrorReporter(new ErrorReporter() {
public void warning(String message, String sourceName, int line, String lineSource, int lineOffset) {
}
public EvaluatorException runtimeError(String message, String sourceName, int line, String lineSource, int lineOffset) {
return null;
}
public void error(String message, String sourceName, int line, String lineSource, int lineOffset) {
}
});
return new Parser(env);
}
/**
* returns a list of all visible variables depending on the code position of the given var
* @param var
* @return
*/
private Map<String,TMLScriptVariableDeclaration> retrieveVisibleVars(TMLScriptVariableDeclaration var) {
return retrieveVars(var.getScope(), var.getOffset());
}
/**
* returns a list of visible variables at the current code position
* @return
*/
private Map<String, TMLScriptVariableDeclaration> retrieveVisibleVars() {
int codeOffset = _cursorInDocument - _region.getOffset();
// determine scope of code offset
TMLScriptScope relatedScope = _scopes.get("0");
Iterator<TMLScriptScope> scopes = _scopes.values().iterator();
while (scopes.hasNext()) {
TMLScriptScope scope = scopes.next();
if (scope.getOffset() < codeOffset && (scope.getOffset() + scope.getLength()) > codeOffset) {
if (relatedScope.getPath().split("\\.").length < scope.getPath().split("\\.").length) {
relatedScope = scope;
}
}
}
return retrieveVars(relatedScope, codeOffset);
}
private Map<String,TMLScriptVariableDeclaration> retrieveVars(TMLScriptScope scope, int offset) {
Map<String, TMLScriptVariableDeclaration> visibleVars = new HashMap<String,TMLScriptVariableDeclaration>();
Iterator<TMLScriptScope> scopes = _scopes.values().iterator();
while (scopes.hasNext()) {
TMLScriptScope current = scopes.next();
if (scope.getPath().startsWith(current.getPath())) {
Iterator<TMLScriptVariableDeclaration> vars = current.getVariableDeclarations().iterator();
while (vars.hasNext()) {
TMLScriptVariableDeclaration var = vars.next();
visibleVars.put(var.getName(), var);
}
} else if (current.getOffset() < offset) {
Iterator<TMLScriptVariableDeclaration> vars = current.getVariableDeclarations().iterator();
while (vars.hasNext()) {
TMLScriptVariableDeclaration var = vars.next();
if (var.isTmlVariable()) {
visibleVars.put(var.getName(), var);
}
}
}
}
return visibleVars;
}
private static class TypeVisitor implements NodeVisitor {
private String _env = null;
private int _envFlags = ReflectionManager.ENV_DEFAULT;
private String _type = null;
private Map<String,TMLScriptVariableDeclaration> _visibleVars;
private VersionCompliance _versionCompliance;
//private boolean _skipJSWrapping = false;
// public boolean skipJSWrapping() {
// return _skipJSWrapping;
// }
public TypeVisitor(String env, Map<String,TMLScriptVariableDeclaration> visibleVars, VersionCompliance versionCompliance, int envFlags) {
_env = env;
_envFlags = envFlags;
_visibleVars = visibleVars;
_versionCompliance = versionCompliance;
}
public boolean visit(AstNode node) {
if (node instanceof FunctionCall) {
FunctionCall call = (FunctionCall) node;
String functionName = call.getTarget().toSource();
boolean arrayGet = false;
if (call.getParent() != null && call.getParent() instanceof ElementGet) {
arrayGet = true;
}
// first try to find property of environment object with given name
Iterator<TMLScriptMethod> methods = ReflectionManager.getInstance(_versionCompliance).getPublicMethods(_env, _envFlags).iterator();
while (methods.hasNext()) {
TMLScriptMethod method = methods.next();
if (method.matchName(functionName) && method.getParameters().size() == call.getArguments().size()) {
if (method instanceof TMLScriptMethodExtension1) {
// compute type of parameters
List<String> paramTypes = new ArrayList<String>();
for (AstNode paramNode : call.getArguments()) {
String currentEnvironmentObject = ReflectionManager.GLOBAL_SCOPE_CLASSNAME;
String[] values = splitVariableValue(paramNode.toSource(), false);
for (String value : values) {
AstRoot valueAst = createParser().parse(value, "", 0);
TypeVisitor visitor = new TypeVisitor(currentEnvironmentObject, _visibleVars, _versionCompliance, _envFlags);
valueAst.visit(visitor);
currentEnvironmentObject = visitor.getType();
if (currentEnvironmentObject == null) {
break;
}
}
paramTypes.add(currentEnvironmentObject);
//TypeVisitor visitor = new TypeVisitor(ReflectionManager.GLOBAL_SCOPE_CLASSNAME, _visibleVars, _versionCompliance, _envFlags);
//paramNode.visit(visitor);
//paramTypes.add(visitor.getType());
}
_type = ((TMLScriptMethodExtension1)method).getType(paramTypes);
//_skipJSWrapping = ((TMLScriptMethodExtension1)method).skipJSWrapping();
} else {
_type = method.getType();
}
// handle array get
if (_type != null && arrayGet) {
if (_type.contains("[")) {
_type = _type.substring(0, _type.indexOf("["));
}
}
return false;
}
}
} else if (node instanceof Name) {
Name name = (Name)node;
String propertyName = name.toSource();
boolean result = lookupProperty(propertyName);
if (result) {
return false;
}
} else if (node instanceof ExpressionStatement) {
ExpressionStatement statement = (ExpressionStatement)node;
if (statement.getExpression() instanceof StringLiteral) {
_type = "java.lang.String";
//_skipJSWrapping = true;
return false;
} else if (statement.getExpression() instanceof org.mozilla.javascript.ast.NumberLiteral) {
_type = "java.lang.Double";
//_skipJSWrapping = true;
return false;
} else if (statement.getExpression() instanceof ArrayLiteral) {
_type = "java.lang.Object[]";
//_skipJSWrapping = true;
return false;
} else if (statement.getExpression() instanceof ElementGet) {
String source = statement.getExpression().toSource();
source = source.substring(0, source.indexOf("["));
String propertyName = source;
boolean result = lookupProperty(propertyName);
if (result) {
if (_type != null && _type.contains("[")) {
_type = _type.substring(0, _type.indexOf("["));
}
return false;
}
}
}
return true;
}
public String getType() {
return _type;
}
private boolean lookupProperty(String propertyName) {
Iterator<TMLScriptMethod> methods = ReflectionManager.getInstance(_versionCompliance).getPublicMethods(_env, _envFlags).iterator();
while (methods.hasNext()) {
TMLScriptMethod method = methods.next();
if (method.isAccessibleAsProperty() && method.getPropertyName().equals(propertyName)) {
_type = method.getType();
return true;
}
}
Iterator<TMLScriptProperty> properties = ReflectionManager.getInstance(_versionCompliance).getPublicProperties(_env, _envFlags).iterator();
while (properties.hasNext()) {
TMLScriptProperty property = properties.next();
if (property.getName().equals(propertyName)) {
_type = property.getType();
return true;
}
}
// second try - look for declared variable
TMLScriptVariableDeclaration var = _visibleVars.get(propertyName);
if (var != null) {
_type = var.getType();
//_skipJSWrapping = true;
return true;
}
return false;
}
}
public List<TMLScriptCompletionProposal> createProposals() {
List<TMLScriptCompletionProposal> proposals = new ArrayList<TMLScriptCompletionProposal>();
if (_env != null && _env.startsWith(PACKAGE_PREFIX)) {
String packageName = _env.substring(PACKAGE_PREFIX.length());
Iterator<TMLScriptPackage> packages = ReflectionManager.getInstance(_versionCompliance).getPackages().iterator();
while (packages.hasNext()) {
TMLScriptPackage tmlScriptPackage = packages.next();
proposals.addAll(tmlScriptPackage.createProposals(packageName, _prefix, _cursorInDocument, _versionCompliance, _typed.startsWith("new")));
}
} else if (_env != null && _env.startsWith(CLASS_PREFIX)) {
String className = _env.substring(CLASS_PREFIX.length());
Iterator<TMLScriptMethod> methods = ReflectionManager.getInstance(_versionCompliance).getPublicStaticMethods(className, _envFlags).iterator();
while (methods.hasNext()) {
TMLScriptMethod method = methods.next();
proposals.addAll(method.createProposals(_prefix, _cursorInDocument));
}
Iterator<TMLScriptProperty> properties = ReflectionManager.getInstance(_versionCompliance).getPublicStaticProperties(className, _envFlags).iterator();
while (properties.hasNext()) {
TMLScriptProperty property = properties.next();
proposals.addAll(property.createProposals(_prefix, _cursorInDocument));
}
} else {
Iterator<TMLScriptMethod> methods = ReflectionManager.getInstance(_versionCompliance).getPublicMethods(_env, _envFlags).iterator();
while (methods.hasNext()) {
TMLScriptMethod method = methods.next();
proposals.addAll(method.createProposals(_prefix, _cursorInDocument));
}
Iterator<TMLScriptProperty> properties = ReflectionManager.getInstance(_versionCompliance).getPublicProperties(_env, _envFlags).iterator();
while (properties.hasNext()) {
TMLScriptProperty property = properties.next();
proposals.addAll(property.createProposals(_prefix, _cursorInDocument));
}
if (_env != null && _env.equals(ReflectionManager.GLOBAL_SCOPE_CLASSNAME)) {
Iterator<TMLScriptVariableDeclaration> variableDeclarations = retrieveVisibleVars().values().iterator();
while (variableDeclarations.hasNext()) {
TMLScriptVariableDeclaration var = variableDeclarations.next();
proposals.addAll(var.createProposals(_prefix, _cursorInDocument));
}
}
}
Collections.sort(proposals, new Comparator<TMLScriptCompletionProposal>() {
public int compare(TMLScriptCompletionProposal o1, TMLScriptCompletionProposal o2) {
if (_prefix != null && !_prefix.equals("")) {
CodeCompletionLRUManager manager = CodeCompletionLRUManager.getInstance();
if (manager.isInLRU(o1) && !manager.isInLRU(o2)) {
return -1;
} else if (!manager.isInLRU(o1) && manager.isInLRU(o2)) {
return 1;
}
}
return o1.getDisplayString().compareTo(o2.getDisplayString());
}
});
return proposals;
}
}