• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
16 package javassist.bytecode;
17 
18 import javassist.ClassPool;
19 import javassist.CtClass;
20 import javassist.CtPrimitiveType;
21 import javassist.NotFoundException;
22 import 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  */
29 public 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      */
toJvmName(String classname)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      */
toJavaName(String classname)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      */
toJvmName(CtClass clazz)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      */
toClassName(String descriptor)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      */
of(String classname)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      */
rename(String desc, String oldname, String newname)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      */
rename(String desc, Map map)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      */
of(CtClass type)244     public static String of(CtClass type) {
245         StringBuffer sbuf = new StringBuffer();
246         toDescriptor(sbuf, type);
247         return sbuf.toString();
248     }
249 
toDescriptor(StringBuffer desc, CtClass type)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      */
ofConstructor(CtClass[] paramTypes)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      */
ofMethod(CtClass returnType, CtClass[] paramTypes)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      */
ofParameters(CtClass[] paramTypes)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      */
appendParameter(String classname, String desc)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      */
insertParameter(String classname, String desc)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      */
appendParameter(CtClass type, String descriptor)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      */
insertParameter(CtClass type, String descriptor)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      */
changeReturnType(String classname, String desc)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      */
getParameterTypes(String desc, ClassPool cp)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      */
eqParamTypes(String desc1, String desc2)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      */
getParamDescriptor(String decl)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      */
getReturnType(String desc, ClassPool cp)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      */
numOfParameters(String desc)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      */
toCtClass(String desc, ClassPool cp)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 
toCtClass(ClassPool cp, String desc, int i, CtClass[] args, int n)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 
toPrimitiveClass(char c)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      */
arrayDimension(String desc)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      */
toArrayComponent(String desc, int dim)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      */
dataSize(String desc)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      */
paramSize(String desc)683     public static int paramSize(String desc) {
684         return -dataSize(desc, false);
685     }
686 
dataSize(String desc, boolean withRet)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      */
toString(String desc)736     public static String toString(String desc) {
737         return PrettyPrinter.toString(desc);
738     }
739 
740     static class PrettyPrinter {
toString(String desc)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 
readType(StringBuffer sbuf, int pos, String desc)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          */
Iterator(String s)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          */
hasNext()814         public boolean hasNext() {
815             return index < desc.length();
816         }
817 
818         /**
819          * Returns true if the current element is a parameter type.
820          */
isParameter()821         public boolean isParameter() { return param; }
822 
823         /**
824          * Returns the first character of the current element.
825          */
currentChar()826         public char currentChar() { return desc.charAt(curPos); }
827 
828         /**
829          * Returns true if the current element is double or long type.
830          */
is2byte()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          */
next()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