001: package org.wfmc.wapi;
002:
003: import java.io.InvalidObjectException;
004: import java.io.ObjectStreamException;
005: import java.io.Serializable;
006: import java.lang.reflect.Array;
007: import java.util.HashSet;
008: import java.util.Set;
009:
010: /**
011: * Abstract base that represents the state of an object.
012: *
013: * @author Adrian Price
014: */
015: public abstract class WMObjectState implements Serializable {
016: private static final long serialVersionUID = 3506741109231223508L;
017:
018: /**
019: * Signifies that the object remains in its current state.
020: */
021: public static final int DEFAULT_INT = -1;
022: /**
023: * Action does not cause a state transition.
024: */
025: public static final int NO_ACTION = -1;
026: /**
027: * Action is invalid for the current state.
028: */
029: public static final int ILLEGAL_ACTION = -2;
030: /**
031: * Action is illegal to API caller, but is being forced by the engine.
032: */
033: public static final int FORCED_ACTION = -3;
034:
035: public static final String MESSAGE = "Not a valid transition for an object in this state";
036:
037: /**
038: * Array of states to which legal transitions from the current state exist.
039: */
040: private transient WMObjectState[] _allowedStatesList;
041: /**
042: * The object state code.
043: */
044: protected int _state;
045:
046: protected static WMObjectState valueOf(String[] tags,
047: WMObjectState[] values, String state) {
048:
049: for (int i = 0; i < tags.length; i++) {
050: if (tags[i].equals(state))
051: return values[i];
052: }
053: throw new IllegalArgumentException(state);
054: }
055:
056: /**
057: * Construct a new <code>WMObjectState</code>. The array type parameters
058: * are references to arrays statically defined in the calling subclass.
059: *
060: * @param state The integer code for this state.
061: */
062: protected WMObjectState(int state) {
063: _state = state;
064: }
065:
066: /**
067: * Returns the list of states to which legal transitions are possible.
068: *
069: * @return List of legal states.
070: */
071: public final WMObjectState[] getStates() {
072: if (_allowedStatesList == null) {
073: int[] this State = getStatesByAction();
074: int n = this State.length;
075: Set states = new HashSet(n);
076: for (int i = 0; i < n; i++) {
077: int action = this State[i];
078: if (action != ILLEGAL_ACTION)
079: states.add(getValues()[action]);
080: }
081: _allowedStatesList = (WMObjectState[]) states
082: .toArray((Object[]) Array.newInstance(getClass(),
083: states.size()));
084: }
085: return (WMObjectState[]) _allowedStatesList.clone();
086: }
087:
088: /**
089: * Returns the state that would result from a specified action.
090: *
091: * @param action Action code.
092: * @return State code.
093: * @throws WMTransitionNotAllowedException
094: * if the specified action is
095: * inapplicable to the current state.
096: */
097: protected final int stateFromAction(int action)
098: throws WMTransitionNotAllowedException {
099:
100: int newState = getStatesByAction()[action];
101: if (newState < 0) {
102: throw new WMTransitionNotAllowedException(
103: getValues()[_state].stringValue(), action, MESSAGE);
104: }
105: return newState;
106: }
107:
108: /**
109: * Returns the action required to transition to a specified state.
110: *
111: * @param newState The new state required.
112: * @param throwException Causes an exception to be thrown if the transition
113: * would be illegal.
114: * @return Action code.
115: * @throws WMTransitionNotAllowedException
116: * if a transition from the current
117: * state to the new state would be illegal.
118: */
119: public final int checkTransition(WMObjectState newState,
120: boolean throwException)
121: throws WMTransitionNotAllowedException {
122:
123: return checkTransition(newState.value(), throwException);
124: }
125:
126: /**
127: * Returns the action required to transition to a specified state.
128: *
129: * @param newState The new state required.
130: * @param throwException Causes an exception to be thrown if the transition
131: * would be illegal.
132: * @return Action code.
133: * @throws WMTransitionNotAllowedException
134: * if a transition from the current
135: * state to the new state would be illegal.
136: */
137: public final int checkTransition(int newState,
138: boolean throwException)
139: throws WMTransitionNotAllowedException {
140:
141: int action = getActionsByState()[newState];
142: if (action == ILLEGAL_ACTION && throwException) {
143: WMObjectState[] states = getValues();
144: throw new WMTransitionNotAllowedException(states[_state]
145: .stringValue(), states[newState].stringValue(),
146: MESSAGE);
147: }
148: return action;
149: }
150:
151: /**
152: * Returns the list of all state tags applicable to this instance's class.
153: * The array is indexed by state code.
154: * This is be a reference to a final array defined statically in the
155: * instance's subclass.
156: *
157: * @return Array of state tags.
158: */
159: protected abstract String[] getTags();
160:
161: /**
162: * Returns the list of all state values applicable to this instance's class.
163: * This is a reference to a final array defined statically in the
164: * instance's subclass.
165: *
166: * @return Array of state objects.
167: */
168: protected abstract WMObjectState[] getValues();
169:
170: /**
171: * Returns the transitions from the current state, indexed by action.
172: * Illegal transitions are marked by the array element value
173: * {@link #ILLEGAL_ACTION}.
174: *
175: * @return Array of state codes. This is a reference to a final array
176: * defined statically in the instance's subclass.
177: */
178: protected abstract int[] getStatesByAction();
179:
180: /**
181: * Returns the transitions from the current state, indexed by new state.
182: * Illegal transitions are marked by the array element value
183: * {@link #ILLEGAL_ACTION}.
184: *
185: * @return Array of action codes. This is a reference to a final array
186: * defined statically in the instance's subclass.
187: */
188: protected abstract int[] getActionsByState();
189:
190: /**
191: * Tests for object identity. Only one instance of each ordinal value can
192: * ever exist.
193: *
194: * @param obj The with which to compare object this instance.
195: * @return <code>true</code> if the two references point to the same object.
196: */
197: public final boolean equals(Object obj) {
198: // This works because we have ensured that only one instance of each
199: // value can exist in a VM, even when deserializing instances from an
200: // ObjectInputStream.
201: return this == obj;
202: }
203:
204: /**
205: * Equal objects must have equal hash codes.
206: *
207: * @return The hash code.
208: */
209: public final int hashCode() {
210: return _state;
211: }
212:
213: /**
214: * Returns the object state as an integer. This ordinal state number is how
215: * the state is represented in persistent storage.
216: *
217: * @return Ordinal state number, as defined in subclasses.
218: */
219: public final int value() {
220: return _state;
221: }
222:
223: /**
224: * JavaBean-compliant property accessor, synonym for {@link #value}.
225: */
226: public final int getValue() {
227: return _state;
228: }
229:
230: protected final Object readResolve() throws ObjectStreamException {
231: // This code ensures that only one instance of a given class and state
232: // value can ever exist per JVM instance.
233: WMObjectState obj = getValues()[_state];
234: if (obj == null) {
235: throw new InvalidObjectException(
236: "Invalid code in object stream: " + _state);
237: }
238: return obj;
239: }
240:
241: /**
242: * Returns the object state as a string.
243: *
244: * @return The object state.
245: */
246: public final String stringValue() {
247: return getTags()[_state];
248: }
249:
250: public final String toString() {
251: // String clazz = getClass().getName();
252: // clazz = clazz.substring(clazz.lastIndexOf('.') + 1);
253: // return clazz + "[state=" + stringValue() + ']';
254: return stringValue();
255: }
256: }
|