001package org.apache.commons.digester3;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *   http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.util.ArrayList;
023import java.util.HashMap;
024import java.util.LinkedList;
025import java.util.List;
026
027import org.xml.sax.Attributes;
028
029/**
030 * <p>
031 * Default implementation of the <code>Rules</code> interface that supports the standard rule matching behavior. This
032 * class can also be used as a base class for specialized <code>Rules</code> implementations.
033 * </p>
034 * <p>
035 * The matching policies implemented by this class support two different types of pattern matching rules:
036 * </p>
037 * <ul>
038 * <li><em>Exact Match</em> - A pattern "a/b/c" exactly matches a <code>&lt;c&gt;</code> element, nested inside a
039 * <code>&lt;b&gt;</code> element, which is nested inside an <code>&lt;a&gt;</code> element.</li>
040 * <li><em>Tail Match</em> - A pattern "&#42;/a/b" matches a <code>&lt;b&gt;</code> element, nested inside an
041 * <code>&lt;a&gt;</code> element, no matter how deeply the pair is nested.</li>
042 * </ul>
043 * <p>
044 * Note that wildcard patterns are ignored if an explicit match can be found (and when multiple wildcard patterns match,
045 * only the longest, ie most explicit, pattern is considered a match).
046 * </p>
047 * <p>
048 * See the package documentation for package org.apache.commons.digester3 for more information.
049 * </p>
050 */
051
052public class RulesBase
053    extends AbstractRulesImpl
054{
055
056    // ----------------------------------------------------- Instance Variables
057
058    /**
059     * The set of registered Rule instances, keyed by the matching pattern. Each value is a List containing the Rules
060     * for that pattern, in the order that they were orginally registered.
061     */
062    protected HashMap<String, List<Rule>> cache = new HashMap<String, List<Rule>>();
063
064    /**
065     * The subset of registered Rule instances with wildcard pattern.
066     */
067    protected List<String> wildcardCache = new LinkedList<String>();
068
069    /**
070     * The set of registered Rule instances, in the order that they were originally registered.
071     */
072    protected ArrayList<Rule> rules = new ArrayList<Rule>();
073
074    // ------------------------------------------------------------- Properties
075
076    /**
077     * {@inheritDoc}
078     */
079    @Override
080    public void setDigester( Digester digester )
081    {
082        super.setDigester( digester );
083        for ( Rule rule : rules )
084        {
085            rule.setDigester( digester );
086        }
087    }
088
089    // --------------------------------------------------------- Public Methods
090
091    /**
092     * {@inheritDoc}
093     */
094    @Override
095    protected void registerRule( String pattern, Rule rule )
096    {
097        // to help users who accidently add '/' to the end of their patterns
098        int patternLength = pattern.length();
099        if ( patternLength > 1 && pattern.endsWith( "/" ) )
100        {
101            pattern = pattern.substring( 0, patternLength - 1 );
102        }
103
104        List<Rule> list = cache.get( pattern );
105        if ( list == null )
106        {
107            list = new ArrayList<Rule>();
108            if ( pattern.startsWith( "*/" ) )
109            {
110                wildcardCache.add( pattern.substring( 1 ) );
111            }
112            cache.put( pattern, list );
113        }
114        list.add( rule );
115        rules.add( rule );
116    }
117
118    /**
119     * {@inheritDoc}
120     */
121    public void clear()
122    {
123        wildcardCache.clear();
124        cache.clear();
125        rules.clear();
126    }
127
128    /**
129     * {@inheritDoc}
130     */
131    public List<Rule> match( String namespaceURI, String pattern, String name, Attributes attributes )
132    {
133        // List rulesList = (List) this.cache.get(pattern);
134        List<Rule> rulesList = lookup( namespaceURI, pattern );
135        if ( ( rulesList == null ) || ( rulesList.size() < 1 ) )
136        {
137            // Find the longest key, ie more discriminant
138            String longKey = "";
139            for ( String key : wildcardCache )
140            {
141                if ( ( pattern.equals( key.substring( 1 ) ) || pattern.endsWith( key ) )
142                    && key.length() > longKey.length() )
143                {
144                    longKey = key;
145                }
146            }
147            if ( longKey.length() > 0 )
148            {
149                rulesList = lookup( namespaceURI, "*" + longKey );
150            }
151        }
152        if ( rulesList == null )
153        {
154            rulesList = new ArrayList<Rule>();
155        }
156        return ( rulesList );
157    }
158
159    /**
160     * {@inheritDoc}
161     */
162    public List<Rule> rules()
163    {
164        return ( this.rules );
165    }
166
167    // ------------------------------------------------------ Protected Methods
168
169    /**
170     * Return a List of Rule instances for the specified pattern that also match the specified namespace URI (if any).
171     * If there are no such rules, return <code>null</code>.
172     *
173     * @param namespaceURI Namespace URI to match, or <code>null</code> to select matching rules regardless of namespace
174     *            URI
175     * @param pattern Pattern to be matched
176     * @return a List of Rule instances for the specified pattern that also match the specified namespace URI (if any)
177     */
178    protected List<Rule> lookup( String namespaceURI, String pattern )
179    {
180        // Optimize when no namespace URI is specified
181        List<Rule> list = this.cache.get( pattern );
182        if ( list == null )
183        {
184            return ( null );
185        }
186        if ( ( namespaceURI == null ) || ( namespaceURI.length() == 0 ) )
187        {
188            return ( list );
189        }
190
191        // Select only Rules that match on the specified namespace URI
192        ArrayList<Rule> results = new ArrayList<Rule>();
193        for ( Rule item : list )
194        {
195            if ( ( namespaceURI.equals( item.getNamespaceURI() ) ) || ( item.getNamespaceURI() == null ) )
196            {
197                results.add( item );
198            }
199        }
200        return ( results );
201    }
202
203}