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.util.HashSet;
20 import java.util.IdentityHashMap;
21 import java.util.Iterator;
22 import java.util.List;
23 import java.util.Map;
24 import java.util.Set;
25
26 import org.apache.commons.logging.Log;
27 import org.apache.commons.logging.LogFactory;
28 import org.apache.commons.scxml.model.Data;
29 import org.apache.commons.scxml.model.Datamodel;
30 import org.apache.commons.scxml.model.Parallel;
31 import org.apache.commons.scxml.model.Path;
32 import org.apache.commons.scxml.model.State;
33 import org.apache.commons.scxml.model.Transition;
34 import org.apache.commons.scxml.model.TransitionTarget;
35 import org.apache.commons.scxml.semantics.ErrorConstants;
36 import org.w3c.dom.CharacterData;
37 import org.w3c.dom.Node;
38 import org.w3c.dom.Text;
39
40 /***
41 * Helper class, all methods static final.
42 *
43 */
44 public final class SCXMLHelper {
45
46 /***
47 * Return true if the string is empty.
48 *
49 * @param attr The String to test
50 * @return Is string empty
51 */
52 public static boolean isStringEmpty(final String attr) {
53 if (attr == null || attr.trim().length() == 0) {
54 return true;
55 }
56 return false;
57 }
58
59 /***
60 * Checks whether a transition target tt (State or Parallel) is a
61 * descendant of the transition target context.
62 *
63 * @param tt
64 * TransitionTarget to check - a potential descendant
65 * @param ctx
66 * TransitionTarget context - a potential ancestor
67 * @return true iff tt is a descendant of ctx, false otherwise
68 */
69 public static boolean isDescendant(final TransitionTarget tt,
70 final TransitionTarget ctx) {
71 TransitionTarget parent = tt.getParent();
72 while (parent != null) {
73 if (parent == ctx) {
74 return true;
75 }
76 parent = parent.getParent();
77 }
78 return false;
79 }
80
81 /***
82 * Creates a set which contains given states and all their ancestors
83 * recursively up to the upper bound. Null upperBound means root
84 * of the state machine.
85 *
86 * @param states The Set of States
87 * @param upperBounds The Set of upper bound States
88 * @return transitive closure of a given state set
89 */
90 public static Set getAncestorClosure(final Set states,
91 final Set upperBounds) {
92 Set closure = new HashSet(states.size() * 2);
93 for (Iterator i = states.iterator(); i.hasNext();) {
94 TransitionTarget tt = (TransitionTarget) i.next();
95 while (tt != null) {
96 if (!closure.add(tt)) {
97
98 break;
99 }
100 if (upperBounds != null && upperBounds.contains(tt)) {
101 break;
102 }
103 tt = tt.getParent();
104 }
105 }
106 return closure;
107 }
108
109 /***
110 * Checks whether a given set of states is a legal Harel State Table
111 * configuration (with the respect to the definition of the OR and AND
112 * states).
113 *
114 * @param states
115 * a set of states
116 * @param errRep
117 * ErrorReporter to report detailed error info if needed
118 * @return true if a given state configuration is legal, false otherwise
119 */
120 public static boolean isLegalConfig(final Set states,
121 final ErrorReporter errRep) {
122
123
124
125
126
127
128
129
130 boolean legalConfig = true;
131 Map counts = new IdentityHashMap();
132 Set scxmlCount = new HashSet();
133 for (Iterator i = states.iterator(); i.hasNext();) {
134 TransitionTarget tt = (TransitionTarget) i.next();
135 TransitionTarget parent = null;
136 while ((parent = tt.getParent()) != null) {
137 HashSet cnt = (HashSet) counts.get(parent);
138 if (cnt == null) {
139 cnt = new HashSet();
140 counts.put(parent, cnt);
141 }
142 cnt.add(tt);
143 tt = parent;
144 }
145
146 scxmlCount.add(tt);
147 }
148
149 for (Iterator i = counts.entrySet().iterator(); i.hasNext();) {
150 Map.Entry entry = (Map.Entry) i.next();
151 TransitionTarget tt = (TransitionTarget) entry.getKey();
152 Set count = (Set) entry.getValue();
153 if (tt instanceof Parallel) {
154 Parallel p = (Parallel) tt;
155 if (count.size() < p.getChildren().size()) {
156 errRep.onError(ErrorConstants.ILLEGAL_CONFIG,
157 "Not all AND states active for parallel "
158 + p.getId(), entry);
159 legalConfig = false;
160 }
161 } else {
162 if (count.size() > 1) {
163 errRep.onError(ErrorConstants.ILLEGAL_CONFIG,
164 "Multiple OR states active for state "
165 + tt.getId(), entry);
166 legalConfig = false;
167 }
168 }
169 count.clear();
170 }
171 if (scxmlCount.size() > 1) {
172 errRep.onError(ErrorConstants.ILLEGAL_CONFIG,
173 "Multiple top-level OR states active!", scxmlCount);
174 }
175
176 scxmlCount.clear();
177 counts.clear();
178 return legalConfig;
179 }
180
181 /***
182 * Finds the least common ancestor of transition targets tt1 and tt2 if
183 * one exists.
184 *
185 * @param tt1 First TransitionTarget
186 * @param tt2 Second TransitionTarget
187 * @return closest common ancestor of tt1 and tt2 or null
188 */
189 public static TransitionTarget getLCA(final TransitionTarget tt1,
190 final TransitionTarget tt2) {
191 if (tt1 == tt2) {
192 return tt1;
193 } else if (isDescendant(tt1, tt2)) {
194 return tt2;
195 } else if (isDescendant(tt2, tt1)) {
196 return tt1;
197 }
198 Set parents = new HashSet();
199 TransitionTarget tmp = tt1;
200 while ((tmp = tmp.getParent()) != null) {
201 parents.add(tmp);
202 }
203 tmp = tt2;
204 while ((tmp = tmp.getParent()) != null) {
205
206 if (!parents.add(tmp)) {
207 parents.clear();
208 return tmp;
209 }
210 }
211 return null;
212 }
213
214 /***
215 * Returns the set of all states (and parallels) which are exited if a
216 * given transition t is going to be taken.
217 * Current states are necessary to be taken into account
218 * due to orthogonal states and cross-region transitions -
219 * see UML specs for more details.
220 *
221 * @param t
222 * transition to be taken
223 * @param currentStates
224 * the set of current states (simple states only)
225 * @return a set of all states (including composite) which are exited if a
226 * given transition is taken
227 */
228 public static Set getStatesExited(final Transition t,
229 final Set currentStates) {
230 Set allStates = new HashSet();
231 if (t.getTargets().size() == 0) {
232 return allStates;
233 }
234 Path p = (Path) t.getPaths().get(0);
235
236 allStates.addAll(p.getUpwardSegment());
237 TransitionTarget source = t.getParent();
238 for (Iterator act = currentStates.iterator(); act.hasNext();) {
239 TransitionTarget a = (TransitionTarget) act.next();
240 if (isDescendant(a, source)) {
241 boolean added = false;
242 added = allStates.add(a);
243 while (added && a != source) {
244 a = a.getParent();
245 added = allStates.add(a);
246 }
247 }
248 }
249 if (p.isCrossRegion()) {
250 for (Iterator regions = p.getRegionsExited().iterator();
251 regions.hasNext();) {
252 Parallel par = ((Parallel) ((State) regions.next()).
253 getParent());
254
255 for (Iterator siblings = par.getChildren().iterator();
256 siblings.hasNext();) {
257 State s = (State) siblings.next();
258 for (Iterator act = currentStates.iterator();
259 act.hasNext();) {
260 TransitionTarget a = (TransitionTarget) act.next();
261 if (isDescendant(a, s)) {
262
263 boolean added = false;
264 added = allStates.add(a);
265 while (added && a != s) {
266 a = a.getParent();
267 added = allStates.add(a);
268 }
269 }
270 }
271 }
272 }
273 }
274 return allStates;
275 }
276
277 /***
278 * According to the UML definition, two transitions
279 * are conflicting if the sets of states they exit overlap.
280 *
281 * @param t1 a transition to check against t2
282 * @param t2 a transition to check against t1
283 * @param currentStates the set of current states (simple states only)
284 * @return true if the t1 and t2 are conflicting transitions
285 * @see #getStatesExited(Transition, Set)
286 */
287 public static boolean inConflict(final Transition t1,
288 final Transition t2, final Set currentStates) {
289 Set ts1 = getStatesExited(t1, currentStates);
290 Set ts2 = getStatesExited(t2, currentStates);
291 ts1.retainAll(ts2);
292 if (ts1.isEmpty()) {
293 return false;
294 }
295 return true;
296 }
297
298 /***
299 * Whether the first argument is a subtype of the second.
300 *
301 * @param child The candidate subtype
302 * @param parent The supertype
303 * @return true if child is subtype of parent, otherwise false
304 */
305 public static boolean subtypeOf(final Class child, final Class parent) {
306 if (child == null || parent == null) {
307 return false;
308 }
309 for (Class current = child; current != Object.class;
310 current = current.getSuperclass()) {
311 if (current == parent) {
312 return true;
313 }
314 }
315 return false;
316 }
317
318 /***
319 * Whether the class implements the interface.
320 *
321 * @param clas The candidate class
322 * @param interfayce The interface
323 * @return true if clas implements interfayce, otherwise false
324 */
325 public static boolean implementationOf(final Class clas,
326 final Class interfayce) {
327 if (clas == null || interfayce == null || !interfayce.isInterface()) {
328 return false;
329 }
330 for (Class current = clas; current != Object.class;
331 current = current.getSuperclass()) {
332 Class[] implementedInterfaces = current.getInterfaces();
333 for (int i = 0; i < implementedInterfaces.length; i++) {
334 if (implementedInterfaces[i] == interfayce) {
335 return true;
336 }
337 }
338 }
339 return false;
340 }
341
342 /***
343 * Set node value, depending on its type, from a String.
344 *
345 * @param node A Node whose value is to be set
346 * @param value The new value
347 */
348 public static void setNodeValue(final Node node, final String value) {
349 switch(node.getNodeType()) {
350 case Node.ATTRIBUTE_NODE:
351 node.setNodeValue(value);
352 break;
353 case Node.ELEMENT_NODE:
354
355 if (node.hasChildNodes()) {
356 Node child = node.getFirstChild();
357 while (child != null) {
358 if (child.getNodeType() == Node.TEXT_NODE) {
359 node.removeChild(child);
360 }
361 child = child.getNextSibling();
362 }
363 }
364
365 Text txt = node.getOwnerDocument().createTextNode(value);
366 node.appendChild(txt);
367 break;
368 case Node.TEXT_NODE:
369 case Node.CDATA_SECTION_NODE:
370 ((CharacterData) node).setData(value);
371 break;
372 default:
373 String err = "Trying to set value of a strange Node type: "
374 + node.getNodeType();
375
376 throw new IllegalArgumentException(err);
377 }
378 }
379
380 /***
381 * Retrieve a DOM node value as a string depending on its type.
382 *
383 * @param node A node to be retreived
384 * @return The value as a string
385 */
386 public static String getNodeValue(final Node node) {
387 String result = "";
388 if (node == null) {
389 return result;
390 }
391 switch(node.getNodeType()) {
392 case Node.ATTRIBUTE_NODE:
393 result = node.getNodeValue();
394 break;
395 case Node.ELEMENT_NODE:
396 if (node.hasChildNodes()) {
397 Node child = node.getFirstChild();
398 StringBuffer buf = new StringBuffer();
399 while (child != null) {
400 if (child.getNodeType() == Node.TEXT_NODE) {
401 buf.append(((CharacterData) child).getData());
402 }
403 child = child.getNextSibling();
404 }
405 result = buf.toString();
406 }
407 break;
408 case Node.TEXT_NODE:
409 case Node.CDATA_SECTION_NODE:
410 result = ((CharacterData) node).getData();
411 break;
412 default:
413 String err = "Trying to get value of a strange Node type: "
414 + node.getNodeType();
415
416 throw new IllegalArgumentException(err);
417 }
418 return result.trim();
419 }
420
421 /***
422 * Clone data model.
423 *
424 * @param ctx The context to clone to.
425 * @param datamodel The datamodel to clone.
426 * @param evaluator The expression evaluator.
427 * @param log The error log.
428 */
429 public static void cloneDatamodel(final Datamodel datamodel,
430 final Context ctx, final Evaluator evaluator,
431 final Log log) {
432 if (datamodel == null) {
433 return;
434 }
435 List data = datamodel.getData();
436 if (data == null) {
437 return;
438 }
439 for (Iterator iter = data.iterator(); iter.hasNext();) {
440 Data datum = (Data) iter.next();
441 Node datumNode = datum.getNode();
442 Node valueNode = null;
443 if (datumNode != null) {
444 valueNode = datumNode.cloneNode(true);
445 }
446
447 if (!SCXMLHelper.isStringEmpty(datum.getSrc())) {
448 ctx.setLocal(datum.getName(), valueNode);
449 } else if (!SCXMLHelper.isStringEmpty(datum.
450 getExpr())) {
451 Object value = null;
452 try {
453 ctx.setLocal(NAMESPACES_KEY, datum.getNamespaces());
454 value = evaluator.eval(ctx, datum.getExpr());
455 ctx.setLocal(NAMESPACES_KEY, null);
456 } catch (SCXMLExpressionException see) {
457 if (log != null) {
458 log.error(see.getMessage(), see);
459 } else {
460 Log defaultLog = LogFactory.getLog(SCXMLHelper.class);
461 defaultLog.error(see.getMessage(), see);
462 }
463 }
464 ctx.setLocal(datum.getName(), value);
465 } else {
466 ctx.setLocal(datum.getName(), valueNode);
467 }
468 }
469 }
470
471 /***
472 * Discourage instantiation since this is a utility class.
473 */
474 private SCXMLHelper() {
475 super();
476 }
477
478 /***
479 * Current document namespaces are saved under this key in the parent
480 * state's context.
481 */
482 private static final String NAMESPACES_KEY = "_ALL_NAMESPACES";
483
484 }
485