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 }