Package org.voltdb.utils

Source Code of org.voltdb.utils.InMemoryJarfile$JarLoader

/* This file is part of VoltDB.
* Copyright (C) 2008-2014 VoltDB Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with VoltDB.  If not, see <http://www.gnu.org/licenses/>.
*/

package org.voltdb.utils;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import java.util.jar.JarOutputStream;

import org.apache.hadoop_voltpatches.util.PureJavaCrc32;

/**
* Given a jarfile, construct a map of entry name => byte array representing
* the contents. Allow it to be modified and written out in flexible ways.
*
*/
public class InMemoryJarfile extends TreeMap<String, byte[]> {

    private static final long serialVersionUID = 1L;
    protected final JarLoader m_loader = new JarLoader();;

    ///////////////////////////////////////////////////////
    // CONSTRUCTION
    ///////////////////////////////////////////////////////

    public InMemoryJarfile() {}

    public InMemoryJarfile(String pathOrURL) throws IOException {
        InputStream fin = null;
        try {
            URL url = new URL(pathOrURL);
            fin = url.openStream();
        } catch (MalformedURLException ex) {
            // Invalid URL. Try as a file.
            fin = new FileInputStream(pathOrURL);
        }
        loadFromStream(fin);
    }

    public InMemoryJarfile(URL url) throws IOException {
        loadFromStream(url.openStream());
    }

    public InMemoryJarfile(File file) throws IOException {
        loadFromStream(new FileInputStream(file));
    }

    public InMemoryJarfile(byte[] bytes) throws IOException {
        loadFromStream(new ByteArrayInputStream(bytes));
    }

    private void loadFromStream(InputStream in) throws IOException {
        JarInputStream jarIn = new JarInputStream(in);
        JarEntry catEntry = null;
        while ((catEntry = jarIn.getNextJarEntry()) != null) {
            byte[] value = readFromJarEntry(jarIn, catEntry);
            String key = catEntry.getName();
            put(key, value);
        }
    }

    public static byte[] readFromJarEntry(JarInputStream jarIn, JarEntry entry) throws IOException {
        int totalRead = 0;
        int maxToRead = 4096 << 10;
        byte[] buffer = new byte[maxToRead];
        byte[] bytes = new byte[maxToRead * 2];

        // Keep reading until we run out of bytes for this entry
        // We will resize our return value byte array if we run out of space
        while (jarIn.available() == 1) {
            int readSize = jarIn.read(buffer, 0, buffer.length);
            if (readSize > 0) {
                totalRead += readSize;
                if (totalRead > bytes.length) {
                    byte[] temp = new byte[bytes.length * 2];
                    System.arraycopy(bytes, 0, temp, 0, bytes.length);
                    bytes = temp;
                }
                System.arraycopy(buffer, 0, bytes, totalRead - readSize, readSize);
            }
        }

        // Trim bytes to proper size
        byte retval[] = new byte[totalRead];
        System.arraycopy(bytes, 0, retval, 0, totalRead);
        return retval;
    }

    ///////////////////////////////////////////////////////
    // OUTPUT
    ///////////////////////////////////////////////////////

