001: // You can redistribute this software and/or modify it under the terms of
002: // the Ozone Library License version 1 published by ozone-db.org.
003: //
004: // The original code and portions created by SMB are
005: // Copyright (C) 1997-@year@ by SMB GmbH. All rights reserved.
006: //
007: // $Id: SAXChunkProducer.java,v 1.1 2001/12/18 10:31:31 per_nyfelt Exp $
008:
009: package org.ozoneDB.xml.util;
010:
011: import java.io.IOException;
012: import java.io.Serializable;
013:
014: import org.ozoneDB.DxLib.DxDeque;
015: import org.ozoneDB.DxLib.DxArrayDeque;
016:
017: import org.w3c.dom.Node;
018: import org.w3c.dom.NodeList;
019: import org.w3c.dom.Document;
020: import org.w3c.dom.Element;
021: import org.w3c.dom.NamedNodeMap;
022:
023: import org.xml.sax.ContentHandler;
024: import org.xml.sax.ext.LexicalHandler;
025: import org.xml.sax.Attributes;
026: import org.xml.sax.Locator;
027: import org.xml.sax.SAXException;
028: import org.xml.sax.helpers.AttributesImpl;
029:
030: /**
031: * This class produces a sequence of {@link SAXEventChunk}s out of SAX events or
032: * out of a DOM tree. These chunks are used to communicate between client and
033: * server.
034: *
035: *
036: * @version $Revision: 1.1 $ $Date: 2001/12/18 10:31:31 $
037: * @author <a href="http://www.smb-tec.com">SMB</a>
038: */
039: public final class SAXChunkProducer implements ContentHandler,
040: LexicalHandler, Serializable {
041:
042: // Constants
043:
044: private final static boolean debug = false;
045:
046: private final static int DEFAULT_CHUNK_SIZE = 100000;
047:
048: private final static int DEFAULT_CHUNK_INCREASE = 4096;
049:
050: /** the default state */
051: private final static int CHUNK_STATE_INVALID = -1;
052:
053: /** the initial state */
054: private final static int CHUNK_STATE_INIT = 0;
055:
056: /** a new node has to be processed */
057: private final static int CHUNK_STATE_NODE = 1;
058:
059: /** next step is to close an element (or document) */
060: private final static int CHUNK_STATE_CLOSE = 3;
061:
062: /** conversion was finished. i.e. there are no more events to throw */
063: private final static int CHUNK_STATE_FINISH = 4;
064:
065: // Data
066:
067: /**
068: * Used for SAX storage. Because chunk processing is done using
069: * {@link SAXChunkProducerDelegate} (which means consumer creation and
070: * consumer usage happens in different methods) the consumer has to be
071: * stored somewhere. I choose the corresponding producer.
072: */
073: public SAXChunkConsumer dbConsumer;
074:
075: private final SAXChunkProducerDelegate delegate;
076:
077: private final ChunkOutputStream chunkOutput;
078:
079: private final CXMLContentHandler contentHandler;
080:
081: private final LexicalHandler lexicalHandler;
082:
083: /**
084: * True if all descendant children shall be traversed, false otherwise.
085: * @see #depth
086: */
087: private final boolean deep;
088:
089: /**
090: * The depth of the traversal. If {@link #deep} is false this determines
091: * how deep descendant shall be traversed, otherwise this member is ignored.
092: */
093: private int depth;
094:
095: private int processLevel = 0;
096:
097: /**
098: * Keeps information about which nodes have already been opened.
099: * (used by {@link #createNextChunk() createNextChunk})
100: */
101: private final DxDeque endEvents;
102:
103: private NodeList sourceNodes;
104: private int sourceNodesIndex;
105:
106: /**
107: * The node that has to be converted next; used by
108: * {@link createNextChunk() createNextChunk}. This has to be initialized to
109: * the start node before calling {@link createNextChunk() createNextChunk}
110: * the first time.
111: */
112: private Node nextNode;
113:
114: /**
115: * implies that the node to be converted was created by a DOM Level 2
116: * implementation.
117: * used by {@link createNextChunk() createNextChunk}.
118: */
119: private final boolean domLevel2;
120:
121: /**
122: * the state of the {@link createNextChunk() createNextChunk} method
123: * @see #CHUNK_STATE_INVALID
124: * @see #CHUNK_STATE_INIT
125: * @see #CHUNK_STATE_NODE
126: * @see #CHUNK_STATE_CLOSE
127: */
128: private int chunkState = CHUNK_STATE_INVALID;
129:
130: /**
131: */
132: /* public SAXChunkProducer( Node node ) throws IOException{
133: this( node, SAXChunkProducer.DEFAULT_CHUNK_SIZE, -1 );
134: }
135: */
136:
137: /**
138: */
139: /* public SAXChunkProducer( Node node, int _depth ) throws IOException{
140: this( node, SAXChunkProducer.DEFAULT_CHUNK_SIZE, _depth );
141: }
142: */
143:
144: /**
145: */
146: /* public SAXChunkProducer( Node _node, int _maxParts, int _depth ) throws IOException{
147: ModifiableNodeList mnl = new ModifiableNodeList(1);
148: mnl.addNode(_node);
149: this(mnl, _maxParts, _depth);
150: }
151: */
152:
153: /**
154: */
155: public SAXChunkProducer(NodeList _nodelist) throws IOException {
156: this (_nodelist, SAXChunkProducer.DEFAULT_CHUNK_SIZE, -1);
157: }
158:
159: /**
160: */
161: public SAXChunkProducer(NodeList _nodelist, int _depth)
162: throws IOException {
163: this (_nodelist, SAXChunkProducer.DEFAULT_CHUNK_SIZE, _depth);
164: }
165:
166: /**
167: */
168: public SAXChunkProducer(NodeList _nodelist, int _maxParts,
169: int _depth) throws IOException {
170: this .deep = _depth < 0;
171: this .depth = _depth < 0 ? 0 : _depth;
172:
173: this .delegate = null;
174:
175: this .sourceNodes = _nodelist;
176: this .sourceNodesIndex = 0;
177: this .nextNode = _nodelist.item(0);
178:
179: this .chunkState = CHUNK_STATE_NODE;
180: this .endEvents = new DxArrayDeque();
181:
182: Document factory = (Document) ((this .nextNode.getNodeType() == Node.DOCUMENT_NODE) ? this .nextNode
183: : this .nextNode.getOwnerDocument());
184: this .domLevel2 = factory.getImplementation().hasFeature("XML",
185: "2.0");
186:
187: this .chunkOutput = new ChunkOutputStream(
188: SAXChunkProducer.DEFAULT_CHUNK_SIZE,
189: SAXChunkProducer.DEFAULT_CHUNK_INCREASE);
190: this .contentHandler = new CXMLContentHandler(this .chunkOutput);
191: this .lexicalHandler = (this .contentHandler instanceof LexicalHandler) ? this .contentHandler
192: : null;
193: }
194:
195: /**
196: */
197: public SAXChunkProducer(SAXChunkProducerDelegate _delegate)
198: throws IOException {
199: this (_delegate, SAXChunkProducer.DEFAULT_CHUNK_SIZE, -1);
200: }
201:
202: /**
203: */
204: public SAXChunkProducer(SAXChunkProducerDelegate _delegate,
205: int _depth) throws IOException {
206: this (_delegate, SAXChunkProducer.DEFAULT_CHUNK_SIZE, _depth);
207: }
208:
209: /**
210: */
211: public SAXChunkProducer(SAXChunkProducerDelegate _delegate,
212: int _maxParts, int _depth) throws IOException {
213: this .deep = _depth == -1;
214: this .depth = this .deep ? 0 : _depth;
215:
216: this .delegate = _delegate;
217:
218: this .chunkOutput = new ChunkOutputStream(
219: SAXChunkProducer.DEFAULT_CHUNK_SIZE,
220: SAXChunkProducer.DEFAULT_CHUNK_INCREASE);
221: this .contentHandler = new CXMLContentHandler(this .chunkOutput);
222: this .lexicalHandler = (this .contentHandler instanceof LexicalHandler) ? this .contentHandler
223: : null;
224:
225: this .domLevel2 = false;
226: this .endEvents = null;
227: }
228:
229: /**
230: * @return the data of the chunk created during the last call of
231: * {@link #createNextChunk()}.
232: */
233: public ChunkOutputStream chunkStream() throws SAXException {
234: return this .chunkOutput;
235: }
236:
237: /**
238: * Converts a given DOM tree in multiple steps to SAX events.
239: * Every call throws the specified number of events (or less, if the
240: * conversion is finished).
241: * Before calling this method the first time {@link #nextNode nextNode} has to
242: * be set to the root node of the DOM tree to convert, and
243: * {@link #chunkState} has to be set to
244: * {@link #CHUNK_STATE_INIT}. (The same way you can reset the nextChunk
245: * context.)
246: *
247: * @param _contentHandler SAX content handler that receives the events
248: * @param _eventsToThrow the number of events to throw
249: * @return true if there are still events to throw, false if processing has
250: * finished
251: * @throws SAXException
252: * @throws IllegalStateException if {@link #chunkState} has
253: * not been set or has been set to an unknown value.
254: * @throws IllegalArgumentException if the value of _eventsToThrow was
255: * equal or less than 0.
256: */
257: public final void createNextChunk() throws SAXException {
258: String uri;
259: String qName;
260: String lName;
261:
262: // now on the client
263: // chunkOutput.reset();
264:
265: while (chunkOutput.getState() != ChunkOutputStream.STATE_OVERFLOW
266: && chunkState != CHUNK_STATE_FINISH) {
267: //throw events until the chunk is filled
268:
269: switch (chunkState) {
270: case CHUNK_STATE_NODE:
271: switch (nextNode.getNodeType()) {
272: case Node.DOCUMENT_NODE:
273: contentHandler.startDocument();
274: depth -= deep ? 0 : 1;
275:
276: endEvents.push(nextNode);
277:
278: //any children to process ?
279: nextNode = nextNode.getFirstChild();
280: chunkState = ((nextNode == null) || (depth < 0)) ? CHUNK_STATE_CLOSE
281: : CHUNK_STATE_NODE;
282: nextNode = (chunkState == CHUNK_STATE_CLOSE) ? (Node) endEvents
283: .pop()
284: : nextNode;
285: break;
286: case Node.ELEMENT_NODE:
287: Element elem = (Element) nextNode;
288: Attributes saxAttr = createSAXAttributes(elem);
289: uri = domLevel2 ? elem.getNamespaceURI() : "";
290: qName = elem.getNodeName();
291: lName = domLevel2 ? elem.getLocalName() : qName;
292:
293: contentHandler.startElement(uri == null ? "" : uri,
294: lName == null ? qName : lName, qName,
295: saxAttr);
296:
297: endEvents.push(nextNode);
298:
299: depth -= deep ? 0 : 1;
300:
301: nextNode = nextNode.getFirstChild();
302: chunkState = ((nextNode == null) || (depth < 0)) ? CHUNK_STATE_CLOSE
303: : CHUNK_STATE_NODE;
304: nextNode = (chunkState == CHUNK_STATE_CLOSE) ? (Node) endEvents
305: .pop()
306: : nextNode;
307: break;
308: default:
309: convertSingleEventNode(nextNode);
310:
311: nextNode = nextNode.getNextSibling();
312: chunkState = ((nextNode != null) && (endEvents
313: .peek() != null)) ? CHUNK_STATE_NODE
314: : ((nextNode = (Node) endEvents.pop()) != null) ? CHUNK_STATE_CLOSE
315: : CHUNK_STATE_FINISH;
316:
317: if (this .chunkState == CHUNK_STATE_FINISH) {
318: this .sourceNodesIndex++;
319: if (this .sourceNodesIndex < this .sourceNodes
320: .getLength()) {
321: this .nextNode = this .sourceNodes
322: .item(this .sourceNodesIndex);
323: this .chunkState = CHUNK_STATE_NODE;
324: }
325:
326: }
327:
328: break;
329: }
330: break;
331:
332: case CHUNK_STATE_CLOSE: {
333: switch (nextNode.getNodeType()) {
334: case Node.ELEMENT_NODE:
335: depth += deep ? 0 : 1;
336:
337: Element elem = (Element) nextNode;
338: uri = domLevel2 ? elem.getNamespaceURI() : "";
339: qName = elem.getNodeName();
340: lName = domLevel2 ? elem.getLocalName() : qName;
341: contentHandler.endElement(uri == null ? "" : uri,
342: lName == null ? qName : lName, qName);
343:
344: nextNode = elem.getNextSibling();
345: break;
346: case Node.DOCUMENT_NODE:
347: depth += deep ? 0 : 1;
348: contentHandler.endDocument();
349: nextNode = null;
350: break;
351: default:
352: throw new IllegalStateException(
353: "endEvents stack contains unproper value: "
354: + nextNode);
355: //break;
356: }
357:
358: chunkState = ((nextNode != null) && (endEvents.peek() != null)) ? CHUNK_STATE_NODE
359: : ((nextNode = (Node) endEvents.pop()) != null) ? CHUNK_STATE_CLOSE
360: : CHUNK_STATE_FINISH;
361:
362: if (this .chunkState == CHUNK_STATE_FINISH) {
363: this .sourceNodesIndex++;
364: if (this .sourceNodesIndex < this .sourceNodes
365: .getLength()) {
366: this .nextNode = this .sourceNodes
367: .item(this .sourceNodesIndex);
368: this .chunkState = CHUNK_STATE_NODE;
369: }
370:
371: }
372:
373: break;
374: }
375: default:
376: throw new IllegalStateException(
377: "Unsupported value in member chunkState: "
378: + chunkState);
379: }
380: }
381:
382: if (chunkState == CHUNK_STATE_FINISH) {
383: chunkOutput.setEndFlag();
384: }
385: }
386:
387: /**
388: * This method is used for DOM nodes that don't require two SAX events.
389: * i.e. every node except document and element nodes.
390: * @param _node the node to convert
391: */
392: private void convertSingleEventNode(Node _node) throws SAXException {
393:
394: switch (_node.getNodeType()) {
395: case Node.TEXT_NODE:
396: char[] ch = _node.getNodeValue().toCharArray();
397: this .contentHandler.characters(ch, 0, ch.length);
398: break;
399: case Node.CDATA_SECTION_NODE:
400: char[] cdata = _node.getNodeValue().toCharArray();
401: if (this .lexicalHandler != null) {
402: this .lexicalHandler.startCDATA();
403: }
404:
405: this .contentHandler.characters(cdata, 0, cdata.length);
406:
407: if (this .lexicalHandler != null) {
408: this .lexicalHandler.endCDATA();
409: }
410: break;
411: case Node.PROCESSING_INSTRUCTION_NODE:
412: this .contentHandler.processingInstruction(_node
413: .getNodeName(), _node.getNodeValue());
414: break;
415: case Node.ENTITY_REFERENCE_NODE:
416: this .contentHandler.skippedEntity(_node.getNodeName());
417: break;
418: case Node.COMMENT_NODE:
419: if (this .lexicalHandler != null) {
420: char[] comment = _node.getNodeValue().toCharArray();
421: this .lexicalHandler.comment(comment, 0, comment.length);
422: }
423: break;
424: case Node.DOCUMENT_TYPE_NODE:
425: //"Document type node can't be translated: "
426: break;
427: case Node.DOCUMENT_FRAGMENT_NODE:
428: //"Document fragment node can't be translated: "
429: break;
430: case Node.NOTATION_NODE:
431: //"Notation node can't be translated: "
432: break;
433: case Node.ENTITY_NODE:
434: //"Entity node can't be translated: "
435: break;
436: case Node.ATTRIBUTE_NODE:
437: //"Standalone attribute node can't be translated: "
438: break;
439: default:
440: //"unknown node type or node type not supported by this method
441: break;
442: }
443: }
444:
445: /**
446: * creates a Attributes object (list of SAX attributes) containing the
447: * attributes of a given DOM element node.
448: * @param elem the element whose attributes shall be converted
449: * @return the SAX attribute list
450: */
451: private Attributes createSAXAttributes(Element elem) {
452: NamedNodeMap domAttributes = elem.getAttributes();
453: AttributesImpl saxAttributes = new AttributesImpl();
454:
455: int length = domAttributes.getLength();
456:
457: for (int i = 0; i < length; i++) {
458:
459: Node node = domAttributes.item(i);
460: String uri = domLevel2 ? node.getNamespaceURI() : "";
461: String qName = node.getNodeName();
462: String lName = domLevel2 ? node.getLocalName() : qName;
463:
464: saxAttributes.addAttribute((uri == null) ? "" : uri,
465: (lName == null) ? qName : lName, qName, "", node
466: .getNodeValue());
467: }
468:
469: return saxAttributes;
470: }
471:
472: // SAX ContentHandler implementation
473:
474: /**
475: * Received notification of the beginning of the document.
476: */
477: public final void startDocument() throws SAXException {
478: if (deep || depth >= 0) {
479: if (debug) {
480: System.out
481: .println("SAXChunkProducer.startDocument()...");
482: }
483:
484: this .contentHandler.startDocument();
485: this .processLevel++;
486: checkChunk();
487: }
488:
489: depth -= deep ? 0 : 1;
490: }
491:
492: /**
493: * Received notification of the end of the document.
494: */
495: public final void endDocument() throws SAXException {
496: depth += deep ? 0 : 1;
497:
498: if (deep || depth >= 0) {
499: if (debug) {
500: System.out.println("SAXChunkProducer.endDocument()...");
501: }
502:
503: this .contentHandler.endDocument();
504: this .processLevel--;
505: checkChunk();
506: }
507: }
508:
509: /**
510: * Receive notification of the start of an element.
511: */
512: public final void startElement(String namespaceURI,
513: String localName, String rawName, Attributes atts)
514: throws SAXException {
515: if (deep || depth >= 0) {
516: if (debug) {
517: System.out
518: .println("SAXChunkProducer.startElement()...");
519: }
520:
521: this .contentHandler.startElement(namespaceURI, localName,
522: rawName, atts);
523: this .processLevel++;
524: checkChunk();
525: }
526:
527: depth -= deep ? 0 : 1;
528: }
529:
530: /**
531: * Receive notification of the end of an element.
532: */
533: public final void endElement(String _namespaceURI,
534: String _localName, String _rawName) throws SAXException {
535: depth = deep ? 0 : 1;
536:
537: if (deep || depth >= 0) {
538: if (debug) {
539: System.out.println("SAXChunkProducer.endElement()...");
540: }
541:
542: this .contentHandler.endElement(_namespaceURI, _localName,
543: _rawName);
544: this .processLevel--;
545: checkChunk();
546: }
547: }
548:
549: /**
550: * Receive notification of character data inside an element.
551: */
552: public final void characters(char[] ch, int start, int length)
553: throws SAXException {
554: if (deep || depth >= 0) {
555: if (debug) {
556: System.out.println("SAXChunkProducer.characters()...");
557: }
558:
559: char[] characters = new char[length];
560: System.arraycopy(ch, start, characters, 0, length);
561:
562: this .contentHandler.characters(characters, 0,
563: characters.length);
564: checkChunk();
565: }
566: }
567:
568: /**
569: * Receive notification of a processing instruction.
570: */
571: public final void processingInstruction(String target, String data)
572: throws SAXException {
573: if (deep || depth >= 0) {
574: if (debug) {
575: System.out.println("SAXChunkProducer.pi ...");
576: }
577:
578: this .contentHandler.processingInstruction(target, data);
579: checkChunk();
580: }
581: }
582:
583: /**
584: * Receive notification of a skipped entity.
585: */
586: public final void skippedEntity(java.lang.String name)
587: throws SAXException {
588: if (deep || depth >= 0) {
589: this .contentHandler.skippedEntity(name);
590: checkChunk();
591: }
592: }
593:
594: /**
595: * Begin the scope of a prefix-URI Namespace mapping.
596: */
597: public final void startPrefixMapping(String prefix, String uri)
598: throws SAXException {
599: if (deep || depth >= 0) {
600: if (debug) {
601: System.out
602: .println("SAXChunkProducer.startPrefixMapping ...");
603: }
604:
605: this .contentHandler.startPrefixMapping(prefix, uri);
606: checkChunk();
607: }
608: }
609:
610: /**
611: * End the scope of a prefix-URI mapping.
612: */
613: public final void endPrefixMapping(String prefix)
614: throws SAXException {
615: if (deep || depth >= 0) {
616: if (debug) {
617: System.out
618: .println("SAXChunkProducer.endPrefixMapping()...");
619: }
620:
621: this .contentHandler.endPrefixMapping(prefix);
622: checkChunk();
623: }
624: }
625:
626: /**
627: * Receive notification of ignorable whitespace in element content.
628: */
629: public final void ignorableWhitespace(char[] ch, int start,
630: int length) throws SAXException {
631: // ignorable whitespaces will be ignored
632: }
633:
634: /**
635: * Receive an object for locating the origin of SAX document events.
636: */
637: public final void setDocumentLocator(Locator locator) {
638: // we don't care about the origin of the document
639: }
640:
641: //
642: // SAX LexicalHandler implemenation
643: //
644:
645: public void comment(char[] ch, int start, int length)
646: throws SAXException {
647: if ((this .lexicalHandler != null) && (deep || depth >= 0)) {
648: this .lexicalHandler.comment(ch, start, length);
649: checkChunk();
650: }
651: }
652:
653: public void startCDATA() throws SAXException {
654: if ((this .lexicalHandler != null) && (deep || depth >= 0)) {
655: this .lexicalHandler.startCDATA();
656: checkChunk();
657: }
658: }
659:
660: public void endCDATA() throws SAXException {
661: if ((this .lexicalHandler != null) && (deep || depth >= 0)) {
662: this .lexicalHandler.endCDATA();
663: checkChunk();
664: }
665: }
666:
667: public void startDTD(String name, String publicId, String systemId)
668: throws SAXException {
669: if ((this .lexicalHandler != null) && (deep || depth >= 0)) {
670: this .lexicalHandler.startDTD(name, publicId, systemId);
671: checkChunk();
672: }
673: }
674:
675: public void endDTD() throws SAXException {
676: if ((this .lexicalHandler != null) && (deep || depth >= 0)) {
677: this .lexicalHandler.endDTD();
678: checkChunk();
679: }
680: }
681:
682: public void startEntity(String name) throws SAXException {
683: if ((this .lexicalHandler != null) && (deep || depth >= 0)) {
684: this .lexicalHandler.startEntity(name);
685: checkChunk();
686: }
687: }
688:
689: public void endEntity(String name) throws SAXException {
690: if ((this .lexicalHandler != null) && (deep || depth >= 0)) {
691: this .lexicalHandler.startEntity(name);
692: checkChunk();
693: }
694: }
695:
696: protected final void checkChunk() {
697: if (this .delegate == null) {
698: return;
699: }
700:
701: try {
702: if ((this .chunkOutput.getState() == ChunkOutputStream.STATE_OVERFLOW)
703: || (this .processLevel == 0)) {
704: this .delegate.processChunk(this );
705: this .chunkOutput.reset();
706: }
707: } catch (Exception e) {
708: e.printStackTrace();
709: throw new RuntimeException(e.toString());
710: }
711: }
712:
713: }
|