From 10ca07d4c563ec306d8f45156266f8b4210c3687 Mon Sep 17 00:00:00 2001 From: Andrea Cavalli Date: Sat, 9 Feb 2019 14:23:18 +0100 Subject: [PATCH] Serialized classes update mechanism Added serialized classes update mechanism and removed the useless initialize method. --- src/main/java/org/warp/cowdb/Database.java | 87 +++++++++++++++---- .../java/org/warp/cowdb/EnhancedObject.java | 27 ++++-- .../warp/cowdb/EnhancedObjectFullInfo.java | 11 ++- .../warp/cowdb/EnhancedObjectUpgrader.java | 17 ++++ .../cowdb/lists/EnhancedObjectCowList.java | 5 -- .../org/warp/cowdb/lists/ObjectCowList.java | 5 -- src/main/java/org/warp/jcwdb/ann/DBClass.java | 12 +++ .../warp/jcwdb/tests/EnhancedClassUpdate.java | 58 +++++++++++++ ...ects.java => MultipleEnhancedObjects.java} | 7 +- .../java/org/warp/jcwdb/tests/OldClass.java | 19 ++++ .../java/org/warp/jcwdb/tests/V2Class.java | 33 +++++++ .../org/warp/jcwdb/utils/NSimplestClass.java | 4 - .../java/org/warp/jcwdb/utils/NTestUtils.java | 5 -- 13 files changed, 240 insertions(+), 50 deletions(-) create mode 100644 src/main/java/org/warp/cowdb/EnhancedObjectUpgrader.java create mode 100644 src/main/java/org/warp/jcwdb/ann/DBClass.java create mode 100644 src/test/java/org/warp/jcwdb/tests/EnhancedClassUpdate.java rename src/test/java/org/warp/jcwdb/tests/{NDBMultipleEnhancedObjects.java => MultipleEnhancedObjects.java} (95%) create mode 100644 src/test/java/org/warp/jcwdb/tests/OldClass.java create mode 100644 src/test/java/org/warp/jcwdb/tests/V2Class.java diff --git a/src/main/java/org/warp/cowdb/Database.java b/src/main/java/org/warp/cowdb/Database.java index 996f3f0..cbb2d0b 100644 --- a/src/main/java/org/warp/cowdb/Database.java +++ b/src/main/java/org/warp/cowdb/Database.java @@ -35,7 +35,6 @@ public class Database implements IDatabase { private final DatabaseReferencesIO referencesIO; private final DatabaseReferencesMetadata referencesMetadata; private final DatabaseObjectsIO objectsIO; - private final DatabaseDataInitializer dataInitializer; private EnhancedObject loadedRootObject; 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.referencesIO = new DatabaseReferencesIO(blocksIO, referencesMetadata); this.objectsIO = new DatabaseObjectsIO(this, referencesIO); - this.dataInitializer = new DatabaseDataInitializer(objectsIO); } @Override public IDataInitializer getDataInitializer() { - return dataInitializer; + return objectsIO.dataInitializer; } @Override @@ -77,8 +75,8 @@ public class Database implements IDatabase { public T loadRoot(Class type) throws IOException { return loadRoot(type, () -> { - T obj = objectsIO.instantiateEnhancedObject(type); - dataInitializer.initializeDBObject(obj); + T obj = objectsIO.toInstance(type); + objectsIO.dataInitializer.initializeDBObject(obj); return obj; }); } @@ -118,7 +116,6 @@ public class Database implements IDatabase { public void initializeDBObject(EnhancedObject obj) throws IOException { initializeDBObjectFields(obj); initializeDBObjectProperties(obj); - obj.initialize(); } 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> enhancedClassType) throws IOException { + return objectsIO.loadData(type, fieldRefs[id], enhancedClassType); + } + + @Override + @SuppressWarnings("unchecked") + public Object getMethod(int id, DBDataType type, Supplier> enhancedClassType) throws IOException { + return objectsIO.loadData(type, methodRefs[id], enhancedClassType); + } + } + public static class DatabaseObjectsIO implements IObjectsIO { private final IDatabase database; private final DatabaseReferencesIO referencesIO; private final Object accessLock = new Object(); + private final DatabaseDataInitializer dataInitializer; private Kryo kryo = new Kryo(); private DatabaseObjectsIO(IDatabase database, DatabaseReferencesIO referencesIO) { this.database = database; this.referencesIO = referencesIO; + this.dataInitializer = new DatabaseDataInitializer(this); kryo.setRegistrationRequired(false); int id = -90; registerClass(boolean[].class, id++); @@ -271,6 +294,7 @@ public class Database implements IDatabase { if (buffer.limit() == 0) { return null; } + int serializedVersion = Byte.toUnsignedInt(buffer.get()); int fieldsCount = buffer.getInt(); int methodsCount = buffer.getInt(); long[] fieldRefs = new long[fieldsCount]; @@ -281,7 +305,7 @@ public class Database implements IDatabase { for (int i = 0; i < methodsCount; i++) { 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) { if (value != null) { 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); + buffer.put((byte) objectFullInfo.getVersion()); buffer.putInt(objectFullInfo.getFieldReferences().length); buffer.putInt(objectFullInfo.getPropertyReferences().length); 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++) { 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++) { - try { - setData(objectFullInfo.getFieldReferences()[i], objectFullInfo.getFieldTypes()[i], objectFullInfo.getFields()[i].get(value)); - } catch (IllegalAccessException e) { - throw new IOException(e); + if (fields[i] != null) { + try { + setData(fieldReferences[i], fieldTypes[i], fields[i].get(value)); + } catch (IllegalAccessException e) { + throw new IOException(e); + } } } 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(); referencesIO.writeToReference(reference, totalSize, buffer); @@ -713,7 +748,7 @@ public class Database implements IDatabase { return biggestFieldId; } - private T instantiateEnhancedObject(Class type) throws IOException { + private T toInstance(Class type) throws IOException { try { T obj = type.getConstructor().newInstance(); obj.database = database; @@ -725,10 +760,26 @@ public class Database implements IDatabase { } } - private T preloadEnhancedObject(Class objectType, long[] fieldRefs, long[] methodRefs) throws IOException { - T obj = instantiateEnhancedObject(objectType); - preloadEnhancedObjectFields(obj, fieldRefs); - preloadEnhancedObjectProperties(obj, methodRefs); + private T preloadEnhancedObject(Class objectType, int serializedVersion, long[] fieldRefs, long[] methodRefs) throws IOException { + // 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); + preloadEnhancedObjectProperties(obj, methodRefs); + } return obj; } diff --git a/src/main/java/org/warp/cowdb/EnhancedObject.java b/src/main/java/org/warp/cowdb/EnhancedObject.java index 83be611..b1dcbda 100644 --- a/src/main/java/org/warp/cowdb/EnhancedObject.java +++ b/src/main/java/org/warp/cowdb/EnhancedObject.java @@ -1,17 +1,19 @@ package org.warp.cowdb; +import org.apache.commons.lang3.NotImplementedException; import org.apache.commons.lang3.reflect.MethodUtils; +import org.warp.jcwdb.ann.DBClass; import org.warp.jcwdb.ann.DBDataType; import org.warp.jcwdb.ann.DBPropertyGetter; import org.warp.jcwdb.ann.DBPropertySetter; -import java.io.IOError; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Map; public abstract class EnhancedObject { + protected final int version; protected IDatabase database; private Field[] fields; private DBDataType[] fieldTypes; @@ -26,17 +28,15 @@ public abstract class EnhancedObject { private Map getterMethods; public EnhancedObject() { - + version = getClassVersion(); } public EnhancedObject(IDatabase database) throws IOException { this.database = database; + version = getClassVersion(); database.getDataInitializer().initializeDBObject(this); } - public abstract void initialize() throws IOException; - - public void setFields(Field[] fields, DBDataType[] fieldTypes, long[] fieldReferences) { this.fields = fields; this.fieldTypes = fieldTypes; @@ -63,7 +63,7 @@ public abstract class EnhancedObject { } 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; 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()); + } } diff --git a/src/main/java/org/warp/cowdb/EnhancedObjectFullInfo.java b/src/main/java/org/warp/cowdb/EnhancedObjectFullInfo.java index 6a1ce2c..6cac504 100644 --- a/src/main/java/org/warp/cowdb/EnhancedObjectFullInfo.java +++ b/src/main/java/org/warp/cowdb/EnhancedObjectFullInfo.java @@ -5,6 +5,7 @@ import org.warp.jcwdb.ann.DBDataType; import java.lang.reflect.Field; public class EnhancedObjectFullInfo { + private final int version; private final long[] fieldReferences; private final DBDataType[] fieldTypes; private final Field[] fields; @@ -12,7 +13,11 @@ public class EnhancedObjectFullInfo { private final DBDataType[] propertyTypes; 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.fieldTypes = fieldTypes; this.fields = fields; @@ -21,6 +26,10 @@ public class EnhancedObjectFullInfo { this.loadedPropertyValues = loadedPropertyValues; } + int getVersion() { + return version; + } + long[] getFieldReferences() { return fieldReferences; } diff --git a/src/main/java/org/warp/cowdb/EnhancedObjectUpgrader.java b/src/main/java/org/warp/cowdb/EnhancedObjectUpgrader.java new file mode 100644 index 0000000..a368511 --- /dev/null +++ b/src/main/java/org/warp/cowdb/EnhancedObjectUpgrader.java @@ -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> enhancedClassType) throws IOException; + default Object getField(int id, DBDataType type) throws IOException { + return getField(id, type, null); + } + Object getMethod(int id, DBDataType type, Supplier> enhancedClassType) throws IOException; + default Object getMethod(int id, DBDataType type) throws IOException { + return getField(id, type, null); + } +} diff --git a/src/main/java/org/warp/cowdb/lists/EnhancedObjectCowList.java b/src/main/java/org/warp/cowdb/lists/EnhancedObjectCowList.java index 2d18a1d..9fb879c 100644 --- a/src/main/java/org/warp/cowdb/lists/EnhancedObjectCowList.java +++ b/src/main/java/org/warp/cowdb/lists/EnhancedObjectCowList.java @@ -25,11 +25,6 @@ public class EnhancedObjectCowList extends CowList super(); } - @Override - public void initialize() throws IOException { - - } - public EnhancedObjectCowList(IDatabase database, Class type) throws IOException { super(database); this.type = type; diff --git a/src/main/java/org/warp/cowdb/lists/ObjectCowList.java b/src/main/java/org/warp/cowdb/lists/ObjectCowList.java index 78188bc..ebdc58e 100644 --- a/src/main/java/org/warp/cowdb/lists/ObjectCowList.java +++ b/src/main/java/org/warp/cowdb/lists/ObjectCowList.java @@ -27,11 +27,6 @@ public class ObjectCowList extends CowList { indices = new LongArrayList(); } - @Override - public void initialize() throws IOException { - - } - @Override protected T loadItem(long uid) throws IOException { return database.getObjectsIO().loadObject(uid); diff --git a/src/main/java/org/warp/jcwdb/ann/DBClass.java b/src/main/java/org/warp/jcwdb/ann/DBClass.java new file mode 100644 index 0000000..6902ce0 --- /dev/null +++ b/src/main/java/org/warp/jcwdb/ann/DBClass.java @@ -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; +} diff --git a/src/test/java/org/warp/jcwdb/tests/EnhancedClassUpdate.java b/src/test/java/org/warp/jcwdb/tests/EnhancedClassUpdate.java new file mode 100644 index 0000000..2b2c344 --- /dev/null +++ b/src/test/java/org/warp/jcwdb/tests/EnhancedClassUpdate.java @@ -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); + } +} diff --git a/src/test/java/org/warp/jcwdb/tests/NDBMultipleEnhancedObjects.java b/src/test/java/org/warp/jcwdb/tests/MultipleEnhancedObjects.java similarity index 95% rename from src/test/java/org/warp/jcwdb/tests/NDBMultipleEnhancedObjects.java rename to src/test/java/org/warp/jcwdb/tests/MultipleEnhancedObjects.java index 2e5e9a3..438132e 100644 --- a/src/test/java/org/warp/jcwdb/tests/NDBMultipleEnhancedObjects.java +++ b/src/test/java/org/warp/jcwdb/tests/MultipleEnhancedObjects.java @@ -13,7 +13,7 @@ import org.warp.jcwdb.utils.NTestUtils; import java.io.IOException; -public class NDBMultipleEnhancedObjects { +public class MultipleEnhancedObjects { private NTestUtils.WrappedDb db; private RootTwoClasses root; @@ -81,10 +81,5 @@ public class NDBMultipleEnhancedObjects { public void setClass4(NTestUtils.RootClass value) { setProperty(value); } - - @Override - public void initialize() throws IOException { - - } } } diff --git a/src/test/java/org/warp/jcwdb/tests/OldClass.java b/src/test/java/org/warp/jcwdb/tests/OldClass.java new file mode 100644 index 0000000..1254909 --- /dev/null +++ b/src/test/java/org/warp/jcwdb/tests/OldClass.java @@ -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; +} diff --git a/src/test/java/org/warp/jcwdb/tests/V2Class.java b/src/test/java/org/warp/jcwdb/tests/V2Class.java new file mode 100644 index 0000000..ce6ceb0 --- /dev/null +++ b/src/test/java/org/warp/jcwdb/tests/V2Class.java @@ -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; + } + } + } +} diff --git a/src/test/java/org/warp/jcwdb/utils/NSimplestClass.java b/src/test/java/org/warp/jcwdb/utils/NSimplestClass.java index f73ba00..e43d5bb 100644 --- a/src/test/java/org/warp/jcwdb/utils/NSimplestClass.java +++ b/src/test/java/org/warp/jcwdb/utils/NSimplestClass.java @@ -18,10 +18,6 @@ public class NSimplestClass extends EnhancedObject { public NSimplestClass(Database database) throws IOException { super(database); - } - - @Override - public void initialize() throws IOException { field1 = true; } } diff --git a/src/test/java/org/warp/jcwdb/utils/NTestUtils.java b/src/test/java/org/warp/jcwdb/utils/NTestUtils.java index 55fe2f5..07baa75 100644 --- a/src/test/java/org/warp/jcwdb/utils/NTestUtils.java +++ b/src/test/java/org/warp/jcwdb/utils/NTestUtils.java @@ -374,10 +374,5 @@ public class NTestUtils { public boolean test() { return true; } - - @Override - public void initialize() throws IOException { - - } } }