| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.commons.scxml2.model; |
| |
| import java.util.HashSet; |
| import java.util.Set; |
| |
| /** |
| * The class in this SCXML object model that corresponds to the |
| * simple <transition> SCXML element, without Transition rules for "events" or |
| * "guard-conditions". Used for <history> or <history> elements. |
| * |
| */ |
| public class SimpleTransition extends Executable implements Observable { |
| |
| /** |
| * Serial version UID. |
| */ |
| private static final long serialVersionUID = 2L; |
| |
| /** |
| * The id for this {@link Observable} which is unique within the SCXML state machine |
| */ |
| private Integer observableId; |
| |
| /** |
| * The Transition type: internal or external (default) |
| * @see #isTypeInternal() |
| */ |
| private TransitionType type; |
| |
| /** |
| * The transition domain for this transition. |
| * @see #getTransitionDomain() |
| */ |
| private TransitionalState transitionDomain; |
| |
| /** |
| * Internal flag indicating a null transitionDomain was derived to be the SCXML Document itself. |
| */ |
| private boolean scxmlTransitionDomain; |
| |
| /** |
| * Derived effective Transition type. |
| * @see #isTypeInternal() |
| */ |
| private Boolean typeInternal; |
| |
| /** |
| * Optional property that specifies the new state(s) or parallel(s) |
| * element to transition to. May be specified by reference or in-line. |
| * If multiple state(s) are specified, they must belong to the regions |
| * of the same parallel. |
| */ |
| private final Set<TransitionTarget> targets; |
| |
| /** |
| * The transition target ID |
| */ |
| private String next; |
| |
| /** |
| * Constructor. |
| */ |
| public SimpleTransition() { |
| this.targets = new HashSet<>(); |
| } |
| |
| private boolean isCompoundStateParent(TransitionalState ts) { |
| return ts instanceof State && ((State)ts).isComposite(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public final Integer getObservableId() { |
| return observableId; |
| } |
| |
| /** |
| * Sets the observableId for this Observable, which must be unique within the SCXML state machine |
| * @param observableId the observableId |
| */ |
| public final void setObservableId(Integer observableId) { |
| this.observableId = observableId; |
| } |
| |
| /** |
| * Get the TransitionalState (State or Parallel) parent. |
| * |
| * @return Returns the parent. |
| */ |
| @Override |
| public TransitionalState getParent() { |
| return (TransitionalState)super.getParent(); |
| } |
| |
| /** |
| * Set the TransitionalState (State or Parallel) parent |
| * <p> |
| * For transitions of Initial or History elements their TransitionalState parent must be set. |
| * </p> |
| * |
| * @param parent The parent to set. |
| */ |
| public final void setParent(final TransitionalState parent) { |
| super.setParent(parent); |
| } |
| |
| /** |
| * @return true if Transition type == internal or false if type == external (default) |
| */ |
| public final TransitionType getType() { |
| return type; |
| } |
| |
| /** |
| * Sets the Transition type |
| * @param type the Transition type |
| */ |
| public final void setType(final TransitionType type) { |
| this.type = type; |
| } |
| |
| /** |
| * Returns the effective Transition type. |
| * <p> |
| * A transition type is only effectively internal if: |
| * </p> |
| * <ul> |
| * <li>its {@link #getType()} == {@link TransitionType#internal}</li> |
| * <li>its source state {@link #getParent()} {@link State#isComposite()}</li> |
| * <li>all its {@link #getTargets()} are proper descendants of its {@link #getParent()}</li> |
| * </ul> |
| * <p> |
| * Otherwise it is treated (for determining its exit states) as if it is of type {@link TransitionType#external} |
| * </p> |
| * @see <a href="https://www.w3.org/TR/2015/REC-scxml-20150901/#SelectingTransitions"> |
| * https://www.w3.org/TR/2015/REC-scxml-20150901/#SelectingTransitions</a> |
| * @return true if the effective Transition type is {@link TransitionType#internal} |
| */ |
| public final boolean isTypeInternal() { |
| if (typeInternal == null) { |
| |
| // derive typeInternal |
| typeInternal = TransitionType.internal == type && isCompoundStateParent(getParent()); |
| |
| if (typeInternal && targets.size() > 0) { |
| for (TransitionTarget tt : targets) { |
| if (!tt.isDescendantOf(getParent())) { |
| typeInternal = false; |
| break; |
| } |
| } |
| } |
| } |
| return typeInternal; |
| } |
| |
| /** |
| * Returns the transition domain of this transition |
| * <p> |
| * If this transition is target-less OR if its transition domain is the SCXML document itself, null is returned. |
| * </p> |
| * <p> |
| * This method therefore only is useful to be invoked if the transition has targets! |
| * </p> |
| * <p> |
| * If the transition has targets then the transition domain is the compound State parent such that: |
| * </p> |
| * <ul> |
| * <li>all states that are exited or entered as a result of taking this transition are descendants of it</li> |
| * <li>no descendant of it has this property</li> |
| * </ul> |
| * <p> |
| * If there is no such compound state parent, the transition domain effectively becomes the SCXML document itself, |
| * which is not a (Transitional)State, and thus null will be returned instead. |
| * </p> |
| * |
| * @return The transition domain of this transition |
| */ |
| public TransitionalState getTransitionDomain() { |
| TransitionalState ts = transitionDomain; |
| if (ts == null && targets.size() > 0 && !scxmlTransitionDomain) { |
| |
| if (getParent() != null) { |
| if (isTypeInternal()) { |
| transitionDomain = getParent(); |
| } |
| else { |
| // findLCCA |
| for (int i = getParent().getNumberOfAncestors()-1; i > -1; i--) { |
| if (isCompoundStateParent(getParent().getAncestor(i))) { |
| boolean allDescendants = true; |
| for (TransitionTarget tt : targets) { |
| if (i >= tt.getNumberOfAncestors()) { |
| i = tt.getNumberOfAncestors(); |
| allDescendants = false; |
| break; |
| } |
| if (tt.getAncestor(i) != getParent().getAncestor(i)) { |
| allDescendants = false; |
| break; |
| } |
| } |
| if (allDescendants) { |
| transitionDomain = getParent().getAncestor(i); |
| break; |
| } |
| } |
| } |
| } |
| } |
| ts = transitionDomain; |
| if (ts == null) { |
| scxmlTransitionDomain = true; |
| } |
| } |
| return ts; |
| } |
| |
| /** |
| * Get the set of transition targets (may be an empty list). |
| * |
| * @return Returns the target(s) as specified in SCXML markup. |
| * <p>Remarks: Is <code>empty</code> for "stay" transitions. |
| * |
| * @since 0.7 |
| */ |
| public final Set<TransitionTarget> getTargets() { |
| return targets; |
| } |
| |
| /** |
| * Get the ID of the transition target (may be null, if, for example, |
| * the target is specified inline). |
| * |
| * @return String Returns the transition target ID |
| * @see #getTargets() |
| */ |
| public final String getNext() { |
| return next; |
| } |
| |
| /** |
| * Set the transition target by specifying its ID. |
| * |
| * @param next The the transition target ID |
| */ |
| public final void setNext(final String next) { |
| this.next = next; |
| } |
| } |