1 /*******************************************************************************
\r
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
\r
3 * in Industry THTH ry.
\r
4 * All rights reserved. This program and the accompanying materials
\r
5 * are made available under the terms of the Eclipse Public License v1.0
\r
6 * which accompanies this distribution, and is available at
\r
7 * http://www.eclipse.org/legal/epl-v10.html
\r
10 * VTT Technical Research Centre of Finland - initial API and implementation
\r
11 *******************************************************************************/
\r
13 package org.simantics.utils.threads.ua;
\r
15 import java.util.Set;
\r
16 import java.util.concurrent.CopyOnWriteArrayList;
\r
17 import java.util.concurrent.Executor;
\r
18 import java.util.concurrent.TimeUnit;
\r
19 import java.util.concurrent.TimeoutException;
\r
22 * This is a default implementation to {@link IStatefulObject}.
\r
23 * This class can be subclassed or used as it.
\r
24 * The state type is parametrized (typically an enumeration).
\r
26 * TODO Remove locks - use spin set and test
\r
28 * @see IStatefulObject
\r
29 * @see StateListener Listener for state modifications
\r
30 * @author Toni Kalajainen (toni.kalajainen@vtt.fi)
\r
31 * @param <StateType>
\r
32 * @param <ErrorType>
\r
34 public abstract class AbstractState<StateType, ErrorType extends Throwable> implements IStatefulObject<StateType, ErrorType> {
\r
36 /** Current state */
\r
37 private StateType state = null;
\r
38 /** Optional error state */
\r
39 private StateType errorState = null;
\r
41 private ErrorType errorCause;
\r
43 // Optimization for 1 listener, ListenerList is heavy //
\r
44 private StateListener<StateType> firstListener = null;
\r
45 private CopyOnWriteArrayList<StateListener<StateType>> listenerList = null;
\r
46 private Object lock = new Object();
\r
48 public AbstractState(StateType initialState)
\r
50 state = initialState;
\r
54 * Creates a state with a error state. The state object goes to errorState on setError().
\r
56 * @param initialState
\r
59 public AbstractState(StateType initialState, StateType errorState)
\r
61 state = initialState;
\r
62 this.errorState = errorState;
\r
66 public synchronized StateType getState() {
\r
71 * Attempts to change the state. The state will be changed only if current
\r
72 * state is one of the expected states.
\r
74 * @param prerequisiteState expected current state
\r
76 * @return state after attempt
\r
78 protected StateType attemptSetState(Set<StateType> prerequisiteState, StateType newState)
\r
80 if (prerequisiteState==null || newState==null)
\r
81 throw new IllegalArgumentException("null arg");
\r
82 return setState(newState, null, prerequisiteState);
\r
86 public synchronized void addStateListener(StateListener<StateType> listener) {
\r
87 if (listener==null)
\r
88 throw new IllegalArgumentException("null arg");
\r
89 if (listenerList!=null)
\r
91 listenerList.add(listener);
\r
94 if (firstListener==null) {
\r
95 firstListener = listener;
\r
99 listenerList = new CopyOnWriteArrayList<StateListener<StateType>>();
\r
100 listenerList.add(listener);
\r
104 public void removeStateListener(StateListener<StateType> listener) {
\r
105 if (listener==null)
\r
106 throw new IllegalArgumentException("null arg");
\r
107 if (listenerList!=null) {
\r
108 listenerList.remove(listener);
\r
109 if (listenerList.isEmpty()) listenerList = null;
\r
112 if (listener == firstListener) {
\r
113 firstListener = null;
\r
117 protected boolean setState(StateType state)
\r
119 return setState(state, null, null) == state;
\r
122 protected void setError(ErrorType error)
\r
124 this.errorCause = error;
\r
125 if (errorState==null || !setState(errorState))
\r
127 // wake up sleepers
\r
128 synchronized(lock)
\r
135 protected void clearError()
\r
137 errorCause = null;
\r
140 public ErrorType getError()
\r
145 public boolean hasError()
\r
147 return errorCause!=null;
\r
150 protected void assertNoError()
\r
153 ErrorType e = errorCause;
\r
162 * @param listenerExecutor executor for post listener handling or null for immediate
\r
163 * @param prerequisiteStates old state prerequisite or null
\r
164 * @return state after attempt
\r
166 protected StateType setState(StateType state, Executor listenerExecutor, Set<StateType> prerequisiteStates)
\r
168 boolean hasListeners;
\r
169 StateListener<StateType> fl = null;
\r
170 StateType oldState = null;
\r
171 StateType newState = null;
\r
172 synchronized (this) {
\r
173 oldState = this.state;
\r
175 if (oldState==newState) return state;
\r
176 if (prerequisiteStates!=null && !prerequisiteStates.contains(this.state))
\r
178 if (!isStateTransitionAllowed(oldState, newState))
\r
181 this.state = newState;
\r
182 fl = firstListener;
\r
183 hasListeners = fl!=null || (listenerList!=null && !listenerList.isEmpty());
\r
185 final StateListener<StateType> fl_ = fl;
\r
186 synchronized(lock)
\r
190 // Threads wake up here...
\r
192 // Handle listeners
\r
193 onStateTransition(oldState, newState);
\r
195 if (hasListeners) {
\r
196 final StateType os = oldState;
\r
197 final StateType ns = newState;
\r
199 if (listenerExecutor==null) {
\r
201 fl.onStateTransition(this, oldState, newState);
\r
202 } catch (RuntimeException e) {
\r
203 onListenerException(e);
\r
206 listenerExecutor.execute(new Runnable() {
\r
208 public void run() {
\r
210 fl_.onStateTransition(AbstractState.this, os, ns);
\r
211 } catch (RuntimeException e) {
\r
212 onListenerException(e);
\r
217 if (listenerList!=null && !listenerList.isEmpty())
\r
218 for (final StateListener<StateType> sl : listenerList) {
\r
219 if (listenerExecutor==null) {
\r
221 sl.onStateTransition(this, oldState, newState);
\r
222 } catch (RuntimeException e) {
\r
223 onListenerException(e);
\r
226 listenerExecutor.execute(new Runnable() {
\r
228 public void run() {
\r
230 sl.onStateTransition(AbstractState.this, os, ns);
\r
231 } catch (RuntimeException e) {
\r
232 onListenerException(e);
\r
242 * Checks whether state transition is allowed.
\r
247 * @return true if state transition is allowed
\r
249 protected boolean isStateTransitionAllowed(StateType oldState, StateType newState)
\r
260 protected void onStateTransition(StateType oldState, StateType newState)
\r
265 public StateType waitForState(Set<StateType> set)
\r
266 throws InterruptedException, ErrorType
\r
268 // This impl makes unnecessary wakeups but is memory conservative
\r
269 synchronized(lock) {
\r
270 while (!set.contains(state))
\r
272 ErrorType e = getError();
\r
279 public StateType waitForStateUninterruptibly(Set<StateType> set)
\r
282 // This impl makes unnecessary wakeups but is memory conservative
\r
283 synchronized(lock) {
\r
284 while (!set.contains(state))
\r
287 } catch (InterruptedException qwer) {}
\r
288 ErrorType e = getError();
\r
296 public StateType waitForState(
\r
297 Set<StateType> set,
\r
300 throws InterruptedException, TimeoutException, ErrorType {
\r
301 long abortTime = System.currentTimeMillis() + unit.toMillis(timeout);
\r
302 synchronized(lock) {
\r
303 while (!set.contains(state)) {
\r
304 long waitTime = System.currentTimeMillis() - abortTime;
\r
306 throw new TimeoutException("timeout");
\r
307 lock.wait(waitTime);
\r
308 ErrorType e = getError();
\r
320 protected void onListenerException(RuntimeException rte)
\r
322 rte.printStackTrace();
\r