/*
 * Copyright 2004-2015 the Seasar Foundation and the Others.
 *
 * 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 org.seasar.framework.util;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.seasar.framework.beans.BeanDesc;
import org.seasar.framework.beans.PropertyDesc;
import org.seasar.framework.beans.factory.BeanDescFactory;

/**
 * JSON形式の文字列を解析します。 ObjectをJSON文字列へシリアライズ、JSON文字列をObjectへ評価します。
 * 
 * @author mopemope
 * @deprecated このクラスの代わりに<a
 *             href="http://jsonic.sourceforge.jp/">JSONIC</a>等を使用してください
 */
public class JSONSerializer {
    private static final String COMMA = ",";

    private static final String COLON = ":";

    private static final String SINGLE_QUOTE = "'";

    private static final String DOUBLE_QUOTE = "\"";

    private static final String NULL_STRING = "null";

    private static final String TRUE_STRING = "true";

    private static final String FALSE_STRING = "false";

    private static final String START_BRACKET = "[";

    private static final String END_BRACKET = "]";

    private static final String START_BRACE = "{";

    private static final String END_BRACE = "}";

    /**
     * ObjectをJSONにシリアライズします。
     * 
     * @param o
     *            シリアライズ対象{@link java.lang.Object}
     * @return JSON形式の文字列
     */
    public static String serialize(Object o) {
        StringBuffer buf = new StringBuffer(100);
        appendSerializeObject(buf, o);
        return buf.toString();
    }

    /**
     * 指定したバッファにJSONシリアライズした結果を追加します。
     * 
     * @param buf
     *            {@link java.lang.StringBuffer}
     * @param o
     *            シリアライズ対象{@link java.lang.Object}
     */
    public static void appendSerializeObject(StringBuffer buf, Object o) {
        if (o == null) {
            buf.append(JSONSerializer.NULL_STRING);
        } else if (o instanceof String) {
            buf.append(quote((String) o));
        } else if (o instanceof Float) {
            appendSerializeFloat(buf, (Float) o);
        } else if (o instanceof Double) {
            appendSerializeDouble(buf, (Double) o);
        } else if (o instanceof Number) {
            buf.append(o.toString());
        } else if (o instanceof Boolean) {
            appendSerializeBoolean(buf, (Boolean) o);
        } else if (o instanceof Collection) {
            appendSerializeArray(buf, ((Collection) o).toArray());
        } else if (o instanceof Object[]) {
            appendSerializeArray(buf, (Object[]) o);
        } else if (o instanceof Map) {
            appendSerializeMap(buf, (Map) o);
        } else if (o.getClass().isArray()) {
            appendSerializeObjectArray(buf, o);
        } else {
            appendSerializeBean(buf, o);
        }
    }

    /**
     * 指定したバッファにJSONシリアライズした結果を追加します。
     * 
     * @param buf
     *            {@link java.lang.StringBuffer}
     * @param f
     *            シリアライズ対象{@link java.lang.Float}
     */
    public static void appendSerializeFloat(StringBuffer buf, Float f) {
        if (f.isNaN() || f.isInfinite()) {
            throw new IllegalArgumentException(f.toString());
        }
        buf.append(f.toString());
    }

    /**
     * 指定したバッファにJSONシリアライズした結果を追加します。
     * 
     * @param buf
     *            {@link java.lang.StringBuffer}
     * @param d
     *            シリアライズ対象{@link java.lang.Double}
     */
    public static void appendSerializeDouble(StringBuffer buf, Double d) {
        if (d.isNaN() || d.isInfinite()) {
            throw new IllegalArgumentException(d.toString());
        }
        buf.append(d.toString());
    }

    /**
     * 指定したバッファにJSONシリアライズした結果を追加します。
     * 
     * @param buf
     *            {@link java.lang.StringBuffer}
     * @param b
     *            シリアライズ対象{@link java.lang.Boolean}
     */
    public static void appendSerializeBoolean(StringBuffer buf, Boolean b) {
        if (Boolean.TRUE.equals(b)) {
            buf.append(JSONSerializer.TRUE_STRING);
        } else {
            buf.append(JSONSerializer.FALSE_STRING);
        }
    }

