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/20 01:38:00 UTC

svn commit: r154457 [1/2] - in jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins: ./ strategies/

Author: skitching
Date: Sat Feb 19 16:37:55 2005
New Revision: 154457

URL: http://svn.apache.org/viewcvs?view=rev&rev=154457
Log:
Initial port of plugins module from digester 1.x series

Added:
    jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/
    jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/Declaration.java   (with props)
    jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/LogUtils.java   (with props)
    jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/PluginAssertionFailure.java   (with props)
    jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/PluginConfiguration.java   (with props)
    jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/PluginCreateAction.java   (with props)
    jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/PluginDeclarationAction.java   (with props)
    jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/PluginDeclarationScope.java   (with props)
    jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/PluginException.java   (with props)
    jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/PluginInvalidInputException.java   (with props)
    jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/PluginRuleManager.java   (with props)
    jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/RuleFinder.java   (with props)
    jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/RuleLoader.java   (with props)
    jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/package.html
    jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/strategies/
    jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/strategies/FinderFromClass.java   (with props)
    jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/strategies/FinderFromDfltClass.java   (with props)
    jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/strategies/FinderFromDfltMethod.java   (with props)
    jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/strategies/FinderFromMethod.java   (with props)
    jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/strategies/FinderSetProperties.java   (with props)
    jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/strategies/LoaderFromClass.java   (with props)
    jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/strategies/LoaderSetProperties.java   (with props)
    jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/strategies/package.html

Added: jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/Declaration.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/Declaration.java?view=auto&rev=154457
==============================================================================
--- jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/Declaration.java (added)
+++ jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/Declaration.java Sat Feb 19 16:37:55 2005
@@ -0,0 +1,230 @@
+/* $Id$
+ *
+ * Copyright 2003-2005 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.plugins;
+
+import java.io.IOException;
+import java.util.Properties;
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.digester2.Context;
+
+/**
+ * Represents a Class that can be instantiated by a PluginCreateAction, plus
+ * info on how to load custom digester rules for mapping xml into that
+ * plugged-in class.
+ * <p>
+ * Declaration instances are created by either:
+ * <ul>
+ * <li> PluginCreateAction from method startParse time, in the case where a 
+ *  plugin has a default plugin class.
+ * <li> PluginCreateAction from method begin, in the case where the input
+ *  xml just declares the plugin class on the matching tag, eg
+ *  [widget plugin-class="com.acme.widget" ...]
+ * <li> PluginDeclarationAction, in the case where the input xml pre-declares
+ *  the plugin class, eg [plugin-declaration id="..." class=".." ..../]
+ * </ul>
+ */
+
+public class Declaration {
+   
+    /** The class of the object to be instantiated. */
+    private Class pluginClass;
+
+    /** The name of the class of the object to be instantiated. */
+    private String pluginClassName;
+    
+    /** See {@link #setId}. */ 
+    private String id;
+    
+    /** See {@link #setProperties}. */
+    private Properties properties = new Properties();
+    
+    /** See {@link #init}. */
+    private boolean initialized = false;
+
+    /**
+     * Class which is responsible for dynamically loading this
+     * plugin's rules on demand.
+     */
+    private RuleLoader ruleLoader = null;
+    
+    //---------------------- constructors ----------------------------------
+
+    /**
+     * Constructor.
+     */
+    public Declaration(String pluginClassName) {
+        // We can't load the pluginClass at this time, because we don't
+        // have a digester instance yet to load it through. So just
+        // save the name away, and we'll load the Class object in the
+        // init method.
+        this.pluginClassName = pluginClassName;
+    }
+    
+    /**
+     * Constructor.
+     */
+    public Declaration(Class pluginClass) {
+        this.pluginClass = pluginClass;
+        this.pluginClassName = pluginClass.getName();
+    }
+    
+    /**
+     * Create an instance where a fully-initialised ruleLoader instance
+     * is provided by the caller instead of having the PluginManager
+     * "discover" an appropriate one.
+     */
+    public Declaration(Class pluginClass, RuleLoader ruleLoader) {
+        this.pluginClass = pluginClass;
+        this.pluginClassName = pluginClass.getName();
+        this.ruleLoader = ruleLoader;
+    }
+    
+    //---------------------- properties -----------------------------------
+
+    /** 
+     * The id that the user associated with a particular plugin declaration
+     * in the input xml. This id is later used in the input xml to refer
+     * back to the original declaration.
+     * <p>
+     * For plugins declared "in-line", the id is null.
+     */
+    public void setId(String id) {
+        this.id = id;
+    }
+    
+    /**
+     * Return the id associated with this declaration. For plugins
+     * declared "inline", null will be returned.
+     * 
+     * @return The id value. May be null.
+     */
+    public String getId() {
+        return id;
+    }
+
+    /** 
+     * Copy all (key,value) pairs in the param into the properties member of
+     * this object.
+     * <p>
+     * The declaration properties cannot be explicit member variables,
+     * because the set of useful properties a user can provide on a declaration
+     * depends on what RuleFinder classes are available - and extra RuleFinders
+     * can be added by the user. So here we keep a map of the settings, and
+     * let the RuleFinder objects look for whatever properties they consider
+     * significant.
+     * <p>
+     * The "id" and "class" properties are treated differently.
+     */
+    public void setProperties(Properties p) {
+        properties.putAll(p);
+    }
+    
+    /**
+     * Return plugin class associated with this declaration.
+     * 
+     * @return The pluginClass.
+     */
+    public Class getPluginClass() {
+        return pluginClass;
+    }
+
+    //---------------------- methods -----------------------------------
+    
+    /**
+     * Must be called exactly once, and must be called before any call
+     * to the configure method.
+     */
+    public void init(Context context, PluginDeclarationScope pds) 
+    throws PluginException {
+        Log log = context.getLogger();
+        boolean debug = log.isDebugEnabled();
+        if (debug) {
+            log.debug("init being called!");
+        }
+        
+        if (initialized) {
+            throw new PluginAssertionFailure("Init called multiple times.");
+        }
+
+        if ((pluginClass == null) && (pluginClassName != null)) {
+            try {
+                // load the plugin class object
+                pluginClass = 
+                    context.getClassLoader().loadClass(pluginClassName);
+            } catch(ClassNotFoundException cnfe) {
+                throw new PluginException(
+                    "Unable to load class " + pluginClassName, cnfe);
+            }
+        }
+
+        if (ruleLoader == null) {
+            // the caller didn't provide a ruleLoader to the constructor,
+            // so get the plugin manager to "discover" one.
+            log.debug("Searching for ruleloader...");
+            ruleLoader = pds.findLoader(context, id, pluginClass, properties);
+        } else {
+            log.debug("This declaration has an explicit ruleLoader.");
+        }
+        
+        if (debug) {
+            if (ruleLoader == null) {
+                log.debug(
+                    "No ruleLoader found for plugin declaration"
+                    + " id [" + id + "]"
+                    + ", class [" + pluginClass.getClass().getName() + "].");
+            } else {
+                log.debug(
+                    "RuleLoader of type [" + ruleLoader.getClass().getName()
+                    + "] associated with plugin declaration"
+                    + " id [" + id + "]"
+                    + ", class [" + pluginClass.getClass().getName() + "].");
+            }
+        }
+        
+        initialized = true;        
+    }
+    
+    /**
+     * Attempt to load custom rules for the target class at the specified
+     * pattern.
+     * <p>
+     * On return, any custom rules associated with the plugin class have
+     * been loaded into the Rules object currently associated with the
+     * specified digester object.
+     * <p>
+     * This method is called by PluginCreateAction, after ensuring that
+     * the declaration exists and has been initialised.
+     */
+     
+    public void configure(Context context) throws PluginException {
+        Log log = context.getLogger();
+        boolean debug = log.isDebugEnabled();
+        if (debug) {
+            log.debug("configure being called!");
+        }
+        
+        if (!initialized) {
+            throw new PluginAssertionFailure("Not initialized.");
+        }
+
+        if (ruleLoader != null) {
+            ruleLoader.addRules(context);
+        }
+    }
+}

Propchange: jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/Declaration.java
------------------------------------------------------------------------------
    svn:keywords = Id

