1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements.  See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */

17 package org.apache.tomcat.util.digester;
18
19 import java.util.ArrayList;
20 import java.util.HashMap;
21 import java.util.List;
22
23 /**
24  * <p>Default implementation of the <code>Rules</code> interface that supports
25  * the standard rule matching behavior.  This class can also be used as a
26  * base class for specialized <code>Rules</code> implementations.</p>
27  *
28  * <p>The matching policies implemented by this class support two different
29  * types of pattern matching rules:</p>
30  * <ul>
31  * <li><em>Exact Match</em> - A pattern "a/b/c" exactly matches a
32  *     <code>&lt;c&gt;</code> element, nested inside a <code>&lt;b&gt;</code>
33  *     element, which is nested inside an <code>&lt;a&gt;</code> element.</li>
34  * <li><em>Tail Match</em> - A pattern "&#42;/a/b" matches a
35  *     <code>&lt;b&gt;</code> element, nested inside an <code>&lt;a&gt;</code>
36  *      element, no matter how deeply the pair is nested.</li>
37  * </ul>
38  */

39 public class RulesBase implements Rules {
40
41     // ----------------------------------------------------- Instance Variables
42
43     /**
44      * The set of registered Rule instances, keyed by the matching pattern.
45      * Each value is a List containing the Rules for that pattern, in the
46      * order that they were originally registered.
47      */

48     protected HashMap<String,List<Rule>> cache = new HashMap<>();
49
50
51     /**
52      * The Digester instance with which this Rules instance is associated.
53      */

54     protected Digester digester = null;
55
56
57     /**
58      * The set of registered Rule instances, in the order that they were
59      * originally registered.
60      */

61     protected ArrayList<Rule> rules = new ArrayList<>();
62
63
64     // ------------------------------------------------------------- Properties
65
66     /**
67      * Return the Digester instance with which this Rules instance is
68      * associated.
69      */

70     @Override
71     public Digester getDigester() {
72         return this.digester;
73     }
74
75
76     /**
77      * Set the Digester instance with which this Rules instance is associated.
78      *
79      * @param digester The newly associated Digester instance
80      */

81     @Override
82     public void setDigester(Digester digester) {
83         this.digester = digester;
84         for (Rule item : rules) {
85             item.setDigester(digester);
86         }
87     }
88
89
90     // --------------------------------------------------------- Public Methods
91
92     /**
93      * Register a new Rule instance matching the specified pattern.
94      *
95      * @param pattern Nesting pattern to be matched for this Rule
96      * @param rule Rule instance to be registered
97      */

98     @Override
99     public void add(String pattern, Rule rule) {
100         // to help users who accidentally add '/' to the end of their patterns
101         int patternLength = pattern.length();
102         if (patternLength>1 && pattern.endsWith("/")) {
103             pattern = pattern.substring(0, patternLength-1);
104         }
105
106         List<Rule> list = cache.get(pattern);
107         if (list == null) {
108             list = new ArrayList<>();
109             cache.put(pattern, list);
110         }
111         list.add(rule);
112         rules.add(rule);
113         if (this.digester != null) {
114             rule.setDigester(this.digester);
115         }
116     }
117
118
119     /**
120      * Clear all existing Rule instance registrations.
121      */

122     @Override
123     public void clear() {
124         cache.clear();
125         rules.clear();
126     }
127
128
129     /**
130      * Return a List of all registered Rule instances that match the specified
131      * nesting pattern, or a zero-length List if there are no matches.  If more
132      * than one Rule instance matches, they <strong>must</strong> be returned
133      * in the order originally registered through the <code>add()</code>
134      * method.
135      *
136      * @param namespaceURI Namespace URI for which to select matching rules,
137      *  or <code>null</code> to match regardless of namespace URI
138      * @param pattern Nesting pattern to be matched
139      */

140     @Override
141     public List<Rule> match(String namespaceURI, String pattern) {
142
143         // List rulesList = (List) this.cache.get(pattern);
144         List<Rule> rulesList = lookup(namespaceURI, pattern);
145         if ((rulesList == null) || (rulesList.size() < 1)) {
146             // Find the longest key, ie more discriminant
147             String longKey = "";
148             for (String key : this.cache.keySet()) {
149                 if (key.startsWith("*/")) {
150                     if (pattern.equals(key.substring(2)) ||
151                         pattern.endsWith(key.substring(1))) {
152                         if (key.length() > longKey.length()) {
153                             // rulesList = (List) this.cache.get(key);
154                             rulesList = lookup(namespaceURI, key);
155                             longKey = key;
156                         }
157                     }
158                 }
159             }
160         }
161         if (rulesList == null) {
162             rulesList = new ArrayList<>();
163         }
164         return rulesList;
165     }
166
167
168     /**
169      * Return a List of all registered Rule instances, or a zero-length List
170      * if there are no registered Rule instances.  If more than one Rule
171      * instance has been registered, they <strong>must</strong> be returned
172      * in the order originally registered through the <code>add()</code>
173      * method.
174      */

175     @Override
176     public List<Rule> rules() {
177         return this.rules;
178     }
179
180
181     // ------------------------------------------------------ Protected Methods
182
183     /**
184      * Return a List of Rule instances for the specified pattern that also
185      * match the specified namespace URI (if any).  If there are no such
186      * rules, return <code>null</code>.
187      *
188      * @param namespaceURI Namespace URI to match, or <code>null</code> to
189      *  select matching rules regardless of namespace URI
190      * @param pattern Pattern to be matched
191      * @return a rules list
192      */

193     protected List<Rule> lookup(String namespaceURI, String pattern) {
194         // Optimize when no namespace URI is specified
195         List<Rule> list = this.cache.get(pattern);
196         if (list == null) {
197             return null;
198         }
199         if ((namespaceURI == null) || (namespaceURI.length() == 0)) {
200             return list;
201         }
202
203         // Select only Rules that match on the specified namespace URI
204         List<Rule> results = new ArrayList<>();
205         for (Rule item : list) {
206             if ((namespaceURI.equals(item.getNamespaceURI())) ||
207                     (item.getNamespaceURI() == null)) {
208                 results.add(item);
209             }
210         }
211         return results;
212     }
213 }
214