/* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2010 Eric Lafortune (eric@graphics.cornell.edu) * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package proguard; import proguard.classfile.ClassConstants; import proguard.classfile.util.ClassUtil; import proguard.util.ListUtil; import java.io.*; import java.util.*; /** * This class writes ProGuard configurations to a file. * * @author Eric Lafortune */ public class ConfigurationWriter { private static final String[] KEEP_OPTIONS = new String[] { ConfigurationConstants.KEEP_OPTION, ConfigurationConstants.KEEP_CLASS_MEMBERS_OPTION, ConfigurationConstants.KEEP_CLASSES_WITH_MEMBERS_OPTION }; private final PrintWriter writer; private File baseDir; /** * Creates a new ConfigurationWriter for the given file name. */ public ConfigurationWriter(File configurationFile) throws IOException { this(new PrintWriter(new FileWriter(configurationFile))); baseDir = configurationFile.getParentFile(); } /** * Creates a new ConfigurationWriter for the given OutputStream. */ public ConfigurationWriter(OutputStream outputStream) throws IOException { this(new PrintWriter(outputStream)); } /** * Creates a new ConfigurationWriter for the given PrintWriter. */ public ConfigurationWriter(PrintWriter writer) throws IOException { this.writer = writer; } /** * Closes this ConfigurationWriter. */ public void close() throws IOException { writer.close(); } /** * Writes the given configuration. * @param configuration the configuration that is to be written out. * @throws IOException if an IO error occurs while writing the configuration. */ public void write(Configuration configuration) throws IOException { // Write the program class path (input and output entries). writeJarOptions(ConfigurationConstants.INJARS_OPTION, ConfigurationConstants.OUTJARS_OPTION, configuration.programJars); writer.println(); // Write the library class path (output entries only). writeJarOptions(ConfigurationConstants.LIBRARYJARS_OPTION, ConfigurationConstants.LIBRARYJARS_OPTION, configuration.libraryJars); writer.println(); // Write the other options. writeOption(ConfigurationConstants.SKIP_NON_PUBLIC_LIBRARY_CLASSES_OPTION, configuration.skipNonPublicLibraryClasses); writeOption(ConfigurationConstants.DONT_SKIP_NON_PUBLIC_LIBRARY_CLASS_MEMBERS_OPTION, !configuration.skipNonPublicLibraryClassMembers); writeOption(ConfigurationConstants.KEEP_DIRECTORIES_OPTION, configuration.keepDirectories); writeOption(ConfigurationConstants.TARGET_OPTION, ClassUtil.externalClassVersion(configuration.targetClassVersion)); writeOption(ConfigurationConstants.FORCE_PROCESSING_OPTION, configuration.lastModified == Long.MAX_VALUE); writeOption(ConfigurationConstants.DONT_SHRINK_OPTION, !configuration.shrink); writeOption(ConfigurationConstants.PRINT_USAGE_OPTION, configuration.printUsage); writeOption(ConfigurationConstants.DONT_OPTIMIZE_OPTION, !configuration.optimize); writeOption(ConfigurationConstants.OPTIMIZATIONS, configuration.optimizations); writeOption(ConfigurationConstants.OPTIMIZATION_PASSES, configuration.optimizationPasses); writeOption(ConfigurationConstants.ALLOW_ACCESS_MODIFICATION_OPTION, configuration.allowAccessModification); writeOption(ConfigurationConstants.MERGE_INTERFACES_AGGRESSIVELY_OPTION, configuration.mergeInterfacesAggressively); writeOption(ConfigurationConstants.DONT_OBFUSCATE_OPTION, !configuration.obfuscate); writeOption(ConfigurationConstants.PRINT_MAPPING_OPTION, configuration.printMapping); writeOption(ConfigurationConstants.APPLY_MAPPING_OPTION, configuration.applyMapping); writeOption(ConfigurationConstants.OBFUSCATION_DICTIONARY_OPTION, configuration.obfuscationDictionary); writeOption(ConfigurationConstants.CLASS_OBFUSCATION_DICTIONARY_OPTION, configuration.classObfuscationDictionary); writeOption(ConfigurationConstants.PACKAGE_OBFUSCATION_DICTIONARY_OPTION, configuration.packageObfuscationDictionary); writeOption(ConfigurationConstants.OVERLOAD_AGGRESSIVELY_OPTION, configuration.overloadAggressively); writeOption(ConfigurationConstants.USE_UNIQUE_CLASS_MEMBER_NAMES_OPTION, configuration.useUniqueClassMemberNames); writeOption(ConfigurationConstants.DONT_USE_MIXED_CASE_CLASS_NAMES_OPTION, !configuration.useMixedCaseClassNames); writeOption(ConfigurationConstants.KEEP_PACKAGE_NAMES_OPTION, configuration.keepPackageNames, true); writeOption(ConfigurationConstants.FLATTEN_PACKAGE_HIERARCHY_OPTION, configuration.flattenPackageHierarchy, true); writeOption(ConfigurationConstants.REPACKAGE_CLASSES_OPTION, configuration.repackageClasses, true); writeOption(ConfigurationConstants.KEEP_ATTRIBUTES_OPTION, configuration.keepAttributes); writeOption(ConfigurationConstants.KEEP_PARAMETER_NAMES_OPTION, configuration.keepParameterNames); writeOption(ConfigurationConstants.RENAME_SOURCE_FILE_ATTRIBUTE_OPTION, configuration.newSourceFileAttribute); writeOption(ConfigurationConstants.ADAPT_CLASS_STRINGS_OPTION, configuration.adaptClassStrings, true); writeOption(ConfigurationConstants.ADAPT_RESOURCE_FILE_NAMES_OPTION, configuration.adaptResourceFileNames); writeOption(ConfigurationConstants.ADAPT_RESOURCE_FILE_CONTENTS_OPTION, configuration.adaptResourceFileContents); writeOption(ConfigurationConstants.DONT_PREVERIFY_OPTION, !configuration.preverify); writeOption(ConfigurationConstants.MICRO_EDITION_OPTION, configuration.microEdition); writeOption(ConfigurationConstants.VERBOSE_OPTION, configuration.verbose); writeOption(ConfigurationConstants.DONT_NOTE_OPTION, configuration.note, true); writeOption(ConfigurationConstants.DONT_WARN_OPTION, configuration.warn, true); writeOption(ConfigurationConstants.IGNORE_WARNINGS_OPTION, configuration.ignoreWarnings); writeOption(ConfigurationConstants.PRINT_CONFIGURATION_OPTION, configuration.printConfiguration); writeOption(ConfigurationConstants.DUMP_OPTION, configuration.dump); writeOption(ConfigurationConstants.PRINT_SEEDS_OPTION, configuration.printSeeds); writer.println(); // Write the "why are you keeping" options. writeOptions(ConfigurationConstants.WHY_ARE_YOU_KEEPING_OPTION, configuration.whyAreYouKeeping); // Write the keep options. writeOptions(KEEP_OPTIONS, configuration.keep); // Write the "no side effect methods" options. writeOptions(ConfigurationConstants.ASSUME_NO_SIDE_EFFECTS_OPTION, configuration.assumeNoSideEffects); if (writer.checkError()) { throw new IOException("Can't write configuration"); } } private void writeJarOptions(String inputEntryOptionName, String outputEntryOptionName, ClassPath classPath) { if (classPath != null) { for (int index = 0; index < classPath.size(); index++) { ClassPathEntry entry = classPath.get(index); String optionName = entry.isOutput() ? outputEntryOptionName : inputEntryOptionName; writer.print(optionName); writer.print(' '); writer.print(relativeFileName(entry.getFile())); // Append the filters, if any. boolean filtered = false; filtered = writeFilter(filtered, entry.getZipFilter()); filtered = writeFilter(filtered, entry.getEarFilter()); filtered = writeFilter(filtered, entry.getWarFilter()); filtered = writeFilter(filtered, entry.getJarFilter()); filtered = writeFilter(filtered, entry.getFilter()); if (filtered) { writer.print(ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD); } writer.println(); } } } private boolean writeFilter(boolean filtered, List filter) { if (filtered) { writer.print(ConfigurationConstants.SEPARATOR_KEYWORD); } if (filter != null) { if (!filtered) { writer.print(ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD); } writer.print(ListUtil.commaSeparatedString(filter, true)); filtered = true; } return filtered; } private void writeOption(String optionName, boolean flag) { if (flag) { writer.println(optionName); } } private void writeOption(String optionName, int argument) { if (argument != 1) { writer.print(optionName); writer.print(' '); writer.println(argument); } } private void writeOption(String optionName, List arguments) { writeOption(optionName, arguments, false); } private void writeOption(String optionName, List arguments, boolean replaceInternalClassNames) { if (arguments != null) { if (arguments.isEmpty()) { writer.println(optionName); } else { if (replaceInternalClassNames) { arguments = externalClassNames(arguments); } writer.print(optionName); writer.print(' '); writer.println(ListUtil.commaSeparatedString(arguments, true)); } } } private void writeOption(String optionName, String arguments) { writeOption(optionName, arguments, false); } private void writeOption(String optionName, String arguments, boolean replaceInternalClassNames) { if (arguments != null) { if (replaceInternalClassNames) { arguments = ClassUtil.externalClassName(arguments); } writer.print(optionName); writer.print(' '); writer.println(quotedString(arguments)); } } private void writeOption(String optionName, File file) { if (file != null) { if (file.getPath().length() > 0) { writer.print(optionName); writer.print(' '); writer.println(relativeFileName(file)); } else { writer.println(optionName); } } } private void writeOptions(String[] optionNames, List keepClassSpecifications) { if (keepClassSpecifications != null) { for (int index = 0; index < keepClassSpecifications.size(); index++) { writeOption(optionNames, (KeepClassSpecification)keepClassSpecifications.get(index)); } } } private void writeOption(String[] optionNames, KeepClassSpecification keepClassSpecification) { // Compose the option name. String optionName = optionNames[keepClassSpecification.markConditionally ? 2 : keepClassSpecification.markClasses ? 0 : 1]; if (keepClassSpecification.allowShrinking) { optionName += ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD + ConfigurationConstants.ALLOW_SHRINKING_SUBOPTION; } if (keepClassSpecification.allowOptimization) { optionName += ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD + ConfigurationConstants.ALLOW_OPTIMIZATION_SUBOPTION; } if (keepClassSpecification.allowObfuscation) { optionName += ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD + ConfigurationConstants.ALLOW_OBFUSCATION_SUBOPTION; } // Write out the option with the proper class specification. writeOption(optionName, keepClassSpecification); } private void writeOptions(String optionName, List classSpecifications) { if (classSpecifications != null) { for (int index = 0; index < classSpecifications.size(); index++) { writeOption(optionName, (ClassSpecification)classSpecifications.get(index)); } } } private void writeOption(String optionName, ClassSpecification classSpecification) { writer.println(); // Write out the comments for this option. writeComments(classSpecification.comments); writer.print(optionName); writer.print(' '); // Write out the required annotation, if any. if (classSpecification.annotationType != null) { writer.print(ConfigurationConstants.ANNOTATION_KEYWORD); writer.print(ClassUtil.externalType(classSpecification.annotationType)); writer.print(' '); } // Write out the class access flags. writer.print(ClassUtil.externalClassAccessFlags(classSpecification.requiredUnsetAccessFlags, ConfigurationConstants.NEGATOR_KEYWORD)); writer.print(ClassUtil.externalClassAccessFlags(classSpecification.requiredSetAccessFlags)); // Write out the class keyword, if we didn't write the interface // keyword earlier. if (((classSpecification.requiredSetAccessFlags | classSpecification.requiredUnsetAccessFlags) & (ClassConstants.INTERNAL_ACC_INTERFACE | ClassConstants.INTERNAL_ACC_ENUM)) == 0) { writer.print(ConfigurationConstants.CLASS_KEYWORD); } writer.print(' '); // Write out the class name. writer.print(classSpecification.className != null ? ClassUtil.externalClassName(classSpecification.className) : ConfigurationConstants.ANY_CLASS_KEYWORD); // Write out the extends template, if any. if (classSpecification.extendsAnnotationType != null || classSpecification.extendsClassName != null) { writer.print(' '); writer.print(ConfigurationConstants.EXTENDS_KEYWORD); writer.print(' '); // Write out the required extends annotation, if any. if (classSpecification.extendsAnnotationType != null) { writer.print(ConfigurationConstants.ANNOTATION_KEYWORD); writer.print(ClassUtil.externalType(classSpecification.extendsAnnotationType)); writer.print(' '); } // Write out the extended class name. writer.print(classSpecification.extendsClassName != null ? ClassUtil.externalClassName(classSpecification.extendsClassName) : ConfigurationConstants.ANY_CLASS_KEYWORD); } // Write out the keep field and keep method options, if any. if (classSpecification.fieldSpecifications != null || classSpecification.methodSpecifications != null) { writer.print(' '); writer.println(ConfigurationConstants.OPEN_KEYWORD); writeFieldSpecification( classSpecification.fieldSpecifications); writeMethodSpecification(classSpecification.methodSpecifications); writer.println(ConfigurationConstants.CLOSE_KEYWORD); } else { writer.println(); } } private void writeComments(String comments) { if (comments != null) { int index = 0; while (index < comments.length()) { int breakIndex = comments.indexOf('\n', index); if (breakIndex < 0) { breakIndex = comments.length(); } writer.print('#'); if (comments.charAt(index) != ' ') { writer.print(' '); } writer.println(comments.substring(index, breakIndex)); index = breakIndex + 1; } } } private void writeFieldSpecification(List memberSpecifications) { if (memberSpecifications != null) { for (int index = 0; index < memberSpecifications.size(); index++) { MemberSpecification memberSpecification = (MemberSpecification)memberSpecifications.get(index); writer.print(" "); // Write out the required annotation, if any. if (memberSpecification.annotationType != null) { writer.print(ConfigurationConstants.ANNOTATION_KEYWORD); writer.println(ClassUtil.externalType(memberSpecification.annotationType)); writer.print(" "); } // Write out the field access flags. writer.print(ClassUtil.externalFieldAccessFlags(memberSpecification.requiredUnsetAccessFlags, ConfigurationConstants.NEGATOR_KEYWORD)); writer.print(ClassUtil.externalFieldAccessFlags(memberSpecification.requiredSetAccessFlags)); // Write out the field name and descriptor. String name = memberSpecification.name; String descriptor = memberSpecification.descriptor; writer.print(descriptor == null ? name == null ? ConfigurationConstants.ANY_FIELD_KEYWORD : ConfigurationConstants.ANY_TYPE_KEYWORD + ' ' + name : ClassUtil.externalFullFieldDescription(0, name == null ? ConfigurationConstants.ANY_CLASS_MEMBER_KEYWORD : name, descriptor)); writer.println(ConfigurationConstants.SEPARATOR_KEYWORD); } } } private void writeMethodSpecification(List memberSpecifications) { if (memberSpecifications != null) { for (int index = 0; index < memberSpecifications.size(); index++) { MemberSpecification memberSpecification = (MemberSpecification)memberSpecifications.get(index); writer.print(" "); // Write out the required annotation, if any. if (memberSpecification.annotationType != null) { writer.print(ConfigurationConstants.ANNOTATION_KEYWORD); writer.println(ClassUtil.externalType(memberSpecification.annotationType)); writer.print(" "); } // Write out the method access flags. writer.print(ClassUtil.externalMethodAccessFlags(memberSpecification.requiredUnsetAccessFlags, ConfigurationConstants.NEGATOR_KEYWORD)); writer.print(ClassUtil.externalMethodAccessFlags(memberSpecification.requiredSetAccessFlags)); // Write out the method name and descriptor. String name = memberSpecification.name; String descriptor = memberSpecification.descriptor; writer.print(descriptor == null ? name == null ? ConfigurationConstants.ANY_METHOD_KEYWORD : ConfigurationConstants.ANY_TYPE_KEYWORD + ' ' + name + ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD + ConfigurationConstants.ANY_ARGUMENTS_KEYWORD + ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD : ClassUtil.externalFullMethodDescription(ClassConstants.INTERNAL_METHOD_NAME_INIT, 0, name == null ? ConfigurationConstants.ANY_CLASS_MEMBER_KEYWORD : name, descriptor)); writer.println(ConfigurationConstants.SEPARATOR_KEYWORD); } } } /** * Returns a list with external versions of the given list of internal * class names. */ private List externalClassNames(List internalClassNames) { List externalClassNames = new ArrayList(internalClassNames.size()); for (int index = 0; index < internalClassNames.size(); index++) { externalClassNames.add(ClassUtil.externalClassName((String)internalClassNames.get(index))); } return externalClassNames; } /** * Returns a relative file name of the given file, if possible. * The file name is also quoted, if necessary. */ private String relativeFileName(File file) { String fileName = file.getAbsolutePath(); // See if we can convert the file name into a relative file name. if (baseDir != null) { String baseDirName = baseDir.getAbsolutePath() + File.separator; if (fileName.startsWith(baseDirName)) { fileName = fileName.substring(baseDirName.length()); } } return quotedString(fileName); } /** * Returns a quoted version of the given string, if necessary. */ private String quotedString(String string) { return string.length() == 0 || string.indexOf(' ') >= 0 || string.indexOf('@') >= 0 || string.indexOf('{') >= 0 || string.indexOf('}') >= 0 || string.indexOf('(') >= 0 || string.indexOf(')') >= 0 || string.indexOf(':') >= 0 || string.indexOf(';') >= 0 || string.indexOf(',') >= 0 ? ("'" + string + "'") : ( string ); } /** * A main method for testing configuration writing. */ public static void main(String[] args) { try { ConfigurationWriter writer = new ConfigurationWriter(new File(args[0])); writer.write(new Configuration()); } catch (Exception ex) { ex.printStackTrace(); } } }