/*
*
* Copyright 2012 lexergen.
* This file is part of lexergen.
*
* lexergen 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, either version 3 of the License, or
* (at your option) any later version.
*
* lexergen 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 lexergen. If not, see <http://www.gnu.org/licenses/>.
*
* lexergen:
* A tool to chunk source code into tokens for further processing in a compiler chain.
*
* Projectgroup: bi, bii
*
* Authors: Daniel Rotar
*
* Module: Softwareprojekt Übersetzerbau 2012
*
* Created: Apr. 2012
* Version: 1.0
*
*/
package de.fuberlin.bii.regextodfaconverter.fsm;
import java.io.Serializable;
import java.util.HashMap;
import java.util.HashSet;
import java.util.UUID;
import de.fuberlin.bii.regextodfaconverter.fsm.excpetions.NullStateException;
import de.fuberlin.bii.regextodfaconverter.fsm.excpetions.StateNotReachableException;
/**
* Stellt einen endlichen Automaten (finite state machine, kurz FSM) dar.
*
* @author Daniel Rotar
*
* @param <TransitionConditionType>
* Der Typ der Bedingung für einen Zustandsübergang.
* @param <StatePayloadType>
* Der Typ des Inhalts der Zustände.
*/
public class FiniteStateMachine<TransitionConditionType extends Serializable, StatePayloadType extends Serializable>
implements Serializable {
/**
* UID für die Serialisierung/Abspeicherung als *.dfa
*/
private static final long serialVersionUID = 7451317869119939422L;
/**
* Der Startzustand dieses endlichen Automatens.
*/
private State<TransitionConditionType, StatePayloadType> _initialState;
/**
* Der aktuelle Zustand, in dem sich dieser endlichen Automatens befindet.
*/
private State<TransitionConditionType, StatePayloadType> _currentState;
/**
* Die HashMap die alle Zustände dieses endlichen Automaten, geordnet nach
* ihren eindetige ID enthält.
*/
private HashMap<UUID, State<TransitionConditionType, StatePayloadType>> _states;
/**
* Gibt der Startzustand dieses endlichen Automatens.
*
* @return Der Startzustand dieses endlichen Automatens.
*/
public State<TransitionConditionType, StatePayloadType> getInitialState() {
return _initialState;
}
/**
* Setzt den Startzustand dieses endlichen Automatens.
*
* @param state
* Der neue Startzustand dieses endlichen Automatens.
* @throws NullStateException
* Wenn null als Parameter für den neuen Zustand übergeben wird.
* @throws StateNotReachableException
* Wenn der Zustand nicht erreichbar oder Teil des endlichen
* Automats ist.
*/
protected void setInitialState(
State<TransitionConditionType, StatePayloadType> initialState)
throws NullStateException, StateNotReachableException {
if (initialState == null)
throw new NullStateException();
if (!containsStateWithUUID(initialState.getUUID()))
throw new StateNotReachableException();
_initialState = initialState;
}
/**
* Gibt den aktuelle Zustand, in dem sich dieser endlichen Automatens
* befindet zurück.
*
* @return Der aktuelle Zustand, in dem sich dieser endlichen Automatens
* befindet.
*/
public State<TransitionConditionType, StatePayloadType> getCurrentState() {
return _currentState;
}
/**
* Setzt den aktuelle Zustand, in dem sich dieser endlichen Automatens
* befindet fest.
*
* @param state
* Der neue Zustand, in dem sich dieser endlichen Automatens
* befinden soll.
* @throws NullStateException
* Wenn null als Parameter für den neuen Zustand übergeben wird.
* @throws StateNotReachableException
* Wenn der Zustand nicht erreichbar oder Teil des endlichen
* Automats ist.
*/
public void setCurrentState(
State<TransitionConditionType, StatePayloadType> state)
throws NullStateException, StateNotReachableException {
if (state == null)
throw new NullStateException();
if (!containsStateWithUUID(state.getUUID()))
throw new StateNotReachableException();
_currentState = state;
}
/**
* Gibt die HashMap die alle Zustände dieses endlichen Automaten, geordnet
* nach ihren eindetige ID enthält zurück..
*
* @return Die HashMap die alle Zustände dieses endlichen Automaten,
* geordnet nach ihren eindetige ID enthält.
*/
public HashMap<UUID, State<TransitionConditionType, StatePayloadType>> getStates() {
return _states;
}
/**
* Setzt die HashMap die alle Zustände dieses endlichen Automaten, geordnet
* nach ihren eindetige ID enthält fest.
*
* @param states
* Die HashMap die alle Zustände dieses endlichen Automaten,
* geordnet nach ihren eindetige ID enthält.
*/
protected void setStates(
HashMap<UUID, State<TransitionConditionType, StatePayloadType>> states) {
_states = states;
}
/**
* Gibt den Zustand zurück, der die angegebene eindeutige UUID hat.
*
* @return Die eindeutige ID des Zustands, der zurückgegeben werden soll.
* Gibt es keinen Zustand, der die angegebene ID enthält wird null
* zurückgegeben.
*/
public State<TransitionConditionType, StatePayloadType> getStateByUUID(
UUID uuid) {
return getStates().get(uuid);
}
/**
* Gibt an ob der endliche Automat einen Zustand mit der angegebenen
* eindeutigen UUID beinhaltet.
*
* @param uuid
* Die UUID nach der in diesem endliche Automaten gesucht werden
* soll.
* @return true, wenn die UUID in dem endliche Automat vorhanden ist, sonst
* false.
*/
public boolean containsStateWithUUID(UUID uuid) {
return getStates().containsKey(uuid);
}
/**
* Gibt an, ob es sich bei diesem endlichen Automaten um einen
* deterministisch endlichen Automaten (deterministic finite automaton, kurz
* DFA) handelt.
*
* @return true, wenn es sich um einen deterministisch endlichen Automaten
* (deterministic finite automaton, kurz DFA) handelt, sonst false.
*/
public boolean isDeterministic() {
for (State<TransitionConditionType, StatePayloadType> state : getStates()
.values()) {
HashSet<TransitionConditionType> conditions = new HashSet<TransitionConditionType>();
for (Transition<TransitionConditionType, StatePayloadType> transition : state
.getTransitions()) {
if (transition.getCondition() == null)
return false; // Epsilon-Übergang vorhanden
if (!conditions.add(transition.getCondition()))
return false; // Übergangsbedingung mehrfach vorhanden.
}
}
return true;
}
/**
* Macht eine Zustandsänderung mit der angebenene Bedingung.
*
* @param condition
* Die Bedingung für die Zustandsänderung.
* @return Der Zustand, der durch diese Zustandsänderung erreicht worden
* ist. Ist keine Zustandsänderung möglich wird null ausgegeben und
* der aktuelle Zustand bleibt unverändert.
* @remarks (1) null-Übergänge bzw. Epislon-Übergänge werden nicht
* berücksichtigt, können aber explizit, wie normale Übergänge
* durch die Angabe von null als Übergangsbedinung durchgeführt
* werden. (2) Gibt es mehr als einen passenden Übergang wird
* standardmäßig der erste passende Übergang ausgewählt.
*/
public State<TransitionConditionType, StatePayloadType> changeState(
TransitionConditionType condition) {
for (Transition<TransitionConditionType, StatePayloadType> transition : getCurrentState()
.getTransitions()) {
if (transition.getCondition() != null) {
if (transition.getCondition().equals(condition)) {
State<TransitionConditionType, StatePayloadType> state = transition
.getState();
try {
setCurrentState(state);
} catch (Exception e) {
// Dieser Fall kann niemals eintreten!
e.printStackTrace();
}
return state;
}
} else {
if (condition == null) {
State<TransitionConditionType, StatePayloadType> state = transition
.getState();
try {
setCurrentState(state);
} catch (Exception e) {
// Dieser Fall kann niemals eintreten!
e.printStackTrace();
}
return state;
}
}
}
return null;
}
/**
* Gibt an, ob eine Zustandsänderung mit der angegebenen Bedingung möglich
* ist.
*
* @param condition
* Die Bedingung für die Zustandsänderung.
* @return true, wenn eine Zustandsänderung möglich ist, sonst false.
* @remarks null-Übergänge bzw. Epislon-Übergänge werden nicht
* berücksichtigt, können aber explizit, wie normale Übergänge
* durch die Angabe von null als Übergangsbedinung überprüft
* werden.
*/
public boolean canChangeState(TransitionConditionType condition) {
for (Transition<TransitionConditionType, StatePayloadType> transition : getCurrentState()
.getTransitions()) {
if (transition.getCondition().equals(condition))
return true;
}
return false;
}
/**
* Setzt den aktuellen Zustand auf den Startzustand zurück.
*/
public void resetToInitialState() {
try {
setCurrentState(getInitialState());
} catch (Exception e) {
// Dieser Fall kann niemals eintreten!
e.printStackTrace();
}
}
/**
* Fügt einen neuen Übergang mit angegebener Bedingung von einem
* Ausgangszustand in einen Zielzustand ein.
*
* @param sourceState
* Der Ausgangszustand, von dem ein neuer Übergang erstellt
* werden soll.
* @param destinationState
* Der Zielzustand, in dem der neue Übergang zeigen soll.
* @param condition
* Die Bedingung für den Zustandsübergang (null für einen
* Epsilon-Übergang).
* @return true, wenn der Übergang (bzw. ein äquivalenter Übergang) noch nicht Vorhanden war, sonst false.
* @throws NullStateException
* Wenn null als Parameter für den Ausgangszustand oder
* Zielzustand übergeben wird.
* @throws StateNotReachableException
* Wenn der Ausgangszustand nicht erreichbar oder Teil des
* endlichen Automats ist.
*/
public boolean addTransition(
State<TransitionConditionType, StatePayloadType> sourceState,
State<TransitionConditionType, StatePayloadType> destinationState,
TransitionConditionType condition) throws NullStateException,
StateNotReachableException {
if (sourceState == null || destinationState == null)
throw new NullStateException();
if (!containsStateWithUUID(sourceState.getUUID()))
throw new StateNotReachableException();
if (!containsStateWithUUID(destinationState.getUUID())) {
getStates().put(destinationState.getUUID(), destinationState);
}
return sourceState.addState(condition, destinationState);
}
/**
* Fügt einen neuen Übergang mit angegebener Bedingung von aktuellen Zustand
* in einen Zielzustand ein.
*
* @param destinationState
* Der Zielzustand, in dem der neue Übergang zeigen soll.
* @param condition
* Die Bedingung für den Zustandsübergang (null für einen
* Epsilon-Übergang).
* @return true, wenn der Übergang (bzw. ein äquivalenter Übergang) noch nicht Vorhanden war, sonst false.
* @throws NullStateException
* Wenn null als Parameter für den Zielzustand übergeben wird.
*/
public boolean addTransition(
State<TransitionConditionType, StatePayloadType> destinationState,
TransitionConditionType condition) throws NullStateException {
if (destinationState == null)
throw new NullStateException();
try {
return addTransition(getCurrentState(), destinationState, condition);
} catch (StateNotReachableException e) {
// Dieser Fall kann niemals eintreten!
e.printStackTrace();
}
return false;
}
/**
* Vereinigt diesen endlichen Automaten mit dem angegebenen endlichen
* Automaten.
*
* @param fsm
* Der endliche Automat, mit dem dieser endliche Automat verenigt
* werden soll.
* @remarks Durch diese Methode wird der aktuelle Zustand auf den
* Startzustand zurückgesetzt.
*/
public void union(
FiniteStateMachine<TransitionConditionType, StatePayloadType> fsm) {
State<TransitionConditionType, StatePayloadType> state = new State<TransitionConditionType, StatePayloadType>();
getStates().put(state.getUUID(), state);
try {
addTransition(state, getInitialState(), null);
} catch (Exception e) {
// Dieser Fall kann niemals eintreten!
e.printStackTrace();
}
getInitialState().setInitial(false);
try {
setInitialState(state);
} catch (Exception e) {
// Dieser Fall kann niemals eintreten!
e.printStackTrace();
}
state.setTypeToInitial();
getStates().putAll(fsm.getStates());
try {
addTransition(state, fsm.getInitialState(), null);
} catch (Exception e) {
// Dieser Fall kann niemals eintreten!
e.printStackTrace();
}
fsm.getInitialState().setInitial(false);
resetToInitialState();
}
/**
* Verbindet diesen endlichen Automaten mit dem angegebenen endlichen
* Automaten.
*
* @param fsm
* Der endliche Automat, mit dem dieser endliche Automat
* verbunden werden soll.
* @remarks Durch diese Methode wird der aktuelle Zustand auf den
* Startzustand zurückgesetzt.
*/
public void concat(
FiniteStateMachine<TransitionConditionType, StatePayloadType> fsm) {
HashMap<UUID, State<TransitionConditionType, StatePayloadType>> states = new HashMap<UUID, State<TransitionConditionType, StatePayloadType>>();
states.putAll(getStates());
for (State<TransitionConditionType, StatePayloadType> state : states
.values()) {
if (state.isFiniteState()) {
// state.setPayload(null);
state.setTypeToDefault();
try {
addTransition(state, fsm.getInitialState(), null);
} catch (Exception e) {
// Dieser Fall kann niemals eintreten!
e.printStackTrace();
}
}
}
getStates().putAll(fsm.getStates());
fsm.getInitialState().setInitial(false);
resetToInitialState();
}
/**
* Fügt diesem eindlichen Automaten eine Wiederholung hinzu.
*
* @remarks Durch diese Methode wird der aktuelle Zustand auf den
* Startzustand zurückgesetzt.
*/
public void closure() {
HashMap<UUID, State<TransitionConditionType, StatePayloadType>> states = new HashMap<UUID, State<TransitionConditionType, StatePayloadType>>();
states.putAll(getStates());
State<TransitionConditionType, StatePayloadType> initState = new State<TransitionConditionType, StatePayloadType>();
State<TransitionConditionType, StatePayloadType> finiteState = new State<TransitionConditionType, StatePayloadType>();
State<TransitionConditionType, StatePayloadType> old_initState = getInitialState();
initState.setTypeToInitial();
finiteState.setTypeToFinite();
getStates().put(initState.getUUID(), initState);
getStates().put(finiteState.getUUID(), finiteState);
try {
addTransition(initState, finiteState, null);
} catch (Exception e) {
// Dieser Fall kann niemals eintreten!
e.printStackTrace();
}
try {
addTransition(initState, getInitialState(), null);
} catch (Exception e) {
// Dieser Fall kann niemals eintreten!
e.printStackTrace();
}
getInitialState().setInitial(false);
try {
setInitialState(initState);
} catch (Exception e) {
// Dieser Fall kann niemals eintreten!
e.printStackTrace();
}
for (State<TransitionConditionType, StatePayloadType> state : states
.values()) {
if (state.isFiniteState()) {
// state.setPayload(null);
finiteState.setPayload(state.getPayload());
state.setTypeToDefault();
try {
addTransition(state, finiteState, null);
} catch (Exception e) {
// Dieser Fall kann niemals eintreten!
e.printStackTrace();
}
try {
addTransition(state, old_initState, null);
} catch (Exception e) {
// Dieser Fall kann niemals eintreten!
e.printStackTrace();
}
}
}
resetToInitialState();
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
for (State<TransitionConditionType, StatePayloadType> state : getStates()
.values()) {
if (state.isInitialState()) {
sb.append(">");
}
sb.append(state.getUUID());
if (state.isFiniteState()) {
sb.append(">");
}
sb.append("\n");
if (state.getTransitions().size() == 0) {
sb.append("\t No outgoing transitions.\n");
} else {
for (Transition<TransitionConditionType, StatePayloadType> tran : state
.getTransitions()) {
sb.append("\t" + tran.getCondition() + " -> "
+ tran.getState().getUUID() + "\n");
}
}
}
return sb.toString();
}
/**
* Erstellt ein neues FiniteStateMachine Objekt. Dabei wird direkt ein
* Startzustand für diesen endlichen Automaten erstellt und als aktuellen
* Zustand gesetzt.
*/
public FiniteStateMachine() {
State<TransitionConditionType, StatePayloadType> state = new State<TransitionConditionType, StatePayloadType>();
state.setTypeToInitial();
setStates(new HashMap<UUID, State<TransitionConditionType, StatePayloadType>>());
getStates().put(state.getUUID(), state);
try {
setInitialState(state);
setCurrentState(state);
} catch (Exception e) {
// Dieser Fall kann niemals eintreten!
e.printStackTrace();
}
}
}