/*
* 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.regular;
import automata.State;
import automata.Transition;
import automata.fsa.FSAToRegularExpressionConverter;
import automata.fsa.FSATransition;
import automata.fsa.FiniteStateAutomaton;
import gui.environment.*;
import gui.viewer.AutomatonPane;
import gui.viewer.SelectionDrawer;
import java.awt.*;
import javax.swing.*;
import regular.RegularExpression;
/**
* This object monitors and guides the user actions in the conversion of an FSA
* to a regular expression.
*
* @author Thomas Finley
*/
public class FSAToREController {
/**
* Instantiates a new FSAToREController
.
*
* @param automaton
* the automaton that is in the process of being converted
* @param drawer
* the selection drawer in the editor
* @param mainStep
* the label holding the description of the main step
* @param detailStep
* the label holding the detail description of whatever the user
* must do now
* @param frame
* the window that this is all happening in
*/
public FSAToREController(FiniteStateAutomaton automaton,
SelectionDrawer drawer, JLabel mainStep, JLabel detailStep,
JFrame frame) {
this.automaton = automaton;
this.drawer = drawer;
this.mainStep = mainStep;
this.detailStep = detailStep;
this.frame = frame;
nextStep();
}
/**
* This method should be called when the user undertakes an action that is
* inappropriate for the current step. This merely displays a small dialog
* to the user informing him of this fact, and takes no further action.
*/
protected void outOfOrder() {
JOptionPane.showMessageDialog(frame,
"That action is inappropriate for this step!", "Out of Order",
JOptionPane.ERROR_MESSAGE);
}
/**
* Moves the converter controller to the next step. This will skip any
* unnecessary steps, and set the messages.
*/
protected void nextStep() {
switch (currentStep) {
case -1:
case CREATE_SINGLE_FINAL:
currentStep = CREATE_SINGLE_FINAL;
mainStep.setText("Make Single Noninitial Final State");
detailStep
.setText("Create a new state to make a single final state.");
if (automaton.getFinalStates().length != 1
|| automaton.getFinalStates()[0] == automaton
.getInitialState()) {
return;
}
currentStep = TRANSITIONS_TO_SINGLE_FINAL;
case TRANSITIONS_TO_SINGLE_FINAL:
detailStep
.setText("Put "+Universe.curProfile.getEmptyString()+"-transitions from old final states to new.");
// We know we're done when...
if (drawer.numberSelected() != 0)
return;
currentStep = CONVERT_TRANSITIONS;
remaining = collapsesNeeded();
case CONVERT_TRANSITIONS:
mainStep.setText("Reform Transitions");
detailStep
.setText("Use the collapse tool to turn multiple transitions to one."
+ " " + remaining + " more collapses needed.");
if (remaining != 0)
return;
currentStep = CREATE_EMPTY_TRANSITIONS;
remaining = emptyNeeded();
case CREATE_EMPTY_TRANSITIONS:
detailStep
.setText("Put empty transitions between states with no transitions."
+ " "
+ remaining
+ " more empty transitions needed.");
if (remaining != 0)
return;
remaining = automaton.getStates().length - 2;
currentStep = COLLAPSE_STATES;
case COLLAPSE_STATES:
mainStep.setText("Remove States");
detailStep
.setText("Use the collapse state tool to remove nonfinal, noninitial "
+ "states. " + remaining + " more removals needed.");
if (remaining != 0)
return;
if (transitionWindow != null) {
transitionWindow.setVisible(false);
transitionWindow.dispose();
}
drawer.clearSelected();
drawer.clearSelectedTransitions();
currentStep = FINISHED;
case FINISHED:
mainStep.setText("Generalized Transition Graph Finished!");
computedRE = FSAToRegularExpressionConverter.getExpressionFromGTG(automaton);
detailStep.setText(computedRE);
}
}
/**
* For the collapsing of multiple transitions between states, this counts
* the number of collapses that must take place on the automaton before all
* possible ordered pairs of states have at most one transition from the
* first to the second. This method just counts the number of (from,to)
* pairs with more than one transition between them
*
* @return the number of collapses needed
*/
protected int collapsesNeeded() {
State[] states = automaton.getStates();
int needed = 0;
for (int i = 0; i < states.length; i++)
for (int j = 0; j < states.length; j++)
if (automaton.getTransitionsFromStateToState(states[i],
states[j]).length > 1)
needed++;
return needed;
}
/**
* For the creation of empty transitions between states, this counts the
* number of empty transitions needed.
*
* @return the number of empty transitions needed
*/
protected int emptyNeeded() {
State[] states = automaton.getStates();
int needed = 0;
for (int i = 0; i < states.length; i++)
for (int j = 0; j < states.length; j++)
if (automaton.getTransitionsFromStateToState(states[i],
states[j]).length == 0)
needed++;
return needed;
}
/**
* Creates a state at the specified location. This method is called by an
* external state creation tool for the purposes of creating a single final
* state only. If this is such an event, the state will be created, made
* final, all other final states will be made nonfinal, and selected, and
* the machine will move to the next phase.
*
* @param point
* the point that the state creation tool was clicked at
* @return the state that was created, or null
if it is not
* the time to create a state
*/
public State stateCreate(Point point) {
if (currentStep != CREATE_SINGLE_FINAL) {
outOfOrder();
return null;
}
State[] finals = automaton.getFinalStates();
drawer.clearSelected();
for (int i = 0; i < finals.length; i++) {
automaton.removeFinalState(finals[i]);
drawer.addSelected(finals[i]);
}
State newState = automaton.createState(point);
automaton.addFinalState(newState);
frame.repaint();
nextStep();
return newState;
}
/**
* Creates a new transition. There are two times when this would be
* appropriate: first, when creating the labmda transitions from previously
* final states to the new final state, and two, when creating the empty set
* transitions between states that do not have transitions between
* themselves already. Otherwise, this action should not be undertaken.
* These transition creations do not require any user input since in either
* case, what must go in the label is clear.
*
* @param from
* the from state
* @param to
* the to state
* @return the newly created transition from from
to to,
* or null
if a transition is inappropriate for this
* circumstance
*/
public Transition transitionCreate(State from, State to) {
if (currentStep == TRANSITIONS_TO_SINGLE_FINAL) {
if (automaton.getFinalStates()[0] != to) {
JOptionPane.showMessageDialog(frame,
"Transitions must go to the new final state!",
"Bad Destination", JOptionPane.ERROR_MESSAGE);
return null;
}
if (!drawer.isSelected(from)) {
JOptionPane.showMessageDialog(frame,
"Transitions must come from an old final state!",
"Bad Source", JOptionPane.ERROR_MESSAGE);
return null;
}
Transition t = new FSATransition(from, to, "");
drawer.removeSelected(from);
automaton.addTransition(t);
frame.repaint();
if (drawer.numberSelected() == 0) {
nextStep();
}
return t;
}
if (currentStep == CREATE_EMPTY_TRANSITIONS) {
if (automaton.getTransitionsFromStateToState(from, to).length != 0) {
JOptionPane.showMessageDialog(frame,
"Transitions must go between"
+ "states with no transitions!",
"Transition Already Exists", JOptionPane.ERROR_MESSAGE);
return null;
}
Transition t = FSAToRegularExpressionConverter.addTransitionOnEmptySet(from, to, automaton);
remaining--;
nextStep();
frame.repaint();
return t;
}
outOfOrder();
return null;
}
/**
* This takes all the transitions from one state to another, and combines
* them into a single transition.
*
* @param from
* the from state
* @param to
* the to state
* @return the newly created super transition that replaced all the
* transitions that used to go from from
to to,
* or null
if the transitions could not be collapsed
* (either because there is already only one, or there are none, or
* if this isn't the right time to collapse)
*/
public Transition transitionCollapse(State from, State to) {
if (currentStep != CONVERT_TRANSITIONS) {
outOfOrder();
return null;
}
Transition[] ts = automaton.getTransitionsFromStateToState(from, to);
if (ts.length <= 1) {
JOptionPane.showMessageDialog(frame,
"Collapse requires 2 or more transitions!",
"Too Few Transitions", JOptionPane.ERROR_MESSAGE);
return null;
}
Transition t = FSAToRegularExpressionConverter.combineToSingleTransition(from, to, ts,
automaton);
remaining--;
frame.repaint();
nextStep();
return t;
}
/**
* This takes a state, and prepares to remove it. Note that this does not
* actually remove the state, but notifies the user of what will appear.
*
* @param state
* the state that was selected for removal
* @return false
if this state cannot be removed because it
* is initial or final or because this is the wrong time for this
* operation, true
otherwise
*/
public boolean stateCollapse(State state) {
if (currentStep != COLLAPSE_STATES) {
outOfOrder();
return false;
}
if (automaton.getInitialState() == state) {
JOptionPane.showMessageDialog(frame,
"The initial state cannot be removed!",
"Initial State Selected", JOptionPane.ERROR_MESSAGE);
return false;
}
if (automaton.getFinalStates()[0] == state) {
JOptionPane.showMessageDialog(frame,
"The final state cannot be removed!",
"Final State Selected", JOptionPane.ERROR_MESSAGE);
return false;
}
collapseState = state;
drawer.clearSelected();
drawer.addSelected(collapseState);
transitionWindow = new TransitionWindow(this);
transitionWindow.setTransitions(FSAToRegularExpressionConverter.getTransitionsForRemoveState(
state, automaton));
transitionWindow.setVisible(true);
// transitionWindow.show();
return true;
}
/**
* This finalizes a state remove. This will remove whatever state was
* selected.
*/
public void finalizeStateRemove() {
if (collapseState == null) {
JOptionPane.showMessageDialog(frame,
"A valid state has not been selected yet!",
"No State Selected", JOptionPane.ERROR_MESSAGE);
return;
}
FSAToRegularExpressionConverter.removeState(collapseState, transitionWindow.getTransitions(),
automaton);
remaining--;
nextStep();
collapseState = null;
drawer.clearSelected();
drawer.clearSelectedTransitions();
// transitionWindow.setTransitions(new Transition[0]);
transitionWindow.setVisible(false);
transitionWindow.dispose();
// transitionWindow.hide();
}
/**
* If a transition is selected in the transition window, this method is told
* about it.
*
* @param transition
* the transition that was selected, or null
if
* less or more than one transition is selected
*/
public void tableTransitionSelected(Transition transition) {
drawer.clearSelectedTransitions();
if (transition == null || collapseState == null) {
return;
}
State from = transition.getFromState();
State to = transition.getToState();
Transition a = automaton.getTransitionsFromStateToState(from,
collapseState)[0];
Transition b = automaton.getTransitionsFromStateToState(from, to)[0];
Transition c = automaton.getTransitionsFromStateToState(collapseState,
collapseState)[0];
Transition d = automaton.getTransitionsFromStateToState(collapseState,
to)[0];
drawer.addSelected(a);
drawer.addSelected(b);
drawer.addSelected(c);
drawer.addSelected(d);
frame.repaint();
}
/**
* This will automatically perform the actions to move the conversion to the
* next step.
*/
public void moveNextStep() {
switch (currentStep) {
case CREATE_SINGLE_FINAL:
JOptionPane.showMessageDialog(frame,
"Just create a state.\nIt's not too difficult.",
"Create the State", JOptionPane.ERROR_MESSAGE);
return;
case TRANSITIONS_TO_SINGLE_FINAL:
State[] states = drawer.getSelected();
State finalState = automaton.getFinalStates()[0];
for (int i = 0; i < states.length; i++)
transitionCreate(states[i], finalState);
break;
case CONVERT_TRANSITIONS: {
State[] s = automaton.getStates();
for (int i = 0; i < s.length; i++)
for (int j = 0; j < s.length; j++)
if (automaton.getTransitionsFromStateToState(s[i], s[j]).length > 1)
transitionCollapse(s[i], s[j]);
break;
}
case CREATE_EMPTY_TRANSITIONS: {
State[] s = automaton.getStates();
for (int i = 0; i < s.length; i++)
for (int j = 0; j < s.length; j++)
if (automaton.getTransitionsFromStateToState(s[i], s[j]).length == 0)
transitionCreate(s[i], s[j]);
break;
}
case COLLAPSE_STATES:
State[] s = automaton.getStates();
for (int i = 0; i < s.length; i++) {
if (automaton.getFinalStates()[0] == s[i]
|| automaton.getInitialState() == s[i])
continue;
Transition[] t = FSAToRegularExpressionConverter.getTransitionsForRemoveState(s[i],
automaton);
FSAToRegularExpressionConverter.removeState(s[i], t, automaton);
}
remaining = 0;
nextStep();
break;
case FINISHED:
JOptionPane.showMessageDialog(frame, "You're done. Go away.",
"You're Done!", JOptionPane.ERROR_MESSAGE);
return;
default:
JOptionPane.showMessageDialog(frame,
"This shouldn't happen! Notify Thomas.",
"Uh Oh, I'm Stupid!", JOptionPane.ERROR_MESSAGE);
}
// nextStep();
}
/**
* This will export the regular expression.
*/
public void export() {
if (computedRE == null) {
JOptionPane.showMessageDialog(frame,
"The conversion has not yet finished.", "Not Finished",
JOptionPane.ERROR_MESSAGE);
return;
}
FrameFactory.createFrame(new RegularExpression(computedRE));
}
/**
* This will export the current automaton. Used for special purposes.
*/
void exportAutomaton() {
Environment e = ((EnvironmentFrame) frame).getEnvironment();
AutomatonPane a = new AutomatonPane(drawer);
e.add(a, "Current FA");
e.setActive(a);
}
/** The current step of the conversion process. */
private int currentStep = -1;
/** The automaton that's being converted. */
private FiniteStateAutomaton automaton;
/** The selection drawer that the editor holds. */
private SelectionDrawer drawer;
/** The main step label. */
private JLabel mainStep;
/** The detail step label. */
private JLabel detailStep;
/** The frame holding all this. */
private JFrame frame;
/**
* The number of things left to do. This can be used by different steps.
*/
private int remaining = 0;
/**
* The window holding the list of transitions for state collapsing.
*/
private TransitionWindow transitionWindow = null;
/** The state last selected for state collapsing. */
private State collapseState = null;
/** The final answer, or null if not done. */
private String computedRE = null;
/**
* The state IDs of each of the steps. Fine, this sucks. So sue me.
*/
private static final int CREATE_SINGLE_FINAL = 0,
TRANSITIONS_TO_SINGLE_FINAL = 1, CONVERT_TRANSITIONS = 2,
CREATE_EMPTY_TRANSITIONS = 3, COLLAPSE_STATES = 4, FINISHED = 200;
}