KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > proguard > ConfigurationParser


1 /*
2  * ProGuard -- shrinking, optimization, obfuscation, and preverification
3  * of Java bytecode.
4  *
5  * Copyright (c) 2002-2007 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.ClassConstants;
24 import proguard.classfile.util.ClassUtil;
25 import proguard.util.ListUtil;
26
27 import java.io.*;
28 import java.util.*;
29 import java.net.URL JavaDoc;
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 JavaDoc nextWord;
42     private String JavaDoc lastComments;
43
44
45     /**
46      * Creates a new ConfigurationParser for the given String arguments.
47      */

48     public ConfigurationParser(String JavaDoc[] 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 JavaDoc[] 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 JavaDoc 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 = parseKeepSpecificationArguments(configuration.keep, true, false, false);
119             else if (ConfigurationConstants.KEEP_CLASS_MEMBERS_OPTION .startsWith(nextWord)) configuration.keep = parseKeepSpecificationArguments(configuration.keep, false, false, false);
120             else if (ConfigurationConstants.KEEP_CLASSES_WITH_MEMBERS_OPTION .startsWith(nextWord)) configuration.keep = parseKeepSpecificationArguments(configuration.keep, false, true, false);
121             else if (ConfigurationConstants.KEEP_NAMES_OPTION .startsWith(nextWord)) configuration.keep = parseKeepSpecificationArguments(configuration.keep, true, false, true);
122             else if (ConfigurationConstants.KEEP_CLASS_MEMBER_NAMES_OPTION .startsWith(nextWord)) configuration.keep = parseKeepSpecificationArguments(configuration.keep, false, false, true);
123             else if (ConfigurationConstants.KEEP_CLASSES_WITH_MEMBER_NAMES_OPTION .startsWith(nextWord)) configuration.keep = parseKeepSpecificationArguments(configuration.keep, false, true, true);
124             else if (ConfigurationConstants.PRINT_SEEDS_OPTION .startsWith(nextWord)) configuration.printSeeds = parseOptionalFile();
125
126             else if (ConfigurationConstants.DONT_SHRINK_OPTION .startsWith(nextWord)) configuration.shrink = parseNoArgument(false);
127             else if (ConfigurationConstants.PRINT_USAGE_OPTION .startsWith(nextWord)) configuration.printUsage = parseOptionalFile();
128             else if (ConfigurationConstants.WHY_ARE_YOU_KEEPING_OPTION .startsWith(nextWord)) configuration.whyAreYouKeeping = parseClassSpecificationArguments(configuration.whyAreYouKeeping);
129
130             else if (ConfigurationConstants.DONT_OPTIMIZE_OPTION .startsWith(nextWord)) configuration.optimize = parseNoArgument(false);
131             else if (ConfigurationConstants.OPTIMIZATION_PASSES .startsWith(nextWord)) configuration.optimizationPasses = parseIntegerArgument();
132             else if (ConfigurationConstants.ASSUME_NO_SIDE_EFFECTS_OPTION .startsWith(nextWord)) configuration.assumeNoSideEffects = parseClassSpecificationArguments(configuration.assumeNoSideEffects);
133             else if (ConfigurationConstants.ALLOW_ACCESS_MODIFICATION_OPTION .startsWith(nextWord)) configuration.allowAccessModification = parseNoArgument(true);
134
135             else if (ConfigurationConstants.DONT_OBFUSCATE_OPTION .startsWith(nextWord)) configuration.obfuscate = parseNoArgument(false);
136             else if (ConfigurationConstants.PRINT_MAPPING_OPTION .startsWith(nextWord)) configuration.printMapping = parseOptionalFile();
137             else if (ConfigurationConstants.APPLY_MAPPING_OPTION .startsWith(nextWord)) configuration.applyMapping = parseFile();
138             else if (ConfigurationConstants.OBFUSCATION_DICTIONARY_OPTION .startsWith(nextWord)) configuration.obfuscationDictionary = parseFile();
139             else if (ConfigurationConstants.OVERLOAD_AGGRESSIVELY_OPTION .startsWith(nextWord)) configuration.overloadAggressively = parseNoArgument(true);
140             else if (ConfigurationConstants.USE_UNIQUE_CLASS_MEMBER_NAMES_OPTION .startsWith(nextWord)) configuration.useUniqueClassMemberNames = parseNoArgument(true);
141             else if (ConfigurationConstants.DONT_USE_MIXED_CASE_CLASS_NAMES_OPTION .startsWith(nextWord)) configuration.useMixedCaseClassNames = parseNoArgument(false);
142             else if (ConfigurationConstants.FLATTEN_PACKAGE_HIERARCHY_OPTION .startsWith(nextWord)) configuration.flattenPackageHierarchy = ClassUtil.internalClassName(parseOptionalArgument());
143             else if (ConfigurationConstants.REPACKAGE_CLASSES_OPTION .startsWith(nextWord)) configuration.repackageClasses = ClassUtil.internalClassName(parseOptionalArgument());
144             else if (ConfigurationConstants.DEFAULT_PACKAGE_OPTION .startsWith(nextWord)) configuration.repackageClasses = ClassUtil.internalClassName(parseOptionalArgument());
145             else if (ConfigurationConstants.KEEP_ATTRIBUTES_OPTION .startsWith(nextWord)) configuration.keepAttributes = parseCommaSeparatedList("attribute name", true, true, false, true, false, configuration.keepAttributes);
146             else if (ConfigurationConstants.RENAME_SOURCE_FILE_ATTRIBUTE_OPTION .startsWith(nextWord)) configuration.newSourceFileAttribute = parseOptionalArgument();
147             else if (ConfigurationConstants.ADAPT_RESOURCE_FILE_NAMES_OPTION .startsWith(nextWord)) configuration.adaptResourceFileNames = parseCommaSeparatedList("resource file name", true, true, false, false, false, configuration.adaptResourceFileNames);
148             else if (ConfigurationConstants.ADAPT_RESOURCE_FILE_CONTENTS_OPTION .startsWith(nextWord)) configuration.adaptResourceFileContents = parseCommaSeparatedList("resource file name", true, true, false, false, false, configuration.adaptResourceFileContents);
149
150             else if (ConfigurationConstants.DONT_PREVERIFY_OPTION .startsWith(nextWord)) configuration.preverify = parseNoArgument(false);
151             else if (ConfigurationConstants.MICRO_EDITION_OPTION .startsWith(nextWord)) configuration.microEdition = parseNoArgument(true);
152
153             else if (ConfigurationConstants.VERBOSE_OPTION .startsWith(nextWord)) configuration.verbose = parseNoArgument(true);
154             else if (ConfigurationConstants.DONT_NOTE_OPTION .startsWith(nextWord)) configuration.note = parseNoArgument(false);
155             else if (ConfigurationConstants.DONT_WARN_OPTION .startsWith(nextWord)) configuration.warn = parseNoArgument(false);
156             else if (ConfigurationConstants.IGNORE_WARNINGS_OPTION .startsWith(nextWord)) configuration.ignoreWarnings = parseNoArgument(true);
157             else if (ConfigurationConstants.PRINT_CONFIGURATION_OPTION .startsWith(nextWord)) configuration.printConfiguration = parseOptionalFile();
158             else if (ConfigurationConstants.DUMP_OPTION .startsWith(nextWord)) configuration.dump = parseOptionalFile();
159             else
160             {
161                 throw new ParseException("Unknown option " + reader.locationDescription());
162             }
163         }
164     }
165
166
167
168     /**
169      * Closes the configuration.
170      * @throws IOException if an IO error occurs while closing the configuration.
171      */

