View Javadoc

1   /*
2   	Copyright (c) 2009 Olivier Chafik, All Rights Reserved
3   	
4   	This file is part of JNAerator (http://jnaerator.googlecode.com/).
5   	
6   	JNAerator is free software: you can redistribute it and/or modify
7   	it under the terms of the GNU General Public License as published by
8   	the Free Software Foundation, either version 3 of the License, or
9   	(at your option) any later version.
10  	
11  	JNAerator is distributed in the hope that it will be useful,
12  	but WITHOUT ANY WARRANTY; without even the implied warranty of
13  	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  	GNU General Public License for more details.
15  	
16  	You should have received a copy of the GNU General Public License
17  	along with JNAerator.  If not, see <http://www.gnu.org/licenses/>.
18  */
19  /**
20   * 
21   */
22  package com.ochafik.lang;
23  
24  import java.awt.event.ActionEvent;
25  import java.awt.event.ActionListener;
26  import java.util.ArrayList;
27  import java.util.List;
28  import java.util.NoSuchElementException;
29  import java.util.concurrent.Semaphore;
30  
31  /**
32   * <p>
33   * Group of coordinated Runnable instances that can be started, interrupted and waited for together.
34   * </p><p>
35   * Once you added as many runnable tasks as needed through the add(Runnable) method,  
36   * there are two ways of waiting for the tasks to finish :
37   * <ul>
38   * <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
39   * </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.
40   * </li>
41   * @author Olivier Chafik
42   */
43  public final class Threads {
44  	private final List<Runner> runners = new ArrayList<Runner>();
45  	private final Semaphore semaphore = new Semaphore(0);
46  	private boolean fired = false, started = false;
47  
48  	private List<ActionListener> actionListeners; 
49  
50  	private class Runner extends Thread {
51  		private final Runnable runnable;
52  		public Runner(Runnable runnable) {
53  			this.runnable = runnable;
54  		}
55  		public void run() {
56  			try {
57  				runnable.run();
58  			} finally {
59  				int nThreads = runners.size();
60  				if (semaphore.tryAcquire(nThreads - 1)) {
61  					semaphore.release(nThreads);
62  					synchronized (this) {
63  						if (!fired) {
64  							fired = true;
65  							fireActionPerformed();
66  						}
67  					}
68  				} else {
69  					semaphore.release();
70  				}
71  			}
72  		}
73  	}
74  	
75  	/**
76  	 * Add a task that is to be executed in its own thread.
77  	 * @param runnable task to be executed in its own thread
78  	 * @return the runnable argument unchanged
79  	 */
80  	public synchronized <T extends Runnable> T add(T runnable) {
81  		if (started)
82  			throw new IllegalThreadStateException("Cannot add another runnable to " + getClass().getSimpleName() + " after it started !");
83  		
84  		runners.add(new Runner(runnable));
85  		return runnable;
86  	}
87  	
88  	/**
89  	 * Starts all the threads.
90  	 * @throws IllegalThreadStateException if the threads were already started.
91  	 * @throws NoSuchElementException if no runnable were added to this Threads instance.
92  	 */
93  	public synchronized void start() {
94  		if (started)
95  			throw new IllegalThreadStateException(getClass().getSimpleName() + " already started !");
96  		
97  		if (runners.isEmpty())
98  			throw new NoSuchElementException("No runnable were added to this " + getClass().getSimpleName());
99  		
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 }