| 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 |