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
26package jdk.nashorn.api.scripting.test;
27
28import static org.testng.Assert.assertEquals;
29import static org.testng.Assert.assertFalse;
30import static org.testng.Assert.assertTrue;
31import static org.testng.Assert.fail;
32
33import java.nio.ByteBuffer;
34import java.util.HashMap;
35import java.util.List;
36import java.util.Map;
37import java.util.function.Function;
38import javax.script.Bindings;
39import javax.script.Invocable;
40import javax.script.ScriptContext;
41import javax.script.ScriptEngine;
42import javax.script.ScriptEngineManager;
43import javax.script.ScriptException;
44import jdk.nashorn.api.scripting.AbstractJSObject;
45import jdk.nashorn.api.scripting.JSObject;
46import jdk.nashorn.api.scripting.ScriptObjectMirror;
47import org.testng.annotations.Test;
48
49/**
50 * Tests to check jdk.nashorn.api.scripting.ScriptObjectMirror API.
51 *
52 * @test
53 * @run testng jdk.nashorn.api.scripting.test.ScriptObjectMirrorTest
54 */
55@SuppressWarnings("javadoc")
56public class ScriptObjectMirrorTest {
57
58    @SuppressWarnings("unchecked")
59    @Test
60    public void reflectionTest() throws ScriptException {
61        final ScriptEngineManager m = new ScriptEngineManager();
62        final ScriptEngine e = m.getEngineByName("nashorn");
63
64        e.eval("var obj = { x: 344, y: 'nashorn' }");
65
66        int count = 0;
67        Map<Object, Object> map = (Map<Object, Object>) e.get("obj");
68        assertFalse(map.isEmpty());
69        assertTrue(map.keySet().contains("x"));
70        assertTrue(map.containsKey("x"));
71        assertTrue(map.values().contains("nashorn"));
72        assertTrue(map.containsValue("nashorn"));
73        for (final Map.Entry<?, ?> ex : map.entrySet()) {
74            final Object key = ex.getKey();
75            if (key.equals("x")) {
76                assertTrue(344 == ((Number) ex.getValue()).doubleValue());
77                count++;
78            } else if (key.equals("y")) {
79                assertEquals(ex.getValue(), "nashorn");
80                count++;
81            }
82        }
83        assertEquals(2, count);
84        assertEquals(2, map.size());
85
86        // add property
87        map.put("z", "hello");
88        assertEquals(e.eval("obj.z"), "hello");
89        assertEquals(map.get("z"), "hello");
90        assertTrue(map.keySet().contains("z"));
91        assertTrue(map.containsKey("z"));
92        assertTrue(map.values().contains("hello"));
93        assertTrue(map.containsValue("hello"));
94        assertEquals(map.size(), 3);
95
96        final Map<Object, Object> newMap = new HashMap<>();
97        newMap.put("foo", 23.0);
98        newMap.put("bar", true);
99        map.putAll(newMap);
100
101        assertEquals(e.eval("obj.foo"), 23.0);
102        assertEquals(e.eval("obj.bar"), true);
103
104        // remove using map method
105        map.remove("foo");
106        assertEquals(e.eval("typeof obj.foo"), "undefined");
107
108        count = 0;
109        e.eval("var arr = [ true, 'hello' ]");
110        map = (Map<Object, Object>) e.get("arr");
111        assertFalse(map.isEmpty());
112        assertTrue(map.containsKey("length"));
113        assertTrue(map.containsValue("hello"));
114        for (final Map.Entry<?, ?> ex : map.entrySet()) {
115            final Object key = ex.getKey();
116            if (key.equals("0")) {
117                assertEquals(ex.getValue(), Boolean.TRUE);
118                count++;
119            } else if (key.equals("1")) {
120                assertEquals(ex.getValue(), "hello");
121                count++;
122            }
123        }
124        assertEquals(count, 2);
125        assertEquals(map.size(), 2);
126
127        // add element
128        map.put("2", "world");
129        assertEquals(map.get("2"), "world");
130        assertEquals(map.size(), 3);
131
132        // remove all
133        map.clear();
134        assertTrue(map.isEmpty());
135        assertEquals(e.eval("typeof arr[0]"), "undefined");
136        assertEquals(e.eval("typeof arr[1]"), "undefined");
137        assertEquals(e.eval("typeof arr[2]"), "undefined");
138    }
139
140    @Test
141    public void jsobjectTest() {
142        final ScriptEngineManager m = new ScriptEngineManager();
143        final ScriptEngine e = m.getEngineByName("nashorn");
144        try {
145            e.eval("var obj = { '1': 'world', func: function() { return this.bar; }, bar: 'hello' }");
146            final ScriptObjectMirror obj = (ScriptObjectMirror) e.get("obj");
147
148            // try basic get on existing properties
149            if (!obj.getMember("bar").equals("hello")) {
150                fail("obj.bar != 'hello'");
151            }
152
153            if (!obj.getSlot(1).equals("world")) {
154                fail("obj[1] != 'world'");
155            }
156
157            if (!obj.callMember("func", new Object[0]).equals("hello")) {
158                fail("obj.func() != 'hello'");
159            }
160
161            // try setting properties
162            obj.setMember("bar", "new-bar");
163            obj.setSlot(1, "new-element-1");
164            if (!obj.getMember("bar").equals("new-bar")) {
165                fail("obj.bar != 'new-bar'");
166            }
167
168            if (!obj.getSlot(1).equals("new-element-1")) {
169                fail("obj[1] != 'new-element-1'");
170            }
171
172            // try adding properties
173            obj.setMember("prop", "prop-value");
174            obj.setSlot(12, "element-12");
175            if (!obj.getMember("prop").equals("prop-value")) {
176                fail("obj.prop != 'prop-value'");
177            }
178
179            if (!obj.getSlot(12).equals("element-12")) {
180                fail("obj[12] != 'element-12'");
181            }
182
183            // delete properties
184            obj.removeMember("prop");
185            if ("prop-value".equals(obj.getMember("prop"))) {
186                fail("obj.prop is not deleted!");
187            }
188
189            // Simple eval tests
190            assertEquals(obj.eval("typeof Object"), "function");
191            assertEquals(obj.eval("'nashorn'.substring(3)"), "horn");
192        } catch (final Exception exp) {
193            exp.printStackTrace();
194            fail(exp.getMessage());
195        }
196    }
197
198    @Test
199    public void scriptObjectMirrorToStringTest() {
200        final ScriptEngineManager m = new ScriptEngineManager();
201        final ScriptEngine e = m.getEngineByName("nashorn");
202        try {
203            final Object obj = e.eval("new TypeError('wrong type')");
204            assertEquals(obj.toString(), "TypeError: wrong type", "toString returns wrong value");
205        } catch (final Throwable t) {
206            t.printStackTrace();
207            fail(t.getMessage());
208        }
209
210        try {
211            final Object obj = e.eval("(function func() { print('hello'); })");
212            assertEquals(obj.toString(), "function func() { print('hello'); }", "toString returns wrong value");
213        } catch (final Throwable t) {
214            t.printStackTrace();
215            fail(t.getMessage());
216        }
217    }
218
219    @Test
220    public void mirrorNewObjectGlobalFunctionTest() throws ScriptException {
221        final ScriptEngineManager m = new ScriptEngineManager();
222        final ScriptEngine e = m.getEngineByName("nashorn");
223        final ScriptEngine e2 = m.getEngineByName("nashorn");
224
225        e.eval("function func() {}");
226        e2.put("foo", e.get("func"));
227        final ScriptObjectMirror e2global = (ScriptObjectMirror)e2.eval("this");
228        final Object newObj = ((ScriptObjectMirror)e2global.getMember("foo")).newObject();
229        assertTrue(newObj instanceof ScriptObjectMirror);
230    }
231
232    @Test
233    public void mirrorNewObjectInstanceFunctionTest() throws ScriptException {
234        final ScriptEngineManager m = new ScriptEngineManager();
235        final ScriptEngine e = m.getEngineByName("nashorn");
236        final ScriptEngine e2 = m.getEngineByName("nashorn");
237
238        e.eval("function func() {}");
239        e2.put("func", e.get("func"));
240        final ScriptObjectMirror e2obj = (ScriptObjectMirror)e2.eval("({ foo: func })");
241        final Object newObj = ((ScriptObjectMirror)e2obj.getMember("foo")).newObject();
242        assertTrue(newObj instanceof ScriptObjectMirror);
243    }
244
245    @Test
246    public void indexPropertiesExternalBufferTest() throws ScriptException {
247        final ScriptEngineManager m = new ScriptEngineManager();
248        final ScriptEngine e = m.getEngineByName("nashorn");
249        final ScriptObjectMirror obj = (ScriptObjectMirror)e.eval("var obj = {}; obj");
250        final ByteBuffer buf = ByteBuffer.allocate(5);
251        int i;
252        for (i = 0; i < 5; i++) {
253            buf.put(i, (byte)(i+10));
254        }
255        obj.setIndexedPropertiesToExternalArrayData(buf);
256
257        for (i = 0; i < 5; i++) {
258            assertEquals((byte)(i+10), ((Number)e.eval("obj[" + i + "]")).byteValue());
259        }
260
261        e.eval("for (i = 0; i < 5; i++) obj[i] = 0");
262        for (i = 0; i < 5; i++) {
263            assertEquals((byte)0, ((Number)e.eval("obj[" + i + "]")).byteValue());
264            assertEquals((byte)0, buf.get(i));
265        }
266    }
267
268    @Test
269    public void conversionTest() throws ScriptException {
270        final ScriptEngineManager m = new ScriptEngineManager();
271        final ScriptEngine e = m.getEngineByName("nashorn");
272        final ScriptObjectMirror arr = (ScriptObjectMirror)e.eval("[33, 45, 23]");
273        final int[] intArr = arr.to(int[].class);
274        assertEquals(intArr[0], 33);
275        assertEquals(intArr[1], 45);
276        assertEquals(intArr[2], 23);
277
278        final List<?> list = arr.to(List.class);
279        assertEquals(list.get(0), 33);
280        assertEquals(list.get(1), 45);
281        assertEquals(list.get(2), 23);
282
283        ScriptObjectMirror obj = (ScriptObjectMirror)e.eval(
284            "({ valueOf: function() { return 42 } })");
285        assertEquals(42.0, obj.to(Double.class));
286
287        obj = (ScriptObjectMirror)e.eval(
288            "({ toString: function() { return 'foo' } })");
289        assertEquals("foo", obj.to(String.class));
290    }
291
292    // @bug 8044000: Access to undefined property yields "null" instead of "undefined"
293    @Test
294    public void mapScriptObjectMirrorCallsiteTest() throws ScriptException {
295        final ScriptEngineManager m = new ScriptEngineManager();
296        final ScriptEngine engine = m.getEngineByName("nashorn");
297        final String TEST_SCRIPT = "typeof obj.foo";
298
299        final Bindings global = engine.getContext().getBindings(ScriptContext.ENGINE_SCOPE);
300        engine.eval("var obj = java.util.Collections.emptyMap()");
301        // this will drive callsite "obj.foo" of TEST_SCRIPT
302        // to use "obj instanceof Map" as it's guard
303        engine.eval(TEST_SCRIPT, global);
304        // redefine 'obj' to be a script object
305        engine.eval("obj = {}");
306
307        final Bindings newGlobal = engine.createBindings();
308        // transfer 'obj' from default global to new global
309        // new global will get a ScriptObjectMirror wrapping 'obj'
310        newGlobal.put("obj", global.get("obj"));
311
312        // Every ScriptObjectMirror is a Map! If callsite "obj.foo"
313        // does not see the new 'obj' is a ScriptObjectMirror, it'll
314        // continue to use Map's get("obj.foo") instead of ScriptObjectMirror's
315        // getMember("obj.foo") - thereby getting null instead of undefined
316        assertEquals("undefined", engine.eval(TEST_SCRIPT, newGlobal));
317    }
318
319    public interface MirrorCheckExample {
320        Object test1(Object arg);
321        Object test2(Object arg);
322        boolean compare(Object o1, Object o2);
323    }
324
325    // @bug 8053910: ScriptObjectMirror causing havoc with Invocation interface
326    @Test
327    public void checkMirrorToObject() throws Exception {
328        final ScriptEngineManager engineManager = new ScriptEngineManager();
329        final ScriptEngine engine = engineManager.getEngineByName("nashorn");
330        final Invocable invocable = (Invocable)engine;
331
332        engine.eval("function test1(arg) { return { arg: arg }; }");
333        engine.eval("function test2(arg) { return arg; }");
334        engine.eval("function compare(arg1, arg2) { return arg1 == arg2; }");
335
336        final Map<String, Object> map = new HashMap<>();
337        map.put("option", true);
338
339        final MirrorCheckExample example = invocable.getInterface(MirrorCheckExample.class);
340
341        final Object value1 = invocable.invokeFunction("test1", map);
342        final Object value2 = example.test1(map);
343        final Object value3 = invocable.invokeFunction("test2", value2);
344        final Object value4 = example.test2(value2);
345
346        // check that Object type argument receives a ScriptObjectMirror
347        // when ScriptObject is passed
348        assertEquals(ScriptObjectMirror.class, value1.getClass());
349        assertEquals(ScriptObjectMirror.class, value2.getClass());
350        assertEquals(ScriptObjectMirror.class, value3.getClass());
351        assertEquals(ScriptObjectMirror.class, value4.getClass());
352        assertTrue((boolean)invocable.invokeFunction("compare", value1, value1));
353        assertTrue(example.compare(value1, value1));
354        assertTrue((boolean)invocable.invokeFunction("compare", value3, value4));
355        assertTrue(example.compare(value3, value4));
356    }
357
358    // @bug 8053910: ScriptObjectMirror causing havoc with Invocation interface
359    @Test
360    public void mirrorUnwrapInterfaceMethod() throws Exception {
361        final ScriptEngineManager engineManager = new ScriptEngineManager();
362        final ScriptEngine engine = engineManager.getEngineByName("nashorn");
363        final Invocable invocable = (Invocable)engine;
364        engine.eval("function apply(obj) { " +
365            " return obj instanceof Packages.jdk.nashorn.api.scripting.ScriptObjectMirror; " +
366            "}");
367        @SuppressWarnings("unchecked")
368        final Function<Object,Object> func = invocable.getInterface(Function.class);
369        assertFalse((boolean)func.apply(engine.eval("({ x: 2 })")));
370    }
371
372    // @bug 8055687: Wrong "this" passed to JSObject.eval call
373    @Test
374    public void checkThisForJSObjectEval() throws Exception {
375        final ScriptEngineManager engineManager = new ScriptEngineManager();
376        final ScriptEngine e = engineManager.getEngineByName("nashorn");
377        final JSObject jsobj = (JSObject)e.eval("({foo: 23, bar: 'hello' })");
378        assertEquals(((Number)jsobj.eval("this.foo")).intValue(), 23);
379        assertEquals(jsobj.eval("this.bar"), "hello");
380        assertEquals(jsobj.eval("String(this)"), "[object Object]");
381        final Object global = e.eval("this");
382        assertFalse(global.equals(jsobj.eval("this")));
383    }
384
385    @Test
386    public void topLevelAnonFuncStatement() throws Exception {
387        final ScriptEngineManager engineManager = new ScriptEngineManager();
388        final ScriptEngine e = engineManager.getEngineByName("nashorn");
389        final JSObject func = (JSObject)e.eval("function(x) { return x + ' world' }");
390        assertTrue(func.isFunction());
391        assertEquals(func.call(e.eval("this"), "hello"), "hello world");
392    }
393
394    // @bug 8170565: JSObject call() is passed undefined for the argument 'thiz'
395    @Test
396    public void jsObjectThisTest() throws Exception {
397        final ScriptEngineManager engineManager = new ScriptEngineManager();
398        final ScriptEngine e = engineManager.getEngineByName("nashorn");
399        e.put("func", new AbstractJSObject() {
400            @Override
401            public boolean isFunction() { return true; }
402
403            @Override
404            public Object call(Object thiz, Object...args) {
405                return thiz;
406            }
407        });
408
409        assertTrue((boolean)e.eval("func() === this"));
410
411        // check that there is no blind undefined->Global translation!
412        assertTrue((boolean)e.eval("typeof(Function.prototype.call.call(func, undefined)) == 'undefined'"));
413
414        // make sure that strict functions don't get translated this for scope calls!
415        e.put("sfunc", new AbstractJSObject() {
416            @Override
417            public boolean isFunction() { return true; }
418
419            @Override
420            public boolean isStrictFunction() { return true; }
421
422            @Override
423            public Object call(Object thiz, Object...args) {
424                return thiz;
425            }
426        });
427
428        assertTrue((boolean)e.eval("typeof sfunc() == 'undefined'"));
429    }
430}
431