blob: 5272368335faf18efc8779144ae503a2a884e183 [file] [log] [blame]
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;
/**
* imports references
*/
private Hashtable importsTable;
private Hashtable importsCounterTable;
/**
* 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("/" )) { //$NON-NLS-1$
fRootPath = fRootPath.substring(0, fRootPath.lastIndexOf('/'));
}
fJavaModel = model;
fSourceRanges = new Hashtable();
importsTable = new Hashtable();
importsCounterTable = new Hashtable();
}
/**
* @see ISourceElementRequestor
*/
public void acceptImport(
int declarationStart,
int declarationEnd,
char[] name,
boolean onDemand) {
char[][] imports = (char[][]) this.importsTable.get(fType);
int importsCounter;
if (imports == null) {
imports = new char[5][];
importsCounter = 0;
} else {
importsCounter = ((Integer) this.importsCounterTable.get(fType)).intValue();
}
if (imports.length == importsCounter) {
System.arraycopy(
imports,
0,
(imports = new char[importsCounter * 2][]),
0,
importsCounter);
}
imports[importsCounter++] = name;
this.importsTable.put(fType, imports);
this.importsCounterTable.put(fType, new Integer(importsCounter));
}
/**
* @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) {
char[] sourceFileName = info.sourceFileName();
if (sourceFileName == null)
return null; // no source file attribute
String name = new String(sourceFileName);
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.importsTable.remove(fType);
this.importsCounterTable.remove(fType);
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" ), //$NON-NLS-1$
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 });
}
/**
* Return a char[][] array containing the imports of the attached source for the fType binary
*/
public char[][] getImports(BinaryType type) {
char[][] imports = (char[][]) this.importsTable.get(type);
if (imports != null) {
int importsCounter = ((Integer) this.importsCounterTable.get(type)).intValue();
if (imports.length != importsCounter) {
System.arraycopy(
imports,
0,
(imports = new char[importsCounter][]),
0,
importsCounter);
}
}
this.importsTable.put(type, imports);
return imports;
}
}