/* * Copyright (c) 2013-2017 Cinchapi Inc. * * Licensed 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 com.cinchapi.concourse.util; import java.io.IOException; import java.util.Collection; import java.util.Map; import java.util.Map.Entry; import com.cinchapi.concourse.Link; import com.cinchapi.concourse.Tag; import com.cinchapi.concourse.thrift.TObject; import com.google.common.collect.Iterables; import com.google.gson.Gson; import com.google.gson.TypeAdapter; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter; /** * A collection of {@link TypeAdapter} factories to use when converting java * objects to their appropriate JSON representation. * * @author Jeff Nelson */ public class TypeAdapters { /** * Return the {@link TypeAdapter} that checks the size of a collection and * only converts them to a JSON array if necessary (i.e. a single item * collection will be converted to a single object in JSON). * * @return the {@link TypeAdapter} to use for conversions */ public static TypeAdapter<Collection<?>> forCollection() { return COLLECTION_TYPE_ADAPTER; } /** * Return the {@link TypeAdapter} that converts generic objects to the * correct JSON representation so they can be converted back to java if * necessary. * * @return the {@link TypeAdapter} to use for conversions */ public static TypeAdapter<Object> forGenericObject() { return JAVA_TYPE_ADAPTER; } /** * Return the {@link TypeAdapter} that converts {@link TObject TObjects} to * the correct JSON representation so they can be converted back to java if * necessary. * * @return the {@link TypeAdapter} to use for conversions */ public static TypeAdapter<TObject> forTObject() { return TOBJECT_TYPE_ADAPTER; } /** * Return the {@link TypeAdapter} that converts built-in {@link Map maps} to * the correct JSON representation by deferring to the * {@link #forGenericObject() java type adapter} when serializing values. * This enables Concourse Server to correct convert the resultant JSON * string back to a Java structure when necessary. * * @return the {@link TypeAdapter} to use for conversions */ public static TypeAdapter<Map<?, ?>> forMap() { return MAP_TYPE_ADAPTER; } /** * A singleton instance of the type adapter for generic objects to use * within this class. */ private static TypeAdapter<Object> JAVA_TYPE_ADAPTER = new TypeAdapter<Object>() { /** * A generic type adapter from a standard GSON instance that is used for * maintaining default deserialization semantics for non-primitive * objects. */ private final TypeAdapter<Object> generic = new Gson() .getAdapter(Object.class); @Override public Object read(JsonReader reader) throws IOException { return null; } @Override public void write(JsonWriter writer, Object value) throws IOException { if(value instanceof Double) { value = (Double) value; writer.value(value.toString() + "D"); } else if(value instanceof Link) { writer.value(value.toString()); } else if(value instanceof Tag) { writer.value("'" + value.toString() + "'"); } else if(value instanceof Number) { writer.value((Number) value); } else if(value instanceof Boolean) { writer.value((Boolean) value); } else if(value instanceof String) { writer.value((String) value); } else { generic.write(writer, value); } } }; /** * A singleton instance of the type adapter for tobjects to use within this * class. */ private static TypeAdapter<TObject> TOBJECT_TYPE_ADAPTER = new TypeAdapter<TObject>() { @Override public TObject read(JsonReader in) throws IOException { return null; } @Override public void write(JsonWriter out, TObject value) throws IOException { JAVA_TYPE_ADAPTER.write(out, Convert.thriftToJava(value)); } }; /** * A singleton instance to return from {@link #forMap()}. */ private static TypeAdapter<Map<?, ?>> MAP_TYPE_ADAPTER = new TypeAdapter<Map<?, ?>>() { @Override public void write(JsonWriter out, Map<?, ?> value) throws IOException { out.beginObject(); for (Entry<?, ?> entry : value.entrySet()) { out.name(entry.getKey().toString()); sendJsonValue(out, entry.getValue()); } out.endObject(); } @Override public Map<String, ?> read(JsonReader in) throws IOException { return null; } }; /** * A singleton instance of the type adapter for collections to use within * this class. */ private static TypeAdapter<Collection<?>> COLLECTION_TYPE_ADAPTER = new TypeAdapter<Collection<?>>() { @Override public Collection<?> read(JsonReader in) throws IOException { return null; } @Override public void write(JsonWriter out, Collection<?> value) throws IOException { // TODO: There is an open question about how to handle empty // collections. Right now, an empty JSON array is outputed, but // maybe we want to output null instead? if(value.size() == 1) { sendJsonValue(out, Iterables.get(value, 0)); } else { out.beginArray(); for (Object element : value) { sendJsonValue(out, element); } out.endArray(); } } }; /** * Check the type of {@code value} and send it the appropriate type * adapter with {@code out}. * * @param out the writer * @param value the value to write * @throws IOException */ private static void sendJsonValue(JsonWriter out, Object value) throws IOException { if(value instanceof TObject) { TOBJECT_TYPE_ADAPTER.write(out, (TObject) value); } else if(value instanceof Collection) { COLLECTION_TYPE_ADAPTER.write(out, (Collection<?>) value); } else if(value instanceof Map) { MAP_TYPE_ADAPTER.write(out, (Map<?, ?>) value); } else { JAVA_TYPE_ADAPTER.write(out, value); } } }