Added: jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/LogUtils.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/LogUtils.java?view=auto&rev=154457
==============================================================================
--- jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/LogUtils.java (added)
+++ jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/LogUtils.java Sat Feb 19 16:37:55 2005
@@ -0,0 +1,67 @@
+/* $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.plugins;
+
+import org.apache.commons.digester2.Context;
+import org.apache.commons.logging.Log;
+
+/**
+ * Simple utility class to assist in logging.
+ * <p>
+ * This class is intended only for the use of the code in the
+ * plugins packages. No "user" code should use this package.
+ * <p>
+ * The Digester library has an interesting approach to logging:
+ * all logging should be done via the Log object stored on the
+ * context instance that the object *doing* the logging is associated
+ * with.
+ * <p>
+ * This is done because apparently some "container"-type applications
+ * such as Avalon and Tomcat need to be able to configure different logging
+ * for different <i>instances</i> of the Digester/SAXHandler class which have
+ * been loaded from the same ClassLoader [info from Craig McClanahan]. 
+ * All objects associated with that Digester instance should obey the 
+ * reconfiguration of their owning saxhandler instance's logging. The current 
+ * solution is to force all objects to output logging info via a single 
+ * Log object stored on the saxhandler (and context).instance they are
+ * associated with.
+ * <p>
+ * Of course this causes problems if logging is attempted before an
+ * object <i>has</i> a valid reference to a context. The 
+ * getLogging method provided here resolves this issue by returning a
+ * Log object which silently discards all logging output in this
+ * situation.
+ * <p>
+ * And it also implies that logging filtering can no longer be applied
+ * to subcomponents of the Digester, because all logging is done via
+ * a single Log object (a single Category). C'est la vie...
+ */
+
+class LogUtils {
+    
+  /**
+   * Get the Log object associated with the specified context instance,
+   * or a "no-op" logging object if the context reference is null.
+   */
+  static Log getLogger(Context context) {
+    if (context == null) {
+        return new org.apache.commons.logging.impl.NoOpLog();
+    }
+    
+    return context.getLogger();
+  }
+}

Propchange: jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/LogUtils.java
------------------------------------------------------------------------------
    svn:keywords = Id

