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 * $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 an attribute to this element. May only be called before an child
  165     * element is added or this element has been closed. The attribute value is
  166     * the decimal representation of the given int value.
  167     * 
  168     * @param name
  169     *            attribute name
  170     * @param value
  171     *            attribute value
  172     * 
  173     * @return this element
  174     * @throws IOException
  175     *             in case of problems with the writer
  176     */
  177    public XMLElement attr(final String name, final int value)
  178            throws IOException {
  179        return attr(name, String.valueOf(value));
  180    }
  181
  182    /**
  183     * Adds the given text as a child to this node. The text will be quoted.
  184     * 
  185     * @param text
  186     *            text to add
  187     * @return this element
  188     * @throws IOException
  189     *             in case of problems with the writer
  190     */
  191    public XMLElement text(final String text) throws IOException {
  192        if (closed) {
  193            throw new IOException(format("Element %s already closed.", name));
  194        }
  195        finishOpenTag();
  196        if (lastchild != null) {
  197            lastchild.close();
  198        }
  199        quote(text);
  200        return this;
  201    }
  202
  203    /**
  204     * Creates a new child element for this element,
  205     * 
  206     * @param name
  207     *            name of the child element
  208     * @return child element instance
  209     * @throws IOException
  210     *             in case of problems with the writer
  211     */
  212    public XMLElement element(final String name) throws IOException {
  213        final XMLElement element = new XMLElement(writer, name);
  214        addChildElement(element);
  215        return element;
  216    }
  217
  218    /**
  219     * Closes this element if it has not been closed before.
  220     * 
  221     * @throws IOException
  222     *             in case of problems with the writer
  223     */
  224    public void close() throws IOException {
  225        if (!closed) {
  226            if (lastchild != null) {
  227                lastchild.close();
  228            }
  229            if (openTagDone) {
  230                writer.write(LT);
  231                writer.write(SLASH);
  232                writer.write(name);
  233            } else {
  234                writer.write(SLASH);
  235            }
  236            writer.write(GT);
  237            closed = true;
  238            openTagDone = true;
  239        }
  240    }
  241
  242}