/******************************************************************************* * Copyright (c) 2000, 2013 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 * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.jdt.core.tests.model; import java.io.File; import java.io.IOException; import junit.framework.Test; import org.eclipse.core.resources.*; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.Path; import org.eclipse.jdt.core.*; public class TypeHierarchyNotificationTests extends ModifyingResourceTests implements ITypeHierarchyChangedListener { /** * Whether we received notification of change */ protected boolean changeReceived = false; /** * The hierarchy we received change for */ protected ITypeHierarchy hierarchy = null; /** * The number of notifications */ protected int notifications = 0; public TypeHierarchyNotificationTests(String name) { super(name); } /** * Make sure that one change has been received for the given hierarchy. */ private void assertOneChange(ITypeHierarchy h) { assertTrue("Should receive change", this.changeReceived); assertTrue("Change should be for this hierarchy", this.hierarchy == h); assertEquals("Unexpected number of notifications", 1, this.notifications); } private void addSuper(ICompilationUnit unit, String typeName, String newSuper) throws JavaModelException { ICompilationUnit copy = unit.getWorkingCopy(null); IType type = copy.getTypes()[0]; String source = type.getSource(); int superIndex = -1; String newSource = source.substring(0, (superIndex = source.indexOf(typeName) + typeName.length())) + " extends " + newSuper + source.substring(superIndex); type.delete(true, null); copy.createType(newSource, null, true, null); copy.commitWorkingCopy(true, null); } protected void changeSuper(ICompilationUnit unit, String existingSuper, String newSuper) throws JavaModelException { ICompilationUnit copy = unit.getWorkingCopy(null); IType type = copy.getTypes()[0]; String source = type.getSource(); int superIndex = -1; String newSource = source.substring(0, (superIndex = source.indexOf(" " + existingSuper))) + " " + newSuper + source.substring(superIndex + existingSuper.length() + 1 /*space*/); type.delete(true, null); copy.createType(newSource, null, true, null); copy.commitWorkingCopy(true, null); } protected void changeVisibility(ICompilationUnit unit, String existingModifier, String newModifier) throws JavaModelException { ICompilationUnit copy = unit.getWorkingCopy(null); IType type = copy.getTypes()[0]; String source = type.getSource(); int modifierIndex = -1; String newSource = source.substring(0, (modifierIndex = source.indexOf(existingModifier))) + " " + newModifier + source.substring(modifierIndex + existingModifier.length()); type.delete(true, null); copy.createType(newSource, null, true, null); copy.commitWorkingCopy(true, null); } /** * Reset the flags that watch notification. */ private void reset() { this.changeReceived = false; this.hierarchy = null; this.notifications = 0; } protected void setUp() throws Exception { super.setUp(); reset(); this.setUpJavaProject("TypeHierarchyNotification", "1.5"); } static { // TESTS_NAMES= new String[] { "testAddExtendsSourceType3" }; } public static Test suite() { return buildModelTestSuite(TypeHierarchyNotificationTests.class); } protected void tearDown() throws Exception { this.deleteProject("TypeHierarchyNotification"); super.tearDown(); } /** * When adding an anonymous type in a hierarchy on a region, we should be notified of change. * (regression test for bug 51867 An anonymous type is missing in type hierarchy when editor is modified) */ public void testAddAnonymousInRegion() throws CoreException { ITypeHierarchy h = null; ICompilationUnit copy = null; try { copy = getCompilationUnit("TypeHierarchyNotification", "src", "p3", "A.java"); copy.becomeWorkingCopy(null); IRegion region = JavaCore.newRegion(); region.add(copy.getParent()); h = copy.getJavaProject().newTypeHierarchy(region, null); h.addTypeHierarchyChangedListener(this); // add a field initialized with a 'new B() {...}' anonymous type String newSource = "package p3;\n" + "public class A{\n" + " B field = new B() {};\n" + "}"; copy.getBuffer().setContents(newSource); copy.reconcile(ICompilationUnit.NO_AST, false, null, null); copy.commitWorkingCopy(true, null); assertOneChange(h); } finally { if (h != null) { h.removeTypeHierarchyChangedListener(this); } if (copy != null) { copy.discardWorkingCopy(); } } } /** * When a CU is added the type hierarchy should change * only if one of the types of the CU is part of the * type hierarchy. */ public void testAddCompilationUnit1() throws CoreException { IJavaProject javaProject = getJavaProject("TypeHierarchyNotification"); ICompilationUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.java"); IType type= cu.getType("X"); ITypeHierarchy h = type.newTypeHierarchy(javaProject, null); h.addTypeHierarchyChangedListener(this); // a cu with no types part of the hierarchy IPackageFragment pkg = getPackageFragment("TypeHierarchyNotification", "src", "p"); ICompilationUnit newCU1 = pkg.createCompilationUnit( "Z1.java", "package p;\n" + "\n" + "public class Z1 {\n" + "\n" + " public static main(String[] args) {\n" + " System.out.println(\"HelloWorld\");\n" + " }\n" + "}\n", false, null); try { assertCreation(newCU1); assertTrue("Should not receive change", !this.changeReceived); } finally { // cleanup h.removeTypeHierarchyChangedListener(this); } } /** * When a CU is added the type hierarchy should change * only if one of the types of the CU is part of the * type hierarchy. */ public void testAddCompilationUnit2() throws CoreException { IJavaProject javaProject = getJavaProject("TypeHierarchyNotification"); ICompilationUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.java"); IType type= cu.getType("X"); ITypeHierarchy h = type.newTypeHierarchy(javaProject, null); h.addTypeHierarchyChangedListener(this); // a cu with a top level type which is part of the hierarchy IPackageFragment pkg = getPackageFragment("TypeHierarchyNotification", "src", "p"); ICompilationUnit newCU2 = pkg.createCompilationUnit( "Z2.java", "package p;\n" + "\n" + "public class Z2 extends e.E {\n" + "}\n", false, null); try { assertCreation(newCU2); assertOneChange(h); h.refresh(null); IType eE = getCompilationUnit("TypeHierarchyNotification", "src", "e", "E.java").getType("E"); IType[] subtypes = h.getSubtypes(eE); assertTrue("Should be one subtype of e.E", subtypes.length == 1); assertEquals("Subtype of e.E should be p.Z2", newCU2.getType("Z2"), subtypes[0]); } finally { // cleanup h.removeTypeHierarchyChangedListener(this); } } /** * When a CU is added the type hierarchy should change * only if one of the types of the CU is part of the * type hierarchy. */ public void testAddCompilationUnit3() throws CoreException { IJavaProject javaProject = getJavaProject("TypeHierarchyNotification"); ICompilationUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.java"); IType type= cu.getType("X"); ITypeHierarchy h = type.newTypeHierarchy(javaProject, null); h.addTypeHierarchyChangedListener(this); // a cu with an inner type which is part of the hierarchy IPackageFragment pkg = getPackageFragment("TypeHierarchyNotification", "src", "p"); ICompilationUnit newCU3 = pkg.createCompilationUnit( "Z3.java", "package p;\n" + "\n" + "public class Z3 {\n" + " public class InnerZ extends d.D {\n" + " }\n" + "}\n", false, null); try { assertCreation(newCU3); assertOneChange(h); h.refresh(null); IType dD = getCompilationUnit("TypeHierarchyNotification", "src", "d", "D.java").getType("D"); IType[] subtypes = h.getSubtypes(dD); assertTrue("Should be one subtype of d.D", subtypes.length == 1); assertEquals("Subtype of d.D should be p.Z3.InnerZ", newCU3.getType("Z3").getType("InnerZ"), subtypes[0]); } finally { // cleanup h.removeTypeHierarchyChangedListener(this); } } /** * When a CU is added the type hierarchy should change * only if one of the types of the CU is part of the * type hierarchy. Tests parameterized supertype, see https://bugs.eclipse.org/401726 */ public void testAddCompilationUnit4() throws CoreException { IJavaProject javaProject = getJavaProject("TypeHierarchyNotification"); IType collection= javaProject.findType("java.util.Collection"); ITypeHierarchy h = collection.newTypeHierarchy(javaProject, null); h.addTypeHierarchyChangedListener(this); // a cu with a top level type which is part of the hierarchy IPackageFragment pkg = getPackageFragment("TypeHierarchyNotification", "src", "p"); ICompilationUnit newCU2 = pkg.createCompilationUnit( "Z2.java", "package p;\n" + "\n" + "public abstract class Z2 extends java.util.Collection<java.lang.String> {\n" + "}\n", false, null); try { assertCreation(newCU2); assertOneChange(h); h.refresh(null); IType[] subtypes = h.getSubtypes(collection); assertTrue("Should be one subtype of Collection", subtypes.length == 1); assertEquals("Subtype of Collection should be p.Z2", newCU2.getType("Z2"), subtypes[0]); } finally { // cleanup h.removeTypeHierarchyChangedListener(this); } } /** * When a CU is added, if the type hierarchy doesn't have a focus, it should change * only if one of the types of the CU is part of the region. */ public void testAddCompilationUnitInRegion() throws CoreException, IOException { IJavaProject javaProject = getJavaProject("TypeHierarchyNotification"); IRegion region = JavaCore.newRegion(); region.add(javaProject); ITypeHierarchy h = javaProject.newTypeHierarchy(region, null); h.addTypeHierarchyChangedListener(this); try { setUpJavaProject("TypeHierarchyDependent"); // a cu with no types part of the region IPackageFragment pkg = getPackageFragment("TypeHierarchyDependent", "", ""); ICompilationUnit newCU1 = pkg.createCompilationUnit( "Z1.java", "\n" + "public class Z1 {\n" + "\n" + " public static main(String[] args) {\n" + " System.out.println(\"HelloWorld\");\n" + " }\n" + "}\n", false, null); try { assertCreation(newCU1); assertTrue("Should not receive change", !this.changeReceived); } finally { // cleanup deleteResource(newCU1.getUnderlyingResource()); reset(); } // a cu with a type which is part of the region and is a subtype of an existing type of the region pkg = getPackageFragment("TypeHierarchyNotification", "src", "p"); ICompilationUnit newCU2 = pkg.createCompilationUnit( "Z2.java", "package p;\n" + "\n" + "public class Z2 extends e.E {\n" + "}\n", false, null); try { assertCreation(newCU2); assertOneChange(h); h.refresh(null); IType eE = getCompilationUnit("TypeHierarchyNotification", "src", "e", "E.java").getType("E"); IType[] subtypes = h.getSubtypes(eE); assertTrue("Should be one subtype of e.E", subtypes.length == 1); assertEquals("Subtype of e.E should be p.Z2", newCU2.getType("Z2"), subtypes[0]); } finally { // cleanup deleteResource(newCU2.getUnderlyingResource()); h.refresh(null); reset(); } // a cu with a type which is part of the region and is not a sub type of an existing type of the region ICompilationUnit newCU3 = pkg.createCompilationUnit( "Z3.java", "package p;\n" + "\n" + "public class Z3 extends Throwable {\n" + "}\n", false, null); try { assertCreation(newCU3); assertOneChange(h); h.refresh(null); IType throwableClass = getClassFile("TypeHierarchyNotification", getExternalJCLPathString("1.5"), "java.lang", "Throwable.class").getType(); assertEquals("Superclass of Z3 should be java.lang.Throwable", throwableClass, h.getSuperclass(newCU3.getType("Z3"))); } finally { // cleanup deleteResource(newCU3.getUnderlyingResource()); h.refresh(null); reset(); } } finally { h.removeTypeHierarchyChangedListener(this); this.deleteProject("TypeHierarchyDependent"); } } /** * When a CU is added if the CU does not intersects package fragments in the type hierarchy, * the typehierarchy has not changed. */ public void testAddExternalCompilationUnit() throws CoreException { IJavaProject javaProject = getJavaProject("TypeHierarchyNotification"); ICompilationUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.java"); IType type= cu.getType("X"); ITypeHierarchy h = type.newTypeHierarchy(javaProject, null); h.addTypeHierarchyChangedListener(this); IPackageFragment pkg = getPackageFragment("TypeHierarchyNotification", "src", "p.other"); ICompilationUnit newCU= pkg.createCompilationUnit( "Z.java", "package p.other;\n" + "\n" + "public class Z {\n" + "\n" + " public static main(String[] args) {\n" + " System.out.println(\"HelloWorld\");\n" + " }\n" + "}\n", false, null); try { assertCreation(newCU); assertTrue("Should not receive changes", !this.changeReceived); } finally { // cleanup deleteResource(newCU.getUnderlyingResource()); h.removeTypeHierarchyChangedListener(this); } } /** * When a package is added in an external project, the type hierarchy should not change */ public void testAddExternalPackage() throws CoreException { IJavaProject javaProject = getJavaProject("TypeHierarchyNotification"); ICompilationUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.java"); IType type= cu.getType("X"); ITypeHierarchy h = type.newTypeHierarchy(javaProject, null); try { this.createJavaProject("Other", new String[] {"src"}, "bin"); h.addTypeHierarchyChangedListener(this); IPackageFragmentRoot root= getPackageFragmentRoot("Other", "src"); IPackageFragment frag= root.createPackageFragment("a.day.in.spain", false, null); try { assertCreation(frag); assertTrue("Should not receive changes", !this.changeReceived); } finally { // cleanup frag.delete(true, null); reset(); } } finally { this.deleteProject("Other"); h.removeTypeHierarchyChangedListener(this); } } /** * When a project is added that is not on the class path of the type hierarchy project, * the type hierarchy should not change. */ public void testAddExternalProject() throws CoreException { IJavaProject project= getJavaProject("TypeHierarchyNotification"); ICompilationUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.java"); IType type = cu.getType("X"); ITypeHierarchy h = type.newTypeHierarchy(project, null); h.addTypeHierarchyChangedListener(this); project.getJavaModel().getWorkspace().getRoot().getProject("NewProject").create(null); try { assertTrue("Should not receive change", !this.changeReceived); } finally { // cleanup this.deleteProject("NewProject"); h.removeTypeHierarchyChangedListener(this); } } /** * Test adding the same listener twice. */ public void testAddListenerTwice() throws CoreException { IJavaProject project = getJavaProject("TypeHierarchyNotification"); ICompilationUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.java"); ICompilationUnit superCU = getCompilationUnit("TypeHierarchyNotification", "src", "b", "B.java"); IType type = cu.getType("X"); ITypeHierarchy h = type.newTypeHierarchy(project, null); // add listener twice h.addTypeHierarchyChangedListener(this); h.addTypeHierarchyChangedListener(this); IFile file = (IFile) superCU.getUnderlyingResource(); try { deleteResource(file); assertOneChange(h); } finally { h.removeTypeHierarchyChangedListener(this); } } /** * When a package is added, the type hierarchy should change */ public void testAddPackage() throws CoreException { IJavaProject javaProject = getJavaProject("TypeHierarchyNotification"); ICompilationUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.java"); IType type= cu.getType("X"); ITypeHierarchy h = type.newTypeHierarchy(javaProject, null); h.addTypeHierarchyChangedListener(this); IPackageFragmentRoot root= getPackageFragmentRoot("TypeHierarchyNotification", "src"); IPackageFragment frag= root.createPackageFragment("one.two.three", false, null); try { assertCreation(frag); assertOneChange(h); } finally { // cleanup h.removeTypeHierarchyChangedListener(this); } } /** * When a package fragment root is added, the type hierarchy should change */ public void testAddPackageFragmentRoot() throws CoreException { IJavaProject project = getJavaProject("TypeHierarchyNotification"); ICompilationUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.java"); IType type= cu.getType("X"); ITypeHierarchy h = type.newTypeHierarchy(project, null); h.addTypeHierarchyChangedListener(this); // prepare a classpath entry for the new root IClasspathEntry[] originalCP= project.getRawClasspath(); IClasspathEntry newEntry= JavaCore.newSourceEntry(project.getProject().getFullPath().append("extra")); IClasspathEntry[] newCP= new IClasspathEntry[originalCP.length + 1]; System.arraycopy(originalCP, 0 , newCP, 0, originalCP.length); newCP[originalCP.length]= newEntry; try { // set new classpath project.setRawClasspath(newCP, null); // now create the actual resource for the root and populate it reset(); project.getProject().getFolder("extra").create(false, true, null); IPackageFragmentRoot newRoot= getPackageFragmentRoot("TypeHierarchyNotification", "extra"); assertTrue("New root should now be visible", newRoot != null); assertOneChange(h); } finally { h.removeTypeHierarchyChangedListener(this); } } /** * When a project is added that is on the class path of the type hierarchy project, * the type hierarchy should change. */ public void testAddProject() throws CoreException { IJavaProject project= getJavaProject("TypeHierarchyNotification"); ICompilationUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.java"); IType type = cu.getType("X"); ITypeHierarchy h = type.newTypeHierarchy(project, null); h.addTypeHierarchyChangedListener(this); // prepare a new classpath entry for the new project IClasspathEntry[] originalCP= project.getRawClasspath(); IClasspathEntry newEntry= JavaCore.newProjectEntry(new Path("/NewProject"), false); IClasspathEntry[] newCP= new IClasspathEntry[originalCP.length + 1]; System.arraycopy(originalCP, 0 , newCP, 0, originalCP.length); newCP[originalCP.length]= newEntry; try { // set the new classpath project.setRawClasspath(newCP, null); // now create the actual resource for the root and populate it reset(); final IProject newProject = project.getJavaModel().getWorkspace().getRoot().getProject("NewProject"); IWorkspaceRunnable create = new IWorkspaceRunnable() { public void run(IProgressMonitor monitor) throws CoreException { newProject.create(null, null); newProject.open(null); } }; getWorkspace().run(create, null); IProjectDescription description = newProject.getDescription(); description.setNatureIds(new String[] {JavaCore.NATURE_ID}); newProject.setDescription(description, null); assertOneChange(h); } finally { this.deleteProject("NewProject"); h.removeTypeHierarchyChangedListener(this); } } /** * When a class file is added or removed if the class file intersects package fragments in the type hierarchy, * the type hierarchy has possibly changed (possibly introduce a supertype) */ public void testAddRemoveClassFile() throws CoreException { // Create type hierarchy on 'java.lang.LinkageError' in 'Minimal.zip' IJavaProject project = getJavaProject("TypeHierarchyNotification"); ICompilationUnit unit = getCompilationUnit("TypeHierarchyNotification", "src", "p", "MyError.java"); IType type = unit.getType("MyError"); ITypeHierarchy h = type.newTypeHierarchy(project, null); h.addTypeHierarchyChangedListener(this); // Create 'patch' folder and add it to classpath IFolder pathFolder = project.getProject().getFolder("patch"); pathFolder.create(true, true, null); IClasspathEntry newEntry = JavaCore.newLibraryEntry(pathFolder.getFullPath(), null, null, false); IClasspathEntry[] classpath = project.getRawClasspath(); IClasspathEntry[] newClassPath = new IClasspathEntry[classpath.length+1]; newClassPath[0] = newEntry; System.arraycopy(classpath, 0, newClassPath, 1, classpath.length); try { // Set new classpath setClasspath(project, newClassPath); // Create package 'java.lang' in 'patch' IPackageFragment pf = project.getPackageFragmentRoots()[0].createPackageFragment("java.lang", false, null); h.refresh(null); // Test addition of 'Error.class' in 'java.lang' (it should replace the 'Error.class' of the JCL in the hierarchy) reset(); IFile file = getProject("TypeHierarchyNotification").getFile("Error.class"); ((IFolder) pf.getUnderlyingResource()).getFile("Error.class").create(file.getContents(false), false, null); assertOneChange(h); h.refresh(null); assertEquals("Superclass of MyError should be Error in patch", pf.getClassFile("Error.class").getType(), h.getSuperclass(type)); // Test removal of 'Error.class' reset(); deleteResource(pf.getClassFile("Error.class").getUnderlyingResource()); assertOneChange(h); } finally { h.removeTypeHierarchyChangedListener(this); } } /* * Ensures that changing the modifiers of the focus type in a working copy reports a hierarchy change on save. * (regression test for bug */ public void testChangeFocusModifier() throws CoreException { ITypeHierarchy h = null; ICompilationUnit workingCopy = null; try { createJavaProject("P1"); createFolder("/P1/p"); createFile( "/P1/p/X.java", "package p1;\n" + "public class X {\n" + "}" ); workingCopy = getCompilationUnit("/P1/p/X.java"); workingCopy.becomeWorkingCopy(null/*no progress*/); h = workingCopy.getType("X").newTypeHierarchy(null); h.addTypeHierarchyChangedListener(this); workingCopy.getBuffer().setContents( "package p1;\n" + "class X {\n" + "}" ); workingCopy.reconcile(ICompilationUnit.NO_AST, false/*no pb detection*/, null/*no workingcopy owner*/, null/*no prgress*/); workingCopy.commitWorkingCopy(false/*don't force*/, null/*no progress*/); assertOneChange(h); } finally { if (h != null) h.removeTypeHierarchyChangedListener(this); if (workingCopy != null) workingCopy.discardWorkingCopy(); deleteProjects(new String[] {"P1", "P2"}); } } /** * Ensures that a TypeHierarchyNotification is made invalid when the project is closed. */ public void testCloseProject() throws Exception { IJavaProject javaProject = getJavaProject("TypeHierarchyNotification"); ICompilationUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.java"); IType type= cu.getType("X"); ITypeHierarchy h = type.newTypeHierarchy(javaProject, null); h.addTypeHierarchyChangedListener(this); try { assertTrue(h.exists()); javaProject.getProject().close(null); assertTrue("Should have been invalidated", !h.exists()); } finally { h.removeTypeHierarchyChangedListener(this); } } /* * Ensures that editing a working copy's buffer and committing (without a reconcile) triggers a type hierarchy notification * (regression test for bug 204805 ICompilationUnit.commitWorkingCopy doesn't send typeHierarchyChanged) */ public void testEditBuffer() throws CoreException { ITypeHierarchy h = null; ICompilationUnit workingCopy = null; try { createJavaProject("P"); createFolder("/P/p"); createFile( "/P/p/X.java", "package p;\n" + "public class X {\n" + "}" ); workingCopy = getCompilationUnit("/P/p/X.java"); workingCopy.becomeWorkingCopy(null/*no progress*/); h = workingCopy.getType("X").newTypeHierarchy(null); h.addTypeHierarchyChangedListener(this); workingCopy.getBuffer().setContents( "package p;\n" + "public class X extends Throwable {\n" + "}" ); workingCopy.commitWorkingCopy(false/*don't force*/, null/*no progress*/); assertOneChange(h); } finally { if (h != null) h.removeTypeHierarchyChangedListener(this); if (workingCopy != null) workingCopy.discardWorkingCopy(); deleteProject("P"); } } /** * When editing the extends clause of a source type in a hierarchy, we should be notified of change. */ public void testEditExtendsSourceType() throws CoreException { IJavaProject javaProject = getJavaProject("TypeHierarchyNotification"); ICompilationUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.java"); IType type= cu.getType("X"); ITypeHierarchy h = type.newTypeHierarchy(javaProject, null); h.addTypeHierarchyChangedListener(this); try { // change the superclass to a.A changeSuper(cu, "B", "a.A"); assertOneChange(h); h.refresh(null); // change the superclass back to B reset(); changeSuper(cu, "a.A", "B"); assertOneChange(h); } finally { h.removeTypeHierarchyChangedListener(this); } } public void testAddDependentProject() throws CoreException { ITypeHierarchy h = null; try { createJavaProject("P1"); createFolder("/P1/p"); createFile( "/P1/p/X.java", "package p1;\n" + "public class X {\n" + "}" ); h = getCompilationUnit("/P1/p/X.java").getType("X").newTypeHierarchy(null); h.addTypeHierarchyChangedListener(this); createJavaProject("P2", new String[] {""}, new String[0], new String[] {"/P1"}, ""); assertOneChange(h); } finally { if (h != null) h.removeTypeHierarchyChangedListener(this); deleteProjects(new String[] {"P1", "P2"}); } } /** * When adding an extends clause of a source type in a hierarchy, we should be notified of change. * (regression test for bug 4917 Latest build fails updating TypeHierarchyNotification) */ public void testAddExtendsSourceType1() throws CoreException { IJavaProject javaProject = getJavaProject("TypeHierarchyNotification"); ICompilationUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p2", "A.java"); IType type= cu.getType("A"); ITypeHierarchy h = type.newTypeHierarchy(javaProject, null); h.addTypeHierarchyChangedListener(this); try { // add p2.B as the superclass of p2.A addSuper(cu, "A", "p2.B"); assertOneChange(h); } finally { h.removeTypeHierarchyChangedListener(this); } } /** * When adding an extends clause of a source type in a hierarchy on a region, we should be notified of change. * (regression test for bug 45113 No hierarchy refresh when on region) */ public void testAddExtendsSourceType2() throws CoreException { ITypeHierarchy h = null; ICompilationUnit copy = null; try { copy = getCompilationUnit("TypeHierarchyNotification", "src", "p2", "A.java"); copy.becomeWorkingCopy(null); IRegion region = JavaCore.newRegion(); region.add(copy.getParent()); h = copy.getJavaProject().newTypeHierarchy(region, null); h.addTypeHierarchyChangedListener(this); // add p2.B as the superclass of p2.A String typeName = "A"; String newSuper = "p2.B"; String source = copy.getBuffer().getContents(); int superIndex = -1; String newSource = source.substring(0, (superIndex = source.indexOf(typeName) + typeName.length())) + " extends " + newSuper + source.substring(superIndex); copy.getBuffer().setContents(newSource); copy.reconcile(ICompilationUnit.NO_AST, false, null, null); copy.commitWorkingCopy(true, null); assertOneChange(h); } finally { if (h != null) { h.removeTypeHierarchyChangedListener(this); } if (copy != null) { copy.discardWorkingCopy(); } } } /** * While in a primary working copy, when adding an extends clause with a qualified name in a hierarchy, * we should be notified of change after a reconcile. * (regression test for bug 111396 TypeHierarchy doesn't notify listeners on addition of fully qualified subtypes) */ public void testAddExtendsSourceType3() throws CoreException { IJavaProject javaProject = getJavaProject("TypeHierarchyNotification"); ICompilationUnit copy = getCompilationUnit("TypeHierarchyNotification", "src", "p2", "B.java"); ITypeHierarchy h = null; try { copy.becomeWorkingCopy(null); h = getCompilationUnit("TypeHierarchyNotification", "src", "p2", "A.java").getType("A").newTypeHierarchy(javaProject, null); h.addTypeHierarchyChangedListener(this); // add p2.A as the superclass of p2.B String typeName = "B"; String newSuper = "p2.A"; String source = copy.getBuffer().getContents(); int superIndex = -1; String newSource = source.substring(0, (superIndex = source.indexOf(typeName) + typeName.length())) + " extends " + newSuper + source.substring(superIndex); copy.getBuffer().setContents(newSource); copy.reconcile(ICompilationUnit.NO_AST, false, null, null); copy.commitWorkingCopy(true, null); assertOneChange(h); } finally { if (h != null) h.removeTypeHierarchyChangedListener(this); copy.discardWorkingCopy(); } } /** * When editing a source type NOT in a hierarchy, we should receive NO CHANGES. */ public void testEditExternalSourceType() throws CoreException { IJavaProject javaProject = getJavaProject("TypeHierarchyNotification"); ICompilationUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.java"); IType type= cu.getType("X"); ITypeHierarchy h = type.newTypeHierarchy(javaProject, null); h.addTypeHierarchyChangedListener(this); ICompilationUnit cu2= getCompilationUnit("TypeHierarchyNotification", "src", "p", "External.java"); IField field= cu2.getType("External").getField("field"); try { field.delete(false, null); assertTrue("Should receive NO change", !this.changeReceived); } finally { h.removeTypeHierarchyChangedListener(this); } } /** * When editing the field of a source type in a hierarchy, * we should NOT be notified of a change. */ public void testEditFieldSourceType() throws CoreException { IJavaProject javaProject = getJavaProject("TypeHierarchyNotification"); ICompilationUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.java"); IType type= cu.getType("X"); ITypeHierarchy h = type.newTypeHierarchy(javaProject, null); h.addTypeHierarchyChangedListener(this); try { // remove a field an make sure we don't get any notification IField field= type.getField("field"); String source= field.getSource(); field.delete(false, null); assertTrue("Should not receive change", !this.changeReceived); // add the field back in and make sure we don't get any notification type.createField(source, null, false, null); assertTrue("Should receive change", !this.changeReceived); } finally { h.removeTypeHierarchyChangedListener(this); } } /** * When editing the imports of a source type in a hierarchy, * we should be notified of a change. */ public void testEditImportSourceType() throws CoreException { IJavaProject javaProject = getJavaProject("TypeHierarchyNotification"); ICompilationUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.java"); IType type = cu.getType("X"); ITypeHierarchy h = type.newTypeHierarchy(javaProject, null); h.addTypeHierarchyChangedListener(this); try { // remove an import declaration IImportDeclaration importDecl = cu.getImport("b.*"); importDecl.delete(false, null); assertOneChange(h); h.refresh(null); // remove all remaining import declarations reset(); importDecl = cu.getImport("i.*"); importDecl.delete(false, null); assertOneChange(h); h.refresh(null); // add an import back in reset(); cu.createImport("b.B", null, null); assertOneChange(h); h.refresh(null); // add a second import back in reset(); cu.createImport("i.*", null, null); assertOneChange(h); } finally { h.removeTypeHierarchyChangedListener(this); } } /** * When editing > 1 source type in a hierarchy using a MultiOperation, * we should be notified of ONE change. */ public void testEditSourceTypes() throws CoreException { // TBD: Find a way to do 2 changes in 2 different CUs at once IJavaProject project= getJavaProject("TypeHierarchyNotification"); final ICompilationUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.java"); final ICompilationUnit superCU = getCompilationUnit("TypeHierarchyNotification", "src", "b", "B.java"); IType type = cu.getType("X"); ITypeHierarchy h = type.newTypeHierarchy(project, null); h.addTypeHierarchyChangedListener(this); try { // change the visibility of the super class and the 'extends' of the type we're looking at // in a batch operation JavaCore.run( new IWorkspaceRunnable() { public void run(IProgressMonitor monitor) { try { changeVisibility(superCU, "public", "private"); changeSuper(cu, "X", "a.A"); } catch (JavaModelException e) { assertTrue("No exception", false); } } }, null ); assertOneChange(h); } finally { h.removeTypeHierarchyChangedListener(this); } } /** * When editing a super source type in a hierarchy, we should be notified of change only if * the change affects the visibility of the type. */ public void testEditSuperType() throws CoreException { IJavaProject project= getJavaProject("TypeHierarchyNotification"); ICompilationUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.java"); ICompilationUnit superCU = getCompilationUnit("TypeHierarchyNotification", "src", "b", "B.java"); IType type = cu.getType("X"); IType superType= superCU.getType("B"); ITypeHierarchy h = type.newTypeHierarchy(project, null); h.addTypeHierarchyChangedListener(this); try { // delete a field, there should be no change IField superField= superType.getField("value"); superField.delete(false, null); assertTrue("Should receive no change", !this.changeReceived); // change the visibility of the super class, there should be one change changeVisibility(superCU, "public", "private"); assertOneChange(h); } finally { h.removeTypeHierarchyChangedListener(this); } } /** * When an involved compilation unit is deleted, the type hierarchy should change */ public void testRemoveCompilationUnit() throws CoreException { IJavaProject project = getJavaProject("TypeHierarchyNotification"); ICompilationUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.java"); ICompilationUnit superCU = getCompilationUnit("TypeHierarchyNotification", "src", "b", "B.java"); IType type = cu.getType("X"); ITypeHierarchy h = type.newTypeHierarchy(project, null); h.addTypeHierarchyChangedListener(this); IFile file = (IFile) superCU.getUnderlyingResource(); try { deleteResource(file); assertOneChange(h); } finally { h.removeTypeHierarchyChangedListener(this); } } /** * When an uninvolved compilation unit is deleted, the type hierarchy should not change */ public void testRemoveExternalCompilationUnit() throws CoreException { IJavaProject project = getJavaProject("TypeHierarchyNotification"); ICompilationUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.java"); ICompilationUnit otherCU = getCompilationUnit("TypeHierarchyNotification", "src", "p", "External.java"); IType type = cu.getType("X"); ITypeHierarchy h = type.newTypeHierarchy(project, null); h.addTypeHierarchyChangedListener(this); IFile file = (IFile) otherCU.getUnderlyingResource(); try { deleteResource(file); assertTrue("Should not receive changes", !this.changeReceived); } finally { h.removeTypeHierarchyChangedListener(this); } } /** * When a uninvolved package is deleted, the type hierarchy should NOT change */ public void testRemoveExternalPackage() throws CoreException { IJavaProject project = getJavaProject("TypeHierarchyNotification"); ICompilationUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.java"); IType type = cu.getType("X"); ITypeHierarchy h = type.newTypeHierarchy(project, null); h.addTypeHierarchyChangedListener(this); IPackageFragment pkg = getPackageFragment("TypeHierarchyNotification", "src", "p.other"); IFolder folder = (IFolder) pkg.getUnderlyingResource(); try { deleteResource(folder); assertTrue("Should receive NO change", !this.changeReceived); } finally { h.removeTypeHierarchyChangedListener(this); } } /** * When a package fragment root is removed from the classpath, but does not impact the * package fragments, the type hierarchy should not change. */ public void testRemoveExternalPackageFragmentRoot() throws CoreException { IJavaProject project= getJavaProject("TypeHierarchyNotification"); ICompilationUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.java"); IType type = cu.getType("X"); ITypeHierarchy h = type.newTypeHierarchy(project, null); h.addTypeHierarchyChangedListener(this); // add a classpath entry for the new root IClasspathEntry[] originalCP= project.getRawClasspath(); IClasspathEntry newEntry= JavaCore.newSourceEntry(project.getProject().getFullPath().append("extra")); IClasspathEntry[] newCP= new IClasspathEntry[originalCP.length + 1]; System.arraycopy(originalCP, 0 , newCP, 0, originalCP.length); newCP[originalCP.length]= newEntry; try { // set classpath project.setRawClasspath(newCP, null); // now create the actual resource for the root and populate it reset(); project.getProject().getFolder("extra").create(false, true, null); IPackageFragmentRoot newRoot= getPackageFragmentRoot("TypeHierarchyNotification", "extra"); assertTrue("New root should now be visible", newRoot != null); assertOneChange(h); h.refresh(null); // remove a classpath entry that does not impact the type hierarchy reset(); project.setRawClasspath(originalCP, null); assertTrue("Should not receive change", !this.changeReceived); } finally { h.removeTypeHierarchyChangedListener(this); } } /** * When a project is deleted that contains package fragments that impact the * type hierarchy, the type hierarchy should change */ public void testRemoveExternalProject() throws CoreException { try { this.createJavaProject("External", new String[] {""}, new String[] {"JCL_LIB"}, new String[]{"/TypeHierarchyNotification"}, ""); this.createFolder("/External/p"); this.createFile("/External/p/Y.java", "package p; public class Y extends X {}"); ICompilationUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.java"); IType type = cu.getType("X"); ITypeHierarchy h = type.newTypeHierarchy(null); h.addTypeHierarchyChangedListener(this); try { this.deleteProject("External"); assertTrue("Should receive change", this.changeReceived); } finally { h.removeTypeHierarchyChangedListener(this); } } finally { this.deleteProject("External"); } } /** * Test removing a listener while the type hierarchy is notifying listeners. */ public void testRemoveListener() throws CoreException { IJavaProject project= getJavaProject("TypeHierarchyNotification"); final ICompilationUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.java"); final ICompilationUnit superCU = getCompilationUnit("TypeHierarchyNotification", "src", "b", "B.java"); IType type = cu.getType("X"); ITypeHierarchy h = type.newTypeHierarchy(project, null); ITypeHierarchyChangedListener listener= new ITypeHierarchyChangedListener() { public void typeHierarchyChanged(ITypeHierarchy th) { TypeHierarchyNotificationTests.this.changeReceived= true; TypeHierarchyNotificationTests.this.hierarchy= th; TypeHierarchyNotificationTests.this.notifications++; th.removeTypeHierarchyChangedListener(this); } }; h.addTypeHierarchyChangedListener(listener); try { // change the visibility of the super class and the 'extends' of the type we're looking at // in a batch operation getWorkspace().run( new IWorkspaceRunnable() { public void run(IProgressMonitor monitor) { try { changeVisibility(superCU, "public", "private"); changeSuper(cu, "B", "a.A"); } catch (JavaModelException e) { assertTrue("No exception", false); } } }, null ); assertOneChange(h); } finally { h.removeTypeHierarchyChangedListener(this); } } /** * When a package is deleted, the type hierarchy should change */ public void testRemovePackage() throws CoreException { IJavaProject project = getJavaProject("TypeHierarchyNotification"); ICompilationUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.java"); IType type = cu.getType("X"); ITypeHierarchy h = type.newTypeHierarchy(project, null); h.addTypeHierarchyChangedListener(this); IPackageFragment pkg = type.getPackageFragment(); try { deleteResource(pkg.getUnderlyingResource()); assertOneChange(h); } finally { h.removeTypeHierarchyChangedListener(this); } } /** * When a package fragment root is removed from the classpath, the type hierarchy should change */ public void testRemovePackageFragmentRoots() throws CoreException { IJavaProject project = getJavaProject("TypeHierarchyNotification"); ICompilationUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.java"); IType type = cu.getType("X"); ITypeHierarchy h = type.newTypeHierarchy(project, null); h.addTypeHierarchyChangedListener(this); try { project.setRawClasspath(null, null); assertOneChange(h); } finally { h.removeTypeHierarchyChangedListener(this); } } /** * When a project is deleted that contains package fragments that impact the * type hierarchy, the type hierarchy should change (and be made invalid) */ public void testRemoveProject() throws CoreException, IOException { ITypeHierarchy h = null; try { setUpJavaProject("TypeHierarchyDependent"); IJavaProject project= getJavaProject("TypeHierarchyDependent"); ICompilationUnit cu = getCompilationUnit("TypeHierarchyDependent", "", "", "Dependent.java"); IType type = cu.getType("Dependent"); h = type.newTypeHierarchy(project, null); h.addTypeHierarchyChangedListener(this); // Sanity check assertEquals("Superclass of Dependent is a.A", "a.A", h.getSuperclass(type).getFullyQualifiedName()); // Delete a related project IResource folder = getJavaProject("TypeHierarchyNotification").getUnderlyingResource(); deleteResource(folder); assertOneChange(h); assertTrue("Should still exist", h.exists()); h.refresh(null); IType superType = h.getSuperclass(type); assertTrue("Superclass of Dependent should be null", superType == null); // Delete the project type lives in. folder = getJavaProject("TypeHierarchyDependent").getUnderlyingResource(); deleteResource(folder); assertTrue("Should have been invalidated", ! h.exists()); } finally { this.deleteProject("TypeHierarchyDependent"); if (h != null) h.removeTypeHierarchyChangedListener(this); } } /** * When type used to create a TypeHierarchyNotification is deleted, * the hierarchy should be made invalid. */ public void testRemoveType() throws CoreException { IJavaProject project= getJavaProject("TypeHierarchyNotification"); ICompilationUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.java"); IType type = cu.getType("X"); ITypeHierarchy h = type.newTypeHierarchy(project, null); h.addTypeHierarchyChangedListener(this); try { type.delete(true, null); assertTrue("Should have been invalidated", !h.exists()); } finally { h.removeTypeHierarchyChangedListener(this); } } /** * When an involved compilation unit is renamed, the type hierarchy may change. */ public void testRenameCompilationUnit() throws CoreException { IJavaProject javaProject = getJavaProject("TypeHierarchyNotification"); ICompilationUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.java"); IType type= cu.getType("X"); ITypeHierarchy h = type.newTypeHierarchy(javaProject, null); h.addTypeHierarchyChangedListener(this); try { cu.rename("X2.java", false, null); assertOneChange(h); } finally { h.removeTypeHierarchyChangedListener(this); } } /** * When an uninvolved compilation unit is renamed, the type hierarchy does not change. */ public void testRenameExternalCompilationUnit() throws CoreException { IJavaProject javaProject = getJavaProject("TypeHierarchyNotification"); ICompilationUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.java"); IType type= cu.getType("X"); ITypeHierarchy h = type.newTypeHierarchy(javaProject, null); h.addTypeHierarchyChangedListener(this); ICompilationUnit cu2= getCompilationUnit("TypeHierarchyNotification", "src", "p", "External.java"); try { cu2.rename("External2.java", false, null); assertTrue("Should not receive changes", !this.changeReceived); } finally { h.removeTypeHierarchyChangedListener(this); } } /* * Ensures that getting a non-primary working copy does NOT trigger * a type hierarchy notification for the concerned hierarchy. * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=275805 */ public void testGetWorkingCopy() throws CoreException { ITypeHierarchy h = null; ICompilationUnit superCopy = null; ICompilationUnit aWorkingCopy = null; try { createJavaProject("P"); createFolder("/P/p"); createFile( "/P/p/IX.java", "package p;\n" + "public interface IX {\n" + "}" ); createFile( "/P/p/X.java", "package p;\n" + "public class X implements IX{\n" + "}" ); superCopy = getCompilationUnit("/P/p/IX.java"); superCopy.becomeWorkingCopy(null/*no progress*/); h = superCopy.getType("IX").newTypeHierarchy(null); h.addTypeHierarchyChangedListener(this); aWorkingCopy = getCompilationUnit("P/p/X.java").getWorkingCopy(null); assertTrue("Should receive NO change", !this.changeReceived); } finally { if (h != null) h.removeTypeHierarchyChangedListener(this); if (aWorkingCopy != null) aWorkingCopy.discardWorkingCopy(); if (superCopy!= null) superCopy.discardWorkingCopy(); deleteProject("P"); } } /* * Ensures that working copies with different owner than that of the * type hierarchy listener are ignored. * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=275805 */ public void testOwner() throws CoreException { ITypeHierarchy h = null; ICompilationUnit aWorkingCopy = null; ICompilationUnit anotherWorkingCopy = null; try { createJavaProject("P"); createFolder("/P/p"); createFile( "/P/p/X.java", "package p;\n" + "public class X {\n" + "}" ); aWorkingCopy = getCompilationUnit("/P/p/X.java").getWorkingCopy(null); h = aWorkingCopy.getType("X").newTypeHierarchy(null); h.addTypeHierarchyChangedListener(this); anotherWorkingCopy = getCompilationUnit("/P/p/X.java").getWorkingCopy(null); anotherWorkingCopy.getBuffer().setContents( "package p;\n" + "public class X extends Throwable {\n" + "}" ); anotherWorkingCopy.commitWorkingCopy(false/*don't force*/, null/*no progress*/); assertTrue("Should receive NO change", !this.changeReceived); } finally { if (h != null) h.removeTypeHierarchyChangedListener(this); if (aWorkingCopy != null) aWorkingCopy.discardWorkingCopy(); if (anotherWorkingCopy != null) anotherWorkingCopy.discardWorkingCopy(); deleteProject("P"); } } /** * @bug 316654: ITypeHierarchyChangedListener receive spurious callbacks * * Test that a non-Java resource added to a folder package fragment root doesn't * result in a type hierarchy changed event. * * @see "https://bugs.eclipse.org/bugs/show_bug.cgi?id=316654" * @throws CoreException */ public void testBug316654() throws CoreException { IJavaProject project = getJavaProject("TypeHierarchyNotification"); ICompilationUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.java"); IType type= cu.getType("X"); ITypeHierarchy h = type.newTypeHierarchy(project, null); h.addTypeHierarchyChangedListener(this); IPath filePath = project.getProject().getFullPath().append("src").append("simplefile.txt"); try { createFile(filePath.toOSString(), "A simple text file"); assertTrue("Should not receive change", !this.changeReceived); } finally { deleteFile(filePath.toOSString()); h.removeTypeHierarchyChangedListener(this); } } /** * Additional test - Test that a relevant Java source resource change results in a type hierarchy * change event. * * @see "https://bugs.eclipse.org/bugs/show_bug.cgi?id=316654" * @throws CoreException */ public void testBug316654_a() throws CoreException { IJavaProject project = getJavaProject("TypeHierarchyNotification"); ICompilationUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.java"); IType type= cu.getType("X"); ITypeHierarchy h = type.newTypeHierarchy(project, null); h.addTypeHierarchyChangedListener(this); IPath filePath = project.getProject().getFullPath().append("src").append("p").append("Y.java"); try { createFile(filePath.toOSString(), "package p;\n" + "class Y extends X{\n" + "}"); assertOneChange(h); } finally { deleteFile(filePath.toOSString()); h.removeTypeHierarchyChangedListener(this); } } /** * Additional test - Test that a relevant external archive change results in a type hierarchy * change event. * * @see "https://bugs.eclipse.org/bugs/show_bug.cgi?id=316654" * @throws CoreException */ public void testBug316654_b() throws CoreException { IJavaProject project = getJavaProject("TypeHierarchyNotification"); refreshExternalArchives(project); String externalJCLPathString = getExternalJCLPathString("1.5"); File jarFile = new File(externalJCLPathString); long oldTimestamp = jarFile.lastModified(); assertTrue("File does not exist", jarFile.exists()); IType throwableClass = getClassFile("TypeHierarchyNotification", externalJCLPathString, "java.lang", "Throwable.class").getType(); ITypeHierarchy h = throwableClass.newTypeHierarchy(project, null); h.addTypeHierarchyChangedListener(this); reset(); try { jarFile.setLastModified(System.currentTimeMillis()); refreshExternalArchives(project); assertOneChange(h); } finally { jarFile.setLastModified(oldTimestamp); h.removeTypeHierarchyChangedListener(this); } } /** * Additional test - Test that a relevant archive change results in a type hierarchy * change event. * * @see "https://bugs.eclipse.org/bugs/show_bug.cgi?id=316654" * @throws CoreException */ public void testBug316654_c() throws CoreException { IJavaProject project = getJavaProject("TypeHierarchyNotification"); IPath jarPath = project.getPath().append("test316654.jar"); IFile jarFile = getFile(jarPath.toOSString()); IType type = getClassFile("TypeHierarchyNotification", jarPath.toOSString(), "a", "A.class").getType(); ITypeHierarchy h = type.newTypeHierarchy(project, null); h.addTypeHierarchyChangedListener(this); reset(); try { jarFile.touch(null); refresh(project); assertOneChange(h); } finally { h.removeTypeHierarchyChangedListener(this); } } /** * Make a note of the change */ public void typeHierarchyChanged(ITypeHierarchy typeHierarchy) { this.changeReceived= true; this.hierarchy= typeHierarchy; this.notifications++; } }