X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=blobdiff_plain;f=bundles%2Forg.simantics.scl.compiler%2Fsrc%2Forg%2Fcojen%2Fclassfile%2FTypeDesc.java;fp=bundles%2Forg.simantics.scl.compiler%2Fsrc%2Forg%2Fcojen%2Fclassfile%2FTypeDesc.java;h=084eb3dc85445cef536d561c0eec6cf6d1c2e002;hb=969bd23cab98a79ca9101af33334000879fb60c5;hp=0000000000000000000000000000000000000000;hpb=866dba5cd5a3929bbeae85991796acb212338a08;p=simantics%2Fplatform.git diff --git a/bundles/org.simantics.scl.compiler/src/org/cojen/classfile/TypeDesc.java b/bundles/org.simantics.scl.compiler/src/org/cojen/classfile/TypeDesc.java new file mode 100644 index 000000000..084eb3dc8 --- /dev/null +++ b/bundles/org.simantics.scl.compiler/src/org/cojen/classfile/TypeDesc.java @@ -0,0 +1,921 @@ +/* + * Copyright 2004-2010 Brian S O'Neill + * + * 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.cojen.classfile; + +import java.io.Serializable; +import java.io.Externalizable; +import java.io.ObjectOutput; +import java.io.ObjectInput; +import java.io.IOException; +import java.io.ObjectStreamException; +import java.lang.ref.SoftReference; +import java.lang.reflect.Array; +import java.util.Collections; +import java.util.Map; + +import org.cojen.util.SoftValuedHashMap; +import org.cojen.util.WeakCanonicalSet; +import org.cojen.util.WeakIdentityMap; + +/** + * This class is used to build field and return type descriptor strings as + * defined in The Java Virtual Machine Specification, section 4.3.2. + * TypeDesc instances are canonicalized and therefore "==" comparable. + * + * @author Brian S O'Neill + */ +@SuppressWarnings("rawtypes") +public abstract class TypeDesc extends Descriptor implements Serializable { + /** + * Type code returned from getTypeCode, which can be used with the + * newarray instruction. + */ + public final static int + OBJECT_CODE = 0, + VOID_CODE = 1, + BOOLEAN_CODE = 4, + CHAR_CODE = 5, + FLOAT_CODE = 6, + DOUBLE_CODE = 7, + BYTE_CODE = 8, + SHORT_CODE = 9, + INT_CODE = 10, + LONG_CODE = 11; + + /** Primitive type void */ + public final static TypeDesc VOID; + /** Primitive type boolean */ + public final static TypeDesc BOOLEAN; + /** Primitive type char */ + public final static TypeDesc CHAR; + /** Primitive type byte */ + public final static TypeDesc BYTE; + /** Primitive type short */ + public final static TypeDesc SHORT; + /** Primitive type int */ + public final static TypeDesc INT; + /** Primitive type long */ + public final static TypeDesc LONG; + /** Primitive type float */ + public final static TypeDesc FLOAT; + /** Primitive type double */ + public final static TypeDesc DOUBLE; + + /** Object type java.lang.Object, provided for convenience */ + public final static TypeDesc OBJECT; + /** Object type java.lang.String, provided for convenience */ + public final static TypeDesc STRING; + + // Pool of all shared instances. Ensures identity comparison works. + final static WeakCanonicalSet cInstances; + + // Cache that maps Classes to TypeDescs. + private final static Map cClassesToInstances; + + // Cache that maps String names to TypeDescs. + private final static Map cNamesToInstances; + + // Cache that maps String descriptors to TypeDescs. + private final static Map cDescriptorsToInstances; + + static { + cInstances = new WeakCanonicalSet(); + + cClassesToInstances = Collections.synchronizedMap(new WeakIdentityMap()); + cNamesToInstances = Collections.synchronizedMap(new SoftValuedHashMap()); + cDescriptorsToInstances = Collections.synchronizedMap + (new SoftValuedHashMap()); + + VOID = intern(new PrimitiveType("V", VOID_CODE)); + BOOLEAN = intern(new PrimitiveType("Z", BOOLEAN_CODE)); + CHAR = intern(new PrimitiveType("C", CHAR_CODE)); + BYTE = intern(new PrimitiveType("B", BYTE_CODE)); + SHORT = intern(new PrimitiveType("S", SHORT_CODE)); + INT = intern(new PrimitiveType("I", INT_CODE)); + LONG = intern(new PrimitiveType("J", LONG_CODE)); + FLOAT = intern(new PrimitiveType("F", FLOAT_CODE)); + DOUBLE = intern(new PrimitiveType("D", DOUBLE_CODE)); + + OBJECT = forClass("java.lang.Object"); + STRING = forClass("java.lang.String"); + } + + static TypeDesc intern(TypeDesc type) { + return cInstances.put(type); + } + + /** + * Acquire a TypeDesc from any class, including primitives and arrays. + */ + public static TypeDesc forClass(final Class clazz) { + if (clazz == null) { + return null; + } + + TypeDesc type = (TypeDesc)cClassesToInstances.get(clazz); + + if (type == null || type.toClass() != clazz) { + if (clazz.isArray()) { + type = forClass(clazz.getComponentType()).toArrayType(); + } else if (clazz.isPrimitive()) { + if (clazz == int.class) { + type = INT; + } else if (clazz == boolean.class) { + type = BOOLEAN; + } else if (clazz == char.class) { + type = CHAR; + } else if (clazz == byte.class) { + type = BYTE; + } else if (clazz == long.class) { + type = LONG; + } else if (clazz == float.class) { + type = FLOAT; + } else if (clazz == double.class) { + type = DOUBLE; + } else if (clazz == short.class) { + type = SHORT; + } else if (clazz == void.class) { + type = VOID; + } + } else { + String name = clazz.getName(); + type = intern(new ObjectType(generateDescriptor(name), name)); + } + + if (type.toClass() != clazz) { + type = new ObjectType(type.getDescriptor(), clazz.getName()); + ((ObjectType) type).setClass(clazz); + } + + synchronized (cClassesToInstances) { + if (cClassesToInstances.containsKey(clazz)) { + type = (TypeDesc)cClassesToInstances.get(clazz); + } else { + cClassesToInstances.put(clazz, type); + } + } + } + + return type; + } + + /** + * Acquire a TypeDesc from any class name, including primitives and arrays. + * Primitive and array syntax matches Java declarations. + */ + public static TypeDesc forClass(final String name) throws IllegalArgumentException { + if (name.length() < 1) { + throw invalidName(name); + } + + // TODO: Support generics in name. + + TypeDesc type = (TypeDesc)cNamesToInstances.get(name); + if (type != null) { + return type; + } + + int index1 = name.lastIndexOf('['); + int index2 = name.lastIndexOf(']'); + if (index2 >= 0) { + if (index2 + 1 != name.length() || index1 + 1 != index2) { + throw invalidName(name); + } + try { + type = forClass(name.substring(0, index1)).toArrayType(); + } catch (IllegalArgumentException e) { + throw invalidName(name); + } + } else if (index1 >= 0) { + throw invalidName(name); + } else { + setType: { + switch (name.charAt(0)) { + case 'v': + if (name.equals("void")) { + type = VOID; + break setType; + } + break; + case 'b': + if (name.equals("boolean")) { + type = BOOLEAN; + break setType; + } else if (name.equals("byte")) { + type = BYTE; + break setType; + } + break; + case 'c': + if (name.equals("char")) { + type = CHAR; + break setType; + } + break; + case 's': + if (name.equals("short")) { + type = SHORT; + break setType; + } + break; + case 'i': + if (name.equals("int")) { + type = INT; + break setType; + } + break; + case 'l': + if (name.equals("long")) { + type = LONG; + break setType; + } + break; + case 'f': + if (name.equals("float")) { + type = FLOAT; + break setType; + } + break; + case 'd': + if (name.equals("double")) { + type = DOUBLE; + break setType; + } + break; + } + + String desc = generateDescriptor(name); + if (name.indexOf('/') < 0) { + type = new ObjectType(desc, name); + } else { + type = new ObjectType(desc, name.replace('/', '.')); + } + type = intern(type); + } + } + + cNamesToInstances.put(name, type); + return type; + } + + private static IllegalArgumentException invalidName(String name) { + return new IllegalArgumentException("Invalid name: " + name); + } + + /** + * Acquire a TypeDesc from a type descriptor. This syntax is described in + * section 4.3.2, Field Descriptors. + */ + public static TypeDesc forDescriptor(final String desc) throws IllegalArgumentException { + TypeDesc type = (TypeDesc)cDescriptorsToInstances.get(desc); + if (type != null) { + return type; + } + + // TODO: Support generics in descriptor. + + String rootDesc = desc; + int cursor = 0; + int dim = 0; + try { + char c; + while ((c = rootDesc.charAt(cursor++)) == '[') { + dim++; + } + + switch (c) { + case 'V': + type = VOID; + break; + case 'Z': + type = BOOLEAN; + break; + case 'C': + type = CHAR; + break; + case 'B': + type = BYTE; + break; + case 'S': + type = SHORT; + break; + case 'I': + type = INT; + break; + case 'J': + type = LONG; + break; + case 'F': + type = FLOAT; + break; + case 'D': + type = DOUBLE; + break; + case 'L': + if (dim > 0) { + rootDesc = rootDesc.substring(dim); + cursor = 1; + } + StringBuffer name = new StringBuffer(rootDesc.length() - 2); + while ((c = rootDesc.charAt(cursor++)) != ';') { + if (c == '/') { + c = '.'; + } + name.append(c); + } + type = intern(new ObjectType(rootDesc, name.toString())); + break; + default: + throw invalidDescriptor(desc); + } + } catch (NullPointerException e) { + throw invalidDescriptor(desc); + } catch (IndexOutOfBoundsException e) { + throw invalidDescriptor(desc); + } + + if (cursor != rootDesc.length()) { + throw invalidDescriptor(desc); + } + + while (--dim >= 0) { + type = type.toArrayType(); + } + + cDescriptorsToInstances.put(desc, type); + return type; + } + + private static IllegalArgumentException invalidDescriptor(String desc) { + return new IllegalArgumentException("Invalid descriptor: " + desc); + } + + private static String generateDescriptor(String classname) { + int length = classname.length(); + char[] buf = new char[length + 2]; + buf[0] = 'L'; + classname.getChars(0, length, buf, 1); + int i; + for (i=1; i<=length; i++) { + char c = buf[i]; + if (c == '.') { + buf[i] = '/'; + } + } + buf[i] = ';'; + return new String(buf); + } + + transient final String mDescriptor; + + TypeDesc(String desc) { + mDescriptor = desc; + } + + /** + * Returns a type descriptor string, excluding generics. + */ + public final String getDescriptor() { + return mDescriptor; + } + + /** + * Returns a type descriptor string, including any generics. + */ + //public abstract String getGenericDescriptor(); + + /** + * Returns the class name for this descriptor. If the type is primitive, + * then the Java primitive type name is returned. If the type is an array, + * only the root component type name is returned. + */ + public abstract String getRootName(); + + /** + * Returns the class name for this descriptor. If the type is primitive, + * then the Java primitive type name is returned. If the type is an array, + * "[]" is append at the end of the name for each dimension. + */ + public abstract String getFullName(); + + // TODO + //public abstract String getGenericRootName(); + + // TODO + //public abstract String getGenericFullName(); + + /** + * Returns a type code for operating on primitive types in switches. If + * not primitive, OBJECT_CODE is returned. + */ + public abstract int getTypeCode(); + + /** + * Returns true if this is a primitive type. + */ + public abstract boolean isPrimitive(); + + /** + * Returns true if this is a primitive long or double type. + */ + public abstract boolean isDoubleWord(); + + /** + * Returns true if this is an array type. + */ + public abstract boolean isArray(); + + /** + * Returns the number of dimensions this array type has. If not an array, + * zero is returned. + */ + public abstract int getDimensions(); + + /** + * Returns the component type of this array type. If not an array, null is + * returned. + */ + public abstract TypeDesc getComponentType(); + + /** + * Returns the root component type of this array type. If not an array, + * null is returned. + */ + public abstract TypeDesc getRootComponentType(); + + /** + * Convertes this type to an array type. If already an array, another + * dimension is added. + */ + public abstract TypeDesc toArrayType(); + + /** + * Returns the object peer of this primitive type. For int, the object peer + * is java.lang.Integer. If this type is an object type, it is simply + * returned. + */ + public abstract TypeDesc toObjectType(); + + /** + * Returns the primitive peer of this object type, if one exists. For + * java.lang.Integer, the primitive peer is int. If this type is a + * primitive type, it is simply returned. Arrays have no primitive peer, + * and so null is returned instead. + */ + public abstract TypeDesc toPrimitiveType(); + + /** + * Returns this type as a class. If the class isn't found, null is + * returned. + */ + public abstract Class toClass(); + + /** + * Returns this type as a class. If the class isn't found, null is + * returned. + * @param loader optional ClassLoader to load class from + */ + public abstract Class toClass(ClassLoader loader); + + public String toString() { + // TODO: Return generic descriptor + return mDescriptor; + } + + public int hashCode() { + return mDescriptor.hashCode(); + } + + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (other instanceof TypeDesc) { + return ((TypeDesc)other).mDescriptor.equals(mDescriptor); + } + return false; + } + + Object writeReplace() throws ObjectStreamException { + return new External(mDescriptor); + } + + private static class PrimitiveType extends TypeDesc { + private transient final int mCode; + private transient TypeDesc mArrayType; + private transient TypeDesc mObjectType; + + PrimitiveType(String desc, int code) { + super(desc); + mCode = code; + } + + public String getRootName() { + switch (mCode) { + default: + case VOID_CODE: + return "void"; + case BOOLEAN_CODE: + return "boolean"; + case CHAR_CODE: + return "char"; + case BYTE_CODE: + return "byte"; + case SHORT_CODE: + return "short"; + case INT_CODE: + return "int"; + case LONG_CODE: + return "long"; + case FLOAT_CODE: + return "float"; + case DOUBLE_CODE: + return "double"; + } + } + + public String getFullName() { + return getRootName(); + } + + public int getTypeCode() { + return mCode; + } + + public boolean isPrimitive() { + return true; + } + + public boolean isDoubleWord() { + return mCode == DOUBLE_CODE || mCode == LONG_CODE; + } + + public boolean isArray() { + return false; + } + + public int getDimensions() { + return 0; + } + + public TypeDesc getComponentType() { + return null; + } + + public TypeDesc getRootComponentType() { + return null; + } + + public TypeDesc toArrayType() { + if (mArrayType == null) { + char[] buf = new char[2]; + buf[0] = '['; + buf[1] = mDescriptor.charAt(0); + mArrayType = intern(new ArrayType(new String(buf), this)); + } + return mArrayType; + } + + public TypeDesc toObjectType() { + if (mObjectType == null) { + switch (mCode) { + default: + case VOID_CODE: + mObjectType = forClass("java.lang.Void"); + break; + case BOOLEAN_CODE: + mObjectType = forClass("java.lang.Boolean"); + break; + case CHAR_CODE: + mObjectType = forClass("java.lang.Character"); + break; + case BYTE_CODE: + mObjectType = forClass("java.lang.Byte"); + break; + case SHORT_CODE: + mObjectType = forClass("java.lang.Short"); + break; + case INT_CODE: + mObjectType = forClass("java.lang.Integer"); + break; + case LONG_CODE: + mObjectType = forClass("java.lang.Long"); + break; + case FLOAT_CODE: + mObjectType = forClass("java.lang.Float"); + break; + case DOUBLE_CODE: + mObjectType = forClass("java.lang.Double"); + break; + } + } + return mObjectType; + } + + public TypeDesc toPrimitiveType() { + return this; + } + + public Class toClass() { + switch (mCode) { + default: + case VOID_CODE: + return void.class; + case BOOLEAN_CODE: + return boolean.class; + case CHAR_CODE: + return char.class; + case BYTE_CODE: + return byte.class; + case SHORT_CODE: + return short.class; + case INT_CODE: + return int.class; + case LONG_CODE: + return long.class; + case FLOAT_CODE: + return float.class; + case DOUBLE_CODE: + return double.class; + } + } + + public Class toClass(ClassLoader loader) { + return toClass(); + } + } + + private static class ObjectType extends TypeDesc { + private transient final String mName; + private transient TypeDesc mArrayType; + private transient TypeDesc mPrimitiveType; + + // Since cClassesToInstances may reference this instance, softly + // reference back to class to allow it to be garbage collected. + private transient SoftReference mClassRef; + + ObjectType(String desc, String name) { + super(desc); + mName = name; + } + + public String getRootName() { + return mName; + } + + public String getFullName() { + return mName; + } + + public int getTypeCode() { + return OBJECT_CODE; + } + + public boolean isPrimitive() { + return false; + } + + public boolean isDoubleWord() { + return false; + } + + public boolean isArray() { + return false; + } + + public int getDimensions() { + return 0; + } + + public TypeDesc getComponentType() { + return null; + } + + public TypeDesc getRootComponentType() { + return null; + } + + public TypeDesc toArrayType() { + if (mArrayType == null) { + int length = mDescriptor.length(); + char[] buf = new char[length + 1]; + buf[0] = '['; + mDescriptor.getChars(0, length, buf, 1); + mArrayType = intern(new ArrayType(new String(buf), this)); + } + return mArrayType; + } + + public TypeDesc toObjectType() { + return this; + } + + public TypeDesc toPrimitiveType() { + if (mPrimitiveType == null) { + String name = mName; + if (name.startsWith("java.lang.") && name.length() > 10) { + switch (name.charAt(10)) { + case 'V': + if (name.equals("java.lang.Void")) { + mPrimitiveType = VOID; + } + break; + case 'B': + if (name.equals("java.lang.Boolean")) { + mPrimitiveType = BOOLEAN; + } else if (name.equals("java.lang.Byte")) { + mPrimitiveType = BYTE; + } + break; + case 'C': + if (name.equals("java.lang.Character")) { + mPrimitiveType = CHAR; + } + break; + case 'S': + if (name.equals("java.lang.Short")) { + mPrimitiveType = SHORT; + } + break; + case 'I': + if (name.equals("java.lang.Integer")) { + mPrimitiveType = INT; + } + break; + case 'L': + if (name.equals("java.lang.Long")) { + mPrimitiveType = LONG; + } + break; + case 'F': + if (name.equals("java.lang.Float")) { + mPrimitiveType = FLOAT; + } + break; + case 'D': + if (name.equals("java.lang.Double")) { + mPrimitiveType = DOUBLE; + } + break; + } + } + } + + return mPrimitiveType; + } + + public final synchronized Class toClass() { + Class clazz; + if (mClassRef != null) { + clazz = mClassRef.get(); + if (clazz != null) { + return clazz; + } + } + clazz = toClass(null); + mClassRef = new SoftReference(clazz); + return clazz; + } + + public Class toClass(ClassLoader loader) { + TypeDesc type = toPrimitiveType(); + if (type != null) { + switch (type.getTypeCode()) { + default: + case VOID_CODE: + return Void.class; + case BOOLEAN_CODE: + return Boolean.class; + case CHAR_CODE: + return Character.class; + case FLOAT_CODE: + return Float.class; + case DOUBLE_CODE: + return Double.class; + case BYTE_CODE: + return Byte.class; + case SHORT_CODE: + return Short.class; + case INT_CODE: + return Integer.class; + case LONG_CODE: + return Long.class; + } + } + + try { + if (loader == null) { + return Class.forName(mName); + } else { + return loader.loadClass(mName); + } + } catch (ClassNotFoundException e) { + return null; + } + } + + void setClass(Class clazz) { + mClassRef = new SoftReference(clazz); + } + } + + private static class ArrayType extends ObjectType { + private transient final TypeDesc mComponent; + private transient final String mFullName; + + ArrayType(String desc, TypeDesc component) { + super(desc, component.getRootName()); + mComponent = component; + mFullName = component.getFullName().concat("[]"); + } + + public String getFullName() { + return mFullName; + } + + public boolean isArray() { + return true; + } + + public int getDimensions() { + return mComponent.getDimensions() + 1; + } + + public TypeDesc getComponentType() { + return mComponent; + } + + public TypeDesc getRootComponentType() { + TypeDesc type = mComponent; + while (type.isArray()) { + type = type.getComponentType(); + } + return type; + } + + public TypeDesc toPrimitiveType() { + return null; + } + + public Class toClass(ClassLoader loader) { + if (loader == null) { + return arrayClass(getRootComponentType().toClass()); + } else { + return arrayClass(getRootComponentType().toClass(loader)); + } + } + + private Class arrayClass(Class clazz) { + if (clazz == null) { + return null; + } + int dim = getDimensions(); + try { + if (dim == 1) { + return Array.newInstance(clazz, 0).getClass(); + } else { + return Array.newInstance(clazz, new int[dim]).getClass(); + } + } catch (IllegalArgumentException e) { + return null; + } + } + } + + private static class External implements Externalizable { + private String mDescriptor; + + public External() { + } + + public External(String desc) { + mDescriptor = desc; + } + + public void writeExternal(ObjectOutput out) throws IOException { + out.writeUTF(mDescriptor); + } + + public void readExternal(ObjectInput in) throws IOException { + mDescriptor = in.readUTF(); + } + + public Object readResolve() throws ObjectStreamException { + return forDescriptor(mDescriptor); + } + } +}