0

I am trying to find a way to get all the instances of type X by ReflectionUtil.

A extends of X: 
   List<X> xList;
   X arg1;
   int size;

   A(List<X> xList, X arg1, int size){
      this.xList = xList;
      this.arg1= arg1;
      this.size= size;
   }

B extends of X:
   String argB;
   String arg2

C extends of X:
   X argC;

initialization:

    List<X> list = new ArrayList();
    B item1 = new B("item1", "1");
    B item2 = new B("item2", "2");
    C c1 = new C(item1);
    C c2 = new C(item2);
    list.add(c1);
    list.add(c2);
    B b = new B("test", "3");
    A myAClass = new A(list, b, 5);

I can access to myAClass object and I want to get all the instances of type X from myAClass along the class hierarchy.

In above example after the initialization myAClass looks like: enter image description here

The result that I expected for is: [item1 , item2 , c1, c2, b] - all the instances of type X.

Maria Dorohin
  • 355
  • 4
  • 17
  • can you explain a bit more? you are asking about getting all fields but then in expected result you are talking about `B - an item of xList`, I don't quite understand what you want to achieve. Maybe try writing some actual example code so we can just copy and test stuff with it too. – GotoFinal Sep 02 '19 at 14:33
  • I am trying to get all the fields classes also the nested classes. – Maria Dorohin Sep 02 '19 at 15:03
  • Sorry but I still can't understand that, can you maybe provide example test case (with all example classes, data, and results)? That would help a lot with understand what you are looking for. – GotoFinal Sep 02 '19 at 15:25
  • ok, now I understand what you want to do, but I'm not sure what you mean by "ReflectionUtil". But otherwise the issue is quite complicated, can you maybe also explain why you need this? what exactly you are doing that you need to create such weird scan. Also are they any safe assumptions to do? like classes are only simple data objects and might contains lists & maps, as otherwise there is just a lot of edge cases to think about - and that's why it looks like some bad design and probably should be solved in different way. – GotoFinal Sep 02 '19 at 16:35
  • My purpose is to Autowire beans and Value properties after json desirialization by deserialize json and then call postProcess method to autowire beans: applicationContext.getAutowireCapableBeanFactory().autowireBeanProperties(clzz, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true). Because of that I need to find all the classes that can contain autowire of value and these classes must be of type X. Here I explained my real problem: https://stackoverflow.com/questions/57753753/is-there-a-way-to-call-method-after-contractor-after-deserializion . – Maria Dorohin Sep 03 '19 at 07:26

2 Answers2

1

You "just" need to use recursive search, but there is a lot of things to worry about when scanning like that, as you don't know how deep and complicated this structure might be.
There are also decision to make about scanning java types like lists and maps, in future reflective access to internals of such classes might be restricted, so it is good idea to skip reflections when possible - and if field is of list/set/iterable type then just iterate over it using normal java code. But then you either need to check that given list is of simple well-known type like ArrayList or remember that your scanning code might have some side-effects, like if you would iterate over some resource that reads from some data stream.
Additional thing to remember about are cyclic references, as when scanning you might end up in one of objects you were already in, and introduce endless loop. To prevent this we can just add already scanned objects to Set or to IdentitySet - depending if you care about actual cyclic references or you also want to skip scanning same objects that are different instances.
And another thing - it might be good idea to implement some kind of filter, to stop scanning if you will end up in some java internal objects, like while scanning some kind of concurrent collection using reflections you might end up scanning some internal objects used inside implementation of it.

And after explaining few bigger issues I would want you to think if you really want such solution, maybe it is xyproblem and there is better solution that what you are actually trying too achieve.

So you will need to adjust this code to your needs and assumptions you can make, but you can use this as an example:

