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