package org.simantics.acorn.lru; import java.io.IOException; import java.nio.file.Path; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import org.simantics.acorn.FileIO; import org.simantics.acorn.Persistable; import org.simantics.acorn.exception.AcornAccessVerificationException; import org.simantics.acorn.exception.IllegalAcornStateException; import org.simantics.utils.datastructures.Pair; public abstract class LRUObject> implements Persistable { public static boolean VERIFY = true; // Final stuff final protected LRU LRU; final private Semaphore mutex = new Semaphore(1); final private MapKey key; final private String fileName; // Mutable stuff protected long accessTime = AccessTime.getInstance().getAccessTime(); private int offset; private int length; private boolean resident = true; private boolean dirty = true; private boolean forceResident = false; // DEBUG // private boolean isForceResidentSetAfterLastGet = false; private Path readDirectory; private Thread mutexOwner; // for loading public LRUObject(LRU LRU, MapKey key, Path readDirectory, String fileName, int offset, int length, boolean dirty, boolean resident) { this.LRU = LRU; this.key = key; this.fileName = fileName; this.offset = offset; this.length = length; this.readDirectory = readDirectory; this.dirty = dirty; this.resident = resident; } // for creating public LRUObject(LRU LRU, MapKey key, Path readDirectory, String fileName, boolean dirty, boolean resident) { this(LRU, key, readDirectory, fileName, -1, -1, dirty, resident); } /* * Public interface */ public MapKey getKey() { // This can be called without mutex return key; } public void acquireMutex() throws IllegalAcornStateException { try { while(!mutex.tryAcquire(3, TimeUnit.SECONDS)) { System.err.println("Mutex is taking a long time to acquire - owner is " + mutexOwner); } if(VERIFY) mutexOwner = Thread.currentThread(); } catch (InterruptedException e) { throw new IllegalAcornStateException(e); } } public boolean tryAcquireMutex() { return mutex.tryAcquire(); } public void releaseMutex() { mutex.release(); } @Override public void toFile(Path bytes) throws IOException { if(VERIFY) { try { verifyAccess(); } catch (AcornAccessVerificationException e) { throw new IOException("Exception occured during toFile for file " + fileName, e); } } try { Pair pair = toBytes(); byte[] data = pair.first; int length = pair.second; FileIO fio = FileIO.get(bytes); int offset = fio.saveBytes(data, length, overwrite()); setPosition(offset, length); } catch (AcornAccessVerificationException | IllegalAcornStateException e) { throw new IOException("Exception occured during toFile for file " + fileName, e); } } public int makeResident() throws AcornAccessVerificationException, IllegalAcornStateException { if(VERIFY) verifyAccess(); return LRU.makeResident(this, false); } public int makeResident(boolean keepResident) throws AcornAccessVerificationException, IllegalAcornStateException { if(VERIFY) verifyAccess(); return LRU.makeResident(this, true); } /* * Package implementation details */ abstract void release(); abstract String getExtension(); String getStateKey() throws IllegalAcornStateException, AcornAccessVerificationException { String result = getKey().toString() + "#" + getDirectory().getFileName() + "#" + getOffset() + "#" + getLength(); if(offset == -1) throw new IllegalAcornStateException(result); return result; } long getLastAccessTime() throws AcornAccessVerificationException { if(VERIFY) verifyAccess(); return accessTime; } void accessed() throws AcornAccessVerificationException { if(VERIFY) verifyAccess(); accessTime = AccessTime.getInstance().getAccessTime(); } boolean persist() throws AcornAccessVerificationException { if(VERIFY) verifyAccess(); if(LRU.persist(this)) { readDirectory = LRU.getDirectory(); return true; } else { return false; } } void setForceResident(boolean value) throws AcornAccessVerificationException { if(VERIFY) verifyAccess(); forceResident = value; // isForceResidentSetAfterLastGet = true; } boolean canBePersisted() throws AcornAccessVerificationException { if(VERIFY) verifyAccess(); // isForceResidentSetAfterLastGet = false; return !forceResident; } boolean isDirty() throws AcornAccessVerificationException { if(VERIFY) verifyAccess(); return dirty; } boolean isResident() throws AcornAccessVerificationException { if(VERIFY) verifyAccess(); return resident; } String getFileName() throws AcornAccessVerificationException { if(VERIFY) verifyAccess(); return fileName; } void setResident(boolean value) throws AcornAccessVerificationException { if(VERIFY) verifyAccess(); resident = value; } void setDirty(boolean value) throws AcornAccessVerificationException { if(VERIFY) verifyAccess(); dirty = value; } byte[] readFile() throws IOException, AcornAccessVerificationException { if(VERIFY) verifyAccess(); Path dir = getDirectory(); Path f = dir.resolve(getFileName()); FileIO fio = FileIO.get(f); return fio.readBytes(getOffset(), getLength()); } /* * Protected implementation details */ abstract protected boolean overwrite(); abstract protected Pair toBytes() throws IllegalAcornStateException; protected void setDirty() throws AcornAccessVerificationException { if(VERIFY) verifyAccess(); dirty = true; } protected void verifyAccess() throws AcornAccessVerificationException { if (mutex.availablePermits() != 0) throw new AcornAccessVerificationException("fileName=" + fileName + " mutex has " + mutex.availablePermits() + " available permits, should be 0! Current mutexOwner is " + mutexOwner); } protected synchronized void cancelForceResident() throws AcornAccessVerificationException { setForceResident(false); } /* * Private implementation details */ private int getOffset() throws AcornAccessVerificationException { if(VERIFY) verifyAccess(); return offset; } private int getLength() throws AcornAccessVerificationException { if(VERIFY) verifyAccess(); return length; } private void setPosition(int offset, int length) throws AcornAccessVerificationException, IllegalAcornStateException { if(VERIFY) verifyAccess(); if(offset == -1) throw new IllegalAcornStateException("offset == -1 for " + fileName + " in " + readDirectory.toAbsolutePath() + ", dirty=" + dirty + ", resident=" + resident + ", forceResident=" + forceResident); this.offset = offset; this.length = length; if(overwrite() && offset > 0) throw new IllegalAcornStateException("overwrite() == true && offset > 0 for " + fileName + " in " + readDirectory.toAbsolutePath() + ", dirty=" + dirty + ", resident=" + resident + ", forceResident=" + forceResident); } private Path getDirectory() throws AcornAccessVerificationException { if(VERIFY) verifyAccess(); return readDirectory; } }