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 @SuppressWarnings("javadoc")
  52 public 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 }