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.io.Serializable;
020
021/**
022 * An abstract base class for elements in SCXML that can serve as a
023 * <target> for a <transition>, such as State or Parallel.
024 *
025 */
026public abstract class TransitionTarget implements Serializable, Observable {
027
028    private static final EnterableState[] ZERO_ANCESTORS = new EnterableState[0];
029
030    /**
031     * The id for this {@link Observable} which is unique within the SCXML state machine
032     */
033    private Integer observableId;
034
035    /**
036     * Identifier for this transition target. Other parts of the SCXML
037     * document may refer to this <state> using this ID.
038     */
039    private String id;
040
041    /**
042     * The parent of this transition target (may be null, if the parent
043     * is the SCXML document root).
044     */
045    private EnterableState parent;
046
047    private EnterableState[] ancestors = ZERO_ANCESTORS;
048
049    /**
050     * Constructor.
051     */
052    public TransitionTarget() {
053        super();
054        parent = null;
055    }
056
057    /**
058     * {@inheritDoc}
059     */
060    public final Integer getObservableId() {
061        return observableId;
062    }
063
064    /**
065     * Sets the observableId for this Observable, which must be unique within the SCXML state machine
066     * @param observableId the observableId
067     */
068    public final void setObservableId(Integer observableId) {
069        this.observableId = observableId;
070    }
071
072    /**
073     * Get the identifier for this transition target (may be null).
074     *
075     * @return Returns the id.
076     */
077    public final String getId() {
078        return id;
079    }
080
081    /**
082     * Set the identifier for this transition target.
083     *
084     * @param id The id to set.
085     */
086    public final void setId(final String id) {
087        this.id = id;
088    }
089
090    /**
091     * @return the number of TransitionTarget ancestors
092     */
093    public int getNumberOfAncestors() {
094        return ancestors.length;
095    }
096
097    /**
098     * Get the ancestor of this TransitionTarget at specified level
099     * @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