001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one
003: * or more contributor license agreements. See the NOTICE file
004: * distributed with this work for additional information
005: * regarding copyright ownership. The ASF licenses this file
006: * to you under the Apache License, Version 2.0 (the
007: * "License"); you may not use this file except in compliance
008: * with the License. You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing,
013: * software distributed under the License is distributed on an
014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015: * KIND, either express or implied. See the License for the
016: * specific language governing permissions and limitations
017: * under the License.
018: *
019: */
020: package org.apache.mina.statemachine;
021:
022: import java.lang.annotation.Annotation;
023: import java.lang.reflect.Field;
024: import java.lang.reflect.Method;
025: import java.lang.reflect.Modifier;
026: import java.util.ArrayList;
027: import java.util.Arrays;
028: import java.util.Comparator;
029: import java.util.HashMap;
030: import java.util.LinkedHashMap;
031: import java.util.LinkedList;
032: import java.util.List;
033: import java.util.Map;
034:
035: import org.apache.mina.statemachine.annotation.Transition;
036: import org.apache.mina.statemachine.annotation.TransitionAnnotation;
037: import org.apache.mina.statemachine.annotation.Transitions;
038: import org.apache.mina.statemachine.event.Event;
039: import org.apache.mina.statemachine.transition.MethodTransition;
040:
041: /**
042: * Creates {@link StateMachine}s by reading {@link org.apache.mina.statemachine.annotation.State},
043: * {@link Transition} and {@link Transitions} (or equivalent) annotations from one or more arbitrary
044: * objects.
045: *
046: *
047: * @author The Apache MINA Project (dev@mina.apache.org)
048: * @version $Rev: 592479 $, $Date: 2007-11-06 09:26:11 -0700 (Tue, 06 Nov 2007) $
049: */
050: public class StateMachineFactory {
051: private final Class<? extends Annotation> transitionAnnotation;
052: private final Class<? extends Annotation> transitionsAnnotation;
053:
054: protected StateMachineFactory(
055: Class<? extends Annotation> transitionAnnotation,
056: Class<? extends Annotation> transitionsAnnotation) {
057: this .transitionAnnotation = transitionAnnotation;
058: this .transitionsAnnotation = transitionsAnnotation;
059: }
060:
061: /**
062: * Returns a new {@link StateMachineFactory} instance which creates
063: * {@link StateMachine}s by reading the specified {@link Transition}
064: * equivalent annotation.
065: *
066: * @param transitionAnnotation the {@link Transition} equivalent annotation.
067: * @return the {@link StateMachineFactory}.
068: */
069: public static StateMachineFactory getInstance(
070: Class<? extends Annotation> transitionAnnotation) {
071: TransitionAnnotation a = transitionAnnotation
072: .getAnnotation(TransitionAnnotation.class);
073: if (a == null) {
074: throw new IllegalArgumentException("The annotation class "
075: + transitionAnnotation
076: + " has not been annotated with the "
077: + TransitionAnnotation.class.getName()
078: + " annotation");
079: }
080: return new StateMachineFactory(transitionAnnotation, a.value());
081: }
082:
083: /**
084: * Creates a new {@link StateMachine} from the specified handler object and
085: * using a start state with id <code>start</code>.
086: *
087: * @param handler the object containing the annotations describing the
088: * state machine.
089: * @return the {@link StateMachine} object.
090: */
091: public StateMachine create(Object handler) {
092: return create(handler, new Object[0]);
093: }
094:
095: /**
096: * Creates a new {@link StateMachine} from the specified handler object and
097: * using the {@link State} with the specified id as start state.
098: *
099: * @param start the id of the start {@link State} to use.
100: * @param handler the object containing the annotations describing the
101: * state machine.
102: * @return the {@link StateMachine} object.
103: */
104: public StateMachine create(String start, Object handler) {
105: return create(start, handler, new Object[0]);
106: }
107:
108: /**
109: * Creates a new {@link StateMachine} from the specified handler objects and
110: * using a start state with id <code>start</code>.
111: *
112: * @param handler the first object containing the annotations describing the
113: * state machine.
114: * @param handlers zero or more additional objects containing the
115: * annotations describing the state machine.
116: * @return the {@link StateMachine} object.
117: */
118: public StateMachine create(Object handler, Object... handlers) {
119: return create("start", handler, handlers);
120: }
121:
122: /**
123: * Creates a new {@link StateMachine} from the specified handler objects and
124: * using the {@link State} with the specified id as start state.
125: *
126: * @param start the id of the start {@link State} to use.
127: * @param handler the first object containing the annotations describing the
128: * state machine.
129: * @param handlers zero or more additional objects containing the
130: * annotations describing the state machine.
131: * @return the {@link StateMachine} object.
132: */
133: public StateMachine create(String start, Object handler,
134: Object... handlers) {
135:
136: Map<String, State> states = new HashMap<String, State>();
137: List<Object> handlersList = new ArrayList<Object>(
138: 1 + handlers.length);
139: handlersList.add(handler);
140: handlersList.addAll(Arrays.asList(handlers));
141:
142: LinkedList<Field> fields = new LinkedList<Field>();
143: for (Object h : handlersList) {
144: fields.addAll(getFields(h instanceof Class ? (Class<?>) h
145: : h.getClass()));
146: }
147: for (State state : createStates(fields)) {
148: states.put(state.getId(), state);
149: }
150:
151: if (!states.containsKey(start)) {
152: throw new StateMachineCreationException("Start state '"
153: + start + "' not found.");
154: }
155:
156: setupTransitions(transitionAnnotation, transitionsAnnotation,
157: states, handlersList);
158:
159: return new StateMachine(states.values(), start);
160: }
161:
162: private static void setupTransitions(
163: Class<? extends Annotation> transitionAnnotation,
164: Class<? extends Annotation> transitionsAnnotation,
165: Map<String, State> states, List<Object> handlers) {
166: for (Object handler : handlers) {
167: setupTransitions(transitionAnnotation,
168: transitionsAnnotation, states, handler);
169: }
170: }
171:
172: private static void setupTransitions(
173: Class<? extends Annotation> transitionAnnotation,
174: Class<? extends Annotation> transitionsAnnotation,
175: Map<String, State> states, Object handler) {
176:
177: Method[] methods = handler.getClass().getDeclaredMethods();
178: Arrays.sort(methods, new Comparator<Method>() {
179: public int compare(Method m1, Method m2) {
180: return m1.toString().compareTo(m2.toString());
181: }
182: });
183:
184: for (Method m : methods) {
185: List<TransitionWrapper> transitionAnnotations = new ArrayList<TransitionWrapper>();
186: if (m.isAnnotationPresent(transitionAnnotation)) {
187: transitionAnnotations.add(new TransitionWrapper(
188: transitionAnnotation, m
189: .getAnnotation(transitionAnnotation)));
190: }
191: if (m.isAnnotationPresent(transitionsAnnotation)) {
192: transitionAnnotations.addAll(Arrays
193: .asList(new TransitionsWrapper(
194: transitionAnnotation,
195: transitionsAnnotation,
196: m.getAnnotation(transitionsAnnotation))
197: .value()));
198: }
199:
200: if (transitionAnnotations.isEmpty()) {
201: continue;
202: }
203:
204: for (TransitionWrapper annotation : transitionAnnotations) {
205: Object[] eventIds = annotation.on();
206: if (eventIds.length == 0) {
207: throw new StateMachineCreationException(
208: "Error encountered "
209: + "when processing method " + m
210: + ". No event ids specified.");
211: }
212: if (annotation.in().length == 0) {
213: throw new StateMachineCreationException(
214: "Error encountered "
215: + "when processing method " + m
216: + ". No states specified.");
217: }
218:
219: State next = null;
220: if (!annotation.next().equals(Transition.SELF)) {
221: next = states.get(annotation.next());
222: if (next == null) {
223: throw new StateMachineCreationException(
224: "Error encountered "
225: + "when processing method " + m
226: + ". Unknown next state: "
227: + annotation.next() + ".");
228: }
229: }
230:
231: for (Object event : eventIds) {
232: if (event == null) {
233: event = Event.WILDCARD_EVENT_ID;
234: }
235: if (!(event instanceof String)) {
236: event = event.toString();
237: }
238: for (String in : annotation.in()) {
239: State state = states.get(in);
240: if (state == null) {
241: throw new StateMachineCreationException(
242: "Error encountered "
243: + "when processing method "
244: + m + ". Unknown state: "
245: + in + ".");
246: }
247:
248: state.addTransition(new MethodTransition(event,
249: next, m, handler), annotation.weight());
250: }
251: }
252: }
253: }
254: }
255:
256: static List<Field> getFields(Class<?> clazz) {
257: LinkedList<Field> fields = new LinkedList<Field>();
258:
259: for (Field f : clazz.getDeclaredFields()) {
260: if (!f
261: .isAnnotationPresent(org.apache.mina.statemachine.annotation.State.class)) {
262: continue;
263: }
264:
265: if ((f.getModifiers() & Modifier.STATIC) == 0
266: || (f.getModifiers() & Modifier.FINAL) == 0
267: || !f.getType().equals(String.class)) {
268: throw new StateMachineCreationException(
269: "Error encountered when "
270: + "processing field "
271: + f
272: + ". Only static final "
273: + "String fields can be used with the @State "
274: + "annotation.");
275: }
276:
277: if (!f.isAccessible()) {
278: f.setAccessible(true);
279: }
280:
281: fields.add(f);
282: }
283:
284: return fields;
285: }
286:
287: static State[] createStates(List<Field> fields) {
288: LinkedHashMap<String, State> states = new LinkedHashMap<String, State>();
289:
290: while (!fields.isEmpty()) {
291: int size = fields.size();
292: int numStates = states.size();
293: for (int i = 0; i < size; i++) {
294: Field f = fields.remove(0);
295:
296: String value = null;
297: try {
298: value = (String) f.get(null);
299: } catch (IllegalAccessException iae) {
300: throw new StateMachineCreationException(
301: "Error encountered when "
302: + "processing field " + f + ".",
303: iae);
304: }
305:
306: org.apache.mina.statemachine.annotation.State stateAnnotation = f
307: .getAnnotation(org.apache.mina.statemachine.annotation.State.class);
308: if (stateAnnotation
309: .value()
310: .equals(
311: org.apache.mina.statemachine.annotation.State.ROOT)) {
312: states.put(value, new State(value));
313: } else if (states.containsKey(stateAnnotation.value())) {
314: states.put(value, new State(value, states
315: .get(stateAnnotation.value())));
316: } else {
317: // Move to the back of the list of fields for later
318: // processing
319: fields.add(f);
320: }
321: }
322:
323: /*
324: * If no new states were added to states during this iteration it
325: * means that all fields in fields specify non-existent parents.
326: */
327: if (states.size() == numStates) {
328: throw new StateMachineCreationException(
329: "Error encountered while creating "
330: + "FSM. The following fields specify non-existing "
331: + "parent states: " + fields);
332: }
333: }
334:
335: return states.values().toArray(new State[0]);
336: }
337:
338: private static class TransitionWrapper {
339: private final Class<? extends Annotation> transitionClazz;
340: private final Annotation annotation;
341:
342: public TransitionWrapper(
343: Class<? extends Annotation> transitionClazz,
344: Annotation annotation) {
345: this .transitionClazz = transitionClazz;
346: this .annotation = annotation;
347: }
348:
349: Object[] on() {
350: return getParameter("on", Object[].class);
351: }
352:
353: String[] in() {
354: return getParameter("in", String[].class);
355: }
356:
357: String next() {
358: return getParameter("next", String.class);
359: }
360:
361: int weight() {
362: return getParameter("weight", Integer.TYPE);
363: }
364:
365: @SuppressWarnings("unchecked")
366: private <T> T getParameter(String name, Class<T> returnType) {
367: try {
368: Method m = transitionClazz.getMethod(name);
369: if (!returnType.isAssignableFrom(m.getReturnType())) {
370: throw new NoSuchMethodException();
371: }
372: return (T) m.invoke(annotation);
373: } catch (Throwable t) {
374: throw new StateMachineCreationException(
375: "Could not get parameter '" + name
376: + "' from Transition annotation "
377: + transitionClazz);
378: }
379: }
380: }
381:
382: private static class TransitionsWrapper {
383: private final Class<? extends Annotation> transitionsclazz;
384: private final Class<? extends Annotation> transitionClazz;
385: private final Annotation annotation;
386:
387: public TransitionsWrapper(
388: Class<? extends Annotation> transitionClazz,
389: Class<? extends Annotation> transitionsclazz,
390: Annotation annotation) {
391: this .transitionClazz = transitionClazz;
392: this .transitionsclazz = transitionsclazz;
393: this .annotation = annotation;
394: }
395:
396: TransitionWrapper[] value() {
397: Annotation[] annos = getParameter("value",
398: Annotation[].class);
399: TransitionWrapper[] wrappers = new TransitionWrapper[annos.length];
400: for (int i = 0; i < annos.length; i++) {
401: wrappers[i] = new TransitionWrapper(transitionClazz,
402: annos[i]);
403: }
404: return wrappers;
405: }
406:
407: @SuppressWarnings("unchecked")
408: private <T> T getParameter(String name, Class<T> returnType) {
409: try {
410: Method m = transitionsclazz.getMethod(name);
411: if (!returnType.isAssignableFrom(m.getReturnType())) {
412: throw new NoSuchMethodException();
413: }
414: return (T) m.invoke(annotation);
415: } catch (Throwable t) {
416: throw new StateMachineCreationException(
417: "Could not get parameter '" + name
418: + "' from Transitions annotation "
419: + transitionsclazz);
420: }
421: }
422: }
423: }
|