001    /*
002            Copyright (c) 2009 Olivier Chafik, All Rights Reserved
003            
004            This file is part of JNAerator (http://jnaerator.googlecode.com/).
005            
006            JNAerator is free software: you can redistribute it and/or modify
007            it under the terms of the GNU General Public License as published by
008            the Free Software Foundation, either version 3 of the License, or
009            (at your option) any later version.
010            
011            JNAerator is distributed in the hope that it will be useful,
012            but WITHOUT ANY WARRANTY; without even the implied warranty of
013            MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
014            GNU General Public License for more details.
015            
016            You should have received a copy of the GNU General Public License
017            along with JNAerator.  If not, see <http://www.gnu.org/licenses/>.
018    */
019    /**
020     * 
021     */
022    package com.ochafik.lang;
023    
024    import java.awt.event.ActionEvent;
025    import java.awt.event.ActionListener;
026    import java.util.ArrayList;
027    import java.util.List;
028    import java.util.NoSuchElementException;
029    import java.util.concurrent.Semaphore;
030    
031    /**
032     * <p>
033     * Group of coordinated Runnable instances that can be started, interrupted and waited for together.
034     * </p><p>
035     * Once you added as many runnable tasks as needed through the add(Runnable) method,  
036     * there are two ways of waiting for the tasks to finish :
037     * <ul>
038     * <li>call join() in some thread. This will implicitely start the threads if start() was not called yet, and the join() method will not return until all the thread finished their execution
039     * </li><li>call start() and register some ActionListener instances. Whenever all threads finished their execution, the actionPerformed method of all the listeners will be called.
040     * </li>
041     * @author Olivier Chafik
042     */
043    public final class Threads {
044            private final List<Runner> runners = new ArrayList<Runner>();
045            private final Semaphore semaphore = new Semaphore(0);
046            private boolean fired = false, started = false;
047    
048            private List<ActionListener> actionListeners; 
049    
050            private class Runner extends Thread {
051                    private final Runnable runnable;
052                    public Runner(Runnable runnable) {
053                            this.runnable = runnable;
054                    }
055                    public void run() {
056                            try {
057                                    runnable.run();
058                            } finally {
059                                    int nThreads = runners.size();
060                                    if (semaphore.tryAcquire(nThreads - 1)) {
061                                            semaphore.release(nThreads);
062                                            synchronized (this) {
063                                                    if (!fired) {
064                                                            fired = true;
065                                                            fireActionPerformed();
066                                                    }
067                                            }
068                                    } else {
069                                            semaphore.release();
070                                    }
071                            }
072                    }
073            }
074            
075            /**
076             * Add a task that is to be executed in its own thread.
077             * @param runnable task to be executed in its own thread
078             * @return the runnable argument unchanged
079             */
080            public synchronized <T extends Runnable> T add(T runnable) {
081                    if (started)
082                            throw new IllegalThreadStateException("Cannot add another runnable to " + getClass().getSimpleName() + " after it started !");
083                    
084                    runners.add(new Runner(runnable));
085                    return runnable;
086            }
087            
088            /**
089             * Starts all the threads.
090             * @throws IllegalThreadStateException if the threads were already started.
091             * @throws NoSuchElementException if no runnable were added to this Threads instance.
092             */
093            public synchronized void start() {
094                    if (started)
095                            throw new IllegalThreadStateException(getClass().getSimpleName() + " already started !");
096                    
097                    if (runners.isEmpty())
098                            throw new NoSuchElementException("No runnable were added to this " + getClass().getSimpleName());
099                    
100                    for (Runner t : runners) {
101                            t.start();
102                    }
103                    started = true;
104            }
105            
106            /**
107             * Calls interrupt() on each of the running threads.
108             * @throws IllegalThreadStateException if threads were not started 
109             */
110            public synchronized void interrupt() {
111                    if (!started)
112                            throw new IllegalThreadStateException(getClass().getSimpleName() + " not started !");
113                    
114                    for (Runner t : runners) {
115                            try {
116                                    t.interrupt();
117                            } catch (IllegalThreadStateException ex) {
118                                    // t might have finished its execution
119                                    ex.printStackTrace();
120                            }
121                    } 
122            }
123            
124            /**
125             * Waits for all runnable to have finished their execution.
126             * Can be called multiple times : after the first time, this method always returns immediately.
127             * If the Threads is not started yet, this method will start it implicitely.
128             * @throws InterruptedException if method interrupt() was called on the thread that is calling this method.
129             */
130            public synchronized void join() throws InterruptedException {
131                    int nThreads = runners.size();
132                    if (nThreads == 0)
133                            return;
134                    
135                    if (!started)
136                            start();
137                    
138                    semaphore.acquire(nThreads);
139                    semaphore.release(nThreads);
140            }
141            
142            public enum State {
143                    NotStarted, Running, Finished, NoRunnables
144            }
145            
146            public synchronized State getState() {
147                    int nThreads = runners.size();
148                    if (nThreads == 0)
149                            return State.NoRunnables;
150                    
151                    if (!started)
152                            return State.NotStarted;
153                    
154                    if (semaphore.tryAcquire(nThreads)) {
155                            semaphore.release(nThreads);
156                            return State.Finished;
157                    }
158                    return State.Running;
159            }
160            
161            /**
162             * Adds a listener that will be notified upon completion of all of the running threads.
163             * Its actionPerformed method will be called immediately if the threads already finished.
164             * @param actionListener
165             */
166            public synchronized void addActionListener(ActionListener actionListener) {
167                    if (actionListeners == null)
168                            actionListeners = new ArrayList<ActionListener>();
169                    
170                    actionListeners.add(actionListener);
171                    
172                    if (fired) {
173                            actionListener.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, ""));
174                    }
175            }
176            
177            private synchronized void fireActionPerformed() {
178                    if (actionListeners == null) 
179                            return;
180                    
181                    ActionEvent a = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "");
182                    for (ActionListener l : actionListeners)
183                            l.actionPerformed(a);
184            }
185            
186    }