/*
|
* ProGuard -- shrinking, optimization, obfuscation, and preverification
|
* of Java bytecode.
|
*
|
* Copyright (c) 2002-2014 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();
|
|
// Android-added: Write value of -systemjars option to configuration file.
|
// Write the system class path (output entries only).
|
writeJarOptions(ConfigurationConstants.SYSTEMJARS_OPTION,
|
ConfigurationConstants.SYSTEMJARS_OPTION,
|
configuration.systemJars);
|
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();
|
}
|
}
|
}
|