blob: 68e66a2ca09a43c75cc94e3b2e97e650725a2481 [file] [log] [blame]
/**
* Copyright (c) 2008 INRIA.
* 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:
* INRIA - initial API and implementation
*
* $Id: TCSRuntime.java,v 1.7 2008/06/25 12:27:04 fjouault Exp $
*/
package org.eclipse.gmt.tcs.injector;
import java.text.CharacterIterator;
import java.text.StringCharacterIterator;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import org.eclipse.gmt.tcs.injector.wrappers.ParserWrapper;
/**
* @author Frédéric Jouault
* @author Mikaël Barbero
*/
public class TCSRuntime {
// begin arguments
private ModelAdapter targetModelAdapter;
private ModelAdapter problemsModelAdapter;
private boolean keepLocation;
private boolean keepNL;
private boolean keepComments;
private Map locationByElement;
private Map hyperlinks;
private Map trace;
// end arguments
private ParserWrapper parserWrapper;
private Context currentContext;
private Map contextByElement;
private Stack previousElement;
private boolean lastWasCreation;
private List refSettings;
private Object lastToken;
private CompletionInformation completionInformation = null;
private int nbErrors = 0;
public TCSRuntime(ModelAdapter targetModelAdapter, ParserWrapper parserWrapper, List refSettings, Map arguments) {
this.targetModelAdapter = targetModelAdapter;
this.problemsModelAdapter = (ModelAdapter)arguments.get("problems");
this.parserWrapper = parserWrapper;
this.refSettings = refSettings;
this.keepNL = ("true".equals(arguments.get("keepNL"))) ? true : false;
this.keepLocation = ("false".equals(arguments.get("keepLocation"))) ? false : true;
this.keepComments = ("false".equals(arguments.get("keepComments"))) ? false : true;
this.completionInformation = (CompletionInformation)arguments.get("completionInformation");
// hyperlinks can be null
this.hyperlinks = (Map)arguments.get("hyperlinks");
// trace can be null
this.trace = (Map)arguments.get("trace");
this.locationByElement = arguments.get("locationByElement") == null ? new HashMap() : (Map)arguments.get("locationByElement");
previousElement = new Stack();
currentContext = new Context();
contextByElement = new HashMap();
lastWasCreation = true;
}
public int getNbErrors() {
return nbErrors;
}
/**
* @return the targetModelAdapter
*/
public ModelAdapter getTargetModelAdapter() {
return targetModelAdapter;
}
public boolean isKeepNL() {
return keepNL;
}
public Object createEnumLiteral(String name) {
return getTargetModelAdapter().createEnumLiteral(name);
}
public Object create(String name, boolean context, boolean addToContext) {
Object ret = null;
if(debug) debug("creating " + name);
ret = getTargetModelAdapter().createElement(name);
if(addToContext) currentContext.add(ret);
if(context) currentContext = currentContext.enterContext(ret);
if(lastWasCreation) {
previousElement.push(null);
// prevType.push(name);
}/* else {
prevType.pop();
prevType.push(name);
}*/
lastWasCreation = true;
return ret;
}
public void leaveContext(boolean leave) {
if(leave) currentContext = currentContext.parent();
}
// Usefull? at least, it is called by grammars
public void addToContext(Object ame, boolean addToContext) {
if(addToContext) currentContext.add(ame);
}
public void setLocation(Object ame, String location) {
if(location.matches("^0:0-.+")) {
location = location.split("-")[1];
location = location + "-" + location;
}
locationByElement.put(ame, location);
if(keepLocation) {
try {
getTargetModelAdapter().set(ame, "location", location);
} catch(Exception e) {
reportProblem("Warning", "could not set location of " + ame + ", disabling further location settings", location);
keepLocation = false;
}
}
}
public void setCommentsBefore(Object ame, Object token) {
parserWrapper.setCommentsBefore(ame, token);
}
public void setCommentsAfter(Object ame, Object token) {
parserWrapper.setCommentsAfter(ame, token);
}
public void set(Object ame, String prop, Object value) {
getTargetModelAdapter().set(ame, prop, value);
if(trace != null) {
ElementTrace et = (ElementTrace)trace.get(ame);
if(et == null) {
et = new ElementTrace(ame);
trace.put(ame, et);
}
Object lastToken = parserWrapper.getLastToken();
et.addPropertyLocation(prop, parserWrapper.getLocation(lastToken));
}
// for external resolver
if(completionInformation != null) {
int offset = completionInformation.getOffset();
if(
(offset >= parserWrapper.getStartOffset(lastToken) - 1) &&
(offset <= parserWrapper.getEndOffset(lastToken))) {
if(completionInformation.getProposals().isEmpty()) { // only add the first (and therefore inner-most)
completionInformation.getProposals().add(new Object[] {ame, prop,
((String)value).substring(0, offset - parserWrapper.getStartOffset(lastToken) + 1)
});
}
}
}
}
public void setRef(Object object,
String propertyName,
String valueTypeName,
String keyName,
Object keyValue,
String lookIn,
String autoCreate,
String createAs,
boolean importContext,
String createIn) {
if(keyValue == null) return;
new RefSetting(currentContext,
object,
propertyName,
valueTypeName,
keyName,
keyValue,
lookIn,
autoCreate,
createAs,
importContext,
createIn, lastToken);
}
public void reportProblem(String severity, String msg, Object ame) {
String location = "<unknown location>";
// if(keepLocation) {
// try {
// location = targetModelAdapter.getString(ame, "location");
// } catch(Exception e) {
// e.printStackTrace();
// }
// }
if(locationByElement.containsKey(ame))
location = (String)locationByElement.get(ame);
reportProblem(severity, msg, location);
}
public void reportProblem(String severity, String msg, String location) {
if("error".equals(severity.toLowerCase())) {
nbErrors++;
}
if(problemsModelAdapter == null) {
System.err.println(severity + ": " + location + ": " + msg + ".");
} else {
Object ame = problemsModelAdapter.createElement("Problem");
problemsModelAdapter.set(ame, "severity", problemsModelAdapter.createEnumLiteral(severity.toLowerCase()));
problemsModelAdapter.set(ame, "location", location);
problemsModelAdapter.set(ame, "description", msg);
}
}
public void reportError(Exception re) {
parserWrapper.reportError(re);
}
public void reportError(String msg) {
reportProblem("Error", msg, "?");
}
public void reportWarning(String msg) {
reportProblem("Warning", msg, "?");
}
protected class Context {
public Context enterContext(Object element) {
Context ret = new Context(element, this);
contextByElement.put(element, ret);
return ret;
}
// Only for root Context
public Context() {
this.parent = null;
}
private Context(Object element, Context parent) {
this.parent = parent;
this.element = element;
}
// cannot be called once RefSettings have started populating mapByTypeAndKey,
// add(Object, String, Object) must be called instead
public void add(Object e) {
if(debug) debug("adding " + e + "to context");
contents.add(e);
}
// to add an element after population of mapByTypeAndKey has started,
// Note that the element does not get added in imported contexts
public void add(Object e, String valueTypeName, String keyName, Object keyVal) {
add(e); // might be necessary
Map elementByVal = getElementByVal(valueTypeName, keyName);
addElementByVal(elementByVal, e, keyVal);
}
private void addElementByVal(Map elementByVal, Object ame, Object val) {
Object element = elementByVal.get(val);
if(element == null) {
elementByVal.put(val, ame);
} else if(element instanceof Collection){
((Collection)element).add(ame);
} else {
Collection c = new ArrayList();
c.add(element);
c.add(ame);
elementByVal.put(val, c);
}
}
// public ContextIterator iterator() {
// return new ContextIterator(this, contents.iterator(), importedContexts);
// }
// public ContextIterator iterator(Set traversed) {
// return new ContextIterator(traversed, this, contents.iterator(), importedContexts);
// }
public Context parent() {
return parent;
}
public void importContext(Context c) {
importedContexts.add(c);
}
public Object getElement() {
return element;
}
private Object element;
private Context parent;
private Set contents = new HashSet();
private Set importedContexts = new HashSet();
private Map mapByTypeAndKey = new HashMap();
/**
* @param valueTypeName
* @param keyName
* @param keyVal
*/
public Map getElementByVal(String valueTypeName, String keyName) {
String key = "__" + valueTypeName + "_" + keyName;
Map elementByVal = (Map)mapByTypeAndKey.get(key);
if(elementByVal == null) {
elementByVal = new HashMap();
for(Iterator i = contents.iterator() ; i.hasNext() ; ) {
Object ame = i.next();
// if(debug) debug("\t" + ame + " : " + getTargetModelAdapter().get(getTargetModelAdapter().getType(ame), "name"));
if(isCandidate(ame, valueTypeName)) { // check type
Object val = getTargetModelAdapter().get(ame, keyName);
addElementByVal(elementByVal, ame, val);
}
}
mapByTypeAndKey.put(key, elementByVal);
for(Iterator i = importedContexts.iterator() ; i.hasNext() ; ) {
Context importedContext = (Context)i.next();
Map map = importedContext.getElementByVal(valueTypeName, keyName);
for(Iterator j = map.entrySet().iterator() ; j.hasNext() ; ) {
Map.Entry entry = (Map.Entry)j.next();
if(!elementByVal.containsKey(entry.getKey())) {
elementByVal.put(entry.getKey(), entry.getValue());
}
}
}
}
return elementByVal;
}
private boolean isCandidate(Object ame, String valueTypeName) {
return getTargetModelAdapter().isCandidate(ame, valueTypeName);
}
}
// protected class ContextIterator implements Iterator {
//
// private Iterator i;
// private ContextIterator currentIterator = null;
// private Iterator importedContexts;
// private Set traversed;
// private boolean finishCurrent = false;
//
// public void finishCurrent() {
// finishCurrent = true;
// if(currentIterator != null)
// currentIterator.finishCurrent();
// }
//
// public ContextIterator(Context c, Iterator i, Set imported) {
// this(new HashSet(), c, i, imported);
// }
//
// public ContextIterator(Set traversed, Context c, Iterator i, Set imported) {
// this.traversed = traversed;
// traversed.add(c);
// this.i = i;
// this.importedContexts = imported.iterator();
// }
//
// public boolean hasNext() {
// while(importedContexts.hasNext() && (!i.hasNext()) && !finishCurrent){
// Context c = (Context)importedContexts.next();
// if(!traversed.contains(c)) {
// traversed.add(c);
// currentIterator = c.iterator(traversed);
// i = currentIterator;
// } else {
// System.out.println("ERROR: recursive contexts");
// }
// }
//
// return i.hasNext();
// }
//
// public Object next() {
// return i.next();
// }
//
// public void remove() {
// throw new UnsupportedOperationException();
// }
// }
protected class RefSetting {
public RefSetting(
Context currentContext,
Object object,
String propertyName,
String valueTypeName,
String keyName,
Object keyValue,
String lookIn,
String autoCreate,
String createAs,
boolean importContext,
String createIn,
Object token) {
this.currentContext = currentContext;
this.object = object;
this.propertyName = propertyName;
this.valueTypeName = valueTypeName;
this.keyName = keyName;
this.keyValue = keyValue;
this.lookIn = lookIn;
this.autoCreate = autoCreate;
this.createAs = createAs;
this.importContext = importContext;
this.createIn = createIn;
this.token = token;
refSettings.add(this);
}
private boolean doItForContext(Context context) {
boolean done = false;
Object keyVal = keyValue;
Map elementByVal = context.getElementByVal(valueTypeName, keyName);
int offset = -1;
Collection proposals = null;
if(completionInformation != null) {
offset = completionInformation.getOffset();
if(
(offset >= parserWrapper.getStartOffset(token) - 1) &&
(offset <= parserWrapper.getEndOffset(token))) {
proposals = completionInformation.getProposals();
proposals.addAll(elementByVal.keySet());
if(completionInformation.getPrefix() == null) {
if(keyVal instanceof String) {
completionInformation.setPrefix(
((String)keyVal).substring(0, offset - parserWrapper.getStartOffset(token) + 1)
);
}
}
}
}
Object ame = elementByVal.get(keyVal);
if(ame instanceof Collection) {
for(Iterator i = ((Collection)ame).iterator() ; i.hasNext() ; ) {
reportProblem("Error", "found several " + valueTypeName + " with the same " + keyName + " equals to " + keyVal, i.next());
}
done = true;
} else if(ame != null) {
realValue = ame;
getTargetModelAdapter().set(object, propertyName, ame);
// if(keepLocation && (hyperlinks != null)) {
// try {
// String location = parserWrapper.getLocation(token);
// hyperlinks.put(location, targetModelAdapter.getString(ame, "location"));
// } catch(Exception e) {}
// }
if(hyperlinks != null) {
String location = parserWrapper.getLocation(token);
hyperlinks.put(location, locationByElement.get(ame));
}
if(trace != null) {
ElementTrace et = (ElementTrace)trace.get(object);
if(et == null) {
et = new ElementTrace(object);
trace.put(object, et);
}
ReferenceLocation location = new ReferenceLocation(parserWrapper.getLocation(token), ame);
et.addPropertyLocation(propertyName, location);
}
done = true;
}
if(!done) {
context = context.parent();
if(context != null)
done = doItForContext(context);
}
return done;
}
private void create() {
Object ro = null;
if(createAs != null) {
ro = getTargetModelAdapter().createElement(createAs);
} else {
ro = getTargetModelAdapter().createElement(valueTypeName);
}
realValue = ro;
// do not need this with the model handler
// ASMOclAny realKeyValue = null;
// if(keyValue instanceof String) {
// realKeyValue = new ASMString((String)keyValue);
// } else {
// realKeyValue = (ASMOclAny)keyValue;
// }
getTargetModelAdapter().set(ro, keyName, keyValue);
getTargetModelAdapter().set(object, propertyName, ro);
try {
String location = parserWrapper.getLocation(token);
setLocation(ro, location);
} catch(Exception e) {}
if(createIn != null) {
String path[] = createIn.split("\\.");
Object e = navigateLookIn(path, false);
if(e != null) {
getTargetModelAdapter().set(e, path[path.length - 1], ro);
currentContext.add(ro, valueTypeName, keyName, keyValue);
}
} else if((lookIn != null) && !lookIn.equals("#all")) {
String path[] = lookIn.split("\\.");
Object e = navigateLookIn(path, false);
if(e != null) {
getTargetModelAdapter().set(e, path[path.length - 1], ro);
currentContext.add(ro, valueTypeName, keyName, keyValue);
}
}
}
private Object navigateLookIn(String path[], boolean navigateLast) {
Object ret = object;
for(int i = 0 ; (i < path.length - (navigateLast ? 0 : 1)) && (ret != null) ; i++) {
if(path[i].equals("#context")) {
ret = currentContext.getElement();
} else {
Object v = getTargetModelAdapter().get(ret, path[i]);
if(getTargetModelAdapter().isAModelElement(v)) {
ret = v;
} else {
ret = null;
}
}
}
return ret;
}
public void doIt() {
boolean done = false;
if(debug) debug("setRef(" +
currentContext + ", " +
object + ", " +
propertyName + ", " +
valueTypeName + ", " +
keyName + ", " +
keyValue + ", " +
lookIn + ", " +
autoCreate + ") : "
);
if(autoCreate.equals("always")) {
create();
done = true;
} else {
Context context = currentContext;
if("#all".equals(lookIn)) {
Object val = null;
for(Iterator i = getTargetModelAdapter().getElementsByType(valueTypeName).iterator() ; i.hasNext() && (val == null) ; ) {
Object ame = i.next();
if(debug) debug("\t" + ame + " : " + getTargetModelAdapter().get(getTargetModelAdapter().getType(ame), "name"));
//if(targetModelAdapter.get(ame, keyName).equals(new ASMString((String)keyValue))) {
// this may not work (we should have to override equals of ASMString to compare it to a String)
if(getTargetModelAdapter().get(ame, keyName).equals(keyValue)) {
val = ame;
}
}
if(val != null) {
realValue = val;
getTargetModelAdapter().set(object, propertyName, val);
done = true;
}
} else if((lookIn != null) && !lookIn.equals("#all")) {
String path[] = lookIn.split("\\.");
Object e = navigateLookIn(path, true);
if(e != null) {
context = (Context)contextByElement.get(e);
done = doItForContext(context);
}
} else {
done = doItForContext(context);
}
}
if(!done) {
if(debug) debug("not found");
if(!autoCreate.equals("never")) {
create();
} else {
reportProblem("Error", valueTypeName + " with " + keyName + " = " + keyValue + " was not found for " + propertyName + " of " + getTargetModelAdapter().getType(object), object);
}
}
if(realValue != null) {
Context context = (Context)contextByElement.get(realValue);
if(importContext && (context != null)) {
currentContext.importContext(context);
}
}
}
private Context currentContext;
private Object object;
private String propertyName;
private String valueTypeName;
private String keyName;
private Object keyValue;
private String lookIn;
private String autoCreate;
private String createAs;
protected boolean importContext;
private String createIn;
private Object token;
private Object realValue = null;
}
public void setToken(Object token) {
lastToken = token;
}
// TODO: remove once TCS-importer.jar is bootstrapped
// public void setToken(Token token) {
// lastToken = token;
// }
public String unescapeString(String s, int delimLength) {
StringBuffer ret = new StringBuffer();
// get rid of the starting and ending delimiters (e.g., '\'', '"')
s = s.substring(delimLength, s.length() - delimLength);
CharacterIterator ci = new StringCharacterIterator(s);
char c = ci.first();
while(c != CharacterIterator.DONE) {
char tc = 0;
switch(c) {
case '\\':
c = ci.next();
switch(c) {
case 'n':
tc = '\n';
break;
case 'r':
tc = '\r';
break;
case 't':
tc = '\t';
break;
case 'b':
tc = '\b';
break;
case 'f':
tc = '\f';
break;
case '"':
tc = '"';
break;
case '\'':
tc = '\'';
break;
case '\\':
tc = '\\';
break;
case '0':
case '1':
case '2':
case '3':
throw new RuntimeException("octal escape sequences not supported yet");
default:
throw new RuntimeException("unknown escape sequence: '\\" + c + "'");
}
break;
default:
tc = c;
break;
}
ret.append(tc);
c = ci.next();
}
return ret.toString();
}
private final static boolean debug = false;
private void debug(String msg) {
System.out.println(msg);
}
public void setKeepComments(boolean keepComments) {
this.keepComments = keepComments;
}
public boolean isKeepComments() {
return keepComments;
}
public void setLastWasCreation(boolean lastWasCreation) {
this.lastWasCreation = lastWasCreation;
}
}