001 /* 002 * Copyright 2005 Stephen J. McConnell. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 013 * implied. 014 * 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019 package net.dpml.state; 020 021 import java.io.Serializable; 022 import java.util.Arrays; 023 024 /** 025 * Default implementation of an application state descriptor. 026 * 027 * @author <a href="http://www.dpml.net">Digital Product Meta Library</a> 028 * @version 1.0.4 029 */ 030 public class DefaultState implements State, Serializable 031 { 032 private final String m_name; 033 private final Transition[] m_transitions; 034 private final Operation[] m_operations; 035 private final Interface[] m_interfaces; 036 private final State[] m_states; 037 private final Trigger[] m_triggers; 038 private final boolean m_terminal; 039 040 private transient State m_parent; 041 042 /** 043 * Creation of a new state. 044 */ 045 public DefaultState() 046 { 047 this( "root" ); 048 } 049 050 /** 051 * Creation of a new state. 052 * @param name the state name 053 */ 054 public DefaultState( final String name ) 055 { 056 this( name, new Trigger[0], new Transition[0], new Interface[0], new Operation[0], new State[0], true ); 057 } 058 059 /** 060 * Creation of a new non-terminal state. 061 * @param name the state name 062 * @param triggers an array of triggers 063 * @param transitions an array of state transitions 064 * @param interfaces an array of management interface declarations 065 * @param operations an array of operations 066 * @param states an array of substates 067 */ 068 public DefaultState( 069 final String name, final Trigger[] triggers, final Transition[] transitions, 070 final Interface[] interfaces, final Operation[] operations, final State[] states ) 071 { 072 this( name, triggers, transitions, interfaces, operations, states, false ); 073 } 074 075 /** 076 * Creation of a new state. 077 * @param name the state name 078 * @param triggers an array of triggers 079 * @param transitions an array of state transitions 080 * @param interfaces an array of management interface declarations 081 * @param operations an array of operations 082 * @param states an array of substates 083 * @param terminal the terminal flag 084 */ 085 public DefaultState( 086 final String name, final Trigger[] triggers, final Transition[] transitions, 087 final Interface[] interfaces, final Operation[] operations, final State[] states, boolean terminal ) 088 { 089 if( null == name ) 090 { 091 throw new NullPointerException( "name" ); 092 } 093 094 for( int i=0; i<operations.length; i++ ) 095 { 096 Operation operation = operations[i]; 097 if( null == operation ) 098 { 099 throw new NullPointerException( "operation/" + i ); 100 } 101 } 102 103 m_name = name; 104 m_triggers = triggers; 105 m_transitions = transitions; 106 m_operations = operations; 107 m_interfaces = interfaces; 108 m_states = states; 109 m_terminal = terminal; 110 111 for( int i=0; i<transitions.length; i++ ) 112 { 113 Transition transition = transitions[i]; 114 if( null == transition ) 115 { 116 throw new NullPointerException( "transition/" + i ); 117 } 118 transition.setState( this ); 119 } 120 121 for( int i=0; i<triggers.length; i++ ) 122 { 123 Trigger trigger = triggers[i]; 124 if( null == trigger ) 125 { 126 throw new NullPointerException( "trigger/" + i ); 127 } 128 Action action = trigger.getAction(); 129 if( action instanceof Transition ) 130 { 131 Transition transition = (Transition) action; 132 transition.setState( this ); 133 } 134 } 135 136 for( int i=0; i<interfaces.length; i++ ) 137 { 138 Interface description = interfaces[i]; 139 if( null == description ) 140 { 141 throw new NullPointerException( "interface/" + i ); 142 } 143 } 144 145 for( int i=0; i<states.length; i++ ) 146 { 147 State state = states[i]; 148 if( null == state ) 149 { 150 throw new NullPointerException( "state/ " + i ); 151 } 152 state.setParent( this ); 153 } 154 } 155 156 /** 157 * Return the parent state to this state or null if this is the root of a 158 * state graph. 159 * @return the parent state 160 */ 161 public State getParent() 162 { 163 return m_parent; 164 } 165 166 /** 167 * Set the parent state. 168 * @param state the parent state 169 */ 170 public void setParent( State state ) 171 { 172 if( null == m_parent ) 173 { 174 m_parent = state; 175 } 176 else 177 { 178 final String error = 179 "Illegal attempt to reassign parent."; 180 throw new IllegalStateException( error ); 181 } 182 } 183 184 /** 185 * Return the name of the state. 186 * @return the state name 187 */ 188 public String getName() 189 { 190 return m_name; 191 } 192 193 /** 194 * Return the array of triggers associated with the state. 195 * @return the trigger array 196 */ 197 public Trigger[] getTriggers() 198 { 199 return m_triggers; 200 } 201 202 /** 203 * Return the state path. The path is composed of a sequence of states 204 * from the root to this state. 205 * @return the state path 206 */ 207 public State[] getStatePath() 208 { 209 if( null == m_parent ) 210 { 211 return new State[]{this}; 212 } 213 else 214 { 215 State[] path = m_parent.getStatePath(); 216 State[] result = new State[ path.length + 1 ]; 217 System.arraycopy( path, 0, result, 0, path.length ); 218 result[ path.length ] = this; 219 return result; 220 } 221 } 222 223 /** 224 * Return the substates within this state. 225 * @return the substate array 226 */ 227 public State[] getStates() 228 { 229 return m_states; 230 } 231 232 /** 233 * Return the array of transtions associated with the state. 234 * @return the transition array 235 */ 236 public Transition[] getTransitions() 237 { 238 return m_transitions; 239 } 240 241 /** 242 * Return the array of operations associated with the state. 243 * @return the operation array 244 */ 245 public Operation[] getOperations() 246 { 247 return m_operations; 248 } 249 250 /** 251 * Return the array of management interfaces associated with the state. 252 * @return the interfaces array 253 */ 254 public Interface[] getInterfaces() 255 { 256 return m_interfaces; 257 } 258 259 /** 260 * Return the terminal flag. 261 * @return true if terminal 262 */ 263 public boolean getTerminal() 264 { 265 return isTerminal(); 266 } 267 268 /** 269 * Test is the state is a terminal state. 270 * @return true if terminal 271 */ 272 public boolean isTerminal() 273 { 274 return m_terminal; 275 } 276 277 /** 278 * Return a string representation of the instance. 279 * @return the string value 280 */ 281 public String toString() 282 { 283 StringBuffer buffer = new StringBuffer(); 284 State[] path = getStatePath(); 285 for( int i=0; i<path.length; i++ ) 286 { 287 State state = path[i]; 288 if( i>0 ) 289 { 290 buffer.append( "/" ); 291 } 292 buffer.append( state.getName() ); 293 } 294 return buffer.toString(); 295 //return "[" + m_name + "]"; 296 } 297 298 /** 299 * Compare this object to another for equality. 300 * @param other the other object 301 * @return true if the object is equal to this object 302 */ 303 public boolean equals( Object other ) 304 { 305 if( null == other ) 306 { 307 return false; 308 } 309 else if( other instanceof DefaultState ) 310 { 311 DefaultState state = (DefaultState) other; 312 if( !m_name.equals( state.getName() ) ) 313 { 314 return false; 315 } 316 else if( m_terminal != state.isTerminal() ) 317 { 318 return false; 319 } 320 else if( !Arrays.equals( m_triggers, state.getTriggers() ) ) 321 { 322 return false; 323 } 324 else if( !Arrays.equals( m_transitions, state.getTransitions() ) ) 325 { 326 return false; 327 } 328 else if( !Arrays.equals( m_operations, state.getOperations() ) ) 329 { 330 return false; 331 } 332 else 333 { 334 return Arrays.equals( m_states, state.getStates() ); 335 } 336 } 337 else 338 { 339 return false; 340 } 341 } 342 343 /** 344 * Compute the hashcode for this instance. 345 * @return the hashcode value 346 */ 347 public int hashCode() 348 { 349 if( null == m_parent ) 350 { 351 return m_name.hashCode(); 352 } 353 else 354 { 355 int hash = m_parent.hashCode(); 356 hash ^= m_name.hashCode(); 357 return hash; 358 } 359 } 360 361 private boolean equals( Object a, Object b ) 362 { 363 if( null == a ) 364 { 365 return null == b; 366 } 367 else 368 { 369 return a.equals( b ); 370 } 371 } 372 }