org.csml.tommo.sugar.analysis.OpenedFileCache.java Source code

Java tutorial

Introduction

Here is the source code for org.csml.tommo.sugar.analysis.OpenedFileCache.java

Source

/**
 *    Copyright Masao Nagasaki
 *    Nagasaki Lab
 *    Laboratory of Biomedical Information Analysis,
 *    Department of Integrative Genomics,
 *    Tohoku Medical Megabank Organization, Tohoku University 
 *    @since 2013
 *
 *    This file is part of SUGAR (Subtile-based GUI-Assisted Refiner).
 *    SUGAR is an extension of FastQC (copyright 2010-12 Simon Andrews)
 *
 *    SUGAR is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    SUGAR 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 General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with SUGAR; if not, write to the Free Software
 *    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */
package org.csml.tommo.sugar.analysis;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.csml.tommo.sugar.SugarApplication;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
import org.json.simple.parser.ParseException;

import uk.ac.babraham.FastQC.Analysis.AnalysisListener;
import uk.ac.babraham.FastQC.Modules.QCModule;
import uk.ac.babraham.FastQC.Sequence.SequenceFile;

public class OpenedFileCache implements Serializable, JSONFileSerializable {

    /**
     * 
     */
    private static final long serialVersionUID = -4741929940366094412L;

    private static final String JSON_ATTR_CACHE_VALUES = "cache.values";
    private static final String JSON_ATTR_CACHE_KEYS = "cache.keys";

    private static final String CACHE_FILE_SUFFIX = ".cache";
    private static final String CACHE_FILE_PREFIX = "tmp";
    private static final String CACHE_DIR_NAME = "tmp";
    private static final String CACHE_FILE_NAME = "openedFileCache";

    private static final FilenameFilter CACHE_FILE_FILTER = new FilenameFilter() {

        @Override
        public boolean accept(File dir, String name) {
            // TODO Auto-generated method stub
            return name.startsWith(CACHE_FILE_PREFIX);
        }
    };

    private static final String MAX_SIZE_PROPERTY_NAME = "cache.maxSize";
    private static final String EXPIRATION_PERIOD_PROPERTY_NAME = "cache.expirationPeriod";
    private static final String DIRECTORY_PROPERTY_NAME = "cache.directory";

    protected static File CACHE_DIR = new File(SugarApplication.getAPP_HOME_DIR(), CACHE_DIR_NAME);
    protected static File CACHE_MAP_FILE = new File(getCACHE_DIR(), CACHE_FILE_NAME);

    final protected static long MEGABYTE = 1024 * 1024; // 1 MB
    final protected static long GIGABYTE = 1024 * MEGABYTE; // 1 GB
    protected static long MAXIMUM_CACHE_SIZE = 100 * GIGABYTE; // 100 GB

    final protected static long HOUR = 1000 * 3600; // 1 hour in milliseconds
    final protected static long DAY = 24 * HOUR; // 1 hour in milliseconds
    protected static long EXPIRATION_TIME = 30 * DAY; // one month n milliseconds   

    protected static OpenedFileCache INSTANCE;

    protected Map<CachedFile, File> cache;

    protected OpenedFileCache() {
        cache = Collections.synchronizedMap(new HashMap<CachedFile, File>());
        initProperties();
    }

    private void initProperties() {

        Properties properties = SugarApplication.getApplicationPropeties();

        if (properties.getProperty(MAX_SIZE_PROPERTY_NAME) != null) {
            int size = new Integer(properties.getProperty(MAX_SIZE_PROPERTY_NAME));
            MAXIMUM_CACHE_SIZE = size * MEGABYTE;
        }
        if (properties.getProperty(EXPIRATION_PERIOD_PROPERTY_NAME) != null) {
            int days = new Integer(properties.getProperty(EXPIRATION_PERIOD_PROPERTY_NAME));
            EXPIRATION_TIME = days * DAY;
        }
        if (properties.getProperty(DIRECTORY_PROPERTY_NAME) != null) {
            CACHE_DIR = new File(properties.getProperty(DIRECTORY_PROPERTY_NAME));
            CACHE_MAP_FILE = new File(getCACHE_DIR(), CACHE_FILE_NAME);
        }
    }

    static public OpenedFileCache getInstance() {

        // construct instance
        if (INSTANCE == null) {
            INSTANCE = new OpenedFileCache();

            // try to read from disk file
            if (CACHE_MAP_FILE.exists()) {
                try {
                    INSTANCE.fromJSONFile(CACHE_MAP_FILE);
                } catch (Exception e) {

                }
            }
        }

        return INSTANCE;
    }

    public static File getCACHE_DIR() {
        if (!CACHE_DIR.exists())
            CACHE_DIR.mkdirs();
        return CACHE_DIR;
    }

