object alloc version

This commit is contained in:
2024-12-04 23:15:05 +01:00
parent 80e73fe7af
commit 6da883fef9
5 changed files with 117 additions and 58 deletions

View File

@@ -3,6 +3,7 @@ package com.usatiuk.objects.alloc.deployment;
import com.usatiuk.objects.alloc.runtime.ChangeTrackingJData; import com.usatiuk.objects.alloc.runtime.ChangeTrackingJData;
import com.usatiuk.objects.alloc.runtime.ObjectAllocator; import com.usatiuk.objects.alloc.runtime.ObjectAllocator;
import com.usatiuk.objects.common.runtime.JData; import com.usatiuk.objects.common.runtime.JData;
import com.usatiuk.objects.common.runtime.JDataAllocVersionProvider;
import com.usatiuk.objects.common.runtime.JObjectKey; import com.usatiuk.objects.common.runtime.JObjectKey;
import io.quarkus.arc.deployment.GeneratedBeanBuildItem; import io.quarkus.arc.deployment.GeneratedBeanBuildItem;
import io.quarkus.arc.deployment.GeneratedBeanGizmoAdaptor; 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.ApplicationIndexBuildItem;
import io.quarkus.deployment.builditem.GeneratedClassBuildItem; import io.quarkus.deployment.builditem.GeneratedClassBuildItem;
import io.quarkus.gizmo.*; import io.quarkus.gizmo.*;
import jakarta.inject.Inject;
import jakarta.inject.Singleton; import jakarta.inject.Singleton;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import org.jboss.jandex.ClassInfo; import org.jboss.jandex.ClassInfo;
@@ -20,6 +22,7 @@ import org.jboss.jandex.MethodInfo;
import java.io.Serializable; import java.io.Serializable;
import java.util.*; import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
@@ -41,6 +44,8 @@ class ObjectsAllocProcessor {
} }
private static final String KEY_NAME = "key"; private static final String KEY_NAME = "key";
private static final String VERSION_NAME = "version";
private static final List<String> SPECIAL_FIELDS = List.of(KEY_NAME, VERSION_NAME);
String propNameToFieldName(String name) { String propNameToFieldName(String name) {
return name; return name;
@@ -79,35 +84,24 @@ class ObjectsAllocProcessor {
.build()) { .build()) {
var fieldsMap = item.fields.values().stream().map(jDataFieldInfo -> { var fieldsMap = createFields(item, classCreator);
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));
for (var field : fieldsMap.values()) { for (var field : fieldsMap.values()) {
if (field.getName().equals(KEY_NAME)) { if (!SPECIAL_FIELDS.contains(field.getName())) {
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 {
try (var setter = classCreator.getMethodCreator(propNameToSetterName(field.getName()), void.class, field.getType())) { try (var setter = classCreator.getMethodCreator(propNameToSetterName(field.getName()), void.class, field.getType())) {
setter.writeInstanceField(field, setter.getThis(), setter.getMethodParam(0)); setter.writeInstanceField(field, setter.getThis(), setter.getMethodParam(0));
setter.returnVoid(); 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()); wrapped.returnValue(wrapped.getThis());
} }
var fieldsMap = item.fields.values().stream().map(jDataFieldInfo -> { var fieldsMap = createFields(item, classCreator);
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));
for (var field : fieldsMap.values()) { 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())) { try (var setter = classCreator.getMethodCreator(propNameToSetterName(field.getName()), void.class, field.getType())) {
setter.writeInstanceField(field, setter.getThis(), setter.getMethodParam(0)); setter.writeInstanceField(field, setter.getThis(), setter.getMethodParam(0));
setter.invokeVirtualMethod(MethodDescriptor.ofMethod(classCreator.getClassName(), ON_CHANGE_METHOD_NAME, void.class), setter.getThis()); 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.invokeSpecialMethod(MethodDescriptor.ofConstructor(Object.class), constructor.getThis());
constructor.writeInstanceField(modified.getFieldDescriptor(), constructor.getThis(), constructor.load(false)); constructor.writeInstanceField(modified.getFieldDescriptor(), constructor.getThis(), constructor.load(false));
for (var field : fieldsMap.values()) { for (var field : fieldsMap.values()) {
constructor.writeInstanceField(field, constructor.getThis(), constructor.invokeInterfaceMethod( if (!Objects.equals(field.getName(), VERSION_NAME))
MethodDescriptor.ofMethod(item.klass.name().toString(), propNameToGetterName(field.getName()), field.getType()), constructor.writeInstanceField(field, constructor.getThis(), constructor.invokeInterfaceMethod(
constructor.getMethodParam(0) 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(); constructor.returnVoid();
} }
} }
@@ -199,20 +182,7 @@ class ObjectsAllocProcessor {
.classOutput(gizmoAdapter) .classOutput(gizmoAdapter)
.build()) { .build()) {
var fieldsMap = item.fields.values().stream().map(jDataFieldInfo -> { var fieldsMap = createFields(item, classCreator);
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));
for (var field : fieldsMap.values()) { for (var field : fieldsMap.values()) {
try (var setter = classCreator.getMethodCreator(propNameToSetterName(field.getName()), void.class, field.getType())) { try (var setter = classCreator.getMethodCreator(propNameToSetterName(field.getName()), void.class, field.getType())) {
@@ -235,6 +205,23 @@ class ObjectsAllocProcessor {
} }
private Map<String, FieldDescriptor> 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<ClassInfo> collectInterfaces(ClassInfo type, ApplicationIndexBuildItem jandex) { List<ClassInfo> collectInterfaces(ClassInfo type, ApplicationIndexBuildItem jandex) {
return Stream.concat(Stream.of(type), type.interfaceNames().stream() return Stream.concat(Stream.of(type), type.interfaceNames().stream()
.flatMap(x -> { .flatMap(x -> {
@@ -275,6 +262,7 @@ class ObjectsAllocProcessor {
System.out.println("Missing key getter for " + item.jData); System.out.println("Missing key getter for " + item.jData);
// FIXME!: No matter what, I couldn't get JData to get indexed by jandex // 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(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 // Find pairs of getters and setters
@@ -351,16 +339,25 @@ class ObjectsAllocProcessor {
classCreator.addAnnotation(Singleton.class); classCreator.addAnnotation(Singleton.class);
var versionProvider = classCreator.getFieldCreator("versionProvider", JDataAllocVersionProvider.class);
versionProvider.addAnnotation(Inject.class);
versionProvider.setModifiers(PUBLIC);
Function<BytecodeCreator, ResultHandle> 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)) { try (MethodCreator methodCreator = classCreator.getMethodCreator("create", JData.class, Class.class, JObjectKey.class)) {
matchClassTag(methodCreator, methodCreator.getMethodParam(0), classes, (type, branch, value) -> { 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"); methodCreator.throwException(IllegalArgumentException.class, "Unknown type");
} }
try (MethodCreator methodCreator = classCreator.getMethodCreator("copy", ChangeTrackingJData.class, JData.class)) { try (MethodCreator methodCreator = classCreator.getMethodCreator("copy", ChangeTrackingJData.class, JData.class)) {
matchClass(methodCreator, methodCreator.getMethodParam(0), classes, (type, branch, value) -> { 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"); methodCreator.throwException(IllegalArgumentException.class, "Unknown type");
} }

View File

@@ -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;
}
}

View File

@@ -12,6 +12,40 @@ public class ObjectAllocIT {
@Inject @Inject
ObjectAllocator objectAllocator; 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 @Test
void testCreateObject() { void testCreateObject() {
var newObject = objectAllocator.create(TestJDataEmpty.class, new JObjectKey("TestJDataEmptyKey")); var newObject = objectAllocator.create(TestJDataEmpty.class, new JObjectKey("TestJDataEmptyKey"));

View File

@@ -2,8 +2,11 @@ package com.usatiuk.objects.common.runtime;
// TODO: This could be maybe moved to a separate module? // TODO: This could be maybe moved to a separate module?
// The base class for JObject data // 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. // 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 { public interface JData {
JObjectKey getKey(); JObjectKey getKey();
long getVersion();
} }

View File

@@ -0,0 +1,5 @@
package com.usatiuk.objects.common.runtime;
public interface JDataAllocVersionProvider {
long getVersion();
}