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