172     public void close() throws IOException
173     {
174         if (reader != null)
175         {
176             reader.close();
177         }
178     }
179
180
181     private long parseIncludeArgument(long lastModified) throws ParseException, IOException
182     {
183         // Read the configuation file name.
184
readNextWord("configuration file name");
185
186         File file = file(nextWord);
187         reader.includeWordReader(new FileWordReader(file));
188
189         readNextWord();
190
191         return Math.max(lastModified, file.lastModified());
192     }
193
194
195     private void parseBaseDirectoryArgument() throws ParseException, IOException
196     {
197         // Read the base directory name.
198
readNextWord("base directory name");
199
200         reader.setBaseDir(file(nextWord));
201
202         readNextWord();
203     }
204
205
206     private ClassPath parseClassPathArgument(ClassPath classPath,
207                                              boolean isOutput)
208     throws ParseException, IOException
209     {
210         // Create a new List if necessary.
211
if (classPath == null)
212         {
213             classPath = new ClassPath();
214         }
215
216         while (true)
217         {
218             // Read the next jar name.
219
readNextWord("jar or directory name");
220
221             // Create a new class path entry.
222
ClassPathEntry entry = new ClassPathEntry(file(nextWord), isOutput);
223
224             // Read the opening parenthesis or the separator, if any.
225
readNextWord();
226
227             // Read the optional filters.
228
if (!configurationEnd() &&
229                 ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD.equals(nextWord))
230             {
231                 // Read all filters in an array.
232
String JavaDoc[] filters = new String JavaDoc[5];
233
234                 int counter = 0;
235                 do
236                 {
237                     // Read the filter.
238
filters[counter++] =
239                         ListUtil.commaSeparatedString(
240                         parseCommaSeparatedList("filter", true,
241                                                 false, true, false, true, null));
242                 }
243                 while (counter < filters.length &&
244                        ConfigurationConstants.SEPARATOR_KEYWORD.equals(nextWord));
245
246                 // Make sure there is a closing parenthesis.
247
if (!ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD.equals(nextWord))
248                 {
249                     throw new ParseException("Expecting separating '" + ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD +
250                                              "' or '" + ConfigurationConstants.SEPARATOR_KEYWORD +
251                                              "', or closing '" + ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD +
252                                              "' before " + reader.locationDescription());
253                 }
254
255                 // Set all filters from the array on the entry.
256
entry.setFilter(filters[--counter]);
257                 if (counter > 0)
258                 {
259                     entry.setJarFilter(filters[--counter]);
260                     if (counter > 0)
261                     {
262                         entry.setWarFilter(filters[--counter]);
263                         if (counter > 0)
264                         {
265                             entry.setEarFilter(filters[--counter]);
266                             if (counter > 0)
267                             {
268                                 entry.setZipFilter(filters[--counter]);
269                             }
270                         }
271                     }
272                 }
273
274                 // Read the separator, if any.
275
readNextWord();
276             }
277
278             // Add the entry to the list.
279
classPath.add(entry);
280
281             if (configurationEnd())
282             {
283                 return classPath;
284             }
285
286             if (!nextWord.equals(ConfigurationConstants.JAR_SEPARATOR_KEYWORD))
287             {
288                 throw new ParseException("Expecting class path separator '" + ConfigurationConstants.JAR_SEPARATOR_KEYWORD +
289                                          "' before " + reader.locationDescription());
290             }
291         }
292     }
293
294
295     private int parseClassVersion()
296     throws ParseException, IOException
297     {
298         // Read the obligatory target.
299
readNextWord("java version");
300
301         int classVersion = ClassUtil.internalClassVersion(nextWord);
302         if (classVersion == 0)
303         {
304             throw new ParseException("Unsupported java version " + reader.locationDescription());
305         }
306
307         readNextWord();
308
309         return classVersion;
310     }
311
312
313     private int parseIntegerArgument()
314     throws ParseException, IOException
315     {
316         try
317         {
318             // Read the obligatory integer.
319
readNextWord("integer");
320
321             int integer = Integer.parseInt(nextWord);
322
323             readNextWord();
324
325             return integer;
326         }
327         catch (NumberFormatException JavaDoc e)
328         {
329             throw new ParseException("Expecting integer argument instead of '" + nextWord +
330                                      "' before " + reader.locationDescription());
331         }
332     }
333
334
335     private File parseFile()
336     throws ParseException, IOException
337     {
338         // Read the obligatory file name.
339
readNextWord("file name");
340
341         // Make sure the file is properly resolved.
342
File file = file(nextWord);
343
344         readNextWord();
345
346         return file;
347     }
348
349
350     private File parseOptionalFile()
351     throws ParseException, IOException
352     {
353         // Read the optional file name.
354
readNextWord();
355
356         // Didn't the user specify a file name?
357
if (configurationEnd())
358         {
359             return new File("");
360         }
361
362         // Make sure the file is properly resolved.
363
File file = file(nextWord);
364
365         readNextWord();
366
367         return file;
368     }
369
370
371     private String JavaDoc parseOptionalArgument() throws IOException
372     {
373         // Read the optional argument.
374
readNextWord();
375
376         // Didn't the user specify an argument?
377
if (configurationEnd())
378         {
379             return "";
380         }
381
382         String JavaDoc fileName = nextWord;
383
384         readNextWord();
385
386         return fileName;
387     }
388
389
390     private boolean parseNoArgument(boolean value) throws IOException
391     {
392         readNextWord();
393
394         return value;
395     }
396
397
398     private long parseNoArgument(long value) throws IOException
399     {
400         readNextWord();
401
402         return value;
403     }
404
405
406     private List parseKeepSpecificationArguments(List keepSpecifications,
407                                                  boolean markClasses,
408                                                  boolean markConditionally,
409                                                  boolean allowShrinking)
410     throws ParseException, IOException
411     {
412         // Create a new List if necessary.
413
if (keepSpecifications == null)
414         {
415             keepSpecifications = new ArrayList();
416         }
417
418         //boolean allowShrinking = false;
419
boolean allowOptimization = false;
420         boolean allowObfuscation = false;
421
422         // Read the keep modifiers.
423
while (true)
424         {
425             readNextWord("keyword '" + ConfigurationConstants.CLASS_KEYWORD +
426                          "' or '" + ClassConstants.EXTERNAL_ACC_INTERFACE + "'", true);
427
428             if (!ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD.equals(nextWord))
429             {
430                 // Not a comma. Stop parsing the keep modifiers.
431
break;
432             }
433
434             readNextWord("keyword '" +
435                          ConfigurationConstants.ALLOW_SHRINKING_SUBOPTION + "', '" +
436                          ConfigurationConstants.ALLOW_OPTIMIZATION_SUBOPTION + "', or '" +
437                          ConfigurationConstants.ALLOW_OBFUSCATION_SUBOPTION + "'");
438
439             if (ConfigurationConstants.ALLOW_SHRINKING_SUBOPTION .startsWith(nextWord))
440             {
441                 allowShrinking = true;
442             }
443             else if (ConfigurationConstants.ALLOW_OPTIMIZATION_SUBOPTION.startsWith(nextWord))
444             {
445                 allowOptimization = true;
446             }
447             else if (ConfigurationConstants.ALLOW_OBFUSCATION_SUBOPTION .startsWith(nextWord))
448             {
449                 allowObfuscation = true;
450             }
451             else
452             {
453                 throw new ParseException("Expecting keyword '" +
454                                          ConfigurationConstants.ALLOW_SHRINKING_SUBOPTION + "', '" +
455                                          ConfigurationConstants.ALLOW_OPTIMIZATION_SUBOPTION + "', or '" +
456                                          ConfigurationConstants.ALLOW_OBFUSCATION_SUBOPTION + "' before " +
457                                          reader.locationDescription());
458             }
459         }
460
461         // Read the class configuration.
462
ClassSpecification classSpecification =
463             parseClassSpecificationArguments();
464
465         // Create and add the keep configuration.
466
keepSpecifications.add(new KeepSpecification(markClasses,
467                                                      markConditionally,
468                                                      allowShrinking,
469                                                      allowOptimization,
470                                                      allowObfuscation,
471                                                      classSpecification));
472
473         return keepSpecifications;
474     }
475
476
477     private List parseClassSpecificationArguments(List classSpecifications)
478     throws ParseException, IOException
479     {
480         // Create a new List if necessary.
481
if (classSpecifications == null)
482         {
483             classSpecifications = new ArrayList();
484         }
485
486         // Read and add the class configuration.
487
readNextWord("keyword '" + ConfigurationConstants.CLASS_KEYWORD +
488                      "' or '" + ClassConstants.EXTERNAL_ACC_INTERFACE + "'", true);
489
490         classSpecifications.add(parseClassSpecificationArguments());
491
492         return classSpecifications;
493     }
494
495
496     private ClassSpecification parseClassSpecificationArguments()
497     throws ParseException, IOException
498     {
499         // Clear the annotation type.
500
String JavaDoc annotationType = null;
501
502         // Clear the class access modifiers.
503
int requiredSetClassAccessFlags = 0;
504         int requiredUnsetClassAccessFlags = 0;
505
506         // Parse the class annotations and access modifiers until the class keyword.
507
while (!ConfigurationConstants.CLASS_KEYWORD.equals(nextWord))
508         {
509             // Parse the annotation type, if any.
510
if (ConfigurationConstants.ANNOTATION_KEYWORD.equals(nextWord))
511             {
512                 annotationType =
513                     ClassUtil.internalType(
514                     ListUtil.commaSeparatedString(
515                     parseCommaSeparatedList("annotation type",
516                                             true, false, false, true, false, null)));
517
518                 continue;
519             }
520
521             // Strip the negating sign, if any.
522
String JavaDoc strippedWord = nextWord.startsWith(ConfigurationConstants.NEGATOR_KEYWORD) ?
523                 nextWord.substring(1) :
524                 nextWord;
525
526             // Parse the class access modifiers.
527
int accessFlag =
528                 strippedWord.equals(ClassConstants.EXTERNAL_ACC_PUBLIC) ? ClassConstants.INTERNAL_ACC_PUBLIC :
529                 strippedWord.equals(ClassConstants.EXTERNAL_ACC_FINAL) ? ClassConstants.INTERNAL_ACC_FINAL :
530                 strippedWord.equals(ClassConstants.EXTERNAL_ACC_INTERFACE) ? ClassConstants.INTERNAL_ACC_INTERFACE :
531                 strippedWord.equals(ClassConstants.EXTERNAL_ACC_ABSTRACT) ? ClassConstants.INTERNAL_ACC_ABSTRACT :
532                                                                              unknownAccessFlag();
533             if (strippedWord == nextWord)
534             {
535                 requiredSetClassAccessFlags |= accessFlag;
536             }
537             else
538             {
539                 requiredUnsetClassAccessFlags |= accessFlag;
540             }
541
542
543             if ((requiredSetClassAccessFlags &
544                  requiredUnsetClassAccessFlags) != 0)
545             {
546                 throw new ParseException("Conflicting class access modifiers for '" + strippedWord +
547                                          "' before " + reader.locationDescription());
548             }
549
550             if (ClassConstants.EXTERNAL_ACC_INTERFACE.equals(strippedWord))
551             {
552                 // The interface keyword. Stop parsing the class flags.
553
break;
554             }
555
556             readNextWord("keyword '" + ConfigurationConstants.CLASS_KEYWORD +
557                          "' or '" + ClassConstants.EXTERNAL_ACC_INTERFACE + "'");
558         }
559
560        // Parse the class name part.
561
String JavaDoc externalClassName =
562             ListUtil.commaSeparatedString(
563             parseCommaSeparatedList("class name or interface name",
564                                     true, false, false, true, false, null));
565
566         // For backward compatibility, allow a single "*" wildcard to match any
567
// class.
568
String JavaDoc className = ConfigurationConstants.ANY_CLASS_KEYWORD.equals(externalClassName) ?
569             null :
570             ClassUtil.internalClassName(externalClassName);
571
572         // Clear the annotation type and the class name of the extends part.
573
String JavaDoc extendsAnnotationType = null;
574         String JavaDoc extendsClassName = null;
575
576         if (!configurationEnd())
577         {
578             // Parse 'implements ...' or 'extends ...' part, if any.
579
if (ConfigurationConstants.IMPLEMENTS_KEYWORD.equals(nextWord) ||
580                 ConfigurationConstants.EXTENDS_KEYWORD.equals(nextWord))
581             {
582                 readNextWord("class name or interface name", true);
583
584                 // Parse the annotation type, if any.
585
if (ConfigurationConstants.ANNOTATION_KEYWORD.equals(nextWord))
586                 {
587                     extendsAnnotationType =
588                         ClassUtil.internalType(
589                         ListUtil.commaSeparatedString(
590                         parseCommaSeparatedList("annotation type",
591                                                 true, false, false, true, false, null)));
592                 }
593
594                 String JavaDoc externalExtendsClassName =
595                     ListUtil.commaSeparatedString(
596                     parseCommaSeparatedList("class name or interface name",
597                                             false, false, false, true, false, null));
598
599                 extendsClassName = ConfigurationConstants.ANY_CLASS_KEYWORD.equals(externalExtendsClassName) ?
600                     null :
601                     ClassUtil.internalClassName(externalExtendsClassName);
602             }
603         }
604
605         // Create the basic class specification.
606
ClassSpecification classSpecification =
607             new ClassSpecification(lastComments,
608                                    requiredSetClassAccessFlags,
609                                    requiredUnsetClassAccessFlags,
610                                    annotationType,
611                                    className,
612                                    extendsAnnotationType,
613                                    extendsClassName);
614
615
616         // Now add any class members to this class specification.
617
if (!configurationEnd())
618         {
619             // Check the class member opening part.
620
if (!ConfigurationConstants.OPEN_KEYWORD.equals(nextWord))
621             {
622                 throw new ParseException("Expecting opening '" + ConfigurationConstants.OPEN_KEYWORD +
623                                          "' at " + reader.locationDescription());
624             }
625
626             // Parse all class members.
627
while (true)
628             {
629                 readNextWord("class member description" +
630                              " or closing '" + ConfigurationConstants.CLOSE_KEYWORD + "'", true);
631
632                 if (nextWord.equals(ConfigurationConstants.CLOSE_KEYWORD))
633                 {
634                     // The closing brace. Stop parsing the class members.
635
readNextWord();
636
637                     break;
638                 }
639
640                 parseMemberSpecificationArguments(externalClassName,
641                                                   classSpecification);
642             }
643         }
644
645         return classSpecification;
646     }
647
648
649     private void parseMemberSpecificationArguments(String JavaDoc externalClassName,
650                                                    ClassSpecification classSpecification)
651     throws ParseException, IOException
652     {
653         // Clear the annotation name.
654
String JavaDoc annotationType = null;
655
656         // Parse the class member access modifiers, if any.
657
int requiredSetMemberAccessFlags = 0;
658         int requiredUnsetMemberAccessFlags = 0;
659
660         while (!configurationEnd(true))
661         {
662             // Parse the annotation type, if any.
663
if (ConfigurationConstants.ANNOTATION_KEYWORD.equals(nextWord))
664             {
665                 annotationType =
666                     ClassUtil.internalType(
667                     ListUtil.commaSeparatedString(
668                     parseCommaSeparatedList("annotation type",
669                                             true, false, false, true, false, null)));
670
671                 continue;
672             }
673
674             String JavaDoc strippedWord = nextWord.startsWith("!") ?
675                 nextWord.substring(1) :
676                 nextWord;
677
678             // Parse the class member access modifiers.
679
int accessFlag =
680                 strippedWord.equals(ClassConstants.EXTERNAL_ACC_PUBLIC) ? ClassConstants.INTERNAL_ACC_PUBLIC :
681                 strippedWord.equals(ClassConstants.EXTERNAL_ACC_PRIVATE) ? ClassConstants.INTERNAL_ACC_PRIVATE :
682                 strippedWord.equals(ClassConstants.EXTERNAL_ACC_PROTECTED) ? ClassConstants.INTERNAL_ACC_PROTECTED :
683                 strippedWord.equals(ClassConstants.EXTERNAL_ACC_STATIC) ? ClassConstants.INTERNAL_ACC_STATIC :
684                 strippedWord.equals(ClassConstants.EXTERNAL_ACC_FINAL) ? ClassConstants.INTERNAL_ACC_FINAL :
685                 strippedWord.equals(ClassConstants.EXTERNAL_ACC_SYNCHRONIZED) ? ClassConstants.INTERNAL_ACC_SYNCHRONIZED :
686                 strippedWord.equals(ClassConstants.EXTERNAL_ACC_VOLATILE) ? ClassConstants.INTERNAL_ACC_VOLATILE :
687                 strippedWord.equals(ClassConstants.EXTERNAL_ACC_TRANSIENT) ? ClassConstants.INTERNAL_ACC_TRANSIENT :
688                 strippedWord.equals(ClassConstants.EXTERNAL_ACC_NATIVE) ? ClassConstants.INTERNAL_ACC_NATIVE :
689                 strippedWord.equals(ClassConstants.EXTERNAL_ACC_ABSTRACT) ? ClassConstants.INTERNAL_ACC_ABSTRACT :
690                 strippedWord.equals(ClassConstants.EXTERNAL_ACC_STRICT) ? ClassConstants.INTERNAL_ACC_STRICT :
691                                                                                 0;
692             if (accessFlag == 0)
693             {
694                 // Not a class member access modifier. Stop parsing them.
695
break;
696             }
697
698             if (strippedWord == nextWord)
699             {
700                 requiredSetMemberAccessFlags |= accessFlag;
701             }
702             else
703             {
704                 requiredUnsetMemberAccessFlags |= accessFlag;
705             }
706
707             // Make sure the user doesn't try to set and unset the same
708
// access flags simultaneously.
709
if ((requiredSetMemberAccessFlags &
710                  requiredUnsetMemberAccessFlags) != 0)
711             {
712                 throw new ParseException("Conflicting class member access modifiers for " +
713                                          reader.locationDescription());
714             }
715
716             readNextWord("class member description");
717         }
718
719         // Parse the class member type and name part.
720

721         // Did we get a special wildcard?
722
if (ConfigurationConstants.ANY_CLASS_MEMBER_KEYWORD.equals(nextWord) ||
723             ConfigurationConstants.ANY_FIELD_KEYWORD .equals(nextWord) ||
724             ConfigurationConstants.ANY_METHOD_KEYWORD .equals(nextWord))
725         {
726             // Act according to the type of wildcard..
727
if (ConfigurationConstants.ANY_CLASS_MEMBER_KEYWORD.equals(nextWord))
728             {
729                 checkFieldAccessFlags(requiredSetMemberAccessFlags,
730                                       requiredUnsetMemberAccessFlags);
731                 checkMethodAccessFlags(requiredSetMemberAccessFlags,
732                                        requiredUnsetMemberAccessFlags);
733
734                 classSpecification.addField(
735                     new MemberSpecification(requiredSetMemberAccessFlags,
736                                             requiredUnsetMemberAccessFlags,
737                                             annotationType,
738                                             null,
739                                             null));
740                 classSpecification.addMethod(
741                     new MemberSpecification(requiredSetMemberAccessFlags,
742                                             requiredUnsetMemberAccessFlags,
743                                             annotationType,
744                                             null,
745                                             null));
746             }
747             else if (ConfigurationConstants.ANY_FIELD_KEYWORD.equals(nextWord))
748             {
749                 checkFieldAccessFlags(requiredSetMemberAccessFlags,
750                                       requiredUnsetMemberAccessFlags);
751
752                 classSpecification.addField(
753                     new MemberSpecification(requiredSetMemberAccessFlags,
754                                             requiredUnsetMemberAccessFlags,
755                                             annotationType,
756                                             null,
757                                             null));
758             }
759             else if (ConfigurationConstants.ANY_METHOD_KEYWORD.equals(nextWord))
760             {
761                 checkMethodAccessFlags(requiredSetMemberAccessFlags,
762                                        requiredUnsetMemberAccessFlags);
763
764                 classSpecification.addMethod(
765                     new MemberSpecification(requiredSetMemberAccessFlags,
766                                             requiredUnsetMemberAccessFlags,
767                                             annotationType,
768                                             null,
769                                             null));
770             }
771
772             // We still have to read the closing separator.
773
readNextWord("separator '" + ConfigurationConstants.SEPARATOR_KEYWORD + "'");
774
775             if (!ConfigurationConstants.SEPARATOR_KEYWORD.equals(nextWord))
776             {
777                 throw new ParseException("Expecting separator '" + ConfigurationConstants.SEPARATOR_KEYWORD +
778                                          "' before " + reader.locationDescription());
779             }
780         }
781         else
782         {
783             // Make sure we have a proper type.
784
checkJavaIdentifier("java type");
785             String JavaDoc type = nextWord;
786
787             readNextWord("class member name");
788             String JavaDoc name = nextWord;
789
790             // Did we get just one word before the opening parenthesis?
791
if (ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD.equals(name))
792             {
793                 // This must be a constructor then.
794
// Make sure the type is a proper constructor name.
795
if (!(type.equals(ClassConstants.INTERNAL_METHOD_NAME_INIT) ||
796                       type.equals(externalClassName) ||
797                       type.equals(ClassUtil.externalShortClassName(externalClassName))))
798                 {
799                     throw new ParseException("Expecting type and name " +
800                                              "instead of just '" + type +
801                                              "' before " + reader.locationDescription());
802                 }
803
804                 // Assign the fixed constructor type and name.
805
type = ClassConstants.EXTERNAL_TYPE_VOID;
806                 name = ClassConstants.INTERNAL_METHOD_NAME_INIT;
807             }
808             else
809             {
810                 // It's not a constructor.
811
// Make sure we have a proper name.
812
checkJavaIdentifier("class member name");
813
814                 // Read the opening parenthesis or the separating
815
// semi-colon.
816
readNextWord("opening '" + ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD +
817                              "' or separator '" + ConfigurationConstants.SEPARATOR_KEYWORD + "'");
818             }
819
820             // Are we looking at a field, a method, or something else?
821
if (ConfigurationConstants.SEPARATOR_KEYWORD.equals(nextWord))
822             {
823                 // It's a field.
824
checkFieldAccessFlags(requiredSetMemberAccessFlags,
825                                       requiredUnsetMemberAccessFlags);
826
827                 // We already have a field descriptor.
828
String JavaDoc descriptor = ClassUtil.internalType(type);
829
830                 // Add the field.
831
classSpecification.addField(
832                     new MemberSpecification(requiredSetMemberAccessFlags,
833                                             requiredUnsetMemberAccessFlags,
834                                             annotationType,
835                                             name,
836                                             descriptor));
837             }
838             else if (ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD.equals(nextWord))
839             {
840                 // It's a method.
841
checkMethodAccessFlags(requiredSetMemberAccessFlags,
842                                        requiredUnsetMemberAccessFlags);
843
844                 // Parse the method arguments.
845
String JavaDoc descriptor =
846                     ClassUtil.internalMethodDescriptor(type,
847                                                        parseCommaSeparatedList("argument", true, true, true, true, false, null));
848
849                 if (!ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD.equals(nextWord))
850                 {
851                     throw new ParseException("Expecting separating '" + ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD +
852                                              "' or closing '" + ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD +
853                                              "' before " + reader.locationDescription());
854                 }
855
856                 // Read the separator after the closing parenthesis.
857
readNextWord("separator '" + ConfigurationConstants.SEPARATOR_KEYWORD + "'");
858
859                 if (!ConfigurationConstants.SEPARATOR_KEYWORD.equals(nextWord))
860                 {
861                     throw new ParseException("Expecting separator '" + ConfigurationConstants.SEPARATOR_KEYWORD +
862                                              "' before " + reader.locationDescription());
863                 }
864
865                 // Add the method.
866
classSpecification.addMethod(
867                     new MemberSpecification(requiredSetMemberAccessFlags,
868                                             requiredUnsetMemberAccessFlags,
869                                             annotationType,
870                                             name,
871                                             descriptor));
872             }
873             else
874             {
875                 // It doesn't look like a field or a method.
876
throw new ParseException("Expecting opening '" + ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD +
877                                          "' or separator '" + ConfigurationConstants.SEPARATOR_KEYWORD +
878                                          "' before " + reader.locationDescription());
879             }
880         }
881     }
882
883
884     /**
885      * Reads a comma-separated list of java identifiers or of file names. If an
886      * empty list is allowed, the reading will end after a closing parenthesis
887      * or semi-colon.
888      */

