Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||||||
AbstractStateMachine |
|
| 2.4615384615384617;2.462 |
1 | /* |
|
2 | * |
|
3 | * Copyright 2006 The Apache Software Foundation. |
|
4 | * |
|
5 | * Licensed under the Apache License, Version 2.0 (the "License"); |
|
6 | * you may not use this file except in compliance with the License. |
|
7 | * You may obtain a copy of the License at |
|
8 | * |
|
9 | * http://www.apache.org/licenses/LICENSE-2.0 |
|
10 | * |
|
11 | * Unless required by applicable law or agreed to in writing, software |
|
12 | * distributed under the License is distributed on an "AS IS" BASIS, |
|
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
14 | * See the License for the specific language governing permissions and |
|
15 | * limitations under the License. |
|
16 | * |
|
17 | */ |
|
18 | package org.apache.commons.scxml.env; |
|
19 | ||
20 | import java.io.IOException; |
|
21 | import java.lang.reflect.InvocationTargetException; |
|
22 | import java.lang.reflect.Method; |
|
23 | import java.net.URL; |
|
24 | ||
25 | import org.apache.commons.logging.Log; |
|
26 | import org.apache.commons.logging.LogFactory; |
|
27 | import org.apache.commons.scxml.Context; |
|
28 | import org.apache.commons.scxml.Evaluator; |
|
29 | import org.apache.commons.scxml.SCXMLExecutor; |
|
30 | import org.apache.commons.scxml.SCXMLListener; |
|
31 | import org.apache.commons.scxml.TriggerEvent; |
|
32 | import org.apache.commons.scxml.env.jexl.JexlContext; |
|
33 | import org.apache.commons.scxml.env.jexl.JexlEvaluator; |
|
34 | import org.apache.commons.scxml.io.SCXMLDigester; |
|
35 | import org.apache.commons.scxml.model.ModelException; |
|
36 | import org.apache.commons.scxml.model.SCXML; |
|
37 | import org.apache.commons.scxml.model.Transition; |
|
38 | import org.apache.commons.scxml.model.TransitionTarget; |
|
39 | import org.xml.sax.ErrorHandler; |
|
40 | import org.xml.sax.SAXException; |
|
41 | ||
42 | /** |
|
43 | * This class demonstrates one approach for providing the base |
|
44 | * functionality needed by classes representing stateful entities, |
|
45 | * whose behaviors are defined via SCXML documents. |
|
46 | * |
|
47 | * SCXML documents (more generically, UML state chart diagrams) can be |
|
48 | * used to define stateful behavior of objects, and Commons SCXML enables |
|
49 | * developers to use this model directly into the corresponding code |
|
50 | * artifacts. The resulting artifacts tend to be much simpler, embody |
|
51 | * a useful separation of concerns and are easier to understand and |
|
52 | * maintain. As the size of the modeled entity grows, these benefits |
|
53 | * become more apparent. |
|
54 | * |
|
55 | * This approach functions by registering an SCXMLListener that gets |
|
56 | * notified onentry, and calls the namesake method for each state that |
|
57 | * has been entered. |
|
58 | * |
|
59 | * This class swallows all exceptions only to log them. Developers of |
|
60 | * subclasses should think of themselves as "component developers" |
|
61 | * catering to other end users, and therefore ensure that the subclasses |
|
62 | * are free of <code>ModelException</code>s and the like. Most methods |
|
63 | * are <code>protected</code> for ease of subclassing. |
|
64 | * |
|
65 | */ |
|
66 | public abstract class AbstractStateMachine { |
|
67 | ||
68 | /** |
|
69 | * The state machine that will drive the instances of this class. |
|
70 | */ |
|
71 | private static SCXML stateMachine; |
|
72 | ||
73 | /** |
|
74 | * The instance specific SCXML engine. |
|
75 | */ |
|
76 | private SCXMLExecutor engine; |
|
77 | ||
78 | /** |
|
79 | * The log. |
|
80 | */ |
|
81 | private Log log; |
|
82 | ||
83 | /** |
|
84 | * The method signature for the activities corresponding to each |
|
85 | * state in the SCXML document. |
|
86 | */ |
|
87 | 1 | private static final Class[] SIGNATURE = new Class[0]; |
88 | ||
89 | /** |
|
90 | * The method parameters for the activities corresponding to each |
|
91 | * state in the SCXML document. |
|
92 | */ |
|
93 | 1 | private static final Object[] PARAMETERS = new Object[0]; |
94 | ||
95 | /** |
|
96 | * Convenience constructor. |
|
97 | * |
|
98 | * @param scxmlDocument The URL pointing to the SCXML document that |
|
99 | * describes the "lifecycle" of the |
|
100 | * instances of this class. |
|
101 | */ |
|
102 | public AbstractStateMachine(final URL scxmlDocument) { |
|
103 | // default is JEXL |
|
104 | 1 | this(scxmlDocument, new JexlContext(), new JexlEvaluator()); |
105 | 1 | } |
106 | ||
107 | /** |
|
108 | * Primary constructor. |
|
109 | * |
|
110 | * @param scxmlDocument The URL pointing to the SCXML document that |
|
111 | * describes the "lifecycle" of the |
|
112 | * instances of this class. |
|
113 | * @param rootCtx The root context for this instance. |
|
114 | * @param evaluator The expression evaluator for this instance. |
|
115 | * |
|
116 | * @see Context |
|
117 | * @see Evaluator |
|
118 | */ |
|
119 | public AbstractStateMachine(final URL scxmlDocument, |
|
120 | 1 | final Context rootCtx, final Evaluator evaluator) { |
121 | 1 | log = LogFactory.getLog(this.getClass()); |
122 | 1 | if (stateMachine == null) { |
123 | // parse only once per subclass |
|
124 | 1 | ErrorHandler errHandler = new SimpleErrorHandler(); |
125 | try { |
|
126 | 1 | stateMachine = SCXMLDigester.digest(scxmlDocument, |
127 | errHandler); |
|
128 | 0 | } catch (IOException ioe) { |
129 | 0 | logError(ioe); |
130 | 0 | } catch (SAXException sae) { |
131 | 0 | logError(sae); |
132 | 0 | } catch (ModelException me) { |
133 | 0 | logError(me); |
134 | 1 | } |
135 | } |
|
136 | 1 | engine = new SCXMLExecutor(evaluator, new SimpleDispatcher(), |
137 | new SimpleErrorReporter()); |
|
138 | 1 | engine.setStateMachine(stateMachine); |
139 | 1 | engine.setSuperStep(true); |
140 | 1 | engine.setRootContext(rootCtx); |
141 | 1 | engine.addListener(stateMachine, new EntryListener()); |
142 | try { |
|
143 | 1 | engine.go(); |
144 | 0 | } catch (ModelException me) { |
145 | 0 | logError(me); |
146 | 1 | } |
147 | 1 | } |
148 | ||
149 | /** |
|
150 | * Fire an event on the SCXML engine. |
|
151 | * |
|
152 | * @param event The event name. |
|
153 | * @return Whether the state machine has reached a "final" |
|
154 | * configuration. |
|
155 | */ |
|
156 | public boolean fireEvent(final String event) { |
|
157 | 5 | TriggerEvent[] evts = {new TriggerEvent(event, |
158 | TriggerEvent.SIGNAL_EVENT, null)}; |
|
159 | try { |
|
160 | 5 | engine.triggerEvents(evts); |
161 | 0 | } catch (ModelException me) { |
162 | 0 | logError(me); |
163 | 5 | } |
164 | 5 | return engine.getCurrentStatus().isFinal(); |
165 | } |
|
166 | ||
167 | /** |
|
168 | * Get the SCXML object representing this state machine. |
|
169 | * |
|
170 | * @return Returns the stateMachine. |
|
171 | */ |
|
172 | public static SCXML getStateMachine() { |
|
173 | 0 | return stateMachine; |
174 | } |
|
175 | ||
176 | /** |
|
177 | * Get the SCXML engine driving the "lifecycle" of the |
|
178 | * instances of this class. |
|
179 | * |
|
180 | * @return Returns the engine. |
|
181 | */ |
|
182 | public SCXMLExecutor getEngine() { |
|
183 | 6 | return engine; |
184 | } |
|
185 | ||
186 | /** |
|
187 | * Get the log for this class. |
|
188 | * |
|
189 | * @return Returns the log. |
|
190 | */ |
|
191 | public Log getLog() { |
|
192 | 0 | return log; |
193 | } |
|
194 | ||
195 | /** |
|
196 | * Set the log for this class. |
|
197 | * |
|
198 | * @param log The log to set. |
|
199 | */ |
|
200 | public void setLog(final Log log) { |
|
201 | 0 | this.log = log; |
202 | 0 | } |
203 | ||
204 | /** |
|
205 | * Invoke the no argument method with the following name. |
|
206 | * |
|
207 | * @param methodName The method to invoke. |
|
208 | * @return Whether the invoke was successful. |
|
209 | */ |
|
210 | public boolean invoke(final String methodName) { |
|
211 | 6 | Class clas = this.getClass(); |
212 | try { |
|
213 | 6 | Method method = clas.getDeclaredMethod(methodName, SIGNATURE); |
214 | 6 | method.invoke(this, PARAMETERS); |
215 | 0 | } catch (SecurityException se) { |
216 | 0 | logError(se); |
217 | 0 | return false; |
218 | 0 | } catch (NoSuchMethodException nsme) { |
219 | 0 | logError(nsme); |
220 | 0 | return false; |
221 | 0 | } catch (IllegalArgumentException iae) { |
222 | 0 | logError(iae); |
223 | 0 | return false; |
224 | 0 | } catch (IllegalAccessException iae) { |
225 | 0 | logError(iae); |
226 | 0 | return false; |
227 | 0 | } catch (InvocationTargetException ite) { |
228 | 0 | logError(ite); |
229 | 0 | return false; |
230 | 6 | } |
231 | 6 | return true; |
232 | } |
|
233 | ||
234 | /** |
|
235 | * Reset the state machine. |
|
236 | * |
|
237 | * @return Whether the reset was successful. |
|
238 | */ |
|
239 | public boolean resetMachine() { |
|
240 | try { |
|
241 | 0 | engine.reset(); |
242 | 0 | } catch (ModelException me) { |
243 | 0 | logError(me); |
244 | 0 | return false; |
245 | 0 | } |
246 | 0 | return true; |
247 | } |
|
248 | ||
249 | /** |
|
250 | * Utility method for logging error. |
|
251 | * |
|
252 | * @param exception The exception leading to this error condition. |
|
253 | */ |
|
254 | protected void logError(final Exception exception) { |
|
255 | 0 | if (log.isErrorEnabled()) { |
256 | 0 | log.error(exception.getMessage(), exception); |
257 | } |
|
258 | 0 | } |
259 | ||
260 | /** |
|
261 | * A SCXMLListener that is only concerned about "onentry" |
|
262 | * notifications. |
|
263 | */ |
|
264 | 1 | protected class EntryListener implements SCXMLListener { |
265 | ||
266 | /** |
|
267 | * {@inheritDoc} |
|
268 | */ |
|
269 | public void onEntry(final TransitionTarget entered) { |
|
270 | 6 | invoke(entered.getId()); |
271 | 6 | } |
272 | ||
273 | /** |
|
274 | * No-op. |
|
275 | * |
|
276 | * @param from The "source" transition target. |
|
277 | * @param to The "destination" transition target. |
|
278 | * @param transition The transition being followed. |
|
279 | */ |
|
280 | public void onTransition(final TransitionTarget from, |
|
281 | final TransitionTarget to, final Transition transition) { |
|
282 | // nothing to do |
|
283 | 5 | } |
284 | ||
285 | /** |
|
286 | * No-op. |
|
287 | * |
|
288 | * @param exited The transition target being exited. |
|
289 | */ |
|
290 | public void onExit(final TransitionTarget exited) { |
|
291 | // nothing to do |
|
292 | 5 | } |
293 | ||
294 | } |
|
295 | ||
296 | } |
|
297 |