1. /* $Id: PluginRules.java,v 1.17 2004/05/10 06:44:13 skitching Exp $
  2. *
  3. * Copyright 2003-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.plugins;
  18. import java.util.ArrayList;
  19. import java.util.Iterator;
  20. import java.util.List;
  21. import java.util.ListIterator;
  22. import java.util.Map;
  23. import java.util.HashMap;
  24. import java.util.TreeMap;
  25. import java.util.Comparator;
  26. import org.apache.commons.digester.Digester;
  27. import org.apache.commons.digester.Rule;
  28. import org.apache.commons.digester.Rules;
  29. import org.apache.commons.digester.RulesBase;
  30. import org.apache.commons.logging.Log;
  31. /**
  32. * A custom digester Rules manager which must be used as the Rules object
  33. * when using the plugins module functionality.
  34. * <p>
  35. * During parsing, a linked list of PluginCreateRule instances develop, and
  36. * this list also acts like a stack. The original instance that was set before
  37. * the Digester started parsing is always at the tail of the list, and the
  38. * Digester always holds a reference to the instance at the head of the list
  39. * in the rules member. Initially, this list/stack holds just one instance,
  40. * ie head and tail are the same object.
  41. * <p>
  42. * When the start of an xml element causes a PluginCreateRule to fire, a new
  43. * PluginRules instance is created and inserted at the head of the list (ie
  44. * pushed onto the stack of Rules objects). Digester.getRules() therefore
  45. * returns this new Rules object, and any custom rules associated with that
  46. * plugin are added to that instance.
  47. * <p>
  48. * When the end of the xml element is encountered (and therefore the
  49. * PluginCreateRule end method fires), the stack of Rules objects is popped,
  50. * so that Digester.getRules returns the previous Rules object.
  51. *
  52. * @since 1.6
  53. */
  54. public class PluginRules implements Rules {
  55. /**
  56. * The Digester instance with which this Rules instance is associated.
  57. */
  58. protected Digester digester = null;
  59. /**
  60. * The rules implementation that we are "enhancing" with plugins
  61. * functionality, as per the Decorator pattern.
  62. */
  63. private Rules decoratedRules;
  64. /** Object which contains information about all known plugins. */
  65. private PluginManager pluginManager;
  66. /**
  67. * The path below which this rules object has responsibility.
  68. * For paths shorter than or equal the mountpoint, the parent's
  69. * match is called.
  70. */
  71. private String mountPoint = null;
  72. /**
  73. * The Rules object that holds rules applying "above" the mountpoint,
  74. * ie the next Rules object down in the stack.
  75. */
  76. private PluginRules parent = null;
  77. /**
  78. * A reference to the object that holds all data which should only
  79. * exist once per digester instance.
  80. */
  81. private PluginContext pluginContext = null;
  82. // ------------------------------------------------------------- Constructor
  83. /**
  84. * Constructor for top-level Rules objects. Exactly one of these must
  85. * be created and installed into the Digester instance as the Rules
  86. * object before parsing starts.
  87. */
  88. public PluginRules() {
  89. this(new RulesBase());
  90. }
  91. /**
  92. * Constructor for top-level Rules object which handles rule-matching
  93. * using the specified implementation.
  94. */
  95. public PluginRules(Rules decoratedRules) {
  96. this.decoratedRules = decoratedRules;
  97. pluginContext = new PluginContext();
  98. pluginManager = new PluginManager(pluginContext);
  99. }
  100. /**
  101. * Constructs a Rules instance which has a parent Rules object
  102. * (which is different from having a delegate rules object).
  103. * <p>
  104. * One of these is created each time a PluginCreateRule's begin method
  105. * fires, in order to manage the custom rules associated with whatever
  106. * concrete plugin class the user has specified.
  107. *
  108. * @param mountPoint is the digester match path for the element
  109. * matching a PluginCreateRule which caused this "nested parsing scope"
  110. * to begin.
  111. * @param parent must be non-null.
  112. */
  113. PluginRules(String mountPoint, PluginRules parent) {
  114. // no need to set digester or decoratedRules.digester,
  115. // because when Digester.setRules is called, the setDigester
  116. // method on this object will be called.
  117. decoratedRules = new RulesBase();
  118. pluginContext = parent.pluginContext;
  119. pluginManager = new PluginManager(parent.pluginManager);
  120. this.mountPoint = mountPoint;
  121. this.parent = parent;
  122. }
  123. // ------------------------------------------------------------- Properties
  124. /**
  125. * Return the parent Rules object.
  126. */
  127. public Rules getParent() {
  128. return parent;
  129. }
  130. /**
  131. * Return the Digester instance with which this instance is associated.
  132. */
  133. public Digester getDigester() {
  134. return digester;
  135. }
  136. /**
  137. * Set the Digester instance with which this Rules instance is associated.
  138. *
  139. * @param digester The newly associated Digester instance
  140. */
  141. public void setDigester(Digester digester) {
  142. this.digester = digester;
  143. decoratedRules.setDigester(digester);
  144. }
  145. /**
  146. * Return the namespace URI that will be applied to all subsequently
  147. * added <code>Rule</code> objects.
  148. */
  149. public String getNamespaceURI() {
  150. return decoratedRules.getNamespaceURI();
  151. }
  152. /**
  153. * Set the namespace URI that will be applied to all subsequently
  154. * added <code>Rule</code> objects.
  155. *
  156. * @param namespaceURI Namespace URI that must match on all
  157. * subsequently added rules, or <code>null</code> for matching
  158. * regardless of the current namespace URI
  159. */
  160. public void setNamespaceURI(String namespaceURI) {
  161. decoratedRules.setNamespaceURI(namespaceURI);
  162. }
  163. /**
  164. * Return the object which "knows" about all declared plugins.
  165. *
  166. * @return The pluginManager value
  167. */
  168. public PluginManager getPluginManager() {
  169. return pluginManager;
  170. }
  171. /**
  172. * See {@link PluginContext#getRuleFinders}.
  173. */
  174. public List getRuleFinders() {
  175. return pluginContext.getRuleFinders();
  176. }
  177. /**
  178. * See {@link PluginContext#setRuleFinders}.
  179. */
  180. public void setRuleFinders(List ruleFinders) {
  181. pluginContext.setRuleFinders(ruleFinders);
  182. }
  183. // --------------------------------------------------------- Public Methods
  184. /**
  185. * This package-scope method is used by the PluginCreateRule class to
  186. * get direct access to the rules that were dynamically added by the
  187. * plugin. No other class should need access to this object.
  188. */
  189. Rules getDecoratedRules() {
  190. return decoratedRules;
  191. }
  192. /**
  193. * Return the list of rules registered with this object, in the order
  194. * they were registered with this object.
  195. * <p>
  196. * Note that Rule objects stored in parent Rules objects are not
  197. * returned by this method.
  198. *
  199. * @return list of all Rule objects known to this Rules instance.
  200. */
  201. public List rules() {
  202. return decoratedRules.rules();
  203. }
  204. /**
  205. * Register a new Rule instance matching the specified pattern.
  206. *
  207. * @param pattern Nesting pattern to be matched for this Rule.
  208. * This parameter treats equally patterns that begin with and without
  209. * a leading slash ('/').
  210. * @param rule Rule instance to be registered
  211. */
  212. public void add(String pattern, Rule rule) {
  213. Log log = LogUtils.getLogger(digester);
  214. boolean debug = log.isDebugEnabled();
  215. if (debug) {
  216. log.debug("add entry" + ": mapping pattern [" + pattern + "]" +
  217. " to rule of type [" + rule.getClass().getName() + "]");
  218. }
  219. // allow patterns with a leading slash character
  220. if (pattern.startsWith("/"))
  221. {
  222. pattern = pattern.substring(1);
  223. }
  224. if (mountPoint != null) {
  225. if (!pattern.equals(mountPoint)
  226. && !pattern.startsWith(mountPoint + "/")) {
  227. // This can only occur if a plugin attempts to add a
  228. // rule with a pattern that doesn't start with the
  229. // prefix passed to the addRules method. Plugins mustn't
  230. // add rules outside the scope of the tag they were specified
  231. // on, so refuse this.
  232. // alas, can't throw exception
  233. log.warn(
  234. "An attempt was made to add a rule with a pattern that"
  235. + "is not at or below the mountpoint of the current"
  236. + " PluginRules object."
  237. + " Rule pattern: " + pattern
  238. + ", mountpoint: " + mountPoint
  239. + ", rule type: " + rule.getClass().getName());
  240. return;
  241. }
  242. }
  243. decoratedRules.add(pattern, rule);
  244. if (rule instanceof InitializableRule) {
  245. try {
  246. ((InitializableRule)rule).postRegisterInit(pattern);
  247. } catch (PluginConfigurationException e) {
  248. // Currently, Digester doesn't handle exceptions well
  249. // from the add method. The workaround is for the
  250. // initialisable rule to remember that its initialisation
  251. // failed, and to throw the exception when begin is
  252. // called for the first time.
  253. if (debug) {
  254. log.debug("Rule initialisation failed", e);
  255. }
  256. // throw e; -- alas, can't do this
  257. return;
  258. }
  259. }
  260. if (debug) {
  261. log.debug("add exit" + ": mapped pattern [" + pattern + "]" +
  262. " to rule of type [" + rule.getClass().getName() + "]");
  263. }
  264. }
  265. /**
  266. * Clear all rules.
  267. */
  268. public void clear() {
  269. decoratedRules.clear();
  270. }
  271. /**
  272. * Return a List of all registered Rule instances that match the specified
  273. * nesting pattern, or a zero-length List if there are no matches. If more
  274. * than one Rule instance matches, they <strong>must</strong> be returned
  275. * in the order originally registered through the <code>add()</code>
  276. * method.
  277. *
  278. * @param path the path to the xml nodes to be matched.
  279. *
  280. * @deprecated Call match(namespaceURI,pattern) instead.
  281. */
  282. public List match(String path) {
  283. return (match(null, path));
  284. }
  285. /**
  286. * Return a List of all registered Rule instances that match the specified
  287. * nodepath, or a zero-length List if there are no matches. If more
  288. * than one Rule instance matches, they <strong>must</strong> be returned
  289. * in the order originally registered through the <code>add()</code>
  290. * method.
  291. * <p>
  292. * @param namespaceURI Namespace URI for which to select matching rules,
  293. * or <code>null</code> to match regardless of namespace URI
  294. * @param path the path to the xml nodes to be matched.
  295. */
  296. public List match(String namespaceURI, String path) {
  297. Log log = LogUtils.getLogger(digester);
  298. boolean debug = log.isDebugEnabled();
  299. if (debug) {
  300. log.debug(
  301. "Matching path [" + path +
  302. "] on rules object " + this.toString());
  303. }
  304. List matches;
  305. if ((mountPoint != null) &&
  306. (path.length() <= mountPoint.length())) {
  307. if (debug) {
  308. log.debug(
  309. "Path [" + path + "] delegated to parent.");
  310. }
  311. matches = parent.match(namespaceURI, path);
  312. // Note that in the case where path equals mountPoint,
  313. // we deliberately return only the rules from the parent,
  314. // even though this object may hold some rules matching
  315. // this same path. See PluginCreateRule's begin, body and end
  316. // methods for the reason.
  317. } else {
  318. matches = decoratedRules.match(namespaceURI, path);
  319. }
  320. return matches;
  321. }
  322. /** See {@link PluginContext#setPluginClassAttribute}. */
  323. public void setPluginClassAttribute(String namespaceUri,
  324. String attrName) {
  325. pluginContext.setPluginClassAttribute(namespaceUri, attrName);
  326. }
  327. /** See {@link PluginContext#setPluginIdAttribute}. */
  328. public void setPluginIdAttribute(String namespaceUri,
  329. String attrName) {
  330. pluginContext.setPluginIdAttribute(namespaceUri, attrName);
  331. }
  332. /** See {@link PluginContext#getPluginClassAttrNs}. */
  333. public String getPluginClassAttrNs() {
  334. return pluginContext.getPluginClassAttrNs();
  335. }
  336. /** See {@link PluginContext#getPluginClassAttr}. */
  337. public String getPluginClassAttr() {
  338. return pluginContext.getPluginClassAttr();
  339. }
  340. /** See {@link PluginContext#getPluginIdAttrNs}. */
  341. public String getPluginIdAttrNs() {
  342. return pluginContext.getPluginIdAttrNs();
  343. }
  344. /** See {@link PluginContext#getPluginIdAttr}. */
  345. public String getPluginIdAttr() {
  346. return pluginContext.getPluginIdAttr();
  347. }
  348. }