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.

elifiner
  • 7,347
  • 8
  • 39
  • 48
  • You do realise Java and JavaScript are two complete different languages. They may have similar C-like syntax, but they are not the same. – thecoshman Mar 31 '10 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: http://blog.dorward.me.uk/2006/02/02/spambots_that_drink_coffee.html – Quentin Mar 31 '10 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. – lexicore Mar 31 '10 at 11:41

4 Answers4

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()))
noah
  • 21,289
  • 17
  • 64
  • 88
  • +1 for nice use of closures. I think I'm going to go with your solution. – elifiner Mar 31 '10 at 16:56
  • 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? – Anderson Green Aug 13 '12 at 17:41
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).

Matthew Crumley
  • 101,441
  • 24
  • 103
  • 129
  • I don't think this will work because the deal function object has no reference to the instance. – Geoff Reedy Mar 31 '10 at 15:00
  • @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. – Matthew Crumley Mar 31 '10 at 15:25
  • 1
    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. – elifiner Mar 31 '10 at 16:53
  • Thanks for this, the Rhino documentation is excessively unclear about this part – Rémy DAVID Jun 06 '14 at 09:44
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

Community
  • 1
  • 1
plasma147
  • 2,191
  • 21
  • 35
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);