~ubuntu-branches/ubuntu/trusty/commons-beanutils/trusty

« back to all changes in this revision

Viewing changes to src/java/org/apache/commons/beanutils/MappedPropertyDescriptor.java

  • Committer: Bazaar Package Importer
  • Author(s): Takashi Okamoto
  • Date: 2002-02-16 23:17:39 UTC
  • Revision ID: james.westby@ubuntu.com-20020216231739-eha35b05fam940p6
Tags: upstream-1.2
Import upstream version 1.2

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * $Header: /home/cvs/jakarta-commons/beanutils/src/java/org/apache/commons/beanutils/MappedPropertyDescriptor.java,v 1.3 2001/10/14 00:54:24 craigmcc Exp $
 
3
 * $Revision: 1.3 $
 
4
 * $Date: 2001/10/14 00:54:24 $
 
5
 *
 
6
 * ====================================================================
 
7
 *
 
8
 * The Apache Software License, Version 1.1
 
9
 *
 
10
 * Copyright (c) 1999-2001 The Apache Software Foundation.  All rights
 
11
 * reserved.
 
12
 *
 
13
 * Redistribution and use in source and binary forms, with or without
 
14
 * modification, are permitted provided that the following conditions
 
15
 * are met:
 
16
 *
 
17
 * 1. Redistributions of source code must retain the above copyright
 
18
 *    notice, this list of conditions and the following disclaimer.
 
19
 *
 
20
 * 2. Redistributions in binary form must reproduce the above copyright
 
21
 *    notice, this list of conditions and the following disclaimer in
 
22
 *    the documentation and/or other materials provided with the
 
23
 *    distribution.
 
24
 *
 
25
 * 3. The end-user documentation included with the redistribution, if
 
26
 *    any, must include the following acknowlegement:
 
27
 *       "This product includes software developed by the
 
