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