889     private List parseCommaSeparatedList(String JavaDoc expectedDescription,
890                                          boolean readFirstWord,
891                                          boolean allowEmptyList,
892                                          boolean expectClosingParenthesis,
893                                          boolean checkJavaIdentifiers,
894                                          boolean replaceSystemProperties,
895                                          List list)
896     throws ParseException, IOException
897     {
898         if (list == null)
899         {
900             list = new ArrayList();
901         }
902
903         if (readFirstWord)
904         {
905             if (expectClosingParenthesis || !allowEmptyList)
906             {
907                 // Read the first list entry.
908
readNextWord(expectedDescription);
909             }
910             else
911             {
912                 // Read the first list entry, if there is any.
913
readNextWord();
914
915                 // Check if the list is empty.
916
if (configurationEnd() ||
917                     nextWord.equals(ConfigurationConstants.ANY_ATTRIBUTE_KEYWORD))
918                 {
919                     return list;
920                 }
921             }
922         }
923
924         while (true)
925         {
926             if (expectClosingParenthesis &&
927                 list.size() == 0 &&
928                 (ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD.equals(nextWord) ||
929                  ConfigurationConstants.SEPARATOR_KEYWORD.equals(nextWord)))
930             {
931                 break;
932             }
933
934             if (checkJavaIdentifiers)
935             {
936                 checkJavaIdentifier("java type");
937             }
938
939             if (replaceSystemProperties)
940             {
941                 nextWord = replaceSystemProperties(nextWord);
942             }
943
944             list.add(nextWord);
945
946             if (expectClosingParenthesis)
947             {
948                 // Read a comma (or a closing parenthesis, or a different word).
949
readNextWord("separating '" + ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD +
950                              "' or closing '" + ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD +
951                              "'");
952             }
953             else
954             {
955                 // Read a comma (or a different word).
956
readNextWord();
957             }
958
959             if (!ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD.equals(nextWord))
960             {
961                 break;
962             }
963
964             // Read the next list entry.
965
readNextWord(expectedDescription);
966         }
967
968         return list;
969     }
970
971
972     /**
973      * Throws a ParseException for an unexpected keyword.
974      */

