org.eclipse.skalli.core.rest.XMLRestWriter.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.skalli.core.rest.XMLRestWriter.java

Source

/*******************************************************************************
 * Copyright (c) 2010-2014 SAP AG and others.
 * 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:
 *     SAP AG - initial API and implementation
 *******************************************************************************/
package org.eclipse.skalli.core.rest;

import java.io.IOException;
import java.io.Writer;
import java.util.Stack;
import java.util.UUID;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.time.DateFormatUtils;
import org.apache.commons.lang.time.DurationFormatUtils;
import org.eclipse.skalli.commons.CharacterStack;
import org.eclipse.skalli.commons.FormatUtils;
import org.eclipse.skalli.services.rest.RestWriter;
import org.eclipse.skalli.services.rest.RestWriterBase;
import org.restlet.data.MediaType;

public class XMLRestWriter extends RestWriterBase implements RestWriter {

    private static final MediaType MEDIA_TYPE = MediaType.TEXT_XML;

    private static final char STATE_INITIAL = '\u03B1';
    private static final char STATE_FINAL = '\u03C9';
    private static final char STATE_ARRAY = 'A';
    private static final char STATE_OBJECT = 'O';
    private static final char STATE_ITEM = 'I';

    private static final char EXPECT_OPENING_TAG = '\u03B1';
    private static final char OPENING_TAG = '<';
    private static final char EXPECT_TEXT_NODE = '#';
    private static final char EXPECT_CLOSING_TAG = '\u03C9';

    private static final String MILLIS_KEY = "millis"; //$NON-NLS-1$
    private static final String ITEM = "item"; //$NON-NLS-1$
    private static final String LINK_KEY = "link"; //$NON-NLS-1$
    private static final String HREF_KEY = "href"; //$NON-NLS-1$
    private static final String REL_KEY = "rel"; //$NON-NLS-1$
    private static final String VALUE_KEY = "value"; //$NON-NLS-1$
    private static final String VALUE_TAG = '<' + VALUE_KEY + '>';
    private static final String VALUE_END_TAG = "</" + VALUE_KEY + '>'; //$NON-NLS-1$

    private static final String XML_HEADER = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"; //$NON-NLS-1$
    private static final String XMLNS_PREFIX = "xmlns:"; //$NON-NLS-1$

    // stack for state machine
    private CharacterStack states;

    // stack for names of nested tags
    private Stack<String> tags;

    // the current state
    private char state;

    // state that record where we are currently within a
    // tag, i.e. whether we are in the opening tag,
    // expect the text node or the begin of a new tag
    private char tagState;

    // the name of the tag we are currently in
    private String tag;

    // the name to assign to the next tag
    private String nextKey;

    // the name to assign to the next tag within an array
    private String nextItem;

    public XMLRestWriter(Writer writer, String webLocator) {
        this(writer, webLocator, 0);
    }

    public XMLRestWriter(Writer writer, String webLocator, int options) {
        super(writer, webLocator, options);
        states = new CharacterStack();
        tags = new Stack<String>();
        states.push(state = STATE_INITIAL);
        tagState = EXPECT_OPENING_TAG;
    }

    @Override
    public MediaType getMediaType() {
        return MEDIA_TYPE;
    }

    @Override
    public void flush() throws IOException {
        if (state != STATE_FINAL) {
            throw new IllegalStateException("Final state not yet reached");
        }
        writer.flush();
    }

    @Override
    public RestWriter key(String key) {
        nextKey = StringUtils.isNotBlank(key) ? key : null;
        return this;
    }

    @Override
    public RestWriter array() throws IOException {
        return array(null, null);
    }

    @Override
    public RestWriter array(String itemKey) throws IOException {
        return array(null, itemKey);
    }

    @Override
    public RestWriter array(String key, String itemKey) throws IOException {
        if (state == STATE_FINAL) {
            throw new IllegalStateException("Unexpeced array: Final state already reached");
        }
        if (tagState == EXPECT_CLOSING_TAG) {
            throw new IllegalStateException("Unexpected array after value");
        }
        if (state == STATE_INITIAL) {
            writer.append(XML_HEADER);
        }
        closeOpeningTag();
        String tag = key;
        if (StringUtils.isBlank(tag)) {
            tag = nextKey;
            if (StringUtils.isBlank(tag)) {
                if (state == STATE_ITEM || state == STATE_ARRAY) {
                    tag = nextItem;
                }
            }
        }
        nextItem = itemKey;
        if (StringUtils.isBlank(nextItem)) {
            nextItem = ITEM;
        }
        if (StringUtils.isNotBlank(tag)) {
            writer.append('<').append(tag);
            tagState = OPENING_TAG;
        }
        states.push(state = STATE_ARRAY);
        tags.push(tag);
        nextKey = null;
        return this;
    }

