/*******************************************************************************
 * Copyright (c) 2000, 2015 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Tim Tromey - update method signature syntax (bug 31507)
 *******************************************************************************/
package org.eclipse.jdi.internal;

import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import org.eclipse.jdi.internal.jdwp.JdwpCommandPacket;
import org.eclipse.jdi.internal.jdwp.JdwpMethodID;
import org.eclipse.jdi.internal.jdwp.JdwpReplyPacket;

import com.ibm.icu.text.MessageFormat;
import com.sun.jdi.AbsentInformationException;
import com.sun.jdi.ClassLoaderReference;
import com.sun.jdi.ClassNotLoadedException;
import com.sun.jdi.LocalVariable;
import com.sun.jdi.Locatable;
import com.sun.jdi.Location;
import com.sun.jdi.Method;
import com.sun.jdi.Type;

/**
 * Implementation of com.sun.jdi.Method.
 */
public class MethodImpl extends TypeComponentImpl implements Method, Locatable {
	/** InvokeOptions Constants. */
	public static final int INVOKE_SINGLE_THREADED_JDWP = 0x01;
	public static final int INVOKE_NONVIRTUAL_JDWP = 0x02;

	/** Map with Strings for flag bits. */
	private static String[] fgInvokeOptions = null;

	/** MethodTypeID that corresponds to this reference. */
	private JdwpMethodID fMethodID;

	/** The following are the stored results of JDWP calls. */
	private List<LocalVariable> fVariables = null;
	private long fLowestValidCodeIndex = -1;
	private long fHighestValidCodeIndex = -1;
	private Map<Long, Integer> fCodeIndexToLine = null;
	private Map<Integer, List<Long>> fLineToCodeIndexes = null;
	private Map<String, Map<String, List<Location>>> fStratumAllLineLocations = null;
	private int fArgumentSlotsCount = -1;
	private List<LocalVariable> fArguments = null;
	private List<Type> fArgumentTypes = null;
	private List<String> fArgumentTypeNames = null;
	private List<String> fArgumentTypeSignatures = null;
	private byte[] fByteCodes = null;
	private long[] fCodeIndexTable;
	private int[] fJavaStratumLineNumberTable;

	private String fReturnTypeName = null;

	/**
	 * Creates new MethodImpl.
	 */
	public MethodImpl(VirtualMachineImpl vmImpl,
			ReferenceTypeImpl declaringType, JdwpMethodID methodID,
			String name, String signature, String genericSignature,
			int modifierBits) {
		super(
				"Method", vmImpl, declaringType, name, signature, genericSignature, modifierBits); //$NON-NLS-1$
		fMethodID = methodID;
	}

	/**
	 * Flushes all stored Jdwp results.
	 */
	protected void flushStoredJdwpResults() {
		fVariables = null;
		fLowestValidCodeIndex = -1;
		fHighestValidCodeIndex = -1;
		fCodeIndexToLine = null;
		fLineToCodeIndexes = null;
		fStratumAllLineLocations = null;
		fCodeIndexTable = null;
		fJavaStratumLineNumberTable = null;
		fArgumentSlotsCount = -1;
		fArguments = null;
		fArgumentTypes = null;
		fArgumentTypeNames = null;
		fArgumentTypeSignatures = null;
		fByteCodes = null;
	}

	/**
	 * @return Returns methodID of method.
	 */
	protected JdwpMethodID getMethodID() {
		return fMethodID;
	}

	/**
	 * @return Returns map of location to line number.
	 */
	protected Map<Long, Integer> javaStratumCodeIndexToLine()
			throws AbsentInformationException {
		if (isAbstract()) {
			return Collections.EMPTY_MAP;
		}
		getLineTable();
		return fCodeIndexToLine;
	}

	/**
	 * @return Returns map of line number to locations.
	 */
	protected List<Long> javaStratumLineToCodeIndexes(int line) throws AbsentInformationException {
		if (isAbstract() || isNative()) {
			return null;
		}
		getLineTable();

		return fLineToCodeIndexes.get(new Integer(line));
	}

