package org.eclipse.jdt.internal.core; | |
/* | |
* (c) Copyright IBM Corp. 2000, 2001. | |
* All Rights Reserved. | |
*/ | |
import org.eclipse.core.runtime.IPath; | |
import org.eclipse.core.runtime.CoreException; | |
import org.eclipse.core.resources.*; | |
import java.io.IOException; | |
import java.io.InputStream; | |
import java.util.*; | |
import java.util.zip.ZipFile; | |
import java.util.zip.ZipEntry; | |
import org.eclipse.jdt.internal.compiler.env.*; | |
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader; | |
import org.eclipse.jdt.core.*; | |
import org.eclipse.jdt.internal.compiler.*; | |
import org.eclipse.jdt.internal.core.util.ReferenceInfoAdapter; | |
import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory; | |
/** | |
* A SourceMapper maps source code in a ZIP file to binary types in | |
* a JAR. The SourceMapper uses the fuzzy parser to identify source | |
* fragments in a .java file, and attempts to match the source code | |
* with children in a binary type. A SourceMapper is associated | |
* with a JarPackageFragment by an AttachSourceOperation. | |
* | |
* @see AttachSourceOperation | |
* @see JarPackageFragment | |
*/ | |
public class SourceMapper extends ReferenceInfoAdapter implements ISourceElementRequestor { | |
/** | |
* The binary type source is being mapped for | |
*/ | |
protected BinaryType fType; | |
/** | |
* The location of the zip file containing source. | |
*/ | |
protected IPath fZipPath; | |
/** | |
* Specifies the location of the package fragment root within | |
* the zip (empty specifies the default root). <code>null</code> is | |
* not a valid root path. | |
*/ | |
protected String fRootPath; | |
/** | |
* The Java Model this source mapper is working for. | |
*/ | |
protected JavaModel fJavaModel; | |
/** | |
* Used for efficiency | |
*/ | |
protected static String[] fgEmptyStringArray = new String[0]; | |
/** | |
* Table that maps a binary element to its <code>SourceRange</code>s. | |
* Keys are the element handles, entries are <code>SourceRange[]</code> which | |
* is a two element array; the first being source range, the second | |
* being name range. | |
*/ | |
protected Hashtable fSourceRanges; | |
/** | |
* The unknown source range {-1, 0} | |
*/ | |
protected static SourceRange fgUnknownRange= new SourceRange(-1, 0); | |
/** | |
* The position within the source of the start of the | |
* current member element, or -1 if we are outside a member. | |
*/ | |
protected int fMemberDeclarationStart = -1; | |
/** | |
* The <code>SourceRange</code> of the name of the current member element. | |
*/ | |
protected SourceRange fMemberNameRange; | |
/** | |
* The name of the current member element. | |
*/ | |
protected String fMemberName; | |
/** | |
* The parameter types for the current member method element. | |
*/ | |
protected char[][] fMethodParameterTypes; | |
/** | |
* The element searched for | |
*/ | |
protected IJavaElement searchedElement; | |
/** | |
* Enclosing type information | |
*/ | |
IType[] types; | |
int[] typeDeclarationStarts; | |
SourceRange[] typeNameRanges; | |
int typeDepth; | |
/** | |
* Creates a <code>SourceMapper</code> that locates source in the zip file | |
* at the given location in the specified package fragment root. | |
*/ | |
public SourceMapper(IPath zipPath, String rootPath, JavaModel model) { | |
fZipPath= zipPath; | |
fRootPath= rootPath.replace('\\', '/'); | |
if (fRootPath.endsWith("/"/*nonNLS*/)) { | |
fRootPath = fRootPath.substring(0, fRootPath.lastIndexOf('/')); | |
} | |
fJavaModel= model; | |
fSourceRanges= new Hashtable(); | |
} | |
/** | |
* @see ISourceElementRequestor | |
*/ | |
public void acceptImport(int declarationStart, int declarationEnd, char[] name, boolean onDemand) { | |
//do nothing | |
} | |
/** | |
* @see ISourceElementRequestor | |
*/ | |
public void acceptInitializer(int modifiers, int declarationSourceStart, int declarationSourceEnd) { | |
//do nothing | |
} | |
/** | |
* @see ISourceElementRequestor | |
*/ | |
public void acceptLineSeparatorPositions(int[] positions) { | |
//do nothing | |
} | |
/** | |
* @see ISourceElementRequestor | |
*/ | |
public void acceptPackage(int declarationStart, int declarationEnd, char[] name) { | |
//do nothing | |
} | |
/** | |
* @see ISourceElementRequestor | |
*/ | |
public void acceptProblem(IProblem problem) { | |
//do nothing | |
} | |
/** | |
* Closes this <code>SourceMapper</code>'s zip file. Once this is done, this | |
* <code>SourceMapper</code> cannot be used again. | |
*/ | |
public void close() throws JavaModelException { | |
fSourceRanges= null; | |
} | |
/** | |
* Converts these type names to signatures. | |
* @see Signature. | |
*/ | |
public String[] convertTypeNamesToSigs(char[][] typeNames) { | |
if (typeNames == null) | |
return fgEmptyStringArray; | |
int n = typeNames.length; | |
if (n == 0) | |
return fgEmptyStringArray; | |
String[] typeSigs = new String[n]; | |
for (int i = 0; i < n; ++i) { | |
typeSigs[i] = Signature.createTypeSignature(typeNames[i], false); | |
} | |
return typeSigs; | |
} | |
/** | |
* @see ISourceElementRequestor | |
*/ | |
public void enterClass(int declarationStart, int modifiers, char[] name, int nameSourceStart, int nameSourceEnd, char[] superclass, char[][] superinterfaces) { | |
this.typeDepth++; | |
if (this.typeDepth == this.types.length){ // need to grow | |
System.arraycopy(this.types, 0, this.types = new IType[this.typeDepth*2], 0, this.typeDepth); | |
System.arraycopy(this.typeNameRanges, 0, this.typeNameRanges = new SourceRange[this.typeDepth*2], 0, this.typeDepth); | |
System.arraycopy(this.typeDeclarationStarts, 0, this.typeDeclarationStarts = new int[this.typeDepth*2], 0, this.typeDepth); | |
} | |
this.types[typeDepth] = this.getType(new String(name)); | |
this.typeNameRanges[typeDepth] = new SourceRange(nameSourceStart, nameSourceEnd - nameSourceStart + 1); | |
this.typeDeclarationStarts[typeDepth] = declarationStart; | |
} | |
/** | |
* @see ISourceElementRequestor | |
*/ | |
public void enterCompilationUnit() { | |
// do nothing | |
} | |
/** | |
* @see ISourceElementRequestor | |
*/ | |
public void enterConstructor(int declarationStart, int modifiers, char[] name, int nameSourceStart, int nameSourceEnd, char[][] parameterTypes, char[][] parameterNames, char[][] exceptionTypes) { | |
enterMethod(declarationStart, modifiers, null, name, nameSourceStart, nameSourceEnd, parameterTypes, parameterNames, exceptionTypes); | |
} | |
/** | |
* @see ISourceElementRequestor | |
*/ | |
public void enterField(int declarationStart, int modifiers, char[] type, char[] name, int nameSourceStart, int nameSourceEnd) { | |
if (typeDepth >= 0 && fMemberDeclarationStart == -1) { // don't allow nested member (can only happen with anonymous inner classes) | |
fMemberDeclarationStart= declarationStart; | |
fMemberNameRange= new SourceRange(nameSourceStart, nameSourceEnd - nameSourceStart + 1); | |
fMemberName= new String(name); | |
} | |
} | |
/** | |
* @see ISourceElementRequestor | |
*/ | |
public void enterInterface(int declarationStart, int modifiers, char[] name, int nameSourceStart, int nameSourceEnd, char[][] superinterfaces) { | |
enterClass(declarationStart, modifiers, name, nameSourceStart, nameSourceEnd, null, superinterfaces); | |
} | |
/** | |
* @see ISourceElementRequestor | |
*/ | |
public void enterMethod(int declarationStart, int modifiers, char[] returnType, char[] name, int nameSourceStart, int nameSourceEnd, char[][] parameterTypes, char[][] parameterNames, char[][] exceptionTypes) { | |
if (typeDepth >= 0 && fMemberDeclarationStart == -1) { // don't allow nested member (can only happen with anonymous inner classes) | |
fMemberName= new String(name); | |
fMemberNameRange= new SourceRange(nameSourceStart, nameSourceEnd - nameSourceStart + 1); | |
fMemberDeclarationStart= declarationStart; | |
fMethodParameterTypes= parameterTypes; | |
} | |
} | |
/** | |
* @see ISourceElementRequestor | |
*/ | |
public void exitClass(int declarationEnd) { | |
if (typeDepth >= 0) { | |
IType currentType = this.types[typeDepth]; | |
setSourceRange( | |
currentType, | |
new SourceRange( | |
this.typeDeclarationStarts[typeDepth] , | |
declarationEnd - this.typeDeclarationStarts[typeDepth] + 1), | |
this.typeNameRanges[typeDepth]); | |
this.typeDepth--; | |
} | |
} | |
/** | |
* @see ISourceElementRequestor | |
*/ | |
public void exitCompilationUnit(int declarationEnd) { | |
//do nothing | |
} | |
/** | |
* @see ISourceElementRequestor | |
*/ | |
public void exitConstructor(int declarationEnd) { | |
exitMethod(declarationEnd); | |
} | |
/** | |
* @see ISourceElementRequestor | |
*/ | |
public void exitField(int declarationEnd) { | |
if (typeDepth >= 0 && fMemberDeclarationStart != -1) { | |
IType currentType = this.types[typeDepth]; | |
setSourceRange(currentType.getField(fMemberName), new SourceRange(fMemberDeclarationStart, declarationEnd - fMemberDeclarationStart + 1), fMemberNameRange); | |
fMemberDeclarationStart = -1; | |
} | |
} | |
/** | |
* @see ISourceElementRequestor | |
*/ | |
public void exitInterface(int declarationEnd) { | |
exitClass(declarationEnd); | |
} | |
/** | |
* @see ISourceElementRequestor | |
*/ | |
public void exitMethod(int declarationEnd) { | |
if (typeDepth >= 0 && fMemberDeclarationStart != -1) { | |
IType currentType = this.types[typeDepth]; | |
SourceRange sourceRange= new SourceRange(fMemberDeclarationStart, declarationEnd - fMemberDeclarationStart + 1); | |
setSourceRange(currentType.getMethod(fMemberName, convertTypeNamesToSigs(fMethodParameterTypes)), sourceRange, fMemberNameRange); | |
fMemberDeclarationStart = -1; | |
} | |
} | |
/** | |
* Locates and returns source code for the given (binary) type, in this | |
* SourceMapper's ZIP file, or returns <code>null</code> if source | |
* code cannot be found. | |
*/ | |
public char[] findSource(IType type) { | |
if (!type.isBinary()) { | |
return null; | |
} | |
BinaryType parent= (BinaryType)type.getDeclaringType(); | |
BinaryType declType= (BinaryType)type; | |
while (parent != null) { | |
declType= parent; | |
parent= (BinaryType)declType.getDeclaringType(); | |
} | |
IBinaryType info= null; | |
try { | |
info= (IBinaryType)declType.getRawInfo(); | |
} catch (JavaModelException e) { | |
return null; | |
} | |
return this.findSource(type, info); | |
} | |
/** | |
* Locates and returns source code for the given (binary) type, in this | |
* SourceMapper's ZIP file, or returns <code>null</code> if source | |
* code cannot be found. | |
*/ | |
public char[] findSource(IType type, IBinaryType info) { | |
String name = null; | |
// see 1FVVWZT | |
if (info instanceof ClassFileReader) { | |
char[] sourceFileName = ((ClassFileReader) info).sourceFileName(); | |
if (sourceFileName == null) | |
return null; // no source file attribute | |
name = new String(sourceFileName); | |
} else { | |
return null; | |
} | |
IPackageFragment pkgFrag = type.getPackageFragment(); | |
if (!pkgFrag.isDefaultPackage()) { | |
String pkg= type.getPackageFragment().getElementName().replace('.', '/'); | |
name= pkg + '/' + name; | |
} | |
// try to get the entry | |
ZipEntry entry= null; | |
ZipFile zip = null; | |
char[] source= null; | |
try { | |
String fullName; | |
//add the root path if specified | |
if (!fRootPath.equals(IPackageFragmentRoot.DEFAULT_PACKAGEROOT_PATH)) { | |
fullName= fRootPath + '/' + name; | |
} else { | |
fullName= name; | |
} | |
zip = getZip(); | |
entry= zip.getEntry(fullName); | |
if (entry != null) { | |
// now read the source code | |
byte[] bytes= readEntry(zip, entry); | |
if (bytes != null) { | |
try { | |
source= BufferManager.bytesToChar(bytes); | |
} catch (JavaModelException e) { | |
source= null; | |
} | |
} | |
} | |
} catch (CoreException e) { | |
return null; | |
} finally { | |
if (zip != null) { | |
try { | |
zip.close(); | |
} catch(IOException e) {} | |
} | |
} | |
return source; | |
} | |
/** | |
* Returns the SourceRange for the name of the given element, or | |
* {-1, -1} if no source range is known for the name of the element. | |
*/ | |
public SourceRange getNameRange(IJavaElement element) { | |
if (element.getElementType() == IJavaElement.METHOD && ((IMember)element).isBinary()) { | |
element= getUnqualifiedMethodHandle((IMethod)element); | |
} | |
SourceRange[] ranges= (SourceRange[])fSourceRanges.get(element); | |
if (ranges == null) { | |
return fgUnknownRange; | |
} else { | |
return ranges[1]; | |
} | |
} | |
/** | |
* Returns the <code>SourceRange</code> for the given element, or | |
* {-1, -1} if no source range is known for the element. | |
*/ | |
public SourceRange getSourceRange(IJavaElement element) { | |
if (element.getElementType() == IJavaElement.METHOD && ((IMember)element).isBinary()) { | |
element= getUnqualifiedMethodHandle((IMethod)element); | |
} | |
SourceRange[] ranges= (SourceRange[])fSourceRanges.get(element); | |
if (ranges == null) { | |
return fgUnknownRange; | |
} else { | |
return ranges[0]; | |
} | |
} | |
/** | |
* Returns the type with the given <code>typeName</code>. Returns inner classes | |
* as well. | |
*/ | |
protected IType getType(String typeName) { | |
if (fType.getElementName().equals(typeName)) | |
return fType; | |
else | |
return fType.getType(typeName); | |
} | |
/** | |
* Creates a handle that has parameter types that are not | |
* fully qualified so that the correct source is found. | |
*/ | |
protected IJavaElement getUnqualifiedMethodHandle(IMethod method) { | |
String[] qualifiedParameterTypes = method.getParameterTypes(); | |
String[] unqualifiedParameterTypes = new String[qualifiedParameterTypes.length]; | |
for (int i = 0; i < qualifiedParameterTypes.length; i++) { | |
StringBuffer unqualifiedName= new StringBuffer(); | |
String qualifiedName= qualifiedParameterTypes[i]; | |
int count = 0; | |
while (qualifiedName.charAt(count) == Signature.C_ARRAY) { | |
unqualifiedName.append(Signature.C_ARRAY); | |
++count; | |
} | |
if (qualifiedName.charAt(count) == Signature.C_RESOLVED) { | |
unqualifiedName.append(Signature.C_UNRESOLVED); | |
unqualifiedName.append(Signature.getSimpleName(qualifiedName)); | |
} else { | |
unqualifiedName.append(qualifiedName.substring(count, qualifiedName.length())); | |
} | |
unqualifiedParameterTypes[i]= unqualifiedName.toString(); | |
} | |
return ((IType) method.getParent()).getMethod(method.getElementName(), unqualifiedParameterTypes); | |
} | |
/** | |
* Returns the <code>ZipFile</code> that source is located in. | |
*/ | |
public ZipFile getZip() throws CoreException { | |
return fJavaModel.fgJavaModelManager.getZipFile(fZipPath); | |
} | |
/** | |
* Maps the given source code to the given binary type and its children. | |
*/ | |
public void mapSource(IType type, char[] contents) { | |
this.mapSource(type, contents, null); | |
} | |
/** | |
* Maps the given source code to the given binary type and its children. | |
* If a non-null java element is passed, finds the name range for the | |
* given java element without storing it. | |
*/ | |
public ISourceRange mapSource(IType type, char[] contents, IJavaElement searchedElement) { | |
fType= (BinaryType)type; | |
this.searchedElement = searchedElement; | |
this.types = new IType[1]; | |
this.typeDeclarationStarts = new int[1]; | |
this.typeNameRanges = new SourceRange[1]; | |
this.typeDepth = -1; | |
Hashtable oldSourceRanges = (Hashtable)fSourceRanges.clone(); | |
try { | |
IProblemFactory factory= new DefaultProblemFactory(); | |
SourceElementParser parser = new SourceElementParser(this, factory); | |
parser.parseCompilationUnit(new BasicCompilationUnit(contents, type.getElementName() + ".java"/*nonNLS*/), false); | |
if (searchedElement != null) { | |
ISourceRange range = this.getNameRange(searchedElement); | |
return range; | |
} else { | |
return null; | |
} | |
} finally { | |
if (searchedElement != null) { | |
fSourceRanges = oldSourceRanges; | |
} | |
fType= null; | |
this.searchedElement = null; | |
this.types = null; | |
this.typeDeclarationStarts = null; | |
this.typeNameRanges = null; | |
this.typeDepth = -1; | |
} | |
} | |
/** | |
* Returns the contents of the specified zip entry | |
*/ | |
protected byte[] readEntry(ZipFile zip, ZipEntry entry) { | |
InputStream stream = null; | |
try { | |
stream = zip.getInputStream(entry); | |
int remaining = (int) entry.getSize(); | |
byte[] bytes = new byte[remaining]; | |
int offset = 0; | |
while (remaining > 0) { | |
int read = stream.read(bytes, offset, remaining); | |
if (read == -1) | |
break; | |
remaining -= read; | |
offset += read; | |
} | |
return bytes; | |
} catch (IOException e) { | |
return null; | |
} catch (ArrayIndexOutOfBoundsException e) { | |
return null; | |
} finally { | |
if (stream != null) { | |
try { | |
stream.close(); | |
} catch (IOException ioe) { | |
} | |
} | |
} | |
} | |
/** | |
* Sets the mapping for this element to its source ranges for its source range | |
* and name range. | |
* | |
* @see fSourceRanges | |
*/ | |
protected void setSourceRange(IJavaElement element, SourceRange sourceRange, SourceRange nameRange) { | |
fSourceRanges.put(element, new SourceRange[] {sourceRange, nameRange}); | |
} | |
} |