package org.warp.jcwdb.ann; import org.apache.commons.lang3.reflect.MethodUtils; 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 JCWDatabase 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 setterMethods; private Map getterMethods; private final Object fieldsAccessLock = new Object(); private final Object propertiesAccessLock = new Object(); public DBObject() { } public DBObject(JCWDatabase database) throws IOException { this.database = database; database.initializeDBObject(this); } public abstract void initialize() throws IOException; public 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); } } void setLoadedProperty(int propertyId, T value) { loadedPropertyValues[propertyId] = value; loadedProperties[propertyId] = true; } @SuppressWarnings("unchecked") private T getProperty(int propertyId) throws IOException { synchronized (propertiesAccessLock) { if (!loadedProperties[propertyId]) { long propertyUID = propertyUIDs[propertyId]; database.getDataLoader().loadProperty(this, propertyId, propertyGetters[propertyId], propertyTypes[propertyId], propertyUID); } return (T) loadedPropertyValues[propertyId]; } } public 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 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.getDataLoader().writeObjectInfo(uid, fieldUIDs, propertyUIDs); } } synchronized (fieldsAccessLock) { for (int i = 0; i < fieldUIDs.length; i++) { try { database.getDataLoader().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.getDataLoader().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 setterMethods, Map 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; } } Method[] getPropertyGetters() { return MethodUtils.getMethodsWithAnnotation(this.getClass(), DBPropertyGetter.class); } public Method[] getPropertySetters() { return MethodUtils.getMethodsWithAnnotation(this.getClass(), DBPropertySetter.class); } }