blob: 0c4c7999f21e9ba5686cbc3cca453051b31596a1 [file] [log] [blame]
/**
* <copyright>
* Copyright (c) 2010-2014 Henshin developers. 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
* </copyright>
*/
package org.eclipse.emf.henshin.interpreter.impl;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.util.ECrossReferenceAdapter;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecore.util.EcoreUtil.Copier;
import org.eclipse.emf.henshin.interpreter.EGraph;
import org.eclipse.emf.henshin.interpreter.util.EcoreCopier;
import org.eclipse.emf.henshin.interpreter.util.InterpreterUtil;
/**
* Default {@link EGraph} implementation. Based on linked hash sets.
*/
public class EGraphImpl extends LinkedHashSet<EObject> implements EGraph {
/**
* Generated serial ID.
*/
private static final long serialVersionUID = -1917534761444871093L;
/**
* All involved EPackages.
*/
protected final Set<EPackage> packages;
/**
* Mappings from each type to all its instances.
*/
protected final Map<EClass, List<EObject>> domainMap;
/**
* Mappings from each type to all its extending subtypes.
*/
protected final Map<EClass, Set<EClass>> inheritanceMap;
/**
* Cross reference adapter for determining cross references between registered objects.
*/
protected final ECrossReferenceAdapter crossReferenceAdapter;
/**
* Default constructor.
*/
public EGraphImpl() {
this(32);
}
/**
* Constructor.
* @param initialCapacity Initial capacity of the graph.
*/
public EGraphImpl(int initialCapacity) {
super(initialCapacity);
packages = new LinkedHashSet<EPackage>();
domainMap = new LinkedHashMap<EClass, List<EObject>>();
inheritanceMap = new LinkedHashMap<EClass, Set<EClass>>();
crossReferenceAdapter = new ECrossReferenceAdapter();
}
/**
* Convenience constructor. Adds an object and all reachable objects to this graph.
* @param object An object.
*/
public EGraphImpl(EObject object) {
this();
initializeContents(Collections.singleton(object));
}
/**
* Convenience constructor. Adds a collection of objects and all reachable objects to this graph.
* @param collection A collection of objects.
*/
public EGraphImpl(Collection<? extends EObject> collection) {
this();
initializeContents(collection);
}
/**
* Convenience constructor. Adds the contents of a resource and all reachable objects to this graph.
* @param resource A resource.
*/
public EGraphImpl(Resource resource) {
this();
initializeContents(resource.getContents());
}
/**
* Initialize this EGraph.
* @param collection Collection of objects to be added.
*/
protected void initializeContents(Collection<? extends EObject> collection) {
// First add the collection as a normal tree:
for (EObject object : collection) {
if (!contains(object)) { // omit tree traversal if possible
addTree(object);
}
}
// And now again as a graph:
addGraph(collection);
}
/*
* (non-Javadoc)
* @see java.util.HashSet#add(java.lang.Object)
*/
@Override
public final boolean add(EObject object) {
boolean added = super.add(object);
if (added) {
didAdd(object);
}
return added;
}
/**
* Notify that an object has been added to the graph.
* @param object Added object.
*/
protected void didAdd(EObject object) {
object.eAdapters().add(crossReferenceAdapter);
EClass type = object.eClass();
getDomain(type).add(object);
addEPackage(type.getEPackage());
}
/*
* (non-Javadoc)
* @see java.util.HashSet#remove(java.lang.Object)
*/
@Override
public final boolean remove(Object object) {
boolean removed = super.remove(object);
if (removed && object instanceof EObject) {
didRemove((EObject) object);
}
return removed;
}
/**
* Notify that an object has been removed from the graph.
* @param object Removed object.
*/
protected void didRemove(EObject object) {
object.eAdapters().remove(crossReferenceAdapter);
domainMap.get(object.eClass()).remove(object);
}
/*
* (non-Javadoc)
* @see org.eclipse.emf.henshin.interpreter.EGraph#addTree(org.eclipse.emf.ecore.EObject)
*/
@Override
public boolean addTree(EObject root) {
boolean changed = add(root);
for (Iterator<EObject> it = root.eAllContents(); it.hasNext();) {
if (add(it.next())) changed = true;
}
return changed;
}
/*
* (non-Javadoc)
* @see org.eclipse.emf.henshin.interpreter.EGraph#removeTree(org.eclipse.emf.ecore.EObject)
*/
@Override
public boolean removeTree(EObject root) {
boolean changed = remove(root);
for (final Iterator<EObject> it = root.eAllContents(); it.hasNext();) {
if (remove(it.next())) changed = true;
}
return changed;
}
/*
* (non-Javadoc)
* @see org.eclipse.emf.henshin.interpreter.EGraph#addGraph(org.eclipse.emf.ecore.EObject)
*/
@Override
public boolean addGraph(EObject object) {
return addAll(computeTransitiveClosure(object));
}
/*
* (non-Javadoc)
* @see org.eclipse.emf.henshin.interpreter.EGraph#removeGraph(org.eclipse.emf.ecore.EObject)
*/
@Override
public boolean removeGraph(EObject object) {
return removeAll(computeTransitiveClosure(object));
}
/*
* Add a graph. Internal helper.
*/
private void addGraph(Collection<? extends EObject> collection) {
Set<EObject> visited = new HashSet<EObject>();
for (EObject object : collection) {
if (!visited.contains(object)) { // omit computing the transitive closure if possible
Set<EObject> closure = computeTransitiveClosure(object);
addAll(closure);
visited.addAll(closure);
}
}
}
/**
* Compute the transitive closure of referenced {@link EObject}s.
* It is implemented iteratively to prevent stack overflows.
* @param start The start object.
* @return Set of transitively reachable objects.
*/
private Set<EObject> computeTransitiveClosure(EObject start) {
// Initialize:
Set<EObject> result = new LinkedHashSet<EObject>();
Set<EObject> newObjects = new LinkedHashSet<EObject>();
newObjects.add(start);
// Loop:
while (!newObjects.isEmpty()) {
result.addAll(newObjects);
// Compute references:
Set<EObject> referencedObjects = new LinkedHashSet<EObject>();
for (EObject object : newObjects) {
for (EReference ref : object.eClass().getEAllReferences()) {
if (ref.isMany()) {
@SuppressWarnings("unchecked")
EList<EObject> targets = (EList<EObject>) object.eGet(ref);
referencedObjects.addAll(targets);
} else {
EObject target = (EObject) object.eGet(ref);
if (target!=null) {
referencedObjects.add(target);
}
}
}
}
// Check which of them are new:
newObjects.clear();
for (EObject object : referencedObjects) {
if (!result.contains(object)) {
newObjects.add(object);
}
}
}
// Finished.
return result;
}
/*
* (non-Javadoc)
* @see java.util.AbstractCollection#addAll(java.util.Collection)
*/
@Override
public boolean addAll(Collection<? extends EObject> objs) {
boolean changed = false;
for (EObject object : objs) {
if (add(object)) changed = true;
}
return changed;
}
/*
* (non-Javadoc)
* @see java.util.AbstractSet#removeAll(java.util.Collection)
*/
@Override
public boolean removeAll(Collection<?> objs) {
boolean changed = false;
for (Object object : objs) {
if (remove(object)) changed = true;
}
return changed;
}
/*
* (non-Javadoc)
* @see java.util.HashSet#clear()
*/
@Override
public void clear() {
for (EObject object : this) {
object.eAdapters().remove(crossReferenceAdapter);
}
super.clear();
packages.clear();
domainMap.clear();
inheritanceMap.clear();
}
/**
* Add an {@link EPackage}. Update the internal inheritance map.
* @param ePackage {@link EPackage} to be added.
* @return <code>true</code> if it is new.
*/
protected boolean addEPackage(EPackage ePackage) {
boolean added = packages.add(ePackage);
if (added) {
for (EClassifier classifier : ePackage.getEClassifiers()) {
if (classifier instanceof EClass) {
EClass type = (EClass) classifier;
addChildParentRelation(type, type);
for (EClass parent : type.getEAllSuperTypes()) {
addChildParentRelation(type, parent);
}
}
}
}
return added;
}
/**
* Update the inheritance map. Add a child-parent relationship.
* @param child Child class.
* @param parent Parent class.
*/
protected void addChildParentRelation(EClass child, EClass parent) {
Set<EClass> children = inheritanceMap.get(parent);
if (children == null) {
children = new LinkedHashSet<EClass>();
inheritanceMap.put(parent, children);
}
children.add(child);
}
/*
* (non-Javadoc)
* @see org.eclipse.emf.henshin.interpreter.EGraph#getDomain(org.eclipse.emf.ecore.EClass, boolean)
*/
@Override
public List<EObject> getDomain(EClass type, boolean strict) {
if (strict) {
return new ArrayList<EObject>(getDomain(type));
}
List<EObject> domain = new ArrayList<EObject>();
Set<EClass> inhMap = inheritanceMap.get(type);
if (inhMap != null) {
for (EClass child : inhMap) {
domain.addAll(getDomain(child));
}
}
return domain;
}
/*
* (non-Javadoc)
* @see org.eclipse.emf.henshin.interpreter.EGraph#getDomainSize(org.eclipse.emf.ecore.EClass, boolean)
*/
@Override
public int getDomainSize(EClass type, boolean strict) {
if (strict) {
return getDomain(type).size();
}
Set<EClass> inhMap = inheritanceMap.get(type);
int size = 0;
if (inhMap != null) {
for (EClass child : inhMap) {
size += getDomain(child).size();
}
}
return size;
}
/*
* Get the domain for a given type.
*/
protected Collection<EObject> getDomain(EClass type) {
List<EObject> domain = domainMap.get(type);
if (domain == null) {
domain = new ArrayList<EObject>();
domainMap.put(type, domain);
}
return domain;
}
/*
* (non-Javadoc)
* @see org.eclipse.emf.henshin.interpreter.EGraph#getCrossReferenceAdapter()
*/
@Override
public ECrossReferenceAdapter getCrossReferenceAdapter() {
return crossReferenceAdapter;
}
/*
* (non-Javadoc)
* @see org.eclipse.emf.henshin.interpreter.EGraph#copy(java.util.Map)
*/
@Override
public EGraph copy(Map<EObject, EObject> copies) {
if (copies==null) {
Copier copier = null;
if (packages.contains(EcorePackage.eINSTANCE)) {
copier = new EcoreCopier();
} else {
copier = new Copier();
}
copier.copyAll(getRoots());
copier.copyReferences();
copies = copier;
}
EGraph copy = new EGraphImpl(size());
for (EObject object : this) {
copy.add(copies.get(object));
}
return copy;
}
/*
* (non-Javadoc)
* @see org.eclipse.emf.henshin.interpreter.EGraph#getRoots()
*/
@Override
public List<EObject> getRoots() {
Set<EObject> roots = new LinkedHashSet<EObject>();
for (EObject object : this) {
roots.add(EcoreUtil.getRootContainer(object));
}
return new ArrayList<EObject>(roots);
}
/*
* (non-Javadoc)
* @see java.util.AbstractCollection#toString()
*/
@Override
public String toString() {
return getClass().getSimpleName() + "@" + Integer.toHexString(hashCode()) +
" (nodes: " + size() + ", edges: " + InterpreterUtil.countEdges(this)+")";
}
}