001    /* $Id: RulesBase.java 992060 2010-09-02 19:09:47Z simonetripodi $
002     *
003     * Licensed to the Apache Software Foundation (ASF) under one or more
004     * contributor license agreements.  See the NOTICE file distributed with
005     * this work for additional information regarding copyright ownership.
006     * The ASF licenses this file to You under the Apache License, Version 2.0
007     * (the "License"); you may not use this file except in compliance with
008     * the License.  You may obtain a copy of the License at
009     *
010     *      http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing, software
013     * distributed under the License is distributed on an "AS IS" BASIS,
014     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    
019    
020    package org.apache.commons.digester;
021    
022    
023    import java.util.ArrayList;
024    import java.util.HashMap;
025    import java.util.List;
026    
027    
028    /**
029     * <p>Default implementation of the <code>Rules</code> interface that supports
030     * the standard rule matching behavior.  This class can also be used as a
031     * base class for specialized <code>Rules</code> implementations.</p>
032     *
033     * <p>The matching policies implemented by this class support two different
034     * types of pattern matching rules:</p>
035     * <ul>
036     * <li><em>Exact Match</em> - A pattern "a/b/c" exactly matches a
037     *     <code>&lt;c&gt;</code> element, nested inside a <code>&lt;b&gt;</code>
038     *     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
040     *     <code>&lt;b&gt;</code> element, nested inside an <code>&lt;a&gt;</code>
041     *      element, no matter how deeply the pair is nested.</li>
042     * </ul>
043     *
044     * <p>Note that wildcard patterns are ignored if an explicit match can be found 
045     * (and when multiple wildcard patterns match, only the longest, ie most 
046     * explicit, pattern is considered a match).</p>
047     *
048     * <p>See the package documentation for package org.apache.commons.digester 
049     * for more information.</p>
050     */
051    
052    public class RulesBase implements Rules {
053    
054    
055        // ----------------------------------------------------- Instance Variables
056    
057    
058        /**
059         * The set of registered Rule instances, keyed by the matching pattern.
060         * Each value is a List containing the Rules for that pattern, in the
061         * order that they were orginally registered.
062         */
063        protected HashMap<String, List<Rule>> cache = new HashMap<String, List<Rule>>();
064    
065    
066        /**
067         * The Digester instance with which this Rules instance is associated.
068         */
069        protected Digester digester = null;
070    
071    
072        /**
073         * The namespace URI for which subsequently added <code>Rule</code>
074         * objects are relevant, or <code>null</code> for matching independent
075         * of namespaces.
076         */
077        protected String namespaceURI = null;
078    
079    
080        /**
081         * The set of registered Rule instances, in the order that they were
082         * originally registered.
083         */
084        protected ArrayList<Rule> rules = new ArrayList<Rule>();
085    
086    
087        // ------------------------------------------------------------- Properties
088    
089    
090        /**
091         * Return the Digester instance with which this Rules instance is
092         * associated.
093         */
094        public Digester getDigester() {
095    
096            return (this.digester);
097    
098        }
099    
100    
101        /**
102         * Set the Digester instance with which this Rules instance is associated.
103         *
104         * @param digester The newly associated Digester instance
105         */
106        public void setDigester(Digester digester) {
107    
108            this.digester = digester;
109            for (Rule rule : rules) {
110                rule.setDigester(digester);
111            }
112    
113        }
114    
115    
116        /**
117         * Return the namespace URI that will be applied to all subsequently
118         * added <code>Rule</code> objects.
119         */
120        public String getNamespaceURI() {
121    
122            return (this.namespaceURI);
123    
124        }
125    
126    
127        /**
128         * Set the namespace URI that will be applied to all subsequently
129         * added <code>Rule</code> objects.
130         *
131         * @param namespaceURI Namespace URI that must match on all
132         *  subsequently added rules, or <code>null</code> for matching
133         *  regardless of the current namespace URI
134         */
135        public void setNamespaceURI(String namespaceURI) {
136    
137            this.namespaceURI = namespaceURI;
138    
139        }
140    
141    
142        // --------------------------------------------------------- Public Methods
143    
144    
145        /**
146         * Register a new Rule instance matching the specified pattern.
147         *
148         * @param pattern Nesting pattern to be matched for this Rule
149         * @param rule Rule instance to be registered
150         */
151        public void add(String pattern, Rule rule) {
152            // to help users who accidently add '/' to the end of their patterns
153            int patternLength = pattern.length();
154            if (patternLength>1 && pattern.endsWith("/")) {
155                pattern = pattern.substring(0, patternLength-1);
156            }
157            
158            
159            List<Rule> list = cache.get(pattern);
160            if (list == null) {
161                list = new ArrayList<Rule>();
162                cache.put(pattern, list);
163            }
164            list.add(rule);
165            rules.add(rule);
166            if (this.digester != null) {
167                rule.setDigester(this.digester);
168            }
169            if (this.namespaceURI != null) {
170                rule.setNamespaceURI(this.namespaceURI);
171            }
172    
173        }
174    
175    
176        /**
177         * Clear all existing Rule instance registrations.
178         */
179        public void clear() {
180    
181            cache.clear();
182            rules.clear();
183    
184        }
185    
186    
187        /**
188         * Return a List of all registered Rule instances that match the specified
189         * nesting pattern, or a zero-length List if there are no matches.  If more
190         * than one Rule instance matches, they <strong>must</strong> be returned
191         * in the order originally registered through the <code>add()</code>
192         * method.
193         *
194         * @param pattern Nesting pattern to be matched
195         *
196         * @deprecated Call match(namespaceURI,pattern) instead.
197         */
198        @Deprecated
199        public List<Rule> match(String pattern) {
200    
201            return (match(null, pattern));
202    
203        }
204    
205    
206        /**
207         * Return a List of all registered Rule instances that match the specified
208         * nesting pattern, or a zero-length List if there are no matches.  If more
209         * than one Rule instance matches, they <strong>must</strong> be returned
210         * in the order originally registered through the <code>add()</code>
211         * method.
212         *
213         * @param namespaceURI Namespace URI for which to select matching rules,
214         *  or <code>null</code> to match regardless of namespace URI
215         * @param pattern Nesting pattern to be matched
216         */
217        public List<Rule> match(String namespaceURI, String pattern) {
218    
219            // List rulesList = (List) this.cache.get(pattern);
220            List<Rule> rulesList = lookup(namespaceURI, pattern);
221            if ((rulesList == null) || (rulesList.size() < 1)) {
222                // Find the longest key, ie more discriminant
223                String longKey = "";
224                for (String key : cache.keySet()) {
225                    if (key.startsWith("*/")) {
226                        if (pattern.equals(key.substring(2)) ||
227                            pattern.endsWith(key.substring(1))) {
228                            if (key.length() > longKey.length()) {
229                                // rulesList = (List) this.cache.get(key);
230                                rulesList = lookup(namespaceURI, key);
231                                longKey = key;
232                            }
233                        }
234                    }
235                }
236            }
237            if (rulesList == null) {
238                rulesList = new ArrayList<Rule>();
239            }
240            return (rulesList);
241    
242        }
243    
244    
245        /**
246         * Return a List of all registered Rule instances, or a zero-length List
247         * if there are no registered Rule instances.  If more than one Rule
248         * instance has been registered, they <strong>must</strong> be returned
249         * in the order originally registered through the <code>add()</code>
250         * method.
251         */
252        public List<Rule> rules() {
253    
254            return (this.rules);
255    
256        }
257    
258    
259        // ------------------------------------------------------ Protected Methods
260    
261    
262        /**
263         * Return a List of Rule instances for the specified pattern that also
264         * match the specified namespace URI (if any).  If there are no such
265         * rules, return <code>null</code>.
266         *
267         * @param namespaceURI Namespace URI to match, or <code>null</code> to
268         *  select matching rules regardless of namespace URI
269         * @param pattern Pattern to be matched
270         */
271        protected List<Rule> lookup(String namespaceURI, String pattern) {
272    
273            // Optimize when no namespace URI is specified
274            List<Rule> list = this.cache.get(pattern);
275            if (list == null) {
276                return (null);
277            }
278            if ((namespaceURI == null) || (namespaceURI.length() == 0)) {
279                return (list);
280            }
281    
282            // Select only Rules that match on the specified namespace URI
283            ArrayList<Rule> results = new ArrayList<Rule>();
284            for (Rule item : list) {
285                if ((namespaceURI.equals(item.getNamespaceURI())) ||
286                        (item.getNamespaceURI() == null)) {
287                    results.add(item);
288                }
289            }
290            return (results);
291    
292        }
293    
294    
295    }