diff options
Diffstat (limited to 'src/proguard/ConfigurationWriter.java')
-rw-r--r-- | src/proguard/ConfigurationWriter.java | 643 |
1 files changed, 643 insertions, 0 deletions
diff --git a/src/proguard/ConfigurationWriter.java b/src/proguard/ConfigurationWriter.java new file mode 100644 index 0000000..5d112d6 --- /dev/null +++ b/src/proguard/ConfigurationWriter.java @@ -0,0 +1,643 @@ +/* + * ProGuard -- shrinking, optimization, obfuscation, and preverification + * of Java bytecode. + * + * Copyright (c) 2002-2009 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.List; + + +/** + * 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.DONT_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.optimize ? ListUtil.commaSeparatedString(configuration.optimizations) : null); + 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.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); + } + + for (int index = 0; index < filter.size(); index++) + { + if (index > 0) + { + writer.print(ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD); + } + + writer.print(quotedString((String)filter.get(index))); + } + + 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 + { + String argumentString = ListUtil.commaSeparatedString(arguments); + + if (replaceInternalClassNames) + { + argumentString = ClassUtil.externalClassName(argumentString); + } + + writer.print(optionName); + writer.print(' '); + writer.println(quotedString(argumentString)); + } + } + } + + + 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 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(); + } + } +} |