    @Override
    public RestWriter item() throws IOException {
        if (state == STATE_INITIAL) {
            throw new IllegalStateException("Still in initial state");
        }
        if (tagState == EXPECT_CLOSING_TAG) {
            throw new IllegalStateException("Unexpected item after value");
        }
        closeOpeningTag();
        if (state != STATE_ITEM) {
            while (state != STATE_ARRAY) {
                end();
            }
            if (state == STATE_ARRAY) {
                states.push(state = STATE_ITEM);
                tags.push(nextItem);
            }
        }
        return this;
    }

    @Override
    public RestWriter item(String itemKey) throws IOException {
        nextItem = StringUtils.isNotBlank(itemKey) ? itemKey : null;
        return item();
    }

    @Override
    public RestWriter object() throws IOException {
        return object(null);
    }

    @Override
    public RestWriter object(String key) throws IOException {
        if (state == STATE_FINAL) {
            throw new IllegalStateException("Unexpeced object: Final state already reached");
        }
        if (tagState == EXPECT_CLOSING_TAG) {
            throw new IllegalStateException("Unexpected object after value");
        }
        if (state == STATE_INITIAL) {
            writer.append(XML_HEADER);
        }
        closeOpeningTag();
        String tag = key;
        if (StringUtils.isBlank(tag)) {
            tag = nextKey;
            if (StringUtils.isBlank(tag)) {
                if (state == STATE_ITEM || state == STATE_ARRAY) {
                    tag = nextItem;
                }
            }
        }
        if (StringUtils.isNotBlank(tag)) {
            writer.append('<').append(tag);
            tagState = OPENING_TAG;
        }
        states.push(state = STATE_OBJECT);
        tags.push(tag);
        nextKey = null;
        return this;
    }

    @Override
    public RestWriter end() throws IOException {
        if (state == STATE_FINAL) {
            throw new IllegalStateException("Final state already reached");
        }
        if (state == STATE_INITIAL) {
            throw new IllegalStateException("Still in initial state");
        }
        state = states.pop();
        tag = tags.pop();
        if (state == STATE_INITIAL) {
            throw new IllegalStateException("Still in initial state");
        }
        if (state == STATE_ARRAY || state == STATE_OBJECT) {
            closeTag();
        }
        state = states.peek();
        if (state == STATE_INITIAL) {
            state = STATE_FINAL;
        }
        if (state == STATE_ARRAY || state == STATE_ITEM) {
            nextItem = tag;
        }
        return this;
    }

    @Override
    public RestWriter links() throws IOException {
        return array();
    }

    @Override
    public RestWriter links(String key) throws IOException {
        return key(key).array();
    }

    @Override
    public RestWriter link(String rel, String href) throws IOException {
        return object(LINK_KEY).attribute(REL_KEY, rel).attribute(HREF_KEY, href).end();
    }

    @Override
    public RestWriter value(String value) throws IOException {
        if (state == STATE_FINAL) {
            throw new IllegalStateException("Unexpeced value: Final state already reached");
        }
        boolean isItem = (state == STATE_ITEM || state == STATE_ARRAY) && StringUtils.isNotBlank(nextItem);
        if (isItem) {
            closeOpeningTag();
            writer.append('<').append(nextItem).append('>');
            escaped(value);
            writer.append('<').append('/').append(nextItem).append('>');
            tagState = EXPECT_OPENING_TAG;
        } else if (state == STATE_OBJECT && tagState != EXPECT_CLOSING_TAG) {
            closeOpeningTag();
            if (tagState == EXPECT_OPENING_TAG) {
                writer.append(VALUE_TAG);
                escaped(value);
                writer.append(VALUE_END_TAG);
            } else {
                escaped(value);
            }
            tagState = EXPECT_CLOSING_TAG;
        } else {
            throw new IllegalStateException("Unexpected value");
        }
        nextKey = null;
        return this;
    }

    @Override
    public RestWriter value(long l) throws IOException {
        return value(Long.toString(l));
    }

    @Override
    public RestWriter value(double d) throws IOException {
        return value(Double.toString(d));
    }

    @Override
    public RestWriter value(Number n) throws IOException {
        return value(n.toString());
    }

    @Override
    public RestWriter value(boolean b) throws IOException {
        return value(Boolean.toString(b));
    }

    @Override
    public RestWriter value(UUID uuid) throws IOException {
        return value(uuid.toString());
    }

    @Override
    public RestWriter date(long millis) throws IOException {
        return value(DateFormatUtils.formatUTC(millis, "yyyy-MM-dd")); //$NON-NLS-1$
    }

    @Override
    public RestWriter datetime(long millis) throws IOException {
        return value(FormatUtils.formatUTC(millis));
    }

    @Override
    public RestWriter duration(long millis) throws IOException {
        return value(DurationFormatUtils.formatDurationISO(millis));
    }

    @Override
    public RestWriter href(Object... pathSegments) throws IOException {
        return value(hrefOf(pathSegments));
    }

