/******************************************************************************* * 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 * 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.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; 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.IType; import org.eclipse.jdt.core.ITypeHierarchy; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.WorkingCopyOwner; import org.eclipse.jdt.internal.compiler.util.SuffixConstants; import org.eclipse.jdt.internal.core.JarPackageFragmentRoot; 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.hierarchy.TypeHierarchy; /** * Scope limited to the subtype and supertype hierarchy of a given type. */ 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$ } }