This is an automated email from the ASF dual-hosted git repository. ggregory pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-digester.git
The following commit(s) were added to refs/heads/master by this push: new 5a01544e Normalize Javadoc spelling 5a01544e is described below commit 5a01544eea636989acf27d9214bc855ce65c1f81 Author: Gary Gregory <ggreg...@rocketsoftware.com> AuthorDate: Wed Jun 15 08:22:59 2022 -0400 Normalize Javadoc spelling --- .../digester3/plugins/InitializableRule.java | 78 +- .../digester3/plugins/PluginCreateRule.java | 1266 ++++++++++---------- .../commons/digester3/plugins/PluginRules.java | 944 +++++++-------- 3 files changed, 1144 insertions(+), 1144 deletions(-) diff --git a/core/src/main/java/org/apache/commons/digester3/plugins/InitializableRule.java b/core/src/main/java/org/apache/commons/digester3/plugins/InitializableRule.java index fda1ddb5..ad78afc4 100644 --- a/core/src/main/java/org/apache/commons/digester3/plugins/InitializableRule.java +++ b/core/src/main/java/org/apache/commons/digester3/plugins/InitializableRule.java @@ -1,39 +1,39 @@ -package org.apache.commons.digester3.plugins; - -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/** - * Defines an interface that a Rule class can implement if it wishes to get an initialisation callback after the rule - * has been added to the set of Rules within a PluginRules instance. - * - * @since 1.6 - */ -public interface InitializableRule -{ - - /** - * Called after this Rule object has been added to the list of all Rules. Note that if a single InitializableRule - * instance is associated with more than one pattern, then this method will be called more than once. - * - * @param pattern is the digester match pattern that will trigger this rule. - */ - void postRegisterInit( String pattern ); - -} +package org.apache.commons.digester3.plugins; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * Defines an interface that a Rule class can implement if it wishes to get an initialization callback after the rule + * has been added to the set of Rules within a PluginRules instance. + * + * @since 1.6 + */ +public interface InitializableRule +{ + + /** + * Called after this Rule object has been added to the list of all Rules. Note that if a single InitializableRule + * instance is associated with more than one pattern, then this method will be called more than once. + * + * @param pattern is the digester match pattern that will trigger this rule. + */ + void postRegisterInit( String pattern ); + +} diff --git a/core/src/main/java/org/apache/commons/digester3/plugins/PluginCreateRule.java b/core/src/main/java/org/apache/commons/digester3/plugins/PluginCreateRule.java index 599943d4..72bcc4cc 100644 --- a/core/src/main/java/org/apache/commons/digester3/plugins/PluginCreateRule.java +++ b/core/src/main/java/org/apache/commons/digester3/plugins/PluginCreateRule.java @@ -1,633 +1,633 @@ -package org.apache.commons.digester3.plugins; - -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import java.util.List; - -import org.apache.commons.digester3.Rule; -import org.apache.commons.logging.Log; -import org.xml.sax.Attributes; - -/** - * 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. - * - * @since 1.6 - */ -public class PluginCreateRule - extends Rule - implements InitializableRule -{ - - // see setPluginClassAttribute - private String pluginClassAttrNs = null; - - private String pluginClassAttr = null; - - // see setPluginIdAttribute - private String pluginIdAttrNs = null; - - private String pluginIdAttr = null; - - /** - * In order to invoke the addRules method on the plugin class correctly, we need to know the pattern which this rule - * is matched by. - */ - private String pattern; - - /** 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; - - /** - * Currently, none of the Rules methods allow exceptions to be thrown. Therefore if this class cannot initialize - * itself properly, it cannot cause the digester to stop. Instead, we cache the exception and throw it the first - * time the begin() method is called. - */ - private PluginConfigurationException initException; - - // -------------------- 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 PluginCreateRule( final 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 PluginCreateRule( final Class<?> baseClass, final 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 PluginCreateRule( final Class<?> baseClass, final Class<?> dfltPluginClass, final 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. - * - * @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( final String namespaceUri, final 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. - * - * @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( final String namespaceUri, final String attrName ) - { - pluginIdAttrNs = namespaceUri; - pluginIdAttr = attrName; - } - - // ------------------- methods -------------------------------------------- - - /** - * {@inheritDoc} - */ - @Override - public void postRegisterInit( final String matchPattern ) - { - final Log log = LogUtils.getLogger( getDigester() ); - final boolean debug = log.isDebugEnabled(); - if ( debug ) - { - log.debug( "PluginCreateRule.postRegisterInit" + ": rule registered for pattern [" + matchPattern + "]" ); - } - - if ( getDigester() == null ) - { - // We require setDigester to be called before this method. - // Note that this means that PluginCreateRule cannot be added - // to a Rules object which has not yet been added to a - // Digester object. - initException = - new PluginConfigurationException( "Invalid invocation of postRegisterInit" + ": digester not set." ); - throw initException; - } - - if ( pattern != null ) - { - // We have been called twice, ie a single instance has been - // associated with multiple patterns. - // - // Generally, Digester Rule instances can be associated with - // multiple patterns. However for plugins, this creates some - // complications. Some day this may be supported; however for - // now we just reject this situation. - initException = - new PluginConfigurationException( "A single PluginCreateRule instance has been mapped to" - + " multiple patterns; this is not supported." ); - throw initException; - } - - if ( matchPattern.indexOf( '*' ) != -1 ) - { - // having wildcards in patterns is extremely difficult to - // deal with. For now, we refuse to allow this. - // - // TODO: check for any chars not valid in xml element name - // rather than just *. - // - // Reasons include: - // (a) handling recursive plugins, and - // (b) determining whether one pattern is "below" another, - // as done by PluginRules. Without wildcards, "below" - // just means startsWith, which is easy to check. - initException = - new PluginConfigurationException( "A PluginCreateRule instance has been mapped to" + " pattern [" - + matchPattern + "]." + " This pattern includes a wildcard character." - + " This is not supported by the plugin architecture." ); - throw initException; - } - - if ( baseClass == null ) - { - baseClass = Object.class; - } - - final PluginRules rules = (PluginRules) getDigester().getRules(); - final PluginManager pm = rules.getPluginManager(); - - // check default class is valid - if ( defaultPlugin != null ) - { - if ( !baseClass.isAssignableFrom( defaultPlugin.getPluginClass() ) ) - { - initException = - new PluginConfigurationException( "Default class [" + defaultPlugin.getPluginClass().getName() - + "] does not inherit from [" + baseClass.getName() + "]." ); - throw initException; - } - - try - { - defaultPlugin.init( getDigester(), pm ); - - } - catch ( final PluginException pwe ) - { - - throw new PluginConfigurationException( pwe.getMessage(), pwe.getCause() ); - } - } - - // remember the pattern for later - pattern = matchPattern; - - if ( pluginClassAttr == null ) - { - // the user hasn't set explicit xml attr names on this rule, - // so fetch the default values - pluginClassAttrNs = rules.getPluginClassAttrNs(); - pluginClassAttr = rules.getPluginClassAttr(); - - if ( debug ) - { - log.debug( "init: pluginClassAttr set to per-digester values [" + "ns=" + pluginClassAttrNs + ", name=" - + pluginClassAttr + "]" ); - } - } - else - { - if ( debug ) - { - log.debug( "init: pluginClassAttr set to rule-specific values [" + "ns=" + pluginClassAttrNs - + ", name=" + pluginClassAttr + "]" ); - } - } - - if ( pluginIdAttr == null ) - { - // the user hasn't set explicit xml attr names on this rule, - // so fetch the default values - pluginIdAttrNs = rules.getPluginIdAttrNs(); - pluginIdAttr = rules.getPluginIdAttr(); - - if ( debug ) - { - log.debug( "init: pluginIdAttr set to per-digester values [" + "ns=" + pluginIdAttrNs + ", name=" - + pluginIdAttr + "]" ); - } - } - else - { - if ( debug ) - { - log.debug( "init: pluginIdAttr set to rule-specific values [" + "ns=" + pluginIdAttrNs + ", name=" - + pluginIdAttr + "]" ); - } - } - } - - /** - * 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" PluginRules object is - * then created and set as the digester's default Rules object. Any custom rules associated with the plugin class - * are then loaded into that new Rules object. Finally, any custom rules that are associated with the current - * pattern (such as SetPropertiesRules) have their begin methods executed. - * - * @param namespace the namespace URI of the matching element, or an empty string if the parser is not namespace - * aware or the element has no namespace - * @param name the local name if the parser is namespace aware, or just the element name otherwise - * @param attributes The attribute list of this element - * @throws Exception if any error occurs - */ - @Override - public void begin( final String namespace, final String name, final org.xml.sax.Attributes attributes ) - throws Exception - { - final Log log = getDigester().getLogger(); - final boolean debug = log.isDebugEnabled(); - if ( debug ) - { - log.debug( "PluginCreateRule.begin" + ": pattern=[" + pattern + "]" + " match=[" + getDigester().getMatch() - + "]" ); - } - - if ( initException != null ) - { - // we had a problem during initialisation that we could - // not report then; report it now. - throw initException; - } - - // load any custom rules associated with the plugin - final PluginRules oldRules = (PluginRules) getDigester().getRules(); - final PluginManager pluginManager = oldRules.getPluginManager(); - Declaration currDeclaration = null; - - String pluginClassName; - if ( pluginClassAttrNs == null ) - { - // Yep, this is ugly. - // - // In a namespace-aware parser, the one-param version will - // return attributes with no namespace. - // - // In a non-namespace-aware parser, the two-param version will - // never return any attributes, ever. - pluginClassName = attributes.getValue( pluginClassAttr ); - } - else - { - pluginClassName = attributes.getValue( pluginClassAttrNs, pluginClassAttr ); - } - - String pluginId; - if ( pluginIdAttrNs == null ) - { - pluginId = attributes.getValue( pluginIdAttr ); - } - else - { - pluginId = attributes.getValue( pluginIdAttrNs, 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 = pluginManager.getDeclarationByClass( pluginClassName ); - - if ( currDeclaration == null ) - { - currDeclaration = new Declaration( pluginClassName ); - try - { - currDeclaration.init( getDigester(), pluginManager ); - } - catch ( final PluginException pwe ) - { - throw new PluginInvalidInputException( pwe.getMessage(), pwe.getCause() ); - } - pluginManager.addDeclaration( currDeclaration ); - } - } - else if ( pluginId != null ) - { - currDeclaration = pluginManager.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 " + pattern ); - } - - // get the class of the user plugged-in type - final Class<?> pluginClass = currDeclaration.getPluginClass(); - - final String path = getDigester().getMatch(); - - // create a new Rules object and effectively push it onto a stack of - // rules objects. The stack is actually a linked list; using the - // PluginRules constructor below causes the new instance to link - // to the previous head-of-stack, then the Digester.setRules() makes - // the new instance the new head-of-stack. - final PluginRules newRules = new PluginRules( getDigester(), path, oldRules, pluginClass ); - getDigester().setRules( newRules ); - - if ( debug ) - { - log.debug( "PluginCreateRule.begin: installing new plugin: " + "oldrules=" + oldRules.toString() - + ", newrules=" + newRules.toString() ); - } - - // load up the custom rules - currDeclaration.configure( getDigester(), pattern ); - - // create an instance of the plugin class - final Object instance = pluginClass.newInstance(); - getDigester().push( instance ); - if ( debug ) - { - log.debug( "PluginCreateRule.begin" + ": pattern=[" + pattern + "]" + " match=[" + getDigester().getMatch() - + "]" + " 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. - final List<Rule> rules = newRules.getDecoratedRules().match( namespace, path, name, attributes ); - fireBeginMethods( rules, namespace, name, attributes ); - } - - /** - * {@inheritDoc} - */ - @Override - public void body( final String namespace, final String name, final String text ) - throws Exception - { - - // 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. - - final String path = getDigester().getMatch(); - final PluginRules newRules = (PluginRules) getDigester().getRules(); - final List<Rule> rules = newRules.getDecoratedRules().match( namespace, path, name, null ); - fireBodyMethods( rules, namespace, name, text ); - } - - /** - * {@inheritDoc} - */ - @Override - public void end( final String namespace, final String name ) - throws Exception - { - // see body method for more info - final String path = getDigester().getMatch(); - final PluginRules newRules = (PluginRules) getDigester().getRules(); - final List<Rule> rules = newRules.getDecoratedRules().match( namespace, path, name, null ); - fireEndMethods( rules, namespace, name ); - - // pop the stack of PluginRules instances, which - // discards all custom rules associated with this plugin - getDigester().setRules( newRules.getParent() ); - - // and get rid of the instance of the plugin class from the - // digester object stack. - getDigester().pop(); - } - - /** - * Return the pattern that this Rule is associated with. - * <p> - * In general, Rule instances <i>can</i> be associated with multiple patterns. A PluginCreateRule, however, will - * only function correctly when associated with a single pattern. It is possible to fix this, but I can't be - * bothered just now because this feature is unlikely to be used. - * </p> - * - * @return The pattern value - */ - public String getPattern() - { - return pattern; - } - - /** - * 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. - * - * @param rules The rules which {@link Rule#begin(String, String, Attributes)} method has to be fired - * @param namespace the namespace URI of the matching element, or an empty string if the parser is not namespace - * aware or the element has no namespace - * @param name the local name if the parser is namespace aware, or just the element name otherwise - * @param list The attribute list of this element - * @throws Exception if any error occurs - */ - public void fireBeginMethods( final List<Rule> rules, final String namespace, final String name, final Attributes list ) - throws Exception - { - - if ( ( rules != null ) && ( !rules.isEmpty() ) ) - { - final Log log = getDigester().getLogger(); - final boolean debug = log.isDebugEnabled(); - for ( final Rule rule : rules ) - { - if ( debug ) - { - log.debug( " Fire begin() for " + rule ); - } - try - { - rule.begin( namespace, name, list ); - } - catch ( final Exception e ) - { - throw getDigester().createSAXException( e ); - } - catch ( final Error e ) - { - throw e; - } - } - } - } - - /** - * Duplicate the processing that the Digester does when firing the {@link Rule#body(String, String, String)} methods - * of rules. - * - * It would be really nice if the Digester class provided a way for this functionality to just be invoked directly. - * - * @param rules The rules which {@link Rule#body(String, String, String)} method has to be fired - * @param namespace the namespace URI of the matching element, or an empty string if the parser is not namespace - * aware or the element has no namespace - * @param name the local name if the parser is namespace aware, or just the element name otherwise - * @param text The text of the body of this element - * @throws Exception if any error occurs - */ - private void fireBodyMethods( final List<Rule> rules, final String namespaceURI, final String name, final String text ) - throws Exception - { - if ( ( rules != null ) && ( !rules.isEmpty() ) ) - { - final Log log = getDigester().getLogger(); - final boolean debug = log.isDebugEnabled(); - for ( final Rule rule : rules ) - { - if ( debug ) - { - log.debug( " Fire body() for " + rule ); - } - try - { - rule.body( namespaceURI, name, text ); - } - catch ( final Exception e ) - { - throw getDigester().createSAXException( e ); - } - catch ( final Error 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. - * - * @param rules The rules which {@link Rule#end(String, String)} method has to be fired - * @param namespaceURI the namespace URI of the matching element, or an empty string if the parser is not namespace - * aware or the element has no namespace - * @param name the local name if the parser is namespace aware, or just the element name otherwise - * @throws Exception if any error occurs - */ - public void fireEndMethods( final List<Rule> rules, final String namespaceURI, final String name ) - throws Exception - { - // Fire "end" events for all relevant rules in reverse order - if ( rules != null ) - { - final Log log = getDigester().getLogger(); - final boolean debug = log.isDebugEnabled(); - for ( int i = 0; i < rules.size(); i++ ) - { - final int j = ( rules.size() - i ) - 1; - final Rule rule = rules.get( j ); - if ( debug ) - { - log.debug( " Fire end() for " + rule ); - } - try - { - rule.end( namespaceURI, name ); - } - catch ( final Exception e ) - { - throw getDigester().createSAXException( e ); - } - catch ( final Error e ) - { - throw e; - } - } - } - } - -} +package org.apache.commons.digester3.plugins; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.util.List; + +import org.apache.commons.digester3.Rule; +import org.apache.commons.logging.Log; +import org.xml.sax.Attributes; + +/** + * 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. + * + * @since 1.6 + */ +public class PluginCreateRule + extends Rule + implements InitializableRule +{ + + // see setPluginClassAttribute + private String pluginClassAttrNs = null; + + private String pluginClassAttr = null; + + // see setPluginIdAttribute + private String pluginIdAttrNs = null; + + private String pluginIdAttr = null; + + /** + * In order to invoke the addRules method on the plugin class correctly, we need to know the pattern which this rule + * is matched by. + */ + private String pattern; + + /** 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; + + /** + * Currently, none of the Rules methods allow exceptions to be thrown. Therefore if this class cannot initialize + * itself properly, it cannot cause the digester to stop. Instead, we cache the exception and throw it the first + * time the begin() method is called. + */ + private PluginConfigurationException initException; + + // -------------------- 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 PluginCreateRule( final 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 PluginCreateRule( final Class<?> baseClass, final 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 PluginCreateRule( final Class<?> baseClass, final Class<?> dfltPluginClass, final 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. + * + * @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( final String namespaceUri, final 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. + * + * @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( final String namespaceUri, final String attrName ) + { + pluginIdAttrNs = namespaceUri; + pluginIdAttr = attrName; + } + + // ------------------- methods -------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void postRegisterInit( final String matchPattern ) + { + final Log log = LogUtils.getLogger( getDigester() ); + final boolean debug = log.isDebugEnabled(); + if ( debug ) + { + log.debug( "PluginCreateRule.postRegisterInit" + ": rule registered for pattern [" + matchPattern + "]" ); + } + + if ( getDigester() == null ) + { + // We require setDigester to be called before this method. + // Note that this means that PluginCreateRule cannot be added + // to a Rules object which has not yet been added to a + // Digester object. + initException = + new PluginConfigurationException( "Invalid invocation of postRegisterInit" + ": digester not set." ); + throw initException; + } + + if ( pattern != null ) + { + // We have been called twice, ie a single instance has been + // associated with multiple patterns. + // + // Generally, Digester Rule instances can be associated with + // multiple patterns. However for plugins, this creates some + // complications. Some day this may be supported; however for + // now we just reject this situation. + initException = + new PluginConfigurationException( "A single PluginCreateRule instance has been mapped to" + + " multiple patterns; this is not supported." ); + throw initException; + } + + if ( matchPattern.indexOf( '*' ) != -1 ) + { + // having wildcards in patterns is extremely difficult to + // deal with. For now, we refuse to allow this. + // + // TODO: check for any chars not valid in xml element name + // rather than just *. + // + // Reasons include: + // (a) handling recursive plugins, and + // (b) determining whether one pattern is "below" another, + // as done by PluginRules. Without wildcards, "below" + // just means startsWith, which is easy to check. + initException = + new PluginConfigurationException( "A PluginCreateRule instance has been mapped to" + " pattern [" + + matchPattern + "]." + " This pattern includes a wildcard character." + + " This is not supported by the plugin architecture." ); + throw initException; + } + + if ( baseClass == null ) + { + baseClass = Object.class; + } + + final PluginRules rules = (PluginRules) getDigester().getRules(); + final PluginManager pm = rules.getPluginManager(); + + // check default class is valid + if ( defaultPlugin != null ) + { + if ( !baseClass.isAssignableFrom( defaultPlugin.getPluginClass() ) ) + { + initException = + new PluginConfigurationException( "Default class [" + defaultPlugin.getPluginClass().getName() + + "] does not inherit from [" + baseClass.getName() + "]." ); + throw initException; + } + + try + { + defaultPlugin.init( getDigester(), pm ); + + } + catch ( final PluginException pwe ) + { + + throw new PluginConfigurationException( pwe.getMessage(), pwe.getCause() ); + } + } + + // remember the pattern for later + pattern = matchPattern; + + if ( pluginClassAttr == null ) + { + // the user hasn't set explicit xml attr names on this rule, + // so fetch the default values + pluginClassAttrNs = rules.getPluginClassAttrNs(); + pluginClassAttr = rules.getPluginClassAttr(); + + if ( debug ) + { + log.debug( "init: pluginClassAttr set to per-digester values [" + "ns=" + pluginClassAttrNs + ", name=" + + pluginClassAttr + "]" ); + } + } + else + { + if ( debug ) + { + log.debug( "init: pluginClassAttr set to rule-specific values [" + "ns=" + pluginClassAttrNs + + ", name=" + pluginClassAttr + "]" ); + } + } + + if ( pluginIdAttr == null ) + { + // the user hasn't set explicit xml attr names on this rule, + // so fetch the default values + pluginIdAttrNs = rules.getPluginIdAttrNs(); + pluginIdAttr = rules.getPluginIdAttr(); + + if ( debug ) + { + log.debug( "init: pluginIdAttr set to per-digester values [" + "ns=" + pluginIdAttrNs + ", name=" + + pluginIdAttr + "]" ); + } + } + else + { + if ( debug ) + { + log.debug( "init: pluginIdAttr set to rule-specific values [" + "ns=" + pluginIdAttrNs + ", name=" + + pluginIdAttr + "]" ); + } + } + } + + /** + * 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" PluginRules object is + * then created and set as the digester's default Rules object. Any custom rules associated with the plugin class + * are then loaded into that new Rules object. Finally, any custom rules that are associated with the current + * pattern (such as SetPropertiesRules) have their begin methods executed. + * + * @param namespace the namespace URI of the matching element, or an empty string if the parser is not namespace + * aware or the element has no namespace + * @param name the local name if the parser is namespace aware, or just the element name otherwise + * @param attributes The attribute list of this element + * @throws Exception if any error occurs + */ + @Override + public void begin( final String namespace, final String name, final org.xml.sax.Attributes attributes ) + throws Exception + { + final Log log = getDigester().getLogger(); + final boolean debug = log.isDebugEnabled(); + if ( debug ) + { + log.debug( "PluginCreateRule.begin" + ": pattern=[" + pattern + "]" + " match=[" + getDigester().getMatch() + + "]" ); + } + + if ( initException != null ) + { + // we had a problem during initialization that we could + // not report then; report it now. + throw initException; + } + + // load any custom rules associated with the plugin + final PluginRules oldRules = (PluginRules) getDigester().getRules(); + final PluginManager pluginManager = oldRules.getPluginManager(); + Declaration currDeclaration = null; + + String pluginClassName; + if ( pluginClassAttrNs == null ) + { + // Yep, this is ugly. + // + // In a namespace-aware parser, the one-param version will + // return attributes with no namespace. + // + // In a non-namespace-aware parser, the two-param version will + // never return any attributes, ever. + pluginClassName = attributes.getValue( pluginClassAttr ); + } + else + { + pluginClassName = attributes.getValue( pluginClassAttrNs, pluginClassAttr ); + } + + String pluginId; + if ( pluginIdAttrNs == null ) + { + pluginId = attributes.getValue( pluginIdAttr ); + } + else + { + pluginId = attributes.getValue( pluginIdAttrNs, 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 = pluginManager.getDeclarationByClass( pluginClassName ); + + if ( currDeclaration == null ) + { + currDeclaration = new Declaration( pluginClassName ); + try + { + currDeclaration.init( getDigester(), pluginManager ); + } + catch ( final PluginException pwe ) + { + throw new PluginInvalidInputException( pwe.getMessage(), pwe.getCause() ); + } + pluginManager.addDeclaration( currDeclaration ); + } + } + else if ( pluginId != null ) + { + currDeclaration = pluginManager.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 " + pattern ); + } + + // get the class of the user plugged-in type + final Class<?> pluginClass = currDeclaration.getPluginClass(); + + final String path = getDigester().getMatch(); + + // create a new Rules object and effectively push it onto a stack of + // rules objects. The stack is actually a linked list; using the + // PluginRules constructor below causes the new instance to link + // to the previous head-of-stack, then the Digester.setRules() makes + // the new instance the new head-of-stack. + final PluginRules newRules = new PluginRules( getDigester(), path, oldRules, pluginClass ); + getDigester().setRules( newRules ); + + if ( debug ) + { + log.debug( "PluginCreateRule.begin: installing new plugin: " + "oldrules=" + oldRules.toString() + + ", newrules=" + newRules.toString() ); + } + + // load up the custom rules + currDeclaration.configure( getDigester(), pattern ); + + // create an instance of the plugin class + final Object instance = pluginClass.newInstance(); + getDigester().push( instance ); + if ( debug ) + { + log.debug( "PluginCreateRule.begin" + ": pattern=[" + pattern + "]" + " match=[" + getDigester().getMatch() + + "]" + " 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. + final List<Rule> rules = newRules.getDecoratedRules().match( namespace, path, name, attributes ); + fireBeginMethods( rules, namespace, name, attributes ); + } + + /** + * {@inheritDoc} + */ + @Override + public void body( final String namespace, final String name, final String text ) + throws Exception + { + + // 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. + + final String path = getDigester().getMatch(); + final PluginRules newRules = (PluginRules) getDigester().getRules(); + final List<Rule> rules = newRules.getDecoratedRules().match( namespace, path, name, null ); + fireBodyMethods( rules, namespace, name, text ); + } + + /** + * {@inheritDoc} + */ + @Override + public void end( final String namespace, final String name ) + throws Exception + { + // see body method for more info + final String path = getDigester().getMatch(); + final PluginRules newRules = (PluginRules) getDigester().getRules(); + final List<Rule> rules = newRules.getDecoratedRules().match( namespace, path, name, null ); + fireEndMethods( rules, namespace, name ); + + // pop the stack of PluginRules instances, which + // discards all custom rules associated with this plugin + getDigester().setRules( newRules.getParent() ); + + // and get rid of the instance of the plugin class from the + // digester object stack. + getDigester().pop(); + } + + /** + * Return the pattern that this Rule is associated with. + * <p> + * In general, Rule instances <i>can</i> be associated with multiple patterns. A PluginCreateRule, however, will + * only function correctly when associated with a single pattern. It is possible to fix this, but I can't be + * bothered just now because this feature is unlikely to be used. + * </p> + * + * @return The pattern value + */ + public String getPattern() + { + return pattern; + } + + /** + * 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. + * + * @param rules The rules which {@link Rule#begin(String, String, Attributes)} method has to be fired + * @param namespace the namespace URI of the matching element, or an empty string if the parser is not namespace + * aware or the element has no namespace + * @param name the local name if the parser is namespace aware, or just the element name otherwise + * @param list The attribute list of this element + * @throws Exception if any error occurs + */ + public void fireBeginMethods( final List<Rule> rules, final String namespace, final String name, final Attributes list ) + throws Exception + { + + if ( ( rules != null ) && ( !rules.isEmpty() ) ) + { + final Log log = getDigester().getLogger(); + final boolean debug = log.isDebugEnabled(); + for ( final Rule rule : rules ) + { + if ( debug ) + { + log.debug( " Fire begin() for " + rule ); + } + try + { + rule.begin( namespace, name, list ); + } + catch ( final Exception e ) + { + throw getDigester().createSAXException( e ); + } + catch ( final Error e ) + { + throw e; + } + } + } + } + + /** + * Duplicate the processing that the Digester does when firing the {@link Rule#body(String, String, String)} methods + * of rules. + * + * It would be really nice if the Digester class provided a way for this functionality to just be invoked directly. + * + * @param rules The rules which {@link Rule#body(String, String, String)} method has to be fired + * @param namespace the namespace URI of the matching element, or an empty string if the parser is not namespace + * aware or the element has no namespace + * @param name the local name if the parser is namespace aware, or just the element name otherwise + * @param text The text of the body of this element + * @throws Exception if any error occurs + */ + private void fireBodyMethods( final List<Rule> rules, final String namespaceURI, final String name, final String text ) + throws Exception + { + if ( ( rules != null ) && ( !rules.isEmpty() ) ) + { + final Log log = getDigester().getLogger(); + final boolean debug = log.isDebugEnabled(); + for ( final Rule rule : rules ) + { + if ( debug ) + { + log.debug( " Fire body() for " + rule ); + } + try + { + rule.body( namespaceURI, name, text ); + } + catch ( final Exception e ) + { + throw getDigester().createSAXException( e ); + } + catch ( final Error 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. + * + * @param rules The rules which {@link Rule#end(String, String)} method has to be fired + * @param namespaceURI the namespace URI of the matching element, or an empty string if the parser is not namespace + * aware or the element has no namespace + * @param name the local name if the parser is namespace aware, or just the element name otherwise + * @throws Exception if any error occurs + */ + public void fireEndMethods( final List<Rule> rules, final String namespaceURI, final String name ) + throws Exception + { + // Fire "end" events for all relevant rules in reverse order + if ( rules != null ) + { + final Log log = getDigester().getLogger(); + final boolean debug = log.isDebugEnabled(); + for ( int i = 0; i < rules.size(); i++ ) + { + final int j = ( rules.size() - i ) - 1; + final Rule rule = rules.get( j ); + if ( debug ) + { + log.debug( " Fire end() for " + rule ); + } + try + { + rule.end( namespaceURI, name ); + } + catch ( final Exception e ) + { + throw getDigester().createSAXException( e ); + } + catch ( final Error e ) + { + throw e; + } + } + } + } + +} diff --git a/core/src/main/java/org/apache/commons/digester3/plugins/PluginRules.java b/core/src/main/java/org/apache/commons/digester3/plugins/PluginRules.java index b1faa0c4..f50eb916 100644 --- a/core/src/main/java/org/apache/commons/digester3/plugins/PluginRules.java +++ b/core/src/main/java/org/apache/commons/digester3/plugins/PluginRules.java @@ -1,472 +1,472 @@ -package org.apache.commons.digester3.plugins; - -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import java.util.List; - -import org.apache.commons.digester3.Digester; -import org.apache.commons.digester3.Rule; -import org.apache.commons.digester3.Rules; -import org.apache.commons.digester3.RulesBase; -import org.apache.commons.logging.Log; -import org.xml.sax.Attributes; - -/** - * A custom digester Rules manager which must be used as the Rules object when using the plugins module functionality. - * <p> - * During parsing, a linked list of PluginCreateRule instances develop, and this list also acts like a stack. The - * original instance that was set before the Digester started parsing is always at the tail of the list, and the - * Digester always holds a reference to the instance at the head of the list in the rules member. Initially, this - * list/stack holds just one instance, ie head and tail are the same object. - * <p> - * When the start of an xml element causes a PluginCreateRule to fire, a new PluginRules instance is created and - * inserted at the head of the list (ie pushed onto the stack of Rules objects). Digester.getRules() therefore returns - * this new Rules object, and any custom rules associated with that plugin are added to that instance. - * <p> - * When the end of the xml element is encountered (and therefore the PluginCreateRule end method fires), the stack of - * Rules objects is popped, so that Digester.getRules returns the previous Rules object. - * - * @since 1.6 - */ -public class PluginRules - implements Rules -{ - - /** - * The Digester instance with which this Rules instance is associated. - */ - protected Digester digester = null; - - /** - * The (optional) object which generates new rules instances. - */ - private RulesFactory rulesFactory; - - /** - * The rules implementation that we are "enhancing" with plugins functionality, as per the Decorator pattern. - */ - private final Rules decoratedRules; - - /** Object which contains information about all known plugins. */ - private final PluginManager pluginManager; - - /** - * The path below which this rules object has responsibility. For paths shorter than or equal the mountpoint, the - * parent's match is called. - */ - private String mountPoint = null; - - /** - * The Rules object that holds rules applying "above" the mountpoint, ie the next Rules object down in the stack. - */ - private PluginRules parent = null; - - /** - * A reference to the object that holds all data which should only exist once per digester instance. - */ - private PluginContext pluginContext = null; - - // ------------------------------------------------------------- Constructor - - /** - * Constructor for top-level Rules objects. Exactly one of these must be created and installed into the Digester - * instance as the Rules object before parsing starts. - */ - public PluginRules() - { - this( new RulesBase() ); - } - - /** - * Constructor for top-level Rules object which handles rule-matching using the specified implementation. - * - * @param decoratedRules The top-level Rules object which handles rule-matching using the specified implementation. - */ - public PluginRules( final Rules decoratedRules ) - { - this.decoratedRules = decoratedRules; - - pluginContext = new PluginContext(); - pluginManager = new PluginManager( pluginContext ); - } - - /** - * Constructs a Rules instance which has a parent Rules object (which is different from having a delegate rules - * object). - * <p> - * One of these is created each time a PluginCreateRule's begin method fires, in order to manage the custom rules - * associated with whatever concrete plugin class the user has specified. - * - * @param digester is the object this rules will be associated with. - * @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 digester.getMatch(). - * @param parent must be non-null. - * @param pluginClass is the plugin class whose custom rules will be loaded into this new PluginRules object. - * @throws PluginException if any error occurs - */ - PluginRules( final Digester digester, final String mountPoint, final PluginRules parent, final Class<?> pluginClass ) - throws PluginException - { - // no need to set digester or decoratedRules.digester, - // because when Digester.setRules is called, the setDigester - // method on this object will be called. - - this.digester = digester; - this.mountPoint = mountPoint; - this.parent = parent; - this.rulesFactory = parent.rulesFactory; - - if ( rulesFactory == null ) - { - decoratedRules = new RulesBase(); - } - else - { - decoratedRules = rulesFactory.newRules( digester, pluginClass ); - } - - pluginContext = parent.pluginContext; - pluginManager = new PluginManager( parent.pluginManager ); - } - - // ------------------------------------------------------------- Properties - - /** - * Return the parent Rules object. - * - * @return the parent Rules object. - */ - public Rules getParent() - { - return parent; - } - - /** - * Return the Digester instance with which this instance is associated. - * - * @return the Digester instance with which this instance is associated. - */ - @Override - public Digester getDigester() - { - return digester; - } - - /** - * Set the Digester instance with which this Rules instance is associated. - * - * @param digester The newly associated Digester instance - */ - @Override - public void setDigester( final Digester digester ) - { - this.digester = digester; - decoratedRules.setDigester( digester ); - } - - /** - * Return the namespace URI that will be applied to all subsequently added {@code Rule} objects. - * - * @return the namespace URI that will be applied to all subsequently added {@code Rule} objects. - */ - @Override - public String getNamespaceURI() - { - return decoratedRules.getNamespaceURI(); - } - - /** - * Set the namespace URI that will be applied to all subsequently added {@code Rule} objects. - * - * @param namespaceURI Namespace URI that must match on all subsequently added rules, or {@code null} for - * matching regardless of the current namespace URI - */ - @Override - public void setNamespaceURI( final String namespaceURI ) - { - decoratedRules.setNamespaceURI( namespaceURI ); - } - - /** - * Return the object which "knows" about all declared plugins. - * - * @return The pluginManager value - */ - public PluginManager getPluginManager() - { - return pluginManager; - } - - /** - * See {@link PluginContext#getRuleFinders}. - * - * @return the list of RuleFinder objects - */ - public List<RuleFinder> getRuleFinders() - { - return pluginContext.getRuleFinders(); - } - - /** - * See {@link PluginContext#setRuleFinders}. - * - * @param ruleFinders the list of RuleFinder objects - */ - public void setRuleFinders( final List<RuleFinder> ruleFinders ) - { - pluginContext.setRuleFinders( ruleFinders ); - } - - /** - * Return the rules factory object (or null if one has not been specified). - * - * @return the rules factory object. - */ - public RulesFactory getRulesFactory() - { - return rulesFactory; - } - - /** - * Set the object which is used to generate the new Rules instances created to hold and process the rules associated - * with each plugged-in class. - * - * @param factory the rules factory object - */ - public void setRulesFactory( final RulesFactory factory ) - { - rulesFactory = factory; - } - - // --------------------------------------------------------- Public Methods - - /** - * This package-scope method is used by the PluginCreateRule class to get direct access to the rules that were - * dynamically added by the plugin. No other class should need access to this object. - * - * @return The decorated Rule instance - */ - Rules getDecoratedRules() - { - return decoratedRules; - } - - /** - * Return the list of rules registered with this object, in the order they were registered with this object. - * <p> - * Note that Rule objects stored in parent Rules objects are not returned by this method. - * - * @return list of all Rule objects known to this Rules instance. - */ - @Override - public List<Rule> rules() - { - return decoratedRules.rules(); - } - - /** - * Register a new Rule instance matching the specified pattern. - * - * @param pattern Nesting pattern to be matched for this Rule. This parameter treats equally patterns that begin - * with and without a leading slash ('/'). - * @param rule Rule instance to be registered - */ - @Override - public void add( String pattern, final Rule rule ) - { - final Log log = LogUtils.getLogger( digester ); - final boolean debug = log.isDebugEnabled(); - - if ( debug ) - { - log.debug( "add entry" + ": mapping pattern [" + pattern + "]" + " to rule of type [" - + rule.getClass().getName() + "]" ); - } - - // allow patterns with a leading slash character - if ( pattern.startsWith( "/" ) ) - { - pattern = pattern.substring( 1 ); - } - - if ( mountPoint != null && !pattern.equals( mountPoint ) && !pattern.startsWith( mountPoint + "/" ) ) - { - // This can only occur if a plugin attempts to add a - // rule with a pattern that doesn't start with the - // prefix passed to the addRules method. Plugins mustn't - // add rules outside the scope of the tag they were specified - // on, so refuse this. - - // alas, can't throw exception - log.warn( "An attempt was made to add a rule with a pattern that" - + "is not at or below the mountpoint of the current" + " PluginRules object." + " Rule pattern: " - + pattern + ", mountpoint: " + mountPoint + ", rule type: " + rule.getClass().getName() ); - return; - } - - decoratedRules.add( pattern, rule ); - - if ( rule instanceof InitializableRule ) - { - try - { - ( (InitializableRule) rule ).postRegisterInit( pattern ); - } - catch ( final PluginConfigurationException e ) - { - // Currently, Digester doesn't handle exceptions well - // from the add method. The workaround is for the - // initialisable rule to remember that its initialisation - // failed, and to throw the exception when begin is - // called for the first time. - if ( debug ) - { - log.debug( "Rule initialisation failed", e ); - } - // throw e; -- alas, can't do this - return; - } - } - - if ( debug ) - { - log.debug( "add exit" + ": mapped pattern [" + pattern + "]" + " to rule of type [" - + rule.getClass().getName() + "]" ); - } - } - - /** - * Clear all rules. - */ - @Override - public void clear() - { - decoratedRules.clear(); - } - - /** - * {@inheritDoc} - */ - @Override - public List<Rule> match( final String namespaceURI, final String path, final String name, final Attributes attributes ) - { - final Log log = LogUtils.getLogger( digester ); - final boolean debug = log.isDebugEnabled(); - - if ( debug ) - { - log.debug( "Matching path [" + path + "] on rules object " + this.toString() ); - } - - List<Rule> matches; - if ( ( mountPoint != null ) && ( path.length() <= mountPoint.length() ) ) - { - if ( debug ) - { - log.debug( "Path [" + path + "] delegated to parent." ); - } - - matches = parent.match( namespaceURI, path, name, attributes ); - - // Note that in the case where path equals mountPoint, - // we deliberately return only the rules from the parent, - // even though this object may hold some rules matching - // this same path. See PluginCreateRule's begin, body and end - // methods for the reason. - } - else - { - log.debug( "delegating to decorated rules." ); - matches = decoratedRules.match( namespaceURI, path, name, attributes ); - } - - return matches; - } - - /** - * See {@link PluginContext#setPluginClassAttribute}. - * - * @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( final String namespaceUri, final String attrName ) - { - pluginContext.setPluginClassAttribute( namespaceUri, attrName ); - } - - /** - * See {@link PluginContext#setPluginIdAttribute}. - * - * @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( final String namespaceUri, final String attrName ) - { - pluginContext.setPluginIdAttribute( namespaceUri, attrName ); - } - - /** - * See {@link PluginContext#getPluginClassAttrNs}. - * - * @return the namespace for the xml attribute which indicates which class is to be plugged in. - */ - public String getPluginClassAttrNs() - { - return pluginContext.getPluginClassAttrNs(); - } - - /** - * See {@link PluginContext#getPluginClassAttr}. - * - * @return the namespace for the xml attribute which indicates which class is to be plugged in. - */ - public String getPluginClassAttr() - { - return pluginContext.getPluginClassAttr(); - } - - /** - * See {@link PluginContext#getPluginIdAttrNs}. - * - * @return the namespace for the xml attribute which indicates which previous plugin declaration should be used. - */ - public String getPluginIdAttrNs() - { - return pluginContext.getPluginIdAttrNs(); - } - - /** - * See {@link PluginContext#getPluginIdAttr}. - * - * @return the namespace for the xml attribute which indicates which previous plugin declaration should be used. - */ - public String getPluginIdAttr() - { - return pluginContext.getPluginIdAttr(); - } - -} +package org.apache.commons.digester3.plugins; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.util.List; + +import org.apache.commons.digester3.Digester; +import org.apache.commons.digester3.Rule; +import org.apache.commons.digester3.Rules; +import org.apache.commons.digester3.RulesBase; +import org.apache.commons.logging.Log; +import org.xml.sax.Attributes; + +/** + * A custom digester Rules manager which must be used as the Rules object when using the plugins module functionality. + * <p> + * During parsing, a linked list of PluginCreateRule instances develop, and this list also acts like a stack. The + * original instance that was set before the Digester started parsing is always at the tail of the list, and the + * Digester always holds a reference to the instance at the head of the list in the rules member. Initially, this + * list/stack holds just one instance, ie head and tail are the same object. + * <p> + * When the start of an xml element causes a PluginCreateRule to fire, a new PluginRules instance is created and + * inserted at the head of the list (ie pushed onto the stack of Rules objects). Digester.getRules() therefore returns + * this new Rules object, and any custom rules associated with that plugin are added to that instance. + * <p> + * When the end of the xml element is encountered (and therefore the PluginCreateRule end method fires), the stack of + * Rules objects is popped, so that Digester.getRules returns the previous Rules object. + * + * @since 1.6 + */ +public class PluginRules + implements Rules +{ + + /** + * The Digester instance with which this Rules instance is associated. + */ + protected Digester digester = null; + + /** + * The (optional) object which generates new rules instances. + */ + private RulesFactory rulesFactory; + + /** + * The rules implementation that we are "enhancing" with plugins functionality, as per the Decorator pattern. + */ + private final Rules decoratedRules; + + /** Object which contains information about all known plugins. */ + private final PluginManager pluginManager; + + /** + * The path below which this rules object has responsibility. For paths shorter than or equal the mountpoint, the + * parent's match is called. + */ + private String mountPoint = null; + + /** + * The Rules object that holds rules applying "above" the mountpoint, ie the next Rules object down in the stack. + */ + private PluginRules parent = null; + + /** + * A reference to the object that holds all data which should only exist once per digester instance. + */ + private PluginContext pluginContext = null; + + // ------------------------------------------------------------- Constructor + + /** + * Constructor for top-level Rules objects. Exactly one of these must be created and installed into the Digester + * instance as the Rules object before parsing starts. + */ + public PluginRules() + { + this( new RulesBase() ); + } + + /** + * Constructor for top-level Rules object which handles rule-matching using the specified implementation. + * + * @param decoratedRules The top-level Rules object which handles rule-matching using the specified implementation. + */ + public PluginRules( final Rules decoratedRules ) + { + this.decoratedRules = decoratedRules; + + pluginContext = new PluginContext(); + pluginManager = new PluginManager( pluginContext ); + } + + /** + * Constructs a Rules instance which has a parent Rules object (which is different from having a delegate rules + * object). + * <p> + * One of these is created each time a PluginCreateRule's begin method fires, in order to manage the custom rules + * associated with whatever concrete plugin class the user has specified. + * + * @param digester is the object this rules will be associated with. + * @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 digester.getMatch(). + * @param parent must be non-null. + * @param pluginClass is the plugin class whose custom rules will be loaded into this new PluginRules object. + * @throws PluginException if any error occurs + */ + PluginRules( final Digester digester, final String mountPoint, final PluginRules parent, final Class<?> pluginClass ) + throws PluginException + { + // no need to set digester or decoratedRules.digester, + // because when Digester.setRules is called, the setDigester + // method on this object will be called. + + this.digester = digester; + this.mountPoint = mountPoint; + this.parent = parent; + this.rulesFactory = parent.rulesFactory; + + if ( rulesFactory == null ) + { + decoratedRules = new RulesBase(); + } + else + { + decoratedRules = rulesFactory.newRules( digester, pluginClass ); + } + + pluginContext = parent.pluginContext; + pluginManager = new PluginManager( parent.pluginManager ); + } + + // ------------------------------------------------------------- Properties + + /** + * Return the parent Rules object. + * + * @return the parent Rules object. + */ + public Rules getParent() + { + return parent; + } + + /** + * Return the Digester instance with which this instance is associated. + * + * @return the Digester instance with which this instance is associated. + */ + @Override + public Digester getDigester() + { + return digester; + } + + /** + * Set the Digester instance with which this Rules instance is associated. + * + * @param digester The newly associated Digester instance + */ + @Override + public void setDigester( final Digester digester ) + { + this.digester = digester; + decoratedRules.setDigester( digester ); + } + + /** + * Return the namespace URI that will be applied to all subsequently added {@code Rule} objects. + * + * @return the namespace URI that will be applied to all subsequently added {@code Rule} objects. + */ + @Override + public String getNamespaceURI() + { + return decoratedRules.getNamespaceURI(); + } + + /** + * Set the namespace URI that will be applied to all subsequently added {@code Rule} objects. + * + * @param namespaceURI Namespace URI that must match on all subsequently added rules, or {@code null} for + * matching regardless of the current namespace URI + */ + @Override + public void setNamespaceURI( final String namespaceURI ) + { + decoratedRules.setNamespaceURI( namespaceURI ); + } + + /** + * Return the object which "knows" about all declared plugins. + * + * @return The pluginManager value + */ + public PluginManager getPluginManager() + { + return pluginManager; + } + + /** + * See {@link PluginContext#getRuleFinders}. + * + * @return the list of RuleFinder objects + */ + public List<RuleFinder> getRuleFinders() + { + return pluginContext.getRuleFinders(); + } + + /** + * See {@link PluginContext#setRuleFinders}. + * + * @param ruleFinders the list of RuleFinder objects + */ + public void setRuleFinders( final List<RuleFinder> ruleFinders ) + { + pluginContext.setRuleFinders( ruleFinders ); + } + + /** + * Return the rules factory object (or null if one has not been specified). + * + * @return the rules factory object. + */ + public RulesFactory getRulesFactory() + { + return rulesFactory; + } + + /** + * Set the object which is used to generate the new Rules instances created to hold and process the rules associated + * with each plugged-in class. + * + * @param factory the rules factory object + */ + public void setRulesFactory( final RulesFactory factory ) + { + rulesFactory = factory; + } + + // --------------------------------------------------------- Public Methods + + /** + * This package-scope method is used by the PluginCreateRule class to get direct access to the rules that were + * dynamically added by the plugin. No other class should need access to this object. + * + * @return The decorated Rule instance + */ + Rules getDecoratedRules() + { + return decoratedRules; + } + + /** + * Return the list of rules registered with this object, in the order they were registered with this object. + * <p> + * Note that Rule objects stored in parent Rules objects are not returned by this method. + * + * @return list of all Rule objects known to this Rules instance. + */ + @Override + public List<Rule> rules() + { + return decoratedRules.rules(); + } + + /** + * Register a new Rule instance matching the specified pattern. + * + * @param pattern Nesting pattern to be matched for this Rule. This parameter treats equally patterns that begin + * with and without a leading slash ('/'). + * @param rule Rule instance to be registered + */ + @Override + public void add( String pattern, final Rule rule ) + { + final Log log = LogUtils.getLogger( digester ); + final boolean debug = log.isDebugEnabled(); + + if ( debug ) + { + log.debug( "add entry" + ": mapping pattern [" + pattern + "]" + " to rule of type [" + + rule.getClass().getName() + "]" ); + } + + // allow patterns with a leading slash character + if ( pattern.startsWith( "/" ) ) + { + pattern = pattern.substring( 1 ); + } + + if ( mountPoint != null && !pattern.equals( mountPoint ) && !pattern.startsWith( mountPoint + "/" ) ) + { + // This can only occur if a plugin attempts to add a + // rule with a pattern that doesn't start with the + // prefix passed to the addRules method. Plugins mustn't + // add rules outside the scope of the tag they were specified + // on, so refuse this. + + // alas, can't throw exception + log.warn( "An attempt was made to add a rule with a pattern that" + + "is not at or below the mountpoint of the current" + " PluginRules object." + " Rule pattern: " + + pattern + ", mountpoint: " + mountPoint + ", rule type: " + rule.getClass().getName() ); + return; + } + + decoratedRules.add( pattern, rule ); + + if ( rule instanceof InitializableRule ) + { + try + { + ( (InitializableRule) rule ).postRegisterInit( pattern ); + } + catch ( final PluginConfigurationException e ) + { + // Currently, Digester doesn't handle exceptions well + // from the add method. The workaround is for the + // initialisable rule to remember that its initialization + // failed, and to throw the exception when begin is + // called for the first time. + if ( debug ) + { + log.debug( "Rule initialisation failed", e ); + } + // throw e; -- alas, can't do this + return; + } + } + + if ( debug ) + { + log.debug( "add exit" + ": mapped pattern [" + pattern + "]" + " to rule of type [" + + rule.getClass().getName() + "]" ); + } + } + + /** + * Clear all rules. + */ + @Override + public void clear() + { + decoratedRules.clear(); + } + + /** + * {@inheritDoc} + */ + @Override + public List<Rule> match( final String namespaceURI, final String path, final String name, final Attributes attributes ) + { + final Log log = LogUtils.getLogger( digester ); + final boolean debug = log.isDebugEnabled(); + + if ( debug ) + { + log.debug( "Matching path [" + path + "] on rules object " + this.toString() ); + } + + List<Rule> matches; + if ( ( mountPoint != null ) && ( path.length() <= mountPoint.length() ) ) + { + if ( debug ) + { + log.debug( "Path [" + path + "] delegated to parent." ); + } + + matches = parent.match( namespaceURI, path, name, attributes ); + + // Note that in the case where path equals mountPoint, + // we deliberately return only the rules from the parent, + // even though this object may hold some rules matching + // this same path. See PluginCreateRule's begin, body and end + // methods for the reason. + } + else + { + log.debug( "delegating to decorated rules." ); + matches = decoratedRules.match( namespaceURI, path, name, attributes ); + } + + return matches; + } + + /** + * See {@link PluginContext#setPluginClassAttribute}. + * + * @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( final String namespaceUri, final String attrName ) + { + pluginContext.setPluginClassAttribute( namespaceUri, attrName ); + } + + /** + * See {@link PluginContext#setPluginIdAttribute}. + * + * @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( final String namespaceUri, final String attrName ) + { + pluginContext.setPluginIdAttribute( namespaceUri, attrName ); + } + + /** + * See {@link PluginContext#getPluginClassAttrNs}. + * + * @return the namespace for the xml attribute which indicates which class is to be plugged in. + */ + public String getPluginClassAttrNs() + { + return pluginContext.getPluginClassAttrNs(); + } + + /** + * See {@link PluginContext#getPluginClassAttr}. + * + * @return the namespace for the xml attribute which indicates which class is to be plugged in. + */ + public String getPluginClassAttr() + { + return pluginContext.getPluginClassAttr(); + } + + /** + * See {@link PluginContext#getPluginIdAttrNs}. + * + * @return the namespace for the xml attribute which indicates which previous plugin declaration should be used. + */ + public String getPluginIdAttrNs() + { + return pluginContext.getPluginIdAttrNs(); + } + + /** + * See {@link PluginContext#getPluginIdAttr}. + * + * @return the namespace for the xml attribute which indicates which previous plugin declaration should be used. + */ + public String getPluginIdAttr() + { + return pluginContext.getPluginIdAttr(); + } + +}