001 /* JAPI - (Yet another (hopefully) useful) Java API
002 *
003 * Copyright (C) 2004-2006 Christian Hujer
004 *
005 * This program is free software; you can redistribute it and/or
006 * modify it under the terms of the GNU General Public License as
007 * published by the Free Software Foundation; either version 2 of the
008 * License, or (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful, but
011 * WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013 * General Public License for more details.
014 *
015 * You should have received a copy of the GNU General Public License
016 * along with this program; if not, write to the Free Software
017 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
018 * 02111-1307, USA.
019 */
020
021 package net.sf.japi.swing;
022
023 import java.awt.Component;
024 import java.awt.event.ActionEvent;
025 import java.lang.reflect.InvocationTargetException;
026 import java.lang.reflect.Method;
027 import javax.swing.AbstractAction;
028 import javax.swing.Icon;
029 import org.jetbrains.annotations.Nullable;
030 import net.sf.japi.lang.SuperClassIterator;
031 import static net.sf.japi.swing.ActionFactory.ACTION_ID;
032
033 /** Action implementation which invokes the desired method using Reflection.
034 * Usage example:
035 * <pre>
036 * class SomeClass {
037 * SomeClass() {
038 * ReflectionAction action = new ReflectionAction();
039 * action.putValue(REFLECTION_TARGET, this);
040 * action.putValue(REFLECTION_METHOD_NAME, "myAction");
041 * new JMenuItem(action);
042 * }
043 * void myAction() {
044 * // do something
045 * }
046 * }
047 * </pre>
048 * Note that because of Reflection this Action is slightly slower than implementing your own Action instance, but in most cases this really does not matter at all.
049 * Usually you won't use ReflectionAction yourself. Instead you'll let {@link ActionFactory} create an instance for you.
050 * @author <a href="mailto:chris@riedquat.de">Christian Hujer</a>
051 * @todo ReflectionAction should be able to invoke methods with parameters, three variants: Object..., ActionEvent or void
052 */
053 public final class ReflectionAction extends AbstractAction {
054
055 /** Action Factory for reading strings. */
056 private static final ActionFactory ACTION_FACTORY = ActionFactory.getFactory("net.sf.japi.swing");
057
058 /** The key used for storing the target object to invoke the method on.
059 * Value Type: {@link Object}.
060 */
061 public static final String REFLECTION_TARGET = "ReflectionTarget";
062
063 /** The key used for storing the method name to use when searching for a method using reflection.
064 * Value Type: {@link String} (checked).
065 */
066 public static final String REFLECTION_METHOD_NAME = "ReflectionMethodName";
067
068 /** The key used for storing the method object to use when invoking the method.
069 * Value Type: {@link Method} (checked).
070 */
071 public static final String REFLECTION_METHOD = "ReflectionMethod";
072
073 /** The key used for storing the action factory that provides access to error handlers strings. */
074 public static final String REFLECTION_MESSAGE_PROVIDER = "ActionFactory";
075
076 /** Serial Version. */
077 @SuppressWarnings({"AnalyzingVariableNaming"})
078 private static final long serialVersionUID = 1L;
079
080 /** Create an uninitialized ReflectionAction. */
081 public ReflectionAction() {
082 }
083
084 /** Create a ReflectionAction with method and target.
085 * @param methodName Name of method to invoke
086 * @param target Target object to invoke method at
087 */
088 public ReflectionAction(final String methodName, final Object target) {
089 putValue(REFLECTION_METHOD_NAME, methodName);
090 putValue(REFLECTION_TARGET, target);
091 }
092
093 /** {@inheritDoc}
094 * This implementation checks the type of <var>newValue</var> if the <var>key</var> is {@link #REFLECTION_METHOD_NAME} or {@link
095 * #REFLECTION_METHOD}, so you'll know of errors quite soon.
096 * @throws IllegalArgumentException if <var>newValue</var> is of the wrong type
097 */
098 @Override public void putValue(final String key, final Object newValue) throws IllegalArgumentException {
099 if (REFLECTION_METHOD_NAME.equals(key)) {
100 if (!(newValue == null || newValue instanceof String)) {
101 throw new IllegalArgumentException("Value for key REFLECTION_METHOD_NAME must be of type " + String.class.getName() + " but was " + newValue.getClass().getName());
102 }
103 putValue(REFLECTION_METHOD, null);
104 }
105 if (REFLECTION_METHOD.equals(key)) {
106 if (!(newValue == null || newValue instanceof Method)) {
107 if (newValue instanceof String) {
108 throw new IllegalArgumentException("Value for key REFLECTION_METHOD must be of type " + Method.class.getName() + " but was " + String.class.getName() + " so you might want to use the key REFLECTION_METHOD_NAME instead.");
109 } else {
110 throw new IllegalArgumentException("Value for key REFLECTION_METHOD must be of type " + Method.class.getName() + " but was " + newValue.getClass().getName());
111 }
112 }
113 }
114 if (REFLECTION_TARGET.equals(key)) {
115 if (newValue == null) {
116 putValue(REFLECTION_METHOD, null);
117 }
118 }
119 super.putValue(key, newValue);
120 }
121
122 /** Defines an <code>Action</code> object with the specified description string and a the specified icon.
123 * @param name description string
124 * @param icon icon
125 * @param methodName Name of method to invoke
126 * @param target Target object to invoke method at
127 */
128 public ReflectionAction(final String name, final Icon icon, final String methodName, final Object target) {
129 super(name, icon);
130 putValue(REFLECTION_METHOD_NAME, methodName);
131 putValue(REFLECTION_TARGET, target);
132 }
133
134 /** {@inheritDoc}
135 * The implementation of this method first looks whether the Action is enabled.
136 * If it isn't, the method simply returns.
137 * Otherwise, instance and method are looked up.
138 * If both are null, the method again returns.
139 * If the method is null, it is reflected upon the instance usign the method name. If the method name is null, the method returns.
140 * Finally the method is invoked upon the instance, which may be null for static methods.
141 * @throws RuntimeException with cause in case the invocation of the method threw an exception and there was no handler for that exception.
142 */
143 public void actionPerformed(final ActionEvent e) {
144 if (!isEnabled()) { return; }
145 final Object instance = getValue(REFLECTION_TARGET);
146 try {
147 getMethod(instance).invoke(instance);
148 } catch (final IllegalAccessException ex) {
149 assert false : ACTION_FACTORY.format("ReflectionAction.nonPublicMethod", ex);
150 } catch (final InvocationTargetException ex) {
151 final ActionFactory actionFactory = (ActionFactory) getValue(REFLECTION_MESSAGE_PROVIDER);
152 final Throwable cause = ex.getCause();
153 if (actionFactory != null) {
154 for (final Class<?> c : new SuperClassIterator(cause.getClass())) {
155 final String dialogKey = getValue(ACTION_ID) + ".exception." + c.getName();
156 final String title = actionFactory.getString(dialogKey + ".title");
157 if (title != null) {
158 final Object source = e.getSource();
159 final Component parent = source instanceof Component ? (Component) source : null; // TODO: find better alternative to null
160 actionFactory.showMessageDialog(parent, dialogKey, cause.getLocalizedMessage());
161 return;
162 }
163 }
164 }
165 //noinspection ThrowInsideCatchBlockWhichIgnoresCaughtException
166 throw new RuntimeException(ex.getCause());
167 } catch (final NullPointerException ignore) {
168 /* ignore */
169 }
170 }
171
172 /** Get the method associated with this action.
173 * @param instance Instance to get method for
174 * @return associated method or <code>null</code> if no method could be found
175 */
176 @Nullable private Method getMethod(final Object instance) {
177 if (instance == null) {
178 return null;
179 }
180 Method method = (Method) getValue(REFLECTION_METHOD);
181 if (method == null) {
182 final String methodName = (String) getValue(REFLECTION_METHOD_NAME);
183 if (methodName == null) {
184 return null;
185 }
186 try {
187 final Class<? extends Object> clazz = instance.getClass();
188 method = clazz.getMethod(methodName);
189 putValue(REFLECTION_METHOD, method);
190 } catch (final NoSuchMethodException ex) {
191 assert false : "Action Method not found: " + ex;
192 return null;
193 }
194 }
195 return method;
196 }
197
198 /** {@inheritDoc} */
199 @Override protected Object clone() throws CloneNotSupportedException {
200 return super.clone();
201 }
202
203 } // class ReflectionAction