You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@commons.apache.org by sk...@apache.org on 2005/02/07 09:02:32 UTC

svn commit: r151707 - jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/actions/SetPropertiesAction.java

Author: skitching
Date: Mon Feb  7 00:02:30 2005
New Revision: 151707

URL: http://svn.apache.org/viewcvs?view=rev&rev=151707
Log:
Major rework.

Modified:
    jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/actions/SetPropertiesAction.java   (contents, props changed)

Modified: jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/actions/SetPropertiesAction.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/actions/SetPropertiesAction.java?view=diff&r1=151706&r2=151707
==============================================================================
--- jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/actions/SetPropertiesAction.java (original)
+++ jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/actions/SetPropertiesAction.java Mon Feb  7 00:02:30 2005
@@ -1,24 +1,25 @@
-/* $Id: $
+/* $Id$
+ *
+ * Copyright 2001-2005 The Apache Software Foundation.
  *
- * Copyright 2001-2004 The Apache Software Foundation.
- * 
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
- * 
+ *
  *      http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
- */ 
+ */
 
 
 package org.apache.commons.digester2.actions;
 
 import org.xml.sax.Attributes;
+import java.util.Map;
 import java.util.HashMap;
 
 import org.apache.commons.beanutils.BeanUtils;
@@ -28,77 +29,121 @@
 import org.apache.commons.digester2.AbstractAction;
 import org.apache.commons.digester2.ParseException;
 
