KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > oddjob > arooa > reflect > IntrospectionHelper


1 /*
2  * This source code is heavily based on source code from the Apache
3  * Ant project. As such the following is included:
4  * ------------------------------------------------------------------
5  *
6  * The Apache Software License, Version 1.1
7  *
8  * Copyright (c) 2003 The Apache Software Foundation. All rights
9  * reserved.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  *
15  * 1. Redistributions of source code must retain the above copyright
16  * notice, this list of conditions and the following disclaimer.
17  *
18  * 2. Redistributions in binary form must reproduce the above copyright
19  * notice, this list of conditions and the following disclaimer in
20  * the documentation and/or other materials provided with the
21  * distribution.
22  *
23  * 3. The end-user documentation included with the redistribution, if
24  * any, must include the following acknowlegement:
25  * "This product includes software developed by the
26  * Apache Software Foundation (http://www.apache.org/)."
27  * Alternately, this acknowlegement may appear in the software itself,
28  * if and wherever such third-party acknowlegements normally appear.
29  *
30  * 4. The names "Ant" and "Apache Software
31  * Foundation" must not be used to endorse or promote products derived
32  * from this software without prior written permission. For written
33  * permission, please contact apache@apache.org.
34  *
35  * 5. Products derived from this software may not be called "Apache"
36  * nor may "Apache" appear in their names without prior written
37  * permission of the Apache Group.
38  *
39  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
40  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
41  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
42  * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
43  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
44  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
45  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
46  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
47  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
48  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
49  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
50  * SUCH DAMAGE.
51  * ====================================================================
52  *
53  * This software consists of voluntary contributions made by many
54  * individuals on behalf of the Apache Software Foundation. For more
55  * information on the Apache Software Foundation, please see
56  * <http://www.apache.org/>.
57  */

58 package org.oddjob.arooa.reflect;
59
60 import java.io.PrintStream JavaDoc;
61 import java.lang.reflect.Constructor JavaDoc;
62 import java.lang.reflect.InvocationTargetException JavaDoc;
63 import java.lang.reflect.Method JavaDoc;
64 import java.util.ArrayList JavaDoc;
65 import java.util.Enumeration JavaDoc;
66 import java.util.HashMap JavaDoc;
67 import java.util.Hashtable JavaDoc;
68 import java.util.Iterator JavaDoc;
69 import java.util.List JavaDoc;
70 import java.util.Locale JavaDoc;
71 import java.util.Map JavaDoc;
72
73 import org.apache.log4j.Logger;
74 import org.oddjob.OddjobException;
75 import org.oddjob.arooa.ArooaConstants;
76 import org.oddjob.arooa.ArooaContext;
77 import org.oddjob.arooa.ArooaException;
78 import org.oddjob.arooa.ArooaHandler;
79 import org.oddjob.arooa.handlers.SkipLevelHandler;
80 import org.oddjob.arooa.xml.NamespaceHelper;
81
82 /**
83  * Helper class that collects the methods a component or nested element
84  * holds to set attributes, create nested elements or hold PCDATA
85  * elements.
86  * The class is final as it has a private constructor.
87  * <p>
88  * Based on the original by <b>Stefan Bodewig</b> and <b>Peter Reilly</b>.
89  */