	/**
	 * Gets line table from VM.
	 */
	private void getLineTable() throws AbsentInformationException {
		if (isObsolete()) {
			return;
		}
		if (fCodeIndexToLine != null) {
			if (fCodeIndexToLine.isEmpty()) {
				throw new AbsentInformationException(
						JDIMessages.MethodImpl_Got_empty_line_number_table_for_this_method_1);
			}
			return;
		}

		initJdwpRequest();
		try {
			ByteArrayOutputStream outBytes = new ByteArrayOutputStream();
			DataOutputStream outData = new DataOutputStream(outBytes);
			writeWithReferenceType(this, outData);

			JdwpReplyPacket replyPacket = requestVM(
					JdwpCommandPacket.M_LINE_TABLE, outBytes);
			switch (replyPacket.errorCode()) {
			case JdwpReplyPacket.ABSENT_INFORMATION:
				throw new AbsentInformationException(
						JDIMessages.MethodImpl_No_line_number_information_available_2);
			case JdwpReplyPacket.NATIVE_METHOD:
				throw new AbsentInformationException(
						JDIMessages.MethodImpl_No_line_number_information_available_2);
			}
			defaultReplyErrorHandler(replyPacket.errorCode());

			DataInputStream replyData = replyPacket.dataInStream();
			fLowestValidCodeIndex = readLong("lowest index", replyData); //$NON-NLS-1$
			fHighestValidCodeIndex = readLong("highest index", replyData); //$NON-NLS-1$
			int nrOfElements = readInt("elements", replyData); //$NON-NLS-1$
			fCodeIndexToLine = new HashMap<>();
			fLineToCodeIndexes = new HashMap<>();
			if (nrOfElements == 0) {
				throw new AbsentInformationException(
						JDIMessages.MethodImpl_Got_empty_line_number_table_for_this_method_3);
			}
			fCodeIndexTable = new long[nrOfElements];
			fJavaStratumLineNumberTable = new int[nrOfElements];
			for (int i = 0; i < nrOfElements; i++) {
				long lineCodeIndex = readLong("code index", replyData); //$NON-NLS-1$
				Long lineCodeIndexLong = new Long(lineCodeIndex);
				int lineNr = readInt("line nr", replyData); //$NON-NLS-1$
				Integer lineNrInt = new Integer(lineNr);

				// Add entry to code-index to line mapping.
				fCodeIndexToLine.put(lineCodeIndexLong, lineNrInt);

				fCodeIndexTable[i] = lineCodeIndex;
				fJavaStratumLineNumberTable[i] = lineNr;

				List<Long> lineNrEntry = fLineToCodeIndexes.get(lineNrInt);
				if (lineNrEntry == null) {
					lineNrEntry = new ArrayList<>();
					fLineToCodeIndexes.put(lineNrInt, lineNrEntry);
				}
				lineNrEntry.add(lineCodeIndexLong);
			}
		} catch (IOException e) {
			fCodeIndexToLine = null;
			fLineToCodeIndexes = null;
			defaultIOExceptionHandler(e);
		} finally {
			handledJdwpRequest();
		}
	}

