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
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><c></code> element, nested inside a <code><b></code>
038 * element, which is nested inside an <code><a></code> element.</li>
039 * <li><em>Tail Match</em> - A pattern "*/a/b" matches a
040 * <code><b></code> element, nested inside an <code><a></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 @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 }