2

If a public function of a class updates some java swing GUI elements, I have to check in which context this function is called and to be sure the updates are done in the EDT. This is how I solved it, but as there are maybe much functions which I have to do the same (but call a different private method of the class) or other classes where I have to do the same, I'm tired of writing always the same code

public void updateGUI() 
{
    if(!SwingUtilities.isEventDispatchThread())
    {
        SwingUtilities.invokeLater(new Runnable() 
        {
            public void run() 
            {
                update();
            }
        });
    }
    else
    {
        update();
    }
}

private void update()
{
    //update the swing elements here
}

One possible solution may be to create an interface (named UpdateInterface) which has this update method. The class will now implement this interface and I create a helper class with a static method which takes a reference of this interface and call the update method in the EDT context:

public static void callUpdateInEDT(UpdateInterface f) 
{
    if(!SwingUtilities.isEventDispatchThread())
    {
        SwingUtilities.invokeLater(new Runnable() 
        {
            public void run() 
            {
                f.update();
            }
        });
    }
    else
    {
        f.update();
    }
}

But this is not very flexible as I may have some other private methods which I should call in the EDT context. Are there any solutions with the SwingUtilities which I didn't find so far or any other solutions which are more flexible as the one I proposed?

Semaphor
  • 900
  • 2
  • 12
  • 33
  • If I am understanding you correctly, wouldn't it be simpler to have a class which has the `callUpdateInEdt` and then extend that class? Assuming that you would then mark your `private` methods as `protected` you should still be able to access whatever it is you need. – npinti Oct 12 '15 at 07:48
  • Well, a lot should come to your design and agreed contracts. That is, you methods/classes which update the UI should clearly state that they are either thread safe or not, if they are not then the responsibility should fall to the caller to make sure that they execute correctly on the EDT. The problem you're actually facing becomes so much more interesting when you also need to pass parameters, one solution I came up with involved using reflection – MadProgrammer Oct 12 '15 at 07:49
  • @npinti Interesting idea, expect when your class needs to extend from something else ;) – MadProgrammer Oct 12 '15 at 07:50
  • @MadProgrammer: Yeah, what I was thinking was that if the OP had a class which extended `JFrame` for instance, you could have the class I propose extend `JFrame` and that *might* work around the issue, but I do agree that some clever designing might be required. – npinti Oct 12 '15 at 07:54
  • @npinti There's not a single good clean solution that I've ever found to this problem :P – MadProgrammer Oct 12 '15 at 08:07
  • 1
    @MadProgrammer: If it makes you feel better, I doubt anyone has :P. – npinti Oct 12 '15 at 08:21
  • @npinti Thanks for the hint, could in some cases be a easy solution. – Semaphor Oct 13 '15 at 13:02

3 Answers3

2

So after many years of writing more wrapper code then I care to think about and before the days of inner/anonymous classes, I wrote a reflection based API which would basically simplify the process of calling any method with any number of parameters

So, your code would become something more like...

SwingSafeMethodInvoker.invokeLater(this, "update").execute();

This is just ONE possible solution (and I'm lazy, so I like to reduce the amount of code I have to (re)write)

The code is smart enough to know if you're on the EDT or not and take appropriate action. The brilliant thing about this is it allowed me to call protected methods (remember, before inner/anonymous classes, you would have had to create a stand along, external Runnable class, so it wouldn't have had the ability to call protected methods)

The API is built on top of additional refection utilities, so it's a little involved.

WARNING This API makes use of reflection, reflection is known for been less then efficient, so you need to know when not to use it. It also "breaks" the access restrictions that protected and private apply to classes, so you can do some VERY interesting things, most of which I would discourage you from trying/doing.

This will also not pick up any refactoring you might do to your code, so if you rename a method, this won't see it, be careful.

Remember: This is part of bigger reflection API I wrote which allows me to execute methods on classes, so it's deliberately broken down over several classes

SwingSafeMethodInvoker

public class SwingSafeMethodInvoker {

