vendredi 26 février 2016

Elegant solution for proxy mess

In Java 8, I wanted to write a class that, given one or more listeners, would return a proxied List (using implementation of List of my choosing by passing in its class) that triggers the listener whenever something is added or removed. The code (bear with me) is below:

public final class EventedList {
    private EventedList() {
    }

    protected static class ListInvocationHandler<T> implements InvocationHandler {
        private List<T> theList;
        private ListChangeHandler<T>[] listeners;

        public ListInvocationHandler(Class<? extends List<T>> listClass, ListChangeHandler<T>[] listeners) throws InstantiationException, IllegalAccessException {
            this.listeners = listeners;
            theList = listClass.newInstance();
        }

        public Object invoke(Object self, Method method, Object[] args)
                throws Throwable {
            Object ret = method.invoke(theList, args);
            switch(method.getName()) {
            case "add": 
                trigger((T)args[0], true);
                break;
            case "remove":
                if(args[0] instanceof Integer) {
                    trigger((T)ret, false);
                } else {
                    trigger((T)args[0], false);
                }
                break;
            }
            return ret;
        }

        public void trigger(T obj, boolean added) {
            Arrays.stream(listeners).forEachOrdered(l -> l.onChange(obj, added));
        }
    }

    public static <T, U extends List<T>> List<T> newList(Class<U> listClass, ListChangeHandler<T> ... listeners) throws IllegalArgumentException, InstantiationException, IllegalAccessException {
        @SuppressWarnings("unchecked")
        List<T> obj = (List<T>)Proxy.newProxyInstance(listClass.getClassLoader(), new Class<?>[]{List.class}, new ListInvocationHandler<T>(listClass, listeners));
        return obj;
    }

    public static <T, U extends List<T>> List<T> newListSafe(Class<U> listClass, ListChangeHandler<T> ... listeners) {
        List<T> obj = null;
        try {
            obj = newList(listClass, listeners);
        } catch (IllegalArgumentException | InstantiationException
                | IllegalAccessException e) {
        }
        return obj;
    }   
}

It works, but it is certainly not without its issues.

  1. I initially only had one type T, but I was getting errors using Class<? extends List<T>> so I use U to represent ? extends List<T> instead.
  2. In invoke method of ListInvocationHandler, I'm having to forcively cast Object to T. I think that is unavoidable, but I welcome any alternatives.
  3. newProxyInstance is returning Object that I'm having to cast to List. Also this I believe is unavoidable but I welcome any alternatives.
  4. I'm receiving a "Potential heap pollution via varargs parameter listeners" warning on listeners parameters presumably because they are variable parameter arguments, however I don't see the obvious risk of doing it this way.

The main that I'm using is the following:

public static void main(String[] args) {
    List<String> list = EventedList.newListSafe(ArrayList.class, new ListChangeHandler<String>() {

        @Override
        public void onChange(String value, boolean added) {
            System.out.println(value + ", " + (added ? "added" : "removed"));
        }

    });

    list.add("Badger");                                  // Badger, added
    list.add("Badger");                                  // Badger, added
    list.add("Badger");                                  // Badger, added
    list.add("Badger");                                  // Badger, added
    list.remove("Badger");                               // Badger, removed
    list.add("Mushroom");                                // Mushroom, added
    list.remove("Mushroom");                             // Mushroom, removed

    // [Badger, Badger, Badger]
    System.out.println(Arrays.toString(list.toArray()));  
}

  1. The main itself calling the method has a nice type safety warning, even if the parameters should be implicit.

I apologize for the wall of text. I appreciate any input.





Aucun commentaire:

Enregistrer un commentaire