* Add JSAdapter support from Phobos project <https://phobos.dev.java.net/>.

See <http://blogs.sun.com/sundararajan/entry/self_javascript_and_jsadapter>
  for more information on JSAdapter.
This commit is contained in:
hns 2008-01-10 13:07:53 +00:00
parent 41334905c0
commit 6ef2eabdd9
2 changed files with 337 additions and 0 deletions

View file

@ -0,0 +1,336 @@
/*
* The contents of this file are subject to the terms
* of the Common Development and Distribution License
* (the License). You may not use this file except in
* compliance with the License.
*
* You can obtain a copy of the license at
* https://glassfish.dev.java.net/public/CDDLv1.0.html or
* glassfish/bootstrap/legal/CDDLv1.0.txt.
* See the License for the specific language governing
* permissions and limitations under the License.
*
* When distributing Covered Code, include this CDDL
* Header Notice in each file and include the License file
* at glassfish/bootstrap/legal/CDDLv1.0.txt.
* If applicable, add the following below the CDDL Header,
* with the fields enclosed by brackets [] replaced by
* you own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
*
* Copyright 2006 Sun Microsystems, Inc. All rights reserved.
*/
package helma.scripting.rhino;
import org.mozilla.javascript.*;
/**
* 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.
*
* @version 1.0
* @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__";
}

View file

@ -155,6 +155,7 @@ public final class RhinoCore implements ScopeProvider {
new LazilyLoadedCtor(global, "Remote", new LazilyLoadedCtor(global, "Remote",
"helma.scripting.rhino.extensions.XmlRpcObject", false); "helma.scripting.rhino.extensions.XmlRpcObject", false);
MailObject.init(global, app.getProperties()); MailObject.init(global, app.getProperties());
JSAdapter.init(context, global, false);
// add some convenience functions to string, date and number prototypes // add some convenience functions to string, date and number prototypes
Scriptable stringProto = ScriptableObject.getClassPrototype(global, "String"); Scriptable stringProto = ScriptableObject.getClassPrototype(global, "String");