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