1/*
2 * ProGuard -- shrinking, optimization, obfuscation, and preverification
3 *             of Java bytecode.
4 *
5 * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
6 *
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the Free
9 * Software Foundation; either version 2 of the License, or (at your option)
10 * any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 * more details.
16 *
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 */
21package proguard.classfile.util;
22
23import proguard.classfile.ClassConstants;
24
25import java.util.List;
26
27/**
28 * Utility methods for converting between internal and external representations
29 * of names and descriptions.
30 *
31 * @author Eric Lafortune
32 */
33public class ClassUtil
34{
35    private static final String EMPTY_STRING = "";
36
37
38    /**
39     * Checks whether the given class magic number is correct.
40     * @param magicNumber the magic number.
41     * @throws UnsupportedOperationException when the magic number is incorrect.
42     */
43    public static void checkMagicNumber(int magicNumber) throws UnsupportedOperationException
44    {
45        if (magicNumber != ClassConstants.MAGIC)
46        {
47            throw new UnsupportedOperationException("Invalid magic number ["+Integer.toHexString(magicNumber)+"] in class");
48        }
49    }
50
51
52    /**
53     * Returns the combined class version number.
54     * @param majorVersion the major part of the class version number.
55     * @param minorVersion the minor part of the class version number.
56     * @return the combined class version number.
57     */
58    public static int internalClassVersion(int majorVersion, int minorVersion)
59    {
60        return (majorVersion << 16) | minorVersion;
61    }
62
63
64    /**
65     * Returns the major part of the given class version number.
66     * @param classVersion the combined class version number.
67     * @return the major part of the class version number.
68     */
69    public static int internalMajorClassVersion(int classVersion)
70    {
71        return classVersion >>> 16;
72    }
73
74
75    /**
76     * Returns the internal class version number.
77     * @param classVersion the external class version number.
78     * @return the internal class version number.
79     */
80    public static int internalMinorClassVersion(int classVersion)
81    {
82        return classVersion & 0xffff;
83    }
84
85
86    /**
87     * Returns the internal class version number.
88     * @param classVersion the external class version number.
89     * @return the internal class version number.
90     */
91    public static int internalClassVersion(String classVersion)
92    {
93        return
94            classVersion.equals(ClassConstants.EXTERNAL_CLASS_VERSION_1_0) ||
95            classVersion.equals(ClassConstants.EXTERNAL_CLASS_VERSION_1_1) ? ClassConstants.INTERNAL_CLASS_VERSION_1_0 :
96            classVersion.equals(ClassConstants.EXTERNAL_CLASS_VERSION_1_2) ? ClassConstants.INTERNAL_CLASS_VERSION_1_2 :
97            classVersion.equals(ClassConstants.EXTERNAL_CLASS_VERSION_1_3) ? ClassConstants.INTERNAL_CLASS_VERSION_1_3 :
98            classVersion.equals(ClassConstants.EXTERNAL_CLASS_VERSION_1_4) ? ClassConstants.INTERNAL_CLASS_VERSION_1_4 :
99            classVersion.equals(ClassConstants.EXTERNAL_CLASS_VERSION_1_5_ALIAS) ||
100            classVersion.equals(ClassConstants.EXTERNAL_CLASS_VERSION_1_5) ? ClassConstants.INTERNAL_CLASS_VERSION_1_5 :
101            classVersion.equals(ClassConstants.EXTERNAL_CLASS_VERSION_1_6_ALIAS) ||
102            classVersion.equals(ClassConstants.EXTERNAL_CLASS_VERSION_1_6) ? ClassConstants.INTERNAL_CLASS_VERSION_1_6 :
103                                                                             0;
104    }
105
106
107    /**
108     * Returns the minor part of the given class version number.
109     * @param classVersion the combined class version number.
110     * @return the minor part of the class version number.
111     */
112    public static String externalClassVersion(int classVersion)
113    {
114        switch (classVersion)
115        {
116            case ClassConstants.INTERNAL_CLASS_VERSION_1_0: return ClassConstants.EXTERNAL_CLASS_VERSION_1_0;
117            case ClassConstants.INTERNAL_CLASS_VERSION_1_2: return ClassConstants.EXTERNAL_CLASS_VERSION_1_2;
118            case ClassConstants.INTERNAL_CLASS_VERSION_1_3: return ClassConstants.EXTERNAL_CLASS_VERSION_1_3;
119            case ClassConstants.INTERNAL_CLASS_VERSION_1_4: return ClassConstants.EXTERNAL_CLASS_VERSION_1_4;
120            case ClassConstants.INTERNAL_CLASS_VERSION_1_5: return ClassConstants.EXTERNAL_CLASS_VERSION_1_5;
121            case ClassConstants.INTERNAL_CLASS_VERSION_1_6: return ClassConstants.EXTERNAL_CLASS_VERSION_1_6;
122            default:                                        return null;
123        }
124    }
125
126
127    /**
128     * Checks whether the given class version number is supported.
129     * @param classVersion the combined class version number.
130     * @throws UnsupportedOperationException when the version is not supported.
131     */
132    public static void checkVersionNumbers(int classVersion) throws UnsupportedOperationException
133    {
134        if (classVersion < ClassConstants.INTERNAL_CLASS_VERSION_1_0 ||
135            classVersion > ClassConstants.INTERNAL_CLASS_VERSION_1_6)
136        {
137            throw new UnsupportedOperationException("Unsupported version number ["+
138                                                    internalMajorClassVersion(classVersion)+"."+
139                                                    internalMinorClassVersion(classVersion)+"] for class format");
140        }
141    }
142
143
144    /**
145     * Converts an external class name into an internal class name.
146     * @param externalClassName the external class name,
147     *                          e.g. "<code>java.lang.Object</code>"
148     * @return the internal class name,
149     *                          e.g. "<code>java/lang/Object</code>".
150     */
151    public static String internalClassName(String externalClassName)
152    {
153        return externalClassName.replace(ClassConstants.EXTERNAL_PACKAGE_SEPARATOR,
154                                         ClassConstants.INTERNAL_PACKAGE_SEPARATOR);
155    }
156
157
158    /**
159     * Converts an internal class description into an external class description.
160     * @param accessFlags       the access flags of the class.
161     * @param internalClassName the internal class name,
162     *                          e.g. "<code>java/lang/Object</code>".
163     * @return the external class description,
164     *                          e.g. "<code>public java.lang.Object</code>".
165     */
166    public static String externalFullClassDescription(int    accessFlags,
167                                                      String internalClassName)
168    {
169        return externalClassAccessFlags(accessFlags) +
170               externalClassName(internalClassName);
171    }
172
173
174    /**
175     * Converts an internal class name into an external class name.
176     * @param internalClassName the internal class name,
177     *                          e.g. "<code>java/lang/Object</code>".
178     * @return the external class name,
179     *                          e.g. "<code>java.lang.Object</code>".
180     */
181    public static String externalClassName(String internalClassName)
182    {
183        return //internalClassName.startsWith(ClassConstants.INTERNAL_PACKAGE_JAVA_LANG) &&
184               //internalClassName.indexOf(ClassConstants.INTERNAL_PACKAGE_SEPARATOR, ClassConstants.INTERNAL_PACKAGE_JAVA_LANG.length() + 1) < 0 ?
185               //internalClassName.substring(ClassConstants.INTERNAL_PACKAGE_JAVA_LANG.length()) :
186               internalClassName.replace(ClassConstants.INTERNAL_PACKAGE_SEPARATOR,
187                                         ClassConstants.EXTERNAL_PACKAGE_SEPARATOR);
188    }
189
190
191    /**
192     * Converts an internal class name into an external short class name, without
193     * package specification.
194     * @param externalClassName the external class name,
195     *                          e.g. "<code>java.lang.Object</code>"
196     * @return the external short class name,
197     *                          e.g. "<code>Object</code>".
198     */
199    public static String externalShortClassName(String externalClassName)
200    {
201        int index = externalClassName.lastIndexOf(ClassConstants.EXTERNAL_PACKAGE_SEPARATOR);
202        return externalClassName.substring(index+1);
203    }
204
205
206    /**
207     * Returns whether the given internal type is an array type.
208     * @param internalType the internal type,
209     *                     e.g. "<code>[[Ljava/lang/Object;</code>".
210     * @return <code>true</code> if the given type is an array type,
211     *         <code>false</code> otherwise.
212     */
213    public static boolean isInternalArrayType(String internalType)
214    {
215        return internalType.length() > 1 &&
216               internalType.charAt(0) == ClassConstants.INTERNAL_TYPE_ARRAY;
217    }
218
219
220    /**
221     * Returns the number of dimensions of the given internal type.
222     * @param internalType the internal type,
223     *                     e.g. "<code>[[Ljava/lang/Object;</code>".
224     * @return the number of dimensions, e.g. 2.
225     */
226    public static int internalArrayTypeDimensionCount(String internalType)
227    {
228        int dimensions = 0;
229        while (internalType.charAt(dimensions) == ClassConstants.INTERNAL_TYPE_ARRAY)
230        {
231            dimensions++;
232        }
233
234        return dimensions;
235    }
236
237
238    /**
239     * Returns whether the given internal class name is one of the interfaces
240     * that is implemented by all array types. These class names are
241     * "<code>java/lang/Object</code>", "<code>java/lang/Cloneable</code>", and
242     * "<code>java/io/Serializable</code>"
243     * @param internalClassName the internal class name,
244     *                          e.g. "<code>java/lang/Object</code>".
245     * @return <code>true</code> if the given type is an array interface name,
246     *         <code>false</code> otherwise.
247     */
248    public static boolean isInternalArrayInterfaceName(String internalClassName)
249    {
250        return ClassConstants.INTERNAL_NAME_JAVA_LANG_OBJECT.equals(internalClassName)    ||
251               ClassConstants.INTERNAL_NAME_JAVA_LANG_CLONEABLE.equals(internalClassName) ||
252               ClassConstants.INTERNAL_NAME_JAVA_IO_SERIALIZABLE.equals(internalClassName);
253    }
254
255
256    /**
257     * Returns whether the given internal type is a plain primitive type
258     * (not void).
259     * @param internalType the internal type,
260     *                     e.g. "<code>I</code>".
261     * @return <code>true</code> if the given type is a class type,
262     *         <code>false</code> otherwise.
263     */
264    public static boolean isInternalPrimitiveType(char internalType)
265    {
266        return internalType == ClassConstants.INTERNAL_TYPE_BOOLEAN ||
267               internalType == ClassConstants.INTERNAL_TYPE_BYTE    ||
268               internalType == ClassConstants.INTERNAL_TYPE_CHAR    ||
269               internalType == ClassConstants.INTERNAL_TYPE_SHORT   ||
270               internalType == ClassConstants.INTERNAL_TYPE_INT     ||
271               internalType == ClassConstants.INTERNAL_TYPE_FLOAT   ||
272               internalType == ClassConstants.INTERNAL_TYPE_LONG    ||
273               internalType == ClassConstants.INTERNAL_TYPE_DOUBLE;
274    }
275
276
277    /**
278     * Returns whether the given internal type is a primitive Category 2 type.
279     * @param internalType the internal type,
280     *                     e.g. "<code>L</code>".
281     * @return <code>true</code> if the given type is a Category 2 type,
282     *         <code>false</code> otherwise.
283     */
284    public static boolean isInternalCategory2Type(String internalType)
285    {
286        return internalType.length() == 1 &&
287               (internalType.charAt(0) == ClassConstants.INTERNAL_TYPE_LONG ||
288                internalType.charAt(0) == ClassConstants.INTERNAL_TYPE_DOUBLE);
289    }
290
291
292    /**
293     * Returns whether the given internal type is a plain class type
294     * (including an array type of a plain class type).
295     * @param internalType the internal type,
296     *                     e.g. "<code>Ljava/lang/Object;</code>".
297     * @return <code>true</code> if the given type is a class type,
298     *         <code>false</code> otherwise.
299     */
300    public static boolean isInternalClassType(String internalType)
301    {
302        int length = internalType.length();
303        return length > 1 &&
304//             internalType.charAt(0)        == ClassConstants.INTERNAL_TYPE_CLASS_START &&
305               internalType.charAt(length-1) == ClassConstants.INTERNAL_TYPE_CLASS_END;
306    }
307
308
309    /**
310     * Returns the internal type of a given class name.
311     * @param internalClassName the internal class name,
312     *                          e.g. "<code>java/lang/Object</code>".
313     * @return the internal type,
314     *                          e.g. "<code>Ljava/lang/Object;</code>".
315     */
316    public static String internalTypeFromClassName(String internalClassName)
317    {
318        return internalArrayTypeFromClassName(internalClassName, 0);
319    }
320
321
322    /**
323     * Returns the internal array type of a given class name with a given number
324     * of dimensions. If the number of dimensions is 0, the class name itself is
325     * returned.
326     * @param internalClassName the internal class name,
327     *                          e.g. "<code>java/lang/Object</code>".
328     * @param dimensionCount    the number of array dimensions.
329     * @return the internal array type of the array elements,
330     *                          e.g. "<code>Ljava/lang/Object;</code>".
331     */
332    public static String internalArrayTypeFromClassName(String internalClassName,
333                                                        int    dimensionCount)
334    {
335        StringBuffer buffer = new StringBuffer(internalClassName.length() + dimensionCount + 2);
336
337        for (int dimension = 0; dimension < dimensionCount; dimension++)
338        {
339            buffer.append(ClassConstants.INTERNAL_TYPE_ARRAY);
340        }
341
342        return buffer.append(ClassConstants.INTERNAL_TYPE_CLASS_START)
343                     .append(internalClassName)
344                     .append(ClassConstants.INTERNAL_TYPE_CLASS_END)
345                     .toString();
346    }
347
348
349    /**
350     * Returns the internal element type of a given internal array type.
351     * @param internalArrayType the internal array type,
352     *                          e.g. "<code>[[Ljava/lang/Object;</code>" or
353     *                               "<code>[I</code>".
354     * @return the internal type of the array elements,
355     *                          e.g. "<code>Ljava/lang/Object;</code>" or
356     *                               "<code>I</code>".
357     */
358    public static String internalTypeFromArrayType(String internalArrayType)
359    {
360        int index = internalArrayType.lastIndexOf(ClassConstants.INTERNAL_TYPE_ARRAY);
361        return internalArrayType.substring(index+1);
362    }
363
364
365    /**
366     * Returns the internal class name of a given internal class type
367     * (including an array type). Types involving primitive types are returned
368     * unchanged.
369     * @param internalClassType the internal class type,
370     *                          e.g. "<code>[Ljava/lang/Object;</code>",
371     *                               "<code>Ljava/lang/Object;</code>", or
372     *                               "<code>java/lang/Object</code>".
373     * @return the internal class name,
374     *                          e.g. "<code>java/lang/Object</code>".
375     */
376    public static String internalClassNameFromClassType(String internalClassType)
377    {
378        return isInternalClassType(internalClassType) ?
379            internalClassType.substring(internalClassType.indexOf(ClassConstants.INTERNAL_TYPE_CLASS_START)+1,
380                                        internalClassType.length()-1) :
381            internalClassType;
382    }
383
384
385    /**
386     * Returns the internal class name of any given internal descriptor type,
387     * disregarding array prefixes.
388     * @param internalClassType the internal class type,
389     *                          e.g. "<code>Ljava/lang/Object;</code>" or
390     *                               "<code>[[I</code>".
391     * @return the internal class name,
392     *                          e.g. "<code>java/lang/Object</code>" or
393     *                               <code>null</code>.
394     */
395    public static String internalClassNameFromType(String internalClassType)
396    {
397        if (!isInternalClassType(internalClassType))
398        {
399            return null;
400        }
401
402        // Is it an array type?
403        if (isInternalArrayType(internalClassType))
404        {
405            internalClassType = internalTypeFromArrayType(internalClassType);
406        }
407
408        return internalClassNameFromClassType(internalClassType);
409    }
410
411
412    /**
413     * Returns the internal type of the given internal method descriptor.
414     * @param internalMethodDescriptor the internal method descriptor,
415     *                                 e.g. "<code>(II)Z</code>".
416     * @return the internal return type,
417     *                                 e.g. "<code>Z</code>".
418     */
419    public static String internalMethodReturnType(String internalMethodDescriptor)
420    {
421        int index = internalMethodDescriptor.indexOf(ClassConstants.INTERNAL_METHOD_ARGUMENTS_CLOSE);
422        return internalMethodDescriptor.substring(index + 1);
423    }
424
425
426    /**
427     * Returns the number of parameters of the given internal method descriptor.
428     * @param internalMethodDescriptor the internal method descriptor,
429     *                                 e.g. "<code>(ID)Z</code>".
430     * @return the number of parameters,
431     *                                 e.g. 2.
432     */
433    public static int internalMethodParameterCount(String internalMethodDescriptor)
434    {
435        InternalTypeEnumeration internalTypeEnumeration =
436            new InternalTypeEnumeration(internalMethodDescriptor);
437
438        int counter = 0;
439        while (internalTypeEnumeration.hasMoreTypes())
440        {
441            internalTypeEnumeration.nextType();
442
443            counter++;
444        }
445
446        return counter;
447    }
448
449
450    /**
451     * Returns the size taken up on the stack by the parameters of the given
452     * internal method descriptor. This accounts for long and double parameters
453     * taking up two entries.
454     * @param internalMethodDescriptor the internal method descriptor,
455     *                                 e.g. "<code>(ID)Z</code>".
456     * @return the size taken up on the stack,
457     *                                 e.g. 3.
458     */
459    public static int internalMethodParameterSize(String internalMethodDescriptor)
460    {
461        return internalMethodParameterSize(internalMethodDescriptor, true);
462    }
463
464
465    /**
466     * Returns the size taken up on the stack by the parameters of the given
467     * internal method descriptor. This accounts for long and double parameters
468     * taking up two entries, and a non-static method taking up an additional
469     * entry.
470     * @param internalMethodDescriptor the internal method descriptor,
471     *                                 e.g. "<code>(ID)Z</code>".
472     * @param accessFlags              the access flags of the method,
473     *                                 e.g. 0.
474     * @return the size taken up on the stack,
475     *                                 e.g. 4.
476     */
477    public static int internalMethodParameterSize(String internalMethodDescriptor,
478                                                  int    accessFlags)
479    {
480        return internalMethodParameterSize(internalMethodDescriptor,
481                                           (accessFlags & ClassConstants.INTERNAL_ACC_STATIC) != 0);
482    }
483
484
485    /**
486     * Returns the size taken up on the stack by the parameters of the given
487     * internal method descriptor. This accounts for long and double parameters
488     * taking up two spaces, and a non-static method taking up an additional
489     * entry.
490     * @param internalMethodDescriptor the internal method descriptor,
491     *                                 e.g. "<code>(ID)Z</code>".
492     * @param isStatic                 specifies whether the method is static,
493     *                                 e.g. false.
494     * @return the size taken up on the stack,
495     *                                 e.g. 4.
496     */
497    public static int internalMethodParameterSize(String  internalMethodDescriptor,
498                                                  boolean isStatic)
499    {
500        InternalTypeEnumeration internalTypeEnumeration =
501            new InternalTypeEnumeration(internalMethodDescriptor);
502
503        int size = isStatic ? 0 : 1;
504        while (internalTypeEnumeration.hasMoreTypes())
505        {
506            String internalType = internalTypeEnumeration.nextType();
507
508            size += internalTypeSize(internalType);
509        }
510
511        return size;
512    }
513
514
515    /**
516     * Returns the size taken up on the stack by the given internal type.
517     * The size is 1, except for long and double types, for which it is 2,
518     * and for the void type, for which 0 is returned.
519     * @param internalType the internal type,
520     *                     e.g. "<code>I</code>".
521     * @return the size taken up on the stack,
522     *                     e.g. 1.
523     */
524    public static int internalTypeSize(String internalType)
525    {
526        if (internalType.length() == 1)
527        {
528            char internalPrimitiveType = internalType.charAt(0);
529            if      (internalPrimitiveType == ClassConstants.INTERNAL_TYPE_LONG ||
530                     internalPrimitiveType == ClassConstants.INTERNAL_TYPE_DOUBLE)
531            {
532                return 2;
533            }
534            else if (internalPrimitiveType == ClassConstants.INTERNAL_TYPE_VOID)
535            {
536                return 0;
537            }
538        }
539
540        return 1;
541    }
542
543
544    /**
545     * Converts an external type into an internal type.
546     * @param externalType the external type,
547     *                     e.g. "<code>java.lang.Object[][]</code>" or
548     *                          "<code>int[]</code>".
549     * @return the internal type,
550     *                     e.g. "<code>[[Ljava/lang/Object;</code>" or
551     *                          "<code>[I</code>".
552     */
553    public static String internalType(String externalType)
554    {
555        // Strip the array part, if any.
556        int dimensionCount = externalArrayTypeDimensionCount(externalType);
557        if (dimensionCount > 0)
558        {
559            externalType = externalType.substring(0, externalType.length() - dimensionCount * ClassConstants.EXTERNAL_TYPE_ARRAY.length());
560        }
561
562        // Analyze the actual type part.
563        char internalTypeChar =
564            externalType.equals(ClassConstants.EXTERNAL_TYPE_VOID   ) ?
565                                ClassConstants.INTERNAL_TYPE_VOID     :
566            externalType.equals(ClassConstants.EXTERNAL_TYPE_BOOLEAN) ?
567                                ClassConstants.INTERNAL_TYPE_BOOLEAN  :
568            externalType.equals(ClassConstants.EXTERNAL_TYPE_BYTE   ) ?
569                                ClassConstants.INTERNAL_TYPE_BYTE     :
570            externalType.equals(ClassConstants.EXTERNAL_TYPE_CHAR   ) ?
571                                ClassConstants.INTERNAL_TYPE_CHAR     :
572            externalType.equals(ClassConstants.EXTERNAL_TYPE_SHORT  ) ?
573                                ClassConstants.INTERNAL_TYPE_SHORT    :
574            externalType.equals(ClassConstants.EXTERNAL_TYPE_INT    ) ?
575                                ClassConstants.INTERNAL_TYPE_INT      :
576            externalType.equals(ClassConstants.EXTERNAL_TYPE_FLOAT  ) ?
577                                ClassConstants.INTERNAL_TYPE_FLOAT    :
578            externalType.equals(ClassConstants.EXTERNAL_TYPE_LONG   ) ?
579                                ClassConstants.INTERNAL_TYPE_LONG     :
580            externalType.equals(ClassConstants.EXTERNAL_TYPE_DOUBLE ) ?
581                                ClassConstants.INTERNAL_TYPE_DOUBLE   :
582            externalType.equals("%"                                 ) ?
583                                '%'                                   :
584                                (char)0;
585
586        String internalType =
587            internalTypeChar != 0 ? String.valueOf(internalTypeChar) :
588                                    ClassConstants.INTERNAL_TYPE_CLASS_START +
589                                    internalClassName(externalType) +
590                                    ClassConstants.INTERNAL_TYPE_CLASS_END;
591
592        // Prepend the array part, if any.
593        for (int count = 0; count < dimensionCount; count++)
594        {
595            internalType = ClassConstants.INTERNAL_TYPE_ARRAY + internalType;
596        }
597
598        return internalType;
599    }
600
601
602    /**
603     * Returns the number of dimensions of the given external type.
604     * @param externalType the external type,
605     *                     e.g. "<code>[[Ljava/lang/Object;</code>".
606     * @return the number of dimensions, e.g. 2.
607     */
608    public static int externalArrayTypeDimensionCount(String externalType)
609    {
610        int dimensions = 0;
611        int length = ClassConstants.EXTERNAL_TYPE_ARRAY.length();
612        int offset = externalType.length() - length;
613        while (externalType.regionMatches(offset,
614                                          ClassConstants.EXTERNAL_TYPE_ARRAY,
615                                          0,
616                                          length))
617        {
618            dimensions++;
619            offset -= length;
620        }
621
622        return dimensions;
623    }
624
625
626    /**
627     * Converts an internal type into an external type.
628     * @param internalType the internal type,
629     *                     e.g. "<code>[[Ljava/lang/Object;</code>" or
630     *                          "<code>[I</code>".
631     * @return the external type,
632     *                     e.g. "<code>java.lang.Object[][]</code>" or
633     *                          "<code>int[]</code>".
634     */
635    public static String externalType(String internalType)
636    {
637        // Strip the array part, if any.
638        int dimensionCount = internalArrayTypeDimensionCount(internalType);
639        if (dimensionCount > 0)
640        {
641            internalType = internalType.substring(dimensionCount);
642        }
643
644        // Analyze the actual type part.
645        char internalTypeChar = internalType.charAt(0);
646
647        String externalType =
648            internalTypeChar == ClassConstants.INTERNAL_TYPE_VOID        ?
649                                ClassConstants.EXTERNAL_TYPE_VOID        :
650            internalTypeChar == ClassConstants.INTERNAL_TYPE_BOOLEAN     ?
651                                ClassConstants.EXTERNAL_TYPE_BOOLEAN     :
652            internalTypeChar == ClassConstants.INTERNAL_TYPE_BYTE        ?
653                                ClassConstants.EXTERNAL_TYPE_BYTE        :
654            internalTypeChar == ClassConstants.INTERNAL_TYPE_CHAR        ?
655                                ClassConstants.EXTERNAL_TYPE_CHAR        :
656            internalTypeChar == ClassConstants.INTERNAL_TYPE_SHORT       ?
657                                ClassConstants.EXTERNAL_TYPE_SHORT       :
658            internalTypeChar == ClassConstants.INTERNAL_TYPE_INT         ?
659                                ClassConstants.EXTERNAL_TYPE_INT         :
660            internalTypeChar == ClassConstants.INTERNAL_TYPE_FLOAT       ?
661                                ClassConstants.EXTERNAL_TYPE_FLOAT       :
662            internalTypeChar == ClassConstants.INTERNAL_TYPE_LONG        ?
663                                ClassConstants.EXTERNAL_TYPE_LONG        :
664            internalTypeChar == ClassConstants.INTERNAL_TYPE_DOUBLE      ?
665                                ClassConstants.EXTERNAL_TYPE_DOUBLE      :
666            internalTypeChar == '%'                                      ?
667                                "%"                                      :
668            internalTypeChar == ClassConstants.INTERNAL_TYPE_CLASS_START ?
669                                externalClassName(internalType.substring(1, internalType.indexOf(ClassConstants.INTERNAL_TYPE_CLASS_END))) :
670                                null;
671
672        if (externalType == null)
673        {
674            throw new IllegalArgumentException("Unknown type ["+internalType+"]");
675        }
676
677        // Append the array part, if any.
678        for (int count = 0; count < dimensionCount; count++)
679        {
680            externalType += ClassConstants.EXTERNAL_TYPE_ARRAY;
681        }
682
683        return externalType;
684    }
685
686
687    /**
688     * Returns whether the given internal descriptor String represents a method
689     * descriptor.
690     * @param internalDescriptor the internal descriptor String,
691     *                           e.g. "<code>(II)Z</code>".
692     * @return <code>true</code> if the given String is a method descriptor,
693     *         <code>false</code> otherwise.
694     */
695    public static boolean isInternalMethodDescriptor(String internalDescriptor)
696    {
697        return internalDescriptor.charAt(0) == ClassConstants.INTERNAL_METHOD_ARGUMENTS_OPEN;
698    }
699
700
701    /**
702     * Returns whether the given member String represents an external method
703     * name with arguments.
704     * @param externalMemberNameAndArguments the external member String,
705     *                                       e.g. "<code>myField</code>" or
706     *                                       e.g. "<code>myMethod(int,int)</code>".
707     * @return <code>true</code> if the given String refers to a method,
708     *         <code>false</code> otherwise.
709     */
710    public static boolean isExternalMethodNameAndArguments(String externalMemberNameAndArguments)
711    {
712        return externalMemberNameAndArguments.indexOf(ClassConstants.EXTERNAL_METHOD_ARGUMENTS_OPEN) > 0;
713    }
714
715
716    /**
717     * Returns the name part of the given external method name and arguments.
718     * @param externalMethodNameAndArguments the external method name and arguments,
719     *                                       e.g. "<code>myMethod(int,int)</code>".
720     * @return the name part of the String, e.g. "<code>myMethod</code>".
721     */
722    public static String externalMethodName(String externalMethodNameAndArguments)
723    {
724        ExternalTypeEnumeration externalTypeEnumeration =
725            new ExternalTypeEnumeration(externalMethodNameAndArguments);
726
727        return externalTypeEnumeration.methodName();
728    }
729
730
731    /**
732     * Converts the given external method return type and name and arguments to
733     * an internal method descriptor.
734     * @param externalReturnType             the external method return type,
735     *                                       e.g. "<code>boolean</code>".
736     * @param externalMethodNameAndArguments the external method name and arguments,
737     *                                       e.g. "<code>myMethod(int,int)</code>".
738     * @return the internal method descriptor,
739     *                                       e.g. "<code>(II)Z</code>".
740     */
741    public static String internalMethodDescriptor(String externalReturnType,
742                                                  String externalMethodNameAndArguments)
743    {
744        StringBuffer internalMethodDescriptor = new StringBuffer();
745        internalMethodDescriptor.append(ClassConstants.INTERNAL_METHOD_ARGUMENTS_OPEN);
746
747        ExternalTypeEnumeration externalTypeEnumeration =
748            new ExternalTypeEnumeration(externalMethodNameAndArguments);
749
750        while (externalTypeEnumeration.hasMoreTypes())
751        {
752            internalMethodDescriptor.append(internalType(externalTypeEnumeration.nextType()));
753        }
754
755        internalMethodDescriptor.append(ClassConstants.INTERNAL_METHOD_ARGUMENTS_CLOSE);
756        internalMethodDescriptor.append(internalType(externalReturnType));
757
758        return internalMethodDescriptor.toString();
759    }
760
761
762    /**
763     * Converts the given external method return type and List of arguments to
764     * an internal method descriptor.
765     * @param externalReturnType the external method return type,
766     *                                       e.g. "<code>boolean</code>".
767     * @param externalArguments the external method arguments,
768     *                                       e.g. <code>{ "int", "int" }</code>.
769     * @return the internal method descriptor,
770     *                                       e.g. "<code>(II)Z</code>".
771     */
772    public static String internalMethodDescriptor(String externalReturnType,
773                                                  List   externalArguments)
774    {
775        StringBuffer internalMethodDescriptor = new StringBuffer();
776        internalMethodDescriptor.append(ClassConstants.INTERNAL_METHOD_ARGUMENTS_OPEN);
777
778        for (int index = 0; index < externalArguments.size(); index++)
779        {
780            internalMethodDescriptor.append(internalType((String)externalArguments.get(index)));
781        }
782
783        internalMethodDescriptor.append(ClassConstants.INTERNAL_METHOD_ARGUMENTS_CLOSE);
784        internalMethodDescriptor.append(internalType(externalReturnType));
785
786        return internalMethodDescriptor.toString();
787    }
788
789
790    /**
791     * Converts an internal field description into an external full field description.
792     * @param accessFlags             the access flags of the field.
793     * @param fieldName               the field name,
794     *                                e.g. "<code>myField</code>".
795     * @param internalFieldDescriptor the internal field descriptor,
796     *                                e.g. "<code>Z</code>".
797     * @return the external full field description,
798     *                                e.g. "<code>public boolean myField</code>".
799     */
800    public static String externalFullFieldDescription(int    accessFlags,
801                                                      String fieldName,
802                                                      String internalFieldDescriptor)
803    {
804        return externalFieldAccessFlags(accessFlags) +
805               externalType(internalFieldDescriptor) +
806               ' ' +
807               fieldName;
808    }
809
810
811    /**
812     * Converts an internal method description into an external full method description.
813     * @param internalClassName        the internal name of the class of the method,
814     *                                 e.g. "<code>mypackage/MyClass</code>".
815     * @param accessFlags              the access flags of the method.
816     * @param internalMethodName       the internal method name,
817     *                                 e.g. "<code>myMethod</code>" or
818     *                                      "<code>&lt;init&gt;</code>".
819     * @param internalMethodDescriptor the internal method descriptor,
820     *                                 e.g. "<code>(II)Z</code>".
821     * @return the external full method description,
822     *                                 e.g. "<code>public boolean myMethod(int,int)</code>" or
823     *                                      "<code>public MyClass(int,int)</code>".
824     */
825    public static String externalFullMethodDescription(String internalClassName,
826                                                       int    accessFlags,
827                                                       String internalMethodName,
828                                                       String internalMethodDescriptor)
829    {
830        return externalMethodAccessFlags(accessFlags) +
831               externalMethodReturnTypeAndName(internalClassName,
832                                               internalMethodName,
833                                               internalMethodDescriptor) +
834               ClassConstants.EXTERNAL_METHOD_ARGUMENTS_OPEN +
835               externalMethodArguments(internalMethodDescriptor) +
836               ClassConstants.EXTERNAL_METHOD_ARGUMENTS_CLOSE;
837    }
838
839
840    /**
841     * Converts internal class access flags into an external access description.
842     * @param accessFlags the class access flags.
843     * @return the external class access description,
844     *         e.g. "<code>public final </code>".
845     */
846    public static String externalClassAccessFlags(int accessFlags)
847    {
848        return externalClassAccessFlags(accessFlags, "");
849    }
850
851
852    /**
853     * Converts internal class access flags into an external access description.
854     * @param accessFlags the class access flags.
855     * @param prefix      a prefix that is added to each access modifier.
856     * @return the external class access description,
857     *         e.g. "<code>public final </code>".
858     */
859    public static String externalClassAccessFlags(int accessFlags, String prefix)
860    {
861        if (accessFlags == 0)
862        {
863            return EMPTY_STRING;
864        }
865
866        StringBuffer string = new StringBuffer(50);
867
868        if ((accessFlags & ClassConstants.INTERNAL_ACC_PUBLIC) != 0)
869        {
870            string.append(prefix).append(ClassConstants.EXTERNAL_ACC_PUBLIC).append(' ');
871        }
872        if ((accessFlags & ClassConstants.INTERNAL_ACC_FINAL) != 0)
873        {
874            string.append(prefix).append(ClassConstants.EXTERNAL_ACC_FINAL).append(' ');
875        }
876        if ((accessFlags & ClassConstants.INTERNAL_ACC_ANNOTATTION) != 0)
877        {
878            string.append(prefix).append(ClassConstants.EXTERNAL_ACC_ANNOTATION);
879        }
880        if ((accessFlags & ClassConstants.INTERNAL_ACC_INTERFACE) != 0)
881        {
882            string.append(prefix).append(ClassConstants.EXTERNAL_ACC_INTERFACE).append(' ');
883        }
884        else if ((accessFlags & ClassConstants.INTERNAL_ACC_ENUM) != 0)
885        {
886            string.append(prefix).append(ClassConstants.EXTERNAL_ACC_ENUM).append(' ');
887        }
888        else if ((accessFlags & ClassConstants.INTERNAL_ACC_ABSTRACT) != 0)
889        {
890            string.append(prefix).append(ClassConstants.EXTERNAL_ACC_ABSTRACT).append(' ');
891        }
892
893        return string.toString();
894    }
895
896
897    /**
898     * Converts internal field access flags into an external access description.
899     * @param accessFlags the field access flags.
900     * @return the external field access description,
901     *         e.g. "<code>public volatile </code>".
902     */
903    public static String externalFieldAccessFlags(int accessFlags)
904    {
905        return externalFieldAccessFlags(accessFlags, "");
906    }
907
908
909    /**
910     * Converts internal field access flags into an external access description.
911     * @param accessFlags the field access flags.
912     * @param prefix      a prefix that is added to each access modifier.
913     * @return the external field access description,
914     *         e.g. "<code>public volatile </code>".
915     */
916    public static String externalFieldAccessFlags(int accessFlags, String prefix)
917    {
918        if (accessFlags == 0)
919        {
920            return EMPTY_STRING;
921        }
922
923        StringBuffer string = new StringBuffer(50);
924
925        if ((accessFlags & ClassConstants.INTERNAL_ACC_PUBLIC) != 0)
926        {
927            string.append(prefix).append(ClassConstants.EXTERNAL_ACC_PUBLIC).append(' ');
928        }
929        if ((accessFlags & ClassConstants.INTERNAL_ACC_PRIVATE) != 0)
930        {
931            string.append(prefix).append(ClassConstants.EXTERNAL_ACC_PRIVATE).append(' ');
932        }
933        if ((accessFlags & ClassConstants.INTERNAL_ACC_PROTECTED) != 0)
934        {
935            string.append(prefix).append(ClassConstants.EXTERNAL_ACC_PROTECTED).append(' ');
936        }
937        if ((accessFlags & ClassConstants.INTERNAL_ACC_STATIC) != 0)
938        {
939            string.append(prefix).append(ClassConstants.EXTERNAL_ACC_STATIC).append(' ');
940        }
941        if ((accessFlags & ClassConstants.INTERNAL_ACC_FINAL) != 0)
942        {
943            string.append(prefix).append(ClassConstants.EXTERNAL_ACC_FINAL).append(' ');
944        }
945        if ((accessFlags & ClassConstants.INTERNAL_ACC_VOLATILE) != 0)
946        {
947            string.append(prefix).append(ClassConstants.EXTERNAL_ACC_VOLATILE).append(' ');
948        }
949        if ((accessFlags & ClassConstants.INTERNAL_ACC_TRANSIENT) != 0)
950        {
951            string.append(prefix).append(ClassConstants.EXTERNAL_ACC_TRANSIENT).append(' ');
952        }
953
954        return string.toString();
955    }
956
957
958    /**
959     * Converts internal method access flags into an external access description.
960     * @param accessFlags the method access flags.
961     * @return the external method access description,
962     *                    e.g. "<code>public synchronized </code>".
963     */
964    public static String externalMethodAccessFlags(int accessFlags)
965    {
966        return externalMethodAccessFlags(accessFlags, "");
967    }
968
969
970    /**
971     * Converts internal method access flags into an external access description.
972     * @param accessFlags the method access flags.
973     * @param prefix      a prefix that is added to each access modifier.
974     * @return the external method access description,
975     *                    e.g. "public synchronized ".
976     */
977    public static String externalMethodAccessFlags(int accessFlags, String prefix)
978    {
979        if (accessFlags == 0)
980        {
981            return EMPTY_STRING;
982        }
983
984        StringBuffer string = new StringBuffer(50);
985
986        if ((accessFlags & ClassConstants.INTERNAL_ACC_PUBLIC) != 0)
987        {
988            string.append(prefix).append(ClassConstants.EXTERNAL_ACC_PUBLIC).append(' ');
989        }
990        if ((accessFlags & ClassConstants.INTERNAL_ACC_PRIVATE) != 0)
991        {
992            string.append(prefix).append(ClassConstants.EXTERNAL_ACC_PRIVATE).append(' ');
993        }
994        if ((accessFlags & ClassConstants.INTERNAL_ACC_PROTECTED) != 0)
995        {
996            string.append(prefix).append(ClassConstants.EXTERNAL_ACC_PROTECTED).append(' ');
997        }
998        if ((accessFlags & ClassConstants.INTERNAL_ACC_STATIC) != 0)
999        {
1000            string.append(prefix).append(ClassConstants.EXTERNAL_ACC_STATIC).append(' ');
1001        }
1002        if ((accessFlags & ClassConstants.INTERNAL_ACC_FINAL) != 0)
1003        {
1004            string.append(prefix).append(ClassConstants.EXTERNAL_ACC_FINAL).append(' ');
1005        }
1006        if ((accessFlags & ClassConstants.INTERNAL_ACC_SYNCHRONIZED) != 0)
1007        {
1008            string.append(prefix).append(ClassConstants.EXTERNAL_ACC_SYNCHRONIZED).append(' ');
1009        }
1010        if ((accessFlags & ClassConstants.INTERNAL_ACC_NATIVE) != 0)
1011        {
1012            string.append(prefix).append(ClassConstants.EXTERNAL_ACC_NATIVE).append(' ');
1013        }
1014        if ((accessFlags & ClassConstants.INTERNAL_ACC_ABSTRACT) != 0)
1015        {
1016            string.append(prefix).append(ClassConstants.EXTERNAL_ACC_ABSTRACT).append(' ');
1017        }
1018        if ((accessFlags & ClassConstants.INTERNAL_ACC_STRICT) != 0)
1019        {
1020            string.append(prefix).append(ClassConstants.EXTERNAL_ACC_STRICT).append(' ');
1021        }
1022
1023        return string.toString();
1024    }
1025
1026
1027    /**
1028     * Converts an internal method descriptor into an external method return type.
1029     * @param internalMethodDescriptor the internal method descriptor,
1030     *                                 e.g. "<code>(II)Z</code>".
1031     * @return the external method return type,
1032     *                                 e.g. "<code>boolean</code>".
1033     */
1034    public static String externalMethodReturnType(String internalMethodDescriptor)
1035    {
1036        return externalType(internalMethodReturnType(internalMethodDescriptor));
1037    }
1038
1039
1040    /**
1041     * Converts an internal class name, method name, and method descriptor to
1042     * an external method return type and name.
1043     * @param internalClassName        the internal name of the class of the method,
1044     *                                 e.g. "<code>mypackage/MyClass</code>".
1045     * @param internalMethodName       the internal method name,
1046     *                                 e.g. "<code>myMethod</code>" or
1047     *                                      "<code>&lt;init&gt;</code>".
1048     * @param internalMethodDescriptor the internal method descriptor,
1049     *                                 e.g. "<code>(II)Z</code>".
1050     * @return the external method return type and name,
1051     *                                 e.g. "<code>boolean myMethod</code>" or
1052     *                                      "<code>MyClass</code>".
1053     */
1054    private static String externalMethodReturnTypeAndName(String internalClassName,
1055                                                          String internalMethodName,
1056                                                          String internalMethodDescriptor)
1057    {
1058        return internalMethodName.equals(ClassConstants.INTERNAL_METHOD_NAME_INIT) ?
1059            externalShortClassName(externalClassName(internalClassName)) :
1060            (externalMethodReturnType(internalMethodDescriptor) +
1061             ' ' +
1062             internalMethodName);
1063    }
1064
1065
1066    /**
1067     * Converts an internal method descriptor into an external method argument
1068     * description.
1069     * @param internalMethodDescriptor the internal method descriptor,
1070     *                                 e.g. "<code>(II)Z</code>".
1071     * @return the external method argument description,
1072     *                                 e.g. "<code>int,int</code>".
1073     */
1074    public static String externalMethodArguments(String internalMethodDescriptor)
1075    {
1076        StringBuffer externalMethodNameAndArguments = new StringBuffer();
1077
1078        InternalTypeEnumeration internalTypeEnumeration =
1079            new InternalTypeEnumeration(internalMethodDescriptor);
1080
1081        while (internalTypeEnumeration.hasMoreTypes())
1082        {
1083            externalMethodNameAndArguments.append(externalType(internalTypeEnumeration.nextType()));
1084            if (internalTypeEnumeration.hasMoreTypes())
1085            {
1086                externalMethodNameAndArguments.append(ClassConstants.EXTERNAL_METHOD_ARGUMENTS_SEPARATOR);
1087            }
1088        }
1089
1090        return externalMethodNameAndArguments.toString();
1091    }
1092
1093
1094    /**
1095     * Returns the internal package name of the given internal class name.
1096     * @param internalClassName the internal class name,
1097     *                          e.g. "<code>java/lang/Object</code>".
1098     * @return the internal package name,
1099     *                          e.g. "<code>java/lang</code>".
1100     */
1101    public static String internalPackageName(String internalClassName)
1102    {
1103        String internalPackagePrefix = internalPackagePrefix(internalClassName);
1104        int length = internalPackagePrefix.length();
1105        return length > 0 ?
1106            internalPackagePrefix.substring(0, length - 1) :
1107            "";
1108    }
1109
1110
1111    /**
1112     * Returns the internal package prefix of the given internal class name.
1113     * @param internalClassName the internal class name,
1114     *                          e.g. "<code>java/lang/Object</code>".
1115     * @return the internal package prefix,
1116     *                          e.g. "<code>java/lang/</code>".
1117     */
1118    public static String internalPackagePrefix(String internalClassName)
1119    {
1120        return internalClassName.substring(0, internalClassName.lastIndexOf(ClassConstants.INTERNAL_PACKAGE_SEPARATOR,
1121                                                                            internalClassName.length() - 2) + 1);
1122    }
1123
1124
1125    /**
1126     * Returns the external package name of the given external class name.
1127     * @param externalClassName the external class name,
1128     *                          e.g. "<code>java.lang.Object</code>".
1129     * @return the external package name,
1130     *                          e.g. "<code>java.lang</code>".
1131     */
1132    public static String externalPackageName(String externalClassName)
1133    {
1134        String externalPackagePrefix = externalPackagePrefix(externalClassName);
1135        int length = externalPackagePrefix.length();
1136        return length > 0 ?
1137            externalPackagePrefix.substring(0, length - 1) :
1138            "";
1139    }
1140
1141
1142    /**
1143     * Returns the external package prefix of the given external class name.
1144     * @param externalClassName the external class name,
1145     *                          e.g. "<code>java.lang.Object</code>".
1146     * @return the external package prefix,
1147     *                          e.g. "<code>java.lang.</code>".
1148     */
1149    public static String externalPackagePrefix(String externalClassName)
1150    {
1151        return externalClassName.substring(0, externalClassName.lastIndexOf(ClassConstants.EXTERNAL_PACKAGE_SEPARATOR,
1152                                                                            externalClassName.length() - 2) + 1);
1153    }
1154}
1155