| /******************************************************************************* |
| * Copyright (c) 2010-2015 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.Reader; |
| import java.net.URL; |
| import java.util.ArrayList; |
| import java.util.Calendar; |
| import java.util.List; |
| import java.util.UUID; |
| |
| import javax.xml.bind.DatatypeConverter; |
| |
| import org.apache.commons.lang.StringUtils; |
| import org.eclipse.skalli.commons.CharacterStack; |
| import org.eclipse.skalli.commons.URLUtils; |
| import org.eclipse.skalli.services.rest.RestReader; |
| import org.restlet.data.MediaType; |
| |
| import com.google.gson.stream.JsonReader; |
| import com.google.gson.stream.JsonToken; |
| |
| |
| public class JSONRestReader implements RestReader { |
| |
| /** Option to enable @-prefixed attributes. */ |
| public static final int PREFIXED_ATTRIBUTES = 0x10000; |
| |
| /** Option to enable strict parsing */ |
| public static final int STRICT = 0x20000; |
| |
| |
| 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 EXPECT_KEY = 'k'; |
| private static final char EXPECT_VALUE = 'v'; |
| |
| private JsonReader json; |
| private long options; |
| |
| // stack for state machine |
| private CharacterStack states; |
| |
| // the current state |
| private char state; |
| |
| private char sequenceState; |
| |
| // look-ahead for attribute keys |
| private String nextKey; |
| |
| public JSONRestReader(Reader reader) { |
| this(reader, 0); |
| } |
| |
| public JSONRestReader(Reader reader, int options) { |
| json = new JsonReader(reader); |
| states = new CharacterStack(); |
| states.push(state = STATE_INITIAL); |
| sequenceState = EXPECT_KEY; |
| set(options); |
| } |
| |
| @Override |
| public MediaType getMediaType() { |
| return MEDIA_TYPE; |
| } |
| |
| @Override |
| public boolean isMediaType(MediaType mediaType) { |
| return mediaType != null && mediaType.equals(getMediaType()); |
| } |
| |
| @Override |
| public boolean isSet(long optionsMask) { |
| return (options & optionsMask) == optionsMask; |
| } |
| |
| @Override |
| public void set(long optionsMask) { |
| options |= optionsMask; |
| json.setLenient(!isSet(STRICT)); |
| } |
| |
| @Override |
| public void reset(long optionsMask) { |
| options &= ~optionsMask; |
| } |
| |
| @Override |
| public boolean hasMore() throws IOException { |
| if (state == STATE_FINAL) { |
| return false; |
| } |
| return json.hasNext(); |
| } |
| |
| @Override |
| public boolean isKey() throws IOException { |
| if (state == STATE_FINAL) { |
| return false; |
| } |
| return nextKey != null || json.peek() == JsonToken.NAME; |
| } |
| |
| @Override |
| public boolean isKeyAnyOf(String... keys) throws IOException { |
| if (state == STATE_FINAL) { |
| return false; |
| } |
| if (keys == null) { |
| return false; |
| } |
| if (nextKey == null) { |
| if (json.peek() != JsonToken.NAME) { |
| return false; |
| } |
| nextKey = key(); |
| } |
| for (String key: keys) { |
| if (key.equals(nextKey)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| public String key() throws IOException { |
| assertNotFinal(); |
| if (nextKey == null && sequenceState != EXPECT_KEY) { |
| throw new IllegalStateException("Key expected"); |
| } |
| String key = nextKey; |
| if (key != null) { |
| nextKey = null; |
| return key; |
| } |
| key = json.nextName(); |
| sequenceState = EXPECT_VALUE; |
| return isSet(PREFIXED_ATTRIBUTES)? StringUtils.removeStart(key, "@") : key; //$NON-NLS-1$ |
| } |
| |
| @Override |
| public void skip() throws IOException { |
| assertNotFinal(); |
| skipKey(); |
| skipValue(); |
| sequenceState = EXPECT_KEY; |
| } |
| |
| @Override |
| public String valueString() throws IOException { |
| assertNotFinal(); |
| skipKey(); |
| String value = null; |
| switch (json.peek()) { |
| case NULL: |
| json.nextNull(); |
| break; |
| case BOOLEAN: |
| value = Boolean.toString(json.nextBoolean()); |
| break; |
| default: |
| value = json.nextString(); |
| break; |
| } |
| sequenceState = EXPECT_KEY; |
| return value; |
| } |
| |
| @Override |
| public long valueLong() throws IOException { |
| assertNotFinal(); |
| skipKey(); |
| long value = json.nextLong(); |
| sequenceState = EXPECT_KEY; |
| return value; |
| } |
| |
| @Override |
| public double valueDouble() throws IOException { |
| assertNotFinal(); |
| skipKey(); |
| double value = json.nextDouble(); |
| sequenceState = EXPECT_KEY; |
| return value; |
| } |
| |
| @Override |
| public boolean valueBoolean() throws IOException { |
| assertNotFinal(); |
| skipKey(); |
| boolean value = json.nextBoolean(); |
| sequenceState = EXPECT_KEY; |
| return value; |
| } |
| |
| @Override |
| public UUID valueUUID() throws IOException { |
| assertNotFinal(); |
| skipKey(); |
| UUID value = json.peek() != JsonToken.NULL ? UUID.fromString(valueString()) : null; |
| sequenceState = EXPECT_KEY; |
| return value; |
| } |
| |
| @Override |
| public URL valueURL() throws IOException { |
| assertNotFinal(); |
| skipKey(); |
| URL value = json.peek() != JsonToken.NULL ? URLUtils.stringToURL(valueString()) : null; |
| sequenceState = EXPECT_KEY; |
| return value; |
| } |
| |
| @Override |
| public Calendar valueDatetime() throws IOException { |
| assertNotFinal(); |
| skipKey(); |
| Calendar value = json.peek() != JsonToken.NULL ? DatatypeConverter.parseDateTime(valueString()) : null; |
| sequenceState = EXPECT_KEY; |
| return value; |
| } |
| |
| @Override |
| public Calendar valueDate() throws IOException { |
| assertNotFinal(); |
| skipKey(); |
| Calendar value = json.peek() != JsonToken.NULL ? DatatypeConverter.parseDate(valueString()) : null; |
| sequenceState = EXPECT_KEY; |
| return value; |
| } |
| |
| @Override |
| public String attributeString() throws IOException { |
| return valueString(); |
| } |
| |
| @Override |
| public long attributeLong() throws IOException { |
| return valueLong(); |
| } |
| |
| @Override |
| public double attributeDouble() throws IOException { |
| return valueDouble(); |
| } |
| |
| @Override |
| public boolean attributeBoolean() throws IOException { |
| return valueBoolean(); |
| } |
| |
| @Override |
| public UUID attributeUUID() throws IOException { |
| return valueUUID(); |
| } |
| |
| @Override |
| public URL attributeURL() throws IOException { |
| return valueURL(); |
| } |
| |
| @Override |
| public Calendar attributeDatetime() throws IOException { |
| return valueDatetime(); |
| } |
| |
| @Override |
| public Calendar attributeDate() throws IOException { |
| return valueDate(); |
| } |
| |
| @Override |
| public boolean isArray() throws IOException { |
| if (state == STATE_FINAL) { |
| return false; |
| } |
| return json.peek() == JsonToken.BEGIN_ARRAY; |
| } |
| |
| @Override |
| public void array() throws IOException { |
| array(null); |
| } |
| |
| @Override |
| public void array(String itemKey) throws IOException { |
| assertNotFinal(); |
| json.beginArray(); |
| states.push(state = STATE_ARRAY); |
| sequenceState = EXPECT_VALUE; |
| } |
| |
| @Override |
| public boolean isObject() throws IOException { |
| if (state == STATE_FINAL) { |
| return false; |
| } |
| return json.peek() == JsonToken.BEGIN_OBJECT; |
| } |
| |
| @Override |
| public void object() throws IOException { |
| assertNotFinal(); |
| json.beginObject(); |
| states.push(state = STATE_OBJECT); |
| sequenceState = EXPECT_KEY; |
| } |
| |
| @Override |
| public void end() throws IOException { |
| assertNotFinal(); |
| if (state == STATE_INITIAL) { |
| throw new IllegalStateException("Still in initial state"); |
| } |
| state = states.pop(); |
| if (state == STATE_ARRAY) { |
| json.endArray(); |
| } else if (state == STATE_OBJECT) { |
| json.endObject(); |
| } else if (state == STATE_INITIAL) { |
| throw new IllegalStateException("Still in initial state"); |
| } |
| state = states.peek(); |
| if (state == STATE_INITIAL) { |
| state = STATE_FINAL; |
| } |
| sequenceState = EXPECT_KEY; |
| } |
| |
| @Override |
| public List<String> collection(String itemKey) throws IOException { |
| List<String> items = new ArrayList<String>(); |
| array(itemKey); |
| while (hasMore()) { |
| items.add(valueString()); |
| } |
| end(); |
| return items; |
| } |
| |
| private void assertNotFinal() { |
| if (state == STATE_FINAL) { |
| throw new IllegalStateException("Final state already reached"); |
| } |
| } |
| |
| private void skipKey() throws IOException { |
| if (isKey()) { |
| key(); |
| } |
| } |
| |
| private void skipValue() throws IOException { |
| if (sequenceState == EXPECT_VALUE) { |
| json.skipValue(); |
| } |
| } |
| } |