| /******************************************************************************* |
| * 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; |
| } |
| } |