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