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 }