View Javadoc
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 &quot;stay&quot; 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 }