338 lines · 11.5 KB · Java
RawDownload
/* * 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__";}