package pl.edu.amu.wmi.daut.base; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.HashMap; import java.util.LinkedList; import java.util.Set; import java.util.Stack; /** * Klasa abstrakcyjna reprezentująca specyfikację (opis) automatu * (jakie są stany, przejścia, który stan jest stanem początkowym, * które stany są stanami akceptującymi). * * Uwaga: klasa ta nie reprezentuje działającego automatu (nie ma tu funkcji * odpowiadających na pytanie, czy automat akceptuje napis, czy nie), * tylko "zawartość" automatu. */ public abstract class AutomatonSpecification implements Cloneable { // metody "budujące" automat /** * Dodaje nowy stan do automatu. * * Zwraca dodany stan. */ public abstract State addState(); /** * Dodaje przejście od stanu 'from' do stanu 'to' etykietowane etykietą transitionLabel. */ public abstract void addTransition(State from, State to, TransitionLabel transitionLabel); /** * Dodaje przejście od stanu 'from' do nowo utworzonego stanu 'to' etykietowane etykietą * transitionLabel, a następnie zwraca utworzony stan. */ public State addTransition(State from, TransitionLabel transitionLabel) { State to = addState(); addTransition(from, to, transitionLabel); return to; } /** * Dla zadanego słowa dodaje stany i przejścia. * * Zwraca stan końcowy. */ public State addTransitionSequence(State from, String text) { State prev = from; State next = prev; for (int i = 0; i < text.length(); ++i) { prev = addTransition(next, new CharTransitionLabel(text.charAt(i))); next = prev; } return prev; } /** * Tworzy "gałąź" w automacie. * Metoda dodaje ciąg przejść od stanu początkowego automatu, * dla podanej listy etykiet przejść. * Metoda zwraca (nowo utworzony) stan docelowy ostatniego przejścia. */ public State addBranch(State from, List<TransitionLabel> oTransition) { State prev = from; State next = prev; for (TransitionLabel transition : oTransition) { prev = addTransition(next, transition); next = prev; } return prev; } /** * Oznacza stan jako początkowy. */ public abstract void markAsInitial(State state); /** * Oznacza stan jako końcowy (akceptujący). */ public abstract void markAsFinal(State state); /** * Odznacza stan końcowy. */ public abstract void unmarkAsFinalState(State state); // metody zwracające informacje o automacie /** * Zwraca listę wszystkich stanów. * * Stany niekoniecznie muszą być zwrócone w identycznej * kolejności jak były dodane. */ public abstract List<State> allStates(); /** * Zwraca listę wszystkich przejść wychodzących ze stanu 'from'. * * Przejścia niekoniecznie muszą być zwrócone w identycznej * kolejności jak były dodane. */ public abstract List<OutgoingTransition> allOutgoingTransitions(State from); /** * Zwraca stan początkowy. */ public abstract State getInitialState(); /** * Zwraca true wgdy stan jest stanem końcowym. */ public abstract boolean isFinal(State state); /** * Metoda sprawdza czy automat jest pusty. */ public boolean isEmpty() { List<State> states = allStates(); if (states.isEmpty()) return true; return false; } /** * Zwraca zawartość automatu w czytelnej dla człowieka postaci String'a. * @return String */ @Override public String toString() { StringBuffer retString = new StringBuffer("Automaton:\n-States: "); //Dopisanie stanów z automatu List<State> listOfStates = allStates(); for (int i = 0; i < listOfStates.size(); i++) { retString.append("q" + i + " "); } //Dopisanie przejść z automatu retString.append("\n-Transitions:\n"); for (int i = 0; i < listOfStates.size(); i++) { List<OutgoingTransition> listOfTrans = allOutgoingTransitions(listOfStates.get(i)); for (int j = 0; j < listOfTrans.size(); j++) { retString.append(" q" + i + " -" + listOfTrans.get(j).getTransitionLabel() + "-> q"); State target = listOfTrans.get(j).getTargetState(); for (int m = 0; m < listOfStates.size(); m++) { if (target == listOfStates.get(m)) { retString.append(m); break; } } retString.append("\n"); } } //Dopisanie stanu początkowego automatu retString.append("-Initial state: "); for (int i = 0; i < listOfStates.size(); i++) { if (listOfStates.get(i) == getInitialState()) { retString.append("q" + i); break; } } //Dopisanie stanów końcowych automatu retString.append("\n-Final states: "); for (int i = 0; i < listOfStates.size(); i++) { if (isFinal(listOfStates.get(i))) { retString.append("q" + i + " "); } } //Zwrócenie wyniku return retString.toString(); } /** * Sprawdza, czy język akceptowany przez automat jest niepusty. */ public boolean isNotEmpty() { List<State> states = allStates(); if (states.isEmpty()) return false; else { int counter = 0; boolean isThereNoInitialState = false; for (State s : allStates()) { if (this.isFinal(s)) counter++; } if (this.getInitialState() == null) isThereNoInitialState = true; if (counter == 0 || isThereNoInitialState) return false; else { Stack<Integer> stateStack = new Stack<Integer>(); Stack<Integer> transitionStack = new Stack<Integer>(); Stack<Integer> branchStack = new Stack<Integer>(); Integer state = states.indexOf(this.getInitialState()); Integer transition = 0; Integer branches = this.allOutgoingTransitions(states.get(state)).size(); stateStack.push(state); transitionStack.push(transition); do { state = stateStack.pop(); transition = transitionStack.pop(); if (this.isFinal(states.get(state))) return true; if (transition < branches) { stateStack.push(state); transitionStack.push(transition++); branchStack.push(branches); State stateForWhile = this.allOutgoingTransitions(states.get(state)) .get(transition).getTargetState(); branchStack.push(this.allOutgoingTransitions(stateForWhile).size()); stateStack.push(states.indexOf(stateForWhile)); transitionStack.push(0); } } while (!stateStack.empty()); } } return false; } /** * Sprawdza, czy automat jest deterministyczny (to znaczy, czy ma * przynajmniej jeden stan, czy nie zawiera epsilon-przejść (za wyjątkiem * sytuacji, gdy epsilon-przejście jest jedynym sposobem wyjścia ze stanu) * oraz czy przejścia z danego stanu do innych stanów odbywają się po * różnych znakach). */ public boolean isDeterministic() { List<State> states = allStates(); if (states.isEmpty()) return false; for (State state : states) { List<OutgoingTransition> transitions = allOutgoingTransitions(state); if (transitions.size() <= 1) continue; for (int i = 0; i < transitions.size(); ++i) { TransitionLabel label = transitions.get(i).getTransitionLabel(); if (label.canBeEpsilon()) return false; for (int j = i + 1; j < transitions.size(); ++j) { TransitionLabel label2 = transitions.get(j).getTransitionLabel(); if (!label2.intersect(label).isEmpty()) return false; } } } return true; } /** * Dodaje przejście od stanu state z powrotem do tego samego stanu * po etykiecie transitionLabel. */ public void addLoop(State state, TransitionLabel transitionLabel) { addTransition(state, state, transitionLabel); } /** * Zwraca obiekt typu String, który zawiera gotowy kod w języku DOT służący do * przedstawienia automatu w formie graficznej, (w ubuntu pakiet * graphviz). Z konsoli wywołuje się przykładowo w następujący sposób: dot * -Tpng -O plik_zkodem.dot który tworzy plik-schemat zapisany w formacie * png. Więcej w: man dot. * * @return Kod źródłowy schematu w języku DOT. */ public String getDotGraph() { class DotGraph { private StringBuffer dotCode; private List<State> states; public DotGraph() { dotCode = new StringBuffer(); states = allStates(); } private void getDotGraphIntro() { dotCode.append( "digraph finite_state_machine {\n" + " rankdir=LR;\n" + " size=\"8,5\"\n" + " node [style=filled fillcolor=\"#00ff005f\" shape = "); if (isFinal(getInitialState())) dotCode.append("double"); dotCode.append("circle];\n" + " \"State #" + states.indexOf(getInitialState()) + "\";\n" + " node [shape = doublecircle style=filled " + "fillcolor=\"#00000000\"];\n "); } private void getDotGraphFinalStates() { for (State it : states) { if (isFinal(it)) { dotCode.append("\"State #" + states.indexOf(it) + "\" "); } } } private void getEdgeLabel(State state, int target, String label) { if (label.length() != 0) { dotCode.append(" \"State #"); dotCode.append(states.indexOf(state) + "\""); dotCode.append(" -> "); dotCode.append("\"State #"); dotCode.append(target + "\""); dotCode.append(" [ label = \"" + label + "\" ]"); dotCode.append(";\n"); } } private void getDotGraphEdges() { for (State it : states) { final StringBuffer[] labelList = new StringBuffer[states.size()]; for (int i = 0; i < labelList.length; ++i) { labelList[i] = new StringBuffer(); } final List<OutgoingTransition> edges = allOutgoingTransitions(it); for (OutgoingTransition edgeIt : edges) { if (labelList[states.indexOf(edgeIt.getTargetState())].length() == 0) { labelList[states.indexOf(edgeIt.getTargetState())] .append(edgeIt.getTransitionLabel()); } else { labelList[states.indexOf(edgeIt.getTargetState())] .append(", " + edgeIt.getTransitionLabel()); } } for (int i = 0; i < labelList.length; ++i) { getEdgeLabel(it, i, labelList[i].toString()); } } } public String getDotGraph() { getDotGraphIntro(); getDotGraphFinalStates(); dotCode.append(";\n" + " node [shape = circle];\n" + ""); getDotGraphEdges(); dotCode.append("\n}\n"); return dotCode.toString(); } } DotGraph tmp = new DotGraph(); return tmp.getDotGraph(); } /** * Zwraca liczbę stanów. */ public int countStates() { return allStates().size(); } /** * Zwraca liczbę przejść. */ public int countTransitions() { int sum = 0; for (State state : allStates()) { sum += allOutgoingTransitions(state).size(); } return sum; } /** * Wstawia począwszy od stanu state kopię automatu automaton. * Stan state będzie utożsamiony ze stanem * początkowym automatu automaton. */ public void insert(State state, AutomatonSpecification automaton) { List<State> loadedStates = automaton.allStates(); HashMap<State, State> connectedStates = new HashMap<State, State>(); State automatonInitialState = automaton.getInitialState(); for (State currentState : loadedStates) { if (currentState == automatonInitialState) connectedStates.put(currentState, state); else connectedStates.put(currentState, this.addState()); } for (State currentState : loadedStates) { if (automaton.isFinal(currentState)) markAsFinal(connectedStates.get(currentState)); List<OutgoingTransition> list = automaton .allOutgoingTransitions(currentState); for (OutgoingTransition transition : list) { this.addTransition(connectedStates.get(currentState), connectedStates.get(transition.getTargetState()), transition.getTransitionLabel()); } } } /** * Funkcja zmieniająca pusty automat na automat akceptujący wyłącznie napis * pusty. */ public AutomatonSpecification makeEmptyStringAutomaton() { State emptyState = this.addState(); this.markAsInitial(emptyState); this.markAsFinal(emptyState); return this; } /** * Sprawdza czy dla danego stanu i znaku istnieje przejście. */ private boolean doesTransitionExist(State state, char c) { for (OutgoingTransition transition1 : allOutgoingTransitions(state)) { if (transition1.getTransitionLabel().canAcceptCharacter(c)) return true; } return false; } /** * Sprawdza, czy dla każdego stanu i dla każdego znaku z alfabetu * istnieje przejście. */ public boolean isFull(String alphabet) { if (allStates().isEmpty()) return false; for (State state : allStates()) { if (allOutgoingTransitions(state).isEmpty()) return false; for (int i = 0; i < alphabet.length(); i++) { if (!doesTransitionExist(state, alphabet.charAt(i))) return false; } } return true; } /** * Dopełnia automat tak, aby isFull zwracało prawdę. */ public void makeFull(String alphabet) { State trash = null; if (this.isEmpty()) { trash = addState(); for (int i = 0; i < alphabet.length(); i++) addLoop(trash, new CharTransitionLabel( alphabet.charAt(i))); return; } for (State state : new ArrayList<State>(allStates())) { for (int i = 0; i < alphabet.length(); i++) { if (allOutgoingTransitions(state).isEmpty()) { if (trash == null) trash = addState(); addTransition(state, trash, new CharTransitionLabel( alphabet.charAt(i))); } if (!doesTransitionExist(state, alphabet.charAt(i))) { if (trash == null) trash = addState(); addTransition(state, trash, new CharTransitionLabel( alphabet.charAt(i))); } } } if (trash != null) { for (int i = 0; i < alphabet.length(); i++) addLoop(trash, new CharTransitionLabel( alphabet.charAt(i))); } } /** * Sprawdza, czy od stanu state można dojść do stanu końcowego. */ public boolean prefixChecker(State state) { if (isFinal(state)) { return true; } List<State> checkedStates = new ArrayList<State>(); List<OutgoingTransition> outgoing = new ArrayList<OutgoingTransition>(); State currentState; checkedStates.add(state); int limit = checkedStates.size(); for (int i = 0; i < limit; i++) { outgoing.clear(); outgoing = allOutgoingTransitions(checkedStates.get(i)); for (int j = 0; j < outgoing.size(); j++) { currentState = outgoing.get(j).getTargetState(); if (isFinal(currentState)) { return true; } if (!checkedStates.contains(currentState)) { checkedStates.add(currentState); limit++; } } } return false; } /** * Funkcja tworzaca zawartość automatu ze Stringa. */ void fromString(String automatonDescription) throws StructureException { MakeAutomatonFromString graph = new MakeAutomatonFromString(this, automatonDescription); graph.make(); } /** * Zwraca true, gdy automat akceptuje napis pusty. */ public boolean acceptEmptyWord() { List<State> tocheck = new ArrayList<State>(); List<OutgoingTransition> transitions = new ArrayList<OutgoingTransition>(); TransitionLabel label; State state; if (isFinal(getInitialState())) { return true; } tocheck.add(getInitialState()); int iterator = tocheck.size(); for (int i = 0; i < iterator; ++i) { transitions.clear(); transitions = allOutgoingTransitions(tocheck.get(i)); for (OutgoingTransition j : transitions) { label = j.getTransitionLabel(); state = j.getTargetState(); if (label.canBeEpsilon() && !tocheck.contains(state)) { tocheck.add(state); ++iterator; if (isFinal(state)) { return true; } } } } return false; } /** * Sprawdza, czy w automacie istnieją zbędne stany. */ public boolean uselessStates() { boolean flag1 = true; boolean flag2 = false; State q = getInitialState(); List<State> stack = new ArrayList<State>(); List<State> used; used = allStates(); int x = 0; while (true) { if (flag1) { for (int i = 1; i <= allOutgoingTransitions(q).size(); i++) { stack.add(allOutgoingTransitions(q).get(i).getTargetState()); } } if (!stack.isEmpty()) { flag1 = true; q = stack.get(stack.size()); for (int i = 1; i <= used.size(); i++) { if (used.get(i) == q) { flag2 = true; x = i; break; } } if (flag2) { used.remove(x); flag2 = false; continue; } else { flag1 = false; } } else { break; } } for (int i = 1; i <= used.size(); i++) { if (used.get(i) != null) { return true; } } return false; } /** * Tworzy automat akceptujący napisy nad alfabetem. */ public AutomatonSpecification makeAllStringsAutomaton(String alphabet) { State state = addState(); markAsInitial(state); markAsFinal(state); for (int i = 0; i < alphabet.length(); i++) addLoop(state, new CharTransitionLabel(alphabet.charAt(i))); return this; } /** * Tworzy automat akceptujący wszystkie niepuste napisy nad alfabetem. */ public AutomatonSpecification makeAllNonEmptyStringsAutomaton(String alphabet) { State s0 = addState(); State s1 = addState(); markAsInitial(s0); markAsFinal(s1); for (int i = 0; i < alphabet.length(); i++) { addTransition(s0, s1, new CharTransitionLabel(alphabet.charAt(i))); addLoop(s1, new CharTransitionLabel(alphabet.charAt(i))); } return this; } /** * Sprawdza, czy można przedłużyć word do słowa akceptowanego. */ public boolean checkPrefix(String word) { List<State> finalStates = new ArrayList<State>(); List<State> nowChecking = new ArrayList<State>(); List<OutgoingTransition> outgoing = new ArrayList<OutgoingTransition>(); TransitionLabel label; State state; finalStates.add(getInitialState()); for (int i = 0; i < word.length(); i++) { nowChecking.clear(); nowChecking.addAll(finalStates); finalStates.clear(); int size = nowChecking.size(); for (int j = 0; j < size; j++) { outgoing.clear(); outgoing = allOutgoingTransitions(nowChecking.get(j)); for (int k = 0; k < outgoing.size(); k++) { label = outgoing.get(k).getTransitionLabel(); state = outgoing.get(k).getTargetState(); if (label.canBeEpsilon() && !nowChecking.contains(state)) { nowChecking.add(state); size++; } boolean term = label.canAcceptCharacter(word.charAt(i)); if (term && !finalStates.contains(state)) { finalStates.add(state); } } } } int size = finalStates.size(); for (int j = 0; j < size; j++) { outgoing.clear(); outgoing = allOutgoingTransitions(finalStates.get(j)); for (int k = 0; k < outgoing.size(); k++) { label = outgoing.get(k).getTransitionLabel(); state = outgoing.get(k).getTargetState(); if (label.canBeEpsilon() && !finalStates.contains(state)) { finalStates.add(state); size++; } } } for (int i = 0; i < finalStates.size(); i++) { boolean istrue = prefixChecker(finalStates.get(i)); if (istrue) return true; } return false; } /** * Klonowanie automatu. */ @Override public AutomatonSpecification clone() { AutomatonSpecification mini = new NaiveAutomatonSpecification(); State q = mini.addState(); mini.markAsInitial(q); mini.insert(q, this); return mini; } /** * Tworzy automat z jednym przejściem. */ public AutomatonSpecification makeOneLoopAutomaton(char c) { State q0 = addState(); addLoop(q0, new CharTransitionLabel(c)); markAsInitial(q0); markAsFinal(q0); return this; } /** * Metoda budująca 2-stanowy automat z jednym przejściem. */ public AutomatonSpecification makeOneTransitionAutomaton(char c) { State q0 = addState(); State q1 = addState(); addTransition(q0, q1, new CharTransitionLabel(c)); markAsInitial(q0); markAsFinal(q1); return this; } /** * Metoda zwracającą wszystkie napisy akceptowane przez automat. */ public AllAcceptedWords returnAllAcceptedWords() { AllAcceptedWords words = new AllAcceptedWords(this); return words; } /** * Sprawdza, czy akceptowany język jest nieskończony. */ public boolean isInfinite() { return checkForLoop(getInitialState(), new ArrayList<State>(), false); } private boolean checkForLoop(State state, List<State> history, boolean flag) { for (State his : history) if (his == state) return findFinals(state, new ArrayList<State>(), flag); if (allOutgoingTransitions(state).isEmpty()) return false; history.add(state); boolean result = false; for (OutgoingTransition child : allOutgoingTransitions(state)) { if (!child.getTransitionLabel().canBeEpsilon()) flag = true; List<State> newHistory = new ArrayList<State>(); for (State s : history) newHistory.add(s); result = result || checkForLoop(child.getTargetState(), newHistory, flag); if (result) break; } return result; } private boolean findFinals(State state, List<State> history, boolean flag) { boolean result = false; if (isFinal(state)) { for (OutgoingTransition transition : allOutgoingTransitions(state)) if (transition.getTargetState() == state) return !(transition.getTransitionLabel().canBeEpsilon()); return flag; } for (State his : history) if (his == state) return false; history.add(state); for (OutgoingTransition child : allOutgoingTransitions(state)) { List<State> newHistory = new ArrayList<State>(); for (State s : history) newHistory.add(s); result = result || findFinals(child.getTargetState(), newHistory, flag); if (result) break; } return result; } /** * Metoda zwracająca pierwszy według kolejności alfabetycznej napis, * akceptowany przez automat. */ public String firstAcceptedWord(String alphabet) { NondeterministicAutomatonByThompsonApproach a = new NondeterministicAutomatonByThompsonApproach(this); boolean found = false; char[] tmp = alphabet.toCharArray(); java.util.Arrays.sort(tmp); String sorted = new String(tmp); String word = ""; int l = alphabet.length(); int x = 1; if (this.isEmpty()) throw new RuntimeException("empty automaton"); if (this.acceptEmptyWord()) { found = true; return ""; } else do { int flag = x; char[] searchWord = new char[x]; while (flag > 0) { searchWord[flag - 1] = sorted.charAt(0); flag--; } for (int i = 0; i < l; i++) { if (x > 1 && searchWord[x - 1] == sorted.charAt(sorted.length() - 1)) { while (flag > 0) { if (searchWord[flag - 1] == sorted.charAt(sorted.length() - 1)) { flag--; } else { int z = 0, y = 0; while (z < sorted.length() - 1 && y == 0) { if (searchWord[flag - 1] == sorted.charAt(z)) y = z + 1; else z++; } searchWord[flag - 1] = sorted.charAt(y); int tempFlag = flag; flag = x; while (flag > tempFlag) { searchWord[flag - 1] = sorted.charAt(0); flag--; } flag = 0; } } } flag = x; searchWord[x - 1] = tmp[i % alphabet.length()]; String acceptedWord = new String(searchWord); if (a.accepts(acceptedWord)) { word = acceptedWord; found = true; } } x++; l = l * alphabet.length(); } while(!found); return word; } /** *Metoda zwraca długość najdłuższego słowa akceptowanego. */ public int maxWordLength() { AllAcceptedWords words = new AllAcceptedWords(this); String word; int tmp; final int infinitereturncode = -2; final int emptyreturncode = -1; int max = 0; if (isInfinite()) { return infinitereturncode; } if (words.hasNext()) { do { word = words.next(); tmp = word.length(); if (max < tmp) { max = tmp; } } while (words.hasNext()); return max; } else { return emptyreturncode; } } /** * Tworzy epsilon domknięcie zadanego stanu. */ public Set<State> getEpsilonClosure(State initial) { AlwaysAcceptingContextChecker contextChecker = new AlwaysAcceptingContextChecker(); return doGetEpsilonClosure(initial, contextChecker); } /** * Dla podanego automatu tworzy równoważny automat z 1 stanem końcowym. */ public AutomatonSpecification makeOneFinalStateAutomaton() { ArrayList<State> allFinalStates = new ArrayList<State>(); ArrayList<State> allStates = new ArrayList<State>(); allStates.addAll(allStates()); for (State someState : allStates) { if (isFinal(someState)) { allFinalStates.add(someState); } } int size = allFinalStates.size(); AutomatonSpecification spec = new NaiveAutomatonSpecification(); switch (size) { case 0: spec.clone(); spec.markAsFinal(spec.addState()); return spec; case 1: spec.clone(); return spec; default: spec.clone(); State stateFinal = spec.addState(); for (State someState : allFinalStates) { spec.unmarkAsFinalState(someState); spec.addTransition(someState, stateFinal, new EpsilonTransitionLabel()); return spec; } } return null; } protected List<State> getFinalStates() { return finalStatess; } /** * Zwraca epsilon domknięcie zadanego stanu, z uwzględnieniem warunków kontekstowych. */ public Set<State> getEpsilonClosureWithContext(State initial, String s, int position) { ReallyCheckingContextChecker contextChecker = new ReallyCheckingContextChecker(s, position); return doGetEpsilonClosure(initial, contextChecker); } /** * Metoda wyszukująca epsilon domknięcie. */ private Set<State> doGetEpsilonClosure(State initial, ContextChecker contextChecker) { Set<State> epsilonClosure = new HashSet<State>(); Set<State> visited = new HashSet<State>(); Stack<State> stack = new Stack<State>(); stack.push(initial); epsilonClosure.add(initial); while (!stack.empty()) { State from = stack.pop(); if (visited.contains(from)) { continue; } visited.add(from); for (OutgoingTransition trans : allOutgoingTransitions(from)) { TransitionLabel label = trans.getTransitionLabel(); State to = trans.getTargetState(); if (label.canBeEpsilon() && contextChecker.check(label)) { epsilonClosure.add(to); stack.push(to); } } } return epsilonClosure; } private LinkedList<State> finalStatess = new LinkedList<State>(); }; class StructureException extends Exception { }