All Downloads are FREE. Search and download functionalities are using the official Maven repository.

edu.vanderbilt.accre.laurelin.root_proxy.ROOTFile Maven / Gradle / Ivy

/**
 * Handles low-level loading C-struct type things and (optionally compressed)
 * byte ranges from low level I/O
 */
package edu.vanderbilt.accre.laurelin.root_proxy;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.concurrent.ExecutionException;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;

import edu.vanderbilt.accre.laurelin.root_proxy.IOProfile.Event;
import edu.vanderbilt.accre.laurelin.root_proxy.IOProfile.FileProfiler;

public class ROOTFile implements AutoCloseable {
    private static final Logger logger = LogManager.getLogger();

    public static class FileBackedBuf implements BackingBuf {
        ROOTFile fh;

        /**
         * Size of a "cache page"
         */
        private static final int CACHE_PAGE_SIZE = 16 * 1024;

        /**
         * Total number of caches pages to store. Past this, the cache library
         * will begin to evict unneeded pages
         */
        private static final int CACHE_PAGE_COUNT = 1024;

        /**
         * Maximum size we'll attempt to cache in a single read. Past that,
         * we'll just pass it directly since more often than not, it's some
         * sort of compressed blob
         */
        private static final int CACHE_READ_MAX = 4 * CACHE_PAGE_SIZE;

        public class CacheKey {
            public ROOTFile fh;
            public long off;

            public CacheKey(ROOTFile fh, long off) {
                this.fh = fh;
                this.off = off;
            }

            @Override
            public boolean equals(Object obj) {
                if (this == obj) {
                    return true;
                }
                if (obj == null) {
                    return false;
                }
                if (getClass() != obj.getClass()) {
                    return false;
                }
                return (((fh == ((CacheKey) obj).fh))
                        && (off == ((CacheKey) obj).off));
            }

            @Override
            public int hashCode() {
                return (int) (fh.hashCode() + (off / (2 * 1024 * 1024)));
            }

        }

        static LoadingCache cache;

        static {
            CacheLoader loader = new CacheLoader() {
                    // FIXME - implement loadAll to get many pages at once
                    @Override
                    public ByteBuffer load(CacheKey key) throws Exception {
                        if (key.fh.getLimit() > key.off + CACHE_PAGE_SIZE) {
                            return key.fh.read(key.off, CACHE_PAGE_SIZE);
                        } else {
                            long shortCount = key.fh.getLimit() - key.off;
                            return key.fh.read(key.off, shortCount);
                        }
                    }
                };
            cache = CacheBuilder.newBuilder()
                .maximumSize(CACHE_PAGE_COUNT)
                .build(loader);
        }

        protected FileBackedBuf(ROOTFile fh) {
            this.fh = fh;

        }

        /*
         * All application-level reads enter the I/O subsystem here
         */
        @Override
        public ByteBuffer read(long off, long len) throws IOException {
            ByteBuffer ret;
            long lowerPage = off / CACHE_PAGE_SIZE;
            long upperPage = (off + len) / CACHE_PAGE_SIZE;
            try (Event ev = this.fh.profile.startUpperOp(off, (int)len)) {
                if ((len > CACHE_READ_MAX)
                        || (lowerPage != upperPage)) {
                    /*
                     *  1) Don't cache very large reads, since they will end up
                     *     being compressed baskets more often than not (and
                     *     the decompressed versions are what's stored)
                     *  2) Shortcut out if the read would otherwise straddle
                     *     a cache to make the initial code simpler
                     */
                    ret = fh.read(off, len);
                } else {
                    try {
                        ret = cache.get(new CacheKey(fh, lowerPage * CACHE_PAGE_SIZE)).duplicate();
                        long newPos = (off - (CACHE_PAGE_SIZE * lowerPage));
                        ret.position((int) newPos);
                        ret.limit((int)(newPos + len));
                        ret = ret.slice();
                    } catch (ExecutionException e) {
                        throw new IOException(e);
                    }
                }
            }  catch (Exception e) {
                throw new IOException(e);
            }
            return ret;
        }

        @Override
        public boolean hasLimit() throws IOException {
            return true;
        }

        @Override
        public long getLimit() throws IOException {
            return fh.getLimit();
        }

        @Override
        public BackingBuf duplicate() {
            return new FileBackedBuf(fh);
        }
    }

    private FileInterface fh;
    private String path;
    protected FileProfiler profile;

    /* Hide constructor */
    private ROOTFile(String path) {
        profile = IOProfile.getInstance().beginProfile(path);
        this.path = path;
    }

    public static ROOTFile getInputFile(String path) throws IOException {
        FileInterface fh = IOFactory.openForRead(path);
        return ROOTFile.getInputFile(path, fh);
    }

    public static ROOTFile getInputFile(String path, FileInterface fh) throws IOException {
        ROOTFile rf = new ROOTFile(path);
        rf.fh = fh;
        return rf;
    }

    public long getLimit() throws IOException {
        return fh.getLimit();
    }

    /*
     * To enable correct caching, any ByteByte buffers that get passed to
     * users must be copies of the internal ByteBuffers we have. Otherwise
     * we couldn't change the contents without breaking the users
     */
    private ByteBuffer readUnsafe(long offset, long l) throws IOException {
        /*
         * This bytebuffer can be a copy of the internal cache
         */
        ByteBuffer ret;
        try (Event time = profile.startLowerOp(offset, (int)l)) {
            // This is a call to the actual filesystem
            ret = fh.read(offset, l);
            ret.position(0);
            ret = ret.asReadOnlyBuffer();
        } catch (Exception e) {
            throw new IOException(e);
        }
        return ret;
    }

    public ByteBuffer read(long offset, long len) throws IOException {
        /*
         * TODO:
         * This bytebuffer must be a completely new and unlinked buffer, so
         * copy the internal array to a new one to make sure there's nothing
         * tying them together
         */
        return readUnsafe(offset, len);
    }

    public Cursor getCursor(long off) {
        return new Cursor(new FileBackedBuf(this), off);
    }

    @Override
    public void close() throws Exception {
        fh.close();

    }

    public FileInterface getFileInterface() {
        return fh;
    }

    public String getPath() {
        return path;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy