/*******************************************************************************
 * 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
 *******************************************************************************/
package org.eclipse.jdi.internal;

import com.sun.jdi.ClassLoaderReference;
import com.sun.jdi.ClassNotLoadedException;
import com.sun.jdi.ClassNotPreparedException;
import com.sun.jdi.Type;
import com.sun.jdi.Value;

/**
 * this class implements the corresponding interfaces declared by the JDI
 * specification. See the com.sun.jdi package for more information.
 *
 */
public abstract class TypeImpl extends AccessibleImpl implements Type {
	/** Text representation of this type. */
	protected String fName = null;
	/** JNI-style signature for this type. */
	protected String fSignature = null;

	/**
	 * Creates new instance, used for REFERENCE types.
	 */
	protected TypeImpl(String description, VirtualMachineImpl vmImpl) {
		super(description, vmImpl);
	}

	/**
	 * Creates new instance, used for PRIMITIVE types or VOID.
	 */
	protected TypeImpl(String description, VirtualMachineImpl vmImpl,
			String name, String signature) {
		super(description, vmImpl);
		setName(name);
		setSignature(signature);
	}

	/**
	 * @return Returns new instance based on signature and (if it is a
	 *         ReferenceType) classLoader.
	 * @throws ClassNotLoadedException
	 *             when type is a ReferenceType and it has not been loaded by
	 *             the specified class loader.
	 */
	public static TypeImpl create(VirtualMachineImpl vmImpl, String signature,
			ClassLoaderReference classLoader) throws ClassNotLoadedException {
		// For void values, a VoidType is always returned.
		if (isVoidSignature(signature))
			return new VoidTypeImpl(vmImpl);

		// For primitive variables, an appropriate PrimitiveType is always
		// returned.
		if (isPrimitiveSignature(signature))
			return PrimitiveTypeImpl.create(vmImpl, signature);

		// For object variables, the appropriate ReferenceType is returned if it
		// has
		// been loaded through the enclosing type's class loader.
		return ReferenceTypeImpl.create(vmImpl, signature, classLoader);
	}

	/**
	 * Assigns name.
	 */
	public void setName(String name) {
		fName = name;
	}

	/**
	 * Assigns signature.
	 */
	public void setSignature(String signature) {
		fSignature = signature;
	}

	/**
	 * @return Returns description of Mirror object.
	 */
	@Override
	public String toString() {
		try {
			return name();
		} catch (ClassNotPreparedException e) {
			return JDIMessages.TypeImpl__Unloaded_Type__1;
		} catch (Exception e) {
			return fDescription;
		}
	}

	/**
	 * @return Create a null value instance of the type.
	 */
	public abstract Value createNullValue();

	/**
	 * @return Returns text representation of this type.
	 */
	@Override
	public String name() {
		return fName;
	}

	/**
	 * @return JNI-style signature for this type.
	 */
	@Override
	public String signature() {
		return fSignature;
	}

	/**
	 * @return Returns modifier bits.
	 */
	@Override
	public abstract int modifiers();

	/**
	 * Converts a class name to a JNI signature.
	 */
	public static String classNameToSignature(String qualifiedName) {
		// L<classname>; : fully-qualified-class
		/*
		 * JNI signature examples: int[][] -> [[I long[] -> [J java.lang.String
		 * -> Ljava/lang/String; java.lang.String[] -> [Ljava/lang/String;
		 */
		StringBuilder signature = new StringBuilder();

		int firstBrace = qualifiedName.indexOf('[');
		if (firstBrace < 0) {
			// Not an array type. Must be class type.
			signature.append('L');
			signature.append(qualifiedName.replace('.', '/'));
			signature.append(';');
			return signature.toString();
		}

		int index = 0;
		while ((index = (qualifiedName.indexOf('[', index) + 1)) > 0) {
			signature.append('[');
		}

		String name = qualifiedName.substring(0, firstBrace);
		switch (name.charAt(0)) {
		// Check for primitive array type
		case 'b':
			if (name.equals("byte")) { //$NON-NLS-1$
				signature.append('B');
				return signature.toString();
			} else if (name.equals("boolean")) { //$NON-NLS-1$
				signature.append('Z');
				return signature.toString();
			}
			break;
		case 'i':
			if (name.equals("int")) { //$NON-NLS-1$
				signature.append('I');
				return signature.toString();
			}
			break;
		case 'd':
			if (name.equals("double")) { //$NON-NLS-1$
				signature.append('D');
				return signature.toString();
			}
			break;
		case 's':
			if (name.equals("short")) { //$NON-NLS-1$
				signature.append('S');
				return signature.toString();
			}
			break;
		case 'c':
			if (name.equals("char")) { //$NON-NLS-1$
				signature.append('C');
				return signature.toString();
			}
			break;
		case 'l':
			if (name.equals("long")) { //$NON-NLS-1$
				signature.append('J');
				return signature.toString();
			}
			break;
		case 'f':
			if (name.equals("float")) { //$NON-NLS-1$
				signature.append('F');
				return signature.toString();
			}
			break;
		}
		// Class type array
		signature.append('L');
		signature.append(name.replace('.', '/'));
		signature.append(';');
		return signature.toString();
	}

