/* * 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 file.xml; import java.io.Serializable; import java.util.ArrayList; import java.util.Comparator; import java.util.Iterator; import java.util.Map; import java.util.Set; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import automata.Automaton; import automata.Note; import automata.State; import automata.Transition; import automata.graph.AutomatonGraph; import automata.graph.LayoutAlgorithm; import automata.graph.layout.GEMLayoutAlgorithm; import automata.mealy.MooreMachine; import file.DataException; import automata.turing.TMState; import automata.turing.TuringMachine; import debug.EDebug; import java.awt.Point; /** * This is an abstract implementation of a transducer that has methods common to * all automaton transducers. * * @author Thomas Finley */ public abstract class AutomatonTransducer extends AbstractTransducer { /** * Returns an empty automaton of the correct type. This method is used by * {@link #fromDOM}. * * @param document * the DOM document that is being read * @return an empty automaton */ protected abstract Automaton createEmptyAutomaton(Document document); /** * Reads the states from the document and adds them to the automaton. Note * that in the event of error, the automaton may have been changed up until * the point where the error was encountered. * * @param document * the DOM document to read states from * @param automaton * the automaton to add states to * @param locatedStates * if not null, this set will be filled with * those states that have their X and Y coordinates specified in * the DOM and do not need to be laid out * @return a map from state identifiers to the specific state * @throws DataException * in the case of non-numeric, negative, or duplicate IDs * @see #readTransitions */ protected Map readStates(Node node, Automaton automaton, Set locatedStates, Document document) { Map i2s = new java.util.HashMap(); if(node == null) return i2s; NodeList allNodes = node.getChildNodes(); ArrayList stateNodes = new ArrayList(); for (int k = 0; k < allNodes.getLength(); k++) { if (allNodes.item(k).getNodeName().equals(STATE_NAME)) { stateNodes.add(allNodes.item(k)); } } // Map state IDs to states, in an attempt to add in numeric // things first. A specialized Comparator is helpful here. Map i2sn = new java.util.TreeMap(new Comparator() { public int compare(Object o1, Object o2) { if (o1 instanceof Integer && !(o2 instanceof Integer)) return -1; if (o1 instanceof Integer) return ((Integer) o1).intValue() - ((Integer) o2).intValue(); if (o2 instanceof Integer) return 1; return ((Comparable) o1).compareTo(o2); } }); createState(stateNodes, i2sn, automaton, locatedStates, i2s, false, document); // i2s = addBlocks(document, automaton, locatedStates, i2s); return i2s; } //Creates a state node protected void createState(ArrayList stateNodes, Map i2sn, Automaton automaton, Set locatedStates, Map i2s, boolean isBlock, Document document) { // Create the map of ids to state nodes. for (int i = 0; i < stateNodes.size(); i++) { Node stateNode = (Node) stateNodes.get(i); if (stateNode.getNodeType() != Node.ELEMENT_NODE) continue; // Extract the ID attribute. String idString = ((Element) stateNode).getAttribute(STATE_ID_NAME); // //System.out.println("State created with id " + idString); if (idString == null) throw new DataException( "State without id attribute encountered!"); Integer id = parseID(idString); // Check for duplicates. if (i2sn.put(id, stateNode) != null) throw new DataException("The state ID " + id + " appears twice!"); } // Go through the map, and turn the state nodes into states. Iterator it = i2sn.keySet().iterator(); while (it.hasNext()) { Integer id = (Integer) it.next(); Element stateNode = (Element) i2sn.get(id); // Get the fields of this state. Map e2t = elementsToText(stateNode); // Create the state. java.awt.Point p = new java.awt.Point(); boolean hasLocation = true; // Try to get the X coord. double x = 0, y = 0; try { x = Double.parseDouble(e2t.get(STATE_X_COORD_NAME).toString()); } catch (NullPointerException e) { hasLocation = false; } catch (NumberFormatException e) { throw new DataException("The x coordinate " + e2t.get(STATE_X_COORD_NAME) + " could not be read for state " + id + "."); } // Try to get the Y coord. try { y = Double.parseDouble(e2t.get(STATE_Y_COORD_NAME).toString()); } catch (NullPointerException e) { hasLocation = false; } catch (NumberFormatException e) { throw new DataException("The y coordinate " + e2t.get(STATE_Y_COORD_NAME) + " could not be read for state " + id + "."); } p.setLocation(x, y); // Create the state. State state = null; // if (!isBlock){ if (!(automaton instanceof TuringMachine)){ state = automaton.createStateWithId(p, id.intValue()); } else { Node tempNode = null; if (e2t.containsKey(FILE_NAME)){ String fileName = e2t.get(FILE_NAME).toString(); tempNode = document.getDocumentElement() .getElementsByTagName(fileName).item(0); Automaton temp = (TuringMachine) readAutomaton(tempNode, document); //MERLIN MERLIN MERLIN MERLIN MERLIN// // EDebug.print("Are we or not creating a block?"); state = ((TuringMachine) automaton).createInnerTM(p, temp, fileName, id.intValue()); } else{ state = ((TuringMachine) automaton).createTMStateWithID(p, id.intValue()); } } if (hasLocation && locatedStates != null) locatedStates.add(state); i2s.put(id, state); String name = ((Element) stateNode).getAttribute(STATE_NAME_NAME); if(name.equals("")) state.setName("q"+id.intValue()); else state.setName(name); // Add various attributes. if (e2t.containsKey(STATE_NAME_NAME)) state.setName((String) e2t.get(STATE_NAME_NAME)); if (e2t.containsKey(STATE_LABEL_NAME)) state.setLabel((String) e2t.get(STATE_LABEL_NAME)); if (e2t.containsKey(STATE_FINAL_NAME)) automaton.addFinalState(state); if (e2t.containsKey(STATE_INITIAL_NAME)) automaton.setInitialState(state); /* * If it is a Moore machine, add state output. */ if(automaton instanceof MooreMachine && e2t.containsKey(MooreTransducer.STATE_OUTPUT_NAME)) ((MooreMachine) automaton).setOutput(state, (String) e2t.get(MooreTransducer.STATE_OUTPUT_NAME)); } } //Add the blocks protected void addBlocks(Node node, Automaton automaton, Set locatedStates, Map i2s, Document document) { assert(automaton instanceof TuringMachine); //this code should really be in TMTransducer, but I see why it's here if(node == null) return; if (!node.hasChildNodes()) return; NodeList allNodes = node.getChildNodes(); ArrayList blockNodes = new ArrayList(); for (int k = 0; k < allNodes.getLength(); k++) { if (allNodes.item(k).getNodeName().equals(BLOCK_NAME)) { blockNodes.add(allNodes.item(k)); } } Map i2sn = new java.util.TreeMap(new Comparator() { public int compare(Object o1, Object o2) { if (o1 instanceof Integer && !(o2 instanceof Integer)) return -1; if (o1 instanceof Integer) return ((Integer) o1).intValue() - ((Integer) o2).intValue(); if (o2 instanceof Integer) return 1; return ((Comparable) o1).compareTo(o2); } }); createState(blockNodes, i2sn, automaton, locatedStates, i2s, true, document); // return i2s; } /** * Used by the {@link #readTransitions}method. This should be overridden by * subclasses. * * @param from * the from state * @param to * the to state * @param node * the DOM node corresponding to the transition * @param e2t * elements to text from {@link #elementsToText} * @return the new transition * @see #readTransitions */ protected abstract Transition createTransition(State from, State to, Node node, Map e2t, boolean isBlock); /** * Reads the transitions from the document and adds them to the automaton. * Note that in the event of error, the automaton may have been changed up * until the point where the error was encountered. * * @param document * the DOM document to read transitions from * @param automaton * the automaton to add transitions to * @param id2state * the map of ID objects to a state * @throws DataException * in the case of absent from/to states * @see #createTransition * @see #readStates */ protected void readTransitions(Node parent, Automaton automaton, Map id2state) { Map i2s = new java.util.HashMap(); if(parent == null || automaton == null) return; NodeList allNodes = parent.getChildNodes(); ArrayList tNodes = new ArrayList(); boolean bool = false; for (int k = 0; k < allNodes.getLength(); k++) { if (allNodes.item(k).getNodeName().equals(TRANSITION_NAME)) { tNodes.add(allNodes.item(k)); } } // Create the transitions. for (int i = 0; i < tNodes.size(); i++) { Node tNode = (Node) tNodes.get(i); // Get the subelements of this transition. Map e2t = elementsToText(tNode); // Get the from state. String isBlock = ((Element) tNode).getAttribute("block"); if(isBlock.equals("true")){ bool = true; //We have a block transition. } String fromName = (String) e2t.get(TRANSITION_FROM_NAME); if (fromName == null) throw new DataException("A transition has no from state!"); int id = parseID(fromName).intValue(); State from = automaton.getStateWithID(id); if (from == null) throw new DataException("A transition is defined from " + "non-existent state " + id + "!"); // Get the to state. String toName = (String) e2t.get(TRANSITION_TO_NAME); if (toName == null) throw new DataException("A transition has no to state!"); id = parseID(toName).intValue(); State to = automaton.getStateWithID(id); if (to == null) throw new DataException("A transition is defined to " + "non-existent state " + id + "!"); // Now, make the transition. Transition transition = createTransition(from, to, tNode, e2t, bool); automaton.addTransition(transition); bool = false; //deal with the shapiness of the transition, if the file specifies it. //add controlX and controlY String controlX = (String) e2t.get(TRANSITION_CONTROL_X); String controlY = (String) e2t.get(TRANSITION_CONTROL_Y); if (controlX != null && controlY != null){ Point p = new Point(Integer.parseInt(controlX), Integer.parseInt(controlY)); transition.setControl(p); } else{ //explicit is better than implicit transition.setControl(null); } } } /** * Used to map a string means to encode a state ID to some unique identifier * object. * * @param string * a string that encodes a state ID * @return an object that is unique for this state * @throws DataException * if the state ID is not in a supported format */ protected static Integer parseID(String string) { try { int num = Integer.parseInt(string); return new Integer(num); } catch (NumberFormatException e) { return new Integer(-1); } } /** * Perform graph layout on the automaton if necessary. This is performed for * those XML files with states that do not have their x and y tags * specified. * * @param automaton * the automaton to lay out * @param locStates * the states that have the x and y tags in the DOM * representation and should be kept as "isonodes" in the layout * algorithm */ private void performLayout(Automaton automaton, Set locStates) { // Apply the graph layout algorithm to those states that // appeared without the and tags. if (locStates.size() == automaton.getStates().length) return; AutomatonGraph graph = new AutomatonGraph(automaton); LayoutAlgorithm layout = new GEMLayoutAlgorithm(); for (int i = 0; i < 3; i++) // Do it a few times... layout.layout(graph, locStates); if (locStates.size() < 2) { // Make sure things don't get too large or too small in // the event that sufficient reference for scaling is not // present. graph.moveWithinFrame(new java.awt.Rectangle(20, 20, 425, 260)); } graph.moveAutomatonStates(); } /** * Given a document, this will return the corresponding automaton encoded in * the DOM document. * * @param document * the DOM document to convert * @return the {@link automata.fsa.FiniteStateAutomaton}instance */ public java.io.Serializable fromDOM(Document document) { automatonMap.clear(); Automaton a = createEmptyAutomaton(document); Node parent = document.getDocumentElement() .getElementsByTagName(AUTOMATON_NAME).item(0); if(parent == null) parent = document.getDocumentElement(); return readAutomaton(parent, document); } public java.io.Serializable readAutomaton(Node parent, Document document) { Set locatedStates = new java.util.HashSet(); Automaton root = createEmptyAutomaton(document); if(parent == null) return root; readBlocks(parent, root, locatedStates, document); // Read the states and transitions. readTransitions(parent, root, readStates(parent, root, locatedStates, document)); //read the notes readnotes(parent, root, document); // Do the layout if necessary. performLayout(root, locatedStates); automatonMap.put(parent.getNodeName(), root); return root; } private void readnotes(Node parent, Automaton root, Document document) { NodeList allNodes = parent.getChildNodes(); ArrayList noteNodes = new ArrayList(); for (int k = 0; k < allNodes.getLength(); k++) { if (allNodes.item(k).getNodeName().equals(NOTE_NAME)) { noteNodes.add(allNodes.item(k)); } } for (int i = 0; i < noteNodes.size(); i++) { Node noteNode = (Node) noteNodes.get(i); if (noteNode.getNodeType() != Node.ELEMENT_NODE) continue; Map e2t = elementsToText(noteNode); java.awt.Point p = new java.awt.Point(); boolean hasLocation = true; Object obj = (e2t).get(NOTE_TEXT_NAME); if(obj == null) continue; String textString = obj.toString(); // Try to get the X coord. double x = 0, y = 0; try { x = Double.parseDouble(e2t.get(STATE_X_COORD_NAME).toString()); } catch (NullPointerException e) { hasLocation = false; } catch (NumberFormatException e) { throw new DataException("The x coordinate " + e2t.get(STATE_X_COORD_NAME) + " could not be read for the note with text " + textString + "."); } // Try to get the Y coord. try { y = Double.parseDouble(e2t.get(STATE_Y_COORD_NAME).toString()); } catch (NullPointerException e) { hasLocation = false; } catch (NumberFormatException e) { throw new DataException("The y coordinate " + e2t.get(STATE_Y_COORD_NAME) + " could not be read for the note with text " + textString + "."); } p.setLocation(x, y); root.addNote(new Note(p, textString)); } } /** * @param document * @param a * */ private void readBlocks(Node parent, Automaton root, Set states, Document document) { Map i2b = new java.util.HashMap(); addBlocks(parent, root, states, i2b, document); } /** * Produces a DOM element that encodes a given state. * * @param document * the document to create the state in * @param state * the state to encode * @return the newly created element that encodes the state * @see #createTransitionElement * @see #toDOM */ protected Element createStateElement(Document document, State state, Automaton container) { // Start the creation of the state tag. Element se = createElement(document, STATE_NAME, null, null); se.setAttribute(STATE_ID_NAME, "" + state.getID()); // Encode position. se.appendChild(createElement(document, STATE_X_COORD_NAME, null, "" + state.getPoint().getX())); se.appendChild(createElement(document, STATE_Y_COORD_NAME, null, "" + state.getPoint().getY())); // Encode label, if set. if (state.getLabel() != null) se.appendChild(createElement(document, STATE_LABEL_NAME, null, state.getLabel())); // Encode the name, if set. if (state.getName() != null) se.setAttribute(STATE_NAME_NAME, "" + state.getName()); // Encode whether the state is initial. // State parent = state.getParentBlock(); // Automaton a = null; // if (parent != null) { // a = (Automaton) container.getBlockMap().get( // parent.getInternalName()); // } // if (a == null) { // a = container; // } //MERLIN MERLIN MERLIN MERLIN MERLIN// Automaton a = state.getAutomaton(); if (a.getInitialState() == state) se.appendChild(createElement(document, STATE_INITIAL_NAME, null, null)); // Encode whether the state is final. if (a.isFinalState(state)) se.appendChild(createElement(document, STATE_FINAL_NAME, null, null)); // Return the completed state encoding element. return se; } /** * Produces a DOM element that encodes a given transition. This * implementation creates a transition element with only the "from" and "to" * elements inserted. The intention is that subclasses will use this to get * the minimal transition element, and fill in whatever else is required * themselves. * * @param document * the document to create the state in * @param transition * the transition to encode * @return the newly created element that encodes the state * @see #createStateElement * @see #toDOM */ protected Element createTransitionElement(Document document, Transition transition) { // Start the creation of the transition. Element te = createElement(document, TRANSITION_NAME, null, null); // Encode the from state. te.appendChild(createElement(document, TRANSITION_FROM_NAME, null, "" + transition.getFromState().getID())); // Encode the to state. te.appendChild(createElement(document, TRANSITION_TO_NAME, null, "" + transition.getToState().getID())); //if the transition has a control point defined,then get that too if (transition.getControl() != null){ Point p = transition.getControl(); te.appendChild(createElement(document, TRANSITION_CONTROL_X, null, p.x+"")); te.appendChild(createElement(document, TRANSITION_CONTROL_Y, null, p.y+"")); } // Return the completed transition encoding element. return te; } /** * @param doc * @param tempAuto * @return */ protected Element createBlockElement(Document document, TMState block, Automaton container) { Element be = createElement(document, BLOCK_NAME, null, null); be.setAttribute(STATE_ID_NAME, "" + block.getID()); if (block.getName() != null) be.setAttribute(STATE_NAME_NAME, "" + block.getName()); be.appendChild(createElement(document, FILE_NAME, null, "" + block.getInternalName())); // Encode position. be.appendChild(createElement(document, STATE_X_COORD_NAME, null, "" + block.getPoint().getX())); be.appendChild(createElement(document, STATE_Y_COORD_NAME, null, "" + block.getPoint().getY())); // Encode whether the block is initial. // State parent = block.getParentBlock(); // Automaton a = null; // if (parent != null) { // a = (Automaton) container.getBlockMap().get( // parent.getInternalName()); // } // if (a == null) { // a = container; // } //MERLIN MERLIN MERLIN MERLIN MERLIN// Automaton a = block.getAutomaton(); //this will fetch the parent - I see no reason to go through the block map if (a.getInitialState() == block) be.appendChild(createElement(document, STATE_INITIAL_NAME, null, null)); // Encode whether the state is final. if (a.isFinalState(block)) be.appendChild(createElement(document, STATE_FINAL_NAME, null, null)); return be; } /** * @param doc * @param tempAuto * @return */ protected Element createAutomatonElement(Document document, Automaton auto, String name) { Element se = document.getDocumentElement(); Element be = createElement(document, name, null, null); se.appendChild(be); //System.out.println("auto: " + auto); writeFields(document, auto, be); return be; } /** * Given a JFLAP automaton, this will return the corresponding DOM encoding * of the structure. * * @param structure * the JFLAP automaton to encode * @return a DOM document instance */ public Document toDOM(java.io.Serializable structure) { Automaton automaton = (Automaton) structure; originalAutomaton = automaton; Document doc = newEmptyDocument(); Element se = doc.getDocumentElement(); se.appendChild(createAutomatonElement(doc, automaton, AUTOMATON_NAME)); // Return the completed document. return doc; } private Element writeFields(Document doc, Automaton auto, Element se) { // Add the states as subelements of the structure element. State[] states = auto.getStates(); if (states.length > 0) se.appendChild(createComment(doc, COMMENT_STATES)); if (auto instanceof TuringMachine) for (int i = 0; i < states.length; i++) se.appendChild(createBlockElement(doc, (TMState)states[i], auto)); else for (int i = 0; i < states.length; i++) se.appendChild(createStateElement(doc, states[i], auto)); // Add the transitions as subelements of the structure element. Transition[] transitions = auto.getTransitions(); if (transitions.length > 0) se.appendChild(createComment(doc, COMMENT_TRANSITIONS)); for (int i = 0; i < transitions.length; i++) se.appendChild(createTransitionElement(doc, transitions[i])); // Add the Automatons the blocks refer to as sub elements of the // structure element. //only really need an internal name and a full TuringMachine //MERLIN MERLIN MERLIN MERLIN MERLIN// if (auto instanceof TuringMachine){ //there should not be building blocks in non-Turing Machines Map references = ((TuringMachine)auto).getBlockMap(); Iterator refer = references.keySet().iterator(); if (refer.hasNext()) se.appendChild(createComment(doc, COMMENT_AUTOMATA)); while (refer.hasNext()) { String name = (String) refer.next(); if (!automatonMap.containsKey((Automaton) references.get(name))) { se.appendChild(createAutomatonElement(doc, (Automaton) references.get(name), name)); automatonMap.put(name, auto); } } } //Add the sticky notes at the very end ArrayList notes = auto.getNotes(); for(int k = 0; k < notes.size(); k++){ se.appendChild(createNoteElement(doc, (Note)notes.get(k))); } return se; } private Node createNoteElement(Document doc, Note note) { // Start the creation of the transition. Element ne = createElement(doc, NOTE_NAME, null, null); // Encode the from state. ne.appendChild(createElement(doc, NOTE_TEXT_NAME, null, "" + note.getText())); // Encode the to state. ne.appendChild(createElement(doc, STATE_X_COORD_NAME, null, "" + note.getLocation().getX())); ne.appendChild(createElement(doc, STATE_Y_COORD_NAME, null, "" + note.getLocation().getY())); // Return the completed note encoding element. return ne; } private Map automatonMap = new java.util.HashMap(); private Automaton originalAutomaton = null; private static final String AUTOMATON_NAME = "automaton"; /** The comment for the list of Automatons. */ private static final String COMMENT_AUTOMATA = "The list of automata"; /** The tag name for individual block elements. */ private static final String FILE_NAME = "tag"; /** The tag name for individual block elements. */ public static final String BLOCK_NAME = "block"; /** The tag name for individual state elements. */ public static final String STATE_NAME = "state"; /** The attribute name for the state ID. */ public static final String STATE_ID_NAME = "id"; /** The tag name for the X coordinate. */ public static final String STATE_X_COORD_NAME = "x"; /** The tag name for the Y coordinate. */ public static final String STATE_Y_COORD_NAME = "y"; /** The tag name for the optional label of the state. */ public static final String STATE_LABEL_NAME = "label"; /** The tag name for the optional special name of the state. */ public static final String STATE_NAME_NAME = "name"; /** The tag name for the final state. */ public static final String STATE_FINAL_NAME = "final"; /** The tag name for the optional special name of the state. */ public static final String STATE_INITIAL_NAME = "initial"; /** The tag name for the individual transition elements. */ public static final String TRANSITION_NAME = "transition"; /** The tag name for the from state ID. */ public static final String TRANSITION_FROM_NAME = "from"; /** The tag name for the to state ID. */ public static final String TRANSITION_TO_NAME = "to"; /**The tag name for the x coordinate of the control point for a transition. */ public static final String TRANSITION_CONTROL_X = "controlx"; /**The tag name for the y coordinate of the control point for a transition. */ public static final String TRANSITION_CONTROL_Y = "controly"; /** The comment for the list of states. */ private static final String COMMENT_STATES = "The list of states."; /** The comment for the list of transitions. */ private static final String COMMENT_TRANSITIONS = "The list of transitions."; /** The tag name for the individual note elements. */ public static final String NOTE_NAME = "note"; /** The tag name for the text of the note elements. */ public static final String NOTE_TEXT_NAME = "text"; /**The tag name for the block transition */ private static final String IS_BLOCK = "block"; }