0

I use ByteBuddy to implement a wrapper class to provide existing domain objects with indexes.

public interface Indexed {
    Long getIndex();
}

Here are my domain classes.

public interface Alpha {
    String getName();
    Long getRating();
}

public static class AlphaImpl implements Alpha {
    private final String name;
    private final Long rating;

    public AlphaImpl(String name, Long rating) {
        this.name = name;
        this.rating = rating;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public Long getRating() {
        return rating;
    }
}

When I generate class with only default constructor and pass both domain object and index via setters, everything works fine.

@Test
public void test1 () throws Exception {
    Class<?> klass = new ByteBuddy()
            .subclass(Alpha.class)
            .implement(Indexed.class)

            .defineProperty("origin", Alpha.class)

            .method(ElementMatchers.isOverriddenFrom(Alpha.class))
            .intercept(MethodCall.invokeSelf().onField("origin"))

            .defineProperty("index", Long.class)

            .make()
            .load(AlphaImpl.class.getClassLoader())
            .getLoaded();

    Alpha origin = new AlphaImpl("Wrapped object", 10L);
    Alpha indexed = (Alpha) klass.getConstructor().newInstance();

    klass.getDeclaredMethod("setOrigin", Alpha.class).invoke(indexed, origin);
    klass.getDeclaredMethod("setIndex", Long.class).invoke(indexed, 13L);

    assertEquals("Wrapped object", indexed.getName());
    assertEquals(10L, (long)indexed.getRating());
    assertEquals(13L, (long)((Indexed)indexed).getIndex());
}

However when I make the properties read-only and define the constructor to set them up, I've got an exception.

@Test
public void test2 () throws Exception {
    Class<?> klass = new ByteBuddy()
            .subclass(Alpha.class, ConstructorStrategy.Default.NO_CONSTRUCTORS
            )
            .implement(Indexed.class)

            .defineConstructor(Visibility.PUBLIC)
            .withParameters(Alpha.class, Long.class)
            .intercept(
                    FieldAccessor.ofField("origin").setsArgumentAt(0)
                    .andThen(FieldAccessor.ofField("index").setsArgumentAt(1)))

            .defineProperty("origin", Alpha.class, true)

            .method(ElementMatchers.isOverriddenFrom(Alpha.class))
            .intercept(MethodCall.invokeSelf().onField("origin"))

            .defineProperty("index", Long.class, true)

            .make()
            .load(AlphaImpl.class.getClassLoader())
            .getLoaded();

    Alpha origin = new AlphaImpl("Wrapped object", 10L);
    Alpha indexed = (Alpha) klass.getConstructor(Alpha.class, Long.class).newInstance(origin, 13L);

    assertEquals("Wrapped object", indexed.getName());
    assertEquals(10L, (long)indexed.getRating());
    assertEquals(13L, (long)((Indexed)indexed).getIndex());
}

The result stacktrace is following.

Constructor must call super() or this() before return
Exception Details:
  Location:
    IndexedWrapperTest$Alpha$ByteBuddy$V9ew7nId.<init>(LIndexedWrapperTest$Alpha;Ljava/lang/Long;)V @10: return
  Reason:
    Error exists in the bytecode
  Bytecode:
    0000000: 2a2b b500 102a 2cb5 001b b1            

java.lang.VerifyError: Constructor must call super() or this() before return
Exception Details:
  Location:
    IndexedWrapperTest$Alpha$ByteBuddy$V9ew7nId.<init>(LIndexedWrapperTest$Alpha;Ljava/lang/Long;)V @10: return
  Reason:
    Error exists in the bytecode
  Bytecode:
    0000000: 2a2b b500 102a 2cb5 001b b1            

    at java.base/java.lang.Class.getDeclaredConstructors0(Native Method)
    at java.base/java.lang.Class.privateGetDeclaredConstructors(Class.java:3138)
    at java.base/java.lang.Class.getConstructor0(Class.java:3343)
    at java.base/java.lang.Class.getConstructor(Class.java:2152)
    at IndexedWrapperTest.test2(IndexedWrapperTest.java:95)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.runTestClass(JUnitTestClassExecutor.java:106)
    at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:58)
    at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:38)
    at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestClassProcessor.processTestClass(AbstractJUnitTestClassProcessor.java:66)
    at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:51)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
    at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:32)
    at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:93)
    at com.sun.proxy.$Proxy2.processTestClass(Unknown Source)
    at org.gradle.api.internal.tasks.testing.worker.TestWorker.processTestClass(TestWorker.java:117)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
    at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:155)
    at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:137)
    at org.gradle.internal.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:404)
    at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:63)
    at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:46)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:55)
    at java.base/java.lang.Thread.run(Thread.java:834)

How can I work around that issue?

And how can I check the result class to be a simple composition with minimal overhead?

NoDataFound
  • 11,381
  • 33
  • 59
  • You have to invoke a super constructor in any constructor as you describe in your answer. – Rafael Winterhalter Apr 21 '19 at 17:37
  • Thank you for reply, I also ought to write here about some pitfalls I've faced. First of all, intercept() method has to receive MethodCall.invokeSelf().onField("origin").withAllArguments() as a parameter. Without .withAllArguments() method call it fails if any of weaved methods receives arguments. And it's better to use ElementMatchers.anyOf(Alpha.class.getMethods()), because if Alpha extends some interfaces, ElementMatchers.isOverriddenFrom(Alpha.class) doesn't include methods of the super interface to the overriden methods list. And ElementMatchers.anyOf(Alpha.class.getMethods()) does. – Anton Kudruk Apr 21 '19 at 21:35

1 Answers1

0

I have found a solution myself.

I had to invoke Objects default constructor explicitly, like in the following listing. After introducing MethodCall.invoke(Object.class.getConstructor()) line everything works fine.

@Test
public void test2 () throws Exception {
    Class<?> klass = new ByteBuddy()
            .subclass(Alpha.class, ConstructorStrategy.Default.NO_CONSTRUCTORS)
            .implement(Indexed.class)

            .defineConstructor(Visibility.PUBLIC)
            .withParameters(Alpha.class, Long.class)
            .intercept(

                    // Invoke Objects default constructor explicitly
                    MethodCall.invoke(Object.class.getConstructor())

                    .andThen(FieldAccessor.ofField("origin").setsArgumentAt(0))
                    .andThen(FieldAccessor.ofField("index").setsArgumentAt(1)))

            .defineProperty("origin", Alpha.class, true)

            .method(ElementMatchers.anyOf(Alpha.class.getMethods()))
            .intercept(MethodCall.invokeSelf().onField("origin").withAllArguments())

            .defineProperty("index", Long.class, true)

            .make()
            .load(AlphaImpl.class.getClassLoader())
            .getLoaded();

    Alpha origin = new AlphaImpl("Wrapped object", 10L);
    Alpha indexed = (Alpha) klass.getConstructor(Alpha.class, Long.class).newInstance(origin, 13L);

    assertEquals("Wrapped object", indexed.getName());
    assertEquals(10L, (long)indexed.getRating());
    assertEquals(13L, (long)((Indexed)indexed).getIndex());
}