package com.senseidb.search.client.json;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import com.senseidb.search.client.ReflectionUtil;

public class JsonSerializer {
    public static Object serialize(Object object) {
        try {
      return serialize(object,true);
        } catch (Exception ex) {
          throw new RuntimeException(ex);
        }
    }
    public static Object serialize(Object object, boolean handleCustomJsonHandler) throws JSONException {
        if (object == null) {
            return null;
        }
        if (object instanceof String || object instanceof Number || object instanceof Boolean || object.getClass().isPrimitive() || object instanceof JSONObject) {
            return object;
        }
        CustomJsonHandler customJsonHandler = getCustomJsonHandlerByType(object.getClass());
        if (customJsonHandler != null && handleCustomJsonHandler) {
          JsonHandler jsonHandler = instantiate(customJsonHandler.value());
            return jsonHandler.serialize(object);
        }
        if (object.getClass().isEnum()) {
            return object.toString();
        }
        if (object instanceof Collection) {
            Collection collection = (Collection) object;
            List<Object> arr = new ArrayList<Object>(collection.size());
            for(Object obj : collection) {
                arr.add(serialize(obj));
            }
            return new JSONArray(arr);
        }
        if (object instanceof Map) {
            Map map = (Map) object;
            JSONObject ret = new JSONObject();
            for(Map.Entry entry : (Set<Map.Entry>) map.entrySet()) {
               ret.put(((String)entry.getKey()), serialize(entry.getValue()));
            }
            return ret;
        }
        // serialize object by reflection
        JSONObject ret = new JSONObject();
        for (Field field : object.getClass().getDeclaredFields()) {
            field.setAccessible(true);
            String name = field.getName();
            if (field.isAnnotationPresent(JsonField.class)) {
                name = field.getAnnotation(JsonField.class).value();
            }
            try {
                CustomJsonHandler customJsonHandlerAnnotation = getCustomJsonHandlerByField(field);
                Object fieldValue = field.get(object);
                if (customJsonHandlerAnnotation == null) {
                    ret.put(name, serialize(fieldValue));
                } else {
                    JsonHandler jsonHandler = instantiate(customJsonHandlerAnnotation.value());
                    Object fieldJson =  jsonHandler.serialize(fieldValue);
                    if (customJsonHandlerAnnotation.flatten() && fieldJson != null) {
                        String[] names = JSONObject.getNames((JSONObject)fieldJson);
                        if (names == null || names.length != 1) {
                            throw new IllegalStateException("It's impossible to flatten the JsonExpression " + fieldJson);
                        }
                        Object internalJson = ((JSONObject) fieldJson).opt(names[0]);
                        if (customJsonHandlerAnnotation.overrideColumnName()) {
                            name = names[0];
                        }
                        fieldJson = internalJson;
                    }
                    ret.put(name, fieldJson);
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
       return ret;
    }
    private static Map<Class<?>, CustomJsonHandler> jsonHandlersByType = Collections.synchronizedMap(new WeakHashMap<Class<?>, CustomJsonHandler>());
    private static Map<Field, CustomJsonHandler> jsonHandlersByField = Collections.synchronizedMap(new WeakHashMap<Field, CustomJsonHandler>());
    private static Map<Class<? extends JsonHandler>, JsonHandler> jsonHandlers = Collections.synchronizedMap(new WeakHashMap<Class<? extends JsonHandler>, JsonHandler>());
    private static CustomJsonHandler getCustomJsonHandlerByType(Class<?> cls) {
        if (!jsonHandlersByType.containsKey(cls)) {
            CustomJsonHandler customJsonHandler = (CustomJsonHandler) ReflectionUtil.getAnnotation(cls, CustomJsonHandler.class);
            jsonHandlersByType.put(cls, customJsonHandler);
        }
        return jsonHandlersByType.get(cls);
    }
    private static CustomJsonHandler getCustomJsonHandlerByField(Field field) {
        if (!jsonHandlersByField.containsKey(field)) {
            CustomJsonHandler customJsonHandler = field.getAnnotation(CustomJsonHandler.class);
            jsonHandlersByField.put(field, customJsonHandler);
        }
        return jsonHandlersByField.get(field);
    }
    private static JsonHandler instantiate(Class<? extends JsonHandler> cls) {
        if (!jsonHandlers.containsKey(cls)) {

            try {
                jsonHandlers.put(cls, cls.newInstance());
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        return jsonHandlers.get(cls);
    }
}

@@ -67,16 +67,51 @@
 .Field;%0A
+import java.lang.reflect.Modifier;%0A
 import j
@@ -523,24 +523,28 @@
 try %7B%0A      
+    
 return seria
@@ -649,24 +649,25 @@
     %7D%0A    %7D%0A
+%0A
     public s
@@ -770,66 +770,537 @@
     
-  if (object == null) %7B%0A            return null;%0A        %7D
+return serialize(new ArrayList%3CObject%3E(), object, handleCustomJsonHandler);%0A    %7D%0A%0A    private static Object serialize(List%3CObject%3E parents,%0A                                    Object object,%0A                                    boolean handleCustomJsonHandler) throws JSONException %7B%0A        if (object == null) %7B%0A            return null;%0A        %7D%0A        for(Object p : parents) %7B%0A            if (p == object) %7B%0A                // Loop reference%0A                return null;%0A            %7D%0A        %7D%0A        parents.add(object);
 %0A   
@@ -2135,19 +2135,34 @@
 rialize(
-obj
+parents, obj, true
 ));%0A    
@@ -2436,33 +2436,83 @@
 put(
-((String)entry.getKey()),
+serialize(parents, entry.getKey(), true).toString(),%0A                      
  ser
@@ -2518,16 +2518,25 @@
 rialize(
+parents, 
 entry.ge
@@ -2543,16 +2543,22 @@
 tValue()
+, true
 ));%0A    
@@ -2750,24 +2750,91 @@
 Fields()) %7B%0A
+            if (Modifier.isStatic(field.getModifiers())) continue;%0A
             
@@ -3316,32 +3316,41 @@
 name, serialize(
+parents, 
 fieldValue));%0A  
@@ -3343,16 +3343,22 @@
 eldValue
+, true
 ));%0A    
@@ -4411,24 +4411,69 @@
 %7D%0A        %7D%0A
+        parents.remove(parents.size() - 1);%0A 
        retur
@@ -4477,32 +4477,33 @@
 turn ret;%0A    %7D%0A
+%0A
     private stat

Fix for static and loop ref error.