Coverage Report - org.apache.commons.betwixt.digester.ElementRule

Classes in this File Line Coverage Branch Coverage Complexity
ElementRule
82% 
93% 
5

 1  
 package org.apache.commons.betwixt.digester;
 2  
 
 3  
 /*
 4  
  * Copyright 2001-2005 The Apache Software Foundation.
 5  
  * 
 6  
  * Licensed under the Apache License, Version 2.0 (the "License");
 7  
  * you may not use this file except in compliance with the License.
 8  
  * You may obtain a copy of the License at
 9  
  * 
 10  
  *      http://www.apache.org/licenses/LICENSE-2.0
 11  
  * 
 12  
  * Unless required by applicable law or agreed to in writing, software
 13  
  * distributed under the License is distributed on an "AS IS" BASIS,
 14  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 15  
  * See the License for the specific language governing permissions and
 16  
  * limitations under the License.
 17  
  */
 18  
 import java.beans.PropertyDescriptor;
 19  
 import java.lang.reflect.Method;
 20  
 import java.lang.reflect.Modifier;
 21  
 import java.util.Map;
 22  
 
 23  
 import org.apache.commons.betwixt.ElementDescriptor;
 24  
 import org.apache.commons.betwixt.XMLBeanInfo;
 25  
 import org.apache.commons.betwixt.XMLUtils;
 26  
 import org.apache.commons.betwixt.expression.ConstantExpression;
 27  
 import org.apache.commons.betwixt.expression.IteratorExpression;
 28  
 import org.apache.commons.betwixt.expression.MethodExpression;
 29  
 import org.apache.commons.betwixt.expression.MethodUpdater;
 30  
 import org.apache.commons.logging.Log;
 31  
 import org.apache.commons.logging.LogFactory;
 32  
 import org.xml.sax.Attributes;
 33  
 import org.xml.sax.SAXException;
 34  
 
 35  
 /**
 36  
  * <p>
 37  
  * <code>ElementRule</code> the digester Rule for parsing the &lt;element&gt;
 38  
  * elements.
 39  
  * </p>
 40  
  * 
 41  
  * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
 42  
  */
 43  604
 public class ElementRule extends MappedPropertyRule {
 44  
 
 45  
     /** Logger */
 46  604
     private static Log log = LogFactory.getLog(ElementRule.class);
 47  
 
 48  
     /**
 49  
      * Sets the log for this class
 50  
      * 
 51  
      * @param newLog
 52  
      *            the new Log implementation for this class to use
 53  
      * @since 0.5
 54  
      */
 55  
     public static final void setLog(Log newLog) {
 56  0
         log = newLog;
 57  0
     }
 58  
 
 59  
     /** Class for which the .bewixt file is being digested */
 60  
     private Class beanClass;
 61  
 
 62  
     /** Base constructor */
 63  1624
     public ElementRule() {
 64  1624
     }
 65  
 
 66  
     // Rule interface
 67  
     // -------------------------------------------------------------------------
 68  
 
 69  
     /**
 70  
      * Process the beginning of this element.
 71  
      * 
 72  
      * @param attributes
 73  
      *            The attribute list of this element
 74  
      * @throws SAXException
 75  
      *             1. If this tag's parent is not either an info or element tag.
 76  
      *             2. If the name attribute is not valid XML element name. 3. If
 77  
      *             the name attribute is not present 4. If the class attribute
 78  
      *             is not a loadable (fully qualified) class name
 79  
      */
 80  
     public void begin(String name, String namespace, Attributes attributes)
 81  
             throws SAXException {
 82  8747
         String nameAttributeValue = attributes.getValue("name");
 83  
 
 84  8747
         ElementDescriptor descriptor = new ElementDescriptor();
 85  8747
         descriptor.setLocalName(nameAttributeValue);
 86  8747
         String uri = attributes.getValue("uri");
 87  8747
         String qName = nameAttributeValue;
 88  8747
         if (uri != null && nameAttributeValue != null) {
 89  2002
             descriptor.setURI(uri);
 90  4004
             String prefix = getXMLIntrospector().getConfiguration()
 91  2002
                     .getPrefixMapper().getPrefix(uri);
 92  2002
             qName = prefix + ":" + nameAttributeValue;
 93  
         }
 94  8747
         descriptor.setQualifiedName(qName);
 95  
 
 96  8747
         String propertyName = attributes.getValue("property");
 97  8747
         descriptor.setPropertyName(propertyName);
 98  
 
 99  8747
         String propertyType = attributes.getValue("type");
 100  
 
 101  8747
         if (log.isTraceEnabled()) {
 102  0
             log.trace("(BEGIN) name=" + nameAttributeValue + " uri=" + uri
 103  0
                     + " property=" + propertyName + " type=" + propertyType);
 104  
         }
 105  
 
 106  
         // set mapping derivation
 107  8747
         String mappingDerivation = attributes.getValue("mappingDerivation");
 108  8747
         if ("introspection".equals(mappingDerivation)) {
 109  26
             descriptor.setUseBindTimeTypeForMapping(false);
 110  8721
         } else if ("bind".equals(mappingDerivation)) {
 111  13
             descriptor.setUseBindTimeTypeForMapping(true);
 112  
         }
 113  
 
 114  
         // set the property type using reflection
 115  17494
         descriptor.setPropertyType(getPropertyType(propertyType, beanClass,
 116  8747
                 propertyName));
 117  
 
 118  17494
         boolean isCollective = getXMLIntrospector().getConfiguration()
 119  8747
                 .isLoopType(descriptor.getPropertyType());
 120  
 
 121  8747
         descriptor.setCollective(isCollective);
 122  
 
 123  
         // check that the name attribute is present
 124  8747
         if (!isCollective
 125  6615
                 && (nameAttributeValue == null || nameAttributeValue.trim()
 126  6550
                         .equals(""))) {
 127  
             // allow polymorphic mappings but log note for user
 128  65
             log
 129  65
                     .info("No name attribute has been specified. This element will be polymorphic.");
 130  
         }
 131  
 
 132  
         // check that name is well formed
 133  8747
         if (nameAttributeValue != null
 134  8201
                 && !XMLUtils.isWellFormedXMLName(nameAttributeValue)) {
 135  26
             throw new SAXException("'" + nameAttributeValue
 136  13
                     + "' would not be a well formed xml element name.");
 137  
         }
 138  
 
 139  8734
         String implementationClass = attributes.getValue("class");
 140  8734
         if (log.isTraceEnabled()) {
 141  0
             log.trace("'class' attribute=" + implementationClass);
 142  
         }
 143  8734
         if (implementationClass != null) {
 144  
             try {
 145  
 
 146  26
                 Class clazz = Class.forName(implementationClass);
 147  26
                 descriptor.setImplementationClass(clazz);
 148  
 
 149  0
             } catch (Exception e) {
 150  0
                 if (log.isDebugEnabled()) {
 151  0
                     log.debug(
 152  0
                             "Cannot load class named: " + implementationClass,
 153  0
                             e);
 154  
                 }
 155  0
                 throw new SAXException("Cannot load class named: "
 156  0
                         + implementationClass);
 157  
             }
 158  
         }
 159  
 
 160  8734
         if (propertyName != null && propertyName.length() > 0) {
 161  8112
             boolean forceAccessible = "true".equals(attributes
 162  4056
                     .getValue("forceAccessible"));
 163  8112
             configureDescriptor(descriptor, attributes.getValue("updater"),
 164  4056
                     forceAccessible);
 165  
 
 166  
         } else {
 167  4678
             String value = attributes.getValue("value");
 168  4678
             if (value != null) {
 169  0
                 descriptor.setTextExpression(new ConstantExpression(value));
 170  
             }
 171  
         }
 172  
 
 173  8734
         Object top = digester.peek();
 174  8734
         if (top instanceof XMLBeanInfo) {
 175  3586
             XMLBeanInfo beanInfo = (XMLBeanInfo) top;
 176  3586
             beanInfo.setElementDescriptor(descriptor);
 177  3586
             beanClass = beanInfo.getBeanClass();
 178  3586
             descriptor.setPropertyType(beanClass);
 179  
 
 180  5148
         } else if (top instanceof ElementDescriptor) {
 181  5148
             ElementDescriptor parent = (ElementDescriptor) top;
 182  5148
             parent.addElementDescriptor(descriptor);
 183  
 
 184  
         } else {
 185  0
             throw new SAXException("Invalid use of <element>. It should "
 186  
                     + "be nested inside <info> or other <element> nodes");
 187  
         }
 188  
 
 189  8734
         digester.push(descriptor);
 190  8734
     }
 191  
 
 192  
     /**
 193  
      * Process the end of this element.
 194  
      */
 195  
     public void end(String name, String namespace) {
 196  8721
         Object top = digester.pop();
 197  8721
     }
 198  
 
 199  
     // Implementation methods
 200  
     // -------------------------------------------------------------------------
 201  
 
 202  
     /**
 203  
      * Sets the Expression and Updater from a bean property name Uses the
 204  
      * default updater (from the standard java bean property).
 205  
      * 
 206  
      * @param elementDescriptor
 207  
      *            configure this <code>ElementDescriptor</code>
 208  
      * @since 0.5
 209  
      */
 210  
     protected void configureDescriptor(ElementDescriptor elementDescriptor) {
 211  0
         configureDescriptor(elementDescriptor, null);
 212  0
     }
 213  
 
 214  
     /**
 215  
      * Sets the Expression and Updater from a bean property name Allows a custom
 216  
      * updater to be passed in.
 217  
      * 
 218  
      * @param elementDescriptor
 219  
      *            configure this <code>ElementDescriptor</code>
 220  
      * @param updateMethodName
 221  
      *            custom update method. If null, then use standard
 222  
      * @since 0.5
 223  
      * @deprecated now calls
 224  
      *             <code>#configureDescriptor(ElementDescriptor, String, boolean)</code>
 225  
      *             which allow accessibility to be forced. The subclassing API
 226  
      *             was not really considered carefully when this class was
 227  
      *             created. If anyone subclasses this method please contact the
 228  
      *             mailing list and suitable hooks will be placed into the code.
 229  
      */
 230  
     protected void configureDescriptor(ElementDescriptor elementDescriptor,
 231  
             String updateMethodName) {
 232  0
         configureDescriptor(elementDescriptor, null, false);
 233  0
     }
 234  
 
 235  
     /**
 236  
      * Sets the Expression and Updater from a bean property name Allows a custom
 237  
      * updater to be passed in.
 238  
      * 
 239  
      * @param elementDescriptor
 240  
      *            configure this <code>ElementDescriptor</code>
 241  
      * @param updateMethodName
 242  
      *            custom update method. If null, then use standard
 243  
      * @param forceAccessible
 244  
      *            if true and updateMethodName is not null, then non-public
 245  
      *            methods will be searched and made accessible
 246  
      *            (Method.setAccessible(true))
 247  
      */
 248  
     private void configureDescriptor(ElementDescriptor elementDescriptor,
 249  
             String updateMethodName, boolean forceAccessible) {
 250  4056
         Class beanClass = getBeanClass();
 251  4056
         if (beanClass != null) {
 252  3991
             String name = elementDescriptor.getPropertyName();
 253  7982
             PropertyDescriptor descriptor = getPropertyDescriptor(beanClass,
 254  3991
                     name);
 255  
 
 256  3991
             if (descriptor == null) {
 257  13
                 if (log.isDebugEnabled()) {
 258  0
                     log.debug("Cannot find property matching " + name);
 259  
                 }
 260  
             } else {
 261  7956
                 configureProperty(elementDescriptor, descriptor,
 262  3978
                         updateMethodName, forceAccessible, beanClass);
 263  
 
 264  3978
                 getProcessedPropertyNameSet().add(name);
 265  
             }
 266  
         }
 267  4056
     }
 268  
 
 269  
     /**
 270  
      * Configure an <code>ElementDescriptor</code> from a
 271  
      * <code>PropertyDescriptor</code>. A custom update method may be set.
 272  
      * 
 273  
      * @param elementDescriptor
 274  
      *            configure this <code>ElementDescriptor</code>
 275  
      * @param propertyDescriptor
 276  
      *            configure from this <code>PropertyDescriptor</code>
 277  
      * @param updateMethodName
 278  
      *            the name of the custom updater method to user. If null, then
 279  
      *            then
 280  
      * @param forceAccessible
 281  
      *            if true and updateMethodName is not null, then non-public
 282  
      *            methods will be searched and made accessible
 283  
      *            (Method.setAccessible(true))
 284  
      * @param beanClass
 285  
      *            the <code>Class</code> from which the update method should
 286  
      *            be found. This may be null only when
 287  
      *            <code>updateMethodName</code> is also null.
 288  
      */
 289  
     private void configureProperty(ElementDescriptor elementDescriptor,
 290  
             PropertyDescriptor propertyDescriptor, String updateMethodName,
 291  
             boolean forceAccessible, Class beanClass) {
 292  
 
 293  3978
         Class type = propertyDescriptor.getPropertyType();
 294  3978
         Method readMethod = propertyDescriptor.getReadMethod();
 295  3978
         Method writeMethod = propertyDescriptor.getWriteMethod();
 296  
 
 297  3978
         elementDescriptor.setPropertyType(type);
 298  
 
 299  
         // TODO: associate more bean information with the descriptor?
 300  
         // nodeDescriptor.setDisplayName( propertyDescriptor.getDisplayName() );
 301  
         // nodeDescriptor.setShortDescription(
 302  
         // propertyDescriptor.getShortDescription() );
 303  
 
 304  3978
         if (readMethod == null) {
 305  0
             log.trace("No read method");
 306  0
             return;
 307  
         }
 308  
 
 309  3978
         if (log.isTraceEnabled()) {
 310  0
             log.trace("Read method=" + readMethod.getName());
 311  
         }
 312  
 
 313  
         // choose response from property type
 314  
 
 315  3978
         if (getXMLIntrospector().isPrimitiveType(type)) {
 316  1456
             elementDescriptor
 317  1456
                     .setTextExpression(new MethodExpression(readMethod));
 318  
 
 319  2522
         } else if (getXMLIntrospector().isLoopType(type)) {
 320  2132
             log.trace("Loop type ??");
 321  
 
 322  
             // don't wrap this in an extra element as its specified in the
 323  
             // XML descriptor so no need.
 324  4264
             elementDescriptor.setContextExpression(new IteratorExpression(
 325  2132
                     new MethodExpression(readMethod)));
 326  2132
             elementDescriptor.setHollow(true);
 327  
 
 328  2132
             writeMethod = null;
 329  
 
 330  2132
             if (Map.class.isAssignableFrom(type)) {
 331  65
                 elementDescriptor.setLocalName("entry");
 332  
                 // add elements for reading
 333  65
                 ElementDescriptor keyDescriptor = new ElementDescriptor("key");
 334  65
                 keyDescriptor.setHollow(true);
 335  65
                 elementDescriptor.addElementDescriptor(keyDescriptor);
 336  
 
 337  130
                 ElementDescriptor valueDescriptor = new ElementDescriptor(
 338  65
                         "value");
 339  65
                 valueDescriptor.setHollow(true);
 340  65
                 elementDescriptor.addElementDescriptor(valueDescriptor);
 341  
             }
 342  
 
 343  
         } else {
 344  390
             log.trace("Standard property");
 345  390
             elementDescriptor.setHollow(true);
 346  780
             elementDescriptor.setContextExpression(new MethodExpression(
 347  390
                     readMethod));
 348  
         }
 349  
 
 350  
         // see if we have a custom method update name
 351  3978
         if (updateMethodName == null) {
 352  
             // set standard write method
 353  3341
             if (writeMethod != null) {
 354  1690
                 elementDescriptor.setUpdater(new MethodUpdater(writeMethod));
 355  
             }
 356  
 
 357  
         } else {
 358  
             // see if we can find and set the custom method
 359  637
             if (log.isTraceEnabled()) {
 360  0
                 log.trace("Finding custom method: ");
 361  0
                 log.trace("  on:" + beanClass);
 362  0
                 log.trace("  name:" + updateMethodName);
 363  
             }
 364  
 
 365  
             Method updateMethod;
 366  637
             boolean isMapTypeProperty = Map.class.isAssignableFrom(type);
 367  637
             if (forceAccessible) {
 368  117
                 updateMethod = findAnyMethod(updateMethodName, beanClass, isMapTypeProperty);
 369  
             } else {
 370  520
                 updateMethod = findPublicMethod(updateMethodName, beanClass, isMapTypeProperty);
 371  
             }
 372  
 
 373  637
             if (updateMethod == null) {
 374  0
                 if (log.isInfoEnabled()) {
 375  
 
 376  0
                     log.info("No method with name '" + updateMethodName
 377  0
                             + "' found for update");
 378  
                 }
 379  
             } else {
 380  
                 // assign updater to elementDescriptor
 381  637
                 if (Map.class.isAssignableFrom(type)) {
 382  
                     
 383  39
                     getXMLIntrospector().assignAdder(updateMethod, elementDescriptor);
 384  
 
 385  
                 } else {
 386  598
                     elementDescriptor
 387  598
                             .setUpdater(new MethodUpdater(updateMethod));
 388  598
                     Class singularType = updateMethod.getParameterTypes()[0];
 389  598
                     elementDescriptor.setSingularPropertyType(singularType);
 390  598
                     if (singularType != null)
 391  
                     {
 392  598
                         boolean isPrimitive = getXMLIntrospector().isPrimitiveType(singularType);
 393  598
                         if (isPrimitive)
 394  
                         {
 395  234
                            log.debug("Primitive collective: setting hollow to false");
 396  234
                            elementDescriptor.setHollow(false);
 397  
                         }
 398  
                     }
 399  598
                     if (log.isTraceEnabled()) {
 400  0
                         log.trace("Set custom updater on " + elementDescriptor);
 401  
                     }
 402  
                 }
 403  
             }
 404  
         }
 405  3978
     }
 406  
 
 407  
     private Method findPublicMethod(String updateMethodName, Class beanType, boolean isMapTypeProperty) {
 408  520
         Method[] methods = beanType.getMethods();
 409  520
         Method updateMethod = searchMethodsForMatch(updateMethodName, methods, isMapTypeProperty);
 410  520
         return updateMethod;
 411  
     }
 412  
 
 413  
     private Method searchMethodsForMatch(String updateMethodName,
 414  
             Method[] methods, boolean isMapType) {
 415  676
         Method updateMethod = null;
 416  2834
         for (int i = 0, size = methods.length; i < size; i++) {
 417  2795
             Method method = methods[i];
 418  2795
             if (updateMethodName.equals(method.getName())) {
 419  
 
 420  
                 // updater should have one parameter unless type is Map
 421  637
                 int numParams = 1;
 422  637
                 if (isMapType) {
 423  
                     // updater for Map should have two parameters
 424  39
                     numParams = 2;
 425  
                 }
 426  
 
 427  
                 // we have a matching name
 428  
                 // check paramters are correct
 429  637
                 if (methods[i].getParameterTypes().length == numParams) {
 430  
                     // we'll use first match
 431  637
                     updateMethod = methods[i];
 432  637
                     if (log.isTraceEnabled()) {
 433  0
                         log.trace("Matched method:" + updateMethod);
 434  
                     }
 435  
                     // done since we're using the first match
 436  0
                     break;
 437  
                 }
 438  
             }
 439  
         }
 440  676
         return updateMethod;
 441  
     }
 442  
 
 443  
     private Method findAnyMethod(String updateMethodName, Class beanType, boolean isMapTypeProperty) {
 444  
         // TODO: suspect that this algorithm may run into difficulties
 445  
         // on older JVMs (particularly with package privilage interfaces).
 446  
         // This seems like too esoteric a use case to worry to much about now
 447  117
         Method updateMethod = null;
 448  117
         Class classToTry = beanType;
 449  
         do {
 450  156
             Method[] methods = classToTry.getDeclaredMethods();
 451  156
             updateMethod = searchMethodsForMatch(updateMethodName, methods, isMapTypeProperty);
 452  
 
 453  
             // try next superclass - Object will return null and end loop if no
 454  
             // method is found
 455  156
             classToTry = classToTry.getSuperclass();
 456  156
         } while (updateMethod == null && classToTry != null);
 457  
 
 458  117
         if (updateMethod != null) {
 459  234
             boolean isPublic = Modifier.isPublic(updateMethod.getModifiers())
 460  0
                     && Modifier.isPublic(beanType.getModifiers());
 461  117
             if (!isPublic) {
 462  117
                 updateMethod.setAccessible(true);
 463  
             }
 464  
         }
 465  117
         return updateMethod;
 466  
     }
 467  
 }