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