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