/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package org.anarres.gradle.plugin.jarjar;
import com.tonicsystems.jarjar.classpath.ClassPath;
import com.tonicsystems.jarjar.transform.JarTransformer;
import com.tonicsystems.jarjar.transform.config.ClassKeepTransitive;
import com.tonicsystems.jarjar.transform.config.ClassDelete;
import com.tonicsystems.jarjar.transform.config.ClassRename;
import com.tonicsystems.jarjar.transform.jar.DefaultJarProcessor;
import groovy.lang.Closure;
import java.io.File;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.annotation.Nonnull;
import org.apache.oro.text.GlobCompiler;
import org.apache.oro.text.regex.MalformedPatternException;
import org.apache.oro.text.regex.Pattern;
import org.apache.oro.text.regex.Perl5Matcher;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.artifacts.dsl.DependencyHandler;
import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.file.FileCollection;
import org.gradle.api.internal.ConventionTask;
import org.gradle.api.specs.Spec;
import org.gradle.api.tasks.InputFiles;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.OutputFiles;
import org.gradle.api.tasks.TaskAction;
import org.gradle.api.tasks.TaskOutputs;
/**
*
* @author shevek
*/
public class JarjarTask extends ConventionTask {
private class FilterSpec implements Spec<File> {
private final String message;
private final Iterable<? extends Pattern> patterns;
private final boolean result;
public FilterSpec(@Nonnull String message, @Nonnull Iterable<? extends Pattern> patterns, boolean result) {
this.message = message;
this.patterns = patterns;
this.result = result;
}
@Override
public boolean isSatisfiedBy(File t) {
if (matchesAny(patterns, t.getName())) {
getLogger().info(message + " " + t);
return result;
}
return !result;
}
@Override
public String toString() {
return getClass().getSimpleName() + "(patterns=" + patterns + ")";
}
}
private static final Perl5Matcher globMatcher = new Perl5Matcher();
private static boolean matchesAny(@Nonnull Iterable<? extends Pattern> patterns, @Nonnull String text) {
for (Pattern pattern : patterns) {
if (globMatcher.matches(text, pattern)) {
return true;
}
}
return false;
}
@Nonnull
private static Iterable<Pattern> toPatterns(@Nonnull Iterable<? extends String>... patterns) throws MalformedPatternException {
GlobCompiler compiler = new GlobCompiler();
List<Pattern> out = new ArrayList<Pattern>();
for (Iterable<? extends String> in : patterns)
for (String pattern : in)
out.add(compiler.compile(pattern));
return out;
}
private final ConfigurableFileCollection sourceFiles;
private final Set<String> archiveBypasses = new HashSet<String>();
private final Set<String> archiveExcludes = new HashSet<String>();
private File destinationDir;
private String destinationName;
private final DefaultJarProcessor processor = new DefaultJarProcessor();
public JarjarTask() {
sourceFiles = getProject().files();
}
@InputFiles
public FileCollection getSourceFiles() {
return sourceFiles;
}
/**
* Returns the directory where the archive is generated into.
*
* @return the directory
*/
public File getDestinationDir() {
File out = destinationDir;
if (out == null)
out = new File(getProject().getBuildDir(), "jarjar");
return out;
}
public void setDestinationDir(File destinationDir) {
this.destinationDir = destinationDir;
}
/**
* Returns the file name of the generated archive.
*
* @return the name
*/
public String getDestinationName() {
String out = destinationName;
if (out == null)
out = getName() + ".jar";
return out;
}
public void setDestinationName(String destinationName) {
this.destinationName = destinationName;
}
/**
* The path where the archive is constructed.
* The path is simply the {@code destinationDir} plus the {@code destinationName}.
*
* @return a File object with the path to the archive
*/
@OutputFile
public File getDestinationPath() {
return new File(getDestinationDir(), getDestinationName());
}
@OutputFiles
public FileCollection getBypassedArchives() throws MalformedPatternException {
return sourceFiles.filter(new FilterSpec("Bypassing archive", toPatterns(archiveBypasses), true));
}
/**
* Processes a FileCollection, which may be simple, a {@link Configuration},
* or derived from a {@link TaskOutputs}.
*
* @param files The input FileCollection to consume.
*/
public void from(@Nonnull FileCollection files) {
sourceFiles.from(files);
}
/**
* Processes a Dependency directly, which may be derived from
* {@link DependencyHandler#create(java.lang.Object)},
* {@link DependencyHandler#project(java.util.Map)},
* {@link DependencyHandler#module(java.lang.Object)},
* {@link DependencyHandler#gradleApi()}, etc.
*
* @param dependency The dependency to process.
*/
public void from(@Nonnull Dependency dependency) {
Configuration configuration = getProject().getConfigurations().detachedConfiguration(dependency);
from(configuration);
}
/**
* Processes a dependency specified by name.
*
* @param dependencyNotation The dependency, in a notation described in {@link DependencyHandler}.
* @param configClosure The closure to use to configure the dependency.
* @see DependencyHandler
*/
public void from(@Nonnull String dependencyNotation, Closure configClosure) {
from(getProject().getDependencies().create(dependencyNotation, configClosure));
}
/**
* Processes a dependency specified by name.
*
* @param dependencyNotation The dependency, in a notation described in {@link DependencyHandler}.
*/
public void from(@Nonnull String dependencyNotation) {
from(getProject().getDependencies().create(dependencyNotation));
}
public void archiveBypass(@Nonnull String pattern) throws MalformedPatternException {
archiveBypasses.add(pattern);
}
public void archiveExclude(@Nonnull String pattern) throws MalformedPatternException {
archiveExcludes.add(pattern);
}
public void classRename(@Nonnull String pattern, @Nonnull String replacement) {
processor.addClassRename(new ClassRename(pattern, replacement));
}
public void classDelete(@Nonnull String pattern) {
processor.addClassDelete(new ClassDelete(pattern));
}
public void classClosureRoot(@Nonnull String pattern) {
processor.addClassKeepTransitive(new ClassKeepTransitive(pattern));
}
@TaskAction
public void run() throws Exception {
FileCollection inputFiles = sourceFiles.filter(new FilterSpec("Excluding archive", toPatterns(archiveBypasses, archiveExcludes), false));
final File outputFile = getDestinationPath();
outputFile.getParentFile().mkdirs();
getLogger().info("Running jarjar for {}", outputFile);
getLogger().info("Inputs are {}", inputFiles);
JarTransformer transformer = new JarTransformer(outputFile, processor);
transformer.transform(new ClassPath(getProject().getProjectDir(), inputFiles));
}
}