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
020package org.apache.commons.digester;
021
022
023import java.util.ArrayList;
024import java.util.HashMap;
025import java.util.Iterator;
026import 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
053public 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}