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 }