/******************************************************************************* * 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 * Stephan Herrmann - Contributions for bug 215139 and bug 295894 *******************************************************************************/ package org.eclipse.jdt.internal.core.search; import java.io.File; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import org.eclipse.core.resources.*; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.jdt.core.*; import org.eclipse.jdt.internal.compiler.util.SuffixConstants; import org.eclipse.jdt.internal.core.*; import org.eclipse.jdt.internal.core.hierarchy.TypeHierarchy; /** * Scope limited to the subtype and supertype hierarchy of a given type. */ @SuppressWarnings({"rawtypes", "unchecked"}) public class HierarchyScope extends AbstractSearchScope implements SuffixConstants { public IType focusType; private String focusPath; private WorkingCopyOwner owner; private ITypeHierarchy hierarchy; private HashSet resourcePaths; private IPath[] enclosingProjectsAndJars; protected IResource[] elements; protected int elementCount; public boolean needsRefresh; private HashSet subTypes = null; // null means: don't filter for subTypes private IJavaProject javaProject = null; // null means: don't constrain the search to a project private boolean allowMemberAndEnclosingTypes = true; private boolean includeFocusType = true; /* (non-Javadoc) * Adds the given resource to this search scope. */ public void add(IResource element) { if (this.elementCount == this.elements.length) { System.arraycopy( this.elements, 0, this.elements = new IResource[this.elementCount * 2], 0, this.elementCount); } this.elements[this.elementCount++] = element; } /** * Creates a new hierarchy scope for the given type with the given configuration options. * @param project constrain the search result to this project, * or <code>null</code> if search should consider all types in the workspace * @param type the focus type of the hierarchy * @param owner the owner of working copies that take precedence over original compilation units, * or <code>null</code> if the primary working copy owner should be used * @param onlySubtypes if true search only subtypes of 'type' * @param noMembersOrEnclosingTypes if true the hierarchy is strict, * i.e., no additional member types or enclosing types of types spanning the hierarchy are included, * otherwise all member and enclosing types of types in the hierarchy are included. * @param includeFocusType if true the focus type <code>type</code> is included in the resulting scope, otherwise it is excluded */ public HierarchyScope(IJavaProject project, IType type, WorkingCopyOwner owner, boolean onlySubtypes, boolean noMembersOrEnclosingTypes, boolean includeFocusType) throws JavaModelException { this(type, owner); this.javaProject = project; if (onlySubtypes) { this.subTypes = new HashSet(); } this.includeFocusType = includeFocusType; this.allowMemberAndEnclosingTypes = !noMembersOrEnclosingTypes; } /* (non-Javadoc) * Creates a new hiearchy scope for the given type. */ public HierarchyScope(IType type, WorkingCopyOwner owner) throws JavaModelException { this.focusType = type; this.owner = owner; this.enclosingProjectsAndJars = computeProjectsAndJars(type); // resource path IPackageFragmentRoot root = (IPackageFragmentRoot)type.getPackageFragment().getParent(); if (root.isArchive()) { IPath jarPath = root.getPath(); Object target = JavaModel.getTarget(jarPath, true); String zipFileName; if (target instanceof IFile) { // internal jar zipFileName = jarPath.toString(); } else if (target instanceof File) { // external jar zipFileName = ((File)target).getPath(); } else { return; // unknown target } this.focusPath = zipFileName + JAR_FILE_ENTRY_SEPARATOR + type.getFullyQualifiedName().replace('.', '/') + SUFFIX_STRING_class; } else { this.focusPath = type.getPath().toString(); } this.needsRefresh = true; //disabled for now as this could be expensive //JavaModelManager.getJavaModelManager().rememberScope(this); } private void buildResourceVector() { HashMap resources = new HashMap(); HashMap paths = new HashMap(); IType[] types = null; if (this.subTypes != null) { types = this.hierarchy.getAllSubtypes(this.focusType); if (this.includeFocusType) { int len = types.length; System.arraycopy(types, 0, types=new IType[len+1], 0, len); types[len] = this.focusType; } } else { types = this.hierarchy.getAllTypes(); } for (int i = 0; i < types.length; i++) { IType type = types[i]; if (this.subTypes != null) { // remember subtypes for later use in encloses() this.subTypes.add(type); } IResource resource = ((JavaElement)type).resource(); if (resource != null && resources.get(resource) == null) { resources.put(resource, resource); add(resource); } IPackageFragmentRoot root = (IPackageFragmentRoot) type.getPackageFragment().getParent(); if (root instanceof JarPackageFragmentRoot) { // type in a jar JarPackageFragmentRoot jar = (JarPackageFragmentRoot) root; IPath jarPath = jar.getPath(); Object target = JavaModel.getTarget(jarPath, true); String zipFileName; if (target instanceof IFile) { // internal jar zipFileName = jarPath.toString(); } else if (target instanceof File) { // external jar zipFileName = ((File)target).getPath(); } else { continue; // unknown target } String resourcePath = zipFileName + JAR_FILE_ENTRY_SEPARATOR + type.getFullyQualifiedName().replace('.', '/') + SUFFIX_STRING_class; this.resourcePaths.add(resourcePath); paths.put(jarPath, type); } else { // type is a project paths.put(type.getJavaProject().getProject().getFullPath(), type); } } this.enclosingProjectsAndJars = new IPath[paths.size()]; int i = 0; for (Iterator iter = paths.keySet().iterator(); iter.hasNext();) { this.enclosingProjectsAndJars[i++] = (IPath) iter.next(); } } /* * Computes the paths of projects and jars that the hierarchy on the given type could contain. * This is a super set of the project and jar paths once the hierarchy is computed. */ private IPath[] computeProjectsAndJars(IType type) throws JavaModelException { HashSet set = new HashSet(); IPackageFragmentRoot root = (IPackageFragmentRoot)type.getPackageFragment().getParent(); if (root.isArchive()) { // add the root set.add(root.getPath()); // add all projects that reference this archive and their dependents IPath rootPath = root.getPath(); IJavaModel model = JavaModelManager.getJavaModelManager().getJavaModel(); IJavaProject[] projects = model.getJavaProjects(); HashSet visited = new HashSet(); for (int i = 0; i < projects.length; i++) { JavaProject project = (JavaProject) projects[i]; IClasspathEntry entry = project.getClasspathEntryFor(rootPath); if (entry != null) { // add the project and its binary pkg fragment roots IPackageFragmentRoot[] roots = project.getAllPackageFragmentRoots(); set.add(project.getPath()); for (int k = 0; k < roots.length; k++) { IPackageFragmentRoot pkgFragmentRoot = roots[k]; if (pkgFragmentRoot.getKind() == IPackageFragmentRoot.K_BINARY) { set.add(pkgFragmentRoot.getPath()); } } // add the dependent projects computeDependents(project, set, visited); } } } else { // add all the project's pkg fragment roots IJavaProject project = (IJavaProject)root.getParent(); IPackageFragmentRoot[] roots = project.getAllPackageFragmentRoots(); for (int i = 0; i < roots.length; i++) { IPackageFragmentRoot pkgFragmentRoot = roots[i]; if (pkgFragmentRoot.getKind() == IPackageFragmentRoot.K_BINARY) { set.add(pkgFragmentRoot.getPath()); } else { set.add(pkgFragmentRoot.getParent().getPath()); } } // add the dependent projects computeDependents(project, set, new HashSet()); } IPath[] result = new IPath[set.size()]; set.toArray(result); return result; } private void computeDependents(IJavaProject project, HashSet set, HashSet visited) { if (visited.contains(project)) return; visited.add(project); IProject[] dependents = project.getProject().getReferencingProjects(); for (int i = 0; i < dependents.length; i++) { try { IJavaProject dependent = JavaCore.create(dependents[i]); IPackageFragmentRoot[] roots = dependent.getPackageFragmentRoots(); set.add(dependent.getPath()); for (int j = 0; j < roots.length; j++) { IPackageFragmentRoot pkgFragmentRoot = roots[j]; if (pkgFragmentRoot.isArchive()) { set.add(pkgFragmentRoot.getPath()); } } computeDependents(dependent, set, visited); } catch (JavaModelException e) { // project is not a java project } } } /* (non-Javadoc) * @see IJavaSearchScope#encloses(String) */ public boolean encloses(String resourcePath) { return encloses(resourcePath, null); } public boolean encloses(String resourcePath, IProgressMonitor progressMonitor) { if (this.hierarchy == null) { if (resourcePath.equals(this.focusPath)) { return true; } else { if (this.needsRefresh) { try { initialize(progressMonitor); } catch (JavaModelException e) { return false; } } else { // the scope is used only to find enclosing projects and jars // clients is responsible for filtering out elements not in the hierarchy (see SearchEngine) return true; } } } if (this.needsRefresh) { try { refresh(progressMonitor); } catch(JavaModelException e) { return false; } } int separatorIndex = resourcePath.indexOf(JAR_FILE_ENTRY_SEPARATOR); if (separatorIndex != -1) { return this.resourcePaths.contains(resourcePath); } else { for (int i = 0; i < this.elementCount; i++) { if (resourcePath.startsWith(this.elements[i].getFullPath().toString())) { return true; } } } return false; } /** * Optionally perform additional checks after element has already passed matching based on index/documents. * * @param element the given element * @return <code>true</code> if the element is enclosed or if no fine grained checking * (regarding subtypes and members) is requested */ public boolean enclosesFineGrained(IJavaElement element) { if ((this.subTypes == null) && this.allowMemberAndEnclosingTypes) return true; // no fine grained checking requested return encloses(element, null); } /* (non-Javadoc) * @see IJavaSearchScope#encloses(IJavaElement) */ public boolean encloses(IJavaElement element) { return encloses(element, null); } public boolean encloses(IJavaElement element, IProgressMonitor progressMonitor) { if (this.hierarchy == null) { if (this.includeFocusType && this.focusType.equals(element.getAncestor(IJavaElement.TYPE))) { return true; } else { if (this.needsRefresh) { try { initialize(progressMonitor); } catch (JavaModelException e) { return false; } } else { // the scope is used only to find enclosing projects and jars // clients is responsible for filtering out elements not in the hierarchy (see SearchEngine) return true; } } } if (this.needsRefresh) { try { refresh(progressMonitor); } catch(JavaModelException e) { return false; } } IType type = null; if (element instanceof IType) { type = (IType) element; } else if (element instanceof IMember) { type = ((IMember) element).getDeclaringType(); } if (type != null) { if (this.focusType.equals(type)) return this.includeFocusType; // potentially allow travelling in: if (enclosesType(type, this.allowMemberAndEnclosingTypes)) { return true; } if (this.allowMemberAndEnclosingTypes) { // travel out: queried type is enclosed in this scope if its (indirect) declaring type is: IType enclosing = type.getDeclaringType(); while (enclosing != null) { // don't allow travelling in again: if (enclosesType(enclosing, false)) { return true; } enclosing = enclosing.getDeclaringType(); } } } return false; } private boolean enclosesType(IType type, boolean recurse) { if (this.subTypes != null) { // searching subtypes if (this.subTypes.contains(type)) { return true; } // be flexible: look at original element (see bug 14106 and below) IType original = type.isBinary() ? null : (IType)type.getPrimaryElement(); if (original != type && this.subTypes.contains(original)) { return true; } } else { if (this.hierarchy.contains(type)) { return true; } else { // be flexible: look at original element (see bug 14106 Declarations in Hierarchy does not find declarations in hierarchy) IType original; if (!type.isBinary() && (original = (IType)type.getPrimaryElement()) != null) { if (this.hierarchy.contains(original)) { return true; } } } } if (recurse) { // queried type is enclosed in this scope if one of its members is: try { IType[] memberTypes = type.getTypes(); for (int i = 0; i < memberTypes.length; i++) { if (enclosesType(memberTypes[i], recurse)) { return true; } } } catch (JavaModelException e) { return false; } } return false; } /* (non-Javadoc) * @see IJavaSearchScope#enclosingProjectsAndJars() * @deprecated */ public IPath[] enclosingProjectsAndJars() { if (this.needsRefresh) { try { refresh(null); } catch(JavaModelException e) { return new IPath[0]; } } return this.enclosingProjectsAndJars; } protected void initialize() throws JavaModelException { initialize(null); } protected void initialize(IProgressMonitor progressMonitor) throws JavaModelException { this.resourcePaths = new HashSet(); this.elements = new IResource[5]; this.elementCount = 0; this.needsRefresh = false; if (this.hierarchy == null) { if (this.javaProject != null) { this.hierarchy = this.focusType.newTypeHierarchy(this.javaProject, this.owner, progressMonitor); } else { this.hierarchy = this.focusType.newTypeHierarchy(this.owner, progressMonitor); } } else { this.hierarchy.refresh(progressMonitor); } buildResourceVector(); } /* * @see AbstractSearchScope#processDelta(IJavaElementDelta) */ public void processDelta(IJavaElementDelta delta, int eventType) { if (this.needsRefresh) return; this.needsRefresh = this.hierarchy == null ? false : ((TypeHierarchy)this.hierarchy).isAffected(delta, eventType); } protected void refresh() throws JavaModelException { refresh(null); } protected void refresh(IProgressMonitor progressMonitor) throws JavaModelException { if (this.hierarchy != null) { initialize(progressMonitor); } } public String toString() { return "HierarchyScope on " + ((JavaElement)this.focusType).toStringWithAncestors(); //$NON-NLS-1$ } }