All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.ocpsoft.shade.org.apache.commons.digester.SetNestedPropertiesRule Maven / Gradle / Ivy

The newest version!
/* $Id: SetNestedPropertiesRule.java 729101 2008-12-23 20:41:52Z rahul $
 *
 * 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 HashMap elementNames = new HashMap(); // ----------------------------------------------------------- Constructors /** * Base constructor, which maps every child element into a bean property * with the same name as the xml element. * *

It 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.

* *
Example One
*

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"});
     * 
*

* *
Example Two
*

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"});
     * 
*

* * @param elementNames names of elements to map * @param propertyNames names of properties mapped to */ public SetNestedPropertiesRule(String[] elementNames, String[] propertyNames) { for (int i=0, size=elementNames.length; i * When set to false, any child element for which there is no * corresponding object property will cause an error to be reported. *

* 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 */ 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. */ 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. */ 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 rules = new ArrayList(1); private AnyChildRule rule; public AnyChildRules(AnyChildRule rule) { this.rule = rule; rules.add(rule); } public Digester getDigester() { return null; } public void setDigester(Digester digester) {} public String getNamespaceURI() {return null;} public void setNamespaceURI(String namespaceURI) {} public void add(String pattern, Rule rule) {} public void clear() {} public List match(String matchPath) { return match(null,matchPath); } public List match(String namespaceURI, String matchPath) { List match = decoratedRules.match(namespaceURI, matchPath); if ((matchPath.startsWith(matchPrefix)) && (matchPath.indexOf('/', matchPrefix.length()) == -1)) { // The current element is a direct child of the element // specified in the init method, so we want to ensure that // the rule passed to this object's constructor is included // in the returned list of matching rules. if ((match == null || match.size()==0)) { // The "real" rules class doesn't have any matches for // the specified path, so we return a list containing // just one rule: the one passed to this object's // constructor. return rules; } else { // The "real" rules class has rules that match the current // node, so we return this list *plus* the rule passed to // this object's constructor. // // It might not be safe to modify the returned list, // so clone it first. LinkedList newMatch = new LinkedList(match); newMatch.addLast(rule); return newMatch; } } else { return match; } } public List rules() { // This is not actually expected to be called during normal // processing. // // There is only one known case where this is called; when a rule // returned from AnyChildRules.match is invoked and throws a // SAXException then method Digester.endDocument will be called // without having "uninstalled" the AnyChildRules ionstance. That // method attempts to invoke the "finish" method for every Rule // instance - and thus needs to call rules() on its Rules object, // which is this one. Actually, java 1.5 and 1.6beta2 have a // bug in their xml implementation such that endDocument is not // called after a SAXException, but other parsers (eg Aelfred) // do call endDocument. Here, we therefore need to return the // rules registered with the underlying Rules object. log.debug("AnyChildRules.rules invoked."); return decoratedRules.rules(); } public void init(String prefix, Rules rules) { matchPrefix = prefix; decoratedRules = rules; } public Rules getOldRules() { return decoratedRules; } } private class AnyChildRule extends Rule { private String currChildNamespaceURI = null; private String currChildElementName = null; public void begin(String namespaceURI, String name, Attributes attributes) throws Exception { currChildNamespaceURI = namespaceURI; currChildElementName = name; } public void body(String value) throws Exception { String propName = currChildElementName; if (elementNames.containsKey(currChildElementName)) { // overide propName propName = (String) elementNames.get(currChildElementName); if (propName == null) { // user wants us to ignore this element return; } } boolean debug = log.isDebugEnabled(); if (debug) { log.debug("[SetNestedPropertiesRule]{" + digester.match + "} Setting property '" + propName + "' to '" + value + "'"); } // Populate the corresponding properties of the top object Object top = digester.peek(); if (debug) { if (top != null) { log.debug("[SetNestedPropertiesRule]{" + digester.match + "} Set " + top.getClass().getName() + " properties"); } else { log.debug("[SetPropertiesRule]{" + digester.match + "} Set NULL properties"); } } if (trimData) { value = value.trim(); } if (!allowUnknownChildElements) { // Force an exception if the property does not exist // (BeanUtils.setProperty() silently returns in this case) if (top instanceof DynaBean) { DynaProperty desc = ((DynaBean) top).getDynaClass().getDynaProperty(propName); if (desc == null) { throw new NoSuchMethodException ("Bean has no property named " + propName); } } else /* this is a standard JavaBean */ { PropertyDescriptor desc = PropertyUtils.getPropertyDescriptor(top, propName); if (desc == null) { throw new NoSuchMethodException ("Bean has no property named " + propName); } } } try { BeanUtils.setProperty(top, propName, value); } catch(NullPointerException e) { log.error("NullPointerException: " + "top=" + top + ",propName=" + propName + ",value=" + value + "!"); throw e; } } public void end(String namespace, String name) throws Exception { currChildElementName = null; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy