001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.scxml2.model;
018
019import java.util.HashSet;
020import java.util.Map;
021import java.util.Set;
022
023/**
024 * The class in this SCXML object model that corresponds to the
025 * simple <transition> SCXML element, without Transition rules for "events" or
026 * "guard-conditions". Used for <history> or <history> elements.
027 *
028 */
029public class SimpleTransition extends Executable
030        implements NamespacePrefixesHolder, Observable {
031
032    /**
033     * Serial version UID.
034     */
035    private static final long serialVersionUID = 2L;
036
037    /**
038     * The id for this {@link Observable} which is unique within the SCXML state machine
039     */
040    private Integer observableId;
041
042    /**
043     * The Transition type: internal or external (default)
044     * @see #isTypeInternal()
045     */
046    private TransitionType type;
047
048    /**
049     * The transition domain for this transition.
050     * @see #getTransitionDomain()
051     */
052    private TransitionalState transitionDomain;
053
054    /**
055     * Internal flag indicating a null transitionDomain was derived to be the SCXML Document itself.
056     */
057    private boolean scxmlTransitionDomain;
058
059    /**
060     * Derived effective Transition type.
061     * @see #isTypeInternal()
062     */
063    private Boolean typeInternal;
064
065    /**
066     * Optional property that specifies the new state(s) or parallel(s)
067     * element to transition to. May be specified by reference or in-line.
068     * If multiple state(s) are specified, they must belong to the regions
069     * of the same parallel.
070     */
071    private Set<TransitionTarget> targets;
072
073    /**
074     * The transition target ID
075     */
076    private String next;
077
078    /**
079     * The current XML namespaces in the SCXML document for this action node,
080     * preserved for deferred XPath evaluation.
081     */
082    private Map<String, String> namespaces;
083
084    /**
085     * Constructor.
086     */
087    public SimpleTransition() {
088        super();
089        this.targets = new HashSet<TransitionTarget>();
090    }
091
092    private boolean isCompoundStateParent(TransitionalState ts) {
093        return ts != null && ts instanceof State && ((State)ts).isComposite();
094    }
095
096    /**
097     * {@inheritDoc}
098     */
099    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}