    public static InvokeLater invokeLater(Object obj, String methodName) {

        return new InvokeLater(obj, methodName);

    }

    public static InvokeLater invokeLater(Class clazz, String methodName) {

        return new InvokeLater(clazz, methodName);

    }

    public static InvokeAndWait invokeAndWait(Object obj, String methodName) {

        return new InvokeAndWait(obj, methodName);

    }

    public static InvokeAndWait invokeAndWait(Class clazz, String methodName) {

        return new InvokeAndWait(clazz, methodName);

    }

    public static InvokeAfter invokeAfter(Object obj, String methodName) {

        return new InvokeAfter(obj, methodName);

    }

    public static InvokeAfter invokeAfter(Class clazz, String methodName) {

        return new InvokeAfter(clazz, methodName);

    }

    public static InvokeAfterAndWait invokeAfterAndWait(Object obj, String methodName) {

        return new InvokeAfterAndWait(obj, methodName);

    }

    public static InvokeAfterAndWait invokeAfterAndWait(Class clazz, String methodName) {

        return new InvokeAfterAndWait(clazz, methodName);

    }

    public static MethodInvoker invoke(Object obj, String methodName) {

        return new MethodInvoker(obj, methodName);

    }

    public static MethodInvoker invoke(Class clazz, String methodName) {

        return new MethodInvoker(clazz, methodName);

    }

}

InvokeLater

import java.awt.EventQueue;
import javax.swing.SwingUtilities;

/**
 * This will make a synchronised call into the EventQueue.
 * 
 * There is no way to determine when the actually call will be made.  If you
 * need to wait for the result of the call, you are better  of using 
 * InvokeAndWait instead
 * 
 * If the invoke method is called within the ETD, it will be executed
 * immediately.
 * 
 * If you want the call to occur later (ie have it placed at the end
 * of the EventQueue, use InvokeAfter)
 * 
 * The invoke method will always return null.
 * @author shane
 */
public class InvokeLater extends AbstractSwingMethodInvoker {

    public InvokeLater(Object parent, Class parentClass, String methodName) {

        super(parent, parentClass, methodName);

    }

    public InvokeLater(Object parent, String methodName) {

        this(parent, parent.getClass(), methodName);

    }

    public InvokeLater(Class clazz, String methodName) {

        this(null, clazz, methodName);

    }

    @Override
    public Object execute() throws SynchronisedDispatcherException {

        if (EventQueue.isDispatchThread()) {

            run();

        } else {

            SwingUtilities.invokeLater(this);

        }

        return null;

    }

}

InvokeAndWait

import java.awt.EventQueue;
import java.lang.reflect.InvocationTargetException;
import javax.swing.SwingUtilities;

public class InvokeAndWait extends AbstractSwingMethodInvoker {

    public InvokeAndWait(Object parent, Class parentClass, String methodName) {

        super(parent, parentClass, methodName);

    }

    public InvokeAndWait(Object parent, String methodName) {

        this(parent, parent.getClass(), methodName);

    }

    public InvokeAndWait(Class clazz, String methodName) {

        this(null, clazz, methodName);

    }

    @Override
    public Object execute() {

        if (EventQueue.isDispatchThread()) {

            run();

        } else {

            try {

                SwingUtilities.invokeAndWait(this);

            } catch (InterruptedException ex) {

                throw new InvocationException("Failed to invokeAndWait", ex);

            } catch (InvocationTargetException ex) {

                throw new InvocationException("Failed to invokeAndWait", ex);

            }

        }

        return getResult();

    }

}

InvokeAfter

import javax.swing.SwingUtilities;

/**
 * This will place the method call onto the end of the event dispatching
 * queue and return immediately.
 * 
 * There is no means to known when the call actually takes and place and if
 * you are interested in the return result, you are better of using InvokeAndWait
 * @author shane
 */
public class InvokeAfter extends InvokeLater {

    public InvokeAfter(Object parent, Class parentClass, String methodName) {

        super(parent, parentClass, methodName);

    }

