XMLElement.java

    1/*******************************************************************************
    2 * Copyright (c) 2009 Mountainminds GmbH & Co. KG and others
    3 * All rights reserved. This program and the accompanying materials
    4 * are made available under the terms of the Eclipse Public License v1.0
    5 * which accompanies this distribution, and is available at
    6 * http://www.eclipse.org/legal/epl-v10.html
    7 *
    8 * Contributors:
    9 *    Marc R. Hoffmann - initial API and implementation
   10 *    
   11 * $Id: $
   12 *******************************************************************************/
   13package org.jacoco.report.xml;
   14
   15import static java.lang.String.format;
   16
   17import java.io.IOException;
   18import java.io.Writer;
   19
   20/**
   21 * Simple API to create well formed XML streams. A {@link XMLElement} instance
   22 * represents a single element in a XML document.
   23 * 
   24 * @see XMLDocument
   25 * @author Marc R. Hoffmann
   26 * @version $Revision: $
   27 */
   28public class XMLElement {
   29
   30    private static final char SPACE = ' ';
   31
   32    private static final char EQ = '=';
   33
   34    private static final char LT = '<';
   35
   36    private static final char GT = '>';
   37
   38    private static final char QUOT = '"';
   39
   40    private static final char AMP = '&';
   41
   42    private static final char SLASH = '/';
   43
   44    /** Writer for content output */
   45    protected final Writer writer;
   46
   47    private final String name;
   48
   49    private boolean openTagDone;
   50
   51    private boolean closed;
   52
   53    private XMLElement lastchild;
   54
   55    /**
   56     * Creates a new element for a XML document.
   57     * 
   58     * @param writer
   59     *            all output will be written directly to this
   60     * @param name
   61     *            element name
   62     */
   63    protected XMLElement(final Writer writer, final String name) {
   64        this.writer = writer;
   65        this.name = name;
   66        this.openTagDone = false;
   67        this.closed = false;
   68        this.lastchild = null;
   69    }
   70
   71    /**
   72     * Emits the beginning of the open tag. This method has to be called before
   73     * other other methods are called on this element.
   74     * 
   75     * @throws IOException
   76     *             in case of problems with the writer
   77     */
   78    protected void beginOpenTag() throws IOException {
   79        writer.write(LT);
   80        writer.write(name);
   81    }
   82
   83    private void finishOpenTag() throws IOException {
   84        if (!openTagDone) {
   85            writer.append(GT);
   86            openTagDone = true;
   87        }
   88    }
   89
   90    /**
   91     * Adds the given child to this element. This will close all previous child
   92     * elements.
   93     * 
   94     * @param child
   95     *            child element to add
   96     * @throws IOException
   97     *             in case of invalid nesting or problems with the writer
   98     */
   99    protected void addChildElement(final XMLElement child) throws IOException {
  100        if (closed) {
  101            throw new IOException(format("Element %s already closed.", name));
  102        }
  103        finishOpenTag();
  104        if (lastchild != null) {
  105            lastchild.close();
  106        }
  107        child.beginOpenTag();
  108        lastchild = child;
  109    }
  110
  111    private void quote(final String text) throws IOException {
  112        final int len = text.length();
  113        for (int i = 0; i < len; i++) {
  114            final char c = text.charAt(i);
  115            switch (c) {
  116            case LT:
  117                writer.write("&lt;");
  118                break;
  119            case GT:
  120                writer.write("&gt;");
  121                break;
  122            case QUOT:
  123                writer.write("&quot;");
  124                break;
  125            case AMP:
  126                writer.write("&amp;");
  127                break;
  128            default:
  129                writer.write(c);
  130            }
  131        }
  132    }
  133
  134    /**
  135     * Adds an attribute to this element. May only be called before an child
  136     * element is added or this element has been closed. The attribute value
  137     * will be quoted.
  138     * 
  139     * @param name
  140     *            attribute name
  141     * @param value
  142     *            attribute value
  143     * 
  144     * @return this element
  145     * @throws IOException
  146     *             in case of problems with the writer
  147     */
  148    public XMLElement attr(final String name, final String value)
  149            throws IOException {
  150        if (closed || openTagDone) {
  151            throw new IOException(format("Element %s already closed.",
  152                    this.name));
  153        }
  154        writer.write(SPACE);
  155        writer.write(name);
  156        writer.write(EQ);
  157        writer.write(QUOT);
  158        quote(value);
  159        writer.write(QUOT);
  160        return this;
  161    }
  162
  163    /**
  164     * Adds the given text as a child to this node. The text will be quoted.
  165     * 
  166     * @param text
  167     *            text to add
  168     * @return this element
  169     * @throws IOException
  170     *             in case of problems with the writer
  171     */
  172    public XMLElement text(final String text) throws IOException {
  173        if (closed) {
  174            throw new IOException(format("Element %s already closed.", name));
  175        }
  176        finishOpenTag();
  177        if (lastchild != null) {
  178            lastchild.close();
  179        }
  180        quote(text);
  181        return this;
  182    }
  183
  184    /**
  185     * Creates a new child element for this element,
  186     * 
  187     * @param name
  188     *            name of the child element
  189     * @return child element instance
  190     * @throws IOException
  191     *             in case of problems with the writer
  192     */
  193    public XMLElement element(final String name) throws IOException {
  194        final XMLElement element = new XMLElement(writer, name);
  195        addChildElement(element);
  196        return element;
  197    }
  198
  199    /**
  200     * Closes this element if it has not been closed before.
  201     * 
  202     * @throws IOException
  203     *             in case of problems with the writer
  204     */
  205    public void close() throws IOException {
  206        if (!closed) {
  207            if (lastchild != null) {
  208                lastchild.close();
  209            }
  210            if (openTagDone) {
  211                writer.write(LT);
  212                writer.write(SLASH);
  213                writer.write(name);
  214            } else {
  215                writer.write(SLASH);
  216            }
  217            writer.write(GT);
  218            closed = true;
  219            openTagDone = true;
  220        }
  221    }
  222
  223}