averroes.JarFile.java Source code

Java tutorial

Introduction

Here is the source code for averroes.JarFile.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.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import java.util.stream.Collectors;

import org.apache.bcel.Repository;
import org.apache.bcel.classfile.ClassFormatException;
import org.apache.bcel.classfile.ClassParser;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.util.ClassPath;
import org.apache.bcel.util.SyntheticRepository;
import org.apache.bcel.verifier.VerificationResult;
import org.apache.bcel.verifier.Verifier;
import org.apache.bcel.verifier.VerifierFactory;
import org.apache.commons.io.FileUtils;

import soot.SootMethod;
import averroes.exceptions.Assertions;
import averroes.soot.Names;
import averroes.util.io.Paths;

/**
 * A JAR file is a collection of class files. We use BCEL to verify that the
 * generated JAR files conforms to the JVM standards.
 * 
 * @author karim
 * 
 */
public class JarFile {

    private JarOutputStream jarOutputStream;
    private File fileName;
    private static Set<JavaClass> bcelClasses = new HashSet<JavaClass>();

    /**
     * Construct a new JAR file.
     * 
     * @param fileName
     */
    public JarFile(File file) {
        jarOutputStream = null;
        this.fileName = file;
    }

    /**
     * Get the output stream of this JAR archive.
     * 
     * @return
     * @throws IOException
     */
    public JarOutputStream getJarOutputStream() throws IOException {
        if (jarOutputStream == null) {
            Manifest manifest = new Manifest();
            manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
            jarOutputStream = new JarOutputStream(new FileOutputStream(fileName), manifest);
        }
        return jarOutputStream;
    }

