/* $Id: SetNestedPropertiesRule.java 992060 2010-09-02 19:09:47Z simonetripodi $ * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.digester; import java.util.List; import java.util.LinkedList; import java.util.ArrayList; import java.util.HashMap; import java.beans.PropertyDescriptor; import org.apache.commons.beanutils.BeanUtils; import org.apache.commons.beanutils.DynaBean; import org.apache.commons.beanutils.DynaProperty; import org.apache.commons.beanutils.PropertyUtils; import org.xml.sax.Attributes; import org.apache.commons.logging.Log; /** *
Rule implementation that sets properties on the object at the top of the * stack, based on child elements with names matching properties on that * object.
* *Example input that can be processed by this rule:
** [widget] * [height]7[/height] * [width]8[/width] * [label]Hello, world[/label] * [/widget] ** *
For each child element of [widget], a corresponding setter method is * located on the object on the top of the digester stack, the body text of * the child element is converted to the type specified for the (sole) * parameter to the setter method, then the setter method is invoked.
* *This rule supports custom mapping of xml element names to property names. * The default mapping for particular elements can be overridden by using * {@link #SetNestedPropertiesRule(String[] elementNames, * String[] propertyNames)}. * This allows child elements to be mapped to properties with different names. * Certain elements can also be marked to be ignored.
* *A very similar effect can be achieved using a combination of the
* BeanPropertySetterRule
and the ExtendedBaseRules
* rules manager; this Rule
, however, works fine with the default
* RulesBase
rules manager.
Note that this rule is designed to be used to set only "primitive" * bean properties, eg String, int, boolean. If some of the child xml elements * match ObjectCreateRule rules (ie cause objects to be created) then you must * use one of the more complex constructors to this rule to explicitly skip * processing of that xml element, and define a SetNextRule (or equivalent) to * handle assigning the child object to the appropriate property instead.
* *Implementation Notes
* *This class works by creating its own simple Rules implementation. When * begin is invoked on this rule, the digester's current rules object is * replaced by a custom one. When end is invoked for this rule, the original * rules object is restored. The digester rules objects therefore behave in * a stack-like manner.
* *For each child element encountered, the custom Rules implementation * ensures that a special AnyChildRule instance is included in the matches * returned to the digester, and it is this rule instance that is responsible * for setting the appropriate property on the target object (if such a property * exists). The effect is therefore like a "trailing wildcard pattern". The * custom Rules implementation also returns the matches provided by the * underlying Rules implementation for the same pattern, so other rules * are not "disabled" during processing of a SetNestedPropertiesRule.
* *TODO: Optimise this class. Currently, each time begin is called, * new AnyChildRules and AnyChildRule objects are created. It should be * possible to cache these in normal use (though watch out for when a rule * instance is invoked re-entrantly!).
* * @since 1.6 */ public class SetNestedPropertiesRule extends Rule { private Log log = null; private boolean trimData = true; private boolean allowUnknownChildElements = false; private HashMapIt is an error if a child xml element exists but the target java * bean has no such property (unless setAllowUnknownChildElements has been * set to true).
*/ public SetNestedPropertiesRule() { // nothing to set up } /** *Convenience constructor which overrides the default mappings for * just one property.
* *For details about how this works, see * {@link #SetNestedPropertiesRule(String[] elementNames, * String[] propertyNames)}.
* * @param elementName is the child xml element to match * @param propertyName is the java bean property to be assigned the value * of the specified xml element. This may be null, in which case the * specified xml element will be ignored. */ public SetNestedPropertiesRule(String elementName, String propertyName) { elementNames.put(elementName, propertyName); } /** *Constructor which allows element->property mapping to be overridden. *
* *Two arrays are passed in. One contains xml element names and the * other java bean property names. The element name / property name pairs * are matched by position; in order words, the first string in the element * name array corresponds to the first string in the property name array * and so on.
* *If a property name is null or the xml element name has no matching * property name due to the arrays being of different lengths then this * indicates that the xml element should be ignored.
* * The following constructs a rule that maps the alt-city
* element to the city
property and the alt-state
* to the state
property. All other child elements are mapped
* as usual using exact name matching.
*
*
* SetNestedPropertiesRule(
* new String[] {"alt-city", "alt-state"},
* new String[] {"city", "state"});
*
The following constructs a rule that maps the class
* xml element to the className
property. The xml element
* ignore-me
is not mapped, ie is ignored. All other elements
* are mapped as usual using exact name matching.
*
*
* SetPropertiesRule(
* new String[] {"class", "ignore-me"},
* new String[] {"className"});
*
* When set to true, any child element for which there is no * corresponding object property will simply be ignored. *
* The default value of this attribute is false (unknown child elements * are not allowed). */ public void setAllowUnknownChildElements(boolean allowUnknownChildElements) { this.allowUnknownChildElements = allowUnknownChildElements; } /** See {@link #setAllowUnknownChildElements}. */ public boolean getAllowUnknownChildElements() { return allowUnknownChildElements; } /** * Process the beginning of this element. * * @param namespace is the namespace this attribute is in, or null * @param name is the name of the current xml element * @param attributes is the attribute list of this element */ @Override public void begin(String namespace, String name, Attributes attributes) throws Exception { Rules oldRules = digester.getRules(); AnyChildRule anyChildRule = new AnyChildRule(); anyChildRule.setDigester(digester); AnyChildRules newRules = new AnyChildRules(anyChildRule); newRules.init(digester.getMatch()+"/", oldRules); digester.setRules(newRules); } /** * This is only invoked after all child elements have been processed, * so we can remove the custom Rules object that does the * child-element-matching. */ @Override public void body(String bodyText) throws Exception { AnyChildRules newRules = (AnyChildRules) digester.getRules(); digester.setRules(newRules.getOldRules()); } /** * Add an additional custom xml-element -> property mapping. *
* This is primarily intended to be used from the xml rules module
* (as it is not possible there to pass the necessary parameters to the
* constructor for this class). However it is valid to use this method
* directly if desired.
*/
public void addAlias(String elementName, String propertyName) {
elementNames.put(elementName, propertyName);
}
/**
* Render a printable version of this Rule.
*/
@Override
public String toString() {
StringBuffer sb = new StringBuffer("SetNestedPropertiesRule[");
sb.append("allowUnknownChildElements=");
sb.append(allowUnknownChildElements);
sb.append(", trimData=");
sb.append(trimData);
sb.append(", elementNames=");
sb.append(elementNames);
sb.append("]");
return sb.toString();
}
//----------------------------------------- local classes
/** Private Rules implementation */
private class AnyChildRules implements Rules {
private String matchPrefix = null;
private Rules decoratedRules = null;
private ArrayList