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