    public Runnable writeToFile(File file) throws IOException {
        final FileOutputStream output = new FileOutputStream(file);
        writeToOutputStream(output);
        return new Runnable() {
            @Override
            public void run() {
                try {
                    output.getFD().sync();
                } catch (Exception e) {
                    throw new RuntimeException(e);
                } finally {
                    try {
                        output.close();
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        };
    }

    public byte[] getFullJarBytes() throws IOException {
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        writeToOutputStream(output);
        output.close();
        return output.toByteArray();
    }

    protected void writeToOutputStream(OutputStream output) throws IOException {
        JarOutputStream jarOut = new JarOutputStream(output);

        for (Entry<String, byte[]> e : super.entrySet()) {
            assert(e.getValue() != null);

            JarEntry entry = new JarEntry(e.getKey());
            entry.setSize(e.getValue().length);
            // Make the entry time the epoch so that the SHA-1 hash
            // built by feeding all of the bytes to it returns the same
            // hash for the same catalog.
            // Maybe we ought to have a getSHA1() method that does the same
            // thing as the getCRC() method below?
            //
            //entry.setTime(System.currentTimeMillis());
            entry.setTime(0);
            jarOut.putNextEntry(entry);
            jarOut.write(e.getValue());
            jarOut.flush();
            jarOut.closeEntry();
        }

        jarOut.finish();
    }

    ///////////////////////////////////////////////////////
    // UTILITY
    ///////////////////////////////////////////////////////

    public long getCRC() throws IOException {

        PureJavaCrc32 crc = new PureJavaCrc32();

        for (Entry<String, byte[]> e : super.entrySet()) {
            if (e.getKey().equals("buildinfo.txt") || e.getKey().equals("catalog-report.html")) {
                continue;
            }
            crc.update(e.getKey().getBytes("UTF-8"));
            crc.update(e.getValue());
        }

        return crc.getValue();
    }

    public byte[] put(String key, File value) throws IOException {
        byte[] bytes = null;

        int bytesRead = 0;
        bytes = new byte[(int) value.length()];
        BufferedInputStream in = new BufferedInputStream(new FileInputStream(value));
        try {
            bytesRead = in.read(bytes);
        }
        finally {
            in.close();
        }
        assert(bytesRead != -1);

        return put(key, bytes);
    }

    private String fileToClassName(String filename)
    {
        return filename.replace(File.separatorChar, '.').substring(0, filename.length() - ".class".length());
    }

    private String classToFileName(String classname)
    {
        return classname.replace('.', File.separatorChar) + ".class";
    }

    /**
     * Remove the provided classname and all inner classes from the jarfile and the classloader
     */
    public void removeClassFromJar(String classname)
    {
        for (String innerclass : getLoader().getInnerClassesForClass(classname)) {
            remove(classToFileName(innerclass));
        }
        remove(classToFileName(classname));
    }

    ///////////////////////////////////////////////////////
    // CLASSLOADING
    ///////////////////////////////////////////////////////

    public class JarLoader extends ClassLoader {
        final Map<String, Class<?>> m_cache = new HashMap<String, Class<?>>();
        final Set<String> m_classNames = new HashSet<String>();

        void noteUpdated(String key) {
            if (!key.endsWith(".class"))
                return;
            m_classNames.add(fileToClassName(key));
        }

        void noteRemoved(String key) {
            if (!key.endsWith(".class"))
                return;
            m_classNames.remove(fileToClassName(key));
            m_cache.remove(fileToClassName(key));
        }

        // prevent this from being publicly called
        JarLoader() {}

        /**
         * @return The InMemoryJarFile instance owning this loader.
         */
        public InMemoryJarfile getInMemoryJarfile() {
            return InMemoryJarfile.this;
        }

        @Override
        public synchronized Class<?> loadClass(String className) throws ClassNotFoundException {
            // try the fast cache first
            Class<?> result;
            if (m_cache.containsKey(className)) {
                //System.out.println("found in cache.");
                return m_cache.get(className);
            }

            // now look through the list
            if (m_classNames.contains(className)) {
                String classPath = classToFileName(className);

                byte bytes[] = get(classPath);
                if (bytes == null)
                    throw new ClassNotFoundException(className);

                result = this.defineClass(className, bytes, 0, bytes.length);

                resolveClass(result);
                m_cache.put(className, result);
                return result;
            }

            // default to parent
            //System.out.println("deferring to parent.");
            return getParent().loadClass(className);
        }

        /**
         * For a given class, find all
         */
        public String[] getInnerClassesForClass(String className) {
            List<String> matches = new ArrayList<>();
            for (String potential : m_classNames) {
                if (potential.startsWith(className + "$")) {
                    matches.add(potential);
                }
            }
            return matches.toArray(new String[0]);
        }

        public Set<String> getClassNames() {
            return m_classNames;
        }
    }

    public JarLoader getLoader() {
        return m_loader;
    }

    ///////////////////////////////////////////////////////
    // OVERRIDDEN TREEMAP OPERATIONS
    ///////////////////////////////////////////////////////

    @Override
    public byte[] put(String key, byte[] value) {
        if (value == null)
            throw new RuntimeException("InMemoryJarFile cannon contain null entries.");
        byte[] retval = super.put(key, value);
        m_loader.noteUpdated(key);
        return retval;
    }

    @Override
    public void putAll(Map<? extends String, ? extends byte[]> m) {
        for (Entry<? extends String, ? extends byte[]> e : m.entrySet()) {
            put(e.getKey(), e.getValue());
        }
    }

    @Override
    public byte[] remove(Object key) {
        String realKey = null;
        try {
            realKey = (String) key;
        }
        catch (Exception e) {
            return null;
        }

        m_loader.noteRemoved(realKey);
        return super.remove(key);
    }

    @Override
    public void clear() {
        for (String key : keySet())
            m_loader.noteRemoved(key);
        super.clear();
    }

    @Override
    public Object clone() {
        throw new UnsupportedOperationException();
    }

    @Override
    public java.util.Map.Entry<String, byte[]> pollFirstEntry() {
        throw new UnsupportedOperationException();
    }

    @Override
    public java.util.Map.Entry<String, byte[]> pollLastEntry() {
        throw new UnsupportedOperationException();
    }
}
TOP

Related Classes of org.voltdb.utils.InMemoryJarfile$JarLoader

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.