1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
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
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
61
62
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
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
144 final HashSet<List<StateTransition<S, L>>> paths = new HashSet<List<StateTransition<S, L>>>();
145
146
147 for (S state : initial) {
148 pathFinder.addPathsBetween(state, terminal, paths, type);
149 }
150
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
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
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
208 private Set<StateTransition<S, L>> castFromImpl(final Set<StateTransitionImpl<S, L>> set) {
209 return (Set) set;
210 }
211 }