001    package 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    
022    import java.util.ArrayList;
023    import java.util.HashMap;
024    import java.util.List;
025    
026    import 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    
051    public 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    }