    @Override
    public RestWriter pair(String key, String value) throws IOException {
        if (state == STATE_FINAL) {
            throw new IllegalStateException("Unexpeced attribute: Final state already reached");
        }
        if (StringUtils.isBlank(key)) {
            throw new IllegalStateException("Missing tag name");
        }
        if (state == STATE_OBJECT) {
            closeOpeningTag();
            if (StringUtils.isNotBlank(value) || isSet(ALL_MEMBERS)) {
                writer.append('<').append(key);
                if (value != null) {
                    writer.append('>');
                    escaped(value);
                    writer.append("</").append(key).append('>'); //$NON-NLS-1$
                } else {
                    writer.append("/>"); //$NON-NLS-1$
                }
            }
        } else {
            throw new IllegalStateException("Unexpected attribute without tag");
        }
        nextKey = null;
        return this;
    }

    @Override
    public RestWriter pair(String key, long l) throws IOException {
        return pair(key, Long.toString(l));
    }

    @Override
    public RestWriter pair(String key, double d) throws IOException {
        return pair(key, Double.toString(d));
    }

    @Override
    public RestWriter pair(String key, Number n) throws IOException {
        return pair(key, n.toString());
    }

    @Override
    public RestWriter pair(String key, boolean b) throws IOException {
        return pair(key, Boolean.toString(b));
    }

    @Override
    public RestWriter pair(String key, UUID uuid) throws IOException {
        return pair(key, uuid != null ? uuid.toString() : null);
    }

    @Override
    public RestWriter date(String key, long millis) throws IOException {
        return object(key).pair(MILLIS_KEY, millis).value(DateFormatUtils.formatUTC(millis, "yyyy-MM-dd")); //$NON-NLS-1$
    }

    @Override
    public RestWriter datetime(String key, long millis) throws IOException {
        return object(key).pair(MILLIS_KEY, millis).value(FormatUtils.formatUTC(millis));
    }

    @Override
    public RestWriter duration(String key, long millis) throws IOException {
        return object(key).pair(MILLIS_KEY, millis).value(DurationFormatUtils.formatDurationISO(millis));
    }

    @Override
    public RestWriter href(String key, Object... pathSegments) throws IOException {
        return pair(key, hrefOf(pathSegments));
    }

    @Override
    public RestWriter attribute(String key, String value) throws IOException {
        if (state == STATE_FINAL) {
            throw new IllegalStateException("Unexpeced attribute: Final state already reached");
        }
        if (StringUtils.isBlank(key)) {
            throw new IllegalStateException("Missing attribute name");
        }
        if (tagState == OPENING_TAG && value != null) {
            writer.append(' ').append(key).append('=');
            writer.append('"');
            if (value != null) {
                escaped(value);
            }
            writer.append('"');
        } else if (tagState == EXPECT_OPENING_TAG) {
            if (state == STATE_OBJECT) {
                pair(key, value);
            } else if (state == STATE_ARRAY || state == STATE_ITEM) {
                array().attribute(key, value).end();
            }
        } else {
            throw new IllegalStateException("Unexpected attribute");
        }
        nextKey = null;
        return this;
    }

    @Override
    public RestWriter attribute(String key, long l) throws IOException {
        return attribute(key, Long.toString(l));
    }

    @Override
    public RestWriter attribute(String key, double d) throws IOException {
        return attribute(key, Double.toString(d));
    }

    @Override
    public RestWriter attribute(String key, Number n) throws IOException {
        return attribute(key, n.toString());
    }

    @Override
    public RestWriter attribute(String key, boolean b) throws IOException {
        return attribute(key, Boolean.toString(b));
    }

    @Override
    public RestWriter attribute(String key, UUID uuid) throws IOException {
        return attribute(key, uuid != null ? uuid.toString() : null);
    }

    @Override
    public RestWriter namespace(String key, String value) throws IOException {
        attribute(StringUtils.isBlank(key) ? XMLNS_PREFIX : key, value);
        return this;
    }

    private void closeOpeningTag() throws IOException {
        if (tagState == OPENING_TAG) {
            writer.append('>');
            tagState = EXPECT_TEXT_NODE;
        }
    }

    private void closeTag() throws IOException {
        if (state == STATE_FINAL) {
            throw new IllegalStateException("Final state already reached");
        }
        if (state == STATE_INITIAL) {
            throw new IllegalStateException("Initial state");
        }
        if (tagState == OPENING_TAG) {
            writer.append("/>"); //$NON-NLS-1$
            tagState = EXPECT_OPENING_TAG;
        } else if (tag != null) {
            writer.append("</").append(tag).append('>'); //$NON-NLS-1$
            tagState = EXPECT_OPENING_TAG;
        }
    }

    @SuppressWarnings("nls")
    private RestWriter escaped(String s) throws IOException {
        int len = s.length();
        for (int i = 0; i < len; ++i) {
            char c = s.charAt(i);
            switch (c) {
            case '&':
                writer.append("&amp;");
                break;
            case '<':
                writer.append("&lt;");
                break;
            case '>':
                writer.append("&gt;");
                break;
            case '"':
                writer.append("&quot;");
                break;
            case '\'':
                writer.append("&apos;");
                break;
            default:
                writer.append(c);
            }
        }
        return this;
    }
}