13

I have a simple Java class that has some methods:

public class Utils {
    public void deal(String price, int amount) {
        // ....
    }
    public void bid(String price, int amount) {
        // ....
    }
    public void offer(String price, int amount) {
        // ....
    }
}

I would like to create an instance of this class and allow the Javascript code to call the methods directly, like so:

deal("1.3736", 100000);
bid("1.3735", 500000);

The only way I could figure out for now was to use

ScriptEngine engine = new ScriptEngineManager().getEngineByName("js");
engine.put("utils", new Utils());

and then use utils.deal(...) in the Javascript code. I can also write wrapper functions in Javascript for each method, but there should be a simpler way to do this automatically for all the public methods of a class.

3
  • You do realise Java and JavaScript are two complete different languages. They may have similar C-like syntax, but they are not the same. Commented Mar 31, 2010 at 11:09
  • 1
    Rhino is an embedded js engine for Java. This sort of thing should be achievable. I managed it in Perl with SpiderMonkey: blog.dorward.me.uk/2006/02/02/spambots_that_drink_coffee.html Commented Mar 31, 2010 at 11:12
  • 3
    @thecoshman This is an absolutely valid question. You can run Javascript in Java and you may need to use Java goodies in JS then. Commented Mar 31, 2010 at 11:41

4 Answers 4

7

I'm not real familiar with Rhino, but something like this should work:

for(var fn in utils) {
  if(typeof utils[fn] === 'function') {
    this[fn] = (function() {
      var method = utils[fn];
      return function() {
         return method.apply(utils,arguments);
      };
    })();
  }
}

Just loop over the properties of utils,and for each one that is a function, create a global function that calls it.

EDIT: I got this working in a Groovy script, but I had to set utils in the bindings, not on the engine like in your code:

import javax.script.*

class Utils {
   void foo(String bar) {
      println bar
   }   
}

ScriptEngine engine = new ScriptEngineManager().getEngineByName("js");

engine.eval("""
for(var fn in utils) {
  if(typeof utils[fn] === 'function') {
    this[fn] = (function() {
      var method = utils[fn];
      return function() {
         return method.apply(utils,arguments);
      };
    })();
  }
}

foo('foo'); // prints foo, sure enough
""",new SimpleBindings("utils":new Utils()))
Sign up to request clarification or add additional context in comments.

2 Comments

+1 for nice use of closures. I think I'm going to go with your solution.
How would you get this to work in Java? Would you use the following: ScriptEngine engine = new ScriptEngineManager().getEngineByName("js"); engine.put("utils", new Utils()); or something else?
4

I'm not sure how this would work using the JSR-223 API, but with the Rhino API, you can create a FunctionObject with the method you want to add like this.

Class[] parameters = new Class[] { String.class, Integer.class };
Method dealMethod = Utils.class.getMethod("deal", parameters);
engine.put("deal", new FunctionObject("deal", dealMethod, scope));

The documentation is available at https://www.mozilla.org/rhino/apidocs/org/mozilla/javascript/FunctionObject.html.

You might need to reference the Rhino library to access the FunctionObject class, and I'm not sure how you would get the scope object with the JSR-223 API (although, it's possible that null would work).

4 Comments

I don't think this will work because the deal function object has no reference to the instance.
@Geoff: I was assuming they were static methods, since they would be called without a (JavaScript) this object. Looking at it again, I guess they aren't, but probably should be.
Thanks for this. Actually, when using the Rhino API, the Context class has a javaToJS method which does exactly what I need. It looks like it should work with non-static methods too. I was hoping for a JSR-223 solution, but I don't think there is one.
Thanks for this, the Rhino documentation is excessively unclear about this part
0

This is possible if you use the rhino API rather than the ScriptEngine API as explained in this answer: https://stackoverflow.com/a/16479685/1089998.

I prefer this approach over Noah's answer as it means you don't need to execute random javascript code before each execution.

I have a working example here:

https://github.com/plasma147/rhino-play

Comments

0

With Java Lambdas (so since 1.8) it is actually possible to just do this:

ScriptEngine engine = new ScriptEngineManager().getEngineByName("js");
BiConsumer<String, Integer> deal = Utils::deal
engine.put("deal", deal);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.