View Javadoc

1   /*
2    * Copyright (C) 2006  Tom Gibara
3    *
4    * This library is free software; you can redistribute it and/or
5    * modify it under the terms of the GNU Lesser General Public
6    * License as published by the Free Software Foundation; either
7    * version 2.1 of the License, or (at your option) any later version.
8    *
9    * This library is distributed in the hope that it will be useful,
10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12   * Lesser General Public License for more details.
13   *
14   * You should have received a copy of the GNU Lesser General Public
15   * License along with this library; if not, write to the Free Software
16   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
17   */
18  
19  package com.tomgibara.pronto.state.impl;
20  
21  import java.util.Collections;
22  import java.util.HashSet;
23  import java.util.List;
24  import java.util.Set;
25  
26  import com.tomgibara.pronto.state.PathType;
27  import com.tomgibara.pronto.state.ProntoStateException;
28  import com.tomgibara.pronto.state.StateGraph;
29  import com.tomgibara.pronto.state.StateGraphEditor;
30  import com.tomgibara.pronto.state.StateTransition;
31  import com.tomgibara.pronto.util.Arguments;
32  
33  public class StateGraphImpl<S, L> implements StateGraph<S, L> {
34  
35      // fields
36  
37      private final HashSet<S> states;
38  
39      private final HashSet<StateTransitionImpl<S, L>> transitions;
40  
41      private final Set<S> publicStates;
42  
43      private final Set<StateTransition<S, L>> publicTransitions;
44  
45      private final PathFinder<S, L> pathFinder;
46  
47      // constructors
48  
49      StateGraphImpl() {
50          states = new HashSet<S>();
51          transitions = new HashSet<StateTransitionImpl<S, L>>();
52          pathFinder = new PathFinder<S, L>(states, transitions);
53  
54          publicStates = Collections.unmodifiableSet(states);
55          publicTransitions = Collections.unmodifiableSet(castFromImpl(transitions));
56      }
57  
58      StateGraphImpl(final Set<S> states, final Set<StateTransitionImpl<S, L>> transitions)
59              throws IllegalArgumentException {
60          // we don't do the work of checking that the two sets are consistent,
61          // because the editor should have done that
62          // we do test for null, because it's quick to do
63          Arguments.notNull(states, "states");
64          Arguments.notNull(transitions, "transitions");
65  
66          this.states = new HashSet<S>(states);
67          this.transitions = new HashSet<StateTransitionImpl<S, L>>(transitions);
68          pathFinder = new PathFinder<S, L>(states, transitions);
69  
70          publicStates = Collections.unmodifiableSet(states);
71          publicTransitions = Collections.unmodifiableSet(castFromImpl(transitions));
72      }
73  
74      // state graph methods
75  
76      public Set<S> states() {
77          return publicStates;
78      }
79  
80      public Set<S> getTerminalStates() {
81          HashSet<S> states = new HashSet<S>(this.states);
82          for (StateTransitionImpl<S, L> transition : transitions) {
83              states.remove(transition.getSource());
84          }
85          return Collections.unmodifiableSet(states);
86      }
87  
88      public S getTerminalState() throws ProntoStateException {
89          Set<S> terminalStates = getTerminalStates();
90          int size = terminalStates.size();
91          if (size == 0) return null;
92          if (size == 1) return terminalStates.iterator().next();
93          throw new ProntoStateException("Ambiguous: more than one terminal state: " + terminalStates);
94      }
95  
96      public Set<S> getInitialStates() {
97          HashSet<S> states = new HashSet<S>(this.states);
98          for (StateTransitionImpl<S, L> transition : transitions) {
99              states.remove(transition.getTarget());
100         }
101         return Collections.unmodifiableSet(states);
102     }
103 
104     public S getInitialState() throws ProntoStateException {
105         Set<S> initialStates = getInitialStates();
106         int size = initialStates.size();
107         if (size == 0) return null;
108         if (size == 1) return initialStates.iterator().next();
109         throw new ProntoStateException("Ambiguous: more than one initial state: " + initialStates);
110     }
111 
112     public StateTransition<S, L> getTransition(final S source, final L label, final S target) {
113         StateTransitionImpl<S, L> transition = new StateTransitionImpl<S, L>(source, label, target);
114         return transitions.contains(transition) ? transition : null;
115     }
116 
117     public Set<StateTransition<S, L>> getTransitionsMatching(final S source, final L label, final S target) {
118         if (source == null && label == null && target == null) return transitions();
119         return Collections.unmodifiableSet(getTransitionsFiltered(new TransitionFilter<S, L>() {
120             public boolean accept(final StateTransitionImpl<S, L> transition) {
121                 if (source != null && !transition.getSource().equals(source)) return false;
122                 if (label != null && !transition.getLabel().equals(label)) return false;
123                 if (target != null && !transition.getTarget().equals(target)) return false;
124                 return true;
125             }
126         }));
127     }
128 
129     public Set<StateTransition<S, L>> transitions() {
130         return publicTransitions;
131     }
132 
133     public Set<List<StateTransition<S, L>>> getMultiPaths(final Set<S> initial, final Set<S> terminal,
134             final PathType type) {
135         Arguments.notNull(initial, "initial");
136         Arguments.notNull(type, "type");
137 
138         if (!states.containsAll(initial)) throw new IllegalArgumentException(
139                 "Initial states not a subset of graph states.");
140         if (terminal != null && !states.containsAll(terminal)) throw new IllegalArgumentException(
141                 "Terminal states not a subset of graph states.");
142 
143         // accumulates the paths we have found
144         final HashSet<List<StateTransition<S, L>>> paths = new HashSet<List<StateTransition<S, L>>>();
145         // iterate over the initial states so that we can find paths from each
146         // one in turn
147         for (S state : initial) {
148             pathFinder.addPathsBetween(state, terminal, paths, type);
149         }
150         // all the heavy lifting has been done for us
151         return Collections.unmodifiableSet(paths);
152     }
153 
154     public Set<List<StateTransition<S, L>>> getPaths(final S initial, final S terminal, final PathType type) {
155         Arguments.notNull(initial, "initial");
156         return getMultiPaths(Collections.singleton(initial), terminal == null ? null : Collections.singleton(terminal),
157                 type);
158     }
159 
160     public List<StateTransition<S, L>> getPath(final S initial, final S terminal, final PathType type)
161             throws ProntoStateException {
162         Set<List<StateTransition<S, L>>> paths = getPaths(initial, terminal, type);
163         int size = paths.size();
164         if (size == 0) return null;
165         if (size == 1) return paths.iterator().next();
166         throw new ProntoStateException("Ambiguous: more than one path: " + paths);
167     }
168 
169     public StateGraphEditor<S, L> newEditor() {
170         return new StateGraphEditorImpl<S, L>(this);
171     }
172 
173     // object methods
174 
175     @SuppressWarnings("unchecked")
176     @Override
177     public boolean equals(final Object obj) {
178         if (this == obj) return true;
179         if (!(obj instanceof StateGraphImpl)) return false;
180         StateGraphImpl<S, L> that = (StateGraphImpl<S, L>) obj;
181         if (!this.states.equals(that.states)) return false;
182         if (!this.transitions.equals(that.transitions)) return false;
183         return true;
184     }
185 
186     @Override
187     public int hashCode() {
188         return states.hashCode() ^ transitions.hashCode();
189     }
190 
191     @Override
192     public String toString() {
193         return "[states: " + states + " transitions: " + transitions + "]";
194     }
195 
196     // private utility methods
197 
198     private Set<StateTransition<S, L>> getTransitionsFiltered(final TransitionFilter<S, L> filter) {
199         Set<StateTransition<S, L>> set = new HashSet<StateTransition<S, L>>();
200         for (StateTransitionImpl<S, L> transition : transitions) {
201             if (filter.accept(transition)) set.add(transition);
202         }
203         return set;
204     }
205 
206     @SuppressWarnings("unchecked")
207     // sleight of hand to dupe the generic type system
208     private Set<StateTransition<S, L>> castFromImpl(final Set<StateTransitionImpl<S, L>> set) {
209         return (Set) set;
210     }
211 }