    public boolean readModulesFromCache(File sequenceFile, QCModule[] modules, List<AnalysisListener> listeners,
            int matrixSize, int qualityThreshold) {
        boolean result = false;

        CachedFile cachedFile = new CachedFile(sequenceFile, matrixSize, qualityThreshold);
        if (cache.containsKey(cachedFile)) {
            File cacheFileBasename = cache.get(cachedFile);

            try {

                // first check if all modules are serializable and if cached files for all modules exist
                for (int i = 0; i < modules.length; i++) {
                    QCModule m = modules[i];
                    File moduleFile = new File(cacheFileBasename.getAbsolutePath() + m.getClass().getSimpleName());

                    //               if (!(m instanceof JSONFileSerializable))
                    //                  throw new Exception("Module " + m.name() + "cannot be serialized to JSON");

                    if (m instanceof JSONFileSerializable && !moduleFile.isFile())
                        throw new Exception("Could not find cache file " + moduleFile.getAbsolutePath());
                }

                for (int i = 0; i < modules.length; i++) {
                    QCModule m = modules[i];

                    if (m instanceof JSONFileSerializable) {
                        File moduleFile = new File(
                                cacheFileBasename.getAbsolutePath() + m.getClass().getSimpleName());

                        fireCacheFileStartedEvent(listeners, m, moduleFile, SugarAnalysisListener.READING_FILE);
                        long startTime = System.currentTimeMillis();

                        ((JSONFileSerializable) m).fromJSONFile(moduleFile);

                        long time = System.currentTimeMillis() - startTime;
                        fireCacheFileCompletedEvent(listeners, m, time, SugarAnalysisListener.READING_FILE);
                    }
                }

                result = true;

            } catch (Throwable e) {
                // an error occurred
                e.printStackTrace();

                // module could not be read from cache 
                // remove the entry from the map
                cache.remove(cachedFile);

                for (QCModule m : modules) {
                    // reset each module
                    m.reset();

                    // and remove the corresponding files from the cache folder
                    File moduleFile = new File(cacheFileBasename.getAbsolutePath() + m.getClass().getSimpleName());
                    if (moduleFile.isFile())
                        moduleFile.delete();
                }

                try {
                    toJSONFile(CACHE_MAP_FILE);
                } catch (IOException e1) {
                    // TODO Auto-generated catch block
                    e1.printStackTrace();
                }

                result = false;

            }

        }

        return result;
    }

    private void fireCacheFileStartedEvent(List<AnalysisListener> listeners, QCModule m, File moduleFile,
            int operation) {
        if (listeners != null) {
            for (AnalysisListener l : listeners) {
                if (l instanceof SugarAnalysisListener) {
                    ((SugarAnalysisListener) l).cacheFileStarted(m, moduleFile.length(), operation);
                }
            }
        }
    }

    private void fireCacheFileCompletedEvent(List<AnalysisListener> listeners, QCModule m, long time,
            int operation) {
        if (listeners != null) {
            for (AnalysisListener l : listeners) {
                if (l instanceof SugarAnalysisListener) {
                    ((SugarAnalysisListener) l).cacheFileCompleted(m, time, operation);
                }
            }
        }
    }

    public void writeModulesToCache(File sequenceFile, QCModule[] modules, List<AnalysisListener> listeners,
            int matrixSize, int qualityThreshold) {

        if (!cache.containsKey(new CachedFile(sequenceFile, matrixSize, qualityThreshold))) {
            try {
                File cacheFileBasename = File.createTempFile(CACHE_FILE_PREFIX, CACHE_FILE_SUFFIX, CACHE_DIR);

                for (QCModule m : modules) {
                    if (m instanceof JSONFileSerializable) {
                        File moduleFile = new File(
                                cacheFileBasename.getAbsolutePath() + m.getClass().getSimpleName());

                        fireCacheFileStartedEvent(listeners, m, moduleFile, SugarAnalysisListener.WRITING_FILE);
                        long startTime = System.currentTimeMillis();

                        ((JSONFileSerializable) m).toJSONFile(moduleFile);

                        long time = System.currentTimeMillis() - startTime;
                        fireCacheFileCompletedEvent(listeners, m, time, SugarAnalysisListener.WRITING_FILE);
                    }
                }
                cache.put(new CachedFile(sequenceFile, matrixSize, qualityThreshold), cacheFileBasename);
                cleanupCacheFolder();
                //            toFile(CACHE_MAP_FILE, this);
                toJSONFile(CACHE_MAP_FILE);
            } catch (Exception e) {

            }
        }
    }

    public void cleanupCacheFolder() {

        File cacheDir = getCACHE_DIR();
        if (cacheDir.exists()) {
            cleanUpByMaximumSize(cacheDir);
            cleanUpByTime(cacheDir);
            cleanUpByInvalidMapEntries(cacheDir);
        }

    }

