/*
* grEMF
*
* Copyright (C) 2006-2012 Institute for Software Technology
* University of Koblenz-Landau, Germany
* ist@uni-koblenz.de
*
* For bug reports, documentation and further information, visit
*
* https://github.com/jgralab/gremf
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, see <http://www.gnu.org/licenses>.
*
* Additional permission under GNU GPL version 3 section 7
*
* If you modify this Program, or any covered work, by linking or combining
* it with Eclipse (or a modified version of that program or an Eclipse
* plugin), containing parts covered by the terms of the Eclipse Public
* License (EPL), the licensors of this Program grant you additional
* permission to convey the resulting work. Corresponding Source for a
* non-source form of such a combination shall include the source code for
* the parts of JGraLab used as well as that of the covered work.
*/
package de.uni_koblenz.gremf.resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;
import java.util.StringTokenizer;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EFactory;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.InternalEObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.xmi.XMLHelper;
import org.eclipse.emf.ecore.xmi.XMLResource;
import org.eclipse.emf.ecore.xmi.impl.SAXXMIHandler;
import de.uni_koblenz.gremf.GrEMFInstanceType;
import de.uni_koblenz.gremf.GrEMFType;
import de.uni_koblenz.gremf.impl.GrEMFEdgeImpl;
import de.uni_koblenz.gremf.impl.GrEMFVertexImpl;
import de.uni_koblenz.gremf.schema.impl.GrEMFAttributeImpl;
import de.uni_koblenz.gremf.schema.impl.GrEMFEdgeClassImpl;
import de.uni_koblenz.gremf.schema.impl.GrEMFIncidenceClassWithRefsImpl;
import de.uni_koblenz.gremf.schema.impl.GrEMFVertexClassImpl;
import de.uni_koblenz.jgralab.Edge;
/**
* Extended SAX handler for grEMF: <br>
* The EMF mechanism is extended to support and handle all grEMF
* characteristics, especially edge objects. The creation of these objects is
* delayed until the very end.
*
* @see {@link de.uni_koblenz.gremf.resource.GrEMFSchemaResourceImpl
* GrEMFResourceImpl} (grEMF resource implementation)
* @see {@link de.uni_koblenz.gremf.resource.GrEMFInstanceLoadImpl
* GrEMFLoadImpl} (load support for grEMF resources)
*
*/
public class GrEMFInstanceSAXHandler extends SAXXMIHandler {
/**
* Mapping of all occurring objects to an identifier which is only used for
* this creation process
*/
private Map<String, EObject> objs;
/**
* Mapping of all objects with delayed edge object features to the
* identifier of these features.
*/
private Map<String, EObject> gaps;
/**
* Mapping of all occurring edge objects to their identifier
*/
private Map<String, GrEMFEdgeClassImpl> edges;
/**
* Mapping of all edge XML-attributes to the edge's identifier. The
* XML-attributes are stored as name:value pairs (list of a 2-element list)
*/
private Map<String, ArrayList<ArrayList<String>>> edgeAttrs;
/**
* Stack of created identifiers which are removed during the object's
* creation
*/
private Stack<String> objIds;
/**
* Stack of the parent elements' identifiers
*/
private Stack<String> parentIds;
/**
* Identifier of the current top level element
*/
private String topId;
/**
* Index of the current top level element
*/
private int topIndex;
/**
* Indices of all features <i>(note: all XML elements which are not at top
* level are structural features)</i>
*/
private Map<String, Integer> featureIndices;
/**
* level of parent element's XML-entry
*/
private int parentLevel;
public GrEMFInstanceSAXHandler(XMLResource xmiResource, XMLHelper helper,
Map<?, ?> options) {
super(xmiResource, helper, options);
this.objs = new HashMap<String, EObject>();
this.gaps = new HashMap<String, EObject>();
this.edges = new HashMap<String, GrEMFEdgeClassImpl>();
this.edgeAttrs = new HashMap<String, ArrayList<ArrayList<String>>>();
this.topIndex = 0;
this.featureIndices = new HashMap<String, Integer>();
this.objIds = new Stack<String>();
this.parentIds = new Stack<String>();
}
@Override
protected void createTopObject(String prefix, String name) {
this.parentIds.clear();
this.objIds.clear();
// create id and push it to the stacks
String id = createIdForTopElement(this.topIndex++);
this.objIds.push(id);
this.parentIds.push(id);
// set values
this.topId = id;
this.parentLevel = this.elements.size();
super.createTopObject(prefix, name);
}
@Override
protected void handleFeature(String prefix, String name) {
// pop all old elements (wrong level)
for (int i = this.parentLevel - this.elements.size(); i >= 0; i--) {
this.parentIds.pop();
}
// create unique feature name
String featureName = createUniqueNameForFeature(this.parentIds.peek(),
name);
// update counter
if (this.featureIndices.containsKey(featureName)) {
this.featureIndices.put(featureName,
this.featureIndices.get(featureName) + 1);
} else {
this.featureIndices.put(featureName, 0);
}
// set value
this.parentLevel = this.elements.size();
// look up feature
EStructuralFeature feature = null;
if (this.objects.peek() != null) {
feature = this.objects.peek().eClass().getEStructuralFeature(name);
}
if ((feature != null) && !(feature instanceof GrEMFType)
&& (feature instanceof EReference)) {
this.handleToEdgeClassReference(feature, featureName);
} else if ((feature == null)
&& this.edges.containsKey(this.parentIds.peek())) {
this.handleFromEdgeClassReference(name);
} else {
// create id
String id = createIdForFeature(featureName,
this.featureIndices.get(featureName),
this.getFeature(this.objects.peek(), prefix, name, true)
.getUpperBound() != 1);
// push id
this.objIds.push(id);
this.parentIds.push(id);
super.handleFeature(prefix, name);
}
}
/**
* @param feature
*/
private void handleToEdgeClassReference(EStructuralFeature feature,
String uniqueFeatureName) {
// reference to an edge class
GrEMFEdgeClassImpl edgeCls = (GrEMFEdgeClassImpl) feature.getEType();
// create edge id
String edgeId = createIdForFeature(this.parentIds.peek(),
edgeCls.getName(), this.featureIndices.get(uniqueFeatureName),
feature.getUpperBound() != 1);
// get vertex id
String vertexId = this.parentIds.peek();
// handle reference of the parent object
this.setValueFromId(this.objects.peek(), (EReference) edgeCls.getTo(),
edgeId);
// "create" edge
this.objIds.push(edgeId);
this.parentIds.push(edgeId);
this.createGrEMFObject(edgeCls.getEPackage().getEFactoryInstance(),
edgeCls, false);
// handle this feature
this.objects.push(null);
this.types.push(OBJECT_TYPE);
// add edge attribute
ArrayList<String> valuePair = new ArrayList<String>(2);
valuePair.add(((GrEMFIncidenceClassWithRefsImpl) edgeCls.getFrom())
.getFromEdgeClass().getName());
valuePair.add(vertexId);
this.edgeAttrs.get(edgeId).add(valuePair);
}
/**
* @param name
*/
private void handleFromEdgeClassReference(String name) {
// reference from an edge class to a vertex class
GrEMFEdgeClassImpl edgeCls = this.edges.get(this.parentIds.peek());
GrEMFVertexClassImpl vertexCls = (GrEMFVertexClassImpl) edgeCls
.getEStructuralFeature(name).getEType();
// get edge id
String edgeId = this.parentIds.peek();
// create vertex id
String vertexId = createIdForFeature(this.parentIds.peek(),
vertexCls.getName());
// create vertex
this.objIds.push(vertexId);
this.parentIds.push(vertexId);
this.processObject(this.createGrEMFObject(vertexCls.getEPackage()
.getEFactoryInstance(), vertexCls, false));
// handle reference of the vertex
this.setValueFromId(this.objects.peek(),
(EReference) edgeCls.getFrom(), edgeId);
// add edge attribute
ArrayList<String> valuePair = new ArrayList<String>(2);
valuePair.add(((GrEMFIncidenceClassWithRefsImpl) edgeCls.getTo())
.getFromEdgeClass().getName());
valuePair.add(vertexId);
this.edgeAttrs.get(edgeId).add(valuePair);
}
@Override
protected void handleProxy(InternalEObject proxy, String uriLiteral) {
// unite the objects in one resource
if (proxy instanceof GrEMFInstanceType) {
if (this.xmlResource.getContents().contains(proxy)) {
this.xmlResource.getContents().add(proxy);
}
} else {
super.handleProxy(proxy, uriLiteral);
}
}
@Override
protected EObject createObject(EFactory factory, EClassifier type,
boolean documentRoot) {
if (factory instanceof GrEMFType) {
return this.createGrEMFObject(factory, type, documentRoot);
} else {
return super.createObject(factory, type, documentRoot);
}
}
@SuppressWarnings("deprecation")
@Override
protected EObject createObjectFromFactory(EFactory factory, String typeName) {
if (factory instanceof GrEMFType) {
return this.createGrEMFObject(factory, factory.getEPackage()
.getEClassifier(typeName), false);
} else {
return super.createObjectFromFactory(factory, typeName);
}
}
private EObject createGrEMFObject(EFactory factory, EClassifier type,
boolean documentRoot) {
// get the object's id
String objId = this.objIds.isEmpty() ? null : this.objIds.pop();
if (objId == null) {
System.err.println(this.parentIds.peek());
System.err.println(this.objects);
System.err.println(this.elements);
System.exit(-1);
}
if ((objId != null) && this.objs.containsKey(objId)) {
if (this.attribs != null) {
this.handleObjectAttribs(this.objs.get(objId));
}
return this.objs.get(objId);
} else {
EObject newEObj;
if (type instanceof GrEMFEdgeClassImpl) {
// delaying edges
this.edges.put(objId, (GrEMFEdgeClassImpl) type);
// save attributes as localName:value pairs
ArrayList<ArrayList<String>> attrs = new ArrayList<ArrayList<String>>(
this.attribs.getLength());
for (int i = 0; i < this.attribs.getLength(); i++) {
ArrayList<String> valuePair = new ArrayList<String>(2);
valuePair.add(this.attribs.getLocalName(i));
valuePair.add(this.attribs.getValue(i));
attrs.add(valuePair);
}
this.edgeAttrs.put(objId, attrs);
newEObj = null;
} else {
newEObj = super.createObject(factory, type, documentRoot);
}
this.objs.put(objId, newEObj);
return newEObj;
}
}
@Override
protected EStructuralFeature getFeature(EObject object, String prefix,
String name, boolean isElement) {
// look up
EStructuralFeature feature = object.eClass()
.getEStructuralFeature(name);
if (feature instanceof GrEMFType) {
// a valid grEMF feature (a GrEMFType and not null)
return super.getFeature(object, prefix, name, isElement);
} else {
// use grEMF prefix
return super.getFeature(object, prefix, "grEMF_" + name, isElement);
}
}
@Override
protected void setValueFromId(EObject object, EReference eReference,
String ids) {
if (object instanceof GrEMFInstanceType) {
StringTokenizer tokenizer = new StringTokenizer(ids, " ");
if (eReference instanceof GrEMFIncidenceClassWithRefsImpl) {
while (tokenizer.hasMoreTokens()) {
String id = decodeXMLId(this.topId, tokenizer.nextToken());
id = createIdForFeature(id,
((GrEMFIncidenceClassWithRefsImpl) eReference)
.getFromEdgeClass().getName());
this.gaps.put(id, object);
this.objs.put(id, null);
}
} else {
String type = "";
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken();
if (token.indexOf(':') >= 0) {
type = token + " ";
if (tokenizer.hasMoreTokens()) {
token = tokenizer.nextToken();
}
}
token = type + decodeXMLId(this.topId, token);
this.objIds.push(token);
super.setValueFromId(object, eReference, token);
}
}
} else {
super.setValueFromId(object, eReference, ids);
}
}
@Override
protected void validateCreateObjectFromFactory(EFactory factory,
String typeName, EObject newObject) {
// no validation (null check) for grEMF types
if (!(factory instanceof GrEMFType)) {
super.validateCreateObjectFromFactory(factory, typeName, newObject);
}
}
@Override
public void endDocument() {
// handle all edges
for (String id : this.edges.keySet()) {
this.handleEdge(this.edges.get(id), id);
}
super.endDocument();
}
/**
* Handles a edge: <br>
* The edge is set indirectly via the special grEMF references.
*
* @param edgeCls
* type of the edge
* @param edgeId
* identifier of the edge
*/
private void handleEdge(GrEMFEdgeClassImpl edgeCls, String edgeId) {
// get the incidence classes
GrEMFIncidenceClassWithRefsImpl fromInc = (GrEMFIncidenceClassWithRefsImpl) edgeCls
.getFrom();
GrEMFIncidenceClassWithRefsImpl toInc = (GrEMFIncidenceClassWithRefsImpl) edgeCls
.getTo();
// get the attributes
ArrayList<ArrayList<String>> attrs = this.edgeAttrs.get(edgeId);
// create the identifiers
String sourceId = createIdForFeature(edgeId, fromInc.getFromEdgeClass()
.getName());
String targetId = createIdForFeature(edgeId, toInc.getFromEdgeClass()
.getName());
GrEMFVertexImpl sourceObj = null;
GrEMFVertexImpl targetObj = null;
// get the connected vertices using the stored attributes
for (int i = 0, size = attrs.size(); i < size; i++) {
ArrayList<String> attr = attrs.get(i);
EStructuralFeature feature = edgeCls.getEStructuralFeature(attr
.get(0));
if (feature.equals(fromInc.getFromEdgeClass())) {
sourceObj = (GrEMFVertexImpl) this.objs.get(decodeXMLId(attr
.get(1)));
} else if (feature.equals(toInc.getFromEdgeClass())) {
targetObj = (GrEMFVertexImpl) this.objs.get(decodeXMLId(attr
.get(1)));
}
}
// one object must exist
if ((sourceObj == null) && (targetObj == null)) {
throw new RuntimeException();
}
// register the connected vertices
this.objs.put(sourceId, sourceObj);
this.objs.put(targetId, targetObj);
// "fill the gaps": create the edge indirectly (only once!)
if (sourceObj != null) {
this.fillGap(fromInc, sourceId);
} else {
this.fillGap(toInc, targetId);
}
// get edge object
GrEMFEdgeImpl edge = null;
if (sourceObj != null) {
edge = this.getEdgeObject(edgeCls, sourceId);
} else {
edge = this.getEdgeObject(edgeCls, targetId);
}
// add all "real" attributes to the edge
for (int i = 0, size = attrs.size(); i < size; i++) {
EStructuralFeature feature = edgeCls.getEStructuralFeature(attrs
.get(i).get(0));
if (feature instanceof GrEMFAttributeImpl) {
super.setAttribValue(edge, feature.getName(),
attrs.get(i).get(1));
}
}
// register edge
this.objs.put(edgeId, edge);
// clear gaps
if (this.gaps.containsKey(sourceId)) {
this.gaps.remove(sourceId);
}
if (this.gaps.containsKey(targetId)) {
this.gaps.remove(targetId);
}
this.processTopObject(edge);
}
/**
* Gets the created edge object: <br>
* The "this" vertex is taken from the
* {@link de.uni_koblenz.gremf.resource.GrEMFInstanceSAXHandler#objs objs}
* map using the given id. The "that" vertex is taken from the
* {@link de.uni_koblenz.gremf.resource.GrEMFInstanceSAXHandler#gaps gaps}
* map using the given id.
*
* @param edgeCls
* type of the edge
* @param thisId
* identifier used to determine "this" and "that"
* @return edge of type <code>edgeCls</code> that matches with "this" and
* "that"
*/
private GrEMFEdgeImpl getEdgeObject(GrEMFEdgeClassImpl edgeCls,
String thisId) {
for (Edge e : ((GrEMFVertexImpl) this.objs.get(thisId))
.incidences(edgeCls)) {
// that matches and edge is not registered
if (e.getThat().equals(this.gaps.get(thisId))
&& !this.objs.containsValue(e)) {
return (GrEMFEdgeImpl) e;
}
}
return null;
}
/**
* Fills the gap with given id as key in the
* {@link de.uni_koblenz.gremf.resource.GrEMFInstanceSAXHandler#gaps gaps}
* map: <br>
* The reference of the object with the gap is set to the the object related
* to the given id in the
* {@link de.uni_koblenz.gremf.resource.GrEMFInstanceSAXHandler#objs objs}
* map.
*
* @param ref
* reference that is set
* @param id
* identifier used to determine both objects
*/
private void fillGap(EReference ref, String id) {
if (this.gaps.containsKey(id)) {
this.setFeatureValue(this.gaps.get(id), ref, this.objs.get(id));
}
}
/**
* Creates an identifier for a top element.
*
* @param topIndex
* index of this top element
*
* @return #/ + [index of the top element]
*/
private static String createIdForTopElement(int topIndex) {
return "gr#/" + topIndex;
}
/**
* Creates an identifier for a feature: <i>(note: all XML elements which are
* not at top level are structural features)</i> <br>
* Therefore, this call is delegated to
* {@link de.uni_koblenz.gremf.resource.GrEMFInstanceSAXHandler#createIdForFeature(String, String, int, boolean)
* createIdForFeature(String, String, int, boolean)} with default values for
* the additional parameters.
*
* @param parentId
* parent element's identifier
* @param name
* (simple) feature name
* @return [unqiue simple name]
*/
private static String createIdForFeature(String parentId, String name) {
return createIdForFeature(parentId, name, 0, false);
}
/**
* Creates an identifier for a feature: <i>(note: all XML elements which are
* not at top level are structural features)</i> <br>
* Therefore, this call is delegated to
* {@link de.uni_koblenz.gremf.resource.GrEMFInstanceSAXHandler#createIdFromUniqueName(String, int, boolean)
* createIdFromUniqueName(String, int, boolean)}.
*
* @param uniqueName
* (simple) feature name
* @param featureIndex
* index of the current feature
* @param listFeature
* true if the feature is a list feature; false otherwise
* @return [unique name] + . + [index of the feature], if
* <code>listFeature</code> <br>
* [unqiue simple name], otherwise
*/
private static String createIdForFeature(String uniqueName,
int featureIndex, boolean listFeature) {
return createIdFromUniqueName(uniqueName, featureIndex, listFeature);
}
/**
* Creates an identifier for a feature: <i>(note: all XML elements which are
* not at top level are structural features)</i> <br>
* Therefore, this call is delegated to
* {@link de.uni_koblenz.gremf.resource.GrEMFInstanceSAXHandler#createIdFromUniqueName(String, int, boolean)
* createIdFromUniqueName(String, int, boolean)} with a unique feature name
* created from the parent element's identifier and the simple name.
*
* @param parentId
* parent element's identifier
* @param name
* (simple) feature name
* @param featureIndex
* index of the current feature
* @param listFeature
* true if the feature is a list feature; false otherwise
* @return [unique name] + . + [index of the feature], if
* <code>listFeature</code> <br>
* [unqiue simple name], otherwise
* @see {@link de.uni_koblenz.gremf.resource.GrEMFInstanceSAXHandler#createUniqueNameForFeature(String, String)
* createUniqueNameForFeature(String, String)}
*/
private static String createIdForFeature(String parentId, String name,
int featureIndex, boolean listFeature) {
return createIdFromUniqueName(
createUniqueNameForFeature(parentId, name), featureIndex,
listFeature);
}
/**
* Creates an identifier from a unique name.
*
* @param uName
* unique name
* @param featureIndex
* index of the current feature
* @param listFeature
* true if the feature is a list feature; false otherwise
* @return [unique name] + . + [index of the feature], if
* <code>listFeature</code> <br>
* [unique name], otherwise
* @see {@link de.uni_koblenz.gremf.resource.GrEMFInstanceSAXHandler#createUniqueNameForFeature(String, String)
* createUniqueNameForFeature(String, String)}
*
*/
private static String createIdFromUniqueName(String uName,
int featureIndex, boolean listFeature) {
if (listFeature) {
return uName + '.' + featureIndex;
} else {
return uName;
}
}
/**
* Creates an unique name for a feature: <i>(note: all XML elements which
* are not at top level are structural features)</i> <br>
*
* @param parentId
* identifier of the parent element
* @param name
* (simple) feature name
* @return [parent element's identifier] + /@ + [simple name]
*/
private static String createUniqueNameForFeature(String parentId,
String name) {
return parentId + "/@" + name;
}
/**
* Decodes an xml identifier:<br>
* Therefore, this call is delegated to
* {@link de.uni_koblenz.gremf.resource.GrEMFInstanceSAXHandler#decodeXMLId(Resource, String, String)
* createIdForFeature(Resource, String, String)} with null as values for the
* additional parameter.
*
* @param resourceURI
* uri of the containing resource
* @param id
* xml identifier
* @return decoded identifier
*/
private static String decodeXMLId(String id) {
return decodeXMLId(null, id);
}
/**
* Decodes an xml identifier:<br>
* The path is added to fragmented identifiers using either the resource or
* the top identifier. Full identifiers are not changed.
*
* @param resourceURI
* uri of the containing resource
* @param topId
* identifier of the top element
* @param id
* xml identifier
* @return decoded identifier
*/
private static String decodeXMLId(String topId, String id) {
// handle different kinds of identifiers
if (id.length() > 1) {
char c0 = id.charAt(0);
char c1 = id.charAt(1);
if ((c0 == '/') && (c1 == '/')) {
// "//identifier"
return topId + id.substring(1);
} else if ((c0 == '/') && Character.isDigit(c1)) {
// "/0"
return "gr#" + id;
} else if ((c0 == '#') && (c1 == '/')) {
// "#/identifier"
return "gr" + id;
} else if ((id.length() > 2) && (c0 == 'g') && (c1 == 'r')
&& (id.charAt(2) == '#')) {
return id;
}
}
// handle other qualified identifier
return "gr" + id.substring(id.indexOf('#'));
}
}