blob: 95451b5df0c0918730afd99474f68d3b275a21ea [file] [log] [blame]
package org.eclipse.epsilon.flexmi;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
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.emf.common.util.URI;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.ENamedElement;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceImpl;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl;
import org.eclipse.epsilon.flexmi.xml.Location;
import org.eclipse.epsilon.flexmi.xml.PseudoSAXParser;
import org.eclipse.epsilon.flexmi.xml.PseudoSAXParser.Handler;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.ProcessingInstruction;
import org.w3c.dom.Text;
public class FlexmiResource extends ResourceImpl implements Handler {
public static final String OPTION_FUZZY_CONTAINMENT_MATCHING = "fuzzyContainmentMatching";
public static final String OPTION_ORPHANS_AS_TOP_LEVEL = "orphansAsTopLevel";
public static final String OPTION_FUZZY_MATCHING_THRESHOLD = "fuzzyMatchingThreshold";
protected EObjectTraceManager eObjectTraceManager = new EObjectTraceManager();
protected List<UnresolvedReference> unresolvedReferences = new ArrayList<UnresolvedReference>();
protected Stack<Object> stack = new Stack<Object>();
protected Node currentNode = null;
protected List<String> scripts = new ArrayList<String>();
protected HashMap<String, EClass> eClassCache = new HashMap<String, EClass>();
protected HashMap<EClass, List<EClass>> allSubtypesCache = new HashMap<EClass, List<EClass>>();
protected StringSimilarityProvider stringSimilarityProvider = new CachedStringSimilarityProvider(new DefaultStringSimilarityProvider());
protected Stack<URI> parsedFragmentURIStack = new Stack<URI>();
protected Set<URI> parsedFragmentURIs = new HashSet<URI>();
protected boolean fuzzyContainmentSlotMatching = true;
protected boolean orphansAsTopLevel = true;
protected int fuzzyMatchingThreshold = 0;
public void startProcessingFragment(URI uri) {
parsedFragmentURIStack.push(uri);
parsedFragmentURIs.add(uri);
}
public void endProcessingFragment() {
parsedFragmentURIStack.pop();
}
public Set<URI> getParsedFragmentURIs() {
return parsedFragmentURIs;
}
public static void main(String[] args) throws Exception {
// Load the metamodel
ResourceSet metamodelResourceSet = new ResourceSetImpl();
metamodelResourceSet.getPackageRegistry().put(EcorePackage.eINSTANCE.getNsURI(), EcorePackage.eINSTANCE);
metamodelResourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put("*", new XMIResourceFactoryImpl());
Resource metamodelResource = metamodelResourceSet.createResource(URI.createFileURI(new File("models/messaging.ecore").getAbsolutePath()));
metamodelResource.load(null);
EPackage metamodel = (EPackage) metamodelResource.getContents().get(0);
// Load the model
ResourceSet modelResourceSet = new ResourceSetImpl();
modelResourceSet.getPackageRegistry().put(metamodel.getNsURI(), metamodel);
modelResourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put("*", new FlexmiResourceFactory());
Resource modelResource = modelResourceSet.createResource(URI.createFileURI(new File("models/messaging.flexmi").getAbsolutePath()));
modelResource.load(null);
System.out.println(modelResource.getEObject("tom"));
}
public FlexmiResource(URI uri) {
super(uri);
}
@Override
protected void doLoad(InputStream inputStream, Map<?, ?> options)
throws IOException {
try {
doLoadImpl(inputStream, options);
}
catch (IOException ioException) {
ioException.printStackTrace();
throw ioException;
}
catch (Exception ex) {
ex.printStackTrace();
throw new RuntimeException(ex);
}
}
protected void processOption(String key, String value) {
try {
if (OPTION_FUZZY_CONTAINMENT_MATCHING.equalsIgnoreCase(key)) {
fuzzyContainmentSlotMatching = Boolean.parseBoolean(value);
}
else if (OPTION_ORPHANS_AS_TOP_LEVEL.equalsIgnoreCase(key)) {
orphansAsTopLevel = Boolean.parseBoolean(value);
}
else if (OPTION_FUZZY_MATCHING_THRESHOLD.equalsIgnoreCase(key)) {
fuzzyMatchingThreshold = Integer.parseInt(value);
}
else throw new Exception("Unknown option");
}
catch (Exception ex) {
addParseWarning("Could not process option " + key + ": " + ex.getMessage());
}
}
public void doLoadImpl(InputStream inputStream, Map<?, ?> options) throws Exception {
getContents().clear();
unresolvedReferences.clear();
stack.clear();
scripts.clear();
eClassCache.clear();
allSubtypesCache.clear();
setIntrinsicIDToEObjectMap(new HashMap<String, EObject>());
if (options != null) {
for (Object key : options.keySet()) {
processOption(key + "", options.get(key) + "");
}
}
new PseudoSAXParser().parse(this, inputStream, this);
}
@Override
public void startDocument(Document document) {}
@Override
public void startElement(Element element) {
currentNode = element;
String name = element.getNodeName();
//Remove prefixes
//TODO: Add option to disable this
if (name.indexOf(":") > -1) {
name = name.substring(name.indexOf(":")+1);
}
EObject eObject = null;
EClass eClass = null;
// We're at the root or we treat orphan elements as top-level
if (stack.isEmpty() || (stack.peek() == null && orphansAsTopLevel)) {
eClass = eClassForName(name);
if (eClass != null) {
eObject = eClass.getEPackage().getEFactoryInstance().create(eClass);
getContents().add(eObject);
setAttributes(eObject, element);
}
else {
addParseWarning("Could not map element " + name + " to an EObject");
}
stack.push(eObject);
}
else {
Object peek = stack.peek();
// We find an orphan element but don't treat it as top-level
if (peek == null) {
stack.push(null);
addParseWarning("Could not map element " + name + " to an EObject");
return;
}
// The parent is an already-established containment slot
else if (peek instanceof EReferenceSlot) {
EReferenceSlot containmentSlot = (EReferenceSlot) peek;
eClass = (EClass) eNamedElementForName(name, getAllSubtypes(containmentSlot.getEReference().getEReferenceType()));
if (eClass != null) {
eObject = eClass.getEPackage().getEFactoryInstance().create(eClass);
containmentSlot.newValue(eObject);
stack.push(eObject);
setAttributes(eObject, element);
}
else {
stack.push(null);
addParseWarning("Could not map element " + name + " to an EObject");
}
}
// The parent is an EObject
else if (peek instanceof EObject) {
EObject parent = (EObject) peek;
if (element.getAttributes().getLength() == 0 && element.getChildNodes().getLength() == 1 && element.getFirstChild() instanceof Text) {
EAttribute eAttribute = (EAttribute) eNamedElementForName(name, parent.eClass().getEAllAttributes());
if (eAttribute != null) {
setEAttributeValue(parent, eAttribute, name, element.getTextContent().trim());
eObjectTraceManager.trace(parent, getCurrentURI(), getLineNumber(element));
stack.push(null);
return;
}
}
EReference containment = null;
// No attributes -> Check whether there is a containment reference with that name
if (element.getAttributes().getLength() == 0) {
if (fuzzyContainmentSlotMatching) {
containment = (EReference) eNamedElementForName(name, parent.eClass().getEAllContainments());
}
else {
containment = (EReference) eNamedElementForName(name, parent.eClass().getEAllContainments(), false);
}
if (containment != null) {
EReferenceSlot containmentSlot = new EReferenceSlot(containment, parent);
eObjectTraceManager.trace(parent, getCurrentURI(), getLineNumber(element));
stack.push(containmentSlot);
return;
}
}
// No containment references found
// Find potential types for the element
Set<EClass> candidates = new HashSet<EClass>();
for (EReference eReference : parent.eClass().getEAllContainments()) {
candidates.addAll(getAllSubtypes(eReference.getEReferenceType()));
}
// Get the best match and an appropriate containment reference
eClass = (EClass) eNamedElementForName(name, candidates);
if (eClass != null) {
for (EReference eReference : parent.eClass().getEAllContainments()) {
if (getAllSubtypes(eReference.getEReferenceType()).contains(eClass)) {
containment = eReference;
break;
}
}
}
// Found an appropriate containment reference
if (containment != null) {
eObject = eClass.getEPackage().getEFactoryInstance().create(eClass);
if (containment.isMany()) {
((List<EObject>) parent.eGet(containment)).add(eObject);
}
else {
parent.eSet(containment, eObject);
}
setAttributes(eObject, element);
stack.push(eObject);
}
// No luck - add warning
else {
stack.push(null);
addParseWarning("Could not map element " + name + " to an EObject");
}
}
}
}
@Override
public void endElement(Element element) {
Object object = stack.pop();
if (object != null && object instanceof EObject) {
EObject eObject = (EObject) object;
eObjectTraceManager.trace(eObject, getCurrentURI(), getLineNumber(element));
}
}
@Override
public void processingInstruction(ProcessingInstruction processingInstruction) {
currentNode = processingInstruction;
String key = processingInstruction.getTarget();
String value = processingInstruction.getData();
if ("nsuri".equalsIgnoreCase(key)) {
EPackage ePackage = EPackage.Registry.INSTANCE.getEPackage(value);
if (ePackage != null) getResourceSet().getPackageRegistry().put(ePackage.getNsURI(), ePackage);
else addParseWarning("Failed to locate EPackage for nsURI " + value + " ");
}
else if ("eol".equalsIgnoreCase(key)) {
scripts.add(value);
}
else processOption(key, value);
}
@Override
public void endDocument(Document document) {
resolveReferences();
}
public List<UnresolvedReference> getUnresolvedReferences() {
return unresolvedReferences;
}
protected void addParseWarning(String message) {
addParseWarning(message, getLineNumber(currentNode));
}
protected void addParseWarning(String message, int line) {
addParseWarning(message, getCurrentURI(), line);
}
protected void addParseWarning(String message, URI uri, int line) {
getWarnings().add(new FlexmiDiagnostic(message, uri, line));
}
@SuppressWarnings("unchecked")
protected void resolveReferences() {
List<UnresolvedReference> unresolvableReferences = new ArrayList<UnresolvedReference>();
for (UnresolvedReference unresolvedReference : unresolvedReferences) {
EReference eReference = unresolvedReference.getEReference();
if (eReference.isMany()) {
if ("*".equals(unresolvedReference.getValue())) {
Iterator<EObject> it = this.getAllContents();
while (it.hasNext()) {
EObject candidate = it.next();
if (eReference.getEReferenceType().isInstance(candidate)) {
new EReferenceSlot(eReference, unresolvedReference.getEObject()).newValue(candidate);
}
}
}
else {
if (!resolveReference(unresolvedReference)) unresolvableReferences.add(unresolvedReference);
}
}
else {
if (!resolveReference(unresolvedReference)) unresolvableReferences.add(unresolvedReference);
}
}
for (UnresolvedReference reference : unresolvableReferences) {
addParseWarning("Could not resolve target " + reference.getValue() + " for reference " + reference.getAttributeName() + " (" + reference.getEReference().getName() + ")", reference.getUri(), reference.getLine());
}
}
protected boolean resolveReference(UnresolvedReference unresolvedReference) {
List<EObject> candidates = Arrays.asList(getIntrinsicIDToEObjectMap().get(unresolvedReference.getValue()));
if (!unresolvedReference.resolve(candidates)) {
System.out.println("Failed");
for (Resource resource : getResourceSet().getResources()) {
if (resource != this) {
candidates = Arrays.asList(resource.getEObject(unresolvedReference.getValue()));
if (unresolvedReference.resolve(candidates)) return true;
}
}
return false;
}
return true;
}
public int getLineNumber(Node node) {
Location location = (Location) node.getUserData(Location.ID);
if (location != null) {
return location.getStartLine();
}
return 0;
}
protected void setAttributes(EObject eObject, Element element) {
NamedNodeMap attributes = element.getAttributes();
List<EStructuralFeature> eStructuralFeatures = getCandidateStructuralFeaturesForAttribute(eObject.eClass());
if (attributes.getLength() == 0 || eStructuralFeatures.size() == 0) return;
if (!(eObject.eClass().getEStructuralFeature("id") instanceof EAttribute)) {
if (attributes.getNamedItem("id") != null) {
String value = attributes.getNamedItem("id").getNodeValue();
attributes.removeNamedItem("id");
getIntrinsicIDToEObjectMap().put(value, eObject);
}
}
if (attributes.getLength() == 0) return;
double[][] inverseSimilarities = new double[attributes.getLength()][eStructuralFeatures.size()];
for (int i=0;i<attributes.getLength();i++) {
int j=0;
String attributeName = attributes.item(i).getNodeName();
for (EStructuralFeature sf : eStructuralFeatures) {
int similarity = stringSimilarityProvider.getSimilarity(attributeName, sf.getName());
double inverseSimilarity = 2;
if (similarity != 0) inverseSimilarity = 1/(double)similarity;
inverseSimilarities[i][j] = inverseSimilarity;
j++;
}
}
int[] assignment = new HungarianAlgorithm(inverseSimilarities).execute();
for (int i=0;i<assignment.length;i++) {
String name = attributes.item(i).getNodeName();
String value = attributes.item(i).getNodeValue();
if (assignment[i]>=eStructuralFeatures.size() || assignment[i]<0) continue;
EStructuralFeature sf = eStructuralFeatures.get(assignment[i]);
if (sf instanceof EAttribute) {
setEAttributeValue(eObject, (EAttribute) sf, name, value);
}
else if (sf instanceof EReference) {
EReference eReference = (EReference) sf;
if (eReference.isMany()) {
for (String valuePart : value.split(",")) {
unresolvedReferences.add(new UnresolvedReference(eObject, getCurrentURI(), eReference, name, valuePart.trim(), getLineNumber(element)));
}
}
else {
unresolvedReferences.add(new UnresolvedReference(eObject, getCurrentURI(), eReference, name, value, getLineNumber(element)));
}
}
}
}
public URI getCurrentURI() {
return parsedFragmentURIStack.isEmpty() ? this.getURI() : parsedFragmentURIStack.peek();
}
@SuppressWarnings("unchecked")
protected void setEAttributeValue(EObject eObject, EAttribute eAttribute, String attributeName, String value) {
if (eAttribute.isMany()) {
for (String valuePart : value.split(",")) {
Object eValue = getEValue(eAttribute, attributeName, valuePart.trim());
if (eValue == null) continue;
((List<Object>) eObject.eGet(eAttribute)).add(eValue);
}
}
else {
Object eValue = getEValue(eAttribute, attributeName, value);
if (eValue == null) return;
eObject.eSet(eAttribute, eValue);
if (eAttribute.isID() || "name".equalsIgnoreCase(eAttribute.getName())) {
getIntrinsicIDToEObjectMap().put(value, eObject);
}
}
}
protected Object getEValue(EAttribute eAttribute, String attributeName, String value) {
try {
return eAttribute.getEAttributeType().getEPackage().getEFactoryInstance().createFromString(eAttribute.getEAttributeType(), value);
}
catch (Exception ex) {
ex.printStackTrace();
addParseWarning(ex.getMessage() + " in the value of " + attributeName);
return null;
}
}
protected List<EStructuralFeature> getCandidateStructuralFeaturesForAttribute(EClass eClass) {
List<EStructuralFeature> eStructuralFeatures = new ArrayList<EStructuralFeature>();
for (EStructuralFeature sf : eClass.getEAllStructuralFeatures()) {
if (sf.isChangeable() && (sf instanceof EAttribute || ((sf instanceof EReference) && !((EReference) sf).isContainment()))) {
eStructuralFeatures.add(sf);
}
}
return eStructuralFeatures;
}
protected List<EClass> getAllConcreteEClasses() {
List<EClass> eClasses = new ArrayList<EClass>();
Iterator<Object> it = getResourceSet().getPackageRegistry().values().iterator();
while (it.hasNext()) {
EPackage ePackage = (EPackage) it.next();
for (EClassifier eClassifier : ePackage.getEClassifiers()) {
if (eClassifier instanceof EClass && !((EClass) eClassifier).isAbstract()) {
eClasses.add((EClass) eClassifier);
}
}
}
return eClasses;
}
protected List<EClass> getAllSubtypes(EClass eClass) {
List<EClass> allSubtypes = allSubtypesCache.get(eClass);
if (allSubtypes == null) {
allSubtypes = new ArrayList<EClass>();
for (EClass candidate : getAllConcreteEClasses()) {
if (candidate.getEAllSuperTypes().contains(eClass)) {
allSubtypes.add(candidate);
}
}
if (!eClass.isAbstract()) allSubtypes.add(eClass);
allSubtypesCache.put(eClass, allSubtypes);
}
return allSubtypes;
}
protected EClass eClassForName(String name) {
EClass eClass = eClassCache.get(name);
if (eClass == null) {
eClass = (EClass) eNamedElementForName(name, getAllConcreteEClasses());
eClassCache.put(name, eClass);
}
return eClass;
}
protected ENamedElement eNamedElementForName(String name, Collection<? extends ENamedElement> candidates) {
ENamedElement eNamedElement = eNamedElementForName(name, candidates, false);
if (eNamedElement == null) eNamedElement = eNamedElementForName(name, candidates, true);
return eNamedElement;
}
public EObjectTraceManager getEObjectTraceManager() {
return eObjectTraceManager;
}
protected ENamedElement eNamedElementForName(String name, Collection<? extends ENamedElement> candidates, boolean fuzzy) {
if (fuzzy) {
int maxSimilarity = 0;
ENamedElement bestMatch = null;
for (ENamedElement candidate : candidates) {
int similarity = stringSimilarityProvider.getSimilarity(candidate.getName().toLowerCase(), name.toLowerCase());
if (similarity > maxSimilarity) {
maxSimilarity = similarity;
bestMatch = candidate;
}
}
if (maxSimilarity == 0 && candidates.size() == 1) {
return candidates.iterator().next();
}
return bestMatch;
}
else {
for (ENamedElement candidate : candidates) {
if (candidate.getName().equalsIgnoreCase(name)) return candidate;
}
}
return null;
}
}