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 }