xvik / generics-resolver

Java generics runtime resolver
https://xvik.github.io/generics-resolver
MIT License
46 stars 9 forks source link

Cannot get GenericsContext from generic Object by position. #7

Closed mikolajmitura closed 4 years ago

mikolajmitura commented 4 years ago

Hi,

public class SomeBean {
    private List<AnotherBean<List<String>>> listInSomeBean;
    //                              ^
    //                              |
    //I want to extract raw class from here                           
}

public class AnotherBean<T> {
    private T someObject;
}

for example please consider that paths:

Target is to validate that some certain paths are correct for certain class (here SomeBean class) But this validation will needs to be done without runtime data.

I tried with code like below:

import ru.vyarus.java.generics.resolver.GenericsResolver;
import ru.vyarus.java.generics.resolver.context.GenericsContext;
import ru.vyarus.java.generics.resolver.context.container.ParameterizedTypeImpl;

import java.lang.reflect.Field;
import java.lang.reflect.Type;

import static org.apache.commons.lang3.reflect.FieldUtils.getField;

public class AnotherApproach {
    public static void main(String[] args) {
        GenericsContext context = GenericsResolver.resolve(SomeBean.class);
        Field listField = getField(SomeBean.class, "listInSomeBean", true);
        GenericsContext genericsContextOfList = context.fieldType(listField);

        ParameterizedTypeImpl anotherBeanGenericMeta = (ParameterizedTypeImpl) genericsContextOfList.genericType(0); // extract 'AnotherBean<List<String>>' part
        Field fieldInAnotherBean = getField(genericsContextOfList.generic(0), "genericFieldInAnotherBean", true);

        Type actualTypeArgument = ((ParameterizedTypeImpl) genericsContextOfList.genericType(0)).getActualTypeArguments()[0];
        // I don't know how to get field 'genericFieldInAnotherBean' from 'anotherBeanGenericMeta' and traverse deeper
    }
}

My proposition is to add method GenericsContext .genericContext(int position)

 import ru.vyarus.java.generics.resolver.GenericsResolver;
import ru.vyarus.java.generics.resolver.context.GenericsContext;

import java.lang.reflect.Field;
import java.lang.reflect.Type;

import static org.apache.commons.lang3.reflect.FieldUtils.getField;

public class TestResolver {
    public static void main(String[] args) {
        GenericsContext context = GenericsResolver.resolve(SomeBean.class);
        Field listField = getField(SomeBean.class, "listInSomeBean", true);
        GenericsContext genericsContextOfList = context.fieldType(listField);

        Class<?> rawTypeOfListInSomeBean = genericsContextOfList.generic(0); // this one return only raw class
        Type typeTypeOfListInSomeBean = genericsContextOfList.genericType(0); // this one return only type
        GenericsContext genericsContextOfListInSomeBean = genericsContextOfList.genericContext(0); // <- THIS ONE WILL BE HELPFUL, will return GenericsContext for 'AnotherBean<List<String>>'
        Field fieldInAnotherBean = getField(genericsContextOfList.generic(0), "genericFieldInAnotherBean", true);
        // so then I will have opportunity to do like below
        GenericsContext someObjectFieldContext = genericsContextOfListInSomeBean.fieldType(fieldInAnotherBean); // this will be for <List<String>
        Class<?> stringClass = someObjectFieldContext.generic(0); // I can get raw class then and traverse and check that path is correct or not...
    }
}       

I don't know that others faced with that kind of problem. Maybe you have a some solution for that? Maybe I don't see something.

Thanks in advance :-)

xvik commented 4 years ago

Hi! Thank you for the detailed explanation!

Your initial attempt was almost correct: generics-resolver job is to correctly substitute variables in types and let you use usual reflection api to investigate types as you want.

So in your example, resolver must be used to resolve field type List<AnotherBean<List<String>>> and, after that, you can do everything without it.

There are several utility methods that may help you (not actually tied to context scope):

Complete example (it's groovy - minor simplifications are not mistakes):

GenericsContext context = GenericsResolver
                // SomeBean
                .resolve(SomeBean)
                // List<AnotherBean<List<String>>>
                .fieldType(SomeBean.getDeclaredField("listInSomeBean"))

        // AnotherBean<List<String>>
        Type type = context.genericType(0)
        // List<String>
        type = context.resolveTypeGenerics(type)[0]
        // String
        Class res = context.resolveGenericOf(type)

Now about

GenericsContext genericsContextOfListInSomeBean = genericsContextOfList.genericContext(0); // <- THIS ONE WILL BE HELPFUL, will return GenericsContext for 'AnotherBean<List<String>>'

This is already possible with inlying types: context.inlyingType(genericsContextOfList.genericType(0)). But in your case, this is useless as generics context is ONLY required to properly track generic variables. When you already have exact type you can investigate it directly (as shown above).

If your type contains variables, you can use context to replace variables with known generic values: context.resolveType(type) after that you'll have pure type to work with (resolver will actually repackage type).

Ask me if something is not clear: api is not simple, I know (don't have good ideas on how to simplify it).

mikolajmitura commented 4 years ago

Great!, The 'inlyingType' is what I looked for, I don't know why I didn't check that method... Generally generics are not so simple, I'm wondering that I had problem with find framework like that. This framework is doing a lot of good job :-)

        GenericsContext context = GenericsResolver.resolve(SomeBean.class);
        Field listField = getField(SomeBean.class, "listInSomeBean", true);
        GenericsContext genericsContextOfList = context.fieldType(listField);
        GenericsContext genericsContext = genericsContextOfList.inlyingType(genericsContextOfList.genericType(0));
        Field someObjectField = getField(genericsContextOfList.generic(0), "someObject", true);
        GenericsContext someObjectFieldGenericContext = genericsContext.fieldType(someObjectField);
        System.out.println(someObjectFieldGenericContext.generic(0));

As I mentioned earlier that validation for paths is my target here:

From Type I will have a problem to get generic context for field 'someObject' from AnotherBean.

mikolajmitura commented 4 years ago

btw I will try make pull request and you will let me know that is acceptable :-)

xvik commented 4 years ago

Summary: have to deny the request, but will add these use-cases in documentation as api usage examples.

@mikolajmitura thank you again for your efforts! It wasn't for nothing!