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 }