	/**
	 * Converts a JNI class signature to a name.
	 */
	public static String classSignatureToName(String signature) {
		// L<classname>; : fully-qualified-class
		return signature.substring(1, signature.length() - 1).replace('/', '.');
	}

	/**
	 * Converts a JNI array signature to a name.
	 */
	public static String arraySignatureToName(String signature) {
		// [<type> : array of type <type>
		if (signature.indexOf('[') < 0) {
			return signature;
		}
		StringBuilder name = new StringBuilder();
		String type = signature.substring(signature.lastIndexOf('[') + 1);
		if (type.length() == 1 && isPrimitiveSignature(type)) {
			name.append(getPrimitiveSignatureToName(type.charAt(0)));
		} else {
			name.append(classSignatureToName(type));
		}
		int index = 0;
		while ((index = (signature.indexOf('[', index) + 1)) > 0) {
			name.append('[').append(']');
		}
		return signatureToName(signature.substring(1)) + "[]"; //$NON-NLS-1$
	}

	/**
	 * @returns Returns Type Name, converted from a JNI signature.
	 */
	public static String signatureToName(String signature) {
		// See JNI 1.1 Specification, Table 3-2 Java VM Type Signatures.
		String primitive = getPrimitiveSignatureToName(signature.charAt(0));
		if (primitive != null) {
			return primitive;
		}
		switch (signature.charAt(0)) {
		case 'V':
			return "void"; //$NON-NLS-1$
		case 'L':
			return classSignatureToName(signature);
		case '[':
			return arraySignatureToName(signature);
		case '(':
			throw new InternalError(
					JDIMessages.TypeImpl_Can__t_convert_method_signature_to_name_2);
		}
		throw new InternalError(JDIMessages.TypeImpl_Invalid_signature____10
				+ signature + JDIMessages.TypeImpl___11); //
	}

	private static String getPrimitiveSignatureToName(char signature) {
		switch (signature) {
		case 'Z':
			return "boolean"; //$NON-NLS-1$
		case 'B':
			return "byte"; //$NON-NLS-1$
		case 'C':
			return "char"; //$NON-NLS-1$
		case 'S':
			return "short"; //$NON-NLS-1$
		case 'I':
			return "int"; //$NON-NLS-1$
		case 'J':
			return "long"; //$NON-NLS-1$
		case 'F':
			return "float"; //$NON-NLS-1$
		case 'D':
			return "double"; //$NON-NLS-1$
		default:
			return null;
		}
	}

	/**
	 * @returns Returns Jdwp Tag, converted from a JNI signature.
	 */
	public static byte signatureToTag(String signature) {
		switch (signature.charAt(0)) {
		case 'Z':
			return BooleanValueImpl.tag;
		case 'B':
			return ByteValueImpl.tag;
		case 'C':
			return CharValueImpl.tag;
		case 'S':
			return ShortValueImpl.tag;
		case 'I':
			return IntegerValueImpl.tag;
		case 'J':
			return LongValueImpl.tag;
		case 'F':
			return FloatValueImpl.tag;
		case 'D':
			return DoubleValueImpl.tag;
		case 'V':
			return VoidValueImpl.tag;
		case 'L':
			return ObjectReferenceImpl.tag;
		case '[':
			return ArrayReferenceImpl.tag;
		case '(':
			throw new InternalError(
					JDIMessages.TypeImpl_Can__t_covert_method_signature_to_tag___9
							+ signature);
		}
		throw new InternalError(JDIMessages.TypeImpl_Invalid_signature____10
				+ signature + JDIMessages.TypeImpl___11); //
	}

	/**
	 * @returns Returns true if signature is an primitive signature.
	 */
	public static boolean isPrimitiveSignature(String signature) {
		switch (signature.charAt(0)) {
		case 'Z':
		case 'B':
		case 'C':
		case 'S':
		case 'I':
		case 'J':
		case 'F':
		case 'D':
			return true;
		}
		return false;
	}

	/**
	 * @returns Returns true if signature is void signature.
	 */
	public static boolean isVoidSignature(String signature) {
		return (signature.charAt(0) == 'V');
	}

}
