/*
* (C) Copyright 2013 Arnaud Bailly (arnaud.oqube@gmail.com),
* Yves Roos (yroos@lifl.fr) and others.
*
* Licensed under the Apache License, Version 2.0 (the License);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an AS IS BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package rationals.ioautomata;
import rationals.Automaton;
import rationals.Builder;
import rationals.NoSuchStateException;
import rationals.State;
/** Builds transitions containing explicit information about send and/or receive messages. */
public class IOATransitionBuilder implements Builder<Object, IOTransition, IOATransitionBuilder> {
/* unique id, used for completing messages sources/destination */
private final String id;
/*
* counter is used for creating new unique states within this automaton context.
*/
private int counter = 0;
private State currentState;
private IOTransition.IOLetter currentLabel;
private Automaton<Object, IOTransition, IOATransitionBuilder> automaton;
public IOATransitionBuilder(String id) {
this.id = id;
}
/**
* Creates a new {@link IOAutomaton} with a given id.
*
* @param id identifies the created automaton to provide context for messages sent and received.
*
* @return a new (empty) IOAutomaton.
*/
public static IOAutomaton<IOTransition, IOATransitionBuilder> automaton(String id) {
return new IOAutomaton<IOTransition, IOATransitionBuilder>(new IOATransitionBuilder(id));
}
@Override
public IOATransitionBuilder build(State state, Automaton<Object, IOTransition, IOATransitionBuilder> auto) {
this.currentState = state;
this.currentLabel = null;
this.automaton = auto;
return this;
}
@Override
/**
* Starts creating a new {@link IOAlphabetType#INTERNAL} transition from the current state.
* <p>
* If a transition was in progress, through previous calls to {@link #send(Object)} or {@link #receive(Object)}
* then this closes the transition to a new "anonymous" state which becomes the <em>current state</em>.
* </p>
*
* @param message the message which is .
* @return this builder for chaining purpose.
*/
public IOATransitionBuilder on(Object label) {
Object message = label;
if (label instanceof String) {
message = Messages.message("^" + id + "." + message);
}
addStateIfNeeded(message, IOAlphabetType.INTERNAL);
return this;
}
@Override
/**
* This method <strong>does not</strong> change the <em>current state</em> which implies using it after a succession
* of {@link #send(Object)} or {@link #receive(Object)} calls will leave the builder in an intermediary state. Use
* {@link #to(Object)} to explicitly change the current state.
*/
public IOATransitionBuilder go(Object o) {
transitionTo(automaton.state(o));
return this;
}
@Override
public IOATransitionBuilder loop() {
try {
automaton.addTransition(new IOTransition(currentState, currentLabel, currentState));
currentLabel = null;
} catch (NoSuchStateException e) {
throw new IllegalStateException("inconsistent automaton structure, missing state " + currentState + " in " + automaton);
}
return this;
}
@Override
public IOATransitionBuilder from(Object label) {
this.currentState = automaton.state(label);
this.currentLabel = null;
return this;
}
@Override
public IOTransition build(State from, Object label, State to) {
return new IOTransition(from, ((IOTransition.IOLetter) label).label, to, ((IOTransition.IOLetter) label).type);
}
@Override
public void setAutomaton(Automaton<Object, IOTransition, IOATransitionBuilder> a) {
this.automaton = a;
}
/**
* Starts creating a new {@link IOAlphabetType#INPUT} transition from the current state.
*
* <p>If a transition was in progress, through previous calls to {@link #send(Object)} or this method
* then this closes the transition to a new "anonymous" state which becomes the <em>current state</em>.</p>
*
* @param message the message which is input.
*
* @return this builder for chaining purpose.
*/
public IOATransitionBuilder receive(Object message) {
Object label = message;
if (message instanceof String) {
label = Messages.message(id + "<-" + message);
}
addStateIfNeeded(label, IOAlphabetType.INPUT);
return this;
}
/**
* Starts creating a new {@link IOAlphabetType#OUTPUT} transition from the current state.
*
* <p>If a transition was in progress, through previous calls to this method or {@link #receive(Object)}
* then this closes the transition to a new "anonymous" state which becomes the <em>current state</em>.</p>
*
* @param message the message which is output.
*
* @return this builder for chaining purpose.
*/
public IOATransitionBuilder send(Object message) {
Object label = message;
if (message instanceof String) {
label = Messages.message(id + "->" + message);
}
addStateIfNeeded(label, IOAlphabetType.OUTPUT);
return this;
}
private void addStateIfNeeded(Object message, IOAlphabetType output) {
if (currentLabel == null) {
currentLabel = new IOTransition.IOLetter(message, output);
} else {
String newState = hiddenState();
go(newState).from(newState);
currentLabel = new IOTransition.IOLetter(message, output);
}
}
private String hiddenState() {
return "s" + (counter++);
}
/**
* Changes the current state to {@code state} and optionally closes the current transition.
*
* <p>This closes the current transition if called after a label has been set with {@link #send(Object)} or {@link
* #receive(Object)}. Otherwise, it has the same effect than {@link #from(Object)}.</p>
*
* @param state end state of transition to close.
*
* @return this object for chaining.
*/
public IOATransitionBuilder to(Object state) {
State s = automaton.state(state);
if (currentLabel != null) {
transitionTo(s);
}
currentState = s;
return this;
}
private void transitionTo(State s) {
try {
automaton.addTransition(new IOTransition(currentState, currentLabel, s));
} catch (NoSuchStateException e) {
throw new IllegalStateException("no state (" + s + " or " + currentState + ") to build transition");
}
currentLabel = null;
}
}