/******************************************************************************* * 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(); } }