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 */ 21package proguard; 22 23import proguard.classfile.ClassConstants; 24import proguard.classfile.util.ClassUtil; 25import proguard.util.ListUtil; 26 27import java.io.*; 28import java.util.*; 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.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.optimizations); 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.KEEP_PARAMETER_NAMES_OPTION, configuration.keepParameterNames); 137 writeOption(ConfigurationConstants.RENAME_SOURCE_FILE_ATTRIBUTE_OPTION, configuration.newSourceFileAttribute); 138 writeOption(ConfigurationConstants.ADAPT_CLASS_STRINGS_OPTION, configuration.adaptClassStrings, true); 139 writeOption(ConfigurationConstants.ADAPT_RESOURCE_FILE_NAMES_OPTION, configuration.adaptResourceFileNames); 140 writeOption(ConfigurationConstants.ADAPT_RESOURCE_FILE_CONTENTS_OPTION, configuration.adaptResourceFileContents); 141 142 writeOption(ConfigurationConstants.DONT_PREVERIFY_OPTION, !configuration.preverify); 143 writeOption(ConfigurationConstants.MICRO_EDITION_OPTION, configuration.microEdition); 144 145 writeOption(ConfigurationConstants.VERBOSE_OPTION, configuration.verbose); 146 writeOption(ConfigurationConstants.DONT_NOTE_OPTION, configuration.note, true); 147 writeOption(ConfigurationConstants.DONT_WARN_OPTION, configuration.warn, true); 148 writeOption(ConfigurationConstants.IGNORE_WARNINGS_OPTION, configuration.ignoreWarnings); 149 writeOption(ConfigurationConstants.PRINT_CONFIGURATION_OPTION, configuration.printConfiguration); 150 writeOption(ConfigurationConstants.DUMP_OPTION, configuration.dump); 151 152 writeOption(ConfigurationConstants.PRINT_SEEDS_OPTION, configuration.printSeeds); 153 writer.println(); 154 155 // Write the "why are you keeping" options. 156 writeOptions(ConfigurationConstants.WHY_ARE_YOU_KEEPING_OPTION, configuration.whyAreYouKeeping); 157 158 // Write the keep options. 159 writeOptions(KEEP_OPTIONS, configuration.keep); 160 161 // Write the "no side effect methods" options. 162 writeOptions(ConfigurationConstants.ASSUME_NO_SIDE_EFFECTS_OPTION, configuration.assumeNoSideEffects); 163 164 if (writer.checkError()) 165 { 166 throw new IOException("Can't write configuration"); 167 } 168 } 169 170 171 private void writeJarOptions(String inputEntryOptionName, 172 String outputEntryOptionName, 173 ClassPath classPath) 174 { 175 if (classPath != null) 176 { 177 for (int index = 0; index < classPath.size(); index++) 178 { 179 ClassPathEntry entry = classPath.get(index); 180 String optionName = entry.isOutput() ? 181 outputEntryOptionName : 182 inputEntryOptionName; 183 184 writer.print(optionName); 185 writer.print(' '); 186 writer.print(relativeFileName(entry.getFile())); 187 188 // Append the filters, if any. 189 boolean filtered = false; 190 191 // For backward compatibility, the aar and apk filters come 192 // first. 193 filtered = writeFilter(filtered, entry.getAarFilter()); 194 filtered = writeFilter(filtered, entry.getApkFilter()); 195 filtered = writeFilter(filtered, entry.getZipFilter()); 196 filtered = writeFilter(filtered, entry.getEarFilter()); 197 filtered = writeFilter(filtered, entry.getWarFilter()); 198 filtered = writeFilter(filtered, entry.getJarFilter()); 199 filtered = writeFilter(filtered, entry.getFilter()); 200 201 if (filtered) 202 { 203 writer.print(ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD); 204 } 205 206 writer.println(); 207 } 208 } 209 } 210 211 212 private boolean writeFilter(boolean filtered, List filter) 213 { 214 if (filtered) 215 { 216 writer.print(ConfigurationConstants.SEPARATOR_KEYWORD); 217 } 218 219 if (filter != null) 220 { 221 if (!filtered) 222 { 223 writer.print(ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD); 224 } 225 226 writer.print(ListUtil.commaSeparatedString(filter, true)); 227 228 filtered = true; 229 } 230 231 return filtered; 232 } 233 234 235 private void writeOption(String optionName, boolean flag) 236 { 237 if (flag) 238 { 239 writer.println(optionName); 240 } 241 } 242 243 244 private void writeOption(String optionName, int argument) 245 { 246 if (argument != 1) 247 { 248 writer.print(optionName); 249 writer.print(' '); 250 writer.println(argument); 251 } 252 } 253 254 255 private void writeOption(String optionName, List arguments) 256 { 257 writeOption(optionName, arguments, false); 258 } 259 260 261 private void writeOption(String optionName, 262 List arguments, 263 boolean replaceInternalClassNames) 264 { 265 if (arguments != null) 266 { 267 if (arguments.isEmpty()) 268 { 269 writer.println(optionName); 270 } 271 else 272 { 273 if (replaceInternalClassNames) 274 { 275 arguments = externalClassNames(arguments); 276 } 277 278 writer.print(optionName); 279 writer.print(' '); 280 writer.println(ListUtil.commaSeparatedString(arguments, true)); 281 } 282 } 283 } 284 285 286 private void writeOption(String optionName, String arguments) 287 { 288 writeOption(optionName, arguments, false); 289 } 290 291 292 private void writeOption(String optionName, 293 String arguments, 294 boolean replaceInternalClassNames) 295 { 296 if (arguments != null) 297 { 298 if (replaceInternalClassNames) 299 { 300 arguments = ClassUtil.externalClassName(arguments); 301 } 302 303 writer.print(optionName); 304 writer.print(' '); 305 writer.println(quotedString(arguments)); 306 } 307 } 308 309 310 private void writeOption(String optionName, File file) 311 { 312 if (file != null) 313 { 314 if (file.getPath().length() > 0) 315 { 316 writer.print(optionName); 317 writer.print(' '); 318 writer.println(relativeFileName(file)); 319 } 320 else 321 { 322 writer.println(optionName); 323 } 324 } 325 } 326 327 328 private void writeOptions(String[] optionNames, 329 List keepClassSpecifications) 330 { 331 if (keepClassSpecifications != null) 332 { 333 for (int index = 0; index < keepClassSpecifications.size(); index++) 334 { 335 writeOption(optionNames, (KeepClassSpecification)keepClassSpecifications.get(index)); 336 } 337 } 338 } 339 340 341 private void writeOption(String[] optionNames, 342 KeepClassSpecification keepClassSpecification) 343 { 344 // Compose the option name. 345 String optionName = optionNames[keepClassSpecification.markConditionally ? 2 : 346 keepClassSpecification.markClasses ? 0 : 347 1]; 348 349 if (keepClassSpecification.markDescriptorClasses) 350 { 351 optionName += ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD + 352 ConfigurationConstants.INCLUDE_DESCRIPTOR_CLASSES_SUBOPTION; 353 } 354 355 if (keepClassSpecification.allowShrinking) 356 { 357 optionName += ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD + 358 ConfigurationConstants.ALLOW_SHRINKING_SUBOPTION; 359 } 360 361 if (keepClassSpecification.allowOptimization) 362 { 363 optionName += ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD + 364 ConfigurationConstants.ALLOW_OPTIMIZATION_SUBOPTION; 365 } 366 367 if (keepClassSpecification.allowObfuscation) 368 { 369 optionName += ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD + 370 ConfigurationConstants.ALLOW_OBFUSCATION_SUBOPTION; 371 } 372 373 // Write out the option with the proper class specification. 374 writeOption(optionName, keepClassSpecification); 375 } 376 377 378 private void writeOptions(String optionName, 379 List classSpecifications) 380 { 381 if (classSpecifications != null) 382 { 383 for (int index = 0; index < classSpecifications.size(); index++) 384 { 385 writeOption(optionName, (ClassSpecification)classSpecifications.get(index)); 386 } 387 } 388 } 389 390 391 private void writeOption(String optionName, 392 ClassSpecification classSpecification) 393 { 394 writer.println(); 395 396 // Write out the comments for this option. 397 writeComments(classSpecification.comments); 398 399 writer.print(optionName); 400 writer.print(' '); 401 402 // Write out the required annotation, if any. 403 if (classSpecification.annotationType != null) 404 { 405 writer.print(ConfigurationConstants.ANNOTATION_KEYWORD); 406 writer.print(ClassUtil.externalType(classSpecification.annotationType)); 407 writer.print(' '); 408 } 409 410 // Write out the class access flags. 411 writer.print(ClassUtil.externalClassAccessFlags(classSpecification.requiredUnsetAccessFlags, 412 ConfigurationConstants.NEGATOR_KEYWORD)); 413 414 writer.print(ClassUtil.externalClassAccessFlags(classSpecification.requiredSetAccessFlags)); 415 416 // Write out the class keyword, if we didn't write the interface 417 // keyword earlier. 418 if (((classSpecification.requiredSetAccessFlags | 419 classSpecification.requiredUnsetAccessFlags) & 420 (ClassConstants.ACC_INTERFACE | 421 ClassConstants.ACC_ENUM)) == 0) 422 { 423 writer.print(ConfigurationConstants.CLASS_KEYWORD); 424 } 425 426 writer.print(' '); 427 428 // Write out the class name. 429 writer.print(classSpecification.className != null ? 430 ClassUtil.externalClassName(classSpecification.className) : 431 ConfigurationConstants.ANY_CLASS_KEYWORD); 432 433 // Write out the extends template, if any. 434 if (classSpecification.extendsAnnotationType != null || 435 classSpecification.extendsClassName != null) 436 { 437 writer.print(' '); 438 writer.print(ConfigurationConstants.EXTENDS_KEYWORD); 439 writer.print(' '); 440 441 // Write out the required extends annotation, if any. 442 if (classSpecification.extendsAnnotationType != null) 443 { 444 writer.print(ConfigurationConstants.ANNOTATION_KEYWORD); 445 writer.print(ClassUtil.externalType(classSpecification.extendsAnnotationType)); 446 writer.print(' '); 447 } 448 449 // Write out the extended class name. 450 writer.print(classSpecification.extendsClassName != null ? 451 ClassUtil.externalClassName(classSpecification.extendsClassName) : 452 ConfigurationConstants.ANY_CLASS_KEYWORD); 453 } 454 455 // Write out the keep field and keep method options, if any. 456 if (classSpecification.fieldSpecifications != null || 457 classSpecification.methodSpecifications != null) 458 { 459 writer.print(' '); 460 writer.println(ConfigurationConstants.OPEN_KEYWORD); 461 462 writeFieldSpecification( classSpecification.fieldSpecifications); 463 writeMethodSpecification(classSpecification.methodSpecifications); 464 465 writer.println(ConfigurationConstants.CLOSE_KEYWORD); 466 } 467 else 468 { 469 writer.println(); 470 } 471 } 472 473 474 475 private void writeComments(String comments) 476 { 477 if (comments != null) 478 { 479 int index = 0; 480 while (index < comments.length()) 481 { 482 int breakIndex = comments.indexOf('\n', index); 483 if (breakIndex < 0) 484 { 485 breakIndex = comments.length(); 486 } 487 488 writer.print('#'); 489 490 if (comments.charAt(index) != ' ') 491 { 492 writer.print(' '); 493 } 494 495 writer.println(comments.substring(index, breakIndex)); 496 497 index = breakIndex + 1; 498 } 499 } 500 } 501 502 503 private void writeFieldSpecification(List memberSpecifications) 504 { 505 if (memberSpecifications != null) 506 { 507 for (int index = 0; index < memberSpecifications.size(); index++) 508 { 509 MemberSpecification memberSpecification = 510 (MemberSpecification)memberSpecifications.get(index); 511 512 writer.print(" "); 513 514 // Write out the required annotation, if any. 515 if (memberSpecification.annotationType != null) 516 { 517 writer.print(ConfigurationConstants.ANNOTATION_KEYWORD); 518 writer.println(ClassUtil.externalType(memberSpecification.annotationType)); 519 writer.print(" "); 520 } 521 522 // Write out the field access flags. 523 writer.print(ClassUtil.externalFieldAccessFlags(memberSpecification.requiredUnsetAccessFlags, 524 ConfigurationConstants.NEGATOR_KEYWORD)); 525 526 writer.print(ClassUtil.externalFieldAccessFlags(memberSpecification.requiredSetAccessFlags)); 527 528 // Write out the field name and descriptor. 529 String name = memberSpecification.name; 530 String descriptor = memberSpecification.descriptor; 531 532 writer.print(descriptor == null ? name == null ? 533 ConfigurationConstants.ANY_FIELD_KEYWORD : 534 ConfigurationConstants.ANY_TYPE_KEYWORD + ' ' + name : 535 ClassUtil.externalFullFieldDescription(0, 536 name == null ? ConfigurationConstants.ANY_CLASS_MEMBER_KEYWORD : name, 537 descriptor)); 538 539 writer.println(ConfigurationConstants.SEPARATOR_KEYWORD); 540 } 541 } 542 } 543 544 545 private void writeMethodSpecification(List memberSpecifications) 546 { 547 if (memberSpecifications != null) 548 { 549 for (int index = 0; index < memberSpecifications.size(); index++) 550 { 551 MemberSpecification memberSpecification = 552 (MemberSpecification)memberSpecifications.get(index); 553 554 writer.print(" "); 555 556 // Write out the required annotation, if any. 557 if (memberSpecification.annotationType != null) 558 { 559 writer.print(ConfigurationConstants.ANNOTATION_KEYWORD); 560 writer.println(ClassUtil.externalType(memberSpecification.annotationType)); 561 writer.print(" "); 562 } 563 564 // Write out the method access flags. 565 writer.print(ClassUtil.externalMethodAccessFlags(memberSpecification.requiredUnsetAccessFlags, 566 ConfigurationConstants.NEGATOR_KEYWORD)); 567 568 writer.print(ClassUtil.externalMethodAccessFlags(memberSpecification.requiredSetAccessFlags)); 569 570 // Write out the method name and descriptor. 571 String name = memberSpecification.name; 572 String descriptor = memberSpecification.descriptor; 573 574 writer.print(descriptor == null ? name == null ? 575 ConfigurationConstants.ANY_METHOD_KEYWORD : 576 ConfigurationConstants.ANY_TYPE_KEYWORD + ' ' + name + ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD + ConfigurationConstants.ANY_ARGUMENTS_KEYWORD + ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD : 577 ClassUtil.externalFullMethodDescription(ClassConstants.METHOD_NAME_INIT, 578 0, 579 name == null ? ConfigurationConstants.ANY_CLASS_MEMBER_KEYWORD : name, 580 descriptor)); 581 582 writer.println(ConfigurationConstants.SEPARATOR_KEYWORD); 583 } 584 } 585 } 586 587 588 /** 589 * Returns a list with external versions of the given list of internal 590 * class names. 591 */ 592 private List externalClassNames(List internalClassNames) 593 { 594 List externalClassNames = new ArrayList(internalClassNames.size()); 595 596 for (int index = 0; index < internalClassNames.size(); index++) 597 { 598 externalClassNames.add(ClassUtil.externalClassName((String)internalClassNames.get(index))); 599 } 600 601 return externalClassNames; 602 } 603 604 605 /** 606 * Returns a relative file name of the given file, if possible. 607 * The file name is also quoted, if necessary. 608 */ 609 private String relativeFileName(File file) 610 { 611 String fileName = file.getAbsolutePath(); 612 613 // See if we can convert the file name into a relative file name. 614 if (baseDir != null) 615 { 616 String baseDirName = baseDir.getAbsolutePath() + File.separator; 617 if (fileName.startsWith(baseDirName)) 618 { 619 fileName = fileName.substring(baseDirName.length()); 620 } 621 } 622 623 return quotedString(fileName); 624 } 625 626 627 /** 628 * Returns a quoted version of the given string, if necessary. 629 */ 630 private String quotedString(String string) 631 { 632 return string.length() == 0 || 633 string.indexOf(' ') >= 0 || 634 string.indexOf('@') >= 0 || 635 string.indexOf('{') >= 0 || 636 string.indexOf('}') >= 0 || 637 string.indexOf('(') >= 0 || 638 string.indexOf(')') >= 0 || 639 string.indexOf(':') >= 0 || 640 string.indexOf(';') >= 0 || 641 string.indexOf(',') >= 0 ? ("'" + string + "'") : 642 ( string ); 643 } 644 645 646 /** 647 * A main method for testing configuration writing. 648 */ 649 public static void main(String[] args) { 650 try 651 { 652 ConfigurationWriter writer = new ConfigurationWriter(new File(args[0])); 653 654 writer.write(new Configuration()); 655 } 656 catch (Exception ex) 657 { 658 ex.printStackTrace(); 659 } 660 } 661} 662