/*
* Copyright 2011, 2012 Odysseus Software GmbH
*
* 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.synapse.commons.staxon.core.base;
import javax.xml.XMLConstants;
import javax.xml.namespace.NamespaceContext;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
/**
* Abstract XML stream writer.
*/
public abstract class AbstractXMLStreamWriter<T> implements XMLStreamWriter {
private final boolean repairingNamespaces;
private XMLStreamWriterScope<T> scope;
private boolean startDocumentWritten;
/**
* Create writer instance.
*
* @param rootInfo root scope information
*/
public AbstractXMLStreamWriter(T rootInfo, boolean repaireNamespaces) {
scope = new XMLStreamWriterScope<T>(XMLConstants.NULL_NS_URI, rootInfo);
startDocumentWritten = false;
repairingNamespaces = repaireNamespaces;
}
private void ensureStartTagClosed() throws XMLStreamException {
if (!scope.isStartTagClosed()) {
if (repairingNamespaces) { // missing declaration?
String namespaceURI = getScope().getNamespaceURI();
if (!XMLConstants.NULL_NS_URI.equals(namespaceURI)) {
String prefix = getScope().getPrefix();
if (!namespaceURI.equals(getScope().getNamespaceURI(prefix))) {
writeNamespace(prefix, namespaceURI);
}
}
}
writeStartElementTagEnd();
scope.setStartTagClosed(true);
if (scope.isEmptyElement()) {
scope = scope.getParent();
}
}
}
private void writeStartElement(String prefix, String localPart, String namespaceURI, boolean emptyElement) throws XMLStreamException {
if (localPart == null) {
throw new XMLStreamException("Local name must not be null");
}
boolean writeNamespace = false;
if (prefix == null) {
if (repairingNamespaces) {
if (XMLConstants.NULL_NS_URI.equals(getScope().getNamespaceURI(XMLConstants.DEFAULT_NS_PREFIX))) { // try empty prefix
prefix = XMLConstants.DEFAULT_NS_PREFIX;
} else { // find unused prefix
int index = 1;
while (getScope().getNamespaceURI("ns" + index) == null) {
index++;
}
prefix = index == 1 ? "ns" : "ns" + index;
}
writeNamespace = true;
} else {
throw new XMLStreamException("Namespace URI has not been bound to a prefix: " + namespaceURI);
}
}
ensureStartTagClosed();
T scopeInfo = writeStartElementTag(prefix, localPart, namespaceURI);
scope = new XMLStreamWriterScope<T>(scope, prefix, localPart, namespaceURI, emptyElement);
scope.setInfo(scopeInfo);
if (writeNamespace) {
writeNamespace(prefix, namespaceURI);
}
}
/**
* @return current scope
*/
protected XMLStreamWriterScope<T> getScope() {
return scope;
}
/**
* @return <code>true</code> if <code>START_DOCUMENT</code> event has been written
*/
protected boolean isStartDocumentWritten() {
return startDocumentWritten;
}
/**
* Write open start element tag.
* The returned scope info is stored in the new scope and will be available via
* <code>getScope().getInfo()</code>.
*
* @param prefix element prefix (may be <code>XMLConstants.DEFAULT_NS_PREFIX</code>)
* @param localPart local name
* @param namespaceURI namespace URI
* @return new scope info
* @throws XMLStreamException
*/
protected abstract T writeStartElementTag(String prefix, String localPart, String namespaceURI) throws XMLStreamException;
/**
* Write close start element tag.
*
* @throws XMLStreamException
*/
protected abstract void writeStartElementTagEnd() throws XMLStreamException;
/**
* Write end element tag.
*
* @throws XMLStreamException
*/
protected abstract void writeEndElementTag() throws XMLStreamException;
/**
* Write attribute.
*
* @param prefix attribute prefix (may be <code>XMLConstants.DEFAULT_NS_PREFIX</code>)
* @param localName local name
* @param namespaceURI namespace URI
* @param value attribute value
* @throws XMLStreamException
*/
protected abstract void writeAttr(String prefix, String localName, String namespaceURI, String value) throws XMLStreamException;
/**
* Write namespace declaration.
*
* @param prefix namespace prefix
* @param namespaceURI namespace URI
* @throws XMLStreamException
*/
protected abstract void writeNsDecl(String prefix, String namespaceURI) throws XMLStreamException;
/**
* Write characters/comment/dtd/entity data.
*
* @param data text/data
* @param type one of <code>CHARACTERS, COMMENT, CDATA, DTD, ENTITY_REFERENCE, SPACE</code>
* @throws XMLStreamException
*/
protected abstract void writeData(Object data, int type) throws XMLStreamException;
/**
* Read processing instruction.
*
* @param target PI target
* @param data PI data (may be <code>null</code>)
* @throws XMLStreamException
*/
protected abstract void writePI(String target, String data) throws XMLStreamException;
@Override
public void writeStartElement(String localName) throws XMLStreamException {
String namespaceURI = scope.getNamespaceURI(XMLConstants.DEFAULT_NS_PREFIX);
if (namespaceURI == null) {
throw new XMLStreamException("Default namespace URI has not been set");
}
writeStartElement(XMLConstants.DEFAULT_NS_PREFIX, localName, namespaceURI, false);
}
@Override
public void writeStartElement(String namespaceURI, String localName) throws XMLStreamException {
if (namespaceURI == null) {
throw new XMLStreamException("Namespace URI must not be null");
}
String prefix = scope.getPrefix(namespaceURI);
if (prefix == null && !repairingNamespaces) {
throw new XMLStreamException("Namespace URI has not been bound to a prefix: " + namespaceURI);
}
writeStartElement(prefix, localName, namespaceURI, false);
}
@Override
public void writeStartElement(String prefix, String localName, String namespaceURI) throws XMLStreamException {
if (prefix == null) {
throw new XMLStreamException("Prefix must not be null");
}
if (namespaceURI == null) {
throw new XMLStreamException("Namespace URI must not be null");
}
writeStartElement(prefix, localName, namespaceURI, false);
}
@Override
public void writeEndElement() throws XMLStreamException {
ensureStartTagClosed();
if (scope.isRoot()) {
throw new XMLStreamException("Cannot write end element in root scope");
}
writeEndElementTag();
scope = scope.getParent();
}
@Override
public void writeEmptyElement(String localName) throws XMLStreamException {
String namespaceURI = scope.getNamespaceURI(XMLConstants.DEFAULT_NS_PREFIX);
if (namespaceURI == null) {
throw new XMLStreamException("Default namespace URI has not been set");
}
writeStartElement(XMLConstants.DEFAULT_NS_PREFIX, localName, namespaceURI, true);
}
@Override
public void writeEmptyElement(String namespaceURI, String localName) throws XMLStreamException {
if (namespaceURI == null) {
throw new XMLStreamException("Namespace URI must not be null");
}
String prefix = scope.getPrefix(namespaceURI);
if (prefix == null && !repairingNamespaces) {
throw new XMLStreamException("Namespace URI has not been bound to a prefix: " + namespaceURI);
}
writeStartElement(prefix, localName, namespaceURI, true);
}
@Override
public void writeEmptyElement(String prefix, String localName, String namespaceURI) throws XMLStreamException {
if (prefix == null) {
throw new XMLStreamException("Prefix must not be null");
}
if (namespaceURI == null) {
throw new XMLStreamException("Namespace URI must not be null");
}
writeStartElement(prefix, localName, namespaceURI, true);
}
@Override
public void writeAttribute(String localName, String value) throws XMLStreamException {
writeAttribute(null, XMLConstants.NULL_NS_URI, localName, value);
}
@Override
public void writeAttribute(String namespaceURI, String localName, String value) throws XMLStreamException {
writeAttribute(null, namespaceURI, localName, value);
}
@Override
public void writeAttribute(String prefix, String namespaceURI, String localName, String value) throws XMLStreamException {
if (scope.isStartTagClosed()) {
throw new XMLStreamException("Cannot write attribute: element has children or text");
}
if (namespaceURI == null) {
throw new XMLStreamException("Namespace URI must not be null");
} else if (XMLConstants.NULL_NS_URI.equals(namespaceURI)) { // no namespace -> no prefix
if (prefix == null) {
prefix = XMLConstants.DEFAULT_NS_PREFIX;
} else if (!XMLConstants.DEFAULT_NS_PREFIX.equals(prefix)) {
throw new XMLStreamException("Cannot write prefixed attribute without a namespace URI: " + prefix);
}
} else { // namespace -> prefixed attribute
if (prefix == null) { // lookup prefix
prefix = scope.getNonEmptyPrefix(namespaceURI);
if (prefix == null) {
throw new XMLStreamException("Namespace URI has not been bound to a prefix: " + namespaceURI);
}
} else if (XMLConstants.DEFAULT_NS_PREFIX.equals(prefix)) {
throw new XMLStreamException("Cannot write attribute without prefix for namespace URI: " + namespaceURI);
}
}
scope.addAttribute(prefix, localName, namespaceURI, value);
writeAttr(prefix, localName, namespaceURI, value);
}
protected final void writeCharacters(Object data, int type) throws XMLStreamException {
ensureStartTagClosed();
writeData(data, type);
}
@Override
public void writeCharacters(String text) throws XMLStreamException {
writeCharacters(text, XMLStreamConstants.CHARACTERS);
}
@Override
public void writeCharacters(char[] text, int start, int length) throws XMLStreamException {
writeCharacters(new String(text, start, length));
}
@Override
public void writeCData(String data) throws XMLStreamException {
writeCharacters(data, XMLStreamConstants.CDATA);
}
@Override
public void writeStartDocument() throws XMLStreamException {
writeStartDocument("UTF-8", null);
}
@Override
public void writeStartDocument(String version) throws XMLStreamException {
writeStartDocument(null, version);
}
@Override
public void writeStartDocument(String encoding, String version) throws XMLStreamException {
if (startDocumentWritten || !scope.isRoot()) {
throw new XMLStreamException("Cannot start document");
}
if (version == null) {
version = "1.0";
}
if (encoding == null) {
writePI("xml", String.format("version=\"%s\"", version));
} else {
writePI("xml", String.format("version=\"%s\" encoding=\"%s\"", version, encoding));
}
startDocumentWritten = true;
}
@Override
public void writeEndDocument() throws XMLStreamException {
if (!startDocumentWritten) {
throw new XMLStreamException("Cannot end document");
}
if (!scope.isRoot()) {
ensureStartTagClosed();
while (!scope.isRoot()) {
writeEndElement();
}
}
startDocumentWritten = false;
}
@Override
public void close() throws XMLStreamException {
ensureStartTagClosed();
flush();
}
@Override
public void writeNamespace(String prefix, String namespaceURI) throws XMLStreamException {
if (scope.isStartTagClosed()) {
throw new XMLStreamException("Cannot write namespace: element has children or text");
}
writeNsDecl(prefix, namespaceURI);
scope.setPrefix(prefix, namespaceURI);
}
@Override
public void writeDefaultNamespace(String namespaceURI) throws XMLStreamException {
writeNamespace(XMLConstants.DEFAULT_NS_PREFIX, namespaceURI);
}
@Override
public String getPrefix(String namespaceURI) throws XMLStreamException {
return scope.getPrefix(namespaceURI);
}
@Override
public void setPrefix(String prefix, String namespaceURI) throws XMLStreamException {
scope.setPrefix(prefix, namespaceURI);
}
@Override
public void setDefaultNamespace(String namespaceURI) throws XMLStreamException {
setPrefix(XMLConstants.DEFAULT_NS_PREFIX, namespaceURI);
}
@Override
public void setNamespaceContext(NamespaceContext context) throws XMLStreamException {
if (!scope.isRoot()) {
throw new XMLStreamException("This method may only be called once at the start of the document");
}
scope = new XMLStreamWriterScope<T>(context, scope.getInfo());
}
@Override
public NamespaceContext getNamespaceContext() {
return scope;
}
@Override
public void writeComment(String data) throws XMLStreamException {
ensureStartTagClosed();
writeData(data, XMLStreamConstants.COMMENT);
}
@Override
public void writeProcessingInstruction(String target) throws XMLStreamException {
ensureStartTagClosed();
writePI(target, null);
}
@Override
public void writeProcessingInstruction(String target, String data) throws XMLStreamException {
ensureStartTagClosed();
writePI(target, data);
}
@Override
public void writeDTD(String dtd) throws XMLStreamException {
ensureStartTagClosed();
writeData(dtd, XMLStreamConstants.DTD);
}
@Override
public void writeEntityRef(String name) throws XMLStreamException {
ensureStartTagClosed();
writeData(name, XMLStreamConstants.ENTITY_REFERENCE);
}
@Override
public Object getProperty(String name) throws IllegalArgumentException {
throw new IllegalArgumentException("Unsupported property: " + name);
}
}