/* 
 * $Id: JSONValue.java,v 1.1 2006/04/15 14:37:04 platform Exp $ 
 * Created on 2006-4-15 
 */
package mine.allen.simplejson;

import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.Writer;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import mine.allen.simplejson.parser.JSONParser;
import mine.allen.simplejson.parser.ParseException;

public class JSONValue {
	/**
	 * Parse JSON text into java object from the input source. Please use
	 * parseWithException() if you don't want to ignore the exception.
	 * 
	 * @see org.json.simple.parser.JSONParser#parse(Reader)
	 * @see #parseWithException(Reader)
	 * 
	 * @param in
	 * @return Instance of the following: org.json.simple.JSONObject,
	 *         org.json.simple.JSONArray, java.lang.String, java.lang.Number,
	 *         java.lang.Boolean, null
	 * 
	 */
	public static Object parse(Reader in) {
		try {
			JSONParser parser = new JSONParser();
			return parser.parse(in);
		} catch (Exception e) {
			return null;
		}
	}

	public static Object parse(String s) {
		StringReader in = new StringReader(s);
		return parse(in);
	}

	/**
	 * Parse JSON text into java object from the input source.
	 * 
	 * @see org.json.simple.parser.JSONParser
	 * 
	 * @param in
	 * @return Instance of the following: org.json.simple.JSONObject,
	 *         org.json.simple.JSONArray, java.lang.String, java.lang.Number,
	 *         java.lang.Boolean, null
	 * 
	 * @throws IOException
	 * @throws ParseException
	 */
	public static Object parseWithException(Reader in) throws IOException, ParseException {
		JSONParser parser = new JSONParser();
		return parser.parse(in);
	}

	public static Object parseWithException(String s) throws ParseException {
		JSONParser parser = new JSONParser();
		return parser.parse(s);
	}

	/**
	 * Encode an object into JSON text and write it to out.
	 * <p>
	 * If this object is a Map or a List, and it's also a JSONStreamAware or a
	 * JSONAware, JSONStreamAware or JSONAware will be considered firstly.
	 * <p>
	 * DO NOT call this method from writeJSONString(Writer) of a class that
	 * implements both JSONStreamAware and (Map or List) with "this" as the
	 * first parameter, use JSONObject.writeJSONString(Map, Writer) or
	 * JSONArray.writeJSONString(List, Writer) instead.
	 * 
	 * @see org.json.simple.JSONObject#writeJSONString(Map, Writer)
	 * @see org.json.simple.JSONArray#writeJSONString(List, Writer)
	 * 
	 * @param value
	 * @param writer
	 */
	public static void writeJSONString(Object value, Writer out) throws IOException {
		if (value == null) {
			out.write("null");
			return;
		}

		if (value instanceof String) {
			out.write('\"');
			out.write(escape((String) value));
			out.write('\"');
			return;
		}

		if (value instanceof Double) {
			if (((Double) value).isInfinite() || ((Double) value).isNaN())
				out.write("null");
			else
				out.write(value.toString());
			return;
		}

		if (value instanceof Float) {
			if (((Float) value).isInfinite() || ((Float) value).isNaN())
				out.write("null");
			else
				out.write(value.toString());
			return;
		}

		if (value instanceof Number) {
			out.write(value.toString());
			return;
		}

		if (value instanceof Boolean) {
			out.write(value.toString());
			return;
		}

		if ((value instanceof JSONStreamAware)) {
			((JSONStreamAware) value).writeJSONString(out);
			return;
		}

		if ((value instanceof JSONAware)) {
			out.write(((JSONAware) value).toJSONString());
			return;
		}

		if (value instanceof Map) {
			JSONObject.writeJSONString((Map<String, Object>) value, out);
			return;
		}

		if (value instanceof List) {
			JSONArray.writeJSONString((List<Object>) value, out);
			return;
		}

		out.write(value.toString());
	}

	/**
	 * Convert an object to JSON text.
	 * <p>
	 * If this object is a Map or a List, and it's also a JSONAware, JSONAware
	 * will be considered firstly.
	 * <p>
	 * DO NOT call this method from toJSONString() of a class that implements
	 * both JSONAware and Map or List with "this" as the parameter, use
	 * JSONObject.toJSONString(Map) or JSONArray.toJSONString(List) instead.
	 * 
	 * @see org.json.simple.JSONObject#toJSONString(Map)
	 * @see org.json.simple.JSONArray#toJSONString(List)
	 * 
	 * @param value
	 * @return JSON text, or "null" if value is null or it's an NaN or an INF
	 *         number.
	 */
	public static String toJSONString(Object value) {
		if (value == null)
			return "null";

		if (value instanceof String)
			return "\"" + escape((String) value) + "\"";

		if (value instanceof Double) {
			if (((Double) value).isInfinite() || ((Double) value).isNaN())
				return "null";
			else
				return value.toString();
		}

		if (value instanceof Float) {
			if (((Float) value).isInfinite() || ((Float) value).isNaN())
				return "null";
			else
				return value.toString();
		}

		if (value instanceof Number)
			return value.toString();

		if (value instanceof Boolean)
			return value.toString();

		if ((value instanceof JSONAware))
			return ((JSONAware) value).toJSONString();

		if (value instanceof Map)
			return JSONObject.toJSONString((Map<String, Object>) value);

		if (value instanceof List)
			return JSONArray.toJSONString((List<Object>) value);

		return value.toString();
	}

