View Javadoc

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  
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         // since the descriptor stack is populated by pushElement,
196         // need to ensure that it's correct popped by popElement
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 		// special case to ensure that root class is appropriately marked
266 		//TODO: is this really necessary?
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 		// TODO: maybe need to deprecate the set bean method
341 		// and push into subclass
342 		// for now, do nothing		
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         // read context is not intended to be used by multiple threads
369         // so no need to worry about lazy creation
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 				// The following isn't really the right way to find the attribute
427 				// but it's quite robust.
428 				// The idea is that you try both namespace and local name first
429 				// and if this returns null try the qName.
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         // TODO: think about whether this is right
502         //       it makes some sense to look back up the 
503         //       stack until a non-empty updater is found.
504         //       actions who need to put a stock to this 
505         //       behaviour can always use an ignoring implementation. 
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 }