* 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:
parent
41334905c0
commit
6ef2eabdd9
2 changed files with 337 additions and 0 deletions
336
src/helma/scripting/rhino/JSAdapter.java
Normal file
336
src/helma/scripting/rhino/JSAdapter.java
Normal 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__";
|
||||||
|
}
|
|
@ -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");
|
||||||
|
|
Loading…
Add table
Reference in a new issue