	/**
	 * Escape quotes, \, /, \r, \n, \b, \f, \t and other control characters
	 * (U+0000 through U+001F).
	 * 
	 * @param s
	 * @return
	 */
	public static String escape(String s) {
		if (s == null)
			return null;
		StringBuffer sb = new StringBuffer();
		escape(s, sb);
		return sb.toString();
	}

	/**
	 * @param s
	 *            - Must not be null.
	 * @param sb
	 */
	static void escape(String s, StringBuffer sb) {
		for (int i = 0; i < s.length(); i++) {
			char ch = s.charAt(i);
			switch (ch) {
			case '"':
				sb.append("\\\"");
				break;
			case '\\':
				sb.append("\\\\");
				break;
			case '\b':
				sb.append("\\b");
				break;
			case '\f':
				sb.append("\\f");
				break;
			case '\n':
				sb.append("\\n");
				break;
			case '\r':
				sb.append("\\r");
				break;
			case '\t':
				sb.append("\\t");
				break;
			case '/':
				sb.append("\\/");
				break;
			default:
				// Reference: http://www.unicode.org/versions/Unicode5.1.0/
				if ((ch >= '\u0000' && ch <= '\u001F') || (ch >= '\u007F' && ch <= '\u009F')
						|| (ch >= '\u2000' && ch <= '\u20FF')) {
					String ss = Integer.toHexString(ch);
					sb.append("\\u");
					for (int k = 0; k < 4 - ss.length(); k++) {
						sb.append('0');
					}
					sb.append(ss.toUpperCase());
				} else {
					sb.append(ch);
				}
			}
		} // for
	}

	public static JSONObject wrapBean(Object bean)
			throws InvocationTargetException, IllegalAccessException, IllegalArgumentException {
		JSONObject jo = new JSONObject();
		Class<? extends Object> klass = bean.getClass();
		// If klass is a System class then set includeSuperClass to false.
		boolean includeSuperClass = klass.getClassLoader() != null;
		Method[] methods = includeSuperClass ? klass.getMethods() : klass.getDeclaredMethods();

		for (int i = 0; i < methods.length; i += 1) {
			try {
				Method method = methods[i];
				if (Modifier.isPublic(method.getModifiers())) {
					String name = method.getName();
					String key = "";
					if (name.startsWith("get")) {
						if ("getClass".equals(name) || "getDeclaringClass".equals(name)) {
							key = "";
						} else {
							key = name.substring(3);
						}
					} else if (name.startsWith("is")) {
						key = name.substring(2);
					}
					if (key.length() > 0 && Character.isUpperCase(key.charAt(0))
							&& method.getParameterTypes().length == 0) {
						if (key.length() == 1) {
							key = key.toLowerCase();
						} else if (!Character.isUpperCase(key.charAt(1))) {
							key = key.substring(0, 1).toLowerCase() + key.substring(1);
						}

						Object result = method.invoke(bean, (Object[]) null);
						if (result != null) {
							jo.put(key, wrap(result));
						}
					}
				}
			} catch (Exception ignore) {
			}
		}
		return jo;
	}

	public static JSONArray wrapArray(Object array) throws ParseException {
		JSONArray ja = new JSONArray();
		if (array.getClass().isArray()) {
			int length = Array.getLength(array);
			for (int i = 0; i < length; i += 1) {
				ja.add(wrap(Array.get(array, i)));
			}
		} else if (array instanceof Collection) {
			@SuppressWarnings("unchecked")
			Collection<Object> collection = (Collection<Object>) array;
			if (collection != null) {
				Iterator<Object> iter = collection.iterator();
				while (iter.hasNext()) {
					ja.add(wrap(iter.next()));
				}
			}
		} else {
			throw new ParseException(0,"JSONArray initial value should be a string or collection or array.");
		}
		return ja;
	}

