[DIGESTER-132] Add a CompoundSubstitutor to support more than one Substitutors at a time

git-svn-id: https://svn.apache.org/repos/asf/commons/proper/digester/trunk@1139046 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/main/java/org/apache/commons/digester3/substitution/CompoundSubstitutor.java b/src/main/java/org/apache/commons/digester3/substitution/CompoundSubstitutor.java
new file mode 100644
index 0000000..22224f1
--- /dev/null
+++ b/src/main/java/org/apache/commons/digester3/substitution/CompoundSubstitutor.java
@@ -0,0 +1,84 @@
+package org.apache.commons.digester3.substitution;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.digester3.Substitutor;
+import org.xml.sax.Attributes;
+
+/**
+ * This Substitutor chains two Substitutors <code>a</code> and <code>b</code>.
+ * All values to substitute are first handled by <code>a</code> and passed to
+ * <code>b</code> afterwards.
+ */
+public class CompoundSubstitutor
+    extends Substitutor
+{
+
+    /**
+     * Substitutor a
+     */
+    private final Substitutor a;
+
+    /**
+     * Substitutor b
+     */
+    private final Substitutor b;
+
+    /**
+     * Creates a new CompoundSubstitutor instance. All values overgiven to <code>substitute()</code>
+     * are first handled by <code>a</code> and passed to <code>b</code> afterwards.
+     * Both Substitutor have to be not null.
+     *
+     * @param a Substitutor a
+     * @param b Substitutor b
+     */
+    public CompoundSubstitutor( Substitutor a, Substitutor b )
+    {
+        if ( a == null )
+        {
+            throw new IllegalArgumentException( "First Substitutor must be not null" );
+        }
+        if ( b == null )
+        {
+            throw new IllegalArgumentException( "Second Substitutor must be not null" );
+        }
+        this.a = a;
+        this.b = b;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Attributes substitute( Attributes attributes )
+    {
+        return b.substitute( a.substitute( attributes ) );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String substitute( String bodyText )
+    {
+        return b.substitute( a.substitute( bodyText ) );
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/digester3/substitution/CompoundSubstitutorTestCase.java b/src/test/java/org/apache/commons/digester3/substitution/CompoundSubstitutorTestCase.java
new file mode 100644
index 0000000..21f526d
--- /dev/null
+++ b/src/test/java/org/apache/commons/digester3/substitution/CompoundSubstitutorTestCase.java
@@ -0,0 +1,169 @@
+package org.apache.commons.digester3.substitution;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.apache.commons.digester3.Substitutor;
+import org.junit.Before;
+import org.junit.Test;
+import org.xml.sax.Attributes;
+import org.xml.sax.helpers.AttributesImpl;
+
+public final class CompoundSubstitutorTestCase
+{
+
+
+    private static class SubstitutorStub
+        extends Substitutor
+    {
+
+        private String newBodyText;
+
+        private String uri;
+
+        private String localName;
+
+        private String type;
+
+        private String value;
+
+        public SubstitutorStub( String bodyText, String uri, String localName, String type, String value )
+        {
+            this.newBodyText = bodyText;
+            this.uri = uri;
+            this.localName = localName;
+            this.type = type;
+            this.value = value;
+        }
+
+        /**
+         * @see org.apache.commons.digester.Substitutor#substitute(org.xml.sax.Attributes)
+         */
+        @Override
+        public Attributes substitute( Attributes attributes )
+        {
+            AttributesImpl attribs = new AttributesImpl( attributes );
+            attribs.addAttribute( uri, localName, uri + ":" + localName, type, value );
+            return attribs;
+        }
+
+        /**
+         * @see org.apache.commons.digester.Substitutor#substitute(java.lang.String)
+         */
+        @Override
+        public String substitute( String bodyText )
+        {
+            return newBodyText;
+        }
+
+    }
+
+    private Attributes attrib;
+
+    private String bodyText;
+
+    @Before
+    public void setUp()
+    {
+        AttributesImpl aImpl = new AttributesImpl();
+        aImpl.addAttribute( "", "b", ":b", "", "bcd" );
+        aImpl.addAttribute( "", "c", ":c", "", "cde" );
+        aImpl.addAttribute( "", "d", ":d", "", "def" );
+
+        attrib = aImpl;
+        bodyText = "Amazing Body Text!";
+    }
+
+    @Test
+    public void testConstructors()
+    {
+        try
+        {
+            new CompoundSubstitutor( null, null );
+            fail();
+        }
+        catch ( IllegalArgumentException e )
+        {
+            // OK
+        }
+
+        Substitutor a = new SubstitutorStub( "XYZ", "", "a", "", "abc" );
+
+        try
+        {
+            new CompoundSubstitutor( a, null );
+            fail();
+        }
+        catch ( IllegalArgumentException e )
+        {
+            // OK
+        }
+
+        try
+        {
+            new CompoundSubstitutor( null, a );
+            fail();
+        }
+        catch ( IllegalArgumentException e )
+        {
+            // OK
+        }
+    }
+
+    @Test
+    public void testChaining()
+    {
+        Substitutor a = new SubstitutorStub( "XYZ", "", "a", "", "abc" );
+        Substitutor b = new SubstitutorStub( "STU", "", "b", "", "bcd" );
+
+        Substitutor test = new CompoundSubstitutor( a, b );
+
+        AttributesImpl attribFixture = new AttributesImpl( attrib );
+        attribFixture.addAttribute( "", "a", ":a", "", "abc" );
+        attribFixture.addAttribute( "", "b", ":b", "", "bcd" );
+
+        assertTrue( areEqual( test.substitute( attrib ), attribFixture ) );
+        assertEquals( test.substitute( bodyText ), "STU" );
+    }
+
+    private boolean areEqual( Attributes a, Attributes b )
+    {
+        if ( a.getLength() != b.getLength() )
+        {
+            return false;
+        }
+
+        boolean success = true;
+        for ( int i = 0; i < a.getLength() && success; i++ )
+        {
+            success = a.getLocalName( i ).equals( b.getLocalName( i ) )
+                    && a.getQName( i ).equals( b.getQName( i ) )
+                    && a.getType( i ).equals( b.getType( i ) )
+                    && a.getURI( i ).equals( b.getURI( i ) )
+                    && a.getValue( i ).equals( b.getValue( i ) );
+        }
+
+        return success;
+    }
+
+}