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