1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.scxml.io;
18
19 import java.text.MessageFormat;
20 import java.util.HashSet;
21 import java.util.Iterator;
22 import java.util.List;
23 import java.util.Map;
24 import java.util.Set;
25 import java.util.StringTokenizer;
26
27 import org.apache.commons.logging.LogFactory;
28 import org.apache.commons.scxml.SCXMLHelper;
29 import org.apache.commons.scxml.model.History;
30 import org.apache.commons.scxml.model.Initial;
31 import org.apache.commons.scxml.model.Invoke;
32 import org.apache.commons.scxml.model.ModelException;
33 import org.apache.commons.scxml.model.Parallel;
34 import org.apache.commons.scxml.model.SCXML;
35 import org.apache.commons.scxml.model.State;
36 import org.apache.commons.scxml.model.Transition;
37 import org.apache.commons.scxml.model.TransitionTarget;
38
39
40
41
42
43
44
45 final class ModelUpdater {
46
47
48
49
50
51
52
53
54
55
56
57
58
59 static void updateSCXML(final SCXML scxml) throws ModelException {
60 String initial = scxml.getInitial();
61
62
63 TransitionTarget initialTarget = (TransitionTarget) scxml.getTargets().
64 get(initial);
65 if (initialTarget == null) {
66
67 logAndThrowModelError(ERR_SCXML_NO_INIT, new Object[] {
68 initial });
69 }
70 scxml.setInitialTarget(initialTarget);
71 Map targets = scxml.getTargets();
72 Map children = scxml.getChildren();
73 Iterator i = children.keySet().iterator();
74 while (i.hasNext()) {
75 TransitionTarget tt = (TransitionTarget) children.get(i.next());
76 if (tt instanceof State) {
77 updateState((State) tt, targets);
78 } else {
79 updateParallel((Parallel) tt, targets);
80 }
81 }
82 }
83
84
85
86
87
88
89
90
91
92 private static void updateState(final State s, final Map targets)
93 throws ModelException {
94
95 Initial ini = s.getInitial();
96 Map c = s.getChildren();
97 List initialStates = null;
98 if (!c.isEmpty()) {
99 if (ini == null) {
100 logAndThrowModelError(ERR_STATE_NO_INIT,
101 new Object[] {getStateName(s)});
102 }
103 Transition initialTransition = ini.getTransition();
104 updateTransition(initialTransition, targets);
105 initialStates = initialTransition.getTargets();
106
107
108 if (initialStates.size() == 0) {
109 logAndThrowModelError(ERR_STATE_BAD_INIT,
110 new Object[] {getStateName(s)});
111 } else {
112 for (int i = 0; i < initialStates.size(); i++) {
113 TransitionTarget initialState = (TransitionTarget)
114 initialStates.get(i);
115 if (!SCXMLHelper.isDescendant(initialState, s)) {
116 logAndThrowModelError(ERR_STATE_BAD_INIT,
117 new Object[] {getStateName(s)});
118 }
119 }
120 }
121 }
122 List histories = s.getHistory();
123 Iterator histIter = histories.iterator();
124 while (histIter.hasNext()) {
125 if (s.isSimple()) {
126 logAndThrowModelError(ERR_HISTORY_SIMPLE_STATE,
127 new Object[] {getStateName(s)});
128 }
129 History h = (History) histIter.next();
130 Transition historyTransition = h.getTransition();
131 if (historyTransition == null) {
132
133 if (initialStates != null && initialStates.size() > 0) {
134 for (int i = 0; i < initialStates.size(); i++) {
135 if (initialStates.get(i) instanceof History) {
136 logAndThrowModelError(ERR_HISTORY_BAD_DEFAULT,
137 new Object[] {h.getId(), getStateName(s)});
138 }
139 }
140 historyTransition = new Transition();
141 historyTransition.getTargets().addAll(initialStates);
142 h.setTransition(historyTransition);
143 } else {
144 logAndThrowModelError(ERR_HISTORY_NO_DEFAULT,
145 new Object[] {h.getId(), getStateName(s)});
146 }
147 }
148 updateTransition(historyTransition, targets);
149 List historyStates = historyTransition.getTargets();
150 if (historyStates.size() == 0) {
151 logAndThrowModelError(ERR_STATE_NO_HIST,
152 new Object[] {getStateName(s)});
153 }
154 for (int i = 0; i < historyStates.size(); i++) {
155 TransitionTarget historyState = (TransitionTarget)
156 historyStates.get(i);
157 if (!h.isDeep()) {
158 if (!c.containsValue(historyState)) {
159 logAndThrowModelError(ERR_STATE_BAD_SHALLOW_HIST,
160 new Object[] {getStateName(s)});
161 }
162 } else {
163 if (!SCXMLHelper.isDescendant(historyState, s)) {
164 logAndThrowModelError(ERR_STATE_BAD_DEEP_HIST,
165 new Object[] {getStateName(s)});
166 }
167 }
168 }
169 }
170 List t = s.getTransitionsList();
171 for (int i = 0; i < t.size(); i++) {
172 Transition trn = (Transition) t.get(i);
173 updateTransition(trn, targets);
174 }
175 Parallel p = s.getParallel();
176 Invoke inv = s.getInvoke();
177 if ((inv != null && p != null)
178 || (inv != null && !c.isEmpty())
179 || (p != null && !c.isEmpty())) {
180 logAndThrowModelError(ERR_STATE_BAD_CONTENTS,
181 new Object[] {getStateName(s)});
182 }
183 if (p != null) {
184 updateParallel(p, targets);
185 } else if (inv != null) {
186 String type = inv.getType();
187 if (type == null || type.trim().length() == 0) {
188 logAndThrowModelError(ERR_INVOKE_NO_TYPE,
189 new Object[] {getStateName(s)});
190 }
191 String src = inv.getSrc();
192 boolean noSrc = (src == null || src.trim().length() == 0);
193 String srcexpr = inv.getSrcexpr();
194 boolean noSrcexpr = (srcexpr == null
195 || srcexpr.trim().length() == 0);
196 if (noSrc && noSrcexpr) {
197 logAndThrowModelError(ERR_INVOKE_NO_SRC,
198 new Object[] {getStateName(s)});
199 }
200 if (!noSrc && !noSrcexpr) {
201 logAndThrowModelError(ERR_INVOKE_AMBIGUOUS_SRC,
202 new Object[] {getStateName(s)});
203 }
204 } else {
205 Iterator j = c.keySet().iterator();
206 while (j.hasNext()) {
207 TransitionTarget tt = (TransitionTarget) c.get(j.next());
208 if (tt instanceof State) {
209 updateState((State) tt, targets);
210 } else if (tt instanceof Parallel) {
211 updateParallel((Parallel) tt, targets);
212 }
213 }
214 }
215 }
216
217
218
219
220
221
222
223
224 private static void updateParallel(final Parallel p, final Map targets)
225 throws ModelException {
226 Iterator i = p.getChildren().iterator();
227 while (i.hasNext()) {
228 updateState((State) i.next(), targets);
229 }
230 Iterator j = p.getTransitionsList().iterator();
231 while (j.hasNext()) {
232 updateTransition((Transition) j.next(), targets);
233 }
234 }
235
236
237
238
239
240
241
242
243 private static void updateTransition(final Transition t,
244 final Map targets) throws ModelException {
245 String next = t.getNext();
246 if (next == null) {
247 return;
248 }
249 List tts = t.getTargets();
250 if (tts.size() == 0) {
251
252 StringTokenizer ids = new StringTokenizer(next);
253 while (ids.hasMoreTokens()) {
254 String id = ids.nextToken();
255 TransitionTarget tt = (TransitionTarget) targets.get(id);
256 if (tt == null) {
257 logAndThrowModelError(ERR_TARGET_NOT_FOUND, new Object[] {
258 id });
259 }
260 tts.add(tt);
261 }
262 if (tts.size() > 1) {
263 boolean legal = verifyTransitionTargets(tts);
264 if (!legal) {
265 logAndThrowModelError(ERR_ILLEGAL_TARGETS, new Object[] {
266 next });
267 }
268 }
269 }
270 t.getPaths();
271 }
272
273
274
275
276
277
278
279
280 private static void logAndThrowModelError(final String errType,
281 final Object[] msgArgs) throws ModelException {
282 MessageFormat msgFormat = new MessageFormat(errType);
283 String errMsg = msgFormat.format(msgArgs);
284 org.apache.commons.logging.Log log = LogFactory.
285 getLog(ModelUpdater.class);
286 log.error(errMsg);
287 throw new ModelException(errMsg);
288 }
289
290
291
292
293
294
295
296
297
298 private static String getStateName(final State state) {
299 String badState = "anonymous state";
300 if (!SCXMLHelper.isStringEmpty(state.getId())) {
301 badState = "state with ID \"" + state.getId() + "\"";
302 }
303 return badState;
304 }
305
306
307
308
309
310
311
312
313
314
315
316
317 private static boolean verifyTransitionTargets(final List tts) {
318 if (tts.size() <= 1) {
319 return true;
320 }
321 TransitionTarget lca = SCXMLHelper.getLCA((TransitionTarget)
322 tts.get(0), (TransitionTarget) tts.get(1));
323 if (lca == null || !(lca instanceof Parallel)) {
324 return false;
325 }
326 Parallel p = (Parallel) lca;
327 Set regions = new HashSet();
328 for (int i = 0; i < tts.size(); i++) {
329 TransitionTarget tt = (TransitionTarget) tts.get(i);
330 while (tt.getParent() != p) {
331 tt = tt.getParent();
332 }
333 if (!regions.add(tt)) {
334 return false;
335 }
336 }
337 if (regions.size() != p.getChildren().size()) {
338 return false;
339 }
340 return true;
341 }
342
343
344
345
346 private ModelUpdater() {
347 super();
348 }
349
350
351
352
353
354 private static final String ERR_SCXML_NO_INIT = "No SCXML child state "
355 + "with ID \"{0}\" found; illegal initialstate for SCXML document";
356
357
358
359
360
361 private static final String ERR_STATE_NO_INIT = "No initial element "
362 + "available for {0}";
363
364
365
366
367
368 private static final String ERR_STATE_BAD_INIT = "Initial state "
369 + "null or not a descendant of {0}";
370
371
372
373
374
375
376 private static final String ERR_STATE_BAD_CONTENTS = "{0} should "
377 + "contain either one <parallel>, one <invoke> or any number of "
378 + "<state> children.";
379
380
381
382
383 private static final String ERR_STATE_NO_HIST = "Referenced history state"
384 + " null for {0}";
385
386
387
388
389 private static final String ERR_STATE_BAD_SHALLOW_HIST = "History state"
390 + " for shallow history is not child for {0}";
391
392
393
394
395 private static final String ERR_STATE_BAD_DEEP_HIST = "History state"
396 + " for deep history is not descendant for {0}";
397
398
399
400
401 private static final String ERR_TARGET_NOT_FOUND =
402 "Transition target with ID \"{0}\" not found";
403
404
405
406
407 private static final String ERR_ILLEGAL_TARGETS =
408 "Transition targets \"{0}\" do not satisfy the requirements for"
409 + " target regions belonging to a <parallel>";
410
411
412
413
414 private static final String ERR_HISTORY_SIMPLE_STATE =
415 "Simple {0} contains history elements";
416
417
418
419
420 private static final String ERR_HISTORY_NO_DEFAULT =
421 "No default target specified for history with ID \"{0}\""
422 + " belonging to {1}";
423
424
425
426
427 private static final String ERR_HISTORY_BAD_DEFAULT =
428 "Default target specified for history with ID \"{0}\""
429 + " belonging to \"{1}\" is also a history";
430
431
432
433
434
435 private static final String ERR_INVOKE_NO_TYPE = "{0} contains "
436 + "<invoke> with no \"type\" attribute specified.";
437
438
439
440
441
442 private static final String ERR_INVOKE_NO_SRC = "{0} contains "
443 + "<invoke> without a \"src\" or \"srcexpr\" attribute specified.";
444
445
446
447
448
449 private static final String ERR_INVOKE_AMBIGUOUS_SRC = "{0} contains "
450 + "<invoke> with both \"src\" and \"srcexpr\" attributes specified,"
451 + " must specify either one, but not both.";
452
453 }