/*
* 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.grammar.automata;
import automata.Automaton;
import automata.State;
import automata.Transition;
import grammar.Grammar;
import grammar.Production;
import gui.environment.FrameFactory;
import gui.grammar.*;
import gui.viewer.SelectionDrawer;
import java.util.*;
import javax.swing.JOptionPane;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
/**
* The ConvertController
abstract class handles the operation to
* convert an automaton to a PDA. At its most basic level, it simply maps
* objects in the automaton to a set of productions in a grammar.
*
* @author Thomas Finley
*/
public abstract class ConvertController {
/**
* Instantiates a ConvertController
for an automaton.
*
* @param pane
* the convert pane that holds the automaton pane and the grammar
* table
* @param drawer
* the selection drawer where the automaton is made
* @param automaton
* the automaton to build the ConvertController
* for; this automaton should be editable
* @see #fillMap
*/
public ConvertController(ConvertPane pane, SelectionDrawer drawer,
Automaton automaton) {
this.convertPane = pane;
this.automaton = automaton;
this.table = pane.getTable();
this.drawer = drawer;
table.getSelectionModel().addListSelectionListener(
new ListSelectionListener() {
public void valueChanged(ListSelectionEvent e) {
changeSelection();
}
});
}
/**
* Changes the selection in the automaton selection pane. This method is
* usually called as a result of the list selection changing.
*/
protected void changeSelection() {
ListSelectionModel model = table.getSelectionModel();
int min = model.getMinSelectionIndex(), max = model
.getMaxSelectionIndex();
drawer.clearSelected();
if (min == -1) {
convertPane.getAutomatonPane().repaint();
return;
}
for (; min <= max; min++) {
if (!model.isSelectedIndex(min))
continue;
Production p = table.getGrammarModel().getProduction(min);
Object o = productionToObject.get(p);
if (o == null)
continue;
if (o instanceof State)
drawer.addSelected((State) o);
else
drawer.addSelected((Transition) o);
}
convertPane.getAutomatonPane().repaint();
}
/**
* Fills the maps. This method should be called by subclasses after the
* constructor, whenever the controller is ready to produce the productions.
*/
protected void fillMap() {
State[] states = automaton.getStates();
for (int i = 0; i < states.length; i++) {
Production[] prods = getProductions(states[i]);
if (prods.length == 0)
continue;
objectToProduction.put(states[i], prods);
for (int j = 0; j < prods.length; j++)
productionToObject.put(prods[j], states[i]);
}
// Now let's get the other cannon!
Transition[] transitions = automaton.getTransitions();
for (int i = 0; i < transitions.length; i++) {
Production[] prods = getProductions(transitions[i]);
if (prods.length == 0)
continue;
objectToProduction.put(transitions[i], prods);
for (int j = 0; j < prods.length; j++)
productionToObject.put(prods[j], transitions[i]);
}
}
/**
* Adds productions to the grammar pane, and makes them selected.
*
* @param productions
* the collection that holds productions to add
*/
private void addProductions(Collection productions) {
Iterator it = productions.iterator();
if (!it.hasNext())
return;
GrammarTableModel model = table.getGrammarModel();
int min = 1000000000, max = 0;
while (it.hasNext()) {
Production p = (Production) it.next();
int row = model.addProduction(p);
min = Math.min(min, row);
max = Math.max(max, row);
}
table.setRowSelectionInterval(min, max);
}
/**
* This method reveals the productions for a particular object, whether it
* be a state or transition.
*
* @param object
* the object whose productions we should reveal
* @return a non-empty array of productions revealed, null
if
* there are no productions for this object, or an empty array if
* there are productions for this object and they have already been
* revealed
*/
public Production[] revealObjectProductions(Object object) {
Production[] p = (Production[]) objectToProduction.get(object);
if (p == null || p.length == 0) {
// There are no productions!
JOptionPane.showMessageDialog(convertPane,
"There are no productions for that object!");
return null;
}
if (alreadyDone.contains(object)) {
// Been there, done that.
JOptionPane.showMessageDialog(convertPane,
"This object has already been converted!");
return new Production[0];
}
alreadyDone.add(object);
addProductions(Arrays.asList(p));
return p;
}
/**
* This will reveal the productions for one object chosen at quasirandom
* (i.e. whatever comes first).
*
* @return the object whose productions were revealed, or null
* if no object remains to have its productions revealed
*/
public Object revealRandomProductions() {
Iterator it = objectToProduction.entrySet().iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
Object key = entry.getKey();
if (alreadyDone.contains(key))
continue;
Production[] p = (Production[]) objectToProduction.get(key);
addProductions(Arrays.asList(p));
alreadyDone.add(entry.getKey());
return key;
}
return null;
}
/**
* This will reveal all the productions for all objects remaining to be
* revealed.
*
* @return the number of objects revealed
*/
public int revealAllProductions() {
Set remaining = new HashSet(objectToProduction.keySet());
remaining.removeAll(alreadyDone);
int number = remaining.size();
Iterator it = remaining.iterator();
Collection ps = new ArrayList();
while (it.hasNext()) {
Production[] p = (Production[]) objectToProduction.get(it.next());
ps.addAll(Arrays.asList(p));
}
addProductions(ps);
alreadyDone.addAll(remaining);
return number;
}
/**
* This method sets all objects that may be tranformed to productions and as
* yet have been unselected to be selected.
*
* @return an array of the objects which as yet have not been transformed
*/
public Object[] highlightUntransformed() {
HashSet unselectedSet = new HashSet(objectToProduction.keySet());
unselectedSet.removeAll(alreadyDone);
Object[] unselected = unselectedSet.toArray();
drawer.clearSelected();
for (int i = 0; i < unselected.length; i++)
if (unselected[i] instanceof State)
drawer.addSelected((State) unselected[i]);
else
drawer.addSelected((Transition) unselected[i]);
convertPane.getAutomatonPane().repaint();
return unselected;
}
/**
* Returns the grammar that resulted from the conversion. This method should
* only be called once the conversion has finished.
*
* @return the grammar that resulted from the conversion
* @throws GrammarCreationException
* if there is some impediment to the creation of the grammar
*/
protected abstract Grammar getGrammar();
/**
* Exports the grammar defined to a new window, or fails if the grammar is
* not yet converted.
*
* @return the grammar that was converted as returned by getGrammar
;
* if there was an error in getGrammar
or if the
* conversion is unfinished, null
is returned
* @see #getGrammar
*/
public Grammar exportGrammar() {
// Are any yet unconverted?
if (objectToProduction.keySet().size() != alreadyDone.size()) {
highlightUntransformed();
JOptionPane
.showMessageDialog(
convertPane,
"Conversion unfinished! Objects to convert are highlighted.",
"Conversion Unfinished", JOptionPane.ERROR_MESSAGE);
changeSelection();
return null;
}
try {
Grammar g = getGrammar();
FrameFactory.createFrame(g);
return g;
} catch (GrammarCreationException e) {
JOptionPane.showMessageDialog(convertPane, e.getMessage(), "Error",
JOptionPane.ERROR_MESSAGE);
return null;
}
}
/**
* Returns the Automaton
.
*
* @return the Automaton
for this controller
*/
protected Automaton getAutomaton() {
return automaton;
}
/**
* Returns the productions for a particular state. This method will only be
* called once.
*
* @param state
* the state to get the productions for
* @return an array containing the productions that correspond to a
* particular state
*/
protected Production[] getProductions(State state) {
return new Production[0];
}
/**
* Returns the productions for a particular transition. This method will
* only be called once.
*
* @param transition
* the transition to get the productions for
* @return an array containing the productions that correspond to a
* particular transition
*/
protected Production[] getProductions(Transition transition) {
return new Production[0];
}
/**
* Returns the table model for the grammar table.
*
* @return the table model for the grammar table
*/
protected GrammarTableModel getModel() {
return table.getGrammarModel();
}
/**
* The mapping of either states or transitions to an array of productions.
* If there are no productions for an object, the map will not contain the
* key.
*/
protected HashMap objectToProduction = new HashMap();
/**
* The mapping of productions to whatever object they correspond to, which
* will be either a state or a transition.
*/
protected HashMap productionToObject = new HashMap();
/** Which objects have already been added? */
protected HashSet alreadyDone = new HashSet();
/**
* The convert pane that holds the automaton pane and the grammar table.
*/
protected ConvertPane convertPane;
/** The automaton we're converting. */
private Automaton automaton;
/** The selection drawer. */
private SelectionDrawer drawer;
/** The grammar table where the productions are stored. */
private GrammarTable table;
}