1/*
2 * Javassist, a Java-bytecode translator toolkit.
3 * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
4 *
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License.  Alternatively, the contents of this file may be used under
8 * the terms of the GNU Lesser General Public License Version 2.1 or later.
9 *
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
14 */
15
16package javassist.bytecode;
17
18import javassist.ClassPool;
19import javassist.CtClass;
20import javassist.CtPrimitiveType;
21import javassist.NotFoundException;
22import java.util.Map;
23
24/**
25 * A support class for dealing with descriptors.
26 *
27 * <p>See chapter 4.3 in "The Java Virtual Machine Specification (2nd ed.)"
28 */
29public class Descriptor {
30    /**
31     * Converts a class name into the internal representation used in
32     * the JVM.
33     *
34     * <p>Note that <code>toJvmName(toJvmName(s))</code> is equivalent
35     * to <code>toJvmName(s)</code>.
36     */
37    public static String toJvmName(String classname) {
38        return classname.replace('.', '/');
39    }
40
41    /**
42     * Converts a class name from the internal representation used in
43     * the JVM to the normal one used in Java.
44     * This method does not deal with an array type name such as
45     * "[Ljava/lang/Object;" and "[I;".  For such names, use
46     * <code>toClassName()</code>.
47     *
48     * @see #toClassName(String)
49     */
50    public static String toJavaName(String classname) {
51        return classname.replace('/', '.');
52    }
53
54    /**
55     * Returns the internal representation of the class name in the
56     * JVM.
57     */
58    public static String toJvmName(CtClass clazz) {
59        if (clazz.isArray())
60            return of(clazz);
61        else
62            return toJvmName(clazz.getName());
63    }
64
65    /**
66     * Converts to a Java class name from a descriptor.
67     *
68     * @param descriptor        type descriptor.
69     */
70    public static String toClassName(String descriptor) {
71        int arrayDim = 0;
72        int i = 0;
73        char c = descriptor.charAt(0);
74        while (c == '[') {
75            ++arrayDim;
76            c = descriptor.charAt(++i);
77        }
78
79        String name;
80        if (c == 'L') {
81            int i2 = descriptor.indexOf(';', i++);
82            name = descriptor.substring(i, i2).replace('/', '.');
83            i = i2;
84        }
85        else if (c == 'V')
86            name =  "void";
87        else if (c == 'I')
88            name = "int";
89        else if (c == 'B')
90            name = "byte";
91        else if (c == 'J')
92            name = "long";
93        else if (c == 'D')
94            name = "double";
95        else if (c == 'F')
96            name = "float";
97        else if (c == 'C')
98            name = "char";
99        else if (c == 'S')
100            name = "short";
101        else if (c == 'Z')
102            name = "boolean";
103        else
104            throw new RuntimeException("bad descriptor: " + descriptor);
105
106        if (i + 1 != descriptor.length())
107            throw new RuntimeException("multiple descriptors?: " + descriptor);
108
109        if (arrayDim == 0)
110            return name;
111        else {
112            StringBuffer sbuf = new StringBuffer(name);
113            do {
114                sbuf.append("[]");
115            } while (--arrayDim > 0);
116
117            return sbuf.toString();
118        }
119    }
120
121    /**
122     * Converts to a descriptor from a Java class name
123     */
124    public static String of(String classname) {
125        if (classname.equals("void"))
126            return "V";
127        else if (classname.equals("int"))
128            return "I";
129        else if (classname.equals("byte"))
130            return "B";
131        else if (classname.equals("long"))
132            return "J";
133        else if (classname.equals("double"))
134            return "D";
135        else if (classname.equals("float"))
136            return "F";
137        else if (classname.equals("char"))
138            return "C";
139        else if (classname.equals("short"))
140            return "S";
141        else if (classname.equals("boolean"))
142            return "Z";
143        else
144            return "L" + toJvmName(classname) + ";";
145    }
146
147    /**
148     * Substitutes a class name
149     * in the given descriptor string.
150     *
151     * @param desc    descriptor string
152     * @param oldname replaced JVM class name
153     * @param newname substituted JVM class name
154     *
155     * @see Descriptor#toJvmName(String)
156     */
157    public static String rename(String desc, String oldname, String newname) {
158        if (desc.indexOf(oldname) < 0)
159            return desc;
160
161        StringBuffer newdesc = new StringBuffer();
162        int head = 0;
163        int i = 0;
164        for (;;) {
165            int j = desc.indexOf('L', i);
166            if (j < 0)
167                break;
168            else if (desc.startsWith(oldname, j + 1)
169                     && desc.charAt(j + oldname.length() + 1) == ';') {
170                newdesc.append(desc.substring(head, j));
171                newdesc.append('L');
172                newdesc.append(newname);
173                newdesc.append(';');
174                head = i = j + oldname.length() + 2;
175            }
176            else {
177                i = desc.indexOf(';', j) + 1;
178                if (i < 1)
179                    break; // ';' was not found.
180            }
181        }
182
183        if (head == 0)
184            return desc;
185        else {
186            int len = desc.length();
187            if (head < len)
188                newdesc.append(desc.substring(head, len));
189
190            return newdesc.toString();
191        }
192    }
193
194    /**
195     * Substitutes class names in the given descriptor string
196     * according to the given <code>map</code>.
197     *
198     * @param map a map between replaced and substituted
199     *            JVM class names.
200     * @see Descriptor#toJvmName(String)
201     */
202    public static String rename(String desc, Map map) {
203        if (map == null)
204            return desc;
205
206        StringBuffer newdesc = new StringBuffer();
207        int head = 0;
208        int i = 0;
209        for (;;) {
210            int j = desc.indexOf('L', i);
211            if (j < 0)
212                break;
213
214            int k = desc.indexOf(';', j);
215            if (k < 0)
216                break;
217
218            i = k + 1;
219            String name = desc.substring(j + 1, k);
220            String name2 = (String)map.get(name);
221            if (name2 != null) {
222                newdesc.append(desc.substring(head, j));
223                newdesc.append('L');
224                newdesc.append(name2);
225                newdesc.append(';');
226                head = i;
227            }
228        }
229
230        if (head == 0)
231            return desc;
232        else {
233            int len = desc.length();
234            if (head < len)
235                newdesc.append(desc.substring(head, len));
236
237            return newdesc.toString();
238        }
239    }
240
241    /**
242     * Returns the descriptor representing the given type.
243     */
244    public static String of(CtClass type) {
245        StringBuffer sbuf = new StringBuffer();
246        toDescriptor(sbuf, type);
247        return sbuf.toString();
248    }
249
250    private static void toDescriptor(StringBuffer desc, CtClass type) {
251        if (type.isArray()) {
252            desc.append('[');
253            try {
254                toDescriptor(desc, type.getComponentType());
255            }
256            catch (NotFoundException e) {
257                desc.append('L');
258                String name = type.getName();
259                desc.append(toJvmName(name.substring(0, name.length() - 2)));
260                desc.append(';');
261            }
262        }
263        else if (type.isPrimitive()) {
264            CtPrimitiveType pt = (CtPrimitiveType)type;
265            desc.append(pt.getDescriptor());
266        }
267        else { // class type
268            desc.append('L');
269            desc.append(type.getName().replace('.', '/'));
270            desc.append(';');
271        }
272    }
273
274    /**
275     * Returns the descriptor representing a constructor receiving
276     * the given parameter types.
277     *
278     * @param paramTypes parameter types
279     */
280    public static String ofConstructor(CtClass[] paramTypes) {
281        return ofMethod(CtClass.voidType, paramTypes);
282    }
283
284    /**
285     * Returns the descriptor representing a method that receives
286     * the given parameter types and returns the given type.
287     *
288     * @param returnType return type
289     * @param paramTypes parameter types
290     */
291    public static String ofMethod(CtClass returnType, CtClass[] paramTypes) {
292        StringBuffer desc = new StringBuffer();
293        desc.append('(');
294        if (paramTypes != null) {
295            int n = paramTypes.length;
296            for (int i = 0; i < n; ++i)
297                toDescriptor(desc, paramTypes[i]);
298        }
299
300        desc.append(')');
301        if (returnType != null)
302            toDescriptor(desc, returnType);
303
304        return desc.toString();
305    }
306
307    /**
308     * Returns the descriptor representing a list of parameter types.
309     * For example, if the given parameter types are two <code>int</code>,
310     * then this method returns <code>"(II)"</code>.
311     *
312     * @param paramTypes parameter types
313     */
314    public static String ofParameters(CtClass[] paramTypes) {
315        return ofMethod(null, paramTypes);
316    }
317
318    /**
319     * Appends a parameter type to the parameter list represented
320     * by the given descriptor.
321     *
322     * <p><code>classname</code> must not be an array type.
323     *
324     * @param classname parameter type (not primitive type)
325     * @param desc      descriptor
326     */
327    public static String appendParameter(String classname, String desc) {
328        int i = desc.indexOf(')');
329        if (i < 0)
330            return desc;
331        else {
332            StringBuffer newdesc = new StringBuffer();
333            newdesc.append(desc.substring(0, i));
334            newdesc.append('L');
335            newdesc.append(classname.replace('.', '/'));
336            newdesc.append(';');
337            newdesc.append(desc.substring(i));
338            return newdesc.toString();
339        }
340    }
341
342    /**
343     * Inserts a parameter type at the beginning of the parameter
344     * list represented
345     * by the given descriptor.
346     *
347     * <p><code>classname</code> must not be an array type.
348     *
349     * @param classname parameter type (not primitive type)
350     * @param desc      descriptor
351     */
352    public static String insertParameter(String classname, String desc) {
353        if (desc.charAt(0) != '(')
354            return desc;
355        else
356            return "(L" + classname.replace('.', '/') + ';'
357                   + desc.substring(1);
358    }
359
360    /**
361     * Appends a parameter type to the parameter list represented
362     * by the given descriptor.  The appended parameter becomes
363     * the last parameter.
364     *
365     * @param type      the type of the appended parameter.
366     * @param descriptor      the original descriptor.
367     */
368    public static String appendParameter(CtClass type, String descriptor) {
369        int i = descriptor.indexOf(')');
370        if (i < 0)
371            return descriptor;
372        else {
373            StringBuffer newdesc = new StringBuffer();
374            newdesc.append(descriptor.substring(0, i));
375            toDescriptor(newdesc, type);
376            newdesc.append(descriptor.substring(i));
377            return newdesc.toString();
378        }
379    }
380
381    /**
382     * Inserts a parameter type at the beginning of the parameter
383     * list represented
384     * by the given descriptor.
385     *
386     * @param type              the type of the inserted parameter.
387     * @param descriptor        the descriptor of the method.
388     */
389    public static String insertParameter(CtClass type,
390                                         String descriptor) {
391        if (descriptor.charAt(0) != '(')
392            return descriptor;
393        else
394            return "(" + of(type) + descriptor.substring(1);
395    }
396
397    /**
398     * Changes the return type included in the given descriptor.
399     *
400     * <p><code>classname</code> must not be an array type.
401     *
402     * @param classname return type
403     * @param desc      descriptor
404     */
405    public static String changeReturnType(String classname, String desc) {
406        int i = desc.indexOf(')');
407        if (i < 0)
408            return desc;
409        else {
410            StringBuffer newdesc = new StringBuffer();
411            newdesc.append(desc.substring(0, i + 1));
412            newdesc.append('L');
413            newdesc.append(classname.replace('.', '/'));
414            newdesc.append(';');
415            return newdesc.toString();
416        }
417    }
418
419    /**
420     * Returns the <code>CtClass</code> objects representing the parameter
421     * types specified by the given descriptor.
422     *
423     * @param desc descriptor
424     * @param cp   the class pool used for obtaining
425     *             a <code>CtClass</code> object.
426     */
427    public static CtClass[] getParameterTypes(String desc, ClassPool cp)
428        throws NotFoundException
429    {
430        if (desc.charAt(0) != '(')
431            return null;
432        else {
433            int num = numOfParameters(desc);
434            CtClass[] args = new CtClass[num];
435            int n = 0;
436            int i = 1;
437            do {
438                i = toCtClass(cp, desc, i, args, n++);
439            } while (i > 0);
440            return args;
441        }
442    }
443
444    /**
445     * Returns true if the list of the parameter types of desc1 is equal to
446     * that of desc2.
447     * For example, "(II)V" and "(II)I" are equal.
448     */
449    public static boolean eqParamTypes(String desc1, String desc2) {
450        if (desc1.charAt(0) != '(')
451            return false;
452
453        for (int i = 0; true; ++i) {
454            char c = desc1.charAt(i);
455            if (c != desc2.charAt(i))
456                return false;
457
458            if (c == ')')
459                return true;
460        }
461    }
462
463    /**
464     * Returns the signature of the given descriptor.  The signature does
465     * not include the return type.  For example, the signature of "(I)V"
466     * is "(I)".
467     */
468    public static String getParamDescriptor(String decl) {
469        return decl.substring(0, decl.indexOf(')') + 1);
470    }
471
472    /**
473     * Returns the <code>CtClass</code> object representing the return
474     * type specified by the given descriptor.
475     *
476     * @param desc descriptor
477     * @param cp   the class pool used for obtaining
478     *             a <code>CtClass</code> object.
479     */
480    public static CtClass getReturnType(String desc, ClassPool cp)
481        throws NotFoundException
482    {
483        int i = desc.indexOf(')');
484        if (i < 0)
485            return null;
486        else {
487            CtClass[] type = new CtClass[1];
488            toCtClass(cp, desc, i + 1, type, 0);
489            return type[0];
490        }
491    }
492
493    /**
494     * Returns the number of the prameters included in the given
495     * descriptor.
496     *
497     * @param desc descriptor
498     */
499    public static int numOfParameters(String desc) {
500        int n = 0;
501        int i = 1;
502        for (;;) {
503            char c = desc.charAt(i);
504            if (c == ')')
505                break;
506
507            while (c == '[')
508                c = desc.charAt(++i);
509
510            if (c == 'L') {
511                i = desc.indexOf(';', i) + 1;
512                if (i <= 0)
513                    throw new IndexOutOfBoundsException("bad descriptor");
514            }
515            else
516                ++i;
517
518            ++n;
519        }
520
521        return n;
522    }
523
524    /**
525     * Returns a <code>CtClass</code> object representing the type
526     * specified by the given descriptor.
527     *
528     * <p>This method works even if the package-class separator is
529     * not <code>/</code> but <code>.</code> (period).  For example,
530     * it accepts <code>Ljava.lang.Object;</code>
531     * as well as <code>Ljava/lang/Object;</code>.
532     *
533     * @param desc descriptor.
534     * @param cp   the class pool used for obtaining
535     *             a <code>CtClass</code> object.
536     */
537    public static CtClass toCtClass(String desc, ClassPool cp)
538        throws NotFoundException
539    {
540        CtClass[] clazz = new CtClass[1];
541        int res = toCtClass(cp, desc, 0, clazz, 0);
542        if (res >= 0)
543            return clazz[0];
544        else {
545            // maybe, you forgot to surround the class name with
546            // L and ;.  It violates the protocol, but I'm tolerant...
547            return cp.get(desc.replace('/', '.'));
548        }
549    }
550
551    private static int toCtClass(ClassPool cp, String desc, int i,
552                                 CtClass[] args, int n)
553        throws NotFoundException
554    {
555        int i2;
556        String name;
557
558        int arrayDim = 0;
559        char c = desc.charAt(i);
560        while (c == '[') {
561            ++arrayDim;
562            c = desc.charAt(++i);
563        }
564
565        if (c == 'L') {
566            i2 = desc.indexOf(';', ++i);
567            name = desc.substring(i, i2++).replace('/', '.');
568        }
569        else {
570            CtClass type = toPrimitiveClass(c);
571            if (type == null)
572                return -1; // error
573
574            i2 = i + 1;
575            if (arrayDim == 0) {
576                args[n] = type;
577                return i2; // neither an array type or a class type
578            }
579            else
580                name = type.getName();
581        }
582
583        if (arrayDim > 0) {
584            StringBuffer sbuf = new StringBuffer(name);
585            while (arrayDim-- > 0)
586                sbuf.append("[]");
587
588            name = sbuf.toString();
589        }
590
591        args[n] = cp.get(name);
592        return i2;
593    }
594
595    static CtClass toPrimitiveClass(char c) {
596        CtClass type = null;
597        switch (c) {
598        case 'Z' :
599            type = CtClass.booleanType;
600            break;
601        case 'C' :
602            type = CtClass.charType;
603            break;
604        case 'B' :
605            type = CtClass.byteType;
606            break;
607        case 'S' :
608            type = CtClass.shortType;
609            break;
610        case 'I' :
611            type = CtClass.intType;
612            break;
613        case 'J' :
614            type = CtClass.longType;
615            break;
616        case 'F' :
617            type = CtClass.floatType;
618            break;
619        case 'D' :
620            type = CtClass.doubleType;
621            break;
622        case 'V' :
623            type = CtClass.voidType;
624            break;
625        }
626
627        return type;
628    }
629
630    /**
631     * Computes the dimension of the array represented by the given
632     * descriptor.  For example, if the descriptor is <code>"[[I"</code>,
633     * then this method returns 2.
634     *
635     * @param desc the descriptor.
636     * @return 0        if the descriptor does not represent an array type.
637     */
638    public static int arrayDimension(String desc) {
639        int dim = 0;
640        while (desc.charAt(dim) == '[')
641            ++dim;
642
643        return dim;
644    }
645
646    /**
647     * Returns the descriptor of the type of the array component.
648     * For example, if the given descriptor is
649     * <code>"[[Ljava/lang/String;"</code> and the given dimension is 2,
650     * then this method returns <code>"Ljava/lang/String;"</code>.
651     *
652     * @param desc the descriptor.
653     * @param dim  the array dimension.
654     */
655    public static String toArrayComponent(String desc, int dim) {
656        return desc.substring(dim);
657    }
658
659    /**
660     * Computes the data size specified by the given descriptor.
661     * For example, if the descriptor is "D", this method returns 2.
662     *
663     * <p>If the descriptor represents a method type, this method returns
664     * (the size of the returned value) - (the sum of the data sizes
665     * of all the parameters).  For example, if the descriptor is
666     * <code>"(I)D"</code>, then this method returns 1 (= 2 - 1).
667     *
668     * @param desc descriptor
669     */
670    public static int dataSize(String desc) {
671        return dataSize(desc, true);
672    }
673
674    /**
675     * Computes the data size of parameters.
676     * If one of the parameters is double type, the size of that parameter
677     * is 2 words.  For example, if the given descriptor is
678     *  <code>"(IJ)D"</code>, then this method returns 3.  The size of the
679     * return type is not computed.
680     *
681     * @param desc      a method descriptor.
682     */
683    public static int paramSize(String desc) {
684        return -dataSize(desc, false);
685    }
686
687    private static int dataSize(String desc, boolean withRet) {
688        int n = 0;
689        char c = desc.charAt(0);
690        if (c == '(') {
691            int i = 1;
692            for (;;) {
693                c = desc.charAt(i);
694                if (c == ')') {
695                    c = desc.charAt(i + 1);
696                    break;
697                }
698
699                boolean array = false;
700                while (c == '[') {
701                    array = true;
702                    c = desc.charAt(++i);
703                }
704
705                if (c == 'L') {
706                    i = desc.indexOf(';', i) + 1;
707                    if (i <= 0)
708                        throw new IndexOutOfBoundsException("bad descriptor");
709                }
710                else
711                    ++i;
712
713                if (!array && (c == 'J' || c == 'D'))
714                    n -= 2;
715                else
716                    --n;
717            }
718        }
719
720        if (withRet)
721            if (c == 'J' || c == 'D')
722                n += 2;
723            else if (c != 'V')
724                ++n;
725
726        return n;
727    }
728
729    /**
730     * Returns a human-readable representation of the
731     * given descriptor.  For example, <code>Ljava/lang/Object;</code>
732     * is converted into <code>java.lang.Object</code>.
733     * <code>(I[I)V</code> is converted into <code>(int, int[])</code>
734     * (the return type is ignored).
735     */
736    public static String toString(String desc) {
737        return PrettyPrinter.toString(desc);
738    }
739
740    static class PrettyPrinter {
741        static String toString(String desc) {
742            StringBuffer sbuf = new StringBuffer();
743            if (desc.charAt(0) == '(') {
744                int pos = 1;
745                sbuf.append('(');
746                while (desc.charAt(pos) != ')') {
747                    if (pos > 1)
748                        sbuf.append(',');
749
750                    pos = readType(sbuf, pos, desc);
751                }
752
753                sbuf.append(')');
754            }
755            else
756                readType(sbuf, 0, desc);
757
758            return sbuf.toString();
759        }
760
761        static int readType(StringBuffer sbuf, int pos, String desc) {
762            char c = desc.charAt(pos);
763            int arrayDim = 0;
764            while (c == '[') {
765                arrayDim++;
766                c = desc.charAt(++pos);
767            }
768
769            if (c == 'L')
770                while (true) {
771                    c = desc.charAt(++pos);
772                    if (c == ';')
773                        break;
774
775                    if (c == '/')
776                        c = '.';
777
778                    sbuf.append(c);
779                }
780            else {
781                CtClass t = toPrimitiveClass(c);
782                sbuf.append(t.getName());
783            }
784
785            while (arrayDim-- > 0)
786                sbuf.append("[]");
787
788            return pos + 1;
789        }
790    }
791
792    /**
793     * An Iterator over a descriptor.
794     */
795    public static class Iterator {
796        private String desc;
797        private int index, curPos;
798        private boolean param;
799
800        /**
801         * Constructs an iterator.
802         *
803         * @param s         descriptor.
804         */
805        public Iterator(String s) {
806            desc = s;
807            index = curPos = 0;
808            param = false;
809        }
810
811        /**
812         * Returns true if the iteration has more elements.
813         */
814        public boolean hasNext() {
815            return index < desc.length();
816        }
817
818        /**
819         * Returns true if the current element is a parameter type.
820         */
821        public boolean isParameter() { return param; }
822
823        /**
824         * Returns the first character of the current element.
825         */
826        public char currentChar() { return desc.charAt(curPos); }
827
828        /**
829         * Returns true if the current element is double or long type.
830         */
831        public boolean is2byte() {
832            char c = currentChar();
833            return c == 'D' || c == 'J';
834        }
835
836        /**
837         * Returns the position of the next type character.
838         * That type character becomes a new current element.
839         */
840        public int next() {
841            int nextPos = index;
842            char c = desc.charAt(nextPos);
843            if (c == '(') {
844                ++index;
845                c = desc.charAt(++nextPos);
846                param = true;
847            }
848
849            if (c == ')') {
850                ++index;
851                c = desc.charAt(++nextPos);
852                param = false;
853            }
854
855            while (c == '[')
856                c = desc.charAt(++nextPos);
857
858            if (c == 'L') {
859                nextPos = desc.indexOf(';', nextPos) + 1;
860                if (nextPos <= 0)
861                    throw new IndexOutOfBoundsException("bad descriptor");
862            }
863            else
864                ++nextPos;
865
866            curPos = index;
867            index = nextPos;
868            return curPos;
869        }
870    }
871}
872