    private void cleanUpByInvalidMapEntries(File cacheDir) {

        List<CachedFile> entriesToRemove = new ArrayList<CachedFile>();
        for (CachedFile cf : cache.keySet()) {
            File f = cf.getFile();
            if (!f.isFile())
                entriesToRemove.add(cf);
        }

        for (CachedFile cf : entriesToRemove) {
            cache.remove(cf);
        }
    }

    private void cleanUpByTime(File cacheDir) {

        long currentTime = System.currentTimeMillis();
        long minTime = currentTime - EXPIRATION_TIME;

        File[] cacheFiles = cacheDir.listFiles(CACHE_FILE_FILTER);
        for (File file : cacheFiles) {
            if (file.lastModified() < minTime)
                file.delete();
        }
    }

    private void cleanUpByMaximumSize(File cacheDir) {
        int totalSize = 0;

        File[] cacheFiles = cacheDir.listFiles(CACHE_FILE_FILTER);
        for (File file : cacheFiles) {
            totalSize += file.length();
        }

        if (totalSize > MAXIMUM_CACHE_SIZE) {
            // sort the array from oldest to newest
            Arrays.sort(cacheFiles, new Comparator<File>() {

                public int compare(File f1, File f2) {
                    return Long.valueOf(f1.lastModified()).compareTo(f2.lastModified());
                }

            });

            for (File file : cacheFiles) {
                totalSize -= file.length();
                file.delete();

                if (totalSize < MAXIMUM_CACHE_SIZE)
                    break;
            }
        }
    }

    @Override
    public String toString() {
        return "OpenedFileCache [cache=" + cache + "]";
    }

    // default Java Serialization

    static public File toFile(File file, Object obj) {
        ObjectOutputStream oos = null;
        try {
            FileOutputStream fos = new FileOutputStream(file);
            BufferedOutputStream bos = new BufferedOutputStream(fos);
            oos = new ObjectOutputStream(bos);
            oos.writeObject(obj);
        } catch (IOException ioe) {
            ioe.printStackTrace();
        } finally {
            if (oos != null) {
                try {
                    oos.close();
                } catch (IOException ex) {
                }
            }
        }
        return file;
    }

    public static Object fromFile(File file) throws FileNotFoundException, IOException, ClassNotFoundException {
        Object result = null;
        ObjectInputStream ois = null;
        try {
            FileInputStream fis = new FileInputStream(file);
            BufferedInputStream bis = new BufferedInputStream(fis);
            ois = new ObjectInputStream(bis);
            result = ois.readObject();
        } finally {
            if (ois != null) {
                try {
                    ois.close();
                } catch (IOException ex) {
                }
            }
        }
        return result;
    }

    // default Java Serialization

    // customized JSON Serialization

    @Override
    public String toJSONString() {
        JSONObject obj = toJSONObject();
        return obj.toString();
    }

    @Override
    public void writeJSONString(Writer out) throws IOException {
        JSONObject obj = toJSONObject();
        JSONValue.writeJSONString(obj, out);
    }

    @Override
    public JSONObject toJSONObject() {
        JSONObject obj = new JSONObject();

        JSONArray keyArray = new JSONArray();
        JSONArray valueArray = new JSONArray();
        for (CachedFile cachedFile : cache.keySet()) {
            keyArray.add(cachedFile);
            valueArray.add(cache.get(cachedFile).getAbsolutePath());
        }

        obj.put(JSON_ATTR_CACHE_KEYS, keyArray);
        obj.put(JSON_ATTR_CACHE_VALUES, valueArray);
        return obj;
    }

    @Override
    public void fromJSONObject(JSONObject jsonObject) {
        JSONArray keys = (JSONArray) jsonObject.get(JSON_ATTR_CACHE_KEYS);
        JSONArray values = (JSONArray) jsonObject.get(JSON_ATTR_CACHE_VALUES);

        if (keys.size() == values.size()) {

            for (int i = 0; i < keys.size(); i++) {
                JSONObject jsonCachedFile = (JSONObject) keys.get(i);
                CachedFile cachedFile = new CachedFile();
                cachedFile.fromJSONObject(jsonCachedFile);
                File file = new File(values.get(i).toString());
                cache.put(cachedFile, file);

            }
        }
    }

    @Override
    public void toJSONFile(File file) throws IOException {
        JSONSerializationUtils.writeJSONFile(file, this);
    }

    @Override
    public void fromJSONFile(File file) throws IOException, ParseException {
        JSONSerializationUtils.fromJSONFile(file, this);
    }

    // customized JSON Serialization

    public Integer[] getAvailableQualityThresholds(SequenceFile selectedSequenceFile, int matrixSize) {

        List<Integer> result = new ArrayList<Integer>();

        for (CachedFile cachedFile : cache.keySet()) {
            if (cachedFile.getFile().equals(selectedSequenceFile.getFile())
                    && cachedFile.getMatrixSize() == matrixSize) {
                result.add(cachedFile.getQualityThreshold());
            }
        }

        Collections.sort(result, Collections.reverseOrder());

        return result.toArray(new Integer[0]);
    }

}