]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.utils.thread/src/org/simantics/utils/threads/ua/AbstractState.java
Fixed all line endings of the repository
[simantics/platform.git] / bundles / org.simantics.utils.thread / src / org / simantics / utils / threads / ua / AbstractState.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management
3  * in Industry THTH ry.
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
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12
13 package org.simantics.utils.threads.ua;
14
15 import java.util.Set;
16 import java.util.concurrent.CopyOnWriteArrayList;
17 import java.util.concurrent.Executor;
18 import java.util.concurrent.TimeUnit;
19 import java.util.concurrent.TimeoutException;
20
21 /**
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). 
25  * 
26  * TODO Remove locks - use spin set and test
27  *
28  * @see IStatefulObject
29  * @see StateListener Listener for state modifications
30  * @author Toni Kalajainen (toni.kalajainen@vtt.fi)
31  * @param <StateType> 
32  * @param <ErrorType> 
33  */
34 public abstract class AbstractState<StateType, ErrorType extends Throwable> implements IStatefulObject<StateType, ErrorType> {
35
36         /** Current state */
37         private StateType state = null;
38         /** Optional error state */
39         private StateType errorState = null;
40         /** Error cause */
41         private ErrorType errorCause;
42         
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();
47         
48         public AbstractState(StateType initialState)
49         {
50                 state = initialState;
51         }
52         
53         /**
54          * Creates a state with a error state. The state object goes to errorState on setError(). 
55          * 
56          * @param initialState
57          * @param errorState
58          */
59         public AbstractState(StateType initialState, StateType errorState)
60         {
61                 state = initialState;
62                 this.errorState = errorState;
63         }
64         
65         @Override
66         public synchronized StateType getState() {
67                 return state;
68         }
69         
70         /**
71          * Attempts to change the state. The state will be changed only if current
72          * state is one of the expected states. 
73          * 
74          * @param prerequisiteState expected current state
75          * @param newState
76          * @return state after attempt
77          */
78         protected StateType attemptSetState(Set<StateType> prerequisiteState, StateType newState)
79         {
80                 if (prerequisiteState==null || newState==null)
81                         throw new IllegalArgumentException("null arg");
82                 return setState(newState, null, prerequisiteState);
83         }
84         
85         @Override
86         public synchronized void addStateListener(StateListener<StateType> listener) {
87                 if (listener==null) 
88                         throw new IllegalArgumentException("null arg");
89                 if (listenerList!=null)
90                 {
91                         listenerList.add(listener);
92                         return;
93                 }
94                 if (firstListener==null) {
95                         firstListener = listener;
96                         return;
97                 }
98                 
99                 listenerList = new CopyOnWriteArrayList<StateListener<StateType>>();
100                 listenerList.add(listener);
101         }
102
103         @Override
104         public void removeStateListener(StateListener<StateType> listener) {
105                 if (listener==null) 
106                         throw new IllegalArgumentException("null arg");
107                 if (listenerList!=null) {
108                         listenerList.remove(listener);
109                         if (listenerList.isEmpty()) listenerList = null;
110                         return;
111                 }
112                 if (listener == firstListener) {
113                         firstListener = null;
114                 }
115         }
116
117         protected boolean setState(StateType state)
118         {
119                 return setState(state, null, null) == state;
120         }
121         
122         protected void setError(ErrorType error)
123         {
124                 this.errorCause = error;
125                 if (errorState==null || !setState(errorState))
126                 {
127                         // wake up sleepers
128                         synchronized(lock) 
129                         {
130                                 lock.notifyAll();
131                         }
132                 }
133         }
134         
135         protected void clearError()
136         {
137                 errorCause = null;               
138         }
139         
140         public ErrorType getError()
141         {
142                 return errorCause;
143         }
144         
145         public boolean hasError()
146         {
147                 return errorCause!=null;
148         }
149         
150         protected void assertNoError()
151         throws ErrorType
152         {
153                 ErrorType e = errorCause;               
154                 if (e!=null)
155                         throw e;
156         }
157         
158         /**
159          * Set state
160          * 
161          * @param state
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
165          */
166         protected StateType setState(StateType state, Executor listenerExecutor, Set<StateType> prerequisiteStates)
167         {               
168                 boolean hasListeners;
169                 StateListener<StateType> fl = null;
170                 StateType oldState = null;
171                 StateType newState = null;
172                 synchronized (this) {
173                         oldState = this.state;
174                         newState = state;
175                         if (oldState==newState) return state;
176                         if (prerequisiteStates!=null && !prerequisiteStates.contains(this.state))
177                                 return state;
178                         if (!isStateTransitionAllowed(oldState, newState))
179                                 return state;
180
181                         this.state = newState;
182                         fl = firstListener;
183                         hasListeners = fl!=null || (listenerList!=null && !listenerList.isEmpty());
184                 }
185                 final StateListener<StateType> fl_ = fl;
186                 synchronized(lock) 
187                 {
188                         lock.notifyAll();
189                 }
190                 // Threads wake up here...
191                 
192                 // Handle listeners
193                 onStateTransition(oldState, newState);
194                 
195                 if (hasListeners) {
196                         final StateType os = oldState;
197                         final StateType ns = newState;
198                         if (fl!=null) {
199                                 if (listenerExecutor==null) {
200                                         try {
201                                                 fl.onStateTransition(this, oldState, newState);
202                                         } catch (RuntimeException e) {
203                                                 onListenerException(e);
204                                         }
205                                 } else {
206                                         listenerExecutor.execute(new Runnable() {
207                                                 @Override
208                                                 public void run() {
209                                                         try {
210                                                                 fl_.onStateTransition(AbstractState.this, os, ns);
211                                                         } catch (RuntimeException e) {
212                                                                 onListenerException(e);
213                                                         }
214                                                 }});
215                                 }
216                         }
217                         if (listenerList!=null && !listenerList.isEmpty())
218                         for (final StateListener<StateType> sl : listenerList) {
219                                 if (listenerExecutor==null) {
220                                         try {
221                                                 sl.onStateTransition(this, oldState, newState);
222                                         } catch (RuntimeException e) {
223                                                 onListenerException(e);
224                                         }
225                                 } else {
226                                         listenerExecutor.execute(new Runnable() {
227                                                 @Override
228                                                 public void run() {
229                                                         try {
230                                                                 sl.onStateTransition(AbstractState.this, os, ns);                                                       
231                                                         } catch (RuntimeException e) {
232                                                                 onListenerException(e);
233                                                         }
234                                                 }});
235                                 }
236                         }
237                 }
238                 return state;
239         }
240         
241         /**
242          * Checks whether state transition is allowed.
243          * Override this
244          * 
245          * @param oldState
246          * @param newState
247          * @return true if state transition is allowed
248          */
249         protected boolean isStateTransitionAllowed(StateType oldState, StateType newState)
250         {
251                 return true;
252         }
253         
254         /**
255          * Override this.
256          * 
257          * @param oldState
258          * @param newState
259          */
260         protected void onStateTransition(StateType oldState, StateType newState)
261         {               
262         }
263
264         @Override
265         public StateType waitForState(Set<StateType> set) 
266         throws InterruptedException, ErrorType
267         {
268                 // This impl makes unnecessary wakeups but is memory conservative               
269                 synchronized(lock) {
270                         while (!set.contains(state))
271                                 lock.wait();
272                         ErrorType e = getError();
273                         if (e!=null)
274                                 throw e;
275                         return state;
276                 }
277         }
278
279         public StateType waitForStateUninterruptibly(Set<StateType> set) 
280         throws ErrorType
281         {
282                 // This impl makes unnecessary wakeups but is memory conservative               
283                 synchronized(lock) {
284                         while (!set.contains(state))
285                                 try {
286                                         lock.wait();
287                                 } catch (InterruptedException qwer) {}
288                         ErrorType e = getError();
289                         if (e!=null)
290                                 throw e;
291                         return state;
292                 }
293         }
294
295         @Override
296         public StateType waitForState(
297                         Set<StateType> set, 
298                         long timeout,
299                         TimeUnit unit) 
300         throws InterruptedException, TimeoutException, ErrorType {
301                 long abortTime = System.currentTimeMillis() + unit.toMillis(timeout);
302                 synchronized(lock) {
303                         while (!set.contains(state)) {
304                                 long waitTime = System.currentTimeMillis() - abortTime;
305                                 if (waitTime<0)
306                                         throw new TimeoutException("timeout");
307                                 lock.wait(waitTime);
308                                 ErrorType e = getError();
309                                 if (e!=null)
310                                         throw e;
311                         }
312                         return state;
313                 }               
314         }
315         
316         /**
317          * Override this.
318          * @param rte
319          */
320         protected void onListenerException(RuntimeException rte)
321         {
322                 rte.printStackTrace();
323         }
324
325 }