| /* Copyright 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.xmlbeans.impl.marshal; |
| |
| import org.apache.xmlbeans.XmlError; |
| import org.apache.xmlbeans.XmlException; |
| import org.apache.xmlbeans.XmlOptions; |
| import org.apache.xmlbeans.XmlRuntimeException; |
| import org.apache.xmlbeans.impl.binding.bts.BindingLoader; |
| import org.apache.xmlbeans.impl.binding.bts.BindingType; |
| import org.apache.xmlbeans.impl.binding.bts.BindingTypeName; |
| import org.apache.xmlbeans.impl.binding.bts.BuiltinBindingType; |
| import org.apache.xmlbeans.impl.binding.bts.ByNameBean; |
| import org.apache.xmlbeans.impl.binding.bts.JaxrpcEnumType; |
| import org.apache.xmlbeans.impl.binding.bts.ListArrayType; |
| import org.apache.xmlbeans.impl.binding.bts.SimpleBindingType; |
| import org.apache.xmlbeans.impl.binding.bts.SimpleContentBean; |
| import org.apache.xmlbeans.impl.binding.bts.SimpleDocumentBinding; |
| import org.apache.xmlbeans.impl.binding.bts.WrappedArrayType; |
| import org.apache.xmlbeans.impl.common.XmlStreamUtils; |
| import org.apache.xmlbeans.impl.common.XmlWhitespace; |
| import org.apache.xmlbeans.impl.marshal.util.AttributeHolder; |
| import org.apache.xmlbeans.impl.util.XsTypeConverter; |
| |
| import javax.xml.namespace.NamespaceContext; |
| import javax.xml.namespace.QName; |
| import javax.xml.stream.Location; |
| import javax.xml.stream.XMLStreamException; |
| import javax.xml.stream.XMLStreamReader; |
| import java.util.Collection; |
| import java.util.Stack; |
| |
| |
| final class MarshalResult implements XMLStreamReader |
| { |
| |
| //per binding context constants |
| private final BindingLoader bindingLoader; |
| private final RuntimeBindingTypeTable typeTable; |
| |
| //state fields |
| private final BindingTypeVisitor bindingTypeVisitor = |
| new BindingTypeVisitor(this); |
| private final Collection errors; |
| private final ScopedNamespaceContext namespaceContext; |
| private final Stack visitorStack = new Stack(); |
| private XmlTypeVisitor currVisitor; |
| private int currentEventType = XMLStreamReader.START_ELEMENT; |
| private boolean initedAttributes = false; |
| private AttributeHolder attributeHolder; |
| private int prefixCnt = 0; |
| |
| //used for some array types |
| private int currIndex; |
| |
| private static final String ATTRIBUTE_XML_TYPE = "CDATA"; |
| private static final String NSPREFIX = "n"; |
| |
| |
| //TODO: REVIEW: consider ways to reduce the number of parameters here |
| MarshalResult(BindingLoader loader, |
| RuntimeBindingTypeTable tbl, |
| NamespaceContext root_nsctx, |
| RuntimeBindingProperty property, |
| Object obj, |
| XmlOptions options) |
| throws XmlException |
| { |
| bindingLoader = loader; |
| typeTable = tbl; |
| namespaceContext = new ScopedNamespaceContext(root_nsctx); |
| namespaceContext.openScope(); |
| errors = BindingContextImpl.extractErrorHandler(options); |
| currVisitor = createVisitor(property, obj); |
| } |
| |
| protected XmlTypeVisitor createVisitor(RuntimeBindingProperty property, |
| Object obj) |
| throws XmlException |
| { |
| assert property != null; |
| |
| BindingType btype = property.getRuntimeBindingType().getBindingType(); |
| bindingTypeVisitor.setParentObject(obj); |
| bindingTypeVisitor.setRuntimeBindingProperty(property); |
| btype.accept(bindingTypeVisitor); |
| return bindingTypeVisitor.getXmlTypeVisitor(); |
| } |
| |
| public Object getProperty(String s) |
| throws IllegalArgumentException |
| { |
| throw new UnsupportedOperationException("UNIMPLEMENTED"); |
| } |
| |
| public int next() throws XMLStreamException |
| { |
| switch (currVisitor.getState()) { |
| case XmlTypeVisitor.START: |
| break; |
| case XmlTypeVisitor.CHARS: |
| case XmlTypeVisitor.END: |
| currVisitor = popVisitor(); |
| break; |
| default: |
| throw new AssertionError("invalid: " + currVisitor.getState()); |
| } |
| |
| try { |
| return (currentEventType = advanceToNext()); |
| } |
| catch (XmlException e) { |
| //TODO: consider passing Location to exception ctor |
| XMLStreamException xse = new XMLStreamException(e); |
| xse.initCause(e); |
| throw xse; |
| } |
| } |
| |
| private int advanceToNext() |
| throws XmlException |
| { |
| final int next_state = currVisitor.advance(); |
| switch (next_state) { |
| case XmlTypeVisitor.CONTENT: |
| pushVisitor(currVisitor); |
| currVisitor = currVisitor.getCurrentChild(); |
| return START_ELEMENT; |
| case XmlTypeVisitor.CHARS: |
| pushVisitor(currVisitor); |
| currVisitor = currVisitor.getCurrentChild(); |
| return CHARACTERS; |
| case XmlTypeVisitor.END: |
| return END_ELEMENT; |
| default: |
| throw new AssertionError("bad state: " + next_state); |
| } |
| } |
| |
| private void pushVisitor(XmlTypeVisitor v) |
| { |
| visitorStack.push(v); |
| namespaceContext.openScope(); |
| initedAttributes = false; |
| } |
| |
| private XmlTypeVisitor popVisitor() |
| { |
| namespaceContext.closeScope(); |
| final XmlTypeVisitor tv = (XmlTypeVisitor)visitorStack.pop(); |
| return tv; |
| } |
| |
| QName fillPrefix(final QName pname) |
| { |
| final String uri = pname.getNamespaceURI(); |
| |
| assert uri != null; //QName's should use "" for no namespace |
| |
| if (uri.length() == 0) { |
| return createQName(pname.getLocalPart()); |
| } else { |
| String prefix = ensurePrefix(uri); |
| return createQName(uri, pname.getLocalPart(), prefix); |
| } |
| } |
| |
| |
| String ensurePrefix(String uri) |
| { |
| assert uri != null; //QName's should use "" for no namespace |
| assert (uri.length() > 0); |
| |
| String prefix = namespaceContext.getPrefix(uri); |
| if (prefix == null) { |
| prefix = bindNextPrefix(uri); |
| } |
| assert prefix != null; |
| return prefix; |
| } |
| |
| |
| private String bindNextPrefix(final String uri) |
| { |
| assert uri != null; |
| String testuri; |
| String prefix; |
| do { |
| prefix = NSPREFIX + (++prefixCnt); |
| testuri = namespaceContext.getNamespaceURI(prefix); |
| } |
| while (testuri != null); |
| assert prefix != null; |
| namespaceContext.bindNamespace(prefix, uri); |
| return prefix; |
| } |
| |
| |
| RuntimeBindingType createRuntimeBindingType(BindingType type, Object instance) |
| throws XmlException |
| { |
| final BindingTypeName type_name = type.getName(); |
| String expectedJavaClass = type_name.getJavaName().toString(); |
| String actualJavaClass = instance.getClass().getName(); |
| if (!actualJavaClass.equals(expectedJavaClass)) { |
| final BindingType actual_type = |
| MarshallerImpl.lookupBindingType(instance.getClass(), |
| type_name.getJavaName(), |
| type_name.getXmlName(), |
| bindingLoader); |
| if (actual_type != null) { |
| type = actual_type; //redefine type param |
| } |
| //else go with original type and hope for the best... |
| } |
| return getRuntimeTypeFactory().createRuntimeType(type, typeTable, bindingLoader); |
| } |
| |
| |
| RuntimeBindingType createRuntimeBindingType(BindingType type) |
| throws XmlException |
| { |
| return getRuntimeTypeFactory().createRuntimeType(type, typeTable, |
| bindingLoader); |
| } |
| |
| public void require(int i, String s, String s1) |
| throws XMLStreamException |
| { |
| throw new UnsupportedOperationException("UNIMPLEMENTED"); |
| } |
| |
| public String getElementText() throws XMLStreamException |
| { |
| throw new UnsupportedOperationException("UNIMPLEMENTED"); |
| } |
| |
| public int nextTag() throws XMLStreamException |
| { |
| throw new UnsupportedOperationException("UNIMPLEMENTED"); |
| } |
| |
| public boolean hasNext() throws XMLStreamException |
| { |
| if (visitorStack.isEmpty()) { |
| return (currVisitor.getState() != XmlTypeVisitor.END); |
| } else { |
| return true; |
| } |
| } |
| |
| public void close() throws XMLStreamException |
| { |
| //TODO: consider freeing memory |
| } |
| |
| public String getNamespaceURI(String s) |
| { |
| if (s == null) |
| throw new IllegalArgumentException("prefix cannot be null"); |
| |
| return getNamespaceContext().getNamespaceURI(s); |
| } |
| |
| public boolean isStartElement() |
| { |
| return currentEventType == START_ELEMENT; |
| } |
| |
| public boolean isEndElement() |
| { |
| return currentEventType == END_ELEMENT; |
| } |
| |
| public boolean isCharacters() |
| { |
| return currentEventType == CHARACTERS; |
| } |
| |
| public boolean isWhiteSpace() |
| { |
| if (!isCharacters()) return false; |
| CharSequence seq = currVisitor.getCharData(); |
| return XmlWhitespace.isAllSpace(seq); |
| } |
| |
| public String getAttributeValue(String uri, String lname) |
| { |
| initAttributes(); |
| |
| //TODO: do better than this basic and slow implementation |
| for (int i = 0, len = getAttributeCount(); i < len; i++) { |
| final QName aname = getAttributeName(i); |
| |
| if (lname.equals(aname.getLocalPart())) { |
| if (uri == null || uri.equals(aname.getNamespaceURI())) |
| return getAttributeValue(i); |
| } |
| } |
| return null; |
| } |
| |
| public int getAttributeCount() |
| { |
| initAttributes(); |
| if (attributeHolder == null) |
| return 0; |
| else |
| return attributeHolder.getAttributeCount(); |
| } |
| |
| public QName getAttributeName(int i) |
| { |
| initAttributes(); |
| assert attributeHolder != null; |
| return attributeHolder.getAttributeName(i); |
| } |
| |
| public String getAttributeNamespace(int i) |
| { |
| initAttributes(); |
| assert attributeHolder != null; |
| return attributeHolder.getAttributeNamespace(i); |
| } |
| |
| public String getAttributeLocalName(int i) |
| { |
| initAttributes(); |
| assert attributeHolder != null; |
| return attributeHolder.getAttributeLocalName(i); |
| } |
| |
| public String getAttributePrefix(int i) |
| { |
| initAttributes(); |
| assert attributeHolder != null; |
| return attributeHolder.getAttributePrefix(i); |
| } |
| |
| public String getAttributeType(int i) |
| { |
| attributeRangeCheck(i); |
| return ATTRIBUTE_XML_TYPE; |
| } |
| |
| public String getAttributeValue(int i) |
| { |
| initAttributes(); |
| assert attributeHolder != null; |
| return attributeHolder.getAttributeValue(i); |
| } |
| |
| public boolean isAttributeSpecified(int i) |
| { |
| initAttributes(); |
| |
| assert attributeHolder != null; |
| return attributeHolder.isAttributeSpecified(i); |
| } |
| |
| public int getNamespaceCount() |
| { |
| initAttributes(); |
| return namespaceContext.getCurrentScopeNamespaceCount(); |
| } |
| |
| |
| public String getNamespacePrefix(int i) |
| { |
| initAttributes(); |
| return namespaceContext.getCurrentScopeNamespacePrefix(i); |
| } |
| |
| public String getNamespaceURI(int i) |
| { |
| initAttributes(); |
| return namespaceContext.getCurrentScopeNamespaceURI(i); |
| } |
| |
| public NamespaceContext getNamespaceContext() |
| { |
| return namespaceContext; |
| } |
| |
| public int getEventType() |
| { |
| return currentEventType; |
| } |
| |
| public String getText() |
| { |
| CharSequence seq = currVisitor.getCharData(); |
| return seq.toString(); |
| } |
| |
| public char[] getTextCharacters() |
| { |
| CharSequence seq = currVisitor.getCharData(); |
| if (seq instanceof String) { |
| return seq.toString().toCharArray(); |
| } |
| |
| final int len = seq.length(); |
| char[] val = new char[len]; |
| for (int i = 0; i < len; i++) { |
| val[i] = seq.charAt(i); |
| } |
| return val; |
| } |
| |
| |
| public int getTextCharacters(int sourceStart, |
| char[] target, |
| int targetStart, |
| int length) |
| throws XMLStreamException |
| { |
| if (length < 0) |
| throw new IndexOutOfBoundsException("negative length: " + length); |
| |
| if (targetStart < 0) |
| throw new IndexOutOfBoundsException("negative targetStart: " + targetStart); |
| |
| final int target_length = target.length; |
| if (targetStart >= target_length) |
| throw new IndexOutOfBoundsException("targetStart(" + targetStart + ") past end of target(" + target_length + ")"); |
| |
| if ((targetStart + length) > target_length) { |
| throw new IndexOutOfBoundsException("insufficient data in target(length is " + target_length + ")"); |
| } |
| |
| CharSequence seq = currVisitor.getCharData(); |
| if (seq instanceof String) { |
| final String s = seq.toString(); |
| s.getChars(sourceStart, sourceStart + length, target, targetStart); |
| return length; |
| } |
| |
| //TODO: review this code |
| int cnt = 0; |
| for (int src_idx = sourceStart, dest_idx = targetStart; cnt < length; cnt++) { |
| target[dest_idx++] = seq.charAt(src_idx++); |
| } |
| return cnt; |
| } |
| |
| public int getTextStart() |
| { |
| return 0; |
| } |
| |
| public int getTextLength() |
| { |
| return currVisitor.getCharData().length(); |
| } |
| |
| public String getEncoding() |
| { |
| return null; |
| } |
| |
| public boolean hasText() |
| { |
| //we'll likely never return some of these but just in case... |
| switch (currentEventType) { |
| case CHARACTERS: |
| case COMMENT: |
| case SPACE: |
| case ENTITY_REFERENCE: |
| case DTD: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| public Location getLocation() |
| { |
| //TODO: something better than this, like give the object instance |
| return EmptyLocation.getInstance(); |
| } |
| |
| public QName getName() |
| { |
| return currVisitor.getName(); |
| } |
| |
| public String getLocalName() |
| { |
| return currVisitor.getLocalPart(); |
| } |
| |
| public boolean hasName() |
| { |
| return ((currentEventType == XMLStreamReader.START_ELEMENT) || |
| (currentEventType == XMLStreamReader.END_ELEMENT)); |
| } |
| |
| public String getNamespaceURI() |
| { |
| return currVisitor.getNamespaceURI(); |
| } |
| |
| public String getPrefix() |
| { |
| return currVisitor.getPrefix(); |
| } |
| |
| public String getVersion() |
| { |
| return null; |
| } |
| |
| public boolean isStandalone() |
| { |
| return false; |
| } |
| |
| public boolean standaloneSet() |
| { |
| return false; |
| } |
| |
| public String getCharacterEncodingScheme() |
| { |
| return null; |
| } |
| |
| public String getPITarget() |
| { |
| throw new IllegalStateException(); |
| } |
| |
| public String getPIData() |
| { |
| throw new IllegalStateException(); |
| } |
| |
| private void initAttributes() |
| { |
| if (!initedAttributes) { |
| try { |
| if (attributeHolder != null) { |
| attributeHolder.clear(); |
| } |
| currVisitor.initAttributes(); |
| } |
| catch (XmlException e) { |
| //public attribute interfaces of XMLStreamReader |
| //force us into this behavior |
| throw new XmlRuntimeException(e); |
| } |
| initedAttributes = true; |
| } |
| } |
| |
| private void attributeRangeCheck(int i) |
| { |
| final int att_cnt = getAttributeCount(); |
| if (i >= att_cnt) { |
| String msg = "index" + i + " invalid. " + |
| " attribute count is " + att_cnt; |
| throw new IndexOutOfBoundsException(msg); |
| } |
| } |
| |
| |
| public String toString() |
| { |
| return "org.apache.xmlbeans.impl.marshal.MarshalResult{" + |
| "currentEvent=" + XmlStreamUtils.printEvent(this) + |
| ", visitorStack=" + (visitorStack == null ? null : "size:" + visitorStack.size() + visitorStack) + |
| ", currVisitor=" + currVisitor + |
| "}"; |
| } |
| |
| Collection getErrorCollection() |
| { |
| return errors; |
| } |
| |
| public RuntimeBindingTypeTable getTypeTable() |
| { |
| return typeTable; |
| } |
| |
| static RuntimeBindingType findActualRuntimeType(Object property_value, |
| RuntimeBindingType expected_type, |
| MarshalResult result) |
| throws XmlException |
| { |
| if (property_value == null) |
| return expected_type; |
| |
| if (expected_type.isJavaPrimitive() || expected_type.isJavaFinal()) |
| return expected_type; |
| |
| final Class instance_class = property_value.getClass(); |
| if (instance_class.equals(expected_type.getJavaType())) |
| return expected_type; |
| |
| BindingType btype = expected_type.getBindingType(); |
| //TODO: improve this method by going up the type hierarchy |
| //also avoid duplicate work going on in this next call. |
| return result.createRuntimeBindingType(btype, property_value); |
| } |
| |
| |
| int getCurrIndex() |
| { |
| return currIndex; |
| } |
| |
| void setCurrIndex(int currIndex) |
| { |
| this.currIndex = currIndex; |
| } |
| |
| BindingLoader getBindingLoader() |
| { |
| return bindingLoader; |
| } |
| |
| public void addWarning(String msg) |
| { |
| XmlError e = XmlError.forMessage(msg, XmlError.SEVERITY_WARNING); |
| getErrorCollection().add(e); |
| } |
| |
| private RuntimeTypeFactory getRuntimeTypeFactory() |
| { |
| return typeTable.getRuntimeTypeFactory(); |
| } |
| |
| private QName createQName(String uri, String localpart, String prefix) |
| { |
| return new QName(uri, localpart, prefix); |
| } |
| |
| private QName createQName(String localpart) |
| { |
| return new QName(localpart); |
| } |
| |
| void fillAndAddAttribute(QName qname_without_prefix, |
| String value) |
| { |
| final String uri = qname_without_prefix.getNamespaceURI(); |
| final String prefix; |
| if (uri.length() == 0) { |
| prefix = null; |
| } else { |
| prefix = ensurePrefix(uri); |
| } |
| addAttribute(uri, qname_without_prefix.getLocalPart(), prefix, value); |
| } |
| |
| private void addAttribute(String namespaceURI, |
| String localPart, |
| String prefix, |
| String value) |
| { |
| if (attributeHolder == null) { |
| attributeHolder = new AttributeHolder(); |
| } |
| attributeHolder.add(namespaceURI, localPart, prefix, value); |
| } |
| |
| void addXsiNilAttribute() |
| { |
| addAttribute(MarshalStreamUtils.XSI_NS, |
| MarshalStreamUtils.XSI_NIL_ATTR, |
| ensurePrefix(MarshalStreamUtils.XSI_NS), |
| NamedXmlTypeVisitor.TRUE_LEX); |
| } |
| |
| void addXsiTypeAttribute(RuntimeBindingType rtt) |
| { |
| final QName schema_type = rtt.getSchemaTypeName(); |
| final String type_uri = schema_type.getNamespaceURI(); |
| |
| //TODO: what about types from a schema with no targetNamespace?? |
| assert type_uri != null; |
| assert type_uri.length() > 0; |
| |
| final String aval = |
| XsTypeConverter.getQNameString(type_uri, |
| schema_type.getLocalPart(), |
| ensurePrefix(type_uri)); |
| |
| addAttribute(MarshalStreamUtils.XSI_NS, |
| MarshalStreamUtils.XSI_TYPE_ATTR, |
| ensurePrefix(MarshalStreamUtils.XSI_NS), |
| aval); |
| } |
| |
| |
| private static final class BindingTypeVisitor |
| implements org.apache.xmlbeans.impl.binding.bts.BindingTypeVisitor |
| { |
| private final MarshalResult marshalResult; |
| |
| private Object parentObject; |
| private RuntimeBindingProperty runtimeBindingProperty; |
| |
| private XmlTypeVisitor xmlTypeVisitor; |
| |
| public BindingTypeVisitor(MarshalResult marshalResult) |
| { |
| this.marshalResult = marshalResult; |
| } |
| |
| public void setParentObject(Object parentObject) |
| { |
| this.parentObject = parentObject; |
| } |
| |
| public void setRuntimeBindingProperty(RuntimeBindingProperty runtimeBindingProperty) |
| { |
| this.runtimeBindingProperty = runtimeBindingProperty; |
| } |
| |
| public XmlTypeVisitor getXmlTypeVisitor() |
| { |
| return xmlTypeVisitor; |
| } |
| |
| public void visit(BuiltinBindingType builtinBindingType) |
| throws XmlException |
| { |
| xmlTypeVisitor = new SimpleTypeVisitor(runtimeBindingProperty, |
| parentObject, |
| marshalResult); |
| } |
| |
| public void visit(ByNameBean byNameBean) |
| throws XmlException |
| { |
| xmlTypeVisitor = new ByNameTypeVisitor(runtimeBindingProperty, |
| parentObject, |
| marshalResult); |
| } |
| |
| public void visit(SimpleContentBean simpleContentBean) |
| throws XmlException |
| { |
| xmlTypeVisitor = new SimpleContentTypeVisitor(runtimeBindingProperty, |
| parentObject, |
| marshalResult); |
| } |
| |
| public void visit(SimpleBindingType simpleBindingType) |
| throws XmlException |
| { |
| xmlTypeVisitor = new SimpleTypeVisitor(runtimeBindingProperty, |
| parentObject, |
| marshalResult); |
| } |
| |
| public void visit(JaxrpcEnumType jaxrpcEnumType) |
| throws XmlException |
| { |
| xmlTypeVisitor = new SimpleTypeVisitor(runtimeBindingProperty, |
| parentObject, |
| marshalResult); |
| } |
| |
| public void visit(SimpleDocumentBinding simpleDocumentBinding) |
| throws XmlException |
| { |
| throw new AssertionError("unexpected type: " + simpleDocumentBinding); |
| } |
| |
| public void visit(WrappedArrayType wrappedArrayType) |
| throws XmlException |
| { |
| xmlTypeVisitor = new WrappedArrayTypeVisitor(runtimeBindingProperty, |
| parentObject, |
| marshalResult); |
| } |
| |
| public void visit(ListArrayType listArrayType) |
| throws XmlException |
| { |
| xmlTypeVisitor = new SimpleTypeVisitor(runtimeBindingProperty, |
| parentObject, |
| marshalResult); |
| } |
| |
| } |
| |
| } |