| /* | |
| * 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(); | |
| } | |
| } | |
| } |