    /**
     * 指定したバッファにJSONシリアライズした結果を追加します。
     * 
     * @param buf
     *            {@link java.lang.StringBuffer}
     * @param array
     *            シリアライズ対象{@link java.lang.Object}配列
     */
    public static void appendSerializeArray(StringBuffer buf, Object[] array) {
        int length = array.length;
        buf.append(JSONSerializer.START_BRACKET);
        for (int i = 0; i < length; ++i) {
            appendSerializeObject(buf, array[i]);
            buf.append(JSONSerializer.COMMA);
        }
        if (length > 0) {
            buf.setLength(buf.length() - 1);
        }
        buf.append(JSONSerializer.END_BRACKET);
    }

    /**
     * 指定したバッファにJSONシリアライズした結果を追加します。
     * 
     * @param buf
     *            {@link java.lang.StringBuffer}
     * @param map
     *            シリアライズ対象{@link java.util.Map}
     */
    public static void appendSerializeMap(StringBuffer buf, Map map) {
        buf.append(JSONSerializer.START_BRACE);
        for (Iterator i = map.keySet().iterator(); i.hasNext();) {
            String key = (String) i.next();
            buf.append(quote(key) + JSONSerializer.COLON);
            appendSerializeObject(buf, map.get(key));
            buf.append(JSONSerializer.COMMA);
        }
        if (map.size() > 0) {
            buf.setLength(buf.length() - 1);
        }
        buf.append(JSONSerializer.END_BRACE);
    }

    /**
     * 指定したバッファにJSONシリアライズした結果を追加します。
     * 
     * @param buf
     *            {@link java.lang.StringBuffer}
     * @param bean
     *            シリアライズ対象{@link java.lang.Object}
     */
    public static void appendSerializeBean(StringBuffer buf, Object bean) {
        buf.append(JSONSerializer.START_BRACE);
        BeanDesc beanDesc = BeanDescFactory.getBeanDesc(bean.getClass());
        for (int i = 0; i < beanDesc.getPropertyDescSize(); ++i) {
            PropertyDesc pd = beanDesc.getPropertyDesc(i);
            buf.append(pd.getPropertyName() + JSONSerializer.COLON);
            appendSerializeObject(buf, pd.getValue(bean));
            buf.append(JSONSerializer.COMMA);
        }
        if (beanDesc.getPropertyDescSize() > 0) {
            buf.setLength(buf.length() - 1);
        }
        buf.append(JSONSerializer.END_BRACE);
    }

    /**
     * 指定したバッファにJSONシリアライズした結果を追加します。
     * 
     * @param buf
     *            {@link java.lang.StringBuffer}
     * @param o
     *            シリアライズ対象{@link java.lang.Object}
     */
    public static void appendSerializeObjectArray(StringBuffer buf, Object o) {
        int length = Array.getLength(o);
        buf.append(JSONSerializer.START_BRACKET);
        for (int i = 0; i < length; i++) {
            Object value = Array.get(o, i);
            appendSerializeObject(buf, value);
            buf.append(JSONSerializer.COMMA);
        }
        if (length > 0) {
            buf.setLength(buf.length() - 1);
        }
        buf.append(JSONSerializer.END_BRACKET);
    }

    /**
     * 文字列を引用付で囲みます。
     * 
     * @param str
     *            対象の文字列
     * @return 引用付で囲まれた{@link java.lang.String}
     */
    public static String quote(String str) {
        if (str == null || str.length() == 0) {
            return "\"\"";
        }
        char current = 0;
        int len = str.length();
        StringBuffer sb = new StringBuffer(len + 4);
        sb.append('"');
        for (int i = 0; i < len; ++i) {
            current = str.charAt(i);
            switch (current) {
            case '\\':
            case '/':
            case '"':
                sb.append('\\');
                sb.append(current);
                break;
            case '\b':
                sb.append("\\b");
                break;
            case '\t':
                sb.append("\\t");
                break;
            case '\n':
                sb.append("\\n");
                break;
            case '\f':
                sb.append("\\f");
                break;
            case '\r':
                sb.append("\\r");
                break;
            default:
                if (current < ' ') {
                    String t = "000" + Integer.toHexString(current);
                    sb.append("\\u" + t.substring(t.length() - 4));
                } else {
                    sb.append(current);
                }
            }
        }
        sb.append('"');
        return sb.toString();
    }

