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.io.Serializable;
20  
21  /**
22   * An abstract base class for elements in SCXML that can serve as a
23   * <target> for a <transition>, such as State or Parallel.
24   *
25   */
26  public abstract class TransitionTarget implements Serializable, Observable {
27  
28      private static final EnterableState[] ZERO_ANCESTORS = new EnterableState[0];
29  
30      /**
31       * The id for this {@link Observable} which is unique within the SCXML state machine
32       */
33      private Integer observableId;
34  
35      /**
36       * Identifier for this transition target. Other parts of the SCXML
37       * document may refer to this <state> using this ID.
38       */
39      private String id;
40  
41      /**
42       * The parent of this transition target (may be null, if the parent
43       * is the SCXML document root).
44       */
45      private EnterableState parent;
46  
47      private EnterableState[] ancestors = ZERO_ANCESTORS;
48  
49      /**
50       * Constructor.
51       */
52      public TransitionTarget() {
53          super();
54          parent = null;
55      }
56  
57      /**
58       * {@inheritDoc}
59       */
60      public final Integer getObservableId() {
61          return observableId;
62      }
63  
64      /**
65       * Sets the observableId for this Observable, which must be unique within the SCXML state machine
66       * @param observableId the observableId
67       */
68      public final void setObservableId(Integer observableId) {
69          this.observableId = observableId;
70      }
71  
72      /**
73       * Get the identifier for this transition target (may be null).
74       *
75       * @return Returns the id.
76       */
77      public final String getId() {
78          return id;
79      }
80  
81      /**
82       * Set the identifier for this transition target.
83       *
84       * @param id The id to set.
85       */
86      public final void setId(final String id) {
87          this.id = id;
88      }
89  
90      /**
91       * @return the number of TransitionTarget ancestors
92       */
93      public int getNumberOfAncestors() {
94          return ancestors.length;
95      }
96  
97      /**
98       * Get the ancestor of this TransitionTarget at specified level
99       * @param level the level of the ancestor to return, zero being top
100      * @return the ancestor at specified level
101      */
102     public EnterableState getAncestor(int level) {
103         return ancestors[level];
104     }
105 
106     /**
107      * Get the parent TransitionTarget.
108      *
109      * @return Returns the parent state
110      * (null if parent is <scxml> element)
111      */
112     public EnterableState getParent() {
113         return parent;
114     }
115 
116     /**
117      * Set the parent EnterableState.
118      * <p>
119      * The parent of a TransitionTarget must be of type EnterableState as a History (as only non-EnterableState)
120      * TransitionTarget cannot have children.
121      * </p>
122      *
123      * @param parent The parent state to set
124      */
125     protected void setParent(final EnterableState parent) {
126         if (parent == null) {
127             throw new IllegalArgumentException("Parent parameter cannot be null");
128         }
129         if (parent == this) {
130             throw new IllegalArgumentException("Cannot set self as parent");
131         }
132         if (this.parent != parent) {
133             this.parent = parent;
134             updateDescendantsAncestors();
135         }
136     }
137 
138     /**
139      * Update TransitionTarget descendants their ancestors
140      */
141     protected void updateDescendantsAncestors() {
142         TransitionTarget ttParent = parent;
143         ancestors = new EnterableState[ttParent.ancestors.length+1];
144         System.arraycopy(ttParent.ancestors, 0, ancestors, 0, ttParent.ancestors.length);
145         ancestors[ttParent.ancestors.length] = parent;
146     }
147 
148     /**
149      * Checks whether this transition target (State or Parallel) is a
150      * descendant of the transition target context.
151      *
152      * @param context
153      *            TransitionTarget context - a potential ancestor
154      * @return true if this is a descendant of context, false otherwise
155      */
156     public final boolean isDescendantOf(TransitionTarget context) {
157         return getNumberOfAncestors() > context.getNumberOfAncestors()
158                 && getAncestor(context.getNumberOfAncestors()) == context;
159     }
160     
161     /**
162      * Enforce identity equality only
163      * @param other other object to compare with
164      * @return this == other
165      */
166     @Override
167     public final boolean equals(final Object other) {
168         return this == other;
169     }
170 
171     /**
172      * Enforce returning identity based hascode
173      * @return {@link System#identityHashCode(Object) System.identityHashCode(this)}
174      */
175     @Override
176     public final int hashCode() {
177         return System.identityHashCode(this);
178     }
179 }
180