You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@commons.apache.org by rd...@apache.org on 2003/11/19 21:40:26 UTC
cvs commit: jakarta-commons/digester/src/java/org/apache/commons/digester SetNestedPropertiesRule.java
rdonkin 2003/11/19 12:40:26
Added: digester/src/java/org/apache/commons/digester
SetNestedPropertiesRule.java
Log:
New rule implementation that allows flexible treatment of properties. Submitted by Simon Kitching
Revision Changes Path
1.1 jakarta-commons/digester/src/java/org/apache/commons/digester/SetNestedPropertiesRule.java
Index: SetNestedPropertiesRule.java
===================================================================
/*
* $Header: /home/cvs/jakarta-commons/digester/src/java/org/apache/commons/digester/SetNestedPropertiesRule.java,v 1.1 2003/11/19 20:40:26 rdonkin Exp $
* $Revision: 1.1 $
* $Date: 2003/11/19 20:40:26 $
*
* ====================================================================
*
* The Apache Software License, Version 1.1
*
* Copyright (c) 2001-2003 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgement:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowledgement may appear in the software itself,
* if and wherever such third-party acknowledgements normally appear.
*
* 4. The names "Apache", "The Jakarta Project", "Commons", and "Apache Software
* Foundation" must not be used to endorse or promote products derived
* from this software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache",
* "Apache" nor may "Apache" appear in their names without prior
* written permission of the Apache Software Foundation.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package org.apache.commons.digester;
import java.util.List;
import java.util.LinkedList;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.ListIterator;
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;
import org.apache.commons.logging.LogFactory;
/**
* <p>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.</p>
*
* <p>Example input that can be processed by this rule:</p>
* <pre>
* [point]
* [x]7[/x]
* [y]9[/y]
* [/point]
* </pre>
*
* <p>This rule supports custom mapping of attribute names to property names.
* The default mapping for particular attributes 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.</p>
*
* <p>
* A very similar effect can be achieved using a combination of the
* <code>BeanPropertySetterRule</code> and the <code>ExtendedBaseRules</code> rules manager; this
* <code>Rule</code>, however, works fine with the default <code>RulesBase</code> rules manager.</p>
*
* @author Simon Kitching
* @version $Revision: 1.1 $ $Date: 2003/11/19 20:40:26 $
*/
public class SetNestedPropertiesRule extends Rule {
private static final String PROP_IGNORE = "ignore-me";
private Log log = null;
private AnyChildRule anyChildRule = new AnyChildRule();
private AnyChildRules newRules = new AnyChildRules(anyChildRule);
private Rules oldRules = null;
private boolean trimData = true;
private boolean allowUnknownChildElements = false;
private HashMap elementNames = new HashMap();
// ----------------------------------------------------------- Constructors
/**
* Base constructor.
*/
public SetNestedPropertiesRule() {
// nothing to set up
}
/**
* <p>Convenience constructor overrides the mapping for just one property.</p>
*
* <p>For details about how this works, see
* {@link #SetNestedPropertiesRule(String[] elementNames,
* String[] propertyNames)}.</p>
*
* @param elementName map the child element to match
* @param propertyName to a property with this name
*/
public SetNestedPropertiesRule(String elementName, String propertyName) {
elementNames.put(elementName, propertyName);
}
/**
* <p>Constructor allows element->property mapping to be overriden.</p>
*
* <p>Two arrays are passed in.
* One contains the element names and the other the property names.
* The element name / property name pairs are match by position
* In order words, the first string in the element name list matches
* to the first string in the property name list and so on.</p>
*
* <p>If a property name is null or the element name has no matching
* property name, then this indicates that the element should be ignored.</p>
*
* <h5>Example One</h5>
* <p> The following constructs a rule that maps the <code>alt-city</code>
* element to the <code>city</code> property and the <code>alt-state</code>
* to the <code>state</code> property.
* All other child elements are mapped as usual using exact name matching.
* <code><pre>
* SetNestedPropertiesRule(
* 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>
* element to the <code>className</code> property.
* The element <code>ignore-me</code> is not mapped.
* All other elements are mapped as usual using exact name matching.
* <code><pre>
* SetPropertiesRule(
* new String[] {"class", "ignore-me"},
* new String[] {"className"});
* </pre></code>
*
* @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<size; i++) {
String propName = null;
if (i < propertyNames.length) {
propName = propertyNames[i];
}
if (propName == null) {
this.elementNames.put(elementNames[i], PROP_IGNORE);
}
else {
this.elementNames.put(elementNames[i], propName);
}
}
}
// --------------------------------------------------------- Public Methods
public void setDigester(Digester digester) {
super.setDigester(digester);
log = digester.getLogger();
anyChildRule.setDigester(digester);
}
/**
* When set to true, any text within child elements will have leading
* and trailing whitespace removed before assignment to the target
* object. The default value for this attribute is true.
*/
public void setTrimData(boolean trimData) {
this.trimData = trimData;
}
/** See {@link #setTrimData}. */
public boolean getTrimData() {
return trimData;
}
/**
* When set to true, any child element for which there is no
* corresponding object property will cause an error to be reported.
* The default value of this attribute is false (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 {
oldRules = digester.getRules();
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 {
digester.setRules(oldRules);
}
/**
* <p>Add an additional element name to property name mapping.
* This is intended to be used from the xml rules.
*/
public void addAlias(String elementName, String propertyName) {
if (propertyName == null) {
elementNames.put(elementName, PROP_IGNORE);
}
else {
elementNames.put(elementName, propertyName);
}
}
/**
* Render a printable version of this Rule.
*/
public String toString() {
return ("SetNestedPropertiesRule");
}
//----------------------------------------- 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 include it as the
// first rule in the matches list. The way that
// SetNestedPropertiesRule is used, it is in fact very
// likely to be the only match, so we optimise that
// solution by keeping a list with only the AnyChildRule
// instance in it.
if ((match == null || match.size()==0)) {
return rules;
}
else {
// it might not be safe to modify the returned list,
// so clone it first.
LinkedList newMatch = new LinkedList(match);
//newMatch.addFirst(rule);
newMatch.addLast(rule);
return newMatch;
}
}
else {
return match;
}
}
public List rules() {
// This is not actually expected to be called.
throw new RuntimeException(
"AnyChildRules.rules not implemented.");
}
public void init(String prefix, Rules rules) {
matchPrefix = prefix;
decoratedRules = rules;
}
}
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 {
boolean debug = log.isDebugEnabled();
String propName = (String) elementNames.get(currChildElementName);
if (propName == PROP_IGNORE) {
// note: above deliberately tests for IDENTITY, not EQUALITY
return;
}
if (propName == null) {
propName = currChildElementName;
}
if (digester.log.isDebugEnabled()) {
digester.log.debug("[SetNestedPropertiesRule]{" + digester.match +
"} Setting property '" + propName + "' to '" +
value + "'");
}
// Populate the corresponding properties of the top object
Object top = digester.peek();
if (digester.log.isDebugEnabled()) {
if (top != null) {
digester.log.debug("[SetNestedPropertiesRule]{" + digester.match +
"} Set " + top.getClass().getName() +
" properties");
} else {
digester.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);
}
}
}
BeanUtils.setProperty(top, propName, value);
}
public void end(String namespace, String name) throws Exception {
currChildElementName = null;
}
}
}
---------------------------------------------------------------------
To unsubscribe, e-mail: commons-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: commons-dev-help@jakarta.apache.org