/*******************************************************************************
* Copyright (c) 2012-2016 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.everrest.core.impl.provider.json;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import org.everrest.core.impl.provider.json.JsonUtils.Types;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import static com.google.common.base.Throwables.propagateIfPossible;
import static com.google.common.collect.Sets.newHashSet;
import static java.util.concurrent.TimeUnit.MINUTES;
import static org.everrest.core.impl.provider.json.JsonUtils.Types.ARRAY_BOOLEAN;
import static org.everrest.core.impl.provider.json.JsonUtils.Types.ARRAY_BYTE;
import static org.everrest.core.impl.provider.json.JsonUtils.Types.ARRAY_CHAR;
import static org.everrest.core.impl.provider.json.JsonUtils.Types.ARRAY_DOUBLE;
import static org.everrest.core.impl.provider.json.JsonUtils.Types.ARRAY_FLOAT;
import static org.everrest.core.impl.provider.json.JsonUtils.Types.ARRAY_INT;
import static org.everrest.core.impl.provider.json.JsonUtils.Types.ARRAY_LONG;
import static org.everrest.core.impl.provider.json.JsonUtils.Types.ARRAY_OBJECT;
import static org.everrest.core.impl.provider.json.JsonUtils.Types.ARRAY_SHORT;
import static org.everrest.core.impl.provider.json.JsonUtils.Types.ARRAY_STRING;
import static org.everrest.core.impl.provider.json.JsonUtils.Types.COLLECTION;
import static org.everrest.core.impl.provider.json.JsonUtils.Types.MAP;
import static org.everrest.core.impl.provider.json.JsonUtils.getFieldName;
import static org.everrest.core.impl.provider.json.JsonUtils.getTransientFields;
public class JsonGenerator {
private static final Collection<String> SKIP_METHODS = newHashSet("getClass", "getMetaClass");
private static LoadingCache<Class<?>, JsonMethod[]> methodsCache = CacheBuilder.newBuilder()
.concurrencyLevel(8)
.maximumSize(256)
.expireAfterAccess(10, MINUTES)
.build(new CacheLoader<Class<?>, JsonMethod[]>() {
@Override
public JsonMethod[] load(Class<?> aClass)
throws Exception {
return getGetters(aClass);
}
});
private static JsonMethod[] getGetters(Class<?> aClass) {
Set<String> transientFieldNames = getTransientFields(aClass);
List<JsonMethod> result = new ArrayList<>();
for (Method method : aClass.getMethods()) {
if (shouldBeProcessed(method)) {
String field = getFieldName(method);
if (!transientFieldNames.contains(field)) {
result.add(new JsonMethod(method, field));
}
}
}
return result.toArray(new JsonMethod[result.size()]);
}
private static boolean shouldBeProcessed(Method method) {
return !SKIP_METHODS.contains(method.getName()) && isGetter(method);
}
private static boolean isGetter(Method method) {
String methodName = method.getName();
Class<?> returnType = method.getReturnType();
if (methodName.startsWith("get") && methodName.length() > 3) {
return method.getParameterTypes().length == 0;
} else if (methodName.startsWith("is") && methodName.length() > 2) {
return method.getParameterTypes().length == 0
&& (returnType == Boolean.class || returnType == boolean.class);
}
return false;
}
/* ------------------------------------------------------------------------------ */
/**
* Create JSON array from specified collection.
*
* @param collection
* source collection
* @return JSON representation of collection
* @throws JsonException
* if collection can't be transformed in JSON
* representation
*/
public static JsonValue createJsonArray(Collection<?> collection) throws JsonException {
if (collection == null) {
return new NullValue();
}
return createJsonValue(collection, COLLECTION);
}
/**
* Create JSON array from specified object. Parameter <code>array</code> must
* be array.
*
* @param array
* source array
* @return JSON representation of array
* @throws JsonException
* if array can't be transformed in JSON representation
*/
public static JsonValue createJsonArray(Object array) throws JsonException {
if (array == null) {
return new NullValue();
}
Types type = JsonUtils.getType(array);
if (type == ARRAY_BOOLEAN
|| type == ARRAY_BYTE
|| type == ARRAY_SHORT
|| type == ARRAY_INT
|| type == ARRAY_LONG
|| type == ARRAY_FLOAT
|| type == ARRAY_DOUBLE
|| type == ARRAY_CHAR
|| type == ARRAY_STRING
|| type == ARRAY_OBJECT) {
return createJsonValue(array, type);
} else {
throw new JsonException("Invalid argument, must be array.");
}
}
/**
* Create JSON object from specified map.
*
* @param map
* source map
* @return JSON representation of map
* @throws JsonException
* if map can't be transformed in JSON representation
*/
public static JsonValue createJsonObjectFromMap(Map<String, ?> map) throws JsonException {
if (map == null) {
return new NullValue();
}
return createJsonValue(map, MAP);
}
/**
* Create JSON object from specified object. Object must be conform with java
* bean structure.
*
* @param object
* source object
* @return JSON representation of object
* @throws JsonException
* if map can't be transformed in JSON representation
*/
public static JsonValue createJsonObject(Object object) throws JsonException {
if (object == null) {
return new NullValue();
}
Class<?> aClass = object.getClass();
JsonValue jsonRootValue = new ObjectValue();
JsonMethod[] getters;
try {
getters = methodsCache.get(aClass);
} catch (ExecutionException e) {
propagateIfPossible(e.getCause());
throw new JsonException(e.getCause());
}
for (JsonMethod getter : getters) {
try {
Object getterResult = getter.method.invoke(object);
Types getterResultType = JsonUtils.getType(getterResult);
if (getterResultType == null) {
jsonRootValue.addElement(getter.field, createJsonObject(getterResult));
} else {
jsonRootValue.addElement(getter.field, createJsonValue(getterResult, getterResultType));
}
} catch (InvocationTargetException | IllegalAccessException e) {
throw new JsonException(e.getMessage(), e);
}
}
return jsonRootValue;
}
@SuppressWarnings({"unchecked"})
private static JsonValue createJsonValue(Object object, Types type) throws JsonException {
switch (type) {
case NULL:
return new NullValue();
case BOOLEAN:
return new BooleanValue((Boolean)object);
case BYTE:
return new LongValue((Byte)object);
case SHORT:
return new LongValue((Short)object);
case INT:
return new LongValue((Integer)object);
case LONG:
return new LongValue((Long)object);
case FLOAT:
return new DoubleValue((Float)object);
case DOUBLE:
return new DoubleValue((Double)object);
case CHAR:
return new StringValue(Character.toString((Character)object));
case STRING:
return new StringValue((String)object);
case ENUM:
return new StringValue(((Enum)object).name());
case CLASS:
return new StringValue(((Class)object).getName());
case ARRAY_BOOLEAN: {
JsonValue jsonArray = new ArrayValue();
int length = Array.getLength(object);
for (int i = 0; i < length; i++) {
jsonArray.addElement(new BooleanValue(Array.getBoolean(object, i)));
}
return jsonArray;
}
case ARRAY_BYTE: {
JsonValue jsonArray = new ArrayValue();
int length = Array.getLength(object);
for (int i = 0; i < length; i++) {
jsonArray.addElement(new LongValue(Array.getByte(object, i)));
}
return jsonArray;
}
case ARRAY_SHORT: {
JsonValue jsonArray = new ArrayValue();
int length = Array.getLength(object);
for (int i = 0; i < length; i++) {
jsonArray.addElement(new LongValue(Array.getShort(object, i)));
}
return jsonArray;
}
case ARRAY_INT: {
JsonValue jsonArray = new ArrayValue();
int length = Array.getLength(object);
for (int i = 0; i < length; i++) {
jsonArray.addElement(new LongValue(Array.getInt(object, i)));
}
return jsonArray;
}
case ARRAY_LONG: {
JsonValue jsonArray = new ArrayValue();
int length = Array.getLength(object);
for (int i = 0; i < length; i++) {
jsonArray.addElement(new LongValue(Array.getLong(object, i)));
}
return jsonArray;
}
case ARRAY_FLOAT: {
JsonValue jsonArray = new ArrayValue();
int length = Array.getLength(object);
for (int i = 0; i < length; i++) {
jsonArray.addElement(new DoubleValue(Array.getFloat(object, i)));
}
return jsonArray;
}
case ARRAY_DOUBLE: {
JsonValue jsonArray = new ArrayValue();
int length = Array.getLength(object);
for (int i = 0; i < length; i++) {
jsonArray.addElement(new DoubleValue(Array.getDouble(object, i)));
}
return jsonArray;
}
case ARRAY_CHAR: {
JsonValue jsonArray = new ArrayValue();
int length = Array.getLength(object);
for (int i = 0; i < length; i++) {
jsonArray.addElement(new StringValue(Character.toString(Array.getChar(object, i))));
}
return jsonArray;
}
case ARRAY_STRING: {
JsonValue jsonArray = new ArrayValue();
int length = Array.getLength(object);
for (int i = 0; i < length; i++) {
jsonArray.addElement(new StringValue((String)Array.get(object, i)));
}
return jsonArray;
}
case ARRAY_OBJECT: {
JsonValue jsonArray = new ArrayValue();
int length = Array.getLength(object);
for (int i = 0; i < length; i++) {
Object item = Array.get(object, i);
Types itemType = JsonUtils.getType(item);
if (itemType == null) {
jsonArray.addElement(createJsonObject(item));
} else {
jsonArray.addElement(createJsonValue(item, itemType));
}
}
return jsonArray;
}
case COLLECTION: {
JsonValue jsonArray = new ArrayValue();
List<Object> list = new ArrayList<>((Collection<?>)object);
for (Object item : list) {
Types itemType = JsonUtils.getType(item);
if (itemType == null) {
jsonArray.addElement(createJsonObject(item));
} else {
jsonArray.addElement(createJsonValue(item, itemType));
}
}
return jsonArray;
}
case MAP:
JsonValue jsonObject = new ObjectValue();
Map<String, Object> map = (Map<String, Object>)object;
Set<String> keys = map.keySet();
for (String key : keys) {
Object item = map.get(key);
Types itemType = JsonUtils.getType(item);
if (itemType == null) {
jsonObject.addElement(key, createJsonObject(item));
} else {
jsonObject.addElement(key, createJsonValue(item, itemType));
}
}
return jsonObject;
default:
throw new IllegalStateException(String.format("Unsupported type %s", type));
}
}
}