/* * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.script.javascript; import sun.org.mozilla.javascript.internal.*; import java.util.*; /** * JSAdapter is java.lang.reflect.Proxy equivalent for JavaScript. JSAdapter * calls specially named JavaScript methods on an adaptee object when property * access is attempted on it. * * Example: * * var y = { * __get__ : function (name) { ... } * __has__ : function (name) { ... } * __put__ : function (name, value) {...} * __delete__ : function (name) { ... } * __getIds__ : function () { ... } * }; * * var x = new JSAdapter(y); * * x.i; // calls y.__get__ * i in x; // calls y.__has__ * x.p = 10; // calls y.__put__ * delete x.p; // calls y.__delete__ * for (i in x) { print(i); } // calls y.__getIds__ * * If a special JavaScript method is not found in the adaptee, then JSAdapter * forwards the property access to the adaptee itself. * * JavaScript caller of adapter object is isolated from the fact that * the property access/mutation/deletion are really calls to * JavaScript methods on adaptee. Use cases include 'smart' * properties, property access tracing/debugging, encaptulation with * easy client access - in short JavaScript becomes more "Self" like. * * Note that Rhino already supports special properties like __proto__ * (to set, get prototype), __parent__ (to set, get parent scope). We * follow the same double underscore nameing convention here. Similarly * the name JSAdapter is derived from JavaAdapter -- which is a facility * to extend, implement Java classes/interfaces by JavaScript. * * @author A. Sundararajan * @since 1.6 */ public final class JSAdapter implements Scriptable, Function { private JSAdapter(Scriptable obj) { setAdaptee(obj); } // initializer to setup JSAdapter prototype in the given scope public static void init(Context cx, Scriptable scope, boolean sealed) throws RhinoException { JSAdapter obj = new JSAdapter(cx.newObject(scope)); obj.setParentScope(scope); obj.setPrototype(getFunctionPrototype(scope)); obj.isPrototype = true; ScriptableObject.defineProperty(scope, "JSAdapter", obj, ScriptableObject.DONTENUM); } public String getClassName() { return "JSAdapter"; } public Object get(String name, Scriptable start) { Function func = getAdapteeFunction(GET_PROP); if (func != null) { return call(func, new Object[] { name }); } else { start = getAdaptee(); return start.get(name, start); } } public Object get(int index, Scriptable start) { Function func = getAdapteeFunction(GET_PROP); if (func != null) { return call(func, new Object[] { new Integer(index) }); } else { start = getAdaptee(); return start.get(index, start); } } public boolean has(String name, Scriptable start) { Function func = getAdapteeFunction(HAS_PROP); if (func != null) { Object res = call(func, new Object[] { name }); return Context.toBoolean(res); } else { start = getAdaptee(); return start.has(name, start); } } public boolean has(int index, Scriptable start) { Function func = getAdapteeFunction(HAS_PROP); if (func != null) { Object res = call(func, new Object[] { new Integer(index) }); return Context.toBoolean(res); } else { start = getAdaptee(); return start.has(index, start); } } public void put(String name, Scriptable start, Object value) { if (start == this) { Function func = getAdapteeFunction(PUT_PROP); if (func != null) { call(func, new Object[] { name, value }); } else { start = getAdaptee(); start.put(name, start, value); } } else { start.put(name, start, value); } } public void put(int index, Scriptable start, Object value) { if (start == this) { Function func = getAdapteeFunction(PUT_PROP); if( func != null) { call(func, new Object[] { new Integer(index), value }); } else { start = getAdaptee(); start.put(index, start, value); } } else { start.put(index, start, value); } } public void delete(String name) { Function func = getAdapteeFunction(DEL_PROP); if (func != null) { call(func, new Object[] { name }); } else { getAdaptee().delete(name); } } public void delete(int index) { Function func = getAdapteeFunction(DEL_PROP); if (func != null) { call(func, new Object[] { new Integer(index) }); } else { getAdaptee().delete(index); } } public Scriptable getPrototype() { return prototype; } public void setPrototype(Scriptable prototype) { this.prototype = prototype; } public Scriptable getParentScope() { return parent; } public void setParentScope(Scriptable parent) { this.parent = parent; } public Object[] getIds() { Function func = getAdapteeFunction(GET_PROPIDS); if (func != null) { Object val = call(func, new Object[0]); // in most cases, adaptee would return native JS array if (val instanceof NativeArray) { NativeArray array = (NativeArray) val; Object[] res = new Object[(int)array.getLength()]; for (int index = 0; index < res.length; index++) { res[index] = mapToId(array.get(index, array)); } return res; } else if (val instanceof NativeJavaArray) { // may be attempt wrapped Java array Object tmp = ((NativeJavaArray)val).unwrap(); Object[] res; if (tmp.getClass() == Object[].class) { Object[] array = (Object[]) tmp; res = new Object[array.length]; for (int index = 0; index < array.length; index++) { res[index] = mapToId(array[index]); } } else { // just return an empty array res = Context.emptyArgs; } return res; } else { // some other return type, just return empty array return Context.emptyArgs; } } else { return getAdaptee().getIds(); } } public boolean hasInstance(Scriptable scriptable) { if (scriptable instanceof JSAdapter) { return true; } else { Scriptable proto = scriptable.getPrototype(); while (proto != null) { if (proto.equals(this)) return true; proto = proto.getPrototype(); } return false; } } public Object getDefaultValue(Class hint) { return getAdaptee().getDefaultValue(hint); } public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) throws RhinoException { if (isPrototype) { return construct(cx, scope, args); } else { Scriptable tmp = getAdaptee(); if (tmp instanceof Function) { return ((Function)tmp).call(cx, scope, tmp, args); } else { throw Context.reportRuntimeError("TypeError: not a function"); } } } public Scriptable construct(Context cx, Scriptable scope, Object[] args) throws RhinoException { if (isPrototype) { Scriptable topLevel = ScriptableObject.getTopLevelScope(scope); JSAdapter newObj; if (args.length > 0) { newObj = new JSAdapter(Context.toObject(args[0], topLevel)); } else { throw Context.reportRuntimeError("JSAdapter requires adaptee"); } return newObj; } else { Scriptable tmp = getAdaptee(); if (tmp instanceof Function) { return ((Function)tmp).construct(cx, scope, args); } else { throw Context.reportRuntimeError("TypeError: not a constructor"); } } } public Scriptable getAdaptee() { return adaptee; } public void setAdaptee(Scriptable adaptee) { if (adaptee == null) { throw new NullPointerException("adaptee can not be null"); } this.adaptee = adaptee; } //-- internals only below this point // map a property id. Property id can only be an Integer or String private Object mapToId(Object tmp) { if (tmp instanceof Double) { return new Integer(((Double)tmp).intValue()); } else { return Context.toString(tmp); } } private static Scriptable getFunctionPrototype(Scriptable scope) { return ScriptableObject.getFunctionPrototype(scope); } private Function getAdapteeFunction(String name) { Object o = ScriptableObject.getProperty(getAdaptee(), name); return (o instanceof Function)? (Function)o : null; } private Object call(Function func, Object[] args) { Context cx = Context.getCurrentContext(); Scriptable thisObj = getAdaptee(); Scriptable scope = func.getParentScope(); try { return func.call(cx, scope, thisObj, args); } catch (RhinoException re) { throw Context.reportRuntimeError(re.getMessage()); } } private Scriptable prototype; private Scriptable parent; private Scriptable adaptee; private boolean isPrototype; // names of adaptee JavaScript functions private static final String GET_PROP = "__get__"; private static final String HAS_PROP = "__has__"; private static final String PUT_PROP = "__put__"; private static final String DEL_PROP = "__delete__"; private static final String GET_PROPIDS = "__getIds__"; }