You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@commons.apache.org by sk...@apache.org on 2005/02/04 02:51:54 UTC

svn commit: r151287 [4/7] - in jakarta/commons/proper/digester/branches/digester2: ./ src/ src/conf/ src/examples/ src/examples/api/ src/examples/api/addressbook/ src/java/ src/java/org/ src/java/org/apache/ src/java/org/apache/commons/ src/java/org/apache/commons/digester2/ src/java/org/apache/commons/digester2/actions/ src/java/org/apache/commons/digester2/factory/ src/media/ src/test/ src/test/org/ src/test/org/apache/ src/test/org/apache/commons/ src/test/org/apache/commons/digester2/ xdocs/ xdocs/dtds/ xdocs/images/ xdocs/style/

Added: jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/SAXHandler.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/SAXHandler.java?view=auto&rev=151287
==============================================================================
--- jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/SAXHandler.java (added)
+++ jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/SAXHandler.java Thu Feb  3 17:51:43 2005
@@ -0,0 +1,1408 @@
+/* $Id: Digester.java,v 1.107 2004/09/18 09:51:03 skitching Exp $
+ *
+ * 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.digester2;
+
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.lang.reflect.InvocationTargetException;
+import java.util.EmptyStackException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.commons.collections.ArrayStack;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXNotRecognizedException;
+import org.xml.sax.SAXNotSupportedException;
+import org.xml.sax.SAXParseException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.DefaultHandler;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.EntityResolver;
+
+/**
+ * <p>An object which handles SAX events generated during parsing of an
+ * input document, and invokes the appropriate methods on the appropriate
+ * rule objects.</p>
+ *
+ * <p>This class is not responsible for instantiating a SAX parser to
+ * parse the input; it just handles the resulting events.
+ * <p>
+ * When a parser is built by a Digester instance, the resulting parser will
+ * have this object set as the content-handler, entity-resolver, error-handler,
+ * and dtd-handler. However if the user has used Digester APIs to explicitly
+ * specify their own entity-resolver, error-handler or dtd-handler then this
+ * object will delegate to the user-provided class (after some basic logging
+ * and housekeeping).
+ * <p>
+ * If the user chooses to instantiate the parser themselves, then they must
+ * set the content-handler to a SAXHandler, but may point the entity-resolver,
+ * error-handler and dtd-handler callbacks at any object they wish.
+ * <p>
+ * The content-handler callbacks implemented here (beginDocument, endDocument,
+ * beginElement, characters and endElement) are used to provide the standard
+ * digester functionality.
+ * <p>
+ * The entity-resolver callbacks implemented here perform a lookup of the
+ * entity in a (publicid->url) table configured via calls to the register(...)
+ * method. For entities which are not found to be registered, there systemid
+ * (if any) is used to attempt to locate them.
+ * <p>
+ * The error-handler callbacks implemented here cause a DigestionException
+ * to be thrown if the xml parser reports an error. Warnings are logged
+ * but otherwise ignored.
+ * <p>
+ * The dtd-handler callbacks (notationDecl and unparsedEntityDecl) are simply
+ * logged, then ignored.
+ */
+
+public class SAXHandler extends DefaultHandler {
+
+    // --------------------------------------------------------- Constructors
+
+    /**
+     * Construct a new SAXHandler.
+     */
+    public SAXHandler() {
+        super();
+    }
+
+    // --------------------------------------------------- Instance Variables
+
+    /**
+     * The XMLReader object that is generating the SAX events being handled
+     * by this object. This member is currently here for only one purpose:
+     * so that the CreateNodeRule can get access to it and (temporarily)
+     * redirect the sax events to its own handler. This isn't entirely
+     * elegant (particularly as it introduces a cyclic dependency between
+     * this class and its XMLReader); alternative solutions are welcome.
+     */
+    private XMLReader reader;
+
+    /**
+     * The EntityResolver used to look up any external entities referenced
+     * from within the input xml. Note that this class always receives the
+     * event initally, so that they can be logged, but if the user has
+     * specified an entityResolver then the events are also forwarded to
+     * the provided object.
+     */
+    private EntityResolver entityResolver = null;
+
+    /**
+     * The application-supplied error handler that is notified when parsing
+     * warnings, errors, or fatal errors occur. Note that this class always
+     * handles the errors initially, so that they can be logged, but if the
+     * user has specified an errorHandler then the events are also forwarded
+     * to the provided object.
+     */
+    private ErrorHandler errorHandler = null;
+
+    /**
+     * The Locator associated with our parser.
+     */
+    private Locator locator = null;
+
+    /**
+     * The XML schema to use for validating an XML instance.
+     *
+     * TODO: properly implement schema validation
+     */
+    private String schemaLocation = null;
+
+    /**
+     * What language the schema is in (W3C, or RELAXNG or ..)
+     *
+     * TODO: properly implement schema validation. This variable is not
+     * actually currently used.
+     */
+    private String schemaLanguage = null;
+
+    /**
+     * A map of known external entities that input xml documents may refer to.
+     * via public or system IDs. The keys of the map entries are public or
+     * system IDs, and the values are URLs (typically local files) pointing
+     * to locations where those entities can be found.
+     * <p>
+     * See #setKnownEntities, #getKnownEntities, #registerKnownEntity
+     */
+    private Map knownEntities = new HashMap();
+
+    /**
+     * An object which contains state information that evolves
+     * as the parse progresses. Rule object commonly interact with
+     * the context object.
+     */
+    private Context context = null;
+
+    /**
+     * The <code>Rules</code> implementation containing our collection of
+     * <code>Rule</code> instances and associated matching policy.  If not
+     * established before the first rule is added, a default implementation
+     * will be provided.
+     */
+    private RuleManager ruleManager = null;
+
+    /**
+     * The initial object (if any) that the stack should be primed with
+     * before parsing starts.
+     */
+    private Object initialObject = null;
+
+    /**
+     * The Log to which most logging calls will be made.
+     */
+    private Log log =
+        LogFactory.getLog("org.apache.commons.digester.Digester");
+
+    /**
+     * The Log to which all SAX event related logging calls will be made.
+     */
+    private Log saxLog =
+        LogFactory.getLog("org.apache.commons.digester.Digester.sax");
+
+    /**
+     * An optional class that substitutes values in attributes and body text.
+     * This may be null and so a null check is always required before use.
+     */
+    private Substitutor substitutor;
+
+    /**
+     * The class loader to use for instantiating application objects.
+     * If not specified, the context class loader, or the class loader
+     * used to load Digester itself, is used, based on the value of the
+     * <code>useContextClassLoader</code> variable.
+     */
+    private ClassLoader explicitClassLoader;
+
+    /**
+     * Do we want to use the Context classloader when loading classes for
+     * instantiating new objects. Default is false.
+     */
+    private boolean useContextClassLoader = false;
+
+    /**
+     * Has this instance had its initialize method called yet?
+     */
+    private boolean initialized = false;
+
+    // -------------------------------------------------------------------
+    // Instance variables that are modified during a parse.
+    // -------------------------------------------------------------------
+
+    /**
+     * The public identifier of the DTD we are currently parsing under
+     * (if any). The user may set this explicitly, in which case we ignore
+     * the DTD specified in the input xml file and use the one provided
+     * by the user. See method resolveEntity.
+     *
+     * TODO: Consider if this should be moved to Context. Maybe not if the
+     * user has explicitly set it, but certainly if it is extracted from
+     * the input data...
+     */
+    private String publicId = null;
+
+    /**
+     * The body text of the current element. As the parser reports chunks
+     * of text associated with the current element, they are appended here.
+     * When the end of the element is reported, the full text content of the
+     * current element should be here. Note that if the element has mixed
+     * content, ie text intermingled with child elements, then this buffer
+     * ends up with all the different text pieces mixed together.
+     */
+    private StringBuffer bodyText = new StringBuffer();
+
+    /**
+     * When processing an element with mixed content (ie text and child
+     * elements), then when we start a child element we need to store the
+     * current text seen so far, and restore it after we have finished
+     * with the child element. This stack therefore contains StringBuffer
+     * items containing the body text of "interrupted" xml elements.
+     */
+    private ArrayStack bodyTexts = new ArrayStack();
+
+    /**
+     * Registered namespaces we are currently processing.  The key is the
+     * namespace prefix that was declared in the document.  The value is an
+     * ArrayStack of the namespace URIs this prefix has been mapped to --
+     * the top Stack element is the most current one.  (This architecture
+     * is required because documents can declare nested uses of the same
+     * prefix for different Namespace URIs).
+     */
+    private HashMap namespaces = new HashMap();
+
+    // ---------------------------------------------------------------------
+    // General object configuration methods
+    //
+    // These methods are expected to be called by the user or by the
+    // Digester class in order to set this object up ready to perform 
+    // parsing.
+    //
+    // Some methods (particularly the getters) are also used during
+    // parsing.
+    // ---------------------------------------------------------------------
+
+    /**
+     * Specify the XMLReader object that will be generating the SAX events
+     * passed to this object. Some Action classes (CreateNodeAction in
+     * particular) need to access the XMLReader, so this object must be able
+     * to provide this data when requested.
+     */
+    public void setXMLReader(XMLReader reader) {
+        this.reader = reader;
+    }
+
+    /**
+    * See {@link #setXMLReader}.
+     */
+    public XMLReader getXMLReader() {
+        return reader;
+    }
+
+    /**
+     * Set the publid id of the current file being parsed. This will cause
+     * the declared DOCTYPE (if any) of the input document to be ignored.
+     *
+     * Instead the provided publicId will be looked up in the known entities,
+     * and the resource located at the associated URL will be used as the
+     * DTD for this input document.
+     * <p>
+     * NOTE: if the input document doesn't include a DOCTYPE, then the
+     * DTD specified by this call is not used. There is currently no way
+     * to force an input document to be validated against a specific DTD
+     * (due to a lack of this feature in the xml standard).
+     *
+     * @param publicId the DTD/Schema public's id.
+     */
+    public void setPublicId(String publicId){
+        this.publicId = publicId;
+    }
+
+    /**
+     * Return the public identifier of the DTD we are currently parsing under,
+     * if any. If setPublicId has been called previously, then the value
+     * returned here will be the one explicitly set. Otherwise, if we have
+     * already seen a DOCTYPE declaration in the input data, then the
+     * public id in that DOCTYPE will be returned. Otherwise (parsing hasn't
+     * started or the input document had no DOCTYPE) null is returned.
+     */
+    public String getPublicId() {
+        return this.publicId;
+    }
+
+    /**
+     * Set the current logger. This call should be made before parsing
+     * starts.
+     */
+    public void setLogger(Log log) {
+        this.log = log;
+    }
+
+    /**
+     * Return the current Logger associated with this instance.
+     */
+    public Log getLogger() {
+        return log;
+    }
+
+    /**
+     * Sets the logger used for logging SAX-related information.
+     * <strong>Note</strong> the output is finely grained.
+     * @param saxLog Log, not null
+     */
+    public void setSAXLogger(Log saxLog) {
+
+        this.saxLog = saxLog;
+    }
+
+    /**
+     * Gets the logger used for logging SAX-related information.
+     * <strong>Note</strong> the output is finely grained.
+     */
+    public Log getSAXLogger() {
+        return saxLog;
+    }
+
+    /**
+     * Set the <code>RuleManager</code> implementation object containing our
+     * rules collection and associated matching policy.
+     *
+     * @param ruleManager New RuleManager implementation
+     */
+    public void setRuleManager(RuleManager ruleManager) {
+        this.ruleManager = ruleManager;
+    }
+
+    /**
+     * Return the <code>Rules</code> implementation object containing our
+     * rules collection and associated matching policy.  If none has been
+     * established, a default implementation will be created and returned.
+     */
+    public RuleManager getRuleManager() {
+        if (ruleManager == null) {
+            ruleManager = new DefaultRuleManager();
+        }
+        return ruleManager;
+    }
+
+    /**
+     * Set the XML Schema URI used for validating a XML Instance.
+     *
+     * @param schemaLocation a URI to the schema.
+     */
+    public void setSchema(String schemaLocation){
+        this.schemaLocation = schemaLocation;
+    }
+
+    /**
+     * Return the XML Schema URI used for validating an XML instance.
+     */
+    public String getSchema() {
+        return (this.schemaLocation);
+    }
+
+    /**
+     * Set the XML Schema language used when parsing. By default, we use W3C.
+     *
+     * @param schemaLanguage a URI to the schema language.
+     */
+    public void setSchemaLanguage(String schemaLanguage){
+        this.schemaLanguage = schemaLanguage;
+    }
+
+    /**
+     * Return the XML Schema language used when parsing.
+     */
+    public String getSchemaLanguage() {
+        return schemaLanguage;
+    }
+
+    /**
+     * Determine whether to use the Context ClassLoader (the one found by
+     * calling <code>Thread.currentThread().getContextClassLoader()</code>)
+     * to resolve/load classes that are defined in various rules.  If not
+     * using Context ClassLoader, then the class-loading defaults to
+     * using the calling-class' ClassLoader.
+     *
+     * @param use determines whether to use Context ClassLoader.
+     */
+    public void setUseContextClassLoader(boolean use) {
+        useContextClassLoader = use;
+    }
+
+    /**
+     * Return the boolean as to whether the context classloader should be used.
+     */
+    public boolean getUseContextClassLoader() {
+        return useContextClassLoader;
+    }
+
+    /**
+     * Set the class loader to be used for instantiating application objects
+     * when required.
+     *
+     * @param classLoader The new class loader to use, or <code>null</code>
+     *  to revert to the standard rules
+     */
+    public void setExplicitClassLoader(ClassLoader classLoader) {
+        this.explicitClassLoader = classLoader;
+    }
+
+    /**
+     * Get the class loader to be used by actions when instantiating objects
+     * as a result of processing xml input.
+     * </ul>
+     */
+    public ClassLoader getExplicitClassLoader() {
+        return explicitClassLoader;
+    }
+
+    /**
+     * Return the class loader to be used by actions when instantiating objects
+     * as a result of processing xml input.
+     * <p>
+     * The classloader used is determined using the following procedure:
+     * <ul>
+     * <li>The class loader set by <code>setClassLoader()</code>, if any</li>
+     * <li>The thread context class loader, if it exists and the
+     *     <code>useContextClassLoader</code> property is set to true</li>
+     * <li>The class loader used to load this class itself.
+     * </ul>
+     */
+    public ClassLoader getClassLoader() {
+        if (this.explicitClassLoader != null) {
+            return this.explicitClassLoader;
+        }
+        
+        if (this.useContextClassLoader) {
+            ClassLoader classLoader =
+                    Thread.currentThread().getContextClassLoader();
+            if (classLoader != null) {
+                return classLoader;
+            }
+        }
+
+        return this.getClass().getClassLoader();
+    }
+
+    /**
+     * Sets the <code>Substitutor</code> to be used to convert attributes and
+     * body text. This allows manipulation of the xml input to be performed
+     * before any action sees it. One application of this is to allow
+     * variable substitution in the xml text, eg "&lt;foo id='$id'&gt;".
+     *
+     * @param substitutor the Substitutor to be used to convert attributes
+     * and body text or null if no substitution of these values is to be
+     * performed.
+     */
+    public void setSubstitutor(Substitutor substitutor) {
+        this.substitutor = substitutor;
+    }
+
+    /**
+     * Gets the <code>Substitutor</code> used to convert attributes and body text.
+     * @return Substitutor, null if not substitutions are to be performed.
+     */
+    public Substitutor getSubstitutor() {
+        return substitutor;
+    }
+
+    /**
+     * Set the object that the stack should be primed with before parsing
+     * starts.
+     */
+    public void setInitialObject(Object o) {
+        initialObject = o;
+    }
+
+    /**
+     * Return the root of the Context's object stack. Obviously, this method
+     * should not be called until parsing has completed.
+     */
+    public Object getRoot() {
+        if (context == null) {
+            return null;
+        } else {
+            return context.getRoot();
+        }
+    }
+
+    /**
+     * Set the <code>EntityResolver</code> used by SAX when resolving
+     * any entity references present in the input xml (including resolving
+     * any DTD or schema declaration).
+     * <p>
+     * If null is passed, then the default behaviour is restored.
+     * <p>
+     * The default behaviour is to use the default behaviour of the XMLReader,
+     * which is usually to attempt to download from the SYSTEM-ID value (if any)
+     * of the entity definition in the input xml.
+     *
+     * @param entityResolver a class that implement the 
+     * <code>EntityResolver</code> interface.
+     */
+    public void setEntityResolver(EntityResolver entityResolver){
+        this.entityResolver = entityResolver;
+    }
+
+    /**
+     * Return the Entity Resolver used by the SAX parser.
+     * @return Return the Entity Resolver used by the SAX parser.
+     */
+    public EntityResolver getEntityResolver(){
+        return entityResolver;
+    }
+
+    /**
+     * Set the error handler. If none is expliticly set, then the default
+     * behaviour occurs, which is to log an error then throw an exception
+     * which terminates the parsing. To restore the default behaviour after
+     * setting an explicit erorr handler, pass <i>null</i>.
+     *
+     * @param errorHandler The new error handler
+     */
+    public void setErrorHandler(ErrorHandler errorHandler) {
+        this.errorHandler = errorHandler;
+    }
+
+    /**
+     * Return the error handler. Null indicates the default behaviour will
+     * be used; see {@link #setErrorHandler}.
+     */
+    public ErrorHandler getErrorHandler() {
+        return errorHandler;
+    }
+
+    /**
+     * Specifies a map of (publicId->URI) pairings that will be used when
+     * resolving entities in the input xml (including the DTD or schema
+     * specified with the DOCTYPE).
+     */
+    public void setKnownEntities(Map knownEntities) {
+        this.knownEntities = knownEntities;
+    }
+
+    /**
+    * Returns a map of (publicId->URI) pairings. See {@link #setKnownEntities}.
+     */
+    public Map getKnownEntities() {
+        return knownEntities;
+    }
+
+    /**
+     * <p>Register a mapping between a public or system ID (denoting an external
+     * entity, aka resource) and a URL indicating where that resource can be
+     * found.</p>
+     *
+     *<p>When the input xml refers to the entity via a public or system id, the
+     * resource pointed to by the registered URL is returned. This is commonly
+     * done for the input document's DTD, so that the DTD can be retrieved
+     * from a local file.</p>
+     *
+     * <p>This implementation provides only basic functionality. If more
+     * sophisticated features are required,using {@link #setEntityResolver} to
+     * set a custom resolver is recommended. Note in particular that if the
+     * input xml uses a system-ID to refer to an entity that is not registered,
+     * then the parser will attempt to use the system-id directly, potentially
+     * downloading the resource from a remote location.</p>
+     *
+     * <p>
+     * <strong>Note:</strong> This method will have no effect when a custom
+     * <code>EntityResolver</code> has been set. (Setting a custom
+     * <code>EntityResolver</code> overrides the internal implementation.)
+     * </p>
+     * @param publicOrSystemId Public or system identifier of the entity to be 
+     *  resolved
+     * @param entityURL The URL to use for reading this entity
+     */
+    public void registerKnownEntity(String publicOrSystemId, String entityURL) {
+        if (log.isDebugEnabled()) {
+            log.debug("register('" + publicOrSystemId + "', '" + entityURL + "'");
+        }
+        knownEntities.put(publicOrSystemId, entityURL);
+    }
+
+    /**
+     * Add a (pattern, action) pair to the RuleManager instance associated
+     * with this saxHandler. This is equivalent to
+     * <pre>
+     *  getRuleManager().addRule(pattern, action);
+     * </pre>
+     */
+    public void addRule(String pattern, Action action)
+    throws InvalidRuleException {
+        ruleManager.addRule(pattern, action);
+    }
+
+    /**
+     * Cleanup method which releases any memory that is no longer needed
+     * after a parse has completed. There is one exception: the root
+     * object generated during the parse is retained so that it can be
+     * retrieved by getRoot(). If this is no longer needed, then setRoot(null)
+     * should be called to release this member.
+     */
+    public void clear() {
+        namespaces.clear();
+
+        // It would be nice to set
+        //   context = null;
+        // but currently that would stuff up the getRoot() method.
+    }
+
+    /**
+     * Return the currently mapped namespace URI for the specified prefix,
+     * if any; otherwise return <code>null</code>.  These mappings come and
+     * go dynamically as the document is parsed.
+     *
+     * @param prefix Prefix to look up
+     */
+    public String findNamespaceURI(String prefix) {
+        ArrayStack stack = (ArrayStack) namespaces.get(prefix);
+        if (stack == null) {
+            return null;
+        }
+        try {
+            return (String) stack.peek();
+        } catch (EmptyStackException e) {
+            // This should never happen, as endPrefixMapping removes
+            // the prefix from the namespaces map when the stack becomes
+            // empty. Still, better safe than sorry..
+            return null;
+        }
+    }
+
+    /**
+     * Gets the document locator associated with our parser.
+     * See {@link #setDocumentLocator}.
+     *
+     * @return the Locator supplied by the document parser
+     */
+    public Locator getDocumentLocator() {
+        return locator;
+    }
+
+    // ------------------------------------------------------- 
+    // Package Methods
+    //
+    // These methods are intended mainly for the use of Action
+    // classes and other similar "implementation" classes.
+    // ------------------------------------------------------- 
+
+    /**
+     * Create a SAX exception which also understands about the location in
+     * the digester file where the exception occurs. This method is expected
+     * to be called by Action classes when they detect a problem.
+     *
+     * @return the new exception
+     */
+    public SAXException createSAXException(String message, Exception e) {
+        if ((e != null) &&
+            (e instanceof InvocationTargetException)) {
+            Throwable t = ((InvocationTargetException) e).getTargetException();
+            if ((t != null) && (t instanceof Exception)) {
+                e = (Exception) t;
+            }
+        }
+
+        if (locator != null) {
+            String error = "Error at (" + locator.getLineNumber() + ", " +
+                    locator.getColumnNumber() + ": " + message;
+            if (e != null) {
+                return new SAXParseException(error, locator, e);
+            } else {
+                return new SAXParseException(error, locator);
+            }
+        }
+
+        // The SAX parser doesn't have location info enabled, so we'll just
+        // generate the best error message we can without it.
+        log.error("No Locator!");
+        if (e != null) {
+            return new SAXException(message, e);
+        } else {
+            return new SAXException(message);
+        }
+    }
+
+    /**
+     * Create a SAX exception which also understands about the location in
+     * the digester file where the exception occurs. This method is expected
+     * to be called by Action classes when they detect a problem.
+     *
+     * @return the new exception
+     */
+    public SAXException createSAXException(Exception e) {
+        if (e instanceof InvocationTargetException) {
+            Throwable t = ((InvocationTargetException) e).getTargetException();
+            if ((t != null) && (t instanceof Exception)) {
+                e = (Exception) t;
+            }
+        }
+        return createSAXException(e.getMessage(), e);
+    }
+
+    /**
+     * Create a SAX exception which also understands about the location in
+     * the digester file where the exception occurs. This method is expected
+     * to be called by Action classes when they detect a problem.
+     *
+     * @return the new exception
+     */
+    public SAXException createSAXException(String message) {
+        return createSAXException(message, null);
+    }
+
+    // -------------------------------------------------------
+    // Overridable methods
+    //
+    // These methods provide hooks for users to customise the
+    // behavior of this SAXHandler class by subclassing if they
+    // wish.
+    // ------------------------------------------------------- 
+
+    /**
+     * <p>Provide a hook for lazy configuration of this <code>SAXHandler</code>
+     * instance. </p>
+     *
+     * <p>This code is called once only, immediately before the first document
+     * is to be parsed. The default implementation does nothing, but subclasses
+     * can override as needed.</p>
+     */
+     private void initialize() {
+     }
+     
+    /**
+     * <p>Provide a hook for lazy configuration of this <code>SAXHandler</code>
+     * instance. </p>
+     *
+     * <p>This code is called once at the start of each input document being
+     * parsed. The default implementation does nothing, but subclasses
+     * can override as needed.</p>
+     */
+     private void initializePerParse() {
+     }
+     
+    // ------------------------------------------------- 
+    // Private methods for use of this class only
+    // ------------------------------------------------- 
+
+    /**
+     * Invoke the initialize and initializePerParse methods.
+     */
+    private void configure() {
+        if (!initialized) {
+            // Perform lazy configuration as needed, by calling a hook method for
+            // subclasses that want to be initialised once only.
+            initialize();
+            initialized = true;
+        }
+
+        initializePerParse();
+    }
+
+    // ------------------------------------------------- 
+    // ContentHandler Methods
+    // ------------------------------------------------- 
+
+    /**
+     * Sets the document locator associated with our parser. This method
+     * is called by the sax parser before any other parse events are
+     * dispatched this way. The object that was passed here is then
+     * updated before each SAX event is dispatched to this class.
+     *
+     * @param locator The new locator
+     *
+     * TODO: Consider whether this object (and the associated createSAXException
+     * method) should be on the Context rather than this object, to make it
+     * easier for Action instances to access.
+     *
+     */
+    public void setDocumentLocator(Locator locator) {
+        if (saxLog.isDebugEnabled()) {
+            saxLog.debug("setDocumentLocator(" + locator + ")");
+        }
+
+        this.locator = locator;
+    }
+
+    /**
+     * Process notification of the beginning of the document being reached.
+     * Here we perform all the once-per-input-document initialisation.
+     *
+     * @exception SAXException if a parsing error is to be reported
+     * The SAXException thrown may be of subclass NestedSAXException,
+     * in which case it has a "getCause" method that returns a nested
+     * exception.
+     */
+    public void startDocument() throws SAXException {
+        if (saxLog.isDebugEnabled()) {
+            saxLog.debug("startDocument()");
+        }
+
+        // This shouldn't be necesary if a parse has completed cleanly, as
+        // endPrefixMapping should have been called for each namespace. But
+        // on error, problems can occur.
+        namespaces.clear();
+        bodyText.setLength(0);
+        bodyTexts.clear();
+
+        // Create a new parsing context. This guarantees that Actions have
+        // a clean slate for handling this new input document.
+        context = new Context(this, log);
+
+        if (initialObject != null) {
+            context.setRoot(initialObject);
+        }
+
+        // give subclasses a chance to do custom configuration before each
+        // parse if they wish.
+        configure();
+        
+        try {
+            ruleManager.startParse(context);
+        } catch(DigestionException ex) {
+            throw new NestedSAXException(ex);
+        }
+    }
+
+    /**
+     * Process notification of the end of the document being reached.
+     * Here we perform all the once-per-input-document initialisation.
+     * Note, however, that if an error occurs during parsing of an input
+     * document then this method doesn't get called.
+     *
+     * @exception SAXException if a parsing error is to be reported.
+     * The SAXException thrown may be of subclass NestedSAXException,
+     * in which case it has a "getCause" method that returns a nested
+     * exception.
+     */
+    public void endDocument() throws SAXException {
+        if (saxLog.isDebugEnabled()) {
+            if (context.getStackSize() > 1) {
+                // A stack depth of one is ok if an initial object was pushed 
+                // onto the stack. More than one is very likely to be an error.
+                saxLog.debug("endDocument():  " + context.getStackSize() +
+                             " elements left");
+            } else {
+                saxLog.debug("endDocument()");
+            }
+        }
+
+        // Fire "finish" events for all defined rules
+        try {
+            ruleManager.finishParse(context);
+        } catch(DigestionException ex) {
+            log.error("finishParse threw exception", ex);
+            throw new NestedSAXException(ex);
+        }
+
+        // Perform final cleanup to release memory that is no longer needed.
+        clear();
+    }
+
+    /**
+     * Process notification that a namespace prefix is coming in to scope.
+     *
+     * @param prefix Prefix that is being declared
+     * @param namespaceURI Corresponding namespace URI being mapped to
+     *
+     * @exception SAXException if a parsing error is to be reported
+     */
+    public void startPrefixMapping(String prefix, String namespaceURI) {
+        if (saxLog.isDebugEnabled()) {
+            saxLog.debug(
+                "startPrefixMapping(" + prefix + "," + namespaceURI + ")");
+        }
+
+        // Register this prefix mapping
+        ArrayStack stack = (ArrayStack) namespaces.get(prefix);
+        if (stack == null) {
+            stack = new ArrayStack();
+            namespaces.put(prefix, stack);
+        }
+        stack.push(namespaceURI);
+    }
+
+    /**
+     * Process notification that a namespace prefix is going out of scope.
+     *
+     * @param prefix Prefix that is going out of scope
+     *
+     * @exception SAXException if a parsing error is to be reported
+     */
+    public void endPrefixMapping(String prefix) throws SAXException {
+        if (saxLog.isDebugEnabled()) {
+            saxLog.debug("endPrefixMapping(" + prefix + ")");
+        }
+
+        // Deregister this prefix mapping
+        ArrayStack stack = (ArrayStack) namespaces.get(prefix);
+        if (stack == null) {
+            return;
+        }
+        try {
+            stack.pop();
+            if (stack.empty())
+                namespaces.remove(prefix);
+        } catch (EmptyStackException e) {
+            // This should never happen; it would indicate a serious
+            // internal software flaw.
+            throw createSAXException("endPrefixMapping popped too many times");
+        }
+    }
+
+    /**
+     * Process notification of character data received from the body of
+     * an XML element. Note that a sax parser is allowed to split contiguous
+     * text into multiple calls to this method.
+     *
+     * @param buffer The characters from the XML document
+     * @param start Starting offset into the buffer
+     * @param length Number of characters from the buffer
+     *
+     * @exception SAXException if a parsing error is to be reported
+     */
+    public void characters(char buffer[], int start, int length) {
+        if (saxLog.isDebugEnabled()) {
+            saxLog.debug("characters(" + new String(buffer, start, length) + ")");
+        }
+
+        bodyText.append(buffer, start, length);
+    }
+
+    /**
+     * Process notification of ignorable whitespace received from the body of
+     * an XML element. Ignorable-whitespace is whitespace (tabs, spaces, etc)
+     * within an element which the DTD/schema has stated has "element content"
+     * only; in this case the text is regarded as being for xml layout only,
+     * and is not semantically significant.
+     *
+     * @param buffer The characters from the XML document
+     * @param start Starting offset into the buffer
+     * @param len Number of characters from the buffer
+     *
+     * @exception SAXException if a parsing error is to be reported
+     */
+    public void ignorableWhitespace(char buffer[], int start, int len) {
+        if (saxLog.isDebugEnabled()) {
+            saxLog.debug("ignorableWhitespace(" +
+                    new String(buffer, start, len) + ")");
+        }
+
+        ;   // No processing required
+    }
+
+    /**
+     * Process notification of a processing instruction that was encountered.
+     * In text form, a processing instruction is of form
+     * <pre>
+     * &lt;? sometext ?&gt;
+     * </pre>
+     *
+     * Note, however, that a DOCTYPE is not a processing instruction, though
+     * it does look like one.
+     *
+     * @param target The processing instruction target
+     * @param data The processing instruction data (if any)
+     *
+     * @exception SAXException if a parsing error is to be reported
+     */
+    public void processingInstruction(String target, String data)
+            throws SAXException {
+        if (saxLog.isDebugEnabled()) {
+            saxLog.debug("processingInstruction('" + target + "','" + data + "')");
+        }
+
+        ;   // No processing is required
+    }
+
+    /**
+     * Process notification of a skipped entity.
+     *
+     * TODO: document what a skipped entity is!
+     *
+     * @param name Name of the skipped entity
+     *
+     * @exception SAXException if a parsing error is to be reported
+     */
+    public void skippedEntity(String name) throws SAXException {
+        if (saxLog.isDebugEnabled()) {
+            saxLog.debug("skippedEntity(" + name + ")");
+        }
+
+        ; // No processing required
+    }
+
+    /**
+     * Process notification of the start of an XML element being reached.
+     *
+     * @param namespaceURI The Namespace URI, or the empty string if the element
+     *   has no Namespace URI or if Namespace processing is not being performed.
+     * @param localName The local name (without prefix), or the empty
+     *   string if Namespace processing is not being performed.
+     * @param qName The qualified name (with prefix), or the empty
+     *   string if qualified names are not available.\
+     * @param attrs The attributes attached to the element. If there are
+     *   no attributes, it shall be an empty Attributes object.
+     * @exception SAXException if a parsing error is to be reported
+     *
+     * Note that this class expects the XMLReader providing sax events
+     * to be namespace-aware. We do make some effort to make the basics
+     * work without a namespace-aware parser, but no promises...
+     */
+    public void startElement(
+    String namespaceURI, String localName, String qName, Attributes attrs)
+    throws SAXException {
+        boolean debug = log.isDebugEnabled();
+
+        if (saxLog.isDebugEnabled()) {
+            saxLog.debug(
+                "startElement(" + namespaceURI + "," + localName + "," +
+                    qName + ")");
+        }
+
+        // Save the body text accumulated for our surrounding element
+        bodyTexts.push(bodyText);
+        if (debug) {
+            log.debug("  Pushing body text '" + bodyText.toString() + "'");
+        }
+        bodyText = new StringBuffer();
+
+        // the actual element name is either in localName or qName, depending
+        // on whether the parser is namespace aware
+        String name = localName;
+        if ((name == null) || (name.length() < 1)) {
+            name = qName;
+        }
+
+        // Compute the current matching rule
+        String matchPath = context.pushMatchPath(namespaceURI, name);
+        if (debug) {
+            log.debug("  New matchpath='" + matchPath + "'");
+        }
+
+        // Fire "begin" events for all relevant rules
+        List actions;
+        try {
+            actions = ruleManager.getMatchingActions(matchPath);
+        } catch(DigestionException ex) {
+            throw new NestedSAXException(ex);
+        }
+
+        context.pushMatchingActions(actions);
+        if ((actions != null) && !actions.isEmpty()) {
+            Substitutor substitutor = getSubstitutor();
+            if (substitutor!= null) {
+                attrs = substitutor.substitute(attrs);
+            }
+            for (int i = 0; i < actions.size(); ++i) {
+                try {
+                    Action action = (Action) actions.get(i);
+                    if (debug) {
+                        log.debug("  Fire begin() for " + action);
+                    }
+                    action.begin(context, namespaceURI, name, attrs);
+                } catch (Exception e) {
+                    log.error("Begin event threw exception", e);
+                    throw createSAXException(e);
+                } catch (Error e) {
+                    log.error("Begin event threw error", e);
+                    throw e;
+                }
+            }
+        } else {
+            if (debug) {
+                log.debug("  No rules found matching '" + matchPath + "'.");
+            }
+        }
+    }
+
+    /**
+     * Process notification of the end of an XML element being reached.
+     * Here we retrieve the set of matching actions, then fire the body()
+     * methods in order, followed by the end() methods in reverse order.
+     *
+     * @param namespaceURI - The Namespace URI, or the empty string if the
+     *   element has no Namespace URI.
+     * @param localName - The local name (without prefix).
+     * @param qName - The qualified XML 1.0 name (with prefix), or the
+     *   empty string if qualified names are not available.
+     * @exception SAXException if a parsing error is to be reported
+     */
+    public void endElement(String namespaceURI, String localName,
+                           String qName) throws SAXException {
+        boolean debug = log.isDebugEnabled();
+        String matchPath = context.getMatchPath();
+
+        if (debug) {
+            if (saxLog.isDebugEnabled()) {
+                saxLog.debug("endElement(" + namespaceURI + "," + localName +
+                        "," + qName + ")");
+            }
+            log.debug("  matchPath='" + matchPath + "'");
+            log.debug("  bodyText='" + bodyText + "'");
+        }
+
+        // The actual element name is either in localName or qName, depending
+        // on whether the parser is namespace aware. We do of course expect
+        // the parser to be namespace-aware, but there's no harm in handling
+        // the non-namespace case here anyway.
+        String name = localName;
+        if ((name == null) || (name.length() < 1)) {
+            name = qName;
+        }
+
+        // Fire "body" events for all relevant rules
+        List actions = (List) context.peekMatchingActions();
+        if ((actions != null) && (actions.size() > 0)) {
+            String bodyText = this.bodyText.toString();
+            Substitutor substitutor = getSubstitutor();
+            if (substitutor!= null) {
+                bodyText = substitutor.substitute(bodyText);
+            }
+            for (int i = 0; i < actions.size(); ++i) {
+                try {
+                    Action action = (Action) actions.get(i);
+                    if (debug) {
+                        log.debug("  Fire body() for " + action);
+                    }
+                    action.body(context, namespaceURI, name, bodyText);
+                } catch (Exception e) {
+                    log.error("Body event threw exception", e);
+                    throw createSAXException(e);
+                } catch (Error e) {
+                    log.error("Body event threw error", e);
+                    throw e;
+                }
+            }
+        } else {
+            if (debug) {
+                log.debug("  No rules found matching '" + matchPath + "'.");
+            }
+        }
+
+        // Restore the body text for the parent element (now that we have
+        // finished with the body text for this current element).
+        bodyText = (StringBuffer) bodyTexts.pop();
+        if (debug) {
+            log.debug("  Popping body text '" + bodyText.toString() + "'");
+        }
+
+        // Fire "end" events for all relevant rules in reverse order
+        // Note that the actions list is actually an ArrayStack, so
+        // out-of-order access isn't a performance problem.
+        if (actions != null) {
+            for (int i = actions.size() - 1; i >= 0; --i) {
+                try {
+                    Action action = (Action) actions.get(i);
+                    if (debug) {
+                        log.debug("  Fire end() for " + action);
+                    }
+                    action.end(context, namespaceURI, name);
+                } catch (Exception e) {
+                    log.error("End event threw exception", e);
+                    throw createSAXException(e);
+                } catch (Error e) {
+                    log.error("End event threw error", e);
+                    throw e;
+                }
+            }
+        }
+
+        // Recover the previous match expression
+        context.popMatchPath();
+        
+        // Discard the list of matching actions
+        context.popMatchingActions();
+    }
+
+    // ----------------------------------------------------- 
+    // DTDHandler Methods
+    // ----------------------------------------------------- 
+
+    /**
+     * Receive notification of a notation declaration event. Currently
+     * we just log these.
+     *
+     * @param name The notation name
+     * @param publicId The public identifier (if any)
+     * @param systemId The system identifier (if any)
+     */
+    public void notationDecl(String name, String publicId, String systemId) {
+        if (saxLog.isDebugEnabled()) {
+            saxLog.debug("notationDecl(" + name + "," + publicId + "," +
+                    systemId + ")");
+        }
+    }
+
+    /**
+     * Receive notification of an unparsed entity declaration event.
+     *
+     * @param name The unparsed entity name
+     * @param publicId The public identifier (if any)
+     * @param systemId The system identifier (if any)
+     * @param notation The name of the associated notation
+     */
+    public void unparsedEntityDecl(String name, String publicId,
+                                   String systemId, String notation) {
+        if (saxLog.isDebugEnabled()) {
+            saxLog.debug("unparsedEntityDecl(" + name + "," + publicId + "," +
+                    systemId + "," + notation + ")");
+        }
+    }
+
+    // ----------------------------------------------- 
+    // EntityResolver Methods
+    // ----------------------------------------------- 
+
+    /**
+     * Resolve the requested external entity. The procedure used is:
+     * <ul>
+     * <li>if the user has registered a custom entity resolver, then invoke
+     *  that; otherwise </li>
+     * <li>if the entities' public or system id has been registered via the
+     * method Digester.registerknownEntity or SAXHandler.setKnownEntities, then
+     * load the entity from the specified URL; otherwise</li>
+     * <li>if the input xml defines a system-id for the entity, then use that
+     * as a URL; otherwise</li>
+     * <li>fall back to whatever the default behaviour is for the XMLParser.</li>
+     * </ul>
+     *
+     * @param publicId The public identifier of the entity being referenced
+     * @param systemId The system identifier of the entity being referenced
+     *
+     * @exception SAXException if a parsing exception occurs
+     *
+     */
+    public InputSource resolveEntity(String publicId, String systemId)
+            throws SAXException, IOException {
+        if (saxLog.isDebugEnabled()) {
+            saxLog.debug("resolveEntity('" + publicId + "', '" + systemId + "')");
+        }
+
+        if (entityResolver != null) {
+            // the user has specified their own EntityResolver, so we just
+            // forward the call to that object:
+            return entityResolver.resolveEntity(publicId, systemId);
+        }
+
+        // TODO: fix this. We can't assume that every external entity
+        // is the doctype!!!!!!
+        if (publicId != null)
+            this.publicId = publicId;
+
+        // Has this system identifier been registered?
+        String entityURL = null;
+        if (publicId != null) {
+            entityURL = (String) knownEntities.get(publicId);
+        }
+
+        // Redirect the schema location to a local destination
+        if (schemaLocation != null && entityURL == null && systemId != null){
+            entityURL = (String)knownEntities.get(systemId);
+        }
+
+        if (entityURL == null) {
+            if (systemId == null) {
+                // cannot resolve
+                if (log.isDebugEnabled()) {
+                    log.debug(" Cannot resolve entity: '" + entityURL + "'");
+                }
+                return null;
+
+            } else {
+                // try to resolve using system ID
+                if (log.isDebugEnabled()) {
+                    log.debug(" Trying to resolve using system ID '" + systemId + "'");
+                }
+                entityURL = systemId;
+            }
+        }
+
+        // Return an input source to our alternative URL
+        if (log.isDebugEnabled()) {
+            log.debug(" Resolving to alternate DTD '" + entityURL + "'");
+        }
+
+        try {
+            return (new InputSource(entityURL));
+        } catch (Exception e) {
+            throw createSAXException(e);
+        }
+    }
+
+    // ------------------------------------------------- 
+    // ErrorHandler Methods
+    // ------------------------------------------------- 
+
+    /**
+     * Handle notification from the XMLReader of a problem in the input xml.
+     * <p>
+     * If the user has registered a custom ErrorHandler via setErrorHandler
+     * then the notification is forwarded to the user object.
+     * <p>
+     * If there is no custom ErrorHandler, then this method does nothing.
+     *
+     * @param exception The warning information
+     *
+     * @exception SAXException if a parsing exception occurs
+     */
+    public void warning(SAXParseException exception) throws SAXException {
+        log.warn(
+            "Parse Warning Error at line " + exception.getLineNumber() +
+            " column " + exception.getColumnNumber() + ": " +
+            exception.getMessage(), exception);
+
+        // default behaviour: ignore the warning
+        if (errorHandler != null) {
+            errorHandler.warning(exception);
+        }
+    }
+
+    /**
+     * Handle notification from the XMLReader of an error in the input xml.
+     * <p>
+     * If the user has registered a custom ErrorHandler via setErrorHandler
+     * then the notification is forwarded to the user object (which will
+     * normally throw the exception provided as a parameter after doing any
+     * custom processing).
+     * <p>
+     * If there is no custom ErrorHandler, then this method just throws the
+     * exception provided as a parameter.
+     *
+     * @param exception The error information
+     *
+     * @exception SAXException if a parsing exception occurs
+     */
+    public void error(SAXParseException exception) throws SAXException {
+        log.error("Parse Error at line " + exception.getLineNumber() +
+                " column " + exception.getColumnNumber() + ": " +
+                exception.getMessage(), exception);
+
+        if (errorHandler == null) {
+            // default behaviour: report the error
+            throw exception;
+        } else {
+            errorHandler.error(exception);
+        }
+    }
+
+
+    /**
+     * Handle notification from the XMLReader of an error in the input xml.
+     * <p>
+     * If the user has registered a custom ErrorHandler via setErrorHandler
+     * then the notification is forwarded to the user object (which will
+     * normally throw the exception provided as a parameter after doing any
+     * custom processing).
+     * <p>
+     * If there is no custom ErrorHandler, then this method just throws the
+     * exception provided as a parameter.
+     *
+     * @param exception The fatal error information
+     *
+     * @exception SAXException if a parsing exception occurs
+     */
+    public void fatalError(SAXParseException exception) throws SAXException {
+        log.error("Parse Fatal Error at line " + exception.getLineNumber() +
+                " column " + exception.getColumnNumber() + ": " +
+                exception.getMessage(), exception);
+
+        if (errorHandler == null) {
+            // default behaviour: report the fatal error
+            throw exception;
+        } else {
+            errorHandler.error(exception);
+        }
+    }
+}