	/**
	 * @return Returns the line number that corresponds to the given
	 *         lineCodeIndex.
	 */
	protected int javaStratumLineNumber(long lineCodeIndex)
			throws AbsentInformationException {
		if (isAbstract() || isNative() || isObsolete()) {
			return -1;
		}
		getLineTable();
		if (lineCodeIndex > fHighestValidCodeIndex) {
			throw new AbsentInformationException(JDIMessages.MethodImpl_Invalid_code_index_of_a_location_given_4);
		}

		Long lineCodeIndexObj;
		Integer lineNrObj;
		long index = lineCodeIndex;
		// Search for the line where this code index is located.
		do {
			lineCodeIndexObj = new Long(index);
			lineNrObj = javaStratumCodeIndexToLine().get(lineCodeIndexObj);
		} while (lineNrObj == null && --index >= fLowestValidCodeIndex);
		if (lineNrObj == null) {
			if (lineCodeIndex >= fLowestValidCodeIndex) {
				index = lineCodeIndex;
				do {
					lineCodeIndexObj = new Long(index);
					lineNrObj = javaStratumCodeIndexToLine().get(lineCodeIndexObj);
				} while (lineNrObj == null && ++index <= fHighestValidCodeIndex);
				if (lineNrObj != null) {
					return lineNrObj.intValue();
				}
			}
			throw new AbsentInformationException(JDIMessages.MethodImpl_Invalid_code_index_of_a_location_given_4);
		}
		return lineNrObj.intValue();
	}

	/* (non-Javadoc)
	 * @see com.sun.jdi.Method#allLineLocations()
	 */
	@Override
	public List<Location> allLineLocations() throws AbsentInformationException {
		return allLineLocations(virtualMachine().getDefaultStratum(), null);
	}

	/* (non-Javadoc)
	 * @see com.sun.jdi.Method#arguments()
	 */
	@Override
	public List<LocalVariable> arguments() throws AbsentInformationException {
		if (isNative() || isAbstract()) {
			throw new AbsentInformationException(JDIMessages.MethodImpl_No_local_variable_information_available_9);
		}
		if (fArguments != null) {
			return fArguments;
		}

		List<LocalVariable> result = new ArrayList<>();
		Iterator<LocalVariable> iter = variables().iterator();
		while (iter.hasNext()) {
			LocalVariable var = iter.next();
			if (var.isArgument()) {
				result.add(var);
			}
		}
		fArguments = result;
		return fArguments;
	}

	/**
	 * @return Returns a text representation of all declared argument types of
	 *         this method.
	 */
	@Override
	public List<String> argumentTypeNames() {
		if (fArgumentTypeNames != null) {
			return fArgumentTypeNames;
		}
		List<String> result = new ArrayList<>();
		for (String signature : argumentTypeSignatures()) {
			result.add(TypeImpl.signatureToName(signature));
		}

		fArgumentTypeNames = result;
		return fArgumentTypeNames;
	}

	/**
	 * @return Returns a signatures of all declared argument types of this
	 *         method.
	 */
	private List<String> argumentTypeSignatures() {
		if (fArgumentTypeSignatures != null) {
			return fArgumentTypeSignatures;
		}

		fArgumentTypeSignatures = GenericSignature.getParameterTypes(signature());
		return fArgumentTypeSignatures;
	}

	/**
	 * @return Returns the list containing the type of each argument.
	 */
	@Override
	public List<Type> argumentTypes() throws ClassNotLoadedException {
		if (fArgumentTypes != null) {
			return fArgumentTypes;
		}
		List<Type> result = new ArrayList<>();
		Iterator<String> iter = argumentTypeSignatures().iterator();
		ClassLoaderReference classLoaderRef = declaringType().classLoader();
		VirtualMachineImpl vm = virtualMachineImpl();
		while (iter.hasNext()) {
			String argumentTypeSignature = iter.next();
			result.add(TypeImpl.create(vm, argumentTypeSignature, classLoaderRef));
		}
		fArgumentTypes = result;
		return fArgumentTypes;
	}

	/**
	 * @return Returns an array containing the bytecodes for this method.
	 */
	@Override
	public byte[] bytecodes() {
		if (fByteCodes != null) {
			return fByteCodes;
		}

		initJdwpRequest();
		try {
			ByteArrayOutputStream outBytes = new ByteArrayOutputStream();
			DataOutputStream outData = new DataOutputStream(outBytes);
			writeWithReferenceType(this, outData);

			JdwpReplyPacket replyPacket = requestVM(
					JdwpCommandPacket.M_BYTECODES, outBytes);
			defaultReplyErrorHandler(replyPacket.errorCode());

			DataInputStream replyData = replyPacket.dataInStream();
			int length = readInt("length", replyData); //$NON-NLS-1$
			fByteCodes = readByteArray(length, "bytecodes", replyData); //$NON-NLS-1$
			return fByteCodes;
		} catch (IOException e) {
			fByteCodes = null;
			defaultIOExceptionHandler(e);
			return null;
		} finally {
			handledJdwpRequest();
		}
	}

