Author: skitching Date: Sun Apr 30 03:19:12 2006 New Revision: 398306 URL: http://svn.apache.org/viewcvs?rev=398306&view=rev Log: Add new Digester.setStackAction feature; this allows user code to monitor the digester stacks. In particular it helps with tracking info on xml file source for created objects.
Added: jakarta/commons/proper/digester/trunk/src/java/org/apache/commons/digester/StackAction.java (with props) jakarta/commons/proper/digester/trunk/src/test/org/apache/commons/digester/LocationTrackerTestCase.java (with props) Modified: jakarta/commons/proper/digester/trunk/src/java/org/apache/commons/digester/Digester.java jakarta/commons/proper/digester/trunk/src/test/org/apache/commons/digester/DigesterTestCase.java Modified: jakarta/commons/proper/digester/trunk/src/java/org/apache/commons/digester/Digester.java URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/digester/trunk/src/java/org/apache/commons/digester/Digester.java?rev=398306&r1=398305&r2=398306&view=diff ============================================================================== --- jakarta/commons/proper/digester/trunk/src/java/org/apache/commons/digester/Digester.java (original) +++ jakarta/commons/proper/digester/trunk/src/java/org/apache/commons/digester/Digester.java Sun Apr 30 03:19:12 2006 @@ -340,6 +340,12 @@ */ private ContentHandler customContentHandler = null; + /** + * Object which will receive callbacks for every pop/push action + * on the default stack or named stacks. + */ + private StackAction stackAction = null; + // ------------------------------------------------------------- Properties /** @@ -1013,6 +1019,14 @@ customContentHandler = handler; } + public void setStackAction(StackAction stackAction) { + this.stackAction = stackAction; + } + + public StackAction getStackAction() { + return stackAction; + } + // ------------------------------------------------- ContentHandler Methods @@ -2622,7 +2636,11 @@ public Object pop() { try { - return (stack.pop()); + Object popped = stack.pop(); + if (stackAction != null) { + popped = stackAction.onPop(this, null, popped); + } + return popped; } catch (EmptyStackException e) { log.warn("Empty stack (returning null)"); return (null); @@ -2638,11 +2656,14 @@ */ public void push(Object object) { + if (stackAction != null) { + object = stackAction.onPush(this, null, object); + } + if (stack.size() == 0) { root = object; } stack.push(object); - } /** @@ -2655,6 +2676,10 @@ * @since 1.6 */ public void push(String stackName, Object value) { + if (stackAction != null) { + value = stackAction.onPush(this, stackName, value); + } + ArrayStack namedStack = (ArrayStack) stacksByName.get(stackName); if (namedStack == null) { namedStack = new ArrayStack(); @@ -2669,7 +2694,7 @@ * <p><strong>Note:</strong> a stack is considered empty * if no objects have been pushed onto it yet.</p> * - * @param stackName the name of the stack from which the top value is to be popped + * @param stackName the name of the stack from which the top value is to be popped. * @return the top <code>Object</code> on the stack or or null if the stack is either * empty or has not been created yet * @throws EmptyStackException if the named stack is empty @@ -2684,11 +2709,14 @@ log.debug("Stack '" + stackName + "' is empty"); } throw new EmptyStackException(); - - } else { + } - result = namedStack.pop(); + result = namedStack.pop(); + + if (stackAction != null) { + result = stackAction.onPop(this, stackName, result); } + return result; } Added: jakarta/commons/proper/digester/trunk/src/java/org/apache/commons/digester/StackAction.java URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/digester/trunk/src/java/org/apache/commons/digester/StackAction.java?rev=398306&view=auto ============================================================================== --- jakarta/commons/proper/digester/trunk/src/java/org/apache/commons/digester/StackAction.java (added) +++ jakarta/commons/proper/digester/trunk/src/java/org/apache/commons/digester/StackAction.java Sun Apr 30 03:19:12 2006 @@ -0,0 +1,75 @@ +/* $Id$ + * + * Copyright 2006 The Apache Software Foundation. + * + * Licensed 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; + +/** + * An interface that can be implemented in order to get notifications of + * objects being pushed onto a digester stack or popped from one. + * <p> + * Because objects are pushed onto the main object stack when a rule + * has created a new object, this gives the ability to intercept such + * operations and perform modifications on created objects. + * <p> + * One use expected for this interface is to store information about the xml + * line that a particular object was created from. An implementation of this + * interface can detect whenever an object is pushed onto the digester object + * stack, call Digester.getDocumentLocator() to get the location within the + * current xml file, and store this either on the object on the stack (if it + * supports some user-specific interface for this purpose), or build a map of + * (object->locationinfo) separately. + * <p> + * It is recommended that objects implementing this interface provide + * a method to set a "next" action, and invoke it from the callback + * methods. This allows multiple actions to be "chained" together. + * <p> + * See also Digester.setStackAction. + */ +public interface StackAction { + /** + * Invoked just before an object is to be pushed onto a digester stack. + * + * @param d is the digester instance. + * + * @param stackName is the name of the stack onto which the object + * has been pushed. Null is passed to indicate the default stack. + * + * @param o is the object that has just been pushed. Calling peek on the + * specified stack will return the same object. + * + * @return the object to be pushed. Normally, parameter o is returned + * but this method could return an alternate object to be pushed + * instead (eg a proxy for the provided object). + */ + public Object onPush(Digester d, String stackName, Object o); + + /** + * Invoked just after an object has been popped from a digester stack. + * + * @param d is the digester instance. + * + * @param stackName is the name of the stack from which the object + * has been popped. Null is passed to indicate the default stack. + * + * @param o is the object that has just been popped. + * + * @return the object to be returned to the called. Normally, parameter + * o is returned but this method could return an alternate object. + */ + public Object onPop(Digester d, String stackName, Object o); +} \ No newline at end of file Propchange: jakarta/commons/proper/digester/trunk/src/java/org/apache/commons/digester/StackAction.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: jakarta/commons/proper/digester/trunk/src/java/org/apache/commons/digester/StackAction.java ------------------------------------------------------------------------------ svn:keywords = Author Date Id Revision Modified: jakarta/commons/proper/digester/trunk/src/test/org/apache/commons/digester/DigesterTestCase.java URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/digester/trunk/src/test/org/apache/commons/digester/DigesterTestCase.java?rev=398306&r1=398305&r2=398306&view=diff ============================================================================== --- jakarta/commons/proper/digester/trunk/src/test/org/apache/commons/digester/DigesterTestCase.java (original) +++ jakarta/commons/proper/digester/trunk/src/test/org/apache/commons/digester/DigesterTestCase.java Sun Apr 30 03:19:12 2006 @@ -18,22 +18,23 @@ package org.apache.commons.digester; +import java.io.StringReader; import java.math.BigDecimal; import java.net.URL; -import java.io.StringReader; +import java.util.ArrayList; +import java.util.EmptyStackException; import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.EmptyStackException; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; -import org.xml.sax.ErrorHandler; import org.xml.sax.Attributes; -import org.xml.sax.helpers.AttributesImpl; +import org.xml.sax.ErrorHandler; import org.xml.sax.InputSource; +import org.xml.sax.helpers.AttributesImpl; /** @@ -496,5 +497,99 @@ Object root = digester.getRoot(); assertNotNull("root object not retrieved", root); assertTrue("root object not a TestRule instance", (root instanceof TestBean)); + } + + /** Utility class for method testStackAction */ + private static class TrackingStackAction implements StackAction { + public ArrayList events = new ArrayList(); + public Object onPush(Digester d, String stackName, Object o) { + String msg = "push:" + stackName + ":" + o.toString(); + events.add(msg); + + String str = o.toString(); + if (str.startsWith("replpush")) { + return new String(str); + } else { + return o; + } + } + public Object onPop(Digester d, String stackName, Object o) { + String msg = "pop:" + stackName + ":" + o.toString(); + events.add(msg); + String str = o.toString(); + if (str.startsWith("replpop")) { + return new String(str); + } else { + return o; + } + } + } + + /** + * Test custom StackAction subclasses. + */ + public void testStackAction() { + TrackingStackAction action = new TrackingStackAction(); + + Object obj1 = new String("obj1"); + Object obj2 = new String("obj2"); + Object obj3 = new String("replpop.obj3"); + Object obj4 = new String("replpush.obj4"); + + Object obj8 = new String("obj8"); + Object obj9 = new String("obj9"); + + Digester d = new Digester(); + d.setStackAction(action); + + assertEquals(0, action.events.size()); + d.push(obj1); + d.push(obj2); + d.push(obj3); + d.push(obj4); + + assertNotNull(d.peek(0)); + // for obj4, a copy should have been pushed + assertFalse(obj4 == d.peek(0)); + assertEquals(obj4, d.peek(0)); + // for obj3, replacement only occurs on pop + assertSame(obj3, d.peek(1)); + assertSame(obj2, d.peek(2)); + assertSame(obj1, d.peek(3)); + + Object obj4a = d.pop(); + Object obj3a = d.pop(); + Object obj2a = d.pop(); + Object obj1a = d.pop(); + + assertFalse(obj4 == obj4a); + assertEquals(obj4, obj4a); + assertFalse(obj3 == obj4a); + assertEquals(obj3, obj3a); + assertSame(obj2, obj2a); + assertSame(obj1, obj1a); + + d.push("stack1", obj8); + d.push("stack1", obj9); + Object obj9a = d.pop("stack1"); + Object obj8a = d.pop("stack1"); + + assertSame(obj8, obj8a); + assertSame(obj9, obj9a); + + assertEquals(12, action.events.size()); + assertEquals("push:null:obj1", action.events.get(0)); + assertEquals("push:null:obj2", action.events.get(1)); + assertEquals("push:null:replpop.obj3", action.events.get(2)); + assertEquals("push:null:replpush.obj4", action.events.get(3)); + assertEquals("pop:null:replpush.obj4", action.events.get(4)); + assertEquals("pop:null:replpop.obj3", action.events.get(5)); + assertEquals("pop:null:obj2", action.events.get(6)); + assertEquals("pop:null:obj1", action.events.get(7)); + + assertEquals("push:stack1:obj8", action.events.get(8)); + assertEquals("push:stack1:obj9", action.events.get(9)); + assertEquals("pop:stack1:obj9", action.events.get(10)); + assertEquals("pop:stack1:obj8", action.events.get(11)); } } Added: jakarta/commons/proper/digester/trunk/src/test/org/apache/commons/digester/LocationTrackerTestCase.java URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/digester/trunk/src/test/org/apache/commons/digester/LocationTrackerTestCase.java?rev=398306&view=auto ============================================================================== --- jakarta/commons/proper/digester/trunk/src/test/org/apache/commons/digester/LocationTrackerTestCase.java (added) +++ jakarta/commons/proper/digester/trunk/src/test/org/apache/commons/digester/LocationTrackerTestCase.java Sun Apr 30 03:19:12 2006 @@ -0,0 +1,95 @@ +/* $Id$ + * + * Copyright 2001-2004 The Apache Software Foundation. + * + * Licensed 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.io.StringReader; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import junit.framework.TestCase; + +import org.xml.sax.Locator; + + +/** + * Tests that StackAction can be used to track the source location + * of objects created from input xml stream. + */ + +public class LocationTrackerTestCase extends TestCase { + + private static class LocationTracker implements StackAction { + public Map locations = new HashMap(); + + public Object onPush(Digester d, String stackName, Object o) { + if (stackName == null) { + // we only care about the real object stack + + // note that a Locator object can also provide + // publicId and systemId info. + Locator l = d.getDocumentLocator(); + StringBuffer locn = new StringBuffer(); + locn.append("line="); + locn.append(l.getLineNumber()); + locations.put(o, locn.toString()); + } + return o; + } + + public Object onPop(Digester d, String stackName, Object o) { + return o; + } + } + + public void testAll() throws Exception { + final String TEST_XML = + "<?xml version='1.0'?>\n" + + "<box id='root'>\n" + + " <subBox id='box1'/>\n" + + " <ignoreme/>\n" + + " <subBox id='box2'/> <subBox id='box3'/>\n" + + "</box>"; + + LocationTracker locnTracker = new LocationTracker(); + + Digester digester = new Digester(); + digester.setStackAction(locnTracker); + digester.addObjectCreate("box", Box.class); + digester.addSetProperties("box"); + digester.addObjectCreate("box/subBox", Box.class); + digester.addSetProperties("box/subBox"); + digester.addSetNext("box/subBox", "addChild"); + + Object result = digester.parse(new StringReader(TEST_XML)); + assertNotNull(result); + Box root = (Box) result; + List children = root.getChildren(); + assertEquals(3, children.size()); + Box box1 = (Box) children.get(0); + Box box2 = (Box) children.get(1); + Box box3 = (Box) children.get(2); + + assertEquals("line=2", locnTracker.locations.get(root)); + assertEquals("line=3", locnTracker.locations.get(box1)); + assertEquals("line=5", locnTracker.locations.get(box2)); + assertEquals("line=5", locnTracker.locations.get(box3)); + } +} Propchange: jakarta/commons/proper/digester/trunk/src/test/org/apache/commons/digester/LocationTrackerTestCase.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: jakarta/commons/proper/digester/trunk/src/test/org/apache/commons/digester/LocationTrackerTestCase.java ------------------------------------------------------------------------------ svn:keywords = Author Date Id Revision --------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]