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?
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 {
}
Related
I'm writing a custom Java annotation for processing CrudRepositories with Reflection in Java Spring. With the org.reflections.reflections library. I'm getting all interfaces annotated with my annotation as a class file like so:
Reflections reflections = new Reflections("basePackage");
Set<Class<?>> annotated = reflections.getTypesAnnotatedWith(MyAnnotation.class);
Only interfaces, which at some point extend JpaRepository are annotated with my #MyAnnotation at the class level.
My repository structure is as follows:
There are two cases,
first case:
public interface SomeRepo extends JpaRepository<SomeEntity, Long> {...}
the second case is composed out of a inheritance hierarchy:
public interface SuperClassRepo <T extends SomeRandomEntity> extends JpaRepository<T, String> {...}
public interface SubClassRepo extends SuperClassRepo<SubEntityOfSomeRandomEntity> {...}
My goal is now to extract the generic type parameters of the underlying JpaRepository.
I achieved to do that if the annotated class is a Java class, not an interface. How can I achieve the same for an interface? I guess I'm also having trouble because of the inheritance. I guess I have to get the "super class" until I reach the JpaRepository and then somewhat extract the generic type arguments.
Help is very much appreciated, thanks in advance
I found a solution by looking at the GenericsUtils#getParameterType as suggested:
private static Class<?> extractKeyFromRepository(Class<?> repository) {
ResolvableType currentType = ResolvableType.forType(repository);
ResolvableType resolvableType = currentType.getInterfaces()[0];
if (JpaRepository.class.equals(resolvableType.getRawClass())) {
ResolvableType[] generics = resolvableType.getGenerics();
ResolvableType generic = generics[1];
return generic.resolve();
} else {
return extractKeyFromRepository(resolvableType.toClass());
}
}
This works only because I ensure beforehand, that what I'm putting in this method is valid. I do it like so, also I pay attention that only one interface is extended, by extending more than one interface one has to figure out which of these interfaces is the JpaRepository:
if (!repository.isInterface()) {throw new IllegalArgumentException();}
if (!JpaRepository.class.isAssignableFrom(repository)) {throw new IllegalArgumentException();}
Imagine there is class
public class sample {
private String fieldName;
}
Which already compiled and the part of the claspath when the jam statsup.
I want add #notnull and #size(min=1,max=5) dynamically for fieldName(not class level)
Any suggestions plz
I'll try to answer the question as a ByteBuddy question, without the synopsis of validation.
Using ByteBuddy, you can change your classes at runtime. But in certain conditions, like you are not on Android (Dalvik's virtual machine is different than ordinary JVMs), and you shouldn't change an already loaded class.
class Subclass {
public void func() {
}
}
class FunctionalInterfaceImpl implements FunctionalInterface {
#Override
public Class<? extends Annotation> annotationType() {
return FunctionalInterface.class;
}
}
public class TryingByteBuddy {
public static void main(String[] args) {
new ByteBuddy()
.redefine(TypePool.Default.ofClassPath().describe("com.experiments.Subclass").resolve(),
ClassFileLocator.ForClassLoader.ofClassPath())
.annotateType(new FunctionalInterfaceImpl())
.make()
.load(ClassLoader.getSystemClassLoader());
System.out.println(Arrays.deepToString(new Subclass().getClass().getAnnotations()));
}
}
In this code Subclass is a class that doesn't have the #FunctionalInterface annotation. Now when you run you will find it has it.
NOTES ON THIS CODE:
You will find that we didn't use Subclass.class when trying to redefine the class
This is to prevent loading of the class before creating the alternative
Using Subclass.class will invoke the class loader to load the original class, this prevents ByteBuddy from doing its work
We "implemented" the annotation interface we want to add (FunctionalInterface in this example).
For more info:
ByteBuddy tutorial
I've got around 5 objects that I want to do similar things with.
I figured out that not to polute the code I will put a logic for those objects in one place.
public class MetaObjectController<T extends MetaObject> {
#Autowired
private final MetaObjectRepository<T> repository;
// generic logic
Here's how repository looks:
public interface MetaObjectRepository<T extends MetaObject> extends GraphRepository<T> {
T findByName(String name);
}
Now, I create concrete class which uses delegation:
public class ExperimentalController {
#Autowired
private final MetaObjectController<MetaCategory> metaController;
#RequestMapping(method = RequestMethod.POST)
public void add(#RequestBody MetaCategory toAdd) {
metaController.add(toAdd);
}
Now, when I look at the generated queries I see, that although instantiated correctly, repository puts MetaObject as an entity name instead of runtime type.
Is there a way to force the repository to use runtime type?
Please don't advise to put a #Query annnotation. That's not what I am looking for.
This is most probably due to type erasure: at runtime there is only the type constraint available which is MetaObject. If you want to use (via spring-data) the actually relevant subclass you will have to create explicit interfaces of the MetaObjectRepository like this:
public class Transmogrifier extends MetaObject
public interface MetaTransmogrifierRepository
extends MetaObjectRepository<Transmogrifier> {}
Anyone know how I can do the following using javapoet
public class MyClassGenerated extends MyMapper<OtherClass>{
}
My code of generation:
TypeSpec generateClass() {
return classBuilder("MyClassGenerated")
.addModifiers(PUBLIC)
.superclass(???????????????)
.build();
}
The ParameterizedTypeName class allows you to specify generic type arguments when declaring the super class. For instance, if your MyClassGenerated class is a subclass of the MyMapper class, you can set a generic type parameter of MyMapper like so:
TypeSpec classSpec = classBuilder("MyClassGenerated")
.addModifiers(PUBLIC)
.superclass(ParameterizedTypeName.get(ClassName.get(MyMapper.class),
ClassName.get(OtherClass.class)))
.build();
This will generate a TypeSpec object that is equivalent to the following class:
public class MyClassGenerated extends MyMapper<OtherClass> { }
While not specified in the question, note that you can set any number of generic type arguments by simply adding them in the correct order to the ParameterizedTypeName.get call:
ParameterizedTypeName.get(
ClassName.get(SuperClass.class),
ClassName.get(TypeArgumentA.class),
ClassName.get(TypeArgumentB.class),
ClassName.get(TypeArgumentC.class)
); // equivalent to SuperClass<TypeArgumentA, TypeArgumentB, TypeArgumentC>
For more information about the ParameterizedTypeName.get() method, see the documentation here or the "$T for Types" section of the JavaPoet GitHub page.
Ok, I know there are a bunch of similar questions, but nothing seems to work.
I have the following structure set up for my entities.
public abstract class MyAbstractClass {
// bunch of properties, getters, and setters that subclasses share
public abstract String getType();
}
public class MySubclass1 extends MyAbstractClass {
// a few unique properties, getters, and setters
public String getType() {
return "Type_1"; //always the same for each instance of MySubclass1
}
}
public class MySubclass2 extends MyAbstractClass {
// a few unique properties, getters, and setters
public String getType() {
return "Type_2"; //always the same for each instance of MySubclass2
}
}
In my controller, I try to map a request to the following method.
public #RequestBody MyAbstractClass saveObject(#RequestBody MyAbstractClass mac) {
// call model to save object
}
I would like to use 1 controller method versus separate ones for the 2 entities. But using the above results in the following.
com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of path.to.my.entity.MyAbstractClass, problem: abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information
Makes sense.
TRY 1
#JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="implementingClass")
public abstract class MyAbstractClass
What I think it does - adds a metadata implementingClass property that will store the subclass class.
What the result is.
Unexpected token (END_OBJECT), expected FIELD_NAME: missing property 'implementingClass' that is to contain type id (for class path.to.my.entity.MyAbstractClass)
Tried with "class" instead of "implementingClass" for the property and got similar results.
TRY 2
#JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.WRAPPER_OBJECT)
#JsonSubTypes({
#Type(name="MySubclass1", value=MySubclass1.class),
#Type(name="MySubclass2", value=MySubclass2.class)
})
public abstract class MyAbstractClass
What I think it does - uses the defined name to do some sort of wrapping thing.
What the result is.
Could not resolve type id 'myUuid' into a subtype of [simple type, class path.to.my.entity.MyAbstractClass]
Same results even when adding #JsonTypeName("MySubclass1") and #JsonTypeName("MySubclass2") to the 2 subclasses.
Other Tries
I tried a lot. Nothing works. Won't include everything here.
I feel like there should be a simple way to do this, but I just keep on configuring things incorrectly.
I feel like the getType could maybe be leveraged, but I don't want to add an actual property for type (it's just a helper method). Also I would like to do this with annotations versus other options.
Thank you.
I figured it out but I guess I'll answer in case anyone else has this problem.
I added a type property to my subclasses instead of just a helper method (one example included below).
public class MySubclass1 extends MyAbstractClass {
#Transient
private final String type = "TYPE_1";
public String getType() {
return type;
}
}
Then I did the following for my abstract superclass.
#JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property="type")
#JsonSubTypes({
#Type(name="TYPE_1", value=MySubclass1.class),
#Type(name="TYPE_2", value=MySubclass2.class)
})
public abstract class MyAbstractClass
When providing the JSON, I was sure to include the type. I won't include this because it's weird knockout insanity.
It's not great. But it worked.