	/**
	 * @return Returns the hash code value.
	 */
	@Override
	public int hashCode() {
		return fMethodID.hashCode();
	}

	/**
	 * @return Returns true if two mirrors refer to the same entity in the
	 *         target VM.
	 * @see java.lang.Object#equals(Object)
	 */
	@Override
	public boolean equals(Object object) {
		return object != null
				&& object.getClass().equals(this.getClass())
				&& fMethodID.equals(((MethodImpl) object).fMethodID)
				&& referenceTypeImpl().equals(
						((MethodImpl) object).referenceTypeImpl());
	}

	/**
	 * @return Returns a negative integer, zero, or a positive integer as this
	 *         {@link Method} is less than, equal to, or greater than the specified
	 *         {@link Method}.
	 */
	@Override
	public int compareTo(Method method) {
		if (method == null || !method.getClass().equals(this.getClass())) {
			throw new ClassCastException(
					JDIMessages.MethodImpl_Can__t_compare_method_to_given_object_6);
		}

		// See if declaring types are the same, if not return comparison between
		// declaring types.
		Method type2 = method;
		if (!declaringType().equals(type2.declaringType())) {
			return declaringType().compareTo(type2.declaringType());
		}

		// Return comparison of position within declaring type.
		int index1 = declaringType().methods().indexOf(this);
		int index2 = type2.declaringType().methods().indexOf(type2);
		if (index1 < index2) {
			return -1;
		} else if (index1 > index2) {
			return 1;
		} else {
			return 0;
		}
	}

	/* (non-Javadoc)
	 * @see com.sun.jdi.Method#isAbstract()
	 */
	@Override
	public boolean isAbstract() {
		return (fModifierBits & MODIFIER_ACC_ABSTRACT) != 0;
	}

	/* (non-Javadoc)
	 * @see com.sun.jdi.Method#isConstructor()
	 */
	@Override
	public boolean isConstructor() {
		return name().equals("<init>"); //$NON-NLS-1$
	}

	/* (non-Javadoc)
	 * @see com.sun.jdi.Method#isNative()
	 */
	@Override
	public boolean isNative() {
		return (fModifierBits & MODIFIER_ACC_NATIVE) != 0;
	}

	/* (non-Javadoc)
	 * @see com.sun.jdi.Method#isStaticInitializer()
	 */
	@Override
	public boolean isStaticInitializer() {
		return name().equals("<clinit>"); //$NON-NLS-1$
	}

	/* (non-Javadoc)
	 * @see com.sun.jdi.Method#isSynchronized()
	 */
	@Override
	public boolean isSynchronized() {
		return (fModifierBits & MODIFIER_ACC_SYNCHRONIZED) != 0;
	}

	/* (non-Javadoc)
	 * @see com.sun.jdi.Method#locationOfCodeIndex(long)
	 */
	@Override
	public Location locationOfCodeIndex(long index) {
		if (isAbstract() || isNative()) {
			return null;
		}
		try {
			Integer lineNrInt = javaStratumCodeIndexToLine().get(new Long(index));
			if (lineNrInt == null) {
				throw new AbsentInformationException(MessageFormat.format(JDIMessages.MethodImpl_No_valid_location_at_the_specified_code_index__0__2, new Object[] { Long.toString(index) }));
			}
		} catch (AbsentInformationException e) {
		}
		return new LocationImpl(virtualMachineImpl(), this, index);
	}

