001 /* $Id: RulesBase.java 471661 2006-11-06 08:09:25Z skitching $
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.Iterator;
026 import java.util.List;
027
028
029 /**
030 * <p>Default implementation of the <code>Rules</code> interface that supports
031 * the standard rule matching behavior. This class can also be used as a
032 * base class for specialized <code>Rules</code> implementations.</p>
033 *
034 * <p>The matching policies implemented by this class support two different
035 * types of pattern matching rules:</p>
036 * <ul>
037 * <li><em>Exact Match</em> - A pattern "a/b/c" exactly matches a
038 * <code><c></code> element, nested inside a <code><b></code>
039 * element, which is nested inside an <code><a></code> element.</li>
040 * <li><em>Tail Match</em> - A pattern "*/a/b" matches a
041 * <code><b></code> element, nested inside an <code><a></code>
042 * element, no matter how deeply the pair is nested.</li>
043 * </ul>
044 *
045 * <p>Note that wildcard patterns are ignored if an explicit match can be found
046 * (and when multiple wildcard patterns match, only the longest, ie most
047 * explicit, pattern is considered a match).</p>
048 *
049 * <p>See the package documentation for package org.apache.commons.digester
050 * for more information.</p>
051 */
052
053 public class RulesBase implements Rules {
054
055
056 // ----------------------------------------------------- Instance Variables
057
058
059 /**
060 * The set of registered Rule instances, keyed by the matching pattern.
061 * Each value is a List containing the Rules for that pattern, in the
062 * order that they were orginally registered.
063 */
064 protected HashMap cache = new HashMap();
065
066
067 /**
068 * The Digester instance with which this Rules instance is associated.
069 */
070 protected Digester digester = null;
071
072
073 /**
074 * The namespace URI for which subsequently added <code>Rule</code>
075 * objects are relevant, or <code>null</code> for matching independent
076 * of namespaces.
077 */
078 protected String namespaceURI = null;
079
080
081 /**
082 * The set of registered Rule instances, in the order that they were
083 * originally registered.
084 */
085 protected ArrayList rules = new ArrayList();
086
087
088 // ------------------------------------------------------------- Properties
089
090
091 /**
092 * Return the Digester instance with which this Rules instance is
093 * associated.
094 */
095 public Digester getDigester() {
096
097 return (this.digester);
098
099 }
100
101
102 /**
103 * Set the Digester instance with which this Rules instance is associated.
104 *
105 * @param digester The newly associated Digester instance
106 */
107 public void setDigester(Digester digester) {
108
109 this.digester = digester;
110 Iterator items = rules.iterator();
111 while (items.hasNext()) {
112 Rule item = (Rule) items.next();
113 item.setDigester(digester);
114 }
115
116 }
117
118
119 /**
120 * Return the namespace URI that will be applied to all subsequently
121 * added <code>Rule</code> objects.
122 */
123 public String getNamespaceURI() {
124
125 return (this.namespaceURI);
126
127 }
128
129
130 /**
131 * Set the namespace URI that will be applied to all subsequently
132 * added <code>Rule</code> objects.
133 *
134 * @param namespaceURI Namespace URI that must match on all
135 * subsequently added rules, or <code>null</code> for matching
136 * regardless of the current namespace URI
137 */
138 public void setNamespaceURI(String namespaceURI) {
139
140 this.namespaceURI = namespaceURI;
141
142 }
143
144
145 // --------------------------------------------------------- Public Methods
146
147
148 /**
149 * Register a new Rule instance matching the specified pattern.
150 *
151 * @param pattern Nesting pattern to be matched for this Rule
152 * @param rule Rule instance to be registered
153 */
154 public void add(String pattern, Rule rule) {
155 // to help users who accidently add '/' to the end of their patterns
156 int patternLength = pattern.length();
157 if (patternLength>1 && pattern.endsWith("/")) {
158 pattern = pattern.substring(0, patternLength-1);
159 }
160
161
162 List list = (List) cache.get(pattern);
163 if (list == null) {
164 list = new ArrayList();
165 cache.put(pattern, list);
166 }
167 list.add(rule);
168 rules.add(rule);
169 if (this.digester != null) {
170 rule.setDigester(this.digester);
171 }
172 if (this.namespaceURI != null) {
173 rule.setNamespaceURI(this.namespaceURI);
174 }
175
176 }
177
178
179 /**
180 * Clear all existing Rule instance registrations.
181 */
182 public void clear() {
183
184 cache.clear();
185 rules.clear();
186
187 }
188
189
190 /**
191 * Return a List of all registered Rule instances that match the specified
192 * nesting pattern, or a zero-length List if there are no matches. If more
193 * than one Rule instance matches, they <strong>must</strong> be returned
194 * in the order originally registered through the <code>add()</code>
195 * method.
196 *
197 * @param pattern Nesting pattern to be matched
198 *
199 * @deprecated Call match(namespaceURI,pattern) instead.
200 */
201 public List match(String pattern) {
202
203 return (match(null, pattern));
204
205 }
206
207
208 /**
209 * Return a List of all registered Rule instances that match the specified
210 * nesting pattern, or a zero-length List if there are no matches. If more
211 * than one Rule instance matches, they <strong>must</strong> be returned
212 * in the order originally registered through the <code>add()</code>
213 * method.
214 *
215 * @param namespaceURI Namespace URI for which to select matching rules,
216 * or <code>null</code> to match regardless of namespace URI
217 * @param pattern Nesting pattern to be matched
218 */
219 public List match(String namespaceURI, String pattern) {
220
221 // List rulesList = (List) this.cache.get(pattern);
222 List rulesList = lookup(namespaceURI, pattern);
223 if ((rulesList == null) || (rulesList.size() < 1)) {
224 // Find the longest key, ie more discriminant
225 String longKey = "";
226 Iterator keys = this.cache.keySet().iterator();
227 while (keys.hasNext()) {
228 String key = (String) keys.next();
229 if (key.startsWith("*/")) {
230 if (pattern.equals(key.substring(2)) ||
231 pattern.endsWith(key.substring(1))) {
232 if (key.length() > longKey.length()) {
233 // rulesList = (List) this.cache.get(key);
234 rulesList = lookup(namespaceURI, key);
235 longKey = key;
236 }
237 }
238 }
239 }
240 }
241 if (rulesList == null) {
242 rulesList = new ArrayList();
243 }
244 return (rulesList);
245
246 }
247
248
249 /**
250 * Return a List of all registered Rule instances, or a zero-length List
251 * if there are no registered Rule instances. If more than one Rule
252 * instance has been registered, they <strong>must</strong> be returned
253 * in the order originally registered through the <code>add()</code>
254 * method.
255 */
256 public List rules() {
257
258 return (this.rules);
259
260 }
261
262
263 // ------------------------------------------------------ Protected Methods
264
265
266 /**
267 * Return a List of Rule instances for the specified pattern that also
268 * match the specified namespace URI (if any). If there are no such
269 * rules, return <code>null</code>.
270 *
271 * @param namespaceURI Namespace URI to match, or <code>null</code> to
272 * select matching rules regardless of namespace URI
273 * @param pattern Pattern to be matched
274 */
275 protected List lookup(String namespaceURI, String pattern) {
276
277 // Optimize when no namespace URI is specified
278 List list = (List) this.cache.get(pattern);
279 if (list == null) {
280 return (null);
281 }
282 if ((namespaceURI == null) || (namespaceURI.length() == 0)) {
283 return (list);
284 }
285
286 // Select only Rules that match on the specified namespace URI
287 ArrayList results = new ArrayList();
288 Iterator items = list.iterator();
289 while (items.hasNext()) {
290 Rule item = (Rule) items.next();
291 if ((namespaceURI.equals(item.getNamespaceURI())) ||
292 (item.getNamespaceURI() == null)) {
293 results.add(item);
294 }
295 }
296 return (results);
297
298 }
299
300
301 }