]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.scl.compiler/src/org/cojen/classfile/TypeDesc.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.scl.compiler / src / org / cojen / classfile / TypeDesc.java
1 /*
2  *  Copyright 2004-2010 Brian S O'Neill
3  *
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
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 package org.cojen.classfile;
18
19 import java.io.Serializable;
20 import java.io.Externalizable;
21 import java.io.ObjectOutput;
22 import java.io.ObjectInput;
23 import java.io.IOException;
24 import java.io.ObjectStreamException;
25 import java.lang.ref.SoftReference;
26 import java.lang.reflect.Array;
27 import java.util.Collections;
28 import java.util.Map;
29
30 import org.cojen.util.SoftValuedHashMap;
31 import org.cojen.util.WeakCanonicalSet;
32 import org.cojen.util.WeakIdentityMap;
33
34 /**
35  * This class is used to build field and return type descriptor strings as 
36  * defined in <i>The Java Virtual Machine Specification</i>, section 4.3.2.
37  * TypeDesc instances are canonicalized and therefore "==" comparable.
38  *
39  * @author Brian S O'Neill
40  */
41 @SuppressWarnings("rawtypes")
42 public abstract class TypeDesc extends Descriptor implements Serializable {
43     /**
44      * Type code returned from getTypeCode, which can be used with the
45      * newarray instruction.
46      */
47     public final static int
48         OBJECT_CODE = 0,
49         VOID_CODE = 1,
50         BOOLEAN_CODE = 4,
51         CHAR_CODE = 5,
52         FLOAT_CODE = 6,
53         DOUBLE_CODE = 7,
54         BYTE_CODE = 8,
55         SHORT_CODE = 9,
56         INT_CODE = 10,
57         LONG_CODE = 11;
58
59     /** Primitive type void */
60     public final static TypeDesc VOID;
61     /** Primitive type boolean */
62     public final static TypeDesc BOOLEAN;
63     /** Primitive type char */
64     public final static TypeDesc CHAR;
65     /** Primitive type byte */
66     public final static TypeDesc BYTE;
67     /** Primitive type short */
68     public final static TypeDesc SHORT;
69     /** Primitive type int */
70     public final static TypeDesc INT;
71     /** Primitive type long */
72     public final static TypeDesc LONG;
73     /** Primitive type float */
74     public final static TypeDesc FLOAT;
75     /** Primitive type double */
76     public final static TypeDesc DOUBLE;
77
78     /** Object type java.lang.Object, provided for convenience */
79     public final static TypeDesc OBJECT;
80     /** Object type java.lang.String, provided for convenience */
81     public final static TypeDesc STRING;
82
83     // Pool of all shared instances. Ensures identity comparison works.
84     final static WeakCanonicalSet<Descriptor> cInstances;
85
86     // Cache that maps Classes to TypeDescs.
87     private final static Map<Class, TypeDesc> cClassesToInstances;
88
89     // Cache that maps String names to TypeDescs.
90     private final static Map<String, TypeDesc> cNamesToInstances;
91
92     // Cache that maps String descriptors to TypeDescs.
93     private final static Map<String, TypeDesc> cDescriptorsToInstances;
94
95     static {
96         cInstances = new WeakCanonicalSet<Descriptor>();
97
98         cClassesToInstances = Collections.synchronizedMap(new WeakIdentityMap<Class, TypeDesc>());
99         cNamesToInstances = Collections.synchronizedMap(new SoftValuedHashMap<String, TypeDesc>());
100         cDescriptorsToInstances = Collections.synchronizedMap
101             (new SoftValuedHashMap<String, TypeDesc>());
102
103         VOID = intern(new PrimitiveType("V", VOID_CODE));
104         BOOLEAN = intern(new PrimitiveType("Z", BOOLEAN_CODE));
105         CHAR = intern(new PrimitiveType("C", CHAR_CODE));
106         BYTE = intern(new PrimitiveType("B", BYTE_CODE));
107         SHORT = intern(new PrimitiveType("S", SHORT_CODE));
108         INT = intern(new PrimitiveType("I", INT_CODE));
109         LONG = intern(new PrimitiveType("J", LONG_CODE));
110         FLOAT = intern(new PrimitiveType("F", FLOAT_CODE));
111         DOUBLE = intern(new PrimitiveType("D", DOUBLE_CODE));
112
113         OBJECT = forClass("java.lang.Object");
114         STRING = forClass("java.lang.String");
115     }
116
117     static TypeDesc intern(TypeDesc type) {
118         return cInstances.put(type);
119     }
120
121     /**
122      * Acquire a TypeDesc from any class, including primitives and arrays.
123      */
124     public static TypeDesc forClass(final Class clazz) {
125         if (clazz == null) {
126             return null;
127         }
128
129         TypeDesc type = (TypeDesc)cClassesToInstances.get(clazz);
130
131         if (type == null || type.toClass() != clazz) {
132             if (clazz.isArray()) {
133                 type = forClass(clazz.getComponentType()).toArrayType();
134             } else if (clazz.isPrimitive()) {
135                 if (clazz == int.class) {
136                     type = INT;
137                 } else if (clazz == boolean.class) {
138                     type = BOOLEAN;
139                 } else if (clazz == char.class) {
140                     type = CHAR;
141                 } else if (clazz == byte.class) {
142                     type = BYTE;
143                 } else if (clazz == long.class) {
144                     type = LONG;
145                 } else if (clazz == float.class) {
146                     type = FLOAT;
147                 } else if (clazz == double.class) {
148                     type = DOUBLE;
149                 } else if (clazz == short.class) {
150                     type = SHORT;
151                 } else if (clazz == void.class) {
152                     type = VOID;
153                 }
154             } else {
155                 String name = clazz.getName();
156                 type = intern(new ObjectType(generateDescriptor(name), name));
157             }
158         
159             if (type.toClass() != clazz) {
160                 type = new ObjectType(type.getDescriptor(), clazz.getName());
161                 ((ObjectType) type).setClass(clazz);
162             }
163
164             synchronized (cClassesToInstances) {
165                 if (cClassesToInstances.containsKey(clazz)) {
166                     type = (TypeDesc)cClassesToInstances.get(clazz);
167                 } else {
168                     cClassesToInstances.put(clazz, type);
169                 }
170             }
171         }
172
173         return type;
174     }
175
176     /**
177      * Acquire a TypeDesc from any class name, including primitives and arrays.
178      * Primitive and array syntax matches Java declarations.
179      */
180     public static TypeDesc forClass(final String name) throws IllegalArgumentException {
181         if (name.length() < 1) {
182             throw invalidName(name);
183         }
184
185         // TODO: Support generics in name.
186
187         TypeDesc type = (TypeDesc)cNamesToInstances.get(name);
188         if (type != null) {
189             return type;
190         }
191
192         int index1 = name.lastIndexOf('[');
193         int index2 = name.lastIndexOf(']');
194         if (index2 >= 0) {
195             if (index2 + 1 != name.length() || index1 + 1 != index2) {
196                 throw invalidName(name);
197             }
198             try {
199                 type = forClass(name.substring(0, index1)).toArrayType();
200             } catch (IllegalArgumentException e) {
201                 throw invalidName(name);
202             }
203         } else if (index1 >= 0) {
204             throw invalidName(name);
205         } else {
206             setType: {
207                 switch (name.charAt(0)) {
208                 case 'v':
209                     if (name.equals("void")) {
210                         type = VOID;
211                         break setType;
212                     }
213                     break;
214                 case 'b':
215                     if (name.equals("boolean")) {
216                         type = BOOLEAN;
217                         break setType;
218                     } else if (name.equals("byte")) {
219                         type =  BYTE;
220                         break setType;
221                     }
222                     break;
223                 case 'c':
224                     if (name.equals("char")) {
225                         type = CHAR;
226                         break setType;
227                     }
228                     break;
229                 case 's':
230                     if (name.equals("short")) {
231                         type = SHORT;
232                         break setType;
233                     }
234                     break;
235                 case 'i':
236                     if (name.equals("int")) {
237                         type = INT;
238                         break setType;
239                     }
240                     break;
241                 case 'l':
242                     if (name.equals("long")) {
243                         type = LONG;
244                         break setType;
245                     }
246                     break;
247                 case 'f':
248                     if (name.equals("float")) {
249                         type = FLOAT;
250                         break setType;
251                     }
252                     break;
253                 case 'd':
254                     if (name.equals("double")) {
255                         type = DOUBLE;
256                         break setType;
257                     }
258                     break;
259                 }
260
261                 String desc = generateDescriptor(name);
262                 if (name.indexOf('/') < 0) {
263                     type = new ObjectType(desc, name);
264                 } else {
265                     type = new ObjectType(desc, name.replace('/', '.'));
266                 }
267                 type = intern(type);
268             }
269         }
270
271         cNamesToInstances.put(name, type);
272         return type;
273     }
274
275     private static IllegalArgumentException invalidName(String name) {
276         return new IllegalArgumentException("Invalid name: " + name);
277     }
278
279     /**
280      * Acquire a TypeDesc from a type descriptor. This syntax is described in
281      * section 4.3.2, Field Descriptors.
282      */
283     public static TypeDesc forDescriptor(final String desc) throws IllegalArgumentException {
284         TypeDesc type = (TypeDesc)cDescriptorsToInstances.get(desc);
285         if (type != null) {
286             return type;
287         }
288
289         // TODO: Support generics in descriptor.
290
291         String rootDesc = desc;
292         int cursor = 0;
293         int dim = 0;
294         try {
295             char c;
296             while ((c = rootDesc.charAt(cursor++)) == '[') {
297                 dim++;
298             }
299
300             switch (c) {
301             case 'V':
302                 type = VOID;
303                 break;
304             case 'Z':
305                 type = BOOLEAN;
306                 break;
307             case 'C':
308                 type = CHAR;
309                 break;
310             case 'B':
311                 type = BYTE;
312                 break;
313             case 'S':
314                 type = SHORT;
315                 break;
316             case 'I':
317                 type = INT;
318                 break;
319             case 'J':
320                 type = LONG;
321                 break;
322             case 'F':
323                 type = FLOAT;
324                 break;
325             case 'D':
326                 type = DOUBLE;
327                 break;
328             case 'L':
329                 if (dim > 0) {
330                     rootDesc = rootDesc.substring(dim);
331                     cursor = 1;
332                 }
333                 StringBuffer name = new StringBuffer(rootDesc.length() - 2);
334                 while ((c = rootDesc.charAt(cursor++)) != ';') {
335                     if (c == '/') {
336                         c = '.';
337                     }
338                     name.append(c);
339                 }
340                 type = intern(new ObjectType(rootDesc, name.toString()));
341                 break;
342             default:
343                 throw invalidDescriptor(desc);
344             }
345         } catch (NullPointerException e) {
346             throw invalidDescriptor(desc);
347         } catch (IndexOutOfBoundsException e) {
348             throw invalidDescriptor(desc);
349         }
350
351         if (cursor != rootDesc.length()) {
352             throw invalidDescriptor(desc);
353         }
354
355         while (--dim >= 0) {
356             type = type.toArrayType();
357         }
358
359         cDescriptorsToInstances.put(desc, type);
360         return type;
361     }
362
363     private static IllegalArgumentException invalidDescriptor(String desc) {
364         return new IllegalArgumentException("Invalid descriptor: " + desc);
365     }
366
367     private static String generateDescriptor(String classname) {
368         int length = classname.length();
369         char[] buf = new char[length + 2];
370         buf[0] = 'L';
371         classname.getChars(0, length, buf, 1);
372         int i;
373         for (i=1; i<=length; i++) {
374             char c = buf[i];
375             if (c == '.') {
376                 buf[i] = '/';
377             }
378         }
379         buf[i] = ';';
380         return new String(buf);
381     }
382
383     transient final String mDescriptor;
384
385     TypeDesc(String desc) {
386         mDescriptor = desc;
387     }
388
389     /**
390      * Returns a type descriptor string, excluding generics.
391      */
392     public final String getDescriptor() {
393         return mDescriptor;
394     }
395
396     /**
397      * Returns a type descriptor string, including any generics.
398      */
399     //public abstract String getGenericDescriptor();
400
401     /**
402      * Returns the class name for this descriptor. If the type is primitive,
403      * then the Java primitive type name is returned. If the type is an array,
404      * only the root component type name is returned.
405      */
406     public abstract String getRootName();
407
408     /**
409      * Returns the class name for this descriptor. If the type is primitive,
410      * then the Java primitive type name is returned. If the type is an array,
411      * "[]" is append at the end of the name for each dimension.
412      */
413     public abstract String getFullName();
414
415     // TODO
416     //public abstract String getGenericRootName();
417
418     // TODO
419     //public abstract String getGenericFullName();
420
421     /**
422      * Returns a type code for operating on primitive types in switches. If
423      * not primitive, OBJECT_CODE is returned.
424      */
425     public abstract int getTypeCode();
426
427     /**
428      * Returns true if this is a primitive type.
429      */
430     public abstract boolean isPrimitive();
431
432     /**
433      * Returns true if this is a primitive long or double type.
434      */
435     public abstract boolean isDoubleWord();
436
437     /**
438      * Returns true if this is an array type.
439      */
440     public abstract boolean isArray();
441
442     /**
443      * Returns the number of dimensions this array type has. If not an array,
444      * zero is returned.
445      */
446     public abstract int getDimensions();
447
448     /**
449      * Returns the component type of this array type. If not an array, null is
450      * returned.
451      */
452     public abstract TypeDesc getComponentType();
453
454     /**
455      * Returns the root component type of this array type. If not an array,
456      * null is returned.
457      */
458     public abstract TypeDesc getRootComponentType();
459
460     /**
461      * Convertes this type to an array type. If already an array, another
462      * dimension is added.
463      */
464     public abstract TypeDesc toArrayType();
465
466     /**
467      * Returns the object peer of this primitive type. For int, the object peer
468      * is java.lang.Integer. If this type is an object type, it is simply 
469      * returned.
470      */
471     public abstract TypeDesc toObjectType();
472
473     /**
474      * Returns the primitive peer of this object type, if one exists. For
475      * java.lang.Integer, the primitive peer is int. If this type is a
476      * primitive type, it is simply returned. Arrays have no primitive peer,
477      * and so null is returned instead.
478      */
479     public abstract TypeDesc toPrimitiveType();
480
481     /**
482      * Returns this type as a class. If the class isn't found, null is
483      * returned.
484      */
485     public abstract Class toClass();
486
487     /**
488      * Returns this type as a class. If the class isn't found, null is
489      * returned.
490      * @param loader optional ClassLoader to load class from
491      */
492     public abstract Class toClass(ClassLoader loader);
493
494     public String toString() {
495         // TODO: Return generic descriptor
496         return mDescriptor;
497     }
498
499     public int hashCode() {
500         return mDescriptor.hashCode();
501     }
502
503     public boolean equals(Object other) {
504         if (this == other) {
505             return true;
506         }
507         if (other instanceof TypeDesc) {
508             return ((TypeDesc)other).mDescriptor.equals(mDescriptor);
509         }
510         return false;
511     }
512
513     Object writeReplace() throws ObjectStreamException {
514         return new External(mDescriptor);
515     }
516
517     private static class PrimitiveType extends TypeDesc {
518         private transient final int mCode;
519         private transient TypeDesc mArrayType;
520         private transient TypeDesc mObjectType;
521         
522         PrimitiveType(String desc, int code) {
523             super(desc);
524             mCode = code;
525         }
526
527         public String getRootName() {
528             switch (mCode) {
529             default:
530             case VOID_CODE:
531                 return "void";
532             case BOOLEAN_CODE:
533                 return "boolean";
534             case CHAR_CODE:
535                 return "char";
536             case BYTE_CODE:
537                 return "byte";
538             case SHORT_CODE:
539                 return "short";
540             case INT_CODE:
541                 return "int";
542             case LONG_CODE:
543                 return "long";
544             case FLOAT_CODE:
545                 return "float";
546             case DOUBLE_CODE:
547                 return "double";
548             }
549         }
550         
551         public String getFullName() {
552             return getRootName();
553         }
554
555         public int getTypeCode() {
556             return mCode;
557         }
558         
559         public boolean isPrimitive() {
560             return true;
561         }
562         
563         public boolean isDoubleWord() {
564             return mCode == DOUBLE_CODE || mCode == LONG_CODE;
565         }
566         
567         public boolean isArray() {
568             return false;
569         }
570         
571         public int getDimensions() {
572             return 0;
573         }
574         
575         public TypeDesc getComponentType() {
576             return null;
577         }
578         
579         public TypeDesc getRootComponentType() {
580             return null;
581         }
582         
583         public TypeDesc toArrayType() {
584             if (mArrayType == null) {
585                 char[] buf = new char[2];
586                 buf[0] = '[';
587                 buf[1] = mDescriptor.charAt(0);
588                 mArrayType = intern(new ArrayType(new String(buf), this));
589             }
590             return mArrayType;
591         }
592         
593         public TypeDesc toObjectType() {
594             if (mObjectType == null) {
595                 switch (mCode) {
596                 default:
597                 case VOID_CODE:
598                     mObjectType = forClass("java.lang.Void");
599                     break;
600                 case BOOLEAN_CODE:
601                     mObjectType = forClass("java.lang.Boolean");
602                     break;
603                 case CHAR_CODE:
604                     mObjectType = forClass("java.lang.Character");
605                     break;
606                 case BYTE_CODE:
607                     mObjectType = forClass("java.lang.Byte");
608                     break;
609                 case SHORT_CODE:
610                     mObjectType = forClass("java.lang.Short");
611                     break;
612                 case INT_CODE:
613                     mObjectType = forClass("java.lang.Integer");
614                     break;
615                 case LONG_CODE:
616                     mObjectType = forClass("java.lang.Long");
617                     break;
618                 case FLOAT_CODE:
619                     mObjectType = forClass("java.lang.Float");
620                     break;
621                 case DOUBLE_CODE:
622                     mObjectType = forClass("java.lang.Double");
623                     break;
624                 }
625             }
626             return mObjectType;
627         }
628         
629         public TypeDesc toPrimitiveType() {
630             return this;
631         }
632
633         public Class toClass() {
634             switch (mCode) {
635             default:
636             case VOID_CODE:
637                 return void.class;
638             case BOOLEAN_CODE:
639                 return boolean.class;
640             case CHAR_CODE:
641                 return char.class;
642             case BYTE_CODE:
643                 return byte.class;
644             case SHORT_CODE:
645                 return short.class;
646             case INT_CODE:
647                 return int.class;
648             case LONG_CODE:
649                 return long.class;
650             case FLOAT_CODE:
651                 return float.class;
652             case DOUBLE_CODE:
653                 return double.class;
654             }
655         }
656
657         public Class toClass(ClassLoader loader) {
658             return toClass();
659         }
660     }
661
662     private static class ObjectType extends TypeDesc {
663         private transient final String mName;
664         private transient TypeDesc mArrayType;
665         private transient TypeDesc mPrimitiveType;
666
667         // Since cClassesToInstances may reference this instance, softly
668         // reference back to class to allow it to be garbage collected.
669         private transient SoftReference<Class> mClassRef;
670
671         ObjectType(String desc, String name) {
672             super(desc);
673             mName = name;
674         }
675
676         public String getRootName() {
677             return mName;
678         }
679
680         public String getFullName() {
681             return mName;
682         }
683
684         public int getTypeCode() {
685             return OBJECT_CODE;
686         }
687
688         public boolean isPrimitive() {
689             return false;
690         }
691         
692         public boolean isDoubleWord() {
693             return false;
694         }
695         
696         public boolean isArray() {
697             return false;
698         }
699         
700         public int getDimensions() {
701             return 0;
702         }
703         
704         public TypeDesc getComponentType() {
705             return null;
706         }
707         
708         public TypeDesc getRootComponentType() {
709             return null;
710         }
711         
712         public TypeDesc toArrayType() {
713             if (mArrayType == null) {
714                 int length = mDescriptor.length();
715                 char[] buf = new char[length + 1];
716                 buf[0] = '[';
717                 mDescriptor.getChars(0, length, buf, 1);
718                 mArrayType = intern(new ArrayType(new String(buf), this));
719             }
720             return mArrayType;
721         }
722         
723         public TypeDesc toObjectType() {
724             return this;
725         }
726         
727         public TypeDesc toPrimitiveType() {
728             if (mPrimitiveType == null) {
729                 String name = mName;
730                 if (name.startsWith("java.lang.") && name.length() > 10) {
731                     switch (name.charAt(10)) {
732                     case 'V':
733                         if (name.equals("java.lang.Void")) {
734                             mPrimitiveType = VOID;
735                         }
736                         break;
737                     case 'B':
738                         if (name.equals("java.lang.Boolean")) {
739                             mPrimitiveType = BOOLEAN;
740                         } else if (name.equals("java.lang.Byte")) {
741                             mPrimitiveType = BYTE;
742                         }
743                         break;
744                     case 'C':
745                         if (name.equals("java.lang.Character")) {
746                             mPrimitiveType = CHAR;
747                         }
748                         break;
749                     case 'S':
750                         if (name.equals("java.lang.Short")) {
751                             mPrimitiveType = SHORT;
752                         }
753                         break;
754                     case 'I':
755                         if (name.equals("java.lang.Integer")) {
756                             mPrimitiveType = INT;
757                         }
758                         break;
759                     case 'L':
760                         if (name.equals("java.lang.Long")) {
761                             mPrimitiveType = LONG;
762                         }
763                         break;
764                     case 'F':
765                         if (name.equals("java.lang.Float")) {
766                             mPrimitiveType = FLOAT;
767                         }
768                         break;
769                     case 'D':
770                         if (name.equals("java.lang.Double")) {
771                             mPrimitiveType = DOUBLE;
772                         }
773                         break;
774                     }
775                 }
776             }
777
778             return mPrimitiveType;
779         }
780
781         public final synchronized Class toClass() {
782             Class clazz;
783             if (mClassRef != null) {
784                 clazz = mClassRef.get();
785                 if (clazz != null) {
786                     return clazz;
787                 }
788             }
789             clazz = toClass(null);
790             mClassRef = new SoftReference<Class>(clazz);
791             return clazz;
792         }
793
794         public Class toClass(ClassLoader loader) {
795             TypeDesc type = toPrimitiveType();
796             if (type != null) {
797                 switch (type.getTypeCode()) {
798                 default:
799                 case VOID_CODE:
800                     return Void.class;
801                 case BOOLEAN_CODE:
802                     return Boolean.class;
803                 case CHAR_CODE:
804                     return Character.class;
805                 case FLOAT_CODE:
806                     return Float.class;
807                 case DOUBLE_CODE:
808                     return Double.class;
809                 case BYTE_CODE:
810                     return Byte.class;
811                 case SHORT_CODE:
812                     return Short.class;
813                 case INT_CODE:
814                     return Integer.class;
815                 case LONG_CODE:
816                     return Long.class;
817                 }
818             }
819
820             try {
821                 if (loader == null) {
822                     return Class.forName(mName);
823                 } else {
824                     return loader.loadClass(mName);
825                 }
826             } catch (ClassNotFoundException e) {
827                 return null;
828             }
829         }
830
831         void setClass(Class clazz) {
832             mClassRef = new SoftReference<Class>(clazz);
833         }
834     }
835
836     private static class ArrayType extends ObjectType {
837         private transient final TypeDesc mComponent;
838         private transient final String mFullName;
839
840         ArrayType(String desc, TypeDesc component) {
841             super(desc, component.getRootName());
842             mComponent = component;
843             mFullName = component.getFullName().concat("[]");
844         }
845
846         public String getFullName() {
847             return mFullName;
848         }
849
850         public boolean isArray() {
851             return true;
852         }
853         
854         public int getDimensions() {
855             return mComponent.getDimensions() + 1;
856         }
857         
858         public TypeDesc getComponentType() {
859             return mComponent;
860         }
861         
862         public TypeDesc getRootComponentType() {
863             TypeDesc type = mComponent;
864             while (type.isArray()) {
865                 type = type.getComponentType();
866             }
867             return type;
868         }
869         
870         public TypeDesc toPrimitiveType() {
871             return null;
872         }
873
874         public Class toClass(ClassLoader loader) {
875             if (loader == null) {
876                 return arrayClass(getRootComponentType().toClass());
877             } else {
878                 return arrayClass(getRootComponentType().toClass(loader));
879             }
880         }
881
882         private Class arrayClass(Class clazz) {
883             if (clazz == null) {
884                 return null;
885             }
886             int dim = getDimensions();
887             try {
888                 if (dim == 1) {
889                     return Array.newInstance(clazz, 0).getClass();
890                 } else {
891                     return Array.newInstance(clazz, new int[dim]).getClass();
892                 }
893             } catch (IllegalArgumentException e) {
894                 return null;
895             }
896         }
897     }
898
899     private static class External implements Externalizable {
900         private String mDescriptor;
901
902         public External() {
903         }
904
905         public External(String desc) {
906             mDescriptor = desc;
907         }
908
909         public void writeExternal(ObjectOutput out) throws IOException {
910             out.writeUTF(mDescriptor);
911         }
912
913         public void readExternal(ObjectInput in) throws IOException {
914             mDescriptor = in.readUTF();
915         }
916
917         public Object readResolve() throws ObjectStreamException {
918             return forDescriptor(mDescriptor);
919         }
920     }
921 }