Added: jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/PluginAssertionFailure.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/PluginAssertionFailure.java?view=auto&rev=154457
==============================================================================
--- jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/PluginAssertionFailure.java (added)
+++ jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/PluginAssertionFailure.java Sat Feb 19 16:37:55 2005
@@ -0,0 +1,68 @@
+/* $Id$
+ *
+ * Copyright 2003-2005 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.plugins;
+
+/**
+ * Thrown when a bug is detected in the plugins code.
+ * <p>
+ * This class is intended to be used in assertion statements, similar to
+ * the way that java 1.4's native assertion mechanism is used. However there
+ * is a difference: when a java 1.4 assertion fails, an AssertionError
+ * is thrown, which is a subclass of Error; here, the PluginAssertionFailure
+ * class extends RuntimeException rather than Error.
+ * <p>
+ * This difference in design is because throwing Error objects is not
+ * good in a container-based architecture.
+ * <p>
+ * Example:
+ * <pre>
+ *   if (impossibleCondition) {
+ *     throw new PluginAssertionFailure(
+ *       "internal error: impossible condition is true");
+ *   }
+ * </pre> 
+ * <p>
+ * Note that PluginAssertionFailure should <i>not</i> be thrown when user 
+ * input is bad, or when code external to the Digester module passes invalid 
+ * parameters to a plugins method. It should be used only in checks for 
+ * problems which indicate internal bugs within the plugins module.
+ */
+public class PluginAssertionFailure extends PluginException {
+
+    /**
+     * @param cause underlying exception that caused this to be thrown
+     */
+    public PluginAssertionFailure(Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * @param msg describes the reason this exception is being thrown.
+     */
+    public PluginAssertionFailure(String msg) {
+        super(msg);
+    }
+
+    /**
+     * @param msg describes the reason this exception is being thrown.
+     * @param cause underlying exception that caused this to be thrown
+     */
+    public PluginAssertionFailure(String msg, Throwable cause) {
+        super(msg, cause);
+    }
+}

Propchange: jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/PluginAssertionFailure.java
------------------------------------------------------------------------------
    svn:keywords = Id

Added: jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/PluginConfiguration.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/PluginConfiguration.java?view=auto&rev=154457
==============================================================================
--- jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/PluginConfiguration.java (added)
+++ jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/PluginConfiguration.java Sat Feb 19 16:37:55 2005
@@ -0,0 +1,250 @@
+/* $Id$
+ *
+ * Copyright 2004,2005 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.plugins;
+
+import java.io.IOException;
+import java.util.Properties;
+import java.util.List;
+import java.util.LinkedList;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.digester2.Digester;
+import org.apache.commons.digester2.SAXHandler;
+import org.apache.commons.digester2.Context;
+import org.apache.commons.digester2.plugins.strategies.*;
+
+/**
+ * Provides configuration that is per-saxhandler (or per-digester).
+ */
+
+public class PluginConfiguration {
+    private static final SAXHandler.ItemId PLUGIN_CONFIGURATION_ITEM
+        = new SAXHandler.ItemId(PluginConfiguration.class, "instance");
+
+    // the xml attribute the user uses on an xml element to specify
+    // the plugin's class
+    public final String DFLT_PLUGIN_CLASS_ATTR_NS = "";
+    public final String DFLT_PLUGIN_CLASS_ATTR = "plugin-class";
+
+    // the xml attribute the user uses on an xml element to specify
+    // the plugin's class
+    public final String DFLT_PLUGIN_ID_ATTR_NS = "";
+    public final String DFLT_PLUGIN_ID_ATTR = "plugin-id";
+
+    /** See {@link #setPluginClassAttribute}. */
+    private String pluginClassAttrNS = DFLT_PLUGIN_CLASS_ATTR_NS;
+
+    /** See {@link #setPluginClassAttribute}. */
+    private String pluginClassAttr = DFLT_PLUGIN_CLASS_ATTR;
+
+    /** See {@link #setPluginClassAttribute}. */
+    private String pluginIdAttrNS = DFLT_PLUGIN_ID_ATTR_NS;
+
+    /** See {@link #setPluginClassAttribute}. */
+    private String pluginIdAttr = DFLT_PLUGIN_ID_ATTR;
+
+    /**
+     * A list of RuleFinder objects used by all Declarations (and thus
+     * indirectly by all PluginCreateAction instances to locate the custom
+     * rules for plugin classes.
+     */
+    private List ruleFinders;
+
+    //------------------- constructors ---------------------------------------
+
+    public static PluginConfiguration getInstance(Digester digester) {
+        return getInstance(digester.getSAXHandler());
+    }
+
+    public static PluginConfiguration getInstance(Context context) {
+        return getInstance(context.getSAXHandler());
+    }
+
+    public static PluginConfiguration getInstance(SAXHandler saxHandler) {
+        PluginConfiguration pc =
+            (PluginConfiguration) saxHandler.getItem(PLUGIN_CONFIGURATION_ITEM);
+
+        if (pc == null) {
+            pc = new PluginConfiguration();
+            saxHandler.putItem(PLUGIN_CONFIGURATION_ITEM, pc);
+        }
+
+        return pc;
+    }
+
+    //------------------- methods ---------------------------------------
+
+    /**
+     * Return the list of RuleFinder objects. Under normal circumstances
+     * this method creates a default list of these objects when first called
+     * (ie "on-demand" or "lazy initialization"). However if setRuleFinders
+     * has been called first, then the list specified there is returned.
+     * <p>
+     * It is explicitly permitted for the caller to modify this list
+     * by inserting or removing RuleFinder objects.
+     */
+    public List getRuleFinders() {
+        if (ruleFinders == null) {
+            // when processing a plugin declaration, attempts are made to
+            // find custom rules in the order in which the Finder objects
+            // are added below. However this list can be modified
+            ruleFinders = new LinkedList();
+            //ruleFinders.add(new FinderFromFile());
+            //ruleFinders.add(new FinderFromResource());
+            ruleFinders.add(new FinderFromClass());
+            ruleFinders.add(new FinderFromMethod());
+            ruleFinders.add(new FinderFromDfltMethod());
+            ruleFinders.add(new FinderFromDfltClass());
+            //ruleFinders.add(new FinderFromDfltResource());
+            //ruleFinders.add(new FinderFromDfltResource(".xml"));
+            ruleFinders.add(new FinderSetProperties());
+        }
+        return ruleFinders;
+    }
+
+    /**
+     * Set the list of RuleFinder objects. This may be useful if working
+     * in a non-english language, allowing the application developer to
+     * replace the standard list with a list of objects which look for xml
+     * attributes in the local language.
+     * <p>
+     * If the intent is just to add an additional rule-finding algorithm, then
+     * it may be better to call #getRuleFinders, and insert a new object into
+     * the start of the list.
+     */
+    public void setRuleFinders(List ruleFinders) {
+        this.ruleFinders = ruleFinders;
+    }
+
+    /**
+     * Sets the xml attribute which the input xml uses to indicate to a
+     * PluginCreateRule which class should be instantiated.
+     * <p>
+     * Example:
+     * <pre>
+     * setPluginClassAttribute(null, "class");
+     * </pre>
+     * will allow this in the input xml:
+     * <pre>
+     *  &lt;root&gt;
+     *    &lt;some-plugin class="com.acme.widget"&gt; ......
+     * </pre>
+     * instead of the default syntax:
+     * <pre>
+     *  &lt;root&gt;
+     *    &lt;some-plugin plugin-class="com.acme.widget"&gt; ......
+     * </pre>
+     * This is particularly useful if the input xml document is not in
+     * English.
+     * <p>
+     * Note that the xml attributes used by PluginDeclarationRules are not
+     * affected by this method.
+     *
+     * @param namespaceUri is the namespace uri that the specified attribute
+     * is in. If the attribute is in no namespace, then this should be null.
+     * Note that if a namespace is used, the attrName value should <i>not</i>
+     * contain any kind of namespace-prefix. Note also that if you are using
+     * a non-namespace-aware parser, this parameter <i>must</i> be null.
+     *
+     * @param attrName is the attribute whose value contains the name of the
+     * class to be instantiated.
+     */
+    public void setPluginClassAttribute(String namespaceUri,
+                                        String attrName) {
+        pluginClassAttrNS = namespaceUri;
+        pluginClassAttr = attrName;
+    }
+
+    /**
+     * Sets the xml attribute which the input xml uses to indicate to a
+     * PluginCreateRule which plugin declaration is being referenced.
+     * <p>
+     * Example:
+     * <pre>
+     * setPluginIdAttribute(null, "id");
+     * </pre>
+     * will allow this in the input xml:
+     * <pre>
+     *  &lt;root&gt;
+     *    &lt;some-plugin id="widget"&gt; ......
+     * </pre>
+     * rather than the default behaviour:
+     * <pre>
+     *  &lt;root&gt;
+     *    &lt;some-plugin plugin-id="widget"&gt; ......
+     * </pre>
+     * This is particularly useful if the input xml document is not in
+     * English.
+     * <p>
+     * Note that the xml attributes used by PluginDeclarationRules are not
+     * affected by this method.
+     *
+     * @param namespaceUri is the namespace uri that the specified attribute
+     * is in. If the attribute is in no namespace, then this should be null.
+     * Note that if a namespace is used, the attrName value should <i>not</i>
+     * contain any kind of namespace-prefix. Note also that if you are using
+     * a non-namespace-aware parser, this parameter <i>must</i> be null.
+     *
+     * @param attrName is the attribute whose value contains the id of the
+     * plugin declaration to be used when instantiating an object.
+     */
+    public void setPluginIdAttribute(String namespaceUri,
+                                     String attrName) {
+        pluginIdAttrNS = namespaceUri;
+        pluginIdAttr = attrName;
+    }
+
+    /**
+     * Get the namespace for the xml attribute which indicates to a
+     * PluginCreateRule which class is to be plugged in.
+     * <p>
+     * May be null (in fact, normally will be).
+     */
+    public String getPluginClassAttrNS() {
+        return pluginClassAttrNS;
+    }
+
+    /**
+     * Get the namespace for the xml attribute which indicates to a
+     * PluginCreateRule which class is to be plugged in.
+     * <p>
+     * The return value is never null.
+     */
+    public String getPluginClassAttr() {
+        return pluginClassAttr;
+    }
+
+    /**
+     * Get the namespace for the xml attribute which indicates to a
+     * PluginCreateRule which previous plugin declaration should be used.
+     * <p>
+     * May be null (in fact, normally will be).
+     */
+    public String getPluginIdAttrNS() {
+        return pluginIdAttrNS;
+    }
+
+    /**
+     * Get the namespace for the xml attribute which indicates to a
+     * PluginCreateRule which previous plugin declaration should be used.
+     * <p>
+     * The return value is never null.
+     */
+    public String getPluginIdAttr() {
+        return pluginIdAttr;
+    }
+}

Propchange: jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/PluginConfiguration.java
------------------------------------------------------------------------------
    svn:keywords = Id

Added: jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/PluginCreateAction.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/PluginCreateAction.java?view=auto&rev=154457
==============================================================================
--- jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/PluginCreateAction.java (added)
+++ jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/PluginCreateAction.java Sat Feb 19 16:37:55 2005
@@ -0,0 +1,589 @@
+/* $Id$
+ *
+ * Copyright 2003-2005 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.plugins;
+
+import java.util.Iterator;
+import java.util.ListIterator;
+import java.util.List;
+import java.io.File;
+
+import org.apache.commons.digester2.Context;
+import org.apache.commons.digester2.Action;
+import org.apache.commons.digester2.AbstractAction;
+import org.apache.commons.digester2.RuleManager;
+import org.apache.commons.digester2.DefaultRuleManager;
+import org.apache.commons.digester2.ParseException;
+import org.apache.commons.digester2.DigestionException;
+import org.apache.commons.logging.Log;
+
+/**
+ * Allows the original rules for parsing the configuration file to define
+ * points at which plugins are allowed, by configuring a PluginCreateRule
+ * with the appropriate pattern.
+ */
+public class PluginCreateAction extends AbstractAction {
+
+    /**
+     * Simple data structure to store the names of the xml attributes
+     * that the xml elements which matches this action will use to
+     * tell us what class to instantiate.
+     */
+    private static class PluginAttrNames {
+        String pluginClassAttrNS = null;
+        String pluginClassAttr = null;
+        String pluginIdAttrNS = null;
+        String pluginIdAttr = null;
+    }
+
+    private final Context.ItemId PLUGIN_ATTR_NAMES
+        = new Context.ItemId(PluginCreateAction.class, "PluginAttrNames", this);
+
+    // See constructors. Note that these contain only values provided by the
+    // user. If we default to a per-digester value or to a global default,
+    // these member variables remain unaltered. This is because Actions are
+    // forbidden to change any member variables during a parse.
+    private String pluginClassAttrNS;
+    private String pluginClassAttr;
+    private String pluginIdAttrNS;
+    private String pluginIdAttr;
+
+    /** A base class that any plugin must derive from. */
+    private Class baseClass = null;
+
+    /**
+     * Info about optional default plugin to be used if no plugin-id is
+     * specified in the input data. This can simplify the syntax where one
+     * particular plugin is usually used.
+     */
+    private Declaration defaultPlugin;
+
+    //-------------------- constructors -------------------------------------
+
+    /**
+     * Create a plugin rule where the user <i>must</i> specify a plugin-class
+     * or plugin-id.
+     *
+     * @param baseClass is the class which any specified plugin <i>must</i> be
+     * descended from.
+     */
+    public PluginCreateAction(Class baseClass) {
+        this.baseClass = baseClass;
+    }
+
+    /**
+     * Create a plugin rule where the user <i>may</i> specify a plugin.
+     * If the user doesn't specify a plugin, then the default class specified
+     * in this constructor is used.
+     *
+     * @param baseClass is the class which any specified plugin <i>must</i> be
+     * descended from.
+     * @param dfltPluginClass is the class which will be used if the user
+     * doesn't specify any plugin-class or plugin-id. This class will have
+     * custom rules installed for it just like a declared plugin.
+     */
+    public PluginCreateAction(Class baseClass, Class dfltPluginClass) {
+        this.baseClass = baseClass;
+        if (dfltPluginClass != null) {
+            defaultPlugin = new Declaration(dfltPluginClass);
+        }
+    }
+
+    /**
+     * Create a plugin rule where the user <i>may</i> specify a plugin.
+     * If the user doesn't specify a plugin, then the default class specified
+     * in this constructor is used.
+     *
+     * @param baseClass is the class which any specified plugin <i>must</i> be
+     * descended from.
+     * @param dfltPluginClass is the class which will be used if the user
+     * doesn't specify any plugin-class or plugin-id. This class will have
+     * custom rules installed for it just like a declared plugin.
+     * @param dfltPluginRuleLoader is a RuleLoader instance which knows how
+     * to load the custom rules associated with this default plugin.
+     */
+    public PluginCreateAction(Class baseClass, Class dfltPluginClass,
+                    RuleLoader dfltPluginRuleLoader) {
+
+        this.baseClass = baseClass;
+        if (dfltPluginClass != null) {
+            defaultPlugin =
+                new Declaration(dfltPluginClass, dfltPluginRuleLoader);
+        }
+    }
+
+    //------------------- properties ---------------------------------------
+
+    /**
+     * Sets the xml attribute which the input xml uses to indicate to a
+     * PluginCreateRule which class should be instantiated.
+     * <p>
+     * See {@link PluginRules#setPluginClassAttribute} for more info.
+     */
+    public void setPluginClassAttribute(String namespaceUri, String attrName) {
+        pluginClassAttrNS = namespaceUri;
+        pluginClassAttr = attrName;
+    }
+
+    /**
+     * Sets the xml attribute which the input xml uses to indicate to a
+     * PluginCreateRule which plugin declaration is being referenced.
+     * <p>
+     * See {@link PluginRules#setPluginIdAttribute} for more info.
+     */
+    public void setPluginIdAttribute(String namespaceUri, String attrName) {
+        pluginIdAttrNS = namespaceUri;
+        pluginIdAttr = attrName;
+    }
+
+    //------------------- methods --------------------------------------------
+
+    /**
+     * Invoked before parsing begins on a document.
+     * <p>
+     * We ensure the default plugin class is loaded into memory, and
+     * that it does indeed implement the declared base class for this
+     * plugin point. We then ensure any custom rules for the default
+     * plugin have been located, though we don't add them to a RuleManager
+     * yet.
+     *
+     *
+     * @exception PluginConfigurationException
+     */
+    public void startParse(Context context)
+    throws PluginException {
+        // TODO: determine whether there will be problems with using
+        // wildcard patterns with a PluginCreateAction, or with adding
+        // the same instance multiple times..
+
+        Log log = LogUtils.getLogger(context);
+        boolean debug = log.isDebugEnabled();
+        if (debug) {
+            log.debug("PluginCreateAction.beginParse");
+        }
+
+        if (baseClass == null) {
+            baseClass = Object.class;
+        }
+
+        PluginDeclarationScope pds  =
+            (PluginDeclarationScope) context.getItem(
+                PluginDeclarationScope.PLUGIN_DECL_SCOPE);
+
+        if (defaultPlugin != null) {
+            // check default class is valid. We can't do this until the parse
+            // begins, as we need to load the baseClass and plugin class via
+            // the classloader associated with the context.
+            if (!baseClass.isAssignableFrom(defaultPlugin.getPluginClass())) {
+                throw new PluginException(
+                     "Default class [" +
+                     defaultPlugin.getPluginClass().getName() +
+                     "] does not inherit from [" +
+                     baseClass.getName() + "].");
+            }
+
+            // initialise the plugin declaration, which means a RuleLoader
+            // will figure out where the custom rules for the default plugin
+            // class are...
+            defaultPlugin.init(context, pds);
+        }
+
+        PluginAttrNames pluginAttrNames = createPluginAttrNames(context);
+        // and now we've done all that work, cache the info in the context
+        // as instance-specific data.
+        context.putItem(PLUGIN_ATTR_NAMES, pluginAttrNames);
+    }
+
+    /**
+     * Invoked when the Digester matches this rule against an xml element.
+     * <p>
+     * A new instance of the target class is created, and pushed onto the
+     * stack. A new "private" PluginRuleManager object is then created and
+     * set as the current RuleManager object. Any custom rules associated with
+     * the plugin class are then loaded into that new RuleManager object.
+     * Finally, any custom rules that are associated with the current pattern
+     * (such as SetPropertiesAction) have their begin methods executed.
+     *
+     * @param namespace
+     * @param name
+     * @param attributes
+     *
+     * @throws ClassNotFoundException
+     * @throws PluginInvalidInputException
+     * @throws PluginConfigurationException
+     */
+    public void begin(
+    Context context,
+    String namespace, String name,
+    org.xml.sax.Attributes attributes)
+    throws ParseException {
+        String path = context.getMatchPath();
+
+        Log log = context.getLogger();
+        boolean debug = log.isDebugEnabled();
+        if (debug) {
+            log.debug("PluginCreateRule.begin" +
+                  " match=[" + path + "]");
+        }
+
+        // load any custom rules associated with the plugin
+        PluginDeclarationScope pds = PluginDeclarationScope.getInstance(context);
+
+        // and get the cached info calculated at startParse time...
+        PluginAttrNames pluginAttrNames = (PluginAttrNames)
+            context.getItem(PLUGIN_ATTR_NAMES);
+            
+        Declaration currDeclaration = null;
+
+        String pluginClassName = attributes.getValue(
+            pluginAttrNames.pluginClassAttrNS,
+            pluginAttrNames.pluginClassAttr);
+
+        String pluginId = attributes.getValue(
+            pluginAttrNames.pluginIdAttrNS,
+            pluginAttrNames.pluginIdAttr);
+
+        if (pluginClassName != null) {
+            // The user is using a plugin "inline", ie without a previous
+            // explicit declaration. If they have used the same plugin class
+            // before, we have already gone to the effort of creating a
+            // Declaration object, so retrieve it. If there is no existing
+            // declaration object for this class, then create one.
+
+            currDeclaration = pds.getDeclarationByClass(pluginClassName);
+
+            if (currDeclaration == null) {
+                currDeclaration = new Declaration(pluginClassName);
+                try {
+                    currDeclaration.init(context, pds);
+                } catch(PluginException pwe) {
+                    throw new PluginInvalidInputException(
+                        pwe.getMessage(), pwe.getCause());
+                }
+                pds.addDeclaration(currDeclaration);
+            }
+        } else if (pluginId != null) {
+            currDeclaration = pds.getDeclarationById(pluginId);
+
+            if (currDeclaration == null) {
+                throw new PluginInvalidInputException(
+                    "Plugin id [" + pluginId + "] is not defined.");
+            }
+        } else if (defaultPlugin != null) {
+            currDeclaration = defaultPlugin;
+        } else {
+            throw new PluginInvalidInputException(
+                "No plugin class specified for element " + path);
+        }
+
+        // get the class of the user plugged-in type
+        Class pluginClass = currDeclaration.getPluginClass();
+
+        // create a new RuleManager object and effectively push it onto a
+        // stack of rules objects. The stack is actually a linked list;
+        // using the PluginRuleManager constructor below causes the new
+        // instance to link to the previous head-of-stack, then calling
+        // context.setRules() makes the new instance the new head-of-stack.
+        RuleManager oldRuleManager = context.getRuleManager();
+        RuleManager delegateRuleManager = new DefaultRuleManager();
+        PluginRuleManager newRuleManager
+            = new PluginRuleManager(oldRuleManager, delegateRuleManager, path);
+        context.setRuleManager(newRuleManager);
+
+        if (debug) {
+            log.debug("PluginCreateRule.begin: installing new plugin: " +
+                "oldrules=" + oldRuleManager.toString() +
+                ", newrules=" + newRuleManager.toString());
+        }
+
+        // load up the custom rules
+        currDeclaration.configure(context);
+
+        // create an instance of the plugin class
+        try {
+            Object instance = pluginClass.newInstance();
+            context.push(instance);
+        } catch(InstantiationException ex) {
+            throw new ParseException(
+                "Unable to instantiate class [" + pluginClass.getName() + "]",
+                ex);
+        } catch(IllegalAccessException ex) {
+            throw new ParseException(
+                "Not permitted to instantiate class [" + pluginClass.getName() + "]",
+                ex);
+        }
+        
+        if (debug) {
+            log.debug(
+                "PluginCreateAction.begin" +
+                " match=[" + context.getMatchPath() + "]" +
+                " pushed instance of plugin [" + pluginClass.getName() + "]");
+        }
+
+        // and now we have to fire any custom rules which would have
+        // been matched by the same path that matched this rule, had
+        // they been loaded at that time.
+        List actions;
+        try {
+            actions = newRuleManager.getMatchingActions(path);
+        } catch(DigestionException ex) {
+            throw new ParseException(
+                "Unable to get matching actions from class " 
+                + this.getClass().getName(),
+                ex);
+        }
+        fireBeginMethods(context, actions, namespace, name, attributes);
+    }
+
+    /**
+     * Process the body text of this element.
+     *
+     * @param text The body text of this element
+     */
+    public void body(Context context, String namespace, String name, String text)
+    throws ParseException {
+
+        // While this class itself has no work to do in the body method,
+        // we do need to fire the body methods of all dynamically-added
+        // rules matching the same path as this rule. During begin, we had
+        // to manually execute the dynamic rules' begin methods because they
+        // didn't exist in the digester's Rules object when the match begin.
+        // So in order to ensure consistent ordering of rule execution, the
+        // PluginRules class deliberately avoids returning any such rules
+        // in later calls to the match method, instead relying on this
+        // object to execute them at the appropriate time.
+        //
+        // Note that this applies only to rules matching exactly the path
+        // which is also matched by this PluginCreateRule.
+
+        String path = context.getMatchPath();
+        RuleManager newRuleManager = context.getRuleManager();
+        List actions;
+        try {
+            actions = newRuleManager.getMatchingActions(context.getMatchPath());
+        } catch(DigestionException ex) {
+            throw new ParseException(
+                "Unable to get matching actions from class " 
+                + this.getClass().getName(),
+                ex);
+        }
+        fireBodyMethods(context, actions, namespace, name, text);
+    }
+
+    /**
+     * Invoked by the digester when the closing tag matching this Rule's
+     * pattern is encountered.
+     *
+     * @param namespace Description of the Parameter
+     * @param name Description of the Parameter
+     * @exception Exception Description of the Exception
+     *
+     * @see #begin
+     */
+    public void end(Context context, String namespace, String name)
+    throws ParseException {
+
+        // see body method for more info
+        String path = context.getMatchPath();
+        PluginRuleManager newRuleManager
+            = (PluginRuleManager) context.getRuleManager();
+
+        List actions;
+        try {
+            actions = newRuleManager.getMatchingActions(context.getMatchPath());
+        } catch(DigestionException ex) {
+            throw new ParseException(
+                "Unable to get matching actions from class " 
+                + this.getClass().getName(),
+                ex);
+        }
+        fireEndMethods(context, actions, namespace, name);
+
+        // pop the stack of PluginRuleManager instances, which
+        // discards all custom rules associated with this plugin
+        context.setRuleManager(newRuleManager.getParent());
+
+        // and get rid of the instance of the plugin class from the
+        // digester object stack.
+        context.pop();
+    }
+
+    /**
+     * Duplicate the processing that the Digester does when firing the
+     * begin methods of rules. It would be really nice if the Digester
+     * class provided a way for this functionality to just be invoked
+     * directly.
+     */
+    public void fireBeginMethods(
+    Context context,
+    List actions,
+    String namespace, String name,
+    org.xml.sax.Attributes list)
+    throws ParseException {
+        Log log = context.getLogger();
+        boolean debug = log.isDebugEnabled();
+        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, namespace, name, list);
+            } catch (PluginException e) {
+                throw e;
+            }
+        }
+    }
+
+    /**
+     * Duplicate the processing that the Digester does when firing the
+     * body methods of rules. It would be really nice if the Digester
+     * class provided a way for this functionality to just be invoked
+     * directly.
+     */
+    private void fireBodyMethods(
+    Context context,
+    List actions,
+    String namespaceURI, String name,
+    String text) throws ParseException {
+
+        if (actions.size() > 0) {
+            Log log = context.getLogger();
+            boolean debug = log.isDebugEnabled();
+            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, text);
+                } catch (ParseException e) {
+                    throw e;
+                }
+            }
+        }
+    }
+
+    /**
+     * Duplicate the processing that the Digester does when firing the
+     * end methods of rules. It would be really nice if the Digester
+     * class provided a way for this functionality to just be invoked
+     * directly.
+     */
+    public void fireEndMethods(
+    Context context,
+    List actions,
+    String namespaceURI, String name)
+    throws ParseException {
+
+        // Fire "end" events for all relevant rules in reverse order
+        Log log = context.getLogger();
+        boolean debug = log.isDebugEnabled();
+        for (int i = 0; i < actions.size(); i++) {
+            int j = (actions.size() - i) - 1;
+            try {
+                Action action = (Action) actions.get(j);
+                if (debug) {
+                    log.debug("  Fire end() for " + action);
+                }
+                action.end(context, namespaceURI, name);
+            } catch (ParseException e) {
+                throw e;
+            }
+        }
+    }
+
+    /**
+     * Determine what xml attributews are expected to be present on the matched
+     * xml element in order to tell us which plugin class to load.
+     */
+    private PluginAttrNames createPluginAttrNames(Context context) {
+        Log log = context.getLogger();
+        boolean debug = log.isDebugEnabled();
+
+        PluginConfiguration pc = PluginConfiguration.getInstance(context);
+
+        PluginAttrNames pluginAttrNames = new PluginAttrNames();
+
+        if (pluginClassAttr ==  null) {
+            // the user hasn't set explicit xml attr names on this action,
+            // so fetch the per-saxhandler default values
+            pluginAttrNames.pluginClassAttrNS = pc.getPluginClassAttrNS();
+            if (pluginAttrNames.pluginClassAttrNS == null) {
+                pluginAttrNames.pluginClassAttrNS = pc.DFLT_PLUGIN_CLASS_ATTR_NS;
+            }
+
+            pluginAttrNames.pluginClassAttr = pc.getPluginClassAttr();
+            if (pluginAttrNames.pluginClassAttr == null) {
+                pluginAttrNames.pluginClassAttr = pc.DFLT_PLUGIN_CLASS_ATTR;
+            }
+
+            if (debug) {
+                log.debug(
+                    "init: pluginClassAttr set to values ["
+                    + "ns=" + pluginAttrNames.pluginClassAttrNS
+                    + ", name=" + pluginAttrNames.pluginClassAttr + "]");
+            }
+        } else {
+            // ok, we use the values specified in setPluginClassAttribute method.
+            pluginAttrNames.pluginClassAttrNS = pluginClassAttrNS;
+            pluginAttrNames.pluginClassAttr = pluginClassAttr;
+            if (debug) {
+                log.debug(
+                    "init: pluginClassAttr set to action-specific values ["
+                    + "ns=" + pluginClassAttrNS
+                    + ", name=" + pluginClassAttr + "]");
+            }
+        }
+
+        // what xml attributes are expected to be present on the matched
+        // xml element in order to tell us which preceding plugin declaration
+        // to use?
+        if (pluginIdAttr ==  null) {
+            // the user hasn't set explicit xml attr names on this rule,
+            // so fetch the default values
+            pluginAttrNames.pluginIdAttrNS = pc.getPluginIdAttrNS();
+            if (pluginAttrNames.pluginIdAttrNS == null) {
+                pluginAttrNames.pluginIdAttrNS = pc.DFLT_PLUGIN_ID_ATTR_NS;
+            }
+
+            pluginAttrNames.pluginIdAttr = pc.getPluginIdAttr();
+            if (pluginAttrNames.pluginIdAttr == null) {
+                pluginAttrNames.pluginIdAttr = pc.DFLT_PLUGIN_ID_ATTR;
+            }
+
+            if (debug) {
+                log.debug(
+                    "init: pluginIdAttr set to values ["
+                    + "ns=" + pluginAttrNames.pluginIdAttrNS
+                    + ", name=" + pluginAttrNames.pluginIdAttr + "]");
+            }
+        } else {
+            // ok, we use the values specified in setPluginIdAttribute method.
+            pluginAttrNames.pluginIdAttrNS = pluginIdAttrNS;
+            pluginAttrNames.pluginIdAttr = pluginIdAttr;
+            if (debug) {
+                log.debug(
+                    "init: pluginIdAttr set to rule-specific values ["
+                    + "ns=" + pluginIdAttrNS
+                    + ", name=" + pluginIdAttr + "]");
+            }
+        }
+
+        return pluginAttrNames;
+    }
+}

