blob: 6224c5be9f9384f9c4770e7ea1f6e655d0a95f22 [file] [log] [blame]
/*******************************************************************************
* 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.*;
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.
*/
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$
}
}