28
 *        Apache Software Foundation (http://www.apache.org/)."
 
29
 *    Alternately, this acknowlegement may appear in the software itself,
 
30
 *    if and wherever such third-party acknowlegements normally appear.
 
31
 *
 
32
 * 4. The names "The Jakarta Project", "Commons", and "Apache Software
 
33
 *    Foundation" must not be used to endorse or promote products derived
 
34
 *    from this software without prior written permission. For written
 
35
 *    permission, please contact apache@apache.org.
 
36
 *
 
37
 * 5. Products derived from this software may not be called "Apache"
 
38
 *    nor may "Apache" appear in their names without prior written
 
39
 *    permission of the Apache Group.
 
40
 *
 
41
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 
42
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 
43
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 
44
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 
45
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 
46
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 
47
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 
48
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 
49
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 
50
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 
51
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 
52
 * SUCH DAMAGE.
 
53
 * ====================================================================
 
54
 *
 
55
 * This software consists of voluntary contributions made by many
 
56
 * individuals on behalf of the Apache Software Foundation.  For more
 
57
 * information on the Apache Software Foundation, please see
 
58
 * <http://www.apache.org/>.
 
59
 *
 
60
 */
 
61
 
 
62
 
 
63
package org.apache.commons.beanutils;
 
64
 
 
65
import java.beans.IntrospectionException;
 
66
import java.beans.FeatureDescriptor;
 
67
import java.beans.PropertyDescriptor;
 
68
import java.lang.reflect.*;
 
69
import java.security.*;
 
70
 
 
71
/**
 
72
 * A MappedPropertyDescriptor describes one mapped property.
 
73
 * Mapped properties are multivalued properties like indexed properties
 
74
 * but that are accessed with a String key instead of an index.
 
75
 * Such property values are typically stored in a Map collection.
 
76
 * For this class to work properly, a mapped value must have
 
77
 * getter and setter methods of the form
 
78
 * <p><code>get<strong>Property</strong>(String key)<code> and
 
79
 * <p><code>set&ltProperty&gt(String key, Object value)<code>,
 
80
 * <p>where <code><strong>Property</strong></code> must be replaced
 
81
 * by the name of the property.
 
82
 * @see java.beans.PropertyDescriptor
 
83
 *
 
84
 * @author Rey Fran�ois
 
85
 * @author Gregor Ra�man
 
86
 * @version $Revision: 1.3 $ $Date: 2001/10/14 00:54:24 $
 
87
 */
 
88
 
 
89
public class MappedPropertyDescriptor extends PropertyDescriptor {
 
90
 
 
91
 
 
92
    // ----------------------------------------------------- Instance Variables
 
93
 
 
94
 
 
95
 
 
96
    /**
 
97
     * The underlying data type of the property we are describing.
 
98
     */
 
99
    private Class mappedPropertyType;
 
100
 
 
101
 
 
102
 
 
103
    /**
 
104
     * The reader method for this property (if any).
 
105
     */
 
106
    private Method mappedReadMethod;
 
107
 
 
108
 
 
109
    /**
 
110
     * The writer method for this property (if any).
 
111
     */
 
112
    private Method mappedWriteMethod;
 
113
 
 
114
 
 
115
 
 
116
    /**
 
117
     * The parameter types array for the reader method signature.
 
118
     */
 
119
    private static final Class[] stringClassArray = new Class[] {String.class};
 
120
 
 
121
 
 
122
    // ----------------------------------------------------------- Constructors
 
123
 
 
124
 
 
125
    /**
 
126
     * Constructs a MappedPropertyDescriptor for a property that follows
 
127
     * the standard Java convention by having getFoo and setFoo
 
128
     * accessor methods, with the addition of a String parameter (the key).
 
129
     * Thus if the argument name is "fred", it will
 
130
     * assume that the writer method is "setFred" and the reader method
 
131
     * is "getFred".  Note that the property name should start with a lower
 
132
     * case character, which will be capitalized in the method names.
 
133
     *
 
134
     * @param propertyName The programmatic name of the property.
 
135
     * @param beanClass The Class object for the target bean.  For
 
136
     *          example sun.beans.OurButton.class.
 
137
     *
 
138
     * @exception IntrospectionException if an exception occurs during
 
139
     *              introspection.
 
140
     */
 
141
    public MappedPropertyDescriptor(String propertyName, Class beanClass)
 
142
            throws IntrospectionException {
 
143
 
 
144
        super(propertyName, null, null);
 
145
        if (propertyName == null || propertyName.length() == 0) {
 
146
            throw new IntrospectionException("bad property name");
 
147
        }
 
148
        setName(propertyName);
 
149
        String base = capitalize(propertyName);
 
150
 
 
151
        // Look for mapped get and set methods
 
152
        try {
 
153
            mappedReadMethod = findMethod(beanClass, "get" + base, 1,
 
154
                                          stringClassArray);
 
155
            Class params[] =
 
156
                { String.class, mappedReadMethod.getReturnType() };
 
157
            mappedWriteMethod = findMethod(beanClass, "set" + base, 2,
 
158
                                           params);
 
159
        } catch (IntrospectionException e) {
 
160
            ;
 
161
        }
 
162
        if ((mappedReadMethod == null) && (mappedWriteMethod == null))
 
163
            throw new IntrospectionException("Property '" + propertyName +
 
164
                                             "' not found on " +
 
165
                                             beanClass.getName());
 
166
        findMappedPropertyType();
 
167
 
 
168
    }
 
169
 
 
170
 
 
171
    /**
 
172
     * This constructor takes the name of a mapped property, and method
 
173
     * names for reading and writing the property.
 
174
     *
 
175
     * @param propertyName The programmatic name of the property.
 
176
     * @param beanClass The Class object for the target bean.  For
 
177
     *          example sun.beans.OurButton.class.
 
178
     * @param mappedGetterName The name of the method used for
 
179
     *          reading one of the property values.  May be null if the
 
180
     *          property is write-only.
 
181
     * @param mappedSetterName The name of the method used for writing
 
182
     *          one of the property values.  May be null if the property is
 
183
     *          read-only.
 
184
     *
 
185
     * @exception IntrospectionException if an exception occurs during
 
186
     *              introspection.
 
187
     */
 
188
    public MappedPropertyDescriptor(String propertyName, Class beanClass,
 
189
                String mappedGetterName, String mappedSetterName)
 
190
                throws IntrospectionException {
 
191
 
 
192
        super(propertyName, null, null);
 
193
        if (propertyName == null || propertyName.length() == 0) {
 
194
            throw new IntrospectionException("bad property name");
 
195
        }
 
196
        setName(propertyName);
 
197
 
 
198
        // search the mapped get and set methods
 
199
        mappedReadMethod =
 
200
            findMethod(beanClass, mappedGetterName, 1, stringClassArray);
 
201
        if (mappedReadMethod != null) {
 
202
            Class params[] = { String.class,
 
203
                               mappedReadMethod.getReturnType() };
 
204
            mappedWriteMethod =
 
205
                findMethod(beanClass, mappedSetterName, 2, params);
 
206
        } else {
 
207
            mappedWriteMethod =
 
208
                findMethod(beanClass, mappedSetterName, 2);
 
209
        }
 
210
        findMappedPropertyType();
 
211
 
 
212
    }
 
213
 
 
214
 
 
215
    /**
 
216
     * This constructor takes the name of a mapped property, and Method
 
217
     * objects for reading and writing the property.
 
218
     *
 
219
     * @param propertyName The programmatic name of the property.
 
220
     * @param mappedGetter The method used for reading one of
 
221
     *          the property values.  May be be null if the property
 
222
     *          is write-only.
 
223
     * @param mappedSetter The method used for writing one the
 
224
     *          property values.  May be null if the property is read-only.
 
225
     *
 
226
     * @exception IntrospectionException if an exception occurs during
 
227
     *              introspection.
 
228
     */
 
229
    public MappedPropertyDescriptor(String propertyName,
 
230
                Method mappedGetter, Method mappedSetter)
 
231
                                throws IntrospectionException {
 
232
 
 
233
        super(propertyName, mappedGetter, mappedSetter);
 
234
        if (propertyName == null || propertyName.length() == 0) {
 
235
            throw new IntrospectionException("bad property name");
 
236
        }
 
237
        setName(propertyName);
 
238
        mappedReadMethod = mappedGetter;
 
239
        mappedWriteMethod = mappedSetter;
 
240
        findMappedPropertyType();
 
241
 
 
242
    }
 
243
 
 
244
 
 
245
    // -------------------------------------------------------- Public Methods
 
246
 
 
247
 
 
248
   /**
 
249
     * Gets the Class object for the property values.
 
250
     *
 
251
     * @return The Java type info for the property values.  Note that
 
252
     * the "Class" object may describe a built-in Java type such as "int".
 
253
     * The result may be "null" if this is a mapped property that
 
254
     * does not support non-keyed access.
 
255
     * <p>
 
256
     * This is the type that will be returned by the mappedReadMethod.
 
257
     */
 
258
    public Class getMappedPropertyType() {
 
259
 
 
260
        return mappedPropertyType;
 
261
 
 
262
    }
 
263
 
 
264
 
 
265
    /**
 
266
     * Gets the method that should be used to read one of the property value.
 
267
     *
 
268
     * @return The method that should be used to read the property value.
 
269
     * May return null if the property can't be read.
 
270
     */
 
271
    public Method getMappedReadMethod() {
 
272
 
 
273
        return mappedReadMethod;
 
274
 
 
275
    }
 
276
 
 
277
 
 
278
    /**
 
279
     * Sets the method that should be used to read one of the property value.
 
280
     *
 
281
     * @param getter The new getter method.
 
282
     */
 
283
    public void setMappedReadMethod(Method mappedGetter)
 
284
                                throws IntrospectionException {
 
285
 
 
286
        mappedReadMethod = mappedGetter;
 
287
        findMappedPropertyType();
 
288
 
 
289
    }
 
290
 
 
291
 
 
292
    /**
 
293
     * Gets the method that should be used to write one of the property value.
 
294
     *
 
295
     * @return The method that should be used to write one of the property value.
 
296
     * May return null if the property can't be written.
 
297
     */
 
298
    public Method getMappedWriteMethod() {
 
299
 
 
300
        return mappedWriteMethod;
 
301
 
 
302
    }
 
303
 
 
304
 
 
305
    /**
 
306
     * Sets the method that should be used to write the property value.
 
307
     *
 
308
     * @param setter The new setter method.
 
309
     */
 
310
    public void setMappedWriteMethod(Method mappedSetter)
 
311
                                throws IntrospectionException {
 
312
 
 
313
        mappedWriteMethod = mappedSetter;
 
314
        findMappedPropertyType();
 
315
 
 
316
    }
 
317
 
 
318
 
 
319
    // ------------------------------------------------------- Private Methods
 
320
 
 
321
 
 
322
    /**
 
323
     * Introspect our bean class to identify the corresponding getter
 
324
     * and setter methods.
 
325
     */
 
326
    private void findMappedPropertyType() throws IntrospectionException {
 
327
 
 
328
        try {
 
329
            mappedPropertyType = null;
 
330
            if (mappedReadMethod != null) {
 
331
                if (mappedReadMethod.getParameterTypes().length != 1) {
 
332
                    throw new IntrospectionException
 
333
                        ("bad mapped read method arg count");
 
334
                }
 
335
                mappedPropertyType =
 
336
                    mappedReadMethod.getReturnType();
 
337
                if (mappedPropertyType == Void.TYPE) {
 
338
                    throw new IntrospectionException
 
339
                        ("mapped read method " +
 
340
                         mappedReadMethod.getName() + " returns void");
 
341
                }
 
342
            }
 
343
            if (mappedWriteMethod != null) {
 
344
                Class params[] = mappedWriteMethod.getParameterTypes();
 
345
                if (params.length != 2) {
 
346
                    throw new IntrospectionException
 
347
                        ("bad mapped write method arg count");
 
348
                }
 
349
                if (mappedPropertyType != null &&
 
350
                    mappedPropertyType != params[1]) {
 
351
                    throw new IntrospectionException
 
352
                        ("type mismatch between mapped read and write methods");
 
353
                }
 
354
                mappedPropertyType = params[1];
 
355
            }
 
356
        } catch (IntrospectionException ex) {
 
357
            throw ex;
 
358
        }
 
359
 
 
360
    }
 
361
 
 
362
 
 
363
    /**
 
364
     * Return a capitalized version of the specified property name.
 
365
     *
 
366
     * @param s The property name
 
367
     */
 
368
    static String capitalize(String s) {
 
369
 
 
370
        if (s.length() == 0) {
 
371
            return s;
 
372
        }
 
373
        char chars[] = s.toCharArray();
 
374
        chars[0] = Character.toUpperCase(chars[0]);
 
375
        return new String(chars);
 
376
 
 
377
    }
 
378
 
 
379
 
 
380
    //======================================================================
 
381
    // Package private support methods (copied from java.beans.Introspector).
 
382
    //======================================================================
 
383
 
 
384
    // Cache of Class.getDeclaredMethods:
 
385
    private static java.util.Hashtable
 
386
        declaredMethodCache = new java.util.Hashtable();
 
387
 
 
388
 
 
389
    /*
 
390
     * Internal method to return *public* methods within a class.
 
391
     */
 
392
    private static synchronized Method[] getPublicDeclaredMethods(Class clz) {
 
393
 
 
394
        // Looking up Class.getDeclaredMethods is relatively expensive,
 
395
        // so we cache the results.
 
396
        final Class fclz = clz;
 
397
        Method[] result = (Method[])declaredMethodCache.get(fclz);
 
398
        if (result != null) {
 
399
            return result;
 
400
        }
 
401
 
 
402
        // We have to raise privilege for getDeclaredMethods
 
403
        result = (Method[])
 
404
            AccessController.doPrivileged(new PrivilegedAction() {
 
405
                public Object run() {
 
406
                    return fclz.getDeclaredMethods();
 
407
                }
 
408
            });
 
409
 
 
410
        // Null out any non-public methods.
 
411
        for (int i = 0; i < result.length; i++) {
 
412
            Method method = result[i];
 
413
            int mods = method.getModifiers();
 
414
            if (!Modifier.isPublic(mods)) {
 
415
                result[i] = null;
 
416
            }
 
417
        }
 
418
 
 
419
        // Add it to the cache.
 
420
        declaredMethodCache.put(clz, result);
 
421
        return result;
 
422
 
 
423
    }
 
424
 
 
425
 
 
426
    /**
 
427
     * Internal support for finding a target methodName on a given class.
 
428
     */
 
429
    private static Method internalFindMethod(Class start, String methodName,
 
430
                                             int argCount) {
 
431
 
 
432
        // For overridden methods we need to find the most derived version.
 
433
        // So we start with the given class and walk up the superclass chain.
 
434
        for (Class cl = start; cl != null; cl = cl.getSuperclass()) {
 
435
            Method methods[] = getPublicDeclaredMethods(cl);
 
436
            for (int i = 0; i < methods.length; i++) {
 
437
                Method method = methods[i];
 
438
                if (method == null) {
 
439
                    continue;
 
440
                }
 
441
                // skip static methods.
 
442
                int mods = method.getModifiers();
 
443
                if (Modifier.isStatic(mods)) {
 
444
                    continue;
 
445
                }
 
446
                if (method.getName().equals(methodName) &&
 
447
                    method.getParameterTypes().length == argCount) {
 
448
                    return method;
 
449
                }
 
450
            }
 
451
        }
 
452
 
 
453
        // Now check any inherited interfaces.  This is necessary both when
 
454
        // the argument class is itself an interface, and when the argument
 
455
        // class is an abstract class.
 
456
        Class ifcs[] = start.getInterfaces();
 
457
        for (int i = 0 ; i < ifcs.length; i++) {
 
458
            Method m = internalFindMethod(ifcs[i], methodName, argCount);
 
459
            if (m != null) {
 
460
                return m;
 
461
            }
 
462
        }
 
463
 
 
464
        return null;
 
465
 
 
466
    }
 
467
 
 
468
 
 
469
    /**
 
470
     * Internal support for finding a target methodName with a given
 
471
     * parameter list on a given class.
 
472
     */
 
473
    private static Method internalFindMethod(Class start, String methodName,
 
474
                                             int argCount, Class args[]) {
 
475
 
 
476
        // For overriden methods we need to find the most derived version.
 
477
        // So we start with the given class and walk up the superclass chain.
 
478
        for (Class cl = start; cl != null; cl = cl.getSuperclass()) {
 
479
            Method methods[] = getPublicDeclaredMethods(cl);
 
480
            for (int i = 0; i < methods.length; i++) {
 
481
                Method method = methods[i];
 
482
                if (method == null) {
 
483
                    continue;
 
484
                }
 
485
                // skip static methods.
 
486
                int mods = method.getModifiers();
 
487
                if (Modifier.isStatic(mods)) {
 
488
                    continue;
 
489
                }
 
490
                // make sure method signature matches.
 
491
                Class params[] = method.getParameterTypes();
 
492
                if (method.getName().equals(methodName) &&
 
493
                    params.length == argCount) {
 
494
                    boolean different = false;
 
495
                    if (argCount > 0) {
 
496
                        for (int j = 0; j < argCount; j++) {
 
497
                            if (params[j] != args[j]) {
 
498
                                different = true;
 
499
                                continue;
 
500
                            }
 
501
                        }
 
502
                        if (different) {
 
503
                            continue;
 
504
                        }
 
505
                    }
 
506
                    return method;
 
507
                }
 
508
            }
 
509
        }
 
510
 
 
511
        // Now check any inherited interfaces.  This is necessary both when
 
512
        // the argument class is itself an interface, and when the argument
 
513
        // class is an abstract class.
 
514
        Class ifcs[] = start.getInterfaces();
 
515
        for (int i = 0 ; i < ifcs.length; i++) {
 
516
            Method m = internalFindMethod(ifcs[i], methodName, argCount);
 
517
            if (m != null) {
 
518
                return m;
 
519
            }
 
520
        }
 
521
        
 
522
        return null;
 
523
 
 
524
    }
 
525
 
 
526
 
 
527
    /**
 
528
     * Find a target methodName on a given class.
 
529
     */
 
530
    static Method findMethod(Class cls, String methodName, int argCount)
 
531
        throws IntrospectionException {
 
532
 
 
533
        if (methodName == null) {
 
534
            return null;
 
535
        }
 
536
 
 
537
        Method m = internalFindMethod(cls, methodName, argCount);
 
538
        if (m != null ) {
 
539
            return m;
 
540
        }
 
541
 
 
542
        // We failed to find a suitable method
 
543
        throw new IntrospectionException("No method \"" + methodName +
 
544
                                         "\" with " + argCount + " arg(s)");
 
545
    }
 
546
 
 
547
 
 
548
    /**
 
549
     * Find a target methodName with specific parameter list on a given class.
 
550
     */
 
551
    static Method findMethod(Class cls, String methodName, int argCount,
 
552
                             Class args[]) throws IntrospectionException {
 
553
 
 
554
        if (methodName == null) {
 
555
            return null;
 
556
        }
 
557
 
 
558
        Method m = internalFindMethod(cls, methodName, argCount, args);
 
559
        if (m != null ) {
 
560
            return m;
 
561
        }
 
562
 
 
563
        // We failed to find a suitable method
 
564
        throw new IntrospectionException("No method \"" + methodName +
 
565
                   "\" with " + argCount + " arg(s) of matching types.");
 
566
    }
 
567
 
 
568
 
 
569
    /**
 
570
     * Return true if class a is either equivalent to class b, or
 
571
     * if class a is a subclass of class b, i.e. if a either "extends"
 
572
     * or "implements" b.
 
573
     * Note tht either or both "Class" objects may represent interfaces.
 
574
     */
 
575
    static  boolean isSubclass(Class a, Class b) {
 
576
 
 
577
        // We rely on the fact that for any given java class or
 
578
        // primtitive type there is a unqiue Class object, so
 
579
        // we can use object equivalence in the comparisons.
 
580
        if (a == b) {
 
581
            return true;
 
582
        }
 
583
        if (a == null || b == null) {
 
584
            return false;
 
585
        }
 
586
        for (Class x = a; x != null; x = x.getSuperclass()) {
 
587
            if (x == b) {
 
588
                return true;
 
589
            }
 
590
            if (b.isInterface()) {
 
591
                Class interfaces[] = x.getInterfaces();
 
592
                for (int i = 0; i < interfaces.length; i++) {
 
593
                    if (isSubclass(interfaces[i], b)) {
 
594
                        return true;
 
595
                    }
 
596
                }
 
597
            }
 
598
        }
 
599
        return false;
 
600
 
 
601
    }
 
602
 
 
603
 
 
604
    /**
 
605
     * Return true iff the given method throws the given exception.
 
606
     */
 
607
    private boolean throwsException(Method method, Class exception) {
 
608
 
 
609
        Class exs[] = method.getExceptionTypes();
 
610
        for (int i = 0; i < exs.length; i++) {
 
611
            if (exs[i] == exception) {
 
612
                return true;
 
613
            }
 
614
        }
 
615
        return false;
 
616
 
 
617
    }
 
618
 
 
619
 
 
620
}