/* * $Id$ * * Copyright (C) 2003-2015 JNode.org * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library 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 Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; If not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ package org.jnode.fs.jfat; import java.io.IOException; import java.nio.ByteBuffer; import java.util.LinkedHashMap; import java.util.Map; import java.util.Stack; import org.jnode.driver.block.BlockDeviceAPI; import org.jnode.util.LittleEndian; public class FatCache { private final float loadFactor = 0.75f; private final Fat fat; private final BlockDeviceAPI api; private final long fatsize; private final int nrfats; private int elementSize; private CacheMap map; private long access = 0; private long hit = 0; public FatCache(Fat fat, int cacheSize, int elementSize) { this.fat = fat; this.api = fat.getApi(); this.fatsize = fat.getBootSector().getSectorsPerFat() * fat.getBootSector().getBytesPerSector(); this.nrfats = fat.getBootSector().getNrFats(); this.elementSize = elementSize; // allocate the LinkedHashMap // that do the dirty LRU job this.map = new CacheMap(cacheSize); } public int getCacheSize() { return map.getCacheSize(); } public int usedEntries() { return map.usedEntries(); } public int freeEntries() { return map.freeEntries(); } private CacheElement put(long address) throws IOException { /** * get a CacheElement from the stack object pool */ CacheElement c = map.pop(); /** * read the element from the device */ c.read(address); /** * and insert the element into the LinkedHashMap */ map.put(c); /** * stack "must" contains at least one entry the placeholder ... so let * it throw an exception if this is false */ CacheElement e = map.peek(); // if an element was discarded from the LRU cache // now we can free it ... this will send the element // to storage if is marked as dirty if (!e.isFree()) e.free(); return c; } private CacheElement get(long address) throws IOException { CacheElement c = map.get(address); access++; // if the cache contains the element just return it, we have a cache hit // this will update the LRU order: the LinkedHashMap will make it the // newest // // the cache element cannot be null so we can avoid to call // containsKey(); if (c != null) hit++; // otherwise put a new element inside the cache // possibly flushing and discarding the eldest element else c = put(address); return c; } private long getUInt16(long offset) throws IOException { long addr = offset / elementSize; int ofs = (int) (offset % elementSize); byte[] data = get(addr).getData(); return LittleEndian.getUInt16(data, ofs); } private long getUInt32(long offset) throws IOException { long addr = (long) (offset / elementSize); int ofs = (int) (offset % elementSize); byte[] data = get(addr).getData(); return LittleEndian.getUInt32(data, ofs); } private void setInt16(long offset, int value) throws IOException { long addr = offset / elementSize; int ofs = (int) (offset % elementSize); CacheElement c = get(addr); byte[] data = c.getData(); LittleEndian.setInt16(data, ofs, value); c.setDirty(); } private void setInt32(long offset, int value) throws IOException { long addr = (long) (offset / elementSize); int ofs = (int) (offset % elementSize); CacheElement c = get(addr); byte[] data = c.getData(); LittleEndian.setInt32(data, ofs, value); c.setDirty(); } public long getUInt16(int index) throws IOException { return getUInt16(fat.position(0, index)); } public long getUInt32(int index) throws IOException { return getUInt32(fat.position(0, index)); } public void setInt16(int index, int element) throws IOException { setInt16(fat.position(0, index), element); } public void setInt32(int index, int element) throws IOException { setInt32(fat.position(0, index), element); } public void flush(long address) throws IOException { CacheElement c = map.get(address); if (c != null) c.flush(); } public void flush() throws IOException { for (CacheElement c : map.values()) { c.flush(); } } public long getHit() { return hit; } public long getAccess() { return access; } public double getRatio() { if (access > 0) return ((double) hit / (double) access); else return 0.0f; } public String flushOrder() { return map.flushOrder(); } public String toString() { StrWriter out = new StrWriter(); out.print(map); out.println("size=" + getCacheSize() + " used=" + usedEntries() + " free=" + freeEntries()); return out.toString(); } private class CacheMap extends LinkedHashMap<CacheKey, CacheElement> { private static final long serialVersionUID = 1L; private final int cacheSize; private final CacheKey key = new CacheKey(); private final Stack<CacheElement> free = new Stack<CacheElement>(); private CacheMap(int cacheSize) { super((int) Math.ceil(cacheSize / loadFactor) + 1, loadFactor, true); this.cacheSize = cacheSize; for (int i = 0; i < cacheSize + 1; i++) free.push(new CacheElement()); } private int getCacheSize() { return cacheSize; } private int usedEntries() { return size(); } private int freeEntries() { return (free.size() - 1); } private CacheElement peek() { return free.peek(); } private CacheElement push(CacheElement c) { return free.push(c); } private CacheElement pop() { return free.pop(); } private CacheElement get(long address) { key.set(address); return get(key); } private CacheElement put(CacheElement c) { return put(c.getAddress(), c); } /** * discard the eldest element when the cache is full */ protected boolean removeEldestEntry(Map.Entry<CacheKey, CacheElement> eldest) { boolean remove = (size() > cacheSize); /** * before going to discard the eldest push it back on the stacked * object pool */ if (remove) push(eldest.getValue()); return remove; } public String flushOrder() { StrWriter out = new StrWriter(); for (CacheElement c : values()) { if (c.isDirty()) out.print("<" + c.getAddress().get() + ">"); } return out.toString(); } public String toString() { StrWriter out = new StrWriter(); for (CacheElement c : values()) out.println(c); return out.toString(); } } /** * Here we need to "wrap" a long because Java Long wrapper is an "immutable" * type */ private class CacheKey { private static final long FREE = -1; private long key; private CacheKey(long key) { this.key = key; } private CacheKey() { free(); } private void free() { key = FREE; } private boolean isFree() { return (key == FREE); } private long get() { return key; } private void set(long value) { key = value; } public int hashCode() { return (int) (key ^ (key >>> 32)); } public boolean equals(Object obj) { return obj instanceof CacheKey && key == ((CacheKey) obj).get(); } public String toString() { return String.valueOf(key); } } private class CacheElement { /** * CacheKey element is allocated and its reference is stored here to * avoid to allocate new CacheKey objects at runtime * <p/> * In this way .. just one global key will be enough to access * CacheElements */ private boolean dirty; private CacheKey address; private final ByteBuffer elem; private CacheElement() { this.dirty = false; this.address = new CacheKey(); // FAT-12 reads in two byte chunks so add an extra element to prevent an array index out of bounds exception // when reading in the last element this.elem = ByteBuffer.wrap(new byte[elementSize + 1]); } private boolean isFree() { return address.isFree(); } private CacheKey getAddress() { return address; } private byte[] getData() { return elem.array(); } /** * some more work is needed in read and write to handle the multiple fat * availability we have to correcly handle the exception to be sure that * if we have at least a correct fat we get it - gvt */ private void read(long address) throws IOException { if (!isFree()) throw new IllegalArgumentException("cannot read a busy element"); this.address.set(address); elem.clear(); api.read(address * elementSize, elem); elem.clear(); } private void write() throws IOException { if (isFree()) throw new IllegalArgumentException("cannot write a free element"); elem.clear(); long addr = address.get() * elementSize; for (int i = 0; i < nrfats; i++) { api.write(addr, elem); addr += fatsize; elem.clear(); } } private boolean isDirty() { return dirty; } private void setDirty() { dirty = true; } private void flush() throws IOException { if (isDirty()) { write(); dirty = false; } } private void free() throws IOException { if (isFree()) throw new IllegalArgumentException("cannot free a free element"); flush(); address.free(); } public String toString() { StrWriter out = new StrWriter(); out.print("address=" + address.get() + " dirty=" + dirty); return out.toString(); } } }