	/**
	 * Wrap an object, if necessary. If the object is null, return the NULL
	 * object. If it is an array or collection, wrap it in a JSONArray. If it is
	 * a map, wrap it in a JSONObject. If it is a standard property (Double,
	 * String, et al) then it is already wrapped. Otherwise, if it comes from
	 * one of the java packages, turn it into a string. And if it doesn't, try
	 * to wrap it in a JSONObject. If the wrapping fails, then null is returned.
	 * 
	 * @param object
	 *            The object to wrap
	 * @return The wrapped value
	 */
	public static Object wrap(Object object) {
		try {
			if (object == null) {
				return null;
			}
			if (object instanceof JSONObject || object instanceof JSONArray || object instanceof Byte
					|| object instanceof Character || object instanceof Short || object instanceof Integer
					|| object instanceof Long || object instanceof Boolean || object instanceof Float
					|| object instanceof Double || object instanceof String || object instanceof Date) {
				return object;
			}

			if (object instanceof Collection) {
				return wrapArray((Collection) object);
			}
			if (object.getClass().isArray()) {
				return wrapArray(object);
			}
			if (object instanceof Map) {
				return new JSONObject((Map) object);
			}
			Package objectPackage = object.getClass().getPackage();
			String objectPackageName = objectPackage != null ? objectPackage.getName() : "";
			if (objectPackageName.startsWith("java.") || objectPackageName.startsWith("javax.")
					|| object.getClass().getClassLoader() == null) {
				return object.toString();
			}
			return wrapBean(object);
		} catch (Exception exception) {
			return null;
		}
	}

	public static <T> T toBean(JSONObject json, Class<T> pojo) throws Exception {
		// 根据传入的Class动态生成pojo对象
		T obj = pojo.newInstance();
		boolean includeSuperClass = pojo.getClassLoader() != null;
		Method[] methods = includeSuperClass ? pojo.getMethods() : pojo.getDeclaredMethods();

		for (int i = 0; i < methods.length; i += 1) {
			try {
				Method method = methods[i];
				if (Modifier.isPublic(method.getModifiers())) {
					String name = method.getName();
					String key = "";
					if (name.startsWith("set")) {
						key = name.substring(3);
					}
					if (key.length() > 0 && Character.isUpperCase(key.charAt(0))
							&& method.getParameterTypes().length == 1) {
						if (key.length() == 1) {
							key = key.toLowerCase();
						} else if (!Character.isUpperCase(key.charAt(1))) {
							key = key.substring(0, 1).toLowerCase() + key.substring(1);
						}
						// method.invoke(obj, new
						// Object[]{method.getParameterTypes()[0].cast(json.get(key))});
						method.invoke(obj, new Object[] { to(json.get(key), method.getParameterTypes()[0]) });
					}
				}
			} catch (Exception ignore) {
				ignore.printStackTrace();
			}
		}
		return obj;
	}

	public static <T> T toArray(JSONArray ja, Class<T> pojo)
			throws InstantiationException, IllegalAccessException {
		Class<? extends Object> temp = pojo.getComponentType();
		T obj = null;
		if (pojo.getName().equals("java.util.Collection")) {
			obj = (T) new ArrayList<Object>();
			for (int i = 0; i < ja.size(); i++) {
				((Collection) obj).add(to(ja.get(i), temp));
			}
		} else if (pojo.getName().equals("java.util.List")) {
			obj = (T) new ArrayList();
			for (int i = 0; i < ja.size(); i++) {
				((List) obj).add(to(ja.get(i), temp));
			}
		} else if (pojo.isArray()) {
			int length = ja.size();
			obj = (T) Array.newInstance(temp, length);
			for (int i = 0; i < length; i++) {
				Array.set(obj, i, to(ja.get(i), temp));
			}
		}
		return obj;
	}

	public static <T> Object to(Object object, Class<T> klass) {
		try {
			if (object == null) {
				return null;
			}
			if (object instanceof String || object instanceof Date) {
				return object;
			}
			if (object instanceof Long) {
				Long num = (Long) object;
				if (klass.getName().equals("Integer") || klass.getName().equals("int")) {
					return num.intValue();
				}
				if (klass.getName().equals("Float") || klass.getName().equals("float")) {
					return num.floatValue();
				}
				if (klass.getName().equals("Short") || klass.getName().equals("short")) {
					return num.shortValue();
				}
				if (klass.getName().equals("Byte") || klass.getName().equals("byte")) {
					return num.byteValue();
				}
				if (klass.getName().equals("Long") || klass.getName().equals("long")) {
					return num;
				}
				if (klass.getName().equals("Double") || klass.getName().equals("double")) {
					return num.doubleValue();
				}
			}
			if (object instanceof JSONArray) {
				return toArray((JSONArray) object, klass);
			}
			return toBean((JSONObject) object, klass);
		} catch (Exception exception) {
			exception.printStackTrace();
			return null;
		}
	}

}
