object alloc dump

This commit is contained in:
2024-12-03 00:43:27 +01:00
parent 5af1d8a712
commit 3370df6d2c
15 changed files with 235 additions and 26 deletions

View File

@@ -26,6 +26,14 @@
<artifactId>quarkus-junit5-internal</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
</dependencies>
<build>

View File

@@ -0,0 +1,6 @@
package com.usatiuk.objects.alloc.deployment;
import org.jboss.jandex.Type;
public record JDataFieldInfo(String name, Type type) {
}

View File

@@ -0,0 +1,12 @@
package com.usatiuk.objects.alloc.deployment;
import io.quarkus.builder.item.MultiBuildItem;
import org.jboss.jandex.ClassInfo;
public final class JDataIndexBuildItem extends MultiBuildItem {
public final ClassInfo jData;
public JDataIndexBuildItem(ClassInfo jData) {
this.jData = jData;
}
}

View File

@@ -0,0 +1,8 @@
package com.usatiuk.objects.alloc.deployment;
import org.jboss.jandex.ClassInfo;
import java.util.Map;
public record JDataInfo(ClassInfo klass, Map<String, JDataFieldInfo> fields) {
}

View File

@@ -1,14 +1,143 @@
package com.usatiuk.objects.alloc.deployment;
import com.usatiuk.objects.alloc.runtime.ObjectAllocator;
import com.usatiuk.objects.common.JData;
import com.usatiuk.objects.common.JObjectKey;
import io.quarkus.arc.deployment.GeneratedBeanBuildItem;
import io.quarkus.arc.deployment.GeneratedBeanGizmoAdaptor;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.FeatureBuildItem;
import io.quarkus.deployment.builditem.ApplicationIndexBuildItem;
import io.quarkus.deployment.builditem.GeneratedClassBuildItem;
import io.quarkus.gizmo.*;
import jakarta.inject.Singleton;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.MethodInfo;
import org.jboss.jandex.Type;
import java.util.Collections;
import java.util.List;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.stream.Collectors;
class ObjectsAllocProcessor {
@BuildStep
void collectJDatas(BuildProducer<JDataIndexBuildItem> producer, ApplicationIndexBuildItem jandex) {
var jdatas = jandex.getIndex().getAllKnownSubinterfaces(JData.class);
private static final String FEATURE = "objects-alloc";
// Collect the leaves
for (var jdata : jdatas) {
System.out.println("Found JData: " + jdata.name());
if (jandex.getIndex().getAllKnownSubinterfaces(jdata.name()).isEmpty()) {
System.out.println("Found JData leaf: " + jdata.name());
producer.produce(new JDataIndexBuildItem(jdata));
}
}
}
JDataInfo collectData(JDataIndexBuildItem item) {
var methodNameToInfo = item.jData.methods().stream()
.collect(Collectors.toUnmodifiableMap(MethodInfo::name, x -> x));
var reducableSet = new TreeSet<>(methodNameToInfo.keySet());
var fields = new TreeMap<String, JDataFieldInfo>();
// Find pairs of getters and setters
// FIXME:
while (!reducableSet.isEmpty()) {
var name = reducableSet.first();
reducableSet.remove(name);
if (name.startsWith("get")) {
var setterName = "set" + name.substring(3);
if (reducableSet.contains(setterName)) {
reducableSet.remove(setterName);
} else {
throw new RuntimeException("Missing setter for getter: " + name);
}
var getter = methodNameToInfo.get(name);
var setter = methodNameToInfo.get(setterName);
if (!getter.returnType().equals(setter.parameters().getFirst().type())) {
throw new RuntimeException("Getter and setter types do not match: " + name);
}
var variableName = name.substring(3, 4).toLowerCase() + name.substring(4);
fields.put(variableName, new JDataFieldInfo(variableName, getter.returnType()));
} else {
throw new RuntimeException("Unknown method name: " + name);
}
}
return new JDataInfo(item.jData, Collections.unmodifiableMap(fields));
}
interface TypeFunction {
void apply(Type type);
}
void matchClass(BytecodeCreator bytecodeCreator, ResultHandle value, List<Type> types, TypeFunction fn) {
// bytecodeCreator.insta
}
interface ClassTagFunction {
void apply(ClassInfo type, BytecodeCreator branch);
}
// Returns false branch
<T> BytecodeCreator matchClassTag(BytecodeCreator bytecodeCreator, ResultHandle toMatch, List<ClassInfo> types, ClassTagFunction fn) {
if (types.isEmpty()) {
return bytecodeCreator;
}
var eq = bytecodeCreator.invokeVirtualMethod(
MethodDescriptor.ofMethod(Object.class, "equals", boolean.class, Object.class),
toMatch,
bytecodeCreator.loadClass(types.getFirst())
);
var cmp = bytecodeCreator.ifTrue(eq);
fn.apply(types.getFirst(), cmp.trueBranch());
return matchClassTag(cmp.falseBranch(), toMatch, types.subList(1, types.size()), fn);
}
@BuildStep
FeatureBuildItem feature() {
return new FeatureBuildItem(FEATURE);
void makeJDataThingy(List<JDataIndexBuildItem> jDataItems,
BuildProducer<GeneratedBeanBuildItem> generatedBeans,
BuildProducer<GeneratedClassBuildItem> generatedClasses) {
var data = jDataItems.stream().map(this::collectData).collect(Collectors.toUnmodifiableMap(JDataInfo::klass, x -> x));
var classes = data.keySet().stream().map(ClassInfo::asClass).toList();
var gizmoAdapter = new GeneratedBeanGizmoAdaptor(generatedBeans);
try (ClassCreator classCreator = ClassCreator.builder()
.className("com.usatiuk.objects.alloc.generated.ObjectAllocatorImpl")
.interfaces(ObjectAllocator.class)
.classOutput(gizmoAdapter)
.build()) {
classCreator.addAnnotation(Singleton.class);
try (MethodCreator methodCreator = classCreator.getMethodCreator("create", JData.class, Class.class, JObjectKey.class)) {
matchClassTag(methodCreator, methodCreator.getMethodParam(0), classes, (type, branch) -> {
branch.returnValue(branch.newInstance(MethodDescriptor.ofConstructor(type.toString(), JObjectKey.class), branch.getMethodParam(1)));
});
methodCreator.throwException(IllegalArgumentException.class, "Unknown type");
}
try (MethodCreator methodCreator = classCreator.getMethodCreator("copy", ObjectAllocator.ChangeTrackingJData.class, JData.class)) {
methodCreator.returnValue(methodCreator.loadNull());
}
try (MethodCreator methodCreator = classCreator.getMethodCreator("unmodifiable", JData.class, JData.class)) {
methodCreator.returnValue(methodCreator.loadNull());
}
}
}
}

View File

@@ -1,19 +1,20 @@
package com.usatiuk.objects.alloc.test;
import com.usatiuk.objects.alloc.runtime.ObjectAllocator;
import io.quarkus.test.QuarkusUnitTest;
import jakarta.inject.Inject;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import io.quarkus.test.QuarkusUnitTest;
public class ObjectsAllocTest {
// Start unit test with your extension loaded
@RegisterExtension
static final QuarkusUnitTest unitTest = new QuarkusUnitTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class));
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class));
@Test
public void writeYourOwnUnitTest() {

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>com.usatiuk</groupId>
<artifactId>objects-alloc-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>objects-alloc-integration-tests</artifactId>
<name>DHFS objects allocation - Integration Tests</name>
@@ -24,6 +24,11 @@
<artifactId>objects-alloc</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.usatiuk</groupId>
<artifactId>objects-alloc-deployment</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
@@ -45,6 +50,8 @@
<execution>
<goals>
<goal>build</goal>
<goal>generate-code</goal>
<goal>generate-code-tests</goal>
</goals>
</execution>
</executions>

View File

@@ -0,0 +1 @@
quarkus.package.jar.decompiler.enabled=true

View File

@@ -1,7 +0,0 @@
package com.usatiuk.objects.alloc.it;
import io.quarkus.test.junit.QuarkusIntegrationTest;
@QuarkusIntegrationTest
public class ObjectsAllocResourceIT extends ObjectsAllocResourceTest {
}

View File

@@ -1,21 +1,21 @@
package com.usatiuk.objects.alloc.it;
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.is;
import org.junit.jupiter.api.Test;
import com.usatiuk.objects.alloc.runtime.ObjectAllocator;
import com.usatiuk.objects.common.JObjectKey;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
@QuarkusTest
public class ObjectsAllocResourceTest {
@Inject
ObjectAllocator objectAllocator;
@Test
public void testHelloEndpoint() {
given()
.when().get("/objects-alloc")
.then()
.statusCode(200)
.body(is("Hello objects-alloc"));
void testCreateObject() {
var newObject = objectAllocator.create(TestJDataEmpty.class, new JObjectKey("TestJDataEmptyKey"));
Assertions.assertNotNull(newObject);
Assertions.assertEquals("TestJDataEmptyKey", newObject.getKey().name());
}
}

View File

@@ -0,0 +1,18 @@
package com.usatiuk.objects.alloc.it;
import com.usatiuk.objects.common.JData;
import com.usatiuk.objects.common.JObjectKey;
interface TestJDataAssorted extends JData {
String getLastName();
void setLastName(String lastName);
long getAge();
void setAge(long age);
JObjectKey getKidKey();
void setKidKey(JObjectKey kid);
}

View File

@@ -0,0 +1,6 @@
package com.usatiuk.objects.alloc.it;
import com.usatiuk.objects.common.JData;
public interface TestJDataEmpty extends JData {
}

View File

@@ -17,6 +17,7 @@
<modules>
<module>deployment</module>
<module>runtime</module>
<module>integration-tests</module>
</modules>
</project>

View File

@@ -0,0 +1,17 @@
package com.usatiuk.objects.alloc.runtime;
import com.usatiuk.objects.common.JData;
import java.io.Serializable;
abstract class ChangeTrackerBase<T extends JData> implements ObjectAllocator.ChangeTrackingJData<T>, Serializable {
private transient boolean _modified = false;
public boolean isModified() {
return _modified;
}
protected void onChange() {
_modified = true;
}
}

View File

@@ -0,0 +1,2 @@
dhfs.objects.persistence=files
quarkus.package.jar.decompiler.enabled=true