Added: jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/Substitutor.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/Substitutor.java?view=auto&rev=151287
==============================================================================
--- jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/Substitutor.java (added)
+++ jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/Substitutor.java Thu Feb  3 17:51:43 2005
@@ -0,0 +1,66 @@
+/* $Id: $
+ *
+ * Copyright 2003-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.digester2;
+
+import org.xml.sax.Attributes;
+
+/**
+ * <p>(Logical) Interface for substitution strategies.
+ * (It happens to be implemented as a Java abstract class to allow
+ * future additions to be made without breaking backwards compatibility.)
+ * </p>
+ * <p>
+ * Usage: When {@link Digester#setSubstitutor} is set, <code>Digester</code>
+ * calls the methods in this interface to create substitute values which will
+ * be passed into the Rule implementations.
+ * Of course, it is perfectly acceptable for implementations not to make 
+ * substitutions and simply return the inputs.
+ * </p>
+ * <p>Different strategies are supported for attributes and body text.</p>
+ *
+ * @since 1.6 
+ */
+public abstract class Substitutor {
+    
+    /**
+     * <p>Substitutes the attributes (before they are passed to the 
+     * <code>Rule</code> implementations's).</p>
+     *
+     * <p><code>Digester</code> will only call this method a second time 
+     * once the original <code>Attributes</code> instance can be safely reused. 
+     * The implementation is therefore free to reuse the same <code>Attributes</code> instance
+     * for all calls.</p>
+     *
+     * @param attributes the <code>Attributes</code> passed into <code>Digester</code> by the SAX parser, 
+     * not null (but may be empty)
+     * @return <code>Attributes</code> to be passed to the <code>Rule</code> implementations. 
+     * This method may pass back the Attributes passed in.
+     * Not null but possibly empty.
+     */
+    public abstract Attributes substitute(Attributes attributes);
+    
+    /**
+     * Substitutes for the body text.
+     * This method may substitute values into the body text of the
+     * elements that Digester parses.
+     *
+     * @param bodyText the body text (as passed to <code>Digester</code>)
+     * @return the body text to be passed to the <code>Rule</code> implementations
+     */
+    public abstract String substitute(String bodyText);
+}