    public InvokeAfter(Object parent, String methodName) {

        this(parent, parent.getClass(), methodName);

    }

    public InvokeAfter(Class clazz, String methodName) {

        this(null, clazz, methodName);

    }

    @Override
    public Object execute() throws SynchronisedDispatcherException {

        SwingUtilities.invokeLater(this);

        return null;

    }
}

AbstractSwingMethodInvoker

import core.util.MethodInvoker;

public abstract class AbstractSwingMethodInvoker extends MethodInvoker implements Runnable {

    private Object result;

    public AbstractSwingMethodInvoker(Object parent, Class parentClass, String methodName) {

        super(parent, parentClass, methodName);


    }

    public AbstractSwingMethodInvoker(Object parent, String methodName) {

        this(parent, parent.getClass(), methodName);

    }

    public AbstractSwingMethodInvoker(Class clazz, String methodName) {

        this(null, clazz, methodName);

    }

    @Override
    public void run() {

        result = super.execute();

    }

    public Object getResult() {

        return result;

    }

    public class SynchronisedDispatcherException extends Error {

        public SynchronisedDispatcherException(String message) {
            super(message);
        }

        public SynchronisedDispatcherException(String message, Throwable cause) {
            super(message, cause);
        }

    }

}

MethodInvoker

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

/**
 * Provides a means to invoke any method on any class/object using reflection
 *
 * @author Shane Whitegead
 */
public class MethodInvoker {

    private Object parent;
    private Class parentClass;
    private String methodName;
    private List<Parameter> lstParameters;

    public MethodInvoker() {

    }

    public MethodInvoker(Object parent, Class parentClass, String methodName) {

        this.parent = parent;
        this.parentClass = parentClass;
        this.methodName = methodName;

        lstParameters = new ArrayList<Parameter>(5);

    }

    public MethodInvoker(Object parent, String methodName) {

        this(parent, parent.getClass(), methodName);

    }

    public MethodInvoker(Class clazz, String methodName) {

        this(null, clazz, methodName);

    }

    public static MethodInvoker invoke(Object parent, String methodName) {

        return new MethodInvoker(parent, methodName);

    }

    public static MethodInvoker invoke(Class parent, String methodName) {

        return new MethodInvoker(parent, methodName);

    }

    public static MethodInvoker invoke(Object parent, Class clazz, String methodName) {

        return new MethodInvoker(parent, clazz, methodName);

    }

    public MethodInvoker setParent(Object parent) {

        this.parent = parent;
        if (parent != null) {

            setParentClass(parentClass.getClass());

        }

        return this;

    }

    public MethodInvoker setParentClass(Class parent) {

        this.parentClass = parent;

        return this;

    }

    public MethodInvoker setMethodName(String method) {

        this.methodName = method;

        return this;

    }

    public <T> MethodInvoker with(Class<T> type, T value) {

        with(new Parameter(value, type));

        return this;

    }

    public MethodInvoker with(Class[] types, Object[] parameters) {

        if (types == null || parameters == null) {
        } else if (types.length != parameters.length) {
        } else {

            for (int index = 0; index < types.length; index++) {

                with(types[index], parameters[index]);

            }

        }

        return this;

    }

    public MethodInvoker with(Parameter parameter) {

        lstParameters.add(parameter);

        return this;

    }

    public Object getParent() {
        return parent;
    }

    public Class getParentClass() {
        return parentClass;
    }

    public String getMethodName() {
        return methodName;
    }

    public Class[] getParameterTypes() {

        List<Class> lstTypes = new ArrayList<Class>(lstParameters.size());
        for (Parameter parameter : lstParameters) {

            lstTypes.add(parameter.getType());

        }

        return lstTypes.toArray(new Class[lstTypes.size()]);

    }

    public Object[] getParameterValues() {

        List<Object> lstTypes = new ArrayList<Object>(lstParameters.size());
        for (Parameter parameter : lstParameters) {

            lstTypes.add(parameter.getValue());

        }

        return lstTypes.toArray(new Object[lstTypes.size()]);

    }

