Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,26 @@ private record RebuildType(@Nonnull RecordType recordType, @Nullable Instantiato
Object newInstance(@Nonnull Object[] args) {
return instantiator != null ? instantiator.instantiate(args) : recordType.newInstance(args);
}

/**
* Reads the record's component values in declaration order, through the generated deconstructor when the
* metamodel registered one, so rebuilds run as generated code on both sides of the round trip.
*/
@SuppressWarnings("unchecked")
Object[] deconstruct(@Nonnull Object record) {
if (instantiator != null) {
Object[] args = ((Instantiator<Object>) instantiator).deconstruct(record);
if (args != null) {
return args;
}
}
List<RecordField> fields = recordType.fields();
Object[] args = new Object[fields.size()];
for (int i = 0; i < fields.size(); i++) {
args[i] = REFLECTION.invoke(fields.get(i), record);
}
return args;
}
}

private static final ClassValue<RebuildType> REBUILD_TYPES = new ClassValue<>() {
Expand All @@ -584,11 +604,7 @@ protected RebuildType computeValue(Class<?> type) {
*/
private Object withComponent(@Nonnull Object record, @Nonnull int[] path, int depth, @Nullable Object newValue) {
RebuildType rebuildType = REBUILD_TYPES.get(record.getClass());
List<RecordField> fields = rebuildType.recordType().fields();
Object[] args = new Object[fields.size()];
for (int i = 0; i < fields.size(); i++) {
args[i] = REFLECTION.invoke(fields.get(i), record);
}
Object[] args = rebuildType.deconstruct(record);
int index = path[depth];
args[index] = depth == path.length - 1
? newValue
Expand Down
21 changes: 21 additions & 0 deletions storm-foundation/src/main/java/st/orm/mapping/Instantiator.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package st.orm.mapping;

import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;

/**
* Constructs record instances without reflection.
Expand Down Expand Up @@ -51,4 +52,24 @@ public interface Instantiator<T> {
* @return the constructed instance.
*/
T instantiate(@Nonnull Object[] args);

/**
* Deconstructs the given instance into its canonical constructor arguments, in declaration order.
*
* <p>Generated instantiators override this to read the components directly, completing the reflection-free
* round trip for record rebuilds: component reads run as generated code, matching {@link #instantiate} on the
* construction side. The returned array is freshly allocated; callers may modify it and pass it to
* {@link #instantiate} to build an adjusted copy of the instance.</p>
*
* <p>The default returns {@code null}, signalling that no generated deconstructor is available; callers fall
* back to reflective component access.</p>
*
* @param instance the instance to deconstruct.
* @return the component values in declaration order, or {@code null} when not supported.
* @since 1.13
*/
@Nullable
default Object[] deconstruct(@Nonnull T instance) {
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -991,6 +991,8 @@ class MetamodelProcessor(
val arguments = primaryConstructor.parameters.mapIndexed { index, parameter ->
" args[$index] as ${getKotlinValueTypeName(parameter.type, packageName)}"
}.joinToString(",\n")
val componentNames = primaryConstructor.parameters.map { it.name?.asString() ?: return }
val components = componentNames.joinToString(",\n") { " instance.`$it`" }
val containingFile = classDeclaration.containingFile
val deps = if (containingFile != null) Dependencies(true, containingFile) else Dependencies(false)
val file = codeGenerator.createNewFile(
Expand All @@ -1016,6 +1018,10 @@ class MetamodelProcessor(
| override fun instantiate(args: Array<Any?>): $className = $className(
|$arguments
| )
|
| override fun deconstruct(instance: $className): Array<Any?> = arrayOf(
|$components
| )
|}
""".trimMargin(),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -906,10 +906,13 @@ private void generateInstantiator(@Nonnull Element recordElement) {
String instantiatorName = recordName + "Instantiator";
var parameters = constructor.getParameters();
StringBuilder arguments = new StringBuilder();
StringBuilder components = new StringBuilder();
for (int i = 0; i < parameters.size(); i++) {
String castType = getBoxedTypeName(parameters.get(i).asType().toString().replaceAll("@\\S+\\s+", ""));
arguments.append(arguments.isEmpty() ? "" : ",\n")
.append(" (").append(castType).append(") args[").append(i).append("]");
components.append(components.isEmpty() ? "" : ",\n")
.append(" instance.").append(parameters.get(i).getSimpleName()).append("()");
}
try {
JavaFileObject fileObject = processingEnv.getFiler()
Expand All @@ -919,7 +922,7 @@ private void generateInstantiator(@Nonnull Element recordElement) {
%simport javax.annotation.processing.Generated;

/**
* Instantiator for %s; constructs instances without reflection.
* Instantiator for %s; constructs and deconstructs instances without reflection.
*/
@Generated("%s")
public final class %s implements st.orm.mapping.Instantiator<%s> {
Expand All @@ -936,6 +939,13 @@ public final class %s implements st.orm.mapping.Instantiator<%s> {
%s
);
}

@Override
public Object[] deconstruct(%s instance) {
return new Object[] {
%s
};
}
}""",
(packageName.isEmpty() ? "" : "package " + packageName + ";\n\n"),
recordName,
Expand All @@ -946,7 +956,9 @@ public final class %s implements st.orm.mapping.Instantiator<%s> {
recordName,
recordName,
recordName,
arguments
arguments,
recordName,
components
));
}
generatedInstantiators.add((packageName.isEmpty() ? "" : packageName + ".") + instantiatorName);
Expand Down
Loading