Serialized classes update mechanism

Added serialized classes update mechanism and removed the useless initialize method.
This commit is contained in:
Andrea Cavalli 2019-02-09 14:23:18 +01:00
parent 1abc761fe6
commit 10ca07d4c5
13 changed files with 240 additions and 50 deletions

View File

@ -35,7 +35,6 @@ public class Database implements IDatabase {
private final DatabaseReferencesIO referencesIO; private final DatabaseReferencesIO referencesIO;
private final DatabaseReferencesMetadata referencesMetadata; private final DatabaseReferencesMetadata referencesMetadata;
private final DatabaseObjectsIO objectsIO; private final DatabaseObjectsIO objectsIO;
private final DatabaseDataInitializer dataInitializer;
private EnhancedObject loadedRootObject; private EnhancedObject loadedRootObject;
public Database(Path dataFile, Path blocksMetaFile, Path referencesMetaFile) throws IOException { public Database(Path dataFile, Path blocksMetaFile, Path referencesMetaFile) throws IOException {
@ -54,12 +53,11 @@ public class Database implements IDatabase {
this.referencesMetadata = new DatabaseReferencesMetadata(referencesMetaFile); this.referencesMetadata = new DatabaseReferencesMetadata(referencesMetaFile);
this.referencesIO = new DatabaseReferencesIO(blocksIO, referencesMetadata); this.referencesIO = new DatabaseReferencesIO(blocksIO, referencesMetadata);
this.objectsIO = new DatabaseObjectsIO(this, referencesIO); this.objectsIO = new DatabaseObjectsIO(this, referencesIO);
this.dataInitializer = new DatabaseDataInitializer(objectsIO);
} }
@Override @Override
public IDataInitializer getDataInitializer() { public IDataInitializer getDataInitializer() {
return dataInitializer; return objectsIO.dataInitializer;
} }
@Override @Override
@ -77,8 +75,8 @@ public class Database implements IDatabase {
public <T extends EnhancedObject> T loadRoot(Class<T> type) throws IOException { public <T extends EnhancedObject> T loadRoot(Class<T> type) throws IOException {
return loadRoot(type, () -> { return loadRoot(type, () -> {
T obj = objectsIO.instantiateEnhancedObject(type); T obj = objectsIO.toInstance(type);
dataInitializer.initializeDBObject(obj); objectsIO.dataInitializer.initializeDBObject(obj);
return obj; return obj;
}); });
} }
@ -118,7 +116,6 @@ public class Database implements IDatabase {
public void initializeDBObject(EnhancedObject obj) throws IOException { public void initializeDBObject(EnhancedObject obj) throws IOException {
initializeDBObjectFields(obj); initializeDBObjectFields(obj);
initializeDBObjectProperties(obj); initializeDBObjectProperties(obj);
obj.initialize();
} }
private void initializeDBObjectFields(EnhancedObject obj) throws IOException { private void initializeDBObjectFields(EnhancedObject obj) throws IOException {
@ -197,18 +194,44 @@ public class Database implements IDatabase {
} }
} }
public static class DatabaseEnhancedObjectUpgrader implements EnhancedObjectUpgrader {
private final DatabaseObjectsIO objectsIO;
private final long[] fieldRefs;
private final long[] methodRefs;
public DatabaseEnhancedObjectUpgrader(DatabaseObjectsIO objectsIO, long[] fieldRefs, long[] methodRefs) {
this.objectsIO = objectsIO;
this.fieldRefs = fieldRefs;
this.methodRefs = methodRefs;
}
@Override
@SuppressWarnings("unchecked")
public Object getField(int id, DBDataType type, Supplier<Class<?>> enhancedClassType) throws IOException {
return objectsIO.loadData(type, fieldRefs[id], enhancedClassType);
}
@Override
@SuppressWarnings("unchecked")
public Object getMethod(int id, DBDataType type, Supplier<Class<?>> enhancedClassType) throws IOException {
return objectsIO.loadData(type, methodRefs[id], enhancedClassType);
}
}
public static class DatabaseObjectsIO implements IObjectsIO { public static class DatabaseObjectsIO implements IObjectsIO {
private final IDatabase database; private final IDatabase database;
private final DatabaseReferencesIO referencesIO; private final DatabaseReferencesIO referencesIO;
private final Object accessLock = new Object(); private final Object accessLock = new Object();
private final DatabaseDataInitializer dataInitializer;
private Kryo kryo = new Kryo(); private Kryo kryo = new Kryo();
private DatabaseObjectsIO(IDatabase database, DatabaseReferencesIO referencesIO) { private DatabaseObjectsIO(IDatabase database, DatabaseReferencesIO referencesIO) {
this.database = database; this.database = database;
this.referencesIO = referencesIO; this.referencesIO = referencesIO;
this.dataInitializer = new DatabaseDataInitializer(this);
kryo.setRegistrationRequired(false); kryo.setRegistrationRequired(false);
int id = -90; int id = -90;
registerClass(boolean[].class, id++); registerClass(boolean[].class, id++);
@ -271,6 +294,7 @@ public class Database implements IDatabase {
if (buffer.limit() == 0) { if (buffer.limit() == 0) {
return null; return null;
} }
int serializedVersion = Byte.toUnsignedInt(buffer.get());
int fieldsCount = buffer.getInt(); int fieldsCount = buffer.getInt();
int methodsCount = buffer.getInt(); int methodsCount = buffer.getInt();
long[] fieldRefs = new long[fieldsCount]; long[] fieldRefs = new long[fieldsCount];
@ -281,7 +305,7 @@ public class Database implements IDatabase {
for (int i = 0; i < methodsCount; i++) { for (int i = 0; i < methodsCount; i++) {
methodRefs[i] = buffer.getLong(); methodRefs[i] = buffer.getLong();
} }
return preloadEnhancedObject(objectType, fieldRefs, methodRefs); return preloadEnhancedObject(objectType, serializedVersion, fieldRefs, methodRefs);
} }
} }
@ -349,8 +373,9 @@ public class Database implements IDatabase {
synchronized (accessLock) { synchronized (accessLock) {
if (value != null) { if (value != null) {
EnhancedObjectFullInfo objectFullInfo = value.getAllInfo(); EnhancedObjectFullInfo objectFullInfo = value.getAllInfo();
int totalSize = Integer.BYTES * 2 + objectFullInfo.getFieldReferences().length * Long.BYTES + objectFullInfo.getPropertyReferences().length * Long.BYTES; int totalSize = Byte.BYTES + Integer.BYTES * 2 + objectFullInfo.getFieldReferences().length * Long.BYTES + objectFullInfo.getPropertyReferences().length * Long.BYTES;
ByteBuffer buffer = ByteBuffer.allocate(totalSize); ByteBuffer buffer = ByteBuffer.allocate(totalSize);
buffer.put((byte) objectFullInfo.getVersion());
buffer.putInt(objectFullInfo.getFieldReferences().length); buffer.putInt(objectFullInfo.getFieldReferences().length);
buffer.putInt(objectFullInfo.getPropertyReferences().length); buffer.putInt(objectFullInfo.getPropertyReferences().length);
for (int i = 0; i < objectFullInfo.getFieldReferences().length; i++) { for (int i = 0; i < objectFullInfo.getFieldReferences().length; i++) {
@ -359,15 +384,25 @@ public class Database implements IDatabase {
for (int i = 0; i < objectFullInfo.getPropertyReferences().length; i++) { for (int i = 0; i < objectFullInfo.getPropertyReferences().length; i++) {
buffer.putLong(objectFullInfo.getPropertyReferences()[i]); buffer.putLong(objectFullInfo.getPropertyReferences()[i]);
} }
long[] fieldReferences = objectFullInfo.getFieldReferences();
DBDataType[] fieldTypes = objectFullInfo.getFieldTypes();
Field[] fields = objectFullInfo.getFields();
long[] propertyReferences = objectFullInfo.getPropertyReferences();
DBDataType[] propertyTypes = objectFullInfo.getPropertyTypes();
Object[] propertyValues = objectFullInfo.getLoadedPropertyValues();
for (int i = 0; i < objectFullInfo.getFieldReferences().length; i++) { for (int i = 0; i < objectFullInfo.getFieldReferences().length; i++) {
if (fields[i] != null) {
try { try {
setData(objectFullInfo.getFieldReferences()[i], objectFullInfo.getFieldTypes()[i], objectFullInfo.getFields()[i].get(value)); setData(fieldReferences[i], fieldTypes[i], fields[i].get(value));
} catch (IllegalAccessException e) { } catch (IllegalAccessException e) {
throw new IOException(e); throw new IOException(e);
} }
} }
}
for (int i = 0; i < objectFullInfo.getPropertyReferences().length; i++) { for (int i = 0; i < objectFullInfo.getPropertyReferences().length; i++) {
setData(objectFullInfo.getPropertyReferences()[i], objectFullInfo.getPropertyTypes()[i], objectFullInfo.getLoadedPropertyValues()[i]); if (propertyValues[i] != null) {
setData(propertyReferences[i], propertyTypes[i], propertyValues[i]);
}
} }
buffer.flip(); buffer.flip();
referencesIO.writeToReference(reference, totalSize, buffer); referencesIO.writeToReference(reference, totalSize, buffer);
@ -713,7 +748,7 @@ public class Database implements IDatabase {
return biggestFieldId; return biggestFieldId;
} }
private <T extends EnhancedObject> T instantiateEnhancedObject(Class<T> type) throws IOException { private <T extends EnhancedObject> T toInstance(Class<T> type) throws IOException {
try { try {
T obj = type.getConstructor().newInstance(); T obj = type.getConstructor().newInstance();
obj.database = database; obj.database = database;
@ -725,10 +760,26 @@ public class Database implements IDatabase {
} }
} }
private <T extends EnhancedObject> T preloadEnhancedObject(Class<T> objectType, long[] fieldRefs, long[] methodRefs) throws IOException { private <T extends EnhancedObject> T preloadEnhancedObject(Class<T> objectType, int serializedVersion, long[] fieldRefs, long[] methodRefs) throws IOException {
T obj = instantiateEnhancedObject(objectType); // Instantiate the class to an object
T obj = toInstance(objectType);
// Check the serialized version
DBClass dbClass = objectType.getAnnotation(DBClass.class);
int classVersion = 0;
if (dbClass != null) {
classVersion = dbClass.version();
}
if (classVersion > serializedVersion) {
DatabaseEnhancedObjectUpgrader enhancedObjectUpgrader = new DatabaseEnhancedObjectUpgrader(this, fieldRefs, methodRefs);
dataInitializer.initializeDBObject(obj);
obj.onUpgrade(serializedVersion, enhancedObjectUpgrader);
} else if (classVersion < serializedVersion) {
throw new IllegalStateException("The serialized class is more recent than the current version of that class!");
} else {
preloadEnhancedObjectFields(obj, fieldRefs); preloadEnhancedObjectFields(obj, fieldRefs);
preloadEnhancedObjectProperties(obj, methodRefs); preloadEnhancedObjectProperties(obj, methodRefs);
}
return obj; return obj;
} }

View File

@ -1,17 +1,19 @@
package org.warp.cowdb; package org.warp.cowdb;
import org.apache.commons.lang3.NotImplementedException;
import org.apache.commons.lang3.reflect.MethodUtils; import org.apache.commons.lang3.reflect.MethodUtils;
import org.warp.jcwdb.ann.DBClass;
import org.warp.jcwdb.ann.DBDataType; import org.warp.jcwdb.ann.DBDataType;
import org.warp.jcwdb.ann.DBPropertyGetter; import org.warp.jcwdb.ann.DBPropertyGetter;
import org.warp.jcwdb.ann.DBPropertySetter; import org.warp.jcwdb.ann.DBPropertySetter;
import java.io.IOError;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Map; import java.util.Map;
public abstract class EnhancedObject { public abstract class EnhancedObject {
protected final int version;
protected IDatabase database; protected IDatabase database;
private Field[] fields; private Field[] fields;
private DBDataType[] fieldTypes; private DBDataType[] fieldTypes;
@ -26,17 +28,15 @@ public abstract class EnhancedObject {
private Map<String, DBPropertyGetter> getterMethods; private Map<String, DBPropertyGetter> getterMethods;
public EnhancedObject() { public EnhancedObject() {
version = getClassVersion();
} }
public EnhancedObject(IDatabase database) throws IOException { public EnhancedObject(IDatabase database) throws IOException {
this.database = database; this.database = database;
version = getClassVersion();
database.getDataInitializer().initializeDBObject(this); database.getDataInitializer().initializeDBObject(this);
} }
public abstract void initialize() throws IOException;
public void setFields(Field[] fields, DBDataType[] fieldTypes, long[] fieldReferences) { public void setFields(Field[] fields, DBDataType[] fieldTypes, long[] fieldReferences) {
this.fields = fields; this.fields = fields;
this.fieldTypes = fieldTypes; this.fieldTypes = fieldTypes;
@ -63,7 +63,7 @@ public abstract class EnhancedObject {
} }
public EnhancedObjectFullInfo getAllInfo() { public EnhancedObjectFullInfo getAllInfo() {
return new EnhancedObjectFullInfo(fieldReferences, fieldTypes, fields, propertyReferences, propertyTypes, loadedPropertyValues); return new EnhancedObjectFullInfo(version, fieldReferences, fieldTypes, fields, propertyReferences, propertyTypes, loadedPropertyValues);
} }
@ -98,4 +98,19 @@ public abstract class EnhancedObject {
loadedPropertyValues[propertyId] = value; loadedPropertyValues[propertyId] = value;
loadedProperties[propertyId] = true; loadedProperties[propertyId] = true;
} }
private int getClassVersion() {
DBClass classAnnotation = this.getClass().getAnnotation(DBClass.class);
return classAnnotation == null ? 0 : classAnnotation.version();
}
/**
* Called when an unloaded class is older than the actual version
* @param oldObjectVersion
* @param enhancedObjectUpgrader
* @throws IOException
*/
public void onUpgrade(int oldObjectVersion, EnhancedObjectUpgrader enhancedObjectUpgrader) throws IOException {
throw new NotImplementedException("Method onUpgrade() is not implemented for class " + this.getClass().getSimpleName());
}
} }

View File

@ -5,6 +5,7 @@ import org.warp.jcwdb.ann.DBDataType;
import java.lang.reflect.Field; import java.lang.reflect.Field;
public class EnhancedObjectFullInfo { public class EnhancedObjectFullInfo {
private final int version;
private final long[] fieldReferences; private final long[] fieldReferences;
private final DBDataType[] fieldTypes; private final DBDataType[] fieldTypes;
private final Field[] fields; private final Field[] fields;
@ -12,7 +13,11 @@ public class EnhancedObjectFullInfo {
private final DBDataType[] propertyTypes; private final DBDataType[] propertyTypes;
private final Object[] loadedPropertyValues; private final Object[] loadedPropertyValues;
EnhancedObjectFullInfo(long[] fieldReferences, DBDataType[] fieldTypes, Field[] fields, long[] propertyReferences, DBDataType[] propertyTypes, Object[] loadedPropertyValues) { EnhancedObjectFullInfo(int version, long[] fieldReferences, DBDataType[] fieldTypes, Field[] fields, long[] propertyReferences, DBDataType[] propertyTypes, Object[] loadedPropertyValues) {
this.version = version;
if (version > 255) {
throw new IllegalArgumentException();
}
this.fieldReferences = fieldReferences; this.fieldReferences = fieldReferences;
this.fieldTypes = fieldTypes; this.fieldTypes = fieldTypes;
this.fields = fields; this.fields = fields;
@ -21,6 +26,10 @@ public class EnhancedObjectFullInfo {
this.loadedPropertyValues = loadedPropertyValues; this.loadedPropertyValues = loadedPropertyValues;
} }
int getVersion() {
return version;
}
long[] getFieldReferences() { long[] getFieldReferences() {
return fieldReferences; return fieldReferences;
} }

View File

@ -0,0 +1,17 @@
package org.warp.cowdb;
import org.warp.jcwdb.ann.DBDataType;
import java.io.IOException;
import java.util.function.Supplier;
public interface EnhancedObjectUpgrader {
Object getField(int id, DBDataType type, Supplier<Class<?>> enhancedClassType) throws IOException;
default Object getField(int id, DBDataType type) throws IOException {
return getField(id, type, null);
}
Object getMethod(int id, DBDataType type, Supplier<Class<?>> enhancedClassType) throws IOException;
default Object getMethod(int id, DBDataType type) throws IOException {
return getField(id, type, null);
}
}

View File

@ -25,11 +25,6 @@ public class EnhancedObjectCowList<T extends EnhancedObject> extends CowList<T>
super(); super();
} }
@Override
public void initialize() throws IOException {
}
public EnhancedObjectCowList(IDatabase database, Class<T> type) throws IOException { public EnhancedObjectCowList(IDatabase database, Class<T> type) throws IOException {
super(database); super(database);
this.type = type; this.type = type;

View File

@ -27,11 +27,6 @@ public class ObjectCowList<T> extends CowList<T> {
indices = new LongArrayList(); indices = new LongArrayList();
} }
@Override
public void initialize() throws IOException {
}
@Override @Override
protected T loadItem(long uid) throws IOException { protected T loadItem(long uid) throws IOException {
return database.getObjectsIO().loadObject(uid); return database.getObjectsIO().loadObject(uid);

View File

@ -0,0 +1,12 @@
package org.warp.jcwdb.ann;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface DBClass {
int version() default 0;
}

View File

@ -0,0 +1,58 @@
package org.warp.jcwdb.tests;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.warp.cowdb.Database;
import org.warp.cowdb.EnhancedObject;
import org.warp.cowdb.IDatabase;
import org.warp.jcwdb.ann.DBDataType;
import org.warp.jcwdb.ann.DBField;
import org.warp.jcwdb.ann.DBPropertyGetter;
import org.warp.jcwdb.ann.DBPropertySetter;
import org.warp.jcwdb.utils.NTestUtils;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import static org.junit.Assert.assertEquals;
public class EnhancedClassUpdate {
private Path path1;
private Path path2;
private Path path3;
private Database db;
@Before
public void setUp() throws Exception {
path1 = Files.createTempFile("db-tests-", ".db");
path2 = Files.createTempFile("db-tests-", ".db");
path3 = Files.createTempFile("db-tests-", ".db");
db = new Database(path1, path2, path3);
OldClass root = db.loadRoot(OldClass.class);
root.field1 = "Abc";
root.field2 = 12;
root.field4 = 13;
db.close();
}
@Test
public void shouldUpdateClass() throws IOException {
db = new Database(path1, path2, path3);
V2Class root = db.loadRoot(V2Class.class);
assertEquals(root.field4, "Abc");
assertEquals(root.field2, 12);
assertEquals(root.field1, 13L);
db.close();
}
@After
public void tearDown() throws Exception {
Files.deleteIfExists(path1);
Files.deleteIfExists(path2);
Files.deleteIfExists(path3);
}
}

View File

@ -13,7 +13,7 @@ import org.warp.jcwdb.utils.NTestUtils;
import java.io.IOException; import java.io.IOException;
public class NDBMultipleEnhancedObjects { public class MultipleEnhancedObjects {
private NTestUtils.WrappedDb db; private NTestUtils.WrappedDb db;
private RootTwoClasses root; private RootTwoClasses root;
@ -81,10 +81,5 @@ public class NDBMultipleEnhancedObjects {
public void setClass4(NTestUtils.RootClass value) { public void setClass4(NTestUtils.RootClass value) {
setProperty(value); setProperty(value);
} }
@Override
public void initialize() throws IOException {
}
} }
} }

View File

@ -0,0 +1,19 @@
package org.warp.jcwdb.tests;
import org.warp.cowdb.EnhancedObject;
import org.warp.jcwdb.ann.DBClass;
import org.warp.jcwdb.ann.DBDataType;
import org.warp.jcwdb.ann.DBField;
import java.io.IOException;
public class OldClass extends EnhancedObject {
@DBField(id = 0, type = DBDataType.OBJECT)
public String field1;
@DBField(id = 1, type = DBDataType.INTEGER)
public int field2;
@DBField(id = 3, type = DBDataType.INTEGER)
public int field4;
}

View File

@ -0,0 +1,33 @@
package org.warp.jcwdb.tests;
import org.warp.cowdb.EnhancedObject;
import org.warp.cowdb.EnhancedObjectUpgrader;
import org.warp.jcwdb.ann.DBClass;
import org.warp.jcwdb.ann.DBDataType;
import org.warp.jcwdb.ann.DBField;
import java.io.IOException;
@DBClass(version = 1)
public class V2Class extends EnhancedObject {
@DBField(id = 0, type = DBDataType.LONG)
public long field1;
@DBField(id = 1, type = DBDataType.INTEGER)
public int field2;
@DBField(id = 3, type = DBDataType.OBJECT)
public String field4;
@Override
public void onUpgrade(int oldObjectVersion, EnhancedObjectUpgrader enhancedObjectUpgrader) throws IOException {
switch (oldObjectVersion) {
case 0: {
field1 = (long) (Integer) enhancedObjectUpgrader.getField(3, DBDataType.INTEGER);
field2 = (int) enhancedObjectUpgrader.getField(1, DBDataType.INTEGER);
field4 = (String) enhancedObjectUpgrader.getField(0, DBDataType.OBJECT);
break;
}
}
}
}

View File

@ -18,10 +18,6 @@ public class NSimplestClass extends EnhancedObject {
public NSimplestClass(Database database) throws IOException { public NSimplestClass(Database database) throws IOException {
super(database); super(database);
}
@Override
public void initialize() throws IOException {
field1 = true; field1 = true;
} }
} }

View File

@ -374,10 +374,5 @@ public class NTestUtils {
public boolean test() { public boolean test() {
return true; return true;
} }
@Override
public void initialize() throws IOException {
}
} }
} }