Merge "Bug 497077 - Change namespace icon to distinguish it from class icon"
diff --git a/core/plugins/org.eclipse.dltk.core/model/org/eclipse/dltk/internal/core/hierarchy/TypeHierarchy.java b/core/plugins/org.eclipse.dltk.core/model/org/eclipse/dltk/internal/core/hierarchy/TypeHierarchy.java
index 1c9a695..e949c00 100644
--- a/core/plugins/org.eclipse.dltk.core/model/org/eclipse/dltk/internal/core/hierarchy/TypeHierarchy.java
+++ b/core/plugins/org.eclipse.dltk.core/model/org/eclipse/dltk/internal/core/hierarchy/TypeHierarchy.java
@@ -16,6 +16,7 @@
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
+import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
@@ -101,6 +102,8 @@
protected Map<IType, TypeVector> classToSuperclass;
protected Map<IType, TypeVector> typeToSubtypes;
protected Map<IType, Integer> typeFlags;
+ protected Set<IType> exploredClasses;
+ protected Set<IType> cyclicClasses;
protected TypeVector rootClasses = new TypeVector();
public ArrayList<String> missingTypes = new ArrayList<String>(4);
@@ -247,14 +250,7 @@
* Adds the given subtype to the type.
*/
protected void addSubtype(IType type, IType subtype) {
- TypeVector subtypes = this.typeToSubtypes.get(type);
- if (subtypes == null) {
- subtypes = new TypeVector();
- this.typeToSubtypes.put(type, subtypes);
- }
- if (!subtypes.contains(subtype)) {
- subtypes.add(subtype);
- }
+ cacheSuperclass(subtype, type);
}
/**
@@ -308,8 +304,17 @@
}
if (!superTypes.contains(superclass)) {
superTypes.add(superclass);
+ resetClassPaths();
}
- addSubtype(superclass, type);
+ TypeVector subtypes = this.typeToSubtypes.get(superclass);
+ if (subtypes == null) {
+ subtypes = new TypeVector();
+ this.typeToSubtypes.put(superclass, subtypes);
+ }
+ if (!subtypes.contains(type)) {
+ subtypes.add(type);
+ resetClassPaths();
+ }
}
}
@@ -568,12 +573,7 @@
*/
@Override
public IType[] getSubclasses(IType type) {
- TypeVector vector = this.typeToSubtypes.get(type);
- if (vector == null) {
- return NO_TYPE;
- } else {
- return vector.elements();
- }
+ return getSubtypesForType(type);
}
/**
@@ -588,12 +588,7 @@
* Returns an array of subtypes for the given type - will never return null.
*/
private IType[] getSubtypesForType(IType type) {
- TypeVector vector = this.typeToSubtypes.get(type);
- if (vector == null) {
- return NO_TYPE;
- } else {
- return vector.elements();
- }
+ return filterSuperOrSubclasses(type, true);
}
/**
@@ -601,11 +596,7 @@
*/
@Override
public IType[] getSuperclass(IType type) {
- TypeVector superTypes = this.classToSuperclass.get(type);
- if (superTypes != null) {
- return superTypes.elements();
- }
- return TypeVector.NoElements;
+ return filterSuperOrSubclasses(type, false);
}
/**
@@ -756,6 +747,8 @@
this.rootClasses = new TypeVector();
this.typeToSubtypes = new HashMap<IType, TypeVector>(smallSize);
this.typeFlags = new HashMap<IType, Integer>(smallSize);
+ this.exploredClasses = new HashSet<IType>(smallSize);
+ this.cyclicClasses = new HashSet<IType>(smallSize);
this.projectRegion = new Region();
this.packageRegion = new Region();
@@ -1478,8 +1471,8 @@
buffer.append(" "); //$NON-NLS-1$
}
ModelElement element = (ModelElement) sortedTypes[i];
- buffer.append(element
- .toStringWithAncestors(false/* don't show key */));
+ buffer.append(
+ element.toStringWithAncestors(false/* don't show key */));
buffer.append('\n');
toString(buffer, types[i], indent + 1, ascendant);
}
@@ -1515,4 +1508,114 @@
checkCanceled();
}
}
+
+ /**
+ * Returns whether a class belongs to an explored class path or not.
+ */
+ protected boolean isExplored(IType type) {
+ return this.exploredClasses.contains(type);
+ }
+
+ /**
+ * Returns whether a class belongs to a cyclic class path or not.
+ */
+ protected boolean isCyclic(IType type) {
+ return this.cyclicClasses.contains(type);
+ }
+
+ /**
+ * Removes all explored class paths. Necessary when new types are added to
+ * maps "classToSuperclass" or "typeToSubtypes" otherwise new cyclic paths
+ * won't be detected.
+ */
+ protected void resetClassPaths() {
+ this.exploredClasses.clear();
+ this.cyclicClasses.clear();
+ }
+
+ /**
+ * Main method to discover cyclic and non-cyclic (graph) paths. It is a
+ * depth-first search that will mark as explored (in set "exploredClasses")
+ * all children (i.e. super classes) of "currentClass" and will store (in
+ * set "cyclicClasses") every child that is on a cyclic graph path. All
+ * cyclic paths will be fully explored and list "currentClassPath" avoids
+ * infinite recursion of this method by storing current class path going
+ * from first explored "currentClass" (included) to actual explored
+ * "currentClass" (excluded).
+ */
+ void explore(LinkedList<IType> currentClassPath, IType currentClass) {
+ if (currentClass == null) {
+ return;
+ }
+ int idx = currentClassPath.indexOf(currentClass);
+ if (idx == -1 && isExplored(currentClass) && !isCyclic(currentClass)) {
+ // we already explored currentClass and its super classes (if any),
+ // we can stop here, this class path (without "currentClass") being
+ // a non-cyclic path
+ return;
+ }
+ this.exploredClasses.add(currentClass);
+ if (idx != -1) {
+ // We found a cyclic path.
+ // Split class path in 2, the delimiter being "currentClass". First
+ // part is non-cyclic whereas second part is cyclic (and includes
+ // "currentClass").
+ this.cyclicClasses.addAll(
+ currentClassPath.subList(idx, currentClassPath.size()));
+ return;
+ }
+ TypeVector superclasses = this.classToSuperclass.get(currentClass);
+ if (superclasses != null && superclasses.size > 0) {
+ // add current class to current class path
+ currentClassPath.addLast(currentClass);
+ // visit all super classes
+ for (IType superclass : superclasses.elements()) {
+ explore(currentClassPath, superclass);
+ }
+ // remove current class from current class path
+ currentClassPath.removeLast();
+ }
+ }
+
+ /**
+ * Filters either super or subclasses (depending on value of parameter
+ * "isFilterForSubclasses"). Only super or subclasses having no cyclic
+ * path/hierarchy will be returned.
+ */
+ IType[] filterSuperOrSubclasses(IType type, boolean isFilterForSubclasses) {
+ if (type == null) {
+ return TypeVector.NoElements;
+ }
+ if (!this.typeToSubtypes.containsKey(type)
+ && !this.classToSuperclass.containsKey(type)) {
+ // this type is not handled by current type hierarchy,
+ // do not cache its path informations
+ return TypeVector.NoElements;
+ }
+ if (!isExplored(type)) {
+ // this type was never explored, go through this type and its
+ // unexplored super classes to cache all path informations
+ explore(new LinkedList<IType>(), type);
+ }
+ assert isExplored(type);
+ TypeVector superOrSubclasses = isFilterForSubclasses
+ ? typeToSubtypes.get(type) : classToSuperclass.get(type);
+ if (superOrSubclasses == null) {
+ return TypeVector.NoElements;
+ }
+ ArrayList<IType> l = new ArrayList<IType>();
+ for (IType superOrSubclass : superOrSubclasses.elements()) {
+ if (isFilterForSubclasses && superOrSubclass != null
+ && !isExplored(superOrSubclass)) {
+ // this type was never explored, go through this type and its
+ // unexplored super classes to cache all path informations
+ explore(new LinkedList<IType>(), superOrSubclass);
+ }
+ assert superOrSubclass == null || isExplored(superOrSubclass);
+ if (superOrSubclass == null || !isCyclic(superOrSubclass)) {
+ l.add(superOrSubclass);
+ }
+ }
+ return l.toArray(new IType[l.size()]);
+ }
}
diff --git a/core/tests/org.eclipse.dltk.core.tests/src/org/eclipse/dltk/core/tests/model/TypeHierarchyTests.java b/core/tests/org.eclipse.dltk.core.tests/src/org/eclipse/dltk/core/tests/model/TypeHierarchyTests.java
new file mode 100644
index 0000000..1872488
--- /dev/null
+++ b/core/tests/org.eclipse.dltk.core.tests/src/org/eclipse/dltk/core/tests/model/TypeHierarchyTests.java
@@ -0,0 +1,485 @@
+/*******************************************************************************
+ * Copyright (c) 2016 IBM Corporation 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
+ *
+ *******************************************************************************/
+package org.eclipse.dltk.core.tests.model;
+
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.dltk.core.IBuffer;
+import org.eclipse.dltk.core.IModelElement;
+import org.eclipse.dltk.core.ISourceModule;
+import org.eclipse.dltk.core.IType;
+import org.eclipse.dltk.core.WorkingCopyOwner;
+import org.eclipse.dltk.internal.core.ModelElement;
+import org.eclipse.dltk.internal.core.SourceType;
+import org.eclipse.dltk.internal.core.hierarchy.TypeHierarchy;
+
+public class TypeHierarchyTests extends ModifyingResourceTests {
+ private static boolean DEBUG = false;
+ private static final String[] TEST_NATURE = new String[] { "org.eclipse.dltk.core.tests.testnature" };
+
+ private ISourceModule cu = null;
+ private ISourceModule copy = null;
+
+ public class TestWorkingCopyOwner extends WorkingCopyOwner {
+ @Override
+ public IBuffer createBuffer(ISourceModule workingCopy) {
+ return new TestBuffer(workingCopy);
+ }
+ }
+
+ private final Set<IType> expectedExploredClasses = new HashSet<IType>();
+ private final Set<IType> expectedCyclicClasses = new HashSet<IType>();
+ private final FakeTypeHierarchy typeHierarchy = new FakeTypeHierarchy();
+ private boolean useCacheSuperclass = true;
+
+ public TypeHierarchyTests(String name) {
+ super(ModelTestsPlugin.PLUGIN_NAME, name);
+ }
+
+ public static Suite suite() {
+ return new Suite(TypeHierarchyTests.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ typeHierarchy.initialize(1);
+ expectedExploredClasses.clear();
+ expectedCyclicClasses.clear();
+ useCacheSuperclass = true;
+ try {
+
+ this.createScriptProject("P", TEST_NATURE, new String[] {
+ "src"
+ } );
+ this.createFolder("P/src/x/y");
+ this.createFile("P/src/x/y/Z.txt", "");
+ this.cu = this.getSourceModule("P/src/x/y/Z.txt");
+ this.copy = cu.getWorkingCopy(null);
+ } catch (CoreException e) {
+ e.printStackTrace();
+ throw new RuntimeException(e.getMessage());
+ }
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ if (this.copy != null)
+ this.copy.discardWorkingCopy();
+ AbstractModelTests.deleteProject("P");
+ super.tearDown();
+ }
+
+ static class FakeTypeHierarchy extends TypeHierarchy {
+ @Override
+ public void addSubtype(IType type, IType subtype) {
+ super.addSubtype(type, subtype);
+ }
+
+ @Override
+ protected void cacheSuperclass(IType type, IType superclass) {
+ super.cacheSuperclass(type, superclass);
+ }
+
+ @Override
+ public void resetClassPaths() {
+ super.resetClassPaths();
+ }
+
+ public Set<IType> getExploredClasses() {
+ return this.exploredClasses;
+ }
+
+ public Set<IType> getCyclicClasses() {
+ return this.cyclicClasses;
+ }
+
+ @Override
+ protected void initialize(int size) {
+ super.initialize(size);
+ }
+ }
+
+ private Map<String, SourceType> createFakeTypes(char begin, char end, ModelElement modelElement) {
+ Map<String, SourceType> types = new HashMap<String, SourceType>();
+ for (char i = begin; i <= end; i++) {
+ String name = Character.toString(i);
+ types.put(name, new SourceType(modelElement, name));
+ }
+ return types;
+ }
+
+ private void populate(SourceType parent, boolean isParentOnCyclicPath, SourceType child,
+ boolean isChildOnCyclicPath) {
+ assertNotNull(parent);
+ assertNotNull(child);
+
+ if (useCacheSuperclass) {
+ typeHierarchy.cacheSuperclass(parent, child);
+ } else {
+ typeHierarchy.addSubtype(child, parent);
+ }
+ expectedExploredClasses.add(parent);
+ expectedExploredClasses.add(child);
+ if (isParentOnCyclicPath) {
+ expectedCyclicClasses.add(parent);
+ }
+ if (isChildOnCyclicPath) {
+ expectedCyclicClasses.add(child);
+ }
+
+ explorePartialGraphThroughSubclasses(parent, false);
+ explorePartialGraphThroughSubclasses(child, false);
+
+ explorePartialGraphThroughSuperclasses(parent, false);
+ explorePartialGraphThroughSuperclasses(child, false);
+ }
+
+ private void print(Set<IType> classPaths) {
+ Set<IType> sortedClassPaths = new TreeSet<IType>(new Comparator<IType>() {
+ @Override
+ public int compare(IType arg0, IType arg1) {
+ if (arg0 == arg1 || arg0.equals(arg1)) {
+ return 0;
+ }
+ return arg0.getElementName().compareTo(arg1.getElementName());
+ }
+ });
+ sortedClassPaths.addAll(classPaths);
+ System.out.println(sortedClassPaths);
+ }
+
+ private void printCurrentState() {
+ if (!DEBUG) {
+ return;
+ }
+ print(typeHierarchy.getExploredClasses());
+ print(expectedExploredClasses);
+ System.out.println();
+ print(typeHierarchy.getCyclicClasses());
+ print(expectedCyclicClasses);
+ System.out.println();
+ }
+
+ private void exploreAllGraphThroughSubclasses(SourceType type, boolean doReset) {
+ assertNotNull(type);
+ if (doReset) {
+ typeHierarchy.resetClassPaths();
+ }
+ typeHierarchy.getSubclasses(type);
+ printCurrentState();
+ assertEquals(typeHierarchy.getExploredClasses(), expectedExploredClasses);
+ assertEquals(typeHierarchy.getCyclicClasses(), expectedCyclicClasses);
+ }
+
+ private void exploreAllGraphThroughSuperclasses(SourceType type, boolean doReset) {
+ assertNotNull(type);
+ if (doReset) {
+ typeHierarchy.resetClassPaths();
+ }
+ typeHierarchy.getSuperclass(type);
+ printCurrentState();
+ assertEquals(typeHierarchy.getExploredClasses(), expectedExploredClasses);
+ assertEquals(typeHierarchy.getCyclicClasses(), expectedCyclicClasses);
+ }
+
+ private void explorePartialGraphThroughSubclasses(SourceType type, boolean doReset) {
+ assertNotNull(type);
+ if (doReset) {
+ typeHierarchy.resetClassPaths();
+ }
+ typeHierarchy.getSubclasses(type);
+ printCurrentState();
+ assertTrue(!expectedExploredClasses.contains(type) || !typeHierarchy.getExploredClasses().isEmpty());
+ assertTrue(typeHierarchy.getExploredClasses().containsAll(typeHierarchy.getCyclicClasses()));
+ assertTrue(expectedExploredClasses.containsAll(typeHierarchy.getExploredClasses()));
+ assertTrue(expectedExploredClasses.containsAll(typeHierarchy.getCyclicClasses()));
+ assertTrue(expectedCyclicClasses.containsAll(typeHierarchy.getCyclicClasses()));
+ }
+
+ private void explorePartialGraphThroughSuperclasses(SourceType type, boolean doReset) {
+ assertNotNull(type);
+ if (doReset) {
+ typeHierarchy.resetClassPaths();
+ }
+ typeHierarchy.getSuperclass(type);
+ printCurrentState();
+ assertTrue(!expectedExploredClasses.contains(type) || !typeHierarchy.getExploredClasses().isEmpty());
+ assertTrue(typeHierarchy.getExploredClasses().containsAll(typeHierarchy.getCyclicClasses()));
+ assertTrue(expectedExploredClasses.containsAll(typeHierarchy.getExploredClasses()));
+ assertTrue(expectedExploredClasses.containsAll(typeHierarchy.getCyclicClasses()));
+ assertTrue(expectedCyclicClasses.containsAll(typeHierarchy.getCyclicClasses()));
+ }
+
+ public void testCyclicHierarchy001() throws Exception {
+ assertNotNull(cu);
+ IModelElement p = cu.getParent();
+ assertTrue(p instanceof ModelElement);
+
+ typeHierarchy.resetClassPaths();
+ Map<String, SourceType> types = createFakeTypes('A', 'U', (ModelElement) p);
+
+ //* Class hierarchy representation, "(c)" means that the relevant class in on a cyclic path
+ //*
+ //* +------+
+ //* | L |
+ //* +------+
+ //* ^
+ //* | +-------------------------------------+
+ //* | v |
+ //* +------+ +------+ +------+ +------+ +------+ +------+ +---+ +------+ +---+ +---+ +---+ |
+ //* +> | B(c) | <-- | A(c) | --> | H(c) | --> | | --> | J(c) | --> | K(c) | --> | O | --> | P(c) | --> | R | --> | S | --> | U | |
+ //* | +------+ +------+ +------+ | | +------+ +------+ +---+ +------+ +---+ +---+ +---+ |
+ //* | | ^ | | | | | ^ |
+ //* | | +---------------------- | I(c) | <------------------+ | | | |
+ //* | v | | v v | |
+ //* | +------+ | | +------+ +---+ | |
+ //* | | C(c) | <------------------+ +- | | --------------+ | Q(c) | | T | ----------------+ |
+ //* | +------+ | | +------+ | +------+ +---+ |
+ //* | | | | | | |
+ //* | | | | +------------+ | | |
+ //* | v | | v | | | |
+ //* | +------+ +------+ +------+ | +------+ +------+ | | |
+ //* +- | D(c) | --> | E(c) | --> | F(c) | +> | M(c) | --> | N(c) | | +-------------------------------------+
+ //* +------+ +------+ +------+ +------+ +------+ |
+ //* | ^ |
+ //* | +---------------------------------+
+ //* v
+ //* +------+
+ //* | G |
+ //* +------+
+ //*
+ populate(types.get("A"), true, types.get("B"), true);
+ populate(types.get("B"), true, types.get("C"), true);
+ populate(types.get("C"), true, types.get("D"), true);
+ populate(types.get("D"), true, types.get("E"), true);
+ populate(types.get("E"), true, types.get("F"), true);
+ populate(types.get("E"), true, types.get("G"), false);
+ populate(types.get("F"), true, types.get("C"), true);
+ populate(types.get("D"), true, types.get("B"), true);
+ populate(types.get("A"), true, types.get("H"), true);
+ populate(types.get("H"), true, types.get("I"), true);
+ populate(types.get("I"), true, types.get("F"), true);
+ populate(types.get("I"), true, types.get("A"), true);
+ populate(types.get("I"), true, types.get("J"), true);
+ populate(types.get("J"), true, types.get("K"), true);
+ populate(types.get("K"), true, types.get("I"), true);
+ populate(types.get("K"), true, types.get("L"), false);
+ populate(types.get("K"), true, types.get("O"), false);
+ populate(types.get("O"), false, types.get("P"), true);
+ populate(types.get("P"), true, types.get("Q"), true);
+ populate(types.get("Q"), true, types.get("P"), true);
+ populate(types.get("P"), true, types.get("R"), false);
+ populate(types.get("I"), true, types.get("M"), true);
+ populate(types.get("M"), true, types.get("N"), true);
+ populate(types.get("N"), true, types.get("M"), true);
+ populate(types.get("R"), false, types.get("S"), false);
+ populate(types.get("R"), false, types.get("T"), false);
+ populate(types.get("S"), false, types.get("U"), false);
+ populate(types.get("T"), false, types.get("U"), false);
+ assertTrue(expectedExploredClasses.containsAll(expectedCyclicClasses));
+
+ exploreAllGraphThroughSuperclasses(types.get("A"), false);
+ exploreAllGraphThroughSuperclasses(types.get("A"), true);
+ exploreAllGraphThroughSuperclasses(types.get("K"), true);
+ explorePartialGraphThroughSuperclasses(types.get("O"), true);
+ exploreAllGraphThroughSuperclasses(types.get("K"), false);
+ explorePartialGraphThroughSuperclasses(types.get("D"), true);
+
+ for (String typeName : types.keySet()) {
+ explorePartialGraphThroughSuperclasses(types.get(typeName), true);
+ }
+
+ exploreAllGraphThroughSubclasses(types.get("B"), true);
+ exploreAllGraphThroughSubclasses(types.get("O"), true);
+
+ for (String typeName : types.keySet()) {
+ explorePartialGraphThroughSubclasses(types.get(typeName), true);
+ }
+ }
+
+ public void testCyclicHierarchy002() throws Exception {
+ useCacheSuperclass = false;
+ testCyclicHierarchy001();
+ }
+
+ public void testCyclicHierarchy003() throws Exception {
+ assertNotNull(cu);
+ IModelElement p = cu.getParent();
+ assertTrue(p instanceof ModelElement);
+
+ typeHierarchy.resetClassPaths();
+ Map<String, SourceType> types = createFakeTypes('A', 'Z', (ModelElement) p);
+
+ //* Class hierarchy representation, "(c)" means that the relevant class in on a cyclic path
+ //*
+ //* +------+ +---+ +---+
+ //* +> | W(c) | --> | V | --> | H |
+ //* | +------+ +---+ +---+
+ //* | |
+ //* | |
+ //* | v
+ //* | +------+
+ //* +- | X(c) |
+ //* +------+
+ //*
+ populate(types.get("W"), true, types.get("X"), true);
+ populate(types.get("W"), true, types.get("V"), false);
+ populate(types.get("V"), false, types.get("H"), false);
+ populate(types.get("X"), true, types.get("W"), true);
+ assertTrue(expectedExploredClasses.containsAll(expectedCyclicClasses));
+
+ explorePartialGraphThroughSuperclasses(types.get("H"), true);
+
+ for (String typeName : types.keySet()) {
+ explorePartialGraphThroughSuperclasses(types.get(typeName), true);
+ }
+
+ for (String typeName : types.keySet()) {
+ explorePartialGraphThroughSubclasses(types.get(typeName), true);
+ }
+ }
+
+ public void testCyclicHierarchy004() throws Exception {
+ useCacheSuperclass = false;
+ testCyclicHierarchy003();
+ }
+
+ public void testNonCyclicHierarchy005() throws Exception {
+ assertNotNull(cu);
+ IModelElement p = cu.getParent();
+ assertTrue(p instanceof ModelElement);
+
+ typeHierarchy.resetClassPaths();
+ Map<String, SourceType> types = createFakeTypes('A', 'F', (ModelElement) p);
+
+ //* Class hierarchy representation, "(c)" means that the relevant class in on a cyclic path
+ //*
+ //* +---+
+ //* | F |
+ //* +---+
+ //* ^
+ //* |
+ //* |
+ //* +---+ +---+ +---+ +---+
+ //* | A | --> | B | --> | D | --> | E |
+ //* +---+ +---+ +---+ +---+
+ //* | ^
+ //* | |
+ //* v |
+ //* +---+ |
+ //* | C | ----------------+
+ //* +---+
+ //*
+ populate(types.get("A"), false, types.get("B"), false);
+ populate(types.get("A"), false, types.get("C"), false);
+ populate(types.get("B"), false, types.get("D"), false);
+ populate(types.get("C"), false, types.get("D"), false);
+ populate(types.get("D"), false, types.get("E"), false);
+ populate(types.get("D"), false, types.get("F"), false);
+ assertTrue(expectedExploredClasses.containsAll(expectedCyclicClasses));
+
+ exploreAllGraphThroughSuperclasses(types.get("A"), false);
+
+ for (String typeName : types.keySet()) {
+ explorePartialGraphThroughSuperclasses(types.get(typeName), true);
+ }
+
+ for (String typeName : types.keySet()) {
+ explorePartialGraphThroughSubclasses(types.get(typeName), true);
+ }
+ }
+
+ public void testCyclicHierarchy006() throws Exception {
+ useCacheSuperclass = false;
+ testNonCyclicHierarchy005();
+ }
+
+ public void testEmptyHierarchy007() throws Exception {
+ assertNotNull(cu);
+ IModelElement p = cu.getParent();
+ assertTrue(p instanceof ModelElement);
+
+ typeHierarchy.resetClassPaths();
+
+ SourceType type = new SourceType((ModelElement) p, "A");
+ if (useCacheSuperclass) {
+ typeHierarchy.cacheSuperclass(type, null);
+ } else {
+ typeHierarchy.addSubtype(null, type);
+ }
+
+ assertTrue(typeHierarchy.getSuperclass(type).length == 0);
+ assertTrue(typeHierarchy.getSubclasses(type).length == 0);
+
+ assertTrue(typeHierarchy.getExploredClasses().isEmpty());
+ assertTrue(typeHierarchy.getCyclicClasses().isEmpty());
+ }
+
+ public void testEmptyHierarchy008() throws Exception {
+ useCacheSuperclass = false;
+ testEmptyHierarchy007();
+ }
+
+ public void testFullyCyclicHierarchy009() throws Exception {
+ assertNotNull(cu);
+ IModelElement p = cu.getParent();
+ assertTrue(p instanceof ModelElement);
+
+ typeHierarchy.resetClassPaths();
+ Map<String, SourceType> types = createFakeTypes('A', 'H', (ModelElement) p);
+
+ //* Class hierarchy representation, "(c)" means that the relevant class in on a cyclic path
+ //*
+ //* +---------------------------------------------------+
+ //* v |
+ //* +------+ +------+ +------+ +------+ +------+ +------+
+ //* | A(c) | --> | B(c) | --> | C(c) | --> | G(c) | --> | H(c) | --> | F(c) |
+ //* +------+ +------+ +------+ +------+ +------+ +------+
+ //* ^ |
+ //* | |
+ //* | v
+ //* | +------+ +------+
+ //* | | D(c) | --> | E(c) |
+ //* | +------+ +------+
+ //* | |
+ //* +--------------------------------------+
+ //*
+ populate(types.get("A"), true, types.get("B"), true);
+ populate(types.get("B"), true, types.get("C"), true);
+ populate(types.get("C"), true, types.get("D"), true);
+ populate(types.get("D"), true, types.get("E"), true);
+ populate(types.get("E"), true, types.get("A"), true);
+ populate(types.get("F"), true, types.get("B"), true);
+ populate(types.get("C"), true, types.get("G"), true);
+ populate(types.get("G"), true, types.get("H"), true);
+ populate(types.get("H"), true, types.get("F"), true);
+ assertTrue(expectedExploredClasses.containsAll(expectedCyclicClasses));
+ assertEquals(expectedExploredClasses, expectedCyclicClasses);
+
+ for (String typeName : types.keySet()) {
+ exploreAllGraphThroughSuperclasses(types.get(typeName), true);
+ }
+
+ for (String typeName : types.keySet()) {
+ exploreAllGraphThroughSubclasses(types.get(typeName), true);
+ }
+ }
+
+ public void testFullyCyclicHierarchy010() throws Exception {
+ useCacheSuperclass = false;
+ testFullyCyclicHierarchy009();
+ }
+}