xvik / generics-resolver

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

Arrays[] are not dereferenced automatically #25

Closed mgood7123 closed 1 year ago

mgood7123 commented 1 year ago
public List<List<String>>[] b;
new TypeContext(GenericFields.class).findField("b").getReturnType().printInfo();
    static class TypeContextField {
        TypeContext parent;
        Field field;

        TypeContextField(TypeContext parent, Field field) {
            this.parent = parent;
            this.field = field;
        }

        TypeContext getReturnType() {
            return new TypeContext(parent.context.fieldType(field));
        }
    }
    static class TypeContext {

        private Class<?> root;
        private GenericsContext context;

        public TypeContext(Class<?> root) {
            this(GenericsResolver.resolve(root));
        }

        TypeContext(GenericsContext context) {
            this.context = context;
            root = (Class<?>) context.getGenericsSource();
        }

        public TypeContextField findField(String fieldName) {
            try {
                return new TypeContextField(this, getFieldRecursive(fieldName));
            } catch (NoSuchFieldException e) {
                throw new RuntimeException(e);
            }
        }

// ...

        public void printInfo() {
            printInfo(context, root);
            println();
            println();
            println();
            println();
        }

        static void printInfo(GenericsContext root, Class<?> sc) {
            while (sc != null) {
                println(sc.toString());
                if (sc == Object.class) {
                    break;
                }
                root = root.type(sc);
                printContext(root);
                for (Class<?> anInterface : sc.getInterfaces()) {
                    printInfo(root, anInterface);
                }
                sc = sc.getSuperclass();
            }
        }

        private static void printContext(GenericsContext resolve) {
            println("root resolve: " + resolve);
            println("root resolve is inlying: " + resolve.isInlying());
            println("root resolve root context: " + resolve.rootContext());
            println("root resolve owner class: " + resolve.ownerClass());
            println("root resolve owner generics map: " + resolve.ownerGenericsMap());
            println("root resolve visible generics map: " + resolve.visibleGenericsMap());
            println("root resolve generics: " + resolve.generics());
            println("root resolve generics info: " + resolve.getGenericsInfo());
            println("root resolve generics scope: " + resolve.getGenericsScope());
            println("root resolve generics source: " + resolve.getGenericsSource());
            println("---- GENERICS INFO");
            printGenericsInfo(resolve.getGenericsInfo());
            println("----");
        }

        private static void printGenericsInfo(GenericsInfo genericsInfo) {
            println("generics info root class: " + genericsInfo.getRootClass());
            println("generics info composing types: " + genericsInfo.getComposingTypes());
            println("generics info types map: " + genericsInfo.getTypesMap());
        }

output

class [Ljava.util.List;
root resolve: class List[]  resolved in context of GenericFields    <-- current
  implements Cloneable
  implements Serializable

root resolve is inlying: true
root resolve root context: class GenericFields    <-- current

root resolve owner class: null
root resolve owner generics map: {}
root resolve visible generics map: {}
root resolve generics: []
root resolve generics info: class List[]
  implements Cloneable
  implements Serializable

root resolve generics scope: CLASS
root resolve generics source: class [Ljava.util.List;
---- GENERICS INFO
generics info root class: class [Ljava.util.List;
generics info composing types: [interface java.io.Serializable, class [Ljava.util.List;, interface java.lang.Cloneable]
generics info types map: {interface java.io.Serializable={}, class [Ljava.util.List;={}, interface java.lang.Cloneable={}}
----

output of List<List<String[]>[]>

interface java.util.List
root resolve: interface List<List<String[]>[]>  resolved in context of GenericFields    <-- current
  extends Collection<List<String[]>[]>
    extends Iterable<List<String[]>[]>

root resolve is inlying: true
root resolve root context: class GenericFields    <-- current

root resolve owner class: null
root resolve owner generics map: {}
root resolve visible generics map: {E=List<String[]>[]}
root resolve generics: [class [Ljava.util.List;]
root resolve generics info: interface List<List<String[]>[]>
  extends Collection<List<String[]>[]>
    extends Iterable<List<String[]>[]>

root resolve generics scope: CLASS
root resolve generics source: interface java.util.List
---- GENERICS INFO
generics info root class: interface java.util.List
generics info composing types: [interface java.lang.Iterable, interface java.util.Collection, interface java.util.List]
generics info types map: {interface java.lang.Iterable={T=List<String[]>[]}, interface java.util.Collection={E=List<String[]>[]}, interface java.util.List={E=List<String[]>[]}}
----
mgood7123 commented 1 year ago

this seems to be an issue with 3.0.3, as master branch seems to have improvements tho i dont know if the master branch is stable

mgood7123 commented 1 year ago

unfortunately the master branch does not seem to fix this

also if arrays[] do under-go resolution to base type in a future commit then i would expect it to handle multi-dimensional resolution (eg a[][][][] -> a)

for example

        } else if (type instanceof GenericArrayType) {
            // we have a generic array, Type[]... or Type ...
            // eg Supplier<T>[][] // array of array of interface Supplier<T>
            Type type1 = type;
            while (type1 instanceof GenericArrayType) {
                this.typeRank++; // so we know how many dimensions our array has
                type1 = ((GenericArrayType) type1).getGenericComponentType();
            }
        } else if (type instanceof Class<?>) {
            Class<?> type1 = (Class<?>) type;
            if (type1.isArray()) {
                // we have a primitive array, primitive[]... or primitive ...
                // eg float[][][] // array of array of float
                // eg 4x4 float matrix (16 floats total, [4][4][4][4], or [16], or [8][8], or however you want to represent it)
                while (type1.isArray()) {
                    this.typeRank++; // so we know how many dimensions our array has
                    type1 = type1.getComponentType();
                }
xvik commented 1 year ago

I don't have time right now to read all your questions carefully, but I will do it this weekend or Monday morning.

Regarding master - it should be stable (and should be more precise in types comparison logic (TypesWalker) - many bugs were fixed).

Master version wasn't released because all changes were required by my unreleased projects (which I can't finish yet). As you could assure yourself, generics resolution is a hard topic - too many possible variations and that's why I want to finsh projects (in parts relative to generics) before releasing library to resolve all possible bugs and, more importantly, to prevent api changes after relese (real usage often forces api changes). I really hope to progress in this direction this year.

mgood7123 commented 1 year ago

thats alright

xvik commented 1 year ago

I have tried array field resolution from your example and I did not see any issues: resolution was correct for both master and 3.0.3. What was the issue?

also if arrays[] do under-go resolution to base type in a future commit then i would expect it to handle multi-dimensional resolution (eg a[][][][] -> a)

Probably there is some misunderstanding: the main goal of the library is not to replace all reflection, but to carefully collect generics information (basically, tracking generics through all class hierarchy) so you can easilly resolve variables on any hierarchy level.

I'll show it with an example:

class Root extends Base<String> {}

class Base<T> {
   private List<T> a;
}

With pure reflection field type resolution is problematic because of type variable, but with generics context you can get rid of variables:

Type field = GenericsResolver.resolve(Root.class).resolveFieldType(Base.class.getDeclaredField("a"));

And now you can do whatever you want using reflection (resolved type does not contain variables anymore).

There is a generic resolveType method in generics context used to resolve variables, but it must be used carefully cause context is very important here:

class Root extends Level1<String> {}
class Level1<T> extends Level2<List<T>> {}
class Level2<T> {}

Here you can see both Level1 and Level2 classes declare variable T, and to properly resolve any varaible correct context class must be selected (for fields and methods context could be selected automatucally - safier to use). E.g. to resolve type in cotext of Level2:

GenerocsResolver.resolve(Root.class)
  // change context
  .type(Level2.class)
  // resolution in correct context
  .resolveType(...)

Master version provide more utilities for working with types (and, especially, types templating), but still it will never be able to replace pure reflection (too many possible cases).

Going back to your array example, library api should not "unbox" arrays automatically - it just should return you correct type, and you could decide to unbox the array (it is easy to do). Some other project may need array unboxed for 2 levels only (just as example) - so library must remain generic simply providing all awailable types information. What library should do is making you shure there is no variables inside type (basically, what resolveType do)

Maybe I misunderstand you (quite possible), if so, please describe your expectation and we'll discuss it

mgood7123 commented 1 year ago

Alright