Save This Page
Home » commons-digester-2.0-src » org.apache.commons » digester » [javadoc | source]
    1   /* $Id: SetPropertiesRule.java 729103 2008-12-23 20:42:59Z rahul $
    2    *
    3    * Licensed to the Apache Software Foundation (ASF) under one or more
    4    * contributor license agreements.  See the NOTICE file distributed with
    5    * this work for additional information regarding copyright ownership.
    6    * The ASF licenses this file to You under the Apache License, Version 2.0
    7    * (the "License"); you may not use this file except in compliance with
    8    * the License.  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   
   19   
   20   package org.apache.commons.digester;
   21   
   22   
   23   import java.util.HashMap;
   24   
   25   import org.apache.commons.beanutils.BeanUtils;
   26   import org.apache.commons.beanutils.PropertyUtils;
   27   import org.xml.sax.Attributes;
   28   
   29   
   30   /**
   31    * <p>Rule implementation that sets properties on the object at the top of the
   32    * stack, based on attributes with corresponding names.</p>
   33    *
   34    * <p>This rule supports custom mapping of attribute names to property names.
   35    * The default mapping for particular attributes can be overridden by using 
   36    * {@link #SetPropertiesRule(String[] attributeNames, String[] propertyNames)}.
   37    * This allows attributes to be mapped to properties with different names.
   38    * Certain attributes can also be marked to be ignored.</p>
   39    */
   40   
   41   public class SetPropertiesRule extends Rule {
   42   
   43   
   44       // ----------------------------------------------------------- Constructors
   45   
   46   
   47       /**
   48        * Default constructor sets only the the associated Digester.
   49        *
   50        * @param digester The digester with which this rule is associated
   51        *
   52        * @deprecated The digester instance is now set in the {@link Digester#addRule} method. 
   53        * Use {@link #SetPropertiesRule()} instead.
   54        */
   55       public SetPropertiesRule(Digester digester) {
   56   
   57           this();
   58   
   59       }
   60       
   61   
   62       /**
   63        * Base constructor.
   64        */
   65       public SetPropertiesRule() {
   66   
   67           // nothing to set up 
   68   
   69       }
   70       
   71       /** 
   72        * <p>Convenience constructor overrides the mapping for just one property.</p>
   73        *
   74        * <p>For details about how this works, see
   75        * {@link #SetPropertiesRule(String[] attributeNames, String[] propertyNames)}.</p>
   76        *
   77        * @param attributeName map this attribute 
   78        * @param propertyName to a property with this name
   79        */
   80       public SetPropertiesRule(String attributeName, String propertyName) {
   81           
   82           attributeNames = new String[1];
   83           attributeNames[0] = attributeName;
   84           propertyNames = new String[1];
   85           propertyNames[0] = propertyName;
   86       }
   87       
   88       /** 
   89        * <p>Constructor allows attribute->property mapping to be overriden.</p>
   90        *
   91        * <p>Two arrays are passed in. 
   92        * One contains the attribute names and the other the property names.
   93        * The attribute name / property name pairs are match by position
   94        * In order words, the first string in the attribute name list matches
   95        * to the first string in the property name list and so on.</p>
   96        *
   97        * <p>If a property name is null or the attribute name has no matching
   98        * property name, then this indicates that the attibute should be ignored.</p>
   99        * 
  100        * <h5>Example One</h5>
  101        * <p> The following constructs a rule that maps the <code>alt-city</code>
  102        * attribute to the <code>city</code> property and the <code>alt-state</code>
  103        * to the <code>state</code> property. 
  104        * All other attributes are mapped as usual using exact name matching.
  105        * <code><pre>
  106        *      SetPropertiesRule(
  107        *                new String[] {"alt-city", "alt-state"}, 
  108        *                new String[] {"city", "state"});
  109        * </pre></code>
  110        *
  111        * <h5>Example Two</h5>
  112        * <p> The following constructs a rule that maps the <code>class</code>
  113        * attribute to the <code>className</code> property.
  114        * The attribute <code>ignore-me</code> is not mapped.
  115        * All other attributes are mapped as usual using exact name matching.
  116        * <code><pre>
  117        *      SetPropertiesRule(
  118        *                new String[] {"class", "ignore-me"}, 
  119        *                new String[] {"className"});
  120        * </pre></code>
  121        *
  122        * @param attributeNames names of attributes to map
  123        * @param propertyNames names of properties mapped to
  124        */
  125       public SetPropertiesRule(String[] attributeNames, String[] propertyNames) {
  126           // create local copies
  127           this.attributeNames = new String[attributeNames.length];
  128           for (int i=0, size=attributeNames.length; i<size; i++) {
  129               this.attributeNames[i] = attributeNames[i];
  130           }
  131           
  132           this.propertyNames = new String[propertyNames.length];
  133           for (int i=0, size=propertyNames.length; i<size; i++) {
  134               this.propertyNames[i] = propertyNames[i];
  135           } 
  136       }
  137           
  138       // ----------------------------------------------------- Instance Variables
  139       
  140       /** 
  141        * Attribute names used to override natural attribute->property mapping
  142        */
  143       private String [] attributeNames;
  144       /** 
  145        * Property names used to override natural attribute->property mapping
  146        */    
  147       private String [] propertyNames;
  148   
  149       /**
  150        * Used to determine whether the parsing should fail if an property specified
  151        * in the XML is missing from the bean. Default is true for backward compatibility.
  152        */
  153       private boolean ignoreMissingProperty = true;
  154   
  155   
  156       // --------------------------------------------------------- Public Methods
  157   
  158   
  159       /**
  160        * Process the beginning of this element.
  161        *
  162        * @param attributes The attribute list of this element
  163        */
  164       public void begin(Attributes attributes) throws Exception {
  165           
  166           // Build a set of attribute names and corresponding values
  167           HashMap<String, String> values = new HashMap<String, String>();
  168           
  169           // set up variables for custom names mappings
  170           int attNamesLength = 0;
  171           if (attributeNames != null) {
  172               attNamesLength = attributeNames.length;
  173           }
  174           int propNamesLength = 0;
  175           if (propertyNames != null) {
  176               propNamesLength = propertyNames.length;
  177           }
  178           
  179           
  180           for (int i = 0; i < attributes.getLength(); i++) {
  181               String name = attributes.getLocalName(i);
  182               if ("".equals(name)) {
  183                   name = attributes.getQName(i);
  184               }
  185               String value = attributes.getValue(i);
  186               
  187               // we'll now check for custom mappings
  188               for (int n = 0; n<attNamesLength; n++) {
  189                   if (name.equals(attributeNames[n])) {
  190                       if (n < propNamesLength) {
  191                           // set this to value from list
  192                           name = propertyNames[n];
  193                       
  194                       } else {
  195                           // set name to null
  196                           // we'll check for this later
  197                           name = null;
  198                       }
  199                       break;
  200                   }
  201               } 
  202               
  203               if (digester.log.isDebugEnabled()) {
  204                   digester.log.debug("[SetPropertiesRule]{" + digester.match +
  205                           "} Setting property '" + name + "' to '" +
  206                           value + "'");
  207               }
  208               
  209               if ((!ignoreMissingProperty) && (name != null)) {
  210                   // The BeanUtils.populate method silently ignores items in
  211                   // the map (ie xml entities) which have no corresponding
  212                   // setter method, so here we check whether each xml attribute
  213                   // does have a corresponding property before calling the
  214                   // BeanUtils.populate method.
  215                   //
  216                   // Yes having the test and set as separate steps is ugly and 
  217                   // inefficient. But BeanUtils.populate doesn't provide the 
  218                   // functionality we need here, and changing the algorithm which 
  219                   // determines the appropriate setter method to invoke is 
  220                   // considered too risky.
  221                   //
  222                   // Using two different classes (PropertyUtils vs BeanUtils) to
  223                   // do the test and the set is also ugly; the codepaths
  224                   // are different which could potentially lead to trouble.
  225                   // However the BeanUtils/ProperyUtils code has been carefully 
  226                   // compared and the PropertyUtils functionality does appear 
  227                   // compatible so we'll accept the risk here.
  228                   
  229                   Object top = digester.peek();
  230                   boolean test =  PropertyUtils.isWriteable(top, name);
  231                   if (!test)
  232                       throw new NoSuchMethodException("Property " + name + " can't be set");
  233               }
  234               
  235               if (name != null) {
  236                   values.put(name, value);
  237               } 
  238           }
  239   
  240           // Populate the corresponding properties of the top object
  241           Object top = digester.peek();
  242           if (digester.log.isDebugEnabled()) {
  243               if (top != null) {
  244                   digester.log.debug("[SetPropertiesRule]{" + digester.match +
  245                                      "} Set " + top.getClass().getName() +
  246                                      " properties");
  247               } else {
  248                   digester.log.debug("[SetPropertiesRule]{" + digester.match +
  249                                      "} Set NULL properties");
  250               }
  251           }
  252           BeanUtils.populate(top, values);
  253   
  254   
  255       }
  256   
  257   
  258       /**
  259        * <p>Add an additional attribute name to property name mapping.
  260        * This is intended to be used from the xml rules.
  261        */
  262       public void addAlias(String attributeName, String propertyName) {
  263           
  264           // this is a bit tricky.
  265           // we'll need to resize the array.
  266           // probably should be synchronized but digester's not thread safe anyway
  267           if (attributeNames == null) {
  268               
  269               attributeNames = new String[1];
  270               attributeNames[0] = attributeName;
  271               propertyNames = new String[1];
  272               propertyNames[0] = propertyName;        
  273               
  274           } else {
  275               int length = attributeNames.length;
  276               String [] tempAttributes = new String[length + 1];
  277               for (int i=0; i<length; i++) {
  278                   tempAttributes[i] = attributeNames[i];
  279               }
  280               tempAttributes[length] = attributeName;
  281               
  282               String [] tempProperties = new String[length + 1];
  283               for (int i=0; i<length && i< propertyNames.length; i++) {
  284                   tempProperties[i] = propertyNames[i];
  285               }
  286               tempProperties[length] = propertyName;
  287               
  288               propertyNames = tempProperties;
  289               attributeNames = tempAttributes;
  290           }        
  291       }
  292     
  293   
  294       /**
  295        * Render a printable version of this Rule.
  296        */
  297       public String toString() {
  298   
  299           StringBuffer sb = new StringBuffer("SetPropertiesRule[");
  300           sb.append("]");
  301           return (sb.toString());
  302   
  303       }
  304   
  305       /**
  306        * <p>Are attributes found in the xml without matching properties to be ignored?
  307        * </p><p>
  308        * If false, the parsing will interrupt with an <code>NoSuchMethodException</code>
  309        * if a property specified in the XML is not found. The default is true.
  310        * </p>
  311        * @return true if skipping the unmatched attributes.
  312        */
  313       public boolean isIgnoreMissingProperty() {
  314   
  315           return this.ignoreMissingProperty;
  316       }
  317   
  318       /**
  319        * Sets whether attributes found in the xml without matching properties 
  320        * should be ignored.
  321        * If set to false, the parsing will throw an <code>NoSuchMethodException</code>
  322        * if an unmatched
  323        * attribute is found. This allows to trap misspellings in the XML file.
  324        * @param ignoreMissingProperty false to stop the parsing on unmatched attributes.
  325        */
  326       public void setIgnoreMissingProperty(boolean ignoreMissingProperty) {
  327   
  328           this.ignoreMissingProperty = ignoreMissingProperty;
  329       }
  330   
  331   
  332   }

Save This Page
Home » commons-digester-2.0-src » org.apache.commons » digester » [javadoc | source]