Javapoet superclass generic - java

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.

Related

Kotlin does not allow T::class.java as a paramterized class type given to a java method

I am about to make a service of mine generic. However I fail to do so when trying to pass a generic Kotlin type T to a Java method that expects a class. Using normal types I'd do it like MyClass::class.java. For the generic type I do T::class.java. This however seems not to be valid.
Cannot use 'T' as reified type parameter. Use a class instead.
Happening here return mongoTemplate.aggregate(resolvedDocument, T::class.java).mappedResults[0]
Service:
#Service
class DocumentAggregator<T: Output>(
#Autowired
private val mongoTemplate: MongoTemplate
) {
fun <S: DocumentEntity>aggregate(document: S): T? {
val resolvedDocument: TypedAggregation<DocumentEntity> = // logic
return mongoTemplate.aggregate(resolvedDocument, T::class.java).mappedResults[0]
}
}
You should try adding the reified keyword to the generic parameter, like this:
class DocumentAggregator<reified T: Output>
That ways the class will be present at runtime. Like when you added an additional Class<T> parameter, just with the nice Kotlin syntax sugar.
EDIT:
Regarding the comments the question would be if you need the generics on the class. What compiles (thanks to Willie for pointing out the mistake) would be:
class Output
class DocumentAggregator(
private val mongoTemplate: Any?
) {
inline fun <S, reified T: Output>aggregate(document: S): T? {
return null
}
}

Java extract generic type parameters with reflection from interface

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();}

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?
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 {
}

Generate parameter annotations in Byte Buddy

I would like to use ByteBuddy to generate simple interfaces like this:
public interface MyInterface {
void myMethod(#Deprecated String myDeprecatedParameter);
}
This is just an example, but the point is that the parameters of the methods need a number of custom annotations.
Does anyone have a simple example that would demonstrate how to achieve this in ByteBuddy?
You can create an interface with an annotated parameter like the following. First define the interface name and modifiers, then define the method with it's name, return type and modifiers and finally the parameters and annotations if have any.
Class<?> myInterface = new ByteBuddy()
.makeInterface()
.name("MyInterface")
.modifiers(Visibility.PUBLIC, TypeManifestation.ABSTRACT)
.defineMethod("myMethod", void.class, Visibility.PUBLIC)
.withParameter(String.class, "myDeprecatedParameter")
.annotateParameter(AnnotationDescription.Builder.ofType(Deprecated.class)
.build())
.withoutCode()
.make()
.load(this.getClass().getClassLoader())
.getLoaded();
You can call annotateParameter(...) many times if you need multiple annotations.
After the make() method you get the unloaded class, just load the class and use it.
Here are some prints with the reflection api of the interface class.
System.out.println(Modifier.toString(myInterface.getModifiers())); // public abstract interface
System.out.println(myInterface.getSimpleName()); // MyInterface
System.out.println(Arrays.toString(myInterface.getDeclaredMethods())); // [public abstract void MyInterface.myMethod(java.lang.String)]
Method method = myInterface.getDeclaredMethod("myMethod", String.class);
System.out.println(method.getName()); // myMethod
System.out.println(Arrays.toString(method.getParameters())); // [java.lang.String myDeprecatedParameter]
Parameter parameter = method.getParameters()[0];
System.out.println(parameter); // java.lang.String myDeprecatedParameter
System.out.println(parameter.getName()); // myDeprecatedParameter
System.out.println(Arrays.toString(parameter.getAnnotations())); // [#java.lang.Deprecated()]
Annotation annotation = parameter.getAnnotations()[0];
System.out.println(annotation); // #java.lang.Deprecated()

Annotation member which holds other annotations?

I want to create a custom annotation (using Java) which would accept other annotations as parameter, something like:
public #interface ExclusiveOr {
Annotation[] value();
}
But this causes compiler error "invalid type for annotation member".
Object[] also doesn't work.
Is there a way to do what I want?
The error is produced because you can't use interfaces as annotation values (change it to Comparable and you'll get the same error). From the JLS:
It is a compile-time error if the return type of a method declared in an annotation type is any type other than one of the following: one of the primitive types, String, Class and any invocation of Class, an enum type, an annotation type, or an array of one of the preceding types. It is also a compile-time error if any method declared in an annotation type has a signature that is override-equivalent to that of any public or protected method declared in class Object or in the interface annotation.Annotation.
I'm afraid I don't know of a good workaround, but now at least you know why you get the error.
Depending on the reason why you would want to specify other annotations there are multiple solutions:
An array of instances of a single annotation type
Probably not what you meant in your question, but if you want to specify multiple instances of a single annotation type it's certainly possible:
public #interface Test {
SomeAnnotation[] value();
}
An array of annotation types instead of instances
If you do not need to specify any parameters on the individual annotations you can just user their class objects instead of instances.
public #interface Test {
Class<? extends Annotation>[] value();
}
But an enum would of course also do the trick in most situations.
Use multiple arrays
If the set of possible annotation types you want to use is limited, you can create a separate parameter for each one.
public #interface Test {
SomeAnnotation[] somes() default { };
ThisAnnotation[] thiss() default { };
ThatAnnotation[] thats() default { };
}
Giving a default value to each member makes it possible to only specify arrays for the types you need.
You can do:
Class<? extends Annotation>[] value();
Not sure if that helps, but . . .
I myself hereby propose a workaround for the given problem:
Well, what I wanted to make possible was something like that:
#Contract({
#ExclusiveOr({
#IsType(IAtomicType.class),
#Or({
#IsType(IListType.class),
#IsType(ISetType.class)
})
})
})
Proposed workaround:
Define a class with parameter-less constructor (which will be called by your own annotation processor later) in following way:
final class MyContract extends Contract{
// parameter-less ctor will be handeled by annotation processor
public MyContract(){
super(
new ExclusiveOr(
new IsType(IAtomicType.class),
new Or(
new IsType(IListType.class),
new IsType(ISetType.class)
)
)
);
}
}
usage:
#Contract(MyContract.class)
class MyClass{
// ...
}
I just ran into this exact problem, but (inspired by #ivan_ivanovich_ivanoff) I have discovered a way to specify a bundle of any combination of Annotations as an annotation member: use a prototype / template class.
In this example I define a WhereOr (i.e. a "where clause" for my model annotation) which I need to contain arbitrary Spring meta-annotations (like #Qualifier meta-annotations).
The minor (?) defect in this is the forced dereferencing that separates the implementation of the where clause with the concrete type that it describes.
#Target({})
#Retention(RetentionPolicy.RUNTIME)
public #interface WhereOr {
Class<?>[] value() default {};
}
#Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface JsonModel {
Class<?> value();
WhereOr where() default #WhereOr;
}
public class Prototypes {
#Qualifier("myContext")
#PreAuthorize("hasRole('ROLE_ADMINISTRATOR')")
public static class ExampleAnd {
}
}
#JsonModel(
value = MusicLibrary.class,
where = #WhereOr(Prototypes.ExampleAnd.class)
)
public interface JsonMusicLibrary {
#JsonIgnore
int getMajorVersion();
// ...
}
I will programmatically extract the possible valid configurations from the "where clause" annotation. In this case I also use the prototypes class as a logical AND grouping and the array of classes as the logical OR.

Categories

Resources