/******************************************************************************* * Copyright (c) 2000, 2010 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.internal.core.search; import java.util.ArrayList; import java.util.HashSet; import java.util.Map; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; import org.eclipse.jdt.core.IClasspathContainer; import org.eclipse.jdt.core.IClasspathEntry; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IJavaElementDelta; import org.eclipse.jdt.core.IJavaModel; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IMember; import org.eclipse.jdt.core.IPackageFragmentRoot; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.internal.compiler.env.AccessRuleSet; import org.eclipse.jdt.internal.core.ClasspathEntry; import org.eclipse.jdt.internal.core.ExternalFoldersManager; import org.eclipse.jdt.internal.core.JavaElement; import org.eclipse.jdt.internal.core.JavaModel; import org.eclipse.jdt.internal.core.JavaModelManager; import org.eclipse.jdt.internal.core.JavaProject; import org.eclipse.jdt.internal.core.PackageFragment; import org.eclipse.jdt.internal.core.PackageFragmentRoot; import org.eclipse.jdt.internal.core.util.Util; /** * A Java-specific scope for searching relative to one or more java elements. */ public class JavaSearchScope extends AbstractJavaSearchScope { private ArrayList elements; /* The paths of the resources in this search scope (or the classpath entries' paths if the resources are projects) */ private ArrayList projectPaths = new ArrayList(); // container paths projects private int[] projectIndexes; // Indexes of projects in list private String[] containerPaths; // path to the container (e.g. /P/src, /P/lib.jar, c:\temp\mylib.jar) private String[] relativePaths; // path relative to the container (e.g. x/y/Z.class, x/y, (empty)) private boolean[] isPkgPath; // in the case of packages, matches must be direct children of the folder protected AccessRuleSet[] pathRestrictions; private int pathsCount; private int threshold; private IPath[] enclosingProjectsAndJars; public final static AccessRuleSet NOT_ENCLOSED = new AccessRuleSet(null, (byte) 0, null); public JavaSearchScope() { this(5); } private JavaSearchScope(int size) { initialize(size); //disabled for now as this could be expensive //JavaModelManager.getJavaModelManager().rememberScope(this); } private void addEnclosingProjectOrJar(IPath path) { int length = this.enclosingProjectsAndJars.length; for (int i = 0; i < length; i++) { if (this.enclosingProjectsAndJars[i].equals(path)) return; } System.arraycopy( this.enclosingProjectsAndJars, 0, this.enclosingProjectsAndJars = new IPath[length+1], 0, length); this.enclosingProjectsAndJars[length] = path; } /** * Add java project all fragment roots to current java search scope. * @see #add(JavaProject, IPath, int, HashSet, HashSet, IClasspathEntry) */ public void add(JavaProject project, int includeMask, HashSet projectsToBeAdded) throws JavaModelException { add(project, null, includeMask, projectsToBeAdded, new HashSet(2), null); } /** * Add a path to current java search scope or all project fragment roots if null. * Use project resolved classpath to retrieve and store access restriction on each classpath entry. * Recurse if dependent projects are found. * @param javaProject Project used to get resolved classpath entries * @param pathToAdd Path to add in case of single element or null if user want to add all project package fragment roots * @param includeMask Mask to apply on classpath entries * @param projectsToBeAdded Set to avoid infinite recursion * @param visitedProjects Set to avoid adding twice the same project * @param referringEntry Project raw entry in referring project classpath * @throws JavaModelException May happen while getting java model info */ void add(JavaProject javaProject, IPath pathToAdd, int includeMask, HashSet projectsToBeAdded, HashSet visitedProjects, IClasspathEntry referringEntry) throws JavaModelException { IProject project = javaProject.getProject(); if (!project.isAccessible() || !visitedProjects.add(project)) return; IPath projectPath = project.getFullPath(); String projectPathString = projectPath.toString(); addEnclosingProjectOrJar(projectPath); IClasspathEntry[] entries = javaProject.getResolvedClasspath(); IJavaModel model = javaProject.getJavaModel(); JavaModelManager.PerProjectInfo perProjectInfo = javaProject.getPerProjectInfo(); for (int i = 0, length = entries.length; i < length; i++) { IClasspathEntry entry = entries[i]; AccessRuleSet access = null; ClasspathEntry cpEntry = (ClasspathEntry) entry; if (referringEntry != null) { // Add only exported entries. // Source folder are implicitly exported. if (!entry.isExported() && entry.getEntryKind() != IClasspathEntry.CPE_SOURCE) { continue; } cpEntry = cpEntry.combineWith((ClasspathEntry)referringEntry); // cpEntry = ((ClasspathEntry)referringEntry).combineWith(cpEntry); } access = cpEntry.getAccessRuleSet(); switch (entry.getEntryKind()) { case IClasspathEntry.CPE_LIBRARY: IClasspathEntry rawEntry = null; Map rootPathToRawEntries = perProjectInfo.rootPathToRawEntries; if (rootPathToRawEntries != null) { rawEntry = (IClasspathEntry) rootPathToRawEntries.get(entry.getPath()); } if (rawEntry == null) break; rawKind: switch (rawEntry.getEntryKind()) { case IClasspathEntry.CPE_LIBRARY: case IClasspathEntry.CPE_VARIABLE: if ((includeMask & APPLICATION_LIBRARIES) != 0) { IPath path = entry.getPath(); if (pathToAdd == null || pathToAdd.equals(path)) { Object target = JavaModel.getTarget(path, false/*don't check existence*/); if (target instanceof IFolder) // case of an external folder path = ((IFolder) target).getFullPath(); String pathToString = path.getDevice() == null ? path.toString() : path.toOSString(); add(projectPath.toString(), "", pathToString, false/*not a package*/, access); //$NON-NLS-1$ addEnclosingProjectOrJar(entry.getPath()); } } break; case IClasspathEntry.CPE_CONTAINER: IClasspathContainer container = JavaCore.getClasspathContainer(rawEntry.getPath(), javaProject); if (container == null) break; switch (container.getKind()) { case IClasspathContainer.K_APPLICATION: if ((includeMask & APPLICATION_LIBRARIES) == 0) break rawKind; break; case IClasspathContainer.K_SYSTEM: case IClasspathContainer.K_DEFAULT_SYSTEM: if ((includeMask & SYSTEM_LIBRARIES) == 0) break rawKind; break; default: break rawKind; } IPath path = entry.getPath(); if (pathToAdd == null || pathToAdd.equals(path)) { Object target = JavaModel.getTarget(path, false/*don't check existence*/); if (target instanceof IFolder) // case of an external folder path = ((IFolder) target).getFullPath(); String pathToString = path.getDevice() == null ? path.toString() : path.toOSString(); add(projectPath.toString(), "", pathToString, false/*not a package*/, access); //$NON-NLS-1$ addEnclosingProjectOrJar(entry.getPath()); } break; } break; case IClasspathEntry.CPE_PROJECT: if ((includeMask & REFERENCED_PROJECTS) != 0) { IPath path = entry.getPath(); if (pathToAdd == null || pathToAdd.equals(path)) { JavaProject referencedProject = (JavaProject) model.getJavaProject(path.lastSegment()); if (!projectsToBeAdded.contains(referencedProject)) { // do not recurse if depending project was used to create the scope add(referencedProject, null, includeMask, projectsToBeAdded, visitedProjects, cpEntry); } } } break; case IClasspathEntry.CPE_SOURCE: if ((includeMask & SOURCES) != 0) { IPath path = entry.getPath(); if (pathToAdd == null || pathToAdd.equals(path)) { add(projectPath.toString(), Util.relativePath(path,1/*remove project segment*/), projectPathString, false/*not a package*/, access); } } break; } } } /** * Add an element to the java search scope. * @param element The element we want to add to current java search scope * @throws JavaModelException May happen if some Java Model info are not available */ public void add(IJavaElement element) throws JavaModelException { IPath containerPath = null; String containerPathToString = null; PackageFragmentRoot root = null; int includeMask = SOURCES | APPLICATION_LIBRARIES | SYSTEM_LIBRARIES; switch (element.getElementType()) { case IJavaElement.JAVA_MODEL: // a workspace sope should be used break; case IJavaElement.JAVA_PROJECT: add((JavaProject)element, null, includeMask, new HashSet(2), new HashSet(2), null); break; case IJavaElement.PACKAGE_FRAGMENT_ROOT: root = (PackageFragmentRoot)element; IPath rootPath = root.internalPath(); containerPath = root.getKind() == IPackageFragmentRoot.K_SOURCE ? root.getParent().getPath() : rootPath; containerPathToString = containerPath.getDevice() == null ? containerPath.toString() : containerPath.toOSString(); IResource rootResource = root.resource(); String projectPath = root.getJavaProject().getPath().toString(); if (rootResource != null && rootResource.isAccessible()) { String relativePath = Util.relativePath(rootResource.getFullPath(), containerPath.segmentCount()); add(projectPath, relativePath, containerPathToString, false/*not a package*/, null); } else { add(projectPath, "", containerPathToString, false/*not a package*/, null); //$NON-NLS-1$ } break; case IJavaElement.PACKAGE_FRAGMENT: root = (PackageFragmentRoot)element.getParent(); projectPath = root.getJavaProject().getPath().toString(); if (root.isArchive()) { String relativePath = Util.concatWith(((PackageFragment) element).names, '/'); containerPath = root.getPath(); containerPathToString = containerPath.getDevice() == null ? containerPath.toString() : containerPath.toOSString(); add(projectPath, relativePath, containerPathToString, true/*package*/, null); } else { IResource resource = ((JavaElement) element).resource(); if (resource != null) { if (resource.isAccessible()) { containerPath = root.getKind() == IPackageFragmentRoot.K_SOURCE ? root.getParent().getPath() : root.internalPath(); } else { // for working copies, get resource container full path containerPath = resource.getParent().getFullPath(); } containerPathToString = containerPath.getDevice() == null ? containerPath.toString() : containerPath.toOSString(); String relativePath = Util.relativePath(resource.getFullPath(), containerPath.segmentCount()); add(projectPath, relativePath, containerPathToString, true/*package*/, null); } } break; default: // remember sub-cu (or sub-class file) java elements if (element instanceof IMember) { if (this.elements == null) { this.elements = new ArrayList(); } this.elements.add(element); } root = (PackageFragmentRoot) element.getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT); projectPath = root.getJavaProject().getPath().toString(); String relativePath; if (root.getKind() == IPackageFragmentRoot.K_SOURCE) { containerPath = root.getParent().getPath(); relativePath = Util.relativePath(getPath(element, false/*full path*/), 1/*remove project segment*/); } else { containerPath = root.internalPath(); relativePath = getPath(element, true/*relative path*/).toString(); } containerPathToString = containerPath.getDevice() == null ? containerPath.toString() : containerPath.toOSString(); add(projectPath, relativePath, containerPathToString, false/*not a package*/, null); } if (root != null) addEnclosingProjectOrJar(root.getKind() == IPackageFragmentRoot.K_SOURCE ? root.getParent().getPath() : root.getPath()); } /** * Adds the given path to this search scope. Remember if subfolders need to be included * and associated access restriction as well. */ private void add(String projectPath, String relativePath, String containerPath, boolean isPackage, AccessRuleSet access) { // normalize containerPath and relativePath containerPath = normalize(containerPath); relativePath = normalize(relativePath); int length = this.containerPaths.length, index = (containerPath.hashCode()& 0x7FFFFFFF) % length; String currentRelativePath, currentContainerPath; while ((currentRelativePath = this.relativePaths[index]) != null && (currentContainerPath = this.containerPaths[index]) != null) { if (currentRelativePath.equals(relativePath) && currentContainerPath.equals(containerPath)) return; if (++index == length) { index = 0; } } int idx = this.projectPaths.indexOf(projectPath); if (idx == -1) { // store project in separated list to minimize memory footprint this.projectPaths.add(projectPath); idx = this.projectPaths.indexOf(projectPath); } this.projectIndexes[index] = idx; this.relativePaths[index] = relativePath; this.containerPaths[index] = containerPath; this.isPkgPath[index] = isPackage; if (this.pathRestrictions != null) this.pathRestrictions[index] = access; else if (access != null) { this.pathRestrictions = new AccessRuleSet[this.relativePaths.length]; this.pathRestrictions[index] = access; } // assumes the threshold is never equal to the size of the table if (++this.pathsCount > this.threshold) rehash(); } /* * E.g. * * 1. /P/src/pkg/X.java * 2. /P/src/pkg * 3. /P/lib.jar|org/eclipse/jdt/core/IJavaElement.class * 4. /home/mylib.jar|x/y/z/X.class * 5. c:\temp\mylib.jar|x/y/Y.class * * @see IJavaSearchScope#encloses(String) */ public boolean encloses(String resourcePathString) { int separatorIndex = resourcePathString.indexOf(JAR_FILE_ENTRY_SEPARATOR); if (separatorIndex != -1) { // internal or external jar (case 3, 4, or 5) String jarPath = resourcePathString.substring(0, separatorIndex); String relativePath = resourcePathString.substring(separatorIndex+1); return indexOf(jarPath, relativePath) >= 0; } // resource in workspace (case 1 or 2) return indexOf(resourcePathString) >= 0; } /** * Returns paths list index of given path or -1 if not found. * NOTE: Use indexOf(String, String) for path inside jars * * @param fullPath the full path of the resource, e.g. * 1. /P/src/pkg/X.java * 2. /P/src/pkg */ private int indexOf(String fullPath) { // cannot guess the index of the container path // fallback to sequentially looking at all known paths for (int i = 0, length = this.relativePaths.length; i < length; i++) { String currentRelativePath = this.relativePaths[i]; if (currentRelativePath == null) continue; String currentContainerPath = this.containerPaths[i]; String currentFullPath = currentRelativePath.length() == 0 ? currentContainerPath : (currentContainerPath + '/' + currentRelativePath); if (encloses(currentFullPath, fullPath, i)) return i; } return -1; } /** * Returns paths list index of given path or -1 if not found. * @param containerPath the path of the container, e.g. * 1. /P/src * 2. /P * 3. /P/lib.jar * 4. /home/mylib.jar * 5. c:\temp\mylib.jar * @param relativePath the forward slash path relatively to the container, e.g. * 1. x/y/Z.class * 2. x/y * 3. X.java * 4. (empty) */ private int indexOf(String containerPath, String relativePath) { // use the hash to get faster comparison int length = this.containerPaths.length, index = (containerPath.hashCode()& 0x7FFFFFFF) % length; String currentContainerPath; while ((currentContainerPath = this.containerPaths[index]) != null) { if (currentContainerPath.equals(containerPath)) { String currentRelativePath = this.relativePaths[index]; if (encloses(currentRelativePath, relativePath, index)) return index; } if (++index == length) { index = 0; } } return -1; } /* * Returns whether the enclosing path encloses the given path (or is equal to it) */ private boolean encloses(String enclosingPath, String path, int index) { // normalize given path as it can come from outside path = normalize(path); int pathLength = path.length(); int enclosingLength = enclosingPath.length(); if (pathLength < enclosingLength) { return false; } if (enclosingLength == 0) { return true; } if (pathLength == enclosingLength) { return path.equals(enclosingPath); } if (!this.isPkgPath[index]) { return path.startsWith(enclosingPath) && path.charAt(enclosingLength) == '/'; } else { // if looking at a package, this scope encloses the given path // if the given path is a direct child of the folder // or if the given path path is the folder path (see bug 13919 Declaration for package not found if scope is not project) if (path.startsWith(enclosingPath) && ((enclosingPath.length() == path.lastIndexOf('/')) || (enclosingPath.length() == path.length()))) { return true; } } return false; } /* (non-Javadoc) * @see IJavaSearchScope#encloses(IJavaElement) */ public boolean encloses(IJavaElement element) { if (this.elements != null) { for (int i = 0, length = this.elements.size(); i < length; i++) { IJavaElement scopeElement = (IJavaElement)this.elements.get(i); IJavaElement searchedElement = element; while (searchedElement != null) { if (searchedElement.equals(scopeElement)) return true; searchedElement = searchedElement.getParent(); } } return false; } IPackageFragmentRoot root = (IPackageFragmentRoot) element.getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT); if (root != null && root.isArchive()) { // external or internal jar IPath rootPath = root.getPath(); String rootPathToString = rootPath.getDevice() == null ? rootPath.toString() : rootPath.toOSString(); IPath relativePath = getPath(element, true/*relative path*/); return indexOf(rootPathToString, relativePath.toString()) >= 0; } // resource in workspace String fullResourcePathString = getPath(element, false/*full path*/).toString(); return indexOf(fullResourcePathString) >= 0; } /* (non-Javadoc) * @see IJavaSearchScope#enclosingProjectsAndJars() */ public IPath[] enclosingProjectsAndJars() { return this.enclosingProjectsAndJars; } private IPath getPath(IJavaElement element, boolean relativeToRoot) { switch (element.getElementType()) { case IJavaElement.JAVA_MODEL: return Path.EMPTY; case IJavaElement.JAVA_PROJECT: return element.getPath(); case IJavaElement.PACKAGE_FRAGMENT_ROOT: if (relativeToRoot) return Path.EMPTY; return element.getPath(); case IJavaElement.PACKAGE_FRAGMENT: String relativePath = Util.concatWith(((PackageFragment) element).names, '/'); return getPath(element.getParent(), relativeToRoot).append(new Path(relativePath)); case IJavaElement.COMPILATION_UNIT: case IJavaElement.CLASS_FILE: return getPath(element.getParent(), relativeToRoot).append(new Path(element.getElementName())); default: return getPath(element.getParent(), relativeToRoot); } } /** * Get access rule set corresponding to a given path. * @param relativePath The path user want to have restriction access * @return The access rule set for given path or null if none is set for it. * Returns specific uninit access rule set when scope does not enclose the given path. */ public AccessRuleSet getAccessRuleSet(String relativePath, String containerPath) { int index = indexOf(containerPath, relativePath); if (index == -1) { // this search scope does not enclose given path return NOT_ENCLOSED; } if (this.pathRestrictions == null) return null; return this.pathRestrictions[index]; } protected void initialize(int size) { this.pathsCount = 0; this.threshold = size; // size represents the expected number of elements int extraRoom = (int) (size * 1.75f); if (this.threshold == extraRoom) extraRoom++; this.relativePaths = new String[extraRoom]; this.containerPaths = new String[extraRoom]; this.projectPaths = new ArrayList(); this.projectIndexes = new int[extraRoom]; this.isPkgPath = new boolean[extraRoom]; this.pathRestrictions = null; // null to optimize case where no access rules are used this.enclosingProjectsAndJars = new IPath[0]; } /* * Removes trailing slashes from the given path */ private String normalize(String path) { int pathLength = path.length(); int index = pathLength-1; while (index >= 0 && path.charAt(index) == '/') index--; if (index != pathLength-1) return path.substring(0, index + 1); return path; } /* * @see AbstractSearchScope#processDelta(IJavaElementDelta) */ public void processDelta(IJavaElementDelta delta, int eventType) { switch (delta.getKind()) { case IJavaElementDelta.CHANGED: IJavaElementDelta[] children = delta.getAffectedChildren(); for (int i = 0, length = children.length; i < length; i++) { IJavaElementDelta child = children[i]; processDelta(child, eventType); } break; case IJavaElementDelta.REMOVED: IJavaElement element = delta.getElement(); if (this.encloses(element)) { if (this.elements != null) { this.elements.remove(element); } String path = null; switch (element.getElementType()) { case IJavaElement.JAVA_PROJECT: path = ((IJavaProject)element).getProject().getFullPath().toString(); break; case IJavaElement.PACKAGE_FRAGMENT_ROOT: path = ((IPackageFragmentRoot)element).getPath().toString(); break; default: return; } for (int i = 0; i < this.pathsCount; i++) { if (this.relativePaths[i].equals(path)) { this.relativePaths[i] = null; rehash(); break; } } } break; } } /** * @see AbstractJavaSearchScope#packageFragmentRoot(String, int, String) */ public IPackageFragmentRoot packageFragmentRoot(String resourcePathString, int jarSeparatorIndex, String jarPath) { int index = -1; boolean isJarFile = jarSeparatorIndex != -1; if (isJarFile) { // internal or external jar (case 3, 4, or 5) String relativePath = resourcePathString.substring(jarSeparatorIndex+1); index = indexOf(jarPath, relativePath); } else { // resource in workspace (case 1 or 2) index = indexOf(resourcePathString); } if (index >= 0) { int idx = this.projectIndexes[index]; String projectPath = idx == -1 ? null : (String) this.projectPaths.get(idx); if (projectPath != null) { IJavaProject project =JavaCore.create(ResourcesPlugin.getWorkspace().getRoot().getProject(projectPath)); if (isJarFile) { IResource resource = JavaModel.getWorkspaceTarget(new Path(jarPath)); if (resource != null) return project.getPackageFragmentRoot(resource); return project.getPackageFragmentRoot(jarPath); } Object target = JavaModel.getWorkspaceTarget(new Path(this.containerPaths[index]+'/'+this.relativePaths[index])); if (target != null) { if (target instanceof IProject) { return project.getPackageFragmentRoot((IProject) target); } IJavaElement element = JavaModelManager.create((IResource) target, project); return (IPackageFragmentRoot) element.getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT); } } } return null; } private void rehash() { JavaSearchScope newScope = new JavaSearchScope(this.pathsCount * 2); // double the number of expected elements newScope.projectPaths.ensureCapacity(this.projectPaths.size()); String currentPath; for (int i=0, length=this.relativePaths.length; i<length; i++) if ((currentPath = this.relativePaths[i]) != null) { int idx = this.projectIndexes[i]; String projectPath = idx == -1 ? null : (String)this.projectPaths.get(idx); newScope.add(projectPath, currentPath, this.containerPaths[i], this.isPkgPath[i], this.pathRestrictions == null ? null : this.pathRestrictions[i]); } this.relativePaths = newScope.relativePaths; this.containerPaths = newScope.containerPaths; this.projectPaths = newScope.projectPaths; this.projectIndexes = newScope.projectIndexes; this.isPkgPath = newScope.isPkgPath; this.pathRestrictions = newScope.pathRestrictions; this.threshold = newScope.threshold; } public String toString() { StringBuffer result = new StringBuffer("JavaSearchScope on "); //$NON-NLS-1$ if (this.elements != null) { result.append("["); //$NON-NLS-1$ for (int i = 0, length = this.elements.size(); i < length; i++) { JavaElement element = (JavaElement)this.elements.get(i); result.append("\n\t"); //$NON-NLS-1$ result.append(element.toStringWithAncestors()); } result.append("\n]"); //$NON-NLS-1$ } else { if (this.pathsCount == 0) { result.append("[empty scope]"); //$NON-NLS-1$ } else { result.append("["); //$NON-NLS-1$ String[] paths = new String[this.relativePaths.length]; int index = 0; for (int i = 0; i < this.relativePaths.length; i++) { String path = this.relativePaths[i]; if (path == null) continue; String containerPath; if (ExternalFoldersManager.isInternalPathForExternalFolder(new Path(this.containerPaths[i]))) { Object target = JavaModel.getWorkspaceTarget(new Path(this.containerPaths[i])); containerPath = ((IFolder) target).getLocation().toOSString(); } else { containerPath = this.containerPaths[i]; } if (path.length() > 0) { paths[index++] = containerPath + '/' + path; } else { paths[index++] = containerPath; } } System.arraycopy(paths, 0, paths = new String[index], 0, index); Util.sort(paths); for (int i = 0; i < index; i++) { result.append("\n\t"); //$NON-NLS-1$ result.append(paths[i]); } result.append("\n]"); //$NON-NLS-1$ } } return result.toString(); } }