it.cnr.icar.eric.client.ui.swing.SwingWorker.java Source code

Java tutorial

Introduction

Here is the source code for it.cnr.icar.eric.client.ui.swing.SwingWorker.java

Source

/*
 * ====================================================================
 * This file is part of the ebXML Registry by Icar Cnr v3.2 
 * ("eRICv32" in the following disclaimer).
 *
 * "eRICv32" is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * "eRICv32" is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License Version 3
 * along with "eRICv32".  If not, see <http://www.gnu.org/licenses/>.
 *
 * eRICv32 is a forked, derivative work, based on:
 *    - freebXML Registry, a royalty-free, open source implementation of the ebXML Registry standard,
 *      which was published under the "freebxml License, Version 1.1";
 *   - ebXML OMAR v3.2 Edition, published under the GNU GPL v3 by S. Krushe & P. Arwanitis.
 * 
 * All derivative software changes and additions are made under
 *
 * Copyright (C) 2013 Ing. Antonio Messina <messina@pa.icar.cnr.it>
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the freebxml Software Foundation.  For more
 * information on the freebxml Software Foundation, please see
 * "http://www.freebxml.org/".
 *
 * This product includes software developed by the Apache Software
 * Foundation (http://www.apache.org/).
 *
 * ====================================================================
 */
package it.cnr.icar.eric.client.ui.swing;

import java.awt.Component;
import java.util.HashMap;
import java.util.Map;
//import javax.swing.JComponent;
import javax.swing.SwingUtilities;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * This is a variant of 3rd version of SwingWorker (also known as
 * SwingWorker 3), an abstract class that you subclass to
 * perform GUI-related work in a dedicated thread.  For
 * instructions on and examples of using this class, see:
 * 
 * http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html
 * http://www.javaworld.com/javaworld/jw-06-2003/jw-0606-swingworker.html
 *
 * This variant of works in conjunction with the GlassPane class.
 * While SwingWorker allows users to execute time-consuming task on a separate
 * thread, keeping the Swing Thread responsive and free for repaints, the
 * GlassPane addition will intercept mouse/keyboard events to the top level
 * component and prevent users from triggering more events. Also, it will set
 * cursors to "WAIT" mode and back to normal.
 *
 * Modifications for freebXML Registry include:
 * - finished() always executed after construct, with invokeLater
 * - RegistryBrowser ContextClassLoader set to worker
 * = Shared Object 'value' re-added, with doNonUILogic constructing and returning it
 * - if a GlassPane is activated n times (by different SwingWorkers for components
 *   with the same topLevelAncestor, for instance), it has to be deactivated n 
 *   times to be really deactivated (i.e, GlassPane set to invisible).
 *
 * Usage pattern:
 *
 *  ...
 *  public void actionPerformed(ActionEvent e) {
 *      ...
 *      final SwingWorker worker = new SwingWorker(this) {
 *          public Object doNonUILogic() {
 *              //...code that might take a while to execute is here...
 *              return something;
 *          }
 *          public void doUIUpdateLogic() {
 *              //...code that touches the GUI goes here - execute by Swing Thread
 *              // if needed, get object returned from doNonUILogic()
 *              //Object something = get();
 *          }
 *      };
 *      worker.start();
 *      // if needed, wait for doNonUILogic to complete/get object returned
 *      //Object something = get();
 *      ...
 *  }
 *
 * @author Yexin Chen
 * @author Diego Ballve / Digital Artefacts
 */
public abstract class SwingWorker {
    private static final Log log = LogFactory.getLog(SwingWorker.class);

    /**
     * Class to maintain reference to current worker thread
     * under separate synchronization control.
     */
    private static class ThreadVar {
        private Thread thread;

        ThreadVar(Thread t) {
            thread = t;
        }

        synchronized Thread get() {
            return thread;
        }

        synchronized void clear() {
            thread = null;
        }
    }

    private Object value; // see getValue(), setValue(), get()
    @SuppressWarnings("unused")
    private Thread thread;
    private ThreadVar threadVar;
    private GlassPane glassPane;
    private Component aComponent;

    private static Map<GlassPane, Integer> countPerGlassPane = new HashMap<GlassPane, Integer>();

    /**
     * Creates a SwingWorker that can be later started with <code>start</code>
     * method. <code>Component<c/ode> 'aComponent's top level ancestor will
     * receive a <code>GlassPane</code> that will intercept user events to that
     * ancestor until no GlassPane is set to that ancestor. Null 'aComponent' is
     * accepted and will not use GlassPane.
     *
     * @param aComponent Component whose top level ancestor will receive a GlassPane
     */
    public SwingWorker(Component aComponent) {
        setAComponent(aComponent);

        final Runnable doFinished = new Runnable() {
            public void run() {
                finished();
            }
        };

        Runnable doConstruct = new Runnable() {
            public void run() {
                try {
                    // Modification to SwingWorker, to use RB context
                    Thread.currentThread().setContextClassLoader(RegistryBrowser.getInstance().classLoader);
                    construct();
                } finally {
                    threadVar.clear();

                    // Execute the doFinished runnable on the Swing dispatcher thread
                    SwingUtilities.invokeLater(doFinished);
                }
            }
        };

        // Group the new worker thread in the same group as the "spawner" thread
        Thread t = new Thread(Thread.currentThread().getThreadGroup(), doConstruct);
        threadVar = new ThreadVar(t);
    }

