blob: a1090f66cadc67cd40d131bbc0f8b9c499b4e81f [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2004 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
*******************************************************************************/
package org.eclipse.jdt.internal.core.search.indexing;
import org.eclipse.jdt.core.Signature;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.core.search.SearchDocument;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException;
import org.eclipse.jdt.internal.compiler.classfmt.FieldInfo;
import org.eclipse.jdt.internal.compiler.classfmt.MethodInfo;
import org.eclipse.jdt.internal.compiler.env.IGenericType;
import org.eclipse.jdt.internal.compiler.util.SuffixConstants;
public class BinaryIndexer extends AbstractIndexer implements SuffixConstants {
private static final char[] BYTE = "byte".toCharArray(); //$NON-NLS-1$
private static final char[] CHAR = "char".toCharArray(); //$NON-NLS-1$
private static final char[] DOUBLE = "double".toCharArray(); //$NON-NLS-1$
private static final char[] FLOAT = "float".toCharArray(); //$NON-NLS-1$
private static final char[] INT = "int".toCharArray(); //$NON-NLS-1$
private static final char[] LONG = "long".toCharArray(); //$NON-NLS-1$
private static final char[] SHORT = "short".toCharArray(); //$NON-NLS-1$
private static final char[] BOOLEAN = "boolean".toCharArray(); //$NON-NLS-1$
private static final char[] VOID = "void".toCharArray(); //$NON-NLS-1$
private static final char[] INIT = "<init>".toCharArray(); //$NON-NLS-1$
public BinaryIndexer(SearchDocument document) {
super(document);
}
public void addTypeReference(char[] typeName) {
int length = typeName.length;
if (length > 2 && typeName[length - 2] == '$') {
switch (typeName[length - 1]) {
case '0' :
case '1' :
case '2' :
case '3' :
case '4' :
case '5' :
case '6' :
case '7' :
case '8' :
case '9' :
return; // skip local type names
}
}
// consider that A$B is a member type: so replace '$' with '.'
// (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=40116)
typeName = CharOperation.replaceOnCopy(typeName, '$', '.'); // copy it so the original is not modified
super.addTypeReference(typeName);
}
/**
* For example:
* - int foo(String[]) is ([Ljava/lang/String;)I => java.lang.String[] in a char[][]
* - void foo(int) is (I)V ==> int
*/
private void convertToArrayType(char[][] parameterTypes, int counter, int arrayDim) {
int length = parameterTypes[counter].length;
char[] arrayType = new char[length + arrayDim*2];
System.arraycopy(parameterTypes[counter], 0, arrayType, 0, length);
for (int i = 0; i < arrayDim; i++) {
arrayType[length + (i * 2)] = '[';
arrayType[length + (i * 2) + 1] = ']';
}
parameterTypes[counter] = arrayType;
}
/**
* For example:
* - int foo(String[]) is ([Ljava/lang/String;)I => java.lang.String[] in a char[][]
* - void foo(int) is (I)V ==> int
*/
private char[] convertToArrayType(char[] typeName, int arrayDim) {
int length = typeName.length;
char[] arrayType = new char[length + arrayDim*2];
System.arraycopy(typeName, 0, arrayType, 0, length);
for (int i = 0; i < arrayDim; i++) {
arrayType[length + (i * 2)] = '[';
arrayType[length + (i * 2) + 1] = ']';
}
return arrayType;
}
private char[] decodeFieldType(char[] signature) throws ClassFormatException {
if (signature == null) return null;
int arrayDim = 0;
for (int i = 0, max = signature.length; i < max; i++) {
switch(signature[i]) {
case 'B':
if (arrayDim > 0)
return convertToArrayType(BYTE, arrayDim);
return BYTE;
case 'C':
if (arrayDim > 0)
return convertToArrayType(CHAR, arrayDim);
return CHAR;
case 'D':
if (arrayDim > 0)
return convertToArrayType(DOUBLE, arrayDim);
return DOUBLE;
case 'F':
if (arrayDim > 0)
return convertToArrayType(FLOAT, arrayDim);
return FLOAT;
case 'I':
if (arrayDim > 0)
return convertToArrayType(INT, arrayDim);
return INT;
case 'J':
if (arrayDim > 0)
return convertToArrayType(LONG, arrayDim);
return LONG;
case 'L':
int indexOfSemiColon = CharOperation.indexOf(';', signature, i+1);
if (indexOfSemiColon == -1) throw new ClassFormatException(ClassFormatException.ErrInvalidMethodSignature);
if (arrayDim > 0) {
return convertToArrayType(replace('/','.',CharOperation.subarray(signature, i + 1, indexOfSemiColon)), arrayDim);
}
return replace('/','.',CharOperation.subarray(signature, i + 1, indexOfSemiColon));
case 'S':
if (arrayDim > 0)
return convertToArrayType(SHORT, arrayDim);
return SHORT;
case 'Z':
if (arrayDim > 0)
return convertToArrayType(BOOLEAN, arrayDim);
return BOOLEAN;
case 'V':
return VOID;
case '[':
arrayDim++;
break;
default:
throw new ClassFormatException(ClassFormatException.ErrInvalidMethodSignature);
}
}
return null;
}
/**
* For example:
* - int foo(String[]) is ([Ljava/lang/String;)I => java.lang.String[] in a char[][]
* - void foo(int) is (I)V ==> int
*/
private char[][] decodeParameterTypes(char[] signature) throws ClassFormatException {
if (signature == null) return null;
int indexOfClosingParen = CharOperation.lastIndexOf(')', signature);
if (indexOfClosingParen == 1) {
// there is no parameter
return null;
}
if (indexOfClosingParen == -1) {
throw new ClassFormatException(ClassFormatException.ErrInvalidMethodSignature);
}
char[][] parameterTypes = new char[3][];
int parameterTypesCounter = 0;
int arrayDim = 0;
for (int i = 1; i < indexOfClosingParen; i++) {
if (parameterTypesCounter == parameterTypes.length) {
// resize
System.arraycopy(parameterTypes, 0, (parameterTypes = new char[parameterTypesCounter * 2][]), 0, parameterTypesCounter);
}
switch(signature[i]) {
case 'B':
parameterTypes[parameterTypesCounter++] = BYTE;
if (arrayDim > 0)
convertToArrayType(parameterTypes, parameterTypesCounter-1, arrayDim);
arrayDim = 0;
break;
case 'C':
parameterTypes[parameterTypesCounter++] = CHAR;
if (arrayDim > 0)
convertToArrayType(parameterTypes, parameterTypesCounter-1, arrayDim);
arrayDim = 0;
break;
case 'D':
parameterTypes[parameterTypesCounter++] = DOUBLE;
if (arrayDim > 0)
convertToArrayType(parameterTypes, parameterTypesCounter-1, arrayDim);
arrayDim = 0;
break;
case 'F':
parameterTypes[parameterTypesCounter++] = FLOAT;
if (arrayDim > 0)
convertToArrayType(parameterTypes, parameterTypesCounter-1, arrayDim);
arrayDim = 0;
break;
case 'I':
parameterTypes[parameterTypesCounter++] = INT;
if (arrayDim > 0)
convertToArrayType(parameterTypes, parameterTypesCounter-1, arrayDim);
arrayDim = 0;
break;
case 'J':
parameterTypes[parameterTypesCounter++] = LONG;
if (arrayDim > 0)
convertToArrayType(parameterTypes, parameterTypesCounter-1, arrayDim);
arrayDim = 0;
break;
case 'L':
int indexOfSemiColon = CharOperation.indexOf(';', signature, i+1);
if (indexOfSemiColon == -1) throw new ClassFormatException(ClassFormatException.ErrInvalidMethodSignature);
parameterTypes[parameterTypesCounter++] = replace('/','.',CharOperation.subarray(signature, i + 1, indexOfSemiColon));
if (arrayDim > 0)
convertToArrayType(parameterTypes, parameterTypesCounter-1, arrayDim);
i = indexOfSemiColon;
arrayDim = 0;
break;
case 'S':
parameterTypes[parameterTypesCounter++] = SHORT;
if (arrayDim > 0)
convertToArrayType(parameterTypes, parameterTypesCounter-1, arrayDim);
arrayDim = 0;
break;
case 'Z':
parameterTypes[parameterTypesCounter++] = BOOLEAN;
if (arrayDim > 0)
convertToArrayType(parameterTypes, parameterTypesCounter-1, arrayDim);
arrayDim = 0;
break;
case '[':
arrayDim++;
break;
default:
throw new ClassFormatException(ClassFormatException.ErrInvalidMethodSignature);
}
}
if (parameterTypes.length != parameterTypesCounter) {
System.arraycopy(parameterTypes, 0, parameterTypes = new char[parameterTypesCounter][], 0, parameterTypesCounter);
}
return parameterTypes;
}
private char[] decodeReturnType(char[] signature) throws ClassFormatException {
if (signature == null) return null;
int indexOfClosingParen = CharOperation.lastIndexOf(')', signature);
if (indexOfClosingParen == -1) throw new ClassFormatException(ClassFormatException.ErrInvalidMethodSignature);
int arrayDim = 0;
for (int i = indexOfClosingParen + 1, max = signature.length; i < max; i++) {
switch(signature[i]) {
case 'B':
if (arrayDim > 0)
return convertToArrayType(BYTE, arrayDim);
return BYTE;
case 'C':
if (arrayDim > 0)
return convertToArrayType(CHAR, arrayDim);
return CHAR;
case 'D':
if (arrayDim > 0)
return convertToArrayType(DOUBLE, arrayDim);
return DOUBLE;
case 'F':
if (arrayDim > 0)
return convertToArrayType(FLOAT, arrayDim);
return FLOAT;
case 'I':
if (arrayDim > 0)
return convertToArrayType(INT, arrayDim);
return INT;
case 'J':
if (arrayDim > 0)
return convertToArrayType(LONG, arrayDim);
return LONG;
case 'L':
int indexOfSemiColon = CharOperation.indexOf(';', signature, i+1);
if (indexOfSemiColon == -1) throw new ClassFormatException(ClassFormatException.ErrInvalidMethodSignature);
if (arrayDim > 0) {
return convertToArrayType(replace('/','.',CharOperation.subarray(signature, i + 1, indexOfSemiColon)), arrayDim);
}
return replace('/','.',CharOperation.subarray(signature, i + 1, indexOfSemiColon));
case 'S':
if (arrayDim > 0)
return convertToArrayType(SHORT, arrayDim);
return SHORT;
case 'Z':
if (arrayDim > 0)
return convertToArrayType(BOOLEAN, arrayDim);
return BOOLEAN;
case 'V':
return VOID;
case '[':
arrayDim++;
break;
default:
throw new ClassFormatException(ClassFormatException.ErrInvalidMethodSignature);
}
}
return null;
}
private int extractArgCount(char[] signature) throws ClassFormatException {
int indexOfClosingParen = CharOperation.lastIndexOf(')', signature);
if (indexOfClosingParen == 1) {
// there is no parameter
return 0;
}
if (indexOfClosingParen == -1) {
throw new ClassFormatException(ClassFormatException.ErrInvalidMethodSignature);
}
int parameterTypesCounter = 0;
for (int i = 1; i < indexOfClosingParen; i++) {
switch(signature[i]) {
case 'B':
case 'C':
case 'D':
case 'F':
case 'I':
case 'J':
case 'S':
case 'Z':
parameterTypesCounter++;
break;
case 'L':
int indexOfSemiColon = CharOperation.indexOf(';', signature, i+1);
if (indexOfSemiColon == -1) throw new ClassFormatException(ClassFormatException.ErrInvalidMethodSignature);
parameterTypesCounter++;
i = indexOfSemiColon;
break;
case '[':
break;
default:
throw new ClassFormatException(ClassFormatException.ErrInvalidMethodSignature);
}
}
return parameterTypesCounter;
}
private char[] extractClassName(int[] constantPoolOffsets, ClassFileReader reader, int index) {
// the entry at i has to be a field ref or a method/interface method ref.
int class_index = reader.u2At(constantPoolOffsets[index] + 1);
int utf8Offset = constantPoolOffsets[reader.u2At(constantPoolOffsets[class_index] + 1)];
return reader.utf8At(utf8Offset + 3, reader.u2At(utf8Offset + 1));
}
private char[] extractName(int[] constantPoolOffsets, ClassFileReader reader, int index) {
int nameAndTypeIndex = reader.u2At(constantPoolOffsets[index] + 3);
int utf8Offset = constantPoolOffsets[reader.u2At(constantPoolOffsets[nameAndTypeIndex] + 1)];
return reader.utf8At(utf8Offset + 3, reader.u2At(utf8Offset + 1));
}
private char[] extractClassReference(int[] constantPoolOffsets, ClassFileReader reader, int index) {
// the entry at i has to be a class ref.
int utf8Offset = constantPoolOffsets[reader.u2At(constantPoolOffsets[index] + 1)];
return reader.utf8At(utf8Offset + 3, reader.u2At(utf8Offset + 1));
}
/**
* Extract all type, method, field and interface method references from the constant pool
*/
private void extractReferenceFromConstantPool(byte[] contents, ClassFileReader reader) throws ClassFormatException {
int[] constantPoolOffsets = reader.getConstantPoolOffsets();
int constantPoolCount = constantPoolOffsets.length;
for (int i = 1; i < constantPoolCount; i++) {
int tag = reader.u1At(constantPoolOffsets[i]);
/**
* u1 tag
* u2 class_index
* u2 name_and_type_index
*/
char[] name = null;
char[] type = null;
switch (tag) {
case ClassFileConstants.FieldRefTag :
// add reference to the class/interface and field name and type
name = extractName(constantPoolOffsets, reader, i);
addFieldReference(name);
break;
case ClassFileConstants.MethodRefTag :
// add reference to the class and method name and type
case ClassFileConstants.InterfaceMethodRefTag :
// add reference to the interface and method name and type
name = extractName(constantPoolOffsets, reader, i);
type = extractType(constantPoolOffsets, reader, i);
if (CharOperation.equals(INIT, name)) {
// add a constructor reference
char[] className = replace('/', '.', extractClassName(constantPoolOffsets, reader, i)); // so that it looks like java.lang.String
addConstructorReference(className, extractArgCount(type));
} else {
// add a method reference
addMethodReference(name, extractArgCount(type));
}
break;
case ClassFileConstants.ClassTag :
// add a type reference
name = extractClassReference(constantPoolOffsets, reader, i);
if (name.length > 0 && name[0] == '[')
break; // skip over array references
name = replace('/', '.', name); // so that it looks like java.lang.String
addTypeReference(name);
// also add a simple reference on each segment of the qualification (see http://bugs.eclipse.org/bugs/show_bug.cgi?id=24741)
char[][] qualification = CharOperation.splitOn('.', name);
for (int j = 0, length = qualification.length; j < length; j++) {
addNameReference(qualification[j]);
}
break;
}
}
}
private char[] extractType(int[] constantPoolOffsets, ClassFileReader reader, int index) {
int constantPoolIndex = reader.u2At(constantPoolOffsets[index] + 3);
int utf8Offset = constantPoolOffsets[reader.u2At(constantPoolOffsets[constantPoolIndex] + 3)];
return reader.utf8At(utf8Offset + 3, reader.u2At(utf8Offset + 1));
}
public void indexDocument() {
try {
byte[] contents = this.document.getByteContents();
ClassFileReader reader = new ClassFileReader(contents, this.document.getPath().toCharArray());
// first add type references
char[] className = replace('/', '.', reader.getName()); // looks like java/lang/String
// need to extract the package name and the simple name
int packageNameIndex = CharOperation.lastIndexOf('.', className);
char[] packageName = null;
char[] name = null;
if (packageNameIndex >= 0) {
packageName = CharOperation.subarray(className, 0, packageNameIndex);
name = CharOperation.subarray(className, packageNameIndex + 1, className.length);
} else {
packageName = CharOperation.NO_CHAR;
name = className;
}
char[] enclosingTypeName = null;
if (reader.isNestedType()) {
if (reader.isAnonymous()) {
name = CharOperation.NO_CHAR;
} else {
name = reader.getInnerSourceName();
}
if (reader.isLocal() || reader.isAnonymous()) {
enclosingTypeName = ONE_ZERO;
} else {
char[] fullEnclosingName = reader.getEnclosingTypeName();
int nameLength = fullEnclosingName.length - packageNameIndex - 1;
if (nameLength <= 0) {
// See PR 1GIR345: ITPJCORE:ALL - Indexer: NegativeArraySizeException
return;
}
enclosingTypeName = new char[nameLength];
System.arraycopy(fullEnclosingName, packageNameIndex + 1, enclosingTypeName, 0, nameLength);
}
}
// type parameters
char[][] typeParameterSignatures = null;
char[] genericSignature = reader.getGenericSignature();
if (genericSignature != null) {
CharOperation.replace(genericSignature, '/', '.');
typeParameterSignatures = Signature.getTypeParameters(genericSignature);
}
// eliminate invalid innerclasses (1G4KCF7)
if (name == null) return;
char[][] superinterfaces = replace('/', '.', reader.getInterfaceNames());
char[][] enclosingTypeNames = enclosingTypeName == null ? null : new char[][] {enclosingTypeName};
switch (reader.getKind()) {
case IGenericType.CLASS_DECL :
char[] superclass = replace('/', '.', reader.getSuperclassName());
addClassDeclaration(reader.getModifiers(), packageName, name, enclosingTypeNames, superclass, superinterfaces, typeParameterSignatures);
break;
case IGenericType.INTERFACE_DECL :
addInterfaceDeclaration(reader.getModifiers(), packageName, name, enclosingTypeNames, superinterfaces, typeParameterSignatures);
break;
case IGenericType.ENUM_DECL :
addEnumDeclaration(reader.getModifiers(), packageName, name, enclosingTypeNames, superinterfaces);
break;
case IGenericType.ANNOTATION_TYPE_DECL :
addAnnotationTypeDeclaration(reader.getModifiers(), packageName, name, enclosingTypeNames);
break;
}
// first reference all methods declarations and field declarations
MethodInfo[] methods = (MethodInfo[]) reader.getMethods();
if (methods != null) {
for (int i = 0, max = methods.length; i < max; i++) {
MethodInfo method = methods[i];
char[] descriptor = method.getMethodDescriptor();
char[][] parameterTypes = decodeParameterTypes(descriptor);
char[] returnType = decodeReturnType(descriptor);
char[][] exceptionTypes = replace('/', '.', method.getExceptionTypeNames());
if (method.isConstructor()) {
addConstructorDeclaration(className, parameterTypes, exceptionTypes);
} else {
if (!method.isClinit()) {
addMethodDeclaration(method.getSelector(), parameterTypes, returnType, exceptionTypes);
}
}
}
}
FieldInfo[] fields = (FieldInfo[]) reader.getFields();
if (fields != null) {
for (int i = 0, max = fields.length; i < max; i++) {
FieldInfo field = fields[i];
char[] fieldName = field.getName();
char[] fieldType = decodeFieldType(replace('/', '.', field.getTypeName()));
addFieldDeclaration(fieldType, fieldName);
}
}
// record all references found inside the .class file
extractReferenceFromConstantPool(contents, reader);
} catch (ClassFormatException e) {
// ignore
}
}
/*
* Modify the array by replacing all occurences of toBeReplaced with newChar
*/
private char[][] replace(char toBeReplaced, char newChar, char[][] array) {
if (array == null) return null;
for (int i = 0, max = array.length; i < max; i++) {
replace(toBeReplaced, newChar, array[i]);
}
return array;
}
/*
* Modify the array by replacing all occurences of toBeReplaced with newChar
*/
private char[] replace(char toBeReplaced, char newChar, char[] array) {
if (array == null) return null;
for (int i = 0, max = array.length; i < max; i++) {
if (array[i] == toBeReplaced) {
array[i] = newChar;
}
}
return array;
}
}