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