To learn more about Debian and Linux in general I'm selecting utilities at random from my PATH using the command below, learning what they do and writing a blog post about it. Previously: Part 1, Part 2
$ (for folder in `echo $PATH | sed "s/:/\\n/g"`; do ls -1 $folder; done; ) | shuf -n 1 | xargs man
The random command I'm looking at this time is
jjs - whose summary is simply "Invokes the Nashorn engine" which is a little vague and as it turns out is underselling things slightly.
Nashorn is a Javascript engine written in Java for the Java VM with very neat integration and access to the range of libraries and functionality provided by the JDK.
While I'm not a Java or Javascript developer by trade I am surprised that I had never seen this pop up on Hacker News or
lobste.rs before, and I'm sure many professional Java devs aren't particularly familiar with it either. I was even more surprised how quick it was to get productive (the man page suggests using the
println function, which doesn't exist) since my hazy memories from using Java at university involved fiddling around with the CLASSPATH env variable and launching things in weird ways.
Entering jjs takes you to a REPL prompt where you can muck around with javascript to your heart's content while you get familiar - here you'll see what I mean about the example in the manpage, println should be print:
$ jjs
jjs> println("hello, world!")
:1 ReferenceError: "println" is not defined
jjs> print("hello, world!")
hello, world!
jjs> function add(x, y) { return x + y }
function add(x, y) { return x + y }
jjs> add(10, 20)
30
jjs> function fib(n) { if (n < 1) { return 0 } else if (n <= 2) { return 1 } else { return fib (n - 1) + fib (n - 2)} }
function fib(n) { if (n < 1) { return 0 } else if (n <= 2) { return 1 } else { return fib (n - 1) + fib (n - 2)} }
jjs> fib(3)
2
jjs> fib(50)
12586269025
jjs>
What I really like is that there's a fuss-free interface to the entire JDK through an object conveniently called java:
jjs> var str = new java.lang.String("Hello, world!")
jjs> java.lang.System.out.println(str)
Hello, world!
jjs> var dict = new java.util.HashMap()
jjs> dict.put("foo", "bar")
null
jjs> dict.put("baf", "baz")
null
jjs> dict.forEach(function(k,v) { print(k + v); })
bafbaz
foobar
That final line pretty much sums up why I think this is cool - having created an instance of a
java.util.ashMap we iterate over it using its
forEach method but we can give it a javascript lambda function as an argument to apply to each key/value pair. There's no denying that this is neat :)
So Nashorn is actually a good deal slower than V8 (and that's with a cold VM, warmed up the difference is more stark) - which isn't a huge problem I suppose unless you're doing some really heavy lifting using Nashorn. I don't think many people are.
My previous "Discover ..." posts generally had to lay a bit of groundwork to introduce various OS concepts before the utility could be understood. However since Java and Javascript are both pretty commonplace there's no introduction needed, and the best way to show it off and understand what it's capable of is to write some code.
So I implemented four demo programs which are small enough to comprehend in a minute or so, explore the interesting relationship between js/JDK and demonstrates some relatively common use-cases for programming languages:
- shell scripting
- unix system utilities
- visualisation
- web services
Demo 1 - a simple shell script
I'd recently read a
blog post at IBM developerWorks tracing this history of UNIX shells (csh, tcsh, bash, etc) and implementing the same task in each one, I figured that this was as good a task as any to start with. I implemented this as
findexec.js below:
The code is a little less elegant than the bash version, we rely pretty heavily on java.io.File to accomplish some of the things built into bash, but realistically we're using the wrong tool for the job here.
$ jjs findexec.js -- /usr/local/bin
/usr/local/bin/fsv
/usr/local/bin/n-m
/usr/local/bin/behave
/usr/local/bin/tzupdate
/usr/local/bin/dotnet
Demo 2 - a unix-y utility
The next program I wrote was a unix-style system utility that reverses the order of its input, which is either
- a list of lines read in from stdin (piped or input)
- a list of files supplied as arguments, which are each written reversed
This was a little more fun to write - couple of interesting things here. First was ability to use generics - could just create new ArrayList without specifying the type. Second was polymorphism between a Java stdlib class BufferedReader and a javascript class I wrote MultipleFileReader which both happen to implement a method readLine() but which don't explicitly implement any common interface or abstract class.
I implemented this as filerev.js, which is a wee bit long for this blog but can be found at
this Gist on GitHub. Below is a little snippet showing its usage:
$ cat > foo << EOF
> herp
> derp
> blorp
> EOF
$ jjs filerev.js -- foo
blorp
derp
herp
$ cat foo | jjs filerev.js
blorp
derp
herp
Demo 3 - a JavaFX utility
In the manpage I noticed the -fx which "launches the script as a JavaFX application". I hadn't used Java since university so I had no clue what JavaFX was, but it's apparently a set of libraries for writing graphical applications in Java, and mostly replaces Swing/AWT toolkits among other things.
After I read a bunch of documentation and puddled around a bit I decided that I wanted an application which can quickly produce little line graphs from input piped via stdin (and dynamically resizes according to the max/min values in the dataset).
This was a little trickier than the previous two examples but after finding the documentation on
extending abstract classes (for the AnimationTimer) the rest was surprisingly straight forward. I created a file containing a a few repetitions of a sine wave, and piped it in to generate the visualisation below:
Again, the code is a Gist on GitHub as
plotstdin.js along with the input data (and the code that generated it).
Demo 4 - a web service
For my final example I wanted to spin up a quick and simple web service, however this apparently not so straight-forward in Java. While the JDK has plenty of libraries available, there's no equivalent to
SimpleHttpServer in Python or
http.createServer() in Node.js - it seems that you need to use a third party package like Tomcat or Jetty. This presents a bit of a problem, since I want to create a little self-contained example without having to resort to resolve any dependencies using Gradle and Maven, which I'm unfamiliar with and would be tricky to work with using Nashorn.
However I found a nice project on GitHub which handles most of this called
Nasven which wraps Maven and let's me easily add a dependency for Spark (lightweight web framework) and launch it.
I created a repo called
thumbooo which will spin up a simple web service running on port 8080, with a single POST resource that produces < 100 x 100 pixel thumbnails for submitted images.
While Nasven handled the toughest part, I still ran into trouble since Nashorn was unable to determine which of the three ImageIO.write() functions it should call since the image resizing produced an object of type ToolkitImage and it expected a RenderedImage.
You can ran the following to start the server:
$ git clone https://github.com/smcl/thumbooo
Cloning into 'thumbooo'...
remote: Counting objects: 42, done.
remote: Compressing objects: 100% (37/37), done.
remote: Total 42 (delta 18), reused 17 (delta 4), pack-reused 0
Unpacking objects: 100% (42/42), done.
Checking connectivity... done.
$ cd thumbooo
$ jjs -scripting nasven.js -- src
and in another xterm use cURL to POST an image to the /thumb endpoint:
$ curl -L -o thumb.png --form "file=@lenna.png" http://localhost:8080/thumb
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 494k 0 32358 100 462k 638k 9353k --:--:-- --:--:-- --:--:-- 9447k
This resized the original 512x512 Lenna image ...
... to a 100x100 thumbnail:
Conclusion
I'm impressed with the integration between Javascript and Java that Nashorn provides, as demonstrated by the snippets above it's relatively flexible and easy to use (if a tad slow!). As a technical exercise it's very impressive, however I'm still not 100% sure what it was originally meant to be used for. Perhaps as a non-js/Java developer I'm missing something, but I feel like there are already better options available for individual hackers all the way to Enterprise-scale projects. I certainly had fun using it though!