X
  • November 5, 2015

Fully Transparent Java Objects in Nashorn

Nashorn's JSAdapter is a powerful tool for handling Java objects in JavaScript with full transparency. It can be used to wrap Java objects and add new properties and behaviour.

For example:

var Date = Java.type('java.time.LocalDate')

function wrapDate(d) {
    return new JSAdapter() {
        __get__: function(name) {
            if (name === 'yesterday') {
                return d.minusDays(1)
            }
            return d[name]
        }
    }
}

var now = wrapDate(Date.now())

When executed, this script will wrap the Date object representing the current moment in time in a JSAdapter that will, as soon as any property access to the wrapped object is attempted, invoke its __get__ function. If the property's name is one of those the adapter understands (here, yesterday), the appropriate value will be returned.

jjs> load('yesterday.js')
jjs> print(now.yesterday)
2015-11-04

All other properties are retrieved from the wrapped object.

But these examples indicate that the wrapping is not entirely perfect yet:

jjs> print(now)
<shell>:1 TypeError: [object JSAdapter] has no such function "toString"
jjs> print(now.lengthOfMonth())
<shell>:1 TypeError: [object JSAdapter] has no such function "lengthOfMonth"

The wrapper is an object in its own right that only forwards those requests to the wrapped object that it is told to forward.

What is needed here to make the wrapper fully transparent is a default case - like the one added for properties - that forwards all method calls to the wrapped Java object. The case is slightly more complicated than property lookups because the receiver and arguments need to be treated correctly.

Here is an idiom that can be used for transparent call forwarding:

function wrapDate(d) {
    return new JSAdapter() {
        ...
        __call__: function() {
            var args = [d].concat(Array.prototype.slice.call(arguments, 1)
            return Function.call.apply(d[arguments[0]], args)
        }
    }
}

These two lines of highly reflective JavaScript achieve the desired functionality. Each JavaScript function has an implicit local variable,
arguments, that holds all arguments passed to the current activation of the function. This arguments array is used in two places here.

In the first line, the actual call arguments are extracted from the array. The first element in arguments is the name of the called method: this is not needed in the arguments. The receiver, however, is needed, and it is prepended to the arguments by means of calling concat.

In the second line, the idiom applies the Function.call function to actually perform the call. The object representing the method to be called is retrieved from the wrapped object by means of a property lookup - recall that the first element in the arguments variable is the name of the called method. The arguments, including the receiver object, are already present in args.

With this, wrapping is transparent:

jjs> print(now)
2015-11-05
jjs> print(now.lengthOfMonth())
30
jjs> print(now.yesterday)
2015-11-04

An example illustrating the transparent forwarding idiom is contained in the JDK 9 Nashorn sources, in samples/jsadapter-fallthrough.js.

Join the discussion

Comments ( 1 )
  • will Monday, January 4, 2016

    Very nice. The trick here is will this wrapper also work on popular browsers? The best use-case I can think of for this tool is to back-stop a HTML GUI front-end via a browser or something like WebKit on a mobile.

    Since it looks like (almost all) browsers won't be supporting Java via NPAPI in the near future, what's the strategy for running Java over the web/browsers? Is there a 'competitive' HTML5 Java based browser in the wings?


Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.Captcha

Recent Content

Oracle

Integrated Cloud Applications & Platform Services