Hydra
Hydra

Reputation: 11

How to copy all methods from one class to another using Gizmo in Quarkus?

I currently have an interface。that looks like this

public interface JRepository<E, ID> {

    // sqlClient
    JSqlClient sql();

    // crud methods
    E findNullable(ID id);

    List<E> findByIds(Iterable<ID> ids);
}

This interface has an implementation class that looks like this

public class JRepositoryImpl<E, ID> implements JRepository<E, ID> {
    @Override
    public JSqlClient sql() {
        return just return sqlClient;
    }

    @Override
    public E findNullable(ID id) {
        // Some logic
        return sql().findById(id);
    }

    @Override
    public List<E> findByIds(Iterable<ID> ids) {
        // Some logic
        return sql().findByIds(ids);
    }
}

Users can choose to extend the interface to get CRUD methods that looks like this

public interface TestRepository extends JRepository<Book, Long> {
}

Now I want to implement the interface for the user-defined TestRepository through the default implementation class JRepositoryImpl.

I encountered some issues with Quarkus’s Gizmo. It seems that Gizmo is unable to copy the method bodies from the methods.

It seems that when I use methods like ClassCreator and MethodCreator, there are no APIs available to copy the method bodies.

If I want to achieve my goal, do I have to use the lower-level ClassVisitor and MethodVisitor, or is there a better way? Are there any similar codes that I can reference?

I’m currently encountering some issues while generating the code using bytecode.

@ApplicationScoped
@Unremovable
public class TestRepository_edb259d1f267785b946cb0bcfd7d69d6e7653180Impl implements TestRepository {
    private final TestInterface defaultImpl;

    public TestRepository_edb259d1f267785b946cb0bcfd7d69d6e7653180Impl() {
    }

    public TestRepository_edb259d1f267785b946cb0bcfd7d69d6e7653180Impl(JSqlClient var1) {
        ClassLoader var2 = Thread.currentThread().getContextClassLoader();
        Class var3 = Class.forName("com.Book", false, var2);
        TestInterfaceImpl var4 = new TestInterfaceImpl(var1, var3);
        this.defaultImpl = (TestInterface)var4;
    }

    public Object findNullable(Object var1) {
        return this.defaultImpl.findNullable(var1);
    }

    public Class entityType() {
        return this.defaultImpl.entityType();
    }

    public JSqlClient sql() {
        return this.defaultImpl.sql();
    }
}

When I inject it, Quarkus throws the following error: Caused by: java.lang.NullPointerException: Cannot invoke "TestInterface.findNullable(Object)" because "this.defaultImpl" is null.

public class TestInterfaceImpl<E, ID> implements TestInterface<E, ID> {

    protected final JSqlClientImplementor sqlClient;

    protected final Class<E> entityType;

    protected TestInterfaceImpl(JSqlClient sqlClient) {
        this(sqlClient, null);
    }

    public TestInterfaceImpl(JSqlClient sqlClient, Class<E> entityType) {
        this.sqlClient = Utils.validateSqlClient(sqlClient);
        this.entityType = entityType;
    }

    @Override
    public JSqlClient sql() {
        return sqlClient;
    }

    @Override
    public Class<E> entityType() {
        return entityType;
    }

    @Override
    public E findNullable(ID id) {
        return sqlClient.getEntities().findById(entityType, id);
    }
}

Upvotes: 0

Views: 83

Answers (1)

Ladicek
Ladicek

Reputation: 6577

If you know that a default implementation exists and can obtain an instance in the constructor (glossing over how exactly you do that), you don't have to copy the method bodies -- you can just forward to them. That should be fairly simple. If you have a Jandex ClassInfo for the interface (iface), you can do just:


for (MethodInfo method : iface.methods()) {
    MethodCreator m = clazz.getMethodCreator(method);
    ResultHandle delegate = m.readInstanceField(m.getThis(), ...);
    ResultHandle [] args = new ResultHandle[method.parametersCount()];
    for (int i = 0; i < method.parametersCount(); i++) {
        args[i] = m.getParam(i);
    }
    m.returnValue(m.invokeInterface(method, delegate, args));
}

Note that I'm writing this from memory on my phone, so there might be typos, but the structure should be obvious.

Upvotes: 1

Related Questions