/*
 * Copyright  2000-2004 The Apache Software Foundation
 *
 *  Licensed under the Apache License, Version 2.0 (the "License"); 
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License. 
 *
 */
package org.apache.bcel.generic;

import java.util.ArrayList;
import java.util.List;

import org.apache.bcel.Constants;
import org.apache.bcel.classfile.ClassFormatException;
import org.apache.bcel.classfile.Utility;

/**
 * Abstract super class for all possible java types, namely basic types such as
 * int, object types like String and array types, e.g. int[]
 * 
 * @version $Id: Type.java 393344 2006-04-12 00:38:34Z tcurdt $
 * @author <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A>
 */
public abstract class Type implements java.io.Serializable {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	protected byte type;
	protected String signature; // signature for the type
	/**
	 * Predefined constants
	 */
	public static final BasicType VOID = new BasicType(Constants.T_VOID);
	public static final BasicType BOOLEAN = new BasicType(Constants.T_BOOLEAN);
	public static final BasicType INT = new BasicType(Constants.T_INT);
	public static final BasicType SHORT = new BasicType(Constants.T_SHORT);
	public static final BasicType BYTE = new BasicType(Constants.T_BYTE);
	public static final BasicType LONG = new BasicType(Constants.T_LONG);
	public static final BasicType DOUBLE = new BasicType(Constants.T_DOUBLE);
	public static final BasicType FLOAT = new BasicType(Constants.T_FLOAT);
	public static final BasicType CHAR = new BasicType(Constants.T_CHAR);
	public static final ObjectType OBJECT = new ObjectType("java.lang.Object");
	public static final ObjectType CLASS = new ObjectType("java.lang.Class");
	public static final ObjectType STRING = new ObjectType("java.lang.String");
	public static final ObjectType STRINGBUFFER = new ObjectType(
			"java.lang.StringBuffer");
	public static final ObjectType THROWABLE = new ObjectType(
			"java.lang.Throwable");
	public static final Type[] NO_ARGS = new Type[0];
	public static final ReferenceType NULL = new ReferenceType() {

		/**
		 * 
		 */
		private static final long serialVersionUID = 1L;
	};
	public static final Type UNKNOWN = new Type(Constants.T_UNKNOWN,
			"<unknown object>") {

		/**
		 * 
		 */
		private static final long serialVersionUID = 1L;
	};

	protected Type(byte t, String s) {
		type = t;
		signature = s;
	}

	/**
	 * @return hashcode of Type
	 */
	@Override
	public int hashCode() {
		return type ^ signature.hashCode();
	}

	/**
	 * @return whether the Types are equal
	 */
	@Override
	public boolean equals(Object o) {
		if (o instanceof Type) {
			Type t = (Type) o;
			return (type == t.type) && signature.equals(t.signature);
		}
		return false;
	}

	/**
	 * @return signature for given type.
	 */
	public String getSignature() {
		return signature;
	}

	/**
	 * @return type as defined in Constants
	 */
	public byte getType() {
		return type;
	}

	/**
	 * @return stack size of this type (2 for long and double, 0 for void, 1
	 *         otherwise)
	 */
	public int getSize() {
		switch (type) {
		case Constants.T_DOUBLE:
		case Constants.T_LONG:
			return 2;
		case Constants.T_VOID:
			return 0;
		default:
			return 1;
		}
	}

	/**
	 * @return Type string, e.g. `int[]'
	 */
	@Override
	public String toString() {
		return ((this.equals(Type.NULL) || (type >= Constants.T_UNKNOWN))) ? signature
				: Utility.signatureToString(signature, false);
	}

	/**
	 * Convert type to Java method signature, e.g. int[] f(java.lang.String x)
	 * becomes (Ljava/lang/String;)[I
	 * 
	 * @param return_type
	 *            what the method returns
	 * @param arg_types
	 *            what are the argument types
	 * @return method signature for given type(s).
	 */
	public static String getMethodSignature(Type return_type, Type[] arg_types) {
		StringBuffer buf = new StringBuffer("(");
		int length = (arg_types == null) ? 0 : arg_types.length;
		for (int i = 0; i < length; i++) {
			buf.append(arg_types[i].getSignature());
		}
		buf.append(')');
		buf.append(return_type.getSignature());
		return buf.toString();
	}

	private static ThreadLocal<Integer> consumed_chars = new ThreadLocal<Integer>() {

		@Override
		protected Integer initialValue() {
			return new Integer(0);
		}
	};// int consumed_chars=0; // Remember position in string, see
		// getArgumentTypes

	private static int unwrap(ThreadLocal<Integer> tl) {
		return tl.get().intValue();
	}

	private static void wrap(ThreadLocal<Integer> tl, int value) {
		tl.set(new Integer(value));
	}

