/* * Copyright (C) 2011. All rights reserved. */ package ro.isdc.wro.extensions.script; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.mozilla.javascript.EcmaError; import org.mozilla.javascript.JavaScriptException; import org.mozilla.javascript.RhinoException; import org.mozilla.javascript.ScriptableObject; /** * Direct conversion between native Rhino objects and JSON. * <p> * This class can be used directly in Rhino. * <p> * This class was inspired from {@link http * ://mongodb-rhino.googlecode.com/svn/trunk/modules/com.mongodb.rhino/src/com/mongodb/rhino/JSON.java} * * @author Alex Objelean * @since 1.3.6 */ public class RhinoUtils { private static final Pattern[] ESCAPE_PATTERNS = new Pattern[] { Pattern.compile("\\\\"), Pattern.compile("\\n"), Pattern.compile("\\r"), Pattern.compile("\\t"), Pattern.compile("\\f"), Pattern.compile("\\\"") }; private static final String[] ESCAPE_REPLACEMENTS = new String[] { Matcher.quoteReplacement("\\\\"), Matcher.quoteReplacement("\\n"), Matcher.quoteReplacement("\\r"), Matcher.quoteReplacement("\\t"), Matcher.quoteReplacement("\\f"), Matcher.quoteReplacement("\\\"") }; /** * Prevent instantiation. */ private RhinoUtils() { } /** * Creates a more detailed message based on {@link RhinoException} thrown by rhino execution. The message will contain * a detailed description of the problem by inspecting the JSON value provided by exception. * * @param e * {@link RhinoException} thrown by rhino execution. * @return detailed string message. */ public static String createExceptionMessage(final RhinoException e) { StringBuffer message = new StringBuffer("Could not execute the script because: \n"); if (e instanceof JavaScriptException) { message.append(toJson(((JavaScriptException) e).getValue())); } else if (e instanceof EcmaError) { final EcmaError ecmaError = (EcmaError) e; message.append(String.format("Error message: %s at line: %s. \nSource: %s", ecmaError.getErrorMessage(), ecmaError.lineNumber(), ecmaError.lineSource())); } else { message.append(e.getMessage()); } return message.toString(); } /** * Recursively convert from native Rhino to JSON. * <p> * Recognizes JavaScript objects, arrays and primitives. * <p> * Special support for JavaScript dates: converts to {"$date": timestamp} in JSON. * <p> * Special support for MongoDB ObjectId: converts to {"$oid": "objectid"} in JSON. * <p> * Also recognizes JVM types: java.util.Map, java.util.Collection, java.util.Date. * * @param object * A Rhino native object * @return The JSON string * @see RhinoUtils#convertSpecial(Object) */ public static String toJson(final Object object) { return toJson(object, true); } /** * Recursively convert from native Rhino to JSON. * <p> * Recognizes JavaScript objects, arrays and primitives. * <p> * Special support for JavaScript dates: converts to {"$date": timestamp} in JSON. * <p> * Special support for MongoDB ObjectId: converts to {"$oid": "objectid"} in JSON. * <p> * Also recognizes JVM types: java.util.Map, java.util.Collection, java.util.Date. * * @param object * A Rhino native object * @param indent * Whether to indent the JSON for human readability * @return The JSON string */ public static String toJson(final Object object, final boolean indent) { final StringBuilder s = new StringBuilder(); encode(s, object, indent, indent ? 0 : -1); return s.toString(); } private static void encode(final StringBuilder s, final Object object, final boolean indent, final int depth) { if (indent) indent(s, depth); if (object == null) { s.append("null"); } else if (object instanceof Number) { s.append(object); } else if (object instanceof Boolean) { s.append(object); } else if (object instanceof Date) { final HashMap<String, Long> map = new HashMap<String, Long>(); map.put("$date", ((Date) object).getTime()); encode(s, map, depth); } else if (object instanceof Collection) { encode(s, (Collection<?>) object, depth); } else if (object instanceof Map) { encode(s, (Map<?, ?>) object, depth); // } else if (object instanceof NativeArray) { // final NativeArray nativeArray = (NativeArray) object; // encode(s, nativeArray, depth); } else if (object instanceof ScriptableObject) { final ScriptableObject scriptable = (ScriptableObject) object; if (scriptable.getClassName().equals("Date")) { // (The NativeDate class is private in Rhino, but we can access // it like a regular object.) final Object time = ScriptableObject.callMethod(scriptable, "getTime", null); if (time instanceof Number) { encode(s, new Date(((Number) time).longValue()), false, depth); return; } } encode(s, scriptable, depth); } else { s.append('\"'); s.append(escape(object.toString())); s.append('\"'); } } private static void encode(final StringBuilder s, final Collection<?> collection, final int depth) { s.append('['); final Iterator<?> i = collection.iterator(); if (i.hasNext()) { if (depth > -1) { s.append('\n'); } while (true) { final Object value = i.next(); encode(s, value, true, depth > -1 ? depth + 1 : -1); if (i.hasNext()) { s.append(','); if (depth > -1) { s.append('\n'); } } else break; } if (depth > -1) { s.append('\n'); } } if (depth > -1) { indent(s, depth); } s.append(']'); } private static void encode(final StringBuilder s, final Map<?, ?> map, final int depth) { s.append('{'); final Iterator<?> i = map.entrySet().iterator(); if (i.hasNext()) { if (depth > -1) { s.append('\n'); } while (true) { final Map.Entry<?, ?> entry = (Map.Entry<?, ?>) i.next(); final String key = entry.getKey().toString(); final Object value = entry.getValue(); if (depth > -1) { indent(s, depth + 1); } s.append('\"'); s.append(escape(key)); s.append("\":"); if (depth > -1) { s.append(' '); } encode(s, value, false, depth > -1 ? depth + 1 : -1); if (i.hasNext()) { s.append(','); if (depth > -1) { s.append('\n'); } } else break; } if (depth > -1) { s.append('\n'); } } if (depth > -1) { indent(s, depth); } s.append('}'); } private static void encode(final StringBuilder s, final ScriptableObject object, final int depth) { s.append('{'); final Object[] ids = object.getAllIds(); if (ids.length > 0) { if (depth > -1) { s.append('\n'); } final int length = ids.length; for (int i = 0; i < length; i++) { final String key = ids[i].toString(); final Object value = ScriptableObject.getProperty(object, key); if (depth > -1) { indent(s, depth + 1); } s.append('\"'); s.append(escape(key)); s.append("\":"); if (depth > -1) { s.append(' '); } encode(s, value, false, depth > -1 ? depth + 1 : -1); if (i < length - 1) { s.append(','); if (depth > -1) { s.append('\n'); } } } if (depth > -1) { s.append('\n'); } } if (depth > -1) { indent(s, depth); } s.append('}'); } private static void indent(final StringBuilder s, final int depth) { for (int i = depth - 1; i >= 0; i--) { s.append(" "); } } private static String escape(final String string) { String result = null; for (int i = 0, length = ESCAPE_PATTERNS.length; i < length; i++) { result = ESCAPE_PATTERNS[i].matcher(string).replaceAll(ESCAPE_REPLACEMENTS[i]); } return result; } }