// You can redistribute this software and/or modify it under the terms of
// the Ozone Library License version 1 published by ozone-db.org.
//
// The original code and portions created by SMB are
// Copyright (C) 1997-@year@ by SMB GmbH. All rights reserved.
//
// $Id: SAXChunkProducer.java,v 1.1 2001/12/18 10:31:31 per_nyfelt Exp $
package org.ozoneDB.xml.util;
import java.io.IOException;
import java.io.Serializable;
import org.ozoneDB.DxLib.DxDeque;
import org.ozoneDB.DxLib.DxArrayDeque;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.xml.sax.ContentHandler;
import org.xml.sax.ext.LexicalHandler;
import org.xml.sax.Attributes;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
/**
* This class produces a sequence of {@link SAXEventChunk}s out of SAX events or
* out of a DOM tree. These chunks are used to communicate between client and
* server.
*
*
* @version $Revision: 1.1 $ $Date: 2001/12/18 10:31:31 $
* @author <a href="http://www.smb-tec.com">SMB</a>
*/
public final class SAXChunkProducer implements ContentHandler, LexicalHandler, Serializable {
// Constants
private final static boolean debug = false;
private final static int DEFAULT_CHUNK_SIZE = 100000;
private final static int DEFAULT_CHUNK_INCREASE = 4096;
/** the default state */
private final static int CHUNK_STATE_INVALID = -1;
/** the initial state */
private final static int CHUNK_STATE_INIT = 0;
/** a new node has to be processed */
private final static int CHUNK_STATE_NODE = 1;
/** next step is to close an element (or document) */
private final static int CHUNK_STATE_CLOSE = 3;
/** conversion was finished. i.e. there are no more events to throw */
private final static int CHUNK_STATE_FINISH = 4;
// Data
/**
* Used for SAX storage. Because chunk processing is done using
* {@link SAXChunkProducerDelegate} (which means consumer creation and
* consumer usage happens in different methods) the consumer has to be
* stored somewhere. I choose the corresponding producer.
*/
public SAXChunkConsumer dbConsumer;
private final SAXChunkProducerDelegate delegate;
private final ChunkOutputStream chunkOutput;
private final CXMLContentHandler contentHandler;
private final LexicalHandler lexicalHandler;
/**
* True if all descendant children shall be traversed, false otherwise.
* @see #depth
*/
private final boolean deep;
/**
* The depth of the traversal. If {@link #deep} is false this determines
* how deep descendant shall be traversed, otherwise this member is ignored.
*/
private int depth;
private int processLevel = 0;
/**
* Keeps information about which nodes have already been opened.
* (used by {@link #createNextChunk() createNextChunk})
*/
private final DxDeque endEvents;
private NodeList sourceNodes;
private int sourceNodesIndex;
/**
* The node that has to be converted next; used by
* {@link createNextChunk() createNextChunk}. This has to be initialized to
* the start node before calling {@link createNextChunk() createNextChunk}
* the first time.
*/
private Node nextNode;
/**
* implies that the node to be converted was created by a DOM Level 2
* implementation.
* used by {@link createNextChunk() createNextChunk}.
*/
private final boolean domLevel2;
/**
* the state of the {@link createNextChunk() createNextChunk} method
* @see #CHUNK_STATE_INVALID
* @see #CHUNK_STATE_INIT
* @see #CHUNK_STATE_NODE
* @see #CHUNK_STATE_CLOSE
*/
private int chunkState = CHUNK_STATE_INVALID;
/**
*/
/* public SAXChunkProducer( Node node ) throws IOException{
this( node, SAXChunkProducer.DEFAULT_CHUNK_SIZE, -1 );
}
*/
/**
*/
/* public SAXChunkProducer( Node node, int _depth ) throws IOException{
this( node, SAXChunkProducer.DEFAULT_CHUNK_SIZE, _depth );
}
*/
/**
*/
/* public SAXChunkProducer( Node _node, int _maxParts, int _depth ) throws IOException{
ModifiableNodeList mnl = new ModifiableNodeList(1);
mnl.addNode(_node);
this(mnl, _maxParts, _depth);
}
*/
/**
*/
public SAXChunkProducer(NodeList _nodelist) throws IOException{
this(_nodelist, SAXChunkProducer.DEFAULT_CHUNK_SIZE, -1);
}
/**
*/
public SAXChunkProducer(NodeList _nodelist, int _depth) throws IOException{
this(_nodelist, SAXChunkProducer.DEFAULT_CHUNK_SIZE, _depth);
}
/**
*/
public SAXChunkProducer(NodeList _nodelist, int _maxParts, int _depth) throws IOException{
this.deep = _depth < 0;
this.depth = _depth < 0 ? 0 : _depth;
this.delegate = null;
this.sourceNodes = _nodelist;
this.sourceNodesIndex = 0;
this.nextNode = _nodelist.item(0);
this.chunkState = CHUNK_STATE_NODE;
this.endEvents = new DxArrayDeque();
Document factory = (Document)((this.nextNode.getNodeType() == Node.DOCUMENT_NODE)
? this.nextNode : this.nextNode.getOwnerDocument());
this.domLevel2 = factory.getImplementation().hasFeature( "XML", "2.0" );
this.chunkOutput = new ChunkOutputStream( SAXChunkProducer.DEFAULT_CHUNK_SIZE, SAXChunkProducer.DEFAULT_CHUNK_INCREASE );
this.contentHandler = new CXMLContentHandler( this.chunkOutput );
this.lexicalHandler = (this.contentHandler instanceof LexicalHandler)
? this.contentHandler
: null;
}
/**
*/
public SAXChunkProducer( SAXChunkProducerDelegate _delegate ) throws IOException{
this( _delegate, SAXChunkProducer.DEFAULT_CHUNK_SIZE, -1 );
}
/**
*/
public SAXChunkProducer( SAXChunkProducerDelegate _delegate, int _depth ) throws IOException{
this( _delegate, SAXChunkProducer.DEFAULT_CHUNK_SIZE, _depth );
}
/**
*/
public SAXChunkProducer( SAXChunkProducerDelegate _delegate, int _maxParts, int _depth ) throws IOException{
this.deep = _depth == -1;
this.depth = this.deep ? 0 : _depth;
this.delegate = _delegate;
this.chunkOutput = new ChunkOutputStream( SAXChunkProducer.DEFAULT_CHUNK_SIZE,
SAXChunkProducer.DEFAULT_CHUNK_INCREASE );
this.contentHandler = new CXMLContentHandler( this.chunkOutput );
this.lexicalHandler = (this.contentHandler instanceof LexicalHandler)
? this.contentHandler
: null;
this.domLevel2 = false;
this.endEvents = null;
}
/**
* @return the data of the chunk created during the last call of
* {@link #createNextChunk()}.
*/
public ChunkOutputStream chunkStream() throws SAXException {
return this.chunkOutput;
}
/**
* Converts a given DOM tree in multiple steps to SAX events.
* Every call throws the specified number of events (or less, if the
* conversion is finished).
* Before calling this method the first time {@link #nextNode nextNode} has to
* be set to the root node of the DOM tree to convert, and
* {@link #chunkState} has to be set to
* {@link #CHUNK_STATE_INIT}. (The same way you can reset the nextChunk
* context.)
*
* @param _contentHandler SAX content handler that receives the events
* @param _eventsToThrow the number of events to throw
* @return true if there are still events to throw, false if processing has
* finished
* @throws SAXException
* @throws IllegalStateException if {@link #chunkState} has
* not been set or has been set to an unknown value.
* @throws IllegalArgumentException if the value of _eventsToThrow was
* equal or less than 0.
*/
public final void createNextChunk() throws SAXException {
String uri;
String qName;
String lName;
// now on the client
// chunkOutput.reset();
while (chunkOutput.getState() != ChunkOutputStream.STATE_OVERFLOW && chunkState != CHUNK_STATE_FINISH) {
//throw events until the chunk is filled
switch (chunkState) {
case CHUNK_STATE_NODE:
switch (nextNode.getNodeType()) {
case Node.DOCUMENT_NODE:
contentHandler.startDocument();
depth -= deep ? 0 : 1;
endEvents.push( nextNode );
//any children to process ?
nextNode = nextNode.getFirstChild();
chunkState = ((nextNode == null) || (depth < 0))
? CHUNK_STATE_CLOSE
: CHUNK_STATE_NODE;
nextNode = (chunkState == CHUNK_STATE_CLOSE) ? (Node)endEvents.pop() : nextNode;
break;
case Node.ELEMENT_NODE:
Element elem = (Element)nextNode;
Attributes saxAttr = createSAXAttributes(elem);
uri = domLevel2 ? elem.getNamespaceURI() : "";
qName = elem.getNodeName();
lName = domLevel2 ? elem.getLocalName() : qName;
contentHandler.startElement(
uri == null ? "" : uri,
lName == null ? qName : lName,
qName, saxAttr );
endEvents.push(nextNode);
depth -= deep ? 0 : 1;
nextNode = nextNode.getFirstChild();
chunkState = ((nextNode == null) || (depth < 0)) ? CHUNK_STATE_CLOSE : CHUNK_STATE_NODE;
nextNode = (chunkState == CHUNK_STATE_CLOSE) ? (Node)endEvents.pop() : nextNode;
break;
default:
convertSingleEventNode( nextNode );
nextNode = nextNode.getNextSibling();
chunkState = ((nextNode != null) && (endEvents.peek() != null))
? CHUNK_STATE_NODE
: ((nextNode = (Node)endEvents.pop()) != null)
? CHUNK_STATE_CLOSE
: CHUNK_STATE_FINISH;
if (this.chunkState == CHUNK_STATE_FINISH) {
this.sourceNodesIndex++;
if (this.sourceNodesIndex < this.sourceNodes.getLength()) {
this.nextNode = this.sourceNodes.item(this.sourceNodesIndex);
this.chunkState = CHUNK_STATE_NODE;
}
}
break;
}
break;
case CHUNK_STATE_CLOSE: {
switch (nextNode.getNodeType()) {
case Node.ELEMENT_NODE:
depth += deep ? 0 : 1;
Element elem = (Element)nextNode;
uri = domLevel2 ? elem.getNamespaceURI() : "";
qName = elem.getNodeName();
lName = domLevel2 ? elem.getLocalName() : qName;
contentHandler.endElement(
uri == null ? "" : uri,
lName == null ? qName : lName,
qName);
nextNode = elem.getNextSibling();
break;
case Node.DOCUMENT_NODE:
depth += deep ? 0 : 1;
contentHandler.endDocument();
nextNode = null;
break;
default:
throw new IllegalStateException( "endEvents stack contains unproper value: " + nextNode );
//break;
}
chunkState = ((nextNode != null) && (endEvents.peek() != null))
? CHUNK_STATE_NODE
: ((nextNode = (Node)endEvents.pop()) != null)
? CHUNK_STATE_CLOSE
: CHUNK_STATE_FINISH;
if (this.chunkState == CHUNK_STATE_FINISH) {
this.sourceNodesIndex++;
if (this.sourceNodesIndex < this.sourceNodes.getLength()) {
this.nextNode = this.sourceNodes.item(this.sourceNodesIndex);
this.chunkState = CHUNK_STATE_NODE;
}
}
break;
}
default:
throw new IllegalStateException("Unsupported value in member chunkState: " + chunkState);
}
}
if (chunkState == CHUNK_STATE_FINISH) {
chunkOutput.setEndFlag();
}
}
/**
* This method is used for DOM nodes that don't require two SAX events.
* i.e. every node except document and element nodes.
* @param _node the node to convert
*/
private void convertSingleEventNode(Node _node) throws SAXException {
switch (_node.getNodeType()) {
case Node.TEXT_NODE:
char[] ch = _node.getNodeValue().toCharArray();
this.contentHandler.characters( ch, 0, ch.length );
break;
case Node.CDATA_SECTION_NODE:
char[] cdata = _node.getNodeValue().toCharArray();
if (this.lexicalHandler != null) {
this.lexicalHandler.startCDATA();
}
this.contentHandler.characters( cdata, 0, cdata.length );
if (this.lexicalHandler != null) {
this.lexicalHandler.endCDATA();
}
break;
case Node.PROCESSING_INSTRUCTION_NODE:
this.contentHandler.processingInstruction( _node.getNodeName(), _node.getNodeValue() );
break;
case Node.ENTITY_REFERENCE_NODE:
this.contentHandler.skippedEntity( _node.getNodeName() );
break;
case Node.COMMENT_NODE:
if (this.lexicalHandler != null) {
char[] comment = _node.getNodeValue().toCharArray();
this.lexicalHandler.comment(comment, 0, comment.length);
}
break;
case Node.DOCUMENT_TYPE_NODE:
//"Document type node can't be translated: "
break;
case Node.DOCUMENT_FRAGMENT_NODE:
//"Document fragment node can't be translated: "
break;
case Node.NOTATION_NODE:
//"Notation node can't be translated: "
break;
case Node.ENTITY_NODE:
//"Entity node can't be translated: "
break;
case Node.ATTRIBUTE_NODE:
//"Standalone attribute node can't be translated: "
break;
default:
//"unknown node type or node type not supported by this method
break;
}
}
/**
* creates a Attributes object (list of SAX attributes) containing the
* attributes of a given DOM element node.
* @param elem the element whose attributes shall be converted
* @return the SAX attribute list
*/
private Attributes createSAXAttributes( Element elem ) {
NamedNodeMap domAttributes = elem.getAttributes();
AttributesImpl saxAttributes = new AttributesImpl();
int length = domAttributes.getLength();
for (int i = 0; i < length; i++) {
Node node = domAttributes.item(i);
String uri = domLevel2 ? node.getNamespaceURI() : "";
String qName = node.getNodeName();
String lName = domLevel2 ? node.getLocalName() : qName;
saxAttributes.addAttribute(
(uri == null) ? "" : uri,
(lName == null) ? qName : lName,
qName, "",
node.getNodeValue());
}
return saxAttributes;
}
// SAX ContentHandler implementation
/**
* Received notification of the beginning of the document.
*/
public final void startDocument() throws SAXException {
if (deep || depth >= 0) {
if (debug) {
System.out.println( "SAXChunkProducer.startDocument()..." );
}
this.contentHandler.startDocument();
this.processLevel++;
checkChunk();
}
depth -= deep ? 0 : 1;
}
/**
* Received notification of the end of the document.
*/
public final void endDocument() throws SAXException {
depth += deep ? 0 : 1;
if (deep || depth >= 0) {
if (debug) {
System.out.println( "SAXChunkProducer.endDocument()..." );
}
this.contentHandler.endDocument();
this.processLevel--;
checkChunk();
}
}
/**
* Receive notification of the start of an element.
*/
public final void startElement( String namespaceURI, String localName, String rawName, Attributes atts )
throws SAXException {
if (deep || depth >= 0) {
if (debug) {
System.out.println( "SAXChunkProducer.startElement()..." );
}
this.contentHandler.startElement( namespaceURI, localName, rawName, atts );
this.processLevel++;
checkChunk();
}
depth -= deep ? 0 : 1;
}
/**
* Receive notification of the end of an element.
*/
public final void endElement( String _namespaceURI, String _localName, String _rawName ) throws SAXException {
depth = deep ? 0 : 1;
if (deep || depth >= 0) {
if (debug) {
System.out.println( "SAXChunkProducer.endElement()..." );
}
this.contentHandler.endElement( _namespaceURI, _localName, _rawName );
this.processLevel--;
checkChunk();
}
}
/**
* Receive notification of character data inside an element.
*/
public final void characters( char[] ch, int start, int length ) throws SAXException {
if (deep || depth >= 0) {
if (debug) {
System.out.println( "SAXChunkProducer.characters()..." );
}
char[] characters = new char[length];
System.arraycopy( ch, start, characters, 0, length );
this.contentHandler.characters( characters, 0, characters.length );
checkChunk();
}
}
/**
* Receive notification of a processing instruction.
*/
public final void processingInstruction( String target, String data ) throws SAXException {
if (deep || depth >= 0) {
if (debug) {
System.out.println( "SAXChunkProducer.pi ..." );
}
this.contentHandler.processingInstruction( target, data );
checkChunk();
}
}
/**
* Receive notification of a skipped entity.
*/
public final void skippedEntity( java.lang.String name ) throws SAXException {
if (deep || depth >= 0) {
this.contentHandler.skippedEntity( name );
checkChunk();
}
}
/**
* Begin the scope of a prefix-URI Namespace mapping.
*/
public final void startPrefixMapping( String prefix, String uri ) throws SAXException {
if (deep || depth >= 0) {
if (debug) {
System.out.println( "SAXChunkProducer.startPrefixMapping ..." );
}
this.contentHandler.startPrefixMapping( prefix, uri );
checkChunk();
}
}
/**
* End the scope of a prefix-URI mapping.
*/
public final void endPrefixMapping( String prefix ) throws SAXException {
if (deep || depth >= 0) {
if (debug) {
System.out.println( "SAXChunkProducer.endPrefixMapping()..." );
}
this.contentHandler.endPrefixMapping( prefix );
checkChunk();
}
}
/**
* Receive notification of ignorable whitespace in element content.
*/
public final void ignorableWhitespace( char[] ch, int start, int length ) throws SAXException {
// ignorable whitespaces will be ignored
}
/**
* Receive an object for locating the origin of SAX document events.
*/
public final void setDocumentLocator( Locator locator ) {
// we don't care about the origin of the document
}
//
// SAX LexicalHandler implemenation
//
public void comment( char[] ch, int start, int length ) throws SAXException {
if ((this.lexicalHandler != null)
&& (deep || depth >= 0)) {
this.lexicalHandler.comment(ch, start, length);
checkChunk();
}
}
public void startCDATA() throws SAXException {
if ((this.lexicalHandler != null)
&& (deep || depth >= 0)) {
this.lexicalHandler.startCDATA();
checkChunk();
}
}
public void endCDATA() throws SAXException {
if ((this.lexicalHandler != null)
&& (deep || depth >= 0)) {
this.lexicalHandler.endCDATA();
checkChunk();
}
}
public void startDTD(String name, String publicId, String systemId)
throws SAXException {
if ((this.lexicalHandler != null)
&& (deep || depth >= 0)) {
this.lexicalHandler.startDTD(name, publicId, systemId);
checkChunk();
}
}
public void endDTD() throws SAXException {
if ((this.lexicalHandler != null)
&& (deep || depth >= 0)) {
this.lexicalHandler.endDTD();
checkChunk();
}
}
public void startEntity(String name) throws SAXException {
if ((this.lexicalHandler != null)
&& (deep || depth >= 0)) {
this.lexicalHandler.startEntity(name);
checkChunk();
}
}
public void endEntity(String name) throws SAXException {
if ((this.lexicalHandler != null)
&& (deep || depth >= 0)) {
this.lexicalHandler.startEntity(name);
checkChunk();
}
}
protected final void checkChunk() {
if (this.delegate == null) {
return;
}
try {
if ((this.chunkOutput.getState() == ChunkOutputStream.STATE_OVERFLOW)
|| (this.processLevel == 0)) {
this.delegate.processChunk(this);
this.chunkOutput.reset();
}
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e.toString());
}
}
}