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 2002/07/16 23:23:28 UTC
cvs commit: jakarta-commons/digester/src/test/org/apache/commons/digester/xmlrules testPropertyAliasRules.xml DigesterLoaderTest.java
rdonkin 2002/07/16 14:23:28
Modified: digester/src/java/org/apache/commons/digester Digester.java
SetPropertiesRule.java package.html
digester/src/java/org/apache/commons/digester/xmlrules
DigesterRuleParser.java digester-rules.dtd
digester/src/test/org/apache/commons/digester
RuleTestCase.java
digester/src/test/org/apache/commons/digester/xmlrules
DigesterLoaderTest.java
Added: digester/src/test/org/apache/commons/digester Test7.xml
digester/src/test/org/apache/commons/digester/xmlrules
testPropertyAliasRules.xml
Log:
This is an implmentation of overridden attribute->property name mappings for SetPropertiesRule. Some of the code plus much of the inspiration came from a patch created by Simon Kitching. The feeling on the list when this was originally discussed was that really there is no good reason why this functionality is needed - alternative mechanisms existed. Recently, one good example was posted on the user list - that of the often problematic mapping of an xml attribute named class. This patch allows attribute->property name mappings to be overriden both through standard digester constructors and through xmlrules.
Revision Changes Path
1.55 +40 -4 jakarta-commons/digester/src/java/org/apache/commons/digester/Digester.java
Index: Digester.java
===================================================================
RCS file: /home/cvs/jakarta-commons/digester/src/java/org/apache/commons/digester/Digester.java,v
retrieving revision 1.54
retrieving revision 1.55
diff -u -r1.54 -r1.55
--- Digester.java 14 Jun 2002 21:53:30 -0000 1.54
+++ Digester.java 16 Jul 2002 21:23:27 -0000 1.55
@@ -1763,6 +1763,42 @@
}
+ /**
+ * Add a "set properties" rule with a single overridden parameter.
+ * See {@link SetPropertiesRule#SetPropertiesRule(String attributeName, String propertyName)}
+ *
+ * @param pattern Element matching pattern
+ * @param attributeName map this attribute
+ * @param propertyNames to this property
+ */
+ public void addSetProperties(
+ String pattern,
+ String attributeName,
+ String propertyName) {
+
+ addRule(pattern,
+ new SetPropertiesRule(attributeName, propertyName));
+
+ }
+
+ /**
+ * Add a "set properties" rule with overridden parameters.
+ * See {@link SetPropertiesRule#SetPropertiesRule(String [] attributeNames, String [] propertyNames)}
+ *
+ * @param pattern Element matching pattern
+ * @param attributeNames names of attributes with custom mappings
+ * @param propertyNames property names these attributes map to
+ */
+ public void addSetProperties(
+ String pattern,
+ String [] attributeNames,
+ String [] propertyNames) {
+
+ addRule(pattern,
+ new SetPropertiesRule(attributeNames, propertyNames));
+
+ }
+
/**
* Add a "set property" rule for the specified parameters.
1.9 +159 -8 jakarta-commons/digester/src/java/org/apache/commons/digester/SetPropertiesRule.java
Index: SetPropertiesRule.java
===================================================================
RCS file: /home/cvs/jakarta-commons/digester/src/java/org/apache/commons/digester/SetPropertiesRule.java,v
retrieving revision 1.8
retrieving revision 1.9
diff -u -r1.8 -r1.9
--- SetPropertiesRule.java 23 Mar 2002 17:45:58 -0000 1.8
+++ SetPropertiesRule.java 16 Jul 2002 21:23:27 -0000 1.9
@@ -70,8 +70,14 @@
/**
- * Rule implementation that sets properties on the object at the top of the
- * stack, based on attributes with corresponding names.
+ * <p>Rule implementation 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
+ * {@link #SetPropertiesRule(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>
*
* @author Craig McClanahan
* @version $Revision$ $Date$
@@ -106,6 +112,84 @@
// nothing to set up
}
+
+ /**
+ * <p>Convenience constructor overrides the mapping for just one property.</p>
+ *
+ * <p>For details about how this works, see
+ * {@link #SetPropertiesRule(String[] attributeNames, String[] propertyNames)}.</p>
+ *
+ * @param attributeName map this attribute
+ * @param propertyName to a property with this name
+ */
+ public SetPropertiesRule(String attributeName, String propertyName) {
+
+ attributeNames = new String[1];
+ attributeNames[0] = attributeName;
+ propertyNames = new String[1];
+ propertyNames[0] = propertyName;
+ }
+
+ /**
+ * <p>Constructor allows 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>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>
+ *
+ * <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.
+ * All other attributes are mapped as usual using exact name matching.
+ * <code><pre>
+ * SetPropertiesRule(
+ * 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.
+ * <code><pre>
+ * SetPropertiesRule(
+ * new String[] {"class", "ignore-me"},
+ * new String[] {"className"});
+ * </pre></code>
+ *
+ * @param attributeNames names of attributes to map
+ * @param proeprtyNames names of properties mapped to
+ */
+ public SetPropertiesRule(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];
+ }
+
+ 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
@@ -118,21 +202,52 @@
* @param attributes The attribute list of this element
*/
public void begin(Attributes attributes) throws Exception {
-
+
// Build a set of attribute 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 (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 (digester.log.isDebugEnabled()) {
digester.log.debug("[SetPropertiesRule]{" + digester.match +
"} Setting property '" + name + "' to '" +
value + "'");
}
- values.put(name, value);
+ if (name != null) {
+ values.put(name, value);
+ }
}
// Populate the corresponding properties of the top object
@@ -146,6 +261,42 @@
}
+
+ /**
+ * <p>Add an additional attribute name to property name mapping.
+ * This is intended to be used from the xml rules.
+ */
+ public void addAlias(String attributeName, String propertyName) {
+
+ // 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];
+ }
+ tempProperties[length] = propertyName;
+
+ propertyNames = tempProperties;
+ attributeNames = tempAttributes;
+ }
+ }
+
/**
* Render a printable version of this Rule.
1.11 +5 -1 jakarta-commons/digester/src/java/org/apache/commons/digester/package.html
Index: package.html
===================================================================
RCS file: /home/cvs/jakarta-commons/digester/src/java/org/apache/commons/digester/package.html,v
retrieving revision 1.10
retrieving revision 1.11
diff -u -r1.10 -r1.11
--- package.html 18 Apr 2002 17:44:43 -0000 1.10
+++ package.html 16 Jul 2002 21:23:27 -0000 1.11
@@ -383,7 +383,11 @@
(on the object at the top of the digester's stack)
who have property names that match the attributes specified on this XML
element, and then call them individually, passing the corresponding
- attribute values. A very common idiom is to define an object create
+ attribute values. These natural mappings can be overridden. This allows
+ (for example) a <code>class</code> attribute to be mapped correctly.
+ It is recommended that this feature should not be overused - in most cases,
+ it's better to use the standard <code>BeanInfo</code> mechanism.
+ A very common idiom is to define an object create
rule, followed by a set properties rule, with the same element matching
pattern. This causes the creation of a new Java object, followed by
"configuration" of that object's properties based on the attributes
1.6 +28 -0 jakarta-commons/digester/src/java/org/apache/commons/digester/xmlrules/DigesterRuleParser.java
Index: DigesterRuleParser.java
===================================================================
RCS file: /home/cvs/jakarta-commons/digester/src/java/org/apache/commons/digester/xmlrules/DigesterRuleParser.java,v
retrieving revision 1.5
retrieving revision 1.6
diff -u -r1.5 -r1.6
--- DigesterRuleParser.java 8 Jul 2002 21:25:29 -0000 1.5
+++ DigesterRuleParser.java 16 Jul 2002 21:23:27 -0000 1.6
@@ -238,6 +238,8 @@
digester.addFactoryCreate("*/set-properties-rule", new SetPropertiesRuleFactory());
digester.addRule("*/set-properties-rule", new PatternRule(digester, "pattern"));
digester.addSetNext("*/set-properties-rule", "add", ruleClassName);
+
+ digester.addRule("*/set-properties-rule/alias", new SetPropertiesAliasRule(digester));
digester.addFactoryCreate("*/set-property-rule", new SetPropertyRuleFactory());
digester.addRule("*/set-property-rule", new PatternRule(digester, "pattern"));
@@ -611,5 +613,31 @@
}
}
+ /**
+ * A rule for adding a attribute-property alias to the custom alias mappings of
+ * the containing SetPropertiesRule rule.
+ */
+ protected class SetPropertiesAliasRule extends Rule {
+
+ /**
+ * <p>Base constructor.
+ *
+ * @param digester the Digester used to parse the rules XML file
+ */
+ public SetPropertiesAliasRule(Digester digester) {
+ super(digester);
+ }
+ /**
+ * Add the alias to the SetPropertiesRule object created by the
+ * enclosing <set-properties-rule> tag.
+ */
+ public void begin(Attributes attributes) {
+ String attrName = attributes.getValue("attr-name");
+ String propName = attributes.getValue("prop-name");
+
+ SetPropertiesRule rule = (SetPropertiesRule) digester.peek();
+ rule.addAlias(attrName, propName);
+ }
+ }
}
1.4 +8 -2 jakarta-commons/digester/src/java/org/apache/commons/digester/xmlrules/digester-rules.dtd
Index: digester-rules.dtd
===================================================================
RCS file: /home/cvs/jakarta-commons/digester/src/java/org/apache/commons/digester/xmlrules/digester-rules.dtd,v
retrieving revision 1.3
retrieving revision 1.4
diff -u -r1.3 -r1.4
--- digester-rules.dtd 8 Jul 2002 21:25:29 -0000 1.3
+++ digester-rules.dtd 16 Jul 2002 21:23:28 -0000 1.4
@@ -96,9 +96,15 @@
attrname CDATA #IMPLIED>
<!-- SetPropertiesRule -->
-<!ELEMENT set-properties-rule EMPTY>
+<!ELEMENT set-properties-rule (alias)*>
<!ATTLIST factory-create-rule
pattern CDATA #IMPLIED>
+
+<!-- An alias is a custom attribute->property name mapping -->
+<!ELEMENT alias EMPTY>
+<!ATTLIST alias
+���� attr-name CDATA #REQUIRED
+���� prop-name CDATA #IMPLIED>
<!-- SetPropertyRule -->
<!ELEMENT set-property-rule EMPTY>
1.17 +72 -5 jakarta-commons/digester/src/test/org/apache/commons/digester/RuleTestCase.java
Index: RuleTestCase.java
===================================================================
RCS file: /home/cvs/jakarta-commons/digester/src/test/org/apache/commons/digester/RuleTestCase.java,v
retrieving revision 1.16
retrieving revision 1.17
diff -u -r1.16 -r1.17
--- RuleTestCase.java 10 Jul 2002 18:12:33 -0000 1.16
+++ RuleTestCase.java 16 Jul 2002 21:23:28 -0000 1.17
@@ -665,7 +665,6 @@
* Test method calls with the CallMethodRule rule. It should be possible
* to call any accessible method of the object on the top of the stack,
* even methods with no arguments.
-
*/
public void testCallMethod2() throws Exception {
/*
@@ -697,6 +696,72 @@
*/
}
+ /**
+ */
+ public void testSetCustomProperties() throws Exception {
+
+ Digester digester = new Digester();
+
+ digester.setValidating(false);
+
+ digester.addObjectCreate("toplevel", ArrayList.class);
+ digester.addObjectCreate("toplevel/one", Address.class);
+ digester.addSetNext("toplevel/one", "add");
+ digester.addObjectCreate("toplevel/two", Address.class);
+ digester.addSetNext("toplevel/two", "add");
+ digester.addObjectCreate("toplevel/three", Address.class);
+ digester.addSetNext("toplevel/three", "add");
+ digester.addObjectCreate("toplevel/four", Address.class);
+ digester.addSetNext("toplevel/four", "add");
+ digester.addSetProperties("toplevel/one");
+ digester.addSetProperties(
+ "toplevel/two",
+ new String[] {"alt-street", "alt-city", "alt-state"},
+ new String[] {"street", "city", "state"});
+ digester.addSetProperties(
+ "toplevel/three",
+ new String[] {"aCity", "state"},
+ new String[] {"city"});
+ digester.addSetProperties("toplevel/four", "alt-city", "city");
+
+
+ ArrayList root = (ArrayList) digester.parse(getInputStream("Test7.xml"));
+
+ assertEquals("Wrong array size", 4, root.size());
+
+ // note that the array is in popped order (rather than pushed)
+
+ Object
+ obj = root.get(0);
+ assertTrue("(1) Should be an Address ", obj instanceof Address);
+ Address addressOne = (Address) obj;
+ assertEquals("(1) Street attribute", "New Street", addressOne.getStreet());
+ assertEquals("(1) City attribute", "Las Vegas", addressOne.getCity());
+ assertEquals("(1) State attribute", "Nevada", addressOne.getState());
+
+ obj = root.get(1);
+ assertTrue("(2) Should be an Address ", obj instanceof Address);
+ Address addressTwo = (Address) obj;
+ assertEquals("(2) Street attribute", "Old Street", addressTwo.getStreet());
+ assertEquals("(2) City attribute", "Portland", addressTwo.getCity());
+ assertEquals("(2) State attribute", "Oregon", addressTwo.getState());
+
+ obj = root.get(2);
+ assertTrue("(3) Should be an Address ", obj instanceof Address);
+ Address addressThree = (Address) obj;
+ assertEquals("(3) Street attribute", "4th Street", addressThree.getStreet());
+ assertEquals("(3) City attribute", "Dayton", addressThree.getCity());
+ assertEquals("(3) State attribute", "US" , addressThree.getState());
+
+ obj = root.get(3);
+ assertTrue("(4) Should be an Address ", obj instanceof Address);
+ Address addressFour = (Address) obj;
+ assertEquals("(4) Street attribute", "6th Street", addressFour.getStreet());
+ assertEquals("(4) City attribute", "Cleveland", addressFour.getCity());
+ assertEquals("(4) State attribute", "Ohio", addressFour.getState());
+
+ }
+
// ------------------------------------------------ Utility Support Methods
@@ -760,4 +825,6 @@
office.getZipCode());
}
+
+
}
1.1 jakarta-commons/digester/src/test/org/apache/commons/digester/Test7.xml
Index: Test7.xml
===================================================================
<?xml version="1.0"?>
<toplevel>
<one street='New Street' city='Las Vegas' state='Nevada'/>
<two alt-street='Old Street' alt-city='Portland' alt-state='Oregon'/>
<three street='4th Street' aCity='Dayton' state='Ignore Me' />
<four street='6th Street' alt-city='Cleveland' state='Ohio'/>
</toplevel>
1.7 +57 -0 jakarta-commons/digester/src/test/org/apache/commons/digester/xmlrules/DigesterLoaderTest.java
Index: DigesterLoaderTest.java
===================================================================
RCS file: /home/cvs/jakarta-commons/digester/src/test/org/apache/commons/digester/xmlrules/DigesterLoaderTest.java,v
retrieving revision 1.6
retrieving revision 1.7
diff -u -r1.6 -r1.7
--- DigesterLoaderTest.java 8 Jul 2002 21:25:30 -0000 1.6
+++ DigesterLoaderTest.java 16 Jul 2002 21:23:28 -0000 1.7
@@ -65,6 +65,8 @@
import org.apache.commons.digester.Digester;
+import org.apache.commons.digester.Address;
+
import junit.framework.TestCase;
import junit.framework.TestSuite;
@@ -168,4 +170,59 @@
}
+ /**
+ */
+ public void testSetCustomProperties() throws Exception {
+ URL rules = ClassLoader.getSystemResource
+ ("org/apache/commons/digester/xmlrules/testPropertyAliasRules.xml");
+ InputStream input = ClassLoader.getSystemResource
+ ("org/apache/commons/digester/Test7.xml").openStream();
+
+ Object obj = DigesterLoader.load(
+ rules,
+ getClass().getClassLoader(),
+ input,
+ new ArrayList());
+
+ if (!(obj instanceof ArrayList)) {
+ fail(
+ "Unexpected object returned from DigesterLoader. Expected ArrayList; got "
+ + obj.getClass().getName());
+ }
+
+ ArrayList root = (ArrayList) obj;
+
+ assertEquals("Wrong array size", 4, root.size());
+
+ // note that the array is in popped order (rather than pushed)
+
+ obj = root.get(0);
+ assertTrue("(1) Should be an Address ", obj instanceof Address);
+ Address addressOne = (Address) obj;
+ assertEquals("(1) Street attribute", "New Street", addressOne.getStreet());
+ assertEquals("(1) City attribute", "Las Vegas", addressOne.getCity());
+ assertEquals("(1) State attribute", "Nevada", addressOne.getState());
+
+ obj = root.get(1);
+ assertTrue("(2) Should be an Address ", obj instanceof Address);
+ Address addressTwo = (Address) obj;
+ assertEquals("(2) Street attribute", "Old Street", addressTwo.getStreet());
+ assertEquals("(2) City attribute", "Portland", addressTwo.getCity());
+ assertEquals("(2) State attribute", "Oregon", addressTwo.getState());
+
+ obj = root.get(2);
+ assertTrue("(3) Should be an Address ", obj instanceof Address);
+ Address addressThree = (Address) obj;
+ assertEquals("(3) Street attribute", "4th Street", addressThree.getStreet());
+ assertEquals("(3) City attribute", "Dayton", addressThree.getCity());
+ assertEquals("(3) State attribute", "US" , addressThree.getState());
+
+ obj = root.get(3);
+ assertTrue("(4) Should be an Address ", obj instanceof Address);
+ Address addressFour = (Address) obj;
+ assertEquals("(4) Street attribute", "6th Street", addressFour.getStreet());
+ assertEquals("(4) City attribute", "Cleveland", addressFour.getCity());
+ assertEquals("(4) State attribute", "Ohio", addressFour.getState());
+
+ }
}
1.1 jakarta-commons/digester/src/test/org/apache/commons/digester/xmlrules/testPropertyAliasRules.xml
Index: testPropertyAliasRules.xml
===================================================================
<?xml version="1.0"?>
<digester-rules>
<pattern value="toplevel/one">
<object-create-rule classname="org.apache.commons.digester.Address"/>
<set-next-rule methodname="add" paramtype="java.lang.Object"/>
<set-properties-rule/>
</pattern>
<pattern value="toplevel/two">
<object-create-rule classname="org.apache.commons.digester.Address"/>
<set-next-rule methodname="add" paramtype="java.lang.Object"/>
<set-properties-rule>
<alias attr-name="alt-street" prop-name="street"/>
<alias attr-name="alt-city" prop-name="city"/>
<alias attr-name="alt-state" prop-name="state"/>
</set-properties-rule>
</pattern>
<pattern value="toplevel/three">
<object-create-rule classname="org.apache.commons.digester.Address"/>
<set-next-rule methodname="add" paramtype="java.lang.Object"/>
<set-properties-rule>
<alias attr-name="aCity" prop-name="city"/>
<alias attr-name="state" />
</set-properties-rule>
</pattern>
<pattern value="toplevel/four">
<object-create-rule classname="org.apache.commons.digester.Address"/>
<set-next-rule methodname="add" paramtype="java.lang.Object"/>
<set-properties-rule>
<alias attr-name="alt-city" prop-name="city"/>
</set-properties-rule>
</pattern>
</digester-rules>
--
To unsubscribe, e-mail: <ma...@jakarta.apache.org>
For additional commands, e-mail: <ma...@jakarta.apache.org>