1/* 2 * ProGuard -- shrinking, optimization, obfuscation, and preverification 3 * of Java bytecode. 4 * 5 * Copyright (c) 2002-2009 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 */ 21package proguard; 22 23import proguard.classfile.ClassConstants; 24import proguard.classfile.util.ClassUtil; 25import proguard.util.ListUtil; 26 27import java.io.*; 28import java.util.List; 29 30 31/** 32 * This class writes ProGuard configurations to a file. 33 * 34 * @author Eric Lafortune 35 */ 36public class ConfigurationWriter 37{ 38 private static final String[] KEEP_OPTIONS = new String[] 39 { 40 ConfigurationConstants.KEEP_OPTION, 41 ConfigurationConstants.KEEP_CLASS_MEMBERS_OPTION, 42 ConfigurationConstants.KEEP_CLASSES_WITH_MEMBERS_OPTION 43 }; 44 45 46 private final PrintWriter writer; 47 private File baseDir; 48 49 50 /** 51 * Creates a new ConfigurationWriter for the given file name. 52 */ 53 public ConfigurationWriter(File configurationFile) throws IOException 54 { 55 this(new PrintWriter(new FileWriter(configurationFile))); 56 57 baseDir = configurationFile.getParentFile(); 58 } 59 60 61 /** 62 * Creates a new ConfigurationWriter for the given OutputStream. 63 */ 64 public ConfigurationWriter(OutputStream outputStream) throws IOException 65 { 66 this(new PrintWriter(outputStream)); 67 } 68 69 70 /** 71 * Creates a new ConfigurationWriter for the given PrintWriter. 72 */ 73 public ConfigurationWriter(PrintWriter writer) throws IOException 74 { 75 this.writer = writer; 76 } 77 78 79 /** 80 * Closes this ConfigurationWriter. 81 */ 82 public void close() throws IOException 83 { 84 writer.close(); 85 } 86 87 88 /** 89 * Writes the given configuration. 90 * @param configuration the configuration that is to be written out. 91 * @throws IOException if an IO error occurs while writing the configuration. 92 */ 93 public void write(Configuration configuration) throws IOException 94 { 95 // Write the program class path (input and output entries). 96 writeJarOptions(ConfigurationConstants.INJARS_OPTION, 97 ConfigurationConstants.OUTJARS_OPTION, 98 configuration.programJars); 99 writer.println(); 100 101 // Write the library class path (output entries only). 102 writeJarOptions(ConfigurationConstants.LIBRARYJARS_OPTION, 103 ConfigurationConstants.LIBRARYJARS_OPTION, 104 configuration.libraryJars); 105 writer.println(); 106 107 // Write the other options. 108 writeOption(ConfigurationConstants.DONT_SKIP_NON_PUBLIC_LIBRARY_CLASSES_OPTION, !configuration.skipNonPublicLibraryClasses); 109 writeOption(ConfigurationConstants.DONT_SKIP_NON_PUBLIC_LIBRARY_CLASS_MEMBERS_OPTION, !configuration.skipNonPublicLibraryClassMembers); 110 writeOption(ConfigurationConstants.KEEP_DIRECTORIES_OPTION, configuration.keepDirectories); 111 writeOption(ConfigurationConstants.TARGET_OPTION, ClassUtil.externalClassVersion(configuration.targetClassVersion)); 112 writeOption(ConfigurationConstants.FORCE_PROCESSING_OPTION, configuration.lastModified == Long.MAX_VALUE); 113 114 writeOption(ConfigurationConstants.DONT_SHRINK_OPTION, !configuration.shrink); 115 writeOption(ConfigurationConstants.PRINT_USAGE_OPTION, configuration.printUsage); 116 117 writeOption(ConfigurationConstants.DONT_OPTIMIZE_OPTION, !configuration.optimize); 118 writeOption(ConfigurationConstants.OPTIMIZATIONS, configuration.optimize ? ListUtil.commaSeparatedString(configuration.optimizations) : null); 119 writeOption(ConfigurationConstants.OPTIMIZATION_PASSES, configuration.optimizationPasses); 120 writeOption(ConfigurationConstants.ALLOW_ACCESS_MODIFICATION_OPTION, configuration.allowAccessModification); 121 writeOption(ConfigurationConstants.MERGE_INTERFACES_AGGRESSIVELY_OPTION, configuration.mergeInterfacesAggressively); 122 123 writeOption(ConfigurationConstants.DONT_OBFUSCATE_OPTION, !configuration.obfuscate); 124 writeOption(ConfigurationConstants.PRINT_MAPPING_OPTION, configuration.printMapping); 125 writeOption(ConfigurationConstants.APPLY_MAPPING_OPTION, configuration.applyMapping); 126 writeOption(ConfigurationConstants.OBFUSCATION_DICTIONARY_OPTION, configuration.obfuscationDictionary); 127 writeOption(ConfigurationConstants.CLASS_OBFUSCATION_DICTIONARY_OPTION, configuration.classObfuscationDictionary); 128 writeOption(ConfigurationConstants.PACKAGE_OBFUSCATION_DICTIONARY_OPTION, configuration.packageObfuscationDictionary); 129 writeOption(ConfigurationConstants.OVERLOAD_AGGRESSIVELY_OPTION, configuration.overloadAggressively); 130 writeOption(ConfigurationConstants.USE_UNIQUE_CLASS_MEMBER_NAMES_OPTION, configuration.useUniqueClassMemberNames); 131 writeOption(ConfigurationConstants.DONT_USE_MIXED_CASE_CLASS_NAMES_OPTION, !configuration.useMixedCaseClassNames); 132 writeOption(ConfigurationConstants.KEEP_PACKAGE_NAMES_OPTION, configuration.keepPackageNames, true); 133 writeOption(ConfigurationConstants.FLATTEN_PACKAGE_HIERARCHY_OPTION, configuration.flattenPackageHierarchy, true); 134 writeOption(ConfigurationConstants.REPACKAGE_CLASSES_OPTION, configuration.repackageClasses, true); 135 writeOption(ConfigurationConstants.KEEP_ATTRIBUTES_OPTION, configuration.keepAttributes); 136 writeOption(ConfigurationConstants.RENAME_SOURCE_FILE_ATTRIBUTE_OPTION, configuration.newSourceFileAttribute); 137 writeOption(ConfigurationConstants.ADAPT_CLASS_STRINGS_OPTION, configuration.adaptClassStrings, true); 138 writeOption(ConfigurationConstants.ADAPT_RESOURCE_FILE_NAMES_OPTION, configuration.adaptResourceFileNames); 139 writeOption(ConfigurationConstants.ADAPT_RESOURCE_FILE_CONTENTS_OPTION, configuration.adaptResourceFileContents); 140 141 writeOption(ConfigurationConstants.DONT_PREVERIFY_OPTION, !configuration.preverify); 142 writeOption(ConfigurationConstants.MICRO_EDITION_OPTION, configuration.microEdition); 143 144 writeOption(ConfigurationConstants.VERBOSE_OPTION, configuration.verbose); 145 writeOption(ConfigurationConstants.DONT_NOTE_OPTION, configuration.note, true); 146 writeOption(ConfigurationConstants.DONT_WARN_OPTION, configuration.warn, true); 147 writeOption(ConfigurationConstants.IGNORE_WARNINGS_OPTION, configuration.ignoreWarnings); 148 writeOption(ConfigurationConstants.PRINT_CONFIGURATION_OPTION, configuration.printConfiguration); 149 writeOption(ConfigurationConstants.DUMP_OPTION, configuration.dump); 150 151 writeOption(ConfigurationConstants.PRINT_SEEDS_OPTION, configuration.printSeeds); 152 writer.println(); 153 154 // Write the "why are you keeping" options. 155 writeOptions(ConfigurationConstants.WHY_ARE_YOU_KEEPING_OPTION, configuration.whyAreYouKeeping); 156 157 // Write the keep options. 158 writeOptions(KEEP_OPTIONS, configuration.keep); 159 160 // Write the "no side effect methods" options. 161 writeOptions(ConfigurationConstants.ASSUME_NO_SIDE_EFFECTS_OPTION, configuration.assumeNoSideEffects); 162 163 if (writer.checkError()) 164 { 165 throw new IOException("Can't write configuration"); 166 } 167 } 168 169 170 private void writeJarOptions(String inputEntryOptionName, 171 String outputEntryOptionName, 172 ClassPath classPath) 173 { 174 if (classPath != null) 175 { 176 for (int index = 0; index < classPath.size(); index++) 177 { 178 ClassPathEntry entry = classPath.get(index); 179 String optionName = entry.isOutput() ? 180 outputEntryOptionName : 181 inputEntryOptionName; 182 183 writer.print(optionName); 184 writer.print(' '); 185 writer.print(relativeFileName(entry.getFile())); 186 187 // Append the filters, if any. 188 boolean filtered = false; 189 190 filtered = writeFilter(filtered, entry.getZipFilter()); 191 filtered = writeFilter(filtered, entry.getEarFilter()); 192 filtered = writeFilter(filtered, entry.getWarFilter()); 193 filtered = writeFilter(filtered, entry.getJarFilter()); 194 filtered = writeFilter(filtered, entry.getFilter()); 195 196 if (filtered) 197 { 198 writer.print(ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD); 199 } 200 201 writer.println(); 202 } 203 } 204 } 205 206 207 private boolean writeFilter(boolean filtered, List filter) 208 { 209 if (filtered) 210 { 211 writer.print(ConfigurationConstants.SEPARATOR_KEYWORD); 212 } 213 214 if (filter != null) 215 { 216 if (!filtered) 217 { 218 writer.print(ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD); 219 } 220 221 for (int index = 0; index < filter.size(); index++) 222 { 223 if (index > 0) 224 { 225 writer.print(ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD); 226 } 227 228 writer.print(quotedString((String)filter.get(index))); 229 } 230 231 filtered = true; 232 } 233 234 return filtered; 235 } 236 237 238 private void writeOption(String optionName, boolean flag) 239 { 240 if (flag) 241 { 242 writer.println(optionName); 243 } 244 } 245 246 247 private void writeOption(String optionName, int argument) 248 { 249 if (argument != 1) 250 { 251 writer.print(optionName); 252 writer.print(' '); 253 writer.println(argument); 254 } 255 } 256 257 258 private void writeOption(String optionName, List arguments) 259 { 260 writeOption(optionName, arguments, false); 261 } 262 263 264 private void writeOption(String optionName, 265 List arguments, 266 boolean replaceInternalClassNames) 267 { 268 if (arguments != null) 269 { 270 if (arguments.isEmpty()) 271 { 272 writer.println(optionName); 273 } 274 else 275 { 276 String argumentString = ListUtil.commaSeparatedString(arguments); 277 278 if (replaceInternalClassNames) 279 { 280 argumentString = ClassUtil.externalClassName(argumentString); 281 } 282 283 writer.print(optionName); 284 writer.print(' '); 285 writer.println(quotedString(argumentString)); 286 } 287 } 288 } 289 290 291 private void writeOption(String optionName, String arguments) 292 { 293 writeOption(optionName, arguments, false); 294 } 295 296 297 private void writeOption(String optionName, 298 String arguments, 299 boolean replaceInternalClassNames) 300 { 301 if (arguments != null) 302 { 303 if (replaceInternalClassNames) 304 { 305 arguments = ClassUtil.externalClassName(arguments); 306 } 307 308 writer.print(optionName); 309 writer.print(' '); 310 writer.println(quotedString(arguments)); 311 } 312 } 313 314 315 private void writeOption(String optionName, File file) 316 { 317 if (file != null) 318 { 319 if (file.getPath().length() > 0) 320 { 321 writer.print(optionName); 322 writer.print(' '); 323 writer.println(relativeFileName(file)); 324 } 325 else 326 { 327 writer.println(optionName); 328 } 329 } 330 } 331 332 333 private void writeOptions(String[] optionNames, 334 List keepClassSpecifications) 335 { 336 if (keepClassSpecifications != null) 337 { 338 for (int index = 0; index < keepClassSpecifications.size(); index++) 339 { 340 writeOption(optionNames, (KeepClassSpecification)keepClassSpecifications.get(index)); 341 } 342 } 343 } 344 345 346 private void writeOption(String[] optionNames, 347 KeepClassSpecification keepClassSpecification) 348 { 349 // Compose the option name. 350 String optionName = optionNames[keepClassSpecification.markConditionally ? 2 : 351 keepClassSpecification.markClasses ? 0 : 352 1]; 353 354 if (keepClassSpecification.allowShrinking) 355 { 356 optionName += ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD + 357 ConfigurationConstants.ALLOW_SHRINKING_SUBOPTION; 358 } 359 360 if (keepClassSpecification.allowOptimization) 361 { 362 optionName += ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD + 363 ConfigurationConstants.ALLOW_OPTIMIZATION_SUBOPTION; 364 } 365 366 if (keepClassSpecification.allowObfuscation) 367 { 368 optionName += ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD + 369 ConfigurationConstants.ALLOW_OBFUSCATION_SUBOPTION; 370 } 371 372 // Write out the option with the proper class specification. 373 writeOption(optionName, keepClassSpecification); 374 } 375 376 377 private void writeOptions(String optionName, 378 List classSpecifications) 379 { 380 if (classSpecifications != null) 381 { 382 for (int index = 0; index < classSpecifications.size(); index++) 383 { 384 writeOption(optionName, (ClassSpecification)classSpecifications.get(index)); 385 } 386 } 387 } 388 389 390 private void writeOption(String optionName, 391 ClassSpecification classSpecification) 392 { 393 writer.println(); 394 395 // Write out the comments for this option. 396 writeComments(classSpecification.comments); 397 398 writer.print(optionName); 399 writer.print(' '); 400 401 // Write out the required annotation, if any. 402 if (classSpecification.annotationType != null) 403 { 404 writer.print(ConfigurationConstants.ANNOTATION_KEYWORD); 405 writer.print(ClassUtil.externalType(classSpecification.annotationType)); 406 writer.print(' '); 407 } 408 409 // Write out the class access flags. 410 writer.print(ClassUtil.externalClassAccessFlags(classSpecification.requiredUnsetAccessFlags, 411 ConfigurationConstants.NEGATOR_KEYWORD)); 412 413 writer.print(ClassUtil.externalClassAccessFlags(classSpecification.requiredSetAccessFlags)); 414 415 // Write out the class keyword, if we didn't write the interface 416 // keyword earlier. 417 if (((classSpecification.requiredSetAccessFlags | 418 classSpecification.requiredUnsetAccessFlags) & 419 (ClassConstants.INTERNAL_ACC_INTERFACE | 420 ClassConstants.INTERNAL_ACC_ENUM)) == 0) 421 { 422 writer.print(ConfigurationConstants.CLASS_KEYWORD); 423 } 424 425 writer.print(' '); 426 427 // Write out the class name. 428 writer.print(classSpecification.className != null ? 429 ClassUtil.externalClassName(classSpecification.className) : 430 ConfigurationConstants.ANY_CLASS_KEYWORD); 431 432 // Write out the extends template, if any. 433 if (classSpecification.extendsAnnotationType != null || 434 classSpecification.extendsClassName != null) 435 { 436 writer.print(' '); 437 writer.print(ConfigurationConstants.EXTENDS_KEYWORD); 438 writer.print(' '); 439 440 // Write out the required extends annotation, if any. 441 if (classSpecification.extendsAnnotationType != null) 442 { 443 writer.print(ConfigurationConstants.ANNOTATION_KEYWORD); 444 writer.print(ClassUtil.externalType(classSpecification.extendsAnnotationType)); 445 writer.print(' '); 446 } 447 448 // Write out the extended class name. 449 writer.print(classSpecification.extendsClassName != null ? 450 ClassUtil.externalClassName(classSpecification.extendsClassName) : 451 ConfigurationConstants.ANY_CLASS_KEYWORD); 452 } 453 454 // Write out the keep field and keep method options, if any. 455 if (classSpecification.fieldSpecifications != null || 456 classSpecification.methodSpecifications != null) 457 { 458 writer.print(' '); 459 writer.println(ConfigurationConstants.OPEN_KEYWORD); 460 461 writeFieldSpecification( classSpecification.fieldSpecifications); 462 writeMethodSpecification(classSpecification.methodSpecifications); 463 464 writer.println(ConfigurationConstants.CLOSE_KEYWORD); 465 } 466 else 467 { 468 writer.println(); 469 } 470 } 471 472 473 474 private void writeComments(String comments) 475 { 476 if (comments != null) 477 { 478 int index = 0; 479 while (index < comments.length()) 480 { 481 int breakIndex = comments.indexOf('\n', index); 482 if (breakIndex < 0) 483 { 484 breakIndex = comments.length(); 485 } 486 487 writer.print('#'); 488 489 if (comments.charAt(index) != ' ') 490 { 491 writer.print(' '); 492 } 493 494 writer.println(comments.substring(index, breakIndex)); 495 496 index = breakIndex + 1; 497 } 498 } 499 } 500 501 502 private void writeFieldSpecification(List memberSpecifications) 503 { 504 if (memberSpecifications != null) 505 { 506 for (int index = 0; index < memberSpecifications.size(); index++) 507 { 508 MemberSpecification memberSpecification = 509 (MemberSpecification)memberSpecifications.get(index); 510 511 writer.print(" "); 512 513 // Write out the required annotation, if any. 514 if (memberSpecification.annotationType != null) 515 { 516 writer.print(ConfigurationConstants.ANNOTATION_KEYWORD); 517 writer.println(ClassUtil.externalType(memberSpecification.annotationType)); 518 writer.print(" "); 519 } 520 521 // Write out the field access flags. 522 writer.print(ClassUtil.externalFieldAccessFlags(memberSpecification.requiredUnsetAccessFlags, 523 ConfigurationConstants.NEGATOR_KEYWORD)); 524 525 writer.print(ClassUtil.externalFieldAccessFlags(memberSpecification.requiredSetAccessFlags)); 526 527 // Write out the field name and descriptor. 528 String name = memberSpecification.name; 529 String descriptor = memberSpecification.descriptor; 530 531 writer.print(descriptor == null ? name == null ? 532 ConfigurationConstants.ANY_FIELD_KEYWORD : 533 ConfigurationConstants.ANY_TYPE_KEYWORD + ' ' + name : 534 ClassUtil.externalFullFieldDescription(0, 535 name == null ? ConfigurationConstants.ANY_CLASS_MEMBER_KEYWORD : name, 536 descriptor)); 537 538 writer.println(ConfigurationConstants.SEPARATOR_KEYWORD); 539 } 540 } 541 } 542 543 544 private void writeMethodSpecification(List memberSpecifications) 545 { 546 if (memberSpecifications != null) 547 { 548 for (int index = 0; index < memberSpecifications.size(); index++) 549 { 550 MemberSpecification memberSpecification = 551 (MemberSpecification)memberSpecifications.get(index); 552 553 writer.print(" "); 554 555 // Write out the required annotation, if any. 556 if (memberSpecification.annotationType != null) 557 { 558 writer.print(ConfigurationConstants.ANNOTATION_KEYWORD); 559 writer.println(ClassUtil.externalType(memberSpecification.annotationType)); 560 writer.print(" "); 561 } 562 563 // Write out the method access flags. 564 writer.print(ClassUtil.externalMethodAccessFlags(memberSpecification.requiredUnsetAccessFlags, 565 ConfigurationConstants.NEGATOR_KEYWORD)); 566 567 writer.print(ClassUtil.externalMethodAccessFlags(memberSpecification.requiredSetAccessFlags)); 568 569 // Write out the method name and descriptor. 570 String name = memberSpecification.name; 571 String descriptor = memberSpecification.descriptor; 572 573 writer.print(descriptor == null ? name == null ? 574 ConfigurationConstants.ANY_METHOD_KEYWORD : 575 ConfigurationConstants.ANY_TYPE_KEYWORD + ' ' + name + ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD + ConfigurationConstants.ANY_ARGUMENTS_KEYWORD + ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD : 576 ClassUtil.externalFullMethodDescription(ClassConstants.INTERNAL_METHOD_NAME_INIT, 577 0, 578 name == null ? ConfigurationConstants.ANY_CLASS_MEMBER_KEYWORD : name, 579 descriptor)); 580 581 writer.println(ConfigurationConstants.SEPARATOR_KEYWORD); 582 } 583 } 584 } 585 586 587 /** 588 * Returns a relative file name of the given file, if possible. 589 * The file name is also quoted, if necessary. 590 */ 591 private String relativeFileName(File file) 592 { 593 String fileName = file.getAbsolutePath(); 594 595 // See if we can convert the file name into a relative file name. 596 if (baseDir != null) 597 { 598 String baseDirName = baseDir.getAbsolutePath() + File.separator; 599 if (fileName.startsWith(baseDirName)) 600 { 601 fileName = fileName.substring(baseDirName.length()); 602 } 603 } 604 605 return quotedString(fileName); 606 } 607 608 609 /** 610 * Returns a quoted version of the given string, if necessary. 611 */ 612 private String quotedString(String string) 613 { 614 return string.length() == 0 || 615 string.indexOf(' ') >= 0 || 616 string.indexOf('@') >= 0 || 617 string.indexOf('{') >= 0 || 618 string.indexOf('}') >= 0 || 619 string.indexOf('(') >= 0 || 620 string.indexOf(')') >= 0 || 621 string.indexOf(':') >= 0 || 622 string.indexOf(';') >= 0 || 623 string.indexOf(',') >= 0 ? ("'" + string + "'") : 624 ( string ); 625 } 626 627 628 /** 629 * A main method for testing configuration writing. 630 */ 631 public static void main(String[] args) { 632 try 633 { 634 ConfigurationWriter writer = new ConfigurationWriter(new File(args[0])); 635 636 writer.write(new Configuration()); 637 } 638 catch (Exception ex) 639 { 640 ex.printStackTrace(); 641 } 642 } 643} 644