    /**
     * Add all the generated class files to the Jar file.
     * 
     * @throws IOException
     */
    public void addGeneratedLibraryClassFiles() throws IOException {
        Set<String> classFiles = new HashSet<String>();
        File dir = Paths.libraryClassesOutputDirectory();
        File placeholderJar = Paths.placeholderLibraryJarFile();

        // Add the class files to the crafted JAR file.
        FileUtils.listFiles(dir, new String[] { "class" }, true).stream()
                .filter(f -> !relativize(dir, f).equals(Names.AVERROES_LIBRARY_CLASS_BC_SIG + ".class"))
                .forEach(file -> {
                    try {
                        String className = relativize(dir, file);
                        add(dir, file);
                        classFiles.add(className);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                });
        close();

        // Now add all those class files in the crafted JAR file to the BCEL
        // repository.
        for (String classFile : classFiles) {
            ClassParser parser = new ClassParser(placeholderJar.getPath(), classFile);
            JavaClass cls = parser.parse();
            bcelClasses.add(cls);
        }
    }

    /**
     * Add the generated AverroesLibraryClass file to the Jar file.
     * 
     * @throws IOException
     * @throws URISyntaxException
     */
    public void addAverroesLibraryClassFile() throws IOException, URISyntaxException {
        File dir = Paths.libraryClassesOutputDirectory();
        File placeholderJar = Paths.placeholderLibraryJarFile();
        File averroesLibraryClassJar = Paths.averroesLibraryClassJarFile();

        File file = FileUtils.listFiles(dir, new String[] { "class" }, true).stream()
                .filter(f -> relativize(dir, f).equals(Names.AVERROES_LIBRARY_CLASS_BC_SIG + ".class"))
                .collect(Collectors.toList()).get(0);
        String className = relativize(dir, file);

        // Add the class file to the separately crafted JAR file.
        if (file.isFile()) {
            add(dir, file);
        } else {
            throw new IllegalStateException("cannot find " + Names.AVERROES_LIBRARY_CLASS
                    + System.getProperty("line.separator") + "Invalid path given: " + fileName);
        }
        close();

        // Set BCEL's repository class path.
        SyntheticRepository rep = SyntheticRepository.getInstance(new ClassPath(averroesLibraryClassJar
                + File.pathSeparator + placeholderJar + File.pathSeparator + Paths.organizedApplicationJarFile()));
        Repository.setRepository(rep);

        // Now add the class files (including ones from placeholder JAR) to the
        // BCEL repository.
        ClassParser parser = new ClassParser(averroesLibraryClassJar.getPath(), className);
        JavaClass cls = parser.parse();
        bcelClasses.add(cls);

        // Now we need to add all the BCEL classes (including ones from previous
        // placeholder JAR to force BCEL to load
        // those crafted files when it looks them up
        bcelClasses.forEach(c -> Repository.getRepository().storeClass(c));

        // Now verify all the generated class files
        verify();
    }

    /**
     * Add a class file from source to the Jar file.
     * 
     * @param dir
     * @param source
     * @throws IOException
     */
    public void add(File dir, File source) throws IOException {
        BufferedInputStream in = null;
        try {
            if (source.isDirectory()) {
                String name = relativize(dir, source).replace("\\", "/");
                if (!name.isEmpty()) {
                    if (!name.endsWith("/"))
                        name += "/";
                    JarEntry entry = new JarEntry(name);
                    entry.setTime(source.lastModified());
                    getJarOutputStream().putNextEntry(entry);
                    getJarOutputStream().closeEntry();
                }
                for (File nestedFile : source.listFiles())
                    add(dir, nestedFile);
                return;
            }

            JarEntry entry = new JarEntry(relativize(dir, source).replace("\\", "/"));
            entry.setTime(source.lastModified());
            getJarOutputStream().putNextEntry(entry);
            in = new BufferedInputStream(new FileInputStream(source));

            byte[] buffer = new byte[1024];
            while (true) {
                int count = in.read(buffer);
                if (count == -1)
                    break;
                getJarOutputStream().write(buffer, 0, count);
            }
            getJarOutputStream().closeEntry();
        } finally {
            if (in != null)
                in.close();
        }
    }

    /**
     * Add the given file with the given entry name to this JAR file.
     * 
     * @param source
     * @param entryName
     * @throws IOException
     */
    public void add(File source, String entryName) throws IOException {
        JarEntry entry = new JarEntry(entryName);
        entry.setTime(source.lastModified());
        getJarOutputStream().putNextEntry(entry);
        BufferedInputStream in = new BufferedInputStream(new FileInputStream(source));

        byte[] buffer = new byte[1024];
        int len;
        while ((len = in.read(buffer)) != -1) {
            getJarOutputStream().write(buffer, 0, len);
        }
        getJarOutputStream().closeEntry();
        in.close();
    }

    /**
     * Add the file read from the source input stream with the given entry name
     * to this JAR file.
     * 
     * @param source
     * @param entryName
     * @throws IOException
     */
    public void add(InputStream source, String entryName) throws IOException {
        JarEntry entry = new JarEntry(entryName);
        entry.setTime(System.currentTimeMillis());
        getJarOutputStream().putNextEntry(entry);

        byte[] buffer = new byte[1024];
        int len;
        while ((len = source.read(buffer)) != -1) {
            getJarOutputStream().write(buffer, 0, len);
        }
        getJarOutputStream().closeEntry();
        source.close();
    }

    /**
     * Close the JAR output stream.
     * 
     * @throws IOException
     */
    public void close() throws IOException {
        getJarOutputStream().close();
    }

    /**
     * Verify the integrity of the jar file.
     * 
     * @throws IOException
     * @throws ClassFormatException
     */
    private void verify() throws ClassFormatException, IOException {
        for (JavaClass cls : bcelClasses) {
            Verifier verifier = VerifierFactory.getVerifier(cls.getClassName());
            Method[] methods = cls.getMethods();
            for (int i = 0; i < methods.length; i++) {
                VerificationResult vr;
                // Do a pass 3a for the constructor of java.lang.Object because
                // we are using an uninitialized "this".
                if (cls.getClassName().equals(Names.JAVA_LANG_OBJECT)
                        && methods[i].getName().equals(SootMethod.constructorName)) {
                    vr = verifier.doPass3a(i);
                } else {
                    vr = verifier.doPass3b(i);
                }

                Assertions.verificationResultOKAssertion(vr, cls.getClassName(), methods[i].getName());
            }
        }
    }

    /**
     * Get the relative path for an absolute file path.
     * 
     * @param dir
     * @param file
     * @return
     * @throws IOException
     */
    public static String relativize(File base, File absolute) {
        Path pathAbsolute = java.nio.file.Paths.get(absolute.getPath()).normalize();
        Path pathBase = java.nio.file.Paths.get(base.getPath()).normalize();
        return pathBase.relativize(pathAbsolute).toString();
    }
}