Sander_M
Sander_M

Reputation: 1119

Byte Buddy: java.lang.AbstractMethodError: error when invoking method

EDIT

The actual interface and sample domain in the original question were flawed. Hereby the correct example.

import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy;
import static net.bytebuddy.matcher.ElementMatchers.isGetter;
import static net.bytebuddy.matcher.ElementMatchers.isSetter;

import net.bytebuddy.ByteBuddy;
import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.implementation.FieldAccessor;

public class Example {

    public interface Domain {
        int getIdentifier();
    }

    public class Person implements Domain {

        private int personId;
        private String firstName;
        private String lastName;

        @Override
        public int getIdentifier() {
            return personId;
        }

        public int getPersonId() {
            return personId;
        }

        public void setPersonId(final int personId) {
            this.personId = personId;
        }

        // further  getters and setters ommitted

    }

    public static void main(final String[] args) throws InstantiationException, IllegalAccessException {
        final Domain domain = new ByteBuddy()
                .subclass(Domain.class)
                .name("Person")
                .method(isGetter().or(isSetter())).intercept(FieldAccessor.ofBeanProperty())
                .defineField("personId", int.class, Visibility.PRIVATE)           
                .defineField("firstName", String.class, Visibility.PRIVATE)           
                .defineField("lastName", String.class, Visibility.PRIVATE)           
                .method(isDeclaredBy(Domain.class))
                  .intercept(FieldAccessor.ofField("personId"))
                .make()
                .load(Example.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
                .getLoaded()
                .newInstance();

        System.out.println(domain.getIdentifier());     

    }
}

Does the Byte Buddy code generate the Person class as set out in the above code? (And how can I check this?)

PRE-EDIT

I am trying to, at runtime, generate some classes that implement a domain interface using the Byte Buddy API ( http://bytebuddy.net/#/ )

The application gets some input from an external source and I want to turn that input (like name of the class, name of some variables) into POJOs that implement an interface. In the sample code below the input is represented by the domain names string array.

After succesful code generation I want to use those at runtime generated classes to map files on (e.g. parse lines and map them to the generated POJO).

The following code (clazz.setIdentifier(1);) throws Exception in thread "main" java.lang.AbstractMethodError: Person.setIdentifier(I)V.

public class Sample {

    public interface Domain {
        void setIdentifier(int i);
        int getIdentifier();
    }

    public static void main(String[] args) throws InstantiationException, IllegalAccessException {
        String[] domainNames = {"Person", "City", "Country"};   
        for (String domainName : domainNames) {
            Class<?> domain = new ByteBuddy()
                    .subclass(Domain.class)
                    .name(domainName)
                    .method(ElementMatchers.named("getIdentifier"))
                    .intercept(FixedValue.value(1))                 
                    .make()
                    .load(Sample.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
                    .getLoaded();

            Domain clazz = (Domain) domain.newInstance();
            clazz.setIdentifier(1);
            System.out.println(clazz.getIdentifier());
        }
    }   
}

Right now I am using Roaster to generate the following class:

public class Adres implements Domain {

    @Parsed(index = 0)
    private int id;
    @Parsed(index = 1)
    private String plaatsNaam;
    @Parsed(index = 2)
    private String straatNaam;
    @Parsed(index = 3)
    private String huisnummer;
    @Parsed(index = 4)
    private String postcode;

    @Override
    public int getIdentifier() {
        return id;
    }
}

but the changing nature of the input from the external source makes runtime code generation more desirable than source code generation. So ideally I want to create above code at runtime and use that code afterwards (obviously).

The fields and methods section of http://bytebuddy.net/#/tutorial do mention the use of ElementMatchers and Interceptors, but quite frankly I am at a loss on how to make them work.

Question: How do I create a POJO that implements an interface using Byte Buddy and then call methods on the generated code?

Upvotes: 1

Views: 999

Answers (1)

Rafael Winterhalter
Rafael Winterhalter

Reputation: 44007

You are never implementing the setIdentifier method. Therefore, the method remains abstract and the AbstractMethodError is thrown. While this is forbidden by javac, the VM allows you to load such a type and Byte Buddy therefore also permits it. In your case, this is of course not the desired outcome. What you probably want to do something like the following using the FieldAccessor implementation:

Domain domain = new ByteBuddy()
  .subclass(Domain.class)
  .method(isDeclaredBy(Domain.class))
  .intercept(FieldAccessor.ofField("id").defineAs(int.class, Visibility.PRIVATE))
  .make()
  .load(Sample.class.getClassLoader())
  .getLoaded()
  .newInstance();

  domain.setIdentifier(42);
  System.out.println(domain.getIdentifier());

In your above example for Adres, the setter implementation is also missing; I assume you forgot about it but please ask for clarification if this is not the case.

Edit: Your updated example still suggests this approach but you need to define setters and getters manually. I plan to add a convenience API for this at some point, but you should be able to do this in a loop for a list of properties. You can validate by using the reflection API on the generated class or by looking at the disassembly of the generated class files using a decompiler for Java.

Upvotes: 1

Related Questions