/* * JFLAP - Formal Languages and Automata Package * * * Susan H. Rodger * Computer Science Department * Duke University * August 27, 2009 * Copyright (c) 2002-2009 * All rights reserved. * JFLAP is open source software. Please see the LICENSE for terms. * */ package gui.deterministic; import gui.environment.FrameFactory; import java.awt.Point; import java.awt.Rectangle; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.StringTokenizer; import javax.swing.JOptionPane; import automata.Automaton; import automata.State; import automata.Transition; import automata.event.AutomataStateEvent; import automata.event.AutomataStateListener; import automata.fsa.FSATransition; import automata.fsa.FiniteStateAutomaton; import automata.fsa.NFAToDFA; import automata.graph.Graph; import automata.graph.LayoutAlgorithm; import automata.graph.layout.GEMLayoutAlgorithm; import debug.EDebug; /** * This is the class that controls the conversion of an NFA to a DFA. * * @author Thomas Finley */ public class ConversionController { /** * Instantiates a ConversionController. * * @param nfa * the NFA we're converting to a DFA * @param dfa * the DFA being built * @param view * the view that needs to be repainted */ public ConversionController(FiniteStateAutomaton nfa, FiniteStateAutomaton dfa, ConversionPane view) { this.nfa = nfa; this.dfa = dfa; this.view = view; // Set up an initial state in the DFA. converter.createInitialState(nfa, dfa).setPoint(new Point(50, 50)); registerState(dfa.getInitialState()); answer = converter.convertToDFA(nfa); // Create the graph. initializeGraph(); } private void initializeGraph() { Map stateToSet = new HashMap(); // Different... State[] s = answer.getStates(); Transition[] t = answer.getTransitions(); for (int i = 0; i < s.length; i++) { Set fromNfa = new HashSet(Arrays.asList(getStatesForString(s[i] .getLabel(), nfa))); stateToSet.put(s[i], fromNfa); // setToState.put(s[i], fromNfa); graph.addVertex(fromNfa, s[i].getPoint()); } for (int i = 0; i < t.length; i++) { graph.addEdge(stateToSet.get(t[i].getFromState()), stateToSet .get(t[i].getToState())); } } public void performFirstLayout() { view.validate(); Set isonodes = new HashSet(); Set initialSet = (Set) stateToSet.get(dfa.getInitialState()); isonodes.add(initialSet); graph.addVertex(initialSet, new Point(0, 0)); layout.layout(graph, isonodes); Rectangle r = view.editor.getBounds(null); r.grow(-50, -50); graph.moveWithinFrame(r); // Set the position of the initial state. Point p = new Point(); p.setLocation(graph.pointForVertex(initialSet)); dfa.getInitialState().setPoint(p); } private State[] getStatesForString(String label, Automaton automaton) { StringTokenizer tokenizer = new StringTokenizer(label, " \t\n\r\f,q"); ArrayList states = new ArrayList(); while (tokenizer.hasMoreTokens()) states.add(automaton.getStateWithID(Integer.parseInt(tokenizer .nextToken()))); states.remove(null); return (State[]) states.toArray(new State[0]); } /** * Registers that a state has been added to the DFA. * * @param state * the state that was added * @throws IllegalArgumentException * if the state registered conflicts with any existing */ private void registerState(State state) { Set set = new HashSet(Arrays.asList(getStatesForString( state.getLabel(), nfa))); State inMap = (State) setToState.get(set); EDebug.print(set); EDebug.print(inMap); EDebug.print(state); // EDebug.print(setToState.size()); // EDebug.print(state.getLabel()); for (Object o: setToState.keySet()) EDebug.print(o.toString()); if (inMap != null && inMap != state) throw new IllegalArgumentException("This set is in the DFA!"); setToState.put(set, state); stateToSet.put(state, set); } /** * Expands all the transitions for a state. This will add all of the * transitions of a state with no user interaction. * * @param state * the state to expand */ public void expandState(State state) { List createdStates = converter.expandState(state, nfa, dfa); // We want to lay out those states. // First, get the sets of states the new states represent. Set iso = new HashSet(setToState.keySet()), added = new HashSet(); Iterator it = createdStates.iterator(); while (it.hasNext()) { State dfaState = (State) it.next(); registerState(dfaState); iso.remove(stateToSet.get(dfaState)); } // Run the layout algorithm. layout.layout(graph, iso); it = createdStates.iterator(); while (it.hasNext()) { State dfaState = (State) it.next(); Object o = stateToSet.get(dfaState); dfaState.getPoint().setLocation(graph.pointForVertex(o)); dfaState.setPoint(dfaState.getPoint()); } } /** * Asks the user for input regarding a new state. * * @see gui.deterministic.TransitionExpanderTool * @param start * the initial state we're expanding on a terminal * @param point * the point that the mouse was released on * @param end * optionally the state the mouse was released on, or null */ public void expandState(State start, Point point, State end) { // Ask the user for a terminal. String terminal = JOptionPane.showInputDialog(view, "Expand on what terminal?"); if (terminal == null) return; if (terminal.equals("")) { JOptionPane.showMessageDialog(view, "One can't have lambda in the DFA!", "Improper terminal", JOptionPane.ERROR_MESSAGE); return; } State[] states = getStatesForString(start.getLabel(), nfa); State[] endStates = converter .getStatesOnTerminal(terminal, states, nfa); if (endStates.length == 0) { JOptionPane.showMessageDialog(view, "The group {" + start.getLabel() + "} does not expand on the terminal " + terminal + "!", "Improper expansion", JOptionPane.ERROR_MESSAGE); return; } // Ask the user for the states it should expand to. String userEnd = ""; if (end == null) userEnd = JOptionPane.showInputDialog(view, "Which group of NFA states will that go to on " + terminal + "?"); if (userEnd == null) return; State[] userEndStates = endStates; try { if (end == null) userEndStates = getStatesForString(userEnd, nfa); } catch (NumberFormatException e) { JOptionPane.showMessageDialog(view, "The list of states is not formatted correctly!", "Format error", JOptionPane.ERROR_MESSAGE); return; } // Was the user right? if (!converter.containSameStates(userEndStates, endStates)) { JOptionPane.showMessageDialog(view, "That list of states is incorrect!", "Wrong set", JOptionPane.ERROR_MESSAGE); return; } State end2 = converter.getStateForStates(userEndStates, dfa, nfa); if (end == null) end = end2; if (end != end2) { // Group mismatch. JOptionPane.showMessageDialog(view, "The group {" + start.getLabel() + "} does not go to\n" + "group {" + end.getLabel() + "} on terminal " + terminal + "!", "Improper transition", JOptionPane.ERROR_MESSAGE); return; } if (end == null) { // We need to create the state. end = converter.createStateWithStates(dfa, userEndStates, nfa); registerState(end); end.setPoint(point); } // Set up the transition. Transition t = new FSATransition(start, end, terminal); dfa.addTransition(t); } /** * This method will expand all states in an automaton. */ public void complete() { final LinkedList stateQueue = new LinkedList(); // Add all states to the state queue. stateQueue.addAll(Arrays.asList(dfa.getStates())); // When a state is added to the DFA, make sure we know about it. AutomataStateListener listener = new AutomataStateListener() { public void automataStateChange(AutomataStateEvent e) { if (!e.isAdd()) return; // When the DFA gets a new state, add the state to // the end of the queue. stateQueue.addLast(e.getState()); } }; dfa.addStateListener(listener); while (stateQueue.size() != 0) expandState((State) stateQueue.removeFirst()); // Remove the state listener. dfa.removeStateListener(listener); } /** * This method checks to make sure that the finite state automaton is done. * If it is, then the finished automaton is put in a new window. */ public void done() { int statesRemaining = answer.getStates().length - dfa.getStates().length, transitionsRemaining = answer .getTransitions().length - dfa.getTransitions().length; if (statesRemaining + transitionsRemaining != 0) { String states = statesRemaining == 0 ? "All the states are there.\n" : statesRemaining + " more state" + (statesRemaining == 1 ? "" : "s") + " must be placed.\n"; String trans = transitionsRemaining == 0 ? "All the transitions are there.\n" : transitionsRemaining + " more transition" + (transitionsRemaining == 1 ? "" : "s") + " must be placed.\n"; String message = "The DFA has not been completed.\n" + states + trans; JOptionPane.showMessageDialog(view, message); return; } JOptionPane.showMessageDialog(view, "The DFA is fully built!\n" + "It will now be placed in a new window."); FrameFactory.createFrame((FiniteStateAutomaton) dfa.clone()); } /** The NFA we're converting to a DFA. */ private FiniteStateAutomaton nfa; /** The DFA being built. */ private FiniteStateAutomaton dfa; /** The final answer DFA, used for reference. */ private FiniteStateAutomaton answer; /** The view that's being repainted. */ private ConversionPane view; /** The NFA to DFA converter helper object. */ private NFAToDFA converter = new NFAToDFA(); /** * The graph object that we use to help lay out those states that are * automatically placed. Here, vertex objects are the sets of states from * the original NFA. */ private Graph graph = new Graph(); /** * Whether the user has interacted with the layout of the automaton in such * a way as the automatic layout needs to be redone. */ private boolean validLayout = false; /** The layout algorithm. */ private LayoutAlgorithm layout = new GEMLayoutAlgorithm(); /** * Maps a set of NFA states to a DFA state. This structure is maintained in * part through the registerState method. */ private Map setToState = new HashMap(); /** Maps a state to a set of NFA states. */ private Map stateToSet = new HashMap(); }