975     private int unknownAccessFlag() throws ParseException
976     {
977         throw new ParseException("Unexpected keyword " + reader.locationDescription());
978     }
979
980
981     /**
982      * Creates a properly resolved File, based on the given word.
983      */

984     private File file(String JavaDoc word) throws ParseException
985     {
986         String JavaDoc fileName = replaceSystemProperties(word);
987         File file = new File(fileName);
988
989         // Try to get an absolute file.
990
if (!file.isAbsolute())
991         {
992             file = new File(reader.getBaseDir(), fileName);
993         }
994
995         // Try to get a canonical representation.
996
try
997         {
998             file = file.getCanonicalFile();
999         }
1000        catch (IOException ex)
1001        {
1002        }
1003
1004        return file;
1005    }
1006
1007
1008    /**
1009     * Replaces any system properties in the given word by their values
1010     * (e.g. the substring "<java.home>" is replaced by its value).
1011     */

1012    private String JavaDoc replaceSystemProperties(String JavaDoc word) throws ParseException
1013    {
1014        int fromIndex = 0;
1015        while (true)
1016        {
1017            fromIndex = word.indexOf(ConfigurationConstants.OPEN_SYSTEM_PROPERTY, fromIndex);
1018            if (fromIndex < 0)
1019            {
1020                break;
1021            }
1022
1023            int toIndex = word.indexOf(ConfigurationConstants.CLOSE_SYSTEM_PROPERTY, fromIndex+1);
1024            if (toIndex < 0)
1025            {
1026                throw new ParseException("Expecting closing '" + ConfigurationConstants.CLOSE_SYSTEM_PROPERTY +
1027                                         "' after opening '" + ConfigurationConstants.OPEN_SYSTEM_PROPERTY +
1028                                         "' in " + reader.locationDescription());
1029            }
1030
1031            String JavaDoc propertyName = word.substring(fromIndex+1, toIndex);
1032            String JavaDoc propertyValue = System.getProperty(propertyName);
1033            if (propertyValue == null)
1034            {
1035                throw new ParseException("Value of system property '" + propertyName +
1036                                         "' is undefined in " + reader.locationDescription());
1037            }
1038
1039            word = word.substring(0, fromIndex) +
1040                       propertyValue +
1041                       word.substring(toIndex+1);
1042        }
1043
1044        return word;
1045    }
1046
1047
1048    /**
1049     * Reads the next word of the configuration in the 'nextWord' field,
1050     * throwing an exception if there is no next word.
1051     */

