/******************************************************************************* * Copyright (c) 2000, 2016 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation * Terry Parker <tparker@google.com> - Enable the Java model caches to recover from IO errors - https://bugs.eclipse.org/455042 *******************************************************************************/ package org.eclipse.jdt.core.tests.model; import java.io.IOException; import java.nio.file.FileSystems; import java.nio.file.Files; import org.eclipse.core.resources.IWorkspaceRunnable; 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.*; import org.eclipse.jdt.core.tests.util.Util; import org.eclipse.jdt.internal.core.ClasspathEntry; import org.eclipse.jdt.internal.core.DefaultWorkingCopyOwner; import org.eclipse.jdt.internal.core.JavaModelManager; import org.eclipse.jdt.internal.core.JavaProject; import org.eclipse.jdt.internal.core.NameLookup; import junit.framework.Test; /** * These test ensure that modifications in Java projects are correctly reported as * IJavaEllementDeltas. */ public class NameLookupTests2 extends ModifyingResourceTests { public NameLookupTests2(String name) { super(name); } // Use this static initializer to specify subset for tests // All specified tests which do not belong to the class are skipped... static { // org.eclipse.jdt.internal.core.search.matching.MatchLocator.PRINT_BUFFER = false; // TESTS_PREFIX = "testArray"; // TESTS_NAMES = new String[] { "testFindBinaryTypeWithDollarName" }; // TESTS_NUMBERS = new int[] { 8 }; // TESTS_RANGE = new int[] { 6, -1 }; } public static Test suite() { return buildModelTestSuite(NameLookupTests2.class); } NameLookup getNameLookup(JavaProject project) throws JavaModelException { return project.newNameLookup((WorkingCopyOwner)null); } public void testAddPackageFragmentRootAndPackageFrament() throws CoreException { try { IJavaProject p1 = createJavaProject("P1", new String[] {"src1"}, "bin"); IJavaProject p2 = createJavaProject("P2", new String[] {}, ""); IClasspathEntry[] classpath = new IClasspathEntry[] { JavaCore.newProjectEntry(new Path("/P1")) }; p2.setRawClasspath(classpath, null); IPackageFragment[] res = getNameLookup((JavaProject)p2).findPackageFragments("p1", false); assertTrue("Should get no package fragment", res == null); IClasspathEntry[] classpath2 = new IClasspathEntry[] { JavaCore.newSourceEntry(new Path("/P1/src1")), JavaCore.newSourceEntry(new Path("/P1/src2")) }; p1.setRawClasspath(classpath2, null); createFolder("/P1/src2/p1"); res = getNameLookup((JavaProject)p2).findPackageFragments("p1", false); assertTrue( "Should get 'p1' package fragment", res != null && res.length == 1 && res[0].getElementName().equals("p1")); } finally { deleteProject("P1"); deleteProject("P2"); } } public void testAddPackageFragment() throws CoreException { try { createJavaProject("P1", new String[] {"src1"}, "bin"); IJavaProject p2 = createJavaProject("P2", new String[] {}, ""); IClasspathEntry[] classpath = new IClasspathEntry[] { JavaCore.newProjectEntry(new Path("/P1")) }; p2.setRawClasspath(classpath, null); IPackageFragment[] res = getNameLookup((JavaProject)p2).findPackageFragments("p1", false); assertTrue("Should get no package fragment", res == null); createFolder("/P1/src1/p1"); res = getNameLookup((JavaProject)p2).findPackageFragments("p1", false); assertTrue( "Should get 'p1' package fragment", res != null && res.length == 1 && res[0].getElementName().equals("p1")); } finally { deleteProject("P1"); deleteProject("P2"); } } /* * Resolve, add pkg, resolve again: new pkg should be accessible * (regression test for bug 37962 Unexpected transient problem during reconcile */ public void testAddPackageFragment2() throws CoreException { try { JavaProject project = (JavaProject)createJavaProject("P", new String[] {"src"}, "bin"); createFolder("/P/src/p1"); IPackageFragment[] pkgs = getNameLookup(project).findPackageFragments("p1", false); assertElementsEqual( "Didn't find p1", "p1 [in src [in P]]", pkgs); createFolder("/P/src/p2"); pkgs = getNameLookup(project).findPackageFragments("p2", false); assertElementsEqual( "Didn't find p2", "p2 [in src [in P]]", pkgs); } finally { deleteProject("P"); } } /* * Ensures that a NameLookup can be created with working copies that contain duplicate types * (regression test for bug 63245 findPackageFragment won't return default package) */ public void testDuplicateTypesInWorkingCopies() throws CoreException { // ICompilationUnit[] workingCopies = new ICompilationUnit[3]; this.workingCopies = new ICompilationUnit[3]; try { JavaProject project = (JavaProject)createJavaProject("P"); this.workingCopies[0] = getWorkingCopy( "/P/X.java", "public class X {\n" + "}\n" + "class Other {\n" + "}" ); this.workingCopies[1] = getWorkingCopy( "/P/Y.java", "public class Y {\n" + "}\n" + "class Other {\n" + "}" ); this.workingCopies[2] = getWorkingCopy( "/P/Z.java", "public class Z {\n" + "}\n" + "class Other {\n" + "}" ); NameLookup nameLookup = project.newNameLookup(this.workingCopies); IType type = nameLookup.findType("Other", false, NameLookup.ACCEPT_ALL); // TODO (jerome) should use seekTypes assertTypesEqual( "Unepexted types", "Other\n", new IType[] {type} ); } finally { // discardWorkingCopies(workingCopies); deleteProject("P"); } } /* * Find a default package fragment in a non-default root by its path. * (regression test for bug 63245 findPackageFragment won't return default package) */ public void testFindDefaultPackageFragmentInNonDefaultRoot() throws CoreException { try { JavaProject project = (JavaProject)createJavaProject("P", new String[] {"src"}, "bin"); IPackageFragment pkg = getNameLookup(project).findPackageFragment(new Path("/P/src")); assertElementsEqual( "Didn't find default package", "<default> [in src [in P]]", new IJavaElement[] {pkg}); } finally { deleteProject("P"); } } /* * Creates a package fragment and finds it in a dependent project in a batch operation * (regression test for bug 144776 JavaProject.resetCaches() needs to reset dependent projects */ public void testNameLookupFindPackageFragmentAfterCreation() throws CoreException { try { final IJavaProject p1 = createJavaProject("P1"); final JavaProject p2 = (JavaProject) createJavaProject("P2", new String[] {""}, new String[0], new String[] {"/P1"}, ""); // populate namelookup for p2 getNameLookup(p2); IWorkspaceRunnable runnable = new IWorkspaceRunnable(){ public void run(IProgressMonitor monitor) throws CoreException { p1.getPackageFragmentRoot(p1.getProject()).createPackageFragment("pkg", false/*don't force*/, monitor); IPackageFragment[] pkgs = getNameLookup(p2).findPackageFragments("pkg", false/*exact match*/); assertElementsEqual( "Unexpected package fragments", "pkg [in <project root> [in P1]]", pkgs); } }; JavaCore.run(runnable, null/*no progress*/); } finally { deleteProject("P1"); deleteProject("P2"); } } /* * Ensure that finding a package fragment with a working copy opened returns one element only * (regression test for bug 89624 Open on selection proposes twice the same entry) */ public void testFindPackageFragmentWithWorkingCopy() throws CoreException { this.workingCopies = new ICompilationUnit[1]; try { JavaProject project = (JavaProject)createJavaProject("P"); createFolder("/P/p1"); this.workingCopies[0] = getWorkingCopy( "/P/p1/X.java", "package p1;\n" + "public class X {\n" + "}" ); NameLookup nameLookup = project.newNameLookup(this.workingCopies); IJavaElement[] pkgs = nameLookup.findPackageFragments("p1", false/*not a partial match*/); assertElementsEqual( "Unexpected packages", "p1 [in <project root> [in P]]", pkgs); } finally { deleteProject("P"); } } /* * Ensure that a package fragment with a path with a length equals to an external jar path length + 1 * is not found * (regression test for bug 266771 NameLookup.findPackageFragment returns very incorrect package fragments) */ public void testFindPackageFragment2() throws CoreException { try { JavaProject project = (JavaProject)createJavaProject("P", new String[0], new String[] {"JCL_LIB"}, "bin"); NameLookup nameLookup =getNameLookup(project); IPath pathToSearch = new Path(getExternalJCLPathString() + 'a'); IPackageFragment pkg = nameLookup.findPackageFragment(pathToSearch); assertElementEquals( "Unexpected package", "<null>", pkg); } finally { deleteProject("P"); } } /* * Ensure that a member type with a name ending with a dollar and a number is found * (regression test for bug 103466 Stack Overflow: Requesting Java AST from selection) */ public void testFindBinaryTypeWithDollarName() throws CoreException, IOException { try { IJavaProject project = createJavaProject("P"); addLibrary(project, "lib.jar", "libsrc.zip", new String[] { "p/X.java", "package p;\n" + "public class X {\n" + " public class $1 {\n" + " public class $2 {\n" + " }\n" + " }\n" + "}" }, "1.4"); IType type = getNameLookup((JavaProject) project).findType("p.X$$1", false, NameLookup.ACCEPT_ALL); assertTypesEqual( "Unexpected type", "p.X$$1\n", new IType[] {type}); } finally { deleteProject("P"); } } /* * Ensure that a type with the same simple name as its member type is found * (regression test for bug 102286 Error when trying F4-Type Hierarchy) */ public void testFindBinaryTypeWithSameNameAsMember() throws CoreException, IOException { try { IJavaProject project = createJavaProject("P", new String[] {}, new String[] {"/P/lib"}, new String[] {}, "bin"); createFolder("/P/lib/p"); createFile("/P/lib/p/X.class", ""); createFile("/P/lib/p/X$X.class", ""); IType type = getNameLookup((JavaProject) project).findType("p.X", false, NameLookup.ACCEPT_ALL); assertTypesEqual( "Unexpected type", "p.X\n", new IType[] {type}); } finally { deleteProject("P"); } } /* * Ensures that a type in working copy opened in an unrelated project is not found * (regression test for bug 169970 [model] code assist favorites must honour build path of project in context) */ public void testFindTypeWithUnrelatedWorkingCopy() throws Exception { ICompilationUnit workingCopy = null; try { createJavaProject("P1"); createFolder("/P1/p"); createFile( "/P1/p/X.java", "package p;\n" + "public class X {\n" + "}" ); workingCopy = getCompilationUnit("/P1/p/X.java"); workingCopy.becomeWorkingCopy(null); IJavaProject p2 = createJavaProject("P2"); NameLookup nameLookup = ((JavaProject) p2).newNameLookup(DefaultWorkingCopyOwner.PRIMARY); IType type = nameLookup.findType("p.X", false, NameLookup.ACCEPT_ALL); assertNull("Should not find p.X in P2", type); } finally { if (workingCopy != null) workingCopy.discardWorkingCopy(); deleteProject("P1"); deleteProject("P2"); } } /* * A test for bug 162621. Tests that a library jar that is initially invalid but transitions * to being valid becomes visible in name lookup. Previously the jar would stay in the invalid * archive cache until a classpath change was generated, and would not make it into the project's * JavaProjectElementInfo cache without restarting Eclipse or closing and reopening the project. */ public void testTransitionFromInvalidToValidJar() throws CoreException, IOException { String transitioningJar = getExternalPath() + "transitioningJar.jar"; java.nio.file.Path transitioningJarPath = FileSystems.getDefault().getPath(transitioningJar); IPath transitioningIPath = Path.fromOSString(transitioningJar); try { Util.createJar( new String[] { "test1/IResource.java", //$NON-NLS-1$ "package test1;\n" + //$NON-NLS-1$ "public class IResource {\n" + //$NON-NLS-1$ "}" //$NON-NLS-1$ }, new String[] { "META-INF/MANIFEST.MF", "Manifest-Version: 1.0\n" }, transitioningJar, JavaCore.VERSION_1_4); // Set up the project with the invalid jar and allow all of the classpath validation // and delta processing to complete. JavaModelManager.throwIoExceptionsInGetZipFile = true; JavaProject proj = (JavaProject) createJavaProject("P", new String[] {}, new String[] {transitioningJar}, "bin"); JavaModelManager.getJavaModelManager().getJavaModel().refreshExternalArchives(null, null); waitForAutoBuild(); assertTrue("The invalid archive cache should report that the jar is invalid", !JavaModelManager.getJavaModelManager().getArchiveValidity(transitioningIPath).isValid()); IType type = getNameLookup(proj).findType("test1.IResource", false, NameLookup.ACCEPT_CLASSES); assertEquals("Name lookup should fail when the jar is invalid", null, type); // Cause IO exceptions to be thrown on all file operations JavaModelManager.throwIoExceptionsInGetZipFile = false; // Since the timestamp hasn't changed, an external archive refresh isn't going // to update the caches or cause name lookups to work. JavaModelManager.getJavaModelManager().getJavaModel().refreshExternalArchives(null, null); assertTrue("External archive refresh sees no changes, so the invalid archive cache should be unchanged", !JavaModelManager.getJavaModelManager().getArchiveValidity(transitioningIPath).isValid()); type = getNameLookup(proj).findType("test1.IResource", false, NameLookup.ACCEPT_CLASSES); assertEquals("External archive refresh sees no changes, so the project cache should be unchanged", null, type); // Re-validating the jar via validateClasspathEntry() forces eviction from the // invalid archive cache more quickly than waiting for the time-based eviction, // allowing the test to run more quickly. IClasspathEntry transitioningEntry = JavaCore.newLibraryEntry(transitioningIPath, null, null); ClasspathEntry.validateClasspathEntry(proj, transitioningEntry, false, false); assertFalse("The invalid archive cache should no longer report the jar as invalid", !JavaModelManager.getJavaModelManager().getArchiveValidity(transitioningIPath).isValid()); type = getNameLookup(proj).findType("test1.IResource", false, NameLookup.ACCEPT_CLASSES); assertFalse("Name lookup should be able to find types in the valid jar", type == null); } finally { Files.deleteIfExists(transitioningJarPath); deleteProject("P"); } } }