-
-
 /**
- * <p>Rule implementation that sets properties on the object at the top of the
+ * <p>An Action that sets properties on the object at the top of the
  * stack, based on attributes with corresponding names.</p>
  *
- * <p>This rule supports custom mapping of attribute names to property names.
- * The default mapping for particular attributes can be overridden by using 
+ * <p>By default, any xml attribute xyz='value' causes a call to property-setter
+ * method setXyz(value) on the top object on the stack. If the target method
+ * does not take a String parameter, then the BeanUtils library attempts
+ * to convert the String value of the xml attribute to the required type.</p>
+ *
+ * <p>Custom mapping of attribute names to property names can also be done.
+ * The default mapping for particular attributes can be overridden by using
  * {@link #ActionSetProperties(String[] attributeNames, String[] propertyNames)}.
  * This allows attributes to be mapped to properties with different names.
- * Certain attributes can also be marked to be ignored.</p>
+ * Certain attributes can also be marked to be ignored, by specifying the
+ * target property to be null.</p>
+ *
+ * <p>XML Attributes that are not in the default namespace are ignored.</p>
  */
 
 public class SetPropertiesAction extends AbstractAction {
 
-    // ----------------------------------------------------------- Constructors
+    private HashMap customMap = null;
+
+    // -----------------------------------------------------------
+    // Constructors
+    // -----------------------------------------------------------
 
     /**
      * Base constructor.
      */
     public SetPropertiesAction() {
-        // nothing to set up 
+        // nothing to set up
     }
-    
-    /** 
-     * <p>Convenience constructor overrides the mapping for just one property.</p>
+
+    /**
+     * <p>Constructor which allows the default attribute->property mapping to
+     * be overriden.</p>
+     *
+     * <p>The keys of the map are xml attribute names, and the associated values
+     * are the java property name to map that attribute to. If the value
+     * associated with an attribute name is null then the attibute will be
+     * ignored.</p>
+     *
+     * <h5>Example</h5>
+     * <p> The following constructs a rule that maps the <code>class</code>
+     * attribute to the <code>className</code> property. The attribute
+     * <code>ignore</code> is not mapped, and will therefore be ignored rather
+     * than be passed to a setIgnore method (the default behaviour). All other
+     * attributes are mapped as usual using exact name matching.
+     * <code><pre>
+     *      HashMap map = new HashMap();
+     *      map.put("class", "className");
+     *      map.put("ignore", null);
+     *      SetPropertiesAction(map);
+     * </pre></code></p>
+     *
+     * <p>See also {@link #addAlias} which allows the custom mapping to be
+     * modified after the SetPropertiesAction has been constructed.</p>
+     *
+     * @param customMap is a map. The map is copied, so future changes to
+     * the map will not affect this object. 
+     */
+    public SetPropertiesAction(Map customMap) {
+        this.customMap = new HashMap(customMap);
+    }
+
+    /**
+     * <p>Convenience constructor which overrides the default attribute->property
+     * mapping for just one property.</p>
      *
      * <p>For details about how this works, see
-     * {@link #ActionSetProperties(String[] attributeNames, String[] propertyNames)}.</p>
+     *   {@link #ActionSetProperties(Map customMappings)}.</p>
      *
-     * @param attributeName map this attribute 
-     * @param propertyName to a property with this name
+     * @param attributeName map this attribute. Must not be null.
+     * @param propertyName to a property with this name. May be null.
      */
     public SetPropertiesAction(String attributeName, String propertyName) {
-        attributeNames = new String[1];
-        attributeNames[0] = attributeName;
-        propertyNames = new String[1];
-        propertyNames[0] = propertyName;
+        customMap = new HashMap(1);
+        customMap.put(attributeName, propertyName);
     }
-    
-    /** 
-     * <p>Constructor allows attribute->property mapping to be overriden.</p>
+
+    /**
+     * <p>Constructor which allows the default attribute->property mapping to
+     * be overriden.</p>
      *
-     * <p>Two arrays are passed in. 
-     * One contains the attribute names and the other the property names.
-     * The attribute name / property name pairs are match by position
-     * In order words, the first string in the attribute name list matches
-     * to the first string in the property name list and so on.</p>
+     * <p>Two arrays are passed in. One contains the attribute names and the
+     * other the property names. The attribute name / property name pairs are
+     * matched by position In order words, the first string in the attribute
+     * name list maps to the first string in the property name list and so on.
+     * </p>
      *
      * <p>If a property name is null or the attribute name has no matching
-     * property name, then this indicates that the attibute should be ignored.</p>
-     * 
+     * property name (ie the property array is shorter than the attribute array)
+     * then the attibute will be ignored.</p>
+     *
      * <h5>Example One</h5>
      * <p> The following constructs a rule that maps the <code>alt-city</code>
      * attribute to the <code>city</code> property and the <code>alt-state</code>
-     * to the <code>state</code> property. 
+     * to the <code>state</code> property.
      * All other attributes are mapped as usual using exact name matching.
      * <code><pre>
-     *      SetPropertiesRule(
-     *                new String[] {"alt-city", "alt-state"}, 
+     *      SetPropertiesAction(
+     *                new String[] {"alt-city", "alt-state"},
      *                new String[] {"city", "state"});
      * </pre></code>
      *
      * <h5>Example Two</h5>
      * <p> The following constructs a rule that maps the <code>class</code>
-     * attribute to the <code>className</code> property.
-     * The attribute <code>ignore-me</code> is not mapped.
-     * All other attributes are mapped as usual using exact name matching.
+     * attribute to the <code>className</code> property. The attribute
+     * <code>ignore</code> is not mapped, and will therefore be ignored rather
+     * than be passed to a setIgnore method (the default behaviour). All other
+     * attributes are mapped as usual using exact name matching.
      * <code><pre>
-     *      SetPropertiesRule(
-     *                new String[] {"class", "ignore-me"}, 
+     *      SetPropertiesAction(
+     *                new String[] {"class", "ignore"},
      *                new String[] {"className"});
      * </pre></code>
      *
@@ -106,105 +151,117 @@
      * @param propertyNames names of properties mapped to
      */
     public SetPropertiesAction(String[] attributeNames, String[] propertyNames) {
-        // create local copies
-        this.attributeNames = new String[attributeNames.length];
-        for (int i=0, size=attributeNames.length; i<size; i++) {
-            this.attributeNames[i] = attributeNames[i];
+        int nAttributes = attributeNames.length;
+        int nProperties = propertyNames.length;
+
+        customMap = new HashMap(nAttributes);
+
+        for(int i=0; i<nAttributes; ++i) {
+            if (i < nProperties) {
+                customMap.put(attributeNames[i], propertyNames[i]);
+            } else {
+                customMap.put(attributeNames[i], null);
+            }
         }
-        
-        this.propertyNames = new String[propertyNames.length];
-        for (int i=0, size=propertyNames.length; i<size; i++) {
-            this.propertyNames[i] = propertyNames[i];
-        } 
     }
-        
-    // ----------------------------------------------------- Instance Variables
-    
-    /** 
-     * Attribute names used to override natural attribute->property mapping
-     */
-    private String [] attributeNames;
 
-    /** 
-     * Property names used to override natural attribute->property mapping
-     */    
-    private String [] propertyNames;
+    // ---------------------------------------------------------
+    // Public Methods
+    // ---------------------------------------------------------
 
+    /**
+     * <p>Add an additional attribute name to property name mapping.
+     * This is particularly useful for the xmlrules optional module.</p>
+     *
+     * <p>See {@link #SetPropertiesAction(Map customMap)}.
+     */
+    public void addAlias(String attributeName, String propertyName) {
+        if (customMap == null) {
+            customMap = new HashMap();
+        }
+        customMap.put(attributeName, propertyName);
+    }
 
-    // --------------------------------------------------------- Public Methods
+    // ---------------------------------------------------------
+    // Action Methods
+    // ---------------------------------------------------------
 
     /**
      * Process the beginning of this element.
      *
-     * @param attributes The attribute list of this element
+     * @param context The object on which all parsing state is stored.
+     * @param namespace The xml namespace the current element is in.
+     * @param name The local name of the current element.
+     * @param attributes The attribute list of the current element
      */
     public void begin(
-    Context context, String namespace, String elementName, Attributes attributes) 
+    Context context,
+    String namespace, String elementName,
+    Attributes attributes)
     throws ParseException {
-        
+
         Log log = context.getLogger();
 
-        // Build a set of attribute names and corresponding values
+        // Build a set of property names and corresponding values
         HashMap values = new HashMap();
-        
-        // set up variables for custom names mappings
-        int attNamesLength = 0;
-        if (attributeNames != null) {
-            attNamesLength = attributeNames.length;
-        }
-        int propNamesLength = 0;
-        if (propertyNames != null) {
-            propNamesLength = propertyNames.length;
-        }
-        
+
+        // for each xml attribute
         for (int i = 0; i < attributes.getLength(); i++) {
-            String name = attributes.getLocalName(i);
-            if ("".equals(name)) {
-                name = attributes.getQName(i);
-            }
-            String value = attributes.getValue(i);
-            
-            // we'll now check for custom mappings
-            for (int n = 0; n<attNamesLength; n++) {
-                if (name.equals(attributeNames[n])) {
-                    if (n < propNamesLength) {
-                        // set this to value from list
-                        name = propertyNames[n];
-                    
-                    } else {
-                        // set name to null
-                        // we'll check for this later
-                        name = null;
-                    }
-                    break;
+            if (attributes.getURI(i).length() > 0) {
+                // currently we ignore any attributes with namespaces
+                if (log.isDebugEnabled()) {
+                    log.debug(
+                        "[SetProperties]{" + context.getMatchPath() +
+                        "} Ignoring namespaced xml attribute "
+                        + attributes.getLocalName(i));
+                }
+            } else {
+                String attrName = attributes.getLocalName(i);
+                if ("".equals(attrName)) {
+                    attrName = attributes.getQName(i);
+                }
+                String value = attributes.getValue(i);
+
+                String propName;
+
+                // We'll now check for custom mappings. Note that propName
+                // can be set to null by this...
+                if ((customMap != null) && customMap.containsKey(attrName)) {
+                    propName = customMap.get(attrName).toString();
+                } else {
+                    // if attrName contains a hyphen, it will be converted
+                    // to camelCase, otherwise we just try to use the
+                    // unmodified attrName as the propName.
+                    propName = convertHyphenatedToCamelCase(attrName);
                 }
-            } 
 
-            if (log.isDebugEnabled()) {
-                log.debug("[SetProperties]{" + context.getMatchPath() +
-                        "} Setting property '" + name + "' to '" +
-                        value + "'");
+                if (propName != null) {
+                    if (log.isDebugEnabled()) {
+                        log.debug("[SetProperties]{" + context.getMatchPath() +
+                                "} Setting property '" + propName + "' to '" +
+                                value + "'");
+                    }
+    
+                    values.put(propName, value);
+                }
             }
-            if (name != null) {
-                values.put(name, value);
-            } 
         }
 
         // Populate the corresponding properties of the top object
-        Object top = context.peek();
+        Object target = context.peek();
         if (log.isDebugEnabled()) {
-            if (top != null) {
+            if (target != null) {
                 log.debug("[ActionSetProperties]{" + context.getMatchPath() +
-                                   "} Set " + top.getClass().getName() +
+                                   "} Set " + target.getClass().getName() +
                                    " properties");
             } else {
                 log.debug("[ActionSetProperties]{" + context.getMatchPath() +
                                    "} Set NULL properties");
             }
         }
-
+        
         try {
-            BeanUtils.populate(top, values);
+            BeanUtils.populate(target, values);
         } catch(IllegalAccessException ex) {
             throw new ParseException(ex);
         } catch(java.lang.reflect.InvocationTargetException ex) {
@@ -213,50 +270,69 @@
     }
 
 
+    // ---------------------------------------------------------
+    // Private Methods
+    // ---------------------------------------------------------
+
     /**
-     * <p>Add an additional attribute name to property name mapping.
-     * This is intended to be used from the xml rules.
+     * <p>This method is intended to convert xml hypenated names
+     * into javabean names.</p>
+     *
+     * <p>Traditionally, xml element and attribute names that are 
+     * formed from multiple words use hyphens to separate the words,
+     * for example a-long-attribute-name. However the Java tradition
+     * is to use camel-case, eg aLongAttributeName. This method
+     * converts from the xml to the java convention.</p>
+     *
+     * <p>If src contains no hyphens then a reference to the same object
+     * is returned.</p>
+     *
+     * <p>If src ends in a hyphen, then it is stripped.</p>
+     *
+     * @param src is the string to be converted. Must not be null.
      */
-    public void addAlias(String attributeName, String propertyName) {
+    private String convertHyphenatedToCamelCase(String src) {
+        // check whether there is a hyphen in this string or not 
+        int firstPos = src.indexOf("-");
+        if (firstPos == -1) {
+            return src;
+        }
         
-        // this is a bit tricky.
-        // we'll need to resize the array.
-        // probably should be synchronized but digester's not thread safe anyway
-        if (attributeNames == null) {
-            
-            attributeNames = new String[1];
-            attributeNames[0] = attributeName;
-            propertyNames = new String[1];
-            propertyNames[0] = propertyName;        
-            
-        } else {
-            int length = attributeNames.length;
-            String [] tempAttributes = new String[length + 1];
-            for (int i=0; i<length; i++) {
-                tempAttributes[i] = attributeNames[i];
-            }
-            tempAttributes[length] = attributeName;
-            
-            String [] tempProperties = new String[length + 1];
-            for (int i=0; i<length && i< propertyNames.length; i++) {
-                tempProperties[i] = propertyNames[i];
+        int srcLen = src.length();
+        StringBuffer buf = new StringBuffer(srcLen);
+        
+        // bulk append up until the first hyphen, as we already know
+        // where it is.
+        buf.append(src.substring(0, firstPos));
+        
+        // now it is easiest to simply step through char-by-char until
+        // the end of the string.
+        boolean cap = true;
+        for(int i=firstPos+1; i<srcLen; ++i) {
+            char c = src.charAt(i);
+            if (c == '-') {
+                cap = true;
+            } else if (cap) {
+                buf.append(Character.toUpperCase(c));
+                cap = false;
+            } else {
+                buf.append(c);
             }
-            tempProperties[length] = propertyName;
-            
-            propertyNames = tempProperties;
-            attributeNames = tempAttributes;
-        }        
+        }
+        
+        return buf.toString();
     }
-  
+
+    // ---------------------------------------------------------
+    // Other Methods
+    // ---------------------------------------------------------
 
     /**
      * Render a printable version of this Rule.
      */
     public String toString() {
-
         StringBuffer sb = new StringBuffer("SetPropertiesRule[");
         sb.append("]");
         return (sb.toString());
-
     }
 }

Propchange: jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/actions/SetPropertiesAction.java
------------------------------------------------------------------------------
    svn:keywords = Id



---------------------------------------------------------------------
To unsubscribe, e-mail: commons-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: commons-dev-help@jakarta.apache.org