1052    private void readNextWord(String JavaDoc expectedDescription)
1053    throws ParseException, IOException
1054    {
1055        readNextWord(expectedDescription, false);
1056    }
1057
1058
1059    /**
1060     * Reads the next word of the configuration in the 'nextWord' field,
1061     * throwing an exception if there is no next word.
1062     */

1063    private void readNextWord(String JavaDoc expectedDescription,
1064                              boolean expectingAtCharacter)
1065    throws ParseException, IOException
1066    {
1067        readNextWord();
1068        if (configurationEnd(expectingAtCharacter))
1069        {
1070            throw new ParseException("Expecting " + expectedDescription +
1071                                     " before " + reader.locationDescription());
1072        }
1073    }
1074
1075
1076    /**
1077     * Reads the next word of the configuration in the 'nextWord' field.
1078     */

1079    private void readNextWord() throws IOException
1080    {
1081        nextWord = reader.nextWord();
1082    }
1083
1084
1085    /**
1086     * Returns whether the end of the configuration has been reached.
1087     */

1088    private boolean configurationEnd()
1089    {
1090        return configurationEnd(false);
1091    }
1092
1093
1094    /**
1095     * Returns whether the end of the configuration has been reached.
1096     */

1097    private boolean configurationEnd(boolean expectingAtCharacter)
1098    {
1099        return nextWord == null ||
1100               nextWord.startsWith(ConfigurationConstants.OPTION_PREFIX) ||
1101               (!expectingAtCharacter &&
1102                nextWord.equals(ConfigurationConstants.AT_DIRECTIVE));
1103    }
1104
1105
1106    /**
1107     * Checks whether the given word is a valid Java identifier and throws
1108     * a ParseException if it isn't. Wildcard characters are accepted.
1109     */

