| /* |
| * ProGuard -- shrinking, optimization, obfuscation, and preverification |
| * of Java bytecode. |
| * |
| * Copyright (c) 2002-2017 Eric Lafortune @ GuardSquare |
| * |
| * 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; |
| |
| // For backward compatibility, the aar and apk filters come |
| // first. |
| filtered = writeFilter(filtered, entry.getAarFilter()); |
| filtered = writeFilter(filtered, entry.getApkFilter()); |
| 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.markDescriptorClasses) |
| { |
| optionName += ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD + |
| ConfigurationConstants.INCLUDE_DESCRIPTOR_CLASSES_SUBOPTION; |
| } |
| |
| 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.ACC_INTERFACE | |
| ClassConstants.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.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(); |
| } |
| } |
| } |