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  */