/******************************************************************************* * Copyright (c) 2016, 2017 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.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<>(); private final Set<IType> expectedCyclicClasses = new HashSet<>(); 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<>(); 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<>((arg0, 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(); } }