Added: jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/actions/AbstractObjectCreationFactory.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/actions/AbstractObjectCreationFactory.java?view=auto&rev=151287
==============================================================================
--- jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/actions/AbstractObjectCreationFactory.java (added)
+++ jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/actions/AbstractObjectCreationFactory.java Thu Feb  3 17:51:43 2005
@@ -0,0 +1,42 @@
+/* $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.digester2.actions;
+
+import org.xml.sax.Attributes;
+import org.apache.commons.digester2.Context;
+import org.apache.commons.digester2.ParseException;
+
+/**
+ * <p>Abstract base class for <code>ObjectCreationFactory</code>
+ * implementations.</p>
+ */
+abstract public class AbstractObjectCreationFactory implements ObjectCreationFactory {
+
+    // --------------------------------------------------------- Public Methods
+
+    /**
+     * <p>Factory method called by {@link CreateObjectWithFactoryAction} to 
+     * supply an object based on the element's attributes.
+     *
+     * @param attributes the element's attributes
+     *
+     * @throws Exception any exception thrown will be propagated upwards
+     */
+    public abstract Object createObject(Context context, Attributes attributes) 
+        throws ParseException;
+
+}

Added: jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/actions/BeanPropertySetterAction.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/actions/BeanPropertySetterAction.java?view=auto&rev=151287
==============================================================================
--- jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/actions/BeanPropertySetterAction.java (added)
+++ jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/actions/BeanPropertySetterAction.java Thu Feb  3 17:51:43 2005
@@ -0,0 +1,203 @@
+/* $Id: ActionBeanPropertySetter.java,v 1.20 2004/05/10 06:30:06 skitching Exp $
+ *
+ * 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.digester2.actions;
+
+
+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.apache.commons.logging.Log;
+
+import org.apache.commons.digester2.Context;
+import org.apache.commons.digester2.AbstractAction;
+import org.apache.commons.digester2.ParseException;
+
+/**
+ * <p> Action which sets a bean property on the top object to the body text.</p>
+ *
+ * <p> The property set:</p>
+ * <ul><li>can be specified when the rule is created</li>
+ * <li>or can match the current element when the rule is called.</li></ul>
+ *
+ * <p> Using the second method and the {@link ExtendedRuleManager} child match
+ * pattern, all the child elements can be automatically mapped to properties
+ * on the parent object.</p>
+ */
+
+public class BeanPropertySetterAction extends AbstractAction {
+
+    // ----------------------------------------------------------- Constructors
+
+    /**
+     * <p>Construct instance that sets the given property from the body text.</p>
+     *
+     * @param propertyName name of property to set
+     */
+    public BeanPropertySetterAction(String propertyName) {
+        this.propertyName = propertyName;
+    }
+
+    /**
+     * <p>Construct instance that automatically sets a property from the body text.
+     *
+     * <p> This construct creates an action that sets the property
+     * on the top object named the same as the current element.
+     */
+    public BeanPropertySetterAction() {
+        this((String)null);
+    }
+    
+    // ----------------------------------------------------- Instance Variables
+
+    /**
+     * Set this property on the top object.
+     */
+    protected String propertyName = null;
+
+    /**
+     * The body text used to set the property.
+     */
+    protected String bodyText = null;
+
+    // --------------------------------------------------------- Public Methods
+
+    /**
+     * Process the body text of this element.
+     *
+     * @param context is the current parse context.
+     * @param namespace the namespace URI of the matching element, or an 
+     *   empty string if the element has no namespace
+     * @param name the local name of the element.
+     * @param text The text of the body of this element
+     */
+    public void body(Context context, String namespace, String name, String text)
+        throws ParseException {
+
+        // log some debugging information
+        Log log = context.getLogger();
+        if (log.isDebugEnabled()) {
+            log.debug(
+                "[ActionBeanPropertySetter] Called with text '" + text + "'"
+                + " at path '" + context.getMatchPath() + "'");
+        }
+
+        bodyText = text.trim();
+    }
+
+    /**
+     * Process the end of this element.
+     *
+     * @param context is the current parse context.
+     * @param namespace the namespace URI of the matching element, or an 
+     *   empty string if the element has no namespace
+     * @param name the local name of the element.
+     *
+     * @exception NoSuchMethodException if the bean does not
+     *  have a writeable property of the specified name
+     */
+    public void end(Context context, String namespace, String name) 
+        throws ParseException {
+
+        String property = propertyName;
+
+        if (property == null) {
+            // If we don't have a specific property name,
+            // use the element name.
+            property = name;
+        }
+
+        // Get a reference to the top object
+        Object top = context.peek();
+
+        // log some debugging information
+        Log log = context.getLogger();
+        if (log.isDebugEnabled()) {
+            log.debug(
+                "[ActionBeanPropertySetter]"
+                + " path='" + context.getMatchPath() + "'"
+                + ": class='" + top.getClass().getName() + "'"
+                + ": property '" + property + "'" 
+                + " with text '" + bodyText + "'");
+        }
+
+        // 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(property);
+            if (desc == null) {
+                throw new ParseException
+                    ("DynaBean has no property named " + property);
+            }
+        } else /* this is a standard JavaBean */ {
+            PropertyDescriptor desc;
+            try {
+                desc = PropertyUtils.getPropertyDescriptor(top, property);
+            } catch(Exception ex) {
+                throw new ParseException(
+                    "Unable to access properties for bean of class '"
+                    + top.getClass().getName() + "'", ex);
+            }
+            if (desc == null) {
+                throw new ParseException(
+                    "Bean of class '" + top.getClass().getName() + "'"
+                    + " has no property named '" + property + "'");
+            }
+        }
+
+        // Set the property (with conversion as necessary)
+        try {
+            BeanUtils.setProperty(top, property, bodyText);
+        } catch(Exception ex) {
+            throw new ParseException(
+                "Unable to set property '" + property + "' for bean of class '"
+                + top.getClass().getName() + "'", ex);
+        }
+    }
+
+    /**
+     * Init before parsing commences (just in case a previous parse has
+     * failed, leaving garbage).
+     */
+    public void startParse(Context context) throws ParseException {
+        bodyText = null;
+    }
+
+    /**
+     * Clean up after parsing is complete.
+     */
+    public void finishParse(Context context) throws ParseException {
+        bodyText = null;
+    }
+
+    /**
+     * Render a printable version of this Actuib.
+     */
+    public String toString() {
+
+        StringBuffer sb = new StringBuffer("ActionBeanPropertySetter[");
+        sb.append("propertyName=");
+        sb.append(propertyName);
+        sb.append("]");
+        return (sb.toString());
+    }
+}

