JSONFunctions.java revision 953:221a84ef44c0
18876Srgrimes/*
24Srgrimes * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
34Srgrimes * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44Srgrimes *
58876Srgrimes * This code is free software; you can redistribute it and/or modify it
64Srgrimes * under the terms of the GNU General Public License version 2 only, as
74Srgrimes * published by the Free Software Foundation.  Oracle designates this
84Srgrimes * particular file as subject to the "Classpath" exception as provided
94Srgrimes * by Oracle in the LICENSE file that accompanied this code.
104Srgrimes *
118876Srgrimes * This code is distributed in the hope that it will be useful, but WITHOUT
128876Srgrimes * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
134Srgrimes * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
144Srgrimes * version 2 for more details (a copy is included in the LICENSE file that
158876Srgrimes * accompanied this code).
164Srgrimes *
178876Srgrimes * You should have received a copy of the GNU General Public License version
184Srgrimes * 2 along with this work; if not, write to the Free Software Foundation,
194Srgrimes * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
204Srgrimes *
214Srgrimes * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
228876Srgrimes * or visit www.oracle.com if you need additional information or have any
234Srgrimes * questions.
244Srgrimes */
25118Srgrimes
2611940Sbdepackage jdk.nashorn.internal.runtime;
274Srgrimes
284Srgrimesimport static jdk.nashorn.internal.runtime.Source.sourceFor;
294Srgrimes
304Srgrimesimport java.lang.invoke.MethodHandle;
314Srgrimesimport java.util.Iterator;
322056Swollmanimport java.util.concurrent.Callable;
332056Swollmanimport jdk.nashorn.internal.ir.LiteralNode;
342056Swollmanimport jdk.nashorn.internal.ir.Node;
352056Swollmanimport jdk.nashorn.internal.ir.ObjectNode;
364Srgrimesimport jdk.nashorn.internal.ir.PropertyNode;
374Srgrimesimport jdk.nashorn.internal.ir.UnaryNode;
384Srgrimesimport jdk.nashorn.internal.objects.Global;
394Srgrimesimport jdk.nashorn.internal.parser.JSONParser;
404Srgrimesimport jdk.nashorn.internal.parser.TokenType;
414Srgrimesimport jdk.nashorn.internal.runtime.arrays.ArrayIndex;
424Srgrimesimport jdk.nashorn.internal.runtime.linker.Bootstrap;
434Srgrimes
444Srgrimes/**
454Srgrimes * Utilities used by "JSON" object implementation.
464Srgrimes */
474Srgrimespublic final class JSONFunctions {
484Srgrimes    private JSONFunctions() {}
494Srgrimes
504Srgrimes    private static final Object REVIVER_INVOKER = new Object();
514Srgrimes
524Srgrimes    private static MethodHandle getREVIVER_INVOKER() {
534Srgrimes        return Context.getGlobal().getDynamicInvoker(REVIVER_INVOKER,
544Srgrimes                new Callable<MethodHandle>() {
554Srgrimes                    @Override
564Srgrimes                    public MethodHandle call() {
574Srgrimes                        return Bootstrap.createDynamicInvoker("dyn:call", Object.class,
584Srgrimes                            ScriptFunction.class, ScriptObject.class, String.class, Object.class);
594Srgrimes                    }
604Srgrimes                });
614Srgrimes    }
624Srgrimes
634Srgrimes    /**
644Srgrimes     * Returns JSON-compatible quoted version of the given string.
654Srgrimes     *
664Srgrimes     * @param str String to be quoted
674Srgrimes     * @return JSON-compatible quoted string
684Srgrimes     */
694Srgrimes    public static String quote(final String str) {
704Srgrimes        return JSONParser.quote(str);
714Srgrimes    }
724Srgrimes
734Srgrimes    /**
744Srgrimes     * Parses the given JSON text string and returns object representation.
754Srgrimes     *
764Srgrimes     * @param text JSON text to be parsed
774Srgrimes     * @param reviver  optional value: function that takes two parameters (key, value)
784Srgrimes     * @return Object representation of JSON text given
794Srgrimes     */
804Srgrimes    public static Object parse(final Object text, final Object reviver) {
814Srgrimes        final String     str     = JSType.toString(text);
824Srgrimes        final JSONParser parser  = new JSONParser(sourceFor("<json>", str), new Context.ThrowErrorManager());
834Srgrimes
844Srgrimes        Node node;
854Srgrimes
864Srgrimes        try {
874Srgrimes            node = parser.parse();
884Srgrimes        } catch (final ParserException e) {
894Srgrimes            throw ECMAErrors.syntaxError(e, "invalid.json", e.getMessage());
904Srgrimes        }
9111940Sbde
924Srgrimes        final Global global = Context.getGlobal();
934Srgrimes        final Object unfiltered = convertNode(global, node);
944Srgrimes        return applyReviver(global, unfiltered, reviver);
954Srgrimes    }
964Srgrimes
974Srgrimes    // -- Internals only below this point
984Srgrimes
994Srgrimes    // parse helpers
1004Srgrimes
1014Srgrimes    // apply 'reviver' function if available
1024Srgrimes    private static Object applyReviver(final Global global, final Object unfiltered, final Object reviver) {
10311940Sbde        if (reviver instanceof ScriptFunction) {
1044Srgrimes            final ScriptObject root = global.newObject();
1054Srgrimes            root.addOwnProperty("", Property.WRITABLE_ENUMERABLE_CONFIGURABLE, unfiltered);
1064Srgrimes            return walk(root, "", (ScriptFunction)reviver);
1074Srgrimes        }
1084Srgrimes        return unfiltered;
1094Srgrimes    }
1104Srgrimes
11111921Sphk    // This is the abstract "Walk" operation from the spec.
1124Srgrimes    private static Object walk(final ScriptObject holder, final Object name, final ScriptFunction reviver) {
1134Srgrimes        final Object val = holder.get(name);
1144Srgrimes        if (val instanceof ScriptObject) {
1154Srgrimes            final ScriptObject     valueObj = (ScriptObject)val;
1164Srgrimes            final Iterator<String> iter     = valueObj.propertyIterator();
1174Srgrimes
1184Srgrimes            while (iter.hasNext()) {
1194Srgrimes                final String key        = iter.next();
1204Srgrimes                final Object newElement = walk(valueObj, key, reviver);
1214Srgrimes
12211921Sphk                if (newElement == ScriptRuntime.UNDEFINED) {
1234Srgrimes                    valueObj.delete(key, false);
1244Srgrimes                } else {
1254Srgrimes                    setPropertyValue(valueObj, key, newElement, false);
1264Srgrimes                }
1274Srgrimes            }
1284Srgrimes        }
1294Srgrimes
1304Srgrimes        try {
1314Srgrimes             // Object.class, ScriptFunction.class, ScriptObject.class, String.class, Object.class);
1324Srgrimes             return getREVIVER_INVOKER().invokeExact(reviver, holder, JSType.toString(name), val);
13311921Sphk        } catch(Error|RuntimeException t) {
1344Srgrimes            throw t;
1354Srgrimes        } catch(final Throwable t) {
1364Srgrimes            throw new RuntimeException(t);
1374Srgrimes        }
1384Srgrimes    }
1394Srgrimes
1404Srgrimes    // Converts IR node to runtime value
1414Srgrimes    private static Object convertNode(final Global global, final Node node) {
1424Srgrimes        if (node instanceof LiteralNode) {
1434Srgrimes            // check for array literal
14411921Sphk            if (node.tokenType() == TokenType.ARRAY) {
1454Srgrimes                assert node instanceof LiteralNode.ArrayLiteralNode;
1464Srgrimes                final Node[] elements = ((LiteralNode.ArrayLiteralNode)node).getValue();
1474Srgrimes
1484Srgrimes                // NOTE: We cannot use LiteralNode.isNumericArray() here as that
1494Srgrimes                // method uses symbols of element nodes. Since we don't do lower
1504Srgrimes                // pass, there won't be any symbols!
1514Srgrimes                if (isNumericArray(elements)) {
1524Srgrimes                    final double[] values = new double[elements.length];
1534Srgrimes                    int   index = 0;
1544Srgrimes
1554Srgrimes                    for (final Node elem : elements) {
1564Srgrimes                        values[index++] = JSType.toNumber(convertNode(global, elem));
1574Srgrimes                    }
1584Srgrimes                    return global.wrapAsObject(values);
1594Srgrimes                }
1604Srgrimes
1614Srgrimes                final Object[] values = new Object[elements.length];
1624Srgrimes                int   index = 0;
1634Srgrimes
16411921Sphk                for (final Node elem : elements) {
1654Srgrimes                    values[index++] = convertNode(global, elem);
1664Srgrimes                }
1674Srgrimes
1684Srgrimes                return global.wrapAsObject(values);
1694Srgrimes            }
1704Srgrimes
1714Srgrimes            return ((LiteralNode<?>)node).getValue();
1724Srgrimes
1734Srgrimes        } else if (node instanceof ObjectNode) {
1744Srgrimes            final ObjectNode   objNode  = (ObjectNode) node;
1754Srgrimes            final ScriptObject object   = global.newObject();
1764Srgrimes
1774Srgrimes            for (final PropertyNode pNode: objNode.getElements()) {
1784Srgrimes                final Node         valueNode = pNode.getValue();
1794Srgrimes
1804Srgrimes                final String name = pNode.getKeyName();
1814Srgrimes                final Object value = convertNode(global, valueNode);
1824Srgrimes                setPropertyValue(object, name, value, false);
1834Srgrimes            }
18411921Sphk
1854Srgrimes            return object;
1864Srgrimes        } else if (node instanceof UnaryNode) {
1874Srgrimes            // UnaryNode used only to represent negative number JSON value
1884Srgrimes            final UnaryNode unaryNode = (UnaryNode)node;
1894Srgrimes            return -((LiteralNode<?>)unaryNode.getExpression()).getNumber();
1904Srgrimes        } else {
1914Srgrimes            return null;
1924Srgrimes        }
1934Srgrimes    }
1944Srgrimes
1954Srgrimes    // add a new property if does not exist already, or else set old property
1964Srgrimes    private static void setPropertyValue(final ScriptObject sobj, final String name, final Object value, final boolean strict) {
1974Srgrimes        final int index = ArrayIndex.getArrayIndex(name);
1984Srgrimes        if (ArrayIndex.isValidArrayIndex(index)) {
1994Srgrimes            // array index key
2004Srgrimes            sobj.defineOwnProperty(index, value);
2014Srgrimes        } else if (sobj.getMap().findProperty(name) != null) {
2024Srgrimes            // pre-existing non-inherited property, call set
2034Srgrimes            sobj.set(name, value, strict);
20411921Sphk        } else {
2054Srgrimes            // add new property
2064Srgrimes            sobj.addOwnProperty(name, Property.WRITABLE_ENUMERABLE_CONFIGURABLE, value);
2074Srgrimes        }
2084Srgrimes    }
2094Srgrimes
2104Srgrimes    // does the given IR node represent a numeric array?
2114Srgrimes    private static boolean isNumericArray(final Node[] values) {
2124Srgrimes        for (final Node node : values) {
2134Srgrimes            if (node instanceof LiteralNode && ((LiteralNode<?>)node).getValue() instanceof Number) {
2144Srgrimes                continue;
2154Srgrimes            }
2164Srgrimes            return false;
2174Srgrimes        }
2184Srgrimes        return true;
2194Srgrimes    }
2204Srgrimes}
2214Srgrimes