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.Iterator;
21 import java.util.List;
22 import java.util.Map;
23
24 import org.apache.commons.logging.LogFactory;
25 import org.apache.commons.scxml.SCXMLHelper;
26 import org.apache.commons.scxml.model.History;
27 import org.apache.commons.scxml.model.Initial;
28 import org.apache.commons.scxml.model.Invoke;
29 import org.apache.commons.scxml.model.ModelException;
30 import org.apache.commons.scxml.model.Parallel;
31 import org.apache.commons.scxml.model.SCXML;
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
36 /***
37 * The ModelUpdater provides the utility methods to check the Commons
38 * SCXML model for inconsistencies, detect errors, and wire the Commons
39 * SCXML model appropriately post document parsing by the digester to make
40 * it executor ready.
41 */
42 final class ModelUpdater {
43
44
45
46
47 /***
48 * <p>Update the SCXML object model and make it SCXMLExecutor ready.
49 * This is part of post-digester processing, and sets up the necessary
50 * object references throughtout the SCXML object model for the parsed
51 * document.</p>
52 *
53 * @param scxml The SCXML object (output from Digester)
54 * @throws ModelException If the object model is flawed
55 */
56 static void updateSCXML(final SCXML scxml) throws ModelException {
57
58 String initialstate = scxml.getInitialstate();
59
60
61
62
63
64 State initialState = (State) scxml.getTargets().get(initialstate);
65 if (initialState == null) {
66
67 logAndThrowModelError(ERR_SCXML_NO_INIT, new Object[] {
68 initialstate });
69 }
70 scxml.setInitialState(initialState);
71 Map targets = scxml.getTargets();
72 Map states = scxml.getStates();
73 Iterator i = states.keySet().iterator();
74 while (i.hasNext()) {
75 updateState((State) states.get(i.next()), targets);
76 }
77 }
78
79 /***
80 * Update this State object (part of post-digestion processing).
81 * Also checks for any errors in the document.
82 *
83 * @param s The State object
84 * @param targets The global Map of all transition targets
85 * @throws ModelException If the object model is flawed
86 */
87 private static void updateState(final State s, final Map targets)
88 throws ModelException {
89
90
91 s.getOnEntry().setParent(s);
92 s.getOnExit().setParent(s);
93
94 Initial ini = s.getInitial();
95 Map c = s.getChildren();
96 TransitionTarget initialState = null;
97 if (!c.isEmpty()) {
98 if (ini == null) {
99 logAndThrowModelError(ERR_STATE_NO_INIT,
100 new Object[] {getStateName(s)});
101 }
102 Transition initialTransition = ini.getTransition();
103 updateTransition(initialTransition, targets);
104 initialState = initialTransition.getTarget();
105
106
107 if (initialState == null
108 || !SCXMLHelper.isDescendant(initialState, s)) {
109 logAndThrowModelError(ERR_STATE_BAD_INIT,
110 new Object[] {getStateName(s)});
111 }
112 }
113 List histories = s.getHistory();
114 Iterator histIter = histories.iterator();
115 while (histIter.hasNext()) {
116 if (s.isSimple()) {
117 logAndThrowModelError(ERR_HISTORY_SIMPLE_STATE,
118 new Object[] {getStateName(s)});
119 }
120 History h = (History) histIter.next();
121 Transition historyTransition = h.getTransition();
122 if (historyTransition == null) {
123
124 if (initialState != null
125 && !(initialState instanceof History)) {
126 historyTransition = new Transition();
127 historyTransition.setNext(initialState.getId());
128 historyTransition.setParent(h);
129 h.setTransition(historyTransition);
130 } else {
131 logAndThrowModelError(ERR_HISTORY_NO_DEFAULT,
132 new Object[] {h.getId(), getStateName(s)});
133 }
134 }
135 updateTransition(historyTransition, targets);
136 State historyState = (State) historyTransition.getTarget();
137 if (historyState == null) {
138 logAndThrowModelError(ERR_STATE_NO_HIST,
139 new Object[] {getStateName(s)});
140 }
141 if (!h.isDeep()) {
142 if (!c.containsValue(historyState)) {
143 logAndThrowModelError(ERR_STATE_BAD_SHALLOW_HIST,
144 new Object[] {getStateName(s)});
145 }
146 } else {
147 if (!SCXMLHelper.isDescendant(historyState, s)) {
148 logAndThrowModelError(ERR_STATE_BAD_DEEP_HIST,
149 new Object[] {getStateName(s)});
150 }
151 }
152 }
153 Map t = s.getTransitions();
154 Iterator i = t.keySet().iterator();
155 while (i.hasNext()) {
156 Iterator j = ((List) t.get(i.next())).iterator();
157 while (j.hasNext()) {
158 Transition trn = (Transition) j.next();
159
160 trn.setParent(s);
161 updateTransition(trn, targets);
162 }
163 }
164 Parallel p = s.getParallel();
165 Invoke inv = s.getInvoke();
166 if ((inv != null && p != null)
167 || (inv != null && !c.isEmpty())
168 || (p != null && !c.isEmpty())) {
169 logAndThrowModelError(ERR_STATE_BAD_CONTENTS,
170 new Object[] {getStateName(s)});
171 }
172 if (p != null) {
173 updateParallel(p, targets);
174 } else if (inv != null) {
175 String ttype = inv.getTargettype();
176 if (ttype == null || ttype.trim().length() == 0) {
177 logAndThrowModelError(ERR_INVOKE_NO_TARGETTYPE,
178 new Object[] {getStateName(s)});
179 }
180 String src = inv.getSrc();
181 boolean noSrc = (src == null || src.trim().length() == 0);
182 String srcexpr = inv.getSrcexpr();
183 boolean noSrcexpr = (srcexpr == null
184 || srcexpr.trim().length() == 0);
185 if (noSrc && noSrcexpr) {
186 logAndThrowModelError(ERR_INVOKE_NO_SRC,
187 new Object[] {getStateName(s)});
188 }
189 if (!noSrc && !noSrcexpr) {
190 logAndThrowModelError(ERR_INVOKE_AMBIGUOUS_SRC,
191 new Object[] {getStateName(s)});
192 }
193 } else {
194 Iterator j = c.keySet().iterator();
195 while (j.hasNext()) {
196 updateState((State) c.get(j.next()), targets);
197 }
198 }
199 }
200
201 /***
202 * Update this Parallel object (part of post-digestion processing).
203 *
204 * @param p The Parallel object
205 * @param targets The global Map of all transition targets
206 * @throws ModelException If the object model is flawed
207 */
208 private static void updateParallel(final Parallel p, final Map targets)
209 throws ModelException {
210 Iterator i = p.getStates().iterator();
211 while (i.hasNext()) {
212 updateState((State) i.next(), targets);
213 }
214 }
215
216 /***
217 * Update this Transition object (part of post-digestion processing).
218 *
219 * @param t The Transition object
220 * @param targets The global Map of all transition targets
221 * @throws ModelException If the object model is flawed
222 */
223 private static void updateTransition(final Transition t,
224 final Map targets) throws ModelException {
225 String next = t.getNext();
226 if (next == null) {
227 return;
228 }
229 TransitionTarget tt = t.getTarget();
230 if (tt == null) {
231 tt = (TransitionTarget) targets.get(next);
232 if (tt == null) {
233 logAndThrowModelError(ERR_TARGET_NOT_FOUND, new Object[] {
234 next });
235 }
236 t.setTarget(tt);
237 }
238 }
239
240 /***
241 * Log an error discovered in post-digestion processing.
242 *
243 * @param errType The type of error
244 * @param msgArgs The arguments for formatting the error message
245 * @throws ModelException The model error, always thrown.
246 */
247 private static void logAndThrowModelError(final String errType,
248 final Object[] msgArgs) throws ModelException {
249 MessageFormat msgFormat = new MessageFormat(errType);
250 String errMsg = msgFormat.format(msgArgs);
251 org.apache.commons.logging.Log log = LogFactory.
252 getLog(ModelUpdater.class);
253 log.error(errMsg);
254 throw new ModelException(errMsg);
255 }
256
257 /***
258 * Get state identifier for error message. This method is only
259 * called to produce an appropriate log message in some error
260 * conditions.
261 *
262 * @param state The <code>State</code> object
263 * @return The state identifier for the error message
264 */
265 private static String getStateName(final State state) {
266 String badState = "anonymous state";
267 if (!SCXMLHelper.isStringEmpty(state.getId())) {
268 badState = "state with ID \"" + state.getId() + "\"";
269 }
270 return badState;
271 }
272
273 /***
274 * Discourage instantiation since this is a utility class.
275 */
276 private ModelUpdater() {
277 super();
278 }
279
280
281 /***
282 * Error message when SCXML document specifies an illegal initial state.
283 */
284 private static final String ERR_SCXML_NO_INIT = "No SCXML child state "
285 + "with ID \"{0}\" found; illegal initialstate for SCXML document";
286
287 /***
288 * Error message when a state element specifies an initial state which
289 * cannot be found.
290 */
291 private static final String ERR_STATE_NO_INIT = "No initial element "
292 + "available for {0}";
293
294 /***
295 * Error message when a state element specifies an initial state which
296 * is not a direct descendent.
297 */
298 private static final String ERR_STATE_BAD_INIT = "Initial state "
299 + "null or not a descendant of {0}";
300
301 /***
302 * Error message when a state element contains anything other than
303 * one <parallel>, one <invoke> or any number of
304 * <state> children.
305 */
306 private static final String ERR_STATE_BAD_CONTENTS = "{0} should "
307 + "contain either one <parallel>, one <invoke> or any number of "
308 + "<state> children.";
309
310 /***
311 * Error message when a referenced history state cannot be found.
312 */
313 private static final String ERR_STATE_NO_HIST = "Referenced history state"
314 + " null for {0}";
315
316 /***
317 * Error message when a shallow history state is not a child state.
318 */
319 private static final String ERR_STATE_BAD_SHALLOW_HIST = "History state"
320 + " for shallow history is not child for {0}";
321
322 /***
323 * Error message when a deep history state is not a descendent state.
324 */
325 private static final String ERR_STATE_BAD_DEEP_HIST = "History state"
326 + " for deep history is not descendant for {0}";
327
328 /***
329 * Transition target is not a legal IDREF (not found).
330 */
331 private static final String ERR_TARGET_NOT_FOUND =
332 "Transition target with ID \"{0}\" not found";
333
334 /***
335 * Simple states should not contain a history.
336 */
337 private static final String ERR_HISTORY_SIMPLE_STATE =
338 "Simple {0} contains history elements";
339
340 /***
341 * History does not specify a default transition target.
342 */
343 private static final String ERR_HISTORY_NO_DEFAULT =
344 "No default target specified for history with ID \"{0}\""
345 + " belonging to {1}";
346
347 /***
348 * Error message when an <invoke> does not specify a "targettype"
349 * attribute.
350 */
351 private static final String ERR_INVOKE_NO_TARGETTYPE = "{0} contains "
352 + "<invoke> with no \"targettype\" attribute specified.";
353
354 /***
355 * Error message when an <invoke> does not specify a "src"
356 * or a "srcexpr" attribute.
357 */
358 private static final String ERR_INVOKE_NO_SRC = "{0} contains "
359 + "<invoke> without a \"src\" or \"srcexpr\" attribute specified.";
360
361 /***
362 * Error message when an <invoke> specifies both "src" and "srcexpr"
363 * attributes.
364 */
365 private static final String ERR_INVOKE_AMBIGUOUS_SRC = "{0} contains "
366 + "<invoke> with both \"src\" and \"srcexpr\" attributes specified,"
367 + " must specify either one, but not both.";
368
369 }
370