    public Object execute() {

        Object result = null;

        Class type = getParentClass();
        String methodName = getMethodName();
        Class[] lstTypes = getParameterTypes();
        Object[] lstValues = getParameterValues();
        Object parent = getParent();

        try {


            Method method = findMethod(type, methodName, lstTypes);

            if (method == null) {
                throw new NoSuchMethodException(getMethodDescription(type, methodName, lstTypes));
            }

            method.setAccessible(true);

//          logger.info("Method = " + method);

            result = method.invoke(parent, lstValues);

        } catch (Exception ex) {

            StringBuilder sb = new StringBuilder(64);

            sb.append("parent = ").append(parent).append("\n");
            sb.append("type = ").append(type).append("\n");
            sb.append("methodName = ").append(methodName).append("\n");
            for (int index = 0; index < lstTypes.length; index++) {

                sb.append("[").append(index).append("] ").append(lstTypes[index].getName()).append(lstValues[index]).append("\n");

            }

            System.err.println("Called by\n" + sb.toString());

            throw new InvocationException("Failed to invoke " + methodName, ex);

        }

        return result;

    }

    public static Field findField(Class parent, String name) {

        Field field = null;

        try {

            field = parent.getDeclaredField(name);

        } catch (NoSuchFieldException noSuchFieldException) {

            try {

                field = parent.getField(name);

            } catch (NoSuchFieldException nsf) {

                if (parent.getSuperclass() != null) {

                    field = findField(parent.getSuperclass(), name);

                }

            }

        }

        if (field != null) {

            field.setAccessible(true);

        }

        return field;


    }

    /**
     * This method basically walks the class hierarchy looking for a matching
     * method.
     *
     * The issue is getXMethod only returns a list of the methods for the current
     * class and not the inherited methods. This method basically over comes that
     * limitation.
     *
     * If no method can be found matching the criteria, then this method returns a
     * null value.
     *
     * This makes this method not only very powerful, but also very dangerous, as
     * it has the power of finding ANY declared method within the hierarchy,
     * public, protected or private.
     *
     * @param parent
     * @param name
     * @param lstTypes
     * @return
     */
    public static Method findMethod(Class parent, String name, Class[] lstTypes) {

        Method method = null;

        try {

            method = parent.getDeclaredMethod(name, lstTypes);

        } catch (NoSuchMethodException noSuchMethodException) {

            try {

                method = parent.getMethod(name, lstTypes);

            } catch (NoSuchMethodException nsm) {

                if (parent.getSuperclass() != null) {

                    method = findMethod(parent.getSuperclass(), name, lstTypes);

                }

            }

        }

        return method;

    }

    /**
     * Builds up a description of the method call in the format of
     * [package.class.method([{package.class}{, package.class}{...}])
     *
     * This is typically used to throw a NoMethodFound exception.
     *
     * @param clazz
     * @param name
     * @param lstTypes
     * @return
     */
    public static String getMethodDescription(Class clazz, String name, Class[] lstTypes) {

        StringBuilder sb = new StringBuilder();
        sb.append(clazz.getName()).append(".").append(name).append("(");

        for (Class type : lstTypes) {

            sb.append(type.getName()).append(", ");

        }

        if (lstTypes.length > 0) {
            sb.delete(sb.length() - 2, sb.length());
        }

        sb.append(")");

        return sb.toString();

    }

    public class Parameter<T> {

        private T value;
        private Class<T> clazz;

        public Parameter(T value, Class<T> clazz) {
            this.value = value;
            this.clazz = clazz;
        }

        public T getValue() {
            return value;
        }

        public Class<T> getType() {
            return clazz;
        }
    }

    public class InvocationException extends Error {

        public InvocationException(String message) {
            super(message);
        }

        public InvocationException(String message, Throwable cause) {
            super(message, cause);
        }
    }

    public static boolean hasMethod(Object instance, String methodName) {

        return hasMethod(instance.getClass(), methodName);

    }