	/**
	 * Convert signature to a Type object.
	 * 
	 * @param signature
	 *            signature string such as Ljava/lang/String;
	 * @return type object
	 */
	public static final Type getType(String signature)
			throws StringIndexOutOfBoundsException {
		byte type = Utility.typeOfSignature(signature);
		if (type <= Constants.T_VOID) {
			// corrected concurrent private static field acess
			wrap(consumed_chars, 1);
			return BasicType.getType(type);
		} else if (type == Constants.T_ARRAY) {
			int dim = 0;
			do { // Count dimensions
				dim++;
			} while (signature.charAt(dim) == '[');
			// Recurse, but just once, if the signature is ok
			Type t = getType(signature.substring(dim));
			// corrected concurrent private static field acess
			// consumed_chars += dim; // update counter - is replaced by
			int _temp = unwrap(consumed_chars) + dim;
			wrap(consumed_chars, _temp);
			return new ArrayType(t, dim);
		} else { // type == T_REFERENCE
			int index = signature.indexOf(';'); // Look for closing `;'
			if (index < 0) {
				throw new ClassFormatException("Invalid signature: "
						+ signature);
			}
			// corrected concurrent private static field acess
			wrap(consumed_chars, index + 1); // "Lblabla;" `L' and `;' are
												// removed
			return new ObjectType(signature.substring(1, index).replace('/',
					'.'));
		}
	}

	/**
	 * Convert return value of a method (signature) to a Type object.
	 * 
	 * @param signature
	 *            signature string such as (Ljava/lang/String;)V
	 * @return return type
	 */
	public static Type getReturnType(String signature) {
		try {
			// Read return type after `)'
			int index = signature.lastIndexOf(')') + 1;
			return getType(signature.substring(index));
		} catch (StringIndexOutOfBoundsException e) { // Should never occur
			throw new ClassFormatException("Invalid method signature: "
					+ signature);
		}
	}

	/**
	 * Convert arguments of a method (signature) to an array of Type objects.
	 * 
	 * @param signature
	 *            signature string such as (Ljava/lang/String;)V
	 * @return array of argument types
	 */
	public static Type[] getArgumentTypes(String signature) {
		List<Type> vec = new ArrayList<Type>();
		int index;
		Type[] types;
		try { // Read all declarations between for `(' and `)'
			if (signature.charAt(0) != '(') {
				throw new ClassFormatException("Invalid method signature: "
						+ signature);
			}
			index = 1; // current string position
			while (signature.charAt(index) != ')') {
				vec.add(getType(signature.substring(index)));
				// corrected concurrent private static field acess
				index += unwrap(consumed_chars); // update position
			}
		} catch (StringIndexOutOfBoundsException e) { // Should never occur
			throw new ClassFormatException("Invalid method signature: "
					+ signature);
		}
		types = new Type[vec.size()];
		vec.toArray(types);
		return types;
	}

	/**
	 * Convert runtime java.lang.Class to BCEL Type object.
	 * 
	 * @param cl
	 *            Java class
	 * @return corresponding Type object
	 */
	public static Type getType(java.lang.Class<?> cl) {
		if (cl == null) {
			throw new IllegalArgumentException("Class must not be null");
		}
		/*
		 * That's an amzingly easy case, because getName() returns the
		 * signature. That's what we would have liked anyway.
		 */
		if (cl.isArray()) {
			return getType(cl.getName());
		} else if (cl.isPrimitive()) {
			if (cl == Integer.TYPE) {
				return INT;
			} else if (cl == Void.TYPE) {
				return VOID;
			} else if (cl == Double.TYPE) {
				return DOUBLE;
			} else if (cl == Float.TYPE) {
				return FLOAT;
			} else if (cl == Boolean.TYPE) {
				return BOOLEAN;
			} else if (cl == Byte.TYPE) {
				return BYTE;
			} else if (cl == Short.TYPE) {
				return SHORT;
			} else if (cl == Byte.TYPE) {
				return BYTE;
			} else if (cl == Long.TYPE) {
				return LONG;
			} else if (cl == Character.TYPE) {
				return CHAR;
			} else {
				throw new IllegalStateException(
						"Ooops, what primitive type is " + cl);
			}
		} else { // "Real" class
			return new ObjectType(cl.getName());
		}
	}

	/**
	 * Convert runtime java.lang.Class[] to BCEL Type objects.
	 * 
	 * @param classes
	 *            an array of runtime class objects
	 * @return array of corresponding Type objects
	 */
	public static Type[] getTypes(java.lang.Class<?>[] classes) {
		Type[] ret = new Type[classes.length];
		for (int i = 0; i < ret.length; i++) {
			ret[i] = getType(classes[i]);
		}
		return ret;
	}

	public static String getSignature(java.lang.reflect.Method meth) {
		StringBuffer sb = new StringBuffer("(");
		Class<?>[] params = meth.getParameterTypes(); // avoid clone
		for (int j = 0; j < params.length; j++) {
			sb.append(getType(params[j]).getSignature());
		}
		sb.append(")");
		sb.append(getType(meth.getReturnType()).getSignature());
		return sb.toString();
	}
}
