Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a reflection util class to extract generic type and annotations for a class #10962

Merged
merged 4 commits into from
Jul 15, 2024

Conversation

dstepanov
Copy link
Contributor

Originaly created in the validation module to pass some of the reflection TCK. Moving to Core to allow using it for some edge cases of Jakarta APIs.

@dstepanov dstepanov added the type: improvement A minor improvement to an existing feature label Jul 9, 2024
@dstepanov dstepanov requested review from graemerocher and yawkat July 9, 2024 11:24
* @since 4.6.0
*/
@Internal
public final class AnnotationReflectionUtils {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this be combined with the current ReflectionUtils class? Seems unnecessary to create a new one

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That class doesn't access annotation metadata

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps put it into ClassUtils in that case

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same problem as ReflectionUtils, core doesn't see mutable annotation metadata

* @since 4.6
*/
@Nullable
public static Argument<?> findImplementationAsArgument(@NonNull Class<?> implementationType,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we rename this resolveArgumentFromGenerics the name here is confusing imo

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it understandable that it will return Argument.of(implementedType, ...) not just a generic type?

for (Annotation annotation : annotations) {
Map<CharSequence, Object> values = new LinkedHashMap<>();
Class<? extends Annotation> annotationType = annotation.annotationType();
Method[] methods = annotationType.getMethods();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should add reflection logging for Graal

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

log the methods accessed reflectively with ClassUtils.REFLECTION_LOGGER

dstepanov added 2 commits July 9, 2024 14:40
CR
CR
Map<String, AnnotatedType> resolvedVariables = new LinkedHashMap<>();
for (int i = 0; i < actualTypeArguments.length; i++) {
AnnotatedType actualTypeArgument = actualTypeArguments[i];
if (actualTypeArgument.getType() instanceof TypeVariable<?>) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is very common, e.g. ArrayList<T> implements List<T>

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think this method needs the previous resolved arguments to resolve these type vars

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what do you mean

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when you have interface A<T>, interface B<T> extends A<T>, class C extends B<String>, this code will fail to propagate the String all the way to A

throw new IllegalArgumentException("Unknown type: " + type);
}

private static Map<String, AnnotatedType> resolvedVariables(AnnotatedType annotatedType, Class<?> type) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do you need to pass type here? it should always be the raw type of annotatedType, no?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How else would you get type variable names?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i would just resolve the type by getting the raw type of annotatedType. it's a bit more clear imo. but if you want to keep it for performance, maybe make it clear that one is the raw type of the other

return getClassFromType(parameterizedType.getRawType());
}
if (type instanceof GenericArrayType) {
return Object[].class;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be based on getGenericComponentType

if (resolvedVariable == null) {
resolvedVariable = annotatedActualTypeArguments[i];
TypeVariable<? extends Class<?>> typeParameter = typeParameters[i];
variableBaseType = getClassFromType(typeParameter.getBounds()[0]);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getClassFromType does not support TypeVariable but typeParameter.getBounds()[0] can be a TypeVariable

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't really support 100% cases, if something else we can try to fix it

AnnotatedType[] annotatedInterfaces = implementationType.getAnnotatedInterfaces();
for (int i = 0; i < annotatedInterfaces.length; i++) {
AnnotatedType annotatedInterface = annotatedInterfaces[i];
Class<?> classFromType = getClassFromType(annotatedInterface.getType());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isnt this just interfaces[i]

}
Class<?>[] interfaces = implementationType.getInterfaces();
AnnotatedType[] annotatedInterfaces = implementationType.getAnnotatedInterfaces();
for (int i = 0; i < annotatedInterfaces.length; i++) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure if it's worth it, but maybe you can merge this loop with the superclass type?


@Nullable
private static Argument<?> findResolvedImplementation(Class<?> implementationType,
Class<?> implementedType, // Interface or an abstract class
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

implementedType is always the raw type of annotatedType

private static Argument<?> argumentOf(@NonNull AnnotatedType type,
@NonNull Class<?> interfaceType,
@NonNull Map<String, AnnotatedType> resolvedVariables) {
if (type.getType() instanceof TypeVariable<?> typeVariable) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this happen? i think type is always a concrete type?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It only resolve the generic type of the implemented class not of the superclasses / interfaces. If you are loocking for that we need to geet it from the resolvd variables.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what I mean is that I believe that argumentOf is always called with one of the types that is directly declared inside an extends or implements expression, so it can never be a type variable (you cant class A<T> extends T)

@dstepanov
Copy link
Contributor Author

@yawkat Would you like to refactor it? I don't really understand much of the reflection

@yawkat
Copy link
Member

yawkat commented Jul 10, 2024

i can try tomorrow, meetings all day today

@yawkat yawkat requested a review from graemerocher July 12, 2024 08:45
@yawkat
Copy link
Member

yawkat commented Jul 12, 2024

@dstepanov @graemerocher i've pushed a different implementation. I don't think it's much better, but it is a bit more complete

Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
66.9% Coverage on New Code (required ≥ 70%)

See analysis details on SonarCloud

@dstepanov
Copy link
Contributor Author

Looks fine

@dstepanov dstepanov requested a review from yawkat July 15, 2024 13:41
@yawkat yawkat merged commit ed2bb0b into 4.6.x Jul 15, 2024
16 of 17 checks passed
@yawkat yawkat deleted the refu branch July 15, 2024 14:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: improvement A minor improvement to an existing feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants