Skip to content

Instantly share code, notes, and snippets.

@SirYwell
Created January 22, 2024 19:20
Show Gist options
  • Save SirYwell/b246ffebd53a8c7b500b96d337445a46 to your computer and use it in GitHub Desktop.
Save SirYwell/b246ffebd53a8c7b500b96d337445a46 to your computer and use it in GitHub Desktop.
Implementation of Withers using Babylon Code Reflection
import java.lang.reflect.RecordComponent;
import java.lang.reflect.code.analysis.Patterns;
import java.lang.reflect.code.Op;
import java.lang.reflect.code.Quotable;
import java.lang.reflect.code.Quoted;
import java.lang.reflect.code.descriptor.MethodDesc;
import java.lang.reflect.code.op.CoreOps;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.function.UnaryOperator;
import java.util.regex.Pattern;
import java.util.function.Consumer;
public class Wither<R extends Record> {
public static void main(String[] args) {
MySimpleRecord simple = new MySimpleRecord("Hello");
MyComplexRecord mcr = new MyComplexRecord(100, simple, "This is Babylon!", simple);
System.out.println(mcr);
// Output: MyComplexRecord[integer=100, whatever=MySimpleRecord[text=Hello], message=This is Babylon!, simple=MySimpleRecord[text=Hello]]
mcr = Wither.record(mcr)
.with(MyComplexRecord::whatever, List.of(1, 2, 3))
.withNested(MyComplexRecord::simple, w -> w.with(MySimpleRecord::text, "Hello, World!"))
.with(MyComplexRecord::integer, i -> -i)
.build();
System.out.println(mcr);
// Output: MyComplexRecord[integer=-100, whatever=[1, 2, 3], message=This is Babylon!, simple=MySimpleRecord[text=Hello, World!]]
}
public record MySimpleRecord(String text) {}
public record MyComplexRecord(int integer, Object whatever, String message, MySimpleRecord simple) {}
@FunctionalInterface
interface Accessor<R, C> extends Function<R, C>, Quotable {}
public static <R extends Record> Wither<R> record(R record) {
return new Wither<>(record);
}
private final R record;
private final Map<String, Object> changes = new HashMap<>();
private Wither(R record) {
this.record = record;
}
public <T> Wither<R> with(Accessor<? super R, ? extends T> accessor, T newValue) {
changes.put(accessorName(accessor), newValue);
return this;
}
public <T> Wither<R> with(Accessor<? super R, ? extends T> accessor, UnaryOperator<T> newValueOperator) {
changes.put(accessorName(accessor), newValueOperator.apply(accessor.apply(record)));
return this;
}
public <T extends Record> Wither<R> withNested(Accessor<? super R, ? extends T> accessor, Consumer<? super Wither<T>> nested) {
Wither<T> w = record(accessor.apply(record));
nested.accept(w);
changes.put(accessorName(accessor), w.build());
return this;
}
private String accessorName(Quotable accessor) {
Quoted q = accessor.quoted();
Op op = q.op();
if (!(op instanceof CoreOps.LambdaOp lambda)) {
throw new IllegalArgumentException("Not a method reference");
}
if (!(lambda.body().entryBlock().ops().get(2) instanceof CoreOps.InvokeOp invocation)) {
throw new IllegalArgumentException("Not a method reference: " + lambda.body().entryBlock().ops());
}
MethodDesc methodDesc = invocation.invokeDescriptor();
return methodDesc.name();
}
public R build() {
try {
RecordComponent[] recordComponents = record.getClass().getRecordComponents();
Class<?>[] asParameters = new Class<?>[recordComponents.length];
Object[] arguments = new Object[recordComponents.length];
for (int i = 0; i < recordComponents.length; i++) {
RecordComponent component = recordComponents[i];
asParameters[i] = component.getType();
arguments[i] = changes.containsKey(component.getName()) ? changes.get(component.getName()) : component.getAccessor().invoke(record);
}
return (R) record.getClass().getDeclaredConstructor(asParameters).newInstance(arguments);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment