1. /* $Id: RulesBase.java,v 1.18 2004/05/10 06:30:06 skitching Exp $
  2. *
  3. * Copyright 2001-2004 The Apache Software Foundation.
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * 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.commons.digester;
  18. import java.util.ArrayList;
  19. import java.util.HashMap;
  20. import java.util.Iterator;
  21. import java.util.List;
  22. /**
  23. * <p>Default implementation of the <code>Rules</code> interface that supports
  24. * the standard rule matching behavior. This class can also be used as a
  25. * base class for specialized <code>Rules</code> implementations.</p>
  26. *
  27. * <p>The matching policies implemented by this class support two different
  28. * types of pattern matching rules:</p>
  29. * <ul>
  30. * <li><em>Exact Match</em> - A pattern "a/b/c" exactly matches a
  31. * <code><c></code> element, nested inside a <code><b></code>
  32. * element, which is nested inside an <code><a></code> element.</li>
  33. * <li><em>Tail Match</em> - A pattern "*/a/b" matches a
  34. * <code><b></code> element, nested inside an <code><a></code>
  35. * element, no matter how deeply the pair is nested.</li>
  36. * </ul>
  37. */
  38. public class RulesBase implements Rules {
  39. // ----------------------------------------------------- Instance Variables
  40. /**
  41. * The set of registered Rule instances, keyed by the matching pattern.
  42. * Each value is a List containing the Rules for that pattern, in the
  43. * order that they were orginally registered.
  44. */
  45. protected HashMap cache = new HashMap();
  46. /**
  47. * The Digester instance with which this Rules instance is associated.
  48. */
  49. protected Digester digester = null;
  50. /**
  51. * The namespace URI for which subsequently added <code>Rule</code>
  52. * objects are relevant, or <code>null</code> for matching independent
  53. * of namespaces.
  54. */
  55. protected String namespaceURI = null;
  56. /**
  57. * The set of registered Rule instances, in the order that they were
  58. * originally registered.
  59. */
  60. protected ArrayList rules = new ArrayList();
  61. // ------------------------------------------------------------- Properties
  62. /**
  63. * Return the Digester instance with which this Rules instance is
  64. * associated.
  65. */
  66. public Digester getDigester() {
  67. return (this.digester);
  68. }
  69. /**
  70. * Set the Digester instance with which this Rules instance is associated.
  71. *
  72. * @param digester The newly associated Digester instance
  73. */
  74. public void setDigester(Digester digester) {
  75. this.digester = digester;
  76. Iterator items = rules.iterator();
  77. while (items.hasNext()) {
  78. Rule item = (Rule) items.next();
  79. item.setDigester(digester);
  80. }
  81. }
  82. /**
  83. * Return the namespace URI that will be applied to all subsequently
  84. * added <code>Rule</code> objects.
  85. */
  86. public String getNamespaceURI() {
  87. return (this.namespaceURI);
  88. }
  89. /**
  90. * Set the namespace URI that will be applied to all subsequently
  91. * added <code>Rule</code> objects.
  92. *
  93. * @param namespaceURI Namespace URI that must match on all
  94. * subsequently added rules, or <code>null</code> for matching
  95. * regardless of the current namespace URI
  96. */
  97. public void setNamespaceURI(String namespaceURI) {
  98. this.namespaceURI = namespaceURI;
  99. }
  100. // --------------------------------------------------------- Public Methods
  101. /**
  102. * Register a new Rule instance matching the specified pattern.
  103. *
  104. * @param pattern Nesting pattern to be matched for this Rule
  105. * @param rule Rule instance to be registered
  106. */
  107. public void add(String pattern, Rule rule) {
  108. // to help users who accidently add '/' to the end of their patterns
  109. int patternLength = pattern.length();
  110. if (patternLength>1 && pattern.endsWith("/")) {
  111. pattern = pattern.substring(0, patternLength-1);
  112. }
  113. List list = (List) cache.get(pattern);
  114. if (list == null) {
  115. list = new ArrayList();
  116. cache.put(pattern, list);
  117. }
  118. list.add(rule);
  119. rules.add(rule);
  120. if (this.digester != null) {
  121. rule.setDigester(this.digester);
  122. }
  123. if (this.namespaceURI != null) {
  124. rule.setNamespaceURI(this.namespaceURI);
  125. }
  126. }
  127. /**
  128. * Clear all existing Rule instance registrations.
  129. */
  130. public void clear() {
  131. cache.clear();
  132. rules.clear();
  133. }
  134. /**
  135. * Return a List of all registered Rule instances that match the specified
  136. * nesting pattern, or a zero-length List if there are no matches. If more
  137. * than one Rule instance matches, they <strong>must</strong> be returned
  138. * in the order originally registered through the <code>add()</code>
  139. * method.
  140. *
  141. * @param pattern Nesting pattern to be matched
  142. *
  143. * @deprecated Call match(namespaceURI,pattern) instead.
  144. */
  145. public List match(String pattern) {
  146. return (match(null, pattern));
  147. }
  148. /**
  149. * Return a List of all registered Rule instances that match the specified
  150. * nesting pattern, or a zero-length List if there are no matches. If more
  151. * than one Rule instance matches, they <strong>must</strong> be returned
  152. * in the order originally registered through the <code>add()</code>
  153. * method.
  154. *
  155. * @param namespaceURI Namespace URI for which to select matching rules,
  156. * or <code>null</code> to match regardless of namespace URI
  157. * @param pattern Nesting pattern to be matched
  158. */
  159. public List match(String namespaceURI, String pattern) {
  160. // List rulesList = (List) this.cache.get(pattern);
  161. List rulesList = lookup(namespaceURI, pattern);
  162. if ((rulesList == null) || (rulesList.size() < 1)) {
  163. // Find the longest key, ie more discriminant
  164. String longKey = "";
  165. Iterator keys = this.cache.keySet().iterator();
  166. while (keys.hasNext()) {
  167. String key = (String) keys.next();
  168. if (key.startsWith("*/")) {
  169. if (pattern.equals(key.substring(2)) ||
  170. pattern.endsWith(key.substring(1))) {
  171. if (key.length() > longKey.length()) {
  172. // rulesList = (List) this.cache.get(key);
  173. rulesList = lookup(namespaceURI, key);
  174. longKey = key;
  175. }
  176. }
  177. }
  178. }
  179. }
  180. if (rulesList == null) {
  181. rulesList = new ArrayList();
  182. }
  183. return (rulesList);
  184. }
  185. /**
  186. * Return a List of all registered Rule instances, or a zero-length List
  187. * if there are no registered Rule instances. If more than one Rule
  188. * instance has been registered, they <strong>must</strong> be returned
  189. * in the order originally registered through the <code>add()</code>
  190. * method.
  191. */
  192. public List rules() {
  193. return (this.rules);
  194. }
  195. // ------------------------------------------------------ Protected Methods
  196. /**
  197. * Return a List of Rule instances for the specified pattern that also
  198. * match the specified namespace URI (if any). If there are no such
  199. * rules, return <code>null</code>.
  200. *
  201. * @param namespaceURI Namespace URI to match, or <code>null</code> to
  202. * select matching rules regardless of namespace URI
  203. * @param pattern Pattern to be matched
  204. */
  205. protected List lookup(String namespaceURI, String pattern) {
  206. // Optimize when no namespace URI is specified
  207. List list = (List) this.cache.get(pattern);
  208. if (list == null) {
  209. return (null);
  210. }
  211. if ((namespaceURI == null) || (namespaceURI.length() == 0)) {
  212. return (list);
  213. }
  214. // Select only Rules that match on the specified namespace URI
  215. ArrayList results = new ArrayList();
  216. Iterator items = list.iterator();
  217. while (items.hasNext()) {
  218. Rule item = (Rule) items.next();
  219. if ((namespaceURI.equals(item.getNamespaceURI())) ||
  220. (item.getNamespaceURI() == null)) {
  221. results.add(item);
  222. }
  223. }
  224. return (results);
  225. }
  226. }