On Wed, 2003-11-26 at 09:52, robert burrell donkin wrote:
> On 24 Nov 2003, at 23:54, Simon Kitching wrote:
> > I'm *really* in favour of providing expansion in body text as well as
> > attributes.
> 
> yep. but i'd favour a sequential approach. remy needs attributes right 
> now, so let's do attributes right now :)
> 
> > I also personally favour an interface rather than an abstract class. 
> > Who
> > knows what implementations people might come up with in the future :
> > retrieve values from LDAP? support emacs macros in variables?.
> > If the "replacement strategy" is an abstract class that brings
> > implementation baggage with it, then it might be more difficult to
> > implement those...
> 
> this would be an abstract class that is (from a higher OOP perspective) 
> an interface but will be implemented in java as an abstract class. it 
> will contain no implementation. most folks will probably subclass the 
> base implementation.
> 
> > On the other hand, an abstract class *does* allow us to expand the
> > "interface" later by adding operations with default implementations,
> > without breaking code. I personally still feel more comfortable with a
> > pure interface in this case, though.
> 
> IMHO being able to maintain backwards compatibility is crucial for a 
> good library. unless i can see the need for different inheritance 
> hierarchies to implement the interface (which is more common in 
> frameworks than library's), experience has taught me that (when 
> developing library code) the flexibility that implementing as an 
> abstract class gives in java pays dividends in long run.


Attached is a complete implementation of the "variable expansion"
feature. I do also have some unit tests for it, but unfortunately they
are still on my home PC so I will have to send them tomorrow.

This code implements the "multiple sources, configurable var marker"
features I talked about earlier. The implementation came together so
well that I am not sure that there is any point in trying to provide an
alternative "simpler" implementation that just supports one source and
"$" as the marker. If you're happy with this, class MultiVarExpander
could just be renamed to BaseVarExpander or similar.

Preferring abstract classes over interfaces is a new "pattern" to me,
but I am willing to give it a go. Class VarExpander is therefore an
abstract class rather than an interface.

Note that this implementation does almost *no* dynamic memory allocation
at all, and therefore should perform very well. Only one "attributes
wrapper" object exists per Digester instance; it is reused each time
startElement(...) is called. I can't think of any reason why
startElement would be called re-entrantly, so this should be safe.

It also implements expansion of text in body text (adds about 2 extra
lines of code).

Regards,

Simon



> 
> - robert
> 
> 
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: [EMAIL PROTECTED]
> For additional commands, e-mail: [EMAIL PROTECTED]
> 
> 
Index: src/java/org/apache/commons/digester/Digester.java
===================================================================
RCS file: /home/cvspublic/jakarta-commons/digester/src/java/org/apache/commons/digester/Digester.java,v
retrieving revision 1.86
diff -u -r1.86 Digester.java
--- src/java/org/apache/commons/digester/Digester.java	19 Nov 2003 21:05:19 -0000	1.86
+++ src/java/org/apache/commons/digester/Digester.java	25 Nov 2003 21:57:45 -0000
@@ -360,6 +360,12 @@
     protected static final String W3C_XML_SCHEMA =
         "http://www.w3.org/2001/XMLSchema";;
     
+    /** a map which is looked up to find values for variables */
+    private VarExpander varExpander = null;
+    
+    /** the attributes wrapper (single instance) */
+    private VarAttributes varAttributes = new VarAttributes();
+
 
     
     // ------------------------------------------------------------- Properties
@@ -935,6 +941,14 @@
     }
 
 
+    /**
+     * Defines how variables in xml attributes and body text are expanded.
+     * If null, no expansion is performed (the default behaviour).
+     */
+    public void setVarExpander(VarExpander expander) {
+        varExpander = expander;
+    }
+
     // ------------------------------------------------- ContentHandler Methods
 
 
