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><c></code> element, nested inside a <code><b></code>
33 * element, which is nested inside an <code><a></code> element.</li>
34 * <li><em>Tail Match</em> - A pattern "*/a/b" matches a
35 * <code><b></code> element, nested inside an <code><a></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