Added: jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/actions/CallMethodAction.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/actions/CallMethodAction.java?view=auto&rev=151287
==============================================================================
--- jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/actions/CallMethodAction.java (added)
+++ jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/actions/CallMethodAction.java Thu Feb  3 17:51:43 2005
@@ -0,0 +1,545 @@
+/* $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.digester2.actions;
+
+
+import org.apache.commons.beanutils.ConvertUtils;
+import org.apache.commons.beanutils.MethodUtils;
+import org.apache.commons.logging.Log;
+import org.xml.sax.Attributes;
+import java.lang.reflect.InvocationTargetException;
+
+import org.apache.commons.digester2.Context;
+import org.apache.commons.digester2.AbstractAction;
+import org.apache.commons.digester2.ParseException;
+
+/**
+ * <p>Action that calls a method on an object on the stack
+ * (normally the top/parent object), passing arguments collected from 
+ * subsequent <code>ActionCallParam</code> actions or from the body of this
+ * element. </p>
+ *
+ * <p>By using {@link #ActionCallMethod(String methodName)} 
+ * a method call can be made to a method which accepts no
+ * arguments.</p>
+ *
+ * <p>Incompatible method parameter types are converted 
+ * using <code>org.apache.commons.beanutils.ConvertUtils</code>.
+ * </p>
+ *
+ * <p>Note that the target method is invoked when the <i>end</i> of
+ * the tag the CallMethodAction fired on is encountered, <i>not</i> when the
+ * last parameter becomes available. This implies that rules which fire on
+ * tags nested within the one associated with the CallMethodAction will 
+ * fire before the CallMethodAction invokes the target method. This behaviour is
+ * not configurable. </p>
+ *
+ * <p>Note also that if a CallMethodAction is expecting exactly one parameter
+ * and that parameter is not available (eg CallParamAction is used with an
+ * attribute name but the attribute does not exist) then the method will
+ * not be invoked. If a CallMethodAction is expecting more than one parameter,
+ * then it is always invoked, regardless of whether the parameters were
+ * available or not (missing parameters are passed as null values).</p>
+ */
+
+public class CallMethodAction extends AbstractAction {
+
+    // ----------------------------------------------------------- Constructors
+
+    /**
+     * Construct a "call method" instance with the specified method name.  The
+     * parameter types (if any) default to java.lang.String.
+     *
+     * @param methodName Method name of the parent method to call
+     * @param paramCount The number of parameters to collect, or
+     *  zero for a single argument from the body of this element.
+     */
+    public CallMethodAction(String methodName,
+                          int paramCount) {
+        this(0, methodName, paramCount);
+    }
+
+    /**
+     * Construct a "call method" instance with the specified method name.  The
+     * parameter types (if any) default to java.lang.String.
+     *
+     * @param targetOffset location of the target object. Positive numbers are
+     * relative to the top of the digester object stack. Negative numbers 
+     * are relative to the bottom of the stack. Zero implies the top
+     * object on the stack.
+     * @param methodName Method name of the parent method to call
+     * @param paramCount The number of parameters to collect, or
+     *  zero for a single argument from the body of this element.
+     */
+    public CallMethodAction(int targetOffset,
+                          String methodName,
+                          int paramCount) {
+
+        this.targetOffset = targetOffset;
+        this.methodName = methodName;
+        this.paramCount = paramCount;        
+        if (paramCount == 0) {
+            this.paramTypes = new Class[] { String.class };
+        } else {
+            this.paramTypes = new Class[paramCount];
+            for (int i = 0; i < this.paramTypes.length; i++) {
+                this.paramTypes[i] = String.class;
+            }
+        }
+    }
+
+    /**
+     * Construct a "call method" instance with the specified method name.  
+     * The method should accept no parameters.
+     *
+     * @param methodName Method name of the parent method to call
+     */
+    public CallMethodAction(String methodName) {
+        this(0, methodName, 0, (Class[]) null);
+    }
+    
+    /**
+     * Construct a "call method" instance with the specified method name.  
+     * The method should accept no parameters.
+     *
+     * @param targetOffset location of the target object. Positive numbers are
+     * relative to the top of the digester object stack. Negative numbers 
+     * are relative to the bottom of the stack. Zero implies the top
+     * object on the stack.
+     * @param methodName Method name of the parent method to call
+     */
+    public CallMethodAction(int targetOffset, String methodName) {
+        this(targetOffset, methodName, 0, (Class[]) null);
+    }
+
+    /**
+     * Construct a "call method" rule with the specified method name and
+     * parameter types. If <code>paramCount</code> is set to zero the rule
+     * will use the body of this element as the single argument of the
+     * method, unless <code>paramTypes</code> is null or empty, in this
+     * case the rule will call the specified method with no arguments.
+     *
+     * @param methodName Method name of the parent method to call
+     * @param paramCount The number of parameters to collect, or
+     *  zero for a single argument from the body of ths element
+     * @param paramTypes The Java class names of the arguments
+     *  (if you wish to use a primitive type, specify the corresonding
+     *  Java wrapper class instead, such as <code>java.lang.Boolean</code>
+     *  for a <code>boolean</code> parameter)
+     */
+    public CallMethodAction(
+                            String methodName,
+                            int paramCount, 
+                            String paramTypes[]) {
+        this(0, methodName, paramCount, paramTypes);
+    }
+
+    /**
+     * Construct a "call method" rule with the specified method name and
+     * parameter types. If <code>paramCount</code> is set to zero the rule
+     * will use the body of this element as the single argument of the
+     * method, unless <code>paramTypes</code> is null or empty, in this
+     * case the rule will call the specified method with no arguments.
+     *
+     * @param targetOffset location of the target object. Positive numbers are
+     * relative to the top of the digester object stack. Negative numbers 
+     * are relative to the bottom of the stack. Zero implies the top
+     * object on the stack.
+     * @param methodName Method name of the parent method to call
+     * @param paramCount The number of parameters to collect, or
+     *  zero for a single argument from the body of ths element
+     * @param paramTypes The Java class names of the arguments
+     *  (if you wish to use a primitive type, specify the corresonding
+     *  Java wrapper class instead, such as <code>java.lang.Boolean</code>
+     *  for a <code>boolean</code> parameter)
+     */
+    public CallMethodAction(  int targetOffset,
+                            String methodName,
+                            int paramCount, 
+                            String paramTypes[]) {
+
+        this.targetOffset = targetOffset;
+        this.methodName = methodName;
+        this.paramCount = paramCount;
+        if (paramTypes == null) {
+            this.paramTypes = new Class[paramCount];
+            for (int i = 0; i < this.paramTypes.length; i++) {
+                this.paramTypes[i] = "abc".getClass();
+            }
+        } else {
+            // copy the parameter class names into an array
+            // the classes will be loaded when the digester is set 
+            this.paramClassNames = new String[paramTypes.length];
+            for (int i = 0; i < this.paramClassNames.length; i++) {
+                this.paramClassNames[i] = paramTypes[i];
+            }
+        }
+
+    }
+
+
+    /**
+     * Construct a "call method" rule with the specified method name and
+     * parameter types. If <code>paramCount</code> is set to zero the rule
+     * will use the body of this element as the single argument of the
+     * method, unless <code>paramTypes</code> is null or empty, in this
+     * case the rule will call the specified method with no arguments.
+     *
+     * @param methodName Method name of the parent method to call
+     * @param paramCount The number of parameters to collect, or
+     *  zero for a single argument from the body of ths element
+     * @param paramTypes The Java classes that represent the
+     *  parameter types of the method arguments
+     *  (if you wish to use a primitive type, specify the corresonding
+     *  Java wrapper class instead, such as <code>java.lang.Boolean.TYPE</code>
+     *  for a <code>boolean</code> parameter)
+     */
+    public CallMethodAction(
+                            String methodName,
+                            int paramCount, 
+                            Class paramTypes[]) {
+        this(0, methodName, paramCount, paramTypes);
+    }
+
+    /**
+     * Construct a "call method" rule with the specified method name and
+     * parameter types. If <code>paramCount</code> is set to zero the rule
+     * will use the body of this element as the single argument of the
+     * method, unless <code>paramTypes</code> is null or empty, in this
+     * case the rule will call the specified method with no arguments.
+     *
+     * @param targetOffset location of the target object. Positive numbers are
+     * relative to the top of the digester object stack. Negative numbers 
+     * are relative to the bottom of the stack. Zero implies the top
+     * object on the stack.
+     * @param methodName Method name of the parent method to call
+     * @param paramCount The number of parameters to collect, or
+     *  zero for a single argument from the body of ths element
+     * @param paramTypes The Java classes that represent the
+     *  parameter types of the method arguments
+     *  (if you wish to use a primitive type, specify the corresonding
+     *  Java wrapper class instead, such as <code>java.lang.Boolean.TYPE</code>
+     *  for a <code>boolean</code> parameter)
+     */
+    public CallMethodAction(  int targetOffset,
+                            String methodName,
+                            int paramCount, 
+                            Class paramTypes[]) {
+
+        this.targetOffset = targetOffset;
+        this.methodName = methodName;
+        this.paramCount = paramCount;
+        if (paramTypes == null) {
+            this.paramTypes = new Class[paramCount];
+            for (int i = 0; i < this.paramTypes.length; i++) {
+                this.paramTypes[i] = "abc".getClass();
+            }
+        } else {
+            this.paramTypes = new Class[paramTypes.length];
+            for (int i = 0; i < this.paramTypes.length; i++) {
+                this.paramTypes[i] = paramTypes[i];
+            }
+        }
+
+    }
+
+    // ----------------------------------------------------- Instance Variables
+
+    /**
+     * The body text collected from this element.
+     */
+    protected String bodyText = null;
+
+    /** 
+     * location of the target object for the call, relative to the
+     * top of the digester object stack. The default value of zero
+     * means the target object is the one on top of the stack.
+     */
+    private int targetOffset = 0;
+
+    /**
+     * The method name to call on the parent object.
+     */
+    protected String methodName = null;
+
+    /**
+     * The number of parameters to collect from <code>MethodParam</code> rules.
+     * If this value is zero, a single parameter will be collected from the
+     * body of this element.
+     */
+    protected int paramCount = 0;
+
+    /**
+     * The parameter types of the parameters to be collected.
+     */
+    protected Class paramTypes[] = null;
+
+    /**
+     * The names of the classes of the parameters to be collected.
+     * This attribute allows creation of the classes to be postponed until the digester is set.
+     */
+    private String paramClassNames[] = null;
+    
+    // --------------------------------------------------------- Public Methods
+    
+    /**
+     * If needed, this class loads the parameter classes from their names.
+     *
+     * TODO: Fix this: init method is not allowed to modify object state.
+     * What this probably implies is that the paramTypes member needs to
+     * be stored on the Context object.
+     */
+    public void startParse(Context context)
+    {
+        // if necessary, load parameter classes
+        if (this.paramClassNames != null) {
+            this.paramTypes = new Class[paramClassNames.length];
+            for (int i = 0; i < this.paramClassNames.length; i++) {
+                try {
+                    this.paramTypes[i] =
+                            context.getClassLoader().loadClass(this.paramClassNames[i]);
+                } catch (ClassNotFoundException e) {
+                    // use the digester log
+                    Log log = context.getLogger();
+                    log.error("(ActionCallMethod) Cannot load class " + this.paramClassNames[i], e);
+                    this.paramTypes[i] = null; // Will cause NPE later
+                }
+            }
+        }
+    }
+
+    /**
+     * Process the start of this element.
+     *
+     * @param attributes The attribute list for this element
+     */
+    public void begin(Context context, String namespace, String name, Attributes attributes)
+        throws ParseException {
+
+        // Push an array to capture the parameter values if necessary
+        if (paramCount > 0) {
+            Object parameters[] = new Object[paramCount];
+            for (int i = 0; i < parameters.length; i++) {
+                parameters[i] = null;
+            }
+            context.pushParams(parameters);
+        }
+    }
+
+    /**
+     * Process the body text of this element.
+     *
+     * TODO: FIXME, the bodyText field should be stored on the Context object.
+     *
+     * @param text The body text of this element
+     */
+    public void body(Context context, String namespace, String name, String text)
+        throws ParseException {
+
+        if (paramCount == 0) {
+            this.bodyText = text.trim();
+        }
+    }
+
+
+    /**
+     * Process the end of this element.
+     */
+    public void end(Context context, String namespace, String name)
+        throws ParseException {
+
+        Log log = context.getLogger();
+
+        // Retrieve or construct the parameter values array
+        Object parameters[] = null;
+        if (paramCount > 0) {
+
+            parameters = (Object[]) context.popParams();
+            if (log.isTraceEnabled()) {
+                for (int i=0,size=parameters.length;i<size;i++) {
+                    log.trace("[ActionCallMethod](" + i + ")" + parameters[i]) ;
+                }
+            }
+            
+            // In the case where the parameter for the method
+            // is taken from an attribute, and that attribute
+            // isn't actually defined in the source XML file,
+            // skip the method call
+            if (paramCount == 1 && parameters[0] == null) {
+                return;
+            }
+
+        } else if (paramTypes != null && paramTypes.length != 0) {
+
+            // In the case where the parameter for the method
+            // is taken from the body text, but there is no
+            // body text included in the source XML file,
+            // skip the method call
+            if (bodyText == null) {
+                return;
+            }
+
+            parameters = new Object[1];
+            parameters[0] = bodyText;
+            if (paramTypes.length == 0) {
+                paramTypes = new Class[1];
+                paramTypes[0] = "abc".getClass();
+            }
+
+        }
+
+        // Construct the parameter values array we will need
+        // We only do the conversion if the param value is a String and
+        // the specified paramType is not String. 
+        Object paramValues[] = new Object[paramTypes.length];
+        for (int i = 0; i < paramTypes.length; i++) {
+            // convert nulls and convert stringy parameters 
+            // for non-stringy param types
+            if(
+                parameters[i] == null ||
+                 (parameters[i] instanceof String && 
+                   !String.class.isAssignableFrom(paramTypes[i]))) {
+                
+                paramValues[i] =
+                        ConvertUtils.convert((String) parameters[i], paramTypes[i]);
+            } else {
+                paramValues[i] = parameters[i];
+            }
+        }
+
+        // Determine the target object for the method call
+        Object target;
+        if (targetOffset >= 0) {
+            target = context.peek(targetOffset);
+        } else {
+            target = context.peek(context.getStackSize() + targetOffset );
+        }
+        
+        if (target == null) {
+            StringBuffer sb = new StringBuffer();
+            sb.append("[ActionCallMethod]{");
+            sb.append(context.getMatchPath());
+            sb.append("} Call target is null (");
+            sb.append("targetOffset=");
+            sb.append(targetOffset);
+            sb.append(",stackdepth=");
+            sb.append(context.getStackSize());
+            sb.append(")");
+            throw new ParseException(sb.toString());
+        }
+        
+        // Invoke the required method on the top object
+        if (log.isDebugEnabled()) {
+            StringBuffer sb = new StringBuffer("[ActionCallMethod]{");
+            sb.append(context.getMatchPath());
+            sb.append("} Call ");
+            sb.append(target.getClass().getName());
+            sb.append(".");
+            sb.append(methodName);
+            sb.append("(");
+            for (int i = 0; i < paramValues.length; i++) {
+                if (i > 0) {
+                    sb.append(",");
+                }
+                if (paramValues[i] == null) {
+                    sb.append("null");
+                } else {
+                    sb.append(paramValues[i].toString());
+                }
+                sb.append("/");
+                if (paramTypes[i] == null) {
+                    sb.append("null");
+                } else {
+                    sb.append(paramTypes[i].getName());
+                }
+            }
+            sb.append(")");
+            log.debug(sb.toString());
+        }
+
+        try {        
+            Object result = MethodUtils.invokeMethod(
+                    target, methodName,
+                    paramValues, paramTypes);            
+            
+            processMethodCallResult(result);
+        }
+        catch(NoSuchMethodException ex) {
+            throw new ParseException(
+                "No such method: " + methodName 
+                + " on object type:" + target.getClass().getName(),
+                ex);
+        }
+        catch(IllegalAccessException ex) {
+            throw new ParseException(
+                "Unable to access method: " + methodName 
+                + " on object type:" + target.getClass().getName(),
+                ex);
+        }
+        catch(InvocationTargetException ex) {
+            throw new ParseException(
+                "Method: " + methodName 
+                + " on object type:" + target.getClass().getName()
+                + " threw an exception when it was invoked.",
+                ex);
+        }
+    }
+
+
+    /**
+     * Clean up after parsing is complete.
+     */
+    public void finishParse(Context context) throws ParseException {
+        bodyText = null;
+    }
+
+    /**
+     * Subclasses may override this method to perform additional processing of the 
+     * invoked method's result.
+     *
+     * @param result the Object returned by the method invoked, possibly null
+     */
+    protected void processMethodCallResult(Object result) {
+        // do nothing
+    }
+
+    /**
+     * Render a printable version of this Rule.
+     */
+    public String toString() {
+
+        StringBuffer sb = new StringBuffer("ActionCallMethod[");
+        sb.append("methodName=");
+        sb.append(methodName);
+        sb.append(", paramCount=");
+        sb.append(paramCount);
+        sb.append(", paramTypes={");
+        if (paramTypes != null) {
+            for (int i = 0; i < paramTypes.length; i++) {
+                if (i > 0) {
+                    sb.append(", ");
+                }
+                sb.append(paramTypes[i].getName());
+            }
+        }
+        sb.append("}");
+        sb.append("]");
+        return (sb.toString());
+    }
+}



---------------------------------------------------------------------
To unsubscribe, e-mail: commons-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: commons-dev-help@jakarta.apache.org