/*
*
* 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;
import java.io.Serializable;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Queue;
import java.util.UUID;
import de.fuberlin.bii.regextodfaconverter.fsm.FiniteStateMachine;
import de.fuberlin.bii.regextodfaconverter.fsm.State;
import de.fuberlin.bii.regextodfaconverter.fsm.StatePayload;
import de.fuberlin.bii.regextodfaconverter.fsm.Transition;
/**
* Stellt einen Konverter dar, der aus einem nichtdeterministischen endlichen
* Automaten (nondeterministic finite automaton, kurz NFA) einen neuen
* deterministischen endlichen Automaten (deterministic finite automaton, kurz
* DFA) erstellt.
*
* @author Daniel Rotar
*
* @param <TransitionConditionType>
* Der Typ der Bedingung für einen Zustandsübergang beim verwendeten
* endlichen Automaten.
* @param <StatePayloadType>
* Der Typ des Inhalts der Zustände beim verwendeten endlichen
* Automaten.
*/
public class NfaToDfaConverter<TransitionConditionType extends Serializable, StatePayloadType extends Serializable> {
/**
* Macht aus dem angegebenen endlichen Automaten einen deterministischen
* endlichen Automaten (deterministic finite automaton, kurz DFA).
*
* @param finiteStateMachine
* Der endliche Automat, aus dem ein DFA erstellt werden soll.
* @return Der DFA, der aus dem angegebenen endlichen Automaten erstellt
* worden ist.
*/
public FiniteStateMachine<TransitionConditionType, StatePayloadType> convertToDfa(
FiniteStateMachine<TransitionConditionType, StatePayloadType> finiteStateMachine) {
FiniteStateMachine<TransitionConditionType, StatePayloadType> dfa = new FiniteStateMachine<TransitionConditionType, StatePayloadType>();
HashMap<HashMap<UUID, State<TransitionConditionType, StatePayloadType>>, State<TransitionConditionType, StatePayloadType>> mergedStates = new HashMap<HashMap<UUID, State<TransitionConditionType, StatePayloadType>>, State<TransitionConditionType, StatePayloadType>>();
Queue<DFATask> tasks = new LinkedList<DFATask>();
HashMap<UUID, State<TransitionConditionType, StatePayloadType>> initialNFAStates = getAllStatesByCondition(
finiteStateMachine.getInitialState(), null);
initialNFAStates.put(finiteStateMachine.getInitialState().getUUID(),
finiteStateMachine.getInitialState());
setFiniteStates(dfa.getInitialState(), initialNFAStates);
tasks.add(new DFATask(dfa.getInitialState(), initialNFAStates));
mergedStates.put(initialNFAStates, dfa.getInitialState());
while (!tasks.isEmpty()) {
DFATask task = tasks.poll();
HashSet<TransitionConditionType> conditions = new HashSet<TransitionConditionType>();
for (State<TransitionConditionType, StatePayloadType> state : task
.getNFAStates().values()) {
conditions.addAll(getAllConditions(state));
}
for (TransitionConditionType condition : conditions) {
if (condition != null) {
HashMap<UUID, State<TransitionConditionType, StatePayloadType>> reachableStates = new HashMap<UUID, State<TransitionConditionType, StatePayloadType>>();
for (State<TransitionConditionType, StatePayloadType> state : task
.getNFAStates().values()) {
reachableStates.putAll(getAllStatesByCondition(state,
condition));
}
if (!mergedStates.containsKey(reachableStates)) {
State<TransitionConditionType, StatePayloadType> state = new State<TransitionConditionType, StatePayloadType>();
setFiniteStates(state, reachableStates);
mergedStates.put(reachableStates, state);
tasks.add(new DFATask(state, reachableStates));
}
try {
dfa.addTransition(task.getDFAState(),
mergedStates.get(reachableStates), condition);
} catch (Exception e) {
// Dieser Fall kann niemals eintreten!
e.printStackTrace();
}
}
}
}
return dfa;
}
private void setFiniteStates(
State<TransitionConditionType, StatePayloadType> state,
HashMap<UUID, State<TransitionConditionType, StatePayloadType>> reachableStates) {
for (State<TransitionConditionType, StatePayloadType> s : reachableStates
.values()) {
if (s.isFiniteState()) {
state.setFinite(true);
if (state.getPayload() == null) {
state.setPayload(s.getPayload());
}
if (s.getPayload() != null) {
if (s.getPayload() instanceof StatePayload) {
if (state.getPayload() instanceof StatePayload) {
if (((StatePayload) s.getPayload()).getPriority() > ((StatePayload) state
.getPayload()).getPriority()) {
state.setPayload(s.getPayload());
}
}
}
}
}
}
}
/**
* Gibt ausgehend von einem Zustand alle erreichbaren Zustände, die durch
* die Bedingung und anschließend beliebig vieler Epsilon-Übergänge
* erreichbar sind zurück.
*
* @param state
* Der Ausgangszustand.
* @param condition
* Die Bedingung für den Zustandübergang.
* @return Alle Zustände die durch die Bedingung und anschließend beliebig
* vieler Epsilonübergänge erreichbar sind.
*/
private HashMap<UUID, State<TransitionConditionType, StatePayloadType>> getAllStatesByCondition(
State<TransitionConditionType, StatePayloadType> state,
TransitionConditionType condition) {
int depth = 0;
HashMap<UUID, State<TransitionConditionType, StatePayloadType>> visited = new HashMap<UUID, State<TransitionConditionType, StatePayloadType>>();
Queue<State<TransitionConditionType, StatePayloadType>> tasks = new LinkedList<State<TransitionConditionType, StatePayloadType>>();
tasks.add(state);
while (!tasks.isEmpty()) {
State<TransitionConditionType, StatePayloadType> task = tasks
.poll();
for (Transition<TransitionConditionType, StatePayloadType> transition : task
.getTransitions()) {
if (transition.getCondition() == null) {
if (depth > 0 || condition == null) {
if (!visited.containsKey(transition.getState()
.getUUID())) {
visited.put(transition.getState().getUUID(),
transition.getState());
tasks.add(transition.getState());
}
}
} else {
if (depth == 0) {
if (transition.getCondition().equals(condition)) {
if (!visited.containsKey(transition.getState()
.getUUID())) {
visited.put(transition.getState().getUUID(),
transition.getState());
tasks.add(transition.getState());
}
}
}
}
}
depth++;
}
return visited;
}
/**
* Gibt für den angegebenen Zustand alle möglichen Bedingungen für einen
* Zustandwechsel zurück (Ein null-Übergang bzw. Epsilon-Übergang wird als
* eine "normale" Bedingung behandelt).
*
* @param state
* Der Ausgangszustand.
* @return Alle Bedingungen mit denen ein neuer Zustand erreicht werden
* kann.
*/
private HashSet<TransitionConditionType> getAllConditions(
State<TransitionConditionType, StatePayloadType> state) {
HashSet<TransitionConditionType> conditions = new HashSet<TransitionConditionType>();
for (Transition<TransitionConditionType, StatePayloadType> transition : state
.getTransitions()) {
conditions.add(transition.getCondition());
}
return conditions;
}
/**
* Stellt ein Aufgabenobjekt beim Konvertieren eines NFA zu einem DFA dar.
*
* @author Daniel Rotar
*
*/
private class DFATask {
/**
* Der Zustand im DFA.
*/
private State<TransitionConditionType, StatePayloadType> _dfaState;
/**
* Die Zustände in dem NFA.
*/
private HashMap<UUID, State<TransitionConditionType, StatePayloadType>> _nfaStates;
/**
* Gibt den Zustand im DFA zurück.
*
* @return Der Zustand im DFA.
*/
public State<TransitionConditionType, StatePayloadType> getDFAState() {
return _dfaState;
}
/**
* Setzt den Zustand im DFA fest.
*
* @param dfaState
* Der Zustand im DFA.
*/
private void setDFAState(
State<TransitionConditionType, StatePayloadType> dfaState) {
_dfaState = dfaState;
}
/**
* Gibt die Zustände in dem NFA zurück.
*
* @return Die Zustände in dem NFA.
*/
public HashMap<UUID, State<TransitionConditionType, StatePayloadType>> getNFAStates() {
return _nfaStates;
}
/**
* Setzt die Zustände in dem NFA fest.
*
* @param nfaStates
* Die Zustände in dem NFA.
*/
private void setNFAStates(
HashMap<UUID, State<TransitionConditionType, StatePayloadType>> nfaStates) {
_nfaStates = nfaStates;
}
public DFATask(
State<TransitionConditionType, StatePayloadType> dfaState,
HashMap<UUID, State<TransitionConditionType, StatePayloadType>> nfaStates) {
setDFAState(dfaState);
setNFAStates(nfaStates);
}
}
}