- /*
- * Copyright 2001-2004 The Apache Software Foundation.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- package org.apache.commons.betwixt.io.read;
-
- import java.beans.IntrospectionException;
- import java.util.HashMap;
-
- import org.apache.commons.betwixt.AttributeDescriptor;
- import org.apache.commons.betwixt.BindingConfiguration;
- import org.apache.commons.betwixt.ElementDescriptor;
- import org.apache.commons.betwixt.XMLBeanInfo;
- import org.apache.commons.betwixt.XMLIntrospector;
- import org.apache.commons.betwixt.expression.Context;
- import org.apache.commons.betwixt.expression.Updater;
- import org.apache.commons.betwixt.strategy.ActionMappingStrategy;
- import org.apache.commons.collections.ArrayStack;
- import org.apache.commons.logging.Log;
- import org.apache.commons.logging.LogFactory;
- import org.xml.sax.Attributes;
-
- /**
- * <p>Extends <code>Context</code> to provide read specific functionality.</p>
- * <p>
- * Three stacks are used to manage the reading:
- * </p>
- * <ul>
- * <li><strong>Action mapping stack</strong> contains the {@link MappingAction}'s
- * used to execute the mapping of the current element and it's ancesters back to the
- * document root.</li>
- * <li><strong>Result stack</strong> contains the objects which are bound
- * to the current element and to each of it's ancester's back to the root</li>
- * <li><strong>Element mapping stack</strong> records the names of the element
- * and the classes to which they are bound</li>
- * </ul>
- * @author Robert Burrell Donkina
- * @since 0.5
- */
- public class ReadContext extends Context {
-
- /** Beans indexed by ID strings */
- private HashMap beansById = new HashMap();
- /** Classloader to be used to load beans during reading */
- private ClassLoader classLoader;
- /** The read specific configuration */
- private ReadConfiguration readConfiguration;
- /** Records the element path together with the locations where classes were mapped*/
- private ArrayStack elementMappingStack = new ArrayStack();
- /** Contains actions for each element */
- private ArrayStack actionMappingStack = new ArrayStack();
- /** Stack contains all beans created */
- private ArrayStack objectStack = new ArrayStack();
-
- private ArrayStack descriptorStack = new ArrayStack();
-
- private ArrayStack updaterStack = new ArrayStack();
-
- private Class rootClass;
- /** The <code>XMLIntrospector</code> to be used to map the xml*/
- private XMLIntrospector xmlIntrospector;
-
- /**
- * Constructs a <code>ReadContext</code> with the same settings
- * as an existing <code>Context</code>.
- * @param context not null
- * @param readConfiguration not null
- */
- public ReadContext(Context context, ReadConfiguration readConfiguration) {
- super(context);
- this.readConfiguration = readConfiguration;
- }
-
- /**
- * Constructs a <code>ReadContext</code> with standard log.
- * @param bindingConfiguration the dynamic configuration, not null
- * @param readConfiguration the extra read configuration not null
- */
- public ReadContext(
- BindingConfiguration bindingConfiguration,
- ReadConfiguration readConfiguration) {
- this(
- LogFactory.getLog(ReadContext.class),
- bindingConfiguration,
- readConfiguration);
- }
-
- /**
- * Base constructor
- * @param log log to this Log
- * @param bindingConfiguration the dynamic configuration, not null
- * @param readConfiguration the extra read configuration not null
- */
- public ReadContext(
- Log log,
- BindingConfiguration bindingConfiguration,
- ReadConfiguration readConfiguration) {
- super(null, log, bindingConfiguration);
- this.readConfiguration = readConfiguration;
- }
-
- /**
- * Constructs a <code>ReadContext</code>
- * with the same settings as an existing <code>Context</code>.
- * @param readContext not null
- */
- public ReadContext(ReadContext readContext) {
- super(readContext);
- beansById = readContext.beansById;
- classLoader = readContext.classLoader;
- readConfiguration = readContext.readConfiguration;
- }
-
- /**
- * Puts a bean into storage indexed by an (xml) ID.
- *
- * @param id the ID string of the xml element associated with the bean
- * @param bean the Object to store, not null
- */
- public void putBean(String id, Object bean) {
- beansById.put(id, bean);
- }
-
- /**
- * Gets a bean from storage by an (xml) ID.
- *
- * @param id the ID string of the xml element associated with the bean
- * @return the Object that the ID references, otherwise null
- */
- public Object getBean(String id) {
- return beansById.get(id);
- }
-
- /**
- * Clears the beans indexed by id.
- */
- public void clearBeans() {
- beansById.clear();
- }
-
- /**
- * Gets the classloader to be used.
- * @return the classloader that should be used to load all classes, possibly null
- */
- public ClassLoader getClassLoader() {
- return classLoader;
- }
-
- /**
- * Sets the classloader to be used.
- * @param classLoader the ClassLoader to be used, possibly null
- */
- public void setClassLoader(ClassLoader classLoader) {
- this.classLoader = classLoader;
- }
-
- /**
- * Gets the <code>BeanCreationChange</code> to be used to create beans
- * when an element is mapped.
- * @return the BeanCreationChain not null
- */
- public BeanCreationChain getBeanCreationChain() {
- return readConfiguration.getBeanCreationChain();
- }
-
- /**
- * Gets the strategy used to define default mappings actions
- * for elements.
- * @return <code>ActionMappingStrategy</code>. not null
- */
- public ActionMappingStrategy getActionMappingStrategy() {
- return readConfiguration.getActionMappingStrategy();
- }
-
- /**
- * Pops the top element from the element mapping stack.
- * Also removes any mapped class marks below the top element.
- *
- * @return the name of the element popped
- * if there are any more elements on the stack, otherwise null.
- * This is the local name if the parser is namespace aware, otherwise the name
- */
- public String popElement() {
- // since the descriptor stack is populated by pushElement,
- // need to ensure that it's correct popped by popElement
- if (!descriptorStack.isEmpty()) {
- descriptorStack.pop();
- }
-
- if (!updaterStack.isEmpty()) {
- updaterStack.pop();
- }
-
- Object top = null;
- if (!elementMappingStack.isEmpty()) {
- top = elementMappingStack.pop();
- if (top != null) {
- if (!(top instanceof String)) {
- return popElement();
- }
- }
- }
-
- return (String) top;
- }
-
- /**
- * Gets the element name for the currently mapped element.
- * @return the name of the currently mapped element,
- * or null if there has been no element mapped
- */
- public String getCurrentElement() {
- return (String) elementMappingStack.peek();
- }
-
- /**
- * Gets the Class that was last mapped, if there is one.
- *
- * @return the Class last marked as mapped
- * or null if no class has been mapped
- */
- public Class getLastMappedClass() {
- Class lastMapped = null;
- for (int i = 0, size = elementMappingStack.size();
- i < size;
- i++) {
- Object entry = elementMappingStack.peek(i);
- if (entry instanceof Class) {
- lastMapped = (Class) entry;
- break;
- }
- }
- return lastMapped;
- }
-
- private ElementDescriptor getParentDescriptor() throws IntrospectionException {
- ElementDescriptor result = null;
- if (descriptorStack.size() > 1) {
- result = (ElementDescriptor) descriptorStack.peek(1);
- }
- return result;
- }
-
-
- /**
- * Pushes the given element onto the element mapping stack.
- *
- * @param elementName the local name if the parser is namespace aware,
- * otherwise the full element name. Not null
- */
- public void pushElement(String elementName) throws Exception {
-
- elementMappingStack.push(elementName);
- // special case to ensure that root class is appropriately marked
- //TODO: is this really necessary?
- ElementDescriptor nextDescriptor = null;
- if (elementMappingStack.size() == 1 && rootClass != null) {
- markClassMap(rootClass);
- XMLBeanInfo rootClassInfo
- = getXMLIntrospector().introspect(rootClass);
- nextDescriptor = rootClassInfo.getElementDescriptor();
- } else {
- ElementDescriptor currentDescriptor = getCurrentDescriptor();
- if (currentDescriptor != null) {
- nextDescriptor = currentDescriptor.getElementDescriptor(elementName);
- }
- }
- Updater updater = null;
- if (nextDescriptor != null) {
- updater = nextDescriptor.getUpdater();
- }
- updaterStack.push(updater);
- descriptorStack.push(nextDescriptor);
- }
-
- /**
- * Marks the element name stack with a class mapping.
- * Relative paths and last mapped class are calculated using these marks.
- *
- * @param mappedClazz the Class which has been mapped at the current path, not null
- */
- public void markClassMap(Class mappedClazz) throws IntrospectionException {
- if (mappedClazz.isArray()) {
- mappedClazz = mappedClazz.getComponentType();
- }
- elementMappingStack.push(mappedClazz);
-
- XMLBeanInfo mappedClassInfo = getXMLIntrospector().introspect(mappedClazz);
- ElementDescriptor mappedElementDescriptor = mappedClassInfo.getElementDescriptor();
- descriptorStack.push(mappedElementDescriptor);
-
- Updater updater = mappedElementDescriptor.getUpdater();
- updaterStack.push(updater);
- }
-
- /**
- * Pops an action mapping from the stack
- * @return
- */
- public MappingAction popMappingAction() {
- return (MappingAction) actionMappingStack.pop();
- }
-
- /**
- * Pushs an action mapping onto the stack
- * @param mappingAction
- */
- public void pushMappingAction(MappingAction mappingAction) {
- actionMappingStack.push(mappingAction);
- }
-
- /**
- * Gets the current mapping action
- * @return MappingAction
- */
- public MappingAction currentMappingAction() {
- if (actionMappingStack.size() == 0)
- {
- return null;
- }
- return (MappingAction) actionMappingStack.peek();
- }
-
- public Object getBean() {
- return objectStack.peek();
- }
-
- public void setBean(Object bean) {
- // TODO: maybe need to deprecate the set bean method
- // and push into subclass
- // for now, do nothing
- }
-
- /**
- * Pops the last mapping <code>Object</code> from the
- * stack containing beans that have been mapped.
- * @return
- */
- public Object popBean() {
- return objectStack.pop();
- }
-
- /**
- * Pushs a newly mapped <code>Object</code> onto the mapped bean stack.
- * @param bean
- */
- public void pushBean(Object bean) {
- objectStack.push(bean);
- }
-
- /**
- * Gets the <code>XMLIntrospector</code> to be used to create
- * the mappings for the xml.
- * @return <code>XMLIntrospector, not null
- */
- public XMLIntrospector getXMLIntrospector() {
- // read context is not intended to be used by multiple threads
- // so no need to worry about lazy creation
- if (xmlIntrospector == null) {
- xmlIntrospector = new XMLIntrospector();
- }
- return xmlIntrospector;
- }
-
- /**
- * Sets the <code>XMLIntrospector</code> to be used to create
- * the mappings for the xml.
- * @param xmlIntrospector <code>XMLIntrospector</code>, not null
- */
- public void setXMLIntrospector(XMLIntrospector xmlIntrospector) {
- this.xmlIntrospector = xmlIntrospector;
- }
-
- public Class getRootClass() {
- return rootClass;
- }
-
- public void setRootClass(Class rootClass) {
- this.rootClass = rootClass;
- }
-
- /**
- * Gets the <code>ElementDescriptor</code> that describes the
- * mapping for the current element.
- * @return <code>ElementDescriptor</code> or null if there is no
- * current mapping
- * @throws Exception
- */
- public ElementDescriptor getCurrentDescriptor() throws Exception {
- ElementDescriptor result = null;
- if (!descriptorStack.empty()) {
- result = (ElementDescriptor) descriptorStack.peek();
- }
- return result;
- }
-
- /**
- * Populates the object mapped by the <code>AttributeDescriptor</code>s
- * with the values in the given <code>Attributes</code>.
- * @param attributeDescriptors <code>AttributeDescriptor</code>s, not null
- * @param attributes <code>Attributes</code>, not null
- */
- public void populateAttributes(
- AttributeDescriptor[] attributeDescriptors,
- Attributes attributes) {
-
- Log log = getLog();
- if (attributeDescriptors != null) {
- for (int i = 0, size = attributeDescriptors.length;
- i < size;
- i++) {
- AttributeDescriptor attributeDescriptor =
- attributeDescriptors[i];
-
- // The following isn't really the right way to find the attribute
- // but it's quite robust.
- // The idea is that you try both namespace and local name first
- // and if this returns null try the qName.
- String value =
- attributes.getValue(
- attributeDescriptor.getURI(),
- attributeDescriptor.getLocalName());
-
- if (value == null) {
- value =
- attributes.getValue(
- attributeDescriptor.getQualifiedName());
- }
-
- if (log.isTraceEnabled()) {
- log.trace("Attr URL:" + attributeDescriptor.getURI());
- log.trace(
- "Attr LocalName:" + attributeDescriptor.getLocalName());
- log.trace(value);
- }
-
- Updater updater = attributeDescriptor.getUpdater();
- log.trace(updater);
- if (updater != null && value != null) {
- updater.update(this, value);
- }
- }
- }
- }
-
- /**
- * <p>Pushes an <code>Updater</code> onto the stack.</p>
- * <p>
- * <strong>Note</strong>Any action pushing an <code>Updater</code> onto
- * the stack should take responsibility for popping
- * the updater from the stack at an appropriate time.
- * </p>
- * <p>
- * <strong>Usage:</strong> this may be used by actions
- * which require a temporary object to be updated.
- * Pushing an updater onto the stack allow actions
- * downstream to transparently update the temporary proxy.
- * </p>
- * @param updater Updater, possibly null
- */
- public void pushUpdater(Updater updater) {
- updaterStack.push(updater);
- }
-
- /**
- * Pops the top <code>Updater</code> from the stack.
- * <p>
- * <strong>Note</strong>Any action pushing an <code>Updater</code> onto
- * the stack should take responsibility for popping
- * the updater from the stack at an appropriate time.
- * </p>
- * @return <code>Updater</code>, possibly null
- */
- public Updater popUpdater() {
- return (Updater) updaterStack.pop();
- }
-
- /**
- * Gets the current <code>Updater</code>.
- * This may (or may not) be the updater for the current
- * descriptor.
- * If the current descriptor is a bean child,
- * the the current updater will (most likely)
- * be the updater for the property.
- * Actions (that, for example, use proxy objects)
- * may push updaters onto the stack.
- * @return Updater, possibly null
- */
- public Updater getCurrentUpdater() {
- // TODO: think about whether this is right
- // it makes some sense to look back up the
- // stack until a non-empty updater is found.
- // actions who need to put a stock to this
- // behaviour can always use an ignoring implementation.
- Updater result = null;
- if (!updaterStack.empty()) {
- result = (Updater) updaterStack.peek();
- if ( result == null && updaterStack.size() >1 ) {
- result = (Updater) updaterStack.peek(1);
- }
- }
- return result;
- }
-
- }