2 * Copyright 2004-2010 Brian S O'Neill
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package org.cojen.classfile;
19 import java.io.Serializable;
20 import java.lang.ref.SoftReference;
21 import java.lang.reflect.Array;
22 import java.util.Collections;
25 import org.cojen.util.SoftValuedHashMap;
26 import org.cojen.util.WeakCanonicalSet;
27 import org.cojen.util.WeakIdentityMap;
30 * This class is used to build field and return type descriptor strings as
31 * defined in <i>The Java Virtual Machine Specification</i>, section 4.3.2.
32 * TypeDesc instances are canonicalized and therefore "==" comparable.
34 * @author Brian S O'Neill
36 @SuppressWarnings("rawtypes")
37 public abstract class TypeDesc extends Descriptor implements Serializable {
39 * Type code returned from getTypeCode, which can be used with the
40 * newarray instruction.
42 public final static int
54 /** Primitive type void */
55 public final static TypeDesc VOID;
56 /** Primitive type boolean */
57 public final static TypeDesc BOOLEAN;
58 /** Primitive type char */
59 public final static TypeDesc CHAR;
60 /** Primitive type byte */
61 public final static TypeDesc BYTE;
62 /** Primitive type short */
63 public final static TypeDesc SHORT;
64 /** Primitive type int */
65 public final static TypeDesc INT;
66 /** Primitive type long */
67 public final static TypeDesc LONG;
68 /** Primitive type float */
69 public final static TypeDesc FLOAT;
70 /** Primitive type double */
71 public final static TypeDesc DOUBLE;
73 /** Object type java.lang.Object, provided for convenience */
74 public final static TypeDesc OBJECT;
75 /** Object type java.lang.String, provided for convenience */
76 public final static TypeDesc STRING;
78 // Pool of all shared instances. Ensures identity comparison works.
79 final static WeakCanonicalSet<Descriptor> cInstances;
81 // Cache that maps Classes to TypeDescs.
82 private final static Map<Class, TypeDesc> cClassesToInstances;
84 // Cache that maps String names to TypeDescs.
85 private final static Map<String, TypeDesc> cNamesToInstances;
87 // Cache that maps String descriptors to TypeDescs.
88 private final static Map<String, TypeDesc> cDescriptorsToInstances;
91 cInstances = new WeakCanonicalSet<Descriptor>();
93 cClassesToInstances = Collections.synchronizedMap(new WeakIdentityMap<Class, TypeDesc>());
94 cNamesToInstances = Collections.synchronizedMap(new SoftValuedHashMap<String, TypeDesc>());
95 cDescriptorsToInstances = Collections.synchronizedMap
96 (new SoftValuedHashMap<String, TypeDesc>());
98 VOID = intern(new PrimitiveType("V", VOID_CODE));
99 BOOLEAN = intern(new PrimitiveType("Z", BOOLEAN_CODE));
100 CHAR = intern(new PrimitiveType("C", CHAR_CODE));
101 BYTE = intern(new PrimitiveType("B", BYTE_CODE));
102 SHORT = intern(new PrimitiveType("S", SHORT_CODE));
103 INT = intern(new PrimitiveType("I", INT_CODE));
104 LONG = intern(new PrimitiveType("J", LONG_CODE));
105 FLOAT = intern(new PrimitiveType("F", FLOAT_CODE));
106 DOUBLE = intern(new PrimitiveType("D", DOUBLE_CODE));
108 OBJECT = forClass("java.lang.Object");
109 STRING = forClass("java.lang.String");
112 static TypeDesc intern(TypeDesc type) {
113 return cInstances.put(type);
117 * Acquire a TypeDesc from any class, including primitives and arrays.
119 public static TypeDesc forClass(final Class clazz) {
124 TypeDesc type = (TypeDesc)cClassesToInstances.get(clazz);
126 if (type == null || type.toClass() != clazz) {
127 if (clazz.isArray()) {
128 type = forClass(clazz.getComponentType()).toArrayType();
129 } else if (clazz.isPrimitive()) {
130 if (clazz == int.class) {
132 } else if (clazz == boolean.class) {
134 } else if (clazz == char.class) {
136 } else if (clazz == byte.class) {
138 } else if (clazz == long.class) {
140 } else if (clazz == float.class) {
142 } else if (clazz == double.class) {
144 } else if (clazz == short.class) {
146 } else if (clazz == void.class) {
150 String name = clazz.getName();
151 type = intern(new ObjectType(generateDescriptor(name), name));
154 if (type.toClass() != clazz) {
155 type = new ObjectType(type.getDescriptor(), clazz.getName());
156 ((ObjectType) type).setClass(clazz);
159 synchronized (cClassesToInstances) {
160 if (cClassesToInstances.containsKey(clazz)) {
161 type = (TypeDesc)cClassesToInstances.get(clazz);
163 cClassesToInstances.put(clazz, type);
172 * Acquire a TypeDesc from any class name, including primitives and arrays.
173 * Primitive and array syntax matches Java declarations.
175 public static TypeDesc forClass(final String name) throws IllegalArgumentException {
176 if (name.length() < 1) {
177 throw invalidName(name);
180 // TODO: Support generics in name.
182 TypeDesc type = (TypeDesc)cNamesToInstances.get(name);
187 int index1 = name.lastIndexOf('[');
188 int index2 = name.lastIndexOf(']');
190 if (index2 + 1 != name.length() || index1 + 1 != index2) {
191 throw invalidName(name);
194 type = forClass(name.substring(0, index1)).toArrayType();
195 } catch (IllegalArgumentException e) {
196 throw invalidName(name);
198 } else if (index1 >= 0) {
199 throw invalidName(name);
202 switch (name.charAt(0)) {
204 if (name.equals("void")) {
210 if (name.equals("boolean")) {
213 } else if (name.equals("byte")) {
219 if (name.equals("char")) {
225 if (name.equals("short")) {
231 if (name.equals("int")) {
237 if (name.equals("long")) {
243 if (name.equals("float")) {
249 if (name.equals("double")) {
256 String desc = generateDescriptor(name);
257 if (name.indexOf('/') < 0) {
258 type = new ObjectType(desc, name);
260 type = new ObjectType(desc, name.replace('/', '.'));
266 cNamesToInstances.put(name, type);
270 private static IllegalArgumentException invalidName(String name) {
271 return new IllegalArgumentException("Invalid name: " + name);
275 * Acquire a TypeDesc from a type descriptor. This syntax is described in
276 * section 4.3.2, Field Descriptors.
278 public static TypeDesc forDescriptor(final String desc) throws IllegalArgumentException {
279 TypeDesc type = (TypeDesc)cDescriptorsToInstances.get(desc);
284 // TODO: Support generics in descriptor.
286 String rootDesc = desc;
291 while ((c = rootDesc.charAt(cursor++)) == '[') {
325 rootDesc = rootDesc.substring(dim);
328 StringBuffer name = new StringBuffer(rootDesc.length() - 2);
329 while ((c = rootDesc.charAt(cursor++)) != ';') {
335 type = intern(new ObjectType(rootDesc, name.toString()));
338 throw invalidDescriptor(desc);
340 } catch (NullPointerException e) {
341 throw invalidDescriptor(desc);
342 } catch (IndexOutOfBoundsException e) {
343 throw invalidDescriptor(desc);
346 if (cursor != rootDesc.length()) {
347 throw invalidDescriptor(desc);
351 type = type.toArrayType();
354 cDescriptorsToInstances.put(desc, type);
358 private static IllegalArgumentException invalidDescriptor(String desc) {
359 return new IllegalArgumentException("Invalid descriptor: " + desc);
362 private static String generateDescriptor(String classname) {
363 int length = classname.length();
364 char[] buf = new char[length + 2];
366 classname.getChars(0, length, buf, 1);
368 for (i=1; i<=length; i++) {
375 return new String(buf);
378 transient final String mDescriptor;
380 TypeDesc(String desc) {
385 * Returns a type descriptor string, excluding generics.
387 public final String getDescriptor() {
392 * Returns a type descriptor string, including any generics.
394 //public abstract String getGenericDescriptor();
397 * Returns the class name for this descriptor. If the type is primitive,
398 * then the Java primitive type name is returned. If the type is an array,
399 * only the root component type name is returned.
401 public abstract String getRootName();
404 * Returns the class name for this descriptor. If the type is primitive,
405 * then the Java primitive type name is returned. If the type is an array,
406 * "[]" is append at the end of the name for each dimension.
408 public abstract String getFullName();
411 //public abstract String getGenericRootName();
414 //public abstract String getGenericFullName();
417 * Returns a type code for operating on primitive types in switches. If
418 * not primitive, OBJECT_CODE is returned.
420 public abstract int getTypeCode();
423 * Returns true if this is a primitive type.
425 public abstract boolean isPrimitive();
428 * Returns true if this is a primitive long or double type.
430 public abstract boolean isDoubleWord();
433 * Returns true if this is an array type.
435 public abstract boolean isArray();
438 * Returns the number of dimensions this array type has. If not an array,
441 public abstract int getDimensions();
444 * Returns the component type of this array type. If not an array, null is
447 public abstract TypeDesc getComponentType();
450 * Returns the root component type of this array type. If not an array,
453 public abstract TypeDesc getRootComponentType();
456 * Convertes this type to an array type. If already an array, another
457 * dimension is added.
459 public abstract TypeDesc toArrayType();
462 * Returns the object peer of this primitive type. For int, the object peer
463 * is java.lang.Integer. If this type is an object type, it is simply
466 public abstract TypeDesc toObjectType();
469 * Returns the primitive peer of this object type, if one exists. For
470 * java.lang.Integer, the primitive peer is int. If this type is a
471 * primitive type, it is simply returned. Arrays have no primitive peer,
472 * and so null is returned instead.
474 public abstract TypeDesc toPrimitiveType();
477 * Returns this type as a class. If the class isn't found, null is
480 public abstract Class toClass();
483 * Returns this type as a class. If the class isn't found, null is
485 * @param loader optional ClassLoader to load class from
487 public abstract Class toClass(ClassLoader loader);
489 public String toString() {
490 // TODO: Return generic descriptor
494 public int hashCode() {
495 return mDescriptor.hashCode();
498 public boolean equals(Object other) {
502 if (other instanceof TypeDesc) {
503 return ((TypeDesc)other).mDescriptor.equals(mDescriptor);
508 private static class PrimitiveType extends TypeDesc {
509 private transient final int mCode;
510 private transient TypeDesc mArrayType;
511 private transient TypeDesc mObjectType;
513 PrimitiveType(String desc, int code) {
518 public String getRootName() {
542 public String getFullName() {
543 return getRootName();
546 public int getTypeCode() {
550 public boolean isPrimitive() {
554 public boolean isDoubleWord() {
555 return mCode == DOUBLE_CODE || mCode == LONG_CODE;
558 public boolean isArray() {
562 public int getDimensions() {
566 public TypeDesc getComponentType() {
570 public TypeDesc getRootComponentType() {
574 public TypeDesc toArrayType() {
575 if (mArrayType == null) {
576 char[] buf = new char[2];
578 buf[1] = mDescriptor.charAt(0);
579 mArrayType = intern(new ArrayType(new String(buf), this));
584 public TypeDesc toObjectType() {
585 if (mObjectType == null) {
589 mObjectType = forClass("java.lang.Void");
592 mObjectType = forClass("java.lang.Boolean");
595 mObjectType = forClass("java.lang.Character");
598 mObjectType = forClass("java.lang.Byte");
601 mObjectType = forClass("java.lang.Short");
604 mObjectType = forClass("java.lang.Integer");
607 mObjectType = forClass("java.lang.Long");
610 mObjectType = forClass("java.lang.Float");
613 mObjectType = forClass("java.lang.Double");
620 public TypeDesc toPrimitiveType() {
624 public Class toClass() {
630 return boolean.class;
648 public Class toClass(ClassLoader loader) {
653 private static class ObjectType extends TypeDesc {
654 private transient final String mName;
655 private transient TypeDesc mArrayType;
656 private transient TypeDesc mPrimitiveType;
658 // Since cClassesToInstances may reference this instance, softly
659 // reference back to class to allow it to be garbage collected.
660 private transient SoftReference<Class> mClassRef;
662 ObjectType(String desc, String name) {
667 public String getRootName() {
671 public String getFullName() {
675 public int getTypeCode() {
679 public boolean isPrimitive() {
683 public boolean isDoubleWord() {
687 public boolean isArray() {
691 public int getDimensions() {
695 public TypeDesc getComponentType() {
699 public TypeDesc getRootComponentType() {
703 public TypeDesc toArrayType() {
704 if (mArrayType == null) {
705 int length = mDescriptor.length();
706 char[] buf = new char[length + 1];
708 mDescriptor.getChars(0, length, buf, 1);
709 mArrayType = intern(new ArrayType(new String(buf), this));
714 public TypeDesc toObjectType() {
718 public TypeDesc toPrimitiveType() {
719 if (mPrimitiveType == null) {
721 if (name.startsWith("java.lang.") && name.length() > 10) {
722 switch (name.charAt(10)) {
724 if (name.equals("java.lang.Void")) {
725 mPrimitiveType = VOID;
729 if (name.equals("java.lang.Boolean")) {
730 mPrimitiveType = BOOLEAN;
731 } else if (name.equals("java.lang.Byte")) {
732 mPrimitiveType = BYTE;
736 if (name.equals("java.lang.Character")) {
737 mPrimitiveType = CHAR;
741 if (name.equals("java.lang.Short")) {
742 mPrimitiveType = SHORT;
746 if (name.equals("java.lang.Integer")) {
747 mPrimitiveType = INT;
751 if (name.equals("java.lang.Long")) {
752 mPrimitiveType = LONG;
756 if (name.equals("java.lang.Float")) {
757 mPrimitiveType = FLOAT;
761 if (name.equals("java.lang.Double")) {
762 mPrimitiveType = DOUBLE;
769 return mPrimitiveType;
772 public final synchronized Class toClass() {
774 if (mClassRef != null) {
775 clazz = mClassRef.get();
780 clazz = toClass(null);
781 mClassRef = new SoftReference<Class>(clazz);
785 public Class toClass(ClassLoader loader) {
786 TypeDesc type = toPrimitiveType();
788 switch (type.getTypeCode()) {
793 return Boolean.class;
795 return Character.class;
805 return Integer.class;
812 if (loader == null) {
813 return Class.forName(mName);
815 return loader.loadClass(mName);
817 } catch (ClassNotFoundException e) {
822 void setClass(Class clazz) {
823 mClassRef = new SoftReference<Class>(clazz);
827 private static class ArrayType extends ObjectType {
828 private transient final TypeDesc mComponent;
829 private transient final String mFullName;
831 ArrayType(String desc, TypeDesc component) {
832 super(desc, component.getRootName());
833 mComponent = component;
834 mFullName = component.getFullName().concat("[]");
837 public String getFullName() {
841 public boolean isArray() {
845 public int getDimensions() {
846 return mComponent.getDimensions() + 1;
849 public TypeDesc getComponentType() {
853 public TypeDesc getRootComponentType() {
854 TypeDesc type = mComponent;
855 while (type.isArray()) {
856 type = type.getComponentType();
861 public TypeDesc toPrimitiveType() {
865 public Class toClass(ClassLoader loader) {
866 if (loader == null) {
867 return arrayClass(getRootComponentType().toClass());
869 return arrayClass(getRootComponentType().toClass(loader));
873 private Class arrayClass(Class clazz) {
877 int dim = getDimensions();
880 return Array.newInstance(clazz, 0).getClass();
882 return Array.newInstance(clazz, new int[dim]).getClass();
884 } catch (IllegalArgumentException e) {