All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.permazen.util.AbstractXMLStreaming Maven / Gradle / Ivy

There is a newer version: 5.1.0
Show newest version

/*
 * Copyright (C) 2015 Archie L. Cobbs. All rights reserved.
 */

package io.permazen.util;

import java.util.Arrays;

import javax.xml.namespace.QName;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamWriter;

/**
 * Support superclass for classes that serialize and deserialize via XML.
 */
public abstract class AbstractXMLStreaming {

    protected AbstractXMLStreaming() {
    }

    /**
     * Scan forward until we see an opening or closing tag.
     * If opening tag is seen, it must match one of {@code names} and then we return true, if not or if {@code names}
     * is empty throw an exception; if a closing tag is seen, return false if {@code closingOK}, else throw exception.
     *
     * @param reader XML input
     * @param closingOK true if a closing tag is OK, otherwise false
     * @param names expected opening tag, or null if we expected a closing tag
     * @return true if matching opening tag seen, false otherwise
     * @throws XMLStreamException if something unexpected is encountered
     */
    protected boolean expect(XMLStreamReader reader, boolean closingOK, QName... names) throws XMLStreamException {
        while (true) {
            if (!reader.hasNext())
                throw new XMLStreamException("unexpected end of input", reader.getLocation());
            final int eventType = reader.next();
            if (eventType == XMLStreamConstants.START_ELEMENT)
                break;
            if (eventType == XMLStreamConstants.END_ELEMENT) {
                if (!closingOK) {
                    throw new XMLStreamException("expected " + this.description(names) + " but found closing <"
                      + reader.getName() + "> tag instead", reader.getLocation());
                }
                return false;
            }
        }
        if (!Arrays.asList(names).contains(reader.getName())) {
            throw new XMLStreamException("expected " + this.description(names)
              + " but found <" + reader.getName() + "> instead", reader.getLocation());
        }
        return true;
    }

    /**
     * Skip forward until either the next opening tag is reached, or the currently open tag is closed.
     *
     * @param reader XML input
     * @return the XML opening tag found, or null if a closing tag was seen first
     * @throws XMLStreamException if no opening tag is found before the current tag closes
     * @throws XMLStreamException if something unexpected is encountered
     */
    protected QName next(XMLStreamReader reader) throws XMLStreamException {
        while (true) {
            if (!reader.hasNext())
                throw new XMLStreamException("unexpected end of input", reader.getLocation());
            final int eventType = reader.next();
            if (eventType == XMLStreamConstants.END_ELEMENT)
                return null;
            if (eventType == XMLStreamConstants.START_ELEMENT)
                return reader.getName();
        }
    }

    /**
     * Skip over the remainder of the current XML element, including any nested elements,
     * until the closing XML tag is seen and consumed.
     *
     * @param reader XML input
     * @throws XMLStreamException if something unexpected is encountered
     */
    protected void skip(XMLStreamReader reader) throws XMLStreamException {
        for (int depth = 1; depth > 0; ) {
            if (!reader.hasNext())
                throw new XMLStreamException("unexpected end of input", reader.getLocation());
            switch (reader.next()) {
            case XMLStreamConstants.START_ELEMENT:
                depth++;
                break;
            case XMLStreamConstants.END_ELEMENT:
                depth--;
                break;
            default:
                break;
            }
        }
    }

    /**
     * Scan forward expecting to see a closing tag.
     *
     * 

* Equivalant to: {@link #expect expect}{@code (reader, true)}. * * @param reader XML input * @throws XMLStreamException if something other than a closing tag is encountered */ protected void expectClose(XMLStreamReader reader) throws XMLStreamException { this.expect(reader, true); } private String description(QName[] names) { switch (names.length) { case 0: return "closing tag"; case 1: return "opening <" + names[0] + "> tag"; default: final StringBuilder buf = new StringBuilder(); for (QName name : names) { if (buf.length() == 0) buf.append("one of "); else buf.append(", "); buf.append('<').append(name).append('>'); } return buf.toString(); } } /** * Write out a simple XML element containing the given content. * * @param writer XML output * @param element element name * @param content simple content * @throws XMLStreamException if error occurs writing output */ protected void writeElement(XMLStreamWriter writer, QName element, String content) throws XMLStreamException { writer.writeStartElement(element.getNamespaceURI(), element.getLocalPart()); writer.writeCharacters(content); writer.writeEndElement(); } /** * Get an attribute from the current element. * * @param reader XML input * @param name attribute name * @param required whether attribute must be present * @return attribute value, or null if not {@code required} and no attribute is present * @throws IllegalStateException if the current event is not a start element event * @throws XMLStreamException if {@code required} is true and no such attribute is found */ protected String getAttr(XMLStreamReader reader, QName name, boolean required) throws XMLStreamException { final String value = reader.getAttributeValue(name.getNamespaceURI(), name.getLocalPart()); if (value == null && required) { throw new XMLStreamException("<" + reader.getName().getLocalPart() + "> element is missing required \"" + name + "\" attribute", reader.getLocation()); } return value; } /** * Get a requried attribute from the current element. Equivalent to: {@code getAttr(reader, name, true)}. * * @param reader XML input * @param name attribute name * @return attribute value * @throws IllegalStateException if the current event is not a start element event * @throws XMLStreamException if no such attribute is found */ protected String getAttr(XMLStreamReader reader, QName name) throws XMLStreamException { return this.getAttr(reader, name, true); } /** * Get an attribute from the current element and parse as a decimal integer value. * * @param reader XML input * @param name attribute name * @param required whether attribute must be present * @return attribute value, or null if not {@code required} and no attribute is present * @throws IllegalStateException if the current event is not a start element event * @throws XMLStreamException if {@code required} is true and no such attribute is found * @throws XMLStreamException if attribute is not an integer value */ protected Integer getIntAttr(XMLStreamReader reader, QName name, boolean required) throws XMLStreamException { final String text = this.getAttr(reader, name, required); if (text == null) return null; try { return Integer.parseInt(text, 10); } catch (NumberFormatException e) { throw new XMLStreamException("<" + reader.getName().getLocalPart() + "> element attribute \"" + name + "\" value `" + text + "' is invalid: not a valid value", reader.getLocation(), e); } } /** * Get an attribute from the current element and parse as a decimal long value. * * @param reader XML input * @param name attribute name * @param required whether attribute must be present * @return attribute value, or null if not {@code required} and no attribute is present * @throws IllegalStateException if the current event is not a start element event * @throws XMLStreamException if {@code required} is true and no such attribute is found * @throws XMLStreamException if attribute is not an integer value */ protected Long getLongAttr(XMLStreamReader reader, QName name, boolean required) throws XMLStreamException { final String text = this.getAttr(reader, name, required); if (text == null) return null; try { return Long.parseLong(text, 10); } catch (NumberFormatException e) { throw new XMLStreamException("<" + reader.getName().getLocalPart() + "> element attribute \"" + name + "\" value `" + text + "' is invalid: not a valid long value", reader.getLocation(), e); } } /** * Get a requried integer attribute from the current element. Equivalent to: {@code getIntAttr(reader, name, true)}. * * @param reader XML input * @param name attribute name * @return attribute value * @throws IllegalStateException if the current event is not a start element event * @throws XMLStreamException if no such attribute is found * @throws XMLStreamException if attribute is not an integer value */ protected int getIntAttr(XMLStreamReader reader, QName name) throws XMLStreamException { return this.getIntAttr(reader, name, true); } /** * Get an attribute from the current element and parse as a boolean value. * * @param reader XML input * @param name attribute name * @param required whether attribute must be present * @return attribute value, or null if not {@code required} and no attribute is present * @throws IllegalStateException if the current event is not a start element event * @throws XMLStreamException if {@code required} is true and no such attribute is found * @throws XMLStreamException if attribute is not {@code "true"} or {@code "false"} */ protected Boolean getBooleanAttr(XMLStreamReader reader, QName name, boolean required) throws XMLStreamException { final String text = this.getAttr(reader, name, required); if (text == null) return null; switch (text) { case "true": return true; case "false": return false; default: throw new XMLStreamException("<" + reader.getName().getLocalPart() + "> element attribute \"" + name + "\" value `" + text + "' is invalid: expected either \"true\" or \"false\"", reader.getLocation()); } } /** * Get a requried boolean attribute from the current element. Equivalent to: {@code getBooleanAttr(reader, name, true)}. * * @param reader XML input * @param name attribute name * @return attribute value * @throws IllegalStateException if the current event is not a start element event * @throws XMLStreamException if no such attribute is found * @throws XMLStreamException if attribute is not {@code "true"} or {@code "false"} */ protected boolean getBooleanAttr(XMLStreamReader reader, QName name) throws XMLStreamException { return this.getBooleanAttr(reader, name, true); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy