From 6da883fef91ccde0fd8740afca1abc9c5dbac772 Mon Sep 17 00:00:00 2001 From: Stepan Usatiuk Date: Wed, 4 Dec 2024 23:15:05 +0100 Subject: [PATCH] object alloc version --- .../deployment/ObjectsAllocProcessor.java | 111 +++++++++--------- .../alloc/it/DummyVersionProvider.java | 20 ++++ .../objects/alloc/it/ObjectAllocIT.java | 34 ++++++ .../usatiuk/objects/common/runtime/JData.java | 5 +- .../runtime/JDataAllocVersionProvider.java | 5 + 5 files changed, 117 insertions(+), 58 deletions(-) create mode 100644 dhfs-parent/objects-alloc/integration-tests/src/main/java/com/usatiuk/objects/alloc/it/DummyVersionProvider.java create mode 100644 dhfs-parent/objects-common/runtime/src/main/java/com/usatiuk/objects/common/runtime/JDataAllocVersionProvider.java diff --git a/dhfs-parent/objects-alloc/deployment/src/main/java/com/usatiuk/objects/alloc/deployment/ObjectsAllocProcessor.java b/dhfs-parent/objects-alloc/deployment/src/main/java/com/usatiuk/objects/alloc/deployment/ObjectsAllocProcessor.java index 91bedd0d..5a339234 100644 --- a/dhfs-parent/objects-alloc/deployment/src/main/java/com/usatiuk/objects/alloc/deployment/ObjectsAllocProcessor.java +++ b/dhfs-parent/objects-alloc/deployment/src/main/java/com/usatiuk/objects/alloc/deployment/ObjectsAllocProcessor.java @@ -3,6 +3,7 @@ package com.usatiuk.objects.alloc.deployment; import com.usatiuk.objects.alloc.runtime.ChangeTrackingJData; import com.usatiuk.objects.alloc.runtime.ObjectAllocator; import com.usatiuk.objects.common.runtime.JData; +import com.usatiuk.objects.common.runtime.JDataAllocVersionProvider; import com.usatiuk.objects.common.runtime.JObjectKey; import io.quarkus.arc.deployment.GeneratedBeanBuildItem; import io.quarkus.arc.deployment.GeneratedBeanGizmoAdaptor; @@ -12,6 +13,7 @@ import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.ApplicationIndexBuildItem; import io.quarkus.deployment.builditem.GeneratedClassBuildItem; import io.quarkus.gizmo.*; +import jakarta.inject.Inject; import jakarta.inject.Singleton; import org.apache.commons.lang3.tuple.Pair; import org.jboss.jandex.ClassInfo; @@ -20,6 +22,7 @@ import org.jboss.jandex.MethodInfo; import java.io.Serializable; import java.util.*; +import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -41,6 +44,8 @@ class ObjectsAllocProcessor { } private static final String KEY_NAME = "key"; + private static final String VERSION_NAME = "version"; + private static final List SPECIAL_FIELDS = List.of(KEY_NAME, VERSION_NAME); String propNameToFieldName(String name) { return name; @@ -79,35 +84,24 @@ class ObjectsAllocProcessor { .build()) { - var fieldsMap = item.fields.values().stream().map(jDataFieldInfo -> { - var fc = classCreator.getFieldCreator(propNameToFieldName(jDataFieldInfo.name()), jDataFieldInfo.type().toString()); - - if (jDataFieldInfo.name().equals(KEY_NAME)) { - fc.setModifiers(PRIVATE | FINAL); - } else { - fc.setModifiers(PRIVATE); - } - - try (var getter = classCreator.getMethodCreator(propNameToGetterName(jDataFieldInfo.name()), jDataFieldInfo.type().toString())) { - getter.returnValue(getter.readInstanceField(fc.getFieldDescriptor(), getter.getThis())); - } - return Pair.of(jDataFieldInfo, fc.getFieldDescriptor()); - }).collect(Collectors.toUnmodifiableMap(Pair::getLeft, Pair::getRight)); + var fieldsMap = createFields(item, classCreator); for (var field : fieldsMap.values()) { - if (field.getName().equals(KEY_NAME)) { - try (var constructor = classCreator.getConstructorCreator(JObjectKey.class)) { - constructor.invokeSpecialMethod(MethodDescriptor.ofConstructor(Object.class), constructor.getThis()); - constructor.writeInstanceField(field, constructor.getThis(), constructor.getMethodParam(0)); - constructor.returnVoid(); - } - } else { + if (!SPECIAL_FIELDS.contains(field.getName())) { try (var setter = classCreator.getMethodCreator(propNameToSetterName(field.getName()), void.class, field.getType())) { setter.writeInstanceField(field, setter.getThis(), setter.getMethodParam(0)); setter.returnVoid(); } } } + + try (var constructor = classCreator.getConstructorCreator(JObjectKey.class, long.class)) { + constructor.invokeSpecialMethod(MethodDescriptor.ofConstructor(Object.class), constructor.getThis()); + constructor.writeInstanceField(fieldsMap.get(KEY_NAME), constructor.getThis(), constructor.getMethodParam(0)); + constructor.writeInstanceField(fieldsMap.get(VERSION_NAME), constructor.getThis(), constructor.getMethodParam(1)); + constructor.returnVoid(); + } + } } } @@ -146,23 +140,10 @@ class ObjectsAllocProcessor { wrapped.returnValue(wrapped.getThis()); } - var fieldsMap = item.fields.values().stream().map(jDataFieldInfo -> { - var fc = classCreator.getFieldCreator(propNameToFieldName(jDataFieldInfo.name()), jDataFieldInfo.type().toString()); - - if (jDataFieldInfo.name().equals(KEY_NAME)) { - fc.setModifiers(PRIVATE | FINAL); - } else { - fc.setModifiers(PRIVATE); - } - - try (var getter = classCreator.getMethodCreator(propNameToGetterName(jDataFieldInfo.name()), jDataFieldInfo.type().toString())) { - getter.returnValue(getter.readInstanceField(fc.getFieldDescriptor(), getter.getThis())); - } - return Pair.of(jDataFieldInfo, fc.getFieldDescriptor()); - }).collect(Collectors.toUnmodifiableMap(Pair::getLeft, Pair::getRight)); + var fieldsMap = createFields(item, classCreator); for (var field : fieldsMap.values()) { - if (!field.getName().equals(KEY_NAME)) { + if (!SPECIAL_FIELDS.contains(field.getName())) { try (var setter = classCreator.getMethodCreator(propNameToSetterName(field.getName()), void.class, field.getType())) { setter.writeInstanceField(field, setter.getThis(), setter.getMethodParam(0)); setter.invokeVirtualMethod(MethodDescriptor.ofMethod(classCreator.getClassName(), ON_CHANGE_METHOD_NAME, void.class), setter.getThis()); @@ -171,15 +152,17 @@ class ObjectsAllocProcessor { } } - try (var constructor = classCreator.getConstructorCreator(item.klass.name().toString())) { + try (var constructor = classCreator.getConstructorCreator(item.klass.name().toString(), long.class.getName())) { constructor.invokeSpecialMethod(MethodDescriptor.ofConstructor(Object.class), constructor.getThis()); constructor.writeInstanceField(modified.getFieldDescriptor(), constructor.getThis(), constructor.load(false)); for (var field : fieldsMap.values()) { - constructor.writeInstanceField(field, constructor.getThis(), constructor.invokeInterfaceMethod( - MethodDescriptor.ofMethod(item.klass.name().toString(), propNameToGetterName(field.getName()), field.getType()), - constructor.getMethodParam(0) - )); + if (!Objects.equals(field.getName(), VERSION_NAME)) + constructor.writeInstanceField(field, constructor.getThis(), constructor.invokeInterfaceMethod( + MethodDescriptor.ofMethod(item.klass.name().toString(), propNameToGetterName(field.getName()), field.getType()), + constructor.getMethodParam(0) + )); } + constructor.writeInstanceField(fieldsMap.get(VERSION_NAME), constructor.getThis(), constructor.getMethodParam(1)); constructor.returnVoid(); } } @@ -199,20 +182,7 @@ class ObjectsAllocProcessor { .classOutput(gizmoAdapter) .build()) { - var fieldsMap = item.fields.values().stream().map(jDataFieldInfo -> { - var fc = classCreator.getFieldCreator(propNameToFieldName(jDataFieldInfo.name()), jDataFieldInfo.type().toString()); - - if (jDataFieldInfo.name().equals(KEY_NAME)) { - fc.setModifiers(PRIVATE | FINAL); - } else { - fc.setModifiers(PRIVATE); - } - - try (var getter = classCreator.getMethodCreator(propNameToGetterName(jDataFieldInfo.name()), jDataFieldInfo.type().toString())) { - getter.returnValue(getter.readInstanceField(fc.getFieldDescriptor(), getter.getThis())); - } - return Pair.of(jDataFieldInfo, fc.getFieldDescriptor()); - }).collect(Collectors.toUnmodifiableMap(Pair::getLeft, Pair::getRight)); + var fieldsMap = createFields(item, classCreator); for (var field : fieldsMap.values()) { try (var setter = classCreator.getMethodCreator(propNameToSetterName(field.getName()), void.class, field.getType())) { @@ -235,6 +205,23 @@ class ObjectsAllocProcessor { } + private Map createFields(JDataInfoBuildItem item, ClassCreator classCreator) { + return item.fields.values().stream().map(jDataFieldInfo -> { + var fc = classCreator.getFieldCreator(propNameToFieldName(jDataFieldInfo.name()), jDataFieldInfo.type().toString()); + + if (SPECIAL_FIELDS.contains(jDataFieldInfo.name())) { + fc.setModifiers(PRIVATE | FINAL); + } else { + fc.setModifiers(PRIVATE); + } + + try (var getter = classCreator.getMethodCreator(propNameToGetterName(jDataFieldInfo.name()), jDataFieldInfo.type().toString())) { + getter.returnValue(getter.readInstanceField(fc.getFieldDescriptor(), getter.getThis())); + } + return Pair.of(jDataFieldInfo, fc.getFieldDescriptor()); + }).collect(Collectors.toUnmodifiableMap(i -> i.getLeft().name(), Pair::getRight)); + } + List collectInterfaces(ClassInfo type, ApplicationIndexBuildItem jandex) { return Stream.concat(Stream.of(type), type.interfaceNames().stream() .flatMap(x -> { @@ -275,6 +262,7 @@ class ObjectsAllocProcessor { System.out.println("Missing key getter for " + item.jData); // FIXME!: No matter what, I couldn't get JData to get indexed by jandex fields.put(KEY_NAME, new JDataFieldInfo(KEY_NAME, DotName.createSimple(JObjectKey.class))); + fields.put(VERSION_NAME, new JDataFieldInfo(VERSION_NAME, DotName.createSimple(long.class))); } // Find pairs of getters and setters @@ -351,16 +339,25 @@ class ObjectsAllocProcessor { classCreator.addAnnotation(Singleton.class); + var versionProvider = classCreator.getFieldCreator("versionProvider", JDataAllocVersionProvider.class); + versionProvider.addAnnotation(Inject.class); + versionProvider.setModifiers(PUBLIC); + + Function loadVersion = (block) -> block.invokeInterfaceMethod( + MethodDescriptor.ofMethod(JDataAllocVersionProvider.class, "getVersion", long.class), + block.readInstanceField(versionProvider.getFieldDescriptor(), block.getThis()) + ); + try (MethodCreator methodCreator = classCreator.getMethodCreator("create", JData.class, Class.class, JObjectKey.class)) { matchClassTag(methodCreator, methodCreator.getMethodParam(0), classes, (type, branch, value) -> { - branch.returnValue(branch.newInstance(MethodDescriptor.ofConstructor(getDataClassName(type).toString(), JObjectKey.class), branch.getMethodParam(1))); + branch.returnValue(branch.newInstance(MethodDescriptor.ofConstructor(getDataClassName(type).toString(), JObjectKey.class, long.class), branch.getMethodParam(1), loadVersion.apply(branch))); }); methodCreator.throwException(IllegalArgumentException.class, "Unknown type"); } try (MethodCreator methodCreator = classCreator.getMethodCreator("copy", ChangeTrackingJData.class, JData.class)) { matchClass(methodCreator, methodCreator.getMethodParam(0), classes, (type, branch, value) -> { - branch.returnValue(branch.newInstance(MethodDescriptor.ofConstructor(getCTClassName(type).toString(), type.name().toString()), value)); + branch.returnValue(branch.newInstance(MethodDescriptor.ofConstructor(getCTClassName(type).toString(), type.name().toString(), long.class.getName()), value, loadVersion.apply(branch))); }); methodCreator.throwException(IllegalArgumentException.class, "Unknown type"); } diff --git a/dhfs-parent/objects-alloc/integration-tests/src/main/java/com/usatiuk/objects/alloc/it/DummyVersionProvider.java b/dhfs-parent/objects-alloc/integration-tests/src/main/java/com/usatiuk/objects/alloc/it/DummyVersionProvider.java new file mode 100644 index 00000000..5db49c0c --- /dev/null +++ b/dhfs-parent/objects-alloc/integration-tests/src/main/java/com/usatiuk/objects/alloc/it/DummyVersionProvider.java @@ -0,0 +1,20 @@ +package com.usatiuk.objects.alloc.it; + +import com.usatiuk.objects.common.runtime.JDataAllocVersionProvider; +import jakarta.enterprise.context.ApplicationScoped; + +@ApplicationScoped +public class DummyVersionProvider implements JDataAllocVersionProvider { + + long version = 0; + + @Override + public long getVersion() { + return version; + } + + public void setVersion(long version) { + this.version = version; + } + +} diff --git a/dhfs-parent/objects-alloc/integration-tests/src/test/java/com/usatiuk/objects/alloc/it/ObjectAllocIT.java b/dhfs-parent/objects-alloc/integration-tests/src/test/java/com/usatiuk/objects/alloc/it/ObjectAllocIT.java index 51ea070e..7c70671f 100644 --- a/dhfs-parent/objects-alloc/integration-tests/src/test/java/com/usatiuk/objects/alloc/it/ObjectAllocIT.java +++ b/dhfs-parent/objects-alloc/integration-tests/src/test/java/com/usatiuk/objects/alloc/it/ObjectAllocIT.java @@ -12,6 +12,40 @@ public class ObjectAllocIT { @Inject ObjectAllocator objectAllocator; + @Inject + DummyVersionProvider dummyVersionProvider; + + @Test + void testCreateVersion() { + dummyVersionProvider.setVersion(1); + var newObject = objectAllocator.create(TestJDataEmpty.class, new JObjectKey("TestJDataEmptyKey")); + Assertions.assertNotNull(newObject); + Assertions.assertEquals("TestJDataEmptyKey", newObject.getKey().name()); + Assertions.assertEquals(1, newObject.getVersion()); + } + + @Test + void testCopyVersion() { + dummyVersionProvider.setVersion(1); + var newObject = objectAllocator.create(TestJDataAssorted.class, new JObjectKey("TestJDataAssorted")); + newObject.setLastName("1"); + Assertions.assertNotNull(newObject); + Assertions.assertEquals("TestJDataAssorted", newObject.getKey().name()); + Assertions.assertEquals(1, newObject.getVersion()); + + dummyVersionProvider.setVersion(2); + var copyObject = objectAllocator.copy(newObject); + Assertions.assertNotNull(copyObject); + Assertions.assertFalse(copyObject.isModified()); + Assertions.assertEquals("1", copyObject.wrapped().getLastName()); + Assertions.assertEquals(2, copyObject.wrapped().getVersion()); + Assertions.assertEquals(1, newObject.getVersion()); + copyObject.wrapped().setLastName("2"); + Assertions.assertTrue(copyObject.isModified()); + Assertions.assertEquals("2", copyObject.wrapped().getLastName()); + Assertions.assertEquals("1", newObject.getLastName()); + } + @Test void testCreateObject() { var newObject = objectAllocator.create(TestJDataEmpty.class, new JObjectKey("TestJDataEmptyKey")); diff --git a/dhfs-parent/objects-common/runtime/src/main/java/com/usatiuk/objects/common/runtime/JData.java b/dhfs-parent/objects-common/runtime/src/main/java/com/usatiuk/objects/common/runtime/JData.java index 5d97c2dd..f1ca4baf 100644 --- a/dhfs-parent/objects-common/runtime/src/main/java/com/usatiuk/objects/common/runtime/JData.java +++ b/dhfs-parent/objects-common/runtime/src/main/java/com/usatiuk/objects/common/runtime/JData.java @@ -2,8 +2,11 @@ package com.usatiuk.objects.common.runtime; // TODO: This could be maybe moved to a separate module? // The base class for JObject data -// Only one instance of this exists per key, the instance in the manager is canonical +// Only one instance of this "exists" per key, the instance in the manager is canonical // When committing a transaction, the instance is checked against it, if it isn't the same, a race occurred. +// It is immutable, its version is filled in by the allocator from the AllocVersionProvider public interface JData { JObjectKey getKey(); + + long getVersion(); } diff --git a/dhfs-parent/objects-common/runtime/src/main/java/com/usatiuk/objects/common/runtime/JDataAllocVersionProvider.java b/dhfs-parent/objects-common/runtime/src/main/java/com/usatiuk/objects/common/runtime/JDataAllocVersionProvider.java new file mode 100644 index 00000000..27c2b110 --- /dev/null +++ b/dhfs-parent/objects-common/runtime/src/main/java/com/usatiuk/objects/common/runtime/JDataAllocVersionProvider.java @@ -0,0 +1,5 @@ +package com.usatiuk.objects.common.runtime; + +public interface JDataAllocVersionProvider { + long getVersion(); +}