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