--- /dev/null
+/*******************************************************************************\r
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
+ * in Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ * VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+\r
+package org.simantics.utils.threads.ua;\r
+\r
+import java.util.Set;\r
+import java.util.concurrent.CopyOnWriteArrayList;\r
+import java.util.concurrent.Executor;\r
+import java.util.concurrent.TimeUnit;\r
+import java.util.concurrent.TimeoutException;\r
+\r
+/**\r
+ * This is a default implementation to {@link IStatefulObject}.\r
+ * This class can be subclassed or used as it. \r
+ * The state type is parametrized (typically an enumeration). \r
+ * \r
+ * TODO Remove locks - use spin set and test\r
+ *\r
+ * @see IStatefulObject\r
+ * @see StateListener Listener for state modifications\r
+ * @author Toni Kalajainen (toni.kalajainen@vtt.fi)\r
+ * @param <StateType> \r
+ * @param <ErrorType> \r
+ */\r
+public abstract class AbstractState<StateType, ErrorType extends Throwable> implements IStatefulObject<StateType, ErrorType> {\r
+\r
+ /** Current state */\r
+ private StateType state = null;\r
+ /** Optional error state */\r
+ private StateType errorState = null;\r
+ /** Error cause */\r
+ private ErrorType errorCause;\r
+ \r
+ // Optimization for 1 listener, ListenerList is heavy //\r
+ private StateListener<StateType> firstListener = null; \r
+ private CopyOnWriteArrayList<StateListener<StateType>> listenerList = null;\r
+ private Object lock = new Object();\r
+ \r
+ public AbstractState(StateType initialState)\r
+ {\r
+ state = initialState;\r
+ }\r
+ \r
+ /**\r
+ * Creates a state with a error state. The state object goes to errorState on setError(). \r
+ * \r
+ * @param initialState\r
+ * @param errorState\r
+ */\r
+ public AbstractState(StateType initialState, StateType errorState)\r
+ {\r
+ state = initialState;\r
+ this.errorState = errorState;\r
+ }\r
+ \r
+ @Override\r
+ public synchronized StateType getState() {\r
+ return state;\r
+ }\r
+ \r
+ /**\r
+ * Attempts to change the state. The state will be changed only if current\r
+ * state is one of the expected states. \r
+ * \r
+ * @param prerequisiteState expected current state\r
+ * @param newState\r
+ * @return state after attempt\r
+ */\r
+ protected StateType attemptSetState(Set<StateType> prerequisiteState, StateType newState)\r
+ {\r
+ if (prerequisiteState==null || newState==null)\r
+ throw new IllegalArgumentException("null arg");\r
+ return setState(newState, null, prerequisiteState);\r
+ }\r
+ \r
+ @Override\r
+ public synchronized void addStateListener(StateListener<StateType> listener) {\r
+ if (listener==null) \r
+ throw new IllegalArgumentException("null arg");\r
+ if (listenerList!=null)\r
+ {\r
+ listenerList.add(listener);\r
+ return;\r
+ }\r
+ if (firstListener==null) {\r
+ firstListener = listener;\r
+ return;\r
+ }\r
+ \r
+ listenerList = new CopyOnWriteArrayList<StateListener<StateType>>();\r
+ listenerList.add(listener);\r
+ }\r
+\r
+ @Override\r
+ public void removeStateListener(StateListener<StateType> listener) {\r
+ if (listener==null) \r
+ throw new IllegalArgumentException("null arg");\r
+ if (listenerList!=null) {\r
+ listenerList.remove(listener);\r
+ if (listenerList.isEmpty()) listenerList = null;\r
+ return;\r
+ }\r
+ if (listener == firstListener) {\r
+ firstListener = null;\r
+ }\r
+ }\r
+\r
+ protected boolean setState(StateType state)\r
+ {\r
+ return setState(state, null, null) == state;\r
+ }\r
+ \r
+ protected void setError(ErrorType error)\r
+ {\r
+ this.errorCause = error;\r
+ if (errorState==null || !setState(errorState))\r
+ {\r
+ // wake up sleepers\r
+ synchronized(lock) \r
+ {\r
+ lock.notifyAll();\r
+ }\r
+ }\r
+ }\r
+ \r
+ protected void clearError()\r
+ {\r
+ errorCause = null; \r
+ }\r
+ \r
+ public ErrorType getError()\r
+ {\r
+ return errorCause;\r
+ }\r
+ \r
+ public boolean hasError()\r
+ {\r
+ return errorCause!=null;\r
+ }\r
+ \r
+ protected void assertNoError()\r
+ throws ErrorType\r
+ {\r
+ ErrorType e = errorCause; \r
+ if (e!=null)\r
+ throw e;\r
+ }\r
+ \r
+ /**\r
+ * Set state\r
+ * \r
+ * @param state\r
+ * @param listenerExecutor executor for post listener handling or null for immediate\r
+ * @param prerequisiteStates old state prerequisite or null \r
+ * @return state after attempt\r
+ */\r
+ protected StateType setState(StateType state, Executor listenerExecutor, Set<StateType> prerequisiteStates)\r
+ { \r
+ boolean hasListeners;\r
+ StateListener<StateType> fl = null;\r
+ StateType oldState = null;\r
+ StateType newState = null;\r
+ synchronized (this) {\r
+ oldState = this.state;\r
+ newState = state;\r
+ if (oldState==newState) return state;\r
+ if (prerequisiteStates!=null && !prerequisiteStates.contains(this.state))\r
+ return state;\r
+ if (!isStateTransitionAllowed(oldState, newState))\r
+ return state;\r
+\r
+ this.state = newState;\r
+ fl = firstListener;\r
+ hasListeners = fl!=null || (listenerList!=null && !listenerList.isEmpty());\r
+ }\r
+ final StateListener<StateType> fl_ = fl;\r
+ synchronized(lock) \r
+ {\r
+ lock.notifyAll();\r
+ }\r
+ // Threads wake up here...\r
+ \r
+ // Handle listeners\r
+ onStateTransition(oldState, newState);\r
+ \r
+ if (hasListeners) {\r
+ final StateType os = oldState;\r
+ final StateType ns = newState;\r
+ if (fl!=null) {\r
+ if (listenerExecutor==null) {\r
+ try {\r
+ fl.onStateTransition(this, oldState, newState);\r
+ } catch (RuntimeException e) {\r
+ onListenerException(e);\r
+ }\r
+ } else {\r
+ listenerExecutor.execute(new Runnable() {\r
+ @Override\r
+ public void run() {\r
+ try {\r
+ fl_.onStateTransition(AbstractState.this, os, ns);\r
+ } catch (RuntimeException e) {\r
+ onListenerException(e);\r
+ }\r
+ }});\r
+ }\r
+ }\r
+ if (listenerList!=null && !listenerList.isEmpty())\r
+ for (final StateListener<StateType> sl : listenerList) {\r
+ if (listenerExecutor==null) {\r
+ try {\r
+ sl.onStateTransition(this, oldState, newState);\r
+ } catch (RuntimeException e) {\r
+ onListenerException(e);\r
+ }\r
+ } else {\r
+ listenerExecutor.execute(new Runnable() {\r
+ @Override\r
+ public void run() {\r
+ try {\r
+ sl.onStateTransition(AbstractState.this, os, ns); \r
+ } catch (RuntimeException e) {\r
+ onListenerException(e);\r
+ }\r
+ }});\r
+ }\r
+ }\r
+ }\r
+ return state;\r
+ }\r
+ \r
+ /**\r
+ * Checks whether state transition is allowed.\r
+ * Override this\r
+ * \r
+ * @param oldState\r
+ * @param newState\r
+ * @return true if state transition is allowed\r
+ */\r
+ protected boolean isStateTransitionAllowed(StateType oldState, StateType newState)\r
+ {\r
+ return true;\r
+ }\r
+ \r
+ /**\r
+ * Override this.\r
+ * \r
+ * @param oldState\r
+ * @param newState\r
+ */\r
+ protected void onStateTransition(StateType oldState, StateType newState)\r
+ { \r
+ }\r
+\r
+ @Override\r
+ public StateType waitForState(Set<StateType> set) \r
+ throws InterruptedException, ErrorType\r
+ {\r
+ // This impl makes unnecessary wakeups but is memory conservative \r
+ synchronized(lock) {\r
+ while (!set.contains(state))\r
+ lock.wait();\r
+ ErrorType e = getError();\r
+ if (e!=null)\r
+ throw e;\r
+ return state;\r
+ }\r
+ }\r
+\r
+ public StateType waitForStateUninterruptibly(Set<StateType> set) \r
+ throws ErrorType\r
+ {\r
+ // This impl makes unnecessary wakeups but is memory conservative \r
+ synchronized(lock) {\r
+ while (!set.contains(state))\r
+ try {\r
+ lock.wait();\r
+ } catch (InterruptedException qwer) {}\r
+ ErrorType e = getError();\r
+ if (e!=null)\r
+ throw e;\r
+ return state;\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public StateType waitForState(\r
+ Set<StateType> set, \r
+ long timeout,\r
+ TimeUnit unit) \r
+ throws InterruptedException, TimeoutException, ErrorType {\r
+ long abortTime = System.currentTimeMillis() + unit.toMillis(timeout);\r
+ synchronized(lock) {\r
+ while (!set.contains(state)) {\r
+ long waitTime = System.currentTimeMillis() - abortTime;\r
+ if (waitTime<0)\r
+ throw new TimeoutException("timeout");\r
+ lock.wait(waitTime);\r
+ ErrorType e = getError();\r
+ if (e!=null)\r
+ throw e;\r
+ }\r
+ return state;\r
+ } \r
+ }\r
+ \r
+ /**\r
+ * Override this.\r
+ * @param rte\r
+ */\r
+ protected void onListenerException(RuntimeException rte)\r
+ {\r
+ rte.printStackTrace();\r
+ }\r
+\r
+}\r