1 package org.apache.commons.betwixt;
2
3 /*
4 * ====================================================================
5 *
6 * The Apache Software License, Version 1.1
7 *
8 * Copyright (c) 1999-2002 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 "The Jakarta Project", "Commons", 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
59 import java.beans.BeanDescriptor;
60 import java.beans.BeanInfo;
61 import java.beans.IntrospectionException;
62 import java.beans.Introspector;
63 import java.beans.PropertyDescriptor;
64 import java.net.URL;
65 import java.util.ArrayList;
66 import java.util.List;
67 import java.util.Map;
68
69 import org.apache.commons.betwixt.digester.XMLBeanInfoDigester;
70 import org.apache.commons.betwixt.digester.XMLIntrospectorHelper;
71 import org.apache.commons.betwixt.expression.EmptyExpression;
72 import org.apache.commons.betwixt.expression.IteratorExpression;
73 import org.apache.commons.betwixt.expression.StringExpression;
74 import org.apache.commons.betwixt.registry.DefaultXMLBeanInfoRegistry;
75 import org.apache.commons.betwixt.registry.XMLBeanInfoRegistry;
76 import org.apache.commons.betwixt.strategy.DefaultNameMapper;
77 import org.apache.commons.betwixt.strategy.DefaultPluralStemmer;
78 import org.apache.commons.betwixt.strategy.NameMapper;
79 import org.apache.commons.betwixt.strategy.PluralStemmer;
80 import org.apache.commons.logging.Log;
81 import org.apache.commons.logging.LogFactory;
82
83 /***
84 * <p><code>XMLIntrospector</code> an introspector of beans to create a
85 * XMLBeanInfo instance.</p>
86 *
87 * <p>By default, <code>XMLBeanInfo</code> caching is switched on.
88 * This means that the first time that a request is made for a <code>XMLBeanInfo</code>
89 * for a particular class, the <code>XMLBeanInfo</code> is cached.
90 * Later requests for the same class will return the cached value.</p>
91 *
92 * <p>Note :</p>
93 * <p>This class makes use of the <code>java.bean.Introspector</code>
94 * class, which contains a BeanInfoSearchPath. To make sure betwixt can
95 * do his work correctly, this searchpath is completely ignored during
96 * processing. The original values will be restored after processing finished
97 * </p>
98 *
99 * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
100 * @author <a href="mailto:martin@mvdb.net">Martin van den Bemt</a>
101 * @version $Id: XMLIntrospector.java,v 1.19 2003/01/09 22:34:07 rdonkin Exp $
102 */
103 public class XMLIntrospector {
104
105 /*** Log used for logging (Doh!) */
106 protected Log log = LogFactory.getLog( XMLIntrospector.class );
107
108 /*** should attributes or elements be used for primitive types */
109 private boolean attributesForPrimitives = false;
110
111 /*** should we wrap collections in an extra element? */
112 private boolean wrapCollectionsInElement = true;
113
114 /*** Maps classes to <code>XMLBeanInfo</code>'s */
115 private XMLBeanInfoRegistry registry = new DefaultXMLBeanInfoRegistry();
116
117 /*** Digester used to parse the XML descriptor files */
118 private XMLBeanInfoDigester digester;
119
120 // pluggable strategies
121
122 /*** The strategy used to detect matching singular and plural properties */
123 private PluralStemmer pluralStemmer;
124
125 /*** The strategy used to convert bean type names into element names */
126 private NameMapper elementNameMapper;
127
128 /***
129 * The strategy used to convert bean type names into attribute names
130 * It will default to the normal nameMapper.
131 */
132 private NameMapper attributeNameMapper;
133 /*** Should the existing bean info search path for java.reflect.Introspector be used? */
134 private boolean useBeanInfoSearchPath = false;
135
136 /*** Base constructor */
137 public XMLIntrospector() {
138 }
139
140 /***
141 * <p>Gets the current logging implementation. </p>
142 * @return the Log implementation which this class logs to
143 */
144 public Log getLog() {
145 return log;
146 }
147
148 /***
149 * <p>Sets the current logging implementation.</p>
150 * @param log the Log implementation to use for logging
151 */
152 public void setLog(Log log) {
153 this.log = log;
154 }
155
156 /***
157 * <p>Gets the current registry implementation.
158 * The registry is checked to see if it has an <code>XMLBeanInfo</code> for a class
159 * before introspecting.
160 * After standard introspection is complete, the instance will be passed to the registry.</p>
161 *
162 * <p>This allows finely grained control over the caching strategy.
163 * It also allows the standard introspection mechanism
164 * to be overridden on a per class basis.</p>
165 *
166 * @return the XMLBeanInfoRegistry currently used
167 */
168 public XMLBeanInfoRegistry getRegistry() {
169 return registry;
170 }
171
172 /***
173 * <p>Sets the <code>XMLBeanInfoRegistry</code> implementation.
174 * The registry is checked to see if it has an <code>XMLBeanInfo</code> for a class
175 * before introspecting.
176 * After standard introspection is complete, the instance will be passed to the registry.</p>
177 *
178 * <p>This allows finely grained control over the caching strategy.
179 * It also allows the standard introspection mechanism
180 * to be overridden on a per class basis.</p>
181 *
182 * @param registry the XMLBeanInfoRegistry to use
183 */
184 public void setRegistry(XMLBeanInfoRegistry registry) {
185 this.registry = registry;
186 }
187
188
189 /***
190 * Is <code>XMLBeanInfo</code> caching enabled?
191 *
192 * @deprecated replaced by XMlBeanInfoRegistry
193 * @return true if caching is enabled
194 */
195 public boolean isCachingEnabled() {
196 return true;
197 }
198
199 /***
200 * Set whether <code>XMLBeanInfo</code> caching should be enabled.
201 *
202 * @deprecated replaced by XMlBeanInfoRegistry
203 * @param cachingEnabled ignored
204 */
205 public void setCachingEnabled(boolean cachingEnabled) {
206 //
207 }
208
209 /***
210 * Flush existing cached <code>XMLBeanInfo</code>'s.
211 *
212 * @deprecated use flushable registry instead
213 */
214 public void flushCache() {}
215
216 /*** Create a standard <code>XMLBeanInfo</code> by introspection
217 * The actual introspection depends only on the <code>BeanInfo</code>
218 * associated with the bean.
219 *
220 * @param bean introspect this bean
221 * @return XMLBeanInfo describing bean-xml mapping
222 * @throws IntrospectionException when the bean introspection fails
223 */
224 public XMLBeanInfo introspect(Object bean) throws IntrospectionException {
225 if (log.isDebugEnabled()) {
226 log.debug( "Introspecting..." );
227 log.debug(bean);
228 }
229 return introspect( bean.getClass() );
230 }
231
232 /*** Create a standard <code>XMLBeanInfo</code> by introspection.
233 * The actual introspection depends only on the <code>BeanInfo</code>
234 * associated with the bean.
235 *
236 * @param aClass introspect this class
237 * @return XMLBeanInfo describing bean-xml mapping
238 * @throws IntrospectionException when the bean introspection fails
239 */
240 public XMLBeanInfo introspect(Class aClass) throws IntrospectionException {
241 // we first reset the beaninfo searchpath.
242 String[] searchPath = null;
243 if (!useBeanInfoSearchPath) {
244 searchPath = Introspector.getBeanInfoSearchPath();
245 Introspector.setBeanInfoSearchPath(new String[] { });
246 }
247
248 XMLBeanInfo xmlInfo = registry.get( aClass );
249
250 if (xmlInfo == null) {
251 // lets see if we can find an XML descriptor first
252 if ( log.isDebugEnabled() ) {
253 log.debug( "Attempting to lookup an XML descriptor for class: " + aClass );
254 }
255
256 xmlInfo = findByXMLDescriptor( aClass );
257 if ( xmlInfo == null ) {
258 BeanInfo info = Introspector.getBeanInfo( aClass );
259 xmlInfo = introspect( info );
260 }
261
262 if (xmlInfo != null) {
263 registry.put( aClass, xmlInfo );
264 }
265 } else {
266 log.trace("Used cached XMLBeanInfo.");
267 }
268
269 if (log.isTraceEnabled()) {
270 log.trace(xmlInfo);
271 }
272 if (!useBeanInfoSearchPath) {
273 // we restore the beaninfo searchpath.
274 Introspector.setBeanInfoSearchPath(searchPath);
275 }
276
277 return xmlInfo;
278 }
279
280 /*** Create a standard <code>XMLBeanInfo</code> by introspection.
281 * The actual introspection depends only on the <code>BeanInfo</code>
282 * associated with the bean.
283 *
284 * @param beanInfo the BeanInfo the xml-bean mapping is based on
285 * @return XMLBeanInfo describing bean-xml mapping
286 * @throws IntrospectionException when the bean introspection fails
287 */
288 public XMLBeanInfo introspect(BeanInfo beanInfo) throws IntrospectionException {
289 XMLBeanInfo answer = createXMLBeanInfo( beanInfo );
290
291 BeanDescriptor beanDescriptor = beanInfo.getBeanDescriptor();
292 Class beanClass = beanDescriptor.getBeanClass();
293
294 ElementDescriptor elementDescriptor = new ElementDescriptor();
295 elementDescriptor.setLocalName(
296 getElementNameMapper().mapTypeToElementName( beanDescriptor.getName() ) );
297 elementDescriptor.setPropertyType( beanInfo.getBeanDescriptor().getBeanClass() );
298
299 if (log.isTraceEnabled()) {
300 log.trace(elementDescriptor);
301 }
302
303 // add default string value for primitive types
304 if ( isPrimitiveType( beanClass ) ) {
305 elementDescriptor.setTextExpression( StringExpression.getInstance() );
306 elementDescriptor.setPrimitiveType(true);
307 } else if ( isLoopType( beanClass ) ) {
308 ElementDescriptor loopDescriptor = new ElementDescriptor();
309 loopDescriptor.setContextExpression(
310 new IteratorExpression( EmptyExpression.getInstance() )
311 );
312 if ( Map.class.isAssignableFrom( beanClass ) ) {
313 loopDescriptor.setQualifiedName( "entry" );
314 }
315 elementDescriptor.setElementDescriptors( new ElementDescriptor[] { loopDescriptor } );
316
317 /*
318 elementDescriptor.setContextExpression(
319 new IteratorExpression( EmptyExpression.getInstance() )
320 );
321 */
322 } else {
323 List elements = new ArrayList();
324 List attributes = new ArrayList();
325
326 addProperties( beanInfo, elements, attributes );
327
328 BeanInfo[] additionals = beanInfo.getAdditionalBeanInfo();
329 if ( additionals != null ) {
330 for ( int i = 0, size = additionals.length; i < size; i++ ) {
331 BeanInfo otherInfo = additionals[i];
332 addProperties( otherInfo, elements, attributes );
333 }
334 }
335
336 int size = elements.size();
337 if ( size > 0 ) {
338 ElementDescriptor[] descriptors = new ElementDescriptor[size];
339 elements.toArray( descriptors );
340 elementDescriptor.setElementDescriptors( descriptors );
341 }
342 size = attributes.size();
343 if ( size > 0 ) {
344 AttributeDescriptor[] descriptors = new AttributeDescriptor[size];
345 attributes.toArray( descriptors );
346 elementDescriptor.setAttributeDescriptors( descriptors );
347 }
348 }
349
350 answer.setElementDescriptor( elementDescriptor );
351
352 // default any addProperty() methods
353 XMLIntrospectorHelper.defaultAddMethods( this, elementDescriptor, beanClass );
354
355 return answer;
356 }
357
358
359 // Properties
360 //-------------------------------------------------------------------------
361
362 /***
363 * Should attributes (or elements) be used for primitive types.
364 * @return true if primitive types will be mapped to attributes in the introspection
365 */
366 public boolean isAttributesForPrimitives() {
367 return attributesForPrimitives;
368 }
369
370 /***
371 * Set whether attributes (or elements) should be used for primitive types.
372 * @param attributesForPrimitives pass trus to map primitives to attributes,
373 * pass false to map primitives to elements
374 */
375 public void setAttributesForPrimitives(boolean attributesForPrimitives) {
376 this.attributesForPrimitives = attributesForPrimitives;
377 }
378
379 /***
380 * Should collections be wrapped in an extra element?
381 *
382 * @return whether we should we wrap collections in an extra element?
383 */
384 public boolean isWrapCollectionsInElement() {
385 return wrapCollectionsInElement;
386 }
387
388 /***
389 * Sets whether we should we wrap collections in an extra element.
390 *
391 * @param wrapCollectionsInElement pass true if collections should be wrapped in a
392 * parent element
393 */
394 public void setWrapCollectionsInElement(boolean wrapCollectionsInElement) {
395 this.wrapCollectionsInElement = wrapCollectionsInElement;
396 }
397
398 /***
399 * Get singular and plural matching strategy.
400 *
401 * @return the strategy used to detect matching singular and plural properties
402 */
403 public PluralStemmer getPluralStemmer() {
404 if ( pluralStemmer == null ) {
405 pluralStemmer = createPluralStemmer();
406 }
407 return pluralStemmer;
408 }
409
410 /***
411 * Sets the strategy used to detect matching singular and plural properties
412 *
413 * @param pluralStemmer the PluralStemmer used to match singular and plural
414 */
415 public void setPluralStemmer(PluralStemmer pluralStemmer) {
416 this.pluralStemmer = pluralStemmer;
417 }
418
419 /***
420 * Gets the name mapper strategy.
421 *
422 * @return the strategy used to convert bean type names into element names
423 * @deprecated getNameMapper is split up in
424 * {@link #getElementNameMapper()} and {@link #getAttributeNameMapper()}
425 */
426 public NameMapper getNameMapper() {
427 return getElementNameMapper();
428 }
429
430 /***
431 * Sets the strategy used to convert bean type names into element names
432 * @param nameMapper the NameMapper strategy to be used
433 * @deprecated setNameMapper is split up in
434 * {@link #setElementNameMapper(NameMapper)} and {@link #setAttributeNameMapper(NameMapper)}
435 */
436 public void setNameMapper(NameMapper nameMapper) {
437 setElementNameMapper(nameMapper);
438 }
439
440
441 /***
442 * Gets the name mapping strategy used to convert bean names into elements.
443 *
444 * @return the strategy used to convert bean type names into element
445 * names. If no element mapper is currently defined then a default one is created.
446 */
447 public NameMapper getElementNameMapper() {
448 if ( elementNameMapper == null ) {
449 elementNameMapper = createNameMapper();
450 }
451 return elementNameMapper;
452 }
453
454 /***
455 * Sets the strategy used to convert bean type names into element names
456 * @param nameMapper the NameMapper to use for the conversion
457 */
458 public void setElementNameMapper(NameMapper nameMapper) {
459 this.elementNameMapper = nameMapper;
460 }
461
462
463 /***
464 * Gets the name mapping strategy used to convert bean names into attributes.
465 *
466 * @return the strategy used to convert bean type names into attribute
467 * names. If no attributeNamemapper is known, it will default to the ElementNameMapper
468 */
469 public NameMapper getAttributeNameMapper() {
470 if (attributeNameMapper == null) {
471 attributeNameMapper = createNameMapper();
472 }
473 return attributeNameMapper;
474 }
475
476
477 /***
478 * Sets the strategy used to convert bean type names into attribute names
479 * @param nameMapper the NameMapper to use for the convertion
480 */
481 public void setAttributeNameMapper(NameMapper nameMapper) {
482 this.attributeNameMapper = nameMapper;
483 }
484
485
486
487
488
489
490 // Implementation methods
491 //-------------------------------------------------------------------------
492
493 /***
494 * A Factory method to lazily create a new strategy
495 * to detect matching singular and plural properties.
496 *
497 * @return new defualt PluralStemmer implementation
498 */
499 protected PluralStemmer createPluralStemmer() {
500 return new DefaultPluralStemmer();
501 }
502
503 /***
504 * A Factory method to lazily create a strategy
505 * used to convert bean type names into element names.
506 *
507 * @return new default NameMapper implementation
508 */
509 protected NameMapper createNameMapper() {
510 return new DefaultNameMapper();
511 }
512
513 /***
514 * Attempt to lookup the XML descriptor for the given class using the
515 * classname + ".betwixt" using the same ClassLoader used to load the class
516 * or return null if it could not be loaded
517 *
518 * @param aClass digester .betwixt file for this class
519 * @return XMLBeanInfo digested from the .betwixt file if one can be found.
520 * Otherwise null.
521 */
522 protected synchronized XMLBeanInfo findByXMLDescriptor( Class aClass ) {
523 // trim the package name
524 String name = aClass.getName();
525 int idx = name.lastIndexOf( '.' );
526 if ( idx >= 0 ) {
527 name = name.substring( idx + 1 );
528 }
529 name += ".betwixt";
530
531 URL url = aClass.getResource( name );
532 if ( url != null ) {
533 try {
534 String urlText = url.toString();
535 if ( log.isDebugEnabled( )) {
536 log.debug( "Parsing Betwixt XML descriptor: " + urlText );
537 }
538 // synchronized method so this digester is only used by
539 // one thread at once
540 if ( digester == null ) {
541 digester = new XMLBeanInfoDigester();
542 digester.setXMLIntrospector( this );
543 }
544 digester.setBeanClass( aClass );
545 return (XMLBeanInfo) digester.parse( urlText );
546 } catch (Exception e) {
547 log.warn( "Caught exception trying to parse: " + name, e );
548 }
549 }
550
551 if ( log.isTraceEnabled() ) {
552 log.trace( "Could not find betwixt file " + name );
553 }
554 return null;
555 }
556
557 /***
558 * Loop through properties and process each one
559 *
560 * @param beanInfo the BeanInfo whose properties will be processed
561 * @param elements ElementDescriptor list to which elements will be added
562 * @param attributes AttributeDescriptor list to which attributes will be added
563 * @throws IntrospectionException if the bean introspection fails
564 */
565 protected void addProperties(
566 BeanInfo beanInfo,
567 List elements,
568 List attributes)
569 throws
570 IntrospectionException {
571 PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
572 if ( descriptors != null ) {
573 for ( int i = 0, size = descriptors.length; i < size; i++ ) {
574 addProperty(beanInfo, descriptors[i], elements, attributes);
575 }
576 }
577 if (log.isTraceEnabled()) {
578 log.trace(elements);
579 log.trace(attributes);
580 }
581 }
582
583 /***
584 * Process a property.
585 * Go through and work out whether it's a loop property, a primitive or a standard.
586 * The class property is ignored.
587 *
588 * @param beanInfo the BeanInfo whose property is being processed
589 * @param propertyDescriptor the PropertyDescriptor to process
590 * @param elements ElementDescriptor list to which elements will be added
591 * @param attributes AttributeDescriptor list to which attributes will be added
592 * @throws IntrospectionException if the bean introspection fails
593 */
594 protected void addProperty(
595 BeanInfo beanInfo,
596 PropertyDescriptor propertyDescriptor,
597 List elements,
598 List attributes)
599 throws
600 IntrospectionException {
601 NodeDescriptor nodeDescriptor = XMLIntrospectorHelper
602 .createDescriptor(propertyDescriptor,
603 isAttributesForPrimitives(),
604 this);
605 if (nodeDescriptor == null) {
606 return;
607 }
608 if (nodeDescriptor instanceof ElementDescriptor) {
609 elements.add(nodeDescriptor);
610 } else {
611 attributes.add(nodeDescriptor);
612 }
613 }
614
615 /***
616 * Factory method to create XMLBeanInfo instances
617 *
618 * @param beanInfo the BeanInfo from which the XMLBeanInfo will be created
619 * @return XMLBeanInfo describing the bean-xml mapping
620 */
621 protected XMLBeanInfo createXMLBeanInfo( BeanInfo beanInfo ) {
622 XMLBeanInfo answer = new XMLBeanInfo( beanInfo.getBeanDescriptor().getBeanClass() );
623 return answer;
624 }
625
626 /***
627 * Is this class a loop?
628 *
629 * @param type the Class to test
630 * @return true if the type is a loop type
631 */
632 public boolean isLoopType(Class type) {
633 return XMLIntrospectorHelper.isLoopType(type);
634 }
635
636
637 /***
638 * Is this class a primitive?
639 * @param type the Class to test
640 * @return true for primitive types
641 */
642 public boolean isPrimitiveType(Class type) {
643 return XMLIntrospectorHelper.isPrimitiveType(type);
644 }
645 /***
646 * Should the original <code>java.reflect.Introspector</code> bean info search path be used?
647 * By default it will be false.
648 *
649 * @return boolean if the beanInfoSearchPath should be used.
650 */
651 public boolean useBeanInfoSearchPath() {
652 return useBeanInfoSearchPath;
653 }
654
655 /***
656 * Specifies if you want to use the beanInfoSearchPath
657 * @see java.beans.Introspector for more details
658 * @param useBeanInfoSearchPath
659 */
660 public void setUseBeanInfoSearchPath(boolean useBeanInfoSearchPath) {
661 this.useBeanInfoSearchPath = useBeanInfoSearchPath;
662 }
663
664 }
This page was automatically generated by Maven