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