package net.ayld.facade.api; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import net.ayld.facade.bundle.JarExploder; import net.ayld.facade.bundle.JarMaker; import net.ayld.facade.dependency.matcher.DependencyMatcherStrategy; import net.ayld.facade.dependency.resolver.DependencyResolver; import net.ayld.facade.model.ClassFile; import net.ayld.facade.model.ClassName; import net.ayld.facade.model.SourceFile; import net.ayld.facade.util.Components; import net.ayld.facade.util.Directories; import net.ayld.facade.util.Files; import net.ayld.facade.util.Settings; import java.io.File; import java.io.IOException; import java.util.Arrays; import java.util.Iterator; import java.util.Set; import java.util.jar.JarFile; public final class Minimizer { private static final String JAVA_API_ROOT_PACKAGE = "java"; private final DependencyMatcherStrategy dependencyMatcherStrategy = Components.DEPENDENCY_MATCHER_STRATEGY.getInstance(); private final DependencyResolver<ClassFile> classDependencyResolver = Components.CLASS_DEPENDENCY_RESOLVER.getInstance(); private final DependencyResolver<SourceFile> sourceDependencyResolver = Components.SOURCE_DEPENDENCY_RESOLVER.getInstance(); private final JarMaker jarMaker = Components.JAR_MAKER.getInstance(); private final JarExploder libJarExploder = Components.LIB_JAR_EXPLODER.getInstance(); private final JarExploder explicitJarExploder = Components.EXPLICIT_JAR_EXPLODER.getInstance(); // don't use if you're under 18 private String workDir = Settings.DEFAULT_OUT_DIR.getValue(); private String explicitOutDir = Settings.EXPLICIT_OUT_DIR.getValue(); private File outJar = new File( Joiner .on(File.separator) .join(workDir, Settings.DEFAULT_FACADE_JAR_NAME.getValue()) ); private File libDir; private final File sourceDir; private Set<JarFile> forceIncludeJars = Sets.newHashSet(); private Set<ClassName> forceIncludeClasses = Sets.newHashSet(); private Minimizer(File sourceDir) { final File outJarDir = new File(outJar.getParent()); if (!outJarDir.exists() && !outJarDir.mkdirs()) { throw new IllegalStateException("unable to create parent dir for output jar: " + outJar.getParent()); } this.sourceDir = sourceDir; } public static Minimizer sources(String srcDir) { final File sourceDir = new File(srcDir); if (!sourceDir.exists() || !sourceDir.isDirectory()) { throw new IllegalArgumentException("directory at: " + srcDir + " does not exist or is not a directory"); } return new Minimizer(sourceDir); } public Minimizer libs(String libDir) { final File lib = new File(libDir); if (!sourceDir.exists() || !sourceDir.isDirectory()) { throw new IllegalArgumentException("directory at: " + libDir + " does not exist or is not a directory"); } this.libDir = lib; return this; } public Minimizer output(String outDir) { final File out = new File(outDir); if (!out.exists() && !out.mkdirs()) { throw new IllegalStateException("unable to create dir for output jar: " + outDir); } this.outJar = new File(Joiner.on(File.separator).join(out.getAbsolutePath(), Settings.DEFAULT_FACADE_JAR_NAME.getValue())); return this; } public Minimizer forceInclude(JarFile... jars) { this.forceIncludeJars.addAll(Arrays.asList(jars)); return this; } public Minimizer forceInclude(ClassName... classes) { this.forceIncludeClasses.addAll(Arrays.asList(classes)); return this; } public JarFile getJar() throws IOException { final String libDirPath = libDir.getAbsolutePath(); extractLibJars(libDirPath); final Set<SourceFile> sources = Sets.newHashSet(); for (File sourceFile : Files.in(sourceDir.getAbsolutePath()).withExtension(SourceFile.EXTENSION).list()) { sources.add(SourceFile.fromFile(sourceFile)); } final Set<ClassName> sourceDependencies = sourceDependencyResolver.resolve(sources); final Set<File> libClasses = ImmutableSet.copyOf( Files.in(workDir).withExtension(ClassFile.EXTENSION).list() ); final Set<ClassFile> foundDependencies = findInLib(sourceDependencies, libClasses); addDependenciesOfDependencies(foundDependencies, libClasses); foundDependencies.addAll(forceIncludeDependenciesAsFiles(this.forceIncludeJars, this.forceIncludeClasses, libClasses)); final Set<File> dependenciesForPackaging = Sets.newHashSetWithExpectedSize(foundDependencies.size()); for (ClassFile dep : foundDependencies) { dependenciesForPackaging.add(dep.physicalFile()); } final JarFile result = jarMaker.zip(dependenciesForPackaging); cleanWorkDir(); return result; } private void cleanWorkDir() throws IOException { final Set<File> dirtyDirs = Directories.in(workDir).nameEndsWith(JarMaker.JAR_FILE_EXTENSION).list(); // dirty ho ho ho ;) for (File dirty : dirtyDirs) { Files.deleteRecursive(dirty); } } private Set<ClassFile> forceIncludeDependenciesAsFiles(Set<JarFile> explicitIncludeJars, Set<ClassName> explicitIncludeClasses, final Set<File> libClasses) throws IOException { final Set<ClassFile> result = Sets.newHashSet(); for (ClassName includeClass : explicitIncludeClasses) { final Set<ClassFile> foundInLib = findInLib(ImmutableSet.of(includeClass), libClasses); if (foundInLib.size() < 1) { throw new IllegalStateException("can't find user defined class: " + includeClass + ", in: " + libDir.getAbsolutePath()); } result.addAll(foundInLib); } explicitJarExploder.explode(explicitIncludeJars); for (File extracted : Files.in(explicitOutDir).withExtension(ClassFile.EXTENSION).list()) { result.add(ClassFile.fromFile(extracted)); } return result; } private Set<ClassFile> addDependenciesOfDependencies(Set<ClassFile> deps, final Set<File> libClasses) throws IOException { removeJavaApiDeps(deps); deps.addAll(findInLib(classDependencyResolver.resolve(deps), libClasses)); final int sizeBeforeResolve = deps.size(); if (deps.size() == sizeBeforeResolve) { return deps; } return addDependenciesOfDependencies(deps, libClasses); } private void removeJavaApiDeps(Set<ClassFile> deps) { for (Iterator<ClassFile> iterator = deps.iterator(); iterator.hasNext();) { final ClassFile dep = iterator.next(); if (dep.qualifiedName().toString().startsWith(JAVA_API_ROOT_PACKAGE)) { iterator.remove(); } } } private Set<ClassFile> findInLib(Set<ClassName> dependencyNames, Set<File> libClasses) throws IOException { final Set<ClassFile> result = Sets.newHashSetWithExpectedSize(dependencyNames.size()); for (ClassName dependencyName : dependencyNames) { for (File libClass : libClasses) { final ClassFile libClassFile = ClassFile.fromFile(libClass); if (dependencyMatcherStrategy.matches(dependencyName, libClassFile)) { result.add(libClassFile); } } } return result; } private void extractLibJars(String libDir) throws IOException { final Set<JarFile> libJars = Sets.newHashSet(); for (File jarFile : Files.in(libDir).withExtension(JarMaker.JAR_FILE_EXTENSION).list()) { try { libJars.add(new JarFile(jarFile)); } catch (IOException e) { e.printStackTrace(); throw e; } } libJarExploder.explode(libJars); } }