Closed AsyncProxy closed 1 day ago
Please try Proxy.
Didn't you say before that this feature would take 40 hours to implement?https://github.com/caoccao/Javet/issues/355
请尝试代理。
The agent can only generate a new object, but I need the JS class to inherit from the Java class to directly override the method, such as the Java annotation Override
Please try Proxy.
class obj extends java.io.File {
exists() {
return 'exists';
}
}
console.log(new obj('').exists())//exists```
May I know how much you would like to donate for this feature, USD 5000? It is almost technically impossible.
May I know how much you would like to donate for this feature, USD 5000? It is almost technically impossible.
But how does the Rhino engine's JavaAdapter work?, I just want a feature like Rhino
我能知道你想为此功能捐款多少,5000美元吗?这在技术上几乎是不可能的。
Want this kind of defineClass feature.
我能知道你想为此功能捐款多少,5000美元吗?这在技术上几乎是不可能的。
Want this kind of defineClass feature.
This is based on "rhinojs", maybe you can copy their code and realize it! And I think that it was possible! https://github.com/openautojs/openautojs/blob/f23289d25edbcb1b1b73248006a46462d437f826/autojs/src/main/java/com/stardust/autojs/rhino/AndroidClassLoader.java#L58
@AsyncProxy what was the solution you came up with? I'm also questioning how this might be implemented. If you could reply with little code snippets or make a GitHub repo (or fork) that would be great! I've been stuck on this for a long time and finding a solution would make my day!
You can use a library like Bytebuddy to do this:
const ByteBuddy = javet.package.net.bytebuddy.ByteBuddy;
const Handler = javet.package.Handler;
const HandlerAdapter = javet.package.net.bytebuddy.implementation.InvocationHandlerAdapter;
const ElementMatchers = javet.package.net.bytebuddy.matcher.ElementMatchers;
const defaultLoader = javet.package.java.lang.ClassLoader.getSystemClassLoader();
const FunctionJava = javet.package.java.util.function.Function;
function extend(cls, options){
let loader = options.loader != null ? options.loader : defaultLoader;
let buddy = new ByteBuddy();
let proxy = buddy.subclass(cls);
if (options.name != null) {
proxy = proxy.name(options.name);
}
if (options.methods != null) {
let keys = Object.keys(options.methods);
for (let key of keys){
proxy = proxy.method(ElementMatchers.named(key)).intercept(HandlerAdapter.of(new Handler((args) => {return options.methods[key].apply(null, args)})));
}
}
return proxy.make().load(loader).getLoaded();
}
const Func = extend(FunctionJava, {
name: "FunctionProxy",
methods: {
apply: (test) => {
return test[0]
}
}
});
let f = new Func();
console.log(f.apply("test"))
public class Handler implements InvocationHandler {
private final Function<Object, Object> handler;
public Handler(Function<Object, Object> handler){
this.handler = handler;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return handler.apply(new Object[] {args});
}
}
and by utilizing javascripts classes you can change the function a bit to make it easier to create proxies
function extendClass(cls, jsclass){
let loader = defaultLoader;
let buddy = new ByteBuddy();
let proxy = buddy.subclass(cls).name(jsclass.name);
let keys = Reflect.ownKeys(jsclass.prototype);
for (let key of keys){
proxy = proxy.method(ElementMatchers.named(key)).intercept(HandlerAdapter.of(new Handler((args) => {return Reflect.get(jsclass.prototype, key).apply(args[0], args[1])})));
}
return proxy.make().load(loader).getLoaded();
}
class BiFunctionProxy {
apply(arg, arg2) {
console.log(arg)
console.log(arg2)
return arg + arg2;
}
}
const Functioncls = extendClass(BiFunctionJava, BiFunctionProxy);
let f = new Functioncls();
console.log(f.apply("test", "hej"))
Output:
[String: 'test']
[String: 'hej']
[String: 'testhej']
BiFunctionJava.isAssignableFrom(Functioncls); returns true
you can access the proxy object by using this in the function but you are not able to use super to get the super object.
Added a bit onto it and have gotten it to this result
const ByteBuddy = javet.package.net.bytebuddy.ByteBuddy;
const Handler = javet.package.Handler;
const HandlerAdapter = javet.package.net.bytebuddy.implementation.InvocationHandlerAdapter;
const ElementMatchers = javet.package.net.bytebuddy.matcher.ElementMatchers;
function generate(jsClass){
let loader = javet.package.java.lang.ClassLoader.getSystemClassLoader();
let buddy = new ByteBuddy();
let javaClass = Object.getPrototypeOf(jsClass);
let proxy = buddy.subclass(javaClass).name(jsClass.name);
let keys = Reflect.ownKeys(jsClass.prototype);
for (let key of keys){
proxy = proxy.method(ElementMatchers.named(key)).intercept(HandlerAdapter.of(new Handler((args) => {return Reflect.get(jsClass.prototype, key).apply(args[0], args[1])})));
}
return proxy.make().load(loader).getLoaded();
}
class BiFunctionProxy extends javet.package.java.lang.System {
apply(arg) {
console.log(arg)
return arg;
}
}
const Functioncls = generate(BiFunctionProxy);
let f = new Functioncls();
f.apply("hej")
but there is an issue with this since it doesn't work with interface classes because in the proxy converter interface classes do not get the prototype of classes check out V8ProxyMode::isClassMode
but if this were to change you would be able to use extends
Added a bit onto it and have gotten it to this result
const ByteBuddy = javet.package.net.bytebuddy.ByteBuddy; const Handler = javet.package.Handler; const HandlerAdapter = javet.package.net.bytebuddy.implementation.InvocationHandlerAdapter; const ElementMatchers = javet.package.net.bytebuddy.matcher.ElementMatchers; function generate(jsClass){ let loader = javet.package.java.lang.ClassLoader.getSystemClassLoader(); let buddy = new ByteBuddy(); let javaClass = Object.getPrototypeOf(jsClass); let proxy = buddy.subclass(javaClass).name(jsClass.name); let keys = Reflect.ownKeys(jsClass.prototype); for (let key of keys){ proxy = proxy.method(ElementMatchers.named(key)).intercept(HandlerAdapter.of(new Handler((args) => {return Reflect.get(jsClass.prototype, key).apply(args[0], args[1])}))); } return proxy.make().load(loader).getLoaded(); } class BiFunctionProxy extends javet.package.java.lang.System { apply(arg) { console.log(arg) return arg; } } const Functioncls = generate(BiFunctionProxy); let f = new Functioncls(); f.apply("hej")
but there is an issue with this since it doesn't work with interface classes because in the proxy converter interface classes do not get the prototype of classes check out V8ProxyMode::isClassMode
May I post a question? How to extend final "System" class?
Added a bit onto it and have gotten it to this result
const ByteBuddy = javet.package.net.bytebuddy.ByteBuddy; const Handler = javet.package.Handler; const HandlerAdapter = javet.package.net.bytebuddy.implementation.InvocationHandlerAdapter; const ElementMatchers = javet.package.net.bytebuddy.matcher.ElementMatchers; function generate(jsClass){ let loader = javet.package.java.lang.ClassLoader.getSystemClassLoader(); let buddy = new ByteBuddy(); let javaClass = Object.getPrototypeOf(jsClass); let proxy = buddy.subclass(javaClass).name(jsClass.name); let keys = Reflect.ownKeys(jsClass.prototype); for (let key of keys){ proxy = proxy.method(ElementMatchers.named(key)).intercept(HandlerAdapter.of(new Handler((args) => {return Reflect.get(jsClass.prototype, key).apply(args[0], args[1])}))); } return proxy.make().load(loader).getLoaded(); } class BiFunctionProxy extends javet.package.java.lang.System { apply(arg) { console.log(arg) return arg; } } const Functioncls = generate(BiFunctionProxy); let f = new Functioncls(); f.apply("hej")
but there is an issue with this since it doesn't work with interface classes because in the proxy converter interface classes do not get the prototype of classes check out V8ProxyMode::isClassMode
May I post a question? How to extend final "System" class?
I can't use the example you gave me at all, and there is no method in the generated class.
Added a bit onto it and have gotten it to this result
const ByteBuddy = javet.package.net.bytebuddy.ByteBuddy; const Handler = javet.package.Handler; const HandlerAdapter = javet.package.net.bytebuddy.implementation.InvocationHandlerAdapter; const ElementMatchers = javet.package.net.bytebuddy.matcher.ElementMatchers; function generate(jsClass){ let loader = javet.package.java.lang.ClassLoader.getSystemClassLoader(); let buddy = new ByteBuddy(); let javaClass = Object.getPrototypeOf(jsClass); let proxy = buddy.subclass(javaClass).name(jsClass.name); let keys = Reflect.ownKeys(jsClass.prototype); for (let key of keys){ proxy = proxy.method(ElementMatchers.named(key)).intercept(HandlerAdapter.of(new Handler((args) => {return Reflect.get(jsClass.prototype, key).apply(args[0], args[1])}))); } return proxy.make().load(loader).getLoaded(); } class BiFunctionProxy extends javet.package.java.lang.System { apply(arg) { console.log(arg) return arg; } } const Functioncls = generate(BiFunctionProxy); let f = new Functioncls(); f.apply("hej")
but there is an issue with this since it doesn't work with interface classes because in the proxy converter interface classes do not get the prototype of classes check out V8ProxyMode::isClassMode
May I post a question? How to extend final "System" class?
I can't use the example you gave me at all, and there is no method in the generated class.
That was to show to usage of it and the javascript engine allows the use of extends javet.package.java.lang.System because the System object gets the prototype of a class but interface classes does not. so that was only a concept for it. if you want something that works now you would have to use this
Added a bit onto it and have gotten it to this result
const ByteBuddy = javet.package.net.bytebuddy.ByteBuddy; const Handler = javet.package.Handler; const HandlerAdapter = javet.package.net.bytebuddy.implementation.InvocationHandlerAdapter; const ElementMatchers = javet.package.net.bytebuddy.matcher.ElementMatchers; function generate(jsClass){ let loader = javet.package.java.lang.ClassLoader.getSystemClassLoader(); let buddy = new ByteBuddy(); let javaClass = Object.getPrototypeOf(jsClass); let proxy = buddy.subclass(javaClass).name(jsClass.name); let keys = Reflect.ownKeys(jsClass.prototype); for (let key of keys){ proxy = proxy.method(ElementMatchers.named(key)).intercept(HandlerAdapter.of(new Handler((args) => {return Reflect.get(jsClass.prototype, key).apply(args[0], args[1])}))); } return proxy.make().load(loader).getLoaded(); } class BiFunctionProxy extends javet.package.java.lang.System { apply(arg) { console.log(arg) return arg; } } const Functioncls = generate(BiFunctionProxy); let f = new Functioncls(); f.apply("hej")
but there is an issue with this since it doesn't work with interface classes because in the proxy converter interface classes do not get the prototype of classes check out V8ProxyMode::isClassMode
May I post a question? How to extend final "System" class?
I can't use the example you gave me at all, and there is no method in the generated class.
That was to show to usage of it and the javascript engine allows the use of extends javet.package.java.lang.System because the System object gets the prototype of a class but interface classes does not. so that was only a concept for it. if you want something that works now you would have to use this
Thanks and I roughly inject into "JavetJVMInterceptor.JavetV8.proxyGetStringGetterMap" with
stringGetterMap.put("extend", (propertyName) ->
v8Runtime.createV8ValueFunction(
new JavetCallbackContext(
propertyName,
this, JavetCallbackType.DirectCallNoThisAndResult,
(NoThisAndResult<Exception>) (v8Values) -> {
if (v8Values.length > 1) {
try (V8ValueProxy v8ValueProxy = (V8ValueProxy) v8Values[0];
V8ValueObject v8ValueObject = (V8ValueObject) v8Values[1]) {
Class<?> cls = v8ValueProxy.getV8Runtime().getConverter().toObject(v8ValueProxy);
if (v8ValueObject.getType() == V8ValueReferenceType.Function) {
V8ValueFunction jclass = (V8ValueFunction) v8ValueObject;
ByteBuddy buddy = new ByteBuddy();
Builder<?> builders = buddy.subclass(cls);
final AtomicReference<Builder<?>> proxy = new AtomicReference<>(builders);
V8ValueObject prototype = jclass.getPrototype();
try (V8ValueFunction getOwnKeys = prototype.getV8Runtime()
.createV8ValueFunction("Reflect.ownKeys");
V8ValueArray keys = getOwnKeys.call(null, prototype)) {
keys.forEach((key -> {
proxy.set(proxy.get().method(ElementMatchers.named(key.asString()))
.intercept(InvocationHandlerAdapter.of(new Handler((args) -> {
try (V8ValueFunction v8ValueFunction = prototype.get(key.asString());
V8Value result = v8ValueFunction.call(null, args)) {
return v8ValueFunction.getV8Runtime().toObject(result);
} catch (JavetException e) {
throw new RuntimeException(e);
}
}))));
}));
}
ClassLoader loader = ClassLoader.getSystemClassLoader();
try (Unloaded<?> make = proxy.get().make()) {
return v8ValueProxy.getV8Runtime()
.getConverter()
.toV8Value(v8ValueProxy.getV8Runtime(),
make.load(loader, strategy).getLoaded()
);
}
} else {
try (V8Value _methods = v8ValueObject.get("methods");
V8Value _loader = v8ValueObject.get("loader")) {
ClassLoader loader = ClassLoader.getSystemClassLoader();
if (!_loader.isNullOrUndefined()) {
loader = _loader.getV8Runtime().toObject(_loader);
}
ByteBuddy buddy = new ByteBuddy();
Builder<?> builders = buddy.subclass(cls);
final AtomicReference<Builder<?>> proxy = new AtomicReference<>(builders);
if (!_methods.isNullOrUndefined()) {
V8ValueObject methods = v8ValueObject.get("methods");
try (V8ValueArray keys = ((V8ValueArray) methods.getPropertyNames())) {
keys.forEach((key -> proxy.set(proxy.get().method(ElementMatchers.named(key.asString()))
.intercept(InvocationHandlerAdapter.of(new Handler((args) -> {
try (V8ValueFunction v8ValueFunction = methods.get(key.asString());
V8Value result = v8ValueFunction.call(null, args)) {
return v8ValueFunction.getV8Runtime().toObject(result);
} catch (JavetException e) {
throw new RuntimeException(e);
}
}))))));
}
}
try (Unloaded<?> make = proxy.get().make()) {
return v8ValueProxy.getV8Runtime()
.getConverter()
.toV8Value(v8ValueProxy.getV8Runtime(),
make.load(loader, strategy).getLoaded()
);
}
}
}
}
} else {
try (V8ValueFunction jclass = (V8ValueFunction) v8Values[0];
V8ValueFunction getPrototypeOf = (V8ValueFunction) jclass.getV8Runtime()
.createV8ValueFunction("Object.getPrototypeOf")) {
Class<?> cls = getPrototypeOf.getV8Runtime().toObject(getPrototypeOf.call(null, jclass));
ByteBuddy buddy = new ByteBuddy();
Builder<?> builders = buddy.subclass(cls);
final AtomicReference<Builder<?>> proxy = new AtomicReference<>(builders);
V8ValueObject prototype = jclass.getPrototype();
try (V8ValueFunction getOwnKeys = prototype.getV8Runtime()
.createV8ValueFunction("Reflect.ownKeys");
V8ValueArray keys = getOwnKeys.call(null, prototype)) {
keys.forEach((key -> {
proxy.set(proxy.get().method(ElementMatchers.named(key.asString()))
.intercept(InvocationHandlerAdapter.of(new Handler((args) -> {
try (V8ValueFunction v8ValueFunction = prototype.get(key.asString());
V8Value result = v8ValueFunction.call(null, args)) {
return v8ValueFunction.getV8Runtime().toObject(result);
} catch (JavetException e) {
throw new RuntimeException(e);
}
}))));
}));
}
ClassLoader loader = ClassLoader.getSystemClassLoader();
try (Unloaded<?> make = proxy.get().make()) {
return jclass.getV8Runtime()
.getConverter()
.toV8Value(jclass.getV8Runtime(),
make.load(loader, strategy).getLoaded()
);
}
}
}
})
));
and could be used by "javet.v8.extend" through your ideal of javascript
well if you are gonna change the source code you might as well use this.
well if you are gonna change the source code you might as well use this.
copy JavetJVMInterceptor and design by yourself, also you can add "await" at this place, such that you can use by "javet.v8.await()" from js side, many other methods have to be added into for convenient.
This feature is on the way. The following PoC works.
v8Runtime.getGlobalObject().set("File", File.class);
String codeString = "let ChildFile = extend(File, {\n" +
" exists: () => !$super.exists(),\n" +
" isFile: () => true,\n" +
" isDirectory: () => true,\n" +
"});\n" +
"let file = new ChildFile('/tmp/not-exist-file');\n" +
"JSON.stringify([file.exists(), file.isFile(), file.isDirectory()]);";
assertEquals("[true,true,true]", v8Runtime.getExecutor(codeString).executeString());
此功能即将推出。以下 PoC 的工作原理:
v8Runtime.getGlobalObject().set("File", File.class); String codeString = "let ChildFile = extend(File, {\n" + " exists: () => !$super.exists(),\n" + " isFile: () => true,\n" + " isDirectory: () => true,\n" + "});\n" + "let file = new ChildFile('/tmp/not-exist-file');\n" + "JSON.stringify([file.exists(), file.isFile(), file.isDirectory()]);"; assertEquals("[true,true,true]", v8Runtime.getExecutor(codeString).executeString());
Really, also for android, if it's true, it's the equivalent of JavaApater for rhino js, awesome
此功能即将推出。以下 PoC 的工作原理:
v8Runtime.getGlobalObject().set("File", File.class); String codeString = "let ChildFile = extend(File, {\n" + " exists: () => !$super.exists(),\n" + " isFile: () => true,\n" + " isDirectory: () => true,\n" + "});\n" + "let file = new ChildFile('/tmp/not-exist-file');\n" + "JSON.stringify([file.exists(), file.isFile(), file.isDirectory()]);"; assertEquals("[true,true,true]", v8Runtime.getExecutor(codeString).executeString());
I hope to refer to Rhino JS to achieve better compatibility with JavaApater and support for class-to-interface interfaces.
I hope to support JS to inherit classes from Java and implement rewrites, because many Java interfaces need to be rewritten, read-only, and there are still big limitations