Propchange: jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/PluginCreateAction.java
------------------------------------------------------------------------------
    svn:keywords = Id

Added: jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/PluginDeclarationAction.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/PluginDeclarationAction.java?view=auto&rev=154457
==============================================================================
--- jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/PluginDeclarationAction.java (added)
+++ jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/PluginDeclarationAction.java Sat Feb 19 16:37:55 2005
@@ -0,0 +1,141 @@
+/* $Id$
+ *
+ * Copyright 2003-2005 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.plugins;
+
+import java.util.Properties;
+
+import org.apache.commons.digester2.Action;
+import org.apache.commons.digester2.AbstractAction;
+import org.apache.commons.digester2.Context;
+import org.apache.commons.digester2.ParseException;
+import org.apache.commons.beanutils.MethodUtils;
+
+import org.apache.commons.logging.Log;
+
+/**
+ * A digester Action which allows the user to pre-declare a class which is to
+ * be referenced later at a plugin point by a PluginCreateAction.
+ * <p>
+ * Normally, a PluginDeclarationAction is added to a Digester instance with
+ * the pattern "/{root}/plugin" or "plugin" where {root} is the name of 
+ * the root tag in the input document.
+ */
+
+public class PluginDeclarationAction extends AbstractAction {
+
+    //------------------- constructors ---------------------------------------
+
+    /** constructor  */
+    public PluginDeclarationAction() {
+    }
+
+    //------------------- methods --------------------------------------------
+
+    /**
+     * Invoked upon reading a tag defining a plugin declaration. The tag
+     * must have the following mandatory attributes:
+     * <ul>
+     *   <li> id </li>
+     *   <li> class </li>
+     * </ul>
+     *
+     *@param namespace The xml namespace in which the xml element which
+     * triggered this rule resides.
+     *@param name The name of the xml element which triggered this rule.
+     *@param attributes The set of attributes on the xml element which
+     * triggered this rule.
+     *@exception java.lang.Exception
+     */
+
+    public void begin(
+    Context context, 
+    String namespace, String name,
+    org.xml.sax.Attributes attributes)
+    throws ParseException {
+
+        // copy all the attribute values into a properties object so that
+        // the plugin finder strategies can access the properties later.
+        int nAttrs = attributes.getLength();
+        Properties props = new Properties();
+        for(int i=0; i<nAttrs; ++i) {
+            String key = attributes.getLocalName(i);
+            if ((key == null) || (key.length() == 0)) {
+                key = attributes.getQName(i);
+            }
+            String value = attributes.getValue(i);
+            props.setProperty(key, value);
+        }
+        
+        try {
+            declarePlugin(context, props);
+        } catch(PluginInvalidInputException ex) {
+            throw new PluginInvalidInputException(
+                "Error on element [" + context.getMatchPath() + 
+                "]: " + ex.getMessage());
+        }
+    }
+    
+    /**
+     * Creates a Declaration object to represent this implicit or explicit
+     * declaration of a plugin, and store it away in the PluginManager object
+     * associated with the current context so it can be retrieved later when
+     * a PluginCreateAction fires.
+     * <p>
+     * Note that this (static) method is called directly from the
+     * PluginCreateAction class if an "inline declaration" is found, ie where
+     * the xml element that triggers the PluginCreateAction also provides the
+     * necessary declaration information.
+     */
+    public static void declarePlugin(Context context, Properties props)
+    throws PluginException {
+        
+        Log log = context.getLogger();
+        boolean debug = log.isDebugEnabled();
+        
+//        PluginConfiguration pc = PluginConfiguration.getInstance(context);
+
+        String id = props.getProperty("id");
+        String pluginClassName = props.getProperty("class");
+        
+        if (id == null) {
+            throw new PluginInvalidInputException(
+                "mandatory attribute id not present on plugin declaration");
+        }
+
+        if (pluginClassName == null) {
+            throw new PluginInvalidInputException(
+                "mandatory attribute class not present on plugin declaration");
+        }
+
+        Declaration newDecl = new Declaration(pluginClassName);
+        newDecl.setId(id);
+        newDecl.setProperties(props);
+
+        PluginDeclarationScope pds = PluginDeclarationScope.getInstance(context); 
+        
+        // Create a RuleLoader instance which is capable of adding dynamic
+        // rules for the plugged-in class to a RuleManager instance. The 
+        // dynamic rules don't get added until a PluginCreateAction fires,
+        // so redeclaring a plugin multiple times is fine...
+        newDecl.init(context, pds);
+        
+        // and remember it for later...
+        pds.addDeclaration(newDecl);
+    }
+}
+

