/************************************************************************* * Copyright 2009-2012 Eucalyptus Systems, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see http://www.gnu.org/licenses/. * * Please contact Eucalyptus Systems, Inc., 6755 Hollister Ave., Goleta * CA 93117, USA or visit http://www.eucalyptus.com/licenses/ if you need * additional information or have any questions. * * This file may incorporate work covered under the following copyright * and permission notice: * * Software License Agreement (BSD License) * * Copyright (c) 2008, Regents of the University of California * All rights reserved. * * Redistribution and use of this software in source and binary forms, * with or without modification, are permitted provided that the * following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. USERS OF THIS SOFTWARE ACKNOWLEDGE * THE POSSIBLE PRESENCE OF OTHER OPEN SOURCE LICENSED MATERIAL, * COPYRIGHTED MATERIAL OR PATENTED MATERIAL IN THIS SOFTWARE, * AND IF ANY SUCH MATERIAL IS DISCOVERED THE PARTY DISCOVERING * IT MAY INFORM DR. RICH WOLSKI AT THE UNIVERSITY OF CALIFORNIA, * SANTA BARBARA WHO WILL THEN ASCERTAIN THE MOST APPROPRIATE REMEDY, * WHICH IN THE REGENTS' DISCRETION MAY INCLUDE, WITHOUT LIMITATION, * REPLACEMENT OF THE CODE SO IDENTIFIED, LICENSING OF THE CODE SO * IDENTIFIED, OR WITHDRAWAL OF THE CODE CAPABILITY TO THE EXTENT * NEEDED TO COMPLY WITH ANY SUCH LICENSES OR RIGHTS. ************************************************************************/ package com.eucalyptus.util.fsm; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.Callable; import org.apache.log4j.Logger; import com.eucalyptus.util.Callback; import com.eucalyptus.util.HasName; import com.eucalyptus.util.Callback.Completion; import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import com.google.common.collect.Sets; public class StateMachineBuilder<P extends HasName<P>, S extends Automata.State, T extends Automata.Transition> { private static Logger LOG = Logger.getLogger( StateMachineBuilder.class ); protected P parent; protected S startState; private ImmutableList<S> immutableStates; private final Set<TransitionHandler<P, S, T>> transitions = Sets.newHashSet( ); private final Multimap<S, Callback<P>> inStateListeners = ArrayListMultimap.create( ); private final Multimap<S, Callback<P>> outStateListeners = ArrayListMultimap.create( ); protected class InStateBuilder { S state; public InStateBuilder run( Callback<P> callback ) { inStateListeners.put( state, callback ); return this; } public InStateBuilder run( final Predicate<P> predicate ) { inStateListeners.put( state, new Callback<P>( ) { {} @Override public void fire( P p ) { predicate.apply( p ); } } ); return this; } } protected class OutStateBuilder { S state; public OutStateBuilder run( Callback<P> callback ) { outStateListeners.put( state, callback ); return this; } public OutStateBuilder run( final Predicate<P> predicate ) { outStateListeners.put( state, new Callback<P>( ) { {} @Override public void fire( P p ) { predicate.apply( p ); } } ); return this; } } protected class TransitionBuilder { TransitionHandler<P, S, T> transition; T name; S fromState; S toState; S errorState; TransitionAction<P> action; List<TransitionListener<P>> listeners = Lists.newArrayList( ); TransitionBuilder commit( TransitionAction<P> action ) { this.action = action; this.errorState = ( this.errorState == null ) ? this.fromState : this.errorState; TransitionRule<S, T> rule = new BasicTransitionRule<S, T>( name, fromState, toState, errorState ); this.transition = new TransitionImpl<P, S, T>( rule, this.action, this.listeners.toArray( new TransitionListener[] {} ) ); this.listeners = null; StateMachineBuilder.this.addTransition( transition ); return this; } private void commit( ) {} public TransitionBuilder on( T transitionName ) { this.name = transitionName; return this; } public TransitionBuilder from( S fromState ) { this.fromState = fromState; return this; } public TransitionBuilder to( S toState ) { this.toState = toState; return this; } public TransitionBuilder error( S errorState ) { this.errorState = errorState; return this; } public void run( final Callback<P> callable ) { TransitionAction<P> action = Transitions.callbackAsAction( callable ); this.commit( action ); } public void run( Runnable function ) { TransitionAction<P> action = Transitions.runnableAsAction( function ); this.commit( action ); } public void run( Function<P, TransitionAction<P>> function ) { this.commit( function.apply( parent ) ); } public void run( TransitionAction<P> action ) { this.commit( action ); } public void run( final Predicate<P> predicate ) { TransitionAction<P> action = Transitions.predicateAsAction( predicate ); this.commit( action ); } public TransitionBuilder addListener( Callback<P> callback ) { TransitionListener<P> listener = Transitions.callbackAsListener( callback ); if ( this.listeners == null ) { this.transition.addListener( listener ); } else { this.listeners.add( listener ); } return this; } public TransitionBuilder addListener( TransitionListener<P>... listeners ) { if ( this.listeners == null ) { for ( TransitionListener<P> l : listeners ) { transition.addListener( l ); } } else { for ( TransitionListener<P> l : listeners ) { this.listeners.add( l ); } } return this; } } protected InStateBuilder in( final S input ) { return new InStateBuilder( ) { { state = input; } }; } protected OutStateBuilder out( final S input ) { return new OutStateBuilder( ) { { state = input; } }; } protected TransitionBuilder on( final T input ) { return new TransitionBuilder( ) { { name = input; } }; } protected TransitionBuilder from( final S input ) { return new TransitionBuilder( ) { { fromState = input; } }; } protected StateMachineBuilder<P, S, T> addTransition( TransitionHandler<P, S, T> transition ) { if ( this.transitions.contains( transition ) ) { throw new IllegalArgumentException( "Duplicate transition named: " + transition.getName( ) ); } else { this.transitions.add( transition ); } return this; } public StateMachine<P, S, T> newAtomicMarkedState( ) { if ( startState == null || parent == null || transitions == null || transitions.isEmpty( ) ) { throw new IllegalStateException( "Call to build() for an ill-formed state machine -- did you finish adding all the transition rules?" ); } this.doChecks( ); return new AtomicMarkedState<P, S, T>( startState, parent, transitions, inStateListeners, outStateListeners ); } private void doChecks( ) { this.immutableStates = ImmutableList.copyOf( this.startState.asEnum.getEnumConstants( this.startState ) ); if ( this.transitions.isEmpty( ) ) { throw new IllegalStateException( "Started state machine with no registered transitions." ); } T transitionName = this.transitions.iterator( ).next( ).getName( ); T[] trans = transitionName.asEnum.getEnumConstants( transitionName ); Map<String, TransitionHandler<P, S, T>> alltransitions = Maps.newHashMap( ); for ( S s1 : this.immutableStates ) { for ( S s2 : this.immutableStates ) { alltransitions.put( String.format( "%s.%s->%s.%s", s1, false, s2, false ), null ); alltransitions.put( String.format( "%s.%s->%s.%s", s1, false, s2, true ), null ); alltransitions.put( String.format( "%s.%s->%s.%s", s1, true, s2, false ), null ); alltransitions.put( String.format( "%s.%s->%s.%s", s1, true, s2, true ), null ); } } LOG.debug( "Starting state machine: " + this.parent.getClass( ).getSimpleName( ) + " " + this.parent.getName( ) ); // for ( S s : this.immutableStates ) { // LOG.debug( "fsm " + this.parent.getName( ) + " state:" + s.name( ) ); // } Multimap<T, TransitionHandler<P, S, T>> transNames = ArrayListMultimap.create( ); for ( TransitionHandler<P, S, T> t : this.transitions ) { transNames.put( t.getName( ), t ); } for ( T t : trans ) { // LOG.debug( "fsm " + this.parent.getName( ) + " transitions:" + ( transNames.containsKey( t ) // ? transNames.get( t ) // : t.name( ) + ":NONE" ) ); for ( TransitionHandler<P, S, T> tr : transNames.get( t ) ) { String trKey = String.format( "%s.%s->%s.%s (err=%s.%s)", tr.getFromState( ), tr.getFromStateMark( ), tr.getToState( ), tr.getToStateMark( ), tr.getErrorState( ), tr.getErrorStateMark( ) ); if ( alltransitions.get( trKey ) != null ) { LOG.error( "Duplicate transition: " + tr + " AND " + alltransitions.get( trKey ) ); throw new IllegalStateException( "Duplicate transition: " + tr + " AND " + alltransitions.get( trKey ) ); } else { alltransitions.put( trKey, tr ); } } } for ( String trKey : alltransitions.keySet( ) ) { if ( alltransitions.get( trKey ) != null ) { LOG.debug( String.format( "fsm %s %-60.60s %s", this.parent.getName( ), trKey, alltransitions.get( trKey ) ) ); } } } public StateMachineBuilder( P parent, S startState ) { super( ); this.parent = parent; this.startState = startState; } static class BasicTransitionRule<S extends Automata.State, T extends Automata.Transition> implements TransitionRule<S, T> { private T name; private S fromState; private S toState; private S errorState; private final Boolean fromStateMark; private final Boolean toStateMark; private final Boolean errorStateMark; protected BasicTransitionRule( T name, S fromState, S toState ) { this.name = name; this.fromState = fromState; this.toState = toState; this.errorState = fromState; this.fromStateMark = false; this.toStateMark = false; this.errorStateMark = false; } protected BasicTransitionRule( T name, S fromState, S toState, S errorState ) { this.name = name; this.fromState = fromState; this.toState = toState; this.errorState = errorState; this.fromStateMark = false; this.toStateMark = false; this.errorStateMark = false; } protected BasicTransitionRule( T name, S fromState, Boolean fromStateMark, S toState, Boolean toStateMark, S errorState, Boolean errorStateMark ) { this.name = name; this.fromState = fromState; this.toState = toState; this.errorState = errorState; this.fromStateMark = fromStateMark; this.toStateMark = toStateMark; this.errorStateMark = errorStateMark; } @Override public final T getName( ) { return this.name; } @Override public final S getFromState( ) { return this.fromState; } @Override public final S getToState( ) { return this.toState; } @Override public final S getErrorState( ) { return this.errorState; } @Override public final Boolean getFromStateMark( ) { return this.fromStateMark; } @Override public final Boolean getToStateMark( ) { return this.toStateMark; } @Override public final Boolean getErrorStateMark( ) { return this.errorStateMark; } @Override public int compareTo( TransitionRule<S, T> that ) { return this.getName( ).compareTo( that.getName( ) ); } @Override public String toString( ) { return String.format( "Transition name=%s from=%s/%s to=%s/%s error=%s", this.getName( ), this.getFromState( ), this.getFromStateMark( ), this.getToState( ), this.getToStateMark( ), this.getErrorState( ) ); } } }