/*******************************************************************************
* Copyright © 2011, 2013 IBM Corporation 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:
* IBM Corporation - initial API and implementation
*
*******************************************************************************/
package eglx.json;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.List;
import java.util.Map;
import org.eclipse.edt.javart.AnyBoxedObject;
import org.eclipse.edt.javart.Executable;
import org.eclipse.edt.javart.json.ArrayNode;
import org.eclipse.edt.javart.json.BooleanNode;
import org.eclipse.edt.javart.json.DecimalNode;
import org.eclipse.edt.javart.json.FloatingPointNode;
import org.eclipse.edt.javart.json.IntegerNode;
import org.eclipse.edt.javart.json.Json;
import org.eclipse.edt.javart.json.JsonParser;
import org.eclipse.edt.javart.json.JsonUtilities;
import org.eclipse.edt.javart.json.NameValuePairNode;
import org.eclipse.edt.javart.json.NullNode;
import org.eclipse.edt.javart.json.ObjectNode;
import org.eclipse.edt.javart.json.ParseException;
import org.eclipse.edt.javart.json.StringNode;
import org.eclipse.edt.javart.json.ValueNode;
import org.eclipse.edt.javart.messages.Message;
import org.eclipse.edt.javart.resources.ExecutableBase;
import org.eclipse.edt.javart.util.DateTimeUtil;
import org.eclipse.edt.runtime.java.eglx.lang.AnyValue;
import org.eclipse.edt.runtime.java.eglx.lang.EAny;
import org.eclipse.edt.runtime.java.eglx.lang.EBigint;
import org.eclipse.edt.runtime.java.eglx.lang.EBoolean;
import org.eclipse.edt.runtime.java.eglx.lang.EDate;
import org.eclipse.edt.runtime.java.eglx.lang.EDecimal;
import org.eclipse.edt.runtime.java.eglx.lang.EDictionary;
import org.eclipse.edt.runtime.java.eglx.lang.EFloat;
import org.eclipse.edt.runtime.java.eglx.lang.EInt;
import org.eclipse.edt.runtime.java.eglx.lang.EList;
import org.eclipse.edt.runtime.java.eglx.lang.ESmallfloat;
import org.eclipse.edt.runtime.java.eglx.lang.ESmallint;
import org.eclipse.edt.runtime.java.eglx.lang.EString;
import org.eclipse.edt.runtime.java.eglx.lang.ETime;
import org.eclipse.edt.runtime.java.eglx.lang.ETimestamp;
import org.eclipse.edt.runtime.java.eglx.lang.NullType;
import com.ibm.icu.text.SimpleDateFormat;
import eglx.http.Response;
import eglx.lang.AnyException;
import eglx.lang.InvalidArgumentException;
import eglx.lang.StringLib;
public class JsonLib {
public static String convertToJSON(Object obj){
return process(obj).toJson();
}
public static String convertToJSON(Response response){
EDictionary httpResponse = new EDictionary();
httpResponse.put("headers", response.getHeaders());
httpResponse.put("body", response.body);
httpResponse.put("status", response.status);
httpResponse.put("statusMessage", response.statusMessage);
return process(httpResponse).toJson();
}
public static ValueNode convertToJsonNode(Object obj){
return process(obj);
}
private static ValueNode process(Object object)throws AnyException
{
if(object == null)
return new NullNode();
//first check to see if there is a boxed object that is null
if(object instanceof AnyBoxedObject<?> &&
((AnyBoxedObject<?>)object).ezeUnbox() == null)
return new NullNode();
//EDate and ETimestamp must be done before AnyBoxedObject because they are an AnyBoxedObject
if(object instanceof EDate)
return new StringNode(StringLib.format(((EDate)object).ezeUnbox(), "yyyy-MM-dd"), false);
if(object instanceof ETime)
return new StringNode(StringLib.format(((ETime)object).ezeUnbox(), "HH:mm:ss"), false);
if(object instanceof ETimestamp)
return new StringNode(StringLib.format(((ETimestamp)object).ezeUnbox(), "yyyy-MM-dd HH:mm:ss"), false);
if(object instanceof AnyBoxedObject<?>)
return process(((AnyBoxedObject<?>)object).ezeUnbox());
if(object instanceof BigDecimal)
return new DecimalNode((BigDecimal)object);
if(object instanceof BigInteger)
return new IntegerNode((BigInteger)object);
if(object instanceof Byte)
return new IntegerNode((Byte)object);
if(object instanceof Short)
return new IntegerNode((Short)object);
if(object instanceof Integer)
return new IntegerNode((Integer)object);
if(object instanceof Long)
return new IntegerNode((Long)object);
if(object instanceof Boolean)
return new BooleanNode((Boolean)object);
if(object instanceof Double)
return new FloatingPointNode((Double)object);
if(object instanceof Float)
return new FloatingPointNode((Float)object);
if(object instanceof String)
return new StringNode((String)object, false);
if(object instanceof Enum<?>)
return new IntegerNode(((Enum<?>)object).ordinal() + 1);
if(object instanceof Calendar)
return process((Calendar)object);
if(object instanceof List<?>)
return process((List<?>)object);
if(object instanceof EDictionary)
return process((EDictionary)object);
if(object instanceof AnyValue)
return processObject(object);
if(object instanceof Map)
return process((Map<Object, Object>)object);
if(object instanceof ExecutableBase)
return processObject(object);
if(object instanceof AnyException)
return processObject(object);
InvalidArgumentException ex = new InvalidArgumentException();
throw ex.fillInMessage( Message.SOA_E_JSON_TYPE_EXCEPTION, object.getClass().getName() );
}
private static ValueNode process(List<?> array)throws AnyException
{
ArrayNode node = new ArrayNode();
for(Object object : array)
{
node.addValue(process(object));
}
return node;
}
private static ValueNode process(Calendar calendar)throws AnyException
{
String format = null;
if(calendar.isSet(Calendar.HOUR_OF_DAY) ||
calendar.isSet(Calendar.MINUTE) ||
calendar.isSet(Calendar.SECOND) ||
calendar.isSet(Calendar.MILLISECOND)){
//timestamp
format = "yyyy-MM-dd HH:mm:ss";
}
else{
//date
format = "yyyy-MM-dd";
}
return new StringNode(StringLib.format(calendar, format), false);
}
private static ValueNode process(EDictionary dictionary)throws AnyException
{
ObjectNode objectNode = new ObjectNode();
String[] contents = dictionary.getKeyArray();
for(int i = 0; i < contents.length; i++){
objectNode.addPair(new NameValuePairNode(new StringNode(contents[i], false), process(dictionary.get(contents[i]))));
}
return objectNode;
}
private static ValueNode process(Map<Object, Object> map)throws AnyException
{
ObjectNode objectNode = new ObjectNode();
for(Map.Entry<Object, Object> entry : map.entrySet()){
objectNode.addPair(new NameValuePairNode(
new StringNode(((entry.getKey() == null || entry.getKey().toString() == null) ? "" : entry.getKey().toString()), false),
process(entry.getValue())));
}
return objectNode;
}
private static ValueNode processObject(Object object)throws AnyException
{
//get fields
// if there is a json name use the name and get the field value
// else if is there a get or is method
// use the json name
// else if method is public
ObjectNode objectNode = new ObjectNode();
String name;
for(Field field : getFields(object.getClass())){
name = field.getName();
try {
Annotation annot = field.getAnnotation(Json.class);
if(annot != null){
name = ((Json)annot).name();
}
StringBuilder getterName = new StringBuilder(field.getName().substring(0,1).toUpperCase());
getterName.append(field.getName().substring(1));
Method method = null;
try{
method = object.getClass().getMethod(new StringBuilder("get").append(getterName).toString(), (Class<?>[])null);
}
catch(NoSuchMethodException nsme){
}
if(method == null){
try{
method = object.getClass().getMethod(new StringBuilder("is").append(getterName).toString(), (Class<?>[])null);
}
catch(NoSuchMethodException nsme){
}
}
Class<?> type = null;
String[] asOptions = null;
if(method != null && Modifier.isPublic(method.getModifiers())){
annot = method.getAnnotation(Json.class);
if(annot != null){
name = ((Json)annot).name();
type = ((Json)annot).clazz();
asOptions = ((Json)annot).asOptions();
}
objectNode.addPair(new NameValuePairNode(new StringNode(name, false), process(eglx.json.JsonUtilities.wrapCalendar(method.invoke(object, (Object[])null), type, asOptions))));
}
else if(Modifier.isPublic(field.getModifiers())){
annot = field.getAnnotation(Json.class);
if(annot != null){
name = ((Json)annot).name();
type = ((Json)annot).clazz();
asOptions = ((Json)annot).asOptions();
}
objectNode.addPair(new NameValuePairNode(new StringNode(name, false), process(eglx.json.JsonUtilities.wrapCalendar(field.get(object), type, asOptions))));
}
} catch (Throwable t) {
InvalidArgumentException ex = new InvalidArgumentException();
ex.initCause(t);
throw ex.fillInMessage( Message.SOA_E_JSON_FIELD_TYPE_EXCEPTION, name, object.getClass().getName() );
}
}
return objectNode;
}
public static void convertFromJSON(String jsonString, Object obj)throws AnyException{
try {
convertToEgl(obj, JsonParser.parseValue(jsonString));
} catch (ParseException e) {
InvalidArgumentException ex = new InvalidArgumentException();
ex.initCause(e);
throw ex.fillInMessage( Message.SOA_E_WS_PROXY_PARAMETERS_JSON2EGL, obj, jsonString );
}
}
public static Object convertToEgl(ValueNode jsonValue) throws AnyException{
return convertToEgl(null, jsonValue);
}
public static Object convertToEgl(Object object, ValueNode jsonValue) throws AnyException{
if(object instanceof AnyBoxedObject)
{
convertToEgl(((AnyBoxedObject<?>)object).ezeUnbox(), jsonValue);
}
else if(object instanceof ExecutableBase && jsonValue instanceof ObjectNode){
convertObject(object, (ObjectNode)jsonValue);
}
else if(object instanceof AnyValue && jsonValue instanceof ObjectNode){
convertObject(object, (ObjectNode)jsonValue);
}
else if(object instanceof EDictionary && jsonValue instanceof ObjectNode){
((EDictionary)object).removeAll();
convertObject((EDictionary)object, (ObjectNode)jsonValue);
}
else if(object == null && jsonValue instanceof ObjectNode){
convertObject(new EDictionary(), (ObjectNode)jsonValue);
}
else if(jsonValue instanceof NullNode){
object = null;
}
return object;
}
static Object convertToEgl(final Class<?> fieldType, final String[] fieldTypeOptions, final Object field, ValueNode jsonValue) throws AnyException{
try{
if(jsonValue instanceof NullNode){
return null;
}
if(jsonValue instanceof ArrayNode){
List<Object> list = new ArrayList<Object>();
for(Object node : ((ArrayNode)jsonValue).getValues()){
list.add(convertToEgl(fieldType, fieldTypeOptions, null, (ValueNode)node));
}
return list;
}
if(fieldType.equals(EBigint.class)){
return EBigint.asBigint(jsonValue.toJava());
}
if(fieldType.equals(EBoolean.class) && jsonValue instanceof BooleanNode){
return ((BooleanNode)jsonValue).getValue();
}
if(fieldType.equals(EDate.class)){
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Calendar cal = DateTimeUtil.getBaseCalendar();
cal.setTimeInMillis(sdf.parse(jsonValue.toJava()).getTime());
cal.get(Calendar.YEAR);
return EDate.asDate(cal);
}
if(fieldType.equals(EDecimal.class)){
///get options for the as statement
if(fieldTypeOptions != null && fieldTypeOptions.length > 0){
int length = Integer.parseInt(fieldTypeOptions[0]);
int decimal = 0;
if(fieldTypeOptions.length > 1){
decimal = Integer.parseInt(fieldTypeOptions[1]);
}
return EDecimal.asDecimal(jsonValue.toJava(), length, decimal);
}
else{
return EDecimal.asDecimal(jsonValue.toJava());
}
}
if(fieldType.equals(EFloat.class)){
return EFloat.asFloat(jsonValue.toJava());
}
if(fieldType.equals(EInt.class)){
return EInt.asInt(jsonValue.toJava());
}
if(fieldType.equals(ESmallfloat.class)){
return ESmallfloat.asSmallfloat(jsonValue.toJava());
}
if(fieldType.equals(ESmallint.class)){
return ESmallint.asSmallint(jsonValue.toJava());
}
if(fieldType.equals(EString.class)){
if(fieldTypeOptions != null && fieldTypeOptions.length == 1){
int length = Integer.parseInt(fieldTypeOptions[0]);
return EString.asString(jsonValue.toJava(), length);
}
return jsonValue.toJava();
}
if(fieldType.equals(ETime.class)){
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
Calendar cal = DateTimeUtil.getBaseCalendar();
cal.setTimeInMillis(sdf.parse(jsonValue.toJava()).getTime());
cal.get(Calendar.YEAR);
return ETime.asTime(cal);
}
if(fieldType.equals(ETimestamp.class)){
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Calendar cal = DateTimeUtil.getBaseCalendar();
cal.setTimeInMillis(sdf.parse(jsonValue.toJava()).getTime());
cal.get(Calendar.YEAR);
int start = ETimestamp.YEAR_CODE;
int end = ETimestamp.SECOND_CODE;
if(fieldTypeOptions != null && fieldTypeOptions.length > 1){
start = eglx.json.JsonUtilities.getETimestampStaticField(fieldTypeOptions[0]);
end = eglx.json.JsonUtilities.getETimestampStaticField(fieldTypeOptions[1]);
}
return ETimestamp.asTimestamp(cal, start, end);
}
if(AnyValue.class.isAssignableFrom(fieldType) && jsonValue instanceof ObjectNode){
AnyValue anyValue;
if(field instanceof AnyValue){
anyValue = (AnyValue)field;
}
else {
anyValue = (AnyValue)fieldType.newInstance();
}
return convertObject(anyValue,(ObjectNode)jsonValue);
}
if(ExecutableBase.class.isAssignableFrom(fieldType) && jsonValue instanceof ObjectNode){
ExecutableBase base;
if(field instanceof ExecutableBase){
base = (ExecutableBase)field;
}
else {
base = (ExecutableBase)fieldType.newInstance();
}
return convertObject(base,(ObjectNode)jsonValue);
}
if(fieldType.isEnum() && jsonValue instanceof IntegerNode){
for(Object enumConstant : fieldType.getEnumConstants()){
if(enumConstant instanceof Enum){
int value;
try{
Method method = fieldType.getMethod("getValue", (Class[])null);
value = ((Integer)method.invoke(enumConstant, (Object[])null));
}
catch(NoSuchMethodException nsme){
value = ((Enum<?>)enumConstant).ordinal() + 1;
}
if(((IntegerNode)jsonValue).getBigIntegerValue().intValue() == value){
return enumConstant;
}
}
else{
//FIXME
}
}
}
if(jsonValue instanceof ObjectNode){
EDictionary dict;
if(field instanceof EDictionary){
dict = (EDictionary)field;
}
else {
dict = new EDictionary();
}
convertObject(dict, (ObjectNode)jsonValue);
return dict;
}
} catch (Throwable t) {
InvalidArgumentException ex = new InvalidArgumentException();
ex.initCause(t);
throw ex.fillInMessage( Message.SOA_E_WS_PROXY_PARAMETERS_JSON2EGL, field, jsonValue );
}
InvalidArgumentException ex = new InvalidArgumentException();
throw ex.fillInMessage( Message.SOA_E_WS_PROXY_PARAMETERS_JSON2EGL, field, jsonValue );
}
private static EDictionary convertObject(EDictionary dictionary, ObjectNode objectNode)
throws AnyException
{
for(Object pair : objectNode.getPairs()){
dictionary.put(((NameValuePairNode)pair).getName().getJavaValue(), convertJsonNode(((NameValuePairNode)pair).getValue()));
}
return dictionary;
}
private static Object convertJsonNode(ValueNode jsonValue){
Object retVal = null;
if(jsonValue instanceof ArrayNode){
retVal = new ArrayList<Object>();
for(Object node : ((ArrayNode)jsonValue).getValues()){
((List<Object>)retVal).add(convertJsonNode((ValueNode)node));
}
retVal = EList.ezeBox((List<Object>)retVal, "eglx.lang.EList<eglx.lang.EAny>");
}
else if(jsonValue instanceof BooleanNode){
retVal = EBoolean.ezeBox(((BooleanNode)jsonValue).getValue());
}
else if(jsonValue instanceof DecimalNode){
retVal = EDecimal.ezeBox(EDecimal.asDecimal(jsonValue.toJava()));
}
else if(jsonValue instanceof FloatingPointNode){
retVal = EFloat.ezeBox(EFloat.asFloat(jsonValue.toJava()));
}
else if(jsonValue instanceof IntegerNode){
retVal = EDecimal.ezeBox(EDecimal.asDecimal(jsonValue.toJava()));
}
else if(jsonValue instanceof NullNode){
return NullType.ezeWrap(null);
}
else if(jsonValue instanceof ObjectNode){
retVal = convertToEgl(EDictionary.class, null, null, jsonValue);
}
else if(jsonValue instanceof StringNode){
retVal = EString.ezeBox(jsonValue.toJava());
}
if(!(retVal instanceof eglx.lang.EAny)){
retVal = EAny.ezeBox(retVal);
}
return retVal;
}
private static Object convertObject(Object object, ObjectNode objectNode)
throws AnyException
{
//get fields
// if there is a json name use the name and get the field value
// else if is there a get or is method
// use the json name
// else if method is public
String name;
for(Field field : getFields(object.getClass())){
name = field.getName();
try {
Json jsonAnnotation = field.getAnnotation(Json.class);
if(jsonAnnotation != null){
name = ((Json)jsonAnnotation).name();
}
StringBuilder setterName = new StringBuilder(field.getName().substring(0,1).toUpperCase());
setterName.append(field.getName().substring(1));
Method setMethod = null;
try{
setMethod = object.getClass().getMethod(new StringBuilder("set").append(setterName).toString(), field.getType());
}
catch(NoSuchMethodException nsme){
}
Method getMethod = null;
try{
getMethod = object.getClass().getMethod(new StringBuilder("get").append(setterName).toString(), (Class<?>[])null);
}
catch(NoSuchMethodException nsme){
}
if(getMethod == null){
try{
getMethod = object.getClass().getMethod(new StringBuilder("is").append(setterName).toString(), (Class<?>[])null);
}
catch(NoSuchMethodException nsme){
}
}
Object existingFieldObject = null;
if(getMethod != null && Modifier.isPublic(getMethod.getModifiers())){
if(getMethod.getAnnotation(Json.class) != null){
jsonAnnotation = getMethod.getAnnotation(Json.class);
name = ((Json)jsonAnnotation).name();
}
existingFieldObject = getMethod.invoke(object, (Object[])null);
}
else if(Modifier.isPublic(field.getModifiers())){
existingFieldObject = field.get(object);
}
if(jsonAnnotation != null && (getMethod != null && Modifier.isPublic(getMethod.getModifiers()) ||
Modifier.isPublic(field.getModifiers()))){
Object newEglValue = convertToEgl(jsonAnnotation.clazz(), jsonAnnotation.asOptions(), existingFieldObject, JsonUtilities.getValueNode(objectNode, name));
if(setMethod != null && Modifier.isPublic(setMethod.getModifiers())){
setMethod.invoke(object, newEglValue);
}
else if(Modifier.isPublic(field.getModifiers())){
field.set(object, newEglValue);
}
}
} catch (Throwable t) {
InvalidArgumentException ex = new InvalidArgumentException();
ex.initCause(t);
throw ex.fillInMessage( Message.SOA_E_JSON_FIELD_TYPE_EXCEPTION, name, object.getClass().getName() );
}
}
return object;
}
private static List<Field> getFields(Class<?> clazz){
List<Field> fields = new ArrayList<Field>();
do{
fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
clazz = clazz.getSuperclass();
}while(eglx.lang.EAny.class.isAssignableFrom(clazz) || Executable.class.isAssignableFrom(clazz));
return fields;
}
}