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

Java tutorial

Introduction

Here is the source code for org.eclipse.skalli.core.rest.JSONRestWriter.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.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 JSONRestWriter extends RestWriterBase implements RestWriter {

    /** Option to enable @-prefixed attributes. */
    public static final int PREFIXED_ATTRIBUTES = 0x10000;

    /** Option to enable the rendering of namespace atributes. */
    public static final int NAMESPACE_ATTRIBUTES = 0x20000;

    /** Option to allow a key for the root object/array. */
    public static final int NAMED_ROOT = 0x40000;

    private static final MediaType MEDIA_TYPE = MediaType.APPLICATION_JSON;

    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_SEQUENCE = '\u03B1';
    private static final char IN_SEQUENCE = ',';
    private static final char EXPECT_END_SEQUENCE = '\u03C9';

    private static final String VALUE_KEY = "value"; //$NON-NLS-1$
    private static final String VALUES_KEY = "values"; //$NON-NLS-1$

    private static final String MILLIS_KEY = "millis"; //$NON-NLS-1$
    private static final String LINKS_KEY = "links"; //$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 XMLNS_PREFIX = "xmlns:"; //$NON-NLS-1$

    // stack for state machine
    private CharacterStack states;

    // the current state
    private char state;

    // state that record whether we are in a sequence of elements,
    // or expect the begin/end of a sequence;
    // a sequence is a comma-separated list of elements
    private char sequenceState;

    // true, if the next element is the first in a sequence, i.e.
    // we must not render a comma before the element
    private boolean firstInSequence;

    // the key to assign to the next element
    private String nextKey;

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

    /**
     *
     * @param writer
     * @param options
     */
    public JSONRestWriter(Writer writer, String webLocator, int options) {
        super(writer, webLocator, options);
        this.options = options;
        states = new CharacterStack();
        states.push(state = STATE_INITIAL);
        sequenceState = EXPECT_SEQUENCE;
        firstInSequence = true;
    }

    @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 {
        if (state == STATE_FINAL) {
            throw new IllegalStateException("Unexpeced array: Final state already reached");
        }
        if (sequenceState == EXPECT_END_SEQUENCE) {
            throw new IllegalStateException("Unexpected array after value");
        }
        appendComma();
        if (nextKey != null && (state == STATE_OBJECT || (state == STATE_INITIAL && isSet(NAMED_ROOT)))) {
            appendKey(nextKey);
            writer.append(':');
        }
        writer.append('[');
        states.push(state = STATE_ARRAY);
        sequenceState = IN_SEQUENCE;
        firstInSequence = true;
        nextKey = null;
        return this;
    }

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

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

    @Override

    public RestWriter item() throws IOException {
        if (sequenceState == EXPECT_END_SEQUENCE) {
            throw new IllegalStateException("Unexpected item after value");
        }
        appendComma();
        if (state != STATE_ITEM) {
            while (state != STATE_ARRAY) {
                end();
            }
            if (state == STATE_ARRAY) {
                states.push(state = STATE_ITEM);
            }
        }
        firstInSequence = true;
        return this;
    }

    @Override
    public RestWriter item(String itemKey) throws IOException {
        return item();
    }

    @Override
    public RestWriter object() throws IOException {
        if (state == STATE_FINAL) {
            throw new IllegalStateException("Unexpected object: Final state already reached");
        }
        if (sequenceState == EXPECT_END_SEQUENCE) {
            throw new IllegalStateException("Unexpected object after value");
        }
        appendComma();
        if (nextKey != null && (state == STATE_OBJECT || (state == STATE_INITIAL && isSet(NAMED_ROOT)))) {
            appendKey(nextKey);
            writer.append(':');
        }
        writer.append('{');
        states.push(state = STATE_OBJECT);
        sequenceState = IN_SEQUENCE;
        nextKey = null;
        firstInSequence = true;
        return this;
    }

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

    @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();
        if (state == STATE_ARRAY) {
            writer.append(']');
        } else if (state == STATE_OBJECT) {
            writer.append('}');
        } else if (state == STATE_INITIAL) {
            throw new IllegalStateException("Still in initial state");
        }
        state = states.peek();
        if (state == STATE_INITIAL) {
            state = STATE_FINAL;
        }
        sequenceState = IN_SEQUENCE;
        firstInSequence = false;
        return this;
    }

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

    @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 s) throws IOException {
        return value(s, true);
    }

    @Override
    public RestWriter value(long l) throws IOException {
        return value(l, false);
    }

    @Override
    public RestWriter value(double d) throws IOException {
        return value(d, false);
    }

    @Override
    public RestWriter value(Number n) throws IOException {
        return value(n, false);
    }

    @Override
    public RestWriter value(boolean b) throws IOException {
        return value(b, false);
    }

    @Override
    public RestWriter value(UUID uuid) throws IOException {
        return value(uuid, true);
    }

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

    @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 pair(String key, String s) throws IOException {
        return value(key, s, true);
    }

    @Override
    public RestWriter pair(String key, long l) throws IOException {
        return value(key, l, false);
    }

    @Override
    public RestWriter pair(String key, double d) throws IOException {
        return value(key, d, false);
    }

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

    @Override
    public RestWriter pair(String key, boolean b) throws IOException {
        return value(key, b, false);
    }

    @Override
    public RestWriter pair(String key, UUID uuid) throws IOException {
        return value(key, uuid, true);
    }

    @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 name, String value) throws IOException {
        return attribute(name, value, true);
    }

    @Override
    public RestWriter attribute(String name, long l) throws IOException {
        return attribute(name, l, false);
    }

    @Override
    public RestWriter attribute(String name, double d) throws IOException {
        return attribute(name, d, false);
    }

    @Override
    public RestWriter attribute(String name, Number n) throws IOException {
        return attribute(name, n, false);
    }

    @Override
    public RestWriter attribute(String name, boolean b) throws IOException {
        return attribute(name, b, false);
    }

    @Override
    public RestWriter attribute(String name, UUID uuid) throws IOException {
        return attribute(name, uuid, true);
    }

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

    private void append(String s, boolean quoted) throws IOException {
        if (quoted) {
            writer.append('"');
            escaped(s);
            writer.append('"');
        } else {
            escaped(s);
        }
    }

    private void appendComma() throws IOException {
        if (!firstInSequence) {
            writer.append(',');
        }
    }

    private void appendKey(String key) throws IOException {
        append(StringUtils.isBlank(key) ? VALUE_KEY : key, true);
    }

    private RestWriter value(Object value, boolean quoted) throws IOException {
        if (state == STATE_FINAL) {
            throw new IllegalStateException("Unexpeced value: Final state already reached");
        }
        appendComma();
        if (state == STATE_ARRAY || state == STATE_ITEM) {
            append(value.toString(), quoted);
        } else if (state == STATE_OBJECT && sequenceState == IN_SEQUENCE) {
            append(VALUE_KEY, true);
            writer.append(':');
            append(value.toString(), quoted);
            sequenceState = EXPECT_END_SEQUENCE;
        } else {
            throw new IllegalStateException("Unexpected value");
        }
        nextKey = null;
        firstInSequence = false;
        return this;
    }

    private RestWriter value(String key, Object value, boolean quoted) throws IOException {
        if (state == STATE_FINAL) {
            throw new IllegalStateException("Unexpeced named attribute: Final state already reached");
        }
        if (StringUtils.isBlank(key)) {
            throw new IllegalStateException("Missing key");
        }
        if (state == STATE_OBJECT) {
            String str = value != null ? value.toString() : null;
            if (StringUtils.isNotBlank(str) || isSet(ALL_MEMBERS)) {
                appendComma();
                appendKey(key);
                writer.append(':');
                if (str == null) {
                    append("null", false); //$NON-NLS-1$
                } else {
                    append(str, quoted);
                }
                firstInSequence = false;
            }
        } else {
            throw new IllegalStateException("Unexpected named attribute");
        }
        nextKey = null;
        return this;
    }

    private RestWriter attribute(String name, Object value, boolean quoted) throws IOException {
        if (state == STATE_FINAL) {
            throw new IllegalStateException("Unexpeced attribute: Final state already reached");
        }
        String key = isSet(PREFIXED_ATTRIBUTES) ? "@" + name : name; //$NON-NLS-1$
        if (sequenceState == IN_SEQUENCE) {
            if (state == STATE_ARRAY || state == STATE_ITEM) {
                object();
                value(key, value, quoted);
                end();
            } else {
                value(key, value, quoted);
            }
        } else {
            throw new IllegalStateException("Unexpected attribute");
        }
        nextKey = null;
        return this;
    }

    @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("\\\"");
                break;
            case '\\':
                writer.append("\\\\");
                break;
            case '<':
                writer.append('\u0047');
                break;
            case '\n':
                writer.append("\\n");
                break;
            case '\r':
                writer.append("\\r");
                break;
            case '\t':
                writer.append("\\t");
                break;
            case '\b':
                writer.append("\\b");
                break;
            case '\f':
                writer.append("\\f");
                break;
            default:
                if (c < 0x20 || c >= 0x80 && c <= 0xa0 || c >= '\u2000' && c < '\u2100') {
                    writer.append("\\u");
                    writer.append(StringUtils.leftPad(Integer.toHexString(c), 4, '0'));
                } else {
                    writer.append(c);
                }
            }
        }
        return this;
    }
}