blob: 68c7975c06f2e41f2cbd6b5a4017eb6b70813cee [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2008 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.wst.jsdt.core.tests.model;
import java.io.IOException;
import junit.framework.Test;
import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.wst.jsdt.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(IJavaScriptUnit unit, String typeName, String newSuper) throws JavaScriptModelException {
IJavaScriptUnit 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(IJavaScriptUnit unit, String existingSuper, String newSuper) throws JavaScriptModelException {
IJavaScriptUnit 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(IJavaScriptUnit unit, String existingModifier, String newModifier) throws JavaScriptModelException {
IJavaScriptUnit 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();
this.reset();
this.setUpJavaProject("TypeHierarchyNotification");
}
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;
IJavaScriptUnit copy = null;
try {
copy = getCompilationUnit("TypeHierarchyNotification", "src", "p3", "A.js");
copy.becomeWorkingCopy(null, null);
IRegion region = JavaScriptCore.newRegion();
region.add(copy.getParent());
h = copy.getJavaScriptProject().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(IJavaScriptUnit.NO_AST, false, null, null);
copy.commitWorkingCopy(true, null);
this.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 {
IJavaScriptProject javaProject = getJavaProject("TypeHierarchyNotification");
IJavaScriptUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.js");
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");
IJavaScriptUnit newCU1 = pkg.createCompilationUnit(
"Z1.js",
"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 {
IJavaScriptProject javaProject = getJavaProject("TypeHierarchyNotification");
IJavaScriptUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.js");
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");
IJavaScriptUnit newCU2 = pkg.createCompilationUnit(
"Z2.js",
"package p;\n" +
"\n" +
"public class Z2 extends e.E {\n" +
"}\n",
false,
null);
try {
assertCreation(newCU2);
this.assertOneChange(h);
h.refresh(null);
IType eE = getCompilationUnit("TypeHierarchyNotification", "src", "e", "E.js").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 {
IJavaScriptProject javaProject = getJavaProject("TypeHierarchyNotification");
IJavaScriptUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.js");
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");
IJavaScriptUnit newCU3 = pkg.createCompilationUnit(
"Z3.js",
"package p;\n" +
"\n" +
"public class Z3 {\n" +
" public class InnerZ extends d.D {\n" +
" }\n" +
"}\n",
false,
null);
try {
assertCreation(newCU3);
this.assertOneChange(h);
h.refresh(null);
IType dD = getCompilationUnit("TypeHierarchyNotification", "src", "d", "D.js").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, 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 {
IJavaScriptProject javaProject = getJavaProject("TypeHierarchyNotification");
IRegion region = JavaScriptCore.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", "", "");
IJavaScriptUnit newCU1 = pkg.createCompilationUnit(
"Z1.js",
"\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());
this.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");
IJavaScriptUnit newCU2 = pkg.createCompilationUnit(
"Z2.js",
"package p;\n" +
"\n" +
"public class Z2 extends e.E {\n" +
"}\n",
false,
null);
try {
assertCreation(newCU2);
this.assertOneChange(h);
h.refresh(null);
IType eE = getCompilationUnit("TypeHierarchyNotification", "src", "e", "E.js").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);
this.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
IJavaScriptUnit newCU3 = pkg.createCompilationUnit(
"Z3.js",
"package p;\n" +
"\n" +
"public class Z3 extends Throwable {\n" +
"}\n",
false,
null);
try {
assertCreation(newCU3);
this.assertOneChange(h);
h.refresh(null);
IType throwableClass = getClassFile("TypeHierarchyNotification", getExternalJCLPathString(), "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);
this.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 {
IJavaScriptProject javaProject = getJavaProject("TypeHierarchyNotification");
IJavaScriptUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.js");
IType type= cu.getType("X");
ITypeHierarchy h = type.newTypeHierarchy(javaProject, null);
h.addTypeHierarchyChangedListener(this);
IPackageFragment pkg = getPackageFragment("TypeHierarchyNotification", "src", "p.other");
IJavaScriptUnit newCU= pkg.createCompilationUnit(
"Z.js",
"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 {
IJavaScriptProject javaProject = getJavaProject("TypeHierarchyNotification");
IJavaScriptUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.js");
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);
this.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 {
IJavaScriptProject project= getJavaProject("TypeHierarchyNotification");
IJavaScriptUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.js");
IType type = cu.getType("X");
ITypeHierarchy h = type.newTypeHierarchy(project, null);
h.addTypeHierarchyChangedListener(this);
project.getJavaScriptModel().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 {
IJavaScriptProject project = getJavaProject("TypeHierarchyNotification");
IJavaScriptUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.js");
IJavaScriptUnit superCU = getCompilationUnit("TypeHierarchyNotification", "src", "b", "B.js");
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);
this.assertOneChange(h);
} finally {
h.removeTypeHierarchyChangedListener(this);
}
}
/**
* When a package is added, the type hierarchy should change
*/
public void testAddPackage() throws CoreException {
IJavaScriptProject javaProject = getJavaProject("TypeHierarchyNotification");
IJavaScriptUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.js");
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);
this.assertOneChange(h);
} finally {
// cleanup
h.removeTypeHierarchyChangedListener(this);
}
}
/**
* When a package fragment root is added, the type hierarchy should change
*/
public void testAddPackageFragmentRoot() throws CoreException {
IJavaScriptProject project = getJavaProject("TypeHierarchyNotification");
IJavaScriptUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.js");
IType type= cu.getType("X");
ITypeHierarchy h = type.newTypeHierarchy(project, null);
h.addTypeHierarchyChangedListener(this);
// prepare a classpath entry for the new root
IIncludePathEntry[] originalCP= project.getRawIncludepath();
IIncludePathEntry newEntry= JavaScriptCore.newSourceEntry(project.getProject().getFullPath().append("extra"));
IIncludePathEntry[] newCP= new IIncludePathEntry[originalCP.length + 1];
System.arraycopy(originalCP, 0 , newCP, 0, originalCP.length);
newCP[originalCP.length]= newEntry;
try {
// set new classpath
project.setRawIncludepath(newCP, null);
// now create the actual resource for the root and populate it
this.reset();
project.getProject().getFolder("extra").create(false, true, null);
IPackageFragmentRoot newRoot= getPackageFragmentRoot("TypeHierarchyNotification", "extra");
assertTrue("New root should now be visible", newRoot != null);
this.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 {
IJavaScriptProject project= getJavaProject("TypeHierarchyNotification");
IJavaScriptUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.js");
IType type = cu.getType("X");
ITypeHierarchy h = type.newTypeHierarchy(project, null);
h.addTypeHierarchyChangedListener(this);
// prepare a new classpath entry for the new project
IIncludePathEntry[] originalCP= project.getRawIncludepath();
IIncludePathEntry newEntry= JavaScriptCore.newProjectEntry(new Path("/NewProject"), false);
IIncludePathEntry[] newCP= new IIncludePathEntry[originalCP.length + 1];
System.arraycopy(originalCP, 0 , newCP, 0, originalCP.length);
newCP[originalCP.length]= newEntry;
try {
// set the new classpath
project.setRawIncludepath(newCP, null);
// now create the actual resource for the root and populate it
this.reset();
final IProject newProject = project.getJavaScriptModel().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[] {JavaScriptCore.NATURE_ID});
newProject.setDescription(description, null);
this.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'
IJavaScriptProject project = getJavaProject("TypeHierarchyNotification");
IJavaScriptUnit unit = getCompilationUnit("TypeHierarchyNotification", "src", "p", "MyError.js");
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);
IIncludePathEntry newEntry = JavaScriptCore.newLibraryEntry(pathFolder.getFullPath(), null, null, false);
IIncludePathEntry[] classpath = project.getRawIncludepath();
IIncludePathEntry[] newClassPath = new IIncludePathEntry[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)
this.reset();
IFile file = getProject("TypeHierarchyNotification").getFile("Error.class");
((IFolder) pf.getUnderlyingResource()).getFile("Error.class").create(file.getContents(false), false, null);
this.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'
this.reset();
deleteResource(pf.getClassFile("Error.class").getUnderlyingResource());
this.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;
IJavaScriptUnit workingCopy = null;
try {
createJavaProject("P1");
createFolder("/P1/p");
createFile(
"/P1/p/X.js",
"package p1;\n" +
"public class X {\n" +
"}"
);
workingCopy = getCompilationUnit("/P1/p/X.js");
workingCopy.becomeWorkingCopy(null/*no pb requestor*/, null/*no progress*/);
h = workingCopy.getType("X").newTypeHierarchy(null);
h.addTypeHierarchyChangedListener(this);
workingCopy.getBuffer().setContents(
"package p1;\n" +
"class X {\n" +
"}"
);
workingCopy.reconcile(IJavaScriptUnit.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 {
IJavaScriptProject javaProject = getJavaProject("TypeHierarchyNotification");
IJavaScriptUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.js");
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);
}
}
/**
* When editing the extends clause of a source type in a hierarchy, we should be notified of change.
*/
public void testEditExtendsSourceType() throws CoreException {
IJavaScriptProject javaProject = getJavaProject("TypeHierarchyNotification");
IJavaScriptUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.js");
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.js",
"package p1;\n" +
"public class X {\n" +
"}"
);
h = getCompilationUnit("/P1/p/X.js").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 {
IJavaScriptProject javaProject = getJavaProject("TypeHierarchyNotification");
IJavaScriptUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p2", "A.js");
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");
this.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;
IJavaScriptUnit copy = null;
try {
copy = getCompilationUnit("TypeHierarchyNotification", "src", "p2", "A.js");
copy.becomeWorkingCopy(null, null);
IRegion region = JavaScriptCore.newRegion();
region.add(copy.getParent());
h = copy.getJavaScriptProject().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(IJavaScriptUnit.NO_AST, false, null, null);
copy.commitWorkingCopy(true, null);
this.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 {
IJavaScriptProject javaProject = getJavaProject("TypeHierarchyNotification");
IJavaScriptUnit copy = getCompilationUnit("TypeHierarchyNotification", "src", "p2", "B.js");
ITypeHierarchy h = null;
try {
copy.becomeWorkingCopy(null, null);
h = getCompilationUnit("TypeHierarchyNotification", "src", "p2", "A.js").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(IJavaScriptUnit.NO_AST, false, null, null);
copy.commitWorkingCopy(true, null);
this.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 {
IJavaScriptProject javaProject = getJavaProject("TypeHierarchyNotification");
IJavaScriptUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.js");
IType type= cu.getType("X");
ITypeHierarchy h = type.newTypeHierarchy(javaProject, null);
h.addTypeHierarchyChangedListener(this);
IJavaScriptUnit cu2= getCompilationUnit("TypeHierarchyNotification", "src", "p", "External.js");
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 {
IJavaScriptProject javaProject = getJavaProject("TypeHierarchyNotification");
IJavaScriptUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.js");
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 {
IJavaScriptProject javaProject = getJavaProject("TypeHierarchyNotification");
IJavaScriptUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.js");
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);
this.assertOneChange(h);
h.refresh(null);
// remove all remaining import declarations
this.reset();
importDecl = cu.getImport("i.*");
importDecl.delete(false, null);
this.assertOneChange(h);
h.refresh(null);
// add an import back in
this.reset();
cu.createImport("b.B", null, null);
this.assertOneChange(h);
h.refresh(null);
// add a second import back in
this.reset();
cu.createImport("i.*", null, null);
this.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
IJavaScriptProject project= getJavaProject("TypeHierarchyNotification");
final IJavaScriptUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.js");
final IJavaScriptUnit superCU = getCompilationUnit("TypeHierarchyNotification", "src", "b", "B.js");
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
JavaScriptCore.run(
new IWorkspaceRunnable() {
public void run(IProgressMonitor monitor) {
try {
changeVisibility(superCU, "public", "private");
changeSuper(cu, "X", "a.A");
} catch (JavaScriptModelException 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 {
IJavaScriptProject project= getJavaProject("TypeHierarchyNotification");
IJavaScriptUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.js");
IJavaScriptUnit superCU = getCompilationUnit("TypeHierarchyNotification", "src", "b", "B.js");
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");
this.assertOneChange(h);
} finally {
h.removeTypeHierarchyChangedListener(this);
}
}
/**
* When an involved compilation unit is deleted, the type hierarchy should change
*/
public void testRemoveCompilationUnit() throws CoreException {
IJavaScriptProject project = getJavaProject("TypeHierarchyNotification");
IJavaScriptUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.js");
IJavaScriptUnit superCU = getCompilationUnit("TypeHierarchyNotification", "src", "b", "B.js");
IType type = cu.getType("X");
ITypeHierarchy h = type.newTypeHierarchy(project, null);
h.addTypeHierarchyChangedListener(this);
IFile file = (IFile) superCU.getUnderlyingResource();
try {
deleteResource(file);
this.assertOneChange(h);
} finally {
h.removeTypeHierarchyChangedListener(this);
}
}
/**
* When an uninvolved compilation unit is deleted, the type hierarchy should not change
*/
public void testRemoveExternalCompilationUnit() throws CoreException {
IJavaScriptProject project = getJavaProject("TypeHierarchyNotification");
IJavaScriptUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.js");
IJavaScriptUnit otherCU = getCompilationUnit("TypeHierarchyNotification", "src", "p", "External.js");
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 {
IJavaScriptProject project = getJavaProject("TypeHierarchyNotification");
IJavaScriptUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.js");
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 {
IJavaScriptProject project= getJavaProject("TypeHierarchyNotification");
IJavaScriptUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.js");
IType type = cu.getType("X");
ITypeHierarchy h = type.newTypeHierarchy(project, null);
h.addTypeHierarchyChangedListener(this);
// add a classpath entry for the new root
IIncludePathEntry[] originalCP= project.getRawIncludepath();
IIncludePathEntry newEntry= JavaScriptCore.newSourceEntry(project.getProject().getFullPath().append("extra"));
IIncludePathEntry[] newCP= new IIncludePathEntry[originalCP.length + 1];
System.arraycopy(originalCP, 0 , newCP, 0, originalCP.length);
newCP[originalCP.length]= newEntry;
try {
// set classpath
project.setRawIncludepath(newCP, null);
// now create the actual resource for the root and populate it
this.reset();
project.getProject().getFolder("extra").create(false, true, null);
IPackageFragmentRoot newRoot= getPackageFragmentRoot("TypeHierarchyNotification", "extra");
assertTrue("New root should now be visible", newRoot != null);
this.assertOneChange(h);
h.refresh(null);
// remove a classpath entry that does not impact the type hierarchy
this.reset();
project.setRawIncludepath(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.js", "package p; public class Y extends X {}");
IJavaScriptUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.js");
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 {
IJavaScriptProject project= getJavaProject("TypeHierarchyNotification");
final IJavaScriptUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.js");
final IJavaScriptUnit superCU = getCompilationUnit("TypeHierarchyNotification", "src", "b", "B.js");
IType type = cu.getType("X");
ITypeHierarchy h = type.newTypeHierarchy(project, null);
ITypeHierarchyChangedListener listener= new ITypeHierarchyChangedListener() {
public void typeHierarchyChanged(ITypeHierarchy th) {
changeReceived= true;
hierarchy= th;
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 (JavaScriptModelException e) {
assertTrue("No exception", false);
}
}
},
null
);
this.assertOneChange(h);
} finally {
h.removeTypeHierarchyChangedListener(this);
}
}
/**
* When a package is deleted, the type hierarchy should change
*/
public void testRemovePackage() throws CoreException {
IJavaScriptProject project = getJavaProject("TypeHierarchyNotification");
IJavaScriptUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.js");
IType type = cu.getType("X");
ITypeHierarchy h = type.newTypeHierarchy(project, null);
h.addTypeHierarchyChangedListener(this);
IPackageFragment pkg = type.getPackageFragment();
try {
deleteResource(pkg.getUnderlyingResource());
this.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 {
IJavaScriptProject project = getJavaProject("TypeHierarchyNotification");
IJavaScriptUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.js");
IType type = cu.getType("X");
ITypeHierarchy h = type.newTypeHierarchy(project, null);
h.addTypeHierarchyChangedListener(this);
try {
project.setRawIncludepath(null, null);
this.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");
IJavaScriptProject project= getJavaProject("TypeHierarchyDependent");
IJavaScriptUnit cu = getCompilationUnit("TypeHierarchyDependent", "", "", "Dependent.js");
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);
this.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 {
IJavaScriptProject project= getJavaProject("TypeHierarchyNotification");
IJavaScriptUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.js");
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 {
IJavaScriptProject javaProject = getJavaProject("TypeHierarchyNotification");
IJavaScriptUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.js");
IType type= cu.getType("X");
ITypeHierarchy h = type.newTypeHierarchy(javaProject, null);
h.addTypeHierarchyChangedListener(this);
try {
cu.rename("X2.js", false, null);
this.assertOneChange(h);
} finally {
h.removeTypeHierarchyChangedListener(this);
}
}
/**
* When an uninvolved compilation unit is renamed, the type hierarchy does not change.
*/
public void testRenameExternalCompilationUnit() throws CoreException {
IJavaScriptProject javaProject = getJavaProject("TypeHierarchyNotification");
IJavaScriptUnit cu = getCompilationUnit("TypeHierarchyNotification", "src", "p", "X.js");
IType type= cu.getType("X");
ITypeHierarchy h = type.newTypeHierarchy(javaProject, null);
h.addTypeHierarchyChangedListener(this);
IJavaScriptUnit cu2= getCompilationUnit("TypeHierarchyNotification", "src", "p", "External.js");
try {
cu2.rename("External2.js", false, null);
assertTrue("Should not receive changes", !this.changeReceived);
} finally {
h.removeTypeHierarchyChangedListener(this);
}
}
/**
* Make a note of the change
*/
public void typeHierarchyChanged(ITypeHierarchy typeHierarchy) {
this.changeReceived= true;
this.hierarchy= typeHierarchy;
this.notifications++;
}
}