	/* (non-Javadoc)
	 * @see com.sun.jdi.Method#locationsOfLine(int)
	 */
	@Override
	public List<Location> locationsOfLine(int line) throws AbsentInformationException {
		return locationsOfLine(virtualMachine().getDefaultStratum(), null, line);
	}

	/* (non-Javadoc)
	 * @see com.sun.jdi.Method#returnType()
	 */
	@Override
	public Type returnType() throws ClassNotLoadedException {
		int startIndex = signature().lastIndexOf(')') + 1; // Signature position
															// is just after
															// ending brace.
		return TypeImpl.create(virtualMachineImpl(),
				signature().substring(startIndex), declaringType()
						.classLoader());
	}

	/* (non-Javadoc)
	 * @see com.sun.jdi.Method#returnTypeName()
	 */
	@Override
	public String returnTypeName() {
		if (fReturnTypeName != null) {
			return fReturnTypeName;
		}
		int startIndex = signature().lastIndexOf(')') + 1; // Signature position
															// is just after
															// ending brace.
		fReturnTypeName = TypeImpl.signatureToName(signature().substring(
				startIndex));
		return fReturnTypeName;
	}

	/* (non-Javadoc)
	 * @see com.sun.jdi.Method#variables()
	 */
	@Override
	public List<LocalVariable> variables() throws AbsentInformationException {
		if (isNative() || isAbstract()) {
			throw new AbsentInformationException(JDIMessages.MethodImpl_No_local_variable_information_available_9);
		}
		if (fVariables != null) {
			return fVariables;
		}
		initJdwpRequest();
		try {
			ByteArrayOutputStream outBytes = new ByteArrayOutputStream();
			DataOutputStream outData = new DataOutputStream(outBytes);
			writeWithReferenceType(this, outData);

			boolean withGenericSignature = virtualMachineImpl()
					.isJdwpVersionGreaterOrEqual(1, 5);
			int jdwpCommand = withGenericSignature ? JdwpCommandPacket.M_VARIABLE_TABLE_WITH_GENERIC
					: JdwpCommandPacket.M_VARIABLE_TABLE;
			JdwpReplyPacket replyPacket = requestVM(jdwpCommand, outBytes);
			switch (replyPacket.errorCode()) {
			case JdwpReplyPacket.ABSENT_INFORMATION:
				return inferArguments();
			}

			defaultReplyErrorHandler(replyPacket.errorCode());

			DataInputStream replyData = replyPacket.dataInStream();
			fArgumentSlotsCount = readInt("arg count", replyData); //$NON-NLS-1$
			int nrOfElements = readInt("elements", replyData); //$NON-NLS-1$
			List<LocalVariable> variables = new ArrayList<>(nrOfElements);
			for (int i = 0; i < nrOfElements; i++) {
				long codeIndex = readLong("code index", replyData); //$NON-NLS-1$
				String name = readString("name", replyData); //$NON-NLS-1$
				String signature = readString("signature", replyData); //$NON-NLS-1$
				String genericSignature = null;
				if (withGenericSignature) {
					genericSignature = readString("generic signature", replyData); //$NON-NLS-1$
					if ("".equals(genericSignature)) { //$NON-NLS-1$
						genericSignature = null;
					}
				}
				int length = readInt("length", replyData); //$NON-NLS-1$
				int slot = readInt("slot", replyData); //$NON-NLS-1$
				boolean isArgument = slot < fArgumentSlotsCount;

				// Note that for instance methods, the first slot contains the
				// this reference.
				if (isStatic() || slot > 0) {
					LocalVariableImpl localVar = new LocalVariableImpl(
							virtualMachineImpl(), this, codeIndex, name,
							signature, genericSignature, length, slot,
							isArgument);
					variables.add(localVar);
				}
			}
			fVariables = variables;
			return fVariables;
		} catch (IOException e) {
			fArgumentSlotsCount = -1;
			fVariables = null;
			defaultIOExceptionHandler(e);
			return null;
		} finally {
			handledJdwpRequest();
		}
	}

