square / javapoet

A Java API for generating .java source files.
Apache License 2.0
10.83k stars 1.38k forks source link

Construct transparent objects #968

Open robertvazan opened 1 year ago

robertvazan commented 1 year ago

It would be neat to have CodeBlock API that recursively emits code to construct transparent values (records, arrays, collections, enums, primitives, strings, and null, perhaps others).

For now, I have a utility method that does this for the types I care about:

public class JavaPoetEx {
    public static CodeBlock construct(Object object) {
        if (object == null)
            return CodeBlock.of("null");
        if (object instanceof Integer || object instanceof Long || object instanceof Double || object instanceof Float)
            return CodeBlock.of("$L", object);
        if (object instanceof Enum<?> en)
            return CodeBlock.of("$T.$N", en.getClass(), en.name());
        if (object instanceof Record record) {
            return CodeBlock.of("new $T($L)", record.getClass(), StreamEx.of(record.getClass().getDeclaredFields())
                .filter(f -> !Modifier.isStatic(f.getModifiers()))
                .map(Exceptions.sneak().function(f -> record.getClass().getMethod(f.getName()).invoke(record)))
                .map(JavaPoetEx::construct)
                .collect(CodeBlock.joining(", ")));
        }
        throw new IllegalArgumentException();
    }
}

This could be implemented as a new parameter type (perhaps $V for value) that generalizes $L and $S. It would sort of serialize values as Java code the same way JSON serializes values as JavaScript code.

l3002 commented 9 months ago

Hey @robertvazan, Would like to contribute to this issue? Could you please assign this issue to me?

And just to confirm you are looking for an API which would call a generalized method and outputs the certain result based on the type parameter, right?

Christopher-Chianelli commented 8 months ago

FWIW, I implemented something extremely similar to this for in-lining static final fields of a complex Object: https://github.com/Christopher-Chianelli/timefold-solver/blob/38c98a8b6311ac1ecc7fafccc35db748f7080db8/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/util/PojoInliner.java

In theory, it could be modified to work with any CodeBuilder as a new formatting option, something like "$O" for object or "$P" for POJO. What I needed the feature for was in-lining a calculated complex object (i.e. many different fields and nested classes). The main issue for integrating are:

  1. It expects the CodeBlock.Builder to be valid (i.e. cannot use it in an incomplete fragement).
  2. It needs fresh variable names to store the objects.
Christopher-Chianelli commented 8 months ago

Managed to make it work with fragments: https://github.com/square/javapoet/pull/999 It still need fresh variable names to store objects; it currently uses a $$javapoet$ as a default prefix, and allows it to be changed using useNamePrefix.