/*
* Copyright 1990-2009 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 only, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License version 2 for more details (a copy is
* included at /legal/license.txt).
*
* You should have received a copy of the GNU General Public License
* version 2 along with this work; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
* Clara, CA 95054 or visit www.sun.com if you need additional
* information or have any questions.
*/
package com.sun.ukit.xml;
import java.io.IOException;
import java.io.Writer;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
/**
* The XMLStreamWriter interface specifies how to write XML. The XMLStreamWriter
* does not perform well formedness checking on its input. However
* the writeCharacters method is required to escape & , < and >
* For attribute values the writeAttribute method will escape the
* above characters plus " to ensure that all character content
* and attribute values are well formed.
*
* Each NAMESPACE and ATTRIBUTE must be individually written.
*
* If javax.xml.stream.isPrefixDefaulting is set to <code>false</code> it is a
* fatal error if an element is written with namespace URI that has not been
* bound to a prefix.
*
* If javax.xml.stream.isPrefixDefaulting is set to <code>true</code> the
* XMLStreamWriter implementation must write a prefix for each unbound URI that
* it encounters in the current scope.
*
* @see XMLOutputFactory
* @see XMLStreamWriter
*/
/* pkg */ abstract class WriterStAX
implements XMLStreamWriter
{
/* pkg */ static String INVALID_XML = "Invalid XML";
/* pkg */ static int ELM_EMPTY = 0x01;
/* pkg */ static int ELM_INCOMP = 0x02;
/* pkg */ Writer mOut;
/* pkg */ int mPh; // current phase of document writing
/* pkg */ final static int PH_BEFORE_DOC = -1; // before writing
/* pkg */ final static int PH_DOC_START = 0; // document start
/* pkg */ final static int PH_MISC_DTD = 1; // misc before DTD
/* pkg */ final static int PH_DTD = 2; // DTD
/* pkg */ final static int PH_DTD_MISC = 3; // misc after DTD
/* pkg */ final static int PH_DOCELM = 4; // document's element
/* pkg */ final static int PH_DOCELM_MISC = 5; // misc after element
/* pkg */ final static int PH_AFTER_DOC = 6; // after parsing
// true until first non attr/namespace output after start/empty element
/* pkg */ boolean mIsElm;
// name - prefix; value - namespace
/* pkg */ Pair mHint; // prefix hints set by setPrefix
/* pkg */ Pair mPref; // stack of prefixes
// name - lname/qname; value - namespace
/* pkg */ Pair mElm; // stack of elements
private Pair mDltd; // deleted objects for reuse
/**
* Create a new instance of XMLStreamWriter implementation.
*
* @param writer the output writer
*/
/* pkg */ WriterStAX(Writer writer)
{
if (writer == null)
throw new NullPointerException("");
mOut = writer;
mPh = PH_BEFORE_DOC;
mIsElm = false;
// XML namespace
mPref = pair(mPref);
mPref.name = "xml";
mPref.value = "http://www.w3.org/XML/1998/namespace";
// XML Namespace namespace
mPref = pair(mPref);
mPref.name = "xmlns";
mPref.value = "http://www.w3.org/2000/xmlns/";
}
/**
* Writes a start tag to the output. All writeStartElement methods
* open a new scope in the internal namespace context. Writing the
* corresponding EndElement causes the scope to be closed.
*
* @param localName local name of the tag, may not be null
* @throws XMLStreamException
*/
public void writeStartElement(String localName)
throws XMLStreamException
{
if (localName == null)
throw new NullPointerException("");
newElm(false, localName);
}
/**
* Writes a start tag to the output
*
* @param namespaceURI the namespaceURI of the prefix to use, may not be null
* @param localName local name of the tag, may not be null
* @throws XMLStreamException if the namespace URI has not been bound to a
* prefix and javax.xml.stream.isPrefixDefaulting has not been set to
* <code>true</code>
*/
public void writeStartElement(String namespaceURI, String localName)
throws XMLStreamException
{
if (localName == null || namespaceURI == null)
throw new NullPointerException("");
newElm(false, namespaceURI, localName);
}
/**
* Writes a start tag to the output
*
* @param localName local name of the tag, may not be null
* @param prefix the prefix of the tag, may not be null
* @param namespaceURI the uri to bind the prefix to, may not be null
* @throws XMLStreamException
*/
public void writeStartElement(
String prefix, String localName, String namespaceURI)
throws XMLStreamException
{
if (prefix == null || localName == null || namespaceURI == null)
throw new NullPointerException("");
newElm(false, prefix, namespaceURI, localName);
}
/**
* Writes an empty element tag to the output
*
* @param namespaceURI the uri to bind the tag to, may not be null
* @param localName local name of the tag, may not be null
* @throws XMLStreamException if the namespace URI has not been bound to a
* prefix and javax.xml.stream.isPrefixDefaulting has not been set to
* <code>true</code>
*/
public void writeEmptyElement(String namespaceURI, String localName)
throws XMLStreamException
{
if (localName == null || namespaceURI == null)
throw new NullPointerException("");
newElm(true, namespaceURI, localName);
}
/**
* Writes an empty element tag to the output
*
* @param prefix the prefix of the tag, may not be null
* @param localName local name of the tag, may not be null
* @param namespaceURI the uri to bind the tag to, may not be null
* @throws XMLStreamException
*/
public void writeEmptyElement(
String prefix, String localName, String namespaceURI)
throws XMLStreamException
{
if (prefix == null || localName == null || namespaceURI == null)
throw new NullPointerException("");
newElm(true, prefix, namespaceURI, localName);
}
/**
* Writes an empty element tag to the output
*
* @param localName local name of the tag, may not be null
* @throws XMLStreamException
*/
public void writeEmptyElement(String localName)
throws XMLStreamException
{
if (localName == null)
throw new NullPointerException("");
newElm(true, localName);
}
/**
* Writes an end tag to the output relying on the internal
* state of the writer to determine the prefix and local name
* of the event.
*
* @throws XMLStreamException
*/
public void writeEndElement()
throws XMLStreamException
{
if (mElm == null)
return;
if (mIsElm != false) {
if ((mElm.id & ELM_EMPTY) != 0) {
writeElm();
return;
}
writeElm();
}
try {
mOut.write("</" + mElm.name + '>');
} catch (IOException ioe) {
throw new XMLStreamException(ioe);
}
delElm();
}
/**
* Closes any start tags and writes corresponding end tags.
*
* @throws XMLStreamException
*/
public void writeEndDocument()
throws XMLStreamException
{
while (mElm != null)
writeEndElement();
}
/**
* Close this writer and free any resources associated with the
* writer. This must not close the underlying output stream.
*
* @throws XMLStreamException
*/
public void close()
throws XMLStreamException
{
writeEndDocument();
flush();
}
/**
* Write any cached data to the underlying output mechanism.
*
* @throws XMLStreamException
*/
public void flush()
throws XMLStreamException
{
if (mIsElm != false)
writeElm();
try {
mOut.flush();
} catch (IOException ioe) {
throw new XMLStreamException(ioe);
}
}
/**
* Writes an attribute to the output stream without a prefix.
*
* @param localName the local name of the attribute, may not be null
* @param value the value of the attribute
* @throws IllegalStateException if the current state does not allow
* Attribute writing
* @throws XMLStreamException
* @throws NullPointerException
*/
public abstract void writeAttribute(String localName, String value)
throws XMLStreamException;
/**
* Writes an attribute to the output stream.
*
* @param prefix the prefix for this attribute, may not be null
* @param namespaceURI the uri of the prefix for this attribute, may not be
* null
* @param localName the local name of the attribute, may not be null
* @param value the value of the attribute
* @throws IllegalStateException if the current state does not allow
* Attribute writing
* @throws XMLStreamException if the namespace URI has not been bound to a
* prefix and javax.xml.stream.isPrefixDefaulting has not been set to
* <code>true</code>
* @throws NullPointerException
*/
public abstract void writeAttribute(
String prefix, String namespaceURI, String localName, String value)
throws XMLStreamException;
/**
* Writes an attribute to the output stream.
*
* @param namespaceURI the uri of the prefix for this attribute, may not be
* null
* @param localName the local name of the attribute, may not be null
* @param value the value of the attribute
* @throws IllegalStateException if the current state does not allow
* Attribute writing
* @throws XMLStreamException if the namespace URI has not been bound to a
* prefix and javax.xml.stream.isPrefixDefaulting has not been set to
* <code>true</code>
* @throws NullPointerException
*/
public abstract void writeAttribute(
String namespaceURI, String localName, String value)
throws XMLStreamException;
/**
* Writes a namespace to the output stream.
* If the prefix argument to this method is the empty string,
* "xmlns", or null this method will delegate to writeDefaultNamespace
*
* @param prefix the prefix to bind this namespace to
* @param namespaceURI the uri to bind the prefix to, may not be null
* @throws IllegalStateException if the current state does not allow
* Namespace writing
* @throws XMLStreamException
* @throws NullPointerException
*/
public void writeNamespace(String prefix, String namespaceURI)
throws XMLStreamException
{
if (prefix == null || prefix.length() == 0 || prefix.equals("xmlns")) {
writeDefaultNamespace(namespaceURI);
return;
}
if (mIsElm == false)
throw new IllegalStateException(INVALID_XML);
if (namespaceURI == null)
throw new NullPointerException("");
mPref = pair(mPref);
mPref.name = prefix;
mPref.value = namespaceURI;
mPref.list = mElm;
}
/**
* Writes the default namespace to the stream.
*
* @param namespaceURI the uri to bind the default namespace to, may not be
* null
* @throws IllegalStateException if the current state does not allow
* Namespace writing
* @throws XMLStreamException
* @throws NullPointerException
*/
public void writeDefaultNamespace(String namespaceURI)
throws XMLStreamException
{
if (mIsElm == false)
throw new IllegalStateException(INVALID_XML);
if (namespaceURI == null)
throw new NullPointerException("");
mPref = pair(mPref);
mPref.name = "";
mPref.value = namespaceURI;
mPref.list = mElm;
}
/**
* Writes an xml comment with the data enclosed.
*
* @param data the data contained in the comment, may be null
* @throws XMLStreamException
*/
public void writeComment(String data)
throws XMLStreamException
{
if (mIsElm != false)
writeElm();
if (mPh < PH_DOC_START)
mPh = PH_DOC_START;
try {
mOut.write("<!--" + ((data != null) ? data : "") + "-->");
} catch (IOException ioe) {
throw new XMLStreamException(ioe);
}
}
/**
* Writes a processing instruction
*
* @param target the target of the processing instruction, may not be null
* @throws XMLStreamException
* @throws NullPointerException
*/
public void writeProcessingInstruction(String target)
throws XMLStreamException
{
if (target == null)
throw new NullPointerException();
if (mIsElm != false)
writeElm();
if (mPh < PH_DOC_START)
mPh = PH_DOC_START;
try {
mOut.write("<?" + target + "?>");
} catch (IOException ioe) {
throw new XMLStreamException(ioe);
}
}
/**
* Writes a processing instruction
*
* @param target the target of the processing instruction, may not be null
* @param data the data contained in the processing instruction, may not be null
* @throws XMLStreamException
* @throws NullPointerException
*/
public void writeProcessingInstruction(String target, String data)
throws XMLStreamException
{
if (target == null || data == null)
throw new NullPointerException();
if (mIsElm != false)
writeElm();
if (mPh < PH_DOC_START)
mPh = PH_DOC_START;
try {
mOut.write("<?" + target + ' ' + data + "?>");
} catch (IOException ioe) {
throw new XMLStreamException(ioe);
}
}
/**
* Writes a CData section
*
* @param data the data contained in the CData Section, may not be null
* @throws XMLStreamException
* @throws NullPointerException
*/
public void writeCData(String data)
throws XMLStreamException
{
if (data == null)
throw new NullPointerException();
if (mIsElm != false)
writeElm();
if (mPh != PH_DOCELM)
throw new IllegalStateException(INVALID_XML);
try {
mOut.write("<![CDATA[" + data + "]]>");
} catch (IOException ioe) {
throw new XMLStreamException(ioe);
}
}
/**
* Write a DTD section. This string represents the entire doctypedecl
* production from the XML 1.0 specification.
*
* @param dtd the DTD to be written, may not be null
* @throws XMLStreamException
* @throws NullPointerException
*/
public void writeDTD(String dtd)
throws XMLStreamException
{
if (mPh >= PH_DTD)
throw new IllegalStateException(INVALID_XML);
mPh = PH_DTD;
try {
mOut.write(dtd);
} catch (IOException ioe) {
throw new XMLStreamException(ioe);
}
}
/**
* Writes an entity reference.
*
* @param name the name of the entity, may not be null
* @throws XMLStreamException
* @throws NullPointerException
*/
public void writeEntityRef(String name)
throws XMLStreamException
{
if (mIsElm != false)
writeElm();
if (name == null)
throw new NullPointerException("");
if (mPh != PH_DOCELM)
throw new IllegalStateException(INVALID_XML);
try {
mOut.write('&' + name + ';');
} catch (IOException ioe) {
throw new XMLStreamException(ioe);
}
}
/**
* Write the XML Declaration. Defaults the XML version to 1.0, and the
* encoding to UTF-8.
*
* @throws XMLStreamException
*/
public void writeStartDocument()
throws XMLStreamException
{
writeStartDocument("1.0");
}
/**
* Write the XML Declaration. Defaults the XML version to 1.0
* @param version version of the xml document, may not be null
* @throws XMLStreamException
* @throws NullPointerException
*/
public void writeStartDocument(String version)
throws XMLStreamException
{
writeStartDocument("UTF-8", version);
}
/**
* Write the XML Declaration. Note that the encoding parameter does
* not set the actual encoding of the underlying output. That must
* be set when the instance of the XMLStreamWriter is created using the
* XMLOutputFactory
*
* @param encoding encoding of the xml declaration, may not be null
* @param version version of the xml document, may not be null
* @throws XMLStreamException
* @throws NullPointerException
*/
public void writeStartDocument(String encoding, String version)
throws XMLStreamException
{
if (encoding == null || version == null)
throw new NullPointerException("");
if (mPh >= PH_DOC_START)
throw new IllegalStateException(INVALID_XML);
mPh = PH_DOC_START;
try {
mOut.write("<?xml version=\"" +
version + "\" encoding=\"" + encoding + "\"?>");
} catch (IOException ioe) {
throw new XMLStreamException(ioe);
}
}
/**
* Write text to the output.
*
* @param text the value to write, may not be null
* @throws XMLStreamException
* @throws NullPointerException
*/
public void writeCharacters(String text)
throws XMLStreamException
{
char[] chars = text.toCharArray();
writeCharacters(chars, 0, chars.length);
}
/**
* Write text to the output
*
* @param text the value to write, may not be null
* @param start the starting position in the array
* @param len the number of characters to write
* @throws XMLStreamException
* @throws NullPointerException
*/
public void writeCharacters(char[] text, int start, int len)
throws XMLStreamException
{
if (mIsElm != false)
writeElm();
if (mPh != PH_DOCELM)
throw new IllegalStateException(INVALID_XML);
if (len < 0)
throw new IndexOutOfBoundsException("");
try {
for (int idx = 0; idx < len; idx++) {
char ch = text[start + idx];
switch (ch) {
case '&':
mOut.write("&");
break;
case '<':
mOut.write("<");
break;
case '>':
mOut.write(">");
break;
default:
mOut.write(ch);
}
}
} catch (IOException ioe) {
throw new XMLStreamException(ioe);
}
}
/**
* Gets the prefix the uri is bound to.
*
* @return the prefix or null
* @throws XMLStreamException
*/
public String getPrefix(String uri)
throws XMLStreamException
{
String pref = findName(mPref, uri);
if (pref == null)
pref = findName(mHint, uri);
return pref;
}
/**
* Sets the prefix the uri is bound to. This prefix is bound
* in the scope of the current START_ELEMENT / END_ELEMENT pair.
* If this method is called before a START_ELEMENT has been written
* the prefix is bound in the root scope.
*
* @param prefix the prefix to bind to the uri, may not be null
* @param uri the uri to bind to the prefix, may be null
* @throws XMLStreamException
* @throws NullPointerException
*/
public void setPrefix(String prefix, String uri)
throws XMLStreamException
{
String pref = prefix.trim();
mHint = pair(mHint);
mHint.name = pref;
mHint.value = uri;
mHint.list = mElm;
}
/**
* Binds a URI to the default namespace. This URI is bound
* in the scope of the current START_ELEMENT / END_ELEMENT pair.
* If this method is called before a START_ELEMENT has been written
* the uri is bound in the root scope.
*
* @param uri the uri to bind to the default namespace, may be null
* @throws XMLStreamException
*/
public void setDefaultNamespace(String uri)
throws XMLStreamException
{
setPrefix("", uri);
}
/**
* Get the value of a feature/property from the underlying implementation
*
* @param name The name of the property, may not be null
* @return The value of the property
* @throws IllegalArgumentException if the property is not supported
* @throws NullPointerException if the name is null
*/
public abstract Object getProperty(String name)
throws IllegalArgumentException;
/**
* Creates a new element.
*
* @param isEmpty If <code>true</code> the element is empty.
* @param prefix the prefix of the tag
* @param localName local name of the tag
* @param namespaceURI the uri to bind the tag to
*/
/* pkg */ abstract void newElm(boolean isEmpty,
String prefix, String namespaceURI, String localName)
throws XMLStreamException;
/**
* Creates a new element.
*
* @param isEmpty If <code>true</code> the element is empty.
* @param localName local name of the tag
* @param namespaceURI the uri to bind the tag to
*/
/* pkg */ abstract void newElm(boolean isEmpty,
String namespaceURI, String localName)
throws XMLStreamException;
/**
* Creates a new element.
*
* @param isEmpty If <code>true</code> the element is empty.
* @param localName local name of the tag
*/
/* pkg */ abstract void newElm(boolean isEmpty, String localName)
throws XMLStreamException;
/**
* Writes an element created by <code>newElm</code> method.
*/
/* pkg */ void writeElm()
throws XMLStreamException
{
try {
mOut.write('<' + mElm.name);
Pair attr;
for (attr = mPref; attr != null; attr = attr.next) {
if (attr.list != mElm)
break;
mOut.write(" xmlns");
if (attr.name.length() != 0)
mOut.write(':' + attr.name);
mOut.write("=\"" + attr.value + "\"");
}
for (attr = mElm.list; attr != null; attr = attr.next) {
mOut.write(' ' + attr.name + "=\"" + attr.value + "\"");
}
if ((mElm.id & ELM_EMPTY) != 0) {
mOut.write('/');
delElm();
}
mOut.write('>');
} catch (IOException ioe) {
throw new XMLStreamException(ioe);
}
mIsElm = false;
}
/**
* Removes <code>mElm</code> element.
*/
/* pkg */ void delElm()
{
Pair attr = mHint;
while (attr != null && attr.list == mElm) {
attr = del(attr);
}
attr = mPref;
while (attr != null && attr.list == mElm) {
attr = del(attr);
}
mPref = attr;
attr = mElm.list;
while (attr != null) {
attr = del(attr);
}
mElm = del(mElm);
if (mElm == null)
mPh = PH_DOCELM_MISC;
}
/**
* Escapes an attribute value. Characters which will be escaped are: '"',
* '&', '<', '>'.
*/
/* pkg */ String escValue(String value)
{
if (value.indexOf('"') < 0 && value.indexOf('&') < 0 &&
value.indexOf('<') < 0 && value.indexOf('>') < 0)
return value;
int len = value.length();
StringBuffer val = new StringBuffer(len + 20);
for (int idx = 0; idx < len; idx++) {
char ch = value.charAt(idx);
switch(ch) {
case '"':
val.append(""");
break;
case '&':
val.append("&");
break;
case '<':
val.append("<");
break;
case '>':
val.append(">");
break;
default:
val.append(ch);
}
}
return val.toString();
}
/**
* Retrieves name by value.
*
* @return name or null if there is no equal value in the list
*/
/* pkg */ String findName(Pair list, String value)
{
for (Pair elm = list; elm != null; elm = elm.next) {
if (value.equals(elm.value))
return elm.name;
}
return null;
}
/**
* Retrieves value by name.
*
* @return value or null if there is no equal name in the list
*/
/* pkg */ String findValue(Pair list, String name)
{
for (Pair elm = list; elm != null; elm = elm.next) {
if (name.equals(elm.name))
return elm.value;
}
return null;
}
/**
* Provides an instance of a pair.
*
* @param next The reference to a next pair.
* @return An instance of a pair.
*/
/* pkg */ Pair pair(Pair next)
{
Pair pair;
if (mDltd != null) {
pair = mDltd;
mDltd = pair.next;
} else {
pair = new Pair();
}
pair.next = next;
return pair;
}
/**
* Deletes an instance of a pair.
*
* @param pair The pair to delete.
* @return A reference to the next pair in a chain.
*/
/* pkg */ Pair del(Pair pair)
{
Pair next = pair.next;
pair.name = null;
pair.value = null;
pair.chars = null;
pair.list = null;
pair.next = mDltd;
mDltd = pair;
return next;
}
}