0001: /*
0002: * ProGuard -- shrinking, optimization, obfuscation, and preverification
0003: * of Java bytecode.
0004: *
0005: * Copyright (c) 2002-2007 Eric Lafortune (eric@graphics.cornell.edu)
0006: *
0007: * This program is free software; you can redistribute it and/or modify it
0008: * under the terms of the GNU General Public License as published by the Free
0009: * Software Foundation; either version 2 of the License, or (at your option)
0010: * any later version.
0011: *
0012: * This program is distributed in the hope that it will be useful, but WITHOUT
0013: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0014: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
0015: * more details.
0016: *
0017: * You should have received a copy of the GNU General Public License along
0018: * with this program; if not, write to the Free Software Foundation, Inc.,
0019: * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
0020: */
0021: package proguard;
0022:
0023: import proguard.classfile.ClassConstants;
0024: import proguard.classfile.util.ClassUtil;
0025: import proguard.util.ListUtil;
0026:
0027: import java.io.*;
0028: import java.net.URL;
0029: import java.util.*;
0030:
0031: /**
0032: * This class parses ProGuard configurations. Configurations can be read from an
0033: * array of arguments or from a configuration file or URL.
0034: *
0035: * @author Eric Lafortune
0036: */
0037: public class ConfigurationParser {
0038: private WordReader reader;
0039: private String nextWord;
0040: private String lastComments;
0041:
0042: /**
0043: * Creates a new ConfigurationParser for the given String arguments.
0044: */
0045: public ConfigurationParser(String[] args) throws IOException {
0046: this (args, null);
0047: }
0048:
0049: /**
0050: * Creates a new ConfigurationParser for the given String arguments,
0051: * with the given base directory.
0052: */
0053: public ConfigurationParser(String[] args, File baseDir)
0054: throws IOException {
0055: reader = new ArgumentWordReader(args, baseDir);
0056:
0057: readNextWord();
0058: }
0059:
0060: /**
0061: * Creates a new ConfigurationParser for the given file.
0062: */
0063: public ConfigurationParser(File file) throws IOException {
0064: reader = new FileWordReader(file);
0065:
0066: readNextWord();
0067: }
0068:
0069: /**
0070: * Creates a new ConfigurationParser for the given URL.
0071: */
0072: public ConfigurationParser(URL url) throws IOException {
0073: reader = new FileWordReader(url);
0074:
0075: readNextWord();
0076: }
0077:
0078: /**
0079: * Parses and returns the configuration.
0080: * @param configuration the configuration that is updated as a side-effect.
0081: * @throws ParseException if the any of the configuration settings contains
0082: * a syntax error.
0083: * @throws IOException if an IO error occurs while reading a configuration.
0084: */
0085: public void parse(Configuration configuration)
0086: throws ParseException, IOException {
0087: while (nextWord != null) {
0088: lastComments = reader.lastComments();
0089:
0090: // First include directives.
0091: if (ConfigurationConstants.AT_DIRECTIVE
0092: .startsWith(nextWord)
0093: || ConfigurationConstants.INCLUDE_DIRECTIVE
0094: .startsWith(nextWord))
0095: configuration.lastModified = parseIncludeArgument(configuration.lastModified);
0096: else if (ConfigurationConstants.BASE_DIRECTORY_DIRECTIVE
0097: .startsWith(nextWord))
0098: parseBaseDirectoryArgument();
0099:
0100: // Then configuration options with or without arguments.
0101: else if (ConfigurationConstants.INJARS_OPTION
0102: .startsWith(nextWord))
0103: configuration.programJars = parseClassPathArgument(
0104: configuration.programJars, false);
0105: else if (ConfigurationConstants.OUTJARS_OPTION
0106: .startsWith(nextWord))
0107: configuration.programJars = parseClassPathArgument(
0108: configuration.programJars, true);
0109: else if (ConfigurationConstants.LIBRARYJARS_OPTION
0110: .startsWith(nextWord))
0111: configuration.libraryJars = parseClassPathArgument(
0112: configuration.libraryJars, false);
0113: else if (ConfigurationConstants.RESOURCEJARS_OPTION
0114: .startsWith(nextWord))
0115: throw new ParseException(
0116: "The '-resourcejars' option is no longer supported. Please use the '-injars' option for all input");
0117: else if (ConfigurationConstants.DONT_SKIP_NON_PUBLIC_LIBRARY_CLASSES_OPTION
0118: .startsWith(nextWord))
0119: configuration.skipNonPublicLibraryClasses = parseNoArgument(false);
0120: else if (ConfigurationConstants.DONT_SKIP_NON_PUBLIC_LIBRARY_CLASS_MEMBERS_OPTION
0121: .startsWith(nextWord))
0122: configuration.skipNonPublicLibraryClassMembers = parseNoArgument(false);
0123: else if (ConfigurationConstants.TARGET_OPTION
0124: .startsWith(nextWord))
0125: configuration.targetClassVersion = parseClassVersion();
0126: else if (ConfigurationConstants.FORCE_PROCESSING_OPTION
0127: .startsWith(nextWord))
0128: configuration.lastModified = parseNoArgument(Long.MAX_VALUE);
0129:
0130: else if (ConfigurationConstants.KEEP_OPTION
0131: .startsWith(nextWord))
0132: configuration.keep = parseKeepSpecificationArguments(
0133: configuration.keep, true, false, false);
0134: else if (ConfigurationConstants.KEEP_CLASS_MEMBERS_OPTION
0135: .startsWith(nextWord))
0136: configuration.keep = parseKeepSpecificationArguments(
0137: configuration.keep, false, false, false);
0138: else if (ConfigurationConstants.KEEP_CLASSES_WITH_MEMBERS_OPTION
0139: .startsWith(nextWord))
0140: configuration.keep = parseKeepSpecificationArguments(
0141: configuration.keep, false, true, false);
0142: else if (ConfigurationConstants.KEEP_NAMES_OPTION
0143: .startsWith(nextWord))
0144: configuration.keep = parseKeepSpecificationArguments(
0145: configuration.keep, true, false, true);
0146: else if (ConfigurationConstants.KEEP_CLASS_MEMBER_NAMES_OPTION
0147: .startsWith(nextWord))
0148: configuration.keep = parseKeepSpecificationArguments(
0149: configuration.keep, false, false, true);
0150: else if (ConfigurationConstants.KEEP_CLASSES_WITH_MEMBER_NAMES_OPTION
0151: .startsWith(nextWord))
0152: configuration.keep = parseKeepSpecificationArguments(
0153: configuration.keep, false, true, true);
0154: else if (ConfigurationConstants.PRINT_SEEDS_OPTION
0155: .startsWith(nextWord))
0156: configuration.printSeeds = parseOptionalFile();
0157:
0158: else if (ConfigurationConstants.DONT_SHRINK_OPTION
0159: .startsWith(nextWord))
0160: configuration.shrink = parseNoArgument(false);
0161: else if (ConfigurationConstants.PRINT_USAGE_OPTION
0162: .startsWith(nextWord))
0163: configuration.printUsage = parseOptionalFile();
0164: else if (ConfigurationConstants.WHY_ARE_YOU_KEEPING_OPTION
0165: .startsWith(nextWord))
0166: configuration.whyAreYouKeeping = parseClassSpecificationArguments(configuration.whyAreYouKeeping);
0167:
0168: else if (ConfigurationConstants.DONT_OPTIMIZE_OPTION
0169: .startsWith(nextWord))
0170: configuration.optimize = parseNoArgument(false);
0171: else if (ConfigurationConstants.OPTIMIZATION_PASSES
0172: .startsWith(nextWord))
0173: configuration.optimizationPasses = parseIntegerArgument();
0174: else if (ConfigurationConstants.ASSUME_NO_SIDE_EFFECTS_OPTION
0175: .startsWith(nextWord))
0176: configuration.assumeNoSideEffects = parseClassSpecificationArguments(configuration.assumeNoSideEffects);
0177: else if (ConfigurationConstants.ALLOW_ACCESS_MODIFICATION_OPTION
0178: .startsWith(nextWord))
0179: configuration.allowAccessModification = parseNoArgument(true);
0180:
0181: else if (ConfigurationConstants.DONT_OBFUSCATE_OPTION
0182: .startsWith(nextWord))
0183: configuration.obfuscate = parseNoArgument(false);
0184: else if (ConfigurationConstants.PRINT_MAPPING_OPTION
0185: .startsWith(nextWord))
0186: configuration.printMapping = parseOptionalFile();
0187: else if (ConfigurationConstants.APPLY_MAPPING_OPTION
0188: .startsWith(nextWord))
0189: configuration.applyMapping = parseFile();
0190: else if (ConfigurationConstants.OBFUSCATION_DICTIONARY_OPTION
0191: .startsWith(nextWord))
0192: configuration.obfuscationDictionary = parseFile();
0193: else if (ConfigurationConstants.OVERLOAD_AGGRESSIVELY_OPTION
0194: .startsWith(nextWord))
0195: configuration.overloadAggressively = parseNoArgument(true);
0196: else if (ConfigurationConstants.USE_UNIQUE_CLASS_MEMBER_NAMES_OPTION
0197: .startsWith(nextWord))
0198: configuration.useUniqueClassMemberNames = parseNoArgument(true);
0199: else if (ConfigurationConstants.DONT_USE_MIXED_CASE_CLASS_NAMES_OPTION
0200: .startsWith(nextWord))
0201: configuration.useMixedCaseClassNames = parseNoArgument(false);
0202: else if (ConfigurationConstants.FLATTEN_PACKAGE_HIERARCHY_OPTION
0203: .startsWith(nextWord))
0204: configuration.flattenPackageHierarchy = ClassUtil
0205: .internalClassName(parseOptionalArgument());
0206: else if (ConfigurationConstants.REPACKAGE_CLASSES_OPTION
0207: .startsWith(nextWord))
0208: configuration.repackageClasses = ClassUtil
0209: .internalClassName(parseOptionalArgument());
0210: else if (ConfigurationConstants.DEFAULT_PACKAGE_OPTION
0211: .startsWith(nextWord))
0212: configuration.repackageClasses = ClassUtil
0213: .internalClassName(parseOptionalArgument());
0214: else if (ConfigurationConstants.KEEP_ATTRIBUTES_OPTION
0215: .startsWith(nextWord))
0216: configuration.keepAttributes = parseCommaSeparatedList(
0217: "attribute name", true, true, false, true,
0218: false, configuration.keepAttributes);
0219: else if (ConfigurationConstants.RENAME_SOURCE_FILE_ATTRIBUTE_OPTION
0220: .startsWith(nextWord))
0221: configuration.newSourceFileAttribute = parseOptionalArgument();
0222: else if (ConfigurationConstants.ADAPT_RESOURCE_FILE_NAMES_OPTION
0223: .startsWith(nextWord))
0224: configuration.adaptResourceFileNames = parseCommaSeparatedList(
0225: "resource file name", true, true, false, false,
0226: false, configuration.adaptResourceFileNames);
0227: else if (ConfigurationConstants.ADAPT_RESOURCE_FILE_CONTENTS_OPTION
0228: .startsWith(nextWord))
0229: configuration.adaptResourceFileContents = parseCommaSeparatedList(
0230: "resource file name", true, true, false, false,
0231: false, configuration.adaptResourceFileContents);
0232:
0233: else if (ConfigurationConstants.DONT_PREVERIFY_OPTION
0234: .startsWith(nextWord))
0235: configuration.preverify = parseNoArgument(false);
0236: else if (ConfigurationConstants.MICRO_EDITION_OPTION
0237: .startsWith(nextWord))
0238: configuration.microEdition = parseNoArgument(true);
0239:
0240: else if (ConfigurationConstants.VERBOSE_OPTION
0241: .startsWith(nextWord))
0242: configuration.verbose = parseNoArgument(true);
0243: else if (ConfigurationConstants.DONT_NOTE_OPTION
0244: .startsWith(nextWord))
0245: configuration.note = parseNoArgument(false);
0246: else if (ConfigurationConstants.DONT_WARN_OPTION
0247: .startsWith(nextWord))
0248: configuration.warn = parseNoArgument(false);
0249: else if (ConfigurationConstants.IGNORE_WARNINGS_OPTION
0250: .startsWith(nextWord))
0251: configuration.ignoreWarnings = parseNoArgument(true);
0252: else if (ConfigurationConstants.PRINT_CONFIGURATION_OPTION
0253: .startsWith(nextWord))
0254: configuration.printConfiguration = parseOptionalFile();
0255: else if (ConfigurationConstants.DUMP_OPTION
0256: .startsWith(nextWord))
0257: configuration.dump = parseOptionalFile();
0258: else {
0259: throw new ParseException("Unknown option "
0260: + reader.locationDescription());
0261: }
0262: }
0263: }
0264:
0265: /**
0266: * Closes the configuration.
0267: * @throws IOException if an IO error occurs while closing the configuration.
0268: */
0269: public void close() throws IOException {
0270: if (reader != null) {
0271: reader.close();
0272: }
0273: }
0274:
0275: private long parseIncludeArgument(long lastModified)
0276: throws ParseException, IOException {
0277: // Read the configuation file name.
0278: readNextWord("configuration file name");
0279:
0280: File file = file(nextWord);
0281: reader.includeWordReader(new FileWordReader(file));
0282:
0283: readNextWord();
0284:
0285: return Math.max(lastModified, file.lastModified());
0286: }
0287:
0288: private void parseBaseDirectoryArgument() throws ParseException,
0289: IOException {
0290: // Read the base directory name.
0291: readNextWord("base directory name");
0292:
0293: reader.setBaseDir(file(nextWord));
0294:
0295: readNextWord();
0296: }
0297:
0298: private ClassPath parseClassPathArgument(ClassPath classPath,
0299: boolean isOutput) throws ParseException, IOException {
0300: // Create a new List if necessary.
0301: if (classPath == null) {
0302: classPath = new ClassPath();
0303: }
0304:
0305: while (true) {
0306: // Read the next jar name.
0307: readNextWord("jar or directory name");
0308:
0309: // Create a new class path entry.
0310: ClassPathEntry entry = new ClassPathEntry(file(nextWord),
0311: isOutput);
0312:
0313: // Read the opening parenthesis or the separator, if any.
0314: readNextWord();
0315:
0316: // Read the optional filters.
0317: if (!configurationEnd()
0318: && ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD
0319: .equals(nextWord)) {
0320: // Read all filters in an array.
0321: String[] filters = new String[5];
0322:
0323: int counter = 0;
0324: do {
0325: // Read the filter.
0326: filters[counter++] = ListUtil
0327: .commaSeparatedString(parseCommaSeparatedList(
0328: "filter", true, false, true, false,
0329: true, null));
0330: } while (counter < filters.length
0331: && ConfigurationConstants.SEPARATOR_KEYWORD
0332: .equals(nextWord));
0333:
0334: // Make sure there is a closing parenthesis.
0335: if (!ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD
0336: .equals(nextWord)) {
0337: throw new ParseException(
0338: "Expecting separating '"
0339: + ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD
0340: + "' or '"
0341: + ConfigurationConstants.SEPARATOR_KEYWORD
0342: + "', or closing '"
0343: + ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD
0344: + "' before "
0345: + reader.locationDescription());
0346: }
0347:
0348: // Set all filters from the array on the entry.
0349: entry.setFilter(filters[--counter]);
0350: if (counter > 0) {
0351: entry.setJarFilter(filters[--counter]);
0352: if (counter > 0) {
0353: entry.setWarFilter(filters[--counter]);
0354: if (counter > 0) {
0355: entry.setEarFilter(filters[--counter]);
0356: if (counter > 0) {
0357: entry.setZipFilter(filters[--counter]);
0358: }
0359: }
0360: }
0361: }
0362:
0363: // Read the separator, if any.
0364: readNextWord();
0365: }
0366:
0367: // Add the entry to the list.
0368: classPath.add(entry);
0369:
0370: if (configurationEnd()) {
0371: return classPath;
0372: }
0373:
0374: if (!nextWord
0375: .equals(ConfigurationConstants.JAR_SEPARATOR_KEYWORD)) {
0376: throw new ParseException(
0377: "Expecting class path separator '"
0378: + ConfigurationConstants.JAR_SEPARATOR_KEYWORD
0379: + "' before "
0380: + reader.locationDescription());
0381: }
0382: }
0383: }
0384:
0385: private int parseClassVersion() throws ParseException, IOException {
0386: // Read the obligatory target.
0387: readNextWord("java version");
0388:
0389: int classVersion = ClassUtil.internalClassVersion(nextWord);
0390: if (classVersion == 0) {
0391: throw new ParseException("Unsupported java version "
0392: + reader.locationDescription());
0393: }
0394:
0395: readNextWord();
0396:
0397: return classVersion;
0398: }
0399:
0400: private int parseIntegerArgument() throws ParseException,
0401: IOException {
0402: try {
0403: // Read the obligatory integer.
0404: readNextWord("integer");
0405:
0406: int integer = Integer.parseInt(nextWord);
0407:
0408: readNextWord();
0409:
0410: return integer;
0411: } catch (NumberFormatException e) {
0412: throw new ParseException(
0413: "Expecting integer argument instead of '"
0414: + nextWord + "' before "
0415: + reader.locationDescription());
0416: }
0417: }
0418:
0419: private File parseFile() throws ParseException, IOException {
0420: // Read the obligatory file name.
0421: readNextWord("file name");
0422:
0423: // Make sure the file is properly resolved.
0424: File file = file(nextWord);
0425:
0426: readNextWord();
0427:
0428: return file;
0429: }
0430:
0431: private File parseOptionalFile() throws ParseException, IOException {
0432: // Read the optional file name.
0433: readNextWord();
0434:
0435: // Didn't the user specify a file name?
0436: if (configurationEnd()) {
0437: return new File("");
0438: }
0439:
0440: // Make sure the file is properly resolved.
0441: File file = file(nextWord);
0442:
0443: readNextWord();
0444:
0445: return file;
0446: }
0447:
0448: private String parseOptionalArgument() throws IOException {
0449: // Read the optional argument.
0450: readNextWord();
0451:
0452: // Didn't the user specify an argument?
0453: if (configurationEnd()) {
0454: return "";
0455: }
0456:
0457: String fileName = nextWord;
0458:
0459: readNextWord();
0460:
0461: return fileName;
0462: }
0463:
0464: private boolean parseNoArgument(boolean value) throws IOException {
0465: readNextWord();
0466:
0467: return value;
0468: }
0469:
0470: private long parseNoArgument(long value) throws IOException {
0471: readNextWord();
0472:
0473: return value;
0474: }
0475:
0476: private List parseKeepSpecificationArguments(
0477: List keepSpecifications, boolean markClasses,
0478: boolean markConditionally, boolean allowShrinking)
0479: throws ParseException, IOException {
0480: // Create a new List if necessary.
0481: if (keepSpecifications == null) {
0482: keepSpecifications = new ArrayList();
0483: }
0484:
0485: //boolean allowShrinking = false;
0486: boolean allowOptimization = false;
0487: boolean allowObfuscation = false;
0488:
0489: // Read the keep modifiers.
0490: while (true) {
0491: readNextWord("keyword '"
0492: + ConfigurationConstants.CLASS_KEYWORD + "' or '"
0493: + ClassConstants.EXTERNAL_ACC_INTERFACE + "'", true);
0494:
0495: if (!ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD
0496: .equals(nextWord)) {
0497: // Not a comma. Stop parsing the keep modifiers.
0498: break;
0499: }
0500:
0501: readNextWord("keyword '"
0502: + ConfigurationConstants.ALLOW_SHRINKING_SUBOPTION
0503: + "', '"
0504: + ConfigurationConstants.ALLOW_OPTIMIZATION_SUBOPTION
0505: + "', or '"
0506: + ConfigurationConstants.ALLOW_OBFUSCATION_SUBOPTION
0507: + "'");
0508:
0509: if (ConfigurationConstants.ALLOW_SHRINKING_SUBOPTION
0510: .startsWith(nextWord)) {
0511: allowShrinking = true;
0512: } else if (ConfigurationConstants.ALLOW_OPTIMIZATION_SUBOPTION
0513: .startsWith(nextWord)) {
0514: allowOptimization = true;
0515: } else if (ConfigurationConstants.ALLOW_OBFUSCATION_SUBOPTION
0516: .startsWith(nextWord)) {
0517: allowObfuscation = true;
0518: } else {
0519: throw new ParseException(
0520: "Expecting keyword '"
0521: + ConfigurationConstants.ALLOW_SHRINKING_SUBOPTION
0522: + "', '"
0523: + ConfigurationConstants.ALLOW_OPTIMIZATION_SUBOPTION
0524: + "', or '"
0525: + ConfigurationConstants.ALLOW_OBFUSCATION_SUBOPTION
0526: + "' before "
0527: + reader.locationDescription());
0528: }
0529: }
0530:
0531: // Read the class configuration.
0532: ClassSpecification classSpecification = parseClassSpecificationArguments();
0533:
0534: // Create and add the keep configuration.
0535: keepSpecifications.add(new KeepSpecification(markClasses,
0536: markConditionally, allowShrinking, allowOptimization,
0537: allowObfuscation, classSpecification));
0538:
0539: return keepSpecifications;
0540: }
0541:
0542: private List parseClassSpecificationArguments(
0543: List classSpecifications) throws ParseException,
0544: IOException {
0545: // Create a new List if necessary.
0546: if (classSpecifications == null) {
0547: classSpecifications = new ArrayList();
0548: }
0549:
0550: // Read and add the class configuration.
0551: readNextWord("keyword '" + ConfigurationConstants.CLASS_KEYWORD
0552: + "' or '" + ClassConstants.EXTERNAL_ACC_INTERFACE
0553: + "'", true);
0554:
0555: classSpecifications.add(parseClassSpecificationArguments());
0556:
0557: return classSpecifications;
0558: }
0559:
0560: private ClassSpecification parseClassSpecificationArguments()
0561: throws ParseException, IOException {
0562: // Clear the annotation type.
0563: String annotationType = null;
0564:
0565: // Clear the class access modifiers.
0566: int requiredSetClassAccessFlags = 0;
0567: int requiredUnsetClassAccessFlags = 0;
0568:
0569: // Parse the class annotations and access modifiers until the class keyword.
0570: while (!ConfigurationConstants.CLASS_KEYWORD.equals(nextWord)) {
0571: // Parse the annotation type, if any.
0572: if (ConfigurationConstants.ANNOTATION_KEYWORD
0573: .equals(nextWord)) {
0574: annotationType = ClassUtil.internalType(ListUtil
0575: .commaSeparatedString(parseCommaSeparatedList(
0576: "annotation type", true, false, false,
0577: true, false, null)));
0578:
0579: continue;
0580: }
0581:
0582: // Strip the negating sign, if any.
0583: String strippedWord = nextWord
0584: .startsWith(ConfigurationConstants.NEGATOR_KEYWORD) ? nextWord
0585: .substring(1)
0586: : nextWord;
0587:
0588: // Parse the class access modifiers.
0589: int accessFlag = strippedWord
0590: .equals(ClassConstants.EXTERNAL_ACC_PUBLIC) ? ClassConstants.INTERNAL_ACC_PUBLIC
0591: : strippedWord
0592: .equals(ClassConstants.EXTERNAL_ACC_FINAL) ? ClassConstants.INTERNAL_ACC_FINAL
0593: : strippedWord
0594: .equals(ClassConstants.EXTERNAL_ACC_INTERFACE) ? ClassConstants.INTERNAL_ACC_INTERFACE
0595: : strippedWord
0596: .equals(ClassConstants.EXTERNAL_ACC_ABSTRACT) ? ClassConstants.INTERNAL_ACC_ABSTRACT
0597: : unknownAccessFlag();
0598: if (strippedWord.equals(nextWord)) {
0599: requiredSetClassAccessFlags |= accessFlag;
0600: } else {
0601: requiredUnsetClassAccessFlags |= accessFlag;
0602: }
0603:
0604: if ((requiredSetClassAccessFlags & requiredUnsetClassAccessFlags) != 0) {
0605: throw new ParseException(
0606: "Conflicting class access modifiers for '"
0607: + strippedWord + "' before "
0608: + reader.locationDescription());
0609: }
0610:
0611: if (ClassConstants.EXTERNAL_ACC_INTERFACE
0612: .equals(strippedWord)) {
0613: // The interface keyword. Stop parsing the class flags.
0614: break;
0615: }
0616:
0617: readNextWord("keyword '"
0618: + ConfigurationConstants.CLASS_KEYWORD + "' or '"
0619: + ClassConstants.EXTERNAL_ACC_INTERFACE + "'");
0620: }
0621:
0622: // Parse the class name part.
0623: String externalClassName = ListUtil
0624: .commaSeparatedString(parseCommaSeparatedList(
0625: "class name or interface name", true, false,
0626: false, true, false, null));
0627:
0628: // For backward compatibility, allow a single "*" wildcard to match any
0629: // class.
0630: String className = ConfigurationConstants.ANY_CLASS_KEYWORD
0631: .equals(externalClassName) ? null : ClassUtil
0632: .internalClassName(externalClassName);
0633:
0634: // Clear the annotation type and the class name of the extends part.
0635: String extendsAnnotationType = null;
0636: String extendsClassName = null;
0637:
0638: if (!configurationEnd()) {
0639: // Parse 'implements ...' or 'extends ...' part, if any.
0640: if (ConfigurationConstants.IMPLEMENTS_KEYWORD
0641: .equals(nextWord)
0642: || ConfigurationConstants.EXTENDS_KEYWORD
0643: .equals(nextWord)) {
0644: readNextWord("class name or interface name", true);
0645:
0646: // Parse the annotation type, if any.
0647: if (ConfigurationConstants.ANNOTATION_KEYWORD
0648: .equals(nextWord)) {
0649: extendsAnnotationType = ClassUtil
0650: .internalType(ListUtil
0651: .commaSeparatedString(parseCommaSeparatedList(
0652: "annotation type", true,
0653: false, false, true, false,
0654: null)));
0655: }
0656:
0657: String externalExtendsClassName = ListUtil
0658: .commaSeparatedString(parseCommaSeparatedList(
0659: "class name or interface name", false,
0660: false, false, true, false, null));
0661:
0662: extendsClassName = ConfigurationConstants.ANY_CLASS_KEYWORD
0663: .equals(externalExtendsClassName) ? null
0664: : ClassUtil
0665: .internalClassName(externalExtendsClassName);
0666: }
0667: }
0668:
0669: // Create the basic class specification.
0670: ClassSpecification classSpecification = new ClassSpecification(
0671: lastComments, requiredSetClassAccessFlags,
0672: requiredUnsetClassAccessFlags, annotationType,
0673: className, extendsAnnotationType, extendsClassName);
0674:
0675: // Now add any class members to this class specification.
0676: if (!configurationEnd()) {
0677: // Check the class member opening part.
0678: if (!ConfigurationConstants.OPEN_KEYWORD.equals(nextWord)) {
0679: throw new ParseException("Expecting opening '"
0680: + ConfigurationConstants.OPEN_KEYWORD + "' at "
0681: + reader.locationDescription());
0682: }
0683:
0684: // Parse all class members.
0685: while (true) {
0686: readNextWord("class member description"
0687: + " or closing '"
0688: + ConfigurationConstants.CLOSE_KEYWORD + "'",
0689: true);
0690:
0691: if (nextWord
0692: .equals(ConfigurationConstants.CLOSE_KEYWORD)) {
0693: // The closing brace. Stop parsing the class members.
0694: readNextWord();
0695:
0696: break;
0697: }
0698:
0699: parseMemberSpecificationArguments(externalClassName,
0700: classSpecification);
0701: }
0702: }
0703:
0704: return classSpecification;
0705: }
0706:
0707: private void parseMemberSpecificationArguments(
0708: String externalClassName,
0709: ClassSpecification classSpecification)
0710: throws ParseException, IOException {
0711: // Clear the annotation name.
0712: String annotationType = null;
0713:
0714: // Parse the class member access modifiers, if any.
0715: int requiredSetMemberAccessFlags = 0;
0716: int requiredUnsetMemberAccessFlags = 0;
0717:
0718: while (!configurationEnd(true)) {
0719: // Parse the annotation type, if any.
0720: if (ConfigurationConstants.ANNOTATION_KEYWORD
0721: .equals(nextWord)) {
0722: annotationType = ClassUtil.internalType(ListUtil
0723: .commaSeparatedString(parseCommaSeparatedList(
0724: "annotation type", true, false, false,
0725: true, false, null)));
0726:
0727: continue;
0728: }
0729:
0730: String strippedWord = nextWord.startsWith("!") ? nextWord
0731: .substring(1) : nextWord;
0732:
0733: // Parse the class member access modifiers.
0734: int accessFlag = strippedWord
0735: .equals(ClassConstants.EXTERNAL_ACC_PUBLIC) ? ClassConstants.INTERNAL_ACC_PUBLIC
0736: : strippedWord
0737: .equals(ClassConstants.EXTERNAL_ACC_PRIVATE) ? ClassConstants.INTERNAL_ACC_PRIVATE
0738: : strippedWord
0739: .equals(ClassConstants.EXTERNAL_ACC_PROTECTED) ? ClassConstants.INTERNAL_ACC_PROTECTED
0740: : strippedWord
0741: .equals(ClassConstants.EXTERNAL_ACC_STATIC) ? ClassConstants.INTERNAL_ACC_STATIC
0742: : strippedWord
0743: .equals(ClassConstants.EXTERNAL_ACC_FINAL) ? ClassConstants.INTERNAL_ACC_FINAL
0744: : strippedWord
0745: .equals(ClassConstants.EXTERNAL_ACC_SYNCHRONIZED) ? ClassConstants.INTERNAL_ACC_SYNCHRONIZED
0746: : strippedWord
0747: .equals(ClassConstants.EXTERNAL_ACC_VOLATILE) ? ClassConstants.INTERNAL_ACC_VOLATILE
0748: : strippedWord
0749: .equals(ClassConstants.EXTERNAL_ACC_TRANSIENT) ? ClassConstants.INTERNAL_ACC_TRANSIENT
0750: : strippedWord
0751: .equals(ClassConstants.EXTERNAL_ACC_NATIVE) ? ClassConstants.INTERNAL_ACC_NATIVE
0752: : strippedWord
0753: .equals(ClassConstants.EXTERNAL_ACC_ABSTRACT) ? ClassConstants.INTERNAL_ACC_ABSTRACT
0754: : strippedWord
0755: .equals(ClassConstants.EXTERNAL_ACC_STRICT) ? ClassConstants.INTERNAL_ACC_STRICT
0756: : 0;
0757: if (accessFlag == 0) {
0758: // Not a class member access modifier. Stop parsing them.
0759: break;
0760: }
0761:
0762: if (strippedWord.equals(nextWord)) {
0763: requiredSetMemberAccessFlags |= accessFlag;
0764: } else {
0765: requiredUnsetMemberAccessFlags |= accessFlag;
0766: }
0767:
0768: // Make sure the user doesn't try to set and unset the same
0769: // access flags simultaneously.
0770: if ((requiredSetMemberAccessFlags & requiredUnsetMemberAccessFlags) != 0) {
0771: throw new ParseException(
0772: "Conflicting class member access modifiers for "
0773: + reader.locationDescription());
0774: }
0775:
0776: readNextWord("class member description");
0777: }
0778:
0779: // Parse the class member type and name part.
0780:
0781: // Did we get a special wildcard?
0782: if (ConfigurationConstants.ANY_CLASS_MEMBER_KEYWORD
0783: .equals(nextWord)
0784: || ConfigurationConstants.ANY_FIELD_KEYWORD
0785: .equals(nextWord)
0786: || ConfigurationConstants.ANY_METHOD_KEYWORD
0787: .equals(nextWord)) {
0788: // Act according to the type of wildcard..
0789: if (ConfigurationConstants.ANY_CLASS_MEMBER_KEYWORD
0790: .equals(nextWord)) {
0791: checkFieldAccessFlags(requiredSetMemberAccessFlags,
0792: requiredUnsetMemberAccessFlags);
0793: checkMethodAccessFlags(requiredSetMemberAccessFlags,
0794: requiredUnsetMemberAccessFlags);
0795:
0796: classSpecification.addField(new MemberSpecification(
0797: requiredSetMemberAccessFlags,
0798: requiredUnsetMemberAccessFlags, annotationType,
0799: null, null));
0800: classSpecification.addMethod(new MemberSpecification(
0801: requiredSetMemberAccessFlags,
0802: requiredUnsetMemberAccessFlags, annotationType,
0803: null, null));
0804: } else if (ConfigurationConstants.ANY_FIELD_KEYWORD
0805: .equals(nextWord)) {
0806: checkFieldAccessFlags(requiredSetMemberAccessFlags,
0807: requiredUnsetMemberAccessFlags);
0808:
0809: classSpecification.addField(new MemberSpecification(
0810: requiredSetMemberAccessFlags,
0811: requiredUnsetMemberAccessFlags, annotationType,
0812: null, null));
0813: } else if (ConfigurationConstants.ANY_METHOD_KEYWORD
0814: .equals(nextWord)) {
0815: checkMethodAccessFlags(requiredSetMemberAccessFlags,
0816: requiredUnsetMemberAccessFlags);
0817:
0818: classSpecification.addMethod(new MemberSpecification(
0819: requiredSetMemberAccessFlags,
0820: requiredUnsetMemberAccessFlags, annotationType,
0821: null, null));
0822: }
0823:
0824: // We still have to read the closing separator.
0825: readNextWord("separator '"
0826: + ConfigurationConstants.SEPARATOR_KEYWORD + "'");
0827:
0828: if (!ConfigurationConstants.SEPARATOR_KEYWORD
0829: .equals(nextWord)) {
0830: throw new ParseException("Expecting separator '"
0831: + ConfigurationConstants.SEPARATOR_KEYWORD
0832: + "' before " + reader.locationDescription());
0833: }
0834: } else {
0835: // Make sure we have a proper type.
0836: checkJavaIdentifier("java type");
0837: String type = nextWord;
0838:
0839: readNextWord("class member name");
0840: String name = nextWord;
0841:
0842: // Did we get just one word before the opening parenthesis?
0843: if (ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD
0844: .equals(name)) {
0845: // This must be a constructor then.
0846: // Make sure the type is a proper constructor name.
0847: if (!(type
0848: .equals(ClassConstants.INTERNAL_METHOD_NAME_INIT)
0849: || type.equals(externalClassName) || type
0850: .equals(ClassUtil
0851: .externalShortClassName(externalClassName)))) {
0852: throw new ParseException("Expecting type and name "
0853: + "instead of just '" + type + "' before "
0854: + reader.locationDescription());
0855: }
0856:
0857: // Assign the fixed constructor type and name.
0858: type = ClassConstants.EXTERNAL_TYPE_VOID;
0859: name = ClassConstants.INTERNAL_METHOD_NAME_INIT;
0860: } else {
0861: // It's not a constructor.
0862: // Make sure we have a proper name.
0863: checkJavaIdentifier("class member name");
0864:
0865: // Read the opening parenthesis or the separating
0866: // semi-colon.
0867: readNextWord("opening '"
0868: + ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD
0869: + "' or separator '"
0870: + ConfigurationConstants.SEPARATOR_KEYWORD
0871: + "'");
0872: }
0873:
0874: // Are we looking at a field, a method, or something else?
0875: if (ConfigurationConstants.SEPARATOR_KEYWORD
0876: .equals(nextWord)) {
0877: // It's a field.
0878: checkFieldAccessFlags(requiredSetMemberAccessFlags,
0879: requiredUnsetMemberAccessFlags);
0880:
0881: // We already have a field descriptor.
0882: String descriptor = ClassUtil.internalType(type);
0883:
0884: // Add the field.
0885: classSpecification.addField(new MemberSpecification(
0886: requiredSetMemberAccessFlags,
0887: requiredUnsetMemberAccessFlags, annotationType,
0888: name, descriptor));
0889: } else if (ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD
0890: .equals(nextWord)) {
0891: // It's a method.
0892: checkMethodAccessFlags(requiredSetMemberAccessFlags,
0893: requiredUnsetMemberAccessFlags);
0894:
0895: // Parse the method arguments.
0896: String descriptor = ClassUtil.internalMethodDescriptor(
0897: type, parseCommaSeparatedList("argument", true,
0898: true, true, true, false, null));
0899:
0900: if (!ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD
0901: .equals(nextWord)) {
0902: throw new ParseException(
0903: "Expecting separating '"
0904: + ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD
0905: + "' or closing '"
0906: + ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD
0907: + "' before "
0908: + reader.locationDescription());
0909: }
0910:
0911: // Read the separator after the closing parenthesis.
0912: readNextWord("separator '"
0913: + ConfigurationConstants.SEPARATOR_KEYWORD
0914: + "'");
0915:
0916: if (!ConfigurationConstants.SEPARATOR_KEYWORD
0917: .equals(nextWord)) {
0918: throw new ParseException("Expecting separator '"
0919: + ConfigurationConstants.SEPARATOR_KEYWORD
0920: + "' before "
0921: + reader.locationDescription());
0922: }
0923:
0924: // Add the method.
0925: classSpecification.addMethod(new MemberSpecification(
0926: requiredSetMemberAccessFlags,
0927: requiredUnsetMemberAccessFlags, annotationType,
0928: name, descriptor));
0929: } else {
0930: // It doesn't look like a field or a method.
0931: throw new ParseException("Expecting opening '"
0932: + ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD
0933: + "' or separator '"
0934: + ConfigurationConstants.SEPARATOR_KEYWORD
0935: + "' before " + reader.locationDescription());
0936: }
0937: }
0938: }
0939:
0940: /**
0941: * Reads a comma-separated list of java identifiers or of file names. If an
0942: * empty list is allowed, the reading will end after a closing parenthesis
0943: * or semi-colon.
0944: */
0945: private List parseCommaSeparatedList(String expectedDescription,
0946: boolean readFirstWord, boolean allowEmptyList,
0947: boolean expectClosingParenthesis,
0948: boolean checkJavaIdentifiers,
0949: boolean replaceSystemProperties, List list)
0950: throws ParseException, IOException {
0951: if (list == null) {
0952: list = new ArrayList();
0953: }
0954:
0955: if (readFirstWord) {
0956: if (expectClosingParenthesis || !allowEmptyList) {
0957: // Read the first list entry.
0958: readNextWord(expectedDescription);
0959: } else {
0960: // Read the first list entry, if there is any.
0961: readNextWord();
0962:
0963: // Check if the list is empty.
0964: if (configurationEnd()
0965: || nextWord
0966: .equals(ConfigurationConstants.ANY_ATTRIBUTE_KEYWORD)) {
0967: return list;
0968: }
0969: }
0970: }
0971:
0972: while (true) {
0973: if (expectClosingParenthesis
0974: && list.size() == 0
0975: && (ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD
0976: .equals(nextWord) || ConfigurationConstants.SEPARATOR_KEYWORD
0977: .equals(nextWord))) {
0978: break;
0979: }
0980:
0981: if (checkJavaIdentifiers) {
0982: checkJavaIdentifier("java type");
0983: }
0984:
0985: if (replaceSystemProperties) {
0986: nextWord = replaceSystemProperties(nextWord);
0987: }
0988:
0989: list.add(nextWord);
0990:
0991: if (expectClosingParenthesis) {
0992: // Read a comma (or a closing parenthesis, or a different word).
0993: readNextWord("separating '"
0994: + ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD
0995: + "' or closing '"
0996: + ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD
0997: + "'");
0998: } else {
0999: // Read a comma (or a different word).
1000: readNextWord();
1001: }
1002:
1003: if (!ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD
1004: .equals(nextWord)) {
1005: break;
1006: }
1007:
1008: // Read the next list entry.
1009: readNextWord(expectedDescription);
1010: }
1011:
1012: return list;
1013: }
1014:
1015: /**
1016: * Throws a ParseException for an unexpected keyword.
1017: */
1018: private int unknownAccessFlag() throws ParseException {
1019: throw new ParseException("Unexpected keyword "
1020: + reader.locationDescription());
1021: }
1022:
1023: /**
1024: * Creates a properly resolved File, based on the given word.
1025: */
1026: private File file(String word) throws ParseException {
1027: String fileName = replaceSystemProperties(word);
1028: File file = new File(fileName);
1029:
1030: // Try to get an absolute file.
1031: if (!file.isAbsolute()) {
1032: file = new File(reader.getBaseDir(), fileName);
1033: }
1034:
1035: // Try to get a canonical representation.
1036: try {
1037: file = file.getCanonicalFile();
1038: } catch (IOException ex) {
1039: // Just keep the original representation.
1040: }
1041:
1042: return file;
1043: }
1044:
1045: /**
1046: * Replaces any system properties in the given word by their values
1047: * (e.g. the substring "<java.home>" is replaced by its value).
1048: */
1049: private String replaceSystemProperties(String word)
1050: throws ParseException {
1051: int fromIndex = 0;
1052: while (true) {
1053: fromIndex = word.indexOf(
1054: ConfigurationConstants.OPEN_SYSTEM_PROPERTY,
1055: fromIndex);
1056: if (fromIndex < 0) {
1057: break;
1058: }
1059:
1060: int toIndex = word.indexOf(
1061: ConfigurationConstants.CLOSE_SYSTEM_PROPERTY,
1062: fromIndex + 1);
1063: if (toIndex < 0) {
1064: throw new ParseException("Expecting closing '"
1065: + ConfigurationConstants.CLOSE_SYSTEM_PROPERTY
1066: + "' after opening '"
1067: + ConfigurationConstants.OPEN_SYSTEM_PROPERTY
1068: + "' in " + reader.locationDescription());
1069: }
1070:
1071: String propertyName = word
1072: .substring(fromIndex + 1, toIndex);
1073: String propertyValue = System.getProperty(propertyName);
1074: if (propertyValue == null) {
1075: throw new ParseException("Value of system property '"
1076: + propertyName + "' is undefined in "
1077: + reader.locationDescription());
1078: }
1079:
1080: word = word.substring(0, fromIndex) + propertyValue
1081: + word.substring(toIndex + 1);
1082: }
1083:
1084: return word;
1085: }
1086:
1087: /**
1088: * Reads the next word of the configuration in the 'nextWord' field,
1089: * throwing an exception if there is no next word.
1090: */
1091: private void readNextWord(String expectedDescription)
1092: throws ParseException, IOException {
1093: readNextWord(expectedDescription, false);
1094: }
1095:
1096: /**
1097: * Reads the next word of the configuration in the 'nextWord' field,
1098: * throwing an exception if there is no next word.
1099: */
1100: private void readNextWord(String expectedDescription,
1101: boolean expectingAtCharacter) throws ParseException,
1102: IOException {
1103: readNextWord();
1104: if (configurationEnd(expectingAtCharacter)) {
1105: throw new ParseException("Expecting " + expectedDescription
1106: + " before " + reader.locationDescription());
1107: }
1108: }
1109:
1110: /**
1111: * Reads the next word of the configuration in the 'nextWord' field.
1112: */
1113: private void readNextWord() throws IOException {
1114: nextWord = reader.nextWord();
1115: }
1116:
1117: /**
1118: * Returns whether the end of the configuration has been reached.
1119: */
1120: private boolean configurationEnd() {
1121: return configurationEnd(false);
1122: }
1123:
1124: /**
1125: * Returns whether the end of the configuration has been reached.
1126: */
1127: private boolean configurationEnd(boolean expectingAtCharacter) {
1128: return nextWord == null
1129: || nextWord
1130: .startsWith(ConfigurationConstants.OPTION_PREFIX)
1131: || (!expectingAtCharacter && nextWord
1132: .equals(ConfigurationConstants.AT_DIRECTIVE));
1133: }
1134:
1135: /**
1136: * Checks whether the given word is a valid Java identifier and throws
1137: * a ParseException if it isn't. Wildcard characters are accepted.
1138: */
1139: private void checkJavaIdentifier(String expectedDescription)
1140: throws ParseException {
1141: if (!isJavaIdentifier(nextWord)) {
1142: throw new ParseException("Expecting " + expectedDescription
1143: + " before " + reader.locationDescription());
1144: }
1145: }
1146:
1147: /**
1148: * Returns whether the given word is a valid Java identifier.
1149: * Wildcard characters are accepted.
1150: */
1151: private boolean isJavaIdentifier(String aWord) {
1152: for (int index = 0; index < aWord.length(); index++) {
1153: char c = aWord.charAt(index);
1154: if (!(Character.isJavaIdentifierPart(c) || c == '.'
1155: || c == '[' || c == ']' || c == '<' || c == '>'
1156: || c == '-' || c == '!' || c == '*' || c == '?' || c == '%')) {
1157: return false;
1158: }
1159: }
1160:
1161: return true;
1162: }
1163:
1164: /**
1165: * Checks whether the given access flags are valid field access flags,
1166: * throwing a ParseException if they aren't.
1167: */
1168: private void checkFieldAccessFlags(
1169: int requiredSetMemberAccessFlags,
1170: int requiredUnsetMemberAccessFlags) throws ParseException {
1171: if (((requiredSetMemberAccessFlags | requiredUnsetMemberAccessFlags) & ~ClassConstants.VALID_INTERNAL_ACC_FIELD) != 0) {
1172: throw new ParseException(
1173: "Invalid method access modifier for field before "
1174: + reader.locationDescription());
1175: }
1176: }
1177:
1178: /**
1179: * Checks whether the given access flags are valid method access flags,
1180: * throwing a ParseException if they aren't.
1181: */
1182: private void checkMethodAccessFlags(
1183: int requiredSetMemberAccessFlags,
1184: int requiredUnsetMemberAccessFlags) throws ParseException {
1185: if (((requiredSetMemberAccessFlags | requiredUnsetMemberAccessFlags) & ~ClassConstants.VALID_INTERNAL_ACC_METHOD) != 0) {
1186: throw new ParseException(
1187: "Invalid field access modifier for method before "
1188: + reader.locationDescription());
1189: }
1190: }
1191:
1192: /**
1193: * A main method for testing configuration parsing.
1194: */
1195: public static void main(String[] args) {
1196: try {
1197: ConfigurationParser parser = new ConfigurationParser(args);
1198:
1199: try {
1200: parser.parse(new Configuration());
1201: } catch (ParseException ex) {
1202: ex.printStackTrace();
1203: } finally {
1204: parser.close();
1205: }
1206: } catch (IOException ex) {
1207: ex.printStackTrace();
1208: }
1209: }
1210: }
|