Author: simonetripodi
Date: Sun Jan 30 19:11:49 2011
New Revision: 1065353
URL: http://svn.apache.org/viewvc?rev=1065353&view=rev
Log:
first checkin of SetNestedPropertiesRule class, a tidy version of the proper
one on /trunk
Added:
commons/sandbox/digester3/trunk/src/main/java/org/apache/commons/digester3/SetNestedPropertiesRule.java
(with props)
Added:
commons/sandbox/digester3/trunk/src/main/java/org/apache/commons/digester3/SetNestedPropertiesRule.java
URL:
http://svn.apache.org/viewvc/commons/sandbox/digester3/trunk/src/main/java/org/apache/commons/digester3/SetNestedPropertiesRule.java?rev=1065353&view=auto
==============================================================================
---
commons/sandbox/digester3/trunk/src/main/java/org/apache/commons/digester3/SetNestedPropertiesRule.java
(added)
+++
commons/sandbox/digester3/trunk/src/main/java/org/apache/commons/digester3/SetNestedPropertiesRule.java
Sun Jan 30 19:11:49 2011
@@ -0,0 +1,330 @@
+/* $Id$
+ *
+ * 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.digester3;
+
+import java.beans.PropertyDescriptor;
+import java.util.ArrayList;
+import java.util.Formatter;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+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.apache.commons.digester3.spi.Rules;
+import org.xml.sax.Attributes;
+
+/**
+ * <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>
+ * [widget]
+ * [height]7[/height]
+ * [width]8[/width]
+ * [label]Hello, world[/label]
+ * [/widget]
+ * </pre>
+ *
+ * <p>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.</p>
+ *
+ * <p>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.</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>
+ *
+ * <p>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.</p>
+ *
+ * <p><b>Implementation Notes</b></p>
+ *
+ * <p>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.</p>
+ *
+ * <p>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.</p>
+ *
+ * <p>TODO: Optimize 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!).</p>
+ */
+public class SetNestedPropertiesRule extends Rule {
+
+ private final Map<String, String> elementNames;
+
+ private final boolean trimData;
+
+ private final boolean allowUnknownChildElements;
+
+ /**
+ * Constructor which allows element->property mapping to be overridden.
+ *
+ * @param elementNames
+ * @param trimData
+ * @param allowUnknownChildElements
+ */
+ public SetNestedPropertiesRule(Map<String, String> elementNames, boolean
trimData, boolean allowUnknownChildElements) {
+ this.elementNames = elementNames;
+ this.trimData = trimData;
+ this.allowUnknownChildElements = 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 = this.getDigester().getRules();
+ AnyChildRule anyChildRule = new AnyChildRule();
+ anyChildRule.setDigester(this.getDigester());
+ AnyChildRules newRules = new AnyChildRules(anyChildRule);
+ newRules.init(this.getDigester().getMatch() + "/", oldRules);
+ this.getDigester().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 namespace, String name, String text) throws
Exception {
+ AnyChildRules newRules = (AnyChildRules) this.getDigester().getRules();
+ this.getDigester().setRules(newRules.getOldRules());
+ }
+
+ /**
+ * Render a printable version of this Rule.
+ */
+ @Override
+ public String toString() {
+ return
String.format("SetNestedPropertiesRule[allowUnknownChildElements=%s,
trimData=%s, elementNames=%s]",
+ this.allowUnknownChildElements,
+ this.trimData,
+ this.elementNames);
+ }
+
+ //----------------------------------------- local classes
+
+ /** Private Rules implementation */
+ private class AnyChildRules implements Rules {
+
+ private String matchPrefix = null;
+
+ private Rules decoratedRules = null;
+
+ private List<Rule> rules = new ArrayList<Rule>(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<Rule> match(String namespaceURI, String matchPath) {
+ List<Rule> 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<Rule> newMatch = new LinkedList<Rule>(match);
+ newMatch.addLast(rule);
+ return newMatch;
+ }
+ } else {
+ return match;
+ }
+ }
+
+ public List<Rule> 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.getMatch() 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.
+ if (this.getDigester().getLog().isDebugEnabled()) {
+ this.getDigester().getLog().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;
+
+ @Override
+ public void begin(String namespaceURI, String name, Attributes
attributes) throws Exception {
+ currChildNamespaceURI = namespaceURI;
+ currChildElementName = name;
+ }
+
+ @Override
+ public void body(String namespace, String name, String text) throws
Exception {
+ String propName = currChildElementName;
+ if (elementNames.containsKey(currChildElementName)) {
+ // overide propName
+ propName = elementNames.get(currChildElementName);
+ if (propName == null) {
+ // user wants us to ignore this element
+ return;
+ }
+ }
+
+ boolean debug = this.getDigester().getLog().isDebugEnabled();
+
+ if (debug) {
+ this.getDigester().getLog().debug(
+ String.format("[SetNestedPropertiesRule]{%s} Setting
property '%s' to '%s'",
+ this.getDigester().getMatch(),
+ propName,
+ text));
+ }
+
+ // Populate the corresponding properties of the top object
+ Object top = this.getDigester().peek();
+ if (debug) {
+ Formatter formatter =
+ new Formatter().format("[SetNestedPropertiesRule]{%s} Set
", this.getDigester().getMatch())
+ .format("%s properties", (top != null ?
top.getClass().getName() : "NULL"));
+
+ this.getDigester().getLog().debug(formatter.toString());
+ }
+
+ if (trimData) {
+ text = text.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, text);
+ } catch(NullPointerException e) {
+
this.getDigester().getLog().error(String.format("NullPointerException: top=%s,
propName=%s, value=%s!",
+ top,
+ propName,
+ text));
+ throw e;
+ }
+ }
+
+ @Override
+ public void end(String namespace, String name) throws Exception {
+ this.currChildElementName = null;
+ }
+
+ }
+
+}
Propchange:
commons/sandbox/digester3/trunk/src/main/java/org/apache/commons/digester3/SetNestedPropertiesRule.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange:
commons/sandbox/digester3/trunk/src/main/java/org/apache/commons/digester3/SetNestedPropertiesRule.java
------------------------------------------------------------------------------
svn:keywords = Date Author Id Revision HeadURL
Propchange:
commons/sandbox/digester3/trunk/src/main/java/org/apache/commons/digester3/SetNestedPropertiesRule.java
------------------------------------------------------------------------------
svn:mime-type = text/plain