blob: bd9eef632453f079d51d6f89e2a4a79e9369f824 [file] [log] [blame]
/*
* 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.gradle;
import groovy.lang.Closure;
import org.gradle.api.DefaultTask;
import org.gradle.api.file.*;
import org.gradle.api.logging.*;
import org.gradle.api.tasks.*;
import proguard.*;
import proguard.classfile.*;
import proguard.classfile.util.ClassUtil;
import proguard.util.ListUtil;
import java.io.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
/**
* This Task allows to configure and run ProGuard from Gradle.
*
* @author Eric Lafortune
*/
public class ProGuardTask extends DefaultTask
{
// Accumulated input and output, for the sake of Gradle's lazy file
// resolution and lazy task execution.
private final List inJarFiles = new ArrayList();
private final List inJarFilters = new ArrayList();
private final List outJarFiles = new ArrayList();
private final List outJarFilters = new ArrayList();
private final List inJarCounts = new ArrayList();
private final List libraryJarFiles = new ArrayList();
private final List libraryJarFilters = new ArrayList();
private final List configurationFiles = new ArrayList();
// Accumulated configuration.
private final Configuration configuration = new Configuration();
// Field acting as a parameter for the class member specification methods.
private ClassSpecification classSpecification;
// Gradle task inputs and outputs, because annotations on the List fields
// (private or not) don't seem to work. Private methods don't work either,
// but package visible or protected methods are ok.
@InputFiles
protected FileCollection getInJarFileCollection()
{
return getProject().files(inJarFiles);
}
@Optional @OutputFiles
protected FileCollection getOutJarFileCollection()
{
return getProject().files(outJarFiles);
}
@InputFiles
protected FileCollection getLibraryJarFileCollection()
{
return getProject().files(libraryJarFiles);
}
@InputFiles
protected FileCollection getConfigurationFileCollection()
{
return getProject().files(configurationFiles);
}
// Convenience methods to retrieve settings from outside the task.
/**
* Returns the collected list of input files (directory, jar, aar, etc,
* represented as Object, String, File, etc).
*/
public List getInJarFiles()
{
return inJarFiles;
}
/**
* Returns the collected list of filters (represented as argument Maps)
* corresponding to the list of input files.
*/
public List getInJarFilters()
{
return inJarFilters;
}
/**
* Returns the collected list of output files (directory, jar, aar, etc,
* represented as Object, String, File, etc).
*/
public List getOutJarFiles()
{
return outJarFiles;
}
/**
* Returns the collected list of filters (represented as argument Maps)
* corresponding to the list of output files.
*/
public List getOutJarFilters()
{
return outJarFilters;
}
/**
* Returns the list with the numbers of input files that correspond to the
* list of output files.
*
* For instance, [2, 3] means that
* the contents of the first 2 input files go to the first output file and
* the contents of the next 3 input files go to the second output file.
*/
public List getInJarCounts()
{
return inJarCounts;
}
/**
* Returns the collected list of library files (directory, jar, aar, etc,
* represented as Object, String, File, etc).
*/
public List getLibraryJarFiles()
{
return libraryJarFiles;
}
/**
* Returns the collected list of filters (represented as argument Maps)
* corresponding to the list of library files.
*/
public List getLibraryJarFilters()
{
return libraryJarFilters;
}
/**
* Returns the collected list of configuration files to be included
* (represented as Object, String, File, etc).
*/
public List getConfigurationFiles()
{
return configurationFiles;
}
// Gradle task settings corresponding to all ProGuard options.
public void configuration(Object configurationFiles)
throws ParseException, IOException
{
// Just collect the arguments, so they can be resolved lazily.
this.configurationFiles.add(configurationFiles);
}
public void injars(Object inJarFiles)
throws ParseException
{
injars(null, inJarFiles);
}
public void injars(Map filterArgs, Object inJarFiles)
throws ParseException
{
// Just collect the arguments, so they can be resolved lazily.
this.inJarFiles.add(inJarFiles);
this.inJarFilters.add(filterArgs);
}
public void outjars(Object outJarFiles)
throws ParseException
{
outjars(null, outJarFiles);
}
public void outjars(Map filterArgs, Object outJarFiles)
throws ParseException
{
// Just collect the arguments, so they can be resolved lazily.
this.outJarFiles.add(outJarFiles);
this.outJarFilters.add(filterArgs);
this.inJarCounts.add(Integer.valueOf(inJarFiles.size()));
}
public void libraryjars(Object libraryJarFiles)
throws ParseException
{
libraryjars(null, libraryJarFiles);
}
public void libraryjars(Map filterArgs, Object libraryJarFiles)
throws ParseException
{
// Just collect the arguments, so they can be resolved lazily.
this.libraryJarFiles.add(libraryJarFiles);
this.libraryJarFilters.add(filterArgs);
}
// Hack: support the keyword without parentheses in Groovy.
public Object getskipnonpubliclibraryclasses()
{
skipnonpubliclibraryclasses();
return null;
}
public void skipnonpubliclibraryclasses()
{
configuration.skipNonPublicLibraryClasses = true;
}
// Hack: support the keyword without parentheses in Groovy.
public Object getdontskipnonpubliclibraryclassmembers()
{
dontskipnonpubliclibraryclassmembers();
return null;
}
public void dontskipnonpubliclibraryclassmembers()
{
configuration.skipNonPublicLibraryClassMembers = false;
}
// Hack: support the keyword without parentheses in Groovy.
public Object getkeepdirectories()
{
keepdirectories();
return null;
}
public void keepdirectories()
{
keepdirectories(null);
}
public void keepdirectories(String filter)
{
configuration.keepDirectories =
extendFilter(configuration.keepDirectories, filter);
}
public void target(String targetClassVersion)
{
configuration.targetClassVersion =
ClassUtil.internalClassVersion(targetClassVersion);
}
// Hack: support the keyword without parentheses in Groovy.
public Object getforceprocessing()
{
forceprocessing();
return null;
}
public void forceprocessing()
{
configuration.lastModified = Long.MAX_VALUE;
}
public void keep(String classSpecificationString)
throws ParseException
{
keep(null, classSpecificationString);
}
public void keep(Map keepArgs,
String classSpecificationString)
throws ParseException
{
configuration.keep =
extendClassSpecifications(configuration.keep,
createKeepClassSpecification(false,
true,
false,
keepArgs,
classSpecificationString));
}
public void keep(Map keepClassSpecificationArgs)
throws ParseException
{
keep(keepClassSpecificationArgs, (Closure)null);
}
public void keep(Map keepClassSpecificationArgs,
Closure classMembersClosure)
throws ParseException
{
configuration.keep =
extendClassSpecifications(configuration.keep,
createKeepClassSpecification(false,
true,
false,
keepClassSpecificationArgs,
classMembersClosure));
}
public void keepclassmembers(String classSpecificationString)
throws ParseException
{
keepclassmembers(null, classSpecificationString);
}
public void keepclassmembers(Map keepArgs,
String classSpecificationString)
throws ParseException
{
configuration.keep =
extendClassSpecifications(configuration.keep,
createKeepClassSpecification(false,
false,
false,
keepArgs,
classSpecificationString));
}
public void keepclassmembers(Map keepClassSpecificationArgs)
throws ParseException
{
keepclassmembers(keepClassSpecificationArgs, (Closure)null);
}
public void keepclassmembers(Map keepClassSpecificationArgs,
Closure classMembersClosure)
throws ParseException
{
configuration.keep =
extendClassSpecifications(configuration.keep,
createKeepClassSpecification(false,
false,
false,
keepClassSpecificationArgs,
classMembersClosure));
}
public void keepclasseswithmembers(String classSpecificationString)
throws ParseException
{
keepclasseswithmembers(null, classSpecificationString);
}
public void keepclasseswithmembers(Map keepArgs,
String classSpecificationString)
throws ParseException
{
configuration.keep =
extendClassSpecifications(configuration.keep,
createKeepClassSpecification(false,
false,
true,
keepArgs,
classSpecificationString));
}
public void keepclasseswithmembers(Map keepClassSpecificationArgs)
throws ParseException
{
keepclasseswithmembers(keepClassSpecificationArgs, (Closure)null);
}
public void keepclasseswithmembers(Map keepClassSpecificationArgs,
Closure classMembersClosure)
throws ParseException
{
configuration.keep =
extendClassSpecifications(configuration.keep,
createKeepClassSpecification(false,
false,
true,
keepClassSpecificationArgs,
classMembersClosure));
}
public void keepnames(String classSpecificationString)
throws ParseException
{
keepnames(null, classSpecificationString);
}
public void keepnames(Map keepArgs,
String classSpecificationString)
throws ParseException
{
configuration.keep =
extendClassSpecifications(configuration.keep,
createKeepClassSpecification(true,
true,
false,
keepArgs,
classSpecificationString));
}
public void keepnames(Map keepClassSpecificationArgs)
throws ParseException
{
keepnames(keepClassSpecificationArgs, (Closure)null);
}
public void keepnames(Map keepClassSpecificationArgs,
Closure classMembersClosure)
throws ParseException
{
configuration.keep =
extendClassSpecifications(configuration.keep,
createKeepClassSpecification(true,
true,
false,
keepClassSpecificationArgs,
classMembersClosure));
}
public void keepclassmembernames(String classSpecificationString)
throws ParseException
{
keepclassmembernames(null, classSpecificationString);
}
public void keepclassmembernames(Map keepArgs,
String classSpecificationString)
throws ParseException
{
configuration.keep =
extendClassSpecifications(configuration.keep,
createKeepClassSpecification(true,
false,
false,
keepArgs,
classSpecificationString));
}
public void keepclassmembernames(Map keepClassSpecificationArgs)
throws ParseException
{
keepclassmembernames(keepClassSpecificationArgs, (Closure)null);
}
public void keepclassmembernames(Map keepClassSpecificationArgs,
Closure classMembersClosure)
throws ParseException
{
configuration.keep =
extendClassSpecifications(configuration.keep,
createKeepClassSpecification(true,
false,
false,
keepClassSpecificationArgs,
classMembersClosure));
}
public void keepclasseswithmembernames(String classSpecificationString)
throws ParseException
{
keepclasseswithmembernames(null, classSpecificationString);
}
public void keepclasseswithmembernames(Map keepArgs,
String classSpecificationString)
throws ParseException
{
configuration.keep =
extendClassSpecifications(configuration.keep,
createKeepClassSpecification(true,
false,
true,
keepArgs,
classSpecificationString));
}
public void keepclasseswithmembernames(Map keepClassSpecificationArgs)
throws ParseException
{
keepclasseswithmembernames(keepClassSpecificationArgs, (Closure)null);
}
public void keepclasseswithmembernames(Map keepClassSpecificationArgs,
Closure classMembersClosure)
throws ParseException
{
configuration.keep =
extendClassSpecifications(configuration.keep,
createKeepClassSpecification(true,
false,
true,
keepClassSpecificationArgs,
classMembersClosure));
}
// Hack: support the keyword without parentheses in Groovy.
public Object getprintseeds()
{
printseeds();
return null;
}
public void printseeds()
{
configuration.printSeeds = Configuration.STD_OUT;
}
public void printseeds(Object printSeeds)
throws ParseException
{
configuration.printSeeds = getProject().file(printSeeds);
}
// Hack: support the keyword without parentheses in Groovy.
public Object getdontshrink()
{
dontshrink();
return null;
}
public void dontshrink()
{
configuration.shrink = false;
}
// Hack: support the keyword without parentheses in Groovy.
public Object getprintusage()
{
printusage();
return null;
}
public void printusage()
{
configuration.printUsage = Configuration.STD_OUT;
}
public void printusage(Object printUsage)
throws ParseException
{
configuration.printUsage = getProject().file(printUsage);
}
public void whyareyoukeeping(String classSpecificationString)
throws ParseException
{
configuration.whyAreYouKeeping =
extendClassSpecifications(configuration.whyAreYouKeeping,
createClassSpecification(classSpecificationString));
}
public void whyareyoukeeping(Map classSpecificationArgs)
throws ParseException
{
whyareyoukeeping(classSpecificationArgs, null);
}
public void whyareyoukeeping(Map classSpecificationArgs,
Closure classMembersClosure)
throws ParseException
{
configuration.whyAreYouKeeping =
extendClassSpecifications(configuration.whyAreYouKeeping,
createClassSpecification(classSpecificationArgs,
classMembersClosure));
}
// Hack: support the keyword without parentheses in Groovy.
public Object getdontoptimize()
{
dontoptimize();
return null;
}
public void dontoptimize()
{
configuration.optimize = false;
}
public void optimizations(String filter)
{
configuration.optimizations =
extendFilter(configuration.optimizations, filter);
}
public void optimizationpasses(int optimizationPasses)
{
configuration.optimizationPasses = optimizationPasses;
}
public void assumenosideeffects(String classSpecificationString)
throws ParseException
{
configuration.assumeNoSideEffects =
extendClassSpecifications(configuration.assumeNoSideEffects,
createClassSpecification(classSpecificationString));
}
public void assumenosideeffects(Map classSpecificationArgs,
Closure classMembersClosure)
throws ParseException
{
configuration.assumeNoSideEffects =
extendClassSpecifications(configuration.assumeNoSideEffects,
createClassSpecification(classSpecificationArgs,
classMembersClosure));
}
// Hack: support the keyword without parentheses in Groovy.
public Object getallowaccessmodification()
{
allowaccessmodification();
return null;
}
public void allowaccessmodification()
{
configuration.allowAccessModification = true;
}
// Hack: support the keyword without parentheses in Groovy.
public Object getmergeinterfacesaggressively()
{
mergeinterfacesaggressively();
return null;
}
public void mergeinterfacesaggressively()
{
configuration.mergeInterfacesAggressively = true;
}
// Hack: support the keyword without parentheses in Groovy.
public Object getdontobfuscate()
{
dontobfuscate();
return null;
}
public void dontobfuscate()
{
configuration.obfuscate = false;
}
// Hack: support the keyword without parentheses in Groovy.
public Object getprintmapping()
{
printmapping();
return null;
}
public void printmapping()
{
configuration.printMapping = Configuration.STD_OUT;
}
public void printmapping(Object printMapping)
throws ParseException
{
configuration.printMapping = getProject().file(printMapping);
}
public void applymapping(Object applyMapping)
throws ParseException
{
configuration.applyMapping = getProject().file(applyMapping);
}
public void obfuscationdictionary(Object obfuscationDictionary)
throws ParseException
{
configuration.obfuscationDictionary =
getProject().file(obfuscationDictionary);
}
public void classobfuscationdictionary(Object classObfuscationDictionary)
throws ParseException
{
configuration.classObfuscationDictionary =
getProject().file(classObfuscationDictionary);
}
public void packageobfuscationdictionary(Object packageObfuscationDictionary)
throws ParseException
{
configuration.packageObfuscationDictionary =
getProject().file(packageObfuscationDictionary);
}
// Hack: support the keyword without parentheses in Groovy.
public Object getoverloadaggressively()
{
overloadaggressively();
return null;
}
public void overloadaggressively()
{
configuration.overloadAggressively = true;
}
// Hack: support the keyword without parentheses in Groovy.
public Object getuseuniqueclassmembernames()
{
useuniqueclassmembernames();
return null;
}
public void useuniqueclassmembernames()
{
configuration.useUniqueClassMemberNames = true;
}
// Hack: support the keyword without parentheses in Groovy.
public Object getdontusemixedcaseclassnames()
{
dontusemixedcaseclassnames();
return null;
}
public void dontusemixedcaseclassnames()
{
configuration.useMixedCaseClassNames = false;
}
// Hack: support the keyword without parentheses in Groovy.
public Object getkeeppackagenames()
{
keeppackagenames();
return null;
}
public void keeppackagenames()
{
keeppackagenames(null);
}
public void keeppackagenames(String filter)
{
configuration.keepPackageNames =
extendFilter(configuration.keepPackageNames, filter, true);
}
// Hack: support the keyword without parentheses in Groovy.
public Object getflattenpackagehierarchy()
{
flattenpackagehierarchy();
return null;
}
public void flattenpackagehierarchy()
{
flattenpackagehierarchy("");
}
public void flattenpackagehierarchy(String flattenPackageHierarchy)
{
configuration.flattenPackageHierarchy =
ClassUtil.internalClassName(flattenPackageHierarchy);
}
// Hack: support the keyword without parentheses in Groovy.
public Object getrepackageclasses()
{
repackageclasses();
return null;
}
public void repackageclasses()
{
repackageclasses("");
}
public void repackageclasses(String repackageClasses)
{
configuration.repackageClasses =
ClassUtil.internalClassName(repackageClasses);
}
// Hack: support the keyword without parentheses in Groovy.
public Object getkeepattributes()
{
keepattributes();
return null;
}
public void keepattributes()
{
keepattributes(null);
}
public void keepattributes(String filter)
{
configuration.keepAttributes =
extendFilter(configuration.keepAttributes, filter);
}
// Hack: support the keyword without parentheses in Groovy.
public Object getkeepparameternames()
{
keepparameternames();
return null;
}
public void keepparameternames()
{
configuration.keepParameterNames = true;
}
// Hack: support the keyword without parentheses in Groovy.
public Object getrenamesourcefileattribute()
{
renamesourcefileattribute();
return null;
}
public void renamesourcefileattribute()
{
renamesourcefileattribute("");
}
public void renamesourcefileattribute(String newSourceFileAttribute)
{
configuration.newSourceFileAttribute = newSourceFileAttribute;
}
// Hack: support the keyword without parentheses in Groovy.
public Object getadaptclassstrings()
{
adaptclassstrings();
return null;
}
public void adaptclassstrings()
{
adaptclassstrings(null);
}
public void adaptclassstrings(String filter)
{
configuration.adaptClassStrings =
extendFilter(configuration.adaptClassStrings, filter, true);
}
// Hack: support the keyword without parentheses in Groovy.
public Object getadaptresourcefilenames()
{
adaptresourcefilenames();
return null;
}
public void adaptresourcefilenames()
{
adaptresourcefilenames(null);
}
public void adaptresourcefilenames(String filter)
{
configuration.adaptResourceFileNames =
extendFilter(configuration.adaptResourceFileNames, filter);
}
// Hack: support the keyword without parentheses in Groovy.
public Object getadaptresourcefilecontents()
{
adaptresourcefilecontents();
return null;
}
public void adaptresourcefilecontents()
{
adaptresourcefilecontents(null);
}
public void adaptresourcefilecontents(String filter)
{
configuration.adaptResourceFileContents =
extendFilter(configuration.adaptResourceFileContents, filter);
}
// Hack: support the keyword without parentheses in Groovy.
public Object getdontpreverify()
{
dontpreverify();
return null;
}
public void dontpreverify()
{
configuration.preverify = false;
}
// Hack: support the keyword without parentheses in Groovy.
public Object getmicroedition()
{
microedition();
return null;
}
public void microedition()
{
configuration.microEdition = true;
}
// Hack: support the keyword without parentheses in Groovy.
public Object getverbose()
{
verbose();
return null;
}
public void verbose()
{
configuration.verbose = true;
}
// Hack: support the keyword without parentheses in Groovy.
public Object getdontnote()
{
dontnote();
return null;
}
public void dontnote()
{
dontnote(null);
}
public void dontnote(String filter)
{
configuration.note = extendFilter(configuration.note, filter, true);
}
// Hack: support the keyword without parentheses in Groovy.
public Object getdontwarn()
{
dontwarn();
return null;
}
public void dontwarn()
{
dontwarn(null);
}
public void dontwarn(String filter)
{
configuration.warn = extendFilter(configuration.warn, filter, true);
}
// Hack: support the keyword without parentheses in Groovy.
public Object getignorewarnings()
{
ignorewarnings();
return null;
}
public void ignorewarnings()
{
configuration.ignoreWarnings = true;
}
// Hack: support the keyword without parentheses in Groovy.
public Object getprintconfiguration()
{
printconfiguration();
return null;
}
public void printconfiguration()
{
configuration.printConfiguration = Configuration.STD_OUT;
}
public void printconfiguration(Object printConfiguration)
throws ParseException
{
configuration.printConfiguration =
getProject().file(printConfiguration);
}
// Hack: support the keyword without parentheses in Groovy.
public Object getdump()
{
dump();
return null;
}
public void dump()
{
configuration.dump = Configuration.STD_OUT;
}
public void dump(Object dump)
throws ParseException
{
configuration.dump = getProject().file(dump);
}
// Class member methods.
public void field(Map memberSpecificationArgs)
throws ParseException
{
if (classSpecification == null)
{
throw new IllegalArgumentException("The 'field' method can only be used nested inside a class specification.");
}
classSpecification.addField(createMemberSpecification(false,
false,
memberSpecificationArgs));
}
public void constructor(Map memberSpecificationArgs)
throws ParseException
{
if (classSpecification == null)
{
throw new IllegalArgumentException("The 'constructor' method can only be used nested inside a class specification.");
}
classSpecification.addMethod(createMemberSpecification(true,
true,
memberSpecificationArgs));
}
public void method(Map memberSpecificationArgs)
throws ParseException
{
if (classSpecification == null)
{
throw new IllegalArgumentException("The 'method' method can only be used nested inside a class specification.");
}
classSpecification.addMethod(createMemberSpecification(true,
false,
memberSpecificationArgs));
}
// Gradle task execution.
@TaskAction
public void proguard()
throws ParseException, IOException
{
// Let the logging manager capture the standard output and errors from
// ProGuard.
LoggingManager loggingManager = getLogging();
loggingManager.captureStandardOutput(LogLevel.INFO);
loggingManager.captureStandardError(LogLevel.WARN);
// Run ProGuard with the collected configuration.
new ProGuard(getConfiguration()).execute();
}
/**
* Returns the configuration collected so far, resolving files and
* reading included configurations.
*/
private Configuration getConfiguration() throws IOException, ParseException
{
// Weave the input jars and the output jars into a single class path,
// with lazy resolution of the files.
configuration.programJars = new ClassPath();
int outJarIndex = 0;
int inJarCount = inJarCounts.size() == 0 ? -1 :
((Integer)inJarCounts.get(0)).intValue();
for (int inJarIndex = 0; inJarIndex < inJarFiles.size(); inJarIndex++)
{
configuration.programJars =
extendClassPath(configuration.programJars,
inJarFiles.get(inJarIndex),
(Map)inJarFilters.get(inJarIndex),
false);
while (inJarIndex == inJarCount - 1)
{
configuration.programJars =
extendClassPath(configuration.programJars,
outJarFiles.get(outJarIndex),
(Map)outJarFilters.get(outJarIndex),
true);
outJarIndex++;
inJarCount = inJarCounts.size() == outJarIndex ? -1 :
((Integer)inJarCounts.get(outJarIndex)).intValue();
}
}
// Copy the library jars into a single class path, with lazy resolution
// of the files.
configuration.libraryJars = new ClassPath();
for (int libraryJarIndex = 0; libraryJarIndex < libraryJarFiles.size(); libraryJarIndex++)
{
configuration.libraryJars =
extendClassPath(configuration.libraryJars,
libraryJarFiles.get(libraryJarIndex),
(Map)libraryJarFilters.get(libraryJarIndex),
false);
}
// Lazily apply the external configuration files.
ConfigurableFileCollection fileCollection =
getProject().files(configurationFiles);
Iterator<File> files = fileCollection.iterator();
while (files.hasNext())
{
ConfigurationParser parser =
new ConfigurationParser(files.next(), System.getProperties());
try
{
parser.parse(configuration);
}
finally
{
parser.close();
}
}
// Make sure the code is processed. Gradle has already checked that it
// was necessary.
configuration.lastModified = Long.MAX_VALUE;
return configuration;
}
// Small utility methods.
/**
* Extends the given class path with the given filtered input or output
* files.
*/
private ClassPath extendClassPath(ClassPath classPath,
Object files,
Map filterArgs,
boolean output)
{
ConfigurableFileCollection fileCollection = getProject().files(files);
if (classPath == null)
{
classPath = new ClassPath();
}
Iterator fileIterator = fileCollection.iterator();
while (fileIterator.hasNext())
{
File file = (File)fileIterator.next();
if (output || file.exists())
{
// Create the class path entry.
ClassPathEntry classPathEntry = new ClassPathEntry(file, output);
// Add any filters to the class path entry.
if (filterArgs != null)
{
classPathEntry.setFilter(ListUtil.commaSeparatedList((String)filterArgs.get("filter")));
classPathEntry.setApkFilter(ListUtil.commaSeparatedList((String)filterArgs.get("apkfilter")));
classPathEntry.setJarFilter(ListUtil.commaSeparatedList((String)filterArgs.get("jarfilter")));
classPathEntry.setAarFilter(ListUtil.commaSeparatedList((String)filterArgs.get("aarfilter")));
classPathEntry.setWarFilter(ListUtil.commaSeparatedList((String)filterArgs.get("warfilter")));
classPathEntry.setEarFilter(ListUtil.commaSeparatedList((String)filterArgs.get("earfilter")));
classPathEntry.setZipFilter(ListUtil.commaSeparatedList((String)filterArgs.get("zipfilter")));
}
classPath.add(classPathEntry);
}
}
return classPath;
}
/**
* Creates specifications to keep classes and class members, based on the
* given parameters.
*/
private KeepClassSpecification createKeepClassSpecification(boolean allowShrinking,
boolean markClasses,
boolean markConditionally,
Map keepArgs,
String classSpecificationString)
throws ParseException
{
ClassSpecification classSpecification =
createClassSpecification(classSpecificationString);
return
createKeepClassSpecification(allowShrinking,
markClasses,
markConditionally,
keepArgs,
classSpecification);
}
/**
* Creates specifications to keep classes and class members, based on the
* given parameters.
*/
private KeepClassSpecification createKeepClassSpecification(boolean allowShrinking,
boolean markClasses,
boolean markConditionally,
Map classSpecificationArgs,
Closure classMembersClosure)
throws ParseException
{
ClassSpecification classSpecification =
createClassSpecification(classSpecificationArgs,
classMembersClosure);
return
createKeepClassSpecification(allowShrinking,
markClasses,
markConditionally,
classSpecificationArgs,
classSpecification);
}
/**
* Creates specifications to keep classes and class members, based on the
* given parameters.
*/
private KeepClassSpecification createKeepClassSpecification(boolean allowShrinking,
boolean markClasses,
boolean markConditionally,
Map keepArgs,
ClassSpecification classSpecification)
{
return
new KeepClassSpecification(markClasses,
markConditionally,
retrieveBoolean(keepArgs, "includedescriptorclasses", false),
retrieveBoolean(keepArgs, "allowshrinking", allowShrinking),
retrieveBoolean(keepArgs, "allowoptimization", false),
retrieveBoolean(keepArgs, "allowobfuscation", false),
classSpecification);
}
/**
* Creates specifications to keep classes and class members, based on the
* given ProGuard-style class specification.
*/
private ClassSpecification createClassSpecification(String classSpecificationString)
throws ParseException
{
try
{
ConfigurationParser parser =
new ConfigurationParser(new String[] { classSpecificationString }, null);
try
{
return parser.parseClassSpecificationArguments();
}
finally
{
parser.close();
}
}
catch (IOException e)
{
throw new ParseException(e.getMessage());
}
}
/**
* Creates a specification of classes and class members, based on the
* given parameters.
*/
private ClassSpecification createClassSpecification(Map classSpecificationArgs,
Closure classMembersClosure)
throws ParseException
{
// Extract the arguments.
String access = (String)classSpecificationArgs.get("access");
String annotation = (String)classSpecificationArgs.get("annotation");
String type = (String)classSpecificationArgs.get("type");
String name = (String)classSpecificationArgs.get("name");
String extendsAnnotation = (String)classSpecificationArgs.get("extendsannotation");
String extends_ = (String)classSpecificationArgs.get("extends");
if (extends_ == null)
{
extends_ = (String)classSpecificationArgs.get("implements");
}
// Create the class specification.
ClassSpecification classSpecification =
new ClassSpecification(null,
requiredClassAccessFlags(true, access, type),
requiredClassAccessFlags(false, access, type),
annotation != null ? ClassUtil.internalType(annotation) : null,
name != null ? ClassUtil.internalClassName(name) : null,
extendsAnnotation != null ? ClassUtil.internalType(extendsAnnotation) : null,
extends_ != null ? ClassUtil.internalClassName(extends_) : null);
// Initialize the class specification with its closure.
if (classMembersClosure != null)
{
// Temporarily remember the class specification, so we can add
// class member specifications.
this.classSpecification = classSpecification;
classMembersClosure.call(classSpecification);
this.classSpecification = null;
}
return classSpecification;
}
/**
* Parses the class access flags that must be set (or not), based on the
* given ProGuard-style flag specification.
*/
private int requiredClassAccessFlags(boolean set,
String access,
String type)
throws ParseException
{
int accessFlags = 0;
if (access != null)
{
StringTokenizer tokenizer = new StringTokenizer(access, " ,");
while (tokenizer.hasMoreTokens())
{
String token = tokenizer.nextToken();
if (token.startsWith("!") ^ set)
{
String strippedToken = token.startsWith("!") ?
token.substring(1) :
token;
int accessFlag =
strippedToken.equals(JavaConstants.ACC_PUBLIC) ? ClassConstants.ACC_PUBLIC :
strippedToken.equals(JavaConstants.ACC_FINAL) ? ClassConstants.ACC_FINAL :
strippedToken.equals(JavaConstants.ACC_ABSTRACT) ? ClassConstants.ACC_ABSTRACT :
strippedToken.equals(JavaConstants.ACC_SYNTHETIC) ? ClassConstants.ACC_SYNTHETIC :
strippedToken.equals(JavaConstants.ACC_ANNOTATION) ? ClassConstants.ACC_ANNOTATTION :
0;
if (accessFlag == 0)
{
throw new ParseException("Incorrect class access modifier ["+strippedToken+"]");
}
accessFlags |= accessFlag;
}
}
}
if (type != null && (type.startsWith("!") ^ set))
{
int accessFlag =
type.equals("class") ? 0 :
type.equals( JavaConstants.ACC_INTERFACE) ||
type.equals("!" + JavaConstants.ACC_INTERFACE) ? ClassConstants.ACC_INTERFACE :
type.equals( JavaConstants.ACC_ENUM) ||
type.equals("!" + JavaConstants.ACC_ENUM) ? ClassConstants.ACC_ENUM :
-1;
if (accessFlag == -1)
{
throw new ParseException("Incorrect class type ["+type+"]");
}
accessFlags |= accessFlag;
}
return accessFlags;
}
/**
* Creates a specification of class members, based on the given parameters.
*/
private MemberSpecification createMemberSpecification(boolean isMethod,
boolean isConstructor,
Map classSpecificationArgs)
throws ParseException
{
// Extract the arguments.
String access = (String)classSpecificationArgs.get("access");
String type = (String)classSpecificationArgs.get("type");
String annotation = (String)classSpecificationArgs.get("annotation");
String name = (String)classSpecificationArgs.get("name");
String parameters = (String)classSpecificationArgs.get("parameters");
// Perform some basic conversions and checks on the attributes.
if (annotation != null)
{
annotation = ClassUtil.internalType(annotation);
}
if (isMethod)
{
if (isConstructor)
{
if (type != null)
{
throw new ParseException("Type attribute not allowed in constructor specification ["+type+"]");
}
if (parameters != null)
{
type = JavaConstants.TYPE_VOID;
}
name = ClassConstants.METHOD_NAME_INIT;
}
else if ((type != null) ^ (parameters != null))
{
throw new ParseException("Type and parameters attributes must always be present in combination in method specification");
}
}
else
{
if (parameters != null)
{
throw new ParseException("Parameters attribute not allowed in field specification ["+parameters+"]");
}
}
List parameterList = ListUtil.commaSeparatedList(parameters);
String descriptor =
parameters != null ? ClassUtil.internalMethodDescriptor(type, parameterList) :
type != null ? ClassUtil.internalType(type) :
null;
return new MemberSpecification(requiredMemberAccessFlags(true, access),
requiredMemberAccessFlags(false, access),
annotation,
name,
descriptor);
}
/**
* Parses the class member access flags that must be set (or not), based on
* the given ProGuard-style flag specification.
*/
private int requiredMemberAccessFlags(boolean set,
String access)
throws ParseException
{
int accessFlags = 0;
if (access != null)
{
StringTokenizer tokenizer = new StringTokenizer(access, " ,");
while (tokenizer.hasMoreTokens())
{
String token = tokenizer.nextToken();
if (token.startsWith("!") ^ set)
{
String strippedToken = token.startsWith("!") ?
token.substring(1) :
token;
int accessFlag =
strippedToken.equals(JavaConstants.ACC_PUBLIC) ? ClassConstants.ACC_PUBLIC :
strippedToken.equals(JavaConstants.ACC_PRIVATE) ? ClassConstants.ACC_PRIVATE :
strippedToken.equals(JavaConstants.ACC_PROTECTED) ? ClassConstants.ACC_PROTECTED :
strippedToken.equals(JavaConstants.ACC_STATIC) ? ClassConstants.ACC_STATIC :
strippedToken.equals(JavaConstants.ACC_FINAL) ? ClassConstants.ACC_FINAL :
strippedToken.equals(JavaConstants.ACC_SYNCHRONIZED) ? ClassConstants.ACC_SYNCHRONIZED :
strippedToken.equals(JavaConstants.ACC_VOLATILE) ? ClassConstants.ACC_VOLATILE :
strippedToken.equals(JavaConstants.ACC_TRANSIENT) ? ClassConstants.ACC_TRANSIENT :
strippedToken.equals(JavaConstants.ACC_BRIDGE) ? ClassConstants.ACC_BRIDGE :
strippedToken.equals(JavaConstants.ACC_VARARGS) ? ClassConstants.ACC_VARARGS :
strippedToken.equals(JavaConstants.ACC_NATIVE) ? ClassConstants.ACC_NATIVE :
strippedToken.equals(JavaConstants.ACC_ABSTRACT) ? ClassConstants.ACC_ABSTRACT :
strippedToken.equals(JavaConstants.ACC_STRICT) ? ClassConstants.ACC_STRICT :
strippedToken.equals(JavaConstants.ACC_SYNTHETIC) ? ClassConstants.ACC_SYNTHETIC :
0;
if (accessFlag == 0)
{
throw new ParseException("Incorrect class member access modifier ["+strippedToken+"]");
}
accessFlags |= accessFlag;
}
}
}
return accessFlags;
}
/**
* Retrieves a specified boolean flag from the given map.
*/
private boolean retrieveBoolean(Map args, String name, boolean defaultValue)
{
if (args == null)
{
return defaultValue;
}
Object arg = args.get(name);
return arg == null ? defaultValue : ((Boolean)arg).booleanValue();
}
/**
* Adds the given class specification to the given list, creating a new list
* if necessary.
*/
private List extendClassSpecifications(List classSpecifications,
ClassSpecification classSpecification)
{
if (classSpecifications == null)
{
classSpecifications = new ArrayList();
}
classSpecifications.add(classSpecification);
return classSpecifications;
}
/**
* Adds the given class specifications to the given list, creating a new
* list if necessary.
*/
private List extendClassSpecifications(List classSpecifications,
List additionalClassSpecifications)
{
if (additionalClassSpecifications != null)
{
if (classSpecifications == null)
{
classSpecifications = new ArrayList();
}
classSpecifications.addAll(additionalClassSpecifications);
}
return classSpecifications;
}
/**
* Adds the given filter to the given list, creating a new list if
* necessary.
*/
private List extendFilter(List filter,
String filterString)
{
return extendFilter(filter, filterString, false);
}
/**
* Adds the given filter to the given list, creating a new list if
* necessary. External class names are converted to internal class names,
* if requested.
*/
private List extendFilter(List filter,
String filterString,
boolean convertExternalClassNames)
{
if (filter == null)
{
filter = new ArrayList();
}
if (filterString == null)
{
// Clear the filter to keep all names.
filter.clear();
}
else
{
if (convertExternalClassNames)
{
filterString = ClassUtil.internalClassName(filterString);
}
// Append the filter.
filter.addAll(ListUtil.commaSeparatedList(filterString));
}
return filter;
}
}