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