
/*
 * Copyright (C) 2012 Archie L. Cobbs. All rights reserved.
 *
 * $Id$
 */

package org.dellroad.stuff.xml;

import javax.xml.namespace.NamespaceContext;
import javax.xml.stream.XMLEventFactory;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLEventWriter;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.XMLEvent;

/**
 * {@link XMLEventWriter} that adds an extra annotation element to an XML document as it is written.
 * The annotation element will be added as the first element inside the top-level document element.
 *
 * <p>
 * This class can be used in combination with {@link AnnotatedXMLEventReader} to transparently annotate XML documents.
 *
 * @see AnnotatedXMLEventReader
 */
public abstract class AnnotatedXMLEventWriter implements XMLEventWriter {

    protected final XMLEventFactory xmlEventFactory = XMLEventFactory.newFactory();

    private final StringBuilder trailingSpace = new StringBuilder();
    private final XMLEventWriter inner;

    // State:
    //  0 = before document element
    //  1 = after document element but before annotation element
    //  2 = after annotation element
    private byte state;

    public AnnotatedXMLEventWriter(XMLEventWriter inner) {
        if (inner == null)
            throw new IllegalArgumentException("null inner");
        this.inner = inner;
    }

    @Override
    public void add(XMLEvent event) throws XMLStreamException {
        switch (this.state) {
        case 0:
            if (event.isStartElement())
                this.state++;
            this.inner.add(event);
            break;
        case 1:
            if (event.isNamespace() || event.isAttribute()) {
                this.inner.add(event);
                break;
            }
            if (event.isCharacters() && event.asCharacters().isWhiteSpace()) {
                this.trailingSpace.append(event.asCharacters().getData());
                this.inner.add(event);
                break;
            }
            this.state++;
            this.addAnnotationElement();
            if (this.trailingSpace.length() > 0)
                this.inner.add(this.xmlEventFactory.createCharacters(this.trailingSpace.toString()));
            this.inner.add(event);
            break;
        case 2:
            this.inner.add(event);
            break;
        default:
            throw new RuntimeException("internal error");
        }
    }

    @Override
    public void add(XMLEventReader reader) throws XMLStreamException {
        if (reader == null)
            throw new XMLStreamException("null reader");
        while (reader.hasNext())
            this.add(reader.nextEvent());
    }

    @Override
    public void flush() throws XMLStreamException {
        this.inner.flush();
    }

    @Override
    public void close() throws XMLStreamException {
        this.inner.close();
    }

    @Override
    public String getPrefix(String uri) throws XMLStreamException {
        return this.inner.getPrefix(uri);
    }

    @Override
    public void setPrefix(String prefix, String uri) throws XMLStreamException {
        this.inner.setPrefix(prefix, uri);
    }

    @Override
    public void setDefaultNamespace(String uri) throws XMLStreamException {
        this.inner.setDefaultNamespace(uri);
    }

    @Override
    public void setNamespaceContext(NamespaceContext context) throws XMLStreamException {
        this.inner.setNamespaceContext(context);
    }

    @Override
    public NamespaceContext getNamespaceContext() {
        return this.inner.getNamespaceContext();
    }

    /**
     * Get the whitespace found between the opening document tag and the first non-space child.
     */
    protected String getTrailingSpace() {
        return this.trailingSpace.toString();
    }

    /**
     * Add the annotation element.
     *
     * <p>
     * This method should {@link #add add()} the {@link javax.xml.stream.events.StartElement} for the annotation element, followed
     * by any nested content, and then lastly the {@link javax.xml.stream.events.EndElement} for the annotation element.
     */
    protected abstract void addAnnotationElement() throws XMLStreamException;
}


@@ -110,53 +110,8 @@
 l;%0A%0A
-import javax.xml.namespace.NamespaceContext;%0A
 impo
@@ -736,22 +736,16 @@
 ter 
-implement
+extend
 s 
-XML
 Even
@@ -751,16 +751,24 @@
 ntWriter
+Delegate
  %7B%0A%0A    
@@ -920,48 +920,8 @@
 r();
-%0A    private final XMLEventWriter inner;
 %0A%0A  
@@ -1166,115 +1166,20 @@
     
-if (inner == null)%0A            throw new IllegalArgumentException(%22null inner%22);%0A        this.inner = inner
+super(inner)
 ;%0A  
@@ -1385,32 +1385,27 @@
             
-this.inn
+sup
 er.add(event
@@ -1516,32 +1516,27 @@
             
-this.inn
+sup
 er.add(event
@@ -1741,32 +1741,27 @@
             
-this.inn
+sup
 er.add(event
@@ -1928,32 +1928,27 @@
             
-this.inn
+sup
 er.add(this.
@@ -2021,32 +2021,27 @@
             
-this.inn
+sup
 er.add(event
@@ -2090,24 +2090,19 @@
         
-this.inn
+sup
 er.add(e
@@ -2475,910 +2475,8 @@
  %7D%0A%0A
-    @Override%0A    public void flush() throws XMLStreamException %7B%0A        this.inner.flush();%0A    %7D%0A%0A    @Override%0A    public void close() throws XMLStreamException %7B%0A        this.inner.close();%0A    %7D%0A%0A    @Override%0A    public String getPrefix(String uri) throws XMLStreamException %7B%0A        return this.inner.getPrefix(uri);%0A    %7D%0A%0A    @Override%0A    public void setPrefix(String prefix, String uri) throws XMLStreamException %7B%0A        this.inner.setPrefix(prefix, uri);%0A    %7D%0A%0A    @Override%0A    public void setDefaultNamespace(String uri) throws XMLStreamException %7B%0A        this.inner.setDefaultNamespace(uri);%0A    %7D%0A%0A    @Override%0A    public void setNamespaceContext(NamespaceContext context) throws XMLStreamException %7B%0A        this.inner.setNamespaceContext(context);%0A    %7D%0A%0A    @Override%0A    public NamespaceContext getNamespaceContext() %7B%0A        return this.inner.getNamespaceContext();%0A    %7D%0A%0A
     

Extend EventWriterDelegate.