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.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<String> 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<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) {
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<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)) {
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");
}

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
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"));

View File

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

View File

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