简体   繁体   中英

Running a function in Java Swing EDT if necessary

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?

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;

    }
}

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 )

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

});

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM