/**
* Copyright (c) 2002-2012 "Neo Technology,"
* Network Engine for Objects in Lund AB [http://neotechnology.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.neo4j.cluster;
import java.util.ArrayList;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Logger;
import org.neo4j.cluster.com.message.MessageType;
import org.neo4j.cluster.statemachine.State;
import org.neo4j.cluster.statemachine.StateTransition;
import org.neo4j.cluster.statemachine.StateTransitionListener;
public class StateTransitionExpectations<CONTEXT,MESSAGETYPE extends Enum<MESSAGETYPE> & MessageType>
{
public final StateTransitionListener NO_EXPECTATIONS = new StateTransitionListener()
{
@Override
public void stateTransition( StateTransition messagetypeStateTransition )
{
}
};
private final List<ExpectingStateTransitionListener> expectations = new ArrayList<ExpectingStateTransitionListener>();
public ExpectationsBuilder newExpectations( Enum<?>... initialAlternatingExpectedMessageAndState )
{
ExpectationsBuilder builder = new ExpectationsBuilder();
for ( int i = 0; i < initialAlternatingExpectedMessageAndState.length; i++ )
builder.expect( (MessageType) initialAlternatingExpectedMessageAndState[i++], (State) initialAlternatingExpectedMessageAndState[i] );
return builder;
}
public void verify()
{
StringBuilder builder = new StringBuilder( );
for ( ExpectingStateTransitionListener listener : expectations )
listener.transitionHistory( builder );
if (builder.length() != 0)
{
throw new IllegalStateException( "Failed expectations:"+builder.toString() );
}
}
public boolean areFulfilled()
{
for ( ExpectingStateTransitionListener listener : expectations )
if ( !listener.isFulfilled() )
return false;
return true;
}
public void printRemaining( Logger logger )
{
StringBuilder builder = new StringBuilder( );
builder.append( "=== Remaining state transitions ===\n" );
for( ExpectingStateTransitionListener expectation : expectations )
{
expectation.printRemaining(builder);
}
logger.info( builder.toString() );
}
public class ExpectationsBuilder
{
private final Deque<ExpectedTransition> transitions = new LinkedList<ExpectedTransition>();
private boolean includeUnchanged;
public ExpectationsBuilder expect( MessageType messageToGetHere, State state )
{
transitions.add( new ExpectedTransition( messageToGetHere, state ) );
return this;
}
public ExpectationsBuilder includeUnchangedStates()
{
this.includeUnchanged = true;
return this;
}
public StateTransitionListener build( Object id )
{
ExpectingStateTransitionListener listener = new ExpectingStateTransitionListener( new LinkedList<ExpectedTransition>( transitions ), includeUnchanged, id );
expectations.add( listener );
return listener;
}
public void assertNoMoreExpectations()
{
if ( !transitions.isEmpty() )
throw new IllegalStateException( "Unsatisfied transitions: " + transitions );
}
}
private class ExpectingStateTransitionListener implements StateTransitionListener
{
private final Deque<String> transitionHistory = new LinkedList<String>();
private final Deque<ExpectedTransition> transitions;
private volatile boolean valid = true;
private final Object id;
private final boolean includeUnchanged;
ExpectingStateTransitionListener( Deque<ExpectedTransition> transitions, boolean includeUnchanged, Object id )
{
this.transitions = transitions;
this.includeUnchanged = includeUnchanged;
this.id = id;
}
@Override
public void stateTransition( StateTransition transition )
{
if (valid || transitions.isEmpty())
{
if ( !includeUnchanged && transition.getOldState().equals( transition.getNewState() ) )
return;
if ( transitions.isEmpty())
{
valid = false;
transitionHistory.add( "UNEXPECTED:" + transition );
} else
{
ExpectedTransition expected = transitions.pop();
if ( expected.matches( transition ) )
{
transitionHistory.add( expected.toString() );
} else
{
transitionHistory.add( "EXPECTED " + expected + ", GOT " + transition );
valid = false;
}
}
}
}
void transitionHistory(StringBuilder builder)
{
if (valid && transitions.isEmpty())
return;
builder.append( "\n=== Failed state transition expectations for " ).append( id );
for( String transition : transitionHistory )
{
builder.append( "\n " ).append( transition );
}
if (valid)
{
for( ExpectedTransition transition : transitions )
{
builder.append( "\n " ).append( "MISSING ").append( transition );
}
}
}
void printRemaining( StringBuilder builder )
{
builder.append( "== " ).append( id ).append( "\n" );
for( ExpectedTransition transition : transitions )
{
builder.append( transition.toString() ).append( "\n" );
}
}
public boolean isFulfilled()
{
return valid && transitions.isEmpty();
}
}
private class ExpectedTransition
{
private final MessageType messageToGetHere;
private final State state;
ExpectedTransition( MessageType messageToGetHere, State state )
{
this.messageToGetHere = messageToGetHere;
this.state = state;
}
public boolean matches( StateTransition transition )
{
return state.equals( transition.getNewState() ) && messageToGetHere.equals( transition.getMessage().getMessageType() );
}
@Override
public String toString()
{
return messageToGetHere + "->" + state;
}
}
}