package rationals; import java.util.ArrayList; 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 rationals.transformations.TransformationsToolBox; /** * A class defining Automaton objects * * This class defines the notion of automaton. Following notations are used to * describe this class. * <p> * An automaton is a 5-uple <em>A = (X , Q , I , T , D)</em> where * <ul> * <li><em>X</em> is a finite set of labels named alphabet , * <li><em>Q</em> is a finite set of states, * <li><em>I</em>, included in <em>Q</em>, is the set of initial states, * <li><em>T</em>, included in <em>Q</em>, is the set of terminal states * <li>and <em>D</em> is the set of transitions, which is included in * <em>Q times X times Q</em> (transitions are triples <em>(q , l , q')</em> * where <em>q, q'</em> are states and <em>l</em> a label). * </ul> * The empty word, usually denoted by <em>epsilon</em> will be denoted here by * the symbol <em>@</em>. * <p> * In this implementation of automaton, any object may be a label, states are * instance of class <tt>State</tt> and transitions are intances of class * <tt>Transition</tt>. Only automata should create instances of states * through <tt>Automaton</tt> method <tt>newState</tt>. * @author yroos@lifl.fr * @author bailly@lifl.fr * @version $Id: Automaton.java 15 2008-09-20 13:29:35Z oqube $ * @see Transition State */ public class Automaton<T extends Builder<T>> implements Acceptor, StateMachine, Rational, Cloneable { /* the identification of this automaton */ private Object id; protected T builder; /** * @return Returns the id. */ public Object getId() { return id == null ? "automaton" : id; } /** * @param id * The id to set. */ public void setId(Object id) { this.id = id; } // The set of all objects which are labels of // transitions of this automaton. protected Set<Object> alphabet; // The set of all states of this automaton. private Set<State> states; // the set of initial states private Set<State> initials; // the set of terminale states private Set<State> terminals; // Allows acces to transitions of this automaton // starting from a given state and labelled by // a given object. The keys of this map are instances // of class Key and // values are sets of transitions. private Map<Key, Set<Transition>> transitions; // Allows acces to transitions of this automaton // arriving to a given state and labelled by // a given object. The keys of this map are instances // of class Key and // values are sets of transitions. private Map<Key, Set<Transition>> reverse; // bonte private StateFactory stateFactory = new DefaultStateFactory(this); private Map<Object, State> labels = new HashMap<Object, State>(); /** * @return */ public StateFactory getStateFactory() { return this.stateFactory; } /** * @param factory */ public void setStateFactory(StateFactory factory) { this.stateFactory = factory; factory.setAutomaton(this); } /** * Returns an automaton which recognizes the regular language associated with * the regular expression <em>@</em>, where <em>@</em> denotes the empty * word. * @return an automaton which recognizes <em>@</em> */ public static Automaton epsilonAutomaton() { Automaton v = new Automaton(); v.addState(true, true); return v; } /** * Returns an automaton which recognizes the regular language associated with * the regular expression <em>l</em>, where <em>l</em> is a given label. * * @param label * any object that will be used as a label. * @return an automaton which recognizes <em>label</em> */ public static Automaton labelAutomaton(Object label) { Automaton v = new Automaton(); State start = v.addState(true, false); State end = v.addState(false, true); try { v.addTransition(new Transition(start, label, end)); } catch (NoSuchStateException x) { } return v; } /** * Returns an automaton which recognizes the regular language associated with * the regular expression <em>u</em>, where <em>u</em> is a given word. * * @param word * a List of Object interpreted as a word * @return an automaton which recognizes <em>label</em> */ public static Automaton labelAutomaton(List word) { Automaton v = new Automaton(); State start = null; if (word.isEmpty()) { v.addState(true, true); return v; } else start = v.addState(true, false); State end = null; try { for (Iterator i = word.iterator(); i.hasNext();) { Object o = i.next(); end = v.addState(false, !i.hasNext()); v.addTransition(new Transition(start, o, end)); start = end; } } catch (NoSuchStateException x) { } return v; } /** * Creates a new empty automaton which contains no state and no transition. An * empty automaton recognizes the empty language. */ public Automaton() { this(null); } /** * Create a new empty automaton with given state factory. * * @param sf * the StateFactory object to use for creating new states. May be * null. */ public Automaton(StateFactory sf) { this.stateFactory = sf == null ? new DefaultStateFactory(this) : sf; alphabet = new HashSet<Object>(); states = stateFactory.stateSet(); initials = stateFactory.stateSet(); terminals = stateFactory.stateSet(); transitions = new HashMap<Key, Set<Transition>>(); reverse = new HashMap<Key, Set<Transition>>(); } /** * Returns a new instance of state which will be initial and terminal or not * depending of parameters. * * @param initial * if true, the new state will be initial; otherwise this state will * be non initial. * @param terminal * if true, the new state will be terminal; otherwise this state will * be non terminal. * @return a new state, associated with this automaton. This new state should * be used only with this automaton in order to create a new * transition for this automaton. * @see Transition */ public State addState(boolean initial, boolean terminal) { State state = stateFactory.create(initial, terminal); if (initial) initials.add(state); if (terminal) terminals.add(state); states.add(state); return state; } /** * Returns the alphabet <em>X</em> associated with this automaton. * * @return the alphabet <em>X</em> associated with this automaton. */ public Set<Object> alphabet() { return alphabet; } /** * Returns the set of states <em>Q</em> associated with this automaton. * * @return the set of states <em>Q</em> associated with this automaton. * Objects which are contained in this set are instances of class * <tt>State</tt>. * @see State */ public Set<State> states() { return states; } /** * Returns the set of initial states <em>I</em> associated with this * automaton. * * @return the set of initial states <em>I</em> associated with this * automaton. Objects which are contained in this set are instances of * class <tt>State</tt>. * @see State */ public Set<State> initials() { return initials; } /** * Returns the set of terminal states <em>T</em> associated with this * automaton. * * @return set of terminal states <em>T</em> associated with this automaton. * Objects which are contained in this set are instances of class * <tt>State</tt>. * @see State */ public Set<State> terminals() { return terminals; } // Computes and return the set of all accessible states, starting // from a given set of states and using transitions // contained in a given Map protected Set<State> access(Set<State> start, Map<Key, Set<Transition>> map) { Set<State> current = start; Set<State> old; do { old = current; current = stateFactory.stateSet(); Iterator i = old.iterator(); while (i.hasNext()) { State e = (State) i.next(); current.add(e); Iterator j = alphabet.iterator(); while (j.hasNext()) { Iterator k = find(map, e, j.next()).iterator(); while (k.hasNext()) { current.add(((Transition) k.next()).end()); } } } } while (current.size() != old.size()); return current; } /** * Returns the set of all accessible states in this automaton. * * @return the set of all accessible states in this automaton. A state * <em>s</em> is accessible if there exists a path from an initial * state to <em>s</em>. Objects which are contained in this set are * instances of class <tt>State</tt>. * @see State */ public Set<State> accessibleStates() { return access(initials, transitions); } /** * Returns the set of states that can be accessed in this automaton starting * from given set of states * * @param states * a non null set of starting states * @return a - possibly empty - set of accessible states */ public Set<State> accessibleStates(Set<State> states) { return access(states, transitions); } /* * (non-Javadoc) * * @see rationals.Rational#accessibleStates(rationals.State) */ public Set<State> accessibleStates(State state) { Set<State> s = stateFactory.stateSet(); s.add(state); return access(s, transitions); } /** * Returns the set of co-accesible states for a given set of states, that is * the set of states from this automaton from which there exists a path to a * state in <code>states</code>. * * @param states * a non null set of ending states * @return a - possibly empty - set of coaccessible states */ public Set<State> coAccessibleStates(Set<State> states) { return access(states, reverse); } /** * Returns the set of all co-accessible states in this automaton. * * @return the set of all co-accessible states in this automaton. A state * <em>s</em> is co-accessible if there exists a path from this * state <em>s</em> to a terminal state. Objects which are contained * in this set are instances of class <tt>State</tt>. * @see State */ public Set<State> coAccessibleStates() { return access(terminals, reverse); } /** * Returns the set of all states which are co-accessible and accessible in * this automaton. * * @return the set of all states which are co-accessible and accessible in * this automaton. A state <em>s</em> is accessible if there exists * a path from an initial state to <em>s</em>. A state <em>s</em> * is co-accessible if there exists a path from this state <em>s</em> * to a terminal state. Objects which are contained in this set are * instances of class <tt>State</tt>. * @see State */ public Set<State> accessibleAndCoAccessibleStates() { Set<State> ac = accessibleStates(); ac.retainAll(coAccessibleStates()); return ac; } // Computes and return the set of all transitions, starting // from a given state and labelled by a given label // contained in a given Map protected Set<Transition> find(Map<Key, Set<Transition>> m, State e, Object l) { Key n = new Key(e, l); if (!m.containsKey(n)) return new HashSet<Transition>(); return m.get(n); } // add a given transition in a given Map protected void add(Map<Key, Set<Transition>> m, Transition t) { Key n = new Key(t.start(), t.label()); Set<Transition> s; if (!m.containsKey(n)) { s = new HashSet<Transition>(); m.put(n, s); } else s = m.get(n); s.add(t); } /** * Returns the set of all transitions of this automaton * * @return the set of all transitions of this automaton Objects which are * contained in this set are instances of class <tt>Transition</tt>. * @see Transition */ public Set<Transition> delta() { Set<Transition> s = new HashSet<Transition>(); for (Set<Transition> tr : transitions.values()) s.addAll(tr); return s; } /** * Returns the set of all transitions of this automaton starting from a given * state and labelled b a given label. * * @param state * a state of this automaton. * @param label * a label used in this automaton. * @return the set of all transitions of this automaton starting from state * <tt>state</tt> and labelled by <tt>label</tt>. Objects which * are contained in this set are instances of class * <tt>Transition</tt>. * @see Transition */ public Set<Transition> delta(State state, Object label) { return find(transitions, state, label); } /** * Returns the set of all transitions from state <code>from</code> to state * <code>to</code>. * * @param from * starting state * @param to * ending state * @return a Set of Transition objects */ public Set<Transition> deltaFrom(State from, State to) { Set<Transition> t = delta(from); for (Iterator i = t.iterator(); i.hasNext();) { Transition tr = (Transition) i.next(); if (!to.equals(tr.end())) i.remove(); } return t; } /** * Return all transitions from a State * * @param state * start state * @return a new Set of transitions (maybe empty) */ public Set<Transition> delta(State state) { Set<Transition> s = new HashSet<Transition>(); for (Object lt : alphabet) s.addAll(delta(state, lt)); return s; } /** * Returns all transitions from a given set of states. * * @param s * a Set of State objects * @return a Set of Transition objects */ public Set<Transition> delta(Set<State> s) { Set<Transition> ds = new HashSet<Transition>(); for (State st : s) ds.addAll(delta(st)); return ds; } /** * Return a mapping from couples (q,q') of states to all (q,l,q') transitions * from q to q' * * @return a Map */ public Map couples() { // loop on transition map keys Iterator<Map.Entry<Key, Set<Transition>>> it = transitions.entrySet() .iterator(); Map<Couple, Set<Transition>> ret = new HashMap<Couple, Set<Transition>>(); while (it.hasNext()) { Map.Entry<Key, Set<Transition>> e = it.next(); // get start and end state State st = e.getKey().s; Iterator<Transition> trans = e.getValue().iterator(); while (trans.hasNext()) { Transition tr = trans.next(); State nd = tr.end(); Couple cpl = new Couple(st, nd); Set<Transition> s = (Set<Transition>) ret.get(cpl); if (s == null) s = new HashSet<Transition>(); s.add(tr); ret.put(cpl, s); } } return ret; } /** * Returns the set of all transitions of the reverse of this automaton * * @return the set of all transitions of the reverse of this automaton. A * reverse of an automaton <em>A = (X , Q , I , T , D)</em> is the * automaton <em>A' = (X , Q , T , I , D')</em> where <em>D'</em> * is the set <em>{ (q , l , q') | (q' , l , q) in D}</em>. Objects * which are contained in this set are instances of class * <tt>Transition</tt>. * @see Transition */ public Set<Transition> deltaMinusOne(State state, Object label) { return find(reverse, state, label); } /** * Adds a new transition in this automaton if it is a new transition for this * automaton. The parameter is considered as a new transition if there is no * transition in this automaton which is equal to the parameter in the sense * of method <tt>equals</tt> of class <tt>Transition</tt>. * * @param transition * the transition to add. * @throws NoSuchStateException * if <tt>transition</tt> is <tt>null</<tt> * or if <tt>transition</tt> = <em>(q , l , q')</em> and <em>q</em> or * <em>q'</em> does not belong to <em>Q</em> the set of the states * of this automaton. */ public void addTransition(Transition transition) throws NoSuchStateException { if (!states.contains(transition.start()) || !states.contains(transition.end())) throw new NoSuchStateException(); if (!alphabet.contains(transition.label())) { alphabet.add(transition.label()); } add(transitions, transition); add(reverse, new Transition(transition.end(), transition.label(), transition.start())); } /** * the project method keeps from the Automaton only the transitions labelled * with the letters contained in the set alph, effectively computing a * projection on this alphabet. * * @param alph * the alphabet to project on */ public void projectOn(Set alph) { // remove unwanted transitions from ret Iterator<Map.Entry<Key, Set<Transition>>> trans = transitions.entrySet() .iterator(); Set<Transition> newtrans = new HashSet<Transition>(); while (trans.hasNext()) { Map.Entry<Key, Set<Transition>> entry = trans.next(); Key k = entry.getKey(); Iterator<Transition> tit = entry.getValue().iterator(); while (tit.hasNext()) { Transition tr = tit.next(); if (!alph.contains(k.l)) { // create epsilon transition newtrans.add(new Transition(k.s, null, tr.end())); // remove transtion tit.remove(); } } } // add newly created transitions if (!newtrans.isEmpty()) { for (Transition tr : newtrans) { add(transitions, tr); add(reverse, new Transition(tr.end(), tr.label(), tr.start())); } } // remove alphabet alphabet.retainAll(alph); } /** * returns a textual representation of this automaton. * * @return a textual representation of this automaton based on the converter * <tt>toAscii</tt>. * @see rationals.converters.toAscii */ public String toString() { return new rationals.converters.toAscii().toString(this); } /** * returns a copy of this automaton. * * @return a copy of this automaton with new instances of states and * transitions. */ public Object clone() { Automaton b; b = new Automaton(); Map<State, State> map = new HashMap<State, State>(); for (State e : states) map.put(e, b.addState(e.isInitial(), e.isTerminal())); for (Transition t : delta()) { try { b.addTransition(new Transition((State) map.get(t.start()), t.label(), (State) map.get(t.end()))); } catch (NoSuchStateException x) { } } return b; } private class Key { private State s; private Object l; protected Key(State s, Object l) { this.s = s; this.l = l; } public boolean equals(Object o) { if (o == null) return false; try { Key t = (Key) o; boolean ret = (l == null ? t.l == null : l.equals(t.l)) && (s == null ? t.s == null : s.equals(t.s)); return ret; } catch (ClassCastException x) { return false; } } public int hashCode() { int x, y; if (s == null) x = 0; else x = s.hashCode(); if (l == null) y = 0; else y = l.hashCode(); return y << 16 | x; } } /** * Returns true if this automaton accepts given word -- ie. sequence of * letters. Note that this method accepts words with letters not in this * automaton's alphabet, effectively recognizing all words from any alphabet * projected to this alphabet. * <p> * If you need standard recognition, use * * @see{accept(java.util.List)}. * @param word * @return */ public boolean prefixProjection(List word) { Set s = stepsProject(word); return !s.isEmpty(); } /** * Return the set of steps this automaton will be in after reading word. Note * this method skips letters not in alphabet instead of rejecting them. * * @param l * @return */ public Set<State> stepsProject(List word) { Set<State> s = initials(); Iterator it = word.iterator(); while (it.hasNext()) { Object o = it.next(); if (!alphabet.contains(o)) continue; s = step(s, o); if (s.isEmpty()) return s; } return s; } /* * (non-Javadoc) * * @see rationals.Acceptor#accept(java.util.List) */ public boolean accept(List<Object> word) { Set<State> s = TransformationsToolBox.epsilonClosure(steps(word), this); s.retainAll(terminals()); return !s.isEmpty(); } /** * Return true if this automaton can accept the given word starting from given * set. <em>Note</em> The ending state(s) need not be terminal for this * method to return true. * * @param state * a starting state * @param word * a List of objects in this automaton's alphabet * @return true if there exists a path labelled by word from s to at least one * other state in this automaton. */ public boolean accept(State state, List<Object> word) { Set<State> s = stateFactory.stateSet(); s.add(state); return !steps(s, word).isEmpty(); } /* * (non-Javadoc) * * @see rationals.Acceptor#steps(java.util.List) */ public Set<State> steps(List<Object> word) { Set<State> s = TransformationsToolBox.epsilonClosure(initials(), this); return steps(s, word); } /** * Return the set of states this automaton will be in after reading the word * from start states s. * * @param s * the set of starting states * @param word * the word to read. * @return the set of reached states. */ public Set<State> steps(Set<State> s, List<Object> word) { Iterator it = word.iterator(); while (it.hasNext()) { Object o = it.next(); s = step(s, o); if (s.isEmpty()) return s; } return s; } /** * Return the set of states this automaton will be in after reading the word * from singler start state s. * * @param st * the starting state * @param word * the word to read. * @return the set of reached states. */ public Set<State> steps(State st, List<Object> word) { Set<State> s = stateFactory.stateSet(); s.add(st); Iterator it = word.iterator(); while (it.hasNext()) { Object o = it.next(); s = step(s, o); if (s.isEmpty()) return s; } return s; } /** * Return the list of set of states this automaton will be in after reading * word from start state. Is start state is null, assume reading from * initials(). * * @param word * @param start */ public List<Set<State>> traceStates(List<Object> word, State start) { List<Set<State>> ret = new ArrayList<Set<State>>(); Set<State> s = null; if (start != null) { s = stateFactory.stateSet(); s.add(start); } else { s = initials(); } Iterator it = word.iterator(); while (it.hasNext()) { Object o = it.next(); if (!alphabet.contains(o)) continue; s = step(s, o); ret.add(s); if (s.isEmpty()) return null; } return ret; } /** * Returns the size of the longest word recognized by this automaton where * letters not belonging to its alphabet are ignored. * * * @param word * @return */ public int longestPrefixWithProjection(List word) { int lret = 0; Set<State> s = initials(); Iterator it = word.iterator(); while (it.hasNext()) { Object o = it.next(); if ((o == null) || !alphabet.contains(o)) { lret++; continue; } s = step(s, o); if (s.isEmpty()) break; lret++; } return lret; } /** * Return the set of states accessible in one transition from given set of * states s and letter o. * * @param s * @param o * @return */ public Set<State> step(Set<State> s, Object o) { Set<State> ns = stateFactory.stateSet(); Set<State> ec = TransformationsToolBox.epsilonClosure(s, this); Iterator it = ec.iterator(); while (it.hasNext()) { State st = (State) it.next(); Iterator it2 = delta(st).iterator(); while (it2.hasNext()) { Transition tr = (Transition) it2.next(); if (tr.label() != null && tr.label().equals(o)) ns.add(tr.end()); } } return ns; } /** * @param tr * @param msg */ public void updateTransitionWith(Transition tr, Object msg) { Object lbl = tr.label(); alphabet.remove(lbl); alphabet.add(msg); /* update transition map */ Key k = new Key(tr.start(), lbl); Set<Transition> s = transitions.remove(k); if (s != null) transitions.put(new Key(tr.start(), msg), s); /* update reverse map */ k = new Key(tr.end(), lbl); s = (Set<Transition>) reverse.remove(k); if (s != null) reverse.put(new Key(tr.end(), msg), s); tr.setLabel(msg); } /** * @param st * @return */ public Set<Transition> deltaMinusOne(State st) { Set<Transition> s = new HashSet<Transition>(); Iterator alphit = alphabet().iterator(); while (alphit.hasNext()) { s.addAll(deltaMinusOne(st, alphit.next())); } return s; } /** * Enumerate all prefix of words of length lower or equal than i in this * automaton. This method takes exponential time and space to execute: <em> * use with care !</em>. * * @param i * maximal length of words. * @return a Set of List of Object */ public Set enumerate(int ln) { Set<List> ret = new HashSet<List>(); class EnumState { /** * @param s * @param list */ public EnumState(State s, List<Object> list) { st = s; word = new ArrayList<Object>(list); } State st; List<Object> word; } ; LinkedList<EnumState> ll = new LinkedList<EnumState>(); List<Object> cur = new ArrayList<Object>(); for (Iterator i = initials.iterator(); i.hasNext();) { State s = (State) i.next(); if (s.isTerminal()) ret.add(new ArrayList()); ll.add(new EnumState(s, cur)); } do { EnumState st = (EnumState) ll.removeFirst(); Set trs = delta(st.st); List<Object> word = st.word; for (Iterator k = trs.iterator(); k.hasNext();) { Transition tr = (Transition) k.next(); word.add(tr.label()); if (word.size() <= ln) { EnumState en = new EnumState(tr.end(), word); ll.add(en); ret.add(en.word); } word.remove(word.size() - 1); } } while (!ll.isEmpty()); return ret; } /** * Create a new state with given label. The state is created with as neither * initial nor terminal. * * @param label * the state's label. May not be null. * @return the newly created state. */ public State state(Object label) { State s = labels.get(label); if (s == null) { s = stateFactory.create(false, false); states.add(s); labels.put(label, s); } return s; } /** * Starts creation of a new transition from the given state. Note that the * state is created with given label if it does not exists. * * @param o * the label of state to create transition from. may not be null. * @return a TransitionBuilder that can be used to create a new transition. */ public T from(Object o) { return builder.build(state(o), this); } public void setBuilder(T t) { this.builder = t; } }