blob: ac570e09e47b58c51c6dd4fdec2592749c2e4268 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2003 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Common Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/cpl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.core.util;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.*;
import org.eclipse.jdt.core.IClassFile;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.core.search.IJavaSearchScope;
import org.eclipse.jdt.internal.compiler.ASTVisitor;
import org.eclipse.jdt.internal.compiler.ast.*;
import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration;
import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
import org.eclipse.jdt.internal.compiler.ast.Initializer;
import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
import org.eclipse.jdt.internal.compiler.lookup.BlockScope;
import org.eclipse.jdt.internal.compiler.lookup.ClassScope;
import org.eclipse.jdt.internal.compiler.lookup.CompilationUnitScope;
import org.eclipse.jdt.internal.compiler.lookup.MethodScope;
import org.eclipse.jdt.internal.compiler.lookup.Scope;
import org.eclipse.jdt.internal.core.*;
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.Openable;
import org.eclipse.jdt.internal.core.PackageFragmentRoot;
import org.eclipse.jdt.internal.core.util.Util;
/**
* Creates java element handles.
*/
public class HandleFactory {
/**
* Cache package fragment root information to optimize speed performance.
*/
private String lastPkgFragmentRootPath;
private IPackageFragmentRoot lastPkgFragmentRoot;
/**
* Cache package handles to optimize memory.
*/
private Map packageHandles;
private JavaModel javaModel;
public HandleFactory() {
this.javaModel = JavaModelManager.getJavaModelManager().getJavaModel();
}
/**
* Creates an Openable handle from the given resource path.
* The resource path can be a path to a file in the workbench (eg. /Proj/com/ibm/jdt/core/HandleFactory.java)
* or a path to a file in a jar file - it then contains the path to the jar file and the path to the file in the jar
* (eg. c:/jdk1.2.2/jre/lib/rt.jar|java/lang/Object.class or /Proj/rt.jar|java/lang/Object.class)
* NOTE: This assumes that the resource path is the toString() of an IPath,
* in other words, it uses the IPath.SEPARATOR for file path
* and it uses '/' for entries in a zip file.
* If not null, uses the given scope as a hint for getting Java project handles.
*/
public Openable createOpenable(String resourcePath, IJavaSearchScope scope) {
int separatorIndex;
if ((separatorIndex= resourcePath.indexOf(IJavaSearchScope.JAR_FILE_ENTRY_SEPARATOR)) > -1) {
// path to a class file inside a jar
String jarPath= resourcePath.substring(0, separatorIndex);
// Optimization: cache package fragment root handle and package handles
if (!jarPath.equals(this.lastPkgFragmentRootPath)) {
IPackageFragmentRoot root= this.getJarPkgFragmentRoot(jarPath, scope);
if (root == null)
return null; // match is outside classpath
this.lastPkgFragmentRootPath= jarPath;
this.lastPkgFragmentRoot= root;
this.packageHandles= new HashMap(5);
}
// create handle
String classFilePath= resourcePath.substring(separatorIndex + 1);
int lastSlash= classFilePath.lastIndexOf('/');
String packageName= lastSlash > -1 ? classFilePath.substring(0, lastSlash).replace('/', '.') : IPackageFragment.DEFAULT_PACKAGE_NAME;
IPackageFragment pkgFragment= (IPackageFragment) this.packageHandles.get(packageName);
if (pkgFragment == null) {
pkgFragment= this.lastPkgFragmentRoot.getPackageFragment(packageName);
this.packageHandles.put(packageName, pkgFragment);
}
IClassFile classFile= pkgFragment.getClassFile(classFilePath.substring(lastSlash + 1));
return (Openable) classFile;
} else {
// path to a file in a directory
// Optimization: cache package fragment root handle and package handles
int length = -1;
if (this.lastPkgFragmentRootPath == null
|| !(resourcePath.startsWith(this.lastPkgFragmentRootPath)
&& (length = this.lastPkgFragmentRootPath.length()) > 0
&& resourcePath.charAt(length) == '/')) {
IPackageFragmentRoot root= this.getPkgFragmentRoot(resourcePath);
if (root == null)
return null; // match is outside classpath
this.lastPkgFragmentRoot= root;
this.lastPkgFragmentRootPath= this.lastPkgFragmentRoot.getPath().toString();
this.packageHandles= new HashMap(5);
}
// create handle
int lastSlash= resourcePath.lastIndexOf(IPath.SEPARATOR);
String packageName= lastSlash > (length= this.lastPkgFragmentRootPath.length()) ? resourcePath.substring(length + 1, lastSlash).replace(IPath.SEPARATOR, '.') : IPackageFragment.DEFAULT_PACKAGE_NAME;
IPackageFragment pkgFragment= (IPackageFragment) this.packageHandles.get(packageName);
if (pkgFragment == null) {
pkgFragment= this.lastPkgFragmentRoot.getPackageFragment(packageName);
this.packageHandles.put(packageName, pkgFragment);
}
String simpleName= resourcePath.substring(lastSlash + 1);
if (org.eclipse.jdt.internal.compiler.util.Util.isJavaFileName(simpleName)) {
ICompilationUnit unit= pkgFragment.getCompilationUnit(simpleName);
return (Openable) unit;
} else {
IClassFile classFile= pkgFragment.getClassFile(simpleName);
return (Openable) classFile;
}
}
}
/*
* Returns an element handle corresponding to the given ASTNode in the given parsed unit.
* Returns null if the given ASTNode could not be found.
*/
public IJavaElement createElement(final ASTNode toBeFound, CompilationUnitDeclaration parsedUnit, Openable openable) {
class EndVisit extends RuntimeException {
// marker to stop traversing ast
}
class Visitor extends ASTVisitor {
ASTNode[] nodeStack = new ASTNode[10];
int nodeIndex = -1;
public void push(ASTNode node) {
try {
this.nodeStack[++this.nodeIndex] = node;
} catch (IndexOutOfBoundsException e) {
System.arraycopy(this.nodeStack, 0, this.nodeStack = new ASTNode[this.nodeStack.length*2], 0, this.nodeIndex-1);
this.nodeStack[this.nodeIndex] = node;
}
}
public void pop(ASTNode node) {
while (this.nodeIndex >= 0 && this.nodeStack[this.nodeIndex--] != node);
}
public boolean visit(Argument node, BlockScope scope) {
push(node);
if (node == toBeFound) throw new EndVisit();
return true;
}
public void endVisit(Argument node, BlockScope scope) {
pop(node);
}
public boolean visit(ConstructorDeclaration node, ClassScope scope) {
push(node);
if (node == toBeFound) throw new EndVisit();
return true;
}
public void endVisit(ConstructorDeclaration node, ClassScope scope) {
pop(node);
}
public boolean visit(FieldDeclaration node, MethodScope scope) {
push(node);
if (node == toBeFound) throw new EndVisit();
return true;
}
public void endVisit(FieldDeclaration node, MethodScope scope) {
pop(node);
}
public boolean visit(Initializer node, MethodScope scope) {
push(node);
if (node == toBeFound) throw new EndVisit();
return true;
}
// don't pop initializers (used to count how many occurrences are in the type)
public boolean visit(LocalDeclaration node, BlockScope scope) {
push(node);
if (node == toBeFound) throw new EndVisit();
return true;
}
public void endVisit(LocalDeclaration node, BlockScope scope) {
pop(node);
}
public boolean visit(TypeDeclaration node, BlockScope scope) {
push(node);
if (node == toBeFound) throw new EndVisit();
return true;
}
public void endVisit(TypeDeclaration node, BlockScope scope) {
if ((node.bits & ASTNode.IsMemberTypeMASK) != 0) {
pop(node);
}
// don't pop local/anonymous types (used to count how many occurrences are in the method)
}
public boolean visit(TypeDeclaration node, ClassScope scope) {
push(node);
if (node == toBeFound) throw new EndVisit();
return true;
}
public void endVisit(TypeDeclaration node, ClassScope scope) {
if ((node.bits & ASTNode.IsMemberTypeMASK) != 0) {
pop(node);
}
// don't pop local/anonymous types (used to count how many occurrences are in the initializer)
}
public boolean visit(MethodDeclaration node, ClassScope scope) {
push(node);
if (node == toBeFound) throw new EndVisit();
return true;
}
public void endVisit(MethodDeclaration node, ClassScope scope) {
pop(node);
}
public boolean visit(TypeDeclaration node, CompilationUnitScope scope) {
push(node);
if (node == toBeFound) throw new EndVisit();
return true;
}
public void endVisit(TypeDeclaration node, CompilationUnitScope scope) {
pop(node);
}
}
Visitor visitor = new Visitor();
try {
parsedUnit.traverse(visitor, parsedUnit.scope);
} catch (EndVisit e) {
ASTNode[] nodeStack = visitor.nodeStack;
int end = visitor.nodeIndex;
int start = 0;
// find the inner most type declaration if binary type
ASTNode typeDecl = null;
if (openable instanceof ClassFile) {
for (int i = end; i >= 0; i--) {
if (nodeStack[i] instanceof TypeDeclaration) {
typeDecl = nodeStack[i];
start = i;
break;
}
}
}
// find the openable corresponding to this type declaration
if (typeDecl != null) {
openable = getOpenable(typeDecl, openable);
}
return createElement(nodeStack, start, end, openable);
}
return null;
}
private IJavaElement createElement(ASTNode[] nodeStack, int start, int end, IJavaElement parent) {
if (start > end) return parent;
ASTNode node = nodeStack[start];
IJavaElement element = parent;
switch(parent.getElementType()) {
case IJavaElement.COMPILATION_UNIT:
String typeName = new String(((TypeDeclaration)node).name);
element = ((ICompilationUnit)parent).getType(typeName);
break;
case IJavaElement.CLASS_FILE:
try {
element = ((IClassFile)parent).getType();
} catch (JavaModelException e) {
// class file doesn't exist: ignore
}
break;
case IJavaElement.TYPE:
IType type = (IType)parent;
if (node instanceof ConstructorDeclaration) {
element = type.getMethod(
parent.getElementName(),
Util.typeParameterSignatures((ConstructorDeclaration)node));
} else if (node instanceof MethodDeclaration) {
MethodDeclaration method = (MethodDeclaration)node;
element = type.getMethod(
new String(method.selector),
Util.typeParameterSignatures(method));
} else if (node instanceof Initializer) {
int occurrenceCount = 1;
while (start < end) {
if (nodeStack[start+1] instanceof Initializer) {
start++;
occurrenceCount++;
} else {
break;
}
}
element = type.getInitializer(occurrenceCount);
} else if (node instanceof FieldDeclaration) {
String fieldName = new String(((FieldDeclaration)node).name);
element = type.getField(fieldName);
} else if (node instanceof TypeDeclaration) {
typeName = new String(((TypeDeclaration)node).name);
element = type.getType(typeName);
}
break;
case IJavaElement.FIELD:
IField field = (IField)parent;
if (field.isBinary()) {
return null;
} else {
// child of a field can only be anonymous type
element = field.getType("", 1); //$NON-NLS-1$
}
break;
case IJavaElement.METHOD:
case IJavaElement.INITIALIZER:
IMember member = (IMember)parent;
if (node instanceof TypeDeclaration) {
if (member.isBinary()) {
return null;
} else {
int typeIndex = start;
while (typeIndex <= end) {
ASTNode typeDecl = nodeStack[typeIndex+1];
if (typeDecl instanceof TypeDeclaration && (typeDecl.bits & ASTNode.AnonymousAndLocalMask) != 0) {
typeIndex++;
} else {
break;
}
}
char[] name = ((TypeDeclaration)nodeStack[typeIndex]).name;
int occurrenceCount = 1;
for (int i = start; i < typeIndex; i++) {
if (CharOperation.equals(name, ((TypeDeclaration)nodeStack[i]).name)) {
occurrenceCount++;
}
}
start = typeIndex;
typeName = (node.bits & ASTNode.IsAnonymousTypeMASK) != 0 ? "" : new String(name); //$NON-NLS-1$
element = member.getType(typeName, occurrenceCount);
}
} else if (node instanceof LocalDeclaration) {
if (start == end) {
LocalDeclaration local = (LocalDeclaration)node;
element = new LocalVariable(
(JavaElement)parent,
new String(local.name),
local.declarationSourceStart,
local.declarationSourceEnd,
local.sourceStart,
local.sourceEnd,
Util.typeSignature(local.type));
} // else the next node is an anonymous (initializer of the local variable)
}
break;
}
return createElement(nodeStack, start+1, end, element);
}
/**
* Returns a handle denoting the class member identified by its scope.
*/
public IJavaElement createElement(ClassScope scope, ICompilationUnit unit, HashSet existingElements, HashMap knownScopes) {
return createElement(scope, scope.referenceContext.sourceStart, unit, existingElements, knownScopes);
}
/**
* Create handle by adding child to parent obtained by recursing into parent scopes.
*/
private IJavaElement createElement(Scope scope, int elementPosition, ICompilationUnit unit, HashSet existingElements, HashMap knownScopes) {
IJavaElement newElement = (IJavaElement)knownScopes.get(scope);
if (newElement != null) return newElement;
switch(scope.kind) {
case Scope.COMPILATION_UNIT_SCOPE :
newElement = unit;
break;
case Scope.CLASS_SCOPE :
IJavaElement parentElement = createElement(scope.parent, elementPosition, unit, existingElements, knownScopes);
switch (parentElement.getElementType()) {
case IJavaElement.COMPILATION_UNIT :
newElement = ((ICompilationUnit)parentElement).getType(new String(scope.enclosingSourceType().sourceName));
break;
case IJavaElement.TYPE :
newElement = ((IType)parentElement).getType(new String(scope.enclosingSourceType().sourceName));
break;
case IJavaElement.FIELD :
case IJavaElement.INITIALIZER :
case IJavaElement.METHOD :
IMember member = (IMember)parentElement;
if (member.isBinary()) {
return null;
} else {
newElement = member.getType(new String(scope.enclosingSourceType().sourceName), 1);
// increment occurrence count if collision is detected
if (newElement != null) {
while (!existingElements.add(newElement)) ((JavaElement)newElement).occurrenceCount++;
}
}
break;
}
if (newElement != null) {
knownScopes.put(scope, newElement);
}
break;
case Scope.METHOD_SCOPE :
IType parentType = (IType) createElement(scope.parent, elementPosition, unit, existingElements, knownScopes);
MethodScope methodScope = (MethodScope) scope;
if (methodScope.isInsideInitializer()) {
// inside field or initializer, must find proper one
TypeDeclaration type = methodScope.referenceType();
int occurenceCount = 1;
for (int i = 0, length = type.fields.length; i < length; i++) {
FieldDeclaration field = type.fields[i];
if (field.declarationSourceStart < elementPosition && field.declarationSourceEnd > elementPosition) {
if (field.isField()) {
newElement = parentType.getField(new String(field.name));
} else {
newElement = parentType.getInitializer(occurenceCount);
}
break;
} else if (!field.isField()) {
occurenceCount++;
}
}
} else {
// method element
AbstractMethodDeclaration method = methodScope.referenceMethod();
newElement = parentType.getMethod(new String(method.selector), Util.typeParameterSignatures(method));
if (newElement != null) {
knownScopes.put(scope, newElement);
}
}
break;
case Scope.BLOCK_SCOPE :
// standard block, no element per se
newElement = createElement(scope.parent, elementPosition, unit, existingElements, knownScopes);
break;
}
return newElement;
}
/**
* Returns the package fragment root that corresponds to the given jar path.
* See createOpenable(...) for the format of the jar path string.
* If not null, uses the given scope as a hint for getting Java project handles.
*/
private IPackageFragmentRoot getJarPkgFragmentRoot(String jarPathString, IJavaSearchScope scope) {
IPath jarPath= new Path(jarPathString);
Object target = JavaModel.getTarget(ResourcesPlugin.getWorkspace().getRoot(), jarPath, false);
if (target instanceof IFile) {
// internal jar: is it on the classpath of its project?
// e.g. org.eclipse.swt.win32/ws/win32/swt.jar
// is NOT on the classpath of org.eclipse.swt.win32
IFile jarFile = (IFile)target;
IJavaProject javaProject = this.javaModel.getJavaProject(jarFile);
IClasspathEntry[] classpathEntries;
try {
classpathEntries = javaProject.getResolvedClasspath(true);
for (int j= 0, entryCount= classpathEntries.length; j < entryCount; j++) {
if (classpathEntries[j].getPath().equals(jarPath)) {
return javaProject.getPackageFragmentRoot(jarFile);
}
}
} catch (JavaModelException e) {
// ignore and try to find another project
}
}
// walk projects in the scope and find the first one that has the given jar path in its classpath
IJavaProject[] projects;
if (scope != null) {
IPath[] enclosingProjectsAndJars = scope.enclosingProjectsAndJars();
int length = enclosingProjectsAndJars.length;
projects = new IJavaProject[length];
int index = 0;
for (int i = 0; i < length; i++) {
IPath path = enclosingProjectsAndJars[i];
if (!org.eclipse.jdt.internal.compiler.util.Util.isArchiveFileName(path.lastSegment())) {
projects[index++] = this.javaModel.getJavaProject(path.segment(0));
}
}
if (index < length) {
System.arraycopy(projects, 0, projects = new IJavaProject[index], 0, index);
}
IPackageFragmentRoot root = getJarPkgFragmentRoot(jarPath, target, projects);
if (root != null) {
return root;
}
}
// not found in the scope, walk all projects
try {
projects = this.javaModel.getJavaProjects();
} catch (JavaModelException e) {
// java model is not accessible
return null;
}
return getJarPkgFragmentRoot(jarPath, target, projects);
}
private IPackageFragmentRoot getJarPkgFragmentRoot(
IPath jarPath,
Object target,
IJavaProject[] projects) {
for (int i= 0, projectCount= projects.length; i < projectCount; i++) {
try {
JavaProject javaProject= (JavaProject)projects[i];
IClasspathEntry[] classpathEntries= javaProject.getResolvedClasspath(true);
for (int j= 0, entryCount= classpathEntries.length; j < entryCount; j++) {
if (classpathEntries[j].getPath().equals(jarPath)) {
if (target instanceof IFile) {
// internal jar
return javaProject.getPackageFragmentRoot((IFile)target);
} else {
// external jar
return javaProject.getPackageFragmentRoot0(jarPath);
}
}
}
} catch (JavaModelException e) {
// JavaModelException from getResolvedClasspath - a problem occured while accessing project: nothing we can do, ignore
}
}
return null;
}
/*
* Returns the openable that contains the given AST node.
*/
private Openable getOpenable(ASTNode toBeFound, Openable openable) {
if (openable instanceof ClassFile) {
try {
int sourceStart = toBeFound.sourceStart;
int sourceEnd = toBeFound.sourceEnd;
ClassFile classFile = (ClassFile)openable;
ISourceRange sourceRange = classFile.getType().getSourceRange();
int offset = sourceRange.getOffset();
if (offset == sourceStart && offset + sourceRange.getLength() != sourceEnd) {
return openable;
} else {
String prefix = classFile.getTopLevelTypeName() + '$';
IPackageFragment pkg = (IPackageFragment)classFile.getParent();
IClassFile[] children = pkg.getClassFiles();
for (int i = 0, length = children.length; i < length; i++) {
IClassFile child = children[i];
if (child.getElementName().startsWith(prefix)) {
sourceRange = child.getType().getSourceRange();
offset = sourceRange.getOffset();
if (offset == sourceStart && offset + sourceRange.getLength() != sourceEnd) {
return (Openable)child;
}
}
}
}
} catch (JavaModelException e) {
// class file doesn't exist: ignore
}
}
return openable;
}
/**
* Returns the package fragment root that contains the given resource path.
*/
private IPackageFragmentRoot getPkgFragmentRoot(String pathString) {
IPath path= new Path(pathString);
IProject[] projects= ResourcesPlugin.getWorkspace().getRoot().getProjects();
for (int i= 0, max= projects.length; i < max; i++) {
try {
IProject project = projects[i];
if (!project.isAccessible()
|| !project.hasNature(JavaCore.NATURE_ID)) continue;
IJavaProject javaProject= this.javaModel.getJavaProject(project);
IPackageFragmentRoot[] roots= javaProject.getPackageFragmentRoots();
for (int j= 0, rootCount= roots.length; j < rootCount; j++) {
PackageFragmentRoot root= (PackageFragmentRoot)roots[j];
if (root.getPath().isPrefixOf(path) && !Util.isExcluded(path, root.fullExclusionPatternChars())) {
return root;
}
}
} catch (CoreException e) {
// CoreException from hasNature - should not happen since we check that the project is accessible
// JavaModelException from getPackageFragmentRoots - a problem occured while accessing project: nothing we can do, ignore
}
}
return null;
}
}