--- /dev/null 2012-12-05 14:31:39.000000000 +0000 +++ new/src/share/classes/jdk/internal/util/xml/impl/XMLStreamWriterImpl.java 2012-12-05 14:31:38.000000000 +0000 @@ -0,0 +1,611 @@ +/* + * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 in the LICENSE file that + * accompanied this code). + * + * 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.util.xml.impl; + +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; + +/** + * Implementation of a reduced version of XMLStreamWriter + * + * @author Joe Wang + */ +public class XMLStreamWriterImpl implements XMLStreamWriter { + //Document state + static final int STATE_XML_DECL = 1; + static final int STATE_PROLOG = 2; + static final int STATE_DTD_DECL = 3; + static final int STATE_ELEMENT = 4; + //Element state + static final int ELEMENT_STARTTAG_OPEN = 10; + static final int ELEMENT_STARTTAG_CLOSE = 11; + static final int ELEMENT_ENDTAG_OPEN = 12; + static final int ELEMENT_ENDTAG_CLOSE = 13; + public static final char CLOSE_START_TAG = '>'; + public static final char OPEN_START_TAG = '<'; + public static final String OPEN_END_TAG = "'; + public static final String START_CDATA = ""; + public static final String CLOSE_EMPTY_ELEMENT = "/>"; + public static final String ENCODING_PREFIX = "&#x"; + public static final char SPACE = ' '; + public static final char AMPERSAND = '&'; + public static final char DOUBLEQUOT = '"'; + public static final char SEMICOLON = ';'; + + //current state + private int _state = 0; + private Element _currentEle; + private XMLWriter _writer; + private String _encoding; + /** + * This flag can be used to turn escaping off for content. It does + * not apply to attribute content. + */ + boolean _escapeCharacters = true; + //pretty print by default + private boolean _doIndent = true; + //The system line separator for writing out line breaks. + private char[] _lineSep = + System.getProperty("line.separator").toCharArray(); + + public XMLStreamWriterImpl(OutputStream os) + throws XMLStreamException { + this(os, XMLStreamWriter.DEFAULT_ENCODING); + } + + public XMLStreamWriterImpl(OutputStream os, String encoding) + throws XMLStreamException { + if (encoding == null) { + _encoding = XMLStreamWriter.DEFAULT_ENCODING; + } else { + if (!isEncodingSupported(encoding)) { + throw new XMLStreamException( + new UnsupportedEncodingException("The basic XMLWriter does " + + "not support " + encoding)); + } + this._encoding = encoding; + } + + _writer = new XMLWriter(os, encoding); + } + + /** + * 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(_encoding, XMLStreamWriter.DEFAULT_XML_VERSION); + } + + /** + * Write the XML Declaration. Defaults the encoding to utf-8 + * + * @param version version of the xml document + * @throws XMLStreamException + */ + public void writeStartDocument(String version) + throws XMLStreamException { + writeStartDocument(_encoding, version, null); + } + + /** + * 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 + * + * @param encoding encoding of the xml declaration + * @param version version of the xml document + * @throws XMLStreamException If given encoding does not match encoding of the + * underlying stream + */ + public void writeStartDocument(String encoding, String version) + throws XMLStreamException { + writeStartDocument(encoding, version, null); + } + + /** + * 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 + * + * @param encoding encoding of the xml declaration + * @param version version of the xml document + * @param standalone indicate if the xml document is standalone + * @throws XMLStreamException If given encoding does not match encoding of the + * underlying stream + */ + public void writeStartDocument(String encoding, String version, String standalone) + throws XMLStreamException { + if (_state > 0) { + throw new XMLStreamException("XML declaration must be as the first line in the XML document."); + } + _state = STATE_XML_DECL; + String enc = encoding; + if (enc == null) { + enc = _encoding; + } else { + if (!isEncodingSupported(encoding)) { + throw new XMLStreamException( + new UnsupportedEncodingException("The basic XMLWriter does " + + "not support " + encoding)); + } + } + + if (version == null) { + version = XMLStreamWriter.DEFAULT_XML_VERSION; + } + + _writer.write(""); + writeLineSeparator(); + } + + + /** + * Write a DTD section. This string represents the entire doctypedecl production + * from the XML 1.0 specification. + * + * @param dtd the DTD to be written + * @throws XMLStreamException + */ + public void writeDTD(String dtd) throws XMLStreamException { + if (_currentEle != null && _currentEle.getState() == ELEMENT_STARTTAG_OPEN) { + closeStartTag(); + } + _writer.write(dtd); + writeLineSeparator(); + } + + /** + * Writes a start tag to the output. + * @param localName local name of the tag, may not be null + * @throws XMLStreamException + */ + public void writeStartElement(String localName) throws XMLStreamException { + if (localName == null || localName.length() == 0) { + throw new XMLStreamException("Local Name cannot be null or empty"); + } + + _state = STATE_ELEMENT; + if (_currentEle != null && _currentEle.getState() == ELEMENT_STARTTAG_OPEN) { + closeStartTag(); + } + + _currentEle = new Element(_currentEle, localName, false); + openStartTag(); + + _writer.write(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 (_currentEle != null && _currentEle.getState() == ELEMENT_STARTTAG_OPEN) { + closeStartTag(); + } + + _currentEle = new Element(_currentEle, localName, true); + + openStartTag(); + _writer.write(localName); + } + + /** + * Writes an attribute to the output stream without a prefix. + * @param localName the local name of the attribute + * @param value the value of the attribute + * @throws IllegalStateException if the current state does not allow Attribute writing + * @throws XMLStreamException + */ + public void writeAttribute(String localName, String value) + throws XMLStreamException { + if (_currentEle.getState() != ELEMENT_STARTTAG_OPEN) { + throw new XMLStreamException( + "Attribute not associated with any element"); + } + + _writer.write(SPACE); + _writer.write(localName); + _writer.write("=\""); + writeXMLContent( + value, + true, // true = escapeChars + true); // true = escapeDoubleQuotes + _writer.write(DOUBLEQUOT); + } + + public void writeEndDocument() throws XMLStreamException { + if (_currentEle != null && _currentEle.getState() == ELEMENT_STARTTAG_OPEN) { + closeStartTag(); + } + + /** + * close unclosed elements if any + */ + while (_currentEle != null) { + + if (!_currentEle.isEmpty()) { + _writer.write(OPEN_END_TAG); + _writer.write(_currentEle.getLocalName()); + _writer.write(CLOSE_END_TAG); + } + + _currentEle = _currentEle.getParent(); + } + } + + public void writeEndElement() throws XMLStreamException { + if (_currentEle != null && _currentEle.getState() == ELEMENT_STARTTAG_OPEN) { + closeStartTag(); + } + + if (_currentEle == null) { + throw new XMLStreamException("No element was found to write"); + } + + if (_currentEle.isEmpty()) { + return; + } + + _writer.write(OPEN_END_TAG); + _writer.write(_currentEle.getLocalName()); + _writer.write(CLOSE_END_TAG); + writeLineSeparator(); + + _currentEle = _currentEle.getParent(); + } + + public void writeCData(String cdata) throws XMLStreamException { + if (cdata == null) { + throw new XMLStreamException("cdata cannot be null"); + } + + if (_currentEle != null && _currentEle.getState() == ELEMENT_STARTTAG_OPEN) { + closeStartTag(); + } + + _writer.write(START_CDATA); + _writer.write(cdata); + _writer.write(END_CDATA); + } + + public void writeCharacters(String data) throws XMLStreamException { + if (_currentEle != null && _currentEle.getState() == ELEMENT_STARTTAG_OPEN) { + closeStartTag(); + } + + writeXMLContent(data); + } + + public void writeCharacters(char[] data, int start, int len) + throws XMLStreamException { + if (_currentEle != null && _currentEle.getState() == ELEMENT_STARTTAG_OPEN) { + closeStartTag(); + } + + writeXMLContent(data, start, len, _escapeCharacters); + } + + /** + * Close this XMLStreamWriter by closing underlying writer. + */ + public void close() throws XMLStreamException { + if (_writer != null) { + _writer.close(); + } + _writer = null; + _currentEle = null; + _state = 0; + } + + /** + * Flush this XMLStreamWriter by flushing underlying writer. + */ + public void flush() throws XMLStreamException { + if (_writer != null) { + _writer.flush(); + } + } + + /** + * Set the flag to indicate if the writer should add line separator + * @param doIndent + */ + public void setDoIndent(boolean doIndent) { + _doIndent = doIndent; + } + /** + * Writes XML content to underlying writer. Escapes characters unless + * escaping character feature is turned off. + */ + private void writeXMLContent(char[] content, int start, int length, + boolean escapeChars) throws XMLStreamException { + if (!escapeChars) { + _writer.write(content, start, length); + return; + } + + // Index of the next char to be written + int startWritePos = start; + + final int end = start + length; + + for (int index = start; index < end; index++) { + char ch = content[index]; + + if (!_writer.canEncode(ch)){ + _writer.write(content, startWritePos, index - startWritePos ); + + // Escape this char as underlying encoder cannot handle it + _writer.write( ENCODING_PREFIX ); + _writer.write(Integer.toHexString(ch)); + _writer.write( SEMICOLON ); + startWritePos = index + 1; + continue; + } + + switch (ch) { + case OPEN_START_TAG: + _writer.write(content, startWritePos, index - startWritePos); + _writer.write("<"); + startWritePos = index + 1; + + break; + + case AMPERSAND: + _writer.write(content, startWritePos, index - startWritePos); + _writer.write("&"); + startWritePos = index + 1; + + break; + + case CLOSE_START_TAG: + _writer.write(content, startWritePos, index - startWritePos); + _writer.write(">"); + startWritePos = index + 1; + + break; + } + } + + // Write any pending data + _writer.write(content, startWritePos, end - startWritePos); + } + + private void writeXMLContent(String content) throws XMLStreamException { + if ((content != null) && (content.length() > 0)) { + writeXMLContent(content, + _escapeCharacters, // boolean = escapeChars + false); // false = escapeDoubleQuotes + } + } + + /** + * Writes XML content to underlying writer. Escapes characters unless + * escaping character feature is turned off. + */ + private void writeXMLContent( + String content, + boolean escapeChars, + boolean escapeDoubleQuotes) + throws XMLStreamException { + + if (!escapeChars) { + _writer.write(content); + + return; + } + + // Index of the next char to be written + int startWritePos = 0; + + final int end = content.length(); + + for (int index = 0; index < end; index++) { + char ch = content.charAt(index); + + if (!_writer.canEncode(ch)){ + _writer.write(content, startWritePos, index - startWritePos ); + + // Escape this char as underlying encoder cannot handle it + _writer.write( ENCODING_PREFIX ); + _writer.write(Integer.toHexString(ch)); + _writer.write( SEMICOLON ); + startWritePos = index + 1; + continue; + } + + switch (ch) { + case OPEN_START_TAG: + _writer.write(content, startWritePos, index - startWritePos); + _writer.write("<"); + startWritePos = index + 1; + + break; + + case AMPERSAND: + _writer.write(content, startWritePos, index - startWritePos); + _writer.write("&"); + startWritePos = index + 1; + + break; + + case CLOSE_START_TAG: + _writer.write(content, startWritePos, index - startWritePos); + _writer.write(">"); + startWritePos = index + 1; + + break; + + case DOUBLEQUOT: + _writer.write(content, startWritePos, index - startWritePos); + if (escapeDoubleQuotes) { + _writer.write("""); + } else { + _writer.write(DOUBLEQUOT); + } + startWritePos = index + 1; + + break; + } + } + + // Write any pending data + _writer.write(content, startWritePos, end - startWritePos); + } + + + /** + * marks open of start tag and writes the same into the writer. + */ + private void openStartTag() throws XMLStreamException { + _currentEle.setState(ELEMENT_STARTTAG_OPEN); + _writer.write(OPEN_START_TAG); + } + + /** + * marks close of start tag and writes the same into the writer. + */ + private void closeStartTag() throws XMLStreamException { + if (_currentEle.isEmpty()) { + _writer.write(CLOSE_EMPTY_ELEMENT); + } else { + _writer.write(CLOSE_START_TAG); + + } + + if (_currentEle.getParent() == null) { + writeLineSeparator(); + } + + _currentEle.setState(ELEMENT_STARTTAG_CLOSE); + + } + + /** + * Write a line separator + * @throws XMLStreamException + */ + private void writeLineSeparator() throws XMLStreamException { + if (_doIndent) { + _writer.write(_lineSep, 0, _lineSep.length); + } + } + private boolean isEncodingSupported(String encoding) { + if (encoding.equalsIgnoreCase("UTF-32")) { + return false; + } + return true; + } + /* + * Start of Internal classes. + * + */ + protected class Element { + + /** + * the parent element + */ + protected Element _parent; + /** + * The size of the stack. + */ + protected short _Depth; + /** + * indicate if an element is an empty one + */ + boolean _isEmptyElement = false; + String _localpart; + int _state; + + /** + * Default constructor. + */ + public Element() { + } + + /** + * @param parent the parent of the element + * @param localpart name of the element + * @param isEmpty indicate if the element is an empty one + */ + public Element(Element parent, String localpart, boolean isEmpty) { + _parent = parent; + _localpart = localpart; + _isEmptyElement = isEmpty; + } + + public Element getParent() { + return _parent; + } + + public String getLocalName() { + return _localpart; + } + + /** + * get the state of the element + */ + public int getState() { + return _state; + } + + /** + * Set the state of the element + * + * @param state the state of the element + */ + public void setState(int state) { + _state = state; + } + + public boolean isEmpty() { + return _isEmptyElement; + } + } +}