001/* 002 * $Id: BoundAction.java 3972 2011-03-17 20:31:58Z kschaefe $ 003 * 004 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle, 005 * Santa Clara, California 95054, U.S.A. All rights reserved. 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * You should have received a copy of the GNU Lesser General Public 018 * License along with this library; if not, write to the Free Software 019 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 020 */ 021 022package org.jdesktop.swingx.action; 023 024import java.awt.event.ActionEvent; 025import java.awt.event.ActionListener; 026import java.awt.event.ItemEvent; 027import java.awt.event.ItemListener; 028import java.beans.EventHandler; 029import java.beans.Statement; 030import java.io.IOException; 031import java.io.ObjectInputStream; 032import java.io.ObjectOutputStream; 033import java.io.Serializable; 034import java.lang.reflect.InvocationHandler; 035import java.lang.reflect.Proxy; 036import java.util.EventListener; 037import java.util.logging.Level; 038import java.util.logging.Logger; 039 040import javax.swing.Icon; 041import javax.swing.event.EventListenerList; 042 043/** 044 * A class that represents the many type of actions that this framework supports. 045 * <p> 046 * The command invocation of this action may be delegated to another action or item state 047 * listener. If there isn't an explicit binding then the command is forwarded to 048 * the TargetManager. 049 * 050 * @author Mark Davidson 051 * @author Karl Schaefer (serialization support) 052 */ 053public class BoundAction extends AbstractActionExt { 054 private static final Logger LOG = Logger.getLogger(BoundAction.class .getName()); 055 056 // Holds the listeners 057 private transient EventListenerList listeners; 058 059 public BoundAction() { 060 this("BoundAction"); 061 } 062 063 public BoundAction(String name) { 064 super(name); 065 } 066 067 /** 068 * @param name display name of the action 069 * @param command the value of the action command key 070 */ 071 public BoundAction(String name, String command) { 072 super(name, command); 073 } 074 075 public BoundAction(String name, Icon icon) { 076 super(name, icon); 077 } 078 079 /** 080 * @param name display name of the action 081 * @param command the value of the action command key 082 * @param icon icon to display 083 */ 084 public BoundAction(String name, String command, Icon icon) { 085 super(name, command, icon); 086 } 087 088 /** 089 * The callback string will be called to register the action callback. 090 * Note the toggle property must be set if this is a state action before 091 * this method is called. 092 * For example, 093 * <pre> 094 * <exec>com.sun.foo.FubarHandler#handleBar</exec> 095 * </pre> 096 * will register 097 * <pre> 098 * registerCallback(com.sun.foo.FubarHandler(), "handleBar"); 099 * </pre> 100 */ 101 public void setCallback(String callback) { 102 String[] elems = callback.split("#", 2); 103 if (elems.length == 2) { 104 try { 105 Class<?> clz = Class.forName(elems[0]); 106 107 // May throw a security exception in an Applet 108 // context. 109 Object obj = clz.newInstance(); 110 111 registerCallback(obj, elems[1]); 112 } catch (Exception ex) { 113 LOG.fine("ERROR: setCallback(" + callback 114 + ") - " + ex.getMessage()); 115 } 116 } 117 } 118 119 /** 120 * Registers a callback method when the Action corresponding to 121 * the action id is invoked. When a Component that was constructed from the 122 * Action identified by the action id invokes actionPerformed then the method 123 * named will be invoked on the handler Object. 124 * <p> 125 * If the Action represented by the action id is a StateChangeAction, then 126 * the method passed should take an int as an argument. The value of 127 * getStateChange() on the ItemEvent object will be passed as the parameter. 128 * 129 * @param handler the object which will be perform the action 130 * @param method the name of the method on the handler which will be called. 131 */ 132 public void registerCallback(Object handler, String method) { 133 if (isStateAction()) { 134 // Create a handler for toggle type actions. 135 addItemListener(new BooleanInvocationHandler(handler, method)); 136 } else { 137 // Create a new ActionListener using the dynamic proxy API. 138 addActionListener(EventHandler.create(ActionListener.class, 139 handler, method)); 140 } 141 } 142 143 /** 144 * The callback for the toggle/state changed action that invokes a method 145 * with a boolean argument on a target. 146 * 147 * TODO: should reimplement this class as something that can be persistable. 148 */ 149 private class BooleanInvocationHandler implements ItemListener { 150 151 private Statement falseStatement; 152 private Statement trueStatement; 153 154 public BooleanInvocationHandler(Object target, String methodName) { 155 // Create the true and false statements. 156 falseStatement = new Statement(target, methodName, 157 new Object[] { Boolean.FALSE }); 158 159 trueStatement = new Statement(target, methodName, 160 new Object[] { Boolean.TRUE }); 161 } 162 163 @Override 164 public void itemStateChanged(ItemEvent evt) { 165 Statement statement = (evt.getStateChange() == ItemEvent.DESELECTED) ? falseStatement 166 : trueStatement; 167 168 try { 169 statement.execute(); 170 } catch (Exception ex) { 171 LOG.log(Level.FINE, 172 "Couldn't execute boolean method via Statement " 173 + statement, ex); 174 } 175 } 176 } 177 178 // Listener registration... 179 180 private <T extends EventListener> void addListener(Class<T> clz, T listener) { 181 if (listeners == null) { 182 listeners = new EventListenerList(); 183 } 184 listeners.add(clz, listener); 185 } 186 187 private <T extends EventListener> void removeListener(Class<T> clz, T listener) { 188 if (listeners != null) { 189 listeners.remove(clz, listener); 190 } 191 } 192 193 private EventListener[] getListeners(Class<? extends EventListener> clz) { 194 if (listeners == null) { 195 return null; 196 } 197 return listeners.getListeners(clz); 198 } 199 200 /** 201 * Add an action listener which will be invoked when this action is invoked. 202 */ 203 public void addActionListener(ActionListener listener) { 204 addListener(ActionListener.class, listener); 205 } 206 207 public void removeActionListener(ActionListener listener) { 208 removeListener(ActionListener.class, listener); 209 } 210 211 public ActionListener[] getActionListeners() { 212 return (ActionListener[])getListeners(ActionListener.class); 213 } 214 215 /** 216 * Add an item listener which will be invoked for toggle actions. 217 */ 218 public void addItemListener(ItemListener listener) { 219 addListener(ItemListener.class, listener); 220 } 221 222 public void removeItemListener(ItemListener listener) { 223 removeListener(ItemListener.class, listener); 224 } 225 226 public ItemListener[] getItemListeners() { 227 return (ItemListener[])getListeners(ItemListener.class); 228 } 229 230 // Callbacks... 231 232 /** 233 * Callback for command actions. 234 */ 235 @Override 236 public void actionPerformed(ActionEvent evt) { 237 ActionListener[] alist = getActionListeners(); 238 if (alist != null) { 239 for (int i = 0 ; i < alist.length; i++) { 240 alist[i].actionPerformed(evt); 241 } 242 } 243 } 244 245 /** 246 * Callback for toggle actions. 247 */ 248 @Override 249 public void itemStateChanged(ItemEvent evt) { 250 // Update all objects that share this item 251 boolean newValue; 252 boolean oldValue = isSelected(); 253 254 newValue = evt.getStateChange() == ItemEvent.SELECTED; 255 256 if (oldValue != newValue) { 257 setSelected(newValue); 258 259 // Forward the event to the delgate for handling. 260 ItemListener[] ilist = getItemListeners(); 261 if (ilist != null) { 262 for (int i = 0; i < ilist.length; i++) { 263 ilist[i].itemStateChanged(evt); 264 } 265 } 266 } 267 } 268 269 private void writeObject(ObjectOutputStream s) throws IOException { 270 s.defaultWriteObject(); 271 272 if (listeners != null) { 273 Object[] list = listeners.getListenerList(); 274 275 for (int i = 1; i < list.length; i += 2) { 276 if (Proxy.isProxyClass(list[i].getClass())) { 277 InvocationHandler h = Proxy.getInvocationHandler(list[i]); 278 279 if (h instanceof EventHandler && ((EventHandler) h).getTarget() instanceof Serializable) { 280 EventHandler eh = (EventHandler) h; 281 282 s.writeObject("callback"); 283 s.writeObject(eh.getTarget()); 284 s.writeObject(eh.getAction()); 285 } 286 } else if (list[i] instanceof BooleanInvocationHandler) { 287 BooleanInvocationHandler bih = (BooleanInvocationHandler) list[i]; 288 Object target = bih.trueStatement.getTarget(); 289 290 if (target instanceof Serializable) { 291 s.writeObject(BooleanInvocationHandler.class.getName()); 292 s.writeObject(target); 293 s.writeObject(bih.trueStatement.getMethodName()); 294 } 295 } else if (list[i] instanceof Serializable) { 296 s.writeObject(((Class<?>) list[i - 1]).getName()); 297 s.writeObject(list[i]); 298 } 299 } 300 } 301 302 s.writeObject(null); 303 } 304 305 @SuppressWarnings("unchecked") 306 private void readObject(ObjectInputStream s) throws ClassNotFoundException, 307 IOException { 308 s.defaultReadObject(); 309 310 Object typeOrNull; 311 312 while (null != (typeOrNull = s.readObject())) { 313 if ("callback".equals(typeOrNull)) { 314 Object handler = s.readObject(); 315 String method = (String) s.readObject(); 316 317 addActionListener(EventHandler.create(ActionListener.class, handler, method)); 318 } else if (BooleanInvocationHandler.class.getName().equals(typeOrNull)) { 319 Object handler = s.readObject(); 320 String method = (String) s.readObject(); 321 322 addItemListener(new BooleanInvocationHandler(handler, method)); 323 } else { 324 ClassLoader cl = Thread.currentThread().getContextClassLoader(); 325 EventListener l = (EventListener) s.readObject(); 326 addListener((Class<EventListener>)Class.forName((String)typeOrNull, true, cl), l); 327 } 328 } 329 } 330}