/*
* JBoss, Home of Professional Open Source
* Copyright 2008-10 Red Hat and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*
* @authors Andrew Dinn
*/
package org.jboss.byteman.rule.type;
import org.jboss.byteman.rule.exception.TypeException;
import org.objectweb.asm.Opcodes;
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
/**
* models the type of a rule binding or expression
*/
public class Type {
/**
* create a type with a given name and optionally an associated class
*
* @param typeName the name of the type which may or may not be fully qualified
* @param clazz the class associated with this name if it is know otherwise null
*/
public Type(String typeName, Class clazz)
{
this(typeName, clazz, F_OBJECT, 4, null);
}
/**
* create a type with a given name and no associated class
*
* @param typeName the name of the type which may or may not be fully qualified
*/
public Type(String typeName)
{
this(typeName, null);
}
/**
* create an array type from this base type
* @return an array type
*/
public Type arrayType()
{
return arrayType(null);
}
/**
* create an array type from this base type
* @param clazz the class for the array type derived from the class of base type or
* null if the base type is undefined
* @return an array type
*/
public Type arrayType(Class clazz)
{
if (this.arrayType == null) {
arrayType = new Type(typeName + "[]", clazz, F_ARRAY, 4, this);
}
return arrayType;
}
/**
* retrieve the base type for an array type or null if this is not an array type
* @return an array type
*/
public Type getBaseType()
{
if (isArray()) {
return baseType;
} else {
return null;
}
}
/**
* get the possibly unqualified name with which this type was created
* @return the type name
*/
public String getName()
{
return typeName;
}
/**
* get the internal name for this type used by the class loader. this is only valid for
* defined types, defined array types or primitive types
* @return the type name
*/
public String getInternalName()
{
return getInternalName(false, true);
}
/**
* get the internal name for this type used by the class
* loader. this is only valid for defined types, defined array
* types or primitive types
* @param forDescriptor true if we need the name to appear in a
* decriptor false if not
* @param slashSeparate true if the package separator should be
* slash false if it should be dot
* @return the type name
*/
public String getInternalName(boolean forDescriptor, boolean slashSeparate)
{
if (isArray()) {
// the base name has to be L...; bracketed
return "[" + baseType.getInternalName(true, slashSeparate);
} else if (isPrimitive()) {
return internalNames.get(typeName);
} else if (isVoid()) {
return internalNames.get(typeName);
} else {
Class targetClass = aliasFor.getTargetClass();
Class enclosingClass = targetClass.getEnclosingClass();
String name;
if (enclosingClass != null && !forDescriptor) {
// retain the $ separator for inner classes and local/anon classes
name = targetClass.getName();
} else {
name = targetClass.getCanonicalName();
if (name == null) {
// local or anonymous class
name = targetClass.getName();
}
}
if (slashSeparate) {
name = name.replace('.', '/');
}
if (forDescriptor) {
name = "L" + name + ";";
}
return name;
}
}
/**
* get the class associated with this type if it has one or a special undefined class if
* the type is not defined or null if there is no associated class
* @return the associated class
*/
public Class getTargetClass()
{
return clazz;
}
/**
* get the package component of the name associated with this type or the empty String
* if it has no package or is was defiend with an unqualified name or is a builtin type
* @return the package component or an empty string
*/
public String getPackageName()
{
return packageName;
}
/**
* dereference an object type to the fully qualified named type to which it is aliased where
* such an alias has been found to exist or return the supplied type if no alias exists or the
* type is a non-objecttype or was originally specified using a fully qualified type name.
*
* @param target the type to be dereferenced
* @return the alias where it exists or the supplied type where there is no alias or null
* if a null value is supplied
*/
public static Type dereference(Type target)
{
if (target == null) {
return null;
}
while (target.aliasFor != target) {
target = target.aliasFor;
}
return target;
}
public void resolve(ClassLoader loader)
{
if (this.isDefined() || this == Type.UNDEFINED || this == Type.N) {
return;
}
if (aliasFor != this) {
aliasFor.resolve(loader);
clazz = aliasFor.clazz;
if (clazz != null) {
flags &= ~F_UNKNOWN;
}
} else {
try {
clazz = loader.loadClass(getName());
flags &= ~F_UNKNOWN;
} catch (ClassNotFoundException e) {
// ok give up here -- we should get a type error later
}
}
// ensure any classes which might have been created via this one are also resolved
if (baseType != null) {
baseType.resolve(loader);
}
if (arrayType != null) {
arrayType.resolve(loader);
}
}
/**
* attempt to establish an alias from an package unqualified named object type to a package
* qualified named object type whose unqualified name equals this type's name
* @param target the package qualified named type for which this type should become an alias
* @return true if the alias link can be established or already exsits or false if an alias
* to a different target already exists or this type or the target are not object types
*/
public boolean aliasTo(Type target)
{
// we can only legitimately alias an object type without a package to a type with a package
// ???caller should already have checked this???
if (!isObject() || !target.isObject()) {
return false;
}
// ???caller should already have checked this???
if (packageName.length() != 0 || target.packageName.length() == 0) {
return false;
}
// if there is already an alias for this type then it has to be the same as the supplied type
if (aliasFor != this) {
return (aliasFor == target);
} else {
// we assume the caller has already checked that the names match so . . .
// update the alias
aliasFor = target;
// propagate type-class binding up or down alias link if available
if (clazz != null) {
if (target.clazz != null) {
// oops class mismatch!
return false;
} else {
target.clazz = clazz;
target.flags &= ~F_UNKNOWN;
}
} else if (target.clazz != null) {
clazz = target.clazz;
flags &= ~F_UNKNOWN;
}
if (arrayType != null) {
// ensure array types are also aliased
if (target.arrayType == null) {
target.arrayType(target.clazz);
}
arrayType.aliasTo(target.arrayType);
} else if (target.arrayType != null) {
// point the array type at the target array type
arrayType = arrayType(this.clazz);
arrayType.aliasTo(target.arrayType);
}
return true;
}
}
/**
* check whether this type can be assigned with values of the supplied type including
* the case where numeric conversion from known or unknown numeric types but excluding
* any other cases where this type is undefined
*
* n.b. the caller must dereference the recipient and argument types before calling
* this method
* @param type the type poviding RHS values
* @return true if it is known that the assignment is valid, false if it is not known to be valid or
* is known not to be valid
*/
public boolean isAssignableFrom(Type type)
{
if (this.aliasFor != this) {
return Type.dereference(this).isAssignableFrom(type);
}
type = Type.dereference(type);
// check for unknown cases first
if (isNumeric()) {
// can always coerce numerics even if it involves boxing
return type.isNumeric();
} else if (isUndefined() || type.isUndefined()) {
// cannot answer this question yet
return false;
} else if (this == type) {
return true;
} else if (isString()) {
// can always convert anything to a string via boxing and Object.toString();
return true;
} else if (isVoid()) {
// can always assign to void
return true;
} else if (isPrimitive()) {
if (!type.isPrimitive()) {
// see if we can arrive at the correct type by unboxing
Type unboxedType = boxedTypes.get(type);
return (unboxedType == this);
} else {
return false;
}
} else if (type.isPrimitive()) {
// see if we can arrive at the correct type by boxing
Type boxedType = boxedTypes.get(type);
if (boxedType == this) {
return true;
} else {
// we only get here if we have a known type i.e. both clazz values are non-null
// see if the supplied type is assignable from this type's class
return (this.clazz.isAssignableFrom(boxedType.clazz));
}
} else if (isObject() || isArray()) {
// we only get here if we have a known type i.e. both clazz values are non-null
// see if the supplied type is assignable from this type's class
return (this.clazz.isAssignableFrom(type.clazz));
} else {
return false;
}
}
/**
* test if this type is an unknown type. a type may be unknown either because it is one
* of the pseudo types used as type variables or because it represents an object type
* mentioned in a rule but not yet bound to a specific class
* @return true if the type is unknown otherwise false
*/
public boolean isUndefined()
{
return ((flags & F_UNKNOWN) != 0);
}
/**
* check if this type is a known type. this is just teh oppositeof isUndefined
* @return false if the type is unknown otherwise true
*/
public boolean isDefined()
{
return !isUndefined();
}
/**
* return true if this is a type mentioned in a rule but not yet bound to a specific class
* @return true if the type is not yet bound to a specific class
*/
public boolean isUnbound()
{
// this only happens where we have an object type marked with the UNKNOWN marker
return (flags & (F_OBJECT | F_UNKNOWN)) == (F_OBJECT | F_UNKNOWN);
}
/**
* return true if this is a primitive value type
* @return true if this is a primitive value type
*/
public boolean isPrimitive()
{
return (flags & F_PRIMITIVE) != 0;
}
/**
* return true if this is a value type, which includes the boxed versions of primitive types
* @return true if this is a value type
*/
public boolean isValue()
{
return (flags & F_VALUE) != 0;
}
/**
* return true if this is the void type
* @return true if this is void type
*/
public boolean isVoid()
{
return (flags & F_VOID) != 0;
}
/**
* return true if this is the string type
* @return true if this is string type
*/
public boolean isString()
{
return (flags & F_STRING) != 0;
}
/**
* return true if this is a numeric type, including the unknown primitive numeric type
* @return true if this is a numeric type
*/
public boolean isNumeric()
{
return (flags & F_NUMERIC) != 0;
}
/**
* return true if this is an integral type of whatever size, including the unknown
* primitive numeric type
* @return true if this is an integral type
*/
public boolean isIntegral()
{
return (flags & F_INTEGRAL) == F_INTEGRAL;
}
/**
* return true if this is a floating type of whatever size, including the unknown
* primitive numeric type
* @return true if this is a floating type
*/
public boolean isFloating()
{
return (flags & F_FLOATING) == F_FLOATING;
}
/**
* return true if this is a boolean type
* @return true if this is a boolean type
*/
public boolean isBoolean()
{
return (flags & F_BOOLEAN) != 0;
}
/**
* return true if this is an object type, including unbound types mentioned in rules
* @return true if this is an object type
*/
public boolean isObject()
{
return (flags & F_OBJECT) != 0;
}
/**
* return true if this is an array type
* @return true if this is an array type
*/
public boolean isArray()
{
return (flags & F_ARRAY) != 0;
}
/**
* return the number of stack words occupied by instances of this type
* @return true if this is an array type
*/
public int getNBytes()
{
return nBytes;
}
/**
* return the builtin type associated with a given class
* @param clazz the class for the builtin type
* @return the corresponding builtin type
*/
public static Type builtinType(Class clazz)
{
return builtinTypes.get(clazz.getName());
}
/**
* return the primitive type whose boxed equivalent is associated with a given class
* @param clazz the class for the primitivebuiltin type
* @return the corresponding primitive type
*/
public static Type boxType(Class clazz)
{
Type type = builtinType(clazz);
return boxedTypes.get(type);
}
/**
* return the primitive type for a boxed type or vice versa
* @param type the boxed type
* @return the corresponding primitive type
*/
public static Type boxType(Type type)
{
return boxedTypes.get(type);
}
private String typeName;
private Class clazz;
private String packageName;
private int flags;
private int nBytes;
private Type aliasFor;
private Type baseType;
private Type arrayType;
protected Type(String typeName, Class clazz, int flags, int nBytes)
{
this(typeName, clazz, flags, nBytes, null);
}
protected Type(String typeName, Class clazz, int flags, int nBytes, Type baseType)
{
this.typeName = typeName;
if (clazz == null) {
flags |= F_UNKNOWN;
}
this.clazz = clazz;
this.flags = flags;
this.nBytes = nBytes;
if ((flags & F_ARRAY) != 0) {
this.baseType = baseType;
baseType.arrayType = this;
} else {
this.baseType = null;
}
this.arrayType = null;
// types dereference to themselves until they are aliased
aliasFor = this;
packageName = packagePart(typeName);
}
/**
* compute the type to which a binary arithmetic operator should promote its operands
* before combination based on the two operand types which is also the type to be
* used for the result of the operation
* @param type1 the type of the left operand which must be numeric but may be undefined
* @param type2 the type of the right operand which must be numeric but may be undefined
* @return the corresponding promotion/result type which may be undefined numeric
* @throws TypeException if types are undefined or promotion is invalid
*/
public static Type promote(Type type1, Type type2) throws TypeException {
if (type1.isUndefined() || type2.isUndefined()) {
// don't know for sure which is which so return undefined numeric
return N;
} else if (!type1.isNumeric() || !type2.isNumeric()) {
// should not happen!
throw new TypeException("Type.promote : unexpected non-numeric type argument");
} else if (type1.isFloating() || type2.isFloating()) {
if (type1 == DOUBLE || type2 == DOUBLE || type1 == D || type2 == D) {
return D;
} else {
return F;
}
} else {
// integral types -- ok lets invent^H^H^H declare some rules here :-)
// either arg long forces a long result
// either arg integer forces an int result
// a matched pair of short, char or byte arguments retains the same type in the result
// otherwise the result is an int and args will be coerced to int
if (type1 == LONG || type2 == LONG || type1 == J || type2 == J) {
return J;
} else if (type1 == INTEGER || type2 == INTEGER || type1 == I || type2 == I) {
return I;
} else if ((type1 == SHORT || type1 == S) && (type2 == SHORT || type2 == S)) {
return S;
} else if ((type1 == CHARACTER || type1 == C) && (type2 == CHARACTER || type2 == C)) {
return C;
} else if ((type1 == BYTE || type1 == B) && (type2 == BYTE || type2 == B)) {
return B;
} else {
return I;
}
}
}
/* TODO we don't seem to need this?
private static String classPart(String className)
{
int dotIdx = className.lastIndexOf('.');
if (dotIdx < 0) {
return className;
} else {
return className.substring(dotIdx);
}
}
*/
private static String packagePart(String className)
{
int dotIdx = className.lastIndexOf('.');
if (dotIdx < 0) {
return "";
} else {
return className.substring(0, dotIdx);
}
}
public static List<String> parseMethodDescriptor(String descriptor, boolean includeReturnType)
{
List<String> argTypes = new ArrayList<String>();
int length = descriptor.length();
int idx = descriptor.indexOf("(");
int arrayDepth = 0;
if (idx < 0) {
return null;
}
idx = idx + 1;
while (idx < length) {
char c = descriptor.charAt(idx);
switch(descriptor.charAt(idx))
{
case 'Z':
{
String baseType = "boolean";
argTypes.add(fixArrayType(baseType, arrayDepth));
arrayDepth = 0;
idx++;
}
break;
case 'B':
{
String baseType = "byte";
argTypes.add(fixArrayType(baseType, arrayDepth));
arrayDepth = 0;
idx++;
}
break;
case 'S':
{
String baseType = "short";
argTypes.add(fixArrayType(baseType, arrayDepth));
arrayDepth = 0;
idx++;
}
break;
case 'C':
{
String baseType = "char";
argTypes.add(fixArrayType(baseType, arrayDepth));
arrayDepth = 0;
idx++;
}
break;
case 'I':
{
String baseType = "int";
argTypes.add(fixArrayType(baseType, arrayDepth));
arrayDepth = 0;
idx++;
}
break;
case 'J':
{
String baseType = "long";
argTypes.add(fixArrayType(baseType, arrayDepth));
arrayDepth = 0;
idx++;
}
break;
case 'F':
{
String baseType = "float";
argTypes.add(fixArrayType(baseType, arrayDepth));
arrayDepth = 0;
idx++;
}
break;
case 'D':
{
String baseType = "double";
argTypes.add(fixArrayType(baseType, arrayDepth));
arrayDepth = 0;
idx++;
}
break;
case 'V':
{
if (arrayDepth != 0) {
// hmm void arrays are definitely not kosher
return null;
} else if (!includeReturnType) {
// hmm should not have got here
return null;
}
argTypes.add("void");
idx++;
}
break;
case 'L':
{
int endIdx = descriptor.indexOf(';', idx);
if (endIdx < 0) {
return null;
}
String baseType = descriptor.substring(idx+1, endIdx).replace('/', '.');
argTypes.add(fixArrayType(baseType, arrayDepth));
arrayDepth = 0;
idx = endIdx + 1;
}
break;
case '[':
{
arrayDepth++;
idx++;
}
break;
case ')':
{
if (arrayDepth != 0) {
return null;
} else if (!includeReturnType) {
// stop here
return argTypes;
} else {
// skip any trailing spaces before the return type
idx++;
while (idx < length && descriptor.charAt(idx) == ' ')
{
idx++;
}
if (idx == length) {
// ok, we need to add a void return type
argTypes.add("void");
}
}
}
break;
default:
return null;
}
}
return (arrayDepth == 0 ? argTypes : null);
}
public static String parseFieldDescriptor(String descriptor)
{
int length = descriptor.length();
int idx = 0;
int arrayDepth = 0;
while (idx < length) {
char c = descriptor.charAt(idx);
switch(descriptor.charAt(idx))
{
case 'Z':
{
String baseType = "boolean";
return(fixArrayType(baseType, arrayDepth));
}
case 'B':
{
String baseType = "byte";
return(fixArrayType(baseType, arrayDepth));
}
case 'S':
{
String baseType = "short";
return(fixArrayType(baseType, arrayDepth));
}
case 'C':
{
String baseType = "char";
return(fixArrayType(baseType, arrayDepth));
}
case 'I':
{
String baseType = "int";
return(fixArrayType(baseType, arrayDepth));
}
case 'J':
{
String baseType = "long";
return(fixArrayType(baseType, arrayDepth));
}
case 'F':
{
String baseType = "float";
return(fixArrayType(baseType, arrayDepth));
}
case 'D':
{
String baseType = "double";
return(fixArrayType(baseType, arrayDepth));
}
case 'V':
{
String baseType = "void";
return(fixArrayType(baseType, arrayDepth));
}
case 'L':
{
int endIdx = descriptor.indexOf(';', idx);
if (endIdx < 0) {
return null;
}
String baseType = descriptor.substring(idx+1, endIdx).replace('/', '.');
return(fixArrayType(baseType, arrayDepth));
}
case '[':
{
arrayDepth++;
idx++;
}
break;
default:
return null;
}
}
return null;
}
public static String parseMethodReturnType(String descriptor)
{
int length = descriptor.length();
int idx = descriptor.indexOf(")");
int arrayDepth = 0;
if (idx < 0) {
return "void";
}
idx = idx + 1;
while (idx < length) {
char c = descriptor.charAt(idx);
switch(c)
{
case 'Z':
{
String baseType = "boolean";
return fixArrayType(baseType, arrayDepth);
}
case 'B':
{
String baseType = "byte";
return fixArrayType(baseType, arrayDepth);
}
case 'S':
{
String baseType = "short";
return fixArrayType(baseType, arrayDepth);
}
case 'C':
{
String baseType = "char";
return fixArrayType(baseType, arrayDepth);
}
case 'I':
{
String baseType = "int";
return fixArrayType(baseType, arrayDepth);
}
case 'J':
{
String baseType = "long";
return fixArrayType(baseType, arrayDepth);
}
case 'F':
{
String baseType = "float";
return fixArrayType(baseType, arrayDepth);
}
case 'D':
{
String baseType = "double";
return fixArrayType(baseType, arrayDepth);
}
case 'V':
{
return "void";
}
case 'L':
{
int endIdx = descriptor.indexOf(';', idx);
if (endIdx < 0) {
return "void";
}
String baseType = descriptor.substring(idx+1, endIdx).replace('/', '.');
return fixArrayType(baseType, arrayDepth);
}
case '[':
{
arrayDepth++;
idx++;
}
break;
default:
return "void";
}
}
return "void";
}
/**
* identify the local var slot used to store a method parameter identified by parameter index
* @param access the access flags for the method including whether or not it is static
* @param desc the intrenal form descriptor for the maethod
* @param paramIdx the index of the parameter in the parameter lost starting with 0 for this or 1 for
* actual parameters
* @return the corresponding local var slot or -1 if there is no such parameter
*/
public static int paramSlotIdx(int access, String desc, int paramIdx)
{
boolean isStatic = (access & Opcodes.ACC_STATIC) != 0;
if (paramIdx == 0) {
if (isStatic) {
return -1;
} else {
return 0;
}
} else {
int slotIdx = (isStatic ? 0 : 1);
int descIdx = 0;
char[] chars = desc.toCharArray();
char next = chars[descIdx++];
// skip leading '('
if (next == '(') {
next = chars[descIdx++];
}
// skip all slots preceding the desired one
for (int i = 1; i < paramIdx; i++) {
switch (next) {
case 'Z':
case 'B':
case 'S':
case 'C':
case 'I':
case 'F':
{
slotIdx++;
}
break;
case 'J':
case 'D':
{
slotIdx += 2;
}
break;
case 'L':
{
slotIdx++;
// skip forward in descriptor up to ';'
while (next != ';') {
next = chars[descIdx++];
}
}
break;
case '[':
{
slotIdx++;
// skip any extra '[' chars to get to the base array type
while (next == '[') {
next = chars[descIdx++];
}
if (next == 'L') {
// skip forward in descriptor up to ';'
while (next != ';') {
next = chars[descIdx++];
}
}
}
break;
case ')':
default:
{
// invalid param index or invalid descriptor --either way this is a problem
return -1;
}
}
next = chars[descIdx++];
}
return slotIdx;
}
}
public static String fixArrayType(String baseType, int dimension)
{
String result = baseType;
for (int i = 0; i < dimension; i++) {
result += "[]";
}
return result;
}
public static String internalName(Class<?> clazz)
{
return internalName(clazz, false);
}
public static String internalName(Class<?> clazz, boolean forField)
{
if (clazz.isPrimitive()) {
if (forField) {
return internalNames.get(clazz.getName());
} else {
return clazz.getName();
}
} else if (clazz.isArray()) {
Class base = clazz.getComponentType();
return "[" + internalName(base, true);
} else if (forField) {
return "L" + clazz.getName().replace('.', '/') + ";";
} else {
return clazz.getName().replace('.', '/');
}
}
// private class used to type unknown types
private static class Undefined {
}
// tags divide types into exclusive categories
// unknown types may be associated with a tag group such as numeric, or object
// markers are used to identify type properties such as unknown, primitive
// value type tags
final public static int F_BOOLEAN = 0x0001;
final public static int F_INTEGRAL = 0x0002;
final public static int F_FLOATING = 0x0004;
// object type tag
final public static int F_OBJECT = 0x0008;
// void type tag
final public static int F_VOID = 0x0010;
// array type tag
final public static int F_ARRAY = 0x0020;
// value type tag groups
final public static int F_NUMERIC = F_INTEGRAL | F_FLOATING;
final public static int F_VALUE = F_BOOLEAN | F_NUMERIC;
// unknown type marker
final public static int F_UNKNOWN = 0x1000;
// primitive type marker
final public static int F_PRIMITIVE = 0x2000;
// string type marker
final public static int F_STRING = 0x4000;
// we need to cope with array types
final public static Type Z = new Type("boolean", boolean.class, F_BOOLEAN|F_PRIMITIVE, 4);
final public static Type B = new Type("byte", byte.class, F_INTEGRAL|F_PRIMITIVE, 1);
final public static Type S = new Type("short", short.class, F_INTEGRAL|F_PRIMITIVE, 2);
final public static Type C = new Type("char", char.class, F_INTEGRAL|F_PRIMITIVE, 2);
final public static Type I = new Type("int", int.class, F_INTEGRAL|F_PRIMITIVE, 4);
final public static Type J = new Type("long", long.class, F_INTEGRAL|F_PRIMITIVE, 8);
final public static Type F = new Type("float", float.class, F_FLOATING|F_PRIMITIVE, 4);
final public static Type D = new Type("double", double.class, F_FLOATING|F_PRIMITIVE, 8);
// pseudo type representing an undefined numeric primitive type
final public static Type N = new Type("", null, F_UNKNOWN|F_NUMERIC|F_PRIMITIVE, 0);
final public static Type BOOLEAN = new Type("java.lang.Boolean", Boolean.class, F_OBJECT|F_BOOLEAN, 4);
final public static Type BYTE = new Type("java.lang.Byte", Byte.class, F_OBJECT|F_INTEGRAL, 4);
final public static Type SHORT = new Type("java.lang.Short", Short.class, F_OBJECT|F_INTEGRAL, 4);
final public static Type CHARACTER = new Type("java.lang.Character", Character.class, F_OBJECT|F_INTEGRAL, 4);
final public static Type INTEGER = new Type("java.lang.Integer", Integer.class, F_OBJECT|F_INTEGRAL, 4);
final public static Type LONG = new Type("java.lang.Long", Long.class, F_OBJECT|F_INTEGRAL, 4);
final public static Type FLOAT = new Type("java.lang.Float", Float.class, F_OBJECT|F_FLOATING, 4);
final public static Type DOUBLE = new Type("java.lang.Double", Double.class, F_OBJECT|F_FLOATING, 4);
final public static Type STRING = new Type("java.lang.String", String.class, F_OBJECT|F_STRING, 4);
final public static Type VOID = new Type("void", void.class, F_VOID, 0);
final public static Type NUMBER = new Type("java.lang.Number", Number.class, F_OBJECT|F_NUMERIC, 0);
final public static Type OBJECT = new Type("java.lang.Object", Object.class, F_OBJECT, 0);
// pseudo type representing an undefined primitive or object type
final public static Type UNDEFINED = new Type("", Undefined.class, F_UNKNOWN, 0);
final private static HashMap<String, Type> builtinTypes;
final private static HashMap<String, Type> primitiveTypes;
final private static HashMap<Type, Type> boxedTypes;
final private static HashMap<String, String> internalNames;
static {
builtinTypes = new HashMap<String, Type>();
// primitive type names
builtinTypes.put(Z.getName(), Z);
builtinTypes.put(B.getName(), B);
builtinTypes.put(S.getName(), S);
builtinTypes.put(C.getName(), C);
builtinTypes.put(I.getName(), I);
builtinTypes.put(J.getName(), J);
builtinTypes.put(F.getName(), F);
builtinTypes.put(D.getName(), D);
builtinTypes.put("$number$", N);
// canonical names
builtinTypes.put(BOOLEAN.getTargetClass().getName(), BOOLEAN);
builtinTypes.put(BYTE.getTargetClass().getName(), BYTE);
builtinTypes.put(SHORT.getTargetClass().getName(), SHORT);
builtinTypes.put(CHARACTER.getTargetClass().getName(), CHARACTER);
builtinTypes.put(INTEGER.getTargetClass().getName(), INTEGER);
builtinTypes.put(LONG.getTargetClass().getName(), LONG);
builtinTypes.put(FLOAT.getTargetClass().getName(), FLOAT);
builtinTypes.put(DOUBLE.getTargetClass().getName(), DOUBLE);
builtinTypes.put(STRING.getTargetClass().getName(), STRING);
builtinTypes.put(VOID.getTargetClass().getName(), VOID);
builtinTypes.put(NUMBER.getTargetClass().getName(), NUMBER);
builtinTypes.put(UNDEFINED.getTargetClass().getName(), UNDEFINED);
builtinTypes.put(OBJECT.getTargetClass().getName(), OBJECT);
// nicknames
builtinTypes.put("Boolean", BOOLEAN);
builtinTypes.put("Byte", BYTE);
builtinTypes.put("Short", SHORT);
builtinTypes.put("Character", CHARACTER);
builtinTypes.put("Integer", INTEGER);
builtinTypes.put("Long", LONG);
builtinTypes.put("Float", FLOAT);
builtinTypes.put("Double", DOUBLE);
builtinTypes.put("String", STRING);
builtinTypes.put("Number", NUMBER);
builtinTypes.put("Object", OBJECT);
builtinTypes.put("", UNDEFINED);
// allow undefined to be spelled out
builtinTypes.put("Undefined", UNDEFINED);
primitiveTypes = new HashMap<String, Type>();
primitiveTypes.put(Z.getName(), Z);
primitiveTypes.put(B.getName(), B);
primitiveTypes.put(S.getName(), S);
primitiveTypes.put(C.getName(), C);
primitiveTypes.put(I.getName(), I);
primitiveTypes.put(J.getName(), J);
primitiveTypes.put(F.getName(), F);
primitiveTypes.put(D.getName(), D);
primitiveTypes.put("$number$", N);
// allow for boxing
boxedTypes = new HashMap<Type, Type>();
boxedTypes.put(Z, BOOLEAN);
boxedTypes.put(B, BYTE);
boxedTypes.put(S, SHORT);
boxedTypes.put(C, CHARACTER);
boxedTypes.put(I, INTEGER);
boxedTypes.put(J, LONG);
boxedTypes.put(F, FLOAT);
boxedTypes.put(D, DOUBLE);
// also allow for unboxing
boxedTypes.put(BOOLEAN, Z);
boxedTypes.put(BYTE, B);
boxedTypes.put(SHORT, S);
boxedTypes.put(CHARACTER, C);
boxedTypes.put(INTEGER, I);
boxedTypes.put(LONG, J);
boxedTypes.put(FLOAT, F);
boxedTypes.put(DOUBLE, D);
internalNames = new HashMap<String, String>();
// add translations from primitive names to internal tag
internalNames.put("Z", "boolean");
internalNames.put("B", "byte");
internalNames.put("S", "short");
internalNames.put("C", "char");
internalNames.put("I", "int");
internalNames.put("J", "long");
internalNames.put("F", "float");
internalNames.put("D", "double");
internalNames.put("V", "void");
// also add reverse translations
internalNames.put("boolean", "Z");
internalNames.put("byte", "B");
internalNames.put("short", "S");
internalNames.put("char", "C");
internalNames.put("int", "I");
internalNames.put("long", "J");
internalNames.put("float", "F");
internalNames.put("double", "D");
internalNames.put("void", "V");
}
public String toString()
{
return getName();
}
}