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