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.List;
025
026import org.xml.sax.Attributes;
027
028/**
029 * <p>
030 * Default implementation of the <code>Rules</code> interface that supports the standard rule matching behavior. This
031 * class can also be used as a base class for specialized <code>Rules</code> implementations.
032 * </p>
033 * <p>
034 * The matching policies implemented by this class support two different types of pattern matching rules:
035 * </p>
036 * <ul>
037 * <li><em>Exact Match</em> - A pattern "a/b/c" exactly matches a <code>&lt;c&gt;</code> element, nested inside a
038 * <code>&lt;b&gt;</code> element, which is nested inside an <code>&lt;a&gt;</code> element.</li>
039 * <li><em>Tail Match</em> - A pattern "&#42;/a/b" matches a <code>&lt;b&gt;</code> element, nested inside an
040 * <code>&lt;a&gt;</code> element, no matter how deeply the pair is nested.</li>
041 * </ul>
042 * <p>
043 * Note that wildcard patterns are ignored if an explicit match can be found (and when multiple wildcard patterns match,
044 * only the longest, ie most explicit, pattern is considered a match).
045 * </p>
046 * <p>
047 * See the package documentation for package org.apache.commons.digester3 for more information.
048 * </p>
049 */
050
051public class RulesBase
052    extends AbstractRulesImpl
053{
054
055    // ----------------------------------------------------- Instance Variables
056
057    /**
058     * The set of registered Rule instances, keyed by the matching pattern. Each value is a List containing the Rules
059     * for that pattern, in the order that they were orginally registered.
060     */
061    protected HashMap<String, List<Rule>> cache = new HashMap<String, List<Rule>>();
062
063    /**
064     * The set of registered Rule instances, in the order that they were originally registered.
065     */
066    protected ArrayList<Rule> rules = new ArrayList<Rule>();
067
068    // ------------------------------------------------------------- Properties
069
070    /**
071     * {@inheritDoc}
072     */
073    @Override
074    public void setDigester( Digester digester )
075    {
076        super.setDigester( digester );
077        for ( Rule rule : rules )
078        {
079            rule.setDigester( digester );
080        }
081    }
082
083    // --------------------------------------------------------- Public Methods
084
085    /**
086     * {@inheritDoc}
087     */
088    @Override
089    protected void registerRule( String pattern, Rule rule )
090    {
091        // to help users who accidently add '/' to the end of their patterns
092        int patternLength = pattern.length();
093        if ( patternLength > 1 && pattern.endsWith( "/" ) )
094        {
095            pattern = pattern.substring( 0, patternLength - 1 );
096        }
097
098        List<Rule> list = cache.get( pattern );
099        if ( list == null )
100        {
101            list = new ArrayList<Rule>();
102            cache.put( pattern, list );
103        }
104        list.add( rule );
105        rules.add( rule );
106    }
107
108    /**
109     * {@inheritDoc}
110     */
111    public void clear()
112    {
113        cache.clear();
114        rules.clear();
115    }
116
117    /**
118     * {@inheritDoc}
119     */
120    public List<Rule> match( String namespaceURI, String pattern, String name, Attributes attributes )
121    {
122        // List rulesList = (List) this.cache.get(pattern);
123        List<Rule> rulesList = lookup( namespaceURI, pattern );
124        if ( ( rulesList == null ) || ( rulesList.size() < 1 ) )
125        {
126            // Find the longest key, ie more discriminant
127            String longKey = "";
128            for ( String key : cache.keySet() )
129            {
130                if ( key.startsWith( "*/" )
131                                && ( pattern.equals( key.substring( 2 ) ) || pattern.endsWith( key.substring( 1 ) )
132                                && key.length() > longKey.length() ) )
133                {
134                    // rulesList = (List) this.cache.get(key);
135                    rulesList = lookup( namespaceURI, key );
136                    longKey = key;
137                }
138            }
139        }
140        if ( rulesList == null )
141        {
142            rulesList = new ArrayList<Rule>();
143        }
144        return ( rulesList );
145    }
146
147    /**
148     * {@inheritDoc}
149     */
150    public List<Rule> rules()
151    {
152        return ( this.rules );
153    }
154
155    // ------------------------------------------------------ Protected Methods
156
157    /**
158     * Return a List of Rule instances for the specified pattern that also match the specified namespace URI (if any).
159     * If there are no such rules, return <code>null</code>.
160     *
161     * @param namespaceURI Namespace URI to match, or <code>null</code> to select matching rules regardless of namespace
162     *            URI
163     * @param pattern Pattern to be matched
164     * @return a List of Rule instances for the specified pattern that also match the specified namespace URI (if any)
165     */
166    protected List<Rule> lookup( String namespaceURI, String pattern )
167    {
168        // Optimize when no namespace URI is specified
169        List<Rule> list = this.cache.get( pattern );
170        if ( list == null )
171        {
172            return ( null );
173        }
174        if ( ( namespaceURI == null ) || ( namespaceURI.length() == 0 ) )
175        {
176            return ( list );
177        }
178
179        // Select only Rules that match on the specified namespace URI
180        ArrayList<Rule> results = new ArrayList<Rule>();
181        for ( Rule item : list )
182        {
183            if ( ( namespaceURI.equals( item.getNamespaceURI() ) ) || ( item.getNamespaceURI() == null ) )
184            {
185                results.add( item );
186            }
187        }
188        return ( results );
189    }
190
191}