private static <T> void scanInstance(Object objectToScan, Class<T> lookingFor, Set<? super Object> scanned, Collection<? super T> results) {
    if (objectToScan == null) {
        return;
    }
    if (! scanned.add(objectToScan)) { // to prevent any endless scan loops
        return;
    }
    // you might need some extra code if you want to correctly support scanning for primitive types
    if (lookingFor.isInstance(objectToScan)) {
        results.add(lookingFor.cast(objectToScan));
        // either return or continue to scan of target object might contains references to other objects of this type
    }
    // we won't find anything intresting in Strings, and it is pretty popular type
    if (objectToScan instanceof String) {
        return;
    }
    // basic support for popular java types to prevent scanning too much of java internals in most common cases, but might cause 
    // side-effects in some cases
    else if (objectToScan instanceof Iterable) {
        ((Iterable<?>) objectToScan).forEach(obj -> scanInstance(obj, lookingFor, scanned, results));
    }
    else if (objectToScan instanceof Map) {
        ((Map<?, ?>) objectToScan).forEach((key, value) -> {
            scanInstance(key, lookingFor, scanned, results);
            scanInstance(value, lookingFor, scanned, results);
        });
    }
    // remember about arrays, if you want to support primitive types remember to use Array class instead.
    else if (objectToScan instanceof Object[]) {
        int length = Array.getLength(objectToScan);
        for (int i = 0; i < length; i++) {
            scanInstance(Array.get(objectToScan, i), lookingFor, scanned, results);
        }
    }
    else if (objectToScan.getClass().isArray()) {
        return; // primitive array
    }
    else {
        Class<?> currentClass = objectToScan.getClass();
        while (currentClass != Object.class) {
            for (Field declaredField : currentClass.getDeclaredFields()) {
                // skip static fields
                if (Modifier.isStatic(declaredField.getModifiers())) {
                    continue;
                }
                // skip primitives, to prevent wrapping of "int" to "Integer" and then trying to scan its "value" field and loop endlessly.
                if (declaredField.getType().isPrimitive()) {
                    return;
                }
                if (! declaredField.trySetAccessible()) {
                    // either throw error, skip, or use more black magic like Unsafe class to make field accessible anyways.
                    continue; // I will just skip it, it's probably some internal one.
                }
                try {
                    scanInstance(declaredField.get(objectToScan), lookingFor, scanned, results);
                }
                catch (IllegalAccessException ignored) {
                    continue;
                }
            }
            currentClass = currentClass.getSuperclass();
        }
    }
}

It would be good idea to split this to few methods, but this is just proof of concept, you still should adjust this to your needs, but it works in your example case:

    List<X> instances = new ArrayList<>(); // either use list or set or identity set, depending if you want to see duplicates of data/instances
    scanInstance(myAClass, X.class, Collections.newSetFromMap(new IdentityHashMap<>()), instances);

    System.out.println(instances);

And it will find 6 instances of X class. (including myAClass itself)

GotoFinal
  • 3,585
  • 2
  • 18
  • 33
  • thanks for so detailed answer. My real purpose is to Autowire beans and Value properties after json desirialization by deserialize json and then call postProcess method to autowire beans: applicationContext.getAutowireCapableBeanFactory().autowireBeanProperties(clzz, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true). Because of that I need to find all the classes that can contain autowire of value and these classes must be of type X. Here I explained my real problem: https://stackoverflow.com/questions/57753753/is-there-a-way-to-call-method-after-contractor-after-deserializion . – Maria Dorohin Sep 03 '19 at 07:25
  • you need to find classes or instances of some classes, aka classes or objects? – GotoFinal Sep 03 '19 at 07:30
  • but if this answer does what you asked for in this question, then please accept it – GotoFinal Sep 03 '19 at 07:42
0

You tagged your question with [spring] and in your comments you reiterate that you're working with beans. With Spring, if you want to get a collection of all beans extending a base type you can just do this:

@Autowired
private List<? extends X> someCollection;

Spring will populate the collection with all matching beans that have a base class X.

Andy Brown
  • 11,766
  • 2
  • 42
  • 61