2018-11-19 15:16:12 +01:00
|
|
|
package org.warp.jcwdb;
|
|
|
|
|
2018-11-20 18:39:48 +01:00
|
|
|
import java.io.IOException;
|
2018-12-11 23:00:51 +01:00
|
|
|
import java.util.function.BiConsumer;
|
|
|
|
import java.util.function.BiFunction;
|
|
|
|
import java.util.function.Consumer;
|
2018-12-07 00:26:38 +01:00
|
|
|
import java.util.function.Function;
|
2018-11-20 18:39:48 +01:00
|
|
|
|
2018-11-21 01:02:25 +01:00
|
|
|
/**
|
|
|
|
* You must have only a maximum of 1 reference for each index
|
|
|
|
* @param <T>
|
|
|
|
*/
|
2019-01-09 19:05:41 +01:00
|
|
|
public class EntryReference<T> implements Editable<T>, Saveable, Castable {
|
2018-12-05 02:39:41 +01:00
|
|
|
private final JCWDatabase.EntryReferenceTools db;
|
2018-11-21 01:02:25 +01:00
|
|
|
private final long entryIndex;
|
2018-11-20 18:39:48 +01:00
|
|
|
private final DBTypeParser<T> parser;
|
2018-12-07 00:26:38 +01:00
|
|
|
private T value;
|
2018-12-11 23:00:51 +01:00
|
|
|
private long cachedHash;
|
|
|
|
private volatile boolean isHashCached;
|
|
|
|
private volatile boolean loaded;
|
2018-11-21 01:02:25 +01:00
|
|
|
private volatile boolean closed;
|
2019-01-06 00:31:52 +01:00
|
|
|
private volatile boolean isFlushingAllowed;
|
2018-12-11 23:00:51 +01:00
|
|
|
private final Object hashCacheLock = new Object();
|
|
|
|
private final Object accessLock = new Object();
|
2018-11-21 01:02:25 +01:00
|
|
|
private final Object closeLock = new Object();
|
2018-11-19 15:16:12 +01:00
|
|
|
|
2018-12-11 23:00:51 +01:00
|
|
|
public EntryReference(JCWDatabase.EntryReferenceTools db, long entryId, long hash, DBTypeParser<T> parser) {
|
|
|
|
this.loaded = false;
|
|
|
|
this.isHashCached = false;
|
2018-11-19 15:16:12 +01:00
|
|
|
this.db = db;
|
2018-11-21 01:02:25 +01:00
|
|
|
this.entryIndex = entryId;
|
2018-11-19 15:16:12 +01:00
|
|
|
this.parser = parser;
|
2018-12-11 23:00:51 +01:00
|
|
|
this.value = null;
|
2018-11-21 01:02:25 +01:00
|
|
|
}
|
|
|
|
|
2018-12-11 23:00:51 +01:00
|
|
|
public EntryReference(JCWDatabase.EntryReferenceTools db, long entryId, long hash, DBTypeParser<T> parser, T value) {
|
|
|
|
this.loaded = true;
|
|
|
|
this.isHashCached = true;
|
2018-11-22 23:31:41 +01:00
|
|
|
this.db = db;
|
|
|
|
this.entryIndex = entryId;
|
|
|
|
this.parser = parser;
|
2018-12-11 23:00:51 +01:00
|
|
|
this.cachedHash = hash;
|
2018-11-22 23:31:41 +01:00
|
|
|
this.value = value;
|
|
|
|
}
|
|
|
|
|
2018-11-21 01:02:25 +01:00
|
|
|
public DBTypeParser<T> getParser() {
|
|
|
|
return parser;
|
|
|
|
}
|
|
|
|
|
|
|
|
public long getIndex() {
|
|
|
|
return entryIndex;
|
2018-11-19 15:16:12 +01:00
|
|
|
}
|
2018-12-04 23:57:49 +01:00
|
|
|
|
|
|
|
public long calculateHash() {
|
2018-12-11 23:00:51 +01:00
|
|
|
synchronized(accessLock) {
|
|
|
|
load();
|
|
|
|
synchronized(hashCacheLock) {
|
|
|
|
if (isHashCached) {
|
|
|
|
return cachedHash;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return parser.calculateHash(this.value);
|
|
|
|
}
|
2018-12-04 23:57:49 +01:00
|
|
|
}
|
2018-11-19 15:16:12 +01:00
|
|
|
|
2019-01-09 19:05:41 +01:00
|
|
|
public boolean isClosed() {
|
|
|
|
return closed;
|
|
|
|
}
|
|
|
|
|
2018-12-04 23:57:49 +01:00
|
|
|
/**
|
|
|
|
* Note that this method won't be called when closing without saving
|
2018-12-11 23:00:51 +01:00
|
|
|
*/
|
|
|
|
public void save() {
|
2019-01-06 00:31:52 +01:00
|
|
|
this.save(false);
|
|
|
|
}
|
|
|
|
|
2019-01-09 19:05:41 +01:00
|
|
|
public void saveAndFlush() {
|
|
|
|
this.save(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
private void save(boolean flush) {
|
2018-12-11 23:00:51 +01:00
|
|
|
synchronized(accessLock) {
|
|
|
|
if (loaded && !closed) {
|
|
|
|
try {
|
2019-01-09 19:05:41 +01:00
|
|
|
if (value instanceof Saveable) {
|
|
|
|
if (flush) {
|
|
|
|
((Saveable)value).saveAndFlush();
|
|
|
|
} else {
|
|
|
|
((Saveable)value).save();
|
|
|
|
}
|
2018-12-21 10:03:30 +01:00
|
|
|
}
|
2019-01-06 00:31:52 +01:00
|
|
|
IndexDetails returnedDetails = this.db.write(entryIndex, parser.getWriter(value));
|
2018-12-11 23:00:51 +01:00
|
|
|
synchronized(hashCacheLock) {
|
|
|
|
this.cachedHash = returnedDetails.getHash();
|
|
|
|
this.isHashCached = true;
|
|
|
|
}
|
2019-01-09 19:05:41 +01:00
|
|
|
if (flush) {
|
2019-01-06 00:31:52 +01:00
|
|
|
if (!isFlushingAllowed) {
|
|
|
|
this.db.setFlushingAllowed(entryIndex, true);
|
|
|
|
this.isFlushingAllowed = true;
|
|
|
|
}
|
|
|
|
}
|
2018-12-11 23:00:51 +01:00
|
|
|
} catch (IOException e) {
|
|
|
|
e.printStackTrace();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Reccomended way to edit the value
|
|
|
|
* @param editFunction
|
|
|
|
* @throws IOException
|
|
|
|
*/
|
2018-12-20 00:16:16 +01:00
|
|
|
public void editValue(BiFunction<T, Saveable, T> editFunction) {
|
2018-12-11 23:00:51 +01:00
|
|
|
synchronized(accessLock) {
|
|
|
|
load();
|
|
|
|
this.value = editFunction.apply(this.value, this);
|
2019-01-06 00:31:52 +01:00
|
|
|
this.save(true);
|
2018-12-11 23:00:51 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Reccomended way to edit the value
|
|
|
|
* @param editFunction
|
2018-12-04 23:57:49 +01:00
|
|
|
* @throws IOException
|
|
|
|
*/
|
2018-12-20 00:16:16 +01:00
|
|
|
public void editValue(Function<T, T> editFunction) {
|
|
|
|
synchronized(accessLock) {
|
|
|
|
load();
|
|
|
|
this.value = editFunction.apply(this.value);
|
2019-01-06 00:31:52 +01:00
|
|
|
this.save(true);
|
2018-12-20 00:16:16 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Reccomended way to edit the value
|
|
|
|
* @param editFunction
|
|
|
|
* @throws IOException
|
|
|
|
*/
|
|
|
|
public void editValue(BiConsumer<T, Saveable> editFunction) {
|
2018-12-11 23:00:51 +01:00
|
|
|
synchronized(accessLock) {
|
|
|
|
load();
|
|
|
|
editFunction.accept(this.value, this);
|
2019-01-06 00:31:52 +01:00
|
|
|
this.save(true);
|
2018-11-21 01:02:25 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-20 00:16:16 +01:00
|
|
|
/**
|
|
|
|
* Reccomended way to edit the value
|
|
|
|
* @param editFunction
|
|
|
|
* @throws IOException
|
|
|
|
*/
|
|
|
|
public void editValue(Consumer<T> editFunction) {
|
|
|
|
synchronized(accessLock) {
|
|
|
|
load();
|
|
|
|
editFunction.accept(this.value);
|
2019-01-06 00:31:52 +01:00
|
|
|
this.save(true);
|
2018-12-20 00:16:16 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-09 19:05:41 +01:00
|
|
|
/**
|
|
|
|
* Reccomended way to edit the value
|
|
|
|
* DO NOT EDIT THE VALUE
|
|
|
|
* @param viewFunction
|
|
|
|
* @throws IOException
|
|
|
|
*/
|
|
|
|
public void viewValue(Consumer<T> viewFunction) {
|
|
|
|
synchronized(accessLock) {
|
|
|
|
load();
|
|
|
|
viewFunction.accept(this.value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-11 23:00:51 +01:00
|
|
|
/**
|
|
|
|
* Substitute the old value with a new one
|
|
|
|
* @param val
|
|
|
|
* @throws IOException
|
|
|
|
*/
|
2018-12-20 00:16:16 +01:00
|
|
|
public void setValue(T val) {
|
2018-12-11 23:00:51 +01:00
|
|
|
synchronized(accessLock) {
|
|
|
|
this.loaded = true;
|
|
|
|
this.value = val;
|
|
|
|
synchronized(hashCacheLock) {
|
|
|
|
this.isHashCached = false;
|
|
|
|
}
|
2019-01-06 00:31:52 +01:00
|
|
|
this.save(true);
|
2018-12-11 23:00:51 +01:00
|
|
|
}
|
2018-12-07 00:26:38 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-12-11 23:00:51 +01:00
|
|
|
* Use editValue instead. READ ONLY!!
|
2018-12-07 00:26:38 +01:00
|
|
|
* @return
|
|
|
|
*/
|
|
|
|
@Deprecated()
|
|
|
|
public T getValue() {
|
2019-01-09 19:05:41 +01:00
|
|
|
return getValueReadOnlyUnsafe();
|
2018-12-07 00:26:38 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* DO NOT ATTEMPT TO MODIFY THE VALUE RETURNED
|
|
|
|
* @return
|
|
|
|
*/
|
2019-01-09 19:05:41 +01:00
|
|
|
public T getValueReadOnlyUnsafe() {
|
2018-12-11 23:00:51 +01:00
|
|
|
synchronized(accessLock) {
|
|
|
|
load();
|
|
|
|
return this.value;
|
|
|
|
}
|
|
|
|
|
2018-12-07 00:26:38 +01:00
|
|
|
}
|
|
|
|
|
2018-12-11 23:00:51 +01:00
|
|
|
private void load() {
|
|
|
|
synchronized(accessLock) {
|
|
|
|
if (!loaded) {
|
|
|
|
try {
|
2019-01-06 00:31:52 +01:00
|
|
|
if (this.isFlushingAllowed) {
|
|
|
|
this.db.setFlushingAllowed(entryIndex, false);
|
|
|
|
this.isFlushingAllowed = false;
|
|
|
|
}
|
2018-12-11 23:00:51 +01:00
|
|
|
this.value = db.read(entryIndex, parser.getReader());
|
|
|
|
this.loaded = true;
|
|
|
|
} catch (IOException e) {
|
|
|
|
throw (NullPointerException) new NullPointerException(e.getLocalizedMessage()).initCause(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@SuppressWarnings("unchecked")
|
2018-11-21 01:02:25 +01:00
|
|
|
@Override
|
2018-12-11 23:00:51 +01:00
|
|
|
public <U> U cast() {
|
|
|
|
return (U) this;
|
2018-11-21 01:02:25 +01:00
|
|
|
}
|
|
|
|
|
2018-12-05 02:39:41 +01:00
|
|
|
protected void close() throws IOException {
|
2018-11-21 01:02:25 +01:00
|
|
|
if (closed) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
synchronized (closeLock) {
|
|
|
|
if (closed) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-01-06 00:31:52 +01:00
|
|
|
save(true);
|
2018-11-21 01:02:25 +01:00
|
|
|
|
|
|
|
closed = true;
|
|
|
|
}
|
2018-11-19 15:16:12 +01:00
|
|
|
}
|
2018-12-04 23:57:49 +01:00
|
|
|
|
|
|
|
public void closeWithoutSaving() {
|
|
|
|
if (closed) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
synchronized (closeLock) {
|
|
|
|
if (closed) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-11-21 01:02:25 +01:00
|
|
|
closed = true;
|
|
|
|
}
|
2018-11-19 15:16:12 +01:00
|
|
|
}
|
2018-12-11 23:00:51 +01:00
|
|
|
|
|
|
|
public Object getAccessLock() {
|
|
|
|
return accessLock;
|
|
|
|
}
|
2018-11-19 15:16:12 +01:00
|
|
|
}
|