You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@commons.apache.org by sk...@apache.org on 2005/02/04 02:51:54 UTC
svn commit: r151287 [4/7] - in
jakarta/commons/proper/digester/branches/digester2: ./ src/ src/conf/
src/examples/ src/examples/api/ src/examples/api/addressbook/ src/java/
src/java/org/ src/java/org/apache/ src/java/org/apache/commons/
src/java/org/apache/commons/digester2/
src/java/org/apache/commons/digester2/actions/
src/java/org/apache/commons/digester2/factory/ src/media/ src/test/
src/test/org/ src/test/org/apache/ src/test/org/apache/commons/
src/test/org/apache/commons/digester2/ xdocs/ xdocs/dtds/ xdocs/images/
xdocs/style/
Added: jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/SAXHandler.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/SAXHandler.java?view=auto&rev=151287
==============================================================================
--- jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/SAXHandler.java (added)
+++ jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/SAXHandler.java Thu Feb 3 17:51:43 2005
@@ -0,0 +1,1408 @@
+/* $Id: Digester.java,v 1.107 2004/09/18 09:51:03 skitching Exp $
+ *
+ * Copyright 2001-2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.digester2;
+
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.lang.reflect.InvocationTargetException;
+import java.util.EmptyStackException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.commons.collections.ArrayStack;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXNotRecognizedException;
+import org.xml.sax.SAXNotSupportedException;
+import org.xml.sax.SAXParseException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.DefaultHandler;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.EntityResolver;
+
+/**
+ * <p>An object which handles SAX events generated during parsing of an
+ * input document, and invokes the appropriate methods on the appropriate
+ * rule objects.</p>
+ *
+ * <p>This class is not responsible for instantiating a SAX parser to
+ * parse the input; it just handles the resulting events.
+ * <p>
+ * When a parser is built by a Digester instance, the resulting parser will
+ * have this object set as the content-handler, entity-resolver, error-handler,
+ * and dtd-handler. However if the user has used Digester APIs to explicitly
+ * specify their own entity-resolver, error-handler or dtd-handler then this
+ * object will delegate to the user-provided class (after some basic logging
+ * and housekeeping).
+ * <p>
+ * If the user chooses to instantiate the parser themselves, then they must
+ * set the content-handler to a SAXHandler, but may point the entity-resolver,
+ * error-handler and dtd-handler callbacks at any object they wish.
+ * <p>
+ * The content-handler callbacks implemented here (beginDocument, endDocument,
+ * beginElement, characters and endElement) are used to provide the standard
+ * digester functionality.
+ * <p>
+ * The entity-resolver callbacks implemented here perform a lookup of the
+ * entity in a (publicid->url) table configured via calls to the register(...)
+ * method. For entities which are not found to be registered, there systemid
+ * (if any) is used to attempt to locate them.
+ * <p>
+ * The error-handler callbacks implemented here cause a DigestionException
+ * to be thrown if the xml parser reports an error. Warnings are logged
+ * but otherwise ignored.
+ * <p>
+ * The dtd-handler callbacks (notationDecl and unparsedEntityDecl) are simply
+ * logged, then ignored.
+ */
+
+public class SAXHandler extends DefaultHandler {
+
+ // --------------------------------------------------------- Constructors
+
+ /**
+ * Construct a new SAXHandler.
+ */
+ public SAXHandler() {
+ super();
+ }
+
+ // --------------------------------------------------- Instance Variables
+
+ /**
+ * The XMLReader object that is generating the SAX events being handled
+ * by this object. This member is currently here for only one purpose:
+ * so that the CreateNodeRule can get access to it and (temporarily)
+ * redirect the sax events to its own handler. This isn't entirely
+ * elegant (particularly as it introduces a cyclic dependency between
+ * this class and its XMLReader); alternative solutions are welcome.
+ */
+ private XMLReader reader;
+
+ /**
+ * The EntityResolver used to look up any external entities referenced
+ * from within the input xml. Note that this class always receives the
+ * event initally, so that they can be logged, but if the user has
+ * specified an entityResolver then the events are also forwarded to
+ * the provided object.
+ */
+ private EntityResolver entityResolver = null;
+
+ /**
+ * The application-supplied error handler that is notified when parsing
+ * warnings, errors, or fatal errors occur. Note that this class always
+ * handles the errors initially, so that they can be logged, but if the
+ * user has specified an errorHandler then the events are also forwarded
+ * to the provided object.
+ */
+ private ErrorHandler errorHandler = null;
+
+ /**
+ * The Locator associated with our parser.
+ */
+ private Locator locator = null;
+
+ /**
+ * The XML schema to use for validating an XML instance.
+ *
+ * TODO: properly implement schema validation
+ */
+ private String schemaLocation = null;
+
+ /**
+ * What language the schema is in (W3C, or RELAXNG or ..)
+ *
+ * TODO: properly implement schema validation. This variable is not
+ * actually currently used.
+ */
+ private String schemaLanguage = null;
+
+ /**
+ * A map of known external entities that input xml documents may refer to.
+ * via public or system IDs. The keys of the map entries are public or
+ * system IDs, and the values are URLs (typically local files) pointing
+ * to locations where those entities can be found.
+ * <p>
+ * See #setKnownEntities, #getKnownEntities, #registerKnownEntity
+ */
+ private Map knownEntities = new HashMap();
+
+ /**
+ * An object which contains state information that evolves
+ * as the parse progresses. Rule object commonly interact with
+ * the context object.
+ */
+ private Context context = null;
+
+ /**
+ * The <code>Rules</code> implementation containing our collection of
+ * <code>Rule</code> instances and associated matching policy. If not
+ * established before the first rule is added, a default implementation
+ * will be provided.
+ */
+ private RuleManager ruleManager = null;
+
+ /**
+ * The initial object (if any) that the stack should be primed with
+ * before parsing starts.
+ */
+ private Object initialObject = null;
+
+ /**
+ * The Log to which most logging calls will be made.
+ */
+ private Log log =
+ LogFactory.getLog("org.apache.commons.digester.Digester");
+
+ /**
+ * The Log to which all SAX event related logging calls will be made.
+ */
+ private Log saxLog =
+ LogFactory.getLog("org.apache.commons.digester.Digester.sax");
+
+ /**
+ * An optional class that substitutes values in attributes and body text.
+ * This may be null and so a null check is always required before use.
+ */
+ private Substitutor substitutor;
+
+ /**
+ * The class loader to use for instantiating application objects.
+ * If not specified, the context class loader, or the class loader
+ * used to load Digester itself, is used, based on the value of the
+ * <code>useContextClassLoader</code> variable.
+ */
+ private ClassLoader explicitClassLoader;
+
+ /**
+ * Do we want to use the Context classloader when loading classes for
+ * instantiating new objects. Default is false.
+ */
+ private boolean useContextClassLoader = false;
+
+ /**
+ * Has this instance had its initialize method called yet?
+ */
+ private boolean initialized = false;
+
+ // -------------------------------------------------------------------
+ // Instance variables that are modified during a parse.
+ // -------------------------------------------------------------------
+
+ /**
+ * The public identifier of the DTD we are currently parsing under
+ * (if any). The user may set this explicitly, in which case we ignore
+ * the DTD specified in the input xml file and use the one provided
+ * by the user. See method resolveEntity.
+ *
+ * TODO: Consider if this should be moved to Context. Maybe not if the
+ * user has explicitly set it, but certainly if it is extracted from
+ * the input data...
+ */
+ private String publicId = null;
+
+ /**
+ * The body text of the current element. As the parser reports chunks
+ * of text associated with the current element, they are appended here.
+ * When the end of the element is reported, the full text content of the
+ * current element should be here. Note that if the element has mixed
+ * content, ie text intermingled with child elements, then this buffer
+ * ends up with all the different text pieces mixed together.
+ */
+ private StringBuffer bodyText = new StringBuffer();
+
+ /**
+ * When processing an element with mixed content (ie text and child
+ * elements), then when we start a child element we need to store the
+ * current text seen so far, and restore it after we have finished
+ * with the child element. This stack therefore contains StringBuffer
+ * items containing the body text of "interrupted" xml elements.
+ */
+ private ArrayStack bodyTexts = new ArrayStack();
+
+ /**
+ * Registered namespaces we are currently processing. The key is the
+ * namespace prefix that was declared in the document. The value is an
+ * ArrayStack of the namespace URIs this prefix has been mapped to --
+ * the top Stack element is the most current one. (This architecture
+ * is required because documents can declare nested uses of the same
+ * prefix for different Namespace URIs).
+ */
+ private HashMap namespaces = new HashMap();
+
+ // ---------------------------------------------------------------------
+ // General object configuration methods
+ //
+ // These methods are expected to be called by the user or by the
+ // Digester class in order to set this object up ready to perform
+ // parsing.
+ //
+ // Some methods (particularly the getters) are also used during
+ // parsing.
+ // ---------------------------------------------------------------------
+
+ /**
+ * Specify the XMLReader object that will be generating the SAX events
+ * passed to this object. Some Action classes (CreateNodeAction in
+ * particular) need to access the XMLReader, so this object must be able
+ * to provide this data when requested.
+ */
+ public void setXMLReader(XMLReader reader) {
+ this.reader = reader;
+ }
+
+ /**
+ * See {@link #setXMLReader}.
+ */
+ public XMLReader getXMLReader() {
+ return reader;
+ }
+
+ /**
+ * Set the publid id of the current file being parsed. This will cause
+ * the declared DOCTYPE (if any) of the input document to be ignored.
+ *
+ * Instead the provided publicId will be looked up in the known entities,
+ * and the resource located at the associated URL will be used as the
+ * DTD for this input document.
+ * <p>
+ * NOTE: if the input document doesn't include a DOCTYPE, then the
+ * DTD specified by this call is not used. There is currently no way
+ * to force an input document to be validated against a specific DTD
+ * (due to a lack of this feature in the xml standard).
+ *
+ * @param publicId the DTD/Schema public's id.
+ */
+ public void setPublicId(String publicId){
+ this.publicId = publicId;
+ }
+
+ /**
+ * Return the public identifier of the DTD we are currently parsing under,
+ * if any. If setPublicId has been called previously, then the value
+ * returned here will be the one explicitly set. Otherwise, if we have
+ * already seen a DOCTYPE declaration in the input data, then the
+ * public id in that DOCTYPE will be returned. Otherwise (parsing hasn't
+ * started or the input document had no DOCTYPE) null is returned.
+ */
+ public String getPublicId() {
+ return this.publicId;
+ }
+
+ /**
+ * Set the current logger. This call should be made before parsing
+ * starts.
+ */
+ public void setLogger(Log log) {
+ this.log = log;
+ }
+
+ /**
+ * Return the current Logger associated with this instance.
+ */
+ public Log getLogger() {
+ return log;
+ }
+
+ /**
+ * Sets the logger used for logging SAX-related information.
+ * <strong>Note</strong> the output is finely grained.
+ * @param saxLog Log, not null
+ */
+ public void setSAXLogger(Log saxLog) {
+
+ this.saxLog = saxLog;
+ }
+
+ /**
+ * Gets the logger used for logging SAX-related information.
+ * <strong>Note</strong> the output is finely grained.
+ */
+ public Log getSAXLogger() {
+ return saxLog;
+ }
+
+ /**
+ * Set the <code>RuleManager</code> implementation object containing our
+ * rules collection and associated matching policy.
+ *
+ * @param ruleManager New RuleManager implementation
+ */
+ public void setRuleManager(RuleManager ruleManager) {
+ this.ruleManager = ruleManager;
+ }
+
+ /**
+ * Return the <code>Rules</code> implementation object containing our
+ * rules collection and associated matching policy. If none has been
+ * established, a default implementation will be created and returned.
+ */
+ public RuleManager getRuleManager() {
+ if (ruleManager == null) {
+ ruleManager = new DefaultRuleManager();
+ }
+ return ruleManager;
+ }
+
+ /**
+ * Set the XML Schema URI used for validating a XML Instance.
+ *
+ * @param schemaLocation a URI to the schema.
+ */
+ public void setSchema(String schemaLocation){
+ this.schemaLocation = schemaLocation;
+ }
+
+ /**
+ * Return the XML Schema URI used for validating an XML instance.
+ */
+ public String getSchema() {
+ return (this.schemaLocation);
+ }
+
+ /**
+ * Set the XML Schema language used when parsing. By default, we use W3C.
+ *
+ * @param schemaLanguage a URI to the schema language.
+ */
+ public void setSchemaLanguage(String schemaLanguage){
+ this.schemaLanguage = schemaLanguage;
+ }
+
+ /**
+ * Return the XML Schema language used when parsing.
+ */
+ public String getSchemaLanguage() {
+ return schemaLanguage;
+ }
+
+ /**
+ * Determine whether to use the Context ClassLoader (the one found by
+ * calling <code>Thread.currentThread().getContextClassLoader()</code>)
+ * to resolve/load classes that are defined in various rules. If not
+ * using Context ClassLoader, then the class-loading defaults to
+ * using the calling-class' ClassLoader.
+ *
+ * @param use determines whether to use Context ClassLoader.
+ */
+ public void setUseContextClassLoader(boolean use) {
+ useContextClassLoader = use;
+ }
+
+ /**
+ * Return the boolean as to whether the context classloader should be used.
+ */
+ public boolean getUseContextClassLoader() {
+ return useContextClassLoader;
+ }
+
+ /**
+ * Set the class loader to be used for instantiating application objects
+ * when required.
+ *
+ * @param classLoader The new class loader to use, or <code>null</code>
+ * to revert to the standard rules
+ */
+ public void setExplicitClassLoader(ClassLoader classLoader) {
+ this.explicitClassLoader = classLoader;
+ }
+
+ /**
+ * Get the class loader to be used by actions when instantiating objects
+ * as a result of processing xml input.
+ * </ul>
+ */
+ public ClassLoader getExplicitClassLoader() {
+ return explicitClassLoader;
+ }
+
+ /**
+ * Return the class loader to be used by actions when instantiating objects
+ * as a result of processing xml input.
+ * <p>
+ * The classloader used is determined using the following procedure:
+ * <ul>
+ * <li>The class loader set by <code>setClassLoader()</code>, if any</li>
+ * <li>The thread context class loader, if it exists and the
+ * <code>useContextClassLoader</code> property is set to true</li>
+ * <li>The class loader used to load this class itself.
+ * </ul>
+ */
+ public ClassLoader getClassLoader() {
+ if (this.explicitClassLoader != null) {
+ return this.explicitClassLoader;
+ }
+
+ if (this.useContextClassLoader) {
+ ClassLoader classLoader =
+ Thread.currentThread().getContextClassLoader();
+ if (classLoader != null) {
+ return classLoader;
+ }
+ }
+
+ return this.getClass().getClassLoader();
+ }
+
+ /**
+ * Sets the <code>Substitutor</code> to be used to convert attributes and
+ * body text. This allows manipulation of the xml input to be performed
+ * before any action sees it. One application of this is to allow
+ * variable substitution in the xml text, eg "<foo id='$id'>".
+ *
+ * @param substitutor the Substitutor to be used to convert attributes
+ * and body text or null if no substitution of these values is to be
+ * performed.
+ */
+ public void setSubstitutor(Substitutor substitutor) {
+ this.substitutor = substitutor;
+ }
+
+ /**
+ * Gets the <code>Substitutor</code> used to convert attributes and body text.
+ * @return Substitutor, null if not substitutions are to be performed.
+ */
+ public Substitutor getSubstitutor() {
+ return substitutor;
+ }
+
+ /**
+ * Set the object that the stack should be primed with before parsing
+ * starts.
+ */
+ public void setInitialObject(Object o) {
+ initialObject = o;
+ }
+
+ /**
+ * Return the root of the Context's object stack. Obviously, this method
+ * should not be called until parsing has completed.
+ */
+ public Object getRoot() {
+ if (context == null) {
+ return null;
+ } else {
+ return context.getRoot();
+ }
+ }
+
+ /**
+ * Set the <code>EntityResolver</code> used by SAX when resolving
+ * any entity references present in the input xml (including resolving
+ * any DTD or schema declaration).
+ * <p>
+ * If null is passed, then the default behaviour is restored.
+ * <p>
+ * The default behaviour is to use the default behaviour of the XMLReader,
+ * which is usually to attempt to download from the SYSTEM-ID value (if any)
+ * of the entity definition in the input xml.
+ *
+ * @param entityResolver a class that implement the
+ * <code>EntityResolver</code> interface.
+ */
+ public void setEntityResolver(EntityResolver entityResolver){
+ this.entityResolver = entityResolver;
+ }
+
+ /**
+ * Return the Entity Resolver used by the SAX parser.
+ * @return Return the Entity Resolver used by the SAX parser.
+ */
+ public EntityResolver getEntityResolver(){
+ return entityResolver;
+ }
+
+ /**
+ * Set the error handler. If none is expliticly set, then the default
+ * behaviour occurs, which is to log an error then throw an exception
+ * which terminates the parsing. To restore the default behaviour after
+ * setting an explicit erorr handler, pass <i>null</i>.
+ *
+ * @param errorHandler The new error handler
+ */
+ public void setErrorHandler(ErrorHandler errorHandler) {
+ this.errorHandler = errorHandler;
+ }
+
+ /**
+ * Return the error handler. Null indicates the default behaviour will
+ * be used; see {@link #setErrorHandler}.
+ */
+ public ErrorHandler getErrorHandler() {
+ return errorHandler;
+ }
+
+ /**
+ * Specifies a map of (publicId->URI) pairings that will be used when
+ * resolving entities in the input xml (including the DTD or schema
+ * specified with the DOCTYPE).
+ */
+ public void setKnownEntities(Map knownEntities) {
+ this.knownEntities = knownEntities;
+ }
+
+ /**
+ * Returns a map of (publicId->URI) pairings. See {@link #setKnownEntities}.
+ */
+ public Map getKnownEntities() {
+ return knownEntities;
+ }
+
+ /**
+ * <p>Register a mapping between a public or system ID (denoting an external
+ * entity, aka resource) and a URL indicating where that resource can be
+ * found.</p>
+ *
+ *<p>When the input xml refers to the entity via a public or system id, the
+ * resource pointed to by the registered URL is returned. This is commonly
+ * done for the input document's DTD, so that the DTD can be retrieved
+ * from a local file.</p>
+ *
+ * <p>This implementation provides only basic functionality. If more
+ * sophisticated features are required,using {@link #setEntityResolver} to
+ * set a custom resolver is recommended. Note in particular that if the
+ * input xml uses a system-ID to refer to an entity that is not registered,
+ * then the parser will attempt to use the system-id directly, potentially
+ * downloading the resource from a remote location.</p>
+ *
+ * <p>
+ * <strong>Note:</strong> This method will have no effect when a custom
+ * <code>EntityResolver</code> has been set. (Setting a custom
+ * <code>EntityResolver</code> overrides the internal implementation.)
+ * </p>
+ * @param publicOrSystemId Public or system identifier of the entity to be
+ * resolved
+ * @param entityURL The URL to use for reading this entity
+ */
+ public void registerKnownEntity(String publicOrSystemId, String entityURL) {
+ if (log.isDebugEnabled()) {
+ log.debug("register('" + publicOrSystemId + "', '" + entityURL + "'");
+ }
+ knownEntities.put(publicOrSystemId, entityURL);
+ }
+
+ /**
+ * Add a (pattern, action) pair to the RuleManager instance associated
+ * with this saxHandler. This is equivalent to
+ * <pre>
+ * getRuleManager().addRule(pattern, action);
+ * </pre>
+ */
+ public void addRule(String pattern, Action action)
+ throws InvalidRuleException {
+ ruleManager.addRule(pattern, action);
+ }
+
+ /**
+ * Cleanup method which releases any memory that is no longer needed
+ * after a parse has completed. There is one exception: the root
+ * object generated during the parse is retained so that it can be
+ * retrieved by getRoot(). If this is no longer needed, then setRoot(null)
+ * should be called to release this member.
+ */
+ public void clear() {
+ namespaces.clear();
+
+ // It would be nice to set
+ // context = null;
+ // but currently that would stuff up the getRoot() method.
+ }
+
+ /**
+ * Return the currently mapped namespace URI for the specified prefix,
+ * if any; otherwise return <code>null</code>. These mappings come and
+ * go dynamically as the document is parsed.
+ *
+ * @param prefix Prefix to look up
+ */
+ public String findNamespaceURI(String prefix) {
+ ArrayStack stack = (ArrayStack) namespaces.get(prefix);
+ if (stack == null) {
+ return null;
+ }
+ try {
+ return (String) stack.peek();
+ } catch (EmptyStackException e) {
+ // This should never happen, as endPrefixMapping removes
+ // the prefix from the namespaces map when the stack becomes
+ // empty. Still, better safe than sorry..
+ return null;
+ }
+ }
+
+ /**
+ * Gets the document locator associated with our parser.
+ * See {@link #setDocumentLocator}.
+ *
+ * @return the Locator supplied by the document parser
+ */
+ public Locator getDocumentLocator() {
+ return locator;
+ }
+
+ // -------------------------------------------------------
+ // Package Methods
+ //
+ // These methods are intended mainly for the use of Action
+ // classes and other similar "implementation" classes.
+ // -------------------------------------------------------
+
+ /**
+ * Create a SAX exception which also understands about the location in
+ * the digester file where the exception occurs. This method is expected
+ * to be called by Action classes when they detect a problem.
+ *
+ * @return the new exception
+ */
+ public SAXException createSAXException(String message, Exception e) {
+ if ((e != null) &&
+ (e instanceof InvocationTargetException)) {
+ Throwable t = ((InvocationTargetException) e).getTargetException();
+ if ((t != null) && (t instanceof Exception)) {
+ e = (Exception) t;
+ }
+ }
+
+ if (locator != null) {
+ String error = "Error at (" + locator.getLineNumber() + ", " +
+ locator.getColumnNumber() + ": " + message;
+ if (e != null) {
+ return new SAXParseException(error, locator, e);
+ } else {
+ return new SAXParseException(error, locator);
+ }
+ }
+
+ // The SAX parser doesn't have location info enabled, so we'll just
+ // generate the best error message we can without it.
+ log.error("No Locator!");
+ if (e != null) {
+ return new SAXException(message, e);
+ } else {
+ return new SAXException(message);
+ }
+ }
+
+ /**
+ * Create a SAX exception which also understands about the location in
+ * the digester file where the exception occurs. This method is expected
+ * to be called by Action classes when they detect a problem.
+ *
+ * @return the new exception
+ */
+ public SAXException createSAXException(Exception e) {
+ if (e instanceof InvocationTargetException) {
+ Throwable t = ((InvocationTargetException) e).getTargetException();
+ if ((t != null) && (t instanceof Exception)) {
+ e = (Exception) t;
+ }
+ }
+ return createSAXException(e.getMessage(), e);
+ }
+
+ /**
+ * Create a SAX exception which also understands about the location in
+ * the digester file where the exception occurs. This method is expected
+ * to be called by Action classes when they detect a problem.
+ *
+ * @return the new exception
+ */
+ public SAXException createSAXException(String message) {
+ return createSAXException(message, null);
+ }
+
+ // -------------------------------------------------------
+ // Overridable methods
+ //
+ // These methods provide hooks for users to customise the
+ // behavior of this SAXHandler class by subclassing if they
+ // wish.
+ // -------------------------------------------------------
+
+ /**
+ * <p>Provide a hook for lazy configuration of this <code>SAXHandler</code>
+ * instance. </p>
+ *
+ * <p>This code is called once only, immediately before the first document
+ * is to be parsed. The default implementation does nothing, but subclasses
+ * can override as needed.</p>
+ */
+ private void initialize() {
+ }
+
+ /**
+ * <p>Provide a hook for lazy configuration of this <code>SAXHandler</code>
+ * instance. </p>
+ *
+ * <p>This code is called once at the start of each input document being
+ * parsed. The default implementation does nothing, but subclasses
+ * can override as needed.</p>
+ */
+ private void initializePerParse() {
+ }
+
+ // -------------------------------------------------
+ // Private methods for use of this class only
+ // -------------------------------------------------
+
+ /**
+ * Invoke the initialize and initializePerParse methods.
+ */
+ private void configure() {
+ if (!initialized) {
+ // Perform lazy configuration as needed, by calling a hook method for
+ // subclasses that want to be initialised once only.
+ initialize();
+ initialized = true;
+ }
+
+ initializePerParse();
+ }
+
+ // -------------------------------------------------
+ // ContentHandler Methods
+ // -------------------------------------------------
+
+ /**
+ * Sets the document locator associated with our parser. This method
+ * is called by the sax parser before any other parse events are
+ * dispatched this way. The object that was passed here is then
+ * updated before each SAX event is dispatched to this class.
+ *
+ * @param locator The new locator
+ *
+ * TODO: Consider whether this object (and the associated createSAXException
+ * method) should be on the Context rather than this object, to make it
+ * easier for Action instances to access.
+ *
+ */
+ public void setDocumentLocator(Locator locator) {
+ if (saxLog.isDebugEnabled()) {
+ saxLog.debug("setDocumentLocator(" + locator + ")");
+ }
+
+ this.locator = locator;
+ }
+
+ /**
+ * Process notification of the beginning of the document being reached.
+ * Here we perform all the once-per-input-document initialisation.
+ *
+ * @exception SAXException if a parsing error is to be reported
+ * The SAXException thrown may be of subclass NestedSAXException,
+ * in which case it has a "getCause" method that returns a nested
+ * exception.
+ */
+ public void startDocument() throws SAXException {
+ if (saxLog.isDebugEnabled()) {
+ saxLog.debug("startDocument()");
+ }
+
+ // This shouldn't be necesary if a parse has completed cleanly, as
+ // endPrefixMapping should have been called for each namespace. But
+ // on error, problems can occur.
+ namespaces.clear();
+ bodyText.setLength(0);
+ bodyTexts.clear();
+
+ // Create a new parsing context. This guarantees that Actions have
+ // a clean slate for handling this new input document.
+ context = new Context(this, log);
+
+ if (initialObject != null) {
+ context.setRoot(initialObject);
+ }
+
+ // give subclasses a chance to do custom configuration before each
+ // parse if they wish.
+ configure();
+
+ try {
+ ruleManager.startParse(context);
+ } catch(DigestionException ex) {
+ throw new NestedSAXException(ex);
+ }
+ }
+
+ /**
+ * Process notification of the end of the document being reached.
+ * Here we perform all the once-per-input-document initialisation.
+ * Note, however, that if an error occurs during parsing of an input
+ * document then this method doesn't get called.
+ *
+ * @exception SAXException if a parsing error is to be reported.
+ * The SAXException thrown may be of subclass NestedSAXException,
+ * in which case it has a "getCause" method that returns a nested
+ * exception.
+ */
+ public void endDocument() throws SAXException {
+ if (saxLog.isDebugEnabled()) {
+ if (context.getStackSize() > 1) {
+ // A stack depth of one is ok if an initial object was pushed
+ // onto the stack. More than one is very likely to be an error.
+ saxLog.debug("endDocument(): " + context.getStackSize() +
+ " elements left");
+ } else {
+ saxLog.debug("endDocument()");
+ }
+ }
+
+ // Fire "finish" events for all defined rules
+ try {
+ ruleManager.finishParse(context);
+ } catch(DigestionException ex) {
+ log.error("finishParse threw exception", ex);
+ throw new NestedSAXException(ex);
+ }
+
+ // Perform final cleanup to release memory that is no longer needed.
+ clear();
+ }
+
+ /**
+ * Process notification that a namespace prefix is coming in to scope.
+ *
+ * @param prefix Prefix that is being declared
+ * @param namespaceURI Corresponding namespace URI being mapped to
+ *
+ * @exception SAXException if a parsing error is to be reported
+ */
+ public void startPrefixMapping(String prefix, String namespaceURI) {
+ if (saxLog.isDebugEnabled()) {
+ saxLog.debug(
+ "startPrefixMapping(" + prefix + "," + namespaceURI + ")");
+ }
+
+ // Register this prefix mapping
+ ArrayStack stack = (ArrayStack) namespaces.get(prefix);
+ if (stack == null) {
+ stack = new ArrayStack();
+ namespaces.put(prefix, stack);
+ }
+ stack.push(namespaceURI);
+ }
+
+ /**
+ * Process notification that a namespace prefix is going out of scope.
+ *
+ * @param prefix Prefix that is going out of scope
+ *
+ * @exception SAXException if a parsing error is to be reported
+ */
+ public void endPrefixMapping(String prefix) throws SAXException {
+ if (saxLog.isDebugEnabled()) {
+ saxLog.debug("endPrefixMapping(" + prefix + ")");
+ }
+
+ // Deregister this prefix mapping
+ ArrayStack stack = (ArrayStack) namespaces.get(prefix);
+ if (stack == null) {
+ return;
+ }
+ try {
+ stack.pop();
+ if (stack.empty())
+ namespaces.remove(prefix);
+ } catch (EmptyStackException e) {
+ // This should never happen; it would indicate a serious
+ // internal software flaw.
+ throw createSAXException("endPrefixMapping popped too many times");
+ }
+ }
+
+ /**
+ * Process notification of character data received from the body of
+ * an XML element. Note that a sax parser is allowed to split contiguous
+ * text into multiple calls to this method.
+ *
+ * @param buffer The characters from the XML document
+ * @param start Starting offset into the buffer
+ * @param length Number of characters from the buffer
+ *
+ * @exception SAXException if a parsing error is to be reported
+ */
+ public void characters(char buffer[], int start, int length) {
+ if (saxLog.isDebugEnabled()) {
+ saxLog.debug("characters(" + new String(buffer, start, length) + ")");
+ }
+
+ bodyText.append(buffer, start, length);
+ }
+
+ /**
+ * Process notification of ignorable whitespace received from the body of
+ * an XML element. Ignorable-whitespace is whitespace (tabs, spaces, etc)
+ * within an element which the DTD/schema has stated has "element content"
+ * only; in this case the text is regarded as being for xml layout only,
+ * and is not semantically significant.
+ *
+ * @param buffer The characters from the XML document
+ * @param start Starting offset into the buffer
+ * @param len Number of characters from the buffer
+ *
+ * @exception SAXException if a parsing error is to be reported
+ */
+ public void ignorableWhitespace(char buffer[], int start, int len) {
+ if (saxLog.isDebugEnabled()) {
+ saxLog.debug("ignorableWhitespace(" +
+ new String(buffer, start, len) + ")");
+ }
+
+ ; // No processing required
+ }
+
+ /**
+ * Process notification of a processing instruction that was encountered.
+ * In text form, a processing instruction is of form
+ * <pre>
+ * <? sometext ?>
+ * </pre>
+ *
+ * Note, however, that a DOCTYPE is not a processing instruction, though
+ * it does look like one.
+ *
+ * @param target The processing instruction target
+ * @param data The processing instruction data (if any)
+ *
+ * @exception SAXException if a parsing error is to be reported
+ */
+ public void processingInstruction(String target, String data)
+ throws SAXException {
+ if (saxLog.isDebugEnabled()) {
+ saxLog.debug("processingInstruction('" + target + "','" + data + "')");
+ }
+
+ ; // No processing is required
+ }
+
+ /**
+ * Process notification of a skipped entity.
+ *
+ * TODO: document what a skipped entity is!
+ *
+ * @param name Name of the skipped entity
+ *
+ * @exception SAXException if a parsing error is to be reported
+ */
+ public void skippedEntity(String name) throws SAXException {
+ if (saxLog.isDebugEnabled()) {
+ saxLog.debug("skippedEntity(" + name + ")");
+ }
+
+ ; // No processing required
+ }
+
+ /**
+ * Process notification of the start of an XML element being reached.
+ *
+ * @param namespaceURI The Namespace URI, or the empty string if the element
+ * has no Namespace URI or if Namespace processing is not being performed.
+ * @param localName The local name (without prefix), or the empty
+ * string if Namespace processing is not being performed.
+ * @param qName The qualified name (with prefix), or the empty
+ * string if qualified names are not available.\
+ * @param attrs The attributes attached to the element. If there are
+ * no attributes, it shall be an empty Attributes object.
+ * @exception SAXException if a parsing error is to be reported
+ *
+ * Note that this class expects the XMLReader providing sax events
+ * to be namespace-aware. We do make some effort to make the basics
+ * work without a namespace-aware parser, but no promises...
+ */
+ public void startElement(
+ String namespaceURI, String localName, String qName, Attributes attrs)
+ throws SAXException {
+ boolean debug = log.isDebugEnabled();
+
+ if (saxLog.isDebugEnabled()) {
+ saxLog.debug(
+ "startElement(" + namespaceURI + "," + localName + "," +
+ qName + ")");
+ }
+
+ // Save the body text accumulated for our surrounding element
+ bodyTexts.push(bodyText);
+ if (debug) {
+ log.debug(" Pushing body text '" + bodyText.toString() + "'");
+ }
+ bodyText = new StringBuffer();
+
+ // the actual element name is either in localName or qName, depending
+ // on whether the parser is namespace aware
+ String name = localName;
+ if ((name == null) || (name.length() < 1)) {
+ name = qName;
+ }
+
+ // Compute the current matching rule
+ String matchPath = context.pushMatchPath(namespaceURI, name);
+ if (debug) {
+ log.debug(" New matchpath='" + matchPath + "'");
+ }
+
+ // Fire "begin" events for all relevant rules
+ List actions;
+ try {
+ actions = ruleManager.getMatchingActions(matchPath);
+ } catch(DigestionException ex) {
+ throw new NestedSAXException(ex);
+ }
+
+ context.pushMatchingActions(actions);
+ if ((actions != null) && !actions.isEmpty()) {
+ Substitutor substitutor = getSubstitutor();
+ if (substitutor!= null) {
+ attrs = substitutor.substitute(attrs);
+ }
+ for (int i = 0; i < actions.size(); ++i) {
+ try {
+ Action action = (Action) actions.get(i);
+ if (debug) {
+ log.debug(" Fire begin() for " + action);
+ }
+ action.begin(context, namespaceURI, name, attrs);
+ } catch (Exception e) {
+ log.error("Begin event threw exception", e);
+ throw createSAXException(e);
+ } catch (Error e) {
+ log.error("Begin event threw error", e);
+ throw e;
+ }
+ }
+ } else {
+ if (debug) {
+ log.debug(" No rules found matching '" + matchPath + "'.");
+ }
+ }
+ }
+
+ /**
+ * Process notification of the end of an XML element being reached.
+ * Here we retrieve the set of matching actions, then fire the body()
+ * methods in order, followed by the end() methods in reverse order.
+ *
+ * @param namespaceURI - The Namespace URI, or the empty string if the
+ * element has no Namespace URI.
+ * @param localName - The local name (without prefix).
+ * @param qName - The qualified XML 1.0 name (with prefix), or the
+ * empty string if qualified names are not available.
+ * @exception SAXException if a parsing error is to be reported
+ */
+ public void endElement(String namespaceURI, String localName,
+ String qName) throws SAXException {
+ boolean debug = log.isDebugEnabled();
+ String matchPath = context.getMatchPath();
+
+ if (debug) {
+ if (saxLog.isDebugEnabled()) {
+ saxLog.debug("endElement(" + namespaceURI + "," + localName +
+ "," + qName + ")");
+ }
+ log.debug(" matchPath='" + matchPath + "'");
+ log.debug(" bodyText='" + bodyText + "'");
+ }
+
+ // The actual element name is either in localName or qName, depending
+ // on whether the parser is namespace aware. We do of course expect
+ // the parser to be namespace-aware, but there's no harm in handling
+ // the non-namespace case here anyway.
+ String name = localName;
+ if ((name == null) || (name.length() < 1)) {
+ name = qName;
+ }
+
+ // Fire "body" events for all relevant rules
+ List actions = (List) context.peekMatchingActions();
+ if ((actions != null) && (actions.size() > 0)) {
+ String bodyText = this.bodyText.toString();
+ Substitutor substitutor = getSubstitutor();
+ if (substitutor!= null) {
+ bodyText = substitutor.substitute(bodyText);
+ }
+ for (int i = 0; i < actions.size(); ++i) {
+ try {
+ Action action = (Action) actions.get(i);
+ if (debug) {
+ log.debug(" Fire body() for " + action);
+ }
+ action.body(context, namespaceURI, name, bodyText);
+ } catch (Exception e) {
+ log.error("Body event threw exception", e);
+ throw createSAXException(e);
+ } catch (Error e) {
+ log.error("Body event threw error", e);
+ throw e;
+ }
+ }
+ } else {
+ if (debug) {
+ log.debug(" No rules found matching '" + matchPath + "'.");
+ }
+ }
+
+ // Restore the body text for the parent element (now that we have
+ // finished with the body text for this current element).
+ bodyText = (StringBuffer) bodyTexts.pop();
+ if (debug) {
+ log.debug(" Popping body text '" + bodyText.toString() + "'");
+ }
+
+ // Fire "end" events for all relevant rules in reverse order
+ // Note that the actions list is actually an ArrayStack, so
+ // out-of-order access isn't a performance problem.
+ if (actions != null) {
+ for (int i = actions.size() - 1; i >= 0; --i) {
+ try {
+ Action action = (Action) actions.get(i);
+ if (debug) {
+ log.debug(" Fire end() for " + action);
+ }
+ action.end(context, namespaceURI, name);
+ } catch (Exception e) {
+ log.error("End event threw exception", e);
+ throw createSAXException(e);
+ } catch (Error e) {
+ log.error("End event threw error", e);
+ throw e;
+ }
+ }
+ }
+
+ // Recover the previous match expression
+ context.popMatchPath();
+
+ // Discard the list of matching actions
+ context.popMatchingActions();
+ }
+
+ // -----------------------------------------------------
+ // DTDHandler Methods
+ // -----------------------------------------------------
+
+ /**
+ * Receive notification of a notation declaration event. Currently
+ * we just log these.
+ *
+ * @param name The notation name
+ * @param publicId The public identifier (if any)
+ * @param systemId The system identifier (if any)
+ */
+ public void notationDecl(String name, String publicId, String systemId) {
+ if (saxLog.isDebugEnabled()) {
+ saxLog.debug("notationDecl(" + name + "," + publicId + "," +
+ systemId + ")");
+ }
+ }
+
+ /**
+ * Receive notification of an unparsed entity declaration event.
+ *
+ * @param name The unparsed entity name
+ * @param publicId The public identifier (if any)
+ * @param systemId The system identifier (if any)
+ * @param notation The name of the associated notation
+ */
+ public void unparsedEntityDecl(String name, String publicId,
+ String systemId, String notation) {
+ if (saxLog.isDebugEnabled()) {
+ saxLog.debug("unparsedEntityDecl(" + name + "," + publicId + "," +
+ systemId + "," + notation + ")");
+ }
+ }
+
+ // -----------------------------------------------
+ // EntityResolver Methods
+ // -----------------------------------------------
+
+ /**
+ * Resolve the requested external entity. The procedure used is:
+ * <ul>
+ * <li>if the user has registered a custom entity resolver, then invoke
+ * that; otherwise </li>
+ * <li>if the entities' public or system id has been registered via the
+ * method Digester.registerknownEntity or SAXHandler.setKnownEntities, then
+ * load the entity from the specified URL; otherwise</li>
+ * <li>if the input xml defines a system-id for the entity, then use that
+ * as a URL; otherwise</li>
+ * <li>fall back to whatever the default behaviour is for the XMLParser.</li>
+ * </ul>
+ *
+ * @param publicId The public identifier of the entity being referenced
+ * @param systemId The system identifier of the entity being referenced
+ *
+ * @exception SAXException if a parsing exception occurs
+ *
+ */
+ public InputSource resolveEntity(String publicId, String systemId)
+ throws SAXException, IOException {
+ if (saxLog.isDebugEnabled()) {
+ saxLog.debug("resolveEntity('" + publicId + "', '" + systemId + "')");
+ }
+
+ if (entityResolver != null) {
+ // the user has specified their own EntityResolver, so we just
+ // forward the call to that object:
+ return entityResolver.resolveEntity(publicId, systemId);
+ }
+
+ // TODO: fix this. We can't assume that every external entity
+ // is the doctype!!!!!!
+ if (publicId != null)
+ this.publicId = publicId;
+
+ // Has this system identifier been registered?
+ String entityURL = null;
+ if (publicId != null) {
+ entityURL = (String) knownEntities.get(publicId);
+ }
+
+ // Redirect the schema location to a local destination
+ if (schemaLocation != null && entityURL == null && systemId != null){
+ entityURL = (String)knownEntities.get(systemId);
+ }
+
+ if (entityURL == null) {
+ if (systemId == null) {
+ // cannot resolve
+ if (log.isDebugEnabled()) {
+ log.debug(" Cannot resolve entity: '" + entityURL + "'");
+ }
+ return null;
+
+ } else {
+ // try to resolve using system ID
+ if (log.isDebugEnabled()) {
+ log.debug(" Trying to resolve using system ID '" + systemId + "'");
+ }
+ entityURL = systemId;
+ }
+ }
+
+ // Return an input source to our alternative URL
+ if (log.isDebugEnabled()) {
+ log.debug(" Resolving to alternate DTD '" + entityURL + "'");
+ }
+
+ try {
+ return (new InputSource(entityURL));
+ } catch (Exception e) {
+ throw createSAXException(e);
+ }
+ }
+
+ // -------------------------------------------------
+ // ErrorHandler Methods
+ // -------------------------------------------------
+
+ /**
+ * Handle notification from the XMLReader of a problem in the input xml.
+ * <p>
+ * If the user has registered a custom ErrorHandler via setErrorHandler
+ * then the notification is forwarded to the user object.
+ * <p>
+ * If there is no custom ErrorHandler, then this method does nothing.
+ *
+ * @param exception The warning information
+ *
+ * @exception SAXException if a parsing exception occurs
+ */
+ public void warning(SAXParseException exception) throws SAXException {
+ log.warn(
+ "Parse Warning Error at line " + exception.getLineNumber() +
+ " column " + exception.getColumnNumber() + ": " +
+ exception.getMessage(), exception);
+
+ // default behaviour: ignore the warning
+ if (errorHandler != null) {
+ errorHandler.warning(exception);
+ }
+ }
+
+ /**
+ * Handle notification from the XMLReader of an error in the input xml.
+ * <p>
+ * If the user has registered a custom ErrorHandler via setErrorHandler
+ * then the notification is forwarded to the user object (which will
+ * normally throw the exception provided as a parameter after doing any
+ * custom processing).
+ * <p>
+ * If there is no custom ErrorHandler, then this method just throws the
+ * exception provided as a parameter.
+ *
+ * @param exception The error information
+ *
+ * @exception SAXException if a parsing exception occurs
+ */
+ public void error(SAXParseException exception) throws SAXException {
+ log.error("Parse Error at line " + exception.getLineNumber() +
+ " column " + exception.getColumnNumber() + ": " +
+ exception.getMessage(), exception);
+
+ if (errorHandler == null) {
+ // default behaviour: report the error
+ throw exception;
+ } else {
+ errorHandler.error(exception);
+ }
+ }
+
+
+ /**
+ * Handle notification from the XMLReader of an error in the input xml.
+ * <p>
+ * If the user has registered a custom ErrorHandler via setErrorHandler
+ * then the notification is forwarded to the user object (which will
+ * normally throw the exception provided as a parameter after doing any
+ * custom processing).
+ * <p>
+ * If there is no custom ErrorHandler, then this method just throws the
+ * exception provided as a parameter.
+ *
+ * @param exception The fatal error information
+ *
+ * @exception SAXException if a parsing exception occurs
+ */
+ public void fatalError(SAXParseException exception) throws SAXException {
+ log.error("Parse Fatal Error at line " + exception.getLineNumber() +
+ " column " + exception.getColumnNumber() + ": " +
+ exception.getMessage(), exception);
+
+ if (errorHandler == null) {
+ // default behaviour: report the fatal error
+ throw exception;
+ } else {
+ errorHandler.error(exception);
+ }
+ }
+}
Added: jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/Substitutor.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/Substitutor.java?view=auto&rev=151287
==============================================================================
--- jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/Substitutor.java (added)
+++ jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/Substitutor.java Thu Feb 3 17:51:43 2005
@@ -0,0 +1,66 @@
+/* $Id: $
+ *
+ * Copyright 2003-2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.digester2;
+
+import org.xml.sax.Attributes;
+
+/**
+ * <p>(Logical) Interface for substitution strategies.
+ * (It happens to be implemented as a Java abstract class to allow
+ * future additions to be made without breaking backwards compatibility.)
+ * </p>
+ * <p>
+ * Usage: When {@link Digester#setSubstitutor} is set, <code>Digester</code>
+ * calls the methods in this interface to create substitute values which will
+ * be passed into the Rule implementations.
+ * Of course, it is perfectly acceptable for implementations not to make
+ * substitutions and simply return the inputs.
+ * </p>
+ * <p>Different strategies are supported for attributes and body text.</p>
+ *
+ * @since 1.6
+ */
+public abstract class Substitutor {
+
+ /**
+ * <p>Substitutes the attributes (before they are passed to the
+ * <code>Rule</code> implementations's).</p>
+ *
+ * <p><code>Digester</code> will only call this method a second time
+ * once the original <code>Attributes</code> instance can be safely reused.
+ * The implementation is therefore free to reuse the same <code>Attributes</code> instance
+ * for all calls.</p>
+ *
+ * @param attributes the <code>Attributes</code> passed into <code>Digester</code> by the SAX parser,
+ * not null (but may be empty)
+ * @return <code>Attributes</code> to be passed to the <code>Rule</code> implementations.
+ * This method may pass back the Attributes passed in.
+ * Not null but possibly empty.
+ */
+ public abstract Attributes substitute(Attributes attributes);
+
+ /**
+ * Substitutes for the body text.
+ * This method may substitute values into the body text of the
+ * elements that Digester parses.
+ *
+ * @param bodyText the body text (as passed to <code>Digester</code>)
+ * @return the body text to be passed to the <code>Rule</code> implementations
+ */
+ public abstract String substitute(String bodyText);
+}
Added: jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/actions/AbstractObjectCreationFactory.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/actions/AbstractObjectCreationFactory.java?view=auto&rev=151287
==============================================================================
--- jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/actions/AbstractObjectCreationFactory.java (added)
+++ jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/actions/AbstractObjectCreationFactory.java Thu Feb 3 17:51:43 2005
@@ -0,0 +1,42 @@
+/* $Id: $
+ *
+ * Copyright 2001-2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.digester2.actions;
+
+import org.xml.sax.Attributes;
+import org.apache.commons.digester2.Context;
+import org.apache.commons.digester2.ParseException;
+
+/**
+ * <p>Abstract base class for <code>ObjectCreationFactory</code>
+ * implementations.</p>
+ */
+abstract public class AbstractObjectCreationFactory implements ObjectCreationFactory {
+
+ // --------------------------------------------------------- Public Methods
+
+ /**
+ * <p>Factory method called by {@link CreateObjectWithFactoryAction} to
+ * supply an object based on the element's attributes.
+ *
+ * @param attributes the element's attributes
+ *
+ * @throws Exception any exception thrown will be propagated upwards
+ */
+ public abstract Object createObject(Context context, Attributes attributes)
+ throws ParseException;
+
+}
Added: jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/actions/BeanPropertySetterAction.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/actions/BeanPropertySetterAction.java?view=auto&rev=151287
==============================================================================
--- jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/actions/BeanPropertySetterAction.java (added)
+++ jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/actions/BeanPropertySetterAction.java Thu Feb 3 17:51:43 2005
@@ -0,0 +1,203 @@
+/* $Id: ActionBeanPropertySetter.java,v 1.20 2004/05/10 06:30:06 skitching Exp $
+ *
+ * Copyright 2001-2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.commons.digester2.actions;
+
+
+import java.beans.PropertyDescriptor;
+
+import org.apache.commons.beanutils.BeanUtils;
+import org.apache.commons.beanutils.DynaBean;
+import org.apache.commons.beanutils.DynaProperty;
+import org.apache.commons.beanutils.PropertyUtils;
+import org.apache.commons.logging.Log;
+
+import org.apache.commons.digester2.Context;
+import org.apache.commons.digester2.AbstractAction;
+import org.apache.commons.digester2.ParseException;
+
+/**
+ * <p> Action which sets a bean property on the top object to the body text.</p>
+ *
+ * <p> The property set:</p>
+ * <ul><li>can be specified when the rule is created</li>
+ * <li>or can match the current element when the rule is called.</li></ul>
+ *
+ * <p> Using the second method and the {@link ExtendedRuleManager} child match
+ * pattern, all the child elements can be automatically mapped to properties
+ * on the parent object.</p>
+ */
+
+public class BeanPropertySetterAction extends AbstractAction {
+
+ // ----------------------------------------------------------- Constructors
+
+ /**
+ * <p>Construct instance that sets the given property from the body text.</p>
+ *
+ * @param propertyName name of property to set
+ */
+ public BeanPropertySetterAction(String propertyName) {
+ this.propertyName = propertyName;
+ }
+
+ /**
+ * <p>Construct instance that automatically sets a property from the body text.
+ *
+ * <p> This construct creates an action that sets the property
+ * on the top object named the same as the current element.
+ */
+ public BeanPropertySetterAction() {
+ this((String)null);
+ }
+
+ // ----------------------------------------------------- Instance Variables
+
+ /**
+ * Set this property on the top object.
+ */
+ protected String propertyName = null;
+
+ /**
+ * The body text used to set the property.
+ */
+ protected String bodyText = null;
+
+ // --------------------------------------------------------- Public Methods
+
+ /**
+ * Process the body text of this element.
+ *
+ * @param context is the current parse context.
+ * @param namespace the namespace URI of the matching element, or an
+ * empty string if the element has no namespace
+ * @param name the local name of the element.
+ * @param text The text of the body of this element
+ */
+ public void body(Context context, String namespace, String name, String text)
+ throws ParseException {
+
+ // log some debugging information
+ Log log = context.getLogger();
+ if (log.isDebugEnabled()) {
+ log.debug(
+ "[ActionBeanPropertySetter] Called with text '" + text + "'"
+ + " at path '" + context.getMatchPath() + "'");
+ }
+
+ bodyText = text.trim();
+ }
+
+ /**
+ * Process the end of this element.
+ *
+ * @param context is the current parse context.
+ * @param namespace the namespace URI of the matching element, or an
+ * empty string if the element has no namespace
+ * @param name the local name of the element.
+ *
+ * @exception NoSuchMethodException if the bean does not
+ * have a writeable property of the specified name
+ */
+ public void end(Context context, String namespace, String name)
+ throws ParseException {
+
+ String property = propertyName;
+
+ if (property == null) {
+ // If we don't have a specific property name,
+ // use the element name.
+ property = name;
+ }
+
+ // Get a reference to the top object
+ Object top = context.peek();
+
+ // log some debugging information
+ Log log = context.getLogger();
+ if (log.isDebugEnabled()) {
+ log.debug(
+ "[ActionBeanPropertySetter]"
+ + " path='" + context.getMatchPath() + "'"
+ + ": class='" + top.getClass().getName() + "'"
+ + ": property '" + property + "'"
+ + " with text '" + bodyText + "'");
+ }
+
+ // Force an exception if the property does not exist
+ // (BeanUtils.setProperty() silently returns in this case)
+ if (top instanceof DynaBean) {
+ DynaProperty desc =
+ ((DynaBean) top).getDynaClass().getDynaProperty(property);
+ if (desc == null) {
+ throw new ParseException
+ ("DynaBean has no property named " + property);
+ }
+ } else /* this is a standard JavaBean */ {
+ PropertyDescriptor desc;
+ try {
+ desc = PropertyUtils.getPropertyDescriptor(top, property);
+ } catch(Exception ex) {
+ throw new ParseException(
+ "Unable to access properties for bean of class '"
+ + top.getClass().getName() + "'", ex);
+ }
+ if (desc == null) {
+ throw new ParseException(
+ "Bean of class '" + top.getClass().getName() + "'"
+ + " has no property named '" + property + "'");
+ }
+ }
+
+ // Set the property (with conversion as necessary)
+ try {
+ BeanUtils.setProperty(top, property, bodyText);
+ } catch(Exception ex) {
+ throw new ParseException(
+ "Unable to set property '" + property + "' for bean of class '"
+ + top.getClass().getName() + "'", ex);
+ }
+ }
+
+ /**
+ * Init before parsing commences (just in case a previous parse has
+ * failed, leaving garbage).
+ */
+ public void startParse(Context context) throws ParseException {
+ bodyText = null;
+ }
+
+ /**
+ * Clean up after parsing is complete.
+ */
+ public void finishParse(Context context) throws ParseException {
+ bodyText = null;
+ }
+
+ /**
+ * Render a printable version of this Actuib.
+ */
+ public String toString() {
+
+ StringBuffer sb = new StringBuffer("ActionBeanPropertySetter[");
+ sb.append("propertyName=");
+ sb.append(propertyName);
+ sb.append("]");
+ return (sb.toString());
+ }
+}
Added: jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/actions/CallMethodAction.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/actions/CallMethodAction.java?view=auto&rev=151287
==============================================================================
--- jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/actions/CallMethodAction.java (added)
+++ jakarta/commons/proper/digester/branches/digester2/src/java/org/apache/commons/digester2/actions/CallMethodAction.java Thu Feb 3 17:51:43 2005
@@ -0,0 +1,545 @@
+/* $Id: $
+ *
+ * Copyright 2001-2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.commons.digester2.actions;
+
+
+import org.apache.commons.beanutils.ConvertUtils;
+import org.apache.commons.beanutils.MethodUtils;
+import org.apache.commons.logging.Log;
+import org.xml.sax.Attributes;
+import java.lang.reflect.InvocationTargetException;
+
+import org.apache.commons.digester2.Context;
+import org.apache.commons.digester2.AbstractAction;
+import org.apache.commons.digester2.ParseException;
+
+/**
+ * <p>Action that calls a method on an object on the stack
+ * (normally the top/parent object), passing arguments collected from
+ * subsequent <code>ActionCallParam</code> actions or from the body of this
+ * element. </p>
+ *
+ * <p>By using {@link #ActionCallMethod(String methodName)}
+ * a method call can be made to a method which accepts no
+ * arguments.</p>
+ *
+ * <p>Incompatible method parameter types are converted
+ * using <code>org.apache.commons.beanutils.ConvertUtils</code>.
+ * </p>
+ *
+ * <p>Note that the target method is invoked when the <i>end</i> of
+ * the tag the CallMethodAction fired on is encountered, <i>not</i> when the
+ * last parameter becomes available. This implies that rules which fire on
+ * tags nested within the one associated with the CallMethodAction will
+ * fire before the CallMethodAction invokes the target method. This behaviour is
+ * not configurable. </p>
+ *
+ * <p>Note also that if a CallMethodAction is expecting exactly one parameter
+ * and that parameter is not available (eg CallParamAction is used with an
+ * attribute name but the attribute does not exist) then the method will
+ * not be invoked. If a CallMethodAction is expecting more than one parameter,
+ * then it is always invoked, regardless of whether the parameters were
+ * available or not (missing parameters are passed as null values).</p>
+ */
+
+public class CallMethodAction extends AbstractAction {
+
+ // ----------------------------------------------------------- Constructors
+
+ /**
+ * Construct a "call method" instance with the specified method name. The
+ * parameter types (if any) default to java.lang.String.
+ *
+ * @param methodName Method name of the parent method to call
+ * @param paramCount The number of parameters to collect, or
+ * zero for a single argument from the body of this element.
+ */
+ public CallMethodAction(String methodName,
+ int paramCount) {
+ this(0, methodName, paramCount);
+ }
+
+ /**
+ * Construct a "call method" instance with the specified method name. The
+ * parameter types (if any) default to java.lang.String.
+ *
+ * @param targetOffset location of the target object. Positive numbers are
+ * relative to the top of the digester object stack. Negative numbers
+ * are relative to the bottom of the stack. Zero implies the top
+ * object on the stack.
+ * @param methodName Method name of the parent method to call
+ * @param paramCount The number of parameters to collect, or
+ * zero for a single argument from the body of this element.
+ */
+ public CallMethodAction(int targetOffset,
+ String methodName,
+ int paramCount) {
+
+ this.targetOffset = targetOffset;
+ this.methodName = methodName;
+ this.paramCount = paramCount;
+ if (paramCount == 0) {
+ this.paramTypes = new Class[] { String.class };
+ } else {
+ this.paramTypes = new Class[paramCount];
+ for (int i = 0; i < this.paramTypes.length; i++) {
+ this.paramTypes[i] = String.class;
+ }
+ }
+ }
+
+ /**
+ * Construct a "call method" instance with the specified method name.
+ * The method should accept no parameters.
+ *
+ * @param methodName Method name of the parent method to call
+ */
+ public CallMethodAction(String methodName) {
+ this(0, methodName, 0, (Class[]) null);
+ }
+
+ /**
+ * Construct a "call method" instance with the specified method name.
+ * The method should accept no parameters.
+ *
+ * @param targetOffset location of the target object. Positive numbers are
+ * relative to the top of the digester object stack. Negative numbers
+ * are relative to the bottom of the stack. Zero implies the top
+ * object on the stack.
+ * @param methodName Method name of the parent method to call
+ */
+ public CallMethodAction(int targetOffset, String methodName) {
+ this(targetOffset, methodName, 0, (Class[]) null);
+ }
+
+ /**
+ * Construct a "call method" rule with the specified method name and
+ * parameter types. If <code>paramCount</code> is set to zero the rule
+ * will use the body of this element as the single argument of the
+ * method, unless <code>paramTypes</code> is null or empty, in this
+ * case the rule will call the specified method with no arguments.
+ *
+ * @param methodName Method name of the parent method to call
+ * @param paramCount The number of parameters to collect, or
+ * zero for a single argument from the body of ths element
+ * @param paramTypes The Java class names of the arguments
+ * (if you wish to use a primitive type, specify the corresonding
+ * Java wrapper class instead, such as <code>java.lang.Boolean</code>
+ * for a <code>boolean</code> parameter)
+ */
+ public CallMethodAction(
+ String methodName,
+ int paramCount,
+ String paramTypes[]) {
+ this(0, methodName, paramCount, paramTypes);
+ }
+
+ /**
+ * Construct a "call method" rule with the specified method name and
+ * parameter types. If <code>paramCount</code> is set to zero the rule
+ * will use the body of this element as the single argument of the
+ * method, unless <code>paramTypes</code> is null or empty, in this
+ * case the rule will call the specified method with no arguments.
+ *
+ * @param targetOffset location of the target object. Positive numbers are
+ * relative to the top of the digester object stack. Negative numbers
+ * are relative to the bottom of the stack. Zero implies the top
+ * object on the stack.
+ * @param methodName Method name of the parent method to call
+ * @param paramCount The number of parameters to collect, or
+ * zero for a single argument from the body of ths element
+ * @param paramTypes The Java class names of the arguments
+ * (if you wish to use a primitive type, specify the corresonding
+ * Java wrapper class instead, such as <code>java.lang.Boolean</code>
+ * for a <code>boolean</code> parameter)
+ */
+ public CallMethodAction( int targetOffset,
+ String methodName,
+ int paramCount,
+ String paramTypes[]) {
+
+ this.targetOffset = targetOffset;
+ this.methodName = methodName;
+ this.paramCount = paramCount;
+ if (paramTypes == null) {
+ this.paramTypes = new Class[paramCount];
+ for (int i = 0; i < this.paramTypes.length; i++) {
+ this.paramTypes[i] = "abc".getClass();
+ }
+ } else {
+ // copy the parameter class names into an array
+ // the classes will be loaded when the digester is set
+ this.paramClassNames = new String[paramTypes.length];
+ for (int i = 0; i < this.paramClassNames.length; i++) {
+ this.paramClassNames[i] = paramTypes[i];
+ }
+ }
+
+ }
+
+
+ /**
+ * Construct a "call method" rule with the specified method name and
+ * parameter types. If <code>paramCount</code> is set to zero the rule
+ * will use the body of this element as the single argument of the
+ * method, unless <code>paramTypes</code> is null or empty, in this
+ * case the rule will call the specified method with no arguments.
+ *
+ * @param methodName Method name of the parent method to call
+ * @param paramCount The number of parameters to collect, or
+ * zero for a single argument from the body of ths element
+ * @param paramTypes The Java classes that represent the
+ * parameter types of the method arguments
+ * (if you wish to use a primitive type, specify the corresonding
+ * Java wrapper class instead, such as <code>java.lang.Boolean.TYPE</code>
+ * for a <code>boolean</code> parameter)
+ */
+ public CallMethodAction(
+ String methodName,
+ int paramCount,
+ Class paramTypes[]) {
+ this(0, methodName, paramCount, paramTypes);
+ }
+
+ /**
+ * Construct a "call method" rule with the specified method name and
+ * parameter types. If <code>paramCount</code> is set to zero the rule
+ * will use the body of this element as the single argument of the
+ * method, unless <code>paramTypes</code> is null or empty, in this
+ * case the rule will call the specified method with no arguments.
+ *
+ * @param targetOffset location of the target object. Positive numbers are
+ * relative to the top of the digester object stack. Negative numbers
+ * are relative to the bottom of the stack. Zero implies the top
+ * object on the stack.
+ * @param methodName Method name of the parent method to call
+ * @param paramCount The number of parameters to collect, or
+ * zero for a single argument from the body of ths element
+ * @param paramTypes The Java classes that represent the
+ * parameter types of the method arguments
+ * (if you wish to use a primitive type, specify the corresonding
+ * Java wrapper class instead, such as <code>java.lang.Boolean.TYPE</code>
+ * for a <code>boolean</code> parameter)
+ */
+ public CallMethodAction( int targetOffset,
+ String methodName,
+ int paramCount,
+ Class paramTypes[]) {
+
+ this.targetOffset = targetOffset;
+ this.methodName = methodName;
+ this.paramCount = paramCount;
+ if (paramTypes == null) {
+ this.paramTypes = new Class[paramCount];
+ for (int i = 0; i < this.paramTypes.length; i++) {
+ this.paramTypes[i] = "abc".getClass();
+ }
+ } else {
+ this.paramTypes = new Class[paramTypes.length];
+ for (int i = 0; i < this.paramTypes.length; i++) {
+ this.paramTypes[i] = paramTypes[i];
+ }
+ }
+
+ }
+
+ // ----------------------------------------------------- Instance Variables
+
+ /**
+ * The body text collected from this element.
+ */
+ protected String bodyText = null;
+
+ /**
+ * location of the target object for the call, relative to the
+ * top of the digester object stack. The default value of zero
+ * means the target object is the one on top of the stack.
+ */
+ private int targetOffset = 0;
+
+ /**
+ * The method name to call on the parent object.
+ */
+ protected String methodName = null;
+
+ /**
+ * The number of parameters to collect from <code>MethodParam</code> rules.
+ * If this value is zero, a single parameter will be collected from the
+ * body of this element.
+ */
+ protected int paramCount = 0;
+
+ /**
+ * The parameter types of the parameters to be collected.
+ */
+ protected Class paramTypes[] = null;
+
+ /**
+ * The names of the classes of the parameters to be collected.
+ * This attribute allows creation of the classes to be postponed until the digester is set.
+ */
+ private String paramClassNames[] = null;
+
+ // --------------------------------------------------------- Public Methods
+
+ /**
+ * If needed, this class loads the parameter classes from their names.
+ *
+ * TODO: Fix this: init method is not allowed to modify object state.
+ * What this probably implies is that the paramTypes member needs to
+ * be stored on the Context object.
+ */
+ public void startParse(Context context)
+ {
+ // if necessary, load parameter classes
+ if (this.paramClassNames != null) {
+ this.paramTypes = new Class[paramClassNames.length];
+ for (int i = 0; i < this.paramClassNames.length; i++) {
+ try {
+ this.paramTypes[i] =
+ context.getClassLoader().loadClass(this.paramClassNames[i]);
+ } catch (ClassNotFoundException e) {
+ // use the digester log
+ Log log = context.getLogger();
+ log.error("(ActionCallMethod) Cannot load class " + this.paramClassNames[i], e);
+ this.paramTypes[i] = null; // Will cause NPE later
+ }
+ }
+ }
+ }
+
+ /**
+ * Process the start of this element.
+ *
+ * @param attributes The attribute list for this element
+ */
+ public void begin(Context context, String namespace, String name, Attributes attributes)
+ throws ParseException {
+
+ // Push an array to capture the parameter values if necessary
+ if (paramCount > 0) {
+ Object parameters[] = new Object[paramCount];
+ for (int i = 0; i < parameters.length; i++) {
+ parameters[i] = null;
+ }
+ context.pushParams(parameters);
+ }
+ }
+
+ /**
+ * Process the body text of this element.
+ *
+ * TODO: FIXME, the bodyText field should be stored on the Context object.
+ *
+ * @param text The body text of this element
+ */
+ public void body(Context context, String namespace, String name, String text)
+ throws ParseException {
+
+ if (paramCount == 0) {
+ this.bodyText = text.trim();
+ }
+ }
+
+
+ /**
+ * Process the end of this element.
+ */
+ public void end(Context context, String namespace, String name)
+ throws ParseException {
+
+ Log log = context.getLogger();
+
+ // Retrieve or construct the parameter values array
+ Object parameters[] = null;
+ if (paramCount > 0) {
+
+ parameters = (Object[]) context.popParams();
+ if (log.isTraceEnabled()) {
+ for (int i=0,size=parameters.length;i<size;i++) {
+ log.trace("[ActionCallMethod](" + i + ")" + parameters[i]) ;
+ }
+ }
+
+ // In the case where the parameter for the method
+ // is taken from an attribute, and that attribute
+ // isn't actually defined in the source XML file,
+ // skip the method call
+ if (paramCount == 1 && parameters[0] == null) {
+ return;
+ }
+
+ } else if (paramTypes != null && paramTypes.length != 0) {
+
+ // In the case where the parameter for the method
+ // is taken from the body text, but there is no
+ // body text included in the source XML file,
+ // skip the method call
+ if (bodyText == null) {
+ return;
+ }
+
+ parameters = new Object[1];
+ parameters[0] = bodyText;
+ if (paramTypes.length == 0) {
+ paramTypes = new Class[1];
+ paramTypes[0] = "abc".getClass();
+ }
+
+ }
+
+ // Construct the parameter values array we will need
+ // We only do the conversion if the param value is a String and
+ // the specified paramType is not String.
+ Object paramValues[] = new Object[paramTypes.length];
+ for (int i = 0; i < paramTypes.length; i++) {
+ // convert nulls and convert stringy parameters
+ // for non-stringy param types
+ if(
+ parameters[i] == null ||
+ (parameters[i] instanceof String &&
+ !String.class.isAssignableFrom(paramTypes[i]))) {
+
+ paramValues[i] =
+ ConvertUtils.convert((String) parameters[i], paramTypes[i]);
+ } else {
+ paramValues[i] = parameters[i];
+ }
+ }
+
+ // Determine the target object for the method call
+ Object target;
+ if (targetOffset >= 0) {
+ target = context.peek(targetOffset);
+ } else {
+ target = context.peek(context.getStackSize() + targetOffset );
+ }
+
+ if (target == null) {
+ StringBuffer sb = new StringBuffer();
+ sb.append("[ActionCallMethod]{");
+ sb.append(context.getMatchPath());
+ sb.append("} Call target is null (");
+ sb.append("targetOffset=");
+ sb.append(targetOffset);
+ sb.append(",stackdepth=");
+ sb.append(context.getStackSize());
+ sb.append(")");
+ throw new ParseException(sb.toString());
+ }
+
+ // Invoke the required method on the top object
+ if (log.isDebugEnabled()) {
+ StringBuffer sb = new StringBuffer("[ActionCallMethod]{");
+ sb.append(context.getMatchPath());
+ sb.append("} Call ");
+ sb.append(target.getClass().getName());
+ sb.append(".");
+ sb.append(methodName);
+ sb.append("(");
+ for (int i = 0; i < paramValues.length; i++) {
+ if (i > 0) {
+ sb.append(",");
+ }
+ if (paramValues[i] == null) {
+ sb.append("null");
+ } else {
+ sb.append(paramValues[i].toString());
+ }
+ sb.append("/");
+ if (paramTypes[i] == null) {
+ sb.append("null");
+ } else {
+ sb.append(paramTypes[i].getName());
+ }
+ }
+ sb.append(")");
+ log.debug(sb.toString());
+ }
+
+ try {
+ Object result = MethodUtils.invokeMethod(
+ target, methodName,
+ paramValues, paramTypes);
+
+ processMethodCallResult(result);
+ }
+ catch(NoSuchMethodException ex) {
+ throw new ParseException(
+ "No such method: " + methodName
+ + " on object type:" + target.getClass().getName(),
+ ex);
+ }
+ catch(IllegalAccessException ex) {
+ throw new ParseException(
+ "Unable to access method: " + methodName
+ + " on object type:" + target.getClass().getName(),
+ ex);
+ }
+ catch(InvocationTargetException ex) {
+ throw new ParseException(
+ "Method: " + methodName
+ + " on object type:" + target.getClass().getName()
+ + " threw an exception when it was invoked.",
+ ex);
+ }
+ }
+
+
+ /**
+ * Clean up after parsing is complete.
+ */
+ public void finishParse(Context context) throws ParseException {
+ bodyText = null;
+ }
+
+ /**
+ * Subclasses may override this method to perform additional processing of the
+ * invoked method's result.
+ *
+ * @param result the Object returned by the method invoked, possibly null
+ */
+ protected void processMethodCallResult(Object result) {
+ // do nothing
+ }
+
+ /**
+ * Render a printable version of this Rule.
+ */
+ public String toString() {
+
+ StringBuffer sb = new StringBuffer("ActionCallMethod[");
+ sb.append("methodName=");
+ sb.append(methodName);
+ sb.append(", paramCount=");
+ sb.append(paramCount);
+ sb.append(", paramTypes={");
+ if (paramTypes != null) {
+ for (int i = 0; i < paramTypes.length; i++) {
+ if (i > 0) {
+ sb.append(", ");
+ }
+ sb.append(paramTypes[i].getName());
+ }
+ }
+ sb.append("}");
+ sb.append("]");
+ return (sb.toString());
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: commons-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: commons-dev-help@jakarta.apache.org