blob: e9523cee260aed1d958c37c25ab14fcd353629b5 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2011-2015 EclipseSource Muenchen GmbH and others.
*
* 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:
* jfaltermeier - initial API and implementation
******************************************************************************/
package org.eclipse.emf.ecp.view.edapt;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import org.eclipse.emf.common.util.TreeIterator;
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.EPackage.Registry;
import org.eclipse.emf.ecore.EStructuralFeature;
/**
* Tree like datastructure representing EPackages and their depdendencies to each other. Offers an Iterator to
* navigate over the dependencies.
*
* @author jfaltermeier
*
*/
public class PackageDependencyTree {
private final Map<String, PackageTreeNode> nsURIToNodeMap;
private final Set<PackageTreeNode> roots;
/**
* Constructs a new empty {@link PackageDependencyTree}.
*/
public PackageDependencyTree() {
roots = new LinkedHashSet<PackageTreeNode>();
nsURIToNodeMap = new LinkedHashMap<String, PackageTreeNode>();
}
/**
* Adds a new {@link EPackage} with the given namespace URI to the tree. All required dependencies of the EPackage
* will be registered as well.
*
* @param nsURI the namespace uri of the package to add
*/
public void addPackage(String nsURI) {
if (nsURIToNodeMap.containsKey(nsURI)) {
return;
}
final PackageTreeNode node = createNode(nsURI);
resolveNode(node);
}
private void resolveNode(PackageTreeNode node) {
final Set<String> nsURIs = new LinkedHashSet<String>();
/* resolve direct nsURIs */
final EPackage rootPackage = Registry.INSTANCE.getEPackage(node.getNSURI());
final TreeIterator<EObject> iterator = rootPackage.eAllContents();
while (iterator.hasNext()) {
final EObject next = iterator.next();
/* check super types of contained classes */
if (EClass.class.isInstance(next)) {
final EClass eClass = (EClass) next;
for (final EClass superType : eClass.getESuperTypes()) {
nsURIs.add(superType.getEPackage().getNsURI());
}
}
/* check types of features */
else if (EStructuralFeature.class.isInstance(next)) {
final EStructuralFeature feature = (EStructuralFeature) next;
final EClassifier eType = feature.getEType();
nsURIs.add(eType.getEPackage().getNsURI());
}
}
/* remove self */
nsURIs.remove(node.getNSURI());
/* root? */
if (nsURIs.isEmpty()) {
roots.add(node);
}
/* insert dependencies in tree */
for (final String nsURI : nsURIs) {
/* existing */
if (nsURIToNodeMap.containsKey(nsURI)) {
final PackageTreeNode parent = nsURIToNodeMap.get(nsURI);
node.addParent(parent);
parent.addChild(node);
}
/* non existing */
else {
final PackageTreeNode parent = createNode(nsURI);
resolveNode(parent);
parent.addChild(node);
node.addParent(parent);
}
}
}
private PackageTreeNode createNode(String nsURI) {
final PackageTreeNode node = new PackageTreeNode(nsURI);
nsURIToNodeMap.put(nsURI, node);
return node;
}
/**
* Returns an iterator for the tree. It will return sets of namespace uris. The set that is returned by next will
* always have no unvisited parents, meaning that all parents have been returned by next before.
*
* @return the iterator
*/
public Iterator<Set<String>> getIerator() {
return new PackageDependencyIterator(roots, nsURIToNodeMap.values());
}
/**
* Iterator for nsURIs based on the dependencies beginning from the roots.
*
* @author jfaltermeier
*
*/
private static class PackageDependencyIterator implements Iterator<Set<String>> {
private final Set<PackageTreeNode> nodesToVisit;
private final Set<PackageTreeNode> visitedNodes;
private final Set<PackageTreeNode> unvisitedNodes;
private Set<PackageTreeNode> next;
public PackageDependencyIterator(Collection<PackageTreeNode> roots, Collection<PackageTreeNode> allNodes) {
visitedNodes = new LinkedHashSet<PackageTreeNode>();
unvisitedNodes = new LinkedHashSet<PackageTreeNode>(allNodes);
nodesToVisit = new LinkedHashSet<PackageTreeNode>(roots);
next = findNext();
}
@Override
public boolean hasNext() {
return !next.isEmpty();
}
@Override
public Set<String> next() {
visitedNodes.addAll(next);
unvisitedNodes.removeAll(next);
final Set<String> nsuri = new LinkedHashSet<String>();
for (final PackageTreeNode nextNode : next) {
nsuri.add(nextNode.getNSURI());
}
next = findNext();
return nsuri;
}
private Set<PackageTreeNode> findNext() {
/* we are looking for a node with no parents */
final Set<PackageTreeNode> result = new LinkedHashSet<PackageTreeNode>();
for (final PackageTreeNode node : nodesToVisit) {
boolean hasUnvisitedParent = false;
for (final PackageTreeNode parent : node.getParents()) {
if (!visitedNodes.contains(parent)) {
hasUnvisitedParent = true;
break;
}
}
if (!hasUnvisitedParent) {
for (final PackageTreeNode child : node.getChildren()) {
if (!visitedNodes.contains(child)) {
nodesToVisit.add(child);
}
}
result.add(node);
break;
}
}
if (result.isEmpty() && !unvisitedNodes.isEmpty()) {
// circle detected
result.addAll(getCircleSet());
}
for (final PackageTreeNode packageTreeNode : result) {
nodesToVisit.remove(packageTreeNode);
}
return result;
}
private Collection<? extends PackageTreeNode> getCircleSet() {
/* 1. circle detection: put all nodes which contain to the same circle in a set */
final Map<PackageTreeNode, Set<PackageTreeNode>> nodeToCircleMap = new LinkedHashMap<PackageTreeNode, Set<PackageTreeNode>>();
final Set<Set<PackageTreeNode>> allCircles = new LinkedHashSet<Set<PackageTreeNode>>();
for (final PackageTreeNode nodeToAllocate : unvisitedNodes) {
// get existing circle set from map or create new set
final Set<PackageTreeNode> circle = nodeToCircleMap.containsKey(nodeToAllocate) ?
nodeToCircleMap.get(nodeToAllocate)
: new LinkedHashSet<PackageTreeNode>();
// if new set, fill map
if (!nodeToCircleMap.containsKey(nodeToAllocate)) {
circle.add(nodeToAllocate);
nodeToCircleMap.put(nodeToAllocate, circle);
allCircles.add(circle);
}
// nodes contain to same set if outgoing edge leads back to self
final Set<PackageTreeNode> outgoingEdges = nodeToAllocate.getChildren();
for (final PackageTreeNode outgoingEdge : outgoingEdges) {
final boolean hasPathToOtherNode = hasPathToOtherNode(outgoingEdge, nodeToAllocate,
new LinkedHashSet<PackageTreeNode>());
if (hasPathToOtherNode) {
circle.add(outgoingEdge);
nodeToCircleMap.put(outgoingEdge, circle);
}
}
}
/* 2. find root circle */
return findRootCircle(allCircles);
}
private Collection<? extends PackageTreeNode> findRootCircle(final Set<Set<PackageTreeNode>> allCircles) {
for (final Set<PackageTreeNode> circle : allCircles) {
// root circle is the set where all unvisited parents are from the same set
boolean isRoot = true;
for (final PackageTreeNode node : circle) {
for (final PackageTreeNode mustBeInCircle : node.getParents()) {
if (visitedNodes.contains(mustBeInCircle)) {
// the parent was already returned by the iterator, so we can skip it
continue;
}
if (!circle.contains(mustBeInCircle)) {
isRoot = false;
break;
}
}
if (!isRoot) {
break;
}
}
if (isRoot) {
return circle;
}
}
// this state is unexpected. if this is reached we could have returned a valid set of nsuri beforehand
// (either no circle at all, or the circle detection went wrong)
throw new IllegalStateException("No root circle found"); //$NON-NLS-1$
}
private boolean hasPathToOtherNode(PackageTreeNode start, PackageTreeNode target,
Set<PackageTreeNode> visitedNodes) {
visitedNodes.add(start);
final Set<PackageTreeNode> outgoingNodes = start.getChildren();
if (outgoingNodes.contains(target)) {
return true;
}
boolean result = false;
for (final PackageTreeNode outgoingNode : outgoingNodes) {
if (visitedNodes.contains(outgoingNode)) {
// we already visited/are visiting all children of this node -> skip
continue;
}
result |= hasPathToOtherNode(outgoingNode, target, visitedNodes);
}
return result;
}
/**
* {@inheritDoc}
*
* @see java.util.Iterator#remove()
*/
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
/**
* Simple tree data structure to order the changes of all required epackages during the migration.
*
* @author jfaltermeier
*
*/
private static class PackageTreeNode {
private final String nsURI;
private final Set<PackageTreeNode> parents;
private final Set<PackageTreeNode> children;
public PackageTreeNode(String nsURI) {
this.nsURI = nsURI;
parents = new LinkedHashSet<PackageTreeNode>();
children = new LinkedHashSet<PackageTreeNode>();
}
public String getNSURI() {
return nsURI;
}
public void addParent(PackageTreeNode node) {
parents.add(node);
}
public void addChild(PackageTreeNode node) {
children.add(node);
}
public Set<PackageTreeNode> getParents() {
return parents;
}
public Set<PackageTreeNode> getChildren() {
return children;
}
}
}