    /**
     * 指定した文字列がJSONのObject形式か判断します。
     * 
     * @param str
     *            対象の文字列
     * @return JSONのObject形式であればtrue、そうでなければfalse
     */
    public static boolean isObject(String str) {
        return str.startsWith(JSONSerializer.START_BRACE)
                && str.endsWith(JSONSerializer.END_BRACE);
    }

    /**
     * 指定した文字列がJSONのArray形式か判断します。
     * 
     * @param str
     *            対象の文字列
     * @return JSONのArray形式であればtrue、そうでなければfalse
     */
    public static boolean isArray(String str) {
        return str.startsWith(JSONSerializer.START_BRACKET)
                && str.endsWith(JSONSerializer.END_BRACKET);
    }

    /**
     * 指定した文字列がJSONのString形式か判断します。
     * 
     * @param str
     *            対象の文字列
     * @return JSONのString形式であればtrue、そうでなければfalse
     */
    public static boolean isString(String str) {
        return (str.startsWith(JSONSerializer.SINGLE_QUOTE)
                && str.endsWith(JSONSerializer.SINGLE_QUOTE) || str
                .startsWith(JSONSerializer.DOUBLE_QUOTE)
                && str.endsWith(JSONSerializer.DOUBLE_QUOTE));
    }

    /**
     * 指定したJSON文字列を評価します。
     * 
     * @param str
     *            対象の文字列
     * @return 評価された{@link javac.lang.Object}
     */
    public static Object eval(String str) {
        if (JSONSerializer.isObject(str)) {
            return JSONSerializer.evalMap(str);
        } else if (JSONSerializer.isArray(str)) {
            return JSONSerializer.evalArray(str);
        } else if (JSONSerializer.isString(str)) {
            return JSONSerializer.evalString(str);
        } else {
            return JSONSerializer.evalInt(str);
        }
    }

    /**
     * 指定したJSON文字列を評価します。
     * 
     * @param str
     *            対象の文字列
     * @return 評価された{@link javac.util.Map}
     */
    public static Map evalMap(String str) {
        Map map = new HashMap();
        StringBuffer buf = new StringBuffer();
        String key = null;
        String value = null;
        boolean valueParse = false;
        boolean isListedData = false;
        boolean isMappedData = false;
        for (int i = 0; i < str.length(); i++) {
            char c = str.charAt(i);
            switch (c) {
            case '{':
                if (valueParse) {
                    String sub = str.substring(i, str.length() - 1);
                    int count = 0;
                    for (int j = 0; j < sub.length(); ++j) {
                        char sc = sub.charAt(j);
                        if (sc == '{') {
                            count++;
                            continue;
                        }
                        if (sc == '}' && count > 1) {
                            count--;
                        } else if (sc == '}' && count == 1) {
                            String mapStr = sub.substring(0, j + 1);
                            Map submap = JSONSerializer.evalMap(mapStr);
                            map.put(key, submap);
                            i = i + j;
                            isMappedData = true;
                            break;
                        }
                    }
                }
                break;
            case '}':
                break;
            case '[':
                if (valueParse) {
                    String sub = str.substring(i, str.length() - 1);
                    int count = 0;
                    for (int j = 0; j < sub.length(); ++j) {
                        char sc = sub.charAt(j);
                        if (sc == '[') {
                            count++;
                            continue;
                        }
                        if (sc == ']' && count > 1) {
                            count--;
                        } else if (sc == ']' && count == 1) {
                            String arrayStr = sub.substring(0, j + 1);
                            List list = JSONSerializer.evalArray(arrayStr);
                            map.put(key, list);
                            i = i + j;
                            isListedData = true;
                            break;
                        }
                    }
                }
                break;
            case ']':
                break;
            case ':':
                key = JSONSerializer.evalString(buf.toString().trim());
                valueParse = true;
                buf = new StringBuffer();
                isListedData = false;
                isMappedData = false;
                break;
            case ',':
                if (!isListedData && !isMappedData) {
                    value = buf.toString().trim();
                    valueParse = false;
                    buf = new StringBuffer();
                    evalAndAddMap(key, value, map);
                }
                isListedData = false;
                isMappedData = false;
                break;
            default:
                buf.append(c);
                break;
            }
        }
        if (buf.length() > 0) {
            value = buf.toString().trim();
        }
        if (value != null) {
            evalAndAddMap(key, value, map);
        }
        return map;
    }

