OpenCores
URL https://opencores.org/ocsvn/scarts/scarts/trunk

Subversion Repositories scarts

[/] [scarts/] [trunk/] [toolchain/] [scarts-gcc/] [gcc-4.1.1/] [libjava/] [classpath/] [java/] [beans/] [EventHandler.java] - Blame information for rev 14

Details | Compare with Previous | View Log

Line No. Rev Author Line
1 14 jlechner
/* java.beans.EventHandler
2
   Copyright (C) 2004, 2005 Free Software Foundation, Inc.
3
 
4
This file is part of GNU Classpath.
5
 
6
GNU Classpath is free software; you can redistribute it and/or modify
7
it under the terms of the GNU General Public License as published by
8
the Free Software Foundation; either version 2, or (at your option)
9
any later version.
10
 
11
GNU Classpath is distributed in the hope that it will be useful, but
12
WITHOUT ANY WARRANTY; without even the implied warranty of
13
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
General Public License for more details.
15
 
16
You should have received a copy of the GNU General Public License
17
along with GNU Classpath; see the file COPYING.  If not, write to the
18
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
02110-1301 USA.
20
 
21
Linking this library statically or dynamically with other modules is
22
making a combined work based on this library.  Thus, the terms and
23
conditions of the GNU General Public License cover the whole
24
combination.
25
 
26
As a special exception, the copyright holders of this library give you
27
permission to link this library with independent modules to produce an
28
executable, regardless of the license terms of these independent
29
modules, and to copy and distribute the resulting executable under
30
terms of your choice, provided that you also meet, for each linked
31
independent module, the terms and conditions of the license of that
32
module.  An independent module is a module which is not derived from
33
or based on this library.  If you modify this library, you may extend
34
this exception to your version of the library, but you are not
35
obligated to do so.  If you do not wish to do so, delete this
36
exception statement from your version. */
37
 
38
 
39
package java.beans;
40
 
41
import java.lang.reflect.InvocationHandler;
42
import java.lang.reflect.InvocationTargetException;
43
import java.lang.reflect.Method;
44
import java.lang.reflect.Proxy;
45
 
46
/**
47
 * <p>EventHandler forms a bridge between dynamically created listeners and
48
 * arbitrary properties and methods.</p>
49
 *
50
 * <p>You can use this class to easily create listener implementations for
51
 * some basic interactions between an event source and its target. Using
52
 * the three static methods named <code>create</code> you can create
53
 * these listener implementations.</p>
54
 *
55
 * <p>See the documentation of each method for usage examples.</p>
56
 *
57
 * @author Jerry Quinn (jlquinn@optonline.net)
58
 * @author Robert Schuster (thebohemian@gmx.net)
59
 * @since 1.4
60
 */
