XMLElement.java

    1/*******************************************************************************
    2 * Copyright (c) 2009, 2010 Mountainminds GmbH & Co. KG and Contributors
    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 *******************************************************************************/
   12package org.jacoco.report.xml;
   13
   14import static java.lang.String.format;
   15
   16import java.io.IOException;
   17import java.io.Writer;
   18
   19/**
   20 * Simple API to create well formed XML streams. A {@link XMLElement} instance
   21 * represents a single element in a XML document.
   22 * 
   23 * @see XMLDocument
   24 * @author Marc R. Hoffmann
   25 * @version 0.4.1.20101007204400
   26 */
   27public class XMLElement {
   28
   29    private static final char SPACE = ' ';
   30
   31    private static final char EQ = '=';
   32
   33    private static final char LT = '<';
   34
   35    private static final char GT = '>';
   36
   37    private static final char QUOT = '"';
   38
   39    private static final char AMP = '&';
   40
   41    private static final char SLASH = '/';
   42
   43    /** Writer for content output */
   44    protected final Writer writer;
   45
   46    private final String name;
   47
   48    private boolean openTagDone;
   49
   50    private boolean closed;
   51
   52    private XMLElement lastchild;
   53
   54    /**
   55     * Creates a new element for a XML document.
   56     * 
   57     * @param writer
   58     *            all output will be written directly to this
   59     * @param name
   60     *            element name
   61     */
   62    protected XMLElement(final Writer writer, final String name) {
   63        this.writer = writer;
   64        this.name = name;
   65        this.openTagDone = false;
   66        this.closed = false;
   67        this.lastchild = null;
   68    }
   69
   70    /**
   71     * Emits the beginning of the open tag. This method has to be called before
   72     * other other methods are called on this element.
   73     * 
   74     * @throws IOException
   75     *             in case of problems with the writer
   76     */
   77    protected void beginOpenTag() throws IOException {
   78        writer.write(LT);
   79        writer.write(name);
   80    }
   81
   82    private void finishOpenTag() throws IOException {
   83        if (!openTagDone) {
   84            writer.append(GT);
   85            openTagDone = true;
   86        }
   87    }
   88
   89    /**
   90     * Adds the given child to this element. This will close all previous child
   91     * elements.
   92     * 
   93     * @param child
   94     *            child element to add
   95     * @throws IOException
   96     *             in case of invalid nesting or problems with the writer
   97     */
   98    protected void addChildElement(final XMLElement child) throws IOException {
   99        if (closed) {
  100            throw new IOException(format("Element %s already closed.", name));
  101        }
  102        finishOpenTag();
  103        if (lastchild != null) {
  104            lastchild.close();
  105        }
  106        child.beginOpenTag();
  107        lastchild = child;
  108    }
  109
  110    private void quote(final String text) throws IOException {
  111        final int len = text.length();
  112        for (int i = 0; i < len; i++) {
  113            final char c = text.charAt(i);
  114            switch (c) {
  115            case LT:
  116                writer.write("&lt;");
  117                break;
  118            case GT:
  119                writer.write("&gt;");
  120                break;
  121            case QUOT:
  122                writer.write("&quot;");
  123                break;
  124            case AMP:
  125                writer.write("&amp;");
  126                break;
  127            default:
  128                writer.write(c);
  129            }
  130        }
  131    }
  132
  133    /**
  134     * Adds an attribute to this element. May only be called before an child
  135     * element is added or this element has been closed. The attribute value
  136     * will be quoted. If the value is <code>null</code> the attribute will not
  137     * be added.
  138     * 
  139     * @param name
  140     *            attribute name
  141     * @param value
  142     *            attribute value or <code>null</code>
  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 (value == null) {
  151            return this;
  152        }
  153        if (closed || openTagDone) {
  154            throw new IOException(format("Element %s already closed.",
  155                    this.name));
  156        }
  157        writer.write(SPACE);
  158        writer.write(name);
  159        writer.write(EQ);
  160        writer.write(QUOT);
  161        quote(value);
  162        writer.write(QUOT);
  163        return this;
  164    }
  165
  166    /**
  167     * Adds an attribute to this element. May only be called before an child
  168     * element is added or this element has been closed. The attribute value is
  169     * the decimal representation of the given int value.
  170     * 
  171     * @param name
  172     *            attribute name
  173     * @param value
  174     *            attribute value
  175     * 
  176     * @return this element
  177     * @throws IOException
  178     *             in case of problems with the writer
  179     */
  180    public XMLElement attr(final String name, final int value)
  181            throws IOException {
  182        return attr(name, String.valueOf(value));
  183    }
  184
  185    /**
  186     * Adds an attribute to this element. May only be called before an child
  187     * element is added or this element has been closed. The attribute value is
  188     * the decimal representation of the given long value.
  189     * 
  190     * @param name
  191     *            attribute name
  192     * @param value
  193     *            attribute value
  194     * 
  195     * @return this element
  196     * @throws IOException
  197     *             in case of problems with the writer
  198     */
  199    public XMLElement attr(final String name, final long value)
  200            throws IOException {
  201        return attr(name, String.valueOf(value));
  202    }
  203
  204    /**
  205     * Adds the given text as a child to this node. The text will be quoted.
  206     * 
  207     * @param text
  208     *            text to add
  209     * @return this element
  210     * @throws IOException
  211     *             in case of problems with the writer
  212     */
  213    public XMLElement text(final String text) throws IOException {
  214        if (closed) {
  215            throw new IOException(format("Element %s already closed.", name));
  216        }
  217        finishOpenTag();
  218        if (lastchild != null) {
  219            lastchild.close();
  220        }
  221        quote(text);
  222        return this;
  223    }
  224
  225    /**
  226     * Creates a new child element for this element,
  227     * 
  228     * @param name
  229     *            name of the child element
  230     * @return child element instance
  231     * @throws IOException
  232     *             in case of problems with the writer
  233     */
  234    public XMLElement element(final String name) throws IOException {
  235        final XMLElement element = new XMLElement(writer, name);
  236        addChildElement(element);
  237        return element;
  238    }
  239
  240    /**
  241     * Closes this element if it has not been closed before.
  242     * 
  243     * @throws IOException
  244     *             in case of problems with the writer
  245     */
  246    public void close() throws IOException {
  247        if (!closed) {
  248            if (lastchild != null) {
  249                lastchild.close();
  250            }
  251            if (openTagDone) {
  252                writer.write(LT);
  253                writer.write(SLASH);
  254                writer.write(name);
  255            } else {
  256                writer.write(SLASH);
  257            }
  258            writer.write(GT);
  259            closed = true;
  260            openTagDone = true;
  261        }
  262    }
  263
  264}