averroes.JarOrganizer.java Source code

Java tutorial

Introduction

Here is the source code for averroes.JarOrganizer.java

Source

/*******************************************************************************
 * Copyright (c) 2015 Karim Ali and Ondej Lhotk.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Karim Ali - initial API and implementation and/or initial documentation
 *******************************************************************************/
package averroes;

import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.FileFilterUtils;

import averroes.options.AverroesOptions;
import averroes.util.io.Paths;

/**
 * Utility class to organize the input JAR files to Averroes into two JAR files
 * only: one for the application, and another one for the library. This process
 * does not alter the original class files in any way, it's merely copying the
 * class files into these temporary JAR files for convenience.
 * 
 * @author karim
 * 
 */
public class JarOrganizer {

    private Set<String> classNames;
    private JarFile organizedApplicationJarFile;
    private JarFile organizedLibraryJarFile;

    private Set<String> applicationClassNames;
    private Set<String> libraryClassNames;

    /**
     * Construct a new JAR organizer.
     */
    public JarOrganizer() {
        classNames = new HashSet<String>();
        applicationClassNames = new HashSet<String>();
        libraryClassNames = new HashSet<String>();
        organizedApplicationJarFile = new JarFile(Paths.organizedApplicationJarFile());
        organizedLibraryJarFile = new JarFile(Paths.organizedLibraryJarFile());
    }

    /**
     * Get the set of application class names.
     * 
     * @return
     */
    public Set<String> applicationClassNames() {
        return applicationClassNames;
    }

    /**
     * Get the set of library class names.
     * 
     * @return
     */
    public Set<String> libraryClassNames() {
        return libraryClassNames;
    }

    /**
     * Organize the input JAR files into two JAR files only: one for application
     * classes, the other for library classes.
     * 
     * @throws ZipException
     * @throws IOException
     * @throws URISyntaxException
     */
    public void organizeInputJarFiles() throws ZipException, IOException {
        processInputs();
        processDependencies();
        organizedApplicationJarFile.close();
        organizedLibraryJarFile.close();
    }

    /**
     * Process the input JAR files.
     * 
     * @throws ZipException
     * @throws IOException
     * @throws URISyntaxException
     */
    private void processInputs() throws ZipException, IOException {
        AverroesOptions.getApplicationJars().forEach(jar -> processArchive(jar, true));
    }

    /**
     * Process the dependencies of the input JAR files.
     */
    private void processDependencies() {
        // Add the application library dependencies
        AverroesOptions.getLibraryJarFiles().forEach(lib -> processArchive(lib, false));

        // Add the JRE libraries
        if ("system".equals(AverroesOptions.getJreDirectory())) {
            processJreArchives(System.getProperty("java.home"));
        } else {
            processJreArchives(AverroesOptions.getJreDirectory());
        }
    }

    /**
     * Process the JRE archives (recognized JAR files are: rt.jar, jsse.jar,
     * jce.jar).
     * 
     * @param dir
     */
    private void processJreArchives(String dir) {
        File directory = new File(dir);
        org.apache.commons.io.filefilter.IOFileFilter nameFilter = FileFilterUtils.or(
                FileFilterUtils.nameFileFilter("rt.jar"), FileFilterUtils.nameFileFilter("jsse.jar"),
                FileFilterUtils.nameFileFilter("jce.jar"));

        FileUtils.listFiles(directory, nameFilter, FileFilterUtils.trueFileFilter())
                .forEach(file -> processArchive(file.getPath(), false));
    }

    /**
     * Process a given JAR file.
     * 
     * @param fileName
     * @param fromApplicationArchive
     */
    private void processArchive(String fileName, boolean fromApplicationArchive) {
        // Exit if the fileName is empty
        if (fileName.trim().length() <= 0) {
            return;
        }

        File file = new File(fileName);
        System.out.println("Processing " + (fromApplicationArchive ? "input" : "library") + " archive: "
                + file.getAbsolutePath());

        try {
            ZipFile archive = new ZipFile(file);
            Enumeration<? extends ZipEntry> entries = archive.entries();

            while (entries.hasMoreElements()) {
                ZipEntry entry = entries.nextElement();
                if (entry.getName().endsWith(".class")) {
                    addClass(archive, entry, fromApplicationArchive);
                }
            }
            archive.close();
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(1);
        }
    }

    /**
     * Determine whether the given class file will be added to the list of
     * application or library class files depending on the AverroesProperties
     * file.
     * 
     * @param archive
     * @param entry
     * @param fromApplicationArchive
     * @throws IOException
     */
    private void addClass(ZipFile archive, ZipEntry entry, boolean fromApplicationArchive) throws IOException {
        String className = entry.getName().replace('/', '.').replace(".class", "");

        if (classNames.contains(className)) {
            /*
             * Ignore, this means we encountered another copy of the class later
             * on the path, it should be ignored. This is the case in some
             * benchmarks where the jar file contains both application and
             * library classes, while some of those library classes are also in
             * the rt.jar or deps.jar. In such a case, we want to add the
             * classes from the jar file first and ignore the repetition.
             */
            // System.out.println("class " + className +
            // " has already been added to this class provider.");
        } else {
            /*
             * The class has to be from an application archive & an application
             * class. Some classes in xalan are application classes based on
             * their package name only, while they're in fact not part of the
             * application and they come from rt.jar (e.g.,
             * org.apache.xalan.templates.OutputProperties$1).
             */
            if (AverroesOptions.isApplicationClass(className) && fromApplicationArchive) {
                extractApplicationClassFile(archive, entry);
                applicationClassNames.add(className);
            } else {
                extractLibraryClassFile(archive, entry);
                libraryClassNames.add(className);
            }

            classNames.add(className);
        }
    }

    /**
     * Extract an application class file.
     * 
     * @param sourceArchive
     * @param entry
     * @throws IOException
     */
    private void extractApplicationClassFile(ZipFile sourceArchive, ZipEntry entry) throws IOException {
        extractClassFile(sourceArchive, entry, organizedApplicationJarFile);
    }

    /**
     * Extract a library class file.
     * 
     * @param sourceArchive
     * @param entry
     * @throws IOException
     */
    private void extractLibraryClassFile(ZipFile sourceArchive, ZipEntry entry) throws IOException {
        extractClassFile(sourceArchive, entry, organizedLibraryJarFile);
    }

    /**
     * Extract a class file to specified file.
     * 
     * @param sourceArchive
     * @param entry
     * @param destArchive
     * @throws IOException
     */
    private void extractClassFile(ZipFile sourceArchive, ZipEntry entry, JarFile destArchive) throws IOException {
        // Write out the class file to the destination archive directly. No
        // temporary file used.
        destArchive.add(sourceArchive.getInputStream(entry), entry.getName());
    }
}