1110    private void checkJavaIdentifier(String JavaDoc expectedDescription)
1111    throws ParseException
1112    {
1113        if (!isJavaIdentifier(nextWord))
1114        {
1115            throw new ParseException("Expecting " + expectedDescription +
1116                                     " before " + reader.locationDescription());
1117        }
1118    }
1119
1120
1121    /**
1122     * Returns whether the given word is a valid Java identifier.
1123     * Wildcard characters are accepted.
1124     */

1125    private boolean isJavaIdentifier(String JavaDoc aWord)
1126    {
1127        for (int index = 0; index < aWord.length(); index++)
1128        {
1129            char c = aWord.charAt(index);
1130            if (!(Character.isJavaIdentifierPart(c) ||
1131                  c == '.' ||
1132                  c == '[' ||
1133                  c == ']' ||
1134                  c == '<' ||
1135                  c == '>' ||
1136                  c == '-' ||
1137                  c == '!' ||
1138                  c == '*' ||
1139                  c == '?' ||
1140                  c == '%'))
1141            {
1142                return false;
1143            }
1144        }
1145
1146        return true;
1147    }
1148
1149
1150    /**
1151     * Checks whether the given access flags are valid field access flags,
1152     * throwing a ParseException if they aren't.
1153     */

1154    private void checkFieldAccessFlags(int requiredSetMemberAccessFlags,
1155                                       int requiredUnsetMemberAccessFlags)
1156    throws ParseException
1157    {
1158        if (((requiredSetMemberAccessFlags |
1159              requiredUnsetMemberAccessFlags) &
1160            ~ClassConstants.VALID_INTERNAL_ACC_FIELD) != 0)
1161        {
1162            throw new ParseException("Invalid method access modifier for field before " +
1163                                     reader.locationDescription());
1164        }
1165    }
1166
1167
1168    /**
1169     * Checks whether the given access flags are valid method access flags,
1170     * throwing a ParseException if they aren't.
1171     */

1172    private void checkMethodAccessFlags(int requiredSetMemberAccessFlags,
1173                                        int requiredUnsetMemberAccessFlags)
1174    throws ParseException
1175    {
1176        if (((requiredSetMemberAccessFlags |
1177              requiredUnsetMemberAccessFlags) &
1178            ~ClassConstants.VALID_INTERNAL_ACC_METHOD) != 0)
1179        {
1180            throw new ParseException("Invalid field access modifier for method before " +
1181                                     reader.locationDescription());
1182        }
1183    }
1184
1185
1186    /**
1187     * A main method for testing configuration parsing.
1188     */

1189    public static void main(String JavaDoc[] args)
1190    {
1191        try
1192        {
1193            ConfigurationParser parser = new ConfigurationParser(args);
1194
1195            try
1196            {
1197                parser.parse(new Configuration());
1198            }
1199            catch (ParseException ex)
1200            {
1201                ex.printStackTrace();
1202            }
1203            finally
1204            {
1205                parser.close();
1206            }
1207        }
1208        catch (IOException ex)
1209        {
1210            ex.printStackTrace();
1211        }
1212    }
1213}
1214
Popular Tags