@@ -1038,6 +1052,10 @@
         List rules = getRules().match(namespaceURI, match);
         if ((rules != null) && (rules.size() > 0)) {
             String bodyText = this.bodyText.toString();
+            if (varExpander != null) {
+                bodyText = varExpander.expand(bodyText); 
+            }
+
             for (int i = 0; i < rules.size(); i++) {
                 try {
                     Rule rule = (Rule) rules.get(i);
@@ -1283,6 +1301,13 @@
         List rules = getRules().match(namespaceURI, match);
         if ((rules != null) && (rules.size() > 0)) {
             String bodyText = this.bodyText.toString();
+
+            // variable substitution in attributes
+            if (varExpander != null) {
+                varAttributes.init(list, varExpander);
+                list = varAttributes;
+            }
+
             for (int i = 0; i < rules.size(); i++) {
                 try {
                     Rule rule = (Rule) rules.get(i);
/*
 * $Header: $
 * $Revision: $
 * $Date: $
 *
 * ====================================================================
 * 
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2001-2003 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer. 
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgement:  
 *       "This product includes software developed by the 
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgement may appear in the software itself,
 *    if and wherever such third-party acknowledgements normally appear.
 *
 * 4. The names "Apache", "The Jakarta Project", "Commons", and "Apache Software
 *    Foundation" must not be used to endorse or promote products derived
 *    from this software without prior written permission. For written 
 *    permission, please contact [EMAIL PROTECTED]
 *
 * 5. Products derived from this software may not be called "Apache",
 *    "Apache" nor may "Apache" appear in their names without prior 
 *    written permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 *
 */ 

package org.apache.commons.digester;

import java.util.Map;
import java.util.ArrayList;

/**
 * <p>Expands variable references from multiple sources.</p>
 *
 * @author Simon Kitching
 * @version $Revision: $ $Date: $
 */

public class MultiVarExpander extends VarExpander {
    private int nEntries = 0;
    private ArrayList markers = new ArrayList(2);
    private ArrayList sources = new ArrayList(2);
    
    public MultiVarExpander() {
    }
    
    public void addSource(String marker, Map source) {
        ++nEntries;
        markers.add(marker);
        sources.add(source);
    }

    /*    
     * Expands any variable declarations using any of the known
     * variable marker strings.
     * 
     * @throws IllegalArgumentException if the input param references
     * a variable which is not known to the specified source.
     */
    public String expand(String param) {
        for(int i=0; i<nEntries; ++i) {
            param = expand(
                param, 
                (String) markers.get(i), 
                (Map) sources.get(i));
        }
        return param;
    }
    
    /**
     * Replace any occurrences within the string of the form
     * "marker{key}" with the value from source[key].
     * <p>
     * Commonly, the variable marker is "$", in which case variables
     * are indicated by ${key} in the string.
     * <p>
     * Returns the string after performing all substitutions.
     * <p>
     * If no substitutions were made, the input string object is
     * returned (not a copy).
     *
     * @throws IllegalArgumentException if the input param references
     * a variable which is not known to the specified source.
     */
    public String expand(String str, String marker, Map source) {
        String startMark = marker + "{";
        int markLen = startMark.length();
        
        int index = 0;
        for(;;)
        {
            index = str.indexOf(startMark, index);
            if (index == -1)
            {
                return str;
            }
            
            int startIndex = index + markLen;
            if (startIndex > str.length())
            {
                throw new IllegalArgumentException(
                    "var expression starts at end of string");
            }
            
            int endIndex = str.indexOf("}", index + markLen);
            if (endIndex == -1)
            {
                throw new IllegalArgumentException(
                    "var expression starts but does not end");
            }
            
            String key = str.substring(index+markLen, endIndex);
            Object value =  source.get(key);
            if (value == null) {
                throw new IllegalArgumentException(
                    "parameter [" + key + "] is not defined.");
            }
            String varValue = value.toString();
            
            str = str.substring(0, index) + varValue + str.substring(endIndex+1);
            index += varValue.length();
        }
    }
        
}
/*
 * $Header: $
 * $Revision: $
 * $Date: $
 *
 * ====================================================================
 * 
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2001-2003 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer. 
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgement:  
 *       "This product includes software developed by the 
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgement may appear in the software itself,
 *    if and wherever such third-party acknowledgements normally appear.
 *
 * 4. The names "Apache", "The Jakarta Project", "Commons", and "Apache Software
 *    Foundation" must not be used to endorse or promote products derived
 *    from this software without prior written permission. For written 
 *    permission, please contact [EMAIL PROTECTED]
 *
 * 5. Products derived from this software may not be called "Apache",
 *    "Apache" nor may "Apache" appear in their names without prior 
 *    written permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 *
 */ 


package org.apache.commons.digester;

import org.xml.sax.Attributes;

import java.util.Map;
import java.util.ArrayList;


/**
 * <p>Wrapper for an org.xml.sax.Attributes object which expands any 
 * "variables" referenced in the attribute value via ${foo} or similar. 
 * This is only done something actually asks for the attribute value, 
 * thereby imposing no performance penalty if the attribute is not used.</p>
 *
 * @author Simon Kitching
 * @version $Revision: $ $Date: $
 */

public class VarAttributes implements Attributes {

    // list of mapped attributes.
    private ArrayList values = new ArrayList(10);

    private Attributes attrs;
    private VarExpander expander;
    
    // ------------------- Public Methods
    
    /**
     * Specify which attributes class this object is a proxy for.
     */
    public void init(Attributes attrs, VarExpander expander) {
        this.attrs = attrs;
        this.expander = expander;

        // I hope this doesn't release the memory for this array; for 
        // efficiency, this should just mark the array as being size 0.
        values.clear(); 
    }

    public String getValue(int index) {
        if (index >= values.size()) {
            // Expand the values array with null elements, so the later
            // call to set(index, s) works ok.
            //
            // Unfortunately, there is no easy way to set the size of
            // an arraylist; we must repeatedly add null elements to it..
            values.ensureCapacity(index+1);
            for(int i = values.size(); i<= index; ++i) {
                values.add(null);
            }
        }
        
        String s = (String) values.get(index);
        
        if (s == null) {
            // we have never been asked for this value before.
            // get the real attribute value and perform substitution
            // on it.
            s = attrs.getValue(index);
            if (s != null) {
                s = expander.expand(s);
                values.set(index, s);
            }
        }
        
        return s;
    }
    
    public String getValue(String qname) {
        int index = attrs.getIndex(qname);
        if (index == -1) {
            return null;
        }
        return getValue(index);
    }
    
    public String getValue(String uri, String localname) {
        int index = attrs.getIndex(uri, localname);
        if (index == -1) {
            return null;
        }
        return getValue(index);
    }
    
    // plain proxy methods follow : nothing interesting :-)
    public int getIndex(String qname) {
        return attrs.getIndex(qname); 
    }
    
    public int getIndex(String uri, String localpart) {
        return attrs.getIndex(uri, localpart); 
    }
    
    public int getLength() {
        return attrs.getLength();
    }
    
    public String getLocalName(int index) {
        return attrs.getLocalName(index);
    }
    
    public String getQName(int index) {
        return attrs.getQName(index);
    }
    
    public String getType(int index) {
        return attrs.getType(index);
    }

    public String getType(String qname) {
        return attrs.getType(qname);
    }
    
    public String getType(String uri, String localname) {
        return attrs.getType(uri, localname);
    }
    
    public String getURI(int index) {
        return attrs.getURI(index);
    }
 
}
/*
 * $Header: $
 * $Revision: $
 * $Date: $
 *
 * ====================================================================
 * 
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2001-2003 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer. 
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgement:  
 *       "This product includes software developed by the 
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgement may appear in the software itself,
 *    if and wherever such third-party acknowledgements normally appear.
 *
 * 4. The names "Apache", "The Jakarta Project", "Commons", and "Apache Software
 *    Foundation" must not be used to endorse or promote products derived
 *    from this software without prior written permission. For written 
 *    permission, please contact [EMAIL PROTECTED]
 *
 * 5. Products derived from this software may not be called "Apache",
 *    "Apache" nor may "Apache" appear in their names without prior 
 *    written permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 *
 */ 

/**
 * <p>An Interface describing a class capable of expanding strings which
 * may contain variable references. The exact syntax of the "reference",
 * and the mechanism for determining the corresponding value to be used
 * is up to the concrete implementation.</p>
 *
 * @author Simon Kitching
 * @version $Revision: $ $Date: $
 */

package org.apache.commons.digester;

public abstract class VarExpander {
    /**
     * Return the input string with any variables replaced by their
     * corresponding value. If there are no variables in the string,
     * then the input parameter is returned unaltered.
     */
    abstract public String expand(String param);
}

---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to