PluggableJSObjectTest.java revision 1445:8535274223d7
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.IntBuffer;
34import java.util.Collection;
35import java.util.HashMap;
36import java.util.LinkedHashMap;
37import java.util.Set;
38import javax.script.Invocable;
39import javax.script.ScriptEngine;
40import javax.script.ScriptEngineManager;
41import jdk.nashorn.api.scripting.AbstractJSObject;
42import jdk.nashorn.api.scripting.ScriptObjectMirror;
43import org.testng.annotations.Test;
44
45/**
46 * Tests for pluggable external impls. of jdk.nashorn.api.scripting.JSObject.
47 *
48 * JDK-8024615: Refactor ScriptObjectMirror and JSObject to support external
49 * JSObject implementations.
50 */
51@SuppressWarnings("javadoc")
52public class PluggableJSObjectTest {
53    public static class MapWrapperObject extends AbstractJSObject {
54        private final HashMap<String, Object> map = new LinkedHashMap<>();
55
56        public HashMap<String, Object> getMap() {
57            return map;
58        }
59
60        @Override
61        public Object getMember(final String name) {
62            return map.get(name);
63        }
64
65        @Override
66        public void setMember(final String name, final Object value) {
67            map.put(name, value);
68        }
69
70        @Override
71        public boolean hasMember(final String name) {
72            return map.containsKey(name);
73        }
74
75        @Override
76        public void removeMember(final String name) {
77            map.remove(name);
78        }
79
80        @Override
81        public Set<String> keySet() {
82            return map.keySet();
83        }
84
85        @Override
86        public Collection<Object> values() {
87            return map.values();
88        }
89    }
90
91    @Test
92    // Named property access on a JSObject
93    public void namedAccessTest() {
94        final ScriptEngineManager m = new ScriptEngineManager();
95        final ScriptEngine e = m.getEngineByName("nashorn");
96        try {
97            final MapWrapperObject obj = new MapWrapperObject();
98            e.put("obj", obj);
99            obj.getMap().put("foo", "bar");
100
101            // property-like access on MapWrapperObject objects
102            assertEquals(e.eval("obj.foo"), "bar");
103            e.eval("obj.foo = 'hello'");
104            assertEquals(e.eval("'foo' in obj"), Boolean.TRUE);
105            assertEquals(e.eval("obj.foo"), "hello");
106            assertEquals(obj.getMap().get("foo"), "hello");
107            e.eval("delete obj.foo");
108            assertFalse(obj.getMap().containsKey("foo"));
109            assertEquals(e.eval("'foo' in obj"), Boolean.FALSE);
110        } catch (final Exception exp) {
111            exp.printStackTrace();
112            fail(exp.getMessage());
113        }
114    }
115
116    // @bug 8062030: Nashorn bug retrieving array property after key string concatenation
117    @Test
118    // ConsString attribute access on a JSObject
119    public void consStringTest() {
120        final ScriptEngineManager m = new ScriptEngineManager();
121        final ScriptEngine e = m.getEngineByName("nashorn");
122        try {
123            final MapWrapperObject obj = new MapWrapperObject();
124            e.put("obj", obj);
125            e.put("f", "f");
126            e.eval("obj[f + 'oo'] = 'bar';");
127
128            assertEquals(obj.getMap().get("foo"), "bar");
129            assertEquals(e.eval("obj[f + 'oo']"), "bar");
130            assertEquals(e.eval("obj['foo']"), "bar");
131            assertEquals(e.eval("f + 'oo' in obj"), Boolean.TRUE);
132            assertEquals(e.eval("'foo' in obj"), Boolean.TRUE);
133            e.eval("delete obj[f + 'oo']");
134            assertFalse(obj.getMap().containsKey("foo"));
135            assertEquals(e.eval("obj[f + 'oo']"), null);
136            assertEquals(e.eval("obj['foo']"), null);
137            assertEquals(e.eval("f + 'oo' in obj"), Boolean.FALSE);
138            assertEquals(e.eval("'foo' in obj"), Boolean.FALSE);
139        } catch (final Exception exp) {
140            exp.printStackTrace();
141            fail(exp.getMessage());
142        }
143    }
144
145    public static class BufferObject extends AbstractJSObject {
146        private final IntBuffer buf;
147
148        public BufferObject(final int size) {
149            buf = IntBuffer.allocate(size);
150        }
151
152        public IntBuffer getBuffer() {
153            return buf;
154        }
155
156        @Override
157        public Object getMember(final String name) {
158            return name.equals("length")? buf.capacity() : null;
159        }
160
161        @Override
162        public boolean hasSlot(final int i) {
163            return i > -1 && i < buf.capacity();
164        }
165
166        @Override
167        public Object getSlot(final int i) {
168            return buf.get(i);
169        }
170
171        @Override
172        public void setSlot(final int i, final Object value) {
173            buf.put(i, ((Number)value).intValue());
174        }
175
176        @Override
177        public boolean isArray() {
178            return true;
179        }
180    }
181
182    @Test
183    // array-like indexed access for a JSObject
184    public void indexedAccessTest() {
185        final ScriptEngineManager m = new ScriptEngineManager();
186        final ScriptEngine e = m.getEngineByName("nashorn");
187        try {
188            final BufferObject buf = new BufferObject(2);
189            e.put("buf", buf);
190
191            // array-like access on BufferObject objects
192            assertEquals(e.eval("buf.length"), buf.getBuffer().capacity());
193            e.eval("buf[0] = 23");
194            assertEquals(buf.getBuffer().get(0), 23);
195            assertEquals(e.eval("buf[0]"), 23);
196            assertEquals(e.eval("buf[1]"), 0);
197            buf.getBuffer().put(1, 42);
198            assertEquals(e.eval("buf[1]"), 42);
199            assertEquals(e.eval("Array.isArray(buf)"), Boolean.TRUE);
200        } catch (final Exception exp) {
201            exp.printStackTrace();
202            fail(exp.getMessage());
203        }
204    }
205
206    public static class Adder extends AbstractJSObject {
207        @Override
208        public Object call(final Object thiz, final Object... args) {
209            double res = 0.0;
210            for (final Object arg : args) {
211                res += ((Number)arg).doubleValue();
212            }
213            return res;
214        }
215
216        @Override
217        public boolean isFunction() {
218            return true;
219        }
220    }
221
222    @Test
223    // a callable JSObject
224    public void callableJSObjectTest() {
225        final ScriptEngineManager m = new ScriptEngineManager();
226        final ScriptEngine e = m.getEngineByName("nashorn");
227        try {
228            e.put("sum", new Adder());
229            // check callability of Adder objects
230            assertEquals(e.eval("typeof sum"), "function");
231            assertEquals(((Number)e.eval("sum(1, 2, 3, 4, 5)")).intValue(), 15);
232        } catch (final Exception exp) {
233            exp.printStackTrace();
234            fail(exp.getMessage());
235        }
236    }
237
238    public static class Factory extends AbstractJSObject {
239        @SuppressWarnings("unused")
240        @Override
241        public Object newObject(final Object... args) {
242            return new HashMap<Object, Object>();
243        }
244
245        @Override
246        public boolean isFunction() {
247            return true;
248        }
249    }
250
251    @Test
252    // a factory JSObject
253    public void factoryJSObjectTest() {
254        final ScriptEngineManager m = new ScriptEngineManager();
255        final ScriptEngine e = m.getEngineByName("nashorn");
256        try {
257            e.put("Factory", new Factory());
258
259            // check new on Factory
260            assertEquals(e.eval("typeof Factory"), "function");
261            assertEquals(e.eval("typeof new Factory()"), "object");
262            assertEquals(e.eval("(new Factory()) instanceof java.util.Map"), Boolean.TRUE);
263        } catch (final Exception exp) {
264            exp.printStackTrace();
265            fail(exp.getMessage());
266        }
267    }
268
269    @Test
270    // iteration tests
271    public void iteratingJSObjectTest() {
272        final ScriptEngineManager m = new ScriptEngineManager();
273        final ScriptEngine e = m.getEngineByName("nashorn");
274        try {
275            final MapWrapperObject obj = new MapWrapperObject();
276            obj.setMember("foo", "hello");
277            obj.setMember("bar", "world");
278            e.put("obj", obj);
279
280            // check for..in
281            Object val = e.eval("var str = ''; for (i in obj) str += i; str");
282            assertEquals(val.toString(), "foobar");
283
284            // check for..each..in
285            val = e.eval("var str = ''; for each (i in obj) str += i; str");
286            assertEquals(val.toString(), "helloworld");
287        } catch (final Exception exp) {
288            exp.printStackTrace();
289            fail(exp.getMessage());
290        }
291    }
292
293    // @bug 8137258: JSObjectLinker and BrowserJSObjectLinker should not expose internal JS objects
294    @Test
295    public void hidingInternalObjectsForJSObjectTest() throws Exception {
296        final ScriptEngineManager engineManager = new ScriptEngineManager();
297        final ScriptEngine e = engineManager.getEngineByName("nashorn");
298
299        final String code = "function func(obj) { obj.foo = [5, 5]; obj.bar = {} }";
300        e.eval(code);
301
302        // call the exposed function but pass user defined JSObject impl as argument
303        ((Invocable)e).invokeFunction("func", new AbstractJSObject() {
304            @Override
305            public void setMember(final String name, final Object value) {
306                // make sure that wrapped objects are passed (and not internal impl. objects)
307                assertTrue(value.getClass() == ScriptObjectMirror.class);
308            }
309        });
310    }
311}
312