1. /*
  2. * Copyright 2001-2004 The Apache Software Foundation.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package org.apache.commons.betwixt.io.read;
  17. import java.beans.IntrospectionException;
  18. import java.util.HashMap;
  19. import org.apache.commons.betwixt.AttributeDescriptor;
  20. import org.apache.commons.betwixt.BindingConfiguration;
  21. import org.apache.commons.betwixt.ElementDescriptor;
  22. import org.apache.commons.betwixt.XMLBeanInfo;
  23. import org.apache.commons.betwixt.XMLIntrospector;
  24. import org.apache.commons.betwixt.expression.Context;
  25. import org.apache.commons.betwixt.expression.Updater;
  26. import org.apache.commons.betwixt.strategy.ActionMappingStrategy;
  27. import org.apache.commons.collections.ArrayStack;
  28. import org.apache.commons.logging.Log;
  29. import org.apache.commons.logging.LogFactory;
  30. import org.xml.sax.Attributes;
  31. /**
  32. * <p>Extends <code>Context</code> to provide read specific functionality.</p>
  33. * <p>
  34. * Three stacks are used to manage the reading:
  35. * </p>
  36. * <ul>
  37. * <li><strong>Action mapping stack</strong> contains the {@link MappingAction}'s
  38. * used to execute the mapping of the current element and it's ancesters back to the
  39. * document root.</li>
  40. * <li><strong>Result stack</strong> contains the objects which are bound
  41. * to the current element and to each of it's ancester's back to the root</li>
  42. * <li><strong>Element mapping stack</strong> records the names of the element
  43. * and the classes to which they are bound</li>
  44. * </ul>
  45. * @author Robert Burrell Donkina
  46. * @since 0.5
  47. */
  48. public class ReadContext extends Context {
  49. /** Beans indexed by ID strings */
  50. private HashMap beansById = new HashMap();
  51. /** Classloader to be used to load beans during reading */
  52. private ClassLoader classLoader;
  53. /** The read specific configuration */
  54. private ReadConfiguration readConfiguration;
  55. /** Records the element path together with the locations where classes were mapped*/
  56. private ArrayStack elementMappingStack = new ArrayStack();
  57. /** Contains actions for each element */
  58. private ArrayStack actionMappingStack = new ArrayStack();
  59. /** Stack contains all beans created */
  60. private ArrayStack objectStack = new ArrayStack();
  61. private ArrayStack descriptorStack = new ArrayStack();
  62. private ArrayStack updaterStack = new ArrayStack();
  63. private Class rootClass;
  64. /** The <code>XMLIntrospector</code> to be used to map the xml*/
  65. private XMLIntrospector xmlIntrospector;
  66. /**
  67. * Constructs a <code>ReadContext</code> with the same settings
  68. * as an existing <code>Context</code>.
  69. * @param context not null
  70. * @param readConfiguration not null
  71. */
  72. public ReadContext(Context context, ReadConfiguration readConfiguration) {
  73. super(context);
  74. this.readConfiguration = readConfiguration;
  75. }
  76. /**
  77. * Constructs a <code>ReadContext</code> with standard log.
  78. * @param bindingConfiguration the dynamic configuration, not null
  79. * @param readConfiguration the extra read configuration not null
  80. */
  81. public ReadContext(
  82. BindingConfiguration bindingConfiguration,
  83. ReadConfiguration readConfiguration) {
  84. this(
  85. LogFactory.getLog(ReadContext.class),
  86. bindingConfiguration,
  87. readConfiguration);
  88. }
  89. /**
  90. * Base constructor
  91. * @param log log to this Log
  92. * @param bindingConfiguration the dynamic configuration, not null
  93. * @param readConfiguration the extra read configuration not null
  94. */
  95. public ReadContext(
  96. Log log,
  97. BindingConfiguration bindingConfiguration,
  98. ReadConfiguration readConfiguration) {
  99. super(null, log, bindingConfiguration);
  100. this.readConfiguration = readConfiguration;
  101. }
  102. /**
  103. * Constructs a <code>ReadContext</code>
  104. * with the same settings as an existing <code>Context</code>.
  105. * @param readContext not null
  106. */
  107. public ReadContext(ReadContext readContext) {
  108. super(readContext);
  109. beansById = readContext.beansById;
  110. classLoader = readContext.classLoader;
  111. readConfiguration = readContext.readConfiguration;
  112. }
  113. /**
  114. * Puts a bean into storage indexed by an (xml) ID.
  115. *
  116. * @param id the ID string of the xml element associated with the bean
  117. * @param bean the Object to store, not null
  118. */
  119. public void putBean(String id, Object bean) {
  120. beansById.put(id, bean);
  121. }
  122. /**
  123. * Gets a bean from storage by an (xml) ID.
  124. *
  125. * @param id the ID string of the xml element associated with the bean
  126. * @return the Object that the ID references, otherwise null
  127. */
  128. public Object getBean(String id) {
  129. return beansById.get(id);
  130. }
  131. /**
  132. * Clears the beans indexed by id.
  133. */
  134. public void clearBeans() {
  135. beansById.clear();
  136. }
  137. /**
  138. * Gets the classloader to be used.
  139. * @return the classloader that should be used to load all classes, possibly null
  140. */
  141. public ClassLoader getClassLoader() {
  142. return classLoader;
  143. }
  144. /**
  145. * Sets the classloader to be used.
  146. * @param classLoader the ClassLoader to be used, possibly null
  147. */
  148. public void setClassLoader(ClassLoader classLoader) {
  149. this.classLoader = classLoader;
  150. }
  151. /**
  152. * Gets the <code>BeanCreationChange</code> to be used to create beans
  153. * when an element is mapped.
  154. * @return the BeanCreationChain not null
  155. */
  156. public BeanCreationChain getBeanCreationChain() {
  157. return readConfiguration.getBeanCreationChain();
  158. }
  159. /**
  160. * Gets the strategy used to define default mappings actions
  161. * for elements.
  162. * @return <code>ActionMappingStrategy</code>. not null
  163. */
  164. public ActionMappingStrategy getActionMappingStrategy() {
  165. return readConfiguration.getActionMappingStrategy();
  166. }
  167. /**
  168. * Pops the top element from the element mapping stack.
  169. * Also removes any mapped class marks below the top element.
  170. *
  171. * @return the name of the element popped
  172. * if there are any more elements on the stack, otherwise null.
  173. * This is the local name if the parser is namespace aware, otherwise the name
  174. */
  175. public String popElement() {
  176. // since the descriptor stack is populated by pushElement,
  177. // need to ensure that it's correct popped by popElement
  178. if (!descriptorStack.isEmpty()) {
  179. descriptorStack.pop();
  180. }
  181. if (!updaterStack.isEmpty()) {
  182. updaterStack.pop();
  183. }
  184. Object top = null;
  185. if (!elementMappingStack.isEmpty()) {
  186. top = elementMappingStack.pop();
  187. if (top != null) {
  188. if (!(top instanceof String)) {
  189. return popElement();
  190. }
  191. }
  192. }
  193. return (String) top;
  194. }
  195. /**
  196. * Gets the element name for the currently mapped element.
  197. * @return the name of the currently mapped element,
  198. * or null if there has been no element mapped
  199. */
  200. public String getCurrentElement() {
  201. return (String) elementMappingStack.peek();
  202. }
  203. /**
  204. * Gets the Class that was last mapped, if there is one.
  205. *
  206. * @return the Class last marked as mapped
  207. * or null if no class has been mapped
  208. */
  209. public Class getLastMappedClass() {
  210. Class lastMapped = null;
  211. for (int i = 0, size = elementMappingStack.size();
  212. i < size;
  213. i++) {
  214. Object entry = elementMappingStack.peek(i);
  215. if (entry instanceof Class) {
  216. lastMapped = (Class) entry;
  217. break;
  218. }
  219. }
  220. return lastMapped;
  221. }
  222. private ElementDescriptor getParentDescriptor() throws IntrospectionException {
  223. ElementDescriptor result = null;
  224. if (descriptorStack.size() > 1) {
  225. result = (ElementDescriptor) descriptorStack.peek(1);
  226. }
  227. return result;
  228. }
  229. /**
  230. * Pushes the given element onto the element mapping stack.
  231. *
  232. * @param elementName the local name if the parser is namespace aware,
  233. * otherwise the full element name. Not null
  234. */
  235. public void pushElement(String elementName) throws Exception {
  236. elementMappingStack.push(elementName);
  237. // special case to ensure that root class is appropriately marked
  238. //TODO: is this really necessary?
  239. ElementDescriptor nextDescriptor = null;
  240. if (elementMappingStack.size() == 1 && rootClass != null) {
  241. markClassMap(rootClass);
  242. XMLBeanInfo rootClassInfo
  243. = getXMLIntrospector().introspect(rootClass);
  244. nextDescriptor = rootClassInfo.getElementDescriptor();
  245. } else {
  246. ElementDescriptor currentDescriptor = getCurrentDescriptor();
  247. if (currentDescriptor != null) {
  248. nextDescriptor = currentDescriptor.getElementDescriptor(elementName);
  249. }
  250. }
  251. Updater updater = null;
  252. if (nextDescriptor != null) {
  253. updater = nextDescriptor.getUpdater();
  254. }
  255. updaterStack.push(updater);
  256. descriptorStack.push(nextDescriptor);
  257. }
  258. /**
  259. * Marks the element name stack with a class mapping.
  260. * Relative paths and last mapped class are calculated using these marks.
  261. *
  262. * @param mappedClazz the Class which has been mapped at the current path, not null
  263. */
  264. public void markClassMap(Class mappedClazz) throws IntrospectionException {
  265. if (mappedClazz.isArray()) {
  266. mappedClazz = mappedClazz.getComponentType();
  267. }
  268. elementMappingStack.push(mappedClazz);
  269. XMLBeanInfo mappedClassInfo = getXMLIntrospector().introspect(mappedClazz);
  270. ElementDescriptor mappedElementDescriptor = mappedClassInfo.getElementDescriptor();
  271. descriptorStack.push(mappedElementDescriptor);
  272. Updater updater = mappedElementDescriptor.getUpdater();
  273. updaterStack.push(updater);
  274. }
  275. /**
  276. * Pops an action mapping from the stack
  277. * @return
  278. */
  279. public MappingAction popMappingAction() {
  280. return (MappingAction) actionMappingStack.pop();
  281. }
  282. /**
  283. * Pushs an action mapping onto the stack
  284. * @param mappingAction
  285. */
  286. public void pushMappingAction(MappingAction mappingAction) {
  287. actionMappingStack.push(mappingAction);
  288. }
  289. /**
  290. * Gets the current mapping action
  291. * @return MappingAction
  292. */
  293. public MappingAction currentMappingAction() {
  294. if (actionMappingStack.size() == 0)
  295. {
  296. return null;
  297. }
  298. return (MappingAction) actionMappingStack.peek();
  299. }
  300. public Object getBean() {
  301. return objectStack.peek();
  302. }
  303. public void setBean(Object bean) {
  304. // TODO: maybe need to deprecate the set bean method
  305. // and push into subclass
  306. // for now, do nothing
  307. }
  308. /**
  309. * Pops the last mapping <code>Object</code> from the
  310. * stack containing beans that have been mapped.
  311. * @return
  312. */
  313. public Object popBean() {
  314. return objectStack.pop();
  315. }
  316. /**
  317. * Pushs a newly mapped <code>Object</code> onto the mapped bean stack.
  318. * @param bean
  319. */
  320. public void pushBean(Object bean) {
  321. objectStack.push(bean);
  322. }
  323. /**
  324. * Gets the <code>XMLIntrospector</code> to be used to create
  325. * the mappings for the xml.
  326. * @return <code>XMLIntrospector, not null
  327. */
  328. public XMLIntrospector getXMLIntrospector() {
  329. // read context is not intended to be used by multiple threads
  330. // so no need to worry about lazy creation
  331. if (xmlIntrospector == null) {
  332. xmlIntrospector = new XMLIntrospector();
  333. }
  334. return xmlIntrospector;
  335. }
  336. /**
  337. * Sets the <code>XMLIntrospector</code> to be used to create
  338. * the mappings for the xml.
  339. * @param xmlIntrospector <code>XMLIntrospector</code>, not null
  340. */
  341. public void setXMLIntrospector(XMLIntrospector xmlIntrospector) {
  342. this.xmlIntrospector = xmlIntrospector;
  343. }
  344. public Class getRootClass() {
  345. return rootClass;
  346. }
  347. public void setRootClass(Class rootClass) {
  348. this.rootClass = rootClass;
  349. }
  350. /**
  351. * Gets the <code>ElementDescriptor</code> that describes the
  352. * mapping for the current element.
  353. * @return <code>ElementDescriptor</code> or null if there is no
  354. * current mapping
  355. * @throws Exception
  356. */
  357. public ElementDescriptor getCurrentDescriptor() throws Exception {
  358. ElementDescriptor result = null;
  359. if (!descriptorStack.empty()) {
  360. result = (ElementDescriptor) descriptorStack.peek();
  361. }
  362. return result;
  363. }
  364. /**
  365. * Populates the object mapped by the <code>AttributeDescriptor</code>s
  366. * with the values in the given <code>Attributes</code>.
  367. * @param attributeDescriptors <code>AttributeDescriptor</code>s, not null
  368. * @param attributes <code>Attributes</code>, not null
  369. */
  370. public void populateAttributes(
  371. AttributeDescriptor[] attributeDescriptors,
  372. Attributes attributes) {
  373. Log log = getLog();
  374. if (attributeDescriptors != null) {
  375. for (int i = 0, size = attributeDescriptors.length;
  376. i < size;
  377. i++) {
  378. AttributeDescriptor attributeDescriptor =
  379. attributeDescriptors[i];
  380. // The following isn't really the right way to find the attribute
  381. // but it's quite robust.
  382. // The idea is that you try both namespace and local name first
  383. // and if this returns null try the qName.
  384. String value =
  385. attributes.getValue(
  386. attributeDescriptor.getURI(),
  387. attributeDescriptor.getLocalName());
  388. if (value == null) {
  389. value =
  390. attributes.getValue(
  391. attributeDescriptor.getQualifiedName());
  392. }
  393. if (log.isTraceEnabled()) {
  394. log.trace("Attr URL:" + attributeDescriptor.getURI());
  395. log.trace(
  396. "Attr LocalName:" + attributeDescriptor.getLocalName());
  397. log.trace(value);
  398. }
  399. Updater updater = attributeDescriptor.getUpdater();
  400. log.trace(updater);
  401. if (updater != null && value != null) {
  402. updater.update(this, value);
  403. }
  404. }
  405. }
  406. }
  407. /**
  408. * <p>Pushes an <code>Updater</code> onto the stack.</p>
  409. * <p>
  410. * <strong>Note</strong>Any action pushing an <code>Updater</code> onto
  411. * the stack should take responsibility for popping
  412. * the updater from the stack at an appropriate time.
  413. * </p>
  414. * <p>
  415. * <strong>Usage:</strong> this may be used by actions
  416. * which require a temporary object to be updated.
  417. * Pushing an updater onto the stack allow actions
  418. * downstream to transparently update the temporary proxy.
  419. * </p>
  420. * @param updater Updater, possibly null
  421. */
  422. public void pushUpdater(Updater updater) {
  423. updaterStack.push(updater);
  424. }
  425. /**
  426. * Pops the top <code>Updater</code> from the stack.
  427. * <p>
  428. * <strong>Note</strong>Any action pushing an <code>Updater</code> onto
  429. * the stack should take responsibility for popping
  430. * the updater from the stack at an appropriate time.
  431. * </p>
  432. * @return <code>Updater</code>, possibly null
  433. */
  434. public Updater popUpdater() {
  435. return (Updater) updaterStack.pop();
  436. }
  437. /**
  438. * Gets the current <code>Updater</code>.
  439. * This may (or may not) be the updater for the current
  440. * descriptor.
  441. * If the current descriptor is a bean child,
  442. * the the current updater will (most likely)
  443. * be the updater for the property.
  444. * Actions (that, for example, use proxy objects)
  445. * may push updaters onto the stack.
  446. * @return Updater, possibly null
  447. */
  448. public Updater getCurrentUpdater() {
  449. // TODO: think about whether this is right
  450. // it makes some sense to look back up the
  451. // stack until a non-empty updater is found.
  452. // actions who need to put a stock to this
  453. // behaviour can always use an ignoring implementation.
  454. Updater result = null;
  455. if (!updaterStack.empty()) {
  456. result = (Updater) updaterStack.peek();
  457. if ( result == null && updaterStack.size() >1 ) {
  458. result = (Updater) updaterStack.peek(1);
  459. }
  460. }
  461. return result;
  462. }
  463. }