1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.scxml;
18
19 import java.io.Serializable;
20 import java.util.Collections;
21 import java.util.HashMap;
22 import java.util.HashSet;
23 import java.util.Map;
24 import java.util.Set;
25
26 import org.apache.commons.scxml.invoke.Invoker;
27 import org.apache.commons.scxml.invoke.InvokerException;
28 import org.apache.commons.scxml.model.Datamodel;
29 import org.apache.commons.scxml.model.History;
30 import org.apache.commons.scxml.model.TransitionTarget;
31
32 /***
33 * The <code>SCInstance</code> performs book-keeping functions for
34 * a particular execution of a state chart represented by a
35 * <code>SCXML</code> object.
36 */
37 public class SCInstance implements Serializable {
38
39 /***
40 * Serial version UID.
41 */
42 private static final long serialVersionUID = 1L;
43
44 /***
45 * The notification registry.
46 */
47 private NotificationRegistry notificationRegistry;
48
49 /***
50 * The <code>Map</code> of <code>Context</code>s per
51 * <code>TransitionTarget</code>.
52 */
53 private Map contexts;
54
55 /***
56 * The <code>Map</code> of last known configurations per
57 * <code>History</code>.
58 */
59 private Map histories;
60
61 /***
62 * The <code>Invoker</code> classes <code>Map</code>, keyed by
63 * <invoke> target types (specified using "targettype" attribute).
64 */
65 private Map invokerClasses;
66
67 /***
68 * The <code>Map</code> of active <code>Invoker</code>s, keyed by
69 * (leaf) <code>State</code>s.
70 */
71 private Map invokers;
72
73 /***
74 * The evaluator for expressions.
75 */
76 private Evaluator evaluator;
77
78 /***
79 * The root context.
80 */
81 private Context rootContext;
82
83 /***
84 * The owning state machine executor.
85 */
86 private SCXMLExecutor executor;
87
88 /***
89 * Constructor.
90 *
91 * @param executor The executor that this instance is attached to.
92 */
93 SCInstance(final SCXMLExecutor executor) {
94 this.notificationRegistry = new NotificationRegistry();
95 this.contexts = Collections.synchronizedMap(new HashMap());
96 this.histories = Collections.synchronizedMap(new HashMap());
97 this.invokerClasses = Collections.synchronizedMap(new HashMap());
98 this.invokers = Collections.synchronizedMap(new HashMap());
99 this.evaluator = null;
100 this.rootContext = null;
101 this.executor = executor;
102 }
103
104 /***
105 * Get the <code>Evaluator</code>.
106 *
107 * @return The evaluator.
108 */
109 public Evaluator getEvaluator() {
110 return evaluator;
111 }
112
113 /***
114 * Set the <code>Evaluator</code>.
115 *
116 * @param evaluator The evaluator.
117 */
118 void setEvaluator(final Evaluator evaluator) {
119 this.evaluator = evaluator;
120 }
121
122 /***
123 * Get the root context.
124 *
125 * @return The root context.
126 */
127 public Context getRootContext() {
128 if (rootContext == null && evaluator != null) {
129 rootContext = evaluator.newContext(null);
130 }
131 return rootContext;
132 }
133
134 /***
135 * Set the root context.
136 *
137 * @param context The root context.
138 */
139 void setRootContext(final Context context) {
140 this.rootContext = context;
141 }
142
143 /***
144 * Get the notification registry.
145 *
146 * @return The notification registry.
147 */
148 public NotificationRegistry getNotificationRegistry() {
149 return notificationRegistry;
150 }
151
152 /***
153 * Set the notification registry.
154 *
155 * @param notifRegistry The notification registry.
156 */
157 void setNotificationRegistry(final NotificationRegistry notifRegistry) {
158 this.notificationRegistry = notifRegistry;
159 }
160
161 /***
162 * Get the <code>Context</code> for this <code>TransitionTarget</code>.
163 * If one is not available it is created.
164 *
165 * @param transitionTarget The TransitionTarget.
166 * @return The Context.
167 */
168 public Context getContext(final TransitionTarget transitionTarget) {
169 Context context = (Context) contexts.get(transitionTarget);
170 if (context == null) {
171 TransitionTarget parent = transitionTarget.getParent();
172 if (parent == null) {
173
174 context = evaluator.newContext(getRootContext());
175 } else {
176 context = evaluator.newContext(getContext(parent));
177 }
178 Datamodel datamodel = transitionTarget.getDatamodel();
179 SCXMLHelper.cloneDatamodel(datamodel, context, evaluator, null);
180 contexts.put(transitionTarget, context);
181 }
182 return context;
183 }
184
185 /***
186 * Get the <code>Context</code> for this <code>TransitionTarget</code>.
187 * May return <code>null</code>.
188 *
189 * @param transitionTarget The <code>TransitionTarget</code>.
190 * @return The Context.
191 */
192 Context lookupContext(final TransitionTarget transitionTarget) {
193 return (Context) contexts.get(transitionTarget);
194 }
195
196 /***
197 * Set the <code>Context</code> for this <code>TransitionTarget</code>.
198 *
199 * @param transitionTarget The TransitionTarget.
200 * @param context The Context.
201 */
202 void setContext(final TransitionTarget transitionTarget,
203 final Context context) {
204 contexts.put(transitionTarget, context);
205 }
206
207 /***
208 * Get the last configuration for this history.
209 *
210 * @param history The history.
211 * @return Returns the lastConfiguration.
212 */
213 public Set getLastConfiguration(final History history) {
214 Set lastConfiguration = (Set) histories.get(history);
215 if (lastConfiguration == null) {
216 lastConfiguration = new HashSet();
217 histories.put(history, lastConfiguration);
218 }
219 return lastConfiguration;
220 }
221
222 /***
223 * Set the last configuration for this history.
224 *
225 * @param history The history.
226 * @param lc The lastConfiguration to set.
227 */
228 public void setLastConfiguration(final History history,
229 final Set lc) {
230 Set lastConfiguration = getLastConfiguration(history);
231 lastConfiguration.clear();
232 lastConfiguration.addAll(lc);
233 }
234
235 /***
236 * Check whether we have prior history.
237 *
238 * @param history The history.
239 * @return Whether we have a non-empty last configuration
240 */
241 public boolean isEmpty(final History history) {
242 Set lastConfiguration = (Set) histories.get(history);
243 if (lastConfiguration == null || lastConfiguration.isEmpty()) {
244 return true;
245 }
246 return false;
247 }
248
249 /***
250 * Resets the history state.
251 *
252 * @param history The history.
253 * @see org.apache.commons.scxml.SCXMLExecutor#reset()
254 */
255 public void reset(final History history) {
256 Set lastConfiguration = (Set) histories.get(history);
257 if (lastConfiguration != null) {
258 lastConfiguration.clear();
259 }
260 }
261
262 /***
263 * Get the {@link SCXMLExecutor} this instance is attached to.
264 *
265 * @return The SCXMLExecutor this instance is attached to.
266 * @see org.apache.commons.scxml.SCXMLExecutor
267 */
268 public SCXMLExecutor getExecutor() {
269 return executor;
270 }
271
272 /***
273 * Register an {@link Invoker} class for this target type.
274 *
275 * @param targettype The target type (specified by "targettype"
276 * attribute of <invoke> tag).
277 * @param invokerClass The <code>Invoker</code> <code>Class</code>.
278 */
279 void registerInvokerClass(final String targettype,
280 final Class invokerClass) {
281 invokerClasses.put(targettype, invokerClass);
282 }
283
284 /***
285 * Remove the {@link Invoker} class registered for this target
286 * type (if there is one registered).
287 *
288 * @param targettype The target type (specified by "targettype"
289 * attribute of <invoke> tag).
290 */
291 void unregisterInvokerClass(final String targettype) {
292 invokerClasses.remove(targettype);
293 }
294
295 /***
296 * Get the {@link Invoker} for this {@link TransitionTarget}.
297 * May return <code>null</code>. A non-null <code>Invoker</code> will be
298 * returned if and only if the <code>TransitionTarget</code> is
299 * currently active and contains an <invoke> child.
300 *
301 * @param targettype The type of the target being invoked.
302 * @return An {@link Invoker} for the specified type, if an
303 * invoker class is registered against that type,
304 * <code>null</code> otherwise.
305 * @throws InvokerException When a suitable {@link Invoker} cannot
306 * be instantiated.
307 */
308 public Invoker newInvoker(final String targettype)
309 throws InvokerException {
310 Class invokerClass = (Class) invokerClasses.get(targettype);
311 if (invokerClass == null) {
312 throw new InvokerException("No Invoker registered for "
313 + "targettype \"" + targettype + "\"");
314 }
315 Invoker invoker = null;
316 try {
317 invoker = (Invoker) invokerClass.newInstance();
318 } catch (InstantiationException ie) {
319 throw new InvokerException(ie.getMessage(), ie.getCause());
320 } catch (IllegalAccessException iae) {
321 throw new InvokerException(iae.getMessage(), iae.getCause());
322 }
323 return invoker;
324 }
325
326 /***
327 * Get the {@link Invoker} for this {@link TransitionTarget}.
328 * May return <code>null</code>. A non-null {@link Invoker} will be
329 * returned if and only if the {@link TransitionTarget} is
330 * currently active and contains an <invoke> child.
331 *
332 * @param transitionTarget The <code>TransitionTarget</code>.
333 * @return The Invoker.
334 */
335 public Invoker getInvoker(final TransitionTarget transitionTarget) {
336 return (Invoker) invokers.get(transitionTarget);
337 }
338
339 /***
340 * Set the {@link Invoker} for this {@link TransitionTarget}.
341 *
342 * @param transitionTarget The TransitionTarget.
343 * @param invoker The Invoker.
344 */
345 public void setInvoker(final TransitionTarget transitionTarget,
346 final Invoker invoker) {
347 invokers.put(transitionTarget, invoker);
348 }
349
350 /***
351 * Return the Map of {@link Invoker}s currently "active".
352 *
353 * @return The map of invokers.
354 */
355 public Map getInvokers() {
356 return invokers;
357 }
358
359 }
360