1 /* 2 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package jdk.nashorn.internal.runtime; 27 28 import java.lang.invoke.MethodHandle; 29 import java.util.Iterator; 30 import java.util.List; 31 import jdk.nashorn.internal.ir.LiteralNode; 32 import jdk.nashorn.internal.ir.Node; 33 import jdk.nashorn.internal.ir.ObjectNode; 34 import jdk.nashorn.internal.ir.PropertyNode; 35 import jdk.nashorn.internal.ir.UnaryNode; 36 import jdk.nashorn.internal.parser.JSONParser; 37 import jdk.nashorn.internal.parser.TokenType; 38 import jdk.nashorn.internal.runtime.linker.Bootstrap; 39 40 /** 41 * Utilities used by "JSON" object implementation. 42 */ 43 public final class JSONFunctions { 44 private JSONFunctions() {} 45 private static final MethodHandle REVIVER_INVOKER = Bootstrap.createDynamicInvoker("dyn:call", Object.class, 46 ScriptFunction.class, ScriptObject.class, String.class, Object.class); 47 48 /** 49 * Returns JSON-compatible quoted version of the given string. 50 * 51 * @param str String to be quoted 52 * @return JSON-compatible quoted string 53 */ 54 public static String quote(final String str) { 55 return JSONParser.quote(str); 56 } 57 58 /** 59 * Parses the given JSON text string and returns object representation. 60 * 61 * @param text JSON text to be parsed 62 * @param reviver optional value: function that takes two parameters (key, value) 63 * @return Object representation of JSON text given 64 */ 65 public static Object parse(final Object text, final Object reviver) { 66 final String str = JSType.toString(text); 67 final Context context = Context.getContextTrusted(); 68 final JSONParser parser = new JSONParser( 69 new Source("<json>", str), 70 new Context.ThrowErrorManager(), 71 (context != null) ? 72 context.getEnv()._strict : 73 false); 74 75 Node node; 76 77 try { 78 node = parser.parse(); 79 } catch (final ParserException e) { 80 throw ECMAErrors.syntaxError(e, "invalid.json", e.getMessage()); 81 } 82 83 final ScriptObject global = Context.getGlobalTrusted(); 84 Object unfiltered = convertNode(global, node); 85 return applyReviver(global, unfiltered, reviver); 86 } 87 88 // -- Internals only below this point 89 90 // parse helpers 91 92 // apply 'reviver' function if available 93 private static Object applyReviver(final ScriptObject global, final Object unfiltered, final Object reviver) { 94 if (reviver instanceof ScriptFunction) { 95 assert global instanceof GlobalObject; 96 final ScriptObject root = ((GlobalObject)global).newObject(); 97 root.set("", unfiltered, root.isStrictContext()); 98 return walk(root, "", (ScriptFunction)reviver); 99 } 100 return unfiltered; 101 } 102 103 // This is the abstract "Walk" operation from the spec. 104 private static Object walk(final ScriptObject holder, final Object name, final ScriptFunction reviver) { 105 final Object val = holder.get(name); 106 if (val instanceof ScriptObject) { 107 final ScriptObject valueObj = (ScriptObject)val; 108 final boolean strict = valueObj.isStrictContext(); 109 final Iterator<String> iter = valueObj.propertyIterator(); 110 111 while (iter.hasNext()) { 112 final String key = iter.next(); 113 final Object newElement = walk(valueObj, key, reviver); 114 115 if (newElement == ScriptRuntime.UNDEFINED) { 116 valueObj.delete(key, strict); 117 } else { 118 valueObj.set(key, newElement, strict); 119 } 120 } 121 } 122 123 try { 124 // Object.class, ScriptFunction.class, ScriptObject.class, String.class, Object.class); 125 return REVIVER_INVOKER.invokeExact(reviver, holder, JSType.toString(name), val); 126 } catch(Error|RuntimeException t) { 127 throw t; 128 } catch(final Throwable t) { 129 throw new RuntimeException(t); 130 } 131 } 132 133 // Converts IR node to runtime value 134 private static Object convertNode(final ScriptObject global, final Node node) { 135 assert global instanceof GlobalObject; 136 137 if (node instanceof LiteralNode) { 138 // check for array literal 139 if (node.tokenType() == TokenType.ARRAY) { 140 assert node instanceof LiteralNode.ArrayLiteralNode; 141 final Node[] elements = ((LiteralNode.ArrayLiteralNode)node).getValue(); 142 143 // NOTE: We cannot use LiteralNode.isNumericArray() here as that 144 // method uses symbols of element nodes. Since we don't do lower 145 // pass, there won't be any symbols! 146 if (isNumericArray(elements)) { 147 final double[] values = new double[elements.length]; 148 int index = 0; 149 150 for (final Node elem : elements) { 151 values[index++] = JSType.toNumber(convertNode(global, elem)); 152 } 153 return ((GlobalObject)global).wrapAsObject(values); 154 } 155 156 final Object[] values = new Object[elements.length]; 157 int index = 0; 158 159 for (final Node elem : elements) { 160 values[index++] = convertNode(global, elem); 161 } 162 163 return ((GlobalObject)global).wrapAsObject(values); 164 } 165 166 return ((LiteralNode<?>)node).getValue(); 167 168 } else if (node instanceof ObjectNode) { 169 final ObjectNode objNode = (ObjectNode) node; 170 final ScriptObject object = ((GlobalObject)global).newObject(); 171 final boolean strict = global.isStrictContext(); 172 final List<Node> elements = objNode.getElements(); 173 174 for (final Node elem : elements) { 175 final PropertyNode pNode = (PropertyNode) elem; 176 final Node valueNode = pNode.getValue(); 177 178 object.set(pNode.getKeyName(), convertNode(global, valueNode), strict); 179 } 180 181 return object; 182 } else if (node instanceof UnaryNode) { 183 // UnaryNode used only to represent negative number JSON value 184 final UnaryNode unaryNode = (UnaryNode)node; 185 return -((LiteralNode<?>)unaryNode.rhs()).getNumber(); 186 } else { 187 return null; 188 } 189 } 190 191 // does the given IR node represent a numeric array? 192 private static boolean isNumericArray(final Node[] values) { 193 for (final Node node : values) { 194 if (node instanceof LiteralNode && ((LiteralNode<?>)node).getValue() instanceof Number) { 195 continue; 196 } 197 return false; 198 } 199 return true; 200 } 201 }