    public static boolean hasMethod(Object instance, String methodName, Class[] types) {

        return hasMethod(instance.getClass(), methodName, types);

    }

    public static boolean hasMethod(Class clazz, String methodName) {

        return hasMethod(clazz, methodName, new Class[0]);

    }

    public static boolean hasMethod(Class clazz, String methodName, Class[] types) {

        return findMethod(clazz, methodName, types) != null;

    }
}
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • 1
    I've used the same approach, until I got a bug after renaming of a public method using the eclipse "refactoring". But now we have lambdas and method references in Java8. – Sergiy Medvynskyy Oct 12 '15 at 08:11
  • @SergiyMedvynskyy Yep, I run into ALL the time, but it still beats writing a bunch of boiler code :P. It's a weighing game – MadProgrammer Oct 12 '15 at 08:12
  • Thank you very much for the answer. Maybe you could change the second InvokeLater title to InvokeAndWait. – Semaphor Oct 13 '15 at 12:59
0

I have already solve such boilerplate code through Proxy.

Your UI management abstraction:

public interface UIManager {
    void normal();
    void internalCall();
    void throwException();
}

A tracing implementation:

public class UIManagerImpl implements UIManager {
    private void traceCall(String method) {
        new Exception(Thread.currentThread().getName() + " > " + method).printStackTrace(System.out);
    }
    @Override
    public void normal() {
        traceCall("normal");
    }
    @Override
    public void internalCall() {
        traceCall("internalCall");
        normal();
    }
    @Override
    public void throwException() {
        traceCall("throwException");
        throw new RuntimeException("doB");
    }
}

A simple EDT-aware proxier:

public class EdtHandler implements InvocationHandler {

    private Object target;
    public  EdtHandler(Object target) {
        this.target = target;
    }
    public static <T> T newProxy(Class<T> contract, T impl) {
        return (T) Proxy.newProxyInstance(impl.getClass().getClassLoader(), new Class<?>[] { contract }, new EdtHandler(impl));
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (SwingUtilities.isEventDispatchThread()) {
            dispatch(method, args);
        } else {
            SwingUtilities.invokeLater(() -> dispatch(method, args));
        }
        return null;
    }

    protected void dispatch(Method method, Object[] args) {
        try {
            method.invoke(target, args);
        } catch (IllegalAccessException | InvocationTargetException e) {
            onError(e);
        }
    }
    protected void onError(Throwable e) {
        throw new IllegalStateException(e);
    }
}

Now the main to check:

public static void main(String[] args) {
    UIManagerImpl impl = new UIManagerImpl();
    UIManager     edt  = EdtHandler.newProxy(UIManager.class, impl);
    Runnable      block = () -> { System.out.println("---- Start ---- "); edt.normal(); edt.internalCall(); edt.throwException(); System.out.println("---- Stop ---- "); };
    block.run();
    SwingUtilities.invokeLater(block);
}

Implementation notes:

  • It might be better to always call invokeLater whatever you're on EDT or not.
  • Handler always returns null and is more suitable for void methods.
  • You can easily implements a return aware proxy:
    1. Using blocking mechanism such as a SynchronousQueue
    2. (work with current implementation) Introducing callback parameter (ie Consumer)
LoganMzz
  • 1,597
  • 3
  • 18
  • 31
0

After some readings about Lambda's I arrived with the following solution. I create a simple class with static methods with the same name as the SwingUtilites for example the invokeLater:

  public static void invokeLater(Runnable doRun)
  {
    if(SwingUtilities.isEventDispatchThread())
    {
      doRun.run();      
    }
    else
    {
      SwingUtilities.invokeLater(() -> {        
        doRun.run();        
      });
    }
  }

Now using this function with Lambda is very easy assuming the new class is SwingUtilities2 (I'm still searching for a good name for the new class :-)):

SwingUtilities2.invokeLater(() -> {

  //here is the code which should run in the EDT

});
Semaphor
  • 900
  • 2
  • 12
  • 33