/* * 01/07/2003 - 15:19:32 * * SAutomaton.java - * Copyright (C) 2003 Buero fuer Softwarearchitektur GbR * ralf.meyer@karneim.com * http://jrexx.sf.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ package com.karneim.util.collection.set; import com.karneim.util.collection.automaton.*; import java.util.*; import java.io.*; /** * This class represents a (non-)deterministic final automaton (NFA/DFA). * Use this class to create an automaton manually by adding states to the automaton and transitions to other states * or to browse through the automaton's states and implement your own matching strategies. *
to create an automaton manually try * *
import com.karneim.util.collection.set.*; *
public class Test { *
public static void main(String[] args) { *
SAutomaton automaton = new SAutomaton(); *
{@link IStatePro} s1 = automaton.addState(false); *
IStatePro s2 = automaton.addState(true); *
s1.addTransition(new {@link CharSet}("0123456789"),s2); *
s2.addTransition(new CharSet("0123456789"),s2); *
automaton.setStartState(s1); *
} *
} *
*
*
to browse through the automaton's states try * *
final {@link IStatePro} startState = automaton.getStartState(); *
final {@link StateProSet} states = new StateProSet(startState); *
final {@link StateProSet.Iterator} it = states.iterator(); *
for (IStatePro state=it.next(); state!=null; state=it.next()) { *
IStatePro.ITransition[] transitions = state.getTransitions(); *
for (int i=0; i transitions.length; ++i) { *
states.add(transitions[i].getToState()); *
System.out.println( *
"from " + transitions[i].getFromState() *
+ " through " + transitions[i].getCharSet() *
+ " to " + transitions[i].getToState() *
); *
} *
} *
*
*
to implement own matching strategies try * *
/** *
* returns true if input is an existing path through automaton's states *
* otherwise false. *
* *
public static boolean incompleteMatch(SAutomaton automaton,String input) { *
{@link IState} current = automaton.getStartState().visit(); *
for (int i=0; i input.length(); ++i) { *
current = current.next(input.charAt(i)); *
if (current == null) return false; *
} *
return true; *
} *
* * @author Ralf Meyer * @version 1.0 */ public class SAutomaton { /** * The listener interface for receiving change events of a SAutomaton. * The class that is interested in processing an automaton's change event implements this interface. * A listener instance of that class is registered with the automaton using the automaton's addChangeListener method. * @author Ralf Meyer * @version 1.0 */ public interface IChangeListener { /** The Automaton invokes this method on all registered listener if a new state has been added to the automaton. */ public void stateAdded(IStatePro state); /** The Automaton invokes this method on all registered listener if an existing state has been removed from the automaton. */ public void stateRemoved(IStatePro state); /** The Automaton invokes this method on all registered listener if the automaton's current startState has been changed. Both oldStartState and newStartState can be null. */ public void startStateChanged(IStatePro oldStartState, IStatePro newStartState); } protected class State implements IState { protected final AutomatonSet_String.ISState state; protected State(AutomatonSet_String.ISState state) { this.state = state; } public boolean isFinal() { return this.state.isFinal(); } public IState next(char ch) { final Automaton.IState nextState = this.state.next(ch); return nextState==null ? null : new State((AutomatonSet_String.ISState)nextState); } /* public IState nnext(String s) { return this.nnext(s,0,s.length()); } public IState nnext(String s,int offset) { return this.nnext(s,0,s.length()-offset); } public IState nnext(String s,int offset,int length) { AutomatonSet_String.IState state = this.state; for (;length>0; --length, ++offset) { AutomatonSet_String.IState newState = state.next(s.charAt(offset)); if (newState==null) break; state = newState; } return new State((AutomatonSet_String.ISState)state); } */ public StateProSet getAllReachableStates() { final StateProSet result = new StateProSet(); final Automaton.LinkedSet_State states = this.state.getAllReachableStates(); for (Automaton.Wrapper_State w = states.elements; w!=null; w=w.next) { StatePro wrapper = (StatePro)SAutomaton.this.state2wrapper.get(w.state); if (wrapper==null) wrapper = new StatePro((AutomatonSet_String.SState)w.state); result.add(wrapper); } return result; } public String toString() { return this.state.toString(); } } protected class Transition implements IStatePro.ITransition { protected final Automaton.State.Transition transition; protected Transition(Automaton.State.Transition transition) { this.transition = transition; SAutomaton.this.transition2wrapper.put(transition,this); } /* protected void finalize() { SAutomaton.this.transition2wrapper.remove(this.transition); } */ public IStatePro getFromState() { StatePro wrapper = (StatePro)SAutomaton.this.state2wrapper.get(this.transition.getFromState()); if (wrapper==null) wrapper = new StatePro((AutomatonSet_String.SState)this.transition.getFromState()); return wrapper; } public Set getLabels() { final HashSet labels = (HashSet)this.transition.properties; // if (labels!=null) return java.util.Collections.unmodifiableSet(labels); if (labels!=null) return labels; return java.util.Collections.EMPTY_SET; } public ISet_char getCharSet() { return this.transition.getCharSet(); } public IStatePro getToState() { StatePro wrapper = (StatePro)SAutomaton.this.state2wrapper.get(this.transition.getToState()); if (wrapper==null) wrapper = new StatePro((AutomatonSet_String.SState)this.transition.getToState()); return wrapper; } public String toString() { final StringBuffer buffer = new StringBuffer(); final AutomatonSet_String.SState fromState = (AutomatonSet_String.SState)this.transition.getFromState(); final AutomatonSet_String.SState toState = (AutomatonSet_String.SState)this.transition.getToState(); if (fromState.isFinal()) buffer.append('[').append(fromState.stateNr).append(']'); else buffer.append('(').append(fromState.stateNr).append(')'); if (this.transition.getCharSet()==null) { if (this.transition.properties==null) buffer.append(" --> "); else buffer.append(" - " ).append(this.transition.properties).append(": -> "); } else { if (this.transition.properties==null) buffer.append(" - ").append(this.transition.getCharSet()).append(" -> "); else buffer.append(" - ").append(this.transition.properties).append(':').append(this.transition.getCharSet()).append(" ->"); } if (toState.isFinal()) buffer.append('[').append(toState.stateNr).append(']'); else buffer.append('(').append(toState.stateNr).append(')'); return buffer.toString(); } } protected class StatePro implements IStatePro { protected final Automaton.IStateVisitedListener stateVisitedListener = new AutomatonSet_String.IStateVisitedListener() { public void stateVisited(Automaton.State state) { StatePro wrapper = (StatePro)SAutomaton.this.state2wrapper.get(state); if (wrapper==null) wrapper = new StatePro((AutomatonSet_String.SState)state); final Iterator it = StatePro.this.visitListeners.iterator(); for (int i=StatePro.this.visitListeners.size(); i>0; --i) { ((IStatePro.IVisitListener)it.next()).stateVisited(wrapper); } } public void stateVisited(Automaton.State state,char ch) { StatePro wrapper = (StatePro)SAutomaton.this.state2wrapper.get(state); if (wrapper==null) wrapper = new StatePro((AutomatonSet_String.SState)state); final Iterator it = StatePro.this.visitListeners.iterator(); for (int i=StatePro.this.visitListeners.size(); i>0; --i) { ((IStatePro.IVisitListener)it.next()).stateVisited(wrapper,ch); } } public void stateUnVisited(Automaton.State state) { StatePro wrapper = (StatePro)SAutomaton.this.state2wrapper.get(state); if (wrapper==null) wrapper = new StatePro((AutomatonSet_String.SState)state); final Iterator it = StatePro.this.visitListeners.iterator(); for (int i=StatePro.this.visitListeners.size(); i>0; --i) { ((IStatePro.IVisitListener)it.next()).stateUnVisited(wrapper); } } }; protected final Automaton.IStateChangedListener stateChangedListener = new AutomatonSet_String.ISStateChangedListener() { public void transitionAdded(Automaton.State.Transition transition) { Transition wrapper = (Transition)SAutomaton.this.transition2wrapper.get(transition); if (wrapper==null) wrapper = new Transition(transition); final Iterator it = StatePro.this.changeListeners.iterator(); for (int i=StatePro.this.changeListeners.size(); i>0; --i) { ((IStatePro.IChangeListener)it.next()).transitionAdded(wrapper); } } public void transitionRemoved(Automaton.State.Transition transition) { Transition wrapper = (Transition)SAutomaton.this.transition2wrapper.get(transition); if (wrapper==null) wrapper = new Transition(transition); final Iterator it = StatePro.this.changeListeners.iterator(); for (int i=StatePro.this.changeListeners.size(); i>0; --i) { ((IStatePro.IChangeListener)it.next()).transitionRemoved(wrapper); } } public void isFinalChanged(AutomatonSet_String.SState state, boolean isFinal) { StatePro wrapper = (StatePro)SAutomaton.this.state2wrapper.get(state); if (wrapper==null) wrapper = new StatePro((AutomatonSet_String.SState)state); final Iterator it = StatePro.this.changeListeners.iterator(); for (int i=StatePro.this.changeListeners.size(); i>0; --i) { ((IStatePro.IChangeListener)it.next()).isFinalChanged(wrapper,isFinal); } } }; protected LinkedList visitListeners = null; protected LinkedList changeListeners = null; public void addVisitListener(IStatePro.IVisitListener listener) { if (this.visitListeners==null) { this.visitListeners = new LinkedList(); this.state.addVisitedListener(this.stateVisitedListener); } this.visitListeners.add(listener); } public boolean removeVisitListener(IStatePro.IVisitListener listener) { if (this.visitListeners!=null) { final Iterator it = this.visitListeners.iterator(); for (int i=this.visitListeners.size(); i>0; --i) { if (listener==it.next()) { if (this.visitListeners.size()>1) it.remove(); else { this.state.removeVisitedListener(this.stateVisitedListener); this.visitListeners = null; } return true; } } } return false; } public void addChangeListener(IStatePro.IChangeListener listener) { if (this.changeListeners==null) { this.changeListeners = new LinkedList(); this.state.addChangedListener(this.stateChangedListener); } this.changeListeners.add(listener); } public boolean removeChangeListener(IStatePro.IChangeListener listener) { if (this.changeListeners!=null) { final Iterator it = this.changeListeners.iterator(); for (int i=this.changeListeners.size(); i>0; --i) { if (listener==it.next()) { if (this.changeListeners.size()>1) it.remove(); else { this.state.removeChangedListener(this.stateChangedListener); this.changeListeners = null; } return true; } } } return false; } protected final AutomatonSet_String.SState state; protected StatePro(AutomatonSet_String.SState state) { if (state==null) throw new Error("state==null"); this.state = state; SAutomaton.this.state2wrapper.put(state,this); } protected void finalize() { SAutomaton.this.state2wrapper.remove(this.state); } protected SAutomaton parent() { return SAutomaton.this; } public boolean isFinal() { return this.state.isFinal(); } public void setFinal(boolean isFinal) { this.state.setFinal(isFinal); } public IState visit() { return new State((AutomatonSet_String.ISState)this.state.visit()); } public IStatePro.ITransition addTransition(ISet_char charSet,IStatePro toState) { final AutomatonSet_String.SState.Transition trans = this.state.addTransition(null,charSet,((StatePro)toState).state); Transition wrapper = (Transition)SAutomaton.this.transition2wrapper.get(trans); if (wrapper==null) wrapper = new Transition(trans); return wrapper; } public boolean removeTransition(IStatePro.ITransition transition) { return this.state.removeTransition(((Transition)transition).transition); } public void removeAllTransitions() { this.state.removeAllTransitions(); } public StateProSet getAllReachableStates() { final StateProSet result = new StateProSet(); final Automaton.LinkedSet_State states = this.state.getAllReachableStates(); for (Automaton.Wrapper_State w = states.elements; w!=null; w=w.next) { StatePro wrapper = (StatePro)SAutomaton.this.state2wrapper.get(w.state); if (wrapper==null) wrapper = new StatePro((AutomatonSet_String.SState)w.state); result.add(wrapper); } return result; } public IStatePro.ITransition[] getTransitions() { final LinkedList list = new LinkedList(); for (AutomatonSet_String.State.Transition trans=this.state.transitions; trans!=null; trans=trans.next) { Transition wrapper = (Transition)SAutomaton.this.transition2wrapper.get(trans); if (wrapper==null) wrapper = new Transition(trans); // list.add(wrapper); list.addFirst(wrapper); } return (IStatePro.ITransition[])list.toArray(new IStatePro.ITransition[list.size()]); } public IStatePro.ITransition[] getETransitions() { final LinkedList list = new LinkedList(); for (AutomatonSet_String.State.Transition trans=this.state.eTransitions; trans!=null; trans=trans.next) { Transition wrapper = (Transition)SAutomaton.this.transition2wrapper.get(trans); if (wrapper==null) wrapper = new Transition(trans); // list.add(wrapper); list.addFirst(wrapper); } return (IStatePro.ITransition[])list.toArray(new IStatePro.ITransition[list.size()]); } /** * returns all transitions (normal and epsilon transitions) of this state. * the result array contains first all epsilon transitions and then all normal transitions */ public IStatePro.ITransition[] getAllTransitions() { final LinkedList list = new LinkedList(); for (AutomatonSet_String.State.Transition trans=this.state.transitions; trans!=null; trans=trans.next) { Transition wrapper = (Transition)SAutomaton.this.transition2wrapper.get(trans); if (wrapper==null) wrapper = new Transition(trans); // list.add(wrapper); list.addFirst(wrapper); } for (AutomatonSet_String.State.Transition trans=this.state.eTransitions; trans!=null; trans=trans.next) { Transition wrapper = (Transition)SAutomaton.this.transition2wrapper.get(trans); if (wrapper==null) wrapper = new Transition(trans); // list.add(wrapper); list.addFirst(wrapper); } return (IStatePro.ITransition[])list.toArray(new IStatePro.ITransition[list.size()]); } public int getStateNumber() { return this.state.stateNr; } public String toString() { if (this.isFinal()) return "["+this.state.stateNr+"]"; return "("+this.state.stateNr+")"; } } // should be IdentityMap protected transient HashMap state2wrapper = null; protected transient HashMap transition2wrapper = null; protected transient Automaton.IChangedListener automatonChangedListener = null; protected Automaton.IChangedListener getAutomatonChangedListener() { if (this.automatonChangedListener!=null) return this.automatonChangedListener; this.automatonChangedListener = new Automaton.IChangedListener() { public void stateAdded(Automaton.State state) { StatePro wrapper = (StatePro)SAutomaton.this.state2wrapper.get(state); if (wrapper==null) wrapper = new StatePro((AutomatonSet_String.SState)state); final Iterator it = SAutomaton.this.listeners.iterator(); for (int i=SAutomaton.this.listeners.size(); i>0; --i) { ((SAutomaton.IChangeListener)it.next()).stateAdded(wrapper); } } public void stateRemoved(Automaton.State state) { StatePro wrapper = (StatePro)SAutomaton.this.state2wrapper.get(state); if (wrapper==null) wrapper = new StatePro((AutomatonSet_String.SState)state); final Iterator it = SAutomaton.this.listeners.iterator(); for (int i=SAutomaton.this.listeners.size(); i>0; --i) { ((SAutomaton.IChangeListener)it.next()).stateRemoved(wrapper); } } public void startStateChanged(Automaton.State oldStartState,Automaton.State newStartState) { StatePro oldWrapper = null; if (oldStartState!=null) { oldWrapper = (StatePro)SAutomaton.this.state2wrapper.get(oldStartState); if (oldWrapper==null) oldWrapper = new StatePro((AutomatonSet_String.SState)oldStartState); } StatePro newWrapper = null; if (newStartState!=null) { newWrapper = (StatePro)SAutomaton.this.state2wrapper.get(newStartState); if (newWrapper==null) newWrapper = new StatePro((AutomatonSet_String.SState)newStartState); } final Iterator it = SAutomaton.this.listeners.iterator(); for (int i=SAutomaton.this.listeners.size(); i>0; --i) { ((SAutomaton.IChangeListener)it.next()).startStateChanged(oldWrapper,newWrapper); } } }; return this.automatonChangedListener; } protected transient LinkedList listeners = null; /** * Adds the specified listener to receive change events from this automaton. * The listener will be registered as listener in any case, even if it has been registered yet. * If a listener instance is added two times, it will receive events twice. * important: don't forget to remove the listener, if you don't need it any longer but still have the automaton in use. * Otherwise your listener won't be carbage collected (because it is registered with this automaton) * and still will receive events. */ public void addChangeListener(SAutomaton.IChangeListener listener) { if (this.listeners==null) { this.listeners = new LinkedList(); ((AutomatonSet_String)this.automaton).addChangedListener(this.getAutomatonChangedListener()); } this.listeners.add(listener); } /** * Removes the specified listener so that it no longer receives change events from this automaton. * If the listener instance is registered more than ones, only one instance will be removed. * @param listener * @throw IllegalArgumentException - if null is passed as listener * @return true if the listener was registered else false */ public boolean removeChangeListener(SAutomaton.IChangeListener listener) { if (this.listeners!=null) { final Iterator it = this.listeners.iterator(); for (int i=this.listeners.size(); i>0; --i) { if (listener==it.next()) { if (this.listeners.size()>1) it.remove(); else { this.automaton.removeChangedListener(this.automatonChangedListener); this.automatonChangedListener = null; this.listeners = null; } return true; } } } return false; } protected transient AutomatonSet_String automaton; /** * Creates a new empty automaton */ public SAutomaton() { this(new AutomatonSet_String()); } public SAutomaton(FSAData data) { this(new AutomatonSet_String()); this.init(data); } protected static FSAData toFSAData(Object obj) { if (obj.getClass()!=FSAData.class) { SAutomatonData data = (SAutomatonData)obj; FSAData.State[] newStates = new FSAData.State[data.states==null ? 0 : data.states.length]; for (int i=0; iimportant: the specified state must be a state of this automaton which means *
a) the state must have been created via the addState() method of this automaton *
b) the state must not have been removed from this automaton via the removeState method * @param state */ public void setStartState(IStatePro state) { if ((state instanceof StatePro)==false) throw new IllegalArgumentException("state is no state of mine"); final StatePro wrapper = (StatePro)state; if (wrapper.parent()!=this) throw new IllegalArgumentException("state is no state of mine"); this.automaton.setStartState(wrapper.state); } /** * Adds a new non final state to this automaton. * @return a new state */ public IStatePro addState() { return this.addState(false); } /** * Adds a new final or non final state to this automaton. * @return a new state */ public IStatePro addState(boolean isFinal) { final AutomatonSet_String.SState newState = this.automaton.addState(isFinal); StatePro wrapper = (StatePro)SAutomaton.this.state2wrapper.get(newState); if (wrapper==null) wrapper = new StatePro((AutomatonSet_String.SState)newState); return wrapper; } /** * Removes the specified state from this automaton. *
First all transitions pointing to state are removed and then the state itself. *
If state is this automaton's start state then first of all this automaton's start state is set to null. *
important: the specified state must have been created via the addState method of this automaton * otherwise an IllegalArgumentException is thrown. * @param state * @return true if this automaton owns the specified state else false */ public boolean removeState(IStatePro state) { if ((state instanceof StatePro)==false) throw new IllegalArgumentException("state is no state of mine"); final StatePro wrapper = (StatePro)state; if (wrapper.parent()!=this) throw new IllegalArgumentException("state is no state of mine"); return this.automaton.removeState(wrapper.state); } /** * Removes all states of this automaton. */ public void clear() { this.automaton.clear(); } /** * Minimizes this automaton as much as possible. * The resulting automaton has as less states as possible. *
important: the current implementation removes all properties from all transitions */ public void minimize() { this.automaton.minimize(); } /** * Returns all states of this automaton whatever they are reachable through the current start state or not. * @return */ public StateProSet getStates() { final StateProSet result = new StateProSet(); final Automaton.LinkedSet_State states = this.automaton.getStates(); for (Automaton.Wrapper_State w = states.elements; w!=null; w=w.next) { StatePro wrapper = (StatePro)SAutomaton.this.state2wrapper.get(w.state); if (wrapper==null) wrapper = new StatePro((AutomatonSet_String.SState)w.state); result.add(wrapper); } return result; } public void complement() { this.automaton.complement(); } public void addAll(SAutomaton automaton) { this.automaton.addAll(automaton.automaton); } public void retainAll(SAutomaton automaton) { this.automaton.retainAll(automaton.automaton); } public void removeAll(SAutomaton automaton) { this.automaton.removeAll(automaton.automaton); } public String toString() { return this.automaton.toString(); } private String getCharSet(ISet_char charSet) { if (charSet==null) return null; final StringBuffer buffer = new StringBuffer(charSet.size()); ISet_char.Iterator it_charSet = charSet.iterator(); for (int i=charSet.size(); i>0; --i) buffer.append(it_charSet.next()); ISet_char cs = new CharSet(buffer.toString()); if (cs.equals(charSet)==false) throw new Error(""+charSet+" "+cs); return buffer.toString(); } public FSAData toData() { Automaton.LinkedSet_State xxx = this.automaton.getStates(); AutomatonSet_String.SState[] states = new AutomatonSet_String.SState[xxx.size()]; int x=0; for (Automaton.Wrapper_State w=xxx.elements; w!=null; w=w.next,++x) { states[x] = (AutomatonSet_String.SState)w.state; } FSAData.State[] data_states = new FSAData.State[states.length]; for (int i=0; i