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