com.continuuity.weave.internal.ApplicationBundler.java Source code

Java tutorial

Introduction

Here is the source code for com.continuuity.weave.internal.ApplicationBundler.java

Source

/*
 * Copyright 2012-2013 Continuuity,Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.continuuity.weave.internal;

import com.continuuity.weave.common.filesystem.Location;
import com.continuuity.weave.internal.utils.Dependencies;
import com.google.common.base.Function;
import com.google.common.base.Splitter;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.io.ByteStreams;
import com.google.common.io.Files;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.net.URL;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import java.util.Queue;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;

/**
 * This class builds jar files based on class dependencies.
 */
public final class ApplicationBundler {

    private final List<String> excludePackages;
    private final List<String> includePackages;
    private final Set<String> acceptClassPath;

    /**
     * Constructs a ApplicationBundler.
     *
     * @param excludePackages Class packages to exclude
     * @param includePackages Always includes classes/resources in these packages. Note that
     *                        classes in these packages will not get inspected for dependencies.
     */
    public ApplicationBundler(Iterable<String> excludePackages, Iterable<String> includePackages) {
        this.excludePackages = ImmutableList.copyOf(excludePackages);
        this.includePackages = ImmutableList.copyOf(includePackages);
        this.acceptClassPath = Sets.difference(
                Sets.newHashSet(Splitter.on(File.pathSeparatorChar).split(System.getProperty("java.class.path"))),
                Sets.newHashSet(
                        Splitter.on(File.pathSeparatorChar).split(System.getProperty("sun.boot.class.path"))));
    }

    /**
     * Creates a jar file which includes all the given classes and all the classes that they depended on.
     * The jar will also include all classes and resources under the packages as given as include packages
     * in the constructor.
     *
     * @param target Where to save the target jar file.
     * @param classes Set of classes to start the dependency traversal.
     * @throws IOException
     */
    public void createBundle(Location target, Iterable<Class<?>> classes) throws IOException {
        // Write the jar to local tmp file first
        File tmpJar = File.createTempFile(target.getName(), ".tmp");
        try {
            Set<String> entries = Sets.newHashSet();
            JarOutputStream jarOut = new JarOutputStream(new FileOutputStream(tmpJar));
            try {
                findDependencies(classes, entries, jarOut);

                // Add all classes under the packages as listed in includePackages
                for (String pkg : includePackages) {
                    copyPackage(pkg, entries, jarOut);
                }
            } finally {
                jarOut.close();
            }
            // Copy the tmp jar into destination.
            OutputStream os = new BufferedOutputStream(target.getOutputStream());
            try {
                Files.copy(tmpJar, os);
            } finally {
                os.close();
            }
        } finally {
            tmpJar.delete();
        }
    }

    /**
     * Same as calling {@link #createBundle(Location, Iterable)}.
     */
    public void createBundle(Location target, Class<?> clz, Class<?>... classes) throws IOException {
        createBundle(target, ImmutableSet.<Class<?>>builder().add(clz).add(classes).build());
    }

    private void findDependencies(Iterable<Class<?>> classes, final Set<String> entries,
            final JarOutputStream jarOut) throws IOException {

        Iterable<String> classNames = Iterables.transform(classes, new Function<Class<?>, String>() {
            @Override
            public String apply(Class<?> input) {
                return input.getName();
            }
        });
        Dependencies.findClassDependencies(Thread.currentThread().getContextClassLoader(), acceptClassPath,
                classNames, new Dependencies.ClassAcceptor() {
                    @Override
                    public boolean accept(String className, byte[] bytecode) {
                        for (String exclude : excludePackages) {
                            if (className.startsWith(exclude)) {
                                return false;
                            }
                        }
                        putDirEntry(className.substring(0, className.lastIndexOf('.')), entries, jarOut);
                        putClassEntry(className, bytecode, entries, jarOut);

                        return true;
                    }
                });
    }

    /**
     * Saves a directory entry to the jar output.
     */
    private void putDirEntry(String pkg, Set<String> entries, JarOutputStream jarOut) {
        try {
            String entry = "";
            for (String dir : Splitter.on('.').split(pkg)) {
                entry += dir + '/';
                if (entries.add(entry)) {
                    jarOut.putNextEntry(new JarEntry(entry));
                    jarOut.closeEntry();
                }
            }
        } catch (IOException e) {
            throw Throwables.propagate(e);
        }
    }

    /**
     * Saves a class entry to the jar output.
     */
    private void putClassEntry(String className, byte[] bytecode, Set<String> entries, JarOutputStream jarOut) {
        try {
            String entry = className.replace('.', '/') + ".class";
            if (entries.add(entry)) {
                jarOut.putNextEntry(new JarEntry(entry));
                jarOut.write(bytecode);
                jarOut.closeEntry();
            }
        } catch (IOException e) {
            throw Throwables.propagate(e);
        }
    }

    /**
     * Copies everything under the given package to the jar output.
     */
    private void copyPackage(String pkg, Set<String> entries, JarOutputStream jarOut) throws IOException {
        String prefix = pkg.replace('.', '/');

        Enumeration<URL> resources = Thread.currentThread().getContextClassLoader().getResources(prefix);
        while (resources.hasMoreElements()) {
            URL url = resources.nextElement();
            if ("jar".equals(url.getProtocol())) {
                copyJarEntries(url, entries, jarOut);
            } else if ("file".equals(url.getProtocol())) {
                String path = url.getFile();
                File baseDir = new File(path.substring(0, path.indexOf(prefix)));
                copyFileEntries(baseDir, prefix, entries, jarOut);
            }
            // Otherwise, ignore the package
        }
    }

    /**
     * Copies all entries under the path specified by the jarPath to the JarOutputStream.
     */
    private void copyJarEntries(URL jarPath, Set<String> entries, JarOutputStream jarOut) throws IOException {
        String spec = new URL(jarPath.getFile()).getFile();
        String entryRoot = spec.substring(spec.lastIndexOf('!') + 1);
        if (entryRoot.charAt(0) == '/') {
            entryRoot = entryRoot.substring(1);
        }
        JarFile jarFile = new JarFile(spec.substring(0, spec.lastIndexOf('!')));

        try {
            Enumeration<JarEntry> jarEntries = jarFile.entries();
            while (jarEntries.hasMoreElements()) {
                JarEntry jarEntry = jarEntries.nextElement();
                String name = jarEntry.getName();
                if (name == null || !name.startsWith(entryRoot)) {
                    continue;
                }
                if (entries.add(name)) {
                    jarOut.putNextEntry(new JarEntry(jarEntry));
                    ByteStreams.copy(jarFile.getInputStream(jarEntry), jarOut);
                    jarOut.closeEntry();
                }
            }
        } finally {
            jarFile.close();
        }
    }

    /**
     * Copies all entries under the file path.
     */
    private void copyFileEntries(File baseDir, String entryRoot, Set<String> entries, JarOutputStream jarOut)
            throws IOException {
        URI baseUri = baseDir.toURI();
        File packageFile = new File(baseDir, entryRoot);
        Queue<File> queue = Lists.newLinkedList();
        queue.add(packageFile);
        while (!queue.isEmpty()) {
            File file = queue.remove();

            String entry = baseUri.relativize(file.toURI()).getPath();
            if (entries.add(entry)) {
                jarOut.putNextEntry(new JarEntry(entry));
                if (file.isFile()) {
                    Files.copy(file, jarOut);
                }
                jarOut.closeEntry();
            }

            if (file.isDirectory()) {
                File[] files = file.listFiles();
                if (files != null) {
                    queue.addAll(Arrays.asList(files));
                }
            }
        }
    }
}