strangedb/src/main/java/org/warp/jcwdb/ann/DBObject.java

212 lines
7.7 KiB
Java

package org.warp.jcwdb.ann;
import org.apache.commons.lang3.reflect.FieldUtils;
import java.io.IOError;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.LinkedHashMap;
import java.util.Map;
public abstract class DBObject {
protected final DatabaseManager database;
private Field[] fields;
private DBDataType[] fieldTypes;
private long[] fieldUIDs;
private Method[] propertyGetters;
private Method[] propertySetters;
private DBDataType[] propertyTypes;
private long[] propertyUIDs;
private boolean[] loadedProperties;
private Object[] loadedPropertyValues;
private Map<String, DBPropertySetter> setterMethods;
private Map<String, DBPropertyGetter> getterMethods;
private final Object fieldsAccessLock = new Object();
private final Object propertiesAccessLock = new Object();
public DBObject(JCWDatabase database) {
this.database = database.getDatabaseManager();
try {
initializeDBObject();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private void initializeDBObject() throws IOException {
initializeDBObjectFields();
initializeDBObjectProperties();
}
private void initializeDBObjectFields() throws IOException {
// Declare the variables needed to get the biggest field Id
Field[] unorderedFields = database.getFields(this);
// Find the biggest field Id
int biggestFieldId = database.getBiggestFieldId(unorderedFields);
// Allocate new UIDs
fieldUIDs = database.allocateNewUIDs(biggestFieldId + 1);
// Declare the other variables
Field[] fields = new Field[biggestFieldId + 1];
DBDataType[] orderedFieldTypes = new DBDataType[biggestFieldId + 1];
// Load all fields metadata and load them
for (Field field : unorderedFields) {
DBField fieldAnnotation = field.getAnnotation(DBField.class);
int fieldId = fieldAnnotation.id();
DBDataType fieldType = fieldAnnotation.type();
database.loadField(this, field, fieldType, fieldUIDs[fieldId]);
fields[fieldId] = field;
orderedFieldTypes[fieldId] = fieldType;
}
// Set fields metadata
setFields(fields, orderedFieldTypes, fieldUIDs);
}
private void initializeDBObjectProperties() {
// Declare the variables needed to get the biggest property Id
Method[] unorderedPropertyGetters = database.getPropertyGetters(this);
Method[] unorderedPropertySetters = database.getPropertySetters(this);
// Find the biggest property Id
int biggestGetter = database.getBiggestPropertyGetterId(unorderedPropertyGetters);
int biggestSetter = database.getBiggestPropertySetterId(unorderedPropertySetters);
int biggestPropertyId = biggestGetter > biggestSetter ? biggestGetter : biggestSetter;
// Allocate new UIDs
propertyUIDs = database.allocateNewUIDs(biggestPropertyId + 1);
for (Method property : unorderedPropertySetters) {
DBPropertySetter fieldAnnotation = property.getAnnotation(DBPropertySetter.class);
int propertyId = fieldAnnotation.id();
if (propertyId > biggestPropertyId) {
biggestPropertyId = propertyId;
}
}
// Declare the other variables
DBDataType[] propertyTypes = new DBDataType[biggestPropertyId + 1];
Method[] propertyGetters = new Method[biggestPropertyId + 1];
Method[] propertySetters = new Method[biggestPropertyId + 1];
Map<String, DBPropertySetter> setterMethods = new LinkedHashMap<>();
Map<String, DBPropertyGetter> getterMethods = new LinkedHashMap<>();
// Load the properties metadata
for (Method property : unorderedPropertyGetters) {
DBPropertyGetter propertyAnnotation = property.getAnnotation(DBPropertyGetter.class);
int propertyId = propertyAnnotation.id();
DBDataType propertyType = propertyAnnotation.type();
propertyTypes[propertyId] = propertyType;
propertyGetters[propertyId] = property;
getterMethods.put(property.getName(), propertyAnnotation);
}
for (Method property : unorderedPropertySetters) {
DBPropertySetter propertyAnnotation = property.getAnnotation(DBPropertySetter.class);
int propertyId = propertyAnnotation.id();
DBDataType propertyType = propertyAnnotation.type();
propertyTypes[propertyId] = propertyType;
propertySetters[propertyId] = property;
setterMethods.put(property.getName(), propertyAnnotation);
}
// Set properties metadata
setProperties(propertyGetters, propertySetters, propertyTypes, propertyUIDs, setterMethods, getterMethods);
}
public DBObject(JCWDatabase database, DBObjectIndicesManager.DBObjectInfo objectInfo) throws IOException {
this.database = database.getDatabaseManager();
this.database.preloadDBObject(this, objectInfo);
}
public <T> T getProperty() {
StackWalker walker = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
StackWalker.StackFrame stackFrame = walker.walk(f -> f.skip(1).findFirst().orElse(null));
try {
int propertyId = stackFrame.getDeclaringClass().getDeclaredMethod(stackFrame.getMethodName()).getAnnotation(DBPropertyGetter.class).id();
return getProperty(propertyId);
} catch (IOException | NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
<T> void setLoadedProperty(int propertyId, T value) {
loadedPropertyValues[propertyId] = value;
loadedProperties[propertyId] = true;
}
@SuppressWarnings("unchecked")
private <T> T getProperty(int propertyId) throws IOException {
synchronized (propertiesAccessLock) {
if (!loadedProperties[propertyId]) {
long propertyUID = propertyUIDs[propertyId];
database.loadProperty(this, propertyId, propertyGetters[propertyId], propertyTypes[propertyId], propertyUID);
}
return (T) loadedPropertyValues[propertyId];
}
}
public <T> void setProperty(T value) {
StackWalker walker = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
StackWalker.StackFrame stackFrame = walker.walk(f -> f.skip(1).findFirst().orElse(null));
DBPropertySetter propertyAnnotation = setterMethods.get(stackFrame.getMethodName());
setProperty(propertyAnnotation.id(), propertyAnnotation.type(), value);
}
public <T> void setProperty(int propertyId, DBDataType propertyType, T value) {
synchronized (propertiesAccessLock) {
loadedPropertyValues[propertyId] = value;
loadedProperties[propertyId] = true;
}
}
public void writeToDisk(long uid) {
//System.err.println("Saving object " + uid + ":" + this);
try {
synchronized (propertiesAccessLock) {
synchronized (fieldsAccessLock) {
database.writeObjectInfo(uid, fieldUIDs, propertyUIDs);
}
}
synchronized (fieldsAccessLock) {
for (int i = 0; i < fieldUIDs.length; i++) {
try {
database.writeObjectProperty(fieldUIDs[i], fieldTypes[i], fields[i].get(this));
} catch (IllegalAccessException e) {
throw new IOError(e);
}
}
}
synchronized (propertiesAccessLock) {
for (int i = 0; i < propertyUIDs.length; i++) {
database.writeObjectProperty(propertyUIDs[i], propertyTypes[i], loadedPropertyValues[i]);
}
}
} catch (IOException e) {
throw new IOError(e);
}
}
public final void setFields(Field[] fields, DBDataType[] fieldTypes, long[] fieldUIDs) {
synchronized (fieldsAccessLock) {
this.fields = fields;
this.fieldTypes = fieldTypes;
this.fieldUIDs = fieldUIDs;
}
}
public final void setProperties(Method[] propertyGetters, Method[] propertySetters, DBDataType[] propertyTypes, long[] propertyUIDs, Map<String, DBPropertySetter> setterMethods, Map<String, DBPropertyGetter> getterMethods) {
synchronized (propertiesAccessLock) {
this.propertyGetters = propertyGetters;
this.propertySetters = propertySetters;
this.propertyTypes = propertyTypes;
this.propertyUIDs = propertyUIDs;
this.loadedProperties = new boolean[this.propertyUIDs.length];
this.loadedPropertyValues = new Object[this.propertyUIDs.length];
this.setterMethods = setterMethods;
this.getterMethods = getterMethods;
}
}
}