Home | History | Annotate | Download | only in proguard
      1 /*
      2  * ProGuard -- shrinking, optimization, obfuscation, and preverification
      3  *             of Java bytecode.
      4  *
      5  * Copyright (c) 2002-2009 Eric Lafortune (eric (at) graphics.cornell.edu)
      6  *
      7  * This program is free software; you can redistribute it and/or modify it
      8  * under the terms of the GNU General Public License as published by the Free
      9  * Software Foundation; either version 2 of the License, or (at your option)
     10  * any later version.
     11  *
     12  * This program is distributed in the hope that it will be useful, but WITHOUT
     13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
     14  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
     15  * more details.
     16  *
     17  * You should have received a copy of the GNU General Public License along
     18  * with this program; if not, write to the Free Software Foundation, Inc.,
     19  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
     20  */
     21 package proguard;
     22 
     23 import proguard.classfile.ClassConstants;
     24 import proguard.classfile.util.ClassUtil;
     25 import proguard.util.ListUtil;
     26 
     27 import java.io.*;
     28 import java.net.URL;
     29 import java.util.*;
     30 
     31 
     32 /**
     33  * This class parses ProGuard configurations. Configurations can be read from an
     34  * array of arguments or from a configuration file or URL.
     35  *
     36  * @author Eric Lafortune
     37  */
     38 public class ConfigurationParser
     39 {
     40     private WordReader reader;
     41     private String     nextWord;
     42     private String     lastComments;
     43 
     44 
     45     /**
     46      * Creates a new ConfigurationParser for the given String arguments.
     47      */
     48     public ConfigurationParser(String[] args) throws IOException
     49     {
     50         this(args, null);
     51     }
     52 
     53 
     54     /**
     55      * Creates a new ConfigurationParser for the given String arguments,
     56      * with the given base directory.
     57      */
     58     public ConfigurationParser(String[] args,
     59                                File     baseDir) throws IOException
     60     {
     61         reader = new ArgumentWordReader(args, baseDir);
     62 
     63         readNextWord();
     64     }
     65 
     66 
     67     /**
     68      * Creates a new ConfigurationParser for the given file.
     69      */
     70     public ConfigurationParser(File file) throws IOException
     71     {
     72         reader = new FileWordReader(file);
     73 
     74         readNextWord();
     75     }
     76 
     77 
     78     /**
     79      * Creates a new ConfigurationParser for the given URL.
     80      */
     81     public ConfigurationParser(URL url) throws IOException
     82     {
     83         reader = new FileWordReader(url);
     84 
     85         readNextWord();
     86     }
     87 
     88 
     89     /**
     90      * Parses and returns the configuration.
     91      * @param configuration the configuration that is updated as a side-effect.
     92      * @throws ParseException if the any of the configuration settings contains
     93      *                        a syntax error.
     94      * @throws IOException if an IO error occurs while reading a configuration.
     95      */
     96     public void parse(Configuration configuration)
     97     throws ParseException, IOException
     98     {
     99         while (nextWord != null)
    100         {
    101             lastComments = reader.lastComments();
    102 
    103             // First include directives.
    104             if      (ConfigurationConstants.AT_DIRECTIVE                                     .startsWith(nextWord) ||
    105                      ConfigurationConstants.INCLUDE_DIRECTIVE                                .startsWith(nextWord)) configuration.lastModified                     = parseIncludeArgument(configuration.lastModified);
    106             else if (ConfigurationConstants.BASE_DIRECTORY_DIRECTIVE                         .startsWith(nextWord)) parseBaseDirectoryArgument();
    107 
    108             // Then configuration options with or without arguments.
    109             else if (ConfigurationConstants.INJARS_OPTION                                    .startsWith(nextWord)) configuration.programJars                      = parseClassPathArgument(configuration.programJars, false);
    110             else if (ConfigurationConstants.OUTJARS_OPTION                                   .startsWith(nextWord)) configuration.programJars                      = parseClassPathArgument(configuration.programJars, true);
    111             else if (ConfigurationConstants.LIBRARYJARS_OPTION                               .startsWith(nextWord)) configuration.libraryJars                      = parseClassPathArgument(configuration.libraryJars, false);
    112             else if (ConfigurationConstants.RESOURCEJARS_OPTION                              .startsWith(nextWord)) throw new ParseException("The '-resourcejars' option is no longer supported. Please use the '-injars' option for all input");
    113             else if (ConfigurationConstants.DONT_SKIP_NON_PUBLIC_LIBRARY_CLASSES_OPTION      .startsWith(nextWord)) configuration.skipNonPublicLibraryClasses      = parseNoArgument(false);
    114             else if (ConfigurationConstants.DONT_SKIP_NON_PUBLIC_LIBRARY_CLASS_MEMBERS_OPTION.startsWith(nextWord)) configuration.skipNonPublicLibraryClassMembers = parseNoArgument(false);
    115             else if (ConfigurationConstants.TARGET_OPTION                                    .startsWith(nextWord)) configuration.targetClassVersion               = parseClassVersion();
    116             else if (ConfigurationConstants.FORCE_PROCESSING_OPTION                          .startsWith(nextWord)) configuration.lastModified                     = parseNoArgument(Long.MAX_VALUE);
    117 
    118             else if (ConfigurationConstants.KEEP_OPTION                                      .startsWith(nextWord)) configuration.keep                             = parseKeepClassSpecificationArguments(configuration.keep, true,  false, false);
    119             else if (ConfigurationConstants.KEEP_CLASS_MEMBERS_OPTION                        .startsWith(nextWord)) configuration.keep                             = parseKeepClassSpecificationArguments(configuration.keep, false, false, false);
    120             else if (ConfigurationConstants.KEEP_CLASSES_WITH_MEMBERS_OPTION                 .startsWith(nextWord)) configuration.keep                             = parseKeepClassSpecificationArguments(configuration.keep, false, true,  false);
    121             else if (ConfigurationConstants.KEEP_NAMES_OPTION                                .startsWith(nextWord)) configuration.keep                             = parseKeepClassSpecificationArguments(configuration.keep, true,  false, true);
    122             else if (ConfigurationConstants.KEEP_CLASS_MEMBER_NAMES_OPTION                   .startsWith(nextWord)) configuration.keep                             = parseKeepClassSpecificationArguments(configuration.keep, false, false, true);
    123             else if (ConfigurationConstants.KEEP_CLASSES_WITH_MEMBER_NAMES_OPTION            .startsWith(nextWord)) configuration.keep                             = parseKeepClassSpecificationArguments(configuration.keep, false, true,  true);
    124             else if (ConfigurationConstants.PRINT_SEEDS_OPTION                               .startsWith(nextWord)) configuration.printSeeds                       = parseOptionalFile();
    125 
    126             // After '-keep'.
    127             else if (ConfigurationConstants.KEEP_DIRECTORIES_OPTION                          .startsWith(nextWord)) configuration.keepDirectories                  = parseCommaSeparatedList("directory name", true, true, false, false, true, false, false, configuration.keepDirectories);
    128 
    129             else if (ConfigurationConstants.DONT_SHRINK_OPTION                               .startsWith(nextWord)) configuration.shrink                           = parseNoArgument(false);
    130             else if (ConfigurationConstants.PRINT_USAGE_OPTION                               .startsWith(nextWord)) configuration.printUsage                       = parseOptionalFile();
    131             else if (ConfigurationConstants.WHY_ARE_YOU_KEEPING_OPTION                       .startsWith(nextWord)) configuration.whyAreYouKeeping                 = parseClassSpecificationArguments(configuration.whyAreYouKeeping);
    132 
    133             else if (ConfigurationConstants.DONT_OPTIMIZE_OPTION                             .startsWith(nextWord)) configuration.optimize                         = parseNoArgument(false);
    134             else if (ConfigurationConstants.OPTIMIZATION_PASSES                              .startsWith(nextWord)) configuration.optimizationPasses               = parseIntegerArgument();
    135             else if (ConfigurationConstants.OPTIMIZATIONS                                    .startsWith(nextWord)) configuration.optimizations                    = parseCommaSeparatedList("optimization name", true, false, false, false, false, false, false, configuration.optimizations);
    136             else if (ConfigurationConstants.ASSUME_NO_SIDE_EFFECTS_OPTION                    .startsWith(nextWord)) configuration.assumeNoSideEffects              = parseClassSpecificationArguments(configuration.assumeNoSideEffects);
    137             else if (ConfigurationConstants.ALLOW_ACCESS_MODIFICATION_OPTION                 .startsWith(nextWord)) configuration.allowAccessModification          = parseNoArgument(true);
    138             else if (ConfigurationConstants.MERGE_INTERFACES_AGGRESSIVELY_OPTION             .startsWith(nextWord)) configuration.mergeInterfacesAggressively      = parseNoArgument(true);
    139 
    140             else if (ConfigurationConstants.DONT_OBFUSCATE_OPTION                            .startsWith(nextWord)) configuration.obfuscate                        = parseNoArgument(false);
    141             else if (ConfigurationConstants.PRINT_MAPPING_OPTION                             .startsWith(nextWord)) configuration.printMapping                     = parseOptionalFile();
    142             else if (ConfigurationConstants.APPLY_MAPPING_OPTION                             .startsWith(nextWord)) configuration.applyMapping                     = parseFile();
    143             else if (ConfigurationConstants.OBFUSCATION_DICTIONARY_OPTION                    .startsWith(nextWord)) configuration.obfuscationDictionary            = parseFile();
    144             else if (ConfigurationConstants.CLASS_OBFUSCATION_DICTIONARY_OPTION              .startsWith(nextWord)) configuration.classObfuscationDictionary       = parseFile();
    145             else if (ConfigurationConstants.PACKAGE_OBFUSCATION_DICTIONARY_OPTION            .startsWith(nextWord)) configuration.packageObfuscationDictionary     = parseFile();
    146             else if (ConfigurationConstants.OVERLOAD_AGGRESSIVELY_OPTION                     .startsWith(nextWord)) configuration.overloadAggressively             = parseNoArgument(true);
    147             else if (ConfigurationConstants.USE_UNIQUE_CLASS_MEMBER_NAMES_OPTION             .startsWith(nextWord)) configuration.useUniqueClassMemberNames        = parseNoArgument(true);
    148             else if (ConfigurationConstants.DONT_USE_MIXED_CASE_CLASS_NAMES_OPTION           .startsWith(nextWord)) configuration.useMixedCaseClassNames           = parseNoArgument(false);
    149             else if (ConfigurationConstants.KEEP_PACKAGE_NAMES_OPTION                        .startsWith(nextWord)) configuration.keepPackageNames                 = parseCommaSeparatedList("package name", true, true, false, true, false, true, false, configuration.keepPackageNames);
    150             else if (ConfigurationConstants.FLATTEN_PACKAGE_HIERARCHY_OPTION                 .startsWith(nextWord)) configuration.flattenPackageHierarchy          = ClassUtil.internalClassName(parseOptionalArgument());
    151             else if (ConfigurationConstants.REPACKAGE_CLASSES_OPTION                         .startsWith(nextWord)) configuration.repackageClasses                 = ClassUtil.internalClassName(parseOptionalArgument());
    152             else if (ConfigurationConstants.DEFAULT_PACKAGE_OPTION                           .startsWith(nextWord)) configuration.repackageClasses                 = ClassUtil.internalClassName(parseOptionalArgument());
    153             else if (ConfigurationConstants.KEEP_ATTRIBUTES_OPTION                           .startsWith(nextWord)) configuration.keepAttributes                   = parseCommaSeparatedList("attribute name", true, true, false, true, false, false, false, configuration.keepAttributes);
    154             else if (ConfigurationConstants.RENAME_SOURCE_FILE_ATTRIBUTE_OPTION              .startsWith(nextWord)) configuration.newSourceFileAttribute           = parseOptionalArgument();
    155             else if (ConfigurationConstants.ADAPT_CLASS_STRINGS_OPTION                       .startsWith(nextWord)) configuration.adaptClassStrings                = parseCommaSeparatedList("class name", true, true, false, true, false, true, false, configuration.adaptClassStrings);
    156             else if (ConfigurationConstants.ADAPT_RESOURCE_FILE_NAMES_OPTION                 .startsWith(nextWord)) configuration.adaptResourceFileNames           = parseCommaSeparatedList("resource file name", true, true, false, false, false, false, false, configuration.adaptResourceFileNames);
    157             else if (ConfigurationConstants.ADAPT_RESOURCE_FILE_CONTENTS_OPTION              .startsWith(nextWord)) configuration.adaptResourceFileContents        = parseCommaSeparatedList("resource file name", true, true, false, false, false, false, false, configuration.adaptResourceFileContents);
    158 
    159             else if (ConfigurationConstants.DONT_PREVERIFY_OPTION                            .startsWith(nextWord)) configuration.preverify                        = parseNoArgument(false);
    160             else if (ConfigurationConstants.MICRO_EDITION_OPTION                             .startsWith(nextWord)) configuration.microEdition                     = parseNoArgument(true);
    161 
    162             else if (ConfigurationConstants.VERBOSE_OPTION                                   .startsWith(nextWord)) configuration.verbose                          = parseNoArgument(true);
    163             else if (ConfigurationConstants.DONT_NOTE_OPTION                                 .startsWith(nextWord)) configuration.note                             = parseCommaSeparatedList("class name", true, true, false, true, false, true, false, configuration.note);
    164             else if (ConfigurationConstants.DONT_WARN_OPTION                                 .startsWith(nextWord)) configuration.warn                             = parseCommaSeparatedList("class name", true, true, false, true, false, true, false, configuration.warn);
    165             else if (ConfigurationConstants.IGNORE_WARNINGS_OPTION                           .startsWith(nextWord)) configuration.ignoreWarnings                   = parseNoArgument(true);
    166             else if (ConfigurationConstants.PRINT_CONFIGURATION_OPTION                       .startsWith(nextWord)) configuration.printConfiguration               = parseOptionalFile();
    167             else if (ConfigurationConstants.DUMP_OPTION                                      .startsWith(nextWord)) configuration.dump                             = parseOptionalFile();
    168             else
    169             {
    170                 throw new ParseException("Unknown option " + reader.locationDescription());
    171             }
    172         }
    173     }
    174 
    175 
    176 
    177     /**
    178      * Closes the configuration.
    179      * @throws IOException if an IO error occurs while closing the configuration.
    180      */
    181     public void close() throws IOException
    182     {
    183         if (reader != null)
    184         {
    185             reader.close();
    186         }
    187     }
    188 
    189 
    190     private long parseIncludeArgument(long lastModified) throws ParseException, IOException
    191     {
    192         // Read the configuation file name.
    193         readNextWord("configuration file name");
    194 
    195         File file = file(nextWord);
    196         reader.includeWordReader(new FileWordReader(file));
    197 
    198         readNextWord();
    199 
    200         return Math.max(lastModified, file.lastModified());
    201     }
    202 
    203 
    204     private void parseBaseDirectoryArgument() throws ParseException, IOException
    205     {
    206         // Read the base directory name.
    207         readNextWord("base directory name");
    208 
    209         reader.setBaseDir(file(nextWord));
    210 
    211         readNextWord();
    212     }
    213 
    214 
    215     private ClassPath parseClassPathArgument(ClassPath classPath,
    216                                              boolean   isOutput)
    217     throws ParseException, IOException
    218     {
    219         // Create a new List if necessary.
    220         if (classPath == null)
    221         {
    222             classPath = new ClassPath();
    223         }
    224 
    225         while (true)
    226         {
    227             // Read the next jar name.
    228             readNextWord("jar or directory name");
    229 
    230             // Create a new class path entry.
    231             ClassPathEntry entry = new ClassPathEntry(file(nextWord), isOutput);
    232 
    233             // Read the opening parenthesis or the separator, if any.
    234             readNextWord();
    235 
    236             // Read the optional filters.
    237             if (!configurationEnd() &&
    238                 ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD.equals(nextWord))
    239             {
    240                 // Read all filters in an array.
    241                 List[] filters = new List[5];
    242 
    243                 int counter = 0;
    244                 do
    245                 {
    246                     // Read the filter.
    247                     filters[counter++] =
    248                         parseCommaSeparatedList("filter", true, false, true, false, true, false, false, null);
    249                 }
    250                 while (counter < filters.length &&
    251                        ConfigurationConstants.SEPARATOR_KEYWORD.equals(nextWord));
    252 
    253                 // Make sure there is a closing parenthesis.
    254                 if (!ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD.equals(nextWord))
    255                 {
    256                     throw new ParseException("Expecting separating '" + ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD +
    257                                              "' or '" + ConfigurationConstants.SEPARATOR_KEYWORD +
    258                                              "', or closing '" + ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD +
    259                                              "' before " + reader.locationDescription());
    260                 }
    261 
    262                 // Set all filters from the array on the entry.
    263                 entry.setFilter(filters[--counter]);
    264                 if (counter > 0)
    265                 {
    266                     entry.setJarFilter(filters[--counter]);
    267                     if (counter > 0)
    268                     {
    269                         entry.setWarFilter(filters[--counter]);
    270                         if (counter > 0)
    271                         {
    272                             entry.setEarFilter(filters[--counter]);
    273                             if (counter > 0)
    274                             {
    275                                 entry.setZipFilter(filters[--counter]);
    276                             }
    277                         }
    278                     }
    279                 }
    280 
    281                 // Read the separator, if any.
    282                 readNextWord();
    283             }
    284 
    285             // Add the entry to the list.
    286             classPath.add(entry);
    287 
    288             if (configurationEnd())
    289             {
    290                 return classPath;
    291             }
    292 
    293             if (!nextWord.equals(ConfigurationConstants.JAR_SEPARATOR_KEYWORD))
    294             {
    295                 throw new ParseException("Expecting class path separator '" + ConfigurationConstants.JAR_SEPARATOR_KEYWORD +
    296                                          "' before " + reader.locationDescription());
    297             }
    298         }
    299     }
    300 
    301 
    302     private int parseClassVersion()
    303     throws ParseException, IOException
    304     {
    305         // Read the obligatory target.
    306         readNextWord("java version");
    307 
    308         int classVersion = ClassUtil.internalClassVersion(nextWord);
    309         if (classVersion == 0)
    310         {
    311             throw new ParseException("Unsupported java version " + reader.locationDescription());
    312         }
    313 
    314         readNextWord();
    315 
    316         return classVersion;
    317     }
    318 
    319 
    320     private int parseIntegerArgument()
    321     throws ParseException, IOException
    322     {
    323         try
    324         {
    325             // Read the obligatory integer.
    326             readNextWord("integer");
    327 
    328             int integer = Integer.parseInt(nextWord);
    329 
    330             readNextWord();
    331 
    332             return integer;
    333         }
    334         catch (NumberFormatException e)
    335         {
    336             throw new ParseException("Expecting integer argument instead of '" + nextWord +
    337                                      "' before " + reader.locationDescription());
    338         }
    339     }
    340 
    341 
    342     private File parseFile()
    343     throws ParseException, IOException
    344     {
    345         // Read the obligatory file name.
    346         readNextWord("file name");
    347 
    348         // Make sure the file is properly resolved.
    349         File file = file(nextWord);
    350 
    351         readNextWord();
    352 
    353         return file;
    354     }
    355 
    356 
    357     private File parseOptionalFile()
    358     throws ParseException, IOException
    359     {
    360         // Read the optional file name.
    361         readNextWord();
    362 
    363         // Didn't the user specify a file name?
    364         if (configurationEnd())
    365         {
    366             return new File("");
    367         }
    368 
    369         // Make sure the file is properly resolved.
    370         File file = file(nextWord);
    371 
    372         readNextWord();
    373 
    374         return file;
    375     }
    376 
    377 
    378     private String parseOptionalArgument() throws IOException
    379     {
    380         // Read the optional argument.
    381         readNextWord();
    382 
    383         // Didn't the user specify an argument?
    384         if (configurationEnd())
    385         {
    386             return "";
    387         }
    388 
    389         String fileName = nextWord;
    390 
    391         readNextWord();
    392 
    393         return fileName;
    394     }
    395 
    396 
    397     private boolean parseNoArgument(boolean value) throws IOException
    398     {
    399         readNextWord();
    400 
    401         return value;
    402     }
    403 
    404 
    405     private long parseNoArgument(long value) throws IOException
    406     {
    407         readNextWord();
    408 
    409         return value;
    410     }
    411 
    412 
    413     private List parseKeepClassSpecificationArguments(List    keepClassSpecifications,
    414                                                       boolean markClasses,
    415                                                       boolean markConditionally,
    416                                                       boolean allowShrinking)
    417     throws ParseException, IOException
    418     {
    419         // Create a new List if necessary.
    420         if (keepClassSpecifications == null)
    421         {
    422             keepClassSpecifications = new ArrayList();
    423         }
    424 
    425         //boolean allowShrinking    = false;
    426         boolean allowOptimization = false;
    427         boolean allowObfuscation  = false;
    428 
    429         // Read the keep modifiers.
    430         while (true)
    431         {
    432             readNextWord("keyword '" + ConfigurationConstants.CLASS_KEYWORD +
    433                          "', '"      + ClassConstants.EXTERNAL_ACC_INTERFACE +
    434                          "', or '"   + ClassConstants.EXTERNAL_ACC_ENUM + "'", true);
    435 
    436             if (!ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD.equals(nextWord))
    437             {
    438                 // Not a comma. Stop parsing the keep modifiers.
    439                 break;
    440             }
    441 
    442             readNextWord("keyword '" + ConfigurationConstants.ALLOW_SHRINKING_SUBOPTION +
    443                          "', '"      + ConfigurationConstants.ALLOW_OPTIMIZATION_SUBOPTION +
    444                          "', or '"   + ConfigurationConstants.ALLOW_OBFUSCATION_SUBOPTION + "'");
    445 
    446             if      (ConfigurationConstants.ALLOW_SHRINKING_SUBOPTION   .startsWith(nextWord))
    447             {
    448                 allowShrinking    = true;
    449             }
    450             else if (ConfigurationConstants.ALLOW_OPTIMIZATION_SUBOPTION.startsWith(nextWord))
    451             {
    452                 allowOptimization = true;
    453             }
    454             else if (ConfigurationConstants.ALLOW_OBFUSCATION_SUBOPTION .startsWith(nextWord))
    455             {
    456                 allowObfuscation  = true;
    457             }
    458             else
    459             {
    460                 throw new ParseException("Expecting keyword '" + ConfigurationConstants.ALLOW_SHRINKING_SUBOPTION +
    461                                          "', '"                + ConfigurationConstants.ALLOW_OPTIMIZATION_SUBOPTION +
    462                                          "', or '"             + ConfigurationConstants.ALLOW_OBFUSCATION_SUBOPTION +
    463                                          "' before " + reader.locationDescription());
    464             }
    465         }
    466 
    467         // Read the class configuration.
    468         ClassSpecification classSpecification =
    469             parseClassSpecificationArguments();
    470 
    471         // Create and add the keep configuration.
    472         keepClassSpecifications.add(new KeepClassSpecification(markClasses,
    473                                                                markConditionally,
    474                                                                allowShrinking,
    475                                                                allowOptimization,
    476                                                                allowObfuscation,
    477                                                                classSpecification));
    478         return keepClassSpecifications;
    479     }
    480 
    481 
    482     private List parseClassSpecificationArguments(List classSpecifications)
    483     throws ParseException, IOException
    484     {
    485         // Create a new List if necessary.
    486         if (classSpecifications == null)
    487         {
    488             classSpecifications = new ArrayList();
    489         }
    490 
    491         // Read and add the class configuration.
    492         readNextWord("keyword '" + ConfigurationConstants.CLASS_KEYWORD +
    493                      "', '"      + ClassConstants.EXTERNAL_ACC_INTERFACE +
    494                      "', or '"   + ClassConstants.EXTERNAL_ACC_ENUM + "'", true);
    495 
    496         classSpecifications.add(parseClassSpecificationArguments());
    497 
    498         return classSpecifications;
    499     }
    500 
    501 
    502     private ClassSpecification parseClassSpecificationArguments()
    503     throws ParseException, IOException
    504     {
    505         // Clear the annotation type.
    506         String annotationType = null;
    507 
    508         // Clear the class access modifiers.
    509         int requiredSetClassAccessFlags   = 0;
    510         int requiredUnsetClassAccessFlags = 0;
    511 
    512         // Parse the class annotations and access modifiers until the class keyword.
    513         while (!ConfigurationConstants.CLASS_KEYWORD.equals(nextWord))
    514         {
    515             // Parse the annotation type, if any.
    516 //            if (ConfigurationConstants.ANNOTATION_KEYWORD.equals(nextWord))
    517 //            {
    518 //                annotationType =
    519 //                    ClassUtil.internalType(
    520 //                    ListUtil.commaSeparatedString(
    521 //                    parseCommaSeparatedList("annotation type",
    522 //                                            true, false, false, true, false, null)));
    523 //
    524 //                continue;
    525 //            }
    526 
    527             // Strip the negating sign, if any.
    528             String strippedWord = nextWord.startsWith(ConfigurationConstants.NEGATOR_KEYWORD) ?
    529                 nextWord.substring(1) :
    530                 nextWord;
    531 
    532             // Parse the class access modifiers.
    533             // TODO: Distinguish annotation from annotation modifier.
    534             int accessFlag =
    535                 strippedWord.equals(ClassConstants.EXTERNAL_ACC_PUBLIC)     ? ClassConstants.INTERNAL_ACC_PUBLIC      :
    536                 strippedWord.equals(ClassConstants.EXTERNAL_ACC_FINAL)      ? ClassConstants.INTERNAL_ACC_FINAL       :
    537                 strippedWord.equals(ClassConstants.EXTERNAL_ACC_INTERFACE)  ? ClassConstants.INTERNAL_ACC_INTERFACE   :
    538                 strippedWord.equals(ClassConstants.EXTERNAL_ACC_ABSTRACT)   ? ClassConstants.INTERNAL_ACC_ABSTRACT    :
    539                 strippedWord.equals(ClassConstants.EXTERNAL_ACC_ANNOTATION) ? ClassConstants.INTERNAL_ACC_ANNOTATTION :
    540                 strippedWord.equals(ClassConstants.EXTERNAL_ACC_ENUM)       ? ClassConstants.INTERNAL_ACC_ENUM        :
    541                                                                               unknownAccessFlag();
    542 
    543             // Is it an annotation modifier?
    544             if (accessFlag == ClassConstants.INTERNAL_ACC_ANNOTATTION)
    545             {
    546                 // Is the next word actually an annotation type?
    547                 readNextWord("annotation type or keyword '" + ClassConstants.EXTERNAL_ACC_INTERFACE + "'", false);
    548 
    549                 if (!nextWord.equals(ClassConstants.EXTERNAL_ACC_INTERFACE) &&
    550                     !nextWord.equals(ClassConstants.EXTERNAL_ACC_ENUM)      &&
    551                     !nextWord.equals(ConfigurationConstants.CLASS_KEYWORD))
    552                 {
    553                     // Parse the annotation type.
    554                     annotationType =
    555                         ListUtil.commaSeparatedString(
    556                         parseCommaSeparatedList("annotation type",
    557                                                 false, false, false, true, false, false, true, null));
    558 
    559                     continue;
    560                 }
    561             }
    562 
    563             if (strippedWord.equals(nextWord))
    564             {
    565                 requiredSetClassAccessFlags   |= accessFlag;
    566             }
    567             else
    568             {
    569                 requiredUnsetClassAccessFlags |= accessFlag;
    570             }
    571 
    572 
    573             if ((requiredSetClassAccessFlags &
    574                  requiredUnsetClassAccessFlags) != 0)
    575             {
    576                 throw new ParseException("Conflicting class access modifiers for '" + strippedWord +
    577                                          "' before " + reader.locationDescription());
    578             }
    579 
    580             if (strippedWord.equals(ClassConstants.EXTERNAL_ACC_INTERFACE) ||
    581                 strippedWord.equals(ClassConstants.EXTERNAL_ACC_ENUM)      ||
    582                 strippedWord.equals(ConfigurationConstants.CLASS_KEYWORD))
    583             {
    584                 // The interface or enum keyword. Stop parsing the class flags.
    585                 break;
    586             }
    587 
    588             readNextWord("keyword '" + ConfigurationConstants.CLASS_KEYWORD +
    589                          "', '"      + ClassConstants.EXTERNAL_ACC_INTERFACE +
    590                          "', or '"   + ClassConstants.EXTERNAL_ACC_ENUM + "'", true);
    591         }
    592 
    593        // Parse the class name part.
    594         String externalClassName =
    595             ListUtil.commaSeparatedString(
    596             parseCommaSeparatedList("class name or interface name",
    597                                     true, false, false, true, false, false, false, null));
    598 
    599         // For backward compatibility, allow a single "*" wildcard to match any
    600         // class.
    601         String className = ConfigurationConstants.ANY_CLASS_KEYWORD.equals(externalClassName) ?
    602             null :
    603             ClassUtil.internalClassName(externalClassName);
    604 
    605         // Clear the annotation type and the class name of the extends part.
    606         String extendsAnnotationType = null;
    607         String extendsClassName      = null;
    608 
    609         if (!configurationEnd())
    610         {
    611             // Parse 'implements ...' or 'extends ...' part, if any.
    612             if (ConfigurationConstants.IMPLEMENTS_KEYWORD.equals(nextWord) ||
    613                 ConfigurationConstants.EXTENDS_KEYWORD.equals(nextWord))
    614             {
    615                 readNextWord("class name or interface name", true);
    616 
    617                 // Parse the annotation type, if any.
    618                 if (ConfigurationConstants.ANNOTATION_KEYWORD.equals(nextWord))
    619                 {
    620                     extendsAnnotationType =
    621                         ListUtil.commaSeparatedString(
    622                         parseCommaSeparatedList("annotation type",
    623                                                 true, false, false, true, false, false, true, null));
    624                 }
    625 
    626                 String externalExtendsClassName =
    627                     ListUtil.commaSeparatedString(
    628                     parseCommaSeparatedList("class name or interface name",
    629                                             false, false, false, true, false, false, false, null));
    630 
    631                 extendsClassName = ConfigurationConstants.ANY_CLASS_KEYWORD.equals(externalExtendsClassName) ?
    632                     null :
    633                     ClassUtil.internalClassName(externalExtendsClassName);
    634             }
    635         }
    636 
    637         // Create the basic class specification.
    638         ClassSpecification classSpecification =
    639             new ClassSpecification(lastComments,
    640                                    requiredSetClassAccessFlags,
    641                                    requiredUnsetClassAccessFlags,
    642                                    annotationType,
    643                                    className,
    644                                    extendsAnnotationType,
    645                                    extendsClassName);
    646 
    647 
    648         // Now add any class members to this class specification.
    649         if (!configurationEnd())
    650         {
    651             // Check the class member opening part.
    652             if (!ConfigurationConstants.OPEN_KEYWORD.equals(nextWord))
    653             {
    654                 throw new ParseException("Expecting opening '" + ConfigurationConstants.OPEN_KEYWORD +
    655                                          "' at " + reader.locationDescription());
    656             }
    657 
    658             // Parse all class members.
    659             while (true)
    660             {
    661                 readNextWord("class member description" +
    662                              " or closing '" + ConfigurationConstants.CLOSE_KEYWORD + "'", true);
    663 
    664                 if (nextWord.equals(ConfigurationConstants.CLOSE_KEYWORD))
    665                 {
    666                     // The closing brace. Stop parsing the class members.
    667                     readNextWord();
    668 
    669                     break;
    670                 }
    671 
    672                 parseMemberSpecificationArguments(externalClassName,
    673                                                   classSpecification);
    674             }
    675         }
    676 
    677         return classSpecification;
    678     }
    679 
    680 
    681     private void parseMemberSpecificationArguments(String             externalClassName,
    682                                                    ClassSpecification classSpecification)
    683     throws ParseException, IOException
    684     {
    685         // Clear the annotation name.
    686         String annotationType = null;
    687 
    688         // Parse the class member access modifiers, if any.
    689         int requiredSetMemberAccessFlags   = 0;
    690         int requiredUnsetMemberAccessFlags = 0;
    691 
    692         while (!configurationEnd(true))
    693         {
    694             // Parse the annotation type, if any.
    695             if (ConfigurationConstants.ANNOTATION_KEYWORD.equals(nextWord))
    696             {
    697                 annotationType =
    698                     ListUtil.commaSeparatedString(
    699                     parseCommaSeparatedList("annotation type",
    700                                             true, false, false, true, false, false, true, null));
    701 
    702                 continue;
    703             }
    704 
    705             String strippedWord = nextWord.startsWith("!") ?
    706                 nextWord.substring(1) :
    707                 nextWord;
    708 
    709             // Parse the class member access modifiers.
    710             int accessFlag =
    711                 strippedWord.equals(ClassConstants.EXTERNAL_ACC_PUBLIC)       ? ClassConstants.INTERNAL_ACC_PUBLIC       :
    712                 strippedWord.equals(ClassConstants.EXTERNAL_ACC_PRIVATE)      ? ClassConstants.INTERNAL_ACC_PRIVATE      :
    713                 strippedWord.equals(ClassConstants.EXTERNAL_ACC_PROTECTED)    ? ClassConstants.INTERNAL_ACC_PROTECTED    :
    714                 strippedWord.equals(ClassConstants.EXTERNAL_ACC_STATIC)       ? ClassConstants.INTERNAL_ACC_STATIC       :
    715                 strippedWord.equals(ClassConstants.EXTERNAL_ACC_FINAL)        ? ClassConstants.INTERNAL_ACC_FINAL        :
    716                 strippedWord.equals(ClassConstants.EXTERNAL_ACC_SYNCHRONIZED) ? ClassConstants.INTERNAL_ACC_SYNCHRONIZED :
    717                 strippedWord.equals(ClassConstants.EXTERNAL_ACC_VOLATILE)     ? ClassConstants.INTERNAL_ACC_VOLATILE     :
    718                 strippedWord.equals(ClassConstants.EXTERNAL_ACC_TRANSIENT)    ? ClassConstants.INTERNAL_ACC_TRANSIENT    :
    719                 strippedWord.equals(ClassConstants.EXTERNAL_ACC_NATIVE)       ? ClassConstants.INTERNAL_ACC_NATIVE       :
    720                 strippedWord.equals(ClassConstants.EXTERNAL_ACC_ABSTRACT)     ? ClassConstants.INTERNAL_ACC_ABSTRACT     :
    721                 strippedWord.equals(ClassConstants.EXTERNAL_ACC_STRICT)       ? ClassConstants.INTERNAL_ACC_STRICT       :
    722                                                                                 0;
    723             if (accessFlag == 0)
    724             {
    725                 // Not a class member access modifier. Stop parsing them.
    726                 break;
    727             }
    728 
    729             if (strippedWord.equals(nextWord))
    730             {
    731                 requiredSetMemberAccessFlags   |= accessFlag;
    732             }
    733             else
    734             {
    735                 requiredUnsetMemberAccessFlags |= accessFlag;
    736             }
    737 
    738             // Make sure the user doesn't try to set and unset the same
    739             // access flags simultaneously.
    740             if ((requiredSetMemberAccessFlags &
    741                  requiredUnsetMemberAccessFlags) != 0)
    742             {
    743                 throw new ParseException("Conflicting class member access modifiers for " +
    744                                          reader.locationDescription());
    745             }
    746 
    747             readNextWord("class member description");
    748         }
    749 
    750         // Parse the class member type and name part.
    751 
    752         // Did we get a special wildcard?
    753         if (ConfigurationConstants.ANY_CLASS_MEMBER_KEYWORD.equals(nextWord) ||
    754             ConfigurationConstants.ANY_FIELD_KEYWORD       .equals(nextWord) ||
    755             ConfigurationConstants.ANY_METHOD_KEYWORD      .equals(nextWord))
    756         {
    757             // Act according to the type of wildcard..
    758             if (ConfigurationConstants.ANY_CLASS_MEMBER_KEYWORD.equals(nextWord))
    759             {
    760                 checkFieldAccessFlags(requiredSetMemberAccessFlags,
    761                                       requiredUnsetMemberAccessFlags);
    762                 checkMethodAccessFlags(requiredSetMemberAccessFlags,
    763                                        requiredUnsetMemberAccessFlags);
    764 
    765                 classSpecification.addField(
    766                     new MemberSpecification(requiredSetMemberAccessFlags,
    767                                             requiredUnsetMemberAccessFlags,
    768                                             annotationType,
    769                                             null,
    770                                             null));
    771                 classSpecification.addMethod(
    772                     new MemberSpecification(requiredSetMemberAccessFlags,
    773                                             requiredUnsetMemberAccessFlags,
    774                                             annotationType,
    775                                             null,
    776                                             null));
    777             }
    778             else if (ConfigurationConstants.ANY_FIELD_KEYWORD.equals(nextWord))
    779             {
    780                 checkFieldAccessFlags(requiredSetMemberAccessFlags,
    781                                       requiredUnsetMemberAccessFlags);
    782 
    783                 classSpecification.addField(
    784                     new MemberSpecification(requiredSetMemberAccessFlags,
    785                                             requiredUnsetMemberAccessFlags,
    786                                             annotationType,
    787                                             null,
    788                                             null));
    789             }
    790             else if (ConfigurationConstants.ANY_METHOD_KEYWORD.equals(nextWord))
    791             {
    792                 checkMethodAccessFlags(requiredSetMemberAccessFlags,
    793                                        requiredUnsetMemberAccessFlags);
    794 
    795                 classSpecification.addMethod(
    796                     new MemberSpecification(requiredSetMemberAccessFlags,
    797                                             requiredUnsetMemberAccessFlags,
    798                                             annotationType,
    799                                             null,
    800                                             null));
    801             }
    802 
    803             // We still have to read the closing separator.
    804             readNextWord("separator '" + ConfigurationConstants.SEPARATOR_KEYWORD + "'");
    805 
    806             if (!ConfigurationConstants.SEPARATOR_KEYWORD.equals(nextWord))
    807             {
    808                 throw new ParseException("Expecting separator '" + ConfigurationConstants.SEPARATOR_KEYWORD +
    809                                          "' before " + reader.locationDescription());
    810             }
    811         }
    812         else
    813         {
    814             // Make sure we have a proper type.
    815             checkJavaIdentifier("java type");
    816             String type = nextWord;
    817 
    818             readNextWord("class member name");
    819             String name = nextWord;
    820 
    821             // Did we get just one word before the opening parenthesis?
    822             if (ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD.equals(name))
    823             {
    824                 // This must be a constructor then.
    825                 // Make sure the type is a proper constructor name.
    826                 if (!(type.equals(ClassConstants.INTERNAL_METHOD_NAME_INIT) ||
    827                       type.equals(externalClassName) ||
    828                       type.equals(ClassUtil.externalShortClassName(externalClassName))))
    829                 {
    830                     throw new ParseException("Expecting type and name " +
    831                                              "instead of just '" + type +
    832                                              "' before " + reader.locationDescription());
    833                 }
    834 
    835                 // Assign the fixed constructor type and name.
    836                 type = ClassConstants.EXTERNAL_TYPE_VOID;
    837                 name = ClassConstants.INTERNAL_METHOD_NAME_INIT;
    838             }
    839             else
    840             {
    841                 // It's not a constructor.
    842                 // Make sure we have a proper name.
    843                 checkJavaIdentifier("class member name");
    844 
    845                 // Read the opening parenthesis or the separating
    846                 // semi-colon.
    847                 readNextWord("opening '" + ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD +
    848                              "' or separator '" + ConfigurationConstants.SEPARATOR_KEYWORD + "'");
    849             }
    850 
    851             // Are we looking at a field, a method, or something else?
    852             if (ConfigurationConstants.SEPARATOR_KEYWORD.equals(nextWord))
    853             {
    854                 // It's a field.
    855                 checkFieldAccessFlags(requiredSetMemberAccessFlags,
    856                                       requiredUnsetMemberAccessFlags);
    857 
    858                 // We already have a field descriptor.
    859                 String descriptor = ClassUtil.internalType(type);
    860 
    861                 // Add the field.
    862                 classSpecification.addField(
    863                     new MemberSpecification(requiredSetMemberAccessFlags,
    864                                             requiredUnsetMemberAccessFlags,
    865                                             annotationType,
    866                                             name,
    867                                             descriptor));
    868             }
    869             else if (ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD.equals(nextWord))
    870             {
    871                 // It's a method.
    872                 checkMethodAccessFlags(requiredSetMemberAccessFlags,
    873                                        requiredUnsetMemberAccessFlags);
    874 
    875                 // Parse the method arguments.
    876                 String descriptor =
    877                     ClassUtil.internalMethodDescriptor(type,
    878                                                        parseCommaSeparatedList("argument", true, true, true, true, false, false, false, null));
    879 
    880                 if (!ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD.equals(nextWord))
    881                 {
    882                     throw new ParseException("Expecting separating '" + ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD +
    883                                              "' or closing '" + ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD +
    884                                              "' before " + reader.locationDescription());
    885                 }
    886 
    887                 // Read the separator after the closing parenthesis.
    888                 readNextWord("separator '" + ConfigurationConstants.SEPARATOR_KEYWORD + "'");
    889 
    890                 if (!ConfigurationConstants.SEPARATOR_KEYWORD.equals(nextWord))
    891                 {
    892                     throw new ParseException("Expecting separator '" + ConfigurationConstants.SEPARATOR_KEYWORD +
    893                                              "' before " + reader.locationDescription());
    894                 }
    895 
    896                 // Add the method.
    897                 classSpecification.addMethod(
    898                     new MemberSpecification(requiredSetMemberAccessFlags,
    899                                             requiredUnsetMemberAccessFlags,
    900                                             annotationType,
    901                                             name,
    902                                             descriptor));
    903             }
    904             else
    905             {
    906                 // It doesn't look like a field or a method.
    907                 throw new ParseException("Expecting opening '" + ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD +
    908                                          "' or separator '" + ConfigurationConstants.SEPARATOR_KEYWORD +
    909                                          "' before " + reader.locationDescription());
    910             }
    911         }
    912     }
    913 
    914 
    915     /**
    916      * Reads a comma-separated list of java identifiers or of file names. If an
    917      * empty list is allowed, the reading will end after a closing parenthesis
    918      * or semi-colon.
    919      */
    920     private List parseCommaSeparatedList(String  expectedDescription,
    921                                          boolean readFirstWord,
    922                                          boolean allowEmptyList,
    923                                          boolean expectClosingParenthesis,
    924                                          boolean checkJavaIdentifiers,
    925                                          boolean replaceSystemProperties,
    926                                          boolean replaceExternalClassNames,
    927                                          boolean replaceExternalTypes,
    928                                          List    list)
    929     throws ParseException, IOException
    930     {
    931         if (list == null)
    932         {
    933             list = new ArrayList();
    934         }
    935 
    936         if (readFirstWord)
    937         {
    938             if (expectClosingParenthesis || !allowEmptyList)
    939             {
    940                 // Read the first list entry.
    941                 readNextWord(expectedDescription);
    942             }
    943             else
    944             {
    945                 // Read the first list entry, if there is any.
    946                 readNextWord();
    947 
    948                 // Check if the list is empty.
    949                 if (configurationEnd() ||
    950                     nextWord.equals(ConfigurationConstants.ANY_ATTRIBUTE_KEYWORD))
    951                 {
    952                     return list;
    953                 }
    954             }
    955         }
    956 
    957         while (true)
    958         {
    959             if (expectClosingParenthesis &&
    960                 list.size() == 0         &&
    961                 (ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD.equals(nextWord) ||
    962                  ConfigurationConstants.SEPARATOR_KEYWORD.equals(nextWord)))
    963             {
    964                 break;
    965             }
    966 
    967             if (checkJavaIdentifiers)
    968             {
    969                 checkJavaIdentifier("java type");
    970             }
    971 
    972             if (replaceSystemProperties)
    973             {
    974                 nextWord = replaceSystemProperties(nextWord);
    975             }
    976 
    977             if (replaceExternalClassNames)
    978             {
    979                 nextWord = ClassUtil.internalClassName(nextWord);
    980             }
    981 
    982             if (replaceExternalTypes)
    983             {
    984                 nextWord = ClassUtil.internalType(nextWord);
    985             }
    986 
    987             list.add(nextWord);
    988 
    989             if (expectClosingParenthesis)
    990             {
    991                 // Read a comma (or a closing parenthesis, or a different word).
    992                 readNextWord("separating '" + ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD +
    993                              "' or closing '" + ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD +
    994                              "'");
    995             }
    996             else
    997             {
    998                 // Read a comma (or a different word).
    999                 readNextWord();
   1000             }
   1001 
   1002             if (!ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD.equals(nextWord))
   1003             {
   1004                 break;
   1005             }
   1006 
   1007             // Read the next list entry.
   1008             readNextWord(expectedDescription);
   1009         }
   1010 
   1011         return list;
   1012     }
   1013 
   1014 
   1015     /**
   1016      * Throws a ParseException for an unexpected keyword.
   1017      */
   1018     private int unknownAccessFlag() throws ParseException
   1019     {
   1020         throw new ParseException("Unexpected keyword " + reader.locationDescription());
   1021     }
   1022 
   1023 
   1024     /**
   1025      * Creates a properly resolved File, based on the given word.
   1026      */
   1027     private File file(String word) throws ParseException
   1028     {
   1029         String fileName = replaceSystemProperties(word);
   1030         File   file     = new File(fileName);
   1031 
   1032         // Try to get an absolute file.
   1033         if (!file.isAbsolute())
   1034         {
   1035             file = new File(reader.getBaseDir(), fileName);
   1036         }
   1037 
   1038         // Try to get a canonical representation.
   1039         try
   1040         {
   1041             file = file.getCanonicalFile();
   1042         }
   1043         catch (IOException ex)
   1044         {
   1045             // Just keep the original representation.
   1046         }
   1047 
   1048         return file;
   1049     }
   1050 
   1051 
   1052     /**
   1053      * Replaces any system properties in the given word by their values
   1054      * (e.g. the substring "<java.home>" is replaced by its value).
   1055      */
   1056     private String replaceSystemProperties(String word) throws ParseException
   1057     {
   1058         int fromIndex = 0;
   1059         while (true)
   1060         {
   1061             fromIndex = word.indexOf(ConfigurationConstants.OPEN_SYSTEM_PROPERTY, fromIndex);
   1062             if (fromIndex < 0)
   1063             {
   1064                 break;
   1065             }
   1066 
   1067             int toIndex = word.indexOf(ConfigurationConstants.CLOSE_SYSTEM_PROPERTY, fromIndex+1);
   1068             if (toIndex < 0)
   1069             {
   1070                 throw new ParseException("Expecting closing '" + ConfigurationConstants.CLOSE_SYSTEM_PROPERTY +
   1071                                          "' after opening '" + ConfigurationConstants.OPEN_SYSTEM_PROPERTY +
   1072                                          "' in " + reader.locationDescription());
   1073             }
   1074 
   1075             String propertyName  = word.substring(fromIndex+1, toIndex);
   1076             String propertyValue = System.getProperty(propertyName);
   1077             if (propertyValue == null)
   1078             {
   1079                 throw new ParseException("Value of system property '" + propertyName +
   1080                                          "' is undefined in " + reader.locationDescription());
   1081             }
   1082 
   1083             word = word.substring(0, fromIndex) +
   1084                        propertyValue +
   1085                        word.substring(toIndex+1);
   1086         }
   1087 
   1088         return word;
   1089     }
   1090 
   1091 
   1092     /**
   1093      * Reads the next word of the configuration in the 'nextWord' field,
   1094      * throwing an exception if there is no next word.
   1095      */
   1096     private void readNextWord(String expectedDescription)
   1097     throws ParseException, IOException
   1098     {
   1099         readNextWord(expectedDescription, false);
   1100     }
   1101 
   1102 
   1103     /**
   1104      * Reads the next word of the configuration in the 'nextWord' field,
   1105      * throwing an exception if there is no next word.
   1106      */
   1107     private void readNextWord(String  expectedDescription,
   1108                               boolean expectingAtCharacter)
   1109     throws ParseException, IOException
   1110     {
   1111         readNextWord();
   1112         if (configurationEnd(expectingAtCharacter))
   1113         {
   1114             throw new ParseException("Expecting " + expectedDescription +
   1115                                      " before " + reader.locationDescription());
   1116         }
   1117     }
   1118 
   1119 
   1120     /**
   1121      * Reads the next word of the configuration in the 'nextWord' field.
   1122      */
   1123     private void readNextWord() throws IOException
   1124     {
   1125         nextWord = reader.nextWord();
   1126     }
   1127 
   1128 
   1129     /**
   1130      * Returns whether the end of the configuration has been reached.
   1131      */
   1132     private boolean configurationEnd()
   1133     {
   1134         return configurationEnd(false);
   1135     }
   1136 
   1137 
   1138     /**
   1139      * Returns whether the end of the configuration has been reached.
   1140      */
   1141     private boolean configurationEnd(boolean expectingAtCharacter)
   1142     {
   1143         return nextWord == null ||
   1144                nextWord.startsWith(ConfigurationConstants.OPTION_PREFIX) ||
   1145                (!expectingAtCharacter &&
   1146                 nextWord.equals(ConfigurationConstants.AT_DIRECTIVE));
   1147     }
   1148 
   1149 
   1150     /**
   1151      * Checks whether the given word is a valid Java identifier and throws
   1152      * a ParseException if it isn't. Wildcard characters are accepted.
   1153      */
   1154     private void checkJavaIdentifier(String expectedDescription)
   1155     throws ParseException
   1156     {
   1157         if (!isJavaIdentifier(nextWord))
   1158         {
   1159             throw new ParseException("Expecting " + expectedDescription +
   1160                                      " before " + reader.locationDescription());
   1161         }
   1162     }
   1163 
   1164 
   1165     /**
   1166      * Returns whether the given word is a valid Java identifier.
   1167      * Wildcard characters are accepted.
   1168      */
   1169     private boolean isJavaIdentifier(String aWord)
   1170     {
   1171         for (int index = 0; index < aWord.length(); index++)
   1172         {
   1173             char c = aWord.charAt(index);
   1174             if (!(Character.isJavaIdentifierPart(c) ||
   1175                   c == '.' ||
   1176                   c == '[' ||
   1177                   c == ']' ||
   1178                   c == '<' ||
   1179                   c == '>' ||
   1180                   c == '-' ||
   1181                   c == '!' ||
   1182                   c == '*' ||
   1183                   c == '?' ||
   1184                   c == '%'))
   1185             {
   1186                 return false;
   1187             }
   1188         }
   1189 
   1190         return true;
   1191     }
   1192 
   1193 
   1194     /**
   1195      * Checks whether the given access flags are valid field access flags,
   1196      * throwing a ParseException if they aren't.
   1197      */
   1198     private void checkFieldAccessFlags(int requiredSetMemberAccessFlags,
   1199                                        int requiredUnsetMemberAccessFlags)
   1200     throws ParseException
   1201     {
   1202         if (((requiredSetMemberAccessFlags |
   1203               requiredUnsetMemberAccessFlags) &
   1204             ~ClassConstants.VALID_INTERNAL_ACC_FIELD) != 0)
   1205         {
   1206             throw new ParseException("Invalid method access modifier for field before " +
   1207                                      reader.locationDescription());
   1208         }
   1209     }
   1210 
   1211 
   1212     /**
   1213      * Checks whether the given access flags are valid method access flags,
   1214      * throwing a ParseException if they aren't.
   1215      */
   1216     private void checkMethodAccessFlags(int requiredSetMemberAccessFlags,
   1217                                         int requiredUnsetMemberAccessFlags)
   1218     throws ParseException
   1219     {
   1220         if (((requiredSetMemberAccessFlags |
   1221               requiredUnsetMemberAccessFlags) &
   1222             ~ClassConstants.VALID_INTERNAL_ACC_METHOD) != 0)
   1223         {
   1224             throw new ParseException("Invalid field access modifier for method before " +
   1225                                      reader.locationDescription());
   1226         }
   1227     }
   1228 
   1229 
   1230     /**
   1231      * A main method for testing configuration parsing.
   1232      */
   1233     public static void main(String[] args)
   1234     {
   1235         try
   1236         {
   1237             ConfigurationParser parser = new ConfigurationParser(args);
   1238 
   1239             try
   1240             {
   1241                 parser.parse(new Configuration());
   1242             }
   1243             catch (ParseException ex)
   1244             {
   1245                 ex.printStackTrace();
   1246             }
   1247             finally
   1248             {
   1249                 parser.close();
   1250             }
   1251         }
   1252         catch (IOException ex)
   1253         {
   1254             ex.printStackTrace();
   1255         }
   1256     }
   1257 }
   1258