/*******************************************************************************
 * Copyright (c) 2000, 2011 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
 *******************************************************************************/
package org.eclipse.jdi.internal.jdwp;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

import org.eclipse.jdi.internal.VirtualMachineImpl;

/**
 * From this class all Java Debug Wire Protocol (JDWP) IDs declared by the JDWP
 * specification are derived.
 *
 */
public abstract class JdwpID {
	/** Tag Constants. */
	public static final byte NULL_TAG = 91; // Used for tagged null values.
	public static final byte ARRAY_TAG = 91; // '[' - an array object (objectID
												// size).
	public static final byte BYTE_TAG = 66; // 'B' - a byte value (1 byte).
	public static final byte CHAR_TAG = 67; // 'C' - a character value (2
											// bytes).
	public static final byte OBJECT_TAG = 76; // 'L' - an object (objectID
												// size).
	public static final byte FLOAT_TAG = 70; // 'F' - a float value (4 bytes).
	public static final byte DOUBLE_TAG = 68; // 'D' - a double value (8 bytes).
	public static final byte INT_TAG = 73; // 'I' - an int value (4 bytes).
	public static final byte LONG_TAG = 74; // 'J' - a long value (8 bytes).
	public static final byte SHORT_TAG = 83; // 'S' - a short value (2 bytes).
	public static final byte VOID_TAG = 86; // 'V' - a void value (no bytes).
	public static final byte BOOLEAN_TAG = 90; // 'Z' - a boolean value (1
												// byte).
	public static final byte STRING_TAG = 115; // 's' - a String object
												// (objectID size).
	public static final byte THREAD_TAG = 116; // 't' - a Thread object
												// (objectID size).
	public static final byte THREAD_GROUP_TAG = 103; // 'g' - a ThreadGroup
														// object (objectID
														// size).
	public static final byte CLASS_LOADER_TAG = 108; // 'l' - a ClassLoader
														// object (objectID
														// size).
	public static final byte CLASS_OBJECT_TAG = 99; // 'c' - a class object
													// object (objectID size).

	/** TypeTag Constants. */
	public static final byte TYPE_TAG_CLASS = 1; // ReferenceType is a class.
	public static final byte TYPE_TAG_INTERFACE = 2; // ReferenceType is an
														// interface.
	public static final byte TYPE_TAG_ARRAY = 3; // ReferenceType is an array.

	/** Mapping of command codes to strings. */
	private static HashMap<Integer, String> fTagMap = null;
	private static HashMap<Integer, String> fTypeTagMap = null;

	/** Jdwp representation of null ID. */
	protected static final int VALUE_NULL = 0;

	/** The value of the ID */
	protected long fValue = VALUE_NULL;
	/**
	 * The virtual machine of the mirror object that uses this ID (needed for ID
	 * sizes.
	 */
	protected VirtualMachineImpl fVirtualMachine;

	/**
	 * Creates new JdwpID.
	 */
	public JdwpID(VirtualMachineImpl vmImpl) {
		fVirtualMachine = vmImpl;
	}

	/**
	 * @return Returns true if two IDs refer to the same entity in the target
	 *         VM.
	 * @see java.lang.Object#equals(Object)
	 */
	@Override
	public boolean equals(Object object) {
		return object instanceof JdwpID && fValue == ((JdwpID) object).fValue;
	}

	/**
	 * @return Returns a has code for this object.
	 * @see java.lang.Object#hashCode
	 */
	@Override
	public int hashCode() {
		return (int) fValue;
	}

	/**
	 * @return Returns value of ID.
	 */
	public final long value() {
		return fValue;
	}

	/**
	 * @return Returns string representation.
	 */
	@Override
	public String toString() {
		return new Long(fValue).toString();
	}

	/**
	 * @return Returns VM specific size of ID.
	 */
	protected abstract int getSize();

	/**
	 * @return Returns true if ID is null.
	 */
	public abstract boolean isNull();

	/**
	 * Reads ID.
	 */
	public void read(DataInputStream inStream) throws IOException {
		fValue = 0;
		int size = getSize();
		for (int i = 0; i < size; i++) {
			int b = inStream.readUnsignedByte(); // Note that the byte must be
													// treated as unsigned.
			fValue = fValue << 8 | b;
		}
	}

	/**
	 * Writes ID.
	 */
	public void write(DataOutputStream outStream) throws IOException {
		int size = getSize();
		for (int i = size - 1; i >= 0; i--) {
			byte b = (byte) (fValue >>> 8 * i); // Note that >>> must be used
												// because fValue must be
												// treated as unsigned.
			outStream.write(b);
		}
	}

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

		java.lang.reflect.Field[] fields = JdwpID.class.getDeclaredFields();
		fTagMap = new HashMap<>();
		fTypeTagMap = new HashMap<>();
		for (Field field : fields) {
			if ((field.getModifiers() & java.lang.reflect.Modifier.PUBLIC) == 0
					|| (field.getModifiers() & java.lang.reflect.Modifier.STATIC) == 0
					|| (field.getModifiers() & java.lang.reflect.Modifier.FINAL) == 0)
				continue;

			try {
				String name = field.getName();
				Integer intValue = new Integer(field.getInt(null));
				if (name.startsWith("TYPE_TAG_")) { //$NON-NLS-1$
					name = name.substring(9);
					fTypeTagMap.put(intValue, name);
				} else if (name.endsWith("_TAG")) { //$NON-NLS-1$
					fTagMap.put(intValue, name);
				}
			} 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.
	 */
	public static Map<Integer, String> tagMap() {
		getConstantMaps();
		return fTagMap;
	}

	/**
	 * @return Returns a map with string representations of type tags.
	 */
	public static Map<Integer, String> typeTagMap() {
		getConstantMaps();
		return fTypeTagMap;
	}
}
