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.lang.ref.SoftReference;
20 import java.lang.reflect.Array;
21 import java.util.Collections;
24 import org.cojen.util.SoftValuedHashMap;
25 import org.cojen.util.WeakCanonicalSet;
26 import org.cojen.util.WeakIdentityMap;
29 * This class is used to build field and return type descriptor strings as
30 * defined in <i>The Java Virtual Machine Specification</i>, section 4.3.2.
31 * TypeDesc instances are canonicalized and therefore "==" comparable.
33 * @author Brian S O'Neill
35 @SuppressWarnings("rawtypes")
36 public abstract class TypeDesc extends Descriptor {
38 * Type code returned from getTypeCode, which can be used with the
39 * newarray instruction.
41 public final static int
53 /** Primitive type void */
54 public final static TypeDesc VOID;
55 /** Primitive type boolean */
56 public final static TypeDesc BOOLEAN;
57 /** Primitive type char */
58 public final static TypeDesc CHAR;
59 /** Primitive type byte */
60 public final static TypeDesc BYTE;
61 /** Primitive type short */
62 public final static TypeDesc SHORT;
63 /** Primitive type int */
64 public final static TypeDesc INT;
65 /** Primitive type long */
66 public final static TypeDesc LONG;
67 /** Primitive type float */
68 public final static TypeDesc FLOAT;
69 /** Primitive type double */
70 public final static TypeDesc DOUBLE;
72 /** Object type java.lang.Object, provided for convenience */
73 public final static TypeDesc OBJECT;
74 /** Object type java.lang.String, provided for convenience */
75 public final static TypeDesc STRING;
77 // Pool of all shared instances. Ensures identity comparison works.
78 final static WeakCanonicalSet<Descriptor> cInstances;
80 // Cache that maps Classes to TypeDescs.
81 private final static Map<Class, TypeDesc> cClassesToInstances;
83 // Cache that maps String names to TypeDescs.
84 private final static Map<String, TypeDesc> cNamesToInstances;
86 // Cache that maps String descriptors to TypeDescs.
87 private final static Map<String, TypeDesc> cDescriptorsToInstances;
90 cInstances = new WeakCanonicalSet<Descriptor>();
92 cClassesToInstances = Collections.synchronizedMap(new WeakIdentityMap<Class, TypeDesc>());
93 cNamesToInstances = Collections.synchronizedMap(new SoftValuedHashMap<String, TypeDesc>());
94 cDescriptorsToInstances = Collections.synchronizedMap
95 (new SoftValuedHashMap<String, TypeDesc>());
97 VOID = intern(new PrimitiveType("V", VOID_CODE));
98 BOOLEAN = intern(new PrimitiveType("Z", BOOLEAN_CODE));
99 CHAR = intern(new PrimitiveType("C", CHAR_CODE));
100 BYTE = intern(new PrimitiveType("B", BYTE_CODE));
101 SHORT = intern(new PrimitiveType("S", SHORT_CODE));
102 INT = intern(new PrimitiveType("I", INT_CODE));
103 LONG = intern(new PrimitiveType("J", LONG_CODE));
104 FLOAT = intern(new PrimitiveType("F", FLOAT_CODE));
105 DOUBLE = intern(new PrimitiveType("D", DOUBLE_CODE));
107 OBJECT = forClass("java.lang.Object");
108 STRING = forClass("java.lang.String");
111 static TypeDesc intern(TypeDesc type) {
112 return cInstances.put(type);
116 * Acquire a TypeDesc from any class, including primitives and arrays.
118 public static TypeDesc forClass(final Class clazz) {
123 TypeDesc type = (TypeDesc)cClassesToInstances.get(clazz);
125 if (type == null || type.toClass() != clazz) {
126 if (clazz.isArray()) {
127 type = forClass(clazz.getComponentType()).toArrayType();
128 } else if (clazz.isPrimitive()) {
129 if (clazz == int.class) {
131 } else if (clazz == boolean.class) {
133 } else if (clazz == char.class) {
135 } else if (clazz == byte.class) {
137 } else if (clazz == long.class) {
139 } else if (clazz == float.class) {
141 } else if (clazz == double.class) {
143 } else if (clazz == short.class) {
145 } else if (clazz == void.class) {
149 String name = clazz.getName();
150 type = intern(new ObjectType(generateDescriptor(name), name));
153 if (type.toClass() != clazz) {
154 type = new ObjectType(type.getDescriptor(), clazz.getName());
155 ((ObjectType) type).setClass(clazz);
158 synchronized (cClassesToInstances) {
159 if (cClassesToInstances.containsKey(clazz)) {
160 type = (TypeDesc)cClassesToInstances.get(clazz);
162 cClassesToInstances.put(clazz, type);
171 * Acquire a TypeDesc from any class name, including primitives and arrays.
172 * Primitive and array syntax matches Java declarations.
174 public static TypeDesc forClass(final String name) throws IllegalArgumentException {
175 if (name.length() < 1) {
176 throw invalidName(name);
179 // TODO: Support generics in name.
181 TypeDesc type = (TypeDesc)cNamesToInstances.get(name);
186 int index1 = name.lastIndexOf('[');
187 int index2 = name.lastIndexOf(']');
189 if (index2 + 1 != name.length() || index1 + 1 != index2) {
190 throw invalidName(name);
193 type = forClass(name.substring(0, index1)).toArrayType();
194 } catch (IllegalArgumentException e) {
195 throw invalidName(name);
197 } else if (index1 >= 0) {
198 throw invalidName(name);
201 switch (name.charAt(0)) {
203 if (name.equals("void")) {
209 if (name.equals("boolean")) {
212 } else if (name.equals("byte")) {
218 if (name.equals("char")) {
224 if (name.equals("short")) {
230 if (name.equals("int")) {
236 if (name.equals("long")) {
242 if (name.equals("float")) {
248 if (name.equals("double")) {
255 String desc = generateDescriptor(name);
256 if (name.indexOf('/') < 0) {
257 type = new ObjectType(desc, name);
259 type = new ObjectType(desc, name.replace('/', '.'));
265 cNamesToInstances.put(name, type);
269 private static IllegalArgumentException invalidName(String name) {
270 return new IllegalArgumentException("Invalid name: " + name);
274 * Acquire a TypeDesc from a type descriptor. This syntax is described in
275 * section 4.3.2, Field Descriptors.
277 public static TypeDesc forDescriptor(final String desc) throws IllegalArgumentException {
278 TypeDesc type = (TypeDesc)cDescriptorsToInstances.get(desc);
283 // TODO: Support generics in descriptor.
285 String rootDesc = desc;
290 while ((c = rootDesc.charAt(cursor++)) == '[') {
324 rootDesc = rootDesc.substring(dim);
327 StringBuffer name = new StringBuffer(rootDesc.length() - 2);
328 while ((c = rootDesc.charAt(cursor++)) != ';') {
334 type = intern(new ObjectType(rootDesc, name.toString()));
337 throw invalidDescriptor(desc);
339 } catch (NullPointerException e) {
340 throw invalidDescriptor(desc);
341 } catch (IndexOutOfBoundsException e) {
342 throw invalidDescriptor(desc);
345 if (cursor != rootDesc.length()) {
346 throw invalidDescriptor(desc);
350 type = type.toArrayType();
353 cDescriptorsToInstances.put(desc, type);
357 private static IllegalArgumentException invalidDescriptor(String desc) {
358 return new IllegalArgumentException("Invalid descriptor: " + desc);
361 private static String generateDescriptor(String classname) {
362 int length = classname.length();
363 char[] buf = new char[length + 2];
365 classname.getChars(0, length, buf, 1);
367 for (i=1; i<=length; i++) {
374 return new String(buf);
377 transient final String mDescriptor;
379 TypeDesc(String desc) {
384 * Returns a type descriptor string, excluding generics.
386 public final String getDescriptor() {
391 * Returns a type descriptor string, including any generics.
393 //public abstract String getGenericDescriptor();
396 * Returns the class name for this descriptor. If the type is primitive,
397 * then the Java primitive type name is returned. If the type is an array,
398 * only the root component type name is returned.
400 public abstract String getRootName();
403 * Returns the class name for this descriptor. If the type is primitive,
404 * then the Java primitive type name is returned. If the type is an array,
405 * "[]" is append at the end of the name for each dimension.
407 public abstract String getFullName();
410 //public abstract String getGenericRootName();
413 //public abstract String getGenericFullName();
416 * Returns a type code for operating on primitive types in switches. If
417 * not primitive, OBJECT_CODE is returned.
419 public abstract int getTypeCode();
422 * Returns true if this is a primitive type.
424 public abstract boolean isPrimitive();
427 * Returns true if this is a primitive long or double type.
429 public abstract boolean isDoubleWord();
432 * Returns true if this is an array type.
434 public abstract boolean isArray();
437 * Returns the number of dimensions this array type has. If not an array,
440 public abstract int getDimensions();
443 * Returns the component type of this array type. If not an array, null is
446 public abstract TypeDesc getComponentType();
449 * Returns the root component type of this array type. If not an array,
452 public abstract TypeDesc getRootComponentType();
455 * Convertes this type to an array type. If already an array, another
456 * dimension is added.
458 public abstract TypeDesc toArrayType();
461 * Returns the object peer of this primitive type. For int, the object peer
462 * is java.lang.Integer. If this type is an object type, it is simply
465 public abstract TypeDesc toObjectType();
468 * Returns the primitive peer of this object type, if one exists. For
469 * java.lang.Integer, the primitive peer is int. If this type is a
470 * primitive type, it is simply returned. Arrays have no primitive peer,
471 * and so null is returned instead.
473 public abstract TypeDesc toPrimitiveType();
476 * Returns this type as a class. If the class isn't found, null is
479 public abstract Class toClass();
482 * Returns this type as a class. If the class isn't found, null is
484 * @param loader optional ClassLoader to load class from
486 public abstract Class toClass(ClassLoader loader);
488 public String toString() {
489 // TODO: Return generic descriptor
493 public int hashCode() {
494 return mDescriptor.hashCode();
497 public boolean equals(Object other) {
501 if (other instanceof TypeDesc) {
502 return ((TypeDesc)other).mDescriptor.equals(mDescriptor);
507 private static class PrimitiveType extends TypeDesc {
508 private transient final int mCode;
509 private transient TypeDesc mArrayType;
510 private transient TypeDesc mObjectType;
512 PrimitiveType(String desc, int code) {
517 public String getRootName() {
541 public String getFullName() {
542 return getRootName();
545 public int getTypeCode() {
549 public boolean isPrimitive() {
553 public boolean isDoubleWord() {
554 return mCode == DOUBLE_CODE || mCode == LONG_CODE;
557 public boolean isArray() {
561 public int getDimensions() {
565 public TypeDesc getComponentType() {
569 public TypeDesc getRootComponentType() {
573 public TypeDesc toArrayType() {
574 if (mArrayType == null) {
575 char[] buf = new char[2];
577 buf[1] = mDescriptor.charAt(0);
578 mArrayType = intern(new ArrayType(new String(buf), this));
583 public TypeDesc toObjectType() {
584 if (mObjectType == null) {
588 mObjectType = forClass("java.lang.Void");
591 mObjectType = forClass("java.lang.Boolean");
594 mObjectType = forClass("java.lang.Character");
597 mObjectType = forClass("java.lang.Byte");
600 mObjectType = forClass("java.lang.Short");
603 mObjectType = forClass("java.lang.Integer");
606 mObjectType = forClass("java.lang.Long");
609 mObjectType = forClass("java.lang.Float");
612 mObjectType = forClass("java.lang.Double");
619 public TypeDesc toPrimitiveType() {
623 public Class toClass() {
629 return boolean.class;
647 public Class toClass(ClassLoader loader) {
652 private static class ObjectType extends TypeDesc {
653 private transient final String mName;
654 private transient TypeDesc mArrayType;
655 private transient TypeDesc mPrimitiveType;
657 // Since cClassesToInstances may reference this instance, softly
658 // reference back to class to allow it to be garbage collected.
659 private transient SoftReference<Class> mClassRef;
661 ObjectType(String desc, String name) {
666 public String getRootName() {
670 public String getFullName() {
674 public int getTypeCode() {
678 public boolean isPrimitive() {
682 public boolean isDoubleWord() {
686 public boolean isArray() {
690 public int getDimensions() {
694 public TypeDesc getComponentType() {
698 public TypeDesc getRootComponentType() {
702 public TypeDesc toArrayType() {
703 if (mArrayType == null) {
704 int length = mDescriptor.length();
705 char[] buf = new char[length + 1];
707 mDescriptor.getChars(0, length, buf, 1);
708 mArrayType = intern(new ArrayType(new String(buf), this));
713 public TypeDesc toObjectType() {
717 public TypeDesc toPrimitiveType() {
718 if (mPrimitiveType == null) {
720 if (name.startsWith("java.lang.") && name.length() > 10) {
721 switch (name.charAt(10)) {
723 if (name.equals("java.lang.Void")) {
724 mPrimitiveType = VOID;
728 if (name.equals("java.lang.Boolean")) {
729 mPrimitiveType = BOOLEAN;
730 } else if (name.equals("java.lang.Byte")) {
731 mPrimitiveType = BYTE;
735 if (name.equals("java.lang.Character")) {
736 mPrimitiveType = CHAR;
740 if (name.equals("java.lang.Short")) {
741 mPrimitiveType = SHORT;
745 if (name.equals("java.lang.Integer")) {
746 mPrimitiveType = INT;
750 if (name.equals("java.lang.Long")) {
751 mPrimitiveType = LONG;
755 if (name.equals("java.lang.Float")) {
756 mPrimitiveType = FLOAT;
760 if (name.equals("java.lang.Double")) {
761 mPrimitiveType = DOUBLE;
768 return mPrimitiveType;
771 public final synchronized Class toClass() {
773 if (mClassRef != null) {
774 clazz = mClassRef.get();
779 clazz = toClass(null);
780 mClassRef = new SoftReference<Class>(clazz);
784 public Class toClass(ClassLoader loader) {
785 TypeDesc type = toPrimitiveType();
787 switch (type.getTypeCode()) {
792 return Boolean.class;
794 return Character.class;
804 return Integer.class;
811 if (loader == null) {
812 return Class.forName(mName);
814 return loader.loadClass(mName);
816 } catch (ClassNotFoundException e) {
821 void setClass(Class clazz) {
822 mClassRef = new SoftReference<Class>(clazz);
826 private static class ArrayType extends ObjectType {
827 private transient final TypeDesc mComponent;
828 private transient final String mFullName;
830 ArrayType(String desc, TypeDesc component) {
831 super(desc, component.getRootName());
832 mComponent = component;
833 mFullName = component.getFullName().concat("[]");
836 public String getFullName() {
840 public boolean isArray() {
844 public int getDimensions() {
845 return mComponent.getDimensions() + 1;
848 public TypeDesc getComponentType() {
852 public TypeDesc getRootComponentType() {
853 TypeDesc type = mComponent;
854 while (type.isArray()) {
855 type = type.getComponentType();
860 public TypeDesc toPrimitiveType() {
864 public Class toClass(ClassLoader loader) {
865 if (loader == null) {
866 return arrayClass(getRootComponentType().toClass());
868 return arrayClass(getRootComponentType().toClass(loader));
872 private Class arrayClass(Class clazz) {
876 int dim = getDimensions();
879 return Array.newInstance(clazz, 0).getClass();
881 return Array.newInstance(clazz, new int[dim]).getClass();
883 } catch (IllegalArgumentException e) {
889 public static TypeDesc[] concat(TypeDesc[] a, TypeDesc[] b) {
890 TypeDesc[] result = new TypeDesc[a.length + b.length];
891 System.arraycopy(a, 0, result, 0, a.length);
892 System.arraycopy(b, 0, result, a.length, b.length);