0001: package org.apache.commons.betwixt;
0002:
0003: /*
0004: * Licensed to the Apache Software Foundation (ASF) under one or more
0005: * contributor license agreements. See the NOTICE file distributed with
0006: * this work for additional information regarding copyright ownership.
0007: * The ASF licenses this file to You under the Apache License, Version 2.0
0008: * (the "License"); you may not use this file except in compliance with
0009: * the License. You may obtain a copy of the License at
0010: *
0011: * http://www.apache.org/licenses/LICENSE-2.0
0012: *
0013: * Unless required by applicable law or agreed to in writing, software
0014: * distributed under the License is distributed on an "AS IS" BASIS,
0015: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0016: * See the License for the specific language governing permissions and
0017: * limitations under the License.
0018: */
0019:
0020: import java.beans.BeanDescriptor;
0021: import java.beans.BeanInfo;
0022: import java.beans.IntrospectionException;
0023: import java.beans.Introspector;
0024: import java.beans.PropertyDescriptor;
0025: import java.io.IOException;
0026: import java.lang.reflect.Method;
0027: import java.net.URL;
0028: import java.util.ArrayList;
0029: import java.util.HashMap;
0030: import java.util.Iterator;
0031: import java.util.List;
0032: import java.util.Map;
0033: import java.util.Set;
0034:
0035: import org.apache.commons.beanutils.DynaBean;
0036: import org.apache.commons.beanutils.DynaClass;
0037: import org.apache.commons.beanutils.DynaProperty;
0038: import org.apache.commons.betwixt.digester.MultiMappingBeanInfoDigester;
0039: import org.apache.commons.betwixt.digester.XMLBeanInfoDigester;
0040: import org.apache.commons.betwixt.digester.XMLIntrospectorHelper;
0041: import org.apache.commons.betwixt.expression.CollectionUpdater;
0042: import org.apache.commons.betwixt.expression.EmptyExpression;
0043: import org.apache.commons.betwixt.expression.IteratorExpression;
0044: import org.apache.commons.betwixt.expression.MapEntryAdder;
0045: import org.apache.commons.betwixt.expression.MethodUpdater;
0046: import org.apache.commons.betwixt.expression.StringExpression;
0047: import org.apache.commons.betwixt.registry.DefaultXMLBeanInfoRegistry;
0048: import org.apache.commons.betwixt.registry.PolymorphicReferenceResolver;
0049: import org.apache.commons.betwixt.registry.XMLBeanInfoRegistry;
0050: import org.apache.commons.betwixt.strategy.ClassNormalizer;
0051: import org.apache.commons.betwixt.strategy.DefaultNameMapper;
0052: import org.apache.commons.betwixt.strategy.DefaultPluralStemmer;
0053: import org.apache.commons.betwixt.strategy.NameMapper;
0054: import org.apache.commons.betwixt.strategy.PluralStemmer;
0055: import org.apache.commons.betwixt.strategy.TypeBindingStrategy;
0056: import org.apache.commons.logging.Log;
0057: import org.apache.commons.logging.LogFactory;
0058: import org.xml.sax.InputSource;
0059: import org.xml.sax.SAXException;
0060:
0061: /**
0062: * <p><code>XMLIntrospector</code> an introspector of beans to create a
0063: * XMLBeanInfo instance.</p>
0064: *
0065: * <p>By default, <code>XMLBeanInfo</code> caching is switched on.
0066: * This means that the first time that a request is made for a <code>XMLBeanInfo</code>
0067: * for a particular class, the <code>XMLBeanInfo</code> is cached.
0068: * Later requests for the same class will return the cached value.</p>
0069: *
0070: * <p>Note :</p>
0071: * <p>This class makes use of the <code>java.bean.Introspector</code>
0072: * class, which contains a BeanInfoSearchPath. To make sure betwixt can
0073: * do his work correctly, this searchpath is completely ignored during
0074: * processing. The original values will be restored after processing finished
0075: * </p>
0076: *
0077: * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
0078: * @author <a href="mailto:martin@mvdb.net">Martin van den Bemt</a>
0079: */
0080: public class XMLIntrospector {
0081: /**
0082: * Log used for logging (Doh!)
0083: * @deprecated 0.6 use the {@link #getLog()} property instead
0084: */
0085: protected Log log = LogFactory.getLog(XMLIntrospector.class);
0086:
0087: /** Maps classes to <code>XMLBeanInfo</code>'s */
0088: private XMLBeanInfoRegistry registry;
0089:
0090: /** Digester used to parse the XML descriptor files */
0091: private XMLBeanInfoDigester digester;
0092:
0093: /** Digester used to parse the multi-mapping XML descriptor files */
0094: private MultiMappingBeanInfoDigester multiMappingdigester;
0095:
0096: /** Configuration to be used for introspection*/
0097: private IntrospectionConfiguration configuration;
0098:
0099: /**
0100: * Resolves polymorphic references.
0101: * Though this is used only at bind time,
0102: * it is typically tightly couple to the xml registry.
0103: * It is therefore convenient to keep both references together.
0104: */
0105: private PolymorphicReferenceResolver polymorphicReferenceResolver;
0106:
0107: /** Base constructor */
0108: public XMLIntrospector() {
0109: this (new IntrospectionConfiguration());
0110: }
0111:
0112: /**
0113: * Construct allows a custom configuration to be set on construction.
0114: * This allows <code>IntrospectionConfiguration</code> subclasses
0115: * to be easily used.
0116: * @param configuration IntrospectionConfiguration, not null
0117: */
0118: public XMLIntrospector(IntrospectionConfiguration configuration) {
0119: setConfiguration(configuration);
0120: DefaultXMLBeanInfoRegistry defaultRegistry = new DefaultXMLBeanInfoRegistry();
0121: setRegistry(defaultRegistry);
0122: setPolymorphicReferenceResolver(defaultRegistry);
0123: }
0124:
0125: // Properties
0126: //-------------------------------------------------------------------------
0127:
0128: /**
0129: * <p>Gets the current logging implementation. </p>
0130: * @return the Log implementation which this class logs to
0131: */
0132: public Log getLog() {
0133: return getConfiguration().getIntrospectionLog();
0134: }
0135:
0136: /**
0137: * <p>Sets the current logging implementation.</p>
0138: * @param log the Log implementation to use for logging
0139: */
0140: public void setLog(Log log) {
0141: getConfiguration().setIntrospectionLog(log);
0142: }
0143:
0144: /**
0145: * <p>Gets the current registry implementation.
0146: * The registry is checked to see if it has an <code>XMLBeanInfo</code> for a class
0147: * before introspecting.
0148: * After standard introspection is complete, the instance will be passed to the registry.</p>
0149: *
0150: * <p>This allows finely grained control over the caching strategy.
0151: * It also allows the standard introspection mechanism
0152: * to be overridden on a per class basis.</p>
0153: *
0154: * @return the XMLBeanInfoRegistry currently used
0155: */
0156: public XMLBeanInfoRegistry getRegistry() {
0157: return registry;
0158: }
0159:
0160: /**
0161: * <p>Sets the <code>XMLBeanInfoRegistry</code> implementation.
0162: * The registry is checked to see if it has an <code>XMLBeanInfo</code> for a class
0163: * before introspecting.
0164: * After standard introspection is complete, the instance will be passed to the registry.</p>
0165: *
0166: * <p>This allows finely grained control over the caching strategy.
0167: * It also allows the standard introspection mechanism
0168: * to be overridden on a per class basis.</p>
0169: *
0170: * <p><strong>Note</strong> when using polymophic mapping with a custom
0171: * registry, a call to
0172: * {@link #setPolymorphicReferenceResolver(PolymorphicReferenceResolver)}
0173: * may be necessary.
0174: * </p>
0175: * @param registry the XMLBeanInfoRegistry to use
0176: */
0177: public void setRegistry(XMLBeanInfoRegistry registry) {
0178: this .registry = registry;
0179: }
0180:
0181: /**
0182: * Gets the configuration to be used for introspection.
0183: * The various introspection-time strategies
0184: * and configuration variables have been consolidated as properties
0185: * of this bean.
0186: * This allows the configuration to be more easily shared.
0187: * @return IntrospectionConfiguration, not null
0188: */
0189: public IntrospectionConfiguration getConfiguration() {
0190: return configuration;
0191: }
0192:
0193: /**
0194: * Sets the configuration to be used for introspection.
0195: * The various introspection-time strategies
0196: * and configuration variables have been consolidated as properties
0197: * of this bean.
0198: * This allows the configuration to be more easily shared.
0199: * @param configuration IntrospectionConfiguration, not null
0200: */
0201: public void setConfiguration(
0202: IntrospectionConfiguration configuration) {
0203: this .configuration = configuration;
0204: }
0205:
0206: /**
0207: * Gets the <code>ClassNormalizer</code> strategy.
0208: * This is used to determine the Class to be introspected
0209: * (the normalized Class).
0210: *
0211: * @return the <code>ClassNormalizer</code> used to determine the Class to be introspected
0212: * for a given Object.
0213: * @deprecated 0.6 use getConfiguration().getClassNormalizer
0214: * @since 0.5
0215: */
0216: public ClassNormalizer getClassNormalizer() {
0217: return getConfiguration().getClassNormalizer();
0218: }
0219:
0220: /**
0221: * Sets the <code>ClassNormalizer</code> strategy.
0222: * This is used to determine the Class to be introspected
0223: * (the normalized Class).
0224: *
0225: * @param classNormalizer the <code>ClassNormalizer</code> to be used to determine
0226: * the Class to be introspected for a given Object.
0227: * @deprecated 0.6 use getConfiguration().setClassNormalizer
0228: * @since 0.5
0229: *
0230: */
0231: public void setClassNormalizer(ClassNormalizer classNormalizer) {
0232: getConfiguration().setClassNormalizer(classNormalizer);
0233: }
0234:
0235: /**
0236: * <p>Gets the resolver for polymorphic references.</p>
0237: * <p>
0238: * Though this is used only at bind time,
0239: * it is typically tightly couple to the xml registry.
0240: * It is therefore convenient to keep both references together.
0241: * </p>
0242: * <p><strong>Note:</strong> though the implementation is
0243: * set initially to the default registry,
0244: * this reference is not updated when {@link #setRegistry(XMLBeanInfoRegistry)}
0245: * is called. Therefore, a call to {@link #setPolymorphicReferenceResolver(PolymorphicReferenceResolver)}
0246: * with the instance may be necessary.
0247: * </p>
0248: * @since 0.7
0249: * @return <code>PolymorphicReferenceResolver</code>, not null
0250: */
0251: public PolymorphicReferenceResolver getPolymorphicReferenceResolver() {
0252: return polymorphicReferenceResolver;
0253: }
0254:
0255: /**
0256: * <p>Sets the resolver for polymorphic references.</p>
0257: * <p>
0258: * Though this is used only at bind time,
0259: * it is typically tightly couple to the xml registry.
0260: * It is therefore convenient to keep both references together.
0261: * </p>
0262: * <p><strong>Note:</strong> though the implementation is
0263: * set initially to the default registry,
0264: * this reference is not updated when {@link #setRegistry(XMLBeanInfoRegistry)}
0265: * is called. Therefore, a call to {@link #setPolymorphicReferenceResolver(PolymorphicReferenceResolver)}
0266: * with the instance may be necessary.
0267: * </p>
0268: * @since 0.7
0269: * @param polymorphicReferenceResolver The polymorphicReferenceResolver to set.
0270: */
0271: public void setPolymorphicReferenceResolver(
0272: PolymorphicReferenceResolver polymorphicReferenceResolver) {
0273: this .polymorphicReferenceResolver = polymorphicReferenceResolver;
0274: }
0275:
0276: /**
0277: * Is <code>XMLBeanInfo</code> caching enabled?
0278: *
0279: * @deprecated 0.5 replaced by XMlBeanInfoRegistry
0280: * @return true if caching is enabled
0281: */
0282: public boolean isCachingEnabled() {
0283: return true;
0284: }
0285:
0286: /**
0287: * Set whether <code>XMLBeanInfo</code> caching should be enabled.
0288: *
0289: * @deprecated 0.5 replaced by XMlBeanInfoRegistry
0290: * @param cachingEnabled ignored
0291: */
0292: public void setCachingEnabled(boolean cachingEnabled) {
0293: //
0294: }
0295:
0296: /**
0297: * Should attributes (or elements) be used for primitive types.
0298: * @return true if primitive types will be mapped to attributes in the introspection
0299: * @deprecated 0.6 use getConfiguration().isAttributesForPrimitives
0300: */
0301: public boolean isAttributesForPrimitives() {
0302: return getConfiguration().isAttributesForPrimitives();
0303: }
0304:
0305: /**
0306: * Set whether attributes (or elements) should be used for primitive types.
0307: * @param attributesForPrimitives pass trus to map primitives to attributes,
0308: * pass false to map primitives to elements
0309: * @deprecated 0.6 use getConfiguration().setAttributesForPrimitives
0310: */
0311: public void setAttributesForPrimitives(
0312: boolean attributesForPrimitives) {
0313: getConfiguration().setAttributesForPrimitives(
0314: attributesForPrimitives);
0315: }
0316:
0317: /**
0318: * Should collections be wrapped in an extra element?
0319: *
0320: * @return whether we should we wrap collections in an extra element?
0321: * @deprecated 0.6 use getConfiguration().isWrapCollectionsInElement
0322: */
0323: public boolean isWrapCollectionsInElement() {
0324: return getConfiguration().isWrapCollectionsInElement();
0325: }
0326:
0327: /**
0328: * Sets whether we should we wrap collections in an extra element.
0329: *
0330: * @param wrapCollectionsInElement pass true if collections should be wrapped in a
0331: * parent element
0332: * @deprecated 0.6 use getConfiguration().setWrapCollectionsInElement
0333: */
0334: public void setWrapCollectionsInElement(
0335: boolean wrapCollectionsInElement) {
0336: getConfiguration().setWrapCollectionsInElement(
0337: wrapCollectionsInElement);
0338: }
0339:
0340: /**
0341: * Get singular and plural matching strategy.
0342: *
0343: * @return the strategy used to detect matching singular and plural properties
0344: * @deprecated 0.6 use getConfiguration().getPluralStemmer
0345: */
0346: public PluralStemmer getPluralStemmer() {
0347: return getConfiguration().getPluralStemmer();
0348: }
0349:
0350: /**
0351: * Sets the strategy used to detect matching singular and plural properties
0352: *
0353: * @param pluralStemmer the PluralStemmer used to match singular and plural
0354: * @deprecated 0.6 use getConfiguration().setPluralStemmer
0355: */
0356: public void setPluralStemmer(PluralStemmer pluralStemmer) {
0357: getConfiguration().setPluralStemmer(pluralStemmer);
0358: }
0359:
0360: /**
0361: * Gets the name mapper strategy.
0362: *
0363: * @return the strategy used to convert bean type names into element names
0364: * @deprecated 0.5 getNameMapper is split up in
0365: * {@link #getElementNameMapper()} and {@link #getAttributeNameMapper()}
0366: */
0367: public NameMapper getNameMapper() {
0368: return getElementNameMapper();
0369: }
0370:
0371: /**
0372: * Sets the strategy used to convert bean type names into element names
0373: * @param nameMapper the NameMapper strategy to be used
0374: * @deprecated 0.5 setNameMapper is split up in
0375: * {@link #setElementNameMapper(NameMapper)} and {@link #setAttributeNameMapper(NameMapper)}
0376: */
0377: public void setNameMapper(NameMapper nameMapper) {
0378: setElementNameMapper(nameMapper);
0379: }
0380:
0381: /**
0382: * Gets the name mapping strategy used to convert bean names into elements.
0383: *
0384: * @return the strategy used to convert bean type names into element
0385: * names. If no element mapper is currently defined then a default one is created.
0386: * @deprecated 0.6 use getConfiguration().getElementNameMapper
0387: */
0388: public NameMapper getElementNameMapper() {
0389: return getConfiguration().getElementNameMapper();
0390: }
0391:
0392: /**
0393: * Sets the strategy used to convert bean type names into element names
0394: * @param nameMapper the NameMapper to use for the conversion
0395: * @deprecated 0.6 use getConfiguration().setElementNameMapper
0396: */
0397: public void setElementNameMapper(NameMapper nameMapper) {
0398: getConfiguration().setElementNameMapper(nameMapper);
0399: }
0400:
0401: /**
0402: * Gets the name mapping strategy used to convert bean names into attributes.
0403: *
0404: * @return the strategy used to convert bean type names into attribute
0405: * names. If no attributeNamemapper is known, it will default to the ElementNameMapper
0406: * @deprecated 0.6 getConfiguration().getAttributeNameMapper
0407: */
0408: public NameMapper getAttributeNameMapper() {
0409: return getConfiguration().getAttributeNameMapper();
0410: }
0411:
0412: /**
0413: * Sets the strategy used to convert bean type names into attribute names
0414: * @param nameMapper the NameMapper to use for the convertion
0415: * @deprecated 0.6 use getConfiguration().setAttributeNameMapper
0416: */
0417: public void setAttributeNameMapper(NameMapper nameMapper) {
0418: getConfiguration().setAttributeNameMapper(nameMapper);
0419: }
0420:
0421: /**
0422: * Should the original <code>java.reflect.Introspector</code> bean info search path be used?
0423: * By default it will be false.
0424: *
0425: * @return boolean if the beanInfoSearchPath should be used.
0426: * @deprecated 0.6 use getConfiguration().useBeanInfoSearchPath
0427: */
0428: public boolean useBeanInfoSearchPath() {
0429: return getConfiguration().useBeanInfoSearchPath();
0430: }
0431:
0432: /**
0433: * Specifies if you want to use the beanInfoSearchPath
0434: * @see java.beans.Introspector for more details
0435: * @param useBeanInfoSearchPath
0436: * @deprecated 0.6 use getConfiguration().setUseBeanInfoSearchPath
0437: */
0438: public void setUseBeanInfoSearchPath(boolean useBeanInfoSearchPath) {
0439: getConfiguration().setUseBeanInfoSearchPath(
0440: useBeanInfoSearchPath);
0441: }
0442:
0443: // Methods
0444: //-------------------------------------------------------------------------
0445:
0446: /**
0447: * Flush existing cached <code>XMLBeanInfo</code>'s.
0448: *
0449: * @deprecated 0.5 use flushable registry instead
0450: */
0451: public void flushCache() {
0452: }
0453:
0454: /** Create a standard <code>XMLBeanInfo</code> by introspection
0455: * The actual introspection depends only on the <code>BeanInfo</code>
0456: * associated with the bean.
0457: *
0458: * @param bean introspect this bean
0459: * @return XMLBeanInfo describing bean-xml mapping
0460: * @throws IntrospectionException when the bean introspection fails
0461: */
0462: public XMLBeanInfo introspect(Object bean)
0463: throws IntrospectionException {
0464: if (getLog().isDebugEnabled()) {
0465: getLog().debug("Introspecting...");
0466: getLog().debug(bean);
0467: }
0468:
0469: if (bean instanceof DynaBean) {
0470: // allow DynaBean implementations to be overridden by .betwixt files
0471: XMLBeanInfo xmlBeanInfo = findByXMLDescriptor(bean
0472: .getClass());
0473: if (xmlBeanInfo != null) {
0474: return xmlBeanInfo;
0475: }
0476: // this is DynaBean use the DynaClass for introspection
0477: return introspect(((DynaBean) bean).getDynaClass());
0478:
0479: } else {
0480: // normal bean so normal introspection
0481: Class normalClass = getClassNormalizer()
0482: .getNormalizedClass(bean);
0483: return introspect(normalClass);
0484: }
0485: }
0486:
0487: /**
0488: * Creates XMLBeanInfo by reading the DynaProperties of a DynaBean.
0489: * Customizing DynaBeans using betwixt is not supported.
0490: *
0491: * @param dynaClass the DynaBean to introspect
0492: *
0493: * @return XMLBeanInfo for the DynaClass
0494: */
0495: public XMLBeanInfo introspect(DynaClass dynaClass) {
0496:
0497: // for now this method does not do much, since XMLBeanInfoRegistry cannot
0498: // use a DynaClass as a key
0499: // TODO: add caching for DynaClass XMLBeanInfo
0500: // need to work out if this is possible
0501:
0502: // this line allows subclasses to change creation strategy
0503: XMLBeanInfo xmlInfo = createXMLBeanInfo(dynaClass);
0504:
0505: // populate the created info with
0506: DynaClassBeanType beanClass = new DynaClassBeanType(dynaClass);
0507: populate(xmlInfo, beanClass);
0508:
0509: return xmlInfo;
0510: }
0511:
0512: /**
0513: * <p>Introspects the given <code>Class</code> using the dot betwixt
0514: * document in the given <code>InputSource</code>.
0515: * </p>
0516: * <p>
0517: * <strong>Note:</strong> that the given mapping will <em>not</em>
0518: * be registered by this method. Use {@link #register(Class, InputSource)}
0519: * instead.
0520: * </p>
0521: * @since 0.7
0522: * @param aClass <code>Class</code>, not null
0523: * @param source <code>InputSource</code>, not null
0524: * @return <code>XMLBeanInfo</code> describing the mapping.
0525: * @throws SAXException when the input source cannot be parsed
0526: * @throws IOException
0527: */
0528: public synchronized XMLBeanInfo introspect(Class aClass,
0529: InputSource source) throws IOException, SAXException {
0530: // need to synchronize since we only use one instance and SAX is essentially one thread only
0531: configureDigester(aClass);
0532: XMLBeanInfo result = (XMLBeanInfo) digester.parse(source);
0533: return result;
0534: }
0535:
0536: /** Create a standard <code>XMLBeanInfo</code> by introspection.
0537: * The actual introspection depends only on the <code>BeanInfo</code>
0538: * associated with the bean.
0539: *
0540: * @param aClass introspect this class
0541: * @return XMLBeanInfo describing bean-xml mapping
0542: * @throws IntrospectionException when the bean introspection fails
0543: */
0544: public XMLBeanInfo introspect(Class aClass)
0545: throws IntrospectionException {
0546: // we first reset the beaninfo searchpath.
0547: String[] searchPath = null;
0548: if (!getConfiguration().useBeanInfoSearchPath()) {
0549: try {
0550: searchPath = Introspector.getBeanInfoSearchPath();
0551: Introspector.setBeanInfoSearchPath(new String[] {});
0552: } catch (SecurityException e) {
0553: // this call may fail in some environments
0554: getLog()
0555: .warn(
0556: "Security manager does not allow bean info search path to be set");
0557: getLog()
0558: .debug(
0559: "Security exception whilst setting bean info search page",
0560: e);
0561: }
0562: }
0563:
0564: XMLBeanInfo xmlInfo = registry.get(aClass);
0565:
0566: if (xmlInfo == null) {
0567: // lets see if we can find an XML descriptor first
0568: if (getLog().isDebugEnabled()) {
0569: getLog().debug(
0570: "Attempting to lookup an XML descriptor for class: "
0571: + aClass);
0572: }
0573:
0574: xmlInfo = findByXMLDescriptor(aClass);
0575: if (xmlInfo == null) {
0576: BeanInfo info;
0577: if (getConfiguration().ignoreAllBeanInfo()) {
0578: info = Introspector.getBeanInfo(aClass,
0579: Introspector.IGNORE_ALL_BEANINFO);
0580: } else {
0581: info = Introspector.getBeanInfo(aClass);
0582: }
0583: xmlInfo = introspect(info);
0584: }
0585:
0586: if (xmlInfo != null) {
0587: registry.put(aClass, xmlInfo);
0588: }
0589: } else {
0590: getLog().trace("Used cached XMLBeanInfo.");
0591: }
0592:
0593: if (getLog().isTraceEnabled()) {
0594: getLog().trace(xmlInfo);
0595: }
0596: if (!getConfiguration().useBeanInfoSearchPath()
0597: && searchPath != null) {
0598: try {
0599: // we restore the beaninfo searchpath.
0600: Introspector.setBeanInfoSearchPath(searchPath);
0601: } catch (SecurityException e) {
0602: // this call may fail in some environments
0603: getLog()
0604: .warn(
0605: "Security manager does not allow bean info search path to be set");
0606: getLog()
0607: .debug(
0608: "Security exception whilst setting bean info search page",
0609: e);
0610: }
0611: }
0612:
0613: return xmlInfo;
0614: }
0615:
0616: /** Create a standard <code>XMLBeanInfo</code> by introspection.
0617: * The actual introspection depends only on the <code>BeanInfo</code>
0618: * associated with the bean.
0619: *
0620: * @param beanInfo the BeanInfo the xml-bean mapping is based on
0621: * @return XMLBeanInfo describing bean-xml mapping
0622: * @throws IntrospectionException when the bean introspection fails
0623: */
0624: public XMLBeanInfo introspect(BeanInfo beanInfo)
0625: throws IntrospectionException {
0626: XMLBeanInfo xmlBeanInfo = createXMLBeanInfo(beanInfo);
0627: populate(xmlBeanInfo, new JavaBeanType(beanInfo));
0628: return xmlBeanInfo;
0629: }
0630:
0631: /**
0632: * <p>Registers the class mappings specified in the multi-class document
0633: * given by the <code>InputSource</code>.
0634: * </p>
0635: * <p>
0636: * <strong>Note:</strong> that this method will override any existing mapping
0637: * for the speficied classes.
0638: * </p>
0639: * @since 0.7
0640: * @param source <code>InputSource</code>, not null
0641: * @return <code>Class</code> array containing all mapped classes
0642: * @throws IntrospectionException
0643: * @throws SAXException
0644: * @throws IOException
0645: */
0646: public synchronized Class[] register(InputSource source)
0647: throws IntrospectionException, IOException, SAXException {
0648: Map xmlBeanInfoByClass = loadMultiMapping(source);
0649: Set keySet = xmlBeanInfoByClass.keySet();
0650: Class mappedClasses[] = new Class[keySet.size()];
0651: int i = 0;
0652: for (Iterator it = keySet.iterator(); it.hasNext();) {
0653: Class clazz = (Class) it.next();
0654: mappedClasses[i++] = clazz;
0655: XMLBeanInfo xmlBeanInfo = (XMLBeanInfo) xmlBeanInfoByClass
0656: .get(clazz);
0657: if (xmlBeanInfo != null) {
0658: getRegistry().put(clazz, xmlBeanInfo);
0659: }
0660: }
0661: return mappedClasses;
0662: }
0663:
0664: /**
0665: * Loads the multi-mapping from the given <code>InputSource</code>.
0666: * @param mapping <code>InputSource</code>, not null
0667: * @return <code>Map</code> containing <code>XMLBeanInfo</code>'s
0668: * indexes by the <code>Class</code> they describe
0669: * @throws IOException
0670: * @throws SAXException
0671: */
0672: private synchronized Map loadMultiMapping(InputSource mapping)
0673: throws IOException, SAXException {
0674: // synchronized method so this digester is only used by
0675: // one thread at once
0676: if (multiMappingdigester == null) {
0677: multiMappingdigester = new MultiMappingBeanInfoDigester();
0678: multiMappingdigester.setXMLIntrospector(this );
0679: }
0680: Map multiBeanInfoMap = (Map) multiMappingdigester
0681: .parse(mapping);
0682: return multiBeanInfoMap;
0683: }
0684:
0685: /**
0686: * <p>Registers the class mapping specified in the standard dot-betwixt file.
0687: * Subsequent introspections will use this registered mapping for the class.
0688: * </p>
0689: * <p>
0690: * <strong>Note:</strong> that this method will override any existing mapping
0691: * for this class.
0692: * </p>
0693: * @since 0.7
0694: * @param aClass <code>Class</code>, not null
0695: * @param source <code>InputSource</code>, not null
0696: * @throws SAXException when the source cannot be parsed
0697: * @throws IOException
0698: */
0699: public void register(Class aClass, InputSource source)
0700: throws IOException, SAXException {
0701: XMLBeanInfo xmlBeanInfo = introspect(aClass, source);
0702: getRegistry().put(aClass, xmlBeanInfo);
0703: }
0704:
0705: /**
0706: * Populates the given <code>XMLBeanInfo</code> based on the given type of bean.
0707: *
0708: * @param xmlBeanInfo populate this, not null
0709: * @param bean the type definition for the bean, not null
0710: */
0711: private void populate(XMLBeanInfo xmlBeanInfo, BeanType bean) {
0712: String name = bean.getBeanName();
0713:
0714: ElementDescriptor elementDescriptor = new ElementDescriptor();
0715: elementDescriptor.setLocalName(getElementNameMapper()
0716: .mapTypeToElementName(name));
0717: elementDescriptor.setPropertyType(bean.getElementType());
0718:
0719: if (getLog().isTraceEnabled()) {
0720: getLog().trace("Populating:" + bean);
0721: }
0722:
0723: // add default string value for primitive types
0724: if (bean.isPrimitiveType()) {
0725: getLog().trace("Bean is primitive");
0726: elementDescriptor.setTextExpression(StringExpression
0727: .getInstance());
0728:
0729: } else {
0730:
0731: getLog().trace("Bean is standard type");
0732:
0733: boolean isLoopType = bean.isLoopType();
0734:
0735: List elements = new ArrayList();
0736: List attributes = new ArrayList();
0737: List contents = new ArrayList();
0738:
0739: // add bean properties for all collection which are not basic
0740: if (!(isLoopType && isBasicCollection(bean.getClass()))) {
0741: addProperties(bean.getProperties(), elements,
0742: attributes, contents);
0743: }
0744:
0745: // add iterator for collections
0746: if (isLoopType) {
0747: getLog().trace("Bean is loop");
0748: ElementDescriptor loopDescriptor = new ElementDescriptor();
0749: loopDescriptor.setCollective(true);
0750: loopDescriptor.setHollow(true);
0751: loopDescriptor.setSingularPropertyType(Object.class);
0752: loopDescriptor
0753: .setContextExpression(new IteratorExpression(
0754: EmptyExpression.getInstance()));
0755: loopDescriptor.setUpdater(CollectionUpdater
0756: .getInstance());
0757: if (bean.isMapType()) {
0758: loopDescriptor.setQualifiedName("entry");
0759: }
0760: elements.add(loopDescriptor);
0761: }
0762:
0763: int size = elements.size();
0764: if (size > 0) {
0765: ElementDescriptor[] descriptors = new ElementDescriptor[size];
0766: elements.toArray(descriptors);
0767: elementDescriptor.setElementDescriptors(descriptors);
0768: }
0769: size = attributes.size();
0770: if (size > 0) {
0771: AttributeDescriptor[] descriptors = new AttributeDescriptor[size];
0772: attributes.toArray(descriptors);
0773: elementDescriptor.setAttributeDescriptors(descriptors);
0774: }
0775: size = contents.size();
0776: if (size > 0) {
0777: if (size > 0) {
0778: Descriptor[] descriptors = new Descriptor[size];
0779: contents.toArray(descriptors);
0780: elementDescriptor
0781: .setContentDescriptors(descriptors);
0782: }
0783: }
0784: }
0785:
0786: xmlBeanInfo.setElementDescriptor(elementDescriptor);
0787:
0788: // default any addProperty() methods
0789: defaultAddMethods(elementDescriptor, bean.getElementType());
0790:
0791: if (getLog().isTraceEnabled()) {
0792: getLog().trace("Populated descriptor:");
0793: getLog().trace(elementDescriptor);
0794: }
0795: }
0796:
0797: /**
0798: * <p>Is the given type a basic collection?
0799: * </p><p>
0800: * This is used to determine whether a collective type
0801: * should be introspected as a bean (in addition to a collection).
0802: * </p>
0803: * @param type <code>Class</code>, not null
0804: * @return
0805: */
0806: private boolean isBasicCollection(Class type) {
0807: return type.getName().startsWith("java.util");
0808: }
0809:
0810: /**
0811: * Creates XMLBeanInfo for the given DynaClass.
0812: *
0813: * @param dynaClass the class describing a DynaBean
0814: *
0815: * @return XMLBeanInfo that describes the properties of the given
0816: * DynaClass
0817: */
0818: protected XMLBeanInfo createXMLBeanInfo(DynaClass dynaClass) {
0819: // XXX is the chosen class right?
0820: XMLBeanInfo beanInfo = new XMLBeanInfo(dynaClass.getClass());
0821: return beanInfo;
0822: }
0823:
0824: /**
0825: * Create a XML descriptor from a bean one.
0826: * Go through and work out whether it's a loop property, a primitive or a standard.
0827: * The class property is ignored.
0828: *
0829: * @param propertyDescriptor create a <code>NodeDescriptor</code> for this property
0830: * @param useAttributesForPrimitives write primitives as attributes (rather than elements)
0831: * @return a correctly configured <code>NodeDescriptor</code> for the property
0832: * @throws IntrospectionException when bean introspection fails
0833: * @deprecated 0.5 use {@link #createXMLDescriptor}.
0834: */
0835: public Descriptor createDescriptor(
0836: PropertyDescriptor propertyDescriptor,
0837: boolean useAttributesForPrimitives)
0838: throws IntrospectionException {
0839: return createXMLDescriptor(new BeanProperty(propertyDescriptor));
0840: }
0841:
0842: /**
0843: * Create a XML descriptor from a bean one.
0844: * Go through and work out whether it's a loop property, a primitive or a standard.
0845: * The class property is ignored.
0846: *
0847: * @param beanProperty the BeanProperty specifying the property
0848: * @return a correctly configured <code>NodeDescriptor</code> for the property
0849: * @since 0.5
0850: */
0851: public Descriptor createXMLDescriptor(BeanProperty beanProperty) {
0852: return beanProperty.createXMLDescriptor(configuration);
0853: }
0854:
0855: /**
0856: * Add any addPropety(PropertyType) methods as Updaters
0857: * which are often used for 1-N relationships in beans.
0858: * This method does not preserve null property names.
0859: * <br>
0860: * The tricky part here is finding which ElementDescriptor corresponds
0861: * to the method. e.g. a property 'items' might have an Element descriptor
0862: * which the method addItem() should match to.
0863: * <br>
0864: * So the algorithm we'll use
0865: * by default is to take the decapitalized name of the property being added
0866: * and find the first ElementDescriptor that matches the property starting with
0867: * the string. This should work for most use cases.
0868: * e.g. addChild() would match the children property.
0869: * <br>
0870: * TODO this probably needs refactoring. It probably belongs in the bean wrapper
0871: * (so that it'll work properly with dyna-beans) and so that the operations can
0872: * be optimized by caching. Multiple hash maps are created and getMethods is
0873: * called multiple times. This is relatively expensive and so it'd be better
0874: * to push into a proper class and cache.
0875: * <br>
0876: *
0877: * @param rootDescriptor add defaults to this descriptor
0878: * @param beanClass the <code>Class</code> to which descriptor corresponds
0879: */
0880: public void defaultAddMethods(ElementDescriptor rootDescriptor,
0881: Class beanClass) {
0882: defaultAddMethods(rootDescriptor, beanClass, false);
0883: }
0884:
0885: /**
0886: * Add any addPropety(PropertyType) methods as Updaters
0887: * which are often used for 1-N relationships in beans.
0888: * <br>
0889: * The tricky part here is finding which ElementDescriptor corresponds
0890: * to the method. e.g. a property 'items' might have an Element descriptor
0891: * which the method addItem() should match to.
0892: * <br>
0893: * So the algorithm we'll use
0894: * by default is to take the decapitalized name of the property being added
0895: * and find the first ElementDescriptor that matches the property starting with
0896: * the string. This should work for most use cases.
0897: * e.g. addChild() would match the children property.
0898: * <br>
0899: * TODO this probably needs refactoring. It probably belongs in the bean wrapper
0900: * (so that it'll work properly with dyna-beans) and so that the operations can
0901: * be optimized by caching. Multiple hash maps are created and getMethods is
0902: * called multiple times. This is relatively expensive and so it'd be better
0903: * to push into a proper class and cache.
0904: * <br>
0905: *
0906: * @param rootDescriptor add defaults to this descriptor
0907: * @param beanClass the <code>Class</code> to which descriptor corresponds
0908: * @since 0.8
0909: */
0910: public void defaultAddMethods(ElementDescriptor rootDescriptor,
0911: Class beanClass, boolean preservePropertyName) {
0912: // TODO: this probably does work properly with DynaBeans: need to push
0913: // implementation into an class and expose it on BeanType.
0914:
0915: // lets iterate over all methods looking for one of the form
0916: // add*(PropertyType)
0917: if (beanClass != null) {
0918: ArrayList singleParameterAdders = new ArrayList();
0919: ArrayList twinParameterAdders = new ArrayList();
0920:
0921: Method[] methods = beanClass.getMethods();
0922: for (int i = 0, size = methods.length; i < size; i++) {
0923: Method method = methods[i];
0924: String name = method.getName();
0925: if (name.startsWith("add")) {
0926: // TODO: should we filter out non-void returning methods?
0927: // some beans will return something as a helper
0928: Class[] types = method.getParameterTypes();
0929: if (types != null) {
0930: if (getLog().isTraceEnabled()) {
0931: getLog()
0932: .trace(
0933: "Searching for match for "
0934: + method);
0935: }
0936:
0937: switch (types.length) {
0938: case 1:
0939: singleParameterAdders.add(method);
0940: break;
0941: case 2:
0942: twinParameterAdders.add(method);
0943: break;
0944: default:
0945: // ignore
0946: break;
0947: }
0948: }
0949: }
0950: }
0951:
0952: Map elementsByPropertyName = makeElementDescriptorMap(rootDescriptor);
0953:
0954: for (Iterator it = singleParameterAdders.iterator(); it
0955: .hasNext();) {
0956: Method singleParameterAdder = (Method) it.next();
0957: setIteratorAdder(elementsByPropertyName,
0958: singleParameterAdder, preservePropertyName);
0959: }
0960:
0961: for (Iterator it = twinParameterAdders.iterator(); it
0962: .hasNext();) {
0963: Method twinParameterAdder = (Method) it.next();
0964: setMapAdder(elementsByPropertyName, twinParameterAdder);
0965: }
0966:
0967: // need to call this once all the defaults have been added
0968: // so that all the singular types have been set correctly
0969: configureMappingDerivation(rootDescriptor);
0970: }
0971: }
0972:
0973: /**
0974: * Configures the mapping derivation according to the current
0975: * <code>MappingDerivationStrategy</code> implementation.
0976: * This method acts recursively.
0977: * @param rootDescriptor <code>ElementDescriptor</code>, not null
0978: */
0979: private void configureMappingDerivation(ElementDescriptor descriptor) {
0980: boolean useBindTime = getConfiguration()
0981: .getMappingDerivationStrategy()
0982: .useBindTimeTypeForMapping(
0983: descriptor.getPropertyType(),
0984: descriptor.getSingularPropertyType());
0985: descriptor.setUseBindTimeTypeForMapping(useBindTime);
0986: ElementDescriptor[] childDescriptors = descriptor
0987: .getElementDescriptors();
0988: for (int i = 0, size = childDescriptors.length; i < size; i++) {
0989: configureMappingDerivation(childDescriptors[i]);
0990: }
0991: }
0992:
0993: /**
0994: * Sets the adder method where the corresponding property is an iterator
0995: * @param rootDescriptor
0996: * @param singleParameterAdder
0997: */
0998: private void setIteratorAdder(Map elementsByPropertyName,
0999: Method singleParameterAdderMethod,
1000: boolean preserveNullPropertyName) {
1001:
1002: String adderName = singleParameterAdderMethod.getName();
1003: String propertyName = Introspector.decapitalize(adderName
1004: .substring(3));
1005: ElementDescriptor matchingDescriptor = getMatchForAdder(
1006: propertyName, elementsByPropertyName);
1007: if (matchingDescriptor != null) {
1008: //TODO defensive code: probably should check descriptor type
1009:
1010: Class singularType = singleParameterAdderMethod
1011: .getParameterTypes()[0];
1012: if (getLog().isTraceEnabled()) {
1013: getLog().trace(adderName + "->" + propertyName);
1014: }
1015: // this may match a standard collection or iteration
1016: getLog().trace("Matching collection or iteration");
1017:
1018: matchingDescriptor.setUpdater(new MethodUpdater(
1019: singleParameterAdderMethod));
1020: matchingDescriptor.setSingularPropertyType(singularType);
1021: matchingDescriptor
1022: .setHollow(!isPrimitiveType(singularType));
1023: String localName = matchingDescriptor.getLocalName();
1024: if (!preserveNullPropertyName
1025: && (localName == null || localName.length() == 0)) {
1026: matchingDescriptor.setLocalName(getConfiguration()
1027: .getElementNameMapper().mapTypeToElementName(
1028: propertyName));
1029: }
1030:
1031: if (getLog().isDebugEnabled()) {
1032: getLog().debug("!! " + singleParameterAdderMethod);
1033: getLog().debug("!! " + singularType);
1034: }
1035: }
1036: }
1037:
1038: /**
1039: * Sets the adder where the corresponding property type is an map
1040: * @param rootDescriptor
1041: * @param singleParameterAdder
1042: */
1043: private void setMapAdder(Map elementsByPropertyName,
1044: Method twinParameterAdderMethod) {
1045: String adderName = twinParameterAdderMethod.getName();
1046: String propertyName = Introspector.decapitalize(adderName
1047: .substring(3));
1048: ElementDescriptor matchingDescriptor = getMatchForAdder(
1049: propertyName, elementsByPropertyName);
1050: assignAdder(twinParameterAdderMethod, matchingDescriptor);
1051: }
1052:
1053: /**
1054: * Assigns the given method as an adder method to the given descriptor.
1055: * @param twinParameterAdderMethod adder <code>Method</code>, not null
1056: * @param matchingDescriptor <code>ElementDescriptor</code> describing the element
1057: * @since 0.8
1058: */
1059: public void assignAdder(Method twinParameterAdderMethod,
1060: ElementDescriptor matchingDescriptor) {
1061: if (matchingDescriptor != null
1062: && Map.class.isAssignableFrom(matchingDescriptor
1063: .getPropertyType())) {
1064: // this may match a map
1065: getLog().trace("Matching map");
1066: ElementDescriptor[] children = matchingDescriptor
1067: .getElementDescriptors();
1068: // see if the descriptor's been set up properly
1069: if (children.length == 0) {
1070: getLog().info(
1071: "'entry' descriptor is missing for map. "
1072: + "Updaters cannot be set");
1073:
1074: } else {
1075: assignAdder(twinParameterAdderMethod, children);
1076: }
1077: }
1078: }
1079:
1080: /**
1081: * Assigns the given method as an adder.
1082: * @param twinParameterAdderMethod adder <code>Method</code>, not null
1083: * @param children <code>ElementDescriptor</code> children, not null
1084: */
1085: private void assignAdder(Method twinParameterAdderMethod,
1086: ElementDescriptor[] children) {
1087: Class[] types = twinParameterAdderMethod.getParameterTypes();
1088: Class keyType = types[0];
1089: Class valueType = types[1];
1090:
1091: // loop through children
1092: // adding updaters for key and value
1093: MapEntryAdder adder = new MapEntryAdder(
1094: twinParameterAdderMethod);
1095: for (int n = 0, noOfGrandChildren = children.length; n < noOfGrandChildren; n++) {
1096: if ("key".equals(children[n].getLocalName())) {
1097:
1098: children[n].setUpdater(adder.getKeyUpdater());
1099: children[n].setSingularPropertyType(keyType);
1100: if (children[n].getPropertyType() == null) {
1101: children[n].setPropertyType(valueType);
1102: }
1103: if (isPrimitiveType(keyType)) {
1104: children[n].setHollow(false);
1105: }
1106: if (getLog().isTraceEnabled()) {
1107: getLog().trace("Key descriptor: " + children[n]);
1108: }
1109:
1110: } else if ("value".equals(children[n].getLocalName())) {
1111:
1112: children[n].setUpdater(adder.getValueUpdater());
1113: children[n].setSingularPropertyType(valueType);
1114: if (children[n].getPropertyType() == null) {
1115: children[n].setPropertyType(valueType);
1116: }
1117: if (isPrimitiveType(valueType)) {
1118: children[n].setHollow(false);
1119: }
1120: if (isLoopType(valueType)) {
1121: // need to attach a hollow descriptor
1122: // don't know the element name
1123: // so use null name (to match anything)
1124: ElementDescriptor loopDescriptor = new ElementDescriptor();
1125: loopDescriptor.setHollow(true);
1126: loopDescriptor.setSingularPropertyType(valueType);
1127: loopDescriptor.setPropertyType(valueType);
1128: children[n].addElementDescriptor(loopDescriptor);
1129: loopDescriptor.setCollective(true);
1130: }
1131: if (getLog().isTraceEnabled()) {
1132: getLog().trace("Value descriptor: " + children[n]);
1133: }
1134: }
1135: }
1136: }
1137:
1138: /**
1139: * Gets an ElementDescriptor for the property matching the adder
1140: * @param adderName
1141: * @param rootDescriptor
1142: * @return
1143: */
1144: private ElementDescriptor getMatchForAdder(String propertyName,
1145: Map elementsByPropertyName) {
1146: ElementDescriptor matchingDescriptor = null;
1147: if (propertyName.length() > 0) {
1148: if (getLog().isTraceEnabled()) {
1149: getLog().trace(
1150: "findPluralDescriptor( " + propertyName
1151: + " ):root property name="
1152: + propertyName);
1153: }
1154:
1155: PluralStemmer stemmer = getPluralStemmer();
1156: matchingDescriptor = stemmer.findPluralDescriptor(
1157: propertyName, elementsByPropertyName);
1158:
1159: if (getLog().isTraceEnabled()) {
1160: getLog().trace(
1161: "findPluralDescriptor( " + propertyName
1162: + " ):ElementDescriptor="
1163: + matchingDescriptor);
1164: }
1165: }
1166: return matchingDescriptor;
1167: }
1168:
1169: // Implementation methods
1170: //-------------------------------------------------------------------------
1171:
1172: /**
1173: * Creates a map where the keys are the property names and the values are the ElementDescriptors
1174: */
1175: private Map makeElementDescriptorMap(
1176: ElementDescriptor rootDescriptor) {
1177: Map result = new HashMap();
1178: String rootPropertyName = rootDescriptor.getPropertyName();
1179: if (rootPropertyName != null) {
1180: result.put(rootPropertyName, rootDescriptor);
1181: }
1182: makeElementDescriptorMap(rootDescriptor, result);
1183: return result;
1184: }
1185:
1186: /**
1187: * Creates a map where the keys are the property names and the values are the ElementDescriptors
1188: *
1189: * @param rootDescriptor the values of the maps are the children of this
1190: * <code>ElementDescriptor</code> index by their property names
1191: * @param map the map to which the elements will be added
1192: */
1193: private void makeElementDescriptorMap(
1194: ElementDescriptor rootDescriptor, Map map) {
1195: ElementDescriptor[] children = rootDescriptor
1196: .getElementDescriptors();
1197: if (children != null) {
1198: for (int i = 0, size = children.length; i < size; i++) {
1199: ElementDescriptor child = children[i];
1200: String propertyName = child.getPropertyName();
1201: if (propertyName != null) {
1202: map.put(propertyName, child);
1203: }
1204: makeElementDescriptorMap(child, map);
1205: }
1206: }
1207: }
1208:
1209: /**
1210: * A Factory method to lazily create a new strategy
1211: * to detect matching singular and plural properties.
1212: *
1213: * @return new defualt PluralStemmer implementation
1214: * @deprecated 0.6 this method has been moved into IntrospectionConfiguration.
1215: * Those who need to vary this should subclass that class instead
1216: */
1217: protected PluralStemmer createPluralStemmer() {
1218: return new DefaultPluralStemmer();
1219: }
1220:
1221: /**
1222: * A Factory method to lazily create a strategy
1223: * used to convert bean type names into element names.
1224: *
1225: * @return new default NameMapper implementation
1226: * @deprecated 0.6 this method has been moved into IntrospectionConfiguration.
1227: * Those who need to vary this should subclass that class instead
1228: */
1229: protected NameMapper createNameMapper() {
1230: return new DefaultNameMapper();
1231: }
1232:
1233: /**
1234: * Attempt to lookup the XML descriptor for the given class using the
1235: * classname + ".betwixt" using the same ClassLoader used to load the class
1236: * or return null if it could not be loaded
1237: *
1238: * @param aClass digester .betwixt file for this class
1239: * @return XMLBeanInfo digested from the .betwixt file if one can be found.
1240: * Otherwise null.
1241: */
1242: protected synchronized XMLBeanInfo findByXMLDescriptor(Class aClass) {
1243: // trim the package name
1244: String name = aClass.getName();
1245: int idx = name.lastIndexOf('.');
1246: if (idx >= 0) {
1247: name = name.substring(idx + 1);
1248: }
1249: name += ".betwixt";
1250:
1251: URL url = aClass.getResource(name);
1252: if (url != null) {
1253: try {
1254: String urlText = url.toString();
1255: if (getLog().isDebugEnabled()) {
1256: getLog().debug(
1257: "Parsing Betwixt XML descriptor: "
1258: + urlText);
1259: }
1260: // synchronized method so this digester is only used by
1261: // one thread at once
1262: configureDigester(aClass);
1263: return (XMLBeanInfo) digester.parse(urlText);
1264: } catch (Exception e) {
1265: getLog().warn(
1266: "Caught exception trying to parse: " + name, e);
1267: }
1268: }
1269:
1270: if (getLog().isTraceEnabled()) {
1271: getLog().trace("Could not find betwixt file " + name);
1272: }
1273: return null;
1274: }
1275:
1276: /**
1277: * Configures the single <code>Digester</code> instance used by this introspector.
1278: * @param aClass <code>Class</code>, not null
1279: */
1280: private synchronized void configureDigester(Class aClass) {
1281: if (digester == null) {
1282: digester = new XMLBeanInfoDigester();
1283: digester.setXMLIntrospector(this );
1284: }
1285: digester.setBeanClass(aClass);
1286: }
1287:
1288: /**
1289: * Loop through properties and process each one
1290: *
1291: * @param beanInfo the BeanInfo whose properties will be processed
1292: * @param elements ElementDescriptor list to which elements will be added
1293: * @param attributes AttributeDescriptor list to which attributes will be added
1294: * @param contents Descriptor list to which mixed content will be added
1295: * @throws IntrospectionException if the bean introspection fails
1296: * @deprecated 0.5 use {@link #addProperties(BeanProperty[], List, List,List)}
1297: */
1298: protected void addProperties(BeanInfo beanInfo, List elements,
1299: List attributes, List contents)
1300: throws IntrospectionException {
1301: PropertyDescriptor[] descriptors = beanInfo
1302: .getPropertyDescriptors();
1303: if (descriptors != null) {
1304: for (int i = 0, size = descriptors.length; i < size; i++) {
1305: addProperty(beanInfo, descriptors[i], elements,
1306: attributes, contents);
1307: }
1308: }
1309: if (getLog().isTraceEnabled()) {
1310: getLog().trace(elements);
1311: getLog().trace(attributes);
1312: getLog().trace(contents);
1313: }
1314: }
1315:
1316: /**
1317: * Loop through properties and process each one
1318: *
1319: * @param beanProperties the properties to be processed
1320: * @param elements ElementDescriptor list to which elements will be added
1321: * @param attributes AttributeDescriptor list to which attributes will be added
1322: * @param contents Descriptor list to which mixed content will be added
1323: * @since 0.5
1324: */
1325: protected void addProperties(BeanProperty[] beanProperties,
1326: List elements, List attributes, List contents) {
1327: if (beanProperties != null) {
1328: if (getLog().isTraceEnabled()) {
1329: getLog().trace(
1330: beanProperties.length
1331: + " properties to be added");
1332: }
1333: for (int i = 0, size = beanProperties.length; i < size; i++) {
1334: addProperty(beanProperties[i], elements, attributes,
1335: contents);
1336: }
1337: }
1338: if (getLog().isTraceEnabled()) {
1339: getLog()
1340: .trace(
1341: "After properties have been added (elements, attributes, contents):");
1342: getLog().trace(elements);
1343: getLog().trace(attributes);
1344: getLog().trace(contents);
1345: }
1346: }
1347:
1348: /**
1349: * Process a property.
1350: * Go through and work out whether it's a loop property, a primitive or a standard.
1351: * The class property is ignored.
1352: *
1353: * @param beanInfo the BeanInfo whose property is being processed
1354: * @param propertyDescriptor the PropertyDescriptor to process
1355: * @param elements ElementDescriptor list to which elements will be added
1356: * @param attributes AttributeDescriptor list to which attributes will be added
1357: * @param contents Descriptor list to which mixed content will be added
1358: * @throws IntrospectionException if the bean introspection fails
1359: * @deprecated 0.5 BeanInfo is no longer required.
1360: * Use {@link #addProperty(PropertyDescriptor, List, List, List)} instead.
1361: */
1362: protected void addProperty(BeanInfo beanInfo,
1363: PropertyDescriptor propertyDescriptor, List elements,
1364: List attributes, List contents)
1365: throws IntrospectionException {
1366: addProperty(propertyDescriptor, elements, attributes, contents);
1367: }
1368:
1369: /**
1370: * Process a property.
1371: * Go through and work out whether it's a loop property, a primitive or a standard.
1372: * The class property is ignored.
1373: *
1374: * @param propertyDescriptor the PropertyDescriptor to process
1375: * @param elements ElementDescriptor list to which elements will be added
1376: * @param attributes AttributeDescriptor list to which attributes will be added
1377: * @param contents Descriptor list to which mixed content will be added
1378: * @throws IntrospectionException if the bean introspection fails
1379: * @deprecated 0.5 use {@link #addProperty(BeanProperty, List, List, List)} instead
1380: */
1381: protected void addProperty(PropertyDescriptor propertyDescriptor,
1382: List elements, List attributes, List contents)
1383: throws IntrospectionException {
1384: addProperty(new BeanProperty(propertyDescriptor), elements,
1385: attributes, contents);
1386: }
1387:
1388: /**
1389: * Process a property.
1390: * Go through and work out whether it's a loop property, a primitive or a standard.
1391: * The class property is ignored.
1392: *
1393: * @param beanProperty the bean property to process
1394: * @param elements ElementDescriptor list to which elements will be added
1395: * @param attributes AttributeDescriptor list to which attributes will be added
1396: * @param contents Descriptor list to which mixed content will be added
1397: * @since 0.5
1398: */
1399: protected void addProperty(BeanProperty beanProperty,
1400: List elements, List attributes, List contents) {
1401: Descriptor nodeDescriptor = createXMLDescriptor(beanProperty);
1402: if (nodeDescriptor == null) {
1403: return;
1404: }
1405: if (nodeDescriptor instanceof ElementDescriptor) {
1406: elements.add(nodeDescriptor);
1407: } else if (nodeDescriptor instanceof AttributeDescriptor) {
1408: attributes.add(nodeDescriptor);
1409: } else {
1410: contents.add(nodeDescriptor);
1411: }
1412: }
1413:
1414: /**
1415: * Loop through properties and process each one
1416: *
1417: * @param beanInfo the BeanInfo whose properties will be processed
1418: * @param elements ElementDescriptor list to which elements will be added
1419: * @param attributes AttributeDescriptor list to which attributes will be added
1420: * @throws IntrospectionException if the bean introspection fails
1421: * @deprecated 0.5 this method does not support mixed content.
1422: * Use {@link #addProperties(BeanInfo, List, List, List)} instead.
1423: */
1424: protected void addProperties(BeanInfo beanInfo, List elements,
1425: List attributes) throws IntrospectionException {
1426: PropertyDescriptor[] descriptors = beanInfo
1427: .getPropertyDescriptors();
1428: if (descriptors != null) {
1429: for (int i = 0, size = descriptors.length; i < size; i++) {
1430: addProperty(beanInfo, descriptors[i], elements,
1431: attributes);
1432: }
1433: }
1434: if (getLog().isTraceEnabled()) {
1435: getLog().trace(elements);
1436: getLog().trace(attributes);
1437: }
1438: }
1439:
1440: /**
1441: * Process a property.
1442: * Go through and work out whether it's a loop property, a primitive or a standard.
1443: * The class property is ignored.
1444: *
1445: * @param beanInfo the BeanInfo whose property is being processed
1446: * @param propertyDescriptor the PropertyDescriptor to process
1447: * @param elements ElementDescriptor list to which elements will be added
1448: * @param attributes AttributeDescriptor list to which attributes will be added
1449: * @throws IntrospectionException if the bean introspection fails
1450: * @deprecated 0.5 this method does not support mixed content.
1451: * Use {@link #addProperty(BeanInfo, PropertyDescriptor, List, List, List)} instead.
1452: */
1453: protected void addProperty(BeanInfo beanInfo,
1454: PropertyDescriptor propertyDescriptor, List elements,
1455: List attributes) throws IntrospectionException {
1456: NodeDescriptor nodeDescriptor = XMLIntrospectorHelper
1457: .createDescriptor(propertyDescriptor,
1458: isAttributesForPrimitives(), this );
1459: if (nodeDescriptor == null) {
1460: return;
1461: }
1462: if (nodeDescriptor instanceof ElementDescriptor) {
1463: elements.add(nodeDescriptor);
1464: } else {
1465: attributes.add(nodeDescriptor);
1466: }
1467: }
1468:
1469: /**
1470: * Factory method to create XMLBeanInfo instances
1471: *
1472: * @param beanInfo the BeanInfo from which the XMLBeanInfo will be created
1473: * @return XMLBeanInfo describing the bean-xml mapping
1474: */
1475: protected XMLBeanInfo createXMLBeanInfo(BeanInfo beanInfo) {
1476: XMLBeanInfo xmlBeanInfo = new XMLBeanInfo(beanInfo
1477: .getBeanDescriptor().getBeanClass());
1478: return xmlBeanInfo;
1479: }
1480:
1481: /**
1482: * Is this class a loop?
1483: *
1484: * @param type the Class to test
1485: * @return true if the type is a loop type
1486: */
1487: public boolean isLoopType(Class type) {
1488: return getConfiguration().isLoopType(type);
1489: }
1490:
1491: /**
1492: * Is this class a primitive?
1493: *
1494: * @param type the Class to test
1495: * @return true for primitive types
1496: */
1497: public boolean isPrimitiveType(Class type) {
1498: // TODO: this method will probably be deprecated when primitive types
1499: // are subsumed into the simple type concept
1500: TypeBindingStrategy.BindingType bindingType = configuration
1501: .getTypeBindingStrategy().bindingType(type);
1502: boolean result = (bindingType
1503: .equals(TypeBindingStrategy.BindingType.PRIMITIVE));
1504: return result;
1505: }
1506:
1507: /** Some type of pseudo-bean */
1508: private abstract class BeanType {
1509: /**
1510: * Gets the name for this bean type
1511: * @return the bean type name, not null
1512: */
1513: public abstract String getBeanName();
1514:
1515: /**
1516: * Gets the type to be used by the associated element
1517: * @return a Class that is the type not null
1518: */
1519: public abstract Class getElementType();
1520:
1521: /**
1522: * Is this type a primitive?
1523: * @return true if this type should be treated by betwixt as a primitive
1524: */
1525: public abstract boolean isPrimitiveType();
1526:
1527: /**
1528: * is this type a map?
1529: * @return true this should be treated as a map.
1530: */
1531: public abstract boolean isMapType();
1532:
1533: /**
1534: * Is this type a loop?
1535: * @return true if this should be treated as a loop
1536: */
1537: public abstract boolean isLoopType();
1538:
1539: /**
1540: * Gets the properties associated with this bean.
1541: * @return the BeanProperty's, not null
1542: */
1543: public abstract BeanProperty[] getProperties();
1544:
1545: /**
1546: * Create string representation
1547: * @return something useful for logging
1548: */
1549: public String toString() {
1550: return "Bean[name=" + getBeanName() + ", type="
1551: + getElementType();
1552: }
1553: }
1554:
1555: /** Supports standard Java Beans */
1556: private class JavaBeanType extends BeanType {
1557: /** Introspected bean */
1558: private BeanInfo beanInfo;
1559: /** Bean class */
1560: private Class beanClass;
1561: /** Bean name */
1562: private String name;
1563: /** Bean properties */
1564: private BeanProperty[] properties;
1565:
1566: /**
1567: * Constructs a BeanType for a standard Java Bean
1568: * @param beanInfo the BeanInfo describing the standard Java Bean, not null
1569: */
1570: public JavaBeanType(BeanInfo beanInfo) {
1571: this .beanInfo = beanInfo;
1572: BeanDescriptor beanDescriptor = beanInfo
1573: .getBeanDescriptor();
1574: beanClass = beanDescriptor.getBeanClass();
1575: name = beanDescriptor.getName();
1576: // Array's contain a bad character
1577: if (beanClass.isArray()) {
1578: // called all array's Array
1579: name = "Array";
1580: }
1581:
1582: }
1583:
1584: /** @see BeanType #getElementType */
1585: public Class getElementType() {
1586: return beanClass;
1587: }
1588:
1589: /** @see BeanType#getBeanName */
1590: public String getBeanName() {
1591: return name;
1592: }
1593:
1594: /** @see BeanType#isPrimitiveType */
1595: public boolean isPrimitiveType() {
1596: return XMLIntrospector.this .isPrimitiveType(beanClass);
1597: }
1598:
1599: /** @see BeanType#isLoopType */
1600: public boolean isLoopType() {
1601: return getConfiguration().isLoopType(beanClass);
1602: }
1603:
1604: /** @see BeanType#isMapType */
1605: public boolean isMapType() {
1606: return Map.class.isAssignableFrom(beanClass);
1607: }
1608:
1609: /** @see BeanType#getProperties */
1610: public BeanProperty[] getProperties() {
1611: // lazy creation
1612: if (properties == null) {
1613: ArrayList propertyDescriptors = new ArrayList();
1614: // add base bean info
1615: PropertyDescriptor[] descriptors = beanInfo
1616: .getPropertyDescriptors();
1617: if (descriptors != null) {
1618: for (int i = 0, size = descriptors.length; i < size; i++) {
1619: if (!getConfiguration()
1620: .getPropertySuppressionStrategy()
1621: .suppressProperty(
1622: beanClass,
1623: descriptors[i]
1624: .getPropertyType(),
1625: descriptors[i].getName())) {
1626: propertyDescriptors.add(descriptors[i]);
1627: }
1628: }
1629: }
1630:
1631: // add properties from additional bean infos
1632: BeanInfo[] additionals = beanInfo
1633: .getAdditionalBeanInfo();
1634: if (additionals != null) {
1635: for (int i = 0, outerSize = additionals.length; i < outerSize; i++) {
1636: BeanInfo additionalInfo = additionals[i];
1637: descriptors = additionalInfo
1638: .getPropertyDescriptors();
1639: if (descriptors != null) {
1640: for (int j = 0, innerSize = descriptors.length; j < innerSize; j++) {
1641: if (!getConfiguration()
1642: .getPropertySuppressionStrategy()
1643: .suppressProperty(
1644: beanClass,
1645: descriptors[j]
1646: .getPropertyType(),
1647: descriptors[j]
1648: .getName())) {
1649: propertyDescriptors
1650: .add(descriptors[j]);
1651: }
1652: }
1653: }
1654: }
1655: }
1656:
1657: addAllSuperinterfaces(beanClass, propertyDescriptors);
1658:
1659: // what happens when size is zero?
1660: properties = new BeanProperty[propertyDescriptors
1661: .size()];
1662: int count = 0;
1663: for (Iterator it = propertyDescriptors.iterator(); it
1664: .hasNext(); count++) {
1665: PropertyDescriptor propertyDescriptor = (PropertyDescriptor) it
1666: .next();
1667: properties[count] = new BeanProperty(
1668: propertyDescriptor);
1669: }
1670: }
1671: return properties;
1672: }
1673:
1674: /**
1675: * Adds all super interfaces.
1676: * Super interface methods are not returned within the usual
1677: * bean info for an interface.
1678: * @param clazz <code>Class</code>, not null
1679: * @param propertyDescriptors <code>ArrayList</code> of <code>PropertyDescriptor</code>s', not null
1680: */
1681: private void addAllSuperinterfaces(Class clazz,
1682: ArrayList propertyDescriptors) {
1683: if (clazz.isInterface()) {
1684: Class[] super interfaces = clazz.getInterfaces();
1685: for (int i = 0, size = super interfaces.length; i < size; i++) {
1686: try {
1687:
1688: BeanInfo beanInfo;
1689: if (getConfiguration().ignoreAllBeanInfo()) {
1690: beanInfo = Introspector.getBeanInfo(
1691: super interfaces[i],
1692: Introspector.IGNORE_ALL_BEANINFO);
1693: } else {
1694: beanInfo = Introspector
1695: .getBeanInfo(super interfaces[i]);
1696: }
1697: PropertyDescriptor[] descriptors = beanInfo
1698: .getPropertyDescriptors();
1699: for (int j = 0, descriptorLength = descriptors.length; j < descriptorLength; j++) {
1700: if (!getConfiguration()
1701: .getPropertySuppressionStrategy()
1702: .suppressProperty(
1703: beanClass,
1704: descriptors[j]
1705: .getPropertyType(),
1706: descriptors[j].getName())) {
1707: propertyDescriptors.add(descriptors[j]);
1708: }
1709: }
1710: addAllSuperinterfaces(super interfaces[i],
1711: propertyDescriptors);
1712:
1713: } catch (IntrospectionException ex) {
1714: log
1715: .info(
1716: "Introspection on superinterface failed.",
1717: ex);
1718: }
1719: }
1720: }
1721: }
1722:
1723: }
1724:
1725: /** Implementation for DynaClasses */
1726: private class DynaClassBeanType extends BeanType {
1727: /** BeanType for this DynaClass */
1728: private DynaClass dynaClass;
1729: /** Properties extracted in constuctor */
1730: private BeanProperty[] properties;
1731:
1732: /**
1733: * Constructs a BeanType for a DynaClass
1734: * @param dynaClass not null
1735: */
1736: public DynaClassBeanType(DynaClass dynaClass) {
1737: this .dynaClass = dynaClass;
1738: DynaProperty[] dynaProperties = dynaClass
1739: .getDynaProperties();
1740: properties = new BeanProperty[dynaProperties.length];
1741: for (int i = 0, size = dynaProperties.length; i < size; i++) {
1742: properties[i] = new BeanProperty(dynaProperties[i]);
1743: }
1744: }
1745:
1746: /** @see BeanType#getBeanName */
1747: public String getBeanName() {
1748: return dynaClass.getName();
1749: }
1750:
1751: /** @see BeanType#getElementType */
1752: public Class getElementType() {
1753: return DynaClass.class;
1754: }
1755:
1756: /** @see BeanType#isPrimitiveType */
1757: public boolean isPrimitiveType() {
1758: return false;
1759: }
1760:
1761: /** @see BeanType#isMapType */
1762: public boolean isMapType() {
1763: return false;
1764: }
1765:
1766: /** @see BeanType#isLoopType */
1767: public boolean isLoopType() {
1768: return false;
1769: }
1770:
1771: /** @see BeanType#getProperties */
1772: public BeanProperty[] getProperties() {
1773: return properties;
1774: }
1775: }
1776: }
|