# XMLElement.java

    /*******************************************************************************
     * Copyright (c) 2009, 2013 Mountainminds GmbH & Co. KG and Contributors
     * All rights reserved. This program and the accompanying materials
     * are made available under the terms of the Eclipse Public License v1.0
     * which accompanies this distribution, and is available at
     * http://www.eclipse.org/legal/epl-v10.html
     *
     * Contributors:
     *    Marc R. Hoffmann - initial API and implementation
     *
     *******************************************************************************/
    package org.jacoco.report.internal.xml;

    import static java.lang.String.format;

    import java.io.IOException;
    import java.io.Writer;

    /**
     * Simple API to create well formed XML streams. A {@link XMLElement} instance
     * represents a single element in a XML document.
     *
     * @see XMLDocument
     */
    public class XMLElement {

    	private static final char SPACE = ' ';

    	private static final char EQ = '=';

    	private static final char LT = '<';

    	private static final char GT = '>';

    	private static final char QUOT = '"';

    	private static final char AMP = '&';

    	private static final char SLASH = '/';

    	/** Writer for content output */
    	protected final Writer writer;

    	private final String name;

    	private boolean openTagDone;

    	private boolean closed;

    	private XMLElement lastchild;

    	/**
    	 * Creates a new element for a XML document.
    	 *
    	 * @param writer
    	 *            all output will be written directly to this
    	 * @param name
    	 *            element name
    	 */
    	protected XMLElement(final Writer writer, final String name) {
    		this.writer = writer;
    		this.name = name;
    		this.openTagDone = false;
    		this.closed = false;
    		this.lastchild = null;
    	}

    	/**
    	 * Emits the beginning of the open tag. This method has to be called before
    	 * other other methods are called on this element.
    	 *
    	 * @throws IOException
    	 *             in case of problems with the writer
    	 */
    	protected void beginOpenTag() throws IOException {
    		writer.write(LT);
    		writer.write(name);
    	}

    	private void finishOpenTag() throws IOException {
    		if (!openTagDone) {
    			writer.append(GT);
    			openTagDone = true;
    		}
    	}

    	/**
    	 * Adds the given child to this element. This will close all previous child
    	 * elements.
    	 *
    	 * @param child
    	 *            child element to add
    	 * @throws IOException
    	 *             in case of invalid nesting or problems with the writer
    	 */
    	protected void addChildElement(final XMLElement child) throws IOException {
    		if (closed) {
    			throw new IOException(format("Element %s already closed.", name));
    		}
    		finishOpenTag();
    		if (lastchild != null) {
    			lastchild.close();
    		}
    		child.beginOpenTag();
    		lastchild = child;
    	}

    	private void quote(final String text) throws IOException {
    		final int len = text.length();
    		for (int i = 0; i < len; i++) {
    			final char c = text.charAt(i);
    			switch (c) {
    			case LT:
    				writer.write("&lt;");
    				break;
    			case GT:
    				writer.write("&gt;");
    				break;
    			case QUOT:
    				writer.write("&quot;");
    				break;
    			case AMP:
    				writer.write("&amp;");
    				break;
    			default:
    				writer.write(c);
    			}
    		}
    	}

    	/**
    	 * Adds an attribute to this element. May only be called before an child
    	 * element is added or this element has been closed. The attribute value
    	 * will be quoted. If the value is <code>null</code> the attribute will not
    	 * be added.
    	 *
    	 * @param name
    	 *            attribute name
    	 * @param value
    	 *            attribute value or <code>null</code>
    	 *
    	 * @return this element
    	 * @throws IOException
    	 *             in case of problems with the writer
    	 */
    	public XMLElement attr(final String name, final String value)
    			throws IOException {
    		if (value == null) {
    			return this;
    		}
    		if (closed || openTagDone) {
    			throw new IOException(format("Element %s already closed.",
    					this.name));
    		}
    		writer.write(SPACE);
    		writer.write(name);
    		writer.write(EQ);
    		writer.write(QUOT);
    		quote(value);
    		writer.write(QUOT);
    		return this;
    	}

    	/**
    	 * Adds an attribute to this element. May only be called before an child
    	 * element is added or this element has been closed. The attribute value is
    	 * the decimal representation of the given int value.
    	 *
    	 * @param name
    	 *            attribute name
    	 * @param value
    	 *            attribute value
    	 *
    	 * @return this element
    	 * @throws IOException
    	 *             in case of problems with the writer
    	 */
    	public XMLElement attr(final String name, final int value)
    			throws IOException {
    		return attr(name, String.valueOf(value));
    	}

    	/**
    	 * Adds an attribute to this element. May only be called before an child
    	 * element is added or this element has been closed. The attribute value is
    	 * the decimal representation of the given long value.
    	 *
    	 * @param name
    	 *            attribute name
    	 * @param value
    	 *            attribute value
    	 *
    	 * @return this element
    	 * @throws IOException
    	 *             in case of problems with the writer
    	 */
    	public XMLElement attr(final String name, final long value)
    			throws IOException {
    		return attr(name, String.valueOf(value));
    	}

    	/**
    	 * Adds the given text as a child to this node. The text will be quoted.
    	 *
    	 * @param text
    	 *            text to add
    	 * @return this element
    	 * @throws IOException
    	 *             in case of problems with the writer
    	 */
    	public XMLElement text(final String text) throws IOException {
    		if (closed) {
    			throw new IOException(format("Element %s already closed.", name));
    		}
    		finishOpenTag();
    		if (lastchild != null) {
    			lastchild.close();
    		}
    		quote(text);
    		return this;
    	}

    	/**
    	 * Creates a new child element for this element,
    	 *
    	 * @param name
    	 *            name of the child element
    	 * @return child element instance
    	 * @throws IOException
    	 *             in case of problems with the writer
    	 */
    	public XMLElement element(final String name) throws IOException {
    		final XMLElement element = new XMLElement(writer, name);
    		addChildElement(element);
    		return element;
    	}

    	/**
    	 * Closes this element if it has not been closed before.
    	 *
    	 * @throws IOException
    	 *             in case of problems with the writer
    	 */
    	public void close() throws IOException {
    		if (!closed) {
    			if (lastchild != null) {
    				lastchild.close();
    			}
    			if (openTagDone) {
    				writer.write(LT);
    				writer.write(SLASH);
    				writer.write(name);
    			} else {
    				writer.write(SLASH);
    			}
    			writer.write(GT);
    			closed = true;
    			openTagDone = true;
    		}
    	}

    }