Propchange: jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/PluginDeclarationAction.java
------------------------------------------------------------------------------
    svn:keywords = Id

Added: jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/PluginDeclarationScope.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/PluginDeclarationScope.java?view=auto&rev=154457
==============================================================================
--- jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/PluginDeclarationScope.java (added)
+++ jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/PluginDeclarationScope.java Sat Feb 19 16:37:55 2005
@@ -0,0 +1,248 @@
+/* $Id$
+ *
+ * Copyright 2003-2005 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.plugins;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Properties;
+import java.util.Iterator;
+
+import org.apache.commons.digester2.Context;
+
+import org.apache.commons.logging.Log;
+
+/**
+ * Provides a location to store plugin declarations.
+ * <p>
+ * Plugin declarations are created by either:
+ * <ul>
+ * <li> PluginCreateAction from method startParse time, in the case where a 
+ *  plugin has a default plugin class.
+ * <li> PluginCreateAction from method begin, in the case where the input
+ *  xml just declares the plugin class on the matching tag, eg
+ *  [widget plugin-class="com.acme.widget" ...]
+ * <li> PluginDeclarationAction, in the case where the input xml pre-declares
+ *  the plugin class, eg [plugin-declaration id="..." class=".." ..../]
+ * </ul>
+ * <p>
+ * When plugin declarations are encountered in the input xml, they
+ * should "shadow" (temporarily override) declarations made within
+ * parent elements. And when the element within which the declaration
+ * occurred ends, all information about those declarations should be
+ * discarded, causing:
+ * <ol>
+ * <li>any references to those declarations after they have gone out
+ *  of scope to be errors, and
+ * <li>any declarations previously "shadowed" to be "unshadowed"
+ * </ol>
+ * <p>
+ * This is implemented by having a stack of instances of this class. When
+ * declarations occur in a new scope, a new instance of this class is pushed
+ * onto the corresponding stack. Lookups for declarations are always performed
+ * on the top object on the stack, so the most "recent" declarations are seen.
+ * If a declaration is not found on the top object, it delegates the lookup to
+ * the previous object, etc. And when a declaration scope ends, the stack is
+ * popped, thereby discarding the out-of-scope plugin declarations.
+ * <p>
+ * Note that there is a problem with the current implementation of declaration
+ * scoping. The problem is not in this class, but in the code which decides 
+ * when to push and pop new instances of this class. Simply, declaration scopes
+ * should last until the end of the parent xml element containing the 
+ * declaration (not the end of the declaration element itself) - but Digester 
+ * doesn't provide any hooks to allow detection of the end of the parent 
+ * element. The current solution, therefore, is to regard a scope as starting 
+ * when a PluginCreateAction fires. This does at least separate the scope of 
+ * declarations within a plugin from those "above" the plugin point which is 
+ * the most important issue.
+ * <p>
+ * At some future time, if digester provides the facility to perform actions 
+ * associated with "the parent tag", then the PluginDeclarationScope stack
+ * can be decoupled from the firing of PluginCreateRule instances. 
+ */
+
+public class PluginDeclarationScope {
+
+    public static final Context.ItemId PLUGIN_DECL_SCOPE
+        = new Context.ItemId(PluginDeclarationScope.class, "PluginDecls");
+        
+    /** Map of classname->Declaration */
+    private HashMap declarationsByClass = new HashMap();
+
+    /** Map of id->Declaration  */
+    private HashMap declarationsById = new HashMap();
+
+    /** the parent manager to which this one may delegate lookups. */
+    private PluginDeclarationScope parent;
+    
+    // ---------------------------------------------------------------------
+    // Static Methods
+    // ---------------------------------------------------------------------
+
+    /**
+     * Extract the current (top) PluginDeclarationScope object from the 
+     * provided context. If one hasn't been created yet, then do so.
+     * <p>
+     * This is similar to a singleton method, except that there is a
+     * PluginDeclarationScope per context.
+     * <p>
+     * Note that instead of using a "scratch stack" in the context, we
+     * just use a "scratch item" which returns the most-recent
+     * PluginDeclarationScope. As each of these objects contains a reference
+     * to its "parent" scope, this is effectively a linked-list-stack.
+     */
+    public static PluginDeclarationScope getInstance(Context context) {
+        PluginDeclarationScope pds = 
+            (PluginDeclarationScope) context.getItem(PLUGIN_DECL_SCOPE);
+        if (pds == null) {
+            pds = new PluginDeclarationScope();
+            context.putItem(PLUGIN_DECL_SCOPE, pds);
+        }
+
+        return pds;
+    }
+    
+    //------------------- constructors ---------------------------------------
+
+    /** Construct a "root" PluginDeclarationScope, ie one with no parent. */
+    public PluginDeclarationScope() {
+    }
+
+    /** 
+     * Construct a "child" PluginDeclarationScope. When declarations are added 
+     * to a "child", they are stored within the child and do not modify the
+     * parent, so when the child goes out of scope, those declarations
+     * disappear. When asking a "child" to retrieve a declaration, it 
+     * delegates the search to its parent if it does not hold a matching
+     * entry itself.
+     * <p>
+     * @param parent must be non-null.
+     */
+    public PluginDeclarationScope(PluginDeclarationScope parent) {
+        this.parent = parent;
+    }
+    
+    //------------------- methods --------------------------------------------
+
+    /**
+     * Add the declaration to the set of known declarations.
+     * <p>
+     * TODO: somehow get a reference to a Digester object
+     * so that we can really log here. Currently, all
+     * logging is disabled from this method.
+     *
+     *@param decl an object representing a plugin class.
+     */
+    public void addDeclaration(Declaration decl) {
+        Log log = LogUtils.getLogger(null);
+        boolean debug = log.isDebugEnabled();
+        
+        Class pluginClass = decl.getPluginClass();
+        String id = decl.getId();
+        
+        declarationsByClass.put(pluginClass.getName(), decl);
+            
+        if (id != null) {
+            declarationsById.put(id, decl);
+            if (debug) {
+                log.debug(
+                    "Indexing plugin-id [" + id + "]" +
+                    " -> class [" + pluginClass.getName() + "]");
+            }
+        }
+    }
+
+    /**
+     * Return the declaration object with the specified class.
+     * If no such plugin is known, null is returned.
+     */
+    public Declaration getDeclarationByClass(String className) {
+        Declaration decl = 
+            (Declaration) declarationsByClass.get(className);
+            
+        if ((decl == null) && (parent != null)) {
+            decl = parent.getDeclarationByClass(className);
+        }
+
+        return decl;
+    }
+
+    /**
+     * Return the declaration object with the specified id.
+     * If no such plugin is known, null is returned.
+     *
+     *@param id Description of the Parameter
+     *@return The declaration value
+     */
+    public Declaration getDeclarationById(String id) {
+        Declaration decl = (Declaration) declarationsById.get(id);
+
+        if ((decl == null) && (parent != null)) {
+            decl = parent.getDeclarationById(id);
+        }
+
+        return decl;
+    }
+
+    /**
+     * Given a plugin class and some associated properties, scan the
+     * list of known RuleFinder instances until one detects a source of
+     * custom rules for this plugin (aka a RuleLoader).
+     * <p>
+     * If no source of custom rules can be found, null is returned.
+     */
+    public RuleLoader findLoader(
+    Context context, String id, 
+    Class pluginClass, Properties props) 
+    throws PluginException {    
+
+        // iterate over the list of RuleFinders, trying each one 
+        // until one of them locates a source of dynamic rules given
+        // this specific plugin class and the associated declaration 
+        // properties.
+        Log log = LogUtils.getLogger(context);
+        boolean debug = log.isDebugEnabled();
+        log.debug("scanning ruleFinders to locate loader..");
+        
+        PluginConfiguration pluginConfig = 
+            PluginConfiguration.getInstance(context.getSAXHandler());
+
+        List ruleFinders = pluginConfig.getRuleFinders();
+        RuleLoader ruleLoader = null;
+        try {
+            for(Iterator i = ruleFinders.iterator(); 
+                i.hasNext() && ruleLoader == null; ) {
+                    
+                RuleFinder finder = (RuleFinder) i.next();
+                if (debug) {
+                    log.debug("checking finder of type " + finder.getClass().getName());
+                }
+                ruleLoader = finder.findLoader(context, pluginClass, props);
+            }
+        }
+        catch(PluginException e) {
+            throw new PluginException(
+                "Unable to locate plugin rules for plugin"
+                + " with id [" + id + "]"
+                + ", and class [" + pluginClass.getName() + "]"
+                + ":" + e.getMessage(), e.getCause());
+        }
+        log.debug("scanned ruleFinders.");
+        
+        return ruleLoader;
+    }
+}

