View Javadoc

1   /*
2    * Copyright 2001-2005 (C) MetaStuff, Ltd. All Rights Reserved.
3    *
4    * This software is open source.
5    * See the bottom of this file for the licence.
6    */
7   
8   package org.dom4j.io;
9   
10  import java.io.Externalizable;
11  import java.io.IOException;
12  import java.io.ObjectInput;
13  import java.io.ObjectOutput;
14  import java.util.ArrayList;
15  import java.util.HashMap;
16  import java.util.Iterator;
17  import java.util.List;
18  import java.util.Map;
19  
20  import org.dom4j.Namespace;
21  import org.dom4j.QName;
22  import org.xml.sax.Attributes;
23  import org.xml.sax.ContentHandler;
24  import org.xml.sax.DTDHandler;
25  import org.xml.sax.SAXException;
26  import org.xml.sax.ext.DeclHandler;
27  import org.xml.sax.ext.LexicalHandler;
28  import org.xml.sax.helpers.AttributesImpl;
29  import org.xml.sax.helpers.DefaultHandler;
30  
31  /***
32   * <p>
33   * Records SAX events such that they may be "replayed" at a later time. Provides
34   * an alternative serialization approach when externalizing a DOM4J document.
35   * Rather than serializing a document as text and re-parsing, the sax events may
36   * be serialized instead.
37   * </p>
38   * Example usage:
39   * 
40   * <pre>
41   * 
42   *  
43   *  
44   *         SAXEventRecorder recorder = new SAXEventRecorder();
45   *         SAXWriter saxWriter = new SAXWriter(recorder, recorder);
46   *         saxWriter.write(document);
47   *         out.writeObject(recorder);
48   *         ...
49   *         SAXEventRecorder recorder = (SAXEventRecorder)in.readObject();
50   *         SAXContentHandler saxContentHandler = new SAXContentHandler();
51   *         recorder.replay(saxContentHandler);
52   *         Document document = saxContentHandler.getDocument();
53   *  
54   *   
55   *  
56   * </pre>
57   * 
58   * @author Todd Wolff (Bluestem Software)
59   */
60  public class SAXEventRecorder extends DefaultHandler implements LexicalHandler,
61          DeclHandler, DTDHandler, Externalizable {
62      public static final long serialVersionUID = 1;
63  
64      private static final byte STRING = 0;
65  
66      private static final byte OBJECT = 1;
67  
68      private static final byte NULL = 2;
69  
70      private List events = new ArrayList();
71  
72      private Map prefixMappings = new HashMap();
73  
74      private static final String XMLNS = "xmlns";
75  
76      private static final String EMPTY_STRING = "";
77  
78      public SAXEventRecorder() {
79      }
80  
81      public void replay(ContentHandler handler) throws SAXException {
82          SAXEvent saxEvent;
83          Iterator itr = events.iterator();
84  
85          while (itr.hasNext()) {
86              saxEvent = (SAXEvent) itr.next();
87  
88              switch (saxEvent.event) {
89                  // replay to ContentHandler
90                  case SAXEvent.PROCESSING_INSTRUCTION:
91                      handler.processingInstruction((String) saxEvent.getParm(0),
92                              (String) saxEvent.getParm(1));
93  
94                      break;
95  
96                  case SAXEvent.START_PREFIX_MAPPING:
97                      handler.startPrefixMapping((String) saxEvent.getParm(0),
98                              (String) saxEvent.getParm(1));
99  
100                     break;
101 
102                 case SAXEvent.END_PREFIX_MAPPING:
103                     handler.endPrefixMapping((String) saxEvent.getParm(0));
104 
105                     break;
106 
107                 case SAXEvent.START_DOCUMENT:
108                     handler.startDocument();
109 
110                     break;
111 
112                 case SAXEvent.END_DOCUMENT:
113                     handler.endDocument();
114 
115                     break;
116 
117                 case SAXEvent.START_ELEMENT:
118 
119                     AttributesImpl attributes = new AttributesImpl();
120                     List attParmList = (List) saxEvent.getParm(3);
121 
122                     if (attParmList != null) {
123                         Iterator attsItr = attParmList.iterator();
124 
125                         while (attsItr.hasNext()) {
126                             String[] attParms = (String[]) attsItr.next();
127                             attributes.addAttribute(attParms[0], attParms[1],
128                                     attParms[2], attParms[3], attParms[4]);
129                         }
130                     }
131 
132                     handler.startElement((String) saxEvent.getParm(0),
133                             (String) saxEvent.getParm(1), (String) saxEvent
134                                     .getParm(2), attributes);
135 
136                     break;
137 
138                 case SAXEvent.END_ELEMENT:
139                     handler.endElement((String) saxEvent.getParm(0),
140                             (String) saxEvent.getParm(1), (String) saxEvent
141                                     .getParm(2));
142 
143                     break;
144 
145                 case SAXEvent.CHARACTERS:
146 
147                     char[] chars = (char[]) saxEvent.getParm(0);
148                     int start = ((Integer) saxEvent.getParm(1)).intValue();
149                     int end = ((Integer) saxEvent.getParm(2)).intValue();
150                     handler.characters(chars, start, end);
151 
152                     break;
153 
154                 // replay to LexicalHandler
155                 case SAXEvent.START_DTD:
156                     ((LexicalHandler) handler).startDTD((String) saxEvent
157                             .getParm(0), (String) saxEvent.getParm(1),
158                             (String) saxEvent.getParm(2));
159 
160                     break;
161 
162                 case SAXEvent.END_DTD:
163                     ((LexicalHandler) handler).endDTD();
164 
165                     break;
166 
167                 case SAXEvent.START_ENTITY:
168                     ((LexicalHandler) handler).startEntity((String) saxEvent
169                             .getParm(0));
170 
171                     break;
172 
173                 case SAXEvent.END_ENTITY:
174                     ((LexicalHandler) handler).endEntity((String) saxEvent
175                             .getParm(0));
176 
177                     break;
178 
179                 case SAXEvent.START_CDATA:
180                     ((LexicalHandler) handler).startCDATA();
181 
182                     break;
183 
184                 case SAXEvent.END_CDATA:
185                     ((LexicalHandler) handler).endCDATA();
186 
187                     break;
188 
189                 case SAXEvent.COMMENT:
190 
191                     char[] cchars = (char[]) saxEvent.getParm(0);
192                     int cstart = ((Integer) saxEvent.getParm(1)).intValue();
193                     int cend = ((Integer) saxEvent.getParm(2)).intValue();
194                     ((LexicalHandler) handler).comment(cchars, cstart, cend);
195 
196                     break;
197 
198                 // replay to DeclHandler
199                 case SAXEvent.ELEMENT_DECL:
200                     ((DeclHandler) handler).elementDecl((String) saxEvent
201                             .getParm(0), (String) saxEvent.getParm(1));
202 
203                     break;
204 
205                 case SAXEvent.ATTRIBUTE_DECL:
206                     ((DeclHandler) handler).attributeDecl((String) saxEvent
207                             .getParm(0), (String) saxEvent.getParm(1),
208                             (String) saxEvent.getParm(2), (String) saxEvent
209                                     .getParm(3), (String) saxEvent.getParm(4));
210 
211                     break;
212 
213                 case SAXEvent.INTERNAL_ENTITY_DECL:
214                     ((DeclHandler) handler).internalEntityDecl(
215                             (String) saxEvent.getParm(0), (String) saxEvent
216                                     .getParm(1));
217 
218                     break;
219 
220                 case SAXEvent.EXTERNAL_ENTITY_DECL:
221                     ((DeclHandler) handler).externalEntityDecl(
222                             (String) saxEvent.getParm(0), (String) saxEvent
223                                     .getParm(1), (String) saxEvent.getParm(2));
224 
225                     break;
226 
227                 default:
228                     throw new SAXException("Unrecognized event: "
229                             + saxEvent.event);
230             }
231         }
232     }
233 
234     // ContentHandler interface
235     // -------------------------------------------------------------------------
236     public void processingInstruction(String target, String data)
237             throws SAXException {
238         SAXEvent saxEvent = new SAXEvent(SAXEvent.PROCESSING_INSTRUCTION);
239         saxEvent.addParm(target);
240         saxEvent.addParm(data);
241         events.add(saxEvent);
242     }
243 
244     public void startPrefixMapping(String prefix, String uri)
245             throws SAXException {
246         SAXEvent saxEvent = new SAXEvent(SAXEvent.START_PREFIX_MAPPING);
247         saxEvent.addParm(prefix);
248         saxEvent.addParm(uri);
249         events.add(saxEvent);
250     }
251 
252     public void endPrefixMapping(String prefix) throws SAXException {
253         SAXEvent saxEvent = new SAXEvent(SAXEvent.END_PREFIX_MAPPING);
254         saxEvent.addParm(prefix);
255         events.add(saxEvent);
256     }
257 
258     public void startDocument() throws SAXException {
259         SAXEvent saxEvent = new SAXEvent(SAXEvent.START_DOCUMENT);
260         events.add(saxEvent);
261     }
262 
263     public void endDocument() throws SAXException {
264         SAXEvent saxEvent = new SAXEvent(SAXEvent.END_DOCUMENT);
265         events.add(saxEvent);
266     }
267 
268     public void startElement(String namespaceURI, String localName,
269             String qualifiedName, Attributes attributes) throws SAXException {
270         SAXEvent saxEvent = new SAXEvent(SAXEvent.START_ELEMENT);
271         saxEvent.addParm(namespaceURI);
272         saxEvent.addParm(localName);
273         saxEvent.addParm(qualifiedName);
274 
275         QName qName = null;
276         if (namespaceURI != null) {
277             qName = new QName(localName, Namespace.get(namespaceURI));
278         } else {
279             qName = new QName(localName);
280         }
281 
282         if ((attributes != null) && (attributes.getLength() > 0)) {
283             List attParmList = new ArrayList(attributes.getLength());
284             String[] attParms = null;
285 
286             for (int i = 0; i < attributes.getLength(); i++) {
287 
288                 String attLocalName = attributes.getLocalName(i);
289 
290                 if (attLocalName.startsWith(XMLNS)) {
291 
292                     // if SAXWriter is writing a DOMDocument, namespace
293                     // decls are treated as attributes. record a start
294                     // prefix mapping event
295                     String prefix = null;
296                     if (attLocalName.length() > 5) {
297                         prefix = attLocalName.substring(6);
298                     } else {
299                         prefix = EMPTY_STRING;
300                     }
301 
302                     SAXEvent prefixEvent = new SAXEvent(
303                             SAXEvent.START_PREFIX_MAPPING);
304                     prefixEvent.addParm(prefix);
305                     prefixEvent.addParm(attributes.getValue(i));
306                     events.add(prefixEvent);
307 
308                     // 'register' the prefix so that we can generate
309                     // an end prefix mapping event within endElement
310                     List prefixes = (List) prefixMappings.get(qName);
311                     if (prefixes == null) {
312                         prefixes = new ArrayList();
313                         prefixMappings.put(qName, prefixes);
314                     }
315                     prefixes.add(prefix);
316 
317                 } else {
318 
319                     attParms = new String[5];
320                     attParms[0] = attributes.getURI(i);
321                     attParms[1] = attLocalName;
322                     attParms[2] = attributes.getQName(i);
323                     attParms[3] = attributes.getType(i);
324                     attParms[4] = attributes.getValue(i);
325                     attParmList.add(attParms);
326 
327                 }
328 
329             }
330 
331             saxEvent.addParm(attParmList);
332         }
333 
334         events.add(saxEvent);
335     }
336 
337     public void endElement(String namespaceURI, String localName, String qName)
338             throws SAXException {
339 
340         SAXEvent saxEvent = new SAXEvent(SAXEvent.END_ELEMENT);
341         saxEvent.addParm(namespaceURI);
342         saxEvent.addParm(localName);
343         saxEvent.addParm(qName);
344         events.add(saxEvent);
345 
346         // check to see if a we issued a start prefix mapping event
347         // for DOMDocument namespace decls
348 
349         QName elementName = null;
350         if (namespaceURI != null) {
351             elementName = new QName(localName, Namespace.get(namespaceURI));
352         } else {
353             elementName = new QName(localName);
354         }
355 
356         List prefixes = (List) prefixMappings.get(elementName);
357         if (prefixes != null) {
358             Iterator itr = prefixes.iterator();
359             while (itr.hasNext()) {
360                 SAXEvent prefixEvent = 
361                         new SAXEvent(SAXEvent.END_PREFIX_MAPPING);
362                 prefixEvent.addParm(itr.next());
363                 events.add(prefixEvent);
364             }
365         }
366 
367     }
368 
369     public void characters(char[] ch, int start, int end) throws SAXException {
370         SAXEvent saxEvent = new SAXEvent(SAXEvent.CHARACTERS);
371         saxEvent.addParm(ch);
372         saxEvent.addParm(new Integer(start));
373         saxEvent.addParm(new Integer(end));
374         events.add(saxEvent);
375     }
376 
377     // LexicalHandler interface
378     // -------------------------------------------------------------------------
379     public void startDTD(String name, String publicId, String systemId)
380             throws SAXException {
381         SAXEvent saxEvent = new SAXEvent(SAXEvent.START_DTD);
382         saxEvent.addParm(name);
383         saxEvent.addParm(publicId);
384         saxEvent.addParm(systemId);
385         events.add(saxEvent);
386     }
387 
388     public void endDTD() throws SAXException {
389         SAXEvent saxEvent = new SAXEvent(SAXEvent.END_DTD);
390         events.add(saxEvent);
391     }
392 
393     public void startEntity(String name) throws SAXException {
394         SAXEvent saxEvent = new SAXEvent(SAXEvent.START_ENTITY);
395         saxEvent.addParm(name);
396         events.add(saxEvent);
397     }
398 
399     public void endEntity(String name) throws SAXException {
400         SAXEvent saxEvent = new SAXEvent(SAXEvent.END_ENTITY);
401         saxEvent.addParm(name);
402         events.add(saxEvent);
403     }
404 
405     public void startCDATA() throws SAXException {
406         SAXEvent saxEvent = new SAXEvent(SAXEvent.START_CDATA);
407         events.add(saxEvent);
408     }
409 
410     public void endCDATA() throws SAXException {
411         SAXEvent saxEvent = new SAXEvent(SAXEvent.END_CDATA);
412         events.add(saxEvent);
413     }
414 
415     public void comment(char[] ch, int start, int end) throws SAXException {
416         SAXEvent saxEvent = new SAXEvent(SAXEvent.COMMENT);
417         saxEvent.addParm(ch);
418         saxEvent.addParm(new Integer(start));
419         saxEvent.addParm(new Integer(end));
420         events.add(saxEvent);
421     }
422 
423     // DeclHandler interface
424     // -------------------------------------------------------------------------
425     public void elementDecl(String name, String model) throws SAXException {
426         SAXEvent saxEvent = new SAXEvent(SAXEvent.ELEMENT_DECL);
427         saxEvent.addParm(name);
428         saxEvent.addParm(model);
429         events.add(saxEvent);
430     }
431 
432     public void attributeDecl(String eName, String aName, String type,
433             String valueDefault, String value) throws SAXException {
434         SAXEvent saxEvent = new SAXEvent(SAXEvent.ATTRIBUTE_DECL);
435         saxEvent.addParm(eName);
436         saxEvent.addParm(aName);
437         saxEvent.addParm(type);
438         saxEvent.addParm(valueDefault);
439         saxEvent.addParm(value);
440         events.add(saxEvent);
441     }
442 
443     public void internalEntityDecl(String name, String value)
444             throws SAXException {
445         SAXEvent saxEvent = new SAXEvent(SAXEvent.INTERNAL_ENTITY_DECL);
446         saxEvent.addParm(name);
447         saxEvent.addParm(value);
448         events.add(saxEvent);
449     }
450 
451     public void externalEntityDecl(String name, String publicId, String sysId)
452             throws SAXException {
453         SAXEvent saxEvent = new SAXEvent(SAXEvent.EXTERNAL_ENTITY_DECL);
454         saxEvent.addParm(name);
455         saxEvent.addParm(publicId);
456         saxEvent.addParm(sysId);
457         events.add(saxEvent);
458     }
459 
460     public void writeExternal(ObjectOutput out) throws IOException {
461         if (events == null) {
462             out.writeByte(NULL);
463         } else {
464             out.writeByte(OBJECT);
465             out.writeObject(events);
466         }
467     }
468 
469     public void readExternal(ObjectInput in) throws ClassNotFoundException,
470             IOException {
471         if (in.readByte() != NULL) {
472             events = (List) in.readObject();
473         }
474     }
475 
476     // SAXEvent inner class
477     // -------------------------------------------------------------------------
478     static class SAXEvent implements Externalizable {
479         public static final long serialVersionUID = 1;
480 
481         static final byte PROCESSING_INSTRUCTION = 1;
482 
483         static final byte START_PREFIX_MAPPING = 2;
484 
485         static final byte END_PREFIX_MAPPING = 3;
486 
487         static final byte START_DOCUMENT = 4;
488 
489         static final byte END_DOCUMENT = 5;
490 
491         static final byte START_ELEMENT = 6;
492 
493         static final byte END_ELEMENT = 7;
494 
495         static final byte CHARACTERS = 8;
496 
497         static final byte START_DTD = 9;
498 
499         static final byte END_DTD = 10;
500 
501         static final byte START_ENTITY = 11;
502 
503         static final byte END_ENTITY = 12;
504 
505         static final byte START_CDATA = 13;
506 
507         static final byte END_CDATA = 14;
508 
509         static final byte COMMENT = 15;
510 
511         static final byte ELEMENT_DECL = 16;
512 
513         static final byte ATTRIBUTE_DECL = 17;
514 
515         static final byte INTERNAL_ENTITY_DECL = 18;
516 
517         static final byte EXTERNAL_ENTITY_DECL = 19;
518 
519         protected byte event;
520 
521         protected List parms;
522 
523         public SAXEvent() {
524         }
525 
526         SAXEvent(byte event) {
527             this.event = event;
528         }
529 
530         void addParm(Object parm) {
531             if (parms == null) {
532                 parms = new ArrayList(3);
533             }
534 
535             parms.add(parm);
536         }
537 
538         Object getParm(int index) {
539             if ((parms != null) && (index < parms.size())) {
540                 return parms.get(index);
541             } else {
542                 return null;
543             }
544         }
545 
546         public void writeExternal(ObjectOutput out) throws IOException {
547             out.writeByte(event);
548 
549             if (parms == null) {
550                 out.writeByte(NULL);
551             } else {
552                 out.writeByte(OBJECT);
553                 out.writeObject(parms);
554             }
555         }
556 
557         public void readExternal(ObjectInput in) throws ClassNotFoundException,
558                 IOException {
559             event = in.readByte();
560 
561             if (in.readByte() != NULL) {
562                 parms = (List) in.readObject();
563             }
564         }
565     }
566 }
567 
568 /*
569  * Redistribution and use of this software and associated documentation
570  * ("Software"), with or without modification, are permitted provided that the
571  * following conditions are met:
572  * 
573  * 1. Redistributions of source code must retain copyright statements and
574  * notices. Redistributions must also contain a copy of this document.
575  * 
576  * 2. Redistributions in binary form must reproduce the above copyright notice,
577  * this list of conditions and the following disclaimer in the documentation
578  * and/or other materials provided with the distribution.
579  * 
580  * 3. The name "DOM4J" must not be used to endorse or promote products derived
581  * from this Software without prior written permission of MetaStuff, Ltd. For
582  * written permission, please contact dom4j-info@metastuff.com.
583  * 
584  * 4. Products derived from this Software may not be called "DOM4J" nor may
585  * "DOM4J" appear in their names without prior written permission of MetaStuff,
586  * Ltd. DOM4J is a registered trademark of MetaStuff, Ltd.
587  * 
588  * 5. Due credit should be given to the DOM4J Project - http://www.dom4j.org
589  * 
590  * THIS SOFTWARE IS PROVIDED BY METASTUFF, LTD. AND CONTRIBUTORS ``AS IS'' AND
591  * ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
592  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
593  * ARE DISCLAIMED. IN NO EVENT SHALL METASTUFF, LTD. OR ITS CONTRIBUTORS BE
594  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
595  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
596  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
597  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
598  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
599  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
600  * POSSIBILITY OF SUCH DAMAGE.
601  * 
602  * Copyright 2001-2005 (C) MetaStuff, Ltd. All Rights Reserved.
603  */