    /**
     * 指定したJSON文字列を評価します。
     * 
     * @param str
     *            対象の文字列
     * @return 評価された{@link javac.util.List}
     */
    public static List evalArray(String str) {
        List list = new ArrayList();
        StringBuffer buf = new StringBuffer();
        String value = null;
        boolean isMappedData = false;
        for (int i = 1; i < str.length() - 1; i++) {
            char c = str.charAt(i);
            switch (c) {
            case '{':
                String sub = str.substring(i, str.length() - 1);
                int count = 0;
                for (int j = 0; j < sub.length(); ++j) {
                    char sc = sub.charAt(j);
                    if (sc == '{') {
                        count++;
                        continue;
                    }
                    if (sc == '}' && count > 1) {
                        count--;
                    } else if (sc == '}' && count == 1) {
                        String mapStr = sub.substring(0, j + 1);
                        Map map = JSONSerializer.evalMap(mapStr);
                        list.add(map);
                        i = i + j;
                        isMappedData = true;
                        break;
                    }
                }
                break;
            case '}':
                break;
            case '[':
                buf.append(c);
                break;
            case ']':
                buf.append(c);
                break;
            case ':':
                break;
            case ',':
                if (!isMappedData) {
                    value = buf.toString().trim();
                    buf = new StringBuffer();
                    JSONSerializer.evalAndAddList(value, list);
                }
                isMappedData = false;
                break;
            default:
                buf.append(c);
                break;
            }
        }
        if (buf.length() > 0) {
            value = buf.toString().trim();
        }
        if (value != null) {
            JSONSerializer.evalAndAddList(value, list);
        }
        return list;
    }

    private static void evalAndAddList(String value, List list) {
        if (JSONSerializer.isObject(value)) {
            list.add(JSONSerializer.evalMap(value));
        } else if (JSONSerializer.isArray(value)) {
            list.add(JSONSerializer.evalArray(value));
        } else {
            Integer intValue = evalInt(value);
            if (intValue != null) {
                list.add(intValue);
            } else {
                list.add(JSONSerializer.evalString(value));
            }
        }
    }

    private static void evalAndAddMap(String key, String value, Map map) {
        if (JSONSerializer.isObject(value)) {
            Map v = JSONSerializer.evalMap(value);
            map.put(key, v);
        } else if (JSONSerializer.isArray(value)) {
            List list = JSONSerializer.evalArray(value);
            map.put(key, list);
        } else {
            Integer intValue = evalInt(value);
            if (intValue != null) {
                map.put(key, intValue);
            } else {
                map.put(key, JSONSerializer.evalString(value));
            }
        }
    }

    /**
     * 指定したJSON文字列を評価します。
     * 
     * @param str
     *            対象の文字列
     * @return 評価された{@link javac.lang.String}
     */
    public static String evalString(String str) {
        if (JSONSerializer.isString(str)) {
            return str.substring(1, str.length() - 1);
        }
        return str;
    }

    /**
     * 指定したJSON文字列を評価します。
     * 
     * @param str
     *            対象の文字列
     * @return 評価された{@link javac.lang.Integer}
     */
    public static Integer evalInt(String str) {
        try {
            int i = Integer.parseInt(str);
            return new Integer(i);
        } catch (NumberFormatException e) {
            return null;
        }
    }

}
