/*******************************************************************************
 * Copyright (c) 2000, 2017 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.core.runtime.IStatus;
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.ExtraFlags;
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
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.ClassSignature;
import org.eclipse.jdt.internal.compiler.env.EnumConstantSignature;
import org.eclipse.jdt.internal.compiler.env.IBinaryAnnotation;
import org.eclipse.jdt.internal.compiler.env.IBinaryElementValuePair;
import org.eclipse.jdt.internal.compiler.env.IModule;
import org.eclipse.jdt.internal.compiler.env.IModule.IModuleReference;
import org.eclipse.jdt.internal.compiler.env.IModule.IPackageExport;
import org.eclipse.jdt.internal.compiler.env.IModule.IService;
import org.eclipse.jdt.internal.compiler.lookup.TagBits;
import org.eclipse.jdt.internal.compiler.lookup.TypeConstants;
import org.eclipse.jdt.internal.compiler.util.SuffixConstants;
import org.eclipse.jdt.internal.core.util.Util;

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);
	}
	private void addBinaryStandardAnnotations(long annotationTagBits) {
		if ((annotationTagBits & TagBits.AllStandardAnnotationsMask) == 0) {
			return;
		}
		if ((annotationTagBits & TagBits.AnnotationTargetMASK) != 0) {
			char[][] compoundName = TypeConstants.JAVA_LANG_ANNOTATION_TARGET;
			addAnnotationTypeReference(compoundName[compoundName.length-1]);
			addBinaryTargetAnnotation(annotationTagBits);
		}
		if ((annotationTagBits & TagBits.AnnotationRetentionMASK) != 0) {
			char[][] compoundName = TypeConstants.JAVA_LANG_ANNOTATION_RETENTION;
			addAnnotationTypeReference(compoundName[compoundName.length-1]);
			addBinaryRetentionAnnotation(annotationTagBits);
		}
		if ((annotationTagBits & TagBits.AnnotationDeprecated) != 0) {
			char[][] compoundName = TypeConstants.JAVA_LANG_DEPRECATED;
			addAnnotationTypeReference(compoundName[compoundName.length-1]);
		}
		if ((annotationTagBits & TagBits.AnnotationDocumented) != 0) {
			char[][] compoundName = TypeConstants.JAVA_LANG_ANNOTATION_DOCUMENTED;
			addAnnotationTypeReference(compoundName[compoundName.length-1]);
		}
		if ((annotationTagBits & TagBits.AnnotationInherited) != 0) {
			char[][] compoundName = TypeConstants.JAVA_LANG_ANNOTATION_INHERITED;
			addAnnotationTypeReference(compoundName[compoundName.length-1]);
		}
		if ((annotationTagBits & TagBits.AnnotationOverride) != 0) {
			char[][] compoundName = TypeConstants.JAVA_LANG_OVERRIDE;
			addAnnotationTypeReference(compoundName[compoundName.length-1]);
		}
		if ((annotationTagBits & TagBits.AnnotationSuppressWarnings) != 0) {
			char[][] compoundName = TypeConstants.JAVA_LANG_SUPPRESSWARNINGS;
			addAnnotationTypeReference(compoundName[compoundName.length-1]);
		}
		if ((annotationTagBits & TagBits.AnnotationSafeVarargs) != 0) {
			char[][] compoundName = TypeConstants.JAVA_LANG_SAFEVARARGS;
			addAnnotationTypeReference(compoundName[compoundName.length-1]);
		}
		if ((annotationTagBits & TagBits.AnnotationPolymorphicSignature) != 0) {
			char[][] compoundName =
					TypeConstants.JAVA_LANG_INVOKE_METHODHANDLE_$_POLYMORPHICSIGNATURE;
			addAnnotationTypeReference(compoundName[compoundName.length-1]);
		}
	}
	private void addBinaryTargetAnnotation(long bits) {
		char[][] compoundName = null;
		if ((bits & TagBits.AnnotationForAnnotationType) != 0) {
			compoundName = TypeConstants.JAVA_LANG_ANNOTATION_ELEMENTTYPE;
			addTypeReference(compoundName[compoundName.length-1]);
			addFieldReference(TypeConstants.UPPER_ANNOTATION_TYPE);
		}
		if ((bits & TagBits.AnnotationForConstructor) != 0) {
			if (compoundName == null) {
				compoundName = TypeConstants.JAVA_LANG_ANNOTATION_ELEMENTTYPE;
				addTypeReference(compoundName[compoundName.length-1]);
			}
			addFieldReference(TypeConstants.UPPER_CONSTRUCTOR);
		}
		if ((bits & TagBits.AnnotationForField) != 0) {
			if (compoundName == null) {
				compoundName = TypeConstants.JAVA_LANG_ANNOTATION_ELEMENTTYPE;
				addTypeReference(compoundName[compoundName.length-1]);
			}
			addFieldReference(TypeConstants.UPPER_FIELD);
		}
		if ((bits & TagBits.AnnotationForLocalVariable) != 0) {
			if (compoundName == null) {
				compoundName = TypeConstants.JAVA_LANG_ANNOTATION_ELEMENTTYPE;
				addTypeReference(compoundName[compoundName.length-1]);
			}
			addFieldReference(TypeConstants.UPPER_LOCAL_VARIABLE);
		}
		if ((bits & TagBits.AnnotationForMethod) != 0) {
			if (compoundName == null) {
				compoundName = TypeConstants.JAVA_LANG_ANNOTATION_ELEMENTTYPE;
				addTypeReference(compoundName[compoundName.length-1]);
			}
			addFieldReference(TypeConstants.UPPER_METHOD);
		}
		if ((bits & TagBits.AnnotationForPackage) != 0) {
			if (compoundName == null) {
				compoundName = TypeConstants.JAVA_LANG_ANNOTATION_ELEMENTTYPE;
				addTypeReference(compoundName[compoundName.length-1]);
			}
			addFieldReference(TypeConstants.UPPER_PACKAGE);
		}
		if ((bits & TagBits.AnnotationForParameter) != 0) {
			if (compoundName == null) {
				compoundName = TypeConstants.JAVA_LANG_ANNOTATION_ELEMENTTYPE;
				addTypeReference(compoundName[compoundName.length-1]);
			}
			addFieldReference(TypeConstants.UPPER_PARAMETER);
		}
		if ((bits & TagBits.AnnotationForType) != 0) {
			if (compoundName == null) {
				compoundName = TypeConstants.JAVA_LANG_ANNOTATION_ELEMENTTYPE;
				addTypeReference(compoundName[compoundName.length-1]);
			}
			addFieldReference(TypeConstants.TYPE);
		}
		if ((bits & TagBits.AnnotationForModule) != 0) {
			if (compoundName == null) {
				compoundName = TypeConstants.JAVA_LANG_ANNOTATION_ELEMENTTYPE;
				addTypeReference(compoundName[compoundName.length-1]);
			}
			addFieldReference(TypeConstants.UPPER_MODULE);
		}
	}
	private void addBinaryRetentionAnnotation(long bits) {
		char[][] compoundName = TypeConstants.JAVA_LANG_ANNOTATION_RETENTIONPOLICY;
		addTypeReference(compoundName[compoundName.length-1]);
		if ((bits & TagBits.AnnotationRuntimeRetention) == TagBits.AnnotationRuntimeRetention) {
			addFieldReference(TypeConstants.UPPER_RUNTIME);
		} else if ((bits & TagBits.AnnotationClassRetention) != 0) {
			addFieldReference(TypeConstants.UPPER_CLASS);
		} else if ((bits & TagBits.AnnotationSourceRetention) != 0) {
			addFieldReference(TypeConstants.UPPER_SOURCE);
		}
	}
	private void addBinaryAnnotation(IBinaryAnnotation annotation) {
		addAnnotationTypeReference(replace('/', '.', Signature.toCharArray(annotation.getTypeName())));
		IBinaryElementValuePair[] valuePairs = annotation.getElementValuePairs();
		if (valuePairs != null) {
			for (int j=0, vpLength=valuePairs.length; j<vpLength; j++) {
				IBinaryElementValuePair valuePair = valuePairs[j];
				addMethodReference(valuePair.getName(), 0);
				Object pairValue = valuePair.getValue();
				addPairValue(pairValue);
			}
		}
	}
	private void addPairValue(Object pairValue) {
		if (pairValue instanceof EnumConstantSignature) {
			EnumConstantSignature enumConstant = (EnumConstantSignature) pairValue;
			addTypeReference(replace('/', '.', Signature.toCharArray(enumConstant.getTypeName())));
			addNameReference(enumConstant.getEnumConstantName());
		} else if (pairValue instanceof ClassSignature) {
			ClassSignature classConstant = (ClassSignature) pairValue;
			addTypeReference(replace('/', '.', Signature.toCharArray(classConstant.getTypeName())));
		} else if (pairValue instanceof IBinaryAnnotation) {
			addBinaryAnnotation((IBinaryAnnotation) pairValue);
		} else if (pairValue instanceof Object[]) {
			Object[] objects = (Object[]) pairValue;
			for (int i=0,l=objects.length; i<l; i++) {
				addPairValue(objects[i]);
			}
		}
	}
	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, boolean firstIsSynthetic) 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);
					if (firstIsSynthetic && parameterTypesCounter == 0) {
						// skip first synthetic parameter
						firstIsSynthetic = false;
					} else {
						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, char[] className) 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);
					// verify if first parameter is synthetic
					if (className != null && parameterTypesCounter == 0) {
						char[] classSignature = Signature.createCharArrayTypeSignature(className, true);
						int length = indexOfSemiColon-i+1;
						if (classSignature.length > (length+1)) {
							// synthetic means that parameter type has same signature than given class
							for (int j=i, k=0; j<indexOfSemiColon; j++, k++) {
								if (!(signature[j] == classSignature[k] || (signature[j] == '/' && classSignature[k] == '.' ))) {
									parameterTypesCounter++;
									break;
								}
							}
						} else {
							parameterTypesCounter++;
						}
						className = null; // do not verify following parameters
					} else {
						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)) {
						// get class name and see if it's a local type or not
						char[] className = extractClassName(constantPoolOffsets, reader, i);
						boolean localType = false;
						if (className !=  null) {
							for (int c = 0, max = className.length; c < max; c++) {
								switch (className[c]) {
									case '/':
										className[c] = '.';
										break;
									case '$':
										localType = true;
										break;
								}
							}
						}
						// add a constructor reference, use class name to extract arg count if it's a local type to remove synthetic parameter
						addConstructorReference(className, extractArgCount(type, localType?className:null));
					} else {
						// add a method reference
						addMethodReference(name, extractArgCount(type, null));
					}
					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 {
			final byte[] contents = this.document.getByteContents();
			// see https://bugs.eclipse.org/bugs/show_bug.cgi?id=107124
			// contents can potentially be null if a IOException occurs while retrieving the contents
			if (contents == null) return;
			final String path = this.document.getPath();
			ClassFileReader reader = new ClassFileReader(contents, path == null ? null : path.toCharArray());
			
			IModule module = reader.getModuleDeclaration();
			if (module != null) {
				indexModule(module);
				return;
			}

			// 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;
			boolean isNestedType = reader.isNestedType();
			if (isNestedType) {
				if (reader.isAnonymous()) {
					name = CharOperation.NO_CHAR;
				} else {
					name = reader.getInnerSourceName();
				}
				if (reader.isLocal() || reader.isAnonymous()) {
					// set specific ['0'] value for local and anonymous to be able to filter them
					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};
			int modifiers = reader.getModifiers();
			switch (TypeDeclaration.kind(modifiers)) {
				case TypeDeclaration.CLASS_DECL :
					char[] superclass = replace('/', '.', reader.getSuperclassName());
					addClassDeclaration(modifiers, packageName, name, enclosingTypeNames, superclass, superinterfaces, typeParameterSignatures, false);
					break;
				case TypeDeclaration.INTERFACE_DECL :
					addInterfaceDeclaration(modifiers, packageName, name, enclosingTypeNames, superinterfaces, typeParameterSignatures, false);
					break;
				case TypeDeclaration.ENUM_DECL :
					superclass = replace('/', '.', reader.getSuperclassName());
					addEnumDeclaration(modifiers, packageName, name, enclosingTypeNames, superclass, superinterfaces, false);
					break;
				case TypeDeclaration.ANNOTATION_TYPE_DECL :
					addAnnotationTypeDeclaration(modifiers, packageName, name, enclosingTypeNames, false);
					break;
			}

			// Look for references in class annotations
			IBinaryAnnotation[] annotations = reader.getAnnotations();
			if (annotations != null) {
				for (int a=0, length=annotations.length; a<length; a++) {
					IBinaryAnnotation annotation = annotations[a];
					addBinaryAnnotation(annotation);
				}
			}
			long tagBits = reader.getTagBits() & TagBits.AllStandardAnnotationsMask;
			if (tagBits != 0) {
				addBinaryStandardAnnotations(tagBits);
			}
			
			int extraFlags = ExtraFlags.getExtraFlags(reader);

			// first reference all methods declarations and field declarations
			MethodInfo[] methods = (MethodInfo[]) reader.getMethods();
			boolean noConstructor = true;
			if (methods != null) {
				for (int i = 0, max = methods.length; i < max; i++) {
					MethodInfo method = methods[i];
					boolean isConstructor = method.isConstructor();
					char[] descriptor = method.getMethodDescriptor();
					char[][] parameterTypes = decodeParameterTypes(descriptor, isConstructor && isNestedType);
					char[] returnType = decodeReturnType(descriptor);
					char[][] exceptionTypes = replace('/', '.', method.getExceptionTypeNames());
					if (isConstructor) {
						noConstructor = false;
						char[] signature = method.getGenericSignature();
						if (signature == null) {
							if (reader.isNestedType() && ((modifiers & ClassFileConstants.AccStatic) == 0)) {
								signature = removeFirstSyntheticParameter(descriptor);
							} else {
								signature = descriptor;
							}
						}
						addConstructorDeclaration(
								name,
								parameterTypes == null ? 0 : parameterTypes.length,
								signature,	
								parameterTypes,
								method.getArgumentNames(),
								method.getModifiers(),
								packageName,
								modifiers,
								exceptionTypes,
								extraFlags);
					} else {
						if (!method.isClinit()) {
							char[] selector = method.getSelector();
							addMethodDeclaration(selector, parameterTypes, returnType, exceptionTypes);
							char[] signature = method.getGenericSignature();
							if (signature == null) {
								signature = descriptor;
							}
							if (name.length > 0)  {
								addMethodDeclaration(
										name,
										null,
										selector,
										parameterTypes == null ? 0 : parameterTypes.length,
												signature,	
												parameterTypes,
												method.getArgumentNames(),
												returnType,
												method.getModifiers(),
												packageName,
												modifiers,
												exceptionTypes,
												extraFlags);
							}
						}
					}
					// look for references in method annotations
					annotations = method.getAnnotations();
					if (annotations != null) {
						for (int a=0, length=annotations.length; a<length; a++) {
							IBinaryAnnotation annotation = annotations[a];
							addBinaryAnnotation(annotation);
						}
					}
					tagBits = method.getTagBits() & TagBits.AllStandardAnnotationsMask;
					if (tagBits != 0) {
						addBinaryStandardAnnotations(tagBits);
					}
				}
			}
			if (noConstructor) {
				addDefaultConstructorDeclaration(className, packageName, modifiers, extraFlags);
			}
			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);
					// look for references in field annotations
					annotations = field.getAnnotations();
					if (annotations != null) {
						for (int a=0, length=annotations.length; a<length; a++) {
							IBinaryAnnotation annotation = annotations[a];
							addBinaryAnnotation(annotation);
						}
					}
					tagBits = field.getTagBits() & TagBits.AllStandardAnnotationsMask;
					if (tagBits != 0) {
						addBinaryStandardAnnotations(tagBits);
					}
				}
			}
			// record all references found inside the .class file
			extractReferenceFromConstantPool(contents, reader);
		} catch (ClassFormatException e) {
			// ignore
			this.document.removeAllIndexEntries();
			Util.log(IStatus.WARNING, "The Java indexing could not index " + this.document.getPath() + ". This .class file doesn't follow the class file format specification. Please report this issue against the .class file vendor"); //$NON-NLS-1$ //$NON-NLS-2$
		} catch (RuntimeException e) {
			// https://bugs.eclipse.org/bugs/show_bug.cgi?id=182154
			// logging the entry that could not be indexed and continue with the next one
			// we remove all entries relative to the boggus document
			this.document.removeAllIndexEntries();
			Util.log(IStatus.WARNING, "The Java indexing could not index " + this.document.getPath() + ". This .class file doesn't follow the class file format specification. Please report this issue against the .class file vendor"); //$NON-NLS-1$ //$NON-NLS-2$
		}
	}
	
	private void indexModule(IModule module) {
		addModuleDeclaration(module.name());
		IModuleReference[] requiredModules = module.requires();
		if (requiredModules != null) {
			for (IModuleReference req : requiredModules) {
				addModuleReference(req.name());
			}
		}
		indexPackageVisibilityDirective(module.exports());
		indexPackageVisibilityDirective(module.opens());
		char[][] refUsed = module.uses();
		if (refUsed != null) {
			for (char[] ref : refUsed) {
				indexTypeReference(ref);
			}
		}
		IService[] services = module.provides();
		if (services != null) {
			for (IService service : services) {
				indexTypeReference(service.name());
				indexTypeReferences(service.with());
			}
		}
	}
	private void indexPackageVisibilityDirective(IPackageExport[] exportedPackages) {
		if (exportedPackages != null) {
			for (IPackageExport pack : exportedPackages) {
				addModuleExportedPackages(pack.name());
				char[][] tgts = pack.targets();
				if (tgts == null || tgts.equals(CharOperation.NO_CHAR_CHAR)) continue;
				for (char[] tgt : tgts) {
					if (tgt != null && !tgt.equals(CharOperation.NO_CHAR)) 
						addModuleReference(tgt);
				}
			}
		}
	}
	private void indexTypeReferences(char[][] ref) {
		if (ref == null || ref.equals(CharOperation.NO_CHAR))
			return;
		for (int i = 0; i < ref.length; i++) {
			addTypeReference(ref[i]);
		}
	}
	private void indexTypeReference(char[] ref) {
		if (ref == null || ref.equals(CharOperation.NO_CHAR))
			return;
		addTypeReference(ref);
	}
	
	private char[] removeFirstSyntheticParameter(char[] descriptor) {
		if (descriptor == null) return null;
		if (descriptor.length < 3) return descriptor;
		if (descriptor[0] != '(') return descriptor;
		if (descriptor[1] != ')') {
			// remove the first synthetic parameter
			int start = org.eclipse.jdt.internal.compiler.util.Util.scanTypeSignature(descriptor, 1) + 1;
			int length = descriptor.length - start;
			char[] signature = new char[length + 1];
			signature[0] = descriptor[0];
			System.arraycopy(descriptor, start, signature, 1, length);
			return signature;
		} else {
			return descriptor;
		}
	}
	/*
	 * 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;
	}
}
