001/*
002 *                    BioJava development code
003 *
004 * This code may be freely distributed and modified under the
005 * terms of the GNU Lesser General Public Licence.  This should
006 * be distributed with the code.  If you do not have a copy,
007 * see:
008 *
009 *      http://www.gnu.org/copyleft/lesser.html
010 *
011 * Copyright for this code is held jointly by the individual
012 * authors.  These should be listed in @author doc comments.
013 *
014 * For more information on the BioJava project and its aims,
015 * or to join the biojava-l mailing list, visit the home page
016 * at:
017 *
018 *      http://www.biojava.org/
019 *
020 * Created on Oct 1, 2009
021 * Author: Andreas Prlic
022 *
023 */
024
025package org.biojava.nbio.core.util;
026
027import java.io.ByteArrayInputStream;
028import java.io.File;
029import java.io.FileInputStream;
030import java.io.IOException;
031import java.io.InputStream;
032
033import org.slf4j.Logger;
034import org.slf4j.LoggerFactory;
035
036/**
037 * Provides a cache for storing multiple small files in memory. Can be used to e.g cache gzip compressed PDB files
038 * for avoiding disk IO bottlenecks.
039 * Note this is just a wrapper for the singleton cache.
040 *
041 * @author Andreas Prlic.
042 *
043 */
044public class FlatFileCache {
045
046        private final static Logger logger = LoggerFactory.getLogger(FlatFileCache.class);
047
048        /**
049         * The cache singleton.
050         */
051        private static SoftHashMap<String, byte[]> cache = new SoftHashMap<String, byte[]>(0);
052
053
054        // no public constructor;
055        private FlatFileCache(){
056
057        }
058
059        /**
060         * The file is read and the bytes stored immediately.
061         * <p/> 
062         * Once added, {@code fileToCache} can be modified or deleted and the cached values will not change.
063         * @param key
064         * @param fileToCache A readable file, of Integer.MAX bytes length or less.
065         */
066        public  static void addToCache(String key, File fileToCache){
067                //logger.debug("storing " + key + " on file cache (cache size: " + cache.size() + ")");
068                try (InputStream is = new FileInputStream(fileToCache)){
069                        
070                        // Get the size of the file
071                        long length = fileToCache.length();
072
073                        // You cannot create an array using a long type.
074                        // It needs to be an int type.
075                        // Before converting to an int type, check
076                        // to ensure that file is not larger than Integer.MAX_VALUE.
077                        if (length > Integer.MAX_VALUE) {
078                                // File is too large
079                                throw new IllegalArgumentException("File must be <= " + Integer.MAX_VALUE + " bytes long");
080                        }
081
082                        // Create the byte array to hold the data
083                        byte[] bytes = new byte[(int)length];
084
085                        // Read in the bytes
086                        int offset = 0;
087                        int numRead = 0;
088                        while (offset < bytes.length
089                                        && (numRead=is.read(bytes, offset, bytes.length-offset)) >= 0) {
090                                offset += numRead;
091                        }
092
093                        // Ensure all the bytes have been read in
094                        if (offset < bytes.length) {
095                                is.close();
096                                throw new IOException("Could not completely read file "+fileToCache.getName());
097                        }
098
099                        // Close the input stream and return bytes
100                        is.close();
101
102                        cache.put(key,bytes);
103
104                } catch (Exception e){
105                        logger.error("Error adding to cache! " + e.getMessage(), e);
106                }
107        }
108        /**
109         * Gets the cached file as an InputStream.
110         * Clients should check for null as the item might have expired in the  cache.
111         * @param key
112         * @return An {@code InputStream} or null. 
113         */
114        public  static InputStream getInputStream(String key){
115                //logger.debug("returning " + key + " from file cache (cache size: " + cache.size() + ")");
116                byte[] bytes = cache.get(key);
117                if ( bytes == null)
118                        return null;
119
120                return new ByteArrayInputStream(bytes);
121
122        }
123
124        /**
125         * Returns the number of items in the cache.
126         * If the cache is {@}, returns -1
127         * @return
128         */
129        public static int size() {
130                if ( cache != null)
131                        return cache.size();
132                else
133                        return -1;
134        }
135
136        /**
137         * Removes all elements from the cache
138         */
139        public static void clear(){
140           cache.clear();
141        }
142
143}