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