Search code examples
javacode-generationjavapoet

Implementing generated interface with JavaPoet


I would like to use JavaPoet to generate an interface and a class implementing this interface.

TypeSpec if = TypeSpec.interfaceBuilder("MyInterface")
                      .build();

TypeSpec cl = TypeSpec.classBuilder("MyClass")
                      .build();

But I am strugelling to tell JavaPoet that MyClass should implement MyInterface. The method addSuperinterface(TypeName) requires a type name and I didn't findout how to turn a TypeSpec into an TypeName. The only way I found is calling ClassName#get(String, String).

Is there an better way to achive this and to use the type specification for the interface directly?


Solution

  • It is not as complicated as it may seem. The TypeSpec.Builder has two versions of the addSuperInterface method:

    TypeSpec.Builder    addSuperinterface(Type superinterface) 
    TypeSpec.Builder    addSuperinterface(TypeName superinterface)
    

    We could use the second version for example and obtain the super interface as an instance of the TypeName class using ClassName.get

    One of the signatures of the get method of the ClassName class is:

    public static ClassName get(String packageName, String simpleName, String... simpleNames)
    

    So we could use it with empty string for the package name since you did not specify any package name in your interface spec. It will work because ClassName extends TypeName. On the other hand we can obtain the interface's simple name using the type spec's name property.

    Here a complete sample implementation. I modified the name of the variables (the variable name if that you used for the interface spec will not work as it is a java keyword).

    @Data
    public class SimpleClassSpecs {
    
        public final TypeSpec interfaceSpec;
        public final TypeSpec classSpec;
    
        public SimpleClassSpecs() {
            interfaceSpec = TypeSpec.interfaceBuilder("MyInterface")
                    .build();
    
            TypeName interfaceTypeName = ClassName.get("", interfaceSpec.name);
            classSpec = TypeSpec.classBuilder("MyClass")
                    .addSuperinterface(interfaceTypeName)
                    .build();
        }
    }
    

    I used Lombok's @Data for the boilerplate code (getters and setters...) Here is a corresponding test (assertion written with assertj):

    @Test
    public void should_generate_spec_with_superInterface() {
       SimpleClassSpecs ps = new SimpleClassSpecs();
       assertThat(ps.classSpec.toString()).contains("class MyClass implements MyInterface");
    }
    

    Or by simply doing doing a System.out.println(ps.classSpec), one can obtain the following result:

    class MyClass implements MyInterface {
    }