	/**
	 * @throws AbsentInformationException
	 */
	private List<LocalVariable> inferArguments() throws AbsentInformationException {
		// infer arguments, if possible

		// try to generate the right generic signature for each argument
		String genericSignature = genericSignature();
		String[] signatures = argumentTypeSignatures().toArray(new String[0]);
		String[] genericSignatures;
		if (genericSignature == null) {
			genericSignatures = new String[signatures.length];
		} else {
			genericSignatures = GenericSignature.getParameterTypes(genericSignature).toArray(new String[0]);
			for (int i = 0; i < genericSignatures.length; i++) {
				if (genericSignatures[i].equals(signatures[i])) {
					genericSignatures[i] = null;
				}
			}
		}

		int slot = 0;
		if (!isStatic()) {
			slot++;
		}
		if (signatures.length > 0) {
			fArgumentSlotsCount = signatures.length;
			fVariables = new ArrayList<>(fArgumentSlotsCount);
			for (int i = 0; i < signatures.length; i++) {
				String name = "arg" + i; //$NON-NLS-1$
				LocalVariableImpl localVar = new LocalVariableImpl(virtualMachineImpl(), this, 0, name, signatures[i], genericSignatures[i], -1, slot, true);
				fVariables.add(localVar);
				slot++;
			}
			return fVariables;
		}
		throw new AbsentInformationException(
				JDIMessages.MethodImpl_No_local_variable_information_available_9);

	}

	/* (non-Javadoc)
	 * @see com.sun.jdi.Method#variablesByName(java.lang.String)
	 */
	@Override
	public List<LocalVariable> variablesByName(String name) throws AbsentInformationException {
		Iterator<LocalVariable> iter = variables().iterator();
		List<LocalVariable> result = new ArrayList<>();
		while (iter.hasNext()) {
			LocalVariable var = iter.next();
			if (var.name().equals(name)) {
				result.add(var);
			}
		}
		return result;
	}

	/**
	 * @see com.sun.jdi.Locatable#location()
	 */
	@Override
	public Location location() {
		if (isAbstract()) {
			return null;
		}
		if (isNative()) {
			return new LocationImpl(virtualMachineImpl(), this, -1);
		}
		// First retrieve line code table.
		try {
			getLineTable();
		} catch (AbsentInformationException e) {
			return new LocationImpl(virtualMachineImpl(), this, -1);
		}

		// Return location with Lowest Valid Code Index.
		return new LocationImpl(virtualMachineImpl(), this,
				fLowestValidCodeIndex);
	}

	/**
	 * Writes JDWP representation.
	 */
	public void write(MirrorImpl target, DataOutputStream out)
			throws IOException {
		fMethodID.write(out);
		if (target.fVerboseWriter != null) {
			target.fVerboseWriter.println("method", fMethodID.value()); //$NON-NLS-1$
		}
	}

	/**
	 * Writes JDWP representation, including ReferenceType.
	 */
	protected void writeWithReferenceType(MirrorImpl target,
			DataOutputStream out) throws IOException {
		referenceTypeImpl().write(target, out);
		write(target, out);
	}

	/**
	 * Writes JDWP representation, including ReferenceType with Tag.
	 */
	protected void writeWithReferenceTypeWithTag(MirrorImpl target,
			DataOutputStream out) throws IOException {
		referenceTypeImpl().writeWithTag(target, out);
		write(target, out);
	}

	/**
	 * @return Reads JDWP representation and returns new instance.
	 */
	protected static MethodImpl readWithReferenceTypeWithTag(MirrorImpl target,
			DataInputStream in) throws IOException {
		VirtualMachineImpl vmImpl = target.virtualMachineImpl();
		// See Location.
		ReferenceTypeImpl referenceType = ReferenceTypeImpl.readWithTypeTag(
				target, in);
		if (referenceType == null) {
			return null;
		}

		JdwpMethodID ID = new JdwpMethodID(vmImpl);
		if (target.fVerboseWriter != null) {
			target.fVerboseWriter.println("method", ID.value()); //$NON-NLS-1$
		}

		ID.read(in);
		if (ID.isNull()) {
			return null;
		}

		// The method must be part of a known reference type.
		Method method = referenceType.findMethod(ID);
		if (method == null) {
			throw new InternalError(
					JDIMessages.MethodImpl_Got_MethodID_of_ReferenceType_that_is_not_a_member_of_the_ReferenceType_10);
		}
		return (MethodImpl) method;
	}

