001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 package org.apache.commons.scxml; 018 019 import java.io.Serializable; 020 import java.util.Collections; 021 import java.util.HashMap; 022 import java.util.HashSet; 023 import java.util.Map; 024 import java.util.Set; 025 026 import org.apache.commons.scxml.invoke.Invoker; 027 import org.apache.commons.scxml.invoke.InvokerException; 028 import org.apache.commons.scxml.model.Datamodel; 029 import org.apache.commons.scxml.model.History; 030 import org.apache.commons.scxml.model.TransitionTarget; 031 032 /** 033 * The <code>SCInstance</code> performs book-keeping functions for 034 * a particular execution of a state chart represented by a 035 * <code>SCXML</code> object. 036 */ 037 public class SCInstance implements Serializable { 038 039 /** 040 * Serial version UID. 041 */ 042 private static final long serialVersionUID = 2L; 043 044 /** 045 * The notification registry. 046 */ 047 private NotificationRegistry notificationRegistry; 048 049 /** 050 * The <code>Map</code> of <code>Context</code>s per 051 * <code>TransitionTarget</code>. 052 */ 053 private Map contexts; 054 055 /** 056 * The <code>Map</code> of last known configurations per 057 * <code>History</code>. 058 */ 059 private Map histories; 060 061 /** 062 * <code>Map</code> for recording the run to completion status of 063 * composite states. 064 */ 065 private Map completions; 066 067 /** 068 * The <code>Invoker</code> classes <code>Map</code>, keyed by 069 * <invoke> target types (specified using "targettype" attribute). 070 */ 071 private Map invokerClasses; 072 073 /** 074 * The <code>Map</code> of active <code>Invoker</code>s, keyed by 075 * (leaf) <code>State</code>s. 076 */ 077 private Map invokers; 078 079 /** 080 * The evaluator for expressions. 081 */ 082 private Evaluator evaluator; 083 084 /** 085 * The root context. 086 */ 087 private Context rootContext; 088 089 /** 090 * The owning state machine executor. 091 */ 092 private SCXMLExecutor executor; 093 094 /** 095 * Constructor. 096 * 097 * @param executor The executor that this instance is attached to. 098 */ 099 SCInstance(final SCXMLExecutor executor) { 100 this.notificationRegistry = new NotificationRegistry(); 101 this.contexts = Collections.synchronizedMap(new HashMap()); 102 this.histories = Collections.synchronizedMap(new HashMap()); 103 this.invokerClasses = Collections.synchronizedMap(new HashMap()); 104 this.invokers = Collections.synchronizedMap(new HashMap()); 105 this.completions = Collections.synchronizedMap(new HashMap()); 106 this.evaluator = null; 107 this.rootContext = null; 108 this.executor = executor; 109 } 110 111 /** 112 * Get the <code>Evaluator</code>. 113 * 114 * @return The evaluator. 115 */ 116 public Evaluator getEvaluator() { 117 return evaluator; 118 } 119 120 /** 121 * Set the <code>Evaluator</code>. 122 * 123 * @param evaluator The evaluator. 124 */ 125 void setEvaluator(final Evaluator evaluator) { 126 this.evaluator = evaluator; 127 } 128 129 /** 130 * Get the root context. 131 * 132 * @return The root context. 133 */ 134 public Context getRootContext() { 135 if (rootContext == null && evaluator != null) { 136 rootContext = evaluator.newContext(null); 137 } 138 return rootContext; 139 } 140 141 /** 142 * Set the root context. 143 * 144 * @param context The root context. 145 */ 146 void setRootContext(final Context context) { 147 this.rootContext = context; 148 } 149 150 /** 151 * Get the notification registry. 152 * 153 * @return The notification registry. 154 */ 155 public NotificationRegistry getNotificationRegistry() { 156 return notificationRegistry; 157 } 158 159 /** 160 * Set the notification registry. 161 * 162 * @param notifRegistry The notification registry. 163 */ 164 void setNotificationRegistry(final NotificationRegistry notifRegistry) { 165 this.notificationRegistry = notifRegistry; 166 } 167 168 /** 169 * Get the <code>Context</code> for this <code>TransitionTarget</code>. 170 * If one is not available it is created. 171 * 172 * @param transitionTarget The TransitionTarget. 173 * @return The Context. 174 */ 175 public Context getContext(final TransitionTarget transitionTarget) { 176 Context context = (Context) contexts.get(transitionTarget); 177 if (context == null) { 178 TransitionTarget parent = transitionTarget.getParent(); 179 if (parent == null) { 180 // docroot 181 context = evaluator.newContext(getRootContext()); 182 } else { 183 context = evaluator.newContext(getContext(parent)); 184 } 185 Datamodel datamodel = transitionTarget.getDatamodel(); 186 SCXMLHelper.cloneDatamodel(datamodel, context, evaluator, null); 187 contexts.put(transitionTarget, context); 188 } 189 return context; 190 } 191 192 /** 193 * Get the <code>Context</code> for this <code>TransitionTarget</code>. 194 * May return <code>null</code>. 195 * 196 * @param transitionTarget The <code>TransitionTarget</code>. 197 * @return The Context. 198 */ 199 Context lookupContext(final TransitionTarget transitionTarget) { 200 return (Context) contexts.get(transitionTarget); 201 } 202 203 /** 204 * Set the <code>Context</code> for this <code>TransitionTarget</code>. 205 * 206 * @param transitionTarget The TransitionTarget. 207 * @param context The Context. 208 */ 209 void setContext(final TransitionTarget transitionTarget, 210 final Context context) { 211 contexts.put(transitionTarget, context); 212 } 213 214 /** 215 * Get the last configuration for this history. 216 * 217 * @param history The history. 218 * @return Returns the lastConfiguration. 219 */ 220 public Set getLastConfiguration(final History history) { 221 Set lastConfiguration = (Set) histories.get(history); 222 if (lastConfiguration == null) { 223 lastConfiguration = new HashSet(); 224 histories.put(history, lastConfiguration); 225 } 226 return lastConfiguration; 227 } 228 229 /** 230 * Set the last configuration for this history. 231 * 232 * @param history The history. 233 * @param lc The lastConfiguration to set. 234 */ 235 public void setLastConfiguration(final History history, 236 final Set lc) { 237 Set lastConfiguration = getLastConfiguration(history); 238 lastConfiguration.clear(); 239 lastConfiguration.addAll(lc); 240 } 241 242 /** 243 * Check whether we have prior history. 244 * 245 * @param history The history. 246 * @return Whether we have a non-empty last configuration 247 */ 248 public boolean isEmpty(final History history) { 249 Set lastConfiguration = (Set) histories.get(history); 250 if (lastConfiguration == null || lastConfiguration.isEmpty()) { 251 return true; 252 } 253 return false; 254 } 255 256 /** 257 * Resets the history state. 258 * 259 * @param history The history. 260 * @see org.apache.commons.scxml.SCXMLExecutor#reset() 261 */ 262 public void reset(final History history) { 263 Set lastConfiguration = (Set) histories.get(history); 264 if (lastConfiguration != null) { 265 lastConfiguration.clear(); 266 } 267 } 268 269 /** 270 * Get the {@link SCXMLExecutor} this instance is attached to. 271 * 272 * @return The SCXMLExecutor this instance is attached to. 273 * @see org.apache.commons.scxml.SCXMLExecutor 274 */ 275 public SCXMLExecutor getExecutor() { 276 return executor; 277 } 278 279 /** 280 * Register an {@link Invoker} class for this target type. 281 * 282 * @param targettype The target type (specified by "targettype" 283 * attribute of <invoke> tag). 284 * @param invokerClass The <code>Invoker</code> <code>Class</code>. 285 */ 286 void registerInvokerClass(final String targettype, 287 final Class invokerClass) { 288 invokerClasses.put(targettype, invokerClass); 289 } 290 291 /** 292 * Remove the {@link Invoker} class registered for this target 293 * type (if there is one registered). 294 * 295 * @param targettype The target type (specified by "targettype" 296 * attribute of <invoke> tag). 297 */ 298 void unregisterInvokerClass(final String targettype) { 299 invokerClasses.remove(targettype); 300 } 301 302 /** 303 * Get the {@link Invoker} for this {@link TransitionTarget}. 304 * May return <code>null</code>. A non-null <code>Invoker</code> will be 305 * returned if and only if the <code>TransitionTarget</code> is 306 * currently active and contains an <invoke> child. 307 * 308 * @param targettype The type of the target being invoked. 309 * @return An {@link Invoker} for the specified type, if an 310 * invoker class is registered against that type, 311 * <code>null</code> otherwise. 312 * @throws InvokerException When a suitable {@link Invoker} cannot 313 * be instantiated. 314 */ 315 public Invoker newInvoker(final String targettype) 316 throws InvokerException { 317 Class invokerClass = (Class) invokerClasses.get(targettype); 318 if (invokerClass == null) { 319 throw new InvokerException("No Invoker registered for " 320 + "targettype \"" + targettype + "\""); 321 } 322 Invoker invoker = null; 323 try { 324 invoker = (Invoker) invokerClass.newInstance(); 325 } catch (InstantiationException ie) { 326 throw new InvokerException(ie.getMessage(), ie.getCause()); 327 } catch (IllegalAccessException iae) { 328 throw new InvokerException(iae.getMessage(), iae.getCause()); 329 } 330 return invoker; 331 } 332 333 /** 334 * Get the {@link Invoker} for this {@link TransitionTarget}. 335 * May return <code>null</code>. A non-null {@link Invoker} will be 336 * returned if and only if the {@link TransitionTarget} is 337 * currently active and contains an <invoke> child. 338 * 339 * @param transitionTarget The <code>TransitionTarget</code>. 340 * @return The Invoker. 341 */ 342 public Invoker getInvoker(final TransitionTarget transitionTarget) { 343 return (Invoker) invokers.get(transitionTarget); 344 } 345 346 /** 347 * Set the {@link Invoker} for this {@link TransitionTarget}. 348 * 349 * @param transitionTarget The TransitionTarget. 350 * @param invoker The Invoker. 351 */ 352 public void setInvoker(final TransitionTarget transitionTarget, 353 final Invoker invoker) { 354 invokers.put(transitionTarget, invoker); 355 } 356 357 /** 358 * Return the Map of {@link Invoker}s currently "active". 359 * 360 * @return The map of invokers. 361 */ 362 public Map getInvokers() { 363 return invokers; 364 } 365 366 /** 367 * Get the completion status for this composite 368 * {@link TransitionTarget}. 369 * 370 * @param transitionTarget The <code>TransitionTarget</code>. 371 * @return The completion status. 372 * 373 * @since 0.7 374 */ 375 public boolean isDone(final TransitionTarget transitionTarget) { 376 Boolean done = (Boolean) completions.get(transitionTarget); 377 if (done == null) { 378 return false; 379 } else { 380 return done.booleanValue(); 381 } 382 } 383 384 /** 385 * Set the completion status for this composite 386 * {@link TransitionTarget}. 387 * 388 * @param transitionTarget The TransitionTarget. 389 * @param done The completion status. 390 * 391 * @since 0.7 392 */ 393 public void setDone(final TransitionTarget transitionTarget, 394 final boolean done) { 395 completions.put(transitionTarget, done ? Boolean.TRUE : Boolean.FALSE); 396 } 397 398 } 399