61
public class EventHandler implements InvocationHandler
62
{
63
  // The name of the method that will be implemented.  If null, any method.
64
  private String listenerMethod;
65
 
66
  // The object to call action on.
67
  private Object target;
68
 
69
  // The name of the method or property setter in target.
70
  private String action;
71
 
72
  // The property to extract from an event passed to listenerMethod.
73
  private String property;
74
 
75
  // The target objects Class.
76
  private Class targetClass;
77
 
78
  // String class doesn't already have a capitalize routine.
79
  private String capitalize(String s)
80
  {
81
    return s.substring(0, 1).toUpperCase() + s.substring(1);
82
  }
83
 
84
  /**
85
   * Creates a new <code>EventHandler</code> instance.
86
   *
87
   * <p>Typical creation is done with the create method, not by knewing an
88
   * EventHandler.</p>
89
   *
90
   * <p>This constructs an EventHandler that will connect the method
91
   * listenerMethodName to target.action, extracting eventPropertyName from
92
   * the first argument of listenerMethodName. and sending it to action.</p>
93
   *
94
   * <p>Throws a <code>NullPointerException</code> if the <code>target</code>
95
   * argument is <code>null</code>.
96
   *
97
   * @param target Object that will perform the action.
98
   * @param action A property or method of the target.
99
   * @param eventPropertyName A readable property of the inbound event.
100
   * @param listenerMethodName The listener method name triggering the action.
101
   */
102
  public EventHandler(Object target, String action, String eventPropertyName,
103
                      String listenerMethodName)
104
  {
105
    this.target = target;
106
 
107
    // Retrieving the class is done for two reasons:
108
    // 1) The class object is needed very frequently in the invoke() method.
109
    // 2) The constructor should throw a NullPointerException if target is null.
110
    targetClass = target.getClass();
111
 
112
    this.action = action;       // Turn this into a method or do we wait till
113
                // runtime
114
    property = eventPropertyName;
115
    listenerMethod = listenerMethodName;
116
  }
117
 
118
  /**
119
   * Returns the event property name.
120
   */
121
  public String getEventPropertyName()
122
  {
123
    return property;
124
  }
125
 
126
  /**
127
   * Returns the listener's method name.
128
   */
129
  public String getListenerMethodName()
130
  {
131
    return listenerMethod;
132
  }
133
 
134
  /**
135
   * Returns the target object.
136
   */
137
  public Object getTarget()
138
  {
139
    return target;
140
  }
141
 
142
  /**
143
   * Returns the action method name.
144
   */
145
  public String getAction()
146
  {
147
    return action;
148
  }
149
 
150
  // Fetch a qualified property like a.b.c from object o.  The properties can
151
  // be boolean isProp or object getProp properties.
152
  //
153
  // Returns a length 2 array with the first entry containing the value
154
  // extracted from the property, and the second entry contains the class of
155
  // the method return type.
156
  //
157
  // We play this game because if the method returns a native type, the return
158
  // value will be a wrapper.  If we then take the type of the wrapper and use
159
  // it to locate the action method that takes the native type, it won't match.
160
  private Object[] getProperty(Object o, String prop)
161
  {
162
    // Isolate the first property name from a.b.c.
163
    int pos;
164
    String rest = null;
165
    if ((pos = prop.indexOf('.')) != -1)
166
      {
167
        rest = prop.substring(pos + 1);
168
        prop = prop.substring(0, pos);
169
      }
170
 
171
    // Find a method named getProp.  It could be isProp instead.
172
    Method getter;
173
    try
174
      {
175
        // Look for boolean property getter isProperty
176
        getter = o.getClass().getMethod("is" + capitalize(prop),
177
                                                 null);
178
      }
179
    catch (NoSuchMethodException nsme1)
180
      {
181
        try {
182
          // Look for regular property getter getProperty
183
          getter = o.getClass().getMethod("get" + capitalize(prop),
184
                                                 null);
185
        } catch(NoSuchMethodException nsme2) {
186
            try {
187
            // Finally look for a method of the name prop
188
            getter = o.getClass().getMethod(prop, null);
189
            } catch(NoSuchMethodException nsme3) {
190
                // Ok, give up with an intelligent hint for the user.
191
                throw new RuntimeException("Method not called: Could not find a property or method '" + prop
192
                        + "' in " + o.getClass() + " while following the property argument '" + property + "'.");
193
            }
194
        }
195
      }
196
    try {
197
      Object val = getter.invoke(o, null);
198
 
199
      if (rest != null)
200
        return getProperty(val, rest);
201
 
202
      return new Object[] {val, getter.getReturnType()};
203
    } catch(InvocationTargetException ite) {
204
        throw new RuntimeException("Method not called: Property or method '" + prop + "' has thrown an exception.", ite);
205
    } catch(IllegalAccessException iae) {
206
        // This cannot happen because we looked up method with Class.getMethod()
207
        // which returns public methods only.
208
        throw (InternalError) new InternalError("Non-public method was invoked.").initCause(iae);
209
    }
210
  }
211
 
212
  /**
213
   * Invokes the <code>EventHandler</code>.
214
   *
215
   * <p>This method is normally called by the listener's proxy implementation.</p>
216
   *
217
   * @param proxy The listener interface that is implemented using
218
   * the proxy mechanism.
219
   * @param method The method that was called on the proxy instance.
220
   * @param arguments The arguments which where given to the method.
221
   * @throws Throwable <code>NoSuchMethodException</code> is thrown when the EventHandler's
222
   * action method or property cannot be found.
223
   */
224
  public Object invoke(Object proxy, Method method, Object[] arguments)
225
  {
226
      try {
227
      // The method instance of the target object. We have to find out which
228
      // one we have to invoke.
229
      Method actionMethod = null;
230
 
231
    // Listener methods that weren't specified are ignored.  If listenerMethod
232
    // is null, then all listener methods are processed.
233
    if (listenerMethod != null && !method.getName().equals(listenerMethod))
234
      return null;
235
 
236
    // If a property is defined we definitely need a valid object at
237
    // arguments[0] that can be used to retrieve a value to which the
238
    // property of the target gets set.
239
    if(property != null) {
240
      // Extracts the argument. We will let it fail with a NullPointerException
241
      // the caller used a listener method that has no arguments.
242
      Object event = arguments[0];
243
 
244
      // Obtains the property XXX propertyType keeps showing up null - why?
245
      // because the object inside getProperty changes, but the ref variable
246
      // can't change this way, dolt!  need a better way to get both values out
247
      // - need method and object to do the invoke and get return type
248
      Object v[] = getProperty(event, property);
249
      Object[] args = new Object[] { v[0] };
250
 
251
      // Changes the class array that controls which method signature we are going
252
      // to look up in the target object.
253
      Class[] argTypes = new Class[] { initClass((Class) v[1]) };
254
 
255
      // Tries to  find a setter method to which we can apply the
256
      while(argTypes[0] != null) {
257
      try
258
      {
259
        // Look for a property setter for action.
260
        actionMethod = targetClass.getMethod("set" + capitalize(action), argTypes);
261
 
262
        return actionMethod.invoke(target, args);
263
      }
264
    catch (NoSuchMethodException e)
265
      {
266
        // If action as property didn't work, try as method later.
267
      }
268
 
269
      argTypes[0] = nextClass(argTypes[0]);
270
      }
271
 
272
      // We could not find a suitable setter method. Now we try again interpreting
273
      // action as the method name itself.
274
      // Since we probably have changed the block local argTypes array 
275
      // we need to rebuild it.
276
      argTypes = new Class[] { initClass((Class) v[1]) };
277
 
278
      // Tries to  find a setter method to which we can apply the
279
      while(argTypes[0] != null) {
280
        try
281
        {
282
          actionMethod = targetClass.getMethod(action, argTypes);
283
 
284
          return actionMethod.invoke(target, args);
285
        }
286
        catch (NoSuchMethodException e)
287
        {
288
        }
289
 
290
        argTypes[0] = nextClass(argTypes[0]);
291
      }
292
 
293
        throw new RuntimeException("Method not called: Could not find a public method named '"
294
                + action + "' in target " + targetClass + " which takes a '"
295
                + v[1] + "' argument or a property of this type.");
296
      }
297
 
298
    // If property was null we will search for a no-argument method here.
299
    // Note: The ordering of method lookups is important because we want to prefer no-argument
300
    // calls like the JDK does. This means if we have actionMethod() and actionMethod(Event) we will
301
    // call the first *EVEN* if we have a valid argument for the second method. This is behavior compliant
302
    // to the JDK.
303
    // If actionMethod() is not available but there is a actionMethod(Event) we take this. That makes us
304
    // more specification compliant than the JDK itself because this one will fail in such a case.
305
    try
306
      {
307
      actionMethod = targetClass.getMethod(action, null);
308
      }
309
    catch(NoSuchMethodException nsme)
310
      {
311
        // Note: If we want to be really strict the specification says that a no-argument method should
312
        // accept an EventObject (or subclass I guess). However since the official implementation is broken
313
        // anyways, it's more flexible without the EventObject restriction and we are compatible on everything
314
        // else this can stay this way.
315
        if(arguments != null && arguments.length >= 1/* && arguments[0] instanceof EventObject*/) {
316
            Class[] targetArgTypes = new Class[] { initClass(arguments[0].getClass()) };
317
 
318
            while(targetArgTypes[0] != null) {
319
                try
320
                {
321
                  // If no property exists we expect the first element of the arguments to be
322
                  // an EventObject which is then applied to the target method.
323
 
324
                  actionMethod = targetClass.getMethod(action, targetArgTypes);
325
 
326
                  return actionMethod.invoke(target, new Object[] { arguments[0] });
327
                }
328
                catch(NoSuchMethodException nsme2)
329
                {
330
 
331
                }
332
 
333
                targetArgTypes[0] = nextClass(targetArgTypes[0]);
334
            }
335
 
336
        }
337
      }
338
 
339
    // If we do not have a Method instance at this point this means that all our tries
340
    // failed. The JDK throws an ArrayIndexOutOfBoundsException in this case.
341
    if(actionMethod == null)
342
      throw new ArrayIndexOutOfBoundsException(0);
343
 
344
    // Invoke target.action(property)
345
    return actionMethod.invoke(target, null);
346
      } catch(InvocationTargetException ite) {
347
         throw new RuntimeException(ite.getCause());
348
      } catch(IllegalAccessException iae) {
349
          // Cannot happen because we always use getMethod() which returns public
350
          // methods only. Otherwise there is something seriously broken in
351
          // GNU Classpath.
352
          throw (InternalError) new InternalError("Non-public method was invoked.").initCause(iae);
353
      }
354
  }
355
 
356
  /**
357
   * <p>Returns the primitive type for every wrapper class or the
358
   * class itself if it is no wrapper class.</p>
359
   *
360
   * <p>This is needed because to be able to find both kinds of methods:
361
   * One that takes a wrapper class as the first argument and one that
362
   * accepts a primitive instead.</p>
363
   */
364
  private Class initClass(Class klass) {
365
   if(klass == Boolean.class) {
366
    return Boolean.TYPE;
367
   } else if(klass == Byte.class) {
368
    return Byte.TYPE;
369
   } else if(klass == Short.class) {
370
    return Short.TYPE;
371
   } else if(klass == Integer.class) {
372
    return Integer.TYPE;
373
   } else if(klass == Long.class) {
374
    return Long.TYPE;
375
   } else if(klass == Float.class) {
376
    return Float.TYPE;
377
   } else if(klass == Double.class) {
378
    return Double.TYPE;
379
   } else {
380
    return klass;
381
   }
382
  }
383
 
384
  /**
385
   *
386
   *
387
   * @param klass
388
   * @return
389
   */
390
  private Class nextClass(Class klass) {
391
    if(klass == Boolean.TYPE) {
392
    return Boolean.class;
393
   } else if(klass == Byte.TYPE) {
394
    return Byte.class;
395
   } else if(klass == Short.TYPE) {
396
    return Short.class;
397
   } else if(klass == Integer.TYPE) {
398
    return Integer.class;
399
   } else if(klass == Long.TYPE) {
400
    return Long.class;
401
   } else if(klass == Float.TYPE) {
402
    return Float.class;
403
   } else if(klass == Double.TYPE) {
404
    return Double.class;
405
   } else {
406
    return klass.getSuperclass();
407
   }
408
   }
409
 
410
  /**
411
   * <p>Constructs an implementation of <code>listenerInterface</code>
412
   * to dispatch events.</p>
413
   *
414
   * <p>You can use such an implementation to simply call a public
415
   * no-argument method of an arbitrary target object or to forward
416
   * the first argument of the listener method to the target method.</p>
417
   *
418
   * <p>Call this method like:</p>
419
   * <code>
420
   * button.addActionListener((ActionListener)
421
   *    EventHandler.create(ActionListener.class, target, "dispose"));
422
   * </code>
423
   *
424
   * <p>to achieve the following behavior:</p>
425
   * <code>
426
   * button.addActionListener(new ActionListener() {
427
   *    public void actionPerformed(ActionEvent ae) {
428
   *        target.dispose();
429
   *    }
430
   * });
431
   * </code>
432
   *
433
   * <p>That means if you need a listener implementation that simply calls a
434
   * a no-argument method on a given instance for <strong>each</strong>
435
   * method of the listener interface.</p>
436
   *
437
   * <p>Note: The <code>action</code> is interpreted as a method name. If your target object
438
   * has no no-argument method of the given name the EventHandler tries to find
439
   * a method with the same name but which can accept the first argument of the
440
   * listener method. Usually this will be an event object but any other object
441
   * will be forwarded, too. Keep in mind that using a property name instead of a
442
   * real method here is wrong and will throw an <code>ArrayIndexOutOfBoundsException</code>
443
   * whenever one of the listener methods is called.<p/>
444
   *
445
   * <p>The <code>EventHandler</code> will automatically convert primitives
446
   * to their wrapper class and vice versa. Furthermore it will call
447
   * a target method if it accepts a superclass of the type of the
448
   * first argument of the listener method.</p>
449
   *
450
   * <p>In case that the method of the target object throws an exception
451
   * it will be wrapped in a <code>RuntimeException</code> and thrown out
452
   * of the listener method.</p>
453
   *
454
   * <p>In case that the method of the target object cannot be found an
455
   * <code>ArrayIndexOutOfBoundsException</code> will be thrown when the
456
   * listener method is invoked.</p>
457
   *
458
   * <p>A call to this method is equivalent to:
459
   * <code>create(listenerInterface, target, action, null, null)</code></p>
460
   *
461
   * @param listenerInterface Listener interface to implement.
462
   * @param target Object to invoke action on.
463
   * @param action Target property or method to invoke.
464
   * @return A constructed proxy object.
465
   */
466
  public static Object create(Class listenerInterface, Object target, String action)
467
  {
468
    return create(listenerInterface, target, action, null, null);
469
  }
470
 
471
  /**
472
   * <p>Constructs an implementation of <code>listenerInterface</code>
473
   * to dispatch events.</p>
474
   *
475
   * <p>Use this method if you want to create an implementation that retrieves
476
   * a property value from the <b>first</b> argument of the listener method
477
   * and applies it to the target's property or method. This first argument
478
   * of the listener is usually an event object but any other object is
479
   * valid, too.</p>
480
   *
481
   * <p>You can set the value of <code>eventPropertyName</code> to "prop"
482
   * to denote the retrieval of a property named "prop" from the event
483
   * object. In case that no such property exists the <code>EventHandler</code>
484
   * will try to find a method with that name.</p>
485
   *
486
   * <p>If you set <code>eventPropertyName</code> to a value like this "a.b.c"
487
   * <code>EventHandler</code> will recursively evaluate the properties "a", "b"
488
   * and "c". Again if no property can be found the <code>EventHandler</code>
489
   * tries a method name instead. This allows mixing the names, too: "a.toString"
490
   * will retrieve the property "a" from the event object and will then call
491
   * the method "toString" on it.</p>
492
   *
493
   * <p>An exception thrown in any of these methods will provoke a
494
   * <code>RuntimeException</code> to be thrown which contains an
495
   * <code>InvocationTargetException</code> containing the triggering exception.</p>
496
   *
497
   * <p>If you set <code>eventPropertyName</code> to a non-null value the
498
   * <code>action</code> parameter will be interpreted as a property name
499
   * or a method name of the target object.</p>
500
   *
501
   * <p>Any object retrieved from the event object and applied to the
502
   * target will converted from primitives to their wrapper class or
503
   * vice versa or applied to a method that accepts a superclass
504
   * of the object.</p>
505
   *
506
   * <p>Examples:</p>
507
   * <p>The following code:</p><code>
508
   * button.addActionListener(
509
   *    new ActionListener() {
510
   *        public void actionPerformed(ActionEvent ae) {
511
   *            Object o = ae.getSource().getClass().getName();
512
   *            textField.setText((String) o);
513
   *        }
514
   *    });
515
   * </code>
516
   *
517
   * <p>Can be expressed using the <code>EventHandler</code> like this:</p>
518
   * <p>
519
   * <code>button.addActionListener((ActionListener)
520
   *    EventHandler.create(ActionListener.class, textField, "text", "source.class.name");
521
   * <code>
522
   * </p>
523
   *
524
   * <p>As said above you can specify the target as a method, too:</p>
525
   * <p>
526
   * <code>button.addActionListener((ActionListener)
527
   *    EventHandler.create(ActionListener.class, textField, "setText", "source.class.name");
528
   * <code>
529
   * </p>
530
   *
531
   * <p>Furthermore you can use method names in the property:</p>
532
   * <p>
533
   * <code>button.addActionListener((ActionListener)
534
   *    EventHandler.create(ActionListener.class, textField, "setText", "getSource.getClass.getName");
535
   * <code>
536
   * </p>
537
   *
538
   * <p>Finally you can mix names:</p>
539
   * <p>
540
   * <code>button.addActionListener((ActionListener)
541
   *    EventHandler.create(ActionListener.class, textField, "setText", "source.getClass.name");
542
   * <code>
543
   * </p>
544
   *
545
   * <p>A call to this method is equivalent to:
546
   * <code>create(listenerInterface, target, action, null, null)</code>
547
   * </p>
548
   *
549
   * @param listenerInterface Listener interface to implement.
550
   * @param target Object to invoke action on.
551
   * @param action Target property or method to invoke.
552
   * @param eventPropertyName Name of property to extract from event.
553
   * @return A constructed proxy object.
554
   */
555
  public static Object create(Class listenerInterface, Object target,
556
                              String action, String eventPropertyName)
557
  {
558
    return create(listenerInterface, target, action, eventPropertyName, null);
559
  }
560
 
561
  /**
562
   * <p>Constructs an implementation of <code>listenerInterface</code>
563
   * to dispatch events.</p>
564
   *
565
   * <p>Besides the functionality described for {@link create(Class, Object, String)}
566
   * and {@link create(Class, Object, String, String)} this method allows you
567
   * to filter the listener method that should have an effect. Look at these
568
   * method's documentation for more information about the <code>EventHandler</code>'s
569
   * usage.</p>
570
   *
571
   * <p>If you want to call <code>dispose</code> on a <code>JFrame</code> instance
572
   * when the <code>WindowListener.windowClosing()</code> method was invoked use
573
   * the following code:</p>
574
   * <p>
575
   * <code>
576
   * EventHandler.create(WindowListener.class, jframeInstance, "dispose", null, "windowClosing");
577
   * </code>
578
   * </p>
579
   *
580
   * <p>A <code>NullPointerException</code> is thrown if the <code>listenerInterface</code>
581
   * or <code>target</code> argument are <code>null</code>.
582
   *
583
   * @param listenerInterface Listener interface to implement.
584
   * @param target Object to invoke action on.
585
   * @param action Target method name to invoke.
586
   * @param eventPropertyName Name of property to extract from event.
587
   * @param listenerMethodName Listener method to implement.
588
   * @return A constructed proxy object.
589
   */
590
  public static Object create(Class listenerInterface, Object target,
591
                              String action, String eventPropertyName,
592
                              String listenerMethodName)
593
  {
594
    // Create EventHandler instance
595
    EventHandler eh = new EventHandler(target, action, eventPropertyName,
596
                                       listenerMethodName);
597
 
598
    // Create proxy object passing in the event handler
599
    Object proxy = Proxy.newProxyInstance(listenerInterface.getClassLoader(),
600
                                          new Class[] {listenerInterface},
601
                                          eh);
602
 
603
    return proxy;
604
  }
605
 
606
}

powered by: WebSVN 2.1.0

© copyright 1999-2024 OpenCores.org, equivalent to Oliscience, all rights reserved. OpenCores®, registered trademark.