    /**
     * Activate the capabilities of glasspane
     */
    private void activateGlassPane() {
        Component aComp = getAComponent();
        GlassPane aPane = activateGlassPaneSynch(aComp);

        // keep track of the glasspane as an instance variable
        setGlassPane(aPane);
    }

    /**
     * Activate the capabilities of glasspane, only if this is the 1st running 
     * call for 'aComp'
     *
     * @return GlassPane for aComponent's top level component.
     */
    private static synchronized GlassPane activateGlassPaneSynch(Component aComp) {
        // Mount the glasspane on the component window
        GlassPane aPane = GlassPane.mount(aComp, true);
        if (aPane != null) {
            Integer iCount = countPerGlassPane.get(aPane);
            if (iCount == null) {
                // no GlassPane active for it, create new, cache it and increase count
                countPerGlassPane.put(aPane, new Integer(1));

                // Start interception UI interactions
                aPane.setVisible(true);
            } else {
                // GlassPane already exist, just increase count
                int newCount = iCount.intValue() + 1;
                countPerGlassPane.put(aPane, new Integer(newCount));
            }

        }
        return aPane;
    }

    /**
     * Enable the glass pane (to disable unwanted UI manipulation), then spawn the non-UI logic on a separate thread.
     */
    private void construct() {
        activateGlassPane();
        try {
            setValue(doNonUILogic());
        } catch (RuntimeException e) {
            // ??? why not use e.toString() instead?
            log.error("SwingWorker error: " + e.getClass().getName() + ": " + e.getMessage(), e);
        }
    }

    /**
     * Deactivate the glasspane
     *
     */
    private void deactivateGlassPane() {
        deactivateGlassPaneSynch(getGlassPane());
    }

    /**
     * Activate the capabilities of glasspane, only if this is the 1st running 
     * call for 'aComp'
     *
     * @return GlassPane for aComponent's top level component.
     */
    private static synchronized void deactivateGlassPaneSynch(GlassPane aPane) {
        if (aPane != null) {
            Integer iCount = countPerGlassPane.get(aPane);
            if (iCount == null) {
                // no GlassPane active for it, error?, return
                return;
            } else {
                int count = iCount.intValue();
                if (count == 1) {
                    // only one activateGlassPane call for aPane, reset count
                    countPerGlassPane.remove(aPane);
                    // Stop UI interception
                    aPane.setVisible(false);
                } else {
                    // More than one call, just decrease count
                    int newCount = count - 1;
                    countPerGlassPane.put(aPane, new Integer(newCount));
                }
            }
        }
    }

    /**
     * This method will be implemented by the inner class of SwingWorker
     * It should only consist of the logic that's unrelated to UI
     *
     * @throws java.lang.RuntimeException thrown if there are any errors in the non-ui logic
     */
    protected abstract Object doNonUILogic() throws RuntimeException;

    /**
     * This method will be implemented by the inner class of SwingWorker
     * It should only consist of the logic that's related to UI updating, after
     * the doNonUILogic() method is done.
     *
     * @throws java.lang.RuntimeException thrown if there are any problems executing the ui update logic
     */
    protected abstract void doUIUpdateLogic() throws RuntimeException;

    /**
     * Called on the event dispatching thread (not on the worker thread)
     * after the <code>construct</code> method has returned.
     */
    protected void finished() {
        try {
            deactivateGlassPane();
            doUIUpdateLogic();
        } catch (RuntimeException e) {
            // ??? why not use e.toString() instead?
            log.error("SwingWorker error: " + e.getClass().getName() + ": " + e.getMessage(), e);
        } finally {
            // Allow original component to get the focus
            if (getAComponent() != null) {
                getAComponent().requestFocus();
            }
        }
    }

    /**
     * Return the value created by the <code>construc/doNonUILogic</code> method.  
     * Returns null if either the constructing thread or the current
     * thread was interrupted before a value was produced.
     * 
     * @return the value created by the <code>construct</code> method
     */
    public Object get() {
        while (true) {
            Thread t = threadVar.get();
            if (t == null) {
                return getValue();
            }
            try {
                t.join();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt(); // propagate
                return null;
            }
        }
    }

    /**
     * Getter method
     *
     * @return java.awt.Component
     */
    protected Component getAComponent() {
        return aComponent;
    }

    /**
     * Getter method
     *
     * @return GlassPane
     */
    protected GlassPane getGlassPane() {
        return glassPane;
    }

    /** 
     * Get the value produced by the worker thread, or null if it 
     * hasn't been constructed yet.
     */
    protected synchronized Object getValue() {
        return value;
    }

    /**
     * A new method that interrupts the worker thread.  Call this method
     * to force the worker to stop what it's doing.
     */
    public void interrupt() {
        Thread t = threadVar.get();
        if (t != null) {
            t.interrupt();
        }
        threadVar.clear();
    }

    /**
     * Setter method
     *
     * @param newAComponent java.awt.Component
     */
    protected void setAComponent(Component newAComponent) {
        aComponent = newAComponent;
    }

    /**
     * Setter method
     *
     * @param newGlassPane GlassPane
     */
    protected void setGlassPane(GlassPane newGlassPane) {
        glassPane = newGlassPane;
    }

    /** 
     * Set the value produced by worker thread 
     */
    private synchronized void setValue(Object x) {
        value = x;
    }

    /**
     * Start the worker thread.
     */
    public void start() {
        Thread t = threadVar.get();
        if (t != null) {
            t.start();
        }
    }
}