	/**
	 * @return Reads JDWP representation and returns new instance.
	 */
	protected static MethodImpl readWithNameSignatureModifiers(
			ReferenceTypeImpl target, ReferenceTypeImpl referenceType,
			boolean withGenericSignature, DataInputStream in)
			throws IOException {
		VirtualMachineImpl vmImpl = target.virtualMachineImpl();
		JdwpMethodID ID = new JdwpMethodID(vmImpl);
		ID.read(in);
		if (target.fVerboseWriter != null) {
			target.fVerboseWriter.println("method", ID.value()); //$NON-NLS-1$
		}

		if (ID.isNull()) {
			return null;
		}
		String name = target.readString("name", in); //$NON-NLS-1$
		String signature = target.readString("signature", in); //$NON-NLS-1$
		String genericSignature = null;
		if (withGenericSignature) {
			genericSignature = target.readString("generic signature", in); //$NON-NLS-1$
			if ("".equals(genericSignature)) { //$NON-NLS-1$
				genericSignature = null;
			}
		}
		int modifierBits = target.readInt(
				"modifiers", AccessibleImpl.getModifierStrings(), in); //$NON-NLS-1$

		MethodImpl mirror = new MethodImpl(vmImpl, referenceType, ID, name,
				signature, genericSignature, modifierBits);
		return mirror;
	}

	/**
	 * Retrieves constant mappings.
	 */
	public static void getConstantMaps() {
		if (fgInvokeOptions != null) {
			return;
		}

		Field[] fields = MethodImpl.class.getDeclaredFields();
		fgInvokeOptions = new String[32];

		for (Field field : fields) {
			if ((field.getModifiers() & Modifier.PUBLIC) == 0
					|| (field.getModifiers() & java.lang.reflect.Modifier.STATIC) == 0
					|| (field.getModifiers() & Modifier.FINAL) == 0) {
				continue;
			}

			try {
				String name = field.getName();

				if (name.startsWith("INVOKE_")) { //$NON-NLS-1$
					int value = field.getInt(null);
					for (int j = 0; j < fgInvokeOptions.length; j++) {
						if ((1 << j & value) != 0) {
							fgInvokeOptions[j] = name;
							break;
						}
					}
				}
			} catch (IllegalAccessException e) {
				// Will not occur for own class.
			} catch (IllegalArgumentException e) {
				// Should not occur.
				// We should take care that all public static final constants
				// in this class are numbers that are convertible to int.
			}
		}
	}

	/**
	 * @return Returns a map with string representations of tags.
	 */
	protected static String[] getInvokeOptions() {
		getConstantMaps();
		return fgInvokeOptions;
	}

	/**
	 * @see Method#isObsolete()
	 *
	 *      The JDK 1.4.0 specification states that obsolete methods are given
	 *      an ID of zero. It also states that when a method is redefined, the
	 *      new method gets the ID of the old method. Thus, the JDWP query for
	 *      isObsolete on JDK 1.4 will never return true for a non-zero method
	 *      ID. The query is therefore not needed
	 */
	@Override
	public boolean isObsolete() {
		if (virtualMachineImpl().isJdwpVersionGreaterOrEqual(1, 4)) {
			return fMethodID.value() == 0;
		}
		return false;
	}

