1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.commons.scxml2.model;
18
19 import java.util.HashSet;
20 import java.util.Map;
21 import java.util.Set;
22
23 /**
24 * The class in this SCXML object model that corresponds to the
25 * simple <transition> SCXML element, without Transition rules for "events" or
26 * "guard-conditions". Used for <history> or <history> elements.
27 *
28 */
29 public class SimpleTransition extends Executable
30 implements NamespacePrefixesHolder, Observable {
31
32 /**
33 * Serial version UID.
34 */
35 private static final long serialVersionUID = 2L;
36
37 /**
38 * The id for this {@link Observable} which is unique within the SCXML state machine
39 */
40 private Integer observableId;
41
42 /**
43 * The Transition type: internal or external (default)
44 * @see #isTypeInternal()
45 */
46 private TransitionType type;
47
48 /**
49 * The transition domain for this transition.
50 * @see #getTransitionDomain()
51 */
52 private TransitionalState transitionDomain;
53
54 /**
55 * Internal flag indicating a null transitionDomain was derived to be the SCXML Document itself.
56 */
57 private boolean scxmlTransitionDomain;
58
59 /**
60 * Derived effective Transition type.
61 * @see #isTypeInternal()
62 */
63 private Boolean typeInternal;
64
65 /**
66 * Optional property that specifies the new state(s) or parallel(s)
67 * element to transition to. May be specified by reference or in-line.
68 * If multiple state(s) are specified, they must belong to the regions
69 * of the same parallel.
70 */
71 private Set<TransitionTarget> targets;
72
73 /**
74 * The transition target ID
75 */
76 private String next;
77
78 /**
79 * The current XML namespaces in the SCXML document for this action node,
80 * preserved for deferred XPath evaluation.
81 */
82 private Map<String, String> namespaces;
83
84 /**
85 * Constructor.
86 */
87 public SimpleTransition() {
88 super();
89 this.targets = new HashSet<TransitionTarget>();
90 }
91
92 private boolean isCompoundStateParent(TransitionalState ts) {
93 return ts != null && ts instanceof State && ((State)ts).isComposite();
94 }
95
96 /**
97 * {@inheritDoc}
98 */
99 public final Integer getObservableId() {
100 return observableId;
101 }
102
103 /**
104 * Sets the observableId for this Observable, which must be unique within the SCXML state machine
105 * @param observableId the observableId
106 */
107 public final void setObservableId(Integer observableId) {
108 this.observableId = observableId;
109 }
110
111 /**
112 * Get the TransitionalState (State or Parallel) parent.
113 *
114 * @return Returns the parent.
115 */
116 @Override
117 public TransitionalState getParent() {
118 return (TransitionalState)super.getParent();
119 }
120
121 /**
122 * Set the TransitionalState (State or Parallel) parent
123 * <p>
124 * For transitions of Initial or History elements their TransitionalState parent must be set.
125 * </p>
126 *
127 * @param parent The parent to set.
128 */
129 public final void setParent(final TransitionalState parent) {
130 super.setParent(parent);
131 }
132
133 /**
134 * @return true if Transition type == internal or false if type == external (default)
135 */
136 public final TransitionType getType() {
137 return type;
138 }
139
140 /**
141 * Sets the Transition type
142 * @param type the Transition type
143 */
144 public final void setType(final TransitionType type) {
145 this.type = type;
146 }
147
148 /**
149 * Returns the effective Transition type.
150 * <p>
151 * A transition type is only effectively internal if:
152 * <ul>
153 * <li>its {@link #getType()} == {@link TransitionType#internal}</li>
154 * <li>its source state {@link #getParent()} {@link State#isComposite()}</li>
155 * <li>all its {@link #getTargets()} are proper descendants of its {@link #getParent()}</li>
156 * </ul>
157 * Otherwise it is treated (for determining its exit states) as if it is of type {@link TransitionType#external}
158 * </p>
159 * @see <a href="http://www.w3.org/TR/2014/CR-scxml-20140313/#SelectingTransitions">
160 * http://www.w3.org/TR/2014/CR-scxml-20140313/#SelectingTransitions</a>
161 * </p>
162 * @return true if the effective Transition type is {@link TransitionType#internal}
163 */
164 public final boolean isTypeInternal() {
165 if (typeInternal == null) {
166
167 // derive typeInternal
168 typeInternal = TransitionType.internal == type && isCompoundStateParent(getParent());
169
170 if (typeInternal && targets.size() > 0) {
171 for (TransitionTarget tt : targets) {
172 if (!tt.isDescendantOf(getParent())) {
173 typeInternal = false;
174 break;
175 }
176 }
177 }
178 }
179 return typeInternal;
180 }
181
182 /**
183 * Returns the transition domain of this transition
184 * <p>
185 * If this transition is target-less OR if its transition domain is the SCXML document itself, null is returned.
186 * </p>
187 * <p>
188 * This method therefore only is useful to be invoked if the transition has targets!
189 * </p>
190 * <p>
191 * If the transition has targets then the transition domain is the compound State parent such that:
192 * <ul>
193 * <li>all states that are exited or entered as a result of taking this transition are descendants of it</li>
194 * <li>no descendant of it has this property</li>
195 * </ul>
196 * If there is no such compound state parent, the transition domain effectively becomes the SCXML document itself,
197 * which is not a (Transitional)State, and thus null will be returned instead.
198 * </p>
199 *
200 * @return The transition domain of this transition
201 */
202 public TransitionalState getTransitionDomain() {
203 TransitionalState ts = transitionDomain;
204 if (ts == null && targets.size() > 0 && !scxmlTransitionDomain) {
205
206 if (getParent() != null) {
207 if (isTypeInternal()) {
208 transitionDomain = getParent();
209 }
210 else {
211 // findLCCA
212 for (int i = getParent().getNumberOfAncestors()-1; i > -1; i--) {
213 if (isCompoundStateParent(getParent().getAncestor(i))) {
214 boolean allDescendants = true;
215 for (TransitionTarget tt : targets) {
216 if (i >= tt.getNumberOfAncestors()) {
217 i = tt.getNumberOfAncestors();
218 allDescendants = false;
219 break;
220 }
221 if (tt.getAncestor(i) != getParent().getAncestor(i)) {
222 allDescendants = false;
223 break;
224 }
225 }
226 if (allDescendants) {
227 transitionDomain = getParent().getAncestor(i);
228 break;
229 }
230 }
231 }
232 }
233 }
234 ts = transitionDomain;
235 if (ts == null) {
236 scxmlTransitionDomain = true;
237 }
238 }
239 return ts;
240 }
241
242 /**
243 * Get the XML namespaces at this action node in the SCXML document.
244 *
245 * @return Returns the map of namespaces.
246 */
247 public final Map<String, String> getNamespaces() {
248 return namespaces;
249 }
250
251 /**
252 * Set the XML namespaces at this action node in the SCXML document.
253 *
254 * @param namespaces The document namespaces.
255 */
256 public final void setNamespaces(final Map<String, String> namespaces) {
257 this.namespaces = namespaces;
258 }
259
260 /**
261 * Get the set of transition targets (may be an empty list).
262 *
263 * @return Returns the target(s) as specified in SCXML markup.
264 * <p>Remarks: Is <code>empty</code> for "stay" transitions.
265 *
266 * @since 0.7
267 */
268 public final Set<TransitionTarget> getTargets() {
269 return targets;
270 }
271
272 /**
273 * Get the ID of the transition target (may be null, if, for example,
274 * the target is specified inline).
275 *
276 * @return String Returns the transition target ID
277 * @see #getTargets()
278 */
279 public final String getNext() {
280 return next;
281 }
282
283 /**
284 * Set the transition target by specifying its ID.
285 *
286 * @param next The the transition target ID
287 */
288 public final void setNext(final String next) {
289 this.next = next;
290 }
291 }