/*
*
* 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: Johannes Dahlke
*
* Module: Softwareprojekt Übersetzerbau 2012
*
* Created: Apr. 2012
* Version: 1.0
*
*/
package de.fuberlin.bii.regextodfaconverter;
import de.fuberlin.bii.regextodfaconverter.fsm.FiniteStateMachine;
import de.fuberlin.bii.regextodfaconverter.fsm.State;
import de.fuberlin.bii.regextodfaconverter.fsm.excpetions.NullStateException;
import de.fuberlin.bii.regextodfaconverter.fsm.excpetions.StateNotReachableException;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.UUID;
/**
* Stellt einen Konverter dar, der aus aus einem deterministischen endlichen
* Automaten (deterministic finite automaton, kurz DFA) einen neuen minimalen
* deterministischen endlichen Automaten erstellt.
*
* @author Alexander Niemeier, Philipp Schröter
*
* @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 DfaMinimizer<TransitionConditionType extends Serializable, StatePayloadType extends Serializable> {
private int startPos = 0;
private FiniteStateMachine<TransitionConditionType, StatePayloadType> input; //Eingabe
private HashMap<UUID, State<TransitionConditionType, StatePayloadType>> originalStates; //enthält die ursprünglichen Zustände der Eingabe, die bearbeitet werden
private boolean initialeStateIsMarked; //gibt an, ob der Anfangszustand markiert ist
private UUID[] states; //enthält alle Zustandsbezeichnungen
private LinkedList<String> transitions; //enthält alle Übergänge, die vorhanden bzw. möglich sind
private HashMap<String, String> transitionsMap; //enthält alle Zustände und Übergänge (Schema: Zustand$Übergang) und als Wert den Zielzustand
private LinkedList<String> markedPairs; //enthält alle markierten Paare, die nicht verschmolzen werden können
private LinkedList<String> unmarkedPairs; //enthält alle unmarkierten Paare, die eventuell verschmolzen werden können
/**
* Macht aus dem angegebenen (deterministischen) endlichen Automaten einen minimalen deterministischen endlichen Automaten.
* @param finiteStateMachine Der (deterministischen) endlichen Automaten der minimiert werden soll.
* @return Der minimierte deterministischen endlichen Automaten.
*/
public FiniteStateMachine<TransitionConditionType, StatePayloadType> convertToMimimumDfa(
FiniteStateMachine<TransitionConditionType, StatePayloadType> finiteStateMachine)
throws NotDeterministicException {
this.input = finiteStateMachine;
this.transitions = new LinkedList<String>();
//hole die Zustände und überführe sie in ein Array vom Typ UUID
this.originalStates = finiteStateMachine.getStates();
Collection<State<TransitionConditionType, StatePayloadType>> collect = this.originalStates.values();
this.states = new UUID[collect.size()];
Iterator<State<TransitionConditionType, StatePayloadType>> iterCollection = collect.iterator();
int counter = 0;
while(iterCollection.hasNext()){
this.states[counter] = iterCollection.next().getUUID();
counter++;
}
//prüfe, ob es kein deterministischer Automat ist
if (!finiteStateMachine.isDeterministic())
{
throw new NotDeterministicException("the delivered finite state machine is not deterministic");
}
//trenne die Zustände und die Übergänge
String[] finiteStatesString = finiteStateMachine.toString().split("\n");
//erstelle eine Map der Zustände und möglicher Übergänge mitsamt der neuen Zustände
startPos = finiteStatesString.length;
filterTransitions(finiteStatesString); //wird nicht mehr gebraucht
//erstelle eine Liste mit allen disjunkten Zustandspaaren
LinkedList<String> statePairList = new LinkedList<String>();
LinkedList<UUID> doneList = new LinkedList<UUID>();
for(int i = 0; i < this.states.length; i++){
for(int j = 0; j < this.states.length; j++){
boolean alreadyDoneOrEqual = false;
if(!this.states[i].equals(this.states[j])){
Iterator<UUID> iter = doneList.iterator();
while(iter.hasNext()){
if(this.states[j].equals(iter.next())){
alreadyDoneOrEqual = true;
break;
}
}
}
else{
alreadyDoneOrEqual = true;
}
if(!alreadyDoneOrEqual){
statePairList.add(this.states[i].toString()+"$"+this.states[j].toString());
doneList.add(this.states[i]);
}
}
}
//die Tabelle, wo die Markierungen gesetzt werden, wo genau eine ein Endzustand ist, erstellen
this.markedPairs = new LinkedList<String>();
this.unmarkedPairs = new LinkedList<String>();
//bilde die Zustandspaare
Iterator<String> iIter = statePairList.iterator();
while(iIter.hasNext()){
String[] currentPair = iIter.next().split("\\$");
//und wenn genau einer von beiden ein Endzustand ist...
State<TransitionConditionType, StatePayloadType> current1 = finiteStateMachine.getStateByUUID(UUID.fromString(currentPair[0]));
State<TransitionConditionType, StatePayloadType> current2 = finiteStateMachine.getStateByUUID(UUID.fromString(currentPair[1]));
if( (current1.isFiniteState() && (!current2.isFiniteState())) ||
(!current1.isFiniteState()) && (current2.isFiniteState()) ){
//wenn einer der beiden ein Endzustand ist
//markiere ihn
this.markedPairs.addFirst(currentPair[0]+"$"+currentPair[1]);
}
else{
//ansonsten vermerke, ihn weil er noch benötigt wird
this.unmarkedPairs.addFirst(currentPair[0]+"$"+currentPair[1]);
}
}
//prüfe, welche Eingaben sich gleichen, um diese Zustände zu verschmelzen
while(markStates()){}
//erstelle alle Felder neu mit dem Inhalt aus den alten
if(this.unmarkedPairs.isEmpty()){ //wenn es keine Einträge gibt, ist der Automat bereits minimal
return finiteStateMachine; //dann wird einfach die Maschine neu erzeugt und zurückgegeben
}
//prüfe, ob der Startzustand markiert ist
Iterator<String> iterStart = this.markedPairs.iterator();
this.initialeStateIsMarked = false;
while(iterStart.hasNext()){
String[] currentMarkedPair = iterStart.next().split("\\$");
State<TransitionConditionType, StatePayloadType> currentMarkedState1 = this.input.getStateByUUID(UUID.fromString(currentMarkedPair[0]));
State<TransitionConditionType, StatePayloadType> currentMarkedState2 = this.input.getStateByUUID(UUID.fromString(currentMarkedPair[1]));
if(currentMarkedState1.isInitialState()){
this.initialeStateIsMarked = true;
}
if(currentMarkedState2.isInitialState()){
this.initialeStateIsMarked = true;
}
}
//sorge dafür, dass der Anfangszustand in der Reihenfolge der markierten Zustände vorne ist
//im nächsten Schritt, werden die Zustände in der Matrix so verändert, dass ein Teil der Zustände nicht mehr angegangen wird und der Anfangszustand soll nicht verschwinden
boolean initialNotFound = false;
int runs = 0;
if(this.initialeStateIsMarked){
while(!initialNotFound && runs < this.markedPairs.size()){
String nextMarkedState = this.markedPairs.peekFirst();
String[] currentMarkedPair = nextMarkedState.split("\\$");
if(currentMarkedPair[1].matches(this.input.getInitialState().getUUID().toString())){
this.markedPairs.remove(currentMarkedPair[0]+"$"+currentMarkedPair[1]);
this.markedPairs.add(currentMarkedPair[1]+"$"+currentMarkedPair[0]);
initialNotFound = true;
}
runs++;
}
}
else{
while(!initialNotFound && runs < this.unmarkedPairs.size()){
String nextMarkedState = this.unmarkedPairs.peekFirst();
String[] currentMarkedPair = nextMarkedState.split("\\$");
if(currentMarkedPair[1].matches(this.input.getInitialState().getUUID().toString())){
this.unmarkedPairs.remove(currentMarkedPair[0]+"$"+currentMarkedPair[1]);
this.unmarkedPairs.add(currentMarkedPair[1]+"$"+currentMarkedPair[0]);
initialNotFound = true;
}
runs++;
}
}
if(!this.unmarkedPairs.isEmpty()){
//gehe durch die Zustände und prüfe, welchem Zustand gegangen wird, der nachher nicht mehr existert
for(int i = 0; i < states.length; i++){
Iterator<String> iterTransitions = this.transitions.iterator();
while(iterTransitions.hasNext()){
Iterator<String> iterUnmarkedStatePairs = this.unmarkedPairs.iterator();
String currentTransition = iterTransitions.next();
while(iterUnmarkedStatePairs.hasNext()){
String[] currentUnmarkedPair = iterUnmarkedStatePairs.next().split("\\$");
//wenn der Zustand in einen Zustand führt, den es später nicht mehr geben wird
String currentDestination = this.transitionsMap.get(states[i].toString()+"$"+currentTransition);
if(currentDestination != null){
if(currentDestination.matches(currentUnmarkedPair[1])){
//ersetze ihn durch den, der es später wird
this.transitionsMap.put(states[i].toString()+"$"+currentTransition, currentUnmarkedPair[0]);
}
}
}
}
}
}
//erstelle den neuen Automaten
return finiteStateMachine;
}
/**
* Erstellt die HashMap für transitionsMap, die Schlüssel haben das Schema: Zustand$Übergang und die Werte enthalten den Zustand, zu dem man gelangt
* @param states Die Statuse mitsamt Übergängen
*
*/
private void filterTransitions(String[] statesString){
this.transitionsMap = new HashMap<String, String>();
//parse das String Array
for(int i = startPos; i < statesString.length; i++){
//wenn der String nicht mit einem Tab beginnt, also ein Zustand ist
if(!statesString[i].matches("^\\t")){
for(int j = i+1; j < statesString.length; j++){
if(statesString[j].matches("^\\t.+")){
if(!statesString[j].matches(".+No outgoing transitions.+")){
String noTab = statesString[j].replace("\t", ""); //entferne den Tab
String[] transitionAndNewState = noTab.split(" -> "); //entferne den Pfeil
addTransition(transitionAndNewState[0]); //füge den Übergang in die Liste hinzu, falls sie nicht schon vorhanden ist
this.transitionsMap.put(statesString[i].replace(">", "")+"$"+transitionAndNewState[0], transitionAndNewState[1]); //speicher die Angaben in der Hashmap
}
}
else{
//wenn ein neuer Zustand kommt, brich ab und überspringe alle bisher gegangenen Stellen
i = j-1;
break;
}
}
}
}
}
/**
* Fügt Übergänge in die Liste der Übergänge hinzu, i.e. transitions
*/
private void addTransition(String transition){
Iterator<String> iterTrans = this.transitions.iterator();
boolean isInList = false;
while(iterTrans.hasNext()){
if(iterTrans.next().matches(transition)){
isInList = true;
break;
}
}
if(!isInList){
this.transitions.add(transition);
}
}
/**
* Durchläuft alle Zustandspaare in unmarkedPairs und prüft, welche noch markiert werden können
* @return Gibt zurück, ob ein Zustandspaar gefunden wurde, der markiert wurde
*/
private boolean markStates(){
//initialisiere die Rückgabe
boolean output = false;
//der Iterator, der durch die Übergänge geht
Iterator<String> iterTransitions = this.transitions.iterator();
//gehe durch die Übergänge
while(iterTransitions.hasNext()){
String currentTransition = iterTransitions.next();
//gehe durch die bisher unmarkierten Zustandspaare
Iterator<String> iterUnmarked = this.unmarkedPairs.iterator();
while(iterUnmarked.hasNext()){
//hole das nächste (bisher) unmarkierte Element zum Vergleichen
String[] currentStates = iterUnmarked.next().split("\\$");
String goalState1 = this.transitionsMap.get(currentStates[0]+"$"+currentTransition);
String goalState2 = this.transitionsMap.get(currentStates[1]+"$"+currentTransition);
//prüfe, ob die Zustände in die gegangen wird, markiert sind
if(this.markedPairs.contains(goalState1+"$"+goalState2) || this.markedPairs.contains(goalState2+"$"+goalState1)){
this.unmarkedPairs.remove(currentStates[0]+"$"+currentStates[1]);
this.markedPairs.add(currentStates[1]+"$"+currentStates[1]);
//wenn etwas markiert wurde, gib true zurück, um anzuzeigen, dass etwas markiert wurde
output = true;
break;
}
}
}
return output;
}
/**
*
* @param noUnmarkedStates Gibt an, ob Zustandspaare unmarkiert sind
* @return Der Automat, der zurückgegeben wird
*/
@SuppressWarnings("unused")
private FiniteStateMachine<TransitionConditionType, StatePayloadType> createFiniteStateMachine(boolean noUnmarkedStates){
FiniteStateMachine<TransitionConditionType, StatePayloadType> output = new FiniteStateMachine<TransitionConditionType, StatePayloadType>();
if(noUnmarkedStates){
//wenn keine Zustände markiert sind, überführe einfach die Zustände der Input-Maschine in eine neue
Collection<State<TransitionConditionType, StatePayloadType>> collection = this.input.getStates().values();
LinkedList<State<TransitionConditionType, StatePayloadType>> toAddStates = new LinkedList<State<TransitionConditionType, StatePayloadType>>();
//sortiere die Zustände in eine LinkedList
Iterator<State<TransitionConditionType, StatePayloadType>> iterCollection = collection.iterator();
while(iterCollection.hasNext()){
toAddStates.addFirst(iterCollection.next());
}
//gehe die Zustände durch und versuche sie in den Automaten zu packen
while(!toAddStates.isEmpty()){
//hole das nächste Element
String currentState = toAddStates.pollFirst().getUUID().toString();
//erstelle einen Iterator der ausgehenden Übergänge
Iterator<TransitionConditionType> iterTrans = this.input.getStateByUUID(UUID.fromString(currentState)).getElementsOfOutgoingTransitions().iterator();
while(iterTrans.hasNext()){
//prüfe, ob man von diesem Zustand mit diesem Übergang zu einem anderen Zustand kommt
TransitionConditionType currentTransition = iterTrans.next();
String currentDestination = this.transitionsMap.get(currentState+"$"+currentTransition.toString());
if(currentDestination != null){
try {
output.addTransition(this.input.getStateByUUID(UUID.fromString(currentState)), this.input.getStateByUUID(UUID.fromString(currentDestination)), currentTransition);
} catch (NullStateException e) {
e.printStackTrace();
} catch (StateNotReachableException e) {
//schiebe diesen Zustand vorerst ans Ende der Liste
toAddStates.addLast(this.input.getStateByUUID(UUID.fromString(currentState)));
break;
}
}
}
}
return output;
}
else{
//ansonsten erstelle eine neue Maschine mit den Veränderungen
if(this.initialeStateIsMarked){
//gehe zuerst die Zustände durch, die verschmolzen werden können, weil dort der Anfangszustand ist, und versuche sie in den Automaten zu packen
LinkedList<String> markedStates = getStatesFromPairList(this.markedPairs);
//beginne damit den Anfangszustand zu finden
Iterator<String> iterFindStart = markedStates.iterator();
String start = this.input.getInitialState().getUUID().toString();
while(iterFindStart.hasNext()){
String currentState = iterFindStart.next();
if(currentState.matches(start)){
Collection<TransitionConditionType> startTrans = this.input.getInitialState().getElementsOfOutgoingTransitions();
Iterator<TransitionConditionType> iterStartTrans = startTrans.iterator();
while(iterStartTrans.hasNext()){
TransitionConditionType currentTransition = iterStartTrans.next();
String currentDestination = this.transitionsMap.get(currentState+"$"+currentTransition.toString());
if(currentDestination != null){
try {
output.addTransition(output.getInitialState(), this.input.getStateByUUID(UUID.fromString(currentDestination)), currentTransition);
} catch (NullStateException e) {
e.printStackTrace();
} catch (StateNotReachableException e) {
}
}
}
}
}
//entferne den Startzustand
markedStates.remove(start);
//füge dann die restlichen markierten in den neuen Automaten ein
while(!markedStates.isEmpty()){
String currentState = markedStates.pollFirst();
Collection<TransitionConditionType> transitionsOfCurrentState = this.input.getStateByUUID(UUID.fromString(currentState)).getElementsOfOutgoingTransitions();
Iterator<TransitionConditionType> iterTrans = transitionsOfCurrentState.iterator();
while(iterTrans.hasNext()){
TransitionConditionType currentTransition = iterTrans.next();
String currentDestination = this.transitionsMap.get(currentState+"$"+currentTransition.toString());
//if(currentDestination != null){
try {
output.addTransition(this.input.getStateByUUID(UUID.fromString(currentState)), this.input.getStateByUUID(UUID.fromString(currentDestination)), currentTransition);
} catch (NullStateException e) {
e.printStackTrace();
} catch (StateNotReachableException e) {
markedStates.addLast(currentState);
break;
}
//}
}
}
//und danach die unmarkierten
//überführe einfach die markierten Zustände der Input-Maschine in eine neue
//gehe die Zustände durch und versuche sie in den Automaten zu packen
while(!this.unmarkedPairs.isEmpty()){
String[] currentPair = this.unmarkedPairs.pollFirst().split("\\$");
Collection<TransitionConditionType> transitionsOfCurrentState = this.input.getStateByUUID(UUID.fromString(currentPair[0])).getElementsOfOutgoingTransitions();
Iterator<TransitionConditionType> iterTrans = transitionsOfCurrentState.iterator();
while(iterTrans.hasNext()){
TransitionConditionType currentTransition = iterTrans.next();
String currentDestination = this.transitionsMap.get(currentPair[0]+"$"+currentTransition.toString());
//if(currentDestination != null){
try {
output.addTransition(this.input.getStateByUUID(UUID.fromString(currentPair[0])), this.input.getStateByUUID(UUID.fromString(currentDestination)), currentTransition);
} catch (NullStateException e) {
e.printStackTrace();
} catch (StateNotReachableException e) {
this.unmarkedPairs.addLast(currentPair[0]+"$"+currentPair[1]);
break;
}
//}
}
}
return output;
}
else{
//überführe einfach die unmarkierten Zustände der Input-Maschine in eine neue
//gehe die Zustände durch und versuche sie in den Automaten zu packen
//beginne damit den Anfangszustand zu finden
Iterator<String> iterFindStart = this.unmarkedPairs.iterator();
String start = this.input.getInitialState().getUUID().toString();
while(iterFindStart.hasNext()){
String[] currentPair = iterFindStart.next().split("\\$");
if(currentPair[0].matches(start)){
Collection<TransitionConditionType> startTrans = this.input.getInitialState().getElementsOfOutgoingTransitions();
Iterator<TransitionConditionType> iterStartTrans = startTrans.iterator();
while(iterStartTrans.hasNext()){
TransitionConditionType currentTransition = iterStartTrans.next();
String currentDestination = this.transitionsMap.get(currentPair[0]+"$"+currentTransition.toString());
//if(currentDestination != null){
try {
output.addTransition(output.getInitialState(), this.input.getStateByUUID(UUID.fromString(currentDestination)), currentTransition);
} catch (NullStateException e) {
e.printStackTrace();
} catch (StateNotReachableException e) {
//tue nichts
}
//entferne den Startzustand
this.unmarkedPairs.remove(this.input.getInitialState().getUUID().toString()+"$"+currentDestination);
//}
}
}
}
while(!this.unmarkedPairs.isEmpty()){
String[] currentPair = this.unmarkedPairs.pollFirst().split("\\$");
Collection<TransitionConditionType> transitionsOfCurrentState = this.input.getStateByUUID(UUID.fromString(currentPair[0])).getElementsOfOutgoingTransitions();
Iterator<TransitionConditionType> iterTrans = transitionsOfCurrentState.iterator();
while(iterTrans.hasNext()){
TransitionConditionType currentTransition = iterTrans.next();
String currentDestination = this.transitionsMap.get(currentPair[0]+"$"+currentTransition.toString());
//if(currentDestination != null){
try {
output.addTransition(this.input.getStateByUUID(UUID.fromString(currentPair[0])), this.input.getStateByUUID(UUID.fromString(currentDestination)), currentTransition);
} catch (NullStateException e) {
e.printStackTrace();
} catch (StateNotReachableException e) {
this.unmarkedPairs.addLast(currentPair[0]+"$"+currentPair[1]);
break;
}
//}
}
}
//und danach die markierten
LinkedList<String> markedStates = getStatesFromPairList(this.markedPairs);
//füge dann die restlichen markierten in den neuen Automaten ein
while(!markedStates.isEmpty()){
String currentState = markedStates.pollFirst();
Collection<TransitionConditionType> transitionsOfCurrentState = this.input.getStateByUUID(UUID.fromString(currentState)).getElementsOfOutgoingTransitions();
Iterator<TransitionConditionType> iterTrans = transitionsOfCurrentState.iterator();
while(iterTrans.hasNext()){
TransitionConditionType currentTransition = iterTrans.next();
String currentDestination = this.transitionsMap.get(currentState+"$"+currentTransition.toString());
if(currentDestination != null){
try {
output.addTransition(this.input.getStateByUUID(UUID.fromString(currentState)), this.input.getStateByUUID(UUID.fromString(currentDestination)), currentTransition);
} catch (NullStateException e) {
e.printStackTrace();
} catch (StateNotReachableException e) {
markedStates.addLast(currentState);
break;
}
}
}
}
return output;
}
}
}
/**
*
* @param PairList Die Liste mit Paaren von Zuständen, die auseinander gebracht werden sollen
* @return Die Liste, die alle disjunkten Vorkommen von Zuständen enthält
*/
private LinkedList<String> getStatesFromPairList(LinkedList<String> PairList){
LinkedList<String> output = new LinkedList<String>();
Iterator<String> iter = PairList.iterator();
while(iter.hasNext()){
String[] currentPair = iter.next().split("\\$");
if(!output.contains(currentPair[0])){
output.add(currentPair[0]);
}
if(!output.contains(currentPair[1])){
output.add(currentPair[1]);
}
}
return output;
}
}