	/* (non-Javadoc)
	 * @see com.sun.jdi.Method#allLineLocations(java.lang.String, java.lang.String)
	 */
	@Override
	public List<Location> allLineLocations(String stratum, String sourceName)	throws AbsentInformationException {
		if (isAbstract() || isNative()) {
			return Collections.EMPTY_LIST;
		}
		if (stratum == null) { // if stratum not defined use the default stratum for the declaring type
			stratum = declaringType().defaultStratum();
		}
		List<Location> allLineLocations = null;
		Map<String, List<Location>> sourceNameAllLineLocations = null;
		if (fStratumAllLineLocations == null) { // the stratum map doesn't
												// exist, create it
			fStratumAllLineLocations = new HashMap<>();
		} else {
			// get the source name map
			sourceNameAllLineLocations = fStratumAllLineLocations.get(stratum);
		}
		if (sourceNameAllLineLocations == null) { // the source name map doesn't
													// exist, create it
			sourceNameAllLineLocations = new HashMap<>();
			fStratumAllLineLocations.put(stratum, sourceNameAllLineLocations);
		} else {
			// get the line locations
			allLineLocations = sourceNameAllLineLocations.get(sourceName);
		}
		if (allLineLocations == null) { // the line locations are not know,
										// compute and store them
			getLineTable();
			allLineLocations = referenceTypeImpl().allLineLocations(stratum, sourceName, this, fCodeIndexTable, fJavaStratumLineNumberTable);
			sourceNameAllLineLocations.put(sourceName, allLineLocations);
		}
		return allLineLocations;
	}

	/* (non-Javadoc)
	 * @see com.sun.jdi.Method#locationsOfLine(java.lang.String, java.lang.String, int)
	 */
	@Override
	public List<Location> locationsOfLine(String stratum, String sourceName,
			int lineNumber) throws AbsentInformationException {
		if (isAbstract() || isNative()) {
			return Collections.EMPTY_LIST;
		}
		return referenceTypeImpl().locationsOfLine(stratum, sourceName, lineNumber, this);
	}

	/**
	 * Return a list which contains a location for the each disjoint range of
	 * code indices that have bean assigned to the given lines (by the compiler
	 * or/and the VM). Return an empty list if there is not executable code at
	 * the specified lines.
	 */
	protected List<Location> javaStratumLocationsOfLines(List<Integer> javaLines)	throws AbsentInformationException {
		Set<Long> tmpLocations = new TreeSet<>();
		for (Integer key : javaLines) {
			List<Long> indexes = javaStratumLineToCodeIndexes(key.intValue());
			if (indexes != null) {
				tmpLocations.addAll(indexes);
			}
		}
		List<Location> locations = new ArrayList<>();
		for (Long location : tmpLocations) {
			long index = location.longValue();
			int position = Arrays.binarySearch(fCodeIndexTable, index);
			if(position < 0) {
				//https://bugs.eclipse.org/bugs/show_bug.cgi?id=388172
				//the key is not in the code index, we should not insert it as the line table is supposed to be
				//constant unless the parent class is redefined.
				//See http://docs.oracle.com/javase/6/docs/platform/jpda/jdwp/jdwp-protocol.html#JDWP_Method_LineTable for more information
				continue;
			}
			if (position == 0 || !tmpLocations.contains(new Long(fCodeIndexTable[position - 1]))) {
				locations.add(new LocationImpl(virtualMachineImpl(), this, index));
			}
		}
		return locations;
	}

	/* (non-Javadoc)
	 * @see com.sun.jdi.Method#isBridge()
	 */
	@Override
	public boolean isBridge() {
		return (fModifierBits & MODIFIER_ACC_BRIDGE) != 0;
	}

	/* (non-Javadoc)
	 * @see com.sun.jdi.Method#isVarArgs()
	 */
	@Override
	public boolean isVarArgs() {
		// TODO: remove this test when j9 solve its problem
		// it returns invalid 1.5 flags for 1.4 classes.
		// see bug 53870
		return !virtualMachine().name().equals("j9") && (fModifierBits & MODIFIER_ACC_VARARGS) != 0; //$NON-NLS-1$
	}
}
