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 }