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