Propchange: jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/PluginDeclarationScope.java
------------------------------------------------------------------------------
    svn:keywords = Id

Added: jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/PluginException.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/PluginException.java?view=auto&rev=154457
==============================================================================
--- jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/PluginException.java (added)
+++ jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/PluginException.java Sat Feb 19 16:37:55 2005
@@ -0,0 +1,49 @@
+/* $Id$
+ *
+ * Copyright 2005 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.plugins;
+
+import org.apache.commons.digester2.ParseException;
+
+/**
+ * Thrown when some plugin-related error has occurred, and none of the
+ * other exception types are appropriate.
+ */
+
+public class PluginException extends ParseException {
+    /**
+     * @param cause underlying exception that caused this to be thrown
+     */
+    public PluginException(Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * @param msg describes the reason this exception is being thrown.
+     */
+    public PluginException(String msg) {
+        super(msg);
+    }
+
+    /**
+     * @param msg describes the reason this exception is being thrown.
+     * @param cause underlying exception that caused this to be thrown
+     */
+    public PluginException(String msg, Throwable cause) {
+        super(msg, cause);
+    }
+}

Propchange: jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/PluginException.java
------------------------------------------------------------------------------
    svn:keywords = Id

Added: jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/PluginInvalidInputException.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/PluginInvalidInputException.java?view=auto&rev=154457
==============================================================================
--- jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/PluginInvalidInputException.java (added)
+++ jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/PluginInvalidInputException.java Sat Feb 19 16:37:55 2005
@@ -0,0 +1,46 @@
+/* $Id$
+ *
+ * Copyright 2003-2005 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.plugins;
+
+/**
+ * Thrown when an error occurs due to bad data in the file being parsed.
+ */
+public class PluginInvalidInputException extends PluginException {
+
+    /**
+     * @param cause underlying exception that caused this to be thrown
+     */
+    public PluginInvalidInputException(Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * @param msg describes the reason this exception is being thrown.
+     */
+    public PluginInvalidInputException(String msg) {
+        super(msg);
+    }
+
+    /**
+     * @param msg describes the reason this exception is being thrown.
+     * @param cause underlying exception that caused this to be thrown
+     */
+    public PluginInvalidInputException(String msg, Throwable cause) {
+        super(msg, cause);
+    }
+}

Propchange: jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/PluginInvalidInputException.java
------------------------------------------------------------------------------
    svn:keywords = Id

Added: jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/PluginRuleManager.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/PluginRuleManager.java?view=auto&rev=154457
==============================================================================
--- jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/PluginRuleManager.java (added)
+++ jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/PluginRuleManager.java Sat Feb 19 16:37:55 2005
@@ -0,0 +1,285 @@
+/* $Id$
+ *
+ * Copyright 2003-2005 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.plugins;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+
+import java.util.Map;
+import java.util.HashMap;
+import java.util.TreeMap;
+import java.util.Comparator;
+
+import org.apache.commons.digester2.Context;
+import org.apache.commons.digester2.Action;
+import org.apache.commons.digester2.RuleManager;
+import org.apache.commons.digester2.AbstractRuleManager;
+import org.apache.commons.digester2.DigestionException;
+import org.apache.commons.digester2.InvalidRuleException;
+import org.apache.commons.logging.Log;
+
+/**
+ * A custom digester RuleManager which contains its own rules, but can also
+ * return rules from a "parent" RuleManager.
+ * <p>
+ * Plugged-in classes have custom parsing rules associated with them. These
+ * rules need to be added to the existing set of rules during the scope of
+ * the plugged-in class, and then discarded when the plugged-in class goes
+ * out of scope.
+ * <p>
+ * In order to implement this behaviour, the following occurs when a 
+ * PluginCreateAction's begin method fires:
+ * <ul>
+ *  <li>An instance of this class is created
+ *  <li>Any custom rules associated with the plugged-in class are added to the
+ *    new instance PluginRuleManager instance
+ *  <li>The new PluginRuleManager instance is made the "current" RuleManager.
+ * </li>
+ * <p>
+ * As the SAXHandler continues parsing of the xml within the plugged-in class,
+ * any calls it makes to the current RuleManager cause the custom rules to be
+ * returned.
+ * <p>
+ * When the PluginCreateAction's end method fires, the previous RuleManager
+ * is restored, effectively discarding those custom rules that are no longer
+ * relevant.
+ * <p>
+ * Rather than implement RuleManager functionality directly, this class
+ * holds a reference to a "real" rulemanager, and just forwards any calls
+ * to that delegate. This avoids having to implement full RuleManager
+ * functionality here. More importantly, it allows an instance of this
+ * class to support various RuleManager matching behaviours, simply by having
+ * the delegate rulemanager be of the appropriate class.
+ */
+
+public class PluginRuleManager extends AbstractRuleManager {
+                                               
+    /** 
+     * The rulemanager implementation that holds rules defined before the
+     * PluginRuleManager was created. This object might be a PluginRuleManager
+     * itself, in which case it has a parentRuleManager, etc. Eventually the
+     * chain will lead back to the original rule manager associated with the
+     * SAXHandler.
+     */
+    private RuleManager parentRuleManager;
+    
+    /** 
+     * The rulemanager implementation that we are "enhancing" with plugins
+     * functionality, as per the Decorator pattern. This is never a 
+     * PluginRuleManager, but rather an instance that implements whatever
+     * matching behaviour the plugged-in class wants for its custom rules.
+     */
+    private RuleManager delegateRuleManager;
+    
+    /**
+     * The path below which this rulemanager object has responsibility.
+     * For paths shorter than or equal the mountpoint, the parent's 
+     * match is called.
+     */
+    private String mountPoint;
+    
+    // ------------------------------------------------------------- 
+    // Constructor
+    // ------------------------------------------------------------- 
+    
+    /**
+     * Constructs an instance with the specified parent RuleManager.
+     * <p>
+     * One of these is created each time a PluginCreateAction's begin method 
+     * fires, in order to manage the custom rules associated with whatever 
+     * concrete plugin class the user has specified.
+     *
+     * @param parent must be non-null, and is expected to be the value returned
+     *  by context.getRuleManager().
+     *
+     * @param delegate must be non-null, and is expected to be some new object
+     *  which implements RuleManager, eg a DefaultRuleManager.
+     *  
+     * @param mountPoint is the digester match path for the element 
+     * matching a PluginCreateRule which caused this "nested parsing scope"
+     * to begin. This is expected to be equal to context.getMatchPath().
+     */
+     PluginRuleManager(
+     RuleManager parent, 
+     RuleManager delegate,
+     String mountPoint)
+     throws PluginException {
+        this.parentRuleManager = parent;
+        this.delegateRuleManager = delegate;
+        this.mountPoint = mountPoint;
+    }
+    
+    // --------------------------------------------------------- 
+    // Properties
+    // --------------------------------------------------------- 
+
+    /**
+     * Return the rulemanager that was current before this one took over.
+     */
+    public RuleManager getParent() {
+        return parentRuleManager;
+    }
+    
+    // --------------------------------------------------------- 
+    // AbstractRuleManager methods
+    // --------------------------------------------------------- 
+
+    /**
+     * Always throws UnsupportedOperationException; there should never be
+     * any reason to copy one of these objects.
+     */
+     public RuleManager copy() {
+         // this method should never be called
+         throw new UnsupportedOperationException(
+            "PluginRuleManager.copy is not supported.");
+     }
+
+    /**
+     * Invokes the startParse method on any rules that have been added
+     * specifically to this RuleManager.
+     * <p>
+     * This method should be called after custom rules have been added to
+     * this object, so that the actions get the expected lifecycle called.
+     */
+    public void startParse(Context context) throws DigestionException {
+        delegateRuleManager.startParse(context);
+
+        // We deliberately don't call parent.startParse, as the actions within
+        // that rulemanager should already have had their startParse methods
+        // invoked.
+    }
+
+    /**
+     * Invokes the finishParse method on any rules that have been added
+     * specifically to this RuleManager.
+     * <p>
+     * This method should be called just before this instance is discarded,
+     * so that the actions get the expected lifecycle called. The endParse
+     * method is <i>not</i> invoked on the parent RuleManager, as the actions
+     * stored within it are still in use; their endParse methods will be
+     * called later.
+     */
+    public void finishParse(Context context) throws DigestionException {
+        delegateRuleManager.finishParse(context);
+
+        // We deliberately don't call parent.startParse, as the actions within
+        // that rulemanager should already have had their startParse methods
+        // invoked.
+     }
+
+    /** See {@link RuleManager#addFallbackAction}. */
+    public void addFallbackAction(Action action) {
+        delegateRuleManager.addFallbackAction(action);
+    }
+
+    /** See {@link RuleManager#addFallbackActions}. */
+    public void addFallbackActions(List actions) {
+        delegateRuleManager.addFallbackActions(actions);
+    }
+
+    /** See {@link RuleManager#addMandatoryAction}. */
+    public void addMandatoryAction(Action action) {
+        delegateRuleManager.addMandatoryAction(action);
+    }
+
+    /** See {@link RuleManager#addMandatoryActions}. */
+    public void addMandatoryActions(List actions) {
+       delegateRuleManager.addMandatoryActions(actions);
+    }
+
+    /** See {@link RuleManager#addNamespace}. */
+    public void addNamespace(String prefix, String uri) {
+       delegateRuleManager.addNamespace(prefix, uri);
+    }
+
+    /**
+     * Add a custom rule.
+     * <p>
+     * If the pattern starts with a forward-slash then the current mountpoint
+     * is automatically prepended to the pattern so that custom rules can add
+     * absolute patterns without caring where they are mounted within the 
+     * input document.
+     * <p>
+     * If the pattern is null, then the pattern is set to the current mountpoint,
+     * so that actions can be triggered on the same element which caused the
+     * plugin class to be created, without needing to know the mountpoint
+     * from the code that adds the custom rule.
+     * <p>
+     * Note that this does hard-wire an assumption that the concrete
+     * RuleManager this instance is delegating to accepts the "canonical path"
+     * as a valid pattern prefix, and treats paths starting with a leading
+     * slash as absolute. 
+     *
+     * See {@link RuleManager#addRule}.
+     */
+    public void addRule(String pattern, Action action)
+    throws InvalidRuleException {
+        if (pattern == null) {
+            pattern = mountPoint;
+        } else if (pattern.startsWith("/")) {
+            pattern = mountPoint + pattern;
+        }
+        delegateRuleManager.addRule(pattern, action);
+    }
+
+    /**
+     * This method always throws an exception. It is not obvious whether this
+     * method should return just the actions registered with it, or include
+     * the actions registered with the parent rulemanager too. And in any case
+     * it is not expected that this method will ever need to be invoked on a
+     * instance of this class, so it's safer to disallow this operation.
+     */
+    public List getActions() {
+        throw new UnsupportedOperationException(
+            "PluginRuleManager.getActions is not supported");
+    }
+
+    /**
+     * If the path specified is below the mount-point for this rulemanager,
+     * then only actions that were added to this rulemanager are returned,
+     * as the custom rules for a plugged-in class is expected to be 
+     * "stand-alone".
+     * <p>
+     * In other words, once a plugin has been entered, only custom rules
+     * associated with that action are returned, even if the parent rulemanager
+     * has some matching rules. This includes fallback and mandatory actions.
+     * <p>
+     *
+     * @param path is a string of form
+     * <pre>/{namespace}elementName/{namespace}elementName"</pre>
+     * identifying the path from the root of the input document to the element
+     * for which the caller wants the set of matching Action objects. If an
+     * element has no namespace, then the {} part is omitted.
+     */
+    public List getMatchingActions(String path) throws DigestionException {
+        // As we don't have access to a context object here, we have to do
+        // the following which effectively disables logging. Damn, we need a
+        // better approach to logging...
+        Log log = LogUtils.getLogger(null);
+        boolean debug = log.isDebugEnabled();
+        
+        if (debug) {
+            log.debug(
+                "Matching path [" + path +
+                "] on rulemanager object " + this.toString());
+        }
+
+        return delegateRuleManager.getMatchingActions(path);
+    }
+}

Propchange: jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/plugins/PluginRuleManager.java
------------------------------------------------------------------------------
    svn:keywords = Id



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