| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to you under the Apache License, |
| * Version 2.0 (the "License"); you may not use this file except in |
| * compliance with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.pivot.json; |
| |
| import java.io.BufferedReader; |
| import java.io.BufferedWriter; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.io.LineNumberReader; |
| import java.io.OutputStream; |
| import java.io.OutputStreamWriter; |
| import java.io.Reader; |
| import java.io.StringReader; |
| import java.io.StringWriter; |
| import java.io.Writer; |
| import java.lang.reflect.ParameterizedType; |
| import java.lang.reflect.Type; |
| import java.lang.reflect.TypeVariable; |
| import java.nio.charset.Charset; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.apache.pivot.beans.BeanAdapter; |
| import org.apache.pivot.io.SerializationException; |
| import org.apache.pivot.io.Serializer; |
| import org.apache.pivot.util.ListenerList; |
| |
| /** |
| * Implementation of the {@link Serializer} interface that reads data from |
| * and writes data to a JavaScript Object Notation (JSON) file. |
| */ |
| public class JSONSerializer implements Serializer<Object> { |
| private static class JSONSerializerListenerList |
| extends ListenerList<JSONSerializerListener> |
| implements JSONSerializerListener { |
| @Override |
| public void beginMap(JSONSerializer jsonSerializer, Map<String, ?> value) { |
| for (JSONSerializerListener listener : listeners()) { |
| listener.beginMap(jsonSerializer, value); |
| } |
| } |
| |
| @Override |
| public void endMap(JSONSerializer jsonSerializer) { |
| for (JSONSerializerListener listener : listeners()) { |
| listener.endMap(jsonSerializer); |
| } |
| } |
| |
| @Override |
| public void readKey(JSONSerializer jsonSerializer, String key) { |
| for (JSONSerializerListener listener : listeners()) { |
| listener.readKey(jsonSerializer, key); |
| } |
| } |
| |
| @Override |
| public void beginList(JSONSerializer jsonSerializer, List<?> value) { |
| for (JSONSerializerListener listener : listeners()) { |
| listener.beginList(jsonSerializer, value); |
| } |
| } |
| |
| @Override |
| public void endList(JSONSerializer jsonSerializer) { |
| for (JSONSerializerListener listener : listeners()) { |
| listener.endList(jsonSerializer); |
| } |
| } |
| |
| @Override |
| public void readString(JSONSerializer jsonSerializer, String value) { |
| for (JSONSerializerListener listener : listeners()) { |
| listener.readString(jsonSerializer, value); |
| } |
| } |
| |
| @Override |
| public void readNumber(JSONSerializer jsonSerializer, Number value) { |
| for (JSONSerializerListener listener : listeners()) { |
| listener.readNumber(jsonSerializer, value); |
| } |
| } |
| |
| @Override |
| public void readBoolean(JSONSerializer jsonSerializer, Boolean value) { |
| for (JSONSerializerListener listener : listeners()) { |
| listener.readBoolean(jsonSerializer, value); |
| } |
| } |
| |
| @Override |
| public void readNull(JSONSerializer jsonSerializer) { |
| for (JSONSerializerListener listener : listeners()) { |
| listener.readNull(jsonSerializer); |
| } |
| } |
| } |
| |
| private Charset charset; |
| private Type type; |
| |
| private boolean alwaysDelimitMapKeys = false; |
| |
| private int c = -1; |
| |
| private JSONSerializerListenerList jsonSerializerListeners = null; |
| |
| public static final String DEFAULT_CHARSET_NAME = "UTF-8"; |
| public static final Type DEFAULT_TYPE = Object.class; |
| |
| public static final String JSON_EXTENSION = "json"; |
| public static final String MIME_TYPE = "application/json"; |
| public static final int BUFFER_SIZE = 2048; |
| |
| public JSONSerializer() { |
| this(Charset.forName(DEFAULT_CHARSET_NAME), DEFAULT_TYPE); |
| } |
| |
| public JSONSerializer(Charset charset) { |
| this(charset, DEFAULT_TYPE); |
| } |
| |
| public JSONSerializer(Type type) { |
| this(Charset.forName(DEFAULT_CHARSET_NAME), type); |
| } |
| |
| public JSONSerializer(Charset charset, Type type) { |
| if (charset == null) { |
| throw new IllegalArgumentException("charset is null."); |
| } |
| |
| if (type == null) { |
| throw new IllegalArgumentException("type is null."); |
| } |
| |
| this.charset = charset; |
| this.type = type; |
| } |
| |
| /** |
| * Returns the character set used to encode/decode the JSON data. |
| */ |
| public Charset getCharset() { |
| return charset; |
| } |
| |
| /** |
| * Returns the type of the object that will be returned by {@link #readObject(Reader)}. |
| */ |
| public Type getType() { |
| return type; |
| } |
| |
| /** |
| * Returns a flag indicating whether or not map keys will always be |
| * quote-delimited. |
| */ |
| public boolean getAlwaysDelimitMapKeys() { |
| return alwaysDelimitMapKeys; |
| } |
| |
| /** |
| * Sets a flag indicating that map keys should always be quote-delimited. |
| * |
| * @param alwaysDelimitMapKeys |
| * <tt>true</tt> to bound map keys in double quotes; <tt>false</tt> to |
| * only quote-delimit keys as necessary. |
| */ |
| public void setAlwaysDelimitMapKeys(boolean alwaysDelimitMapKeys) { |
| this.alwaysDelimitMapKeys = alwaysDelimitMapKeys; |
| } |
| |
| /** |
| * Reads data from a JSON stream. |
| * |
| * @param inputStream |
| * The input stream from which data will be read. |
| * |
| * @see #readObject(Reader) |
| */ |
| @Override |
| public Object readObject(InputStream inputStream) |
| throws IOException, SerializationException { |
| if (inputStream == null) { |
| throw new IllegalArgumentException("inputStream is null."); |
| } |
| |
| Reader reader = new BufferedReader(new InputStreamReader(inputStream, charset), BUFFER_SIZE); |
| return readObject(reader); |
| } |
| |
| /** |
| * Reads data from a JSON stream. |
| * |
| * @param reader |
| * The reader from which data will be read. |
| * |
| * @return |
| * One of the following types, depending on the content of the stream |
| * and the value of {@link #getType()}: |
| * |
| * <ul> |
| * <li>pivot.collections.Dictionary</li> |
| * <li>pivot.collections.Sequence</li> |
| * <li>java.lang.String</li> |
| * <li>java.lang.Number</li> |
| * <li>java.lang.Boolean</li> |
| * <li><tt>null</tt></li> |
| * <li>A JavaBean object</li> |
| * </ul> |
| */ |
| public Object readObject(Reader reader) |
| throws IOException, SerializationException { |
| if (reader == null) { |
| throw new IllegalArgumentException("reader is null."); |
| } |
| |
| // Move to the first character |
| LineNumberReader lineNumberReader = new LineNumberReader(reader); |
| c = lineNumberReader.read(); |
| |
| // Ignore BOM (if present) |
| if (c == 0xFEFF) { |
| c = lineNumberReader.read(); |
| } |
| |
| // Read the root value |
| Object object; |
| try { |
| object = readValue(lineNumberReader, type); |
| } catch (SerializationException exception) { |
| System.err.println("An error occurred while processing input at line number " |
| + (lineNumberReader.getLineNumber() + 1)); |
| |
| throw exception; |
| } |
| |
| return object; |
| } |
| |
| private Object readValue(Reader reader, Type type) |
| throws IOException, SerializationException { |
| Object object = null; |
| |
| skipWhitespaceAndComments(reader); |
| |
| if (c == -1) { |
| throw new SerializationException("Unexpected end of input stream."); |
| } |
| |
| if (c == 'n') { |
| object = readNullValue(reader); |
| } else if (c == '"' || c == '\'') { |
| object = readStringValue(reader, type); |
| } else if (c == '+' || c == '-' || Character.isDigit(c)) { |
| object = readNumberValue(reader, type); |
| } else if (c == 't' || c == 'f') { |
| object = readBooleanValue(reader, type); |
| } else if (c == '[') { |
| object = readListValue(reader, type); |
| } else if (c == '{') { |
| object = readMapValue(reader, type); |
| } else { |
| throw new SerializationException("Unexpected character in input stream."); |
| } |
| |
| return object; |
| } |
| |
| private void skipWhitespaceAndComments(Reader reader) |
| throws IOException, SerializationException { |
| while (c != -1 |
| && (Character.isWhitespace(c) |
| || c == '/')) { |
| boolean comment = (c == '/'); |
| |
| // Read the next character |
| c = reader.read(); |
| |
| if (comment) { |
| if (c == '/') { |
| // Single-line comment |
| while (c != -1 |
| && c != '\n' |
| && c != '\r') { |
| c = reader.read(); |
| } |
| } else if (c == '*') { |
| // Multi-line comment |
| boolean closed = false; |
| |
| while (c != -1 |
| && !closed) { |
| c = reader.read(); |
| |
| if (c == '*') { |
| c = reader.read(); |
| closed = (c == '/'); |
| } |
| } |
| |
| if (!closed) { |
| throw new SerializationException("Unexpected end of input stream."); |
| } |
| |
| if (c != -1) { |
| c = reader.read(); |
| } |
| } else { |
| throw new SerializationException("Unexpected character in input stream."); |
| } |
| } |
| } |
| } |
| |
| private Object readNullValue(Reader reader) |
| throws IOException, SerializationException { |
| String nullString = "null"; |
| |
| int n = nullString.length(); |
| int i = 0; |
| |
| while (c != -1 && i < n) { |
| if (nullString.charAt(i) != c) { |
| throw new SerializationException("Unexpected character in input stream."); |
| } |
| |
| c = reader.read(); |
| i++; |
| } |
| |
| if (i < n) { |
| throw new SerializationException("Incomplete null value in input stream."); |
| } |
| |
| // Notify the listeners |
| if (jsonSerializerListeners != null) { |
| jsonSerializerListeners.readNull(this); |
| } |
| |
| return null; |
| } |
| |
| private String readString(Reader reader) |
| throws IOException, SerializationException { |
| StringBuilder stringBuilder = new StringBuilder(); |
| |
| // Use the same delimiter to close the string |
| int t = c; |
| |
| // Move to the next character after the delimiter |
| c = reader.read(); |
| |
| while (c != -1 && c != t) { |
| if (!Character.isISOControl(c)) { |
| if (c == '\\') { |
| c = reader.read(); |
| |
| if (c == 'b') { |
| c = '\b'; |
| } else if (c == 'f') { |
| c = '\f'; |
| } else if (c == 'n') { |
| c = '\n'; |
| } else if (c == 'r') { |
| c = '\r'; |
| } else if (c == 't') { |
| c = '\t'; |
| } else if (c == 'u') { |
| StringBuilder unicodeBuilder = new StringBuilder(); |
| while (unicodeBuilder.length() < 4) { |
| c = reader.read(); |
| unicodeBuilder.append((char)c); |
| } |
| |
| String unicode = unicodeBuilder.toString(); |
| c = (char)Integer.parseInt(unicode, 16); |
| } else { |
| if (!(c == '\\' |
| || c == '/' |
| || c == '\"' |
| || c == '\'' |
| || c == t)) { |
| throw new SerializationException("Unsupported escape sequence in input stream."); |
| } |
| } |
| } |
| |
| stringBuilder.append((char)c); |
| } |
| |
| c = reader.read(); |
| } |
| |
| if (c != t) { |
| throw new SerializationException("Unterminated string in input stream."); |
| } |
| |
| // Move to the next character after the delimiter |
| c = reader.read(); |
| |
| return stringBuilder.toString(); |
| } |
| |
| private Object readStringValue(Reader reader, Type type) |
| throws IOException, SerializationException { |
| if (!(type instanceof Class<?>)) { |
| throw new SerializationException("Cannot convert string to " + type + "."); |
| } |
| |
| String string = readString(reader); |
| |
| // Notify the listeners |
| if (jsonSerializerListeners != null) { |
| jsonSerializerListeners.readString(this, string); |
| } |
| |
| return BeanAdapter.coerce(string, (Class<?>)type); |
| } |
| |
| private Object readNumberValue(Reader reader, Type type) |
| throws IOException, SerializationException { |
| if (!(type instanceof Class<?>)) { |
| throw new SerializationException("Cannot convert number to " + type + "."); |
| } |
| |
| Number number = null; |
| |
| StringBuilder stringBuilder = new StringBuilder(); |
| boolean negative = false; |
| boolean integer = true; |
| |
| if (c == '+' || c == '-') { |
| negative = (c == '-'); |
| c = reader.read(); |
| } |
| |
| while (c != -1 && (Character.isDigit(c) || c == '.' |
| || c == 'e' || c == 'E' || c == '-')) { |
| stringBuilder.append((char)c); |
| integer &= !(c == '.'); |
| c = reader.read(); |
| } |
| |
| if (integer) { |
| long value = Long.parseLong(stringBuilder.toString()) * (negative ? -1 : 1); |
| |
| if (value > Integer.MAX_VALUE |
| || value < Integer.MIN_VALUE) { |
| number = value; |
| } else { |
| number = (int)value; |
| } |
| } else { |
| number = Double.parseDouble(stringBuilder.toString()) * (negative ? -1.0d : 1.0d); |
| } |
| |
| // Notify the listeners |
| if (jsonSerializerListeners != null) { |
| jsonSerializerListeners.readNumber(this, number); |
| } |
| |
| return BeanAdapter.coerce(number, (Class<?>)type); |
| } |
| |
| private Object readBooleanValue(Reader reader, Type type) |
| throws IOException, SerializationException { |
| if (!(type instanceof Class<?>)) { |
| throw new SerializationException("Cannot convert number to " + type + "."); |
| } |
| |
| String text = (c == 't') ? "true" : "false"; |
| int n = text.length(); |
| int i = 0; |
| |
| while (c != -1 && i < n) { |
| if (text.charAt(i) != c) { |
| throw new SerializationException("Unexpected character in input stream."); |
| } |
| |
| c = reader.read(); |
| i++; |
| } |
| |
| if (i < n) { |
| throw new SerializationException("Incomplete boolean value in input stream."); |
| } |
| |
| // Get the boolean value |
| Boolean value = Boolean.parseBoolean(text); |
| |
| // Notify the listeners |
| if (jsonSerializerListeners != null) { |
| jsonSerializerListeners.readBoolean(this, value); |
| } |
| |
| return BeanAdapter.coerce(value, (Class<?>)type); |
| } |
| |
| @SuppressWarnings("unchecked") |
| private Object readListValue(Reader reader, Type type) |
| throws IOException, SerializationException { |
| List<Object> list = null; |
| Type itemType = null; |
| |
| if (type == Object.class) { |
| // Return the default list and item types |
| list = new ArrayList<Object>(); |
| itemType = Object.class; |
| } else { |
| // Determine the item type from generic parameters |
| Type parentType = type; |
| while (parentType != null) { |
| if (parentType instanceof ParameterizedType) { |
| ParameterizedType parameterizedType = (ParameterizedType)parentType; |
| Class<?> rawType = (Class<?>)parameterizedType.getRawType(); |
| |
| if (List.class.isAssignableFrom(rawType)) { |
| itemType = parameterizedType.getActualTypeArguments()[0]; |
| } |
| |
| break; |
| } else { |
| Class<?> classType = (Class<?>)parentType; |
| Type[] genericInterfaces = classType.getGenericInterfaces(); |
| |
| for (int i = 0; i < genericInterfaces.length; i++) { |
| Type genericInterface = genericInterfaces[i]; |
| |
| if (genericInterface instanceof ParameterizedType) { |
| ParameterizedType parameterizedType = (ParameterizedType)genericInterface; |
| Class<?> interfaceType = (Class<?>)parameterizedType.getRawType(); |
| |
| if (List.class.isAssignableFrom(interfaceType)) { |
| itemType = parameterizedType.getActualTypeArguments()[0]; |
| |
| if (itemType instanceof TypeVariable<?>) { |
| itemType = Object.class; |
| } |
| |
| break; |
| } |
| } |
| } |
| |
| if (itemType != null) { |
| break; |
| } |
| |
| parentType = classType.getGenericSuperclass(); |
| } |
| } |
| |
| if (itemType == null) { |
| throw new SerializationException("Could not determine list item type."); |
| } |
| |
| // Instantiate the list type |
| Class<?> listType; |
| if (type instanceof ParameterizedType) { |
| ParameterizedType parameterizedType = (ParameterizedType)type; |
| listType = (Class<?>)parameterizedType.getRawType(); |
| } else { |
| listType = (Class<?>)type; |
| } |
| |
| try { |
| list = (List<Object>)listType.newInstance(); |
| } catch (InstantiationException exception) { |
| throw new RuntimeException(exception); |
| } catch (IllegalAccessException exception) { |
| throw new RuntimeException(exception); |
| } |
| } |
| |
| // Notify the listeners |
| if (jsonSerializerListeners != null) { |
| jsonSerializerListeners.beginList(this, list); |
| } |
| |
| // Move to the next character after '[' |
| c = reader.read(); |
| skipWhitespaceAndComments(reader); |
| |
| while (c != -1 && c != ']') { |
| list.add(readValue(reader, itemType)); |
| skipWhitespaceAndComments(reader); |
| |
| if (c == ',') { |
| c = reader.read(); |
| skipWhitespaceAndComments(reader); |
| } else if (c == -1) { |
| throw new SerializationException("Unexpected end of input stream."); |
| } else { |
| if (c != ']') { |
| throw new SerializationException("Unexpected character in input stream."); |
| } |
| } |
| } |
| |
| // Move to the next character after ']' |
| c = reader.read(); |
| |
| // Notify the listeners |
| if (jsonSerializerListeners != null) { |
| jsonSerializerListeners.endList(this); |
| } |
| |
| return list; |
| } |
| |
| @SuppressWarnings("unchecked") |
| private Object readMapValue(Reader reader, Type type) |
| throws IOException, SerializationException { |
| Map<String, Object> map = null; |
| Type valueType = null; |
| |
| if (type == Object.class) { |
| // Return the default map and value types |
| map = new HashMap<String, Object>(); |
| valueType = Object.class; |
| } else { |
| // Determine the value type from generic parameters |
| Type parentType = type; |
| while (parentType != null) { |
| if (parentType instanceof ParameterizedType) { |
| ParameterizedType parameterizedType = (ParameterizedType)parentType; |
| Class<?> rawType = (Class<?>)parameterizedType.getRawType(); |
| |
| if (Map.class.isAssignableFrom(rawType)) { |
| valueType = parameterizedType.getActualTypeArguments()[1]; |
| } |
| |
| break; |
| } else { |
| Class<?> classType = (Class<?>)parentType; |
| Type[] genericInterfaces = classType.getGenericInterfaces(); |
| |
| for (int i = 0; i < genericInterfaces.length; i++) { |
| Type genericInterface = genericInterfaces[i]; |
| |
| if (genericInterface instanceof ParameterizedType) { |
| ParameterizedType parameterizedType = (ParameterizedType)genericInterface; |
| Class<?> interfaceType = (Class<?>)parameterizedType.getRawType(); |
| |
| if (Map.class.isAssignableFrom(interfaceType)) { |
| valueType = parameterizedType.getActualTypeArguments()[1]; |
| |
| if (valueType instanceof TypeVariable<?>) { |
| valueType = Object.class; |
| } |
| |
| break; |
| } |
| } |
| } |
| |
| if (valueType != null) { |
| break; |
| } |
| |
| parentType = classType.getGenericSuperclass(); |
| } |
| } |
| |
| // Instantiate the map or bean type |
| if (valueType == null) { |
| Class<?> beanType = (Class<?>)type; |
| |
| try { |
| map = new BeanAdapter(beanType.newInstance()); |
| } catch (InstantiationException exception) { |
| throw new RuntimeException(exception); |
| } catch (IllegalAccessException exception) { |
| throw new RuntimeException(exception); |
| } |
| } else { |
| Class<?> mapType; |
| if (type instanceof ParameterizedType) { |
| ParameterizedType parameterizedType = (ParameterizedType)type; |
| mapType = (Class<?>)parameterizedType.getRawType(); |
| } else { |
| mapType = (Class<?>)type; |
| } |
| |
| try { |
| map = (Map<String, Object>)mapType.newInstance(); |
| } catch (InstantiationException exception) { |
| throw new RuntimeException(exception); |
| } catch (IllegalAccessException exception) { |
| throw new RuntimeException(exception); |
| } |
| } |
| } |
| |
| // Notify the listeners |
| if (jsonSerializerListeners != null) { |
| jsonSerializerListeners.beginMap(this, map); |
| } |
| |
| // Move to the next character after '{' |
| c = reader.read(); |
| skipWhitespaceAndComments(reader); |
| |
| while (c != -1 && c != '}') { |
| String key = null; |
| |
| if (c == '"' || c == '\'') { |
| // The key is a delimited string |
| key = readString(reader); |
| } else { |
| // The key is an undelimited string; it must adhere to Java |
| // identifier syntax |
| StringBuilder keyBuilder = new StringBuilder(); |
| |
| if (!Character.isJavaIdentifierStart(c)) { |
| throw new SerializationException("Illegal identifier start character."); |
| } |
| |
| while (c != -1 |
| && c != ':' && !Character.isWhitespace(c)) { |
| if (!Character.isJavaIdentifierPart(c)) { |
| throw new SerializationException("Illegal identifier character."); |
| } |
| |
| keyBuilder.append((char)c); |
| c = reader.read(); |
| } |
| |
| if (c == -1) { |
| throw new SerializationException("Unexpected end of input stream."); |
| } |
| |
| key = keyBuilder.toString(); |
| } |
| |
| if (key == null |
| || key.length() == 0) { |
| throw new SerializationException("\"" + key + "\" is not a valid key."); |
| } |
| |
| // Notify listeners |
| if (jsonSerializerListeners != null) { |
| jsonSerializerListeners.readKey(this, key); |
| } |
| |
| skipWhitespaceAndComments(reader); |
| |
| if (c != ':') { |
| throw new SerializationException("Unexpected character in input stream."); |
| } |
| |
| // Move to the first character after ':' |
| c = reader.read(); |
| |
| if (valueType == null) { |
| // The map is a bean instance; get the generic type of the property |
| Type genericValueType = ((BeanAdapter)map).getGenericType(key); |
| |
| if (genericValueType != null) { |
| // Set the value in the bean |
| map.put(key, readValue(reader, genericValueType)); |
| } else { |
| // The property does not exist; ignore this value |
| readValue(reader, Object.class); |
| } |
| } else { |
| map.put(key, readValue(reader, valueType)); |
| } |
| |
| skipWhitespaceAndComments(reader); |
| |
| if (c == ',') { |
| c = reader.read(); |
| skipWhitespaceAndComments(reader); |
| } else if (c == -1) { |
| throw new SerializationException("Unexpected end of input stream."); |
| } else { |
| if (c != '}') { |
| throw new SerializationException("Unexpected character in input stream."); |
| } |
| } |
| } |
| |
| // Move to the first character after '}' |
| c = reader.read(); |
| |
| // Notify the listeners |
| if (jsonSerializerListeners != null) { |
| jsonSerializerListeners.endMap(this); |
| } |
| |
| return (map instanceof BeanAdapter) ? ((BeanAdapter)map).getBean() : map; |
| } |
| |
| /** |
| * Writes data to a JSON stream. |
| * |
| * @param object |
| * |
| * @param outputStream |
| * The output stream to which data will be written. |
| * |
| * @see #writeObject(Object, Writer) |
| */ |
| @Override |
| public void writeObject(Object object, OutputStream outputStream) |
| throws IOException, SerializationException { |
| if (outputStream == null) { |
| throw new IllegalArgumentException("outputStream is null."); |
| } |
| |
| Writer writer = new BufferedWriter(new OutputStreamWriter(outputStream, charset), BUFFER_SIZE); |
| writeObject(object, writer); |
| } |
| |
| /** |
| * Writes data to a JSON stream. |
| * |
| * @param object |
| * The object to serialize. Must be one of the following types: |
| * |
| * <ul> |
| * <li>pivot.collections.Map</li> |
| * <li>pivot.collections.List</li> |
| * <li>java.lang.String</li> |
| * <li>java.lang.Number</li> |
| * <li>java.lang.Boolean</li> |
| * <li><tt>null</tt></li> |
| * </ul> |
| * |
| * @param writer |
| * The writer to which data will be written. |
| */ |
| @SuppressWarnings("unchecked") |
| public void writeObject(Object object, Writer writer) |
| throws IOException, SerializationException { |
| if (writer == null) { |
| throw new IllegalArgumentException("writer is null."); |
| } |
| |
| if (object == null) { |
| writer.append("null"); |
| } else if (object instanceof String) { |
| String string = (String)object; |
| StringBuilder stringBuilder = new StringBuilder(); |
| |
| for (int i = 0, n = string.length(); i < n; i++) { |
| char c = string.charAt(i); |
| |
| switch(c) { |
| case '\t': { |
| stringBuilder.append("\\t"); |
| break; |
| } |
| |
| case '\n': { |
| stringBuilder.append("\\n"); |
| break; |
| } |
| |
| case '\\': |
| case '\"': |
| case '\'': { |
| stringBuilder.append("\\" + c); |
| break; |
| } |
| |
| default: { |
| if (charset.name().startsWith("UTF") |
| || c <= 0xFF) { |
| stringBuilder.append(c); |
| } else { |
| stringBuilder.append("\\u"); |
| stringBuilder.append(String.format("%04x", (short)c)); |
| } |
| } |
| } |
| |
| } |
| |
| writer.append("\"" + stringBuilder.toString() + "\""); |
| } else if (object instanceof Number) { |
| Number number = (Number)object; |
| |
| if (number instanceof Float) { |
| Float f = (Float)number; |
| if (f.isNaN() |
| || f.isInfinite()) { |
| throw new SerializationException(number + " is not a valid value."); |
| } |
| } else if (number instanceof Double) { |
| Double d = (Double)number; |
| if (d.isNaN() |
| || d.isInfinite()) { |
| throw new SerializationException(number + " is not a valid value."); |
| } |
| } |
| |
| writer.append(number.toString()); |
| } else if (object instanceof Boolean) { |
| writer.append(object.toString()); |
| } else if (object instanceof List<?>) { |
| List<Object> list= (List<Object>)object; |
| writer.append("["); |
| |
| int i = 0; |
| for (Object item : list) { |
| if (i > 0) { |
| writer.append(", "); |
| } |
| |
| writeObject(item, writer); |
| i++; |
| } |
| |
| writer.append("]"); |
| } else { |
| Map<String, Object> map; |
| if (object instanceof Map<?, ?>) { |
| map = (Map<String, Object>)object; |
| } else { |
| map = new BeanAdapter(object); |
| } |
| |
| writer.append("{"); |
| |
| int i = 0; |
| for (Map.Entry<String, Object> entry : map.entrySet()) { |
| String key = entry.getKey(); |
| Object value = entry.getValue(); |
| |
| boolean identifier = true; |
| StringBuilder keyStringBuilder = new StringBuilder(); |
| |
| for (int j = 0, n = key.length(); j < n; j++) { |
| char c = key.charAt(j); |
| identifier &= Character.isJavaIdentifierPart(c); |
| |
| if (c == '"') { |
| keyStringBuilder.append('\\'); |
| } |
| |
| keyStringBuilder.append(c); |
| } |
| |
| key = keyStringBuilder.toString(); |
| |
| if (i > 0) { |
| writer.append(", "); |
| } |
| |
| // Write the key |
| if (!identifier || alwaysDelimitMapKeys) { |
| writer.append('"'); |
| } |
| |
| writer.append(key); |
| |
| if (!identifier || alwaysDelimitMapKeys) { |
| writer.append('"'); |
| } |
| |
| writer.append(": "); |
| |
| // Write the value |
| writeObject(value, writer); |
| |
| i++; |
| } |
| |
| writer.append("}"); |
| } |
| |
| writer.flush(); |
| } |
| |
| @Override |
| public String getMIMEType(Object object) { |
| return MIME_TYPE + "; charset=" + charset.name(); |
| } |
| |
| /** |
| * Converts a JSON value to a Java object. |
| * |
| * @param json |
| * The JSON value. |
| * |
| * @return |
| * The parsed object. |
| */ |
| public static Object parse(String json) throws SerializationException { |
| JSONSerializer jsonSerializer = new JSONSerializer(); |
| |
| Object object; |
| try { |
| object = jsonSerializer.readObject(new StringReader(json)); |
| } catch(IOException exception) { |
| throw new RuntimeException(exception); |
| } |
| |
| return object; |
| } |
| |
| /** |
| * Converts a JSON value to a string. |
| * |
| * @param json |
| * The JSON value. |
| * |
| * @return |
| * The parsed string. |
| */ |
| public static String parseString(String json) throws SerializationException { |
| return (String)parse(json); |
| } |
| |
| /** |
| * Converts a JSON value to a number. |
| * |
| * @param json |
| * The JSON value. |
| * |
| * @return |
| * The parsed number. |
| */ |
| public static Number parseNumber(String json) throws SerializationException { |
| return (Number)parse(json); |
| } |
| |
| /** |
| * Converts a JSON value to a short. |
| * |
| * @param json |
| * The JSON value. |
| * |
| * @return |
| * The parsed short. |
| */ |
| public static Short parseShort(String json) throws SerializationException { |
| return (Short)parse(json); |
| } |
| |
| /** |
| * Converts a JSON value to a integer. |
| * |
| * @param json |
| * The JSON value. |
| * |
| * @return |
| * The parsed integer. |
| */ |
| public static Integer parseInteger(String json) throws SerializationException { |
| return (Integer)parse(json); |
| } |
| |
| /** |
| * Converts a JSON value to a long. |
| * |
| * @param json |
| * The JSON value. |
| * |
| * @return |
| * The parsed number. |
| */ |
| public static Long parseLong(String json) throws SerializationException { |
| return (Long)parse(json); |
| } |
| |
| /** |
| * Converts a JSON value to a float. |
| * |
| * @param json |
| * The JSON value. |
| * |
| * @return |
| * The parsed float. |
| */ |
| public static Float parseFloat(String json) throws SerializationException { |
| return (Float)parse(json); |
| } |
| |
| /** |
| * Converts a JSON value to a double. |
| * |
| * @param json |
| * The JSON value. |
| * |
| * @return |
| * The parsed double. |
| */ |
| public static Double parseDouble(String json) throws SerializationException { |
| return (Double)parse(json); |
| } |
| |
| /** |
| * Converts a JSON value to a boolean. |
| * |
| * @param json |
| * The JSON value. |
| * |
| * @return |
| * The parsed boolean. |
| */ |
| public static Boolean parseBoolean(String json) throws SerializationException { |
| return (Boolean)parse(json); |
| } |
| |
| /** |
| * Converts a JSON value to a list. |
| * |
| * @param json |
| * The JSON value. |
| * |
| * @return |
| * The parsed list. |
| */ |
| public static List<?> parseList(String json) throws SerializationException { |
| return (List<?>)parse(json); |
| } |
| |
| /** |
| * Converts a JSON value to a map. |
| * |
| * @param json |
| * The JSON value. |
| * |
| * @return |
| * The parsed map. |
| */ |
| @SuppressWarnings("unchecked") |
| public static Map<String, ?> parseMap(String json) throws SerializationException { |
| return (Map<String, ?>)parse(json); |
| } |
| |
| /** |
| * Converts a object to a JSON string representation. The map keys will always |
| * be quote-delimited. |
| * |
| * @param value |
| * The object to convert. |
| * |
| * @return |
| * The resulting JSON string. |
| * |
| * @see #toString(Object, boolean) |
| */ |
| public static String toString(Object value) throws SerializationException { |
| return toString(value, false); |
| } |
| |
| /** |
| * Converts a object to a JSON string representation. |
| * |
| * @param value |
| * The object to convert. |
| * |
| * @param alwaysDelimitMapKeys |
| * A flag indicating whether or not map keys will always be quote-delimited. |
| * |
| * @return |
| * The resulting JSON string. |
| */ |
| public static String toString(Object value, boolean alwaysDelimitMapKeys) throws SerializationException { |
| JSONSerializer jsonSerializer = new JSONSerializer(); |
| jsonSerializer.setAlwaysDelimitMapKeys(alwaysDelimitMapKeys); |
| |
| StringWriter writer = new StringWriter(); |
| |
| try { |
| jsonSerializer.writeObject(value, writer); |
| } catch(IOException exception) { |
| throw new RuntimeException(exception); |
| } |
| |
| return writer.toString(); |
| } |
| |
| public ListenerList<JSONSerializerListener> getJSONSerializerListeners() { |
| if (jsonSerializerListeners == null) { |
| jsonSerializerListeners = new JSONSerializerListenerList(); |
| } |
| |
| return jsonSerializerListeners; |
| } |
| } |