I understand that this helper method can be called from a Java code to check if your Java/JS Object has a property you are looking for
but i would like to know if this is called by the Nashorn Engine while we use this JSObject/AbstractJSObject implementation in a JavaScript code.
I am aware of the fact that doing a . inside JavaScript will in turn invoke the Java method .getMember()
If "in" operator in used in JavaScript (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/in) on a JSObject instance, Nashorn will call hasMember method on that JSObject.
Example code:
import javax.script.*;
import jdk.nashorn.api.scripting.*;
public class Main {
public static void main(String[] args) throws Exception {
ScriptEngineManager m = new ScriptEngineManager();
ScriptEngine e = m.getEngineByName("nashorn");
e.put("obj", new AbstractJSObject() {
#Override
public boolean hasMember(String name) {
System.out.println("hasMember called for " + name);
return false;
}
});
// in operator triggers hasMember call on JSObject instance
e.eval("if ('foo' in obj) print('yes')");
}
}
The output from the above program looks like:
hasMember called for foo
Related
I have lot of enum class and all having one common method. I want to invoke that common method and return dynamically using GWT jsni method.
Let say I have following enum classes in different packages.
package A;
enum WidgetType{
TEXT_BOX("A"),SVG("B");
String type;
WidgetType(String type){
this.type = type;
}
public static WidgetType getDescriptiveValue( String type ){
for(WidgetType widgetType : WidgetType.values()){
if(widgetType.type.equalsIgnoreCase(type) ) return widgetType;
}
return null;
}
}
package B;
enum MessageType{
INFO("I"),WARN("W"),ERROR("E");
String type;
MessageType(String type){
this.type = type;
}
public static MessageType getDescriptiveValue( String type ){
for(MessageType messageType : MessageType.values()){
if(messageType.type.equalsIgnoreCase(type) ) return messageType;
}
return null;
}
}
Somewhat I will get enum full qualified names in string like [A.WidgetType,B.MessageType].
Based on this value I want to invoke enum class getDescriptiveValue method using JSNI.
So How to achieve this in GWT jsni.
When I try to pass class name as arguments like below I am getting compile time error.
Approach 1:
native void enumValue(String enumClass,String value) /*-{
console.log($entry(#enumClass::getDescriptiveValue(Ljava/lang/String;)(value)) );
}-*/;
Approach 2:
native void enumValue(String enumClass,String value) /*-{
console.log(#enumClass::getDescriptiveValue(Ljava/lang/String;)(value));
}-*/;
I don't know is there any way to use Java Reflection in GWT client side?
Please suggest me how to achieve this?
Put simply, this will not work - the JSNI syntax for java types/members is only a way to write plain code which will be evaluated at compile time into the JS equivalent of the member you are describing. You cannot simply turn a string into a class literal.
Instead, depending on how many enums you may wish to support in this way, consider constructing a Map<String, Map<String, Object>> of the enum types and param types you are looking for. This will require either manual code to list each enum type, or code generation to do this for you, and has the added advantage of not including JSNI.
Consider a type like this, which requires that each Enum you give it implements some HasType interface, which ensures that all have a getType() method, so they can be reflected on in the same way.
public class EnumLookup {
private final Map<String, Map<String, Object>> enumCache = new HashMap<>();
/** Helper that you can call internally or externally to build up the cache */
public <E extends HasType> void register(Class<E> type, E[] values) {
Map<String, Object> values = enumCache.computeIfAbsent(type.toString(), classname -> new HashMap<>());
for (E enumValue : values) {
values.put(enumValue.getType().toLowerCase(), enumValue);
}
}
/** And now the method you are trying to write */
public <E extends HasType> void enumValue(String enumClass, String value) {
// use of toLowerCase here and above ensures that type strings will match like
// in your original java
return (E) enumCache.get(enumClass).get(value.toLowerCase());
}
}
and then populate the cache somewhere
EnumCache cache = new EnumCache();
cache.register(WidgetType.class, WidgetType.values());
cache.register(MessageType.class, MessageType.values());
//...
Another way would be for the map to contain a Function<String, Object>, and use that to point at the getDescriptiveValue via a method reference. Then the registration would look like this:
cache.register(WidgetType.class, WidgetType::getDescriptiveValue);
cache.register(MessageType.class, MessageType::getDescriptiveValue);
1.is it possible to call the native method in the gwt into the other native method ?
this is the method i am calling from the VectorSource.java into Map.java
https://github.com/VOL3/v-ol3/blob/master/gwt-ol3/src/main/java/org/vaadin/gwtol3/client/source/VectorSource.java
public final native JsArray<Feature> getFeatures()/*-{
return this.getFeatures();
}-*/;
and i created a native method in Map.java class and getting the Features and i want to return these feature values to addOnPostRenderListener method below are the changes in the Map.java class
public native final Feature getFeatures(VectorSource sourceFeature)/*-{
var features=sourceFeature.#org.vaadin.gwtol3.client.source.VectorSource::getFeatures();
return features;
}-*/;
public native final void addOnPostRenderListener(OnPostRenderListener listener)/*-{
if(!this.__registered){
var that=this;
that.once('postrender',function(){
var feature=that.#org.vaadin.gwtol3.client.Map::getFeatures(vectorSource);
if(feature!=null){
var coordinate=feature.getGeometry().getCoordinate();
if(coordinates!=null){
var MapEvent={Pixel:that.getPixelFromCoordinate(that.__transformInputCoordinate(coordinates))};
that.__notifyPostRenderListeners(MapEvent);
}
}})
this.__postRenderListeners.push(listener);
}
}-*/;
the remaining code remains the same as shown in the below link
https://github.com/VOL3/v-ol3/blob/master/gwt-ol3/src/main/java/org/vaadin/gwtol3/client/Map.java
at the below code i am getting the error,as Expected a valid parameter type signature in JSNI method reference these lines of code is in the addOnPostRenderListener method
var feature=that.#org.vaadin.gwtol3.client.Map::getFeatures(vectorSource);
my target is to call the method getFeatures() from VectorSource.java class into the Map.java class and send the values to the other native method which is addOnPostRenderListener method.
Interface
public interface OnPostRenderListener {
public void onRender(MapEvent posEvent);
}
MapEvent
public class MapEvent extends JavaScriptObject {
protected MapEvent() {
}
public static final native Feature create()/*-{
return new $wnd.ol.Feature();
}-*/;
public native final Geometry getGeometry()-{
return this.getGeometry();
}-;*/
public native final Geometry getGeometry()/*-{
return this.geometry;
}-*/;
public native final Coordinate getCoordinate()/*-{
return this.coordinate;
}-*/;
public native final Pixel getPixel()/*-{
return this.Pixel;
}-*/;
//written code not used
public native final Map getPixelFromCoordinate(Coordinate coord)/*-{
return this.getPixelFromCoordinate(coord);
}-*/;
}
You need to pass your VectorSource sourceFeature parameter too. You are missing it in that.#org.vaadin.gwtol3.client.Map::getFeatures(Lcom/google/gwt/core/client/Source;);.
One way to do this, would be add it to your
addOnPostRenderListener(OnPostRenderListener listener)
e.g. addOnPostRenderListener(OnPostRenderListener listener, VectorSource vectorSource)
and then to access it like this:
var feature=that.#org.vaadin.gwtol3.client.Map::getFeatures(vectorSource);
Although I would recommend to completely drop JSNI and use the much better JsInterop. There is an OL-implementation using JsInterop too. Take a look here
I'd like to put native Java objects into the ScriptEngine bindings for easier access.
scriptEngine.put("myApi", myApiInstance);
Here "myApiInstance" has one non-static method "foo()".
Now in JS I have a function:
someJsFunction(func) { func.call(...) }
But the call
someJsFunction(myApiInstance.foo)
results to a "TypeError: func.call is not a function".
At the other hand, "myApiInstance.foo()" works as expected.
This look like a ScripEngine specifics, as "call()" method should be available in any function.
And yes, "typeof myApiInstance.foo" returns "function"
Java methods and function interface objects (lambda objects) are treated as script functions and hence callable from JavaScript as usual. As you mentioned "typeof" on such objects returns true. You can directly call these from script. But these are not real JS function objects in the sense that these don't have their proto to be Function.prototype. That said, if you want to invoke these using Function.prototype.call or .apply, [say you're dispatching to any callable passed), you can do the following:
import javax.script.*;
public class Main {
public static void main(String[] args) throws Exception {
ScriptEngineManager m = new ScriptEngineManager();
ScriptEngine e = m.getEngineByName("nashorn");
// get the java static method to call
e.eval("var getProp = java.lang.System.getProperty");
// direct call
e.eval("print(getProp('java.home'))");
// call using Function.prototype.call
e.eval("print(Function.prototype.call.call(getProp, null, 'java.home'))");
// a java object
e.eval("var out = java.lang.System.out");
// get an instance method
e.eval("var p = out.println");
// call java instance method using Function.prototype.call
e.eval("Function.prototype.call.call(p, out, 'hello world')");
}
}
There is a strange difference when I evaluate a nashorn script with or without bindings: without bindings there is no problem invoking a function, but with the bindings the function is not found. Here is an example:
public class SimpleNashornTester {
public static void main(String[] args) throws ScriptException, NoSuchMethodException {
ScriptEngine jsEngine = new ScriptEngineManager().getEngineByName("nashorn");
Compilable jsCompilable = (Compilable) jsEngine;
Invocable jsInvocable = (Invocable) jsEngine;
ScriptContext scriptCtxt = new SimpleScriptContext();
Bindings engineScope = scriptCtxt.getBindings(ScriptContext.ENGINE_SCOPE);
CompiledScript jsScript = jsCompilable.compile("function definition() {print(\"Hello\")}");
jsScript.eval(engineScope); // no error with jsScript.eval() !
jsInvocable.invokeFunction("definition", new Object[] {});
}
}
This yields the error:
Exception in thread "main" java.lang.NoSuchMethodException: No such function definition
at jdk.nashorn.api.scripting.ScriptObjectMirror.callMember(ScriptObjectMirror.java:204)
Without the parameter engineScope in the evaluation of the script the function is found and invoked. Can anybody explain this difference? How can I use bindings without getting an error?
You're using a new ScriptContext and it's associated ENGINE_SCOPE Bindings to compile the script. invokeFunction/invokeMethod use the default ScriptContext (and it's associated ENGINE_SCOPE Bindings) to search the function. Each different ENGINE_SCOPE Bindings is associated with its own ECMAScript global object (and it's own ECMAScript global objects).
So, you can fix your program by
changing the default context to be the new context before invoke:
// change the default ScriptContext
jsEngine.setContext(scriptCtxt);
jsInvocable.invokeFunction("definition", new Object[] {});
Use the default ScriptContext for compiled script as well. As in:
ScriptContext scriptCtxt = jsEngine.getContext(); // new SimpleScriptContext();
Monodroid does not yet natively support JavaScriptInterface with WebView.
I'm looking for an example .java file that can be used with this workaround.
IntPtr JavaScriptInterface_Class = JNIEnv.FindClass ("the/package/for/JavaScriptInterface");
IntPtr JavaScriptInterface_ctor = JNIEnv.GetMethodID (JavaScriptInterface_Class, "<init>", "()V");
IntPtr instance = JNIEnv.NewObject (JavaScriptInterface_Class, JavaScriptInterface_ctor);
appView.AddJavascriptInterface (new Java.Lang.Object (instance), "Android");
You could use a custom .java such as:
// TODO: use an actually valid package name. :-)
package the.package.for;
public class JavaScriptInterface {
// The JNI in the original question uses a default constructor.
// Either provide one explicitly or use the implicit one...
public JavaScriptInterface ()
{
}
// TODO: add any methods you want invokable from JavaScript here.
}
Don't forget to set the Build action for your .java file to to AndroidJavaSource.
I know that this thread is already a bit old. But i found this when was looking for the same and here is the solution how you can use c# methods
public class AndroidInterface : Java.Lang.Object
{
[Export]
public void Save(string text)
{
}
}
AndroidInterface androidInterace = new AndroidInterface();
webView.AddJavascriptInterface(androidInterface, "Android");