X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=blobdiff_plain;f=bundles%2Forg.simantics.utils.thread%2Fsrc%2Forg%2Fsimantics%2Futils%2Fthreads%2Fua%2FAbstractState.java;fp=bundles%2Forg.simantics.utils.thread%2Fsrc%2Forg%2Fsimantics%2Futils%2Fthreads%2Fua%2FAbstractState.java;h=5f9631ea01fc70ee28c9e44b4c50b5ec0abdad1c;hb=969bd23cab98a79ca9101af33334000879fb60c5;hp=0000000000000000000000000000000000000000;hpb=866dba5cd5a3929bbeae85991796acb212338a08;p=simantics%2Fplatform.git diff --git a/bundles/org.simantics.utils.thread/src/org/simantics/utils/threads/ua/AbstractState.java b/bundles/org.simantics.utils.thread/src/org/simantics/utils/threads/ua/AbstractState.java new file mode 100644 index 000000000..5f9631ea0 --- /dev/null +++ b/bundles/org.simantics.utils.thread/src/org/simantics/utils/threads/ua/AbstractState.java @@ -0,0 +1,325 @@ +/******************************************************************************* + * 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 + * @param + */ +public abstract class AbstractState implements IStatefulObject { + + /** 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 firstListener = null; + private CopyOnWriteArrayList> 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 prerequisiteState, StateType newState) + { + if (prerequisiteState==null || newState==null) + throw new IllegalArgumentException("null arg"); + return setState(newState, null, prerequisiteState); + } + + @Override + public synchronized void addStateListener(StateListener 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>(); + listenerList.add(listener); + } + + @Override + public void removeStateListener(StateListener 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 prerequisiteStates) + { + boolean hasListeners; + StateListener 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 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 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 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 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 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(); + } + +}