1 /*
2 * Copyright 2006-2016 The JGUIraffe Team.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License")
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package net.sf.jguiraffe.gui.builder.action;
17
18 import java.lang.reflect.InvocationHandler;
19 import java.lang.reflect.Method;
20 import java.lang.reflect.Proxy;
21
22 import net.sf.jguiraffe.gui.builder.event.BuilderEvent;
23 import net.sf.jguiraffe.gui.builder.event.filter.AndEventFilter;
24 import net.sf.jguiraffe.gui.builder.event.filter.EventFilter;
25
26 /**
27 * <p>
28 * A class that allows to combine actions with event listeners.
29 * </p>
30 * <p>
31 * This class can be used to create an event listener proxy for an arbitrary
32 * event listener interface that will delegate to a specified {@link FormAction}
33 * object. By defining an {@link EventFilter} it can be exactly specified, which
34 * event should cause the action to be invoked.
35 * </p>
36 * <p>
37 * The purpose of this class is to serve as a bridge between the event listener
38 * API and the action API. This is especially useful when event listeners are to
39 * be defined in builder scripts: Then it is easy to route to actions, which are
40 * defined by action tags anyway.
41 * </p>
42 * <p>
43 * Internally this class makes use of the Proxy mechanism supported by Java 1.3
44 * and higher. For the passed in event listener interface(s) a proxy object is
45 * created. Every invocation of one of the proxy's methods will cause the
46 * specified filter to be called to check whether the current event object
47 * matches the filter's criteria. If this is the case and if the action is
48 * enabled, its <code>execute()</code> method will be invoked.
49 * </p>
50 * <p>
51 * Instances of <code>ActionInvoker</code> should be created using the static
52 * factory methods. These methods return an object, which can be passed to one
53 * of the interfaces that was passed to the factory method. Then it can be
54 * registered as the corresponding event listener at the desired form component.
55 * </p>
56 *
57 * @author Oliver Heger
58 * @version $Id: ActionInvoker.java 205 2012-01-29 18:29:57Z oheger $
59 */
60 public class ActionInvoker implements InvocationHandler
61 {
62 /**
63 * Constant for a dummy filter that will be used when no filter is provided.
64 * This filter will accept all passed in objects.
65 */
66 private static final EventFilter DUMMY_FILTER = new AndEventFilter();
67
68 /** Stores the action to be invoked. */
69 private final FormAction action;
70
71 /** Stores the filter for events. */
72 private final EventFilter filter;
73
74 /**
75 * Creates a new instance of <code>ActionInvoker</code> and initializes it
76 * with the action to be invoked. No event filter is used.
77 *
78 * @param action the action (must not be <b>null</b>)
79 */
80 ActionInvoker(FormAction action)
81 {
82 this(action, null);
83 }
84
85 /**
86 * Creates a new instance of <code>ActionInvoker</code> and initializes it
87 * with the action to be invoked and the event filter.
88 *
89 * @param action the action (must not be <b>null</b>)
90 * @param filter the event filter; if <b>null</b>, all events will be
91 * accepted
92 */
93 ActionInvoker(FormAction action, EventFilter filter)
94 {
95 if (action == null)
96 {
97 throw new IllegalArgumentException("Action must not be null!");
98 }
99 this.action = action;
100 this.filter = (filter != null) ? filter : DUMMY_FILTER;
101 }
102
103 /**
104 * Callback method that is invoked whenever a method on an associated event
105 * listener interface is called. This implementation will pass the first
106 * method argument (or <b>null</b> if there is none) to the associated
107 * event filter. If it is accepted by the filter, the associated action will
108 * be executed if it is enabled. If the first method argument is of type
109 * <code>{@link net.sf.jguiraffe.gui.builder.event.BuilderEvent}</code>,
110 * it will be passed to the action's <code>execute()</code> method;
111 * otherwise <b>null</b> will be passed.
112 *
113 * @param obj the current object instance
114 * @param meth the method to be invoked
115 * @param args the method arguments
116 * @return the method's return value (<b>null</b> in this case)
117 * @throws Throwable for all occurring exceptions
118 */
119 public Object invoke(Object obj, Method meth, Object[] args)
120 throws Throwable
121 {
122 Object testObj = (args == null || args.length < 1) ? null : args[0];
123 if (filter.accept(testObj))
124 {
125 if (action.isEnabled())
126 {
127 BuilderEvent event = (testObj instanceof BuilderEvent) ? (BuilderEvent) testObj
128 : null;
129 action.execute(event);
130 }
131 }
132
133 return null; // return value does not matter
134 }
135
136 /**
137 * Creates an action invoker proxy for the specified listener interface that
138 * will invoke the given action whenever a method of the listener interface
139 * is called. The returned object can be casted to the specified listener
140 * class and then registered at a component.
141 *
142 * @param listenerClass the class of the listener interface
143 * @param action the action to be invoked (must not be <b>null</b>)
144 * @return the event listener proxy
145 */
146 public static Object create(Class<?> listenerClass, FormAction action)
147 {
148 return create(listenerClass, action, null);
149 }
150
151 /**
152 * Creates an action invoker proxy for the specified listener interface that
153 * will invoke the given action when an event is triggered that is accepted
154 * by the passed in filter. The returned object can be casted to the
155 * specified listener class and then registered at a component.
156 *
157 * @param listenerClass the class of the listener interface
158 * @param action the action to be invoked (must not be <b>null</b>)
159 * @param filter the event filter (can be <b>null</b>, then all events will
160 * be accepted)
161 * @return the event listener proxy
162 */
163 public static Object create(Class<?> listenerClass, FormAction action,
164 EventFilter filter)
165 {
166 return create(new Class<?>[] {
167 listenerClass
168 }, action, filter);
169 }
170
171 /**
172 * Creates an action invoker proxy that implements all the specified
173 * listener interfaces. It will invoke the given action when an event is
174 * triggered that is accepted by the passed in filter. The returned object
175 * can be casted to all the specified listener classes and then registered
176 * at a component.
177 *
178 * @param listenerClasses an array of the classes of the listener interfaces
179 * @param action the action to be invoked (must not be <b>null</b>)
180 * @param filter the event filter (can be <b>null</b>, then all events will
181 * be accepted)
182 * @return the event listener proxy
183 */
184 public static Object create(Class<?>[] listenerClasses, FormAction action,
185 EventFilter filter)
186 {
187 if (listenerClasses == null)
188 {
189 throw new IllegalArgumentException(
190 "Listener classes must not be null!");
191 }
192 return Proxy.newProxyInstance(listenerClasses[0].getClassLoader(),
193 listenerClasses, new ActionInvoker(action, filter));
194 }
195 }