    001    /* $Id: RulesBase.java 729099 2008-12-23 20:39:37Z rahul $
    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        public List<Rule> match(String pattern) {
    199
    200            return (match(null, pattern));
    201
    202        }
    203
    204
    205        /**
    206         * Return a List of all registered Rule instances that match the specified
    207         * nesting pattern, or a zero-length List if there are no matches.  If more
    208         * than one Rule instance matches, they <strong>must</strong> be returned
    209         * in the order originally registered through the <code>add()</code>
    210         * method.
    211         *
    212         * @param namespaceURI Namespace URI for which to select matching rules,
    213         *  or <code>null</code> to match regardless of namespace URI
    214         * @param pattern Nesting pattern to be matched
    215         */
    216        public List<Rule> match(String namespaceURI, String pattern) {
    217
    218            // List rulesList = (List) this.cache.get(pattern);
    219            List<Rule> rulesList = lookup(namespaceURI, pattern);
    220            if ((rulesList == null) || (rulesList.size() < 1)) {
    221                // Find the longest key, ie more discriminant
    222                String longKey = "";
    223                for (String key : cache.keySet()) {
    224                    if (key.startsWith("*/")) {
    225                        if (pattern.equals(key.substring(2)) ||
    226                            pattern.endsWith(key.substring(1))) {
    227                            if (key.length() > longKey.length()) {
    228                                // rulesList = (List) this.cache.get(key);
    229                                rulesList = lookup(namespaceURI, key);
    230                                longKey = key;
    231                            }
    232                        }
    233                    }
    234                }
    235            }
    236            if (rulesList == null) {
    237                rulesList = new ArrayList<Rule>();
    238            }
    239            return (rulesList);
    240
    241        }
    242
    243
    244        /**
    245         * Return a List of all registered Rule instances, or a zero-length List
    246         * if there are no registered Rule instances.  If more than one Rule
    247         * instance has been registered, they <strong>must</strong> be returned
    248         * in the order originally registered through the <code>add()</code>
    249         * method.
    250         */
    251        public List<Rule> rules() {
    252
    253            return (this.rules);
    254
    255        }
    256
    257
    258        // ------------------------------------------------------ Protected Methods
    259
    260
    261        /**
    262         * Return a List of Rule instances for the specified pattern that also
    263         * match the specified namespace URI (if any).  If there are no such
    264         * rules, return <code>null</code>.
    265         *
    266         * @param namespaceURI Namespace URI to match, or <code>null</code> to
    267         *  select matching rules regardless of namespace URI
    268         * @param pattern Pattern to be matched
    269         */
    270        protected List<Rule> lookup(String namespaceURI, String pattern) {
    271
    272            // Optimize when no namespace URI is specified
    273            List<Rule> list = this.cache.get(pattern);
    274            if (list == null) {
    275                return (null);
    276            }
    277            if ((namespaceURI == null) || (namespaceURI.length() == 0)) {
    278                return (list);
    279            }
    280
    281            // Select only Rules that match on the specified namespace URI
    282            ArrayList<Rule> results = new ArrayList<Rule>();
    283            for (Rule item : list) {
    284                if ((namespaceURI.equals(item.getNamespaceURI())) ||
    285                        (item.getNamespaceURI() == null)) {
    286                    results.add(item);
    287                }
    288            }
    289            return (results);
    290
    291        }
    292
    293
    294    }