1 /* 2 * Copyright (c) 2005, 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 com.sun.script.javascript; 27 28 import sun.org.mozilla.javascript.internal.*; 29 import java.util.*; 30 31 /** 32 * JSAdapter is java.lang.reflect.Proxy equivalent for JavaScript. JSAdapter 33 * calls specially named JavaScript methods on an adaptee object when property 34 * access is attempted on it. 35 * 36 * Example: 37 * 38 * var y = { 39 * __get__ : function (name) { ... } 40 * __has__ : function (name) { ... } 41 * __put__ : function (name, value) {...} 42 * __delete__ : function (name) { ... } 43 * __getIds__ : function () { ... } 44 * }; 45 * 46 * var x = new JSAdapter(y); 47 * 48 * x.i; // calls y.__get__ 49 * i in x; // calls y.__has__ 50 * x.p = 10; // calls y.__put__ 51 * delete x.p; // calls y.__delete__ 52 * for (i in x) { print(i); } // calls y.__getIds__ 53 * 54 * If a special JavaScript method is not found in the adaptee, then JSAdapter 55 * forwards the property access to the adaptee itself. 56 * 57 * JavaScript caller of adapter object is isolated from the fact that 58 * the property access/mutation/deletion are really calls to 59 * JavaScript methods on adaptee. Use cases include 'smart' 60 * properties, property access tracing/debugging, encaptulation with 61 * easy client access - in short JavaScript becomes more "Self" like. 62 * 63 * Note that Rhino already supports special properties like __proto__ 64 * (to set, get prototype), __parent__ (to set, get parent scope). We 65 * follow the same double underscore nameing convention here. Similarly 66 * the name JSAdapter is derived from JavaAdapter -- which is a facility 67 * to extend, implement Java classes/interfaces by JavaScript. 68 * 69 * @author A. Sundararajan 70 * @since 1.6 71 */ 72 public final class JSAdapter implements Scriptable, Function { 73 private JSAdapter(Scriptable obj) { 74 setAdaptee(obj); 75 } 76 77 // initializer to setup JSAdapter prototype in the given scope 78 public static void init(Context cx, Scriptable scope, boolean sealed) 79 throws RhinoException { 80 JSAdapter obj = new JSAdapter(cx.newObject(scope)); 81 obj.setParentScope(scope); 82 obj.setPrototype(getFunctionPrototype(scope)); 83 obj.isPrototype = true; 84 ScriptableObject.defineProperty(scope, "JSAdapter", obj, 85 ScriptableObject.DONTENUM); 86 } 87 88 public String getClassName() { 89 return "JSAdapter"; 90 } 91 92 public Object get(String name, Scriptable start) { 93 Function func = getAdapteeFunction(GET_PROP); 94 if (func != null) { 95 return call(func, new Object[] { name }); 96 } else { 97 start = getAdaptee(); 98 return start.get(name, start); 99 } 100 } 101 102 public Object get(int index, Scriptable start) { 103 Function func = getAdapteeFunction(GET_PROP); 104 if (func != null) { 105 return call(func, new Object[] { new Integer(index) }); 106 } else { 107 start = getAdaptee(); 108 return start.get(index, start); 109 } 110 } 111 112 public boolean has(String name, Scriptable start) { 113 Function func = getAdapteeFunction(HAS_PROP); 114 if (func != null) { 115 Object res = call(func, new Object[] { name }); 116 return Context.toBoolean(res); 117 } else { 118 start = getAdaptee(); 119 return start.has(name, start); 120 } 121 } 122 123 public boolean has(int index, Scriptable start) { 124 Function func = getAdapteeFunction(HAS_PROP); 125 if (func != null) { 126 Object res = call(func, new Object[] { new Integer(index) }); 127 return Context.toBoolean(res); 128 } else { 129 start = getAdaptee(); 130 return start.has(index, start); 131 } 132 } 133 134 public void put(String name, Scriptable start, Object value) { 135 if (start == this) { 136 Function func = getAdapteeFunction(PUT_PROP); 137 if (func != null) { 138 call(func, new Object[] { name, value }); 139 } else { 140 start = getAdaptee(); 141 start.put(name, start, value); 142 } 143 } else { 144 start.put(name, start, value); 145 } 146 } 147 148 public void put(int index, Scriptable start, Object value) { 149 if (start == this) { 150 Function func = getAdapteeFunction(PUT_PROP); 151 if( func != null) { 152 call(func, new Object[] { new Integer(index), value }); 153 } else { 154 start = getAdaptee(); 155 start.put(index, start, value); 156 } 157 } else { 158 start.put(index, start, value); 159 } 160 } 161 162 public void delete(String name) { 163 Function func = getAdapteeFunction(DEL_PROP); 164 if (func != null) { 165 call(func, new Object[] { name }); 166 } else { 167 getAdaptee().delete(name); 168 } 169 } 170 171 public void delete(int index) { 172 Function func = getAdapteeFunction(DEL_PROP); 173 if (func != null) { 174 call(func, new Object[] { new Integer(index) }); 175 } else { 176 getAdaptee().delete(index); 177 } 178 } 179 180 public Scriptable getPrototype() { 181 return prototype; 182 } 183 184 public void setPrototype(Scriptable prototype) { 185 this.prototype = prototype; 186 } 187 188 public Scriptable getParentScope() { 189 return parent; 190 } 191 192 public void setParentScope(Scriptable parent) { 193 this.parent = parent; 194 } 195 196 public Object[] getIds() { 197 Function func = getAdapteeFunction(GET_PROPIDS); 198 if (func != null) { 199 Object val = call(func, new Object[0]); 200 // in most cases, adaptee would return native JS array 201 if (val instanceof NativeArray) { 202 NativeArray array = (NativeArray) val; 203 Object[] res = new Object[(int)array.getLength()]; 204 for (int index = 0; index < res.length; index++) { 205 res[index] = mapToId(array.get(index, array)); 206 } 207 return res; 208 } else if (val instanceof NativeJavaArray) { 209 // may be attempt wrapped Java array 210 Object tmp = ((NativeJavaArray)val).unwrap(); 211 Object[] res; 212 if (tmp.getClass() == Object[].class) { 213 Object[] array = (Object[]) tmp; 214 res = new Object[array.length]; 215 for (int index = 0; index < array.length; index++) { 216 res[index] = mapToId(array[index]); 217 } 218 } else { 219 // just return an empty array 220 res = Context.emptyArgs; 221 } 222 return res; 223 } else { 224 // some other return type, just return empty array 225 return Context.emptyArgs; 226 } 227 } else { 228 return getAdaptee().getIds(); 229 } 230 } 231 232 public boolean hasInstance(Scriptable scriptable) { 233 if (scriptable instanceof JSAdapter) { 234 return true; 235 } else { 236 Scriptable proto = scriptable.getPrototype(); 237 while (proto != null) { 238 if (proto.equals(this)) return true; 239 proto = proto.getPrototype(); 240 } 241 return false; 242 } 243 } 244 245 public Object getDefaultValue(Class hint) { 246 return getAdaptee().getDefaultValue(hint); 247 } 248 249 public Object call(Context cx, Scriptable scope, Scriptable thisObj, 250 Object[] args) 251 throws RhinoException { 252 if (isPrototype) { 253 return construct(cx, scope, args); 254 } else { 255 Scriptable tmp = getAdaptee(); 256 if (tmp instanceof Function) { 257 return ((Function)tmp).call(cx, scope, tmp, args); 258 } else { 259 throw Context.reportRuntimeError("TypeError: not a function"); 260 } 261 } 262 } 263 264 public Scriptable construct(Context cx, Scriptable scope, Object[] args) 265 throws RhinoException { 266 if (isPrototype) { 267 Scriptable topLevel = ScriptableObject.getTopLevelScope(scope); 268 JSAdapter newObj; 269 if (args.length > 0) { 270 newObj = new JSAdapter(Context.toObject(args[0], topLevel)); 271 } else { 272 throw Context.reportRuntimeError("JSAdapter requires adaptee"); 273 } 274 return newObj; 275 } else { 276 Scriptable tmp = getAdaptee(); 277 if (tmp instanceof Function) { 278 return ((Function)tmp).construct(cx, scope, args); 279 } else { 280 throw Context.reportRuntimeError("TypeError: not a constructor"); 281 } 282 } 283 } 284 285 public Scriptable getAdaptee() { 286 return adaptee; 287 } 288 289 public void setAdaptee(Scriptable adaptee) { 290 if (adaptee == null) { 291 throw new NullPointerException("adaptee can not be null"); 292 } 293 this.adaptee = adaptee; 294 } 295 296 //-- internals only below this point 297 298 // map a property id. Property id can only be an Integer or String 299 private Object mapToId(Object tmp) { 300 if (tmp instanceof Double) { 301 return new Integer(((Double)tmp).intValue()); 302 } else { 303 return Context.toString(tmp); 304 } 305 } 306 307 private static Scriptable getFunctionPrototype(Scriptable scope) { 308 return ScriptableObject.getFunctionPrototype(scope); 309 } 310 311 private Function getAdapteeFunction(String name) { 312 Object o = ScriptableObject.getProperty(getAdaptee(), name); 313 return (o instanceof Function)? (Function)o : null; 314 } 315 316 private Object call(Function func, Object[] args) { 317 Context cx = Context.getCurrentContext(); 318 Scriptable thisObj = getAdaptee(); 319 Scriptable scope = func.getParentScope(); 320 try { 321 return func.call(cx, scope, thisObj, args); 322 } catch (RhinoException re) { 323 throw Context.reportRuntimeError(re.getMessage()); 324 } 325 } 326 327 private Scriptable prototype; 328 private Scriptable parent; 329 private Scriptable adaptee; 330 private boolean isPrototype; 331 332 // names of adaptee JavaScript functions 333 private static final String GET_PROP = "__get__"; 334 private static final String HAS_PROP = "__has__"; 335 private static final String PUT_PROP = "__put__"; 336 private static final String DEL_PROP = "__delete__"; 337 private static final String GET_PROPIDS = "__getIds__"; 338 }