FileReader.java

package com.renomad.minum.utils;

import com.renomad.minum.logging.ILogger;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;

import static com.renomad.minum.utils.FileUtils.checkForBadFilePatterns;

/**
 * Reads files from disk, optionally storing into a LRU cache.
 */
public final class FileReader implements IFileReader {

    private final Map<String, byte[]> lruCache;
    private final boolean useCacheForStaticFiles;
    private final ILogger logger;
    private final ReentrantLock cacheLock = new ReentrantLock();

    public FileReader(Map<String, byte[]> lruCache, boolean useCacheForStaticFiles, ILogger logger) {
        this.lruCache = lruCache;
        this.useCacheForStaticFiles = useCacheForStaticFiles;
        this.logger = logger;
    }

    @Override
    public byte[] readFile(String path) throws IOException {
        if (useCacheForStaticFiles && lruCache.containsKey(path)) {
            cacheLock.lock();
            try {
                return lruCache.get(path);
            } finally {
                cacheLock.unlock();
            }
        }
        checkForBadFilePatterns(path);
        return readTheFile(path, logger, useCacheForStaticFiles, lruCache);
    }

    byte[] readTheFile(String path, ILogger logger, boolean useCacheForStaticFiles, Map<String, byte[]> lruCache) throws IOException {
        try (RandomAccessFile reader = new RandomAccessFile(path, "r");
             ByteArrayOutputStream out = new ByteArrayOutputStream()) {
            FileChannel channel = reader.getChannel();
            int bufferSize = 8 * 1024;
            if (bufferSize > channel.size()) {
                bufferSize = (int) channel.size();
            }
            ByteBuffer buff = ByteBuffer.allocate(bufferSize);

            while (channel.read(buff) > 0) {
                out.write(buff.array(), 0, buff.position());
                buff.clear();
            }

            byte[] bytes = out.toByteArray();
            if (bytes.length == 0) {
                logger.logTrace(() -> path + " filesize was 0, returning empty byte array");
                return new byte[0];
            } else {
                String s = path + " filesize was " + bytes.length + " bytes.";
                logger.logTrace(() -> s);

                if (useCacheForStaticFiles) {
                    logger.logDebug(() -> "Storing " + path + " in the cache");
                    cacheLock.lock();
                    try {
                        lruCache.put(path, bytes);
                    } finally {
                        cacheLock.unlock();
                    }
                }
                return bytes;
            }
        }
    }

    /**
     * Returns the lock used to prevent concurrent modification
     * exceptions when mutating the LRU cache data.
     * <br>
     * This may be useful if you need to access the LRU cache,
     * which, owing to how a least-recently-used cache works,
     * will cause the data to mutate, and which requires to
     * be protected with locks.
     */
    public ReentrantLock getCacheLock() {
        return cacheLock;
    }
}