90 public final class IntrospectionHelper {
91     private static final Logger logger = Logger.getLogger(IntrospectionHelper.class);
92     
93     /**
94      * Map from attribute names to attribute types
95      * (String to Class).
96      */

97     private final Map JavaDoc attributeTypes = new HashMap JavaDoc();
98
99     /**
100      * Map from attribute names to nested types
101      * (String to Class).
102      */

103     private Hashtable JavaDoc nestedTypes;
104
105     /**
106      * Map from attribute names to methods to create nested types
107      * (String to NestedCreator).
108      */

109     private Hashtable JavaDoc nestedCreators;
110
111     /**
112      * Vector of methods matching add[Configured](Class) pattern
113      */

114     private List JavaDoc addTypeMethods;
115
116     /**
117      * List of handler providers
118      */

119     private Hashtable JavaDoc handlerProviders;
120     
121     /**
122      * Component adding methods.
123      */

124     private Map JavaDoc nestedComponents = new HashMap JavaDoc();
125             
126     /**
127      * The method to invoke to add PCDATA.
128      */

129     private Method JavaDoc addText = null;
130
131     /** A type wrapper will have a value
132      * getter method.
133      */

134     private Method JavaDoc valueFor;
135     
136     /**
137      * The class introspected by this instance.
138      */

139     private Class JavaDoc bean;
140
141     /**
142      * Helper instances we've already created (Class to IntrospectionHelper).
143      */

144     private static Hashtable JavaDoc helpers = new Hashtable JavaDoc();
145
146     /**
147      * Map from primitive types to wrapper classes for use in
148      * createAttributeSetter (Class to Class). Note that char
149      * and boolean are in here even though they get special treatment
150      * - this way we only need to test for the wrapper class.
151      */

152     private static final Hashtable JavaDoc PRIMITIVE_TYPE_MAP = new Hashtable JavaDoc(8);
153
154     // Set up PRIMITIVE_TYPE_MAP
155
static {
156         Class JavaDoc[] primitives = {Boolean.TYPE, Byte.TYPE, Character.TYPE,
157                               Short.TYPE, Integer.TYPE, Long.TYPE,
158                               Float.TYPE, Double.TYPE};
159         Class JavaDoc[] wrappers = {Boolean JavaDoc.class, Byte JavaDoc.class, Character JavaDoc.class,
160                             Short JavaDoc.class, Integer JavaDoc.class, Long JavaDoc.class,
161                             Float JavaDoc.class, Double JavaDoc.class};
162         for (int i = 0; i < primitives.length; i++) {
163             PRIMITIVE_TYPE_MAP.put (primitives[i], wrappers[i]);
164         }
165     }
166
167     /**
168      * Sole constructor, which is private to ensure that all
169      * IntrospectionHelpers are created via {@link #getHelper(Class) getHelper}.
170      * Introspects the given class for bean-like methods.
171      * Each method is examined in turn, and the following rules are applied:
172      * <p>
173      * <ul>
174      * <li><code>void addText(String)</code> is recognised as the method for
175      * adding PCDATA to a bean.
176      * <li><code>void setFoo(Bar)</code> is recognised as a method for
177      * setting the value of attribute <code>foo</code>, so long as
178      * <code>Bar</code> is non-void and is not an array type. Non-String
179      * parameter types always overload String parameter types, but that is
180      * the only guarantee made in terms of priority.
181      * <li><code>Foo createBar()</code> is recognised as a method for
182      * creating a nested element called <code>bar</code> of type
183      * <code>Foo</code>, so long as <code>Foo</code> is not a primitive or
184      * array type.
185      * <li><code>void addConfiguredFoo(Bar)</code> is recognised as a
186      * method for storing a pre-configured element called
187      * <code>foo</code> and of type <code>Bar</code>, so long as
188      * <code>Bar</code> is not an array, primitive or String type.
189      * <code>Bar</code> must have an accessible constructor taking no
190      * arguments.
191      * <li><code>void addConstructedFoo(Bar)</code> is recognised as a
192      * method for storing a constructed but as yet
193      * unconfigured element called
194      * <code>foo</code> and of type <code>Bar</code>, so long as
195      * <code>Bar</code> is not an array, primitive or String type.
196      * <code>Bar</code> must have an accessible constructor taking no
197      * arguments.
198      * <li><code>void addFoo(Bar)</code> is recognised as a
199      * method for storing an element called <code>foobar</code>
200      * and of type <code>Baz</code>, so long as
201      * <code>Baz</code> is not an array, primitive or String type.
202      * <code>Baz</code> must have an accessible constructor taking no
203      * arguments.
204      * </ul>
205      * Note that only one method is retained to create/set/addConfigured/add
206      * any element or attribute.
207      *
208      * @param bean The bean type to introspect.
209      * Must not be <code>null</code>.
210      *
211      * @see #getHelper(Class)
212      */

213     private IntrospectionHelper(final Class JavaDoc bean) {
214         nestedTypes = new Hashtable JavaDoc();
215         nestedCreators = new Hashtable JavaDoc();
216         addTypeMethods = new ArrayList JavaDoc();
217         handlerProviders = new Hashtable JavaDoc();
218
219         this.bean = bean;
220
221         Method JavaDoc[] methods = bean.getMethods();
222         for (int i = 0; i < methods.length; i++) {
223             final Method JavaDoc m = methods[i];
224             final String JavaDoc name = m.getName();
225             Class JavaDoc returnType = m.getReturnType();
226             Class JavaDoc[] args = m.getParameterTypes();
227
228             // check of add[Configured](Class) pattern
229
if (args.length == 1 && java.lang.Void.TYPE.equals(returnType)
230                     && (name.equals("add") || name.equals("addConfigured"))) {
231                 insertAddTypeMethod(m);
232                 continue;
233             }
234
235             if ("addText".equals(name)
236                     && java.lang.Void.TYPE.equals(returnType)
237                     && args.length == 1
238                     && java.lang.String JavaDoc.class.equals(args[0])) {
239
240                 addText = methods[i];
241
242             } else if (name.startsWith("set")
243                     && java.lang.Void.TYPE.equals(returnType)
244                     && args.length == 1) {
245                 processSetMethod(m);
246             } else if (name.startsWith("set")
247                     && java.lang.Void.TYPE.equals(returnType)
248                     && args.length == 2 && String JavaDoc.class.equals(args[0])) {
249                 processMappedMethod(m);
250             } else if (name.startsWith("create") && !returnType.isArray()
251                     && !returnType.isPrimitive() && args.length == 0) {
252
253                 String JavaDoc propName = getPropertyName(name, "create");
254                 // Check if a create of this property is already present
255
// add takes preference over create for CB purposes
256
if (nestedCreators.get(propName) == null) {
257                     nestedTypes.put(propName, returnType);
258                     nestedCreators.put(propName, new NestedCreator() {
259                         public Object JavaDoc create(Object JavaDoc parent, Object JavaDoc ignore)
260                                 throws InvocationTargetException JavaDoc,
261                                 IllegalAccessException JavaDoc {
262                             return m.invoke(parent, new Object JavaDoc[] {});
263                         }
264
265                         public void storeConfigured(Object JavaDoc parent, Object JavaDoc child) {
266                         }
267
268                         public String JavaDoc toString() {
269                             return "create NestedCreator";
270                         }
271                     });
272                 }
273             } else if (name.startsWith("addConfigured")
274                     && java.lang.Void.TYPE.equals(returnType)
275                     && args.length == 1
276                     && !java.lang.String JavaDoc.class.equals(args[0])
277                     && !args[0].isArray() && !args[0].isPrimitive()) {
278                 try {
279                     Constructor JavaDoc constructor = args[0]
280                             .getConstructor(new Class JavaDoc[] {});
281
282                     final Constructor JavaDoc c = constructor;
283                     String JavaDoc propName = getPropertyName(name, "addConfigured");
284                     nestedTypes.put(propName, args[0]);
285                     nestedCreators.put(propName, new NestedCreator() {
286                         public Object JavaDoc create(Object JavaDoc parent, Object JavaDoc child)
287                                 throws InvocationTargetException JavaDoc,
288                                 IllegalAccessException JavaDoc, InstantiationException JavaDoc {
289                             if (child != null) {
290                                 return child;
291                             } else {
292                                 return c.newInstance(new Object JavaDoc[] {});
293                             }
294                         }
295
296                         public void storeConfigured(Object JavaDoc parent, Object JavaDoc child)
297                                 throws InvocationTargetException JavaDoc,
298                                 IllegalAccessException JavaDoc, InstantiationException JavaDoc {
299
300                             m.invoke(parent, new Object JavaDoc[] { child });
301                         }
302
303                         public String JavaDoc toString() {
304                             return "addConfigured NestedCreator for " + c;
305                         }
306                     });
307                 } catch (NoSuchMethodException JavaDoc nse) {
308                     logger.debug(nse.getMessage());
309                 }
310             } else if (name.startsWith("addValue")
311                     && java.lang.Void.TYPE.equals(returnType)
312                     && args.length == 1
313                     && !args[0].isPrimitive()) {
314                 String JavaDoc propName = getPropertyName(name, "addValue");
315                 nestedTypes.put(propName, args[0]);
316                 nestedCreators.put(propName, new NestedCreator() {
317                     public Object JavaDoc create(Object JavaDoc parent, Object JavaDoc child)
318                             throws InvocationTargetException JavaDoc,
319                             IllegalAccessException JavaDoc, InstantiationException JavaDoc {
320                         throw new IllegalStateException JavaDoc("Handler should create!");
321                     }
322
323                     public void storeConfigured(Object JavaDoc parent, Object JavaDoc child)
324                             throws InvocationTargetException JavaDoc,
325                             IllegalAccessException JavaDoc, InstantiationException JavaDoc {
326                         m.invoke(parent, new Object JavaDoc[] { child });
327                     }
328
329                     public String JavaDoc toString() {
330                         return "addValue NestedCreator for ";
331                     }
332                 });
333             } else if (name.startsWith("addComponent")
334                     && java.lang.Void.TYPE.equals(returnType)
335                     && args.length == 1
336                     && !java.lang.String JavaDoc.class.equals(args[0])
337                     && !args[0].isArray() && !args[0].isPrimitive()) {
338                 processAddComponent(m);
339             } else if (name.startsWith("add")
340                     && java.lang.Void.TYPE.equals(returnType)
341                     && args.length == 1
342                     && !java.lang.String JavaDoc.class.equals(args[0])
343                     && !args[0].isArray() && !args[0].isPrimitive()) {
344                 try {
345                     Constructor JavaDoc constructor = args[0]
346                             .getConstructor(new Class JavaDoc[] {});
347
348                     final Constructor JavaDoc c = constructor;
349                     String JavaDoc propName = getPropertyName(name, "add");
350                     nestedTypes.put(propName, args[0]);
351                     nestedCreators.put(propName, new NestedCreator() {
352                         public Object JavaDoc create(Object JavaDoc parent, Object JavaDoc child)
353                                 throws InvocationTargetException JavaDoc,
354                                 IllegalAccessException JavaDoc, InstantiationException JavaDoc {
355                             if (child != null) {
356                                 // ignore
357
} else if (c.getParameterTypes().length == 0) {
358                                 child = c.newInstance(new Object JavaDoc[] {});
359                             }
360                             m.invoke(parent, new Object JavaDoc[] { child });
361                             return child;
362                         }
363
364                         public void storeConfigured(Object JavaDoc parent, Object JavaDoc child)
365                                 throws InvocationTargetException JavaDoc,
366                                 IllegalAccessException JavaDoc, InstantiationException JavaDoc {
367                         }
368
369                         public String JavaDoc toString() {
370                             return "add NestedCreator for " + c;
371                         }
372                     });
373                 } catch (NoSuchMethodException JavaDoc nse) {
374                     // ignore
375
}
376             } else if (name.startsWith("handlerFor")
377                     && ArooaHandler.class.isAssignableFrom(returnType)
378                     && args.length == 1
379                     && ArooaContext.class.equals(args[0])) {
380                 processHandlerFor(m);
381             } // this method will exist on a value type.
382
else if (name.equals("valueFor")
383                     && !java.lang.Void.TYPE.equals(returnType)
384                     && args.length == 1 && Class JavaDoc.class.equals(args[0])) {
385                 valueFor = m;
386             }
387
388         }
389     }
390
391     /**
392      * Process a <code>setXyz(SomeType value)</code> method.
393      *
394      * @param m The method.
395      */

396     private void processSetMethod(final Method JavaDoc m) {
397         final String JavaDoc propName = getPropertyName(m.getName(), "set");
398         if ("".equals(propName)) {
399             return;
400         }
401         attributeTypes.put(propName, m.getParameterTypes()[0]);
402         if (handlerProviders.get(propName) == null) {
403             handlerProviders.put(propName, new HandlerProvider() {
404                 public String JavaDoc getElementName() {
405                     return propName;
406                 }
407                 public ArooaHandler provideHandler(Object JavaDoc o,
408                         ArooaContext context) {
409                     return (ArooaHandler) context
410                             .get(ArooaConstants.PROPERTY_HANDLER);
411                 }
412             });
413         }
414     }
415
416     /**
417      * Process a <code>setXyz(String name, SomeType value)</code> method.
418      *
419      * @param m The method.
420      */

421     private void processMappedMethod(final Method JavaDoc m) {
422         final String JavaDoc propName = getPropertyName(m.getName(), "set");
423         if ("".equals(propName)) {
424             return;
425         }
426         attributeTypes.put(propName, m.getParameterTypes()[1]);
427         if (handlerProviders.get(propName) == null) {
428             handlerProviders.put(propName, new HandlerProvider() {
429                 public String JavaDoc getElementName() {
430                     return propName;
431                 }
432                 public ArooaHandler provideHandler(Object JavaDoc o,
433                         ArooaContext context) {
434                     return new SkipLevelHandler(
435                             (ArooaHandler) context
436                             .get(ArooaConstants.MAPPED_HANDLER));
437                 }
438             });
439         }
440     }
441     
442     /**
443      * Process an <code>addComponent</code> or <code>addComponentXyz()</code>
444      * method.
445      *
446      * @param m The method.
447      */

448     private void processAddComponent(final Method JavaDoc m) {
449         final String JavaDoc propName = getPropertyName(m.getName(), "addComponent");
450         nestedComponents.put(propName, m);
451         if (handlerProviders.get(propName) == null) {
452             handlerProviders.put(propName, new HandlerProvider() {
453                 public String JavaDoc getElementName() {
454                     return propName;
455                 }
456                 public ArooaHandler provideHandler(Object JavaDoc o,
457                         ArooaContext context) {
458                     if ("".equals(propName)) {
459                             return (ArooaHandler) context
460                             .get(ArooaConstants.COMPONENT_HANDLER);
461                     }
462                     else {
463                         return new SkipLevelHandler(
464                                 (ArooaHandler) context
465                                 .get(ArooaConstants.COMPONENT_HANDLER));
466                     }
467                 }
468             });
469         } else {
470             // handler provided by handlerFor method.
471
}
472     }
473     
474     /**
475      * Process the <code>handlerFor(ArooaXMLContext context)</code> method.
476      *
477      * @param m The method.
478      */

479     private void processHandlerFor(final Method JavaDoc m) {
480         final String JavaDoc propName = getPropertyName(m.getName(), "handlerFor");
481         HandlerProvider hp = new HandlerProvider() {
482             public String JavaDoc getElementName() {
483                 return propName;
484             }
485             public ArooaHandler provideHandler(Object JavaDoc parent,
486                     ArooaContext context)
487                     throws InvocationTargetException JavaDoc,
488                     IllegalAccessException JavaDoc, InstantiationException JavaDoc {
489                 return (ArooaHandler) m.invoke(parent,
490                         new Object JavaDoc[] { context });
491             }
492         };
493         handlerProviders.put(propName, hp);
494     }
495     
496     /**
497      * Returns a helper for the given class, either from the cache or by
498      * creating a new instance.
499      *
500      * @param c
501      * The class for which a helper is required. Must not be
502      * <code>null</code>.
503      *
504      * @return a helper for the specified class
505      */

506     public static synchronized IntrospectionHelper getHelper(Class JavaDoc c) {
507         IntrospectionHelper ih = (IntrospectionHelper) helpers.get(c);
508         if (ih == null) {
509             ih = new IntrospectionHelper(c);
510             helpers.put(c, ih);
511         }
512         return ih;
513     }
514
515     /**
516      * Sets the named attribute in the given element.
517      *
518      * @param element
519      * The element to set the attribute in. Must not be
520      * <code>null</code>.
521      * @param attributeName
522      * The name of the attribute to set. Must not be
523      * <code>null</code>.
524      * @param value
525      * The value to set the attribute to. This may be interpreted or
526      * converted to the necessary type if the setter method doesn't
527      * just take a string. Must not be <code>null</code>.
528      *
529      * @exception ArooaException
530      * if the introspected class doesn't support the given
531      * attribute, or if the setting method fails.
532      */

533 // public void setAttribute(Object element, String attributeName,
534
// Object value) throws ArooaException {
535
// attributeDemangler.setAttribute(element, attributeName, value);
536
// }
537

538     /**
539      * Adds PCDATA to an element, using the element's
540      * <code>void addText(String)</code> method, if it has one. If no
541      * such method is present, a ArooaException is thrown if the
542      * given text contains non-whitespace.
543      *
544      * @param element The element to add the text to.
545      * Must not be <code>null</code>.
546      * @param text The text to add.
547      * Must not be <code>null</code>.
548      *
549      * @exception ArooaException if non-whitespace text is provided and no
550      * method is available to handle it, or if
551      * the handling method fails.
552      */

553     public void addText(Object JavaDoc element, String JavaDoc text)
554             throws ArooaException {
555         if (addText == null) {
556             // Element doesn't handle text content
557
if (text.trim().length() == 0) {
558                 // Only whitespace - ignore
559
return;
560             } else {
561                 // Not whitespace - fail
562
String JavaDoc msg = element
563                     + " doesn't support nested text data.";
564                 throw new ArooaException(msg);
565             }
566         }
567         try {
568             addText.invoke(element, new String JavaDoc[] {text});
569         } catch (IllegalAccessException JavaDoc ie) {
570             // impossible as getMethods should only return public methods
571
throw new ArooaException(ie);
572         } catch (InvocationTargetException JavaDoc ite) {
573             Throwable JavaDoc t = ite.getTargetException();
574             if (t instanceof ArooaException) {
575                 throw (ArooaException) t;
576             }
577             throw new ArooaException(t);
578         }
579     }
580
581     /**
582      * Utility method to throw a NotSupported exception
583      *
584      * @param parent the object which doesn't support a requested element
585      * @param elementName the name of the Element which is trying to be created.
586      */

587     public void throwNotSupported(Object JavaDoc parent, String JavaDoc elementName) {
588         String JavaDoc msg = parent.getClass()
589             + " doesn't support the nested \"" + elementName + "\" element.";
590         throw new ArooaException(msg);
591     }
592
593     private NestedCreator getNestedCreator(String JavaDoc parentUri, Object JavaDoc parent,
594         String JavaDoc elementName) throws ArooaException {
595
596         String JavaDoc uri = NamespaceHelper.extractUriFromComponentName(elementName);
597         String JavaDoc name = NamespaceHelper.extractNameFromComponentName(elementName);
598
599         NestedCreator nc = null;
600
601         if (uri.equals(parentUri)) { // || uri.equals("")) {
602
nc = (NestedCreator) nestedCreators.get(name);
603         }
604         if (nc == null) {
605             throwNotSupported(parent, elementName);
606         }
607         return nc;
608     }
609
610     /**
611      * Creates a named nested element. Depending on the results of the
612      * initial introspection, either a method in the given parent instance
613      * or a simple no-arg constructor is used to create an instance of the
614      * specified element type.
615      *
616      * @param uri Namespace uri.
617      * @param parent Parent object used to create the instance.
618      * Must not be <code>null</code>.
619      * @param elementName Name of the element to create an instance of.
620      * Must not be <code>null</code>.
621      *
622      * @return an instance of the specified element type
623      *
624      * @exception ArooaException if no method is available to create the
625      * element instance, or if the creating method
626      * fails.
627      */

628     public Object JavaDoc createElement(String JavaDoc uri, Object JavaDoc parent,
629             String JavaDoc elementName) throws ArooaException {
630         NestedCreator nc = getNestedCreator(uri, parent, elementName);
631         try {
632             Object JavaDoc nestedElement = nc.create(parent, null);
633             return nestedElement;
634         } catch (IllegalAccessException JavaDoc ie) {
635             // impossible as getMethods should only return public methods
636
throw new ArooaException(ie);
637         } catch (InstantiationException JavaDoc ine) {
638             // impossible as getMethods should only return public methods
639
throw new ArooaException(ine);
640         } catch (InvocationTargetException JavaDoc ite) {
641             Throwable JavaDoc t = ite.getTargetException();
642             if (t instanceof ArooaException) {
643                 throw (ArooaException) t;
644             }
645             throw new ArooaException(t);
646         }
647     }
648
649     /**
650      * Indicate if this element supports a nested element of the
651      * given name.
652      *
653      * @param elementName the name of the nested element being checked
654      *
655      * @return true if the given nested element is supported
656      */

657     public boolean supportsNestedElement(String JavaDoc elementName) {
658         return nestedCreators.containsKey(elementName.toLowerCase(Locale.US))
659             || addTypeMethods.size() != 0;
660     }
661
662     /**
663      * Stores a named nested element using a storage method determined
664      * by the initial introspection. If no appropriate storage method
665      * is available, this method returns immediately.
666      *
667      * @param parent Parent instance to store the child in.
668      * Must not be <code>null</code>.
669      *
670      * @param child Child instance to store in the parent.
671      * Should not be <code>null</code>.
672      *
673      * @param elementName Name of the child element to store.
674      * May be <code>null</code>, in which case
675      * this method returns immediately.
676      *
677      * @exception ArooaException if the storage method fails.
678      */

679     public void storeConfiguredElement(Object JavaDoc parent, Object JavaDoc child,
680         String JavaDoc elementName) throws ArooaException {
681         if (elementName == null) {
682             throw new ArooaException("Element name blank in storeConfigured!");
683         }
684         NestedCreator ns = (NestedCreator) nestedCreators.get(elementName);
685         if (ns == null) {
686             throw new ArooaException("No way to store element [" + elementName
687                     + "] in object [" + parent + "] (" + parent.getClass().getName() + ")");
688         }
689         try {
690             ns.storeConfigured(parent, child);
691         } catch (IllegalAccessException JavaDoc ie) {
692             // impossible as getMethods should only return public methods
693
throw new ArooaException(ie);
694         } catch (InstantiationException JavaDoc ine) {
695             // impossible as getMethods should only return public methods
696
throw new ArooaException(ine);
697         } catch (InvocationTargetException JavaDoc ite) {
698             Throwable JavaDoc t = ite.getTargetException();
699             if (t instanceof ArooaException) {
700                 throw (ArooaException) t;
701             }
702             throw new ArooaException(t);
703         }
704     }
705     
706     /**
707      * Provide a handler.
708      *
709      * @param parent Parent instance.
710      * Must not be <code>null</code>.
711      *
712      * @param elementName Name of the child the handler is for.
713      * May not be <code>null</code>.
714      *
715      * @exception ArooaException if the storage method fails.
716      */

717     public ArooaHandler provideHandler(Object JavaDoc parent, String JavaDoc elementName,
718                 ArooaContext context)
719             throws ArooaException {
720         // try for the default first
721
HandlerProvider handlerProvider = (HandlerProvider) handlerProviders.get("");
722         if (handlerProvider == null) {
723             handlerProvider = (HandlerProvider) handlerProviders.get(elementName);
724         }
725         if (handlerProvider == null) {
726             return (ArooaHandler) context.get(ArooaConstants.ELEMENT_HANDLER);
727         }
728         context.set(ArooaConstants.ELEMENT_NAME, handlerProvider.getElementName());
729         try {
730             return (ArooaHandler) handlerProvider.provideHandler(parent, context);
731         } catch (IllegalAccessException JavaDoc ie) {
732             // impossible as getMethods should only return public methods
733
throw new ArooaException(ie);
734         } catch (InstantiationException JavaDoc ine) {
735             // impossible as getMethods should only return public methods
736
throw new ArooaException(ine);
737         // The method being called has thrown an exception.
738
} catch (InvocationTargetException JavaDoc ite) {
739             Throwable JavaDoc t = ite.getTargetException();
740             if (t instanceof ArooaException) {
741                 throw (ArooaException) t;
742             }
743             throw new ArooaException(t);
744         }
745     }
746
747     
748     /**
749      * Stores a named nested element using a storage method determined
750      * by the initial introspection. If no appropriate storage method
751      * is available, this method returns immediately.
752      *
753      * @param parent Parent instance to store the child in.
754      * Must not be <code>null</code>.
755      *
756      * @param child Child instance to store in the parent.
757      * Should not be <code>null</code>.
758      *
759      * @param elementName Name of the child element to store.
760      * May be <code>null</code>, in which case
761      * this method returns immediately.
762      *
763      * @exception ArooaException if the storage method fails.
764      */

765     public void storeComponent(Object JavaDoc parent, Object JavaDoc child, String JavaDoc elementName)
766             throws ArooaException {
767         if (elementName == null) {
768             elementName = "";
769         }
770         Method JavaDoc m = (Method JavaDoc) nestedComponents.get(elementName);
771         if (m == null) {
772             throwNotSupported(parent, elementName);
773         }
774         try {
775             m.invoke(parent, new Object JavaDoc[] { child });
776         } catch (IllegalArgumentException JavaDoc ae) {
777             throw new ArooaException("element \"" + elementName + "\" ("
778                     + child.getClass().getName()
779                     + ") must be the correct type in "
780                     + parent.getClass().getName(), ae);
781         } catch (IllegalAccessException JavaDoc ie) {
782             // impossible as getMethods should only return public methods
783
throw new ArooaException(ie);
784         } catch (InvocationTargetException JavaDoc ite) {
785             Throwable JavaDoc t = ite.getTargetException();
786             if (t instanceof ArooaException) {
787                 throw (ArooaException) t;
788             }
789             throw new ArooaException(t);
790         }
791     }
792
793     /**
794      * Returns the type of a named nested element.
795      *
796      * @param elementName
797      * The name of the element to find the type of. Must not be
798      * <code>null</code>.
799      *
800      * @return the type of the nested element with the specified name. This will
801      * never be <code>null</code>.
802      *
803      * @exception ArooaException
804      * if the introspected class does not support the named
805      * nested element.
806      */

807     public Class JavaDoc getElementType(String JavaDoc elementName)
808         throws ArooaException {
809         Class JavaDoc nt = (Class JavaDoc) nestedTypes.get(elementName);
810         if (nt == null) {
811             String JavaDoc msg = "Class " + bean.getName()
812                 + " doesn't support the nested \"" + elementName
813                 + "\" element.";
814             throw new ArooaException(msg);
815         }
816         return nt;
817     }
818
819     /**
820      * Returns the type of a named attribute.
821      *
822      * @param attributeName The name of the attribute to find the type of.
823      * Must not be <code>null</code>.
824      *
825      * @return the type of the attribute with the specified name.
826      * This will never be <code>null</code>.
827      *
828      * @exception ArooaException if the introspected class does not
829      * support the named attribute.
830      */

831     public Class JavaDoc getAttributeType(String JavaDoc attributeName)
832     throws ArooaException {
833         return (Class JavaDoc) attributeTypes.get(attributeName);
834     }
835         
836     /**
837      * Returns whether or not the introspected class supports PCDATA.
838      *
839      * @return whether or not the introspected class supports PCDATA.
840      */

841     public boolean supportsCharacters() {
842         return addText != null;
843     }
844
845     /**
846      * Returns an enumeration of the names of the attributes supported
847      * by the introspected class.
848      *
849      * @return an enumeration of the names of the attributes supported
850      * by the introspected class.
851      */

852 // public Enumeration getAttributes() {
853
// return attributeSetters.keys();
854
// }
855

856     /**
857      * Returns an enumeration of the names of the nested elements supported
858      * by the introspected class.
859      *
860      * @return an enumeration of the names of the nested elements supported
861      * by the introspected class.
862      */

863     public Enumeration JavaDoc getNestedElements() {
864         return nestedTypes.keys();
865     }
866
867
868     /**
869      * Extracts the name of a property from a method name by subtracting
870      * a given prefix and converting into lower case. It is up to calling
871      * code to make sure the method name does actually begin with the
872      * specified prefix - no checking is done in this method.
873      *
874      * @param methodName The name of the method in question.
875      * Must not be <code>null</code>.
876      * @param prefix The prefix to remove.
877      * Must not be <code>null</code>.
878      *
879      * @return the lower-cased method name with the prefix removed.
880      */

881     public static String JavaDoc getPropertyName(String JavaDoc methodName, String JavaDoc prefix) {
882         int start = prefix.length();
883         String JavaDoc name = methodName.substring(start);
884         if (name.length() < 1) {
885             return name;
886         }
887         String JavaDoc firstChar = name.substring(0, 1).toLowerCase(Locale.US);
888         return firstChar + name.substring(1);
889     }
890
891
892     /**
893      * Internal interface used to create nested elements.
894      */

895     private interface NestedCreator {
896                 
897         /**
898          * Create the nested element.
899          * @param parent The parent object
900          * @param child If this is provided, so that a provided child can be
901          * used instead of it being created.
902          * @return A new nested element.
903          */

904         Object JavaDoc create(Object JavaDoc parent, Object JavaDoc child)
905             throws InvocationTargetException JavaDoc, IllegalAccessException JavaDoc, InstantiationException JavaDoc;
906
907         /**
908          * Store the configured element.
909          * @param parent The parent object.
910          * @param child The child element.
911          */

912         void storeConfigured(Object JavaDoc parent, Object JavaDoc child)
913             throws InvocationTargetException JavaDoc, IllegalAccessException JavaDoc, InstantiationException JavaDoc;
914     }
915     
916     private interface HandlerProvider {
917         public String JavaDoc getElementName();
918         public ArooaHandler provideHandler(Object JavaDoc parent, ArooaContext context)
919                 throws InvocationTargetException JavaDoc, IllegalAccessException JavaDoc, InstantiationException JavaDoc;
920     }
921     
922
923     /**
924      * Inserts an add or addConfigured method into
925      * the addTypeMethods array. The array is
926      * ordered so that the more derived classes
927      * are first.
928      */

929
930     private void insertAddTypeMethod(Method JavaDoc method) {
931         Class JavaDoc argClass = method.getParameterTypes()[0];
932         for (int c = 0; c < addTypeMethods.size(); ++c) {
933             Method JavaDoc current = (Method JavaDoc) addTypeMethods.get(c);
934             if (current.getParameterTypes()[0].equals(argClass)) {
935                 return; // Already present
936
}
937             if (current.getParameterTypes()[0].isAssignableFrom(
938                             argClass)) {
939                 addTypeMethods.add(c, method);
940                 return; // higher derived
941
}
942         }
943         addTypeMethods.add(method);
944     }
945
946     /**
947      * Get the value from the given type object if its
948      * a type wrapper, or the object itself.
949      * <p>
950      * The returned object may not be of the required type but a defualt type
951      * which a later {@link org.appache.commons.beanutils.Converter} might be able
952      * to use (Probably a Sring).
953      *
954      * @param from The object which might have a valueFor method.
955      * @param required The required class.
956      * @return The value which may or may not be of the required class.
957      *
958      * @throws OddjobException If the given object
959      * getValue() method can't be invoked.
960      */

961     public static Object JavaDoc valueFor(Object JavaDoc from, Class JavaDoc required) throws ClassCastException JavaDoc, OddjobException {
962         if (required == null) {
963             throw new NullPointerException JavaDoc("Required class must not be null.");
964         }
965         if (from == null) {
966             return null;
967         }
968         
969         IntrospectionHelper ih = getHelper(from.getClass());
970         // if no value
971
if (ih.valueFor == null) {
972             return from;
973         }
974                 
975         // translate primitives
976
if (PRIMITIVE_TYPE_MAP.containsKey(required)) {
977             required = (Class JavaDoc) PRIMITIVE_TYPE_MAP.get(required);
978         }
979                 
980         try {
981             return ih.valueFor.invoke(from, new Object JavaDoc[] { required } );
982         } catch (InvocationTargetException JavaDoc e) {
983             Throwable JavaDoc t = e.getCause();
984             if (t instanceof ClassCastException JavaDoc) {
985                 throw (ClassCastException JavaDoc)t;
986             }
987             throw new ArooaException("Conversion failed converting from "
988                     + from.getClass() + " to " + required, t);
989         } catch (IllegalAccessException JavaDoc e) {
990             throw new ArooaException("Failed to get valueFor in " + from.getClass(),
991                 e);
992         }
993     }
994     
995     /**
996      * As with {@link valueFor(Object, Class)} but for the default type.
997      *
998      * @param from The object which might have a valueFor method.
999      * @return The value which may or may not be of the required class.
1000     *
1001     * @throws OddjobException If the given object
1002     * getValue() method can't be invoked.
1003     */

1004    public static Object JavaDoc valueFor(Object JavaDoc from) throws ClassCastException JavaDoc, OddjobException {
1005        return valueFor(from, Object JavaDoc.class);
1006    }
1007    
1008    /**
1009     * Useful debug method to dump the internals of this class.
1010     * @param out The stream to dump to.
1011     */

1012    public void dump(PrintStream JavaDoc out) {
1013        
1014        out.println("IntrospectionHelper for " + bean);
1015        out.println("Attibutes:");
1016        for (Iterator JavaDoc it = attributeTypes.entrySet().iterator(); it.hasNext(); ) {
1017            Map.Entry JavaDoc entry = (Map.Entry JavaDoc) it.next();
1018            out.println("\t" + entry.getKey() + " (" + entry.getValue() + ")");
1019        }
1020        Enumeration JavaDoc keys = nestedCreators.keys();
1021        Enumeration JavaDoc elements = nestedCreators.elements();
1022        out.println("Nested Creators:");
1023        while (keys.hasMoreElements()) {
1024            out.println("\t" + keys.nextElement() + "=" + elements.nextElement());
1025        }
1026        out.println("Nested Components:");
1027        for (Iterator JavaDoc it = nestedComponents.entrySet().iterator(); it.hasNext(); ) {
1028            Map.Entry JavaDoc entry = (Map.Entry JavaDoc) it.next();
1029            out.println("\t" + entry.getKey() + " (" + entry.getValue() + ")");
1030        }
1031    }
1032    
1033    
1034    public static Class JavaDoc selectBestMatchingClass(Class JavaDoc match, Class JavaDoc[] set) {
1035        int highestScore = 0;
1036        List JavaDoc bestMatches = new ArrayList JavaDoc();
1037        for (int i = 0; i < set.length; ++i) {
1038            if (set[i].isAssignableFrom(match)) {
1039                Class JavaDoc parent = set[i];
1040                int score = 1;
1041                while (true) {
1042                    if (parent.isArray()) {
1043                        parent = parent.getComponentType();
1044                        ++score;
1045                        continue;
1046                    }
1047                    parent = parent.getSuperclass();
1048                    if (parent != null) {
1049                        ++score;
1050                        continue;
1051                    }
1052                    break;
1053                }
1054                if (score == highestScore) {
1055                    bestMatches.add(set[i]);
1056                    highestScore = score;
1057                }
1058                if (score > highestScore) {
1059                    bestMatches.clear();
1060                    bestMatches.add(set[i]);
1061                    highestScore = score;
1062                }
1063            }
1064        }
1065        if (bestMatches.size() == 0) {
1066            return null;
1067        }
1068        if (bestMatches.size() > 1) {
1069            throw new ArooaException("Class " + match
1070                    + " has ambiguous matches.");
1071        }
1072        return (Class JavaDoc) bestMatches.get(0);
1073    }
1074}
1075
Popular Tags