001: /* ===========================================================================
002: * $RCSfile: RgsEnum.java,v $
003: * ===========================================================================
004: *
005: * RetroGuard -- an obfuscation package for Java classfiles.
006: *
007: * Copyright (c) 1998-2006 Mark Welsh (markw@retrologic.com)
008: *
009: * This program can be redistributed and/or modified under the terms of the
010: * Version 2 of the GNU General Public License as published by the Free
011: * Software Foundation.
012: *
013: * This program is distributed in the hope that it will be useful,
014: * but WITHOUT ANY WARRANTY; without even the implied warranty of
015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
016: * GNU General Public License for more details.
017: *
018: */
019:
020: package COM.rl.obf;
021:
022: import java.io.*;
023: import java.util.*;
024: import COM.rl.util.*;
025: import COM.rl.obf.classfile.*;
026:
027: /**
028: * Parser for RGS script files which provides an enumeration of option entries.
029: *
030: * @author Mark Welsh
031: */
032: public class RgsEnum {
033: // Constants -------------------------------------------------------------
034: public static final String DIRECTIVE_OPTION = ".option";
035: public static final String DIRECTIVE_ATTR = ".attribute";
036: public static final String DIRECTIVE_CLASS = ".class";
037: public static final String DIRECTIVE_NOTRIM_CLASS = "^class";
038: public static final String DIRECTIVE_NOT_CLASS = "!class";
039: public static final String DIRECTIVE_FIELD = ".field";
040: public static final String DIRECTIVE_NOTRIM_FIELD = "^field";
041: public static final String DIRECTIVE_NOT_FIELD = "!field";
042: public static final String DIRECTIVE_METHOD = ".method";
043: public static final String DIRECTIVE_NOTRIM_METHOD = "^method";
044: public static final String DIRECTIVE_NOT_METHOD = "!method";
045: public static final String DIRECTIVE_PACKAGE_MAP = ".package_map";
046: public static final String DIRECTIVE_REPACKAGE_MAP = ".repackage_map";
047: public static final String DIRECTIVE_CLASS_MAP = ".class_map";
048: public static final String DIRECTIVE_FIELD_MAP = ".field_map";
049: public static final String DIRECTIVE_METHOD_MAP = ".method_map";
050: public static final String DIRECTIVE_NOWARN = ".nowarn";
051:
052: public static final String OPTION_PUBLIC = "public";
053: public static final String OPTION_PROTECTED = "protected";
054: public static final String OPTION_PUB_PROT_ONLY = "pub_prot_only";
055: public static final String OPTION_METHOD = "method";
056: public static final String OPTION_FIELD = "field";
057: public static final String OPTION_AND_CLASS = "and_class";
058: public static final String OPTION_EXTENDS = "extends";
059:
060: public static final String ACCESS_PUBLIC = "public";
061: public static final String ACCESS_PRIVATE = "private";
062: public static final String ACCESS_PROTECTED = "protected";
063: public static final String ACCESS_STATIC = "static";
064: public static final String ACCESS_FINAL = "final";
065: public static final String ACCESS_SYNCHRONIZED = "synchronized";
066: public static final String ACCESS_BRIDGE = "bridge";
067: public static final String ACCESS_VOLATILE = "volatile";
068: public static final String ACCESS_VARARGS = "varargs";
069: public static final String ACCESS_TRANSIENT = "transient";
070: public static final String ACCESS_NATIVE = "native";
071: public static final String ACCESS_INTERFACE = "interface";
072: public static final String ACCESS_ABSTRACT = "abstract";
073: public static final String ACCESS_STRICT = "strict";
074: public static final String ACCESS_SYNTHETIC = "synthetic";
075: public static final String ACCESS_ANNOTATION = "annotation";
076: public static final String ACCESS_ENUM = "enum";
077:
078: public static final String[] CLASS_ACCESS = { ACCESS_PUBLIC,
079: ACCESS_FINAL, ACCESS_INTERFACE, ACCESS_ABSTRACT,
080: ACCESS_ANNOTATION, ACCESS_ENUM };
081:
082: public static final String[] METHOD_ACCESS = { ACCESS_PUBLIC,
083: ACCESS_PRIVATE, ACCESS_PROTECTED, ACCESS_STATIC,
084: ACCESS_FINAL, ACCESS_SYNCHRONIZED, ACCESS_BRIDGE,
085: ACCESS_VARARGS, ACCESS_NATIVE, ACCESS_ABSTRACT,
086: ACCESS_STRICT };
087:
088: public static final String[] FIELD_ACCESS = { ACCESS_PUBLIC,
089: ACCESS_PRIVATE, ACCESS_PROTECTED, ACCESS_STATIC,
090: ACCESS_FINAL, ACCESS_VOLATILE, ACCESS_TRANSIENT,
091: ACCESS_ENUM };
092:
093: private static final String DEFAULT_RGS = ".option Applet\n"
094: + ".option Application\n" + ".option Serializable\n"
095: + ".option RMI\n" + ".option RuntimeAnnotations\n"
096: + ".option MapClassString\n" + ".option Trim\n"
097: + ".option Repackage\n" + ".option Generic\n";
098:
099: // Fields ----------------------------------------------------------------
100: private StreamTokenizer tk;
101: private RgsEntry next;
102: private Exception nextException;
103:
104: // Class Methods ---------------------------------------------------------
105: /** Return the internal default script file. */
106: public static String getDefaultRgs() {
107: return DEFAULT_RGS;
108: }
109:
110: /** Translate a string access modifier from the script to bit flag */
111: private static int toAccessFlag(String accessString)
112: throws Exception {
113: if (ACCESS_PUBLIC.equals(accessString))
114: return ClassConstants.ACC_PUBLIC;
115: else if (ACCESS_PRIVATE.equals(accessString))
116: return ClassConstants.ACC_PRIVATE;
117: else if (ACCESS_PROTECTED.equals(accessString))
118: return ClassConstants.ACC_PROTECTED;
119: else if (ACCESS_STATIC.equals(accessString))
120: return ClassConstants.ACC_STATIC;
121: else if (ACCESS_FINAL.equals(accessString))
122: return ClassConstants.ACC_FINAL;
123: else if (ACCESS_SYNCHRONIZED.equals(accessString))
124: return ClassConstants.ACC_SYNCHRONIZED;
125: else if (ACCESS_BRIDGE.equals(accessString))
126: return ClassConstants.ACC_BRIDGE;
127: else if (ACCESS_VOLATILE.equals(accessString))
128: return ClassConstants.ACC_VOLATILE;
129: else if (ACCESS_VARARGS.equals(accessString))
130: return ClassConstants.ACC_VARARGS;
131: else if (ACCESS_TRANSIENT.equals(accessString))
132: return ClassConstants.ACC_TRANSIENT;
133: else if (ACCESS_NATIVE.equals(accessString))
134: return ClassConstants.ACC_NATIVE;
135: else if (ACCESS_INTERFACE.equals(accessString))
136: return ClassConstants.ACC_INTERFACE;
137: else if (ACCESS_ABSTRACT.equals(accessString))
138: return ClassConstants.ACC_ABSTRACT;
139: else if (ACCESS_STRICT.equals(accessString))
140: return ClassConstants.ACC_STRICT;
141: else if (ACCESS_SYNTHETIC.equals(accessString))
142: return ClassConstants.ACC_SYNTHETIC;
143: else if (ACCESS_ANNOTATION.equals(accessString))
144: return ClassConstants.ACC_ANNOTATION;
145: else if (ACCESS_ENUM.equals(accessString))
146: return ClassConstants.ACC_ENUM;
147: else
148: throw new Exception();
149: }
150:
151: /** Decode a list of access flags into a bit mask for class, method, or
152: field access flag u2's. */
153: private static int decodeAccessFlags(int entryType,
154: String accessString) throws Exception {
155: int accessMask = 0;
156: int accessSetting = 0;
157: while (accessString != null && accessString.length() >= 5) // ';enum'
158: {
159: boolean invert = false;
160: if (accessString.charAt(0) != ';')
161: throw new Exception();
162: int startIndex = 1;
163: if (accessString.charAt(1) == '!') {
164: invert = true;
165: startIndex = 2;
166: }
167: int endIndex = accessString.indexOf(';', startIndex);
168: String flagString = (endIndex == -1 ? accessString
169: .substring(startIndex) : accessString.substring(
170: startIndex, endIndex));
171: if (endIndex == -1) {
172: accessString = null;
173: } else {
174: accessString = accessString.substring(endIndex);
175: }
176: if (((entryType == RgsEntry.TYPE_CLASS || entryType == RgsEntry.TYPE_NOT_CLASS) && !Tools
177: .isInArray(flagString, CLASS_ACCESS))
178: || ((entryType == RgsEntry.TYPE_METHOD || entryType == RgsEntry.TYPE_NOT_METHOD) && !Tools
179: .isInArray(flagString, METHOD_ACCESS))
180: || ((entryType == RgsEntry.TYPE_FIELD || entryType == RgsEntry.TYPE_NOT_FIELD) && !Tools
181: .isInArray(flagString, FIELD_ACCESS))) {
182: throw new Exception();
183: }
184: int flag = toAccessFlag(flagString);
185: accessMask |= flag;
186: if (!invert)
187: accessSetting |= flag;
188: }
189: return (accessSetting << 16) + accessMask;
190: }
191:
192: /** Does this RGS file have an '.option Trim' line? (expensive) */
193: public static boolean hasOptionTrim(File rgsFile) throws Exception {
194: InputStream rgsInputStream = null;
195: try {
196: rgsInputStream = rgsFile.exists() ? new FileInputStream(
197: rgsFile) : null;
198: RgsEnum rgsEnum = new RgsEnum(rgsInputStream);
199: while (rgsEnum.hasMoreEntries()) {
200: RgsEntry entry = rgsEnum.nextEntry();
201: if (entry.type == RgsEntry.TYPE_OPTION
202: && ClassConstants.OPTION_Trim
203: .equals(entry.name)) {
204: return true;
205: }
206: }
207: return false;
208: } finally {
209: if (rgsInputStream != null) {
210: rgsInputStream.close();
211: }
212: }
213: }
214:
215: // Instance Methods ------------------------------------------------------
216: /** Ctor. */
217: public RgsEnum(InputStream rgs) {
218: tk = new StreamTokenizer(new BufferedReader(
219: rgs != null ? (Reader) new InputStreamReader(rgs)
220: : (Reader) new StringReader(DEFAULT_RGS)));
221: tk.resetSyntax();
222: tk.whitespaceChars(0x00, 0x20);
223: tk.wordChars('^', '^');
224: tk.wordChars('!', '!');
225: tk.wordChars('*', '*');
226: tk.wordChars('.', '.');
227: tk.wordChars(';', ';');
228: tk.wordChars('_', '_');
229: tk.wordChars('[', '[');
230: tk.wordChars('(', ')');
231: tk.wordChars('$', '$');
232: tk.wordChars('/', '9');
233: tk.wordChars('A', 'Z');
234: tk.wordChars('a', 'z');
235: tk.commentChar('#');
236: tk.eolIsSignificant(true);
237: readNext();
238: }
239:
240: /** Are there more script entries? */
241: public boolean hasMoreEntries() throws Exception {
242: if (nextException != null) {
243: throw nextException;
244: }
245: return next != null;
246: }
247:
248: /** Return next script entry. */
249: public RgsEntry nextEntry() throws Exception {
250: RgsEntry this One = next;
251: Exception this Exception = nextException;
252: readNext();
253: if (this Exception != null) {
254: throw this Exception;
255: }
256: return this One;
257: }
258:
259: // Read the next entry, returning true if one is available.
260: private void readNext() {
261: // Reset the 'next error' state
262: nextException = null;
263: RgsEntry entry = null;
264: int ttype;
265: try {
266: int directive = -1;
267: int accessMask = 0;
268: int accessSetting = 0;
269: String name = null;
270: String descriptor = null;
271: boolean hasExtends = false;
272: String extendsName = null;
273: while ((ttype = tk.nextToken()) != StreamTokenizer.TT_EOF) {
274: if (ttype == StreamTokenizer.TT_WORD) {
275: if (directive == -1) {
276: if (tk.sval.equals(DIRECTIVE_OPTION)) {
277: directive = RgsEntry.TYPE_OPTION;
278: } else if (tk.sval.equals(DIRECTIVE_ATTR)) {
279: directive = RgsEntry.TYPE_ATTR;
280: } else if (tk.sval.equals(DIRECTIVE_NOWARN)) {
281: directive = RgsEntry.TYPE_NOWARN;
282: } else if (tk.sval
283: .equals(DIRECTIVE_PACKAGE_MAP)) {
284: directive = RgsEntry.TYPE_PACKAGE_MAP;
285: } else if (tk.sval
286: .equals(DIRECTIVE_REPACKAGE_MAP)) {
287: directive = RgsEntry.TYPE_REPACKAGE_MAP;
288: } else if (tk.sval.equals(DIRECTIVE_CLASS_MAP)) {
289: directive = RgsEntry.TYPE_CLASS_MAP;
290: } else if (tk.sval.equals(DIRECTIVE_METHOD_MAP)) {
291: directive = RgsEntry.TYPE_METHOD_MAP;
292: } else if (tk.sval.equals(DIRECTIVE_FIELD_MAP)) {
293: directive = RgsEntry.TYPE_FIELD_MAP;
294: } else if (tk.sval.startsWith(DIRECTIVE_CLASS)) {
295: directive = RgsEntry.TYPE_CLASS;
296: accessMask = decodeAccessFlags(directive,
297: tk.sval.substring(DIRECTIVE_CLASS
298: .length()));
299: accessSetting = accessMask >> 16;
300: accessMask &= 0xffff;
301: } else if (tk.sval
302: .startsWith(DIRECTIVE_NOTRIM_CLASS)) {
303: directive = RgsEntry.TYPE_NOTRIM_CLASS;
304: accessMask = decodeAccessFlags(
305: directive,
306: tk.sval
307: .substring(DIRECTIVE_NOTRIM_CLASS
308: .length()));
309: accessSetting = accessMask >> 16;
310: accessMask &= 0xffff;
311: } else if (tk.sval
312: .startsWith(DIRECTIVE_NOT_CLASS)) {
313: directive = RgsEntry.TYPE_NOT_CLASS;
314: accessMask = decodeAccessFlags(
315: directive,
316: tk.sval
317: .substring(DIRECTIVE_NOT_CLASS
318: .length()));
319: accessSetting = accessMask >> 16;
320: accessMask &= 0xffff;
321: } else if (tk.sval.startsWith(DIRECTIVE_METHOD)) {
322: directive = RgsEntry.TYPE_METHOD;
323: accessMask = decodeAccessFlags(directive,
324: tk.sval.substring(DIRECTIVE_METHOD
325: .length()));
326: accessSetting = accessMask >> 16;
327: accessMask &= 0xffff;
328: } else if (tk.sval
329: .startsWith(DIRECTIVE_NOTRIM_METHOD)) {
330: directive = RgsEntry.TYPE_NOTRIM_METHOD;
331: accessMask = decodeAccessFlags(
332: directive,
333: tk.sval
334: .substring(DIRECTIVE_NOTRIM_METHOD
335: .length()));
336: accessSetting = accessMask >> 16;
337: accessMask &= 0xffff;
338: } else if (tk.sval
339: .startsWith(DIRECTIVE_NOT_METHOD)) {
340: directive = RgsEntry.TYPE_NOT_METHOD;
341: accessMask = decodeAccessFlags(
342: directive,
343: tk.sval
344: .substring(DIRECTIVE_NOT_METHOD
345: .length()));
346: accessSetting = accessMask >> 16;
347: accessMask &= 0xffff;
348: } else if (tk.sval.startsWith(DIRECTIVE_FIELD)) {
349: directive = RgsEntry.TYPE_FIELD;
350: accessMask = decodeAccessFlags(directive,
351: tk.sval.substring(DIRECTIVE_FIELD
352: .length()));
353: accessSetting = accessMask >> 16;
354: accessMask &= 0xffff;
355: } else if (tk.sval
356: .startsWith(DIRECTIVE_NOTRIM_FIELD)) {
357: directive = RgsEntry.TYPE_NOTRIM_FIELD;
358: accessMask = decodeAccessFlags(
359: directive,
360: tk.sval
361: .substring(DIRECTIVE_NOTRIM_FIELD
362: .length()));
363: accessSetting = accessMask >> 16;
364: accessMask &= 0xffff;
365: } else if (tk.sval
366: .startsWith(DIRECTIVE_NOT_FIELD)) {
367: directive = RgsEntry.TYPE_NOT_FIELD;
368: accessMask = decodeAccessFlags(
369: directive,
370: tk.sval
371: .substring(DIRECTIVE_NOT_FIELD
372: .length()));
373: accessSetting = accessMask >> 16;
374: accessMask &= 0xffff;
375: } else {
376: throw new Exception();
377: }
378: } else if (entry == null) {
379: switch (directive) {
380: case RgsEntry.TYPE_OPTION:
381: if (!Tools.isInArray(tk.sval,
382: ClassConstants.KNOWN_OPTIONS)) {
383: throw new Exception();
384: }
385: entry = new RgsEntry(directive, tk.sval);
386: break;
387: case RgsEntry.TYPE_ATTR:
388: if (!Tools.isInArray(tk.sval,
389: ClassConstants.KNOWN_ATTRS)) {
390: throw new Exception();
391: }
392: entry = new RgsEntry(directive, tk.sval);
393: break;
394: case RgsEntry.TYPE_NOWARN:
395: //checkClassSpec(tk.sval);
396: entry = new RgsEntry(directive, tk.sval);
397: break;
398: case RgsEntry.TYPE_CLASS:
399: case RgsEntry.TYPE_NOTRIM_CLASS:
400: case RgsEntry.TYPE_NOT_CLASS:
401: //checkClassWCSpec(tk.sval);
402: entry = new RgsEntry(directive, tk.sval);
403: entry.accessMask = accessMask;
404: entry.accessSetting = accessSetting;
405: break;
406: case RgsEntry.TYPE_METHOD:
407: case RgsEntry.TYPE_NOTRIM_METHOD:
408: case RgsEntry.TYPE_NOT_METHOD:
409: if (name == null) {
410: name = tk.sval;
411: //checkMethodOrFieldSpec(name);
412: } else if (descriptor == null) {
413: descriptor = tk.sval;
414: //checkMethodDescriptor(descriptor);
415: entry = new RgsEntry(directive, name,
416: descriptor);
417: entry.accessMask = accessMask;
418: entry.accessSetting = accessSetting;
419: }
420: break;
421: case RgsEntry.TYPE_FIELD:
422: case RgsEntry.TYPE_NOTRIM_FIELD:
423: case RgsEntry.TYPE_NOT_FIELD:
424: if (name == null) {
425: name = tk.sval;
426: //checkMethodOrFieldSpec(name);
427: } else {
428: descriptor = tk.sval;
429: //checkJavaType(descriptor);
430: entry = new RgsEntry(directive, name,
431: descriptor);
432: entry.accessMask = accessMask;
433: entry.accessSetting = accessSetting;
434: }
435: break;
436: case RgsEntry.TYPE_PACKAGE_MAP:
437: case RgsEntry.TYPE_REPACKAGE_MAP:
438: if (name == null) {
439: name = tk.sval;
440: checkClassSpec(name);
441: } else {
442: String obfName = tk.sval;
443: checkJavaIdentifier(obfName);
444: entry = new RgsEntry(directive, name);
445: entry.obfName = obfName;
446: }
447: break;
448: case RgsEntry.TYPE_CLASS_MAP:
449: if (name == null) {
450: name = tk.sval;
451: checkClassSpec(name);
452: } else {
453: String obfName = tk.sval;
454: checkJavaInnerIdentifier(obfName);
455: entry = new RgsEntry(directive, name);
456: entry.obfName = obfName;
457: }
458: break;
459: case RgsEntry.TYPE_METHOD_MAP:
460: if (name == null) {
461: name = tk.sval;
462: checkMethodOrFieldSpec(name);
463: } else if (descriptor == null) {
464: descriptor = tk.sval;
465: checkMethodDescriptor(descriptor);
466: } else {
467: String obfName = tk.sval;
468: checkJavaIdentifier(obfName);
469: entry = new RgsEntry(directive, name,
470: descriptor);
471: entry.obfName = obfName;
472: }
473: break;
474: case RgsEntry.TYPE_FIELD_MAP:
475: if (name == null) {
476: name = tk.sval;
477: checkMethodOrFieldSpec(name);
478: } else {
479: String obfName = tk.sval;
480: checkJavaIdentifier(obfName);
481: entry = new RgsEntry(directive, name);
482: entry.obfName = obfName;
483: }
484: break;
485: }
486: } else if (directive == RgsEntry.TYPE_CLASS
487: || directive == RgsEntry.TYPE_NOTRIM_CLASS
488: || directive == RgsEntry.TYPE_NOT_CLASS) {
489: if (tk.sval.equals(OPTION_PUBLIC)) {
490: if (entry.retainToPublic
491: || entry.retainToProtected
492: || entry.retainPubProtOnly) {
493: throw new Exception();
494: }
495: entry.retainToPublic = true;
496: } else if (tk.sval.equals(OPTION_PUB_PROT_ONLY)) {
497: if (entry.retainToPublic
498: || entry.retainToProtected
499: || entry.retainPubProtOnly) {
500: throw new Exception();
501: }
502: entry.retainPubProtOnly = true;
503: } else if (tk.sval.equals(OPTION_PROTECTED)) {
504: if (entry.retainToPublic
505: || entry.retainToProtected
506: || entry.retainPubProtOnly) {
507: throw new Exception();
508: }
509: entry.retainToProtected = true;
510: } else if (tk.sval.equals(OPTION_FIELD)) {
511: if ((!entry.retainToPublic
512: && !entry.retainPubProtOnly && !entry.retainToProtected)
513: || entry.retainMethodsOnly) {
514: throw new Exception();
515: }
516: entry.retainFieldsOnly = true;
517: } else if (tk.sval.equals(OPTION_METHOD)) {
518: if ((!entry.retainToPublic
519: && !entry.retainPubProtOnly && !entry.retainToProtected)
520: || entry.retainFieldsOnly) {
521: throw new Exception();
522: }
523: entry.retainMethodsOnly = true;
524: } else if (tk.sval.equals(OPTION_EXTENDS)) {
525: hasExtends = true;
526: } else if (hasExtends) {
527: extendsName = tk.sval;
528: checkClassSpec(extendsName);
529: entry.extendsName = extendsName;
530: } else {
531: throw new Exception();
532: }
533: } else if (directive == RgsEntry.TYPE_METHOD
534: || directive == RgsEntry.TYPE_NOTRIM_METHOD
535: || directive == RgsEntry.TYPE_NOT_METHOD
536: || directive == RgsEntry.TYPE_FIELD
537: || directive == RgsEntry.TYPE_NOTRIM_FIELD
538: || directive == RgsEntry.TYPE_NOT_FIELD) {
539: if (tk.sval.equals(OPTION_AND_CLASS)) {
540: entry.retainAndClass = true;
541: } else if (tk.sval.equals(OPTION_EXTENDS)) {
542: hasExtends = true;
543: } else if (hasExtends) {
544: extendsName = tk.sval;
545: checkClassSpec(extendsName);
546: entry.extendsName = extendsName;
547: } else {
548: throw new Exception();
549: }
550: } else {
551: throw new Exception();
552: }
553: } else if (ttype == StreamTokenizer.TT_EOL) {
554: if (entry != null) {
555: break;
556: }
557: } else {
558: throw new Exception();
559: }
560: }
561: next = entry;
562: } catch (Exception e) {
563: // Discard to end of erroneous line
564: try {
565: while ((ttype = tk.nextToken()) != StreamTokenizer.TT_EOF) {
566: if (ttype == StreamTokenizer.TT_EOL) {
567: break;
568: }
569: }
570: } catch (IOException ee) {
571: // Take no action if StreamTokenizer fails here
572: }
573:
574: // Save exception for throw from nextEntry()
575: nextException = new Exception("Parser error at line "
576: + Integer.toString(tk.lineno())
577: + " of script file.");
578: }
579: }
580:
581: // Throw if invalid
582: private void checkMethodDescriptor(String s) throws Exception {
583: if (s.length() == 0 || s.charAt(0) != '(') {
584: throw new Exception();
585: }
586: s = s.substring(1);
587:
588: // Check each type
589: while (s.length() > 0 && s.charAt(0) != ')') {
590: s = checkFirstJavaType(s);
591: }
592: checkJavaType(s.substring(1));
593: }
594:
595: // Throw if first type is invalid, else return all but first type in String
596: private String checkFirstJavaType(String s) throws Exception {
597: // Pull off the array specifiers
598: while (s.charAt(0) == '[') {
599: s = s.substring(1);
600: if (s.length() == 0) {
601: throw new Exception();
602: }
603: }
604:
605: // Check a type
606: int pos = 0;
607: switch (s.charAt(0)) {
608: case 'B':
609: case 'C':
610: case 'D':
611: case 'F':
612: case 'I':
613: case 'J':
614: case 'S':
615: case 'V':
616: case 'Z':
617: break;
618:
619: case 'L':
620: pos = s.indexOf(';');
621: if (pos == -1) {
622: throw new Exception();
623: }
624: // Check the class type
625: checkClassSpec(s.substring(0, pos));
626: break;
627:
628: default:
629: throw new Exception();
630: }
631: return s.substring(pos + 1);
632: }
633:
634: // Throw if type is invalid
635: private void checkJavaType(String s) throws Exception {
636: if (!checkFirstJavaType(s).equals("")) {
637: throw new Exception();
638: }
639: }
640:
641: // Throw if invalid
642: private void checkMethodOrFieldSpec(String s) throws Exception {
643: if (s.length() == 0) {
644: throw new Exception();
645: }
646:
647: // Check the method or field name
648: int pos = s.lastIndexOf('/');
649: if (pos == -1) {
650: throw new Exception();
651: }
652: checkJavaIdentifier(s.substring(pos + 1));
653: checkClassSpec(s.substring(0, pos));
654: }
655:
656: // Throw if invalid
657: private void checkClassSpec(String s) throws Exception {
658: if (s.length() == 0) {
659: throw new Exception();
660: }
661:
662: int pos = -1;
663: while ((pos = s.lastIndexOf('$')) != -1) {
664: checkJavaInnerIdentifier(s.substring(pos + 1));
665: s = s.substring(0, pos);
666: }
667: while ((pos = s.lastIndexOf('/')) != -1) {
668: checkJavaIdentifier(s.substring(pos + 1));
669: s = s.substring(0, pos);
670: }
671: checkJavaIdentifier(s);
672: }
673:
674: // Throw if invalid
675: private void checkClassWCSpec(String s) throws Exception {
676: // Check for wildcard package spec first
677: if (s.length() == 0) {
678: throw new Exception();
679: }
680: if (s.charAt(s.length() - 1) == '*') {
681: if (!s.equals("*")) {
682: if (s.length() < 3 || s.charAt(s.length() - 2) != '/') {
683: throw new Exception();
684: } else {
685: s = s.substring(0, s.length() - 2);
686: int pos = -1;
687: while ((pos = s.lastIndexOf('/')) != -1) {
688: checkJavaIdentifier(s.substring(pos + 1));
689: s = s.substring(0, pos);
690: }
691: checkJavaIdentifier(s);
692: }
693: }
694: } else {
695: // Check for regular class spec
696: checkClassSpec(s);
697: }
698: }
699:
700: // Throw if invalid
701: private void checkJavaIdentifier(String s) throws Exception {
702: if (s.length() == 0
703: || !Character.isJavaIdentifierStart(s.charAt(0))) {
704: throw new Exception();
705: }
706: for (int i = 1; i < s.length(); i++) {
707: if (!Character.isJavaIdentifierPart(s.charAt(i))) {
708: throw new Exception();
709: }
710: }
711: }
712:
713: // Throw if invalid (allows for anon. inner class names like '4')
714: private void checkJavaInnerIdentifier(String s) throws Exception {
715: if (s.length() == 0) {
716: throw new Exception();
717: }
718: for (int i = 0; i < s.length(); i++) {
719: if (!Character.isJavaIdentifierPart(s.charAt(i))) {
720: throw new Exception();
721: }
722: }
723: }
724: }
|