Java Interoperability

Calling Java Code from PicoLisp
Why not interface PicoLisp directly to Java? We have that already in Ersatz PicoLisp, the plain vanilla Java version of PicoLisp. It is almost trivial to port this functionality to real PicoLisp, by starting up and communicating with a Java virtual machine (JVM).

For universal use I added a "java/" directory to the PicoLisp core distribution (with version 3.1.9.7). Please note that because of differences in the PLIO format of external symbols, it is fully supported only on the 64-bit version of PicoLisp.

Startup

Naturally, a Java runtime (JRE), and optionally - if you want to use inline Java code (see below) - a development kit (JDK) should be installed.

An application needs to call

   (load "@java/lib.l")

For the following experiments, you can start PicoLisp as

   $ pil @java/lib.l +



The API

Just as in ErsatzLisp, java is the central function. The syntax is very similar, with one major difference: The Java library distinguishes more strictly between internal and transient symbols. Methods are recognized when they are passed as internal symbols, while class names should be transients.

The primitive types are passed directly. This means that transient symbols on the PicoLisp side map to String on the Java side, and PicoLisp numbers map - depending on the size - to int or BigInteger. Other number types see "Extended Data Formats" below. The Lisp symbols T and NIL map to Boolean.

Java Objects appear on the PicoLisp side as external symbols, like remote database objects, by making use of Java reflection and the PicoLisp *Ext mechanism. This allows transparent (read-only) access to Java objects with arbitrary Lisp functions like get, show, edit etc.

To create a new Java object (constructor call):

   (java 'cls 'T ['any ..]) -> obj



Call a static method msg for a class cls:

   (java 'cls 'msg ['any ..]) -> obj



Call a dynamic method msg for an object obj

   (java 'obj 'msg ['any ..]) -> obj



Like in ErsatzLisp, public accesses public fields in Java objects or classes:

   (public 'obj 'any ['any ..]) -> obj

returns the value of a public field any in object obj.

   (public 'cls 'any ['any ..]) -> obj

returns the value of a public field any in class cls.

In both forms, the optional any arguments will in turn access corresponding fields in the object retrieved so far.

To create an interface object:

   (interface 'obj 'cls|lst 'sym 'fun ..) -> obj

creates an interface, i.e. a set of methods for a class cls (or a list of classes lst), and associates it with the object obj. Note that the obj argument does not in ErsatzLisp.

Examples

From the above article:

   : (setq Sb (java "java.lang.StringBuilder" T "abc"))  # Construct object
   -> {OOOO5276732765}
   : (java Sb 'append (char 44))                         # Call 'append' method
   -> {OOOO5276732765}
   : (java Sb 'append 123)
   -> {OOOO5276732765}
   : (java Sb 'toString)                                 # Convert to String
   -> "abc,123"

This example is a bit silly, as strings are better handled directly in PicoLisp.

But a general calendar object might be useful:

   : (setq Cal (java "java.util.GregorianCalendar" T))
   -> {OOOO24137612300}

We can inspect it with show:

   : (show Cal)
   {OOOO24137612300} ({OOOO7764433062} . "java.util.GregorianCalendar")
      BC 0
      BCE 0
      AD 1
      CE 1
      EPOCH_OFFSET 719163
      EPOCH_YEAR 1970
      MONTH_LENGTH (31 28 31 30 31 30 31 31 30 31 30 31)
      LEAP_MONTH_LENGTH (31 29 31 30 31 30 31 31 30 31 30 31)
      ONE_SECOND 1000
      ONE_MINUTE 60000
      ONE_HOUR 3600000
      ONE_DAY 86400000
      ONE_WEEK 604800000
      MIN_VALUES (0 1 0 1 0 1 1 1 1 0 0 0 0 0 0 -46800000 0)
      LEAST_MAX_VALUES (1 292269054 11 52 4 28 365 7 4 1 11 23 59 59 999 50400000 1200000)
      MAX_VALUES (1 292278994 11 53 6 31 366 7 6 1 11 23 59 59 999 50400000 7200000)
      serialVersionUID -8125100834729963327
      gcal {OOOO2322434213}
      jcal NIL
      jeras NIL
      DEFAULT_GREGORIAN_CUTOVER -12219292800000
      gregorianCutover -12219292800000
      gregorianCutoverDate 577736
      gregorianCutoverYear 1582
      gregorianCutoverYearJulian 1582
      gdate {OOOO34261410206}
      cdate {OOOO34261410206}
      calsys {OOOO2322434213}
      zoneOffsets (3600000 0)
      originalFields NIL
      cachedFixedDate 735631
      $assertionsDisabled T
   -> {OOOO24137612300}

look at its class and superclass,

   : (show '{OOOO7764433062})
   {OOOO7764433062} ({OOOO376765756} . "java.util.Calendar")
   ...

   : (show '{OOOO376765756})
   {OOOO376765756} ({OOOO1514706475} . "java.lang.Object")
   -> {OOOO376765756}

or operate on it

   : (java (java Cal 'getTimeZone) 'getDisplayName)      # Get the time zone
   -> "Central European Time"

   : (java Cal 'get (public Cal 'HOUR_OF_DAY))           # Current hour
   -> 10
   : (time (time))                                       # Check it
   -> (10 34 0)

   : (java Cal 'get (public Cal 'WEEK_OF_YEAR))          # Current week
   -> 6
   : (week (date))                                       # Check it
   -> 6

and change values:

   : (java Cal 'get (public Cal 'YEAR))                  # Current year
   -> 2015
   : (java Cal 'set (public Cal 'YEAR) 2020)             # Modify it
   -> NIL
   : (java Cal 'get (public Cal 'YEAR))
   -> 2020



Simple GUI Examples

The source codes of the following three examples can also be downloaded from http://software-lab.de/javaDemo.tgz.

Again from the article:

   (let
      (Frame (java "javax.swing.JFrame" T "Bye-Frame")
         Button (java "javax.swing.JButton" T "OK") )
      (java Frame 'add "South" Button)
      (java Button 'addActionListener
         (interface Button "java.awt.event.ActionListener"  # When button is clicked,
            'actionPerformed '((Ev) (bye)) ) )              # Exit PicoLisp
      (java Frame 'setSize 100 60)
      (java Frame 'setVisible T) )

It shows a use case for interface.

The "Animation" task from http://rosettacode.org:

   (de animate (Str)
      (let
         (Txt (chop Str)
            Dir 1
            Rev (dec (length Txt))
            Frame (java "javax.swing.JFrame" T "Animation")
            Label (java "javax.swing.JLabel" T Str) )
         (java Label 'addMouseListener
            (interface Label "java.awt.event.MouseListener"
               'mouseClicked '((Ev) (setq Dir (if (= 1 Dir) Rev 1))) ) )
         (java Frame 'add Label)
         (java Frame 'pack)
         (java Frame 'setVisible T)
         (until (key 250)
            (java Label 'setText (pack (do Dir (rot Txt)))) )
         (java Frame 'dispose) ) )

You can call it as

   (animate "Hello World! ")

It will show a scrolling text, which reverses the scroll direction when you click on it. A key press stops it.

Inline Java Code

Like in ErsatzLisp, you can embed Java Programs directly into PicoLisp code, with the javac function:

   ### RosettaCode Image Noise ###
   (javac "ImageNoise" "JPanel" NIL
         "java.util.*"
         "java.awt.*" "java.awt.image.*" "javax.swing.*" )
      int DX, DY;
      int[] Pixels;
      MemoryImageSource Source;
      Image Img;
      Random Rnd;

      public ImageNoise(int dx, int dy) {
         DX = dx;
         DY = dy;
         Pixels = new int[DX * DY];
         Source = new MemoryImageSource(DX, DY, Pixels, 0, DX);
         Source.setAnimated(true);
         Img = createImage(Source);
         Rnd = new Random();
      }

      public void paint(Graphics g) {update(g);}
      public void update(Graphics g) {g.drawImage(Img, 0, 0, this);}

      public void draw() {
         for (int i = 0; i < Pixels.length; ++i) {
            int c = Rnd.nextInt(255);
            Pixels[i] = 0xFF000000 | c<<16 | c<<8 | c;
         }
         Source.newPixels();
         paint(getGraphics());
      }
   /**/

   (de imageNoise (DX DY Fps)
      (let
         (Dly (/ -1000 Fps)
            Frame (java "javax.swing.JFrame" T "Image Noise")
            Noise (java "ImageNoise" T DX DY)
            Button (java "javax.swing.JButton" T "OK") )
         (java Frame 'add Noise)
         (java Frame 'add "South" Button)
         (java Button 'addActionListener
            (interface Button 'java.awt.event.ActionListener
               'actionPerformed
               (curry (Dly Frame) (Ev)
                  (task Dly)
                  (java Frame 'setVisible NIL)
                  #{? (java Frame 'dispose) }# ) ) )
         (java Frame 'setSize DX DY)
         (java Frame 'setVisible T)
         (task Dly 0
            Obj Noise
            (java Obj 'draw) ) ) )

javac is analog to the gcc function in "@lib/native.l".

It takes a package or class name (here "ImageNoise"), an optional "extends" (here "JPanel") and "implements" (here empty), and an arbitrary number of "import" arguments (here "java.util.*" etc.).

Then all following code till a separating "/**/" line is taken as Java code, creating a file "ImageNoise.class" in the temporary (process-private) space. You can start it as (imageNoise 320 240 25). A window is shown with random image noise, and an "OK" button to close it.

Extended Data Formats

While integers are directly passed to/from the JVM, other number formats can be passed to the JVM as cons pairs consisting of an ID in the CAR and the value in the CDR. These are: If the CAR is a negative number, the value is taken as a double scaled by the negated number of digits.

   : (scl 6)
   -> 6
   : (round (public "java.lang.Math" 'PI))
   -> "3.142"
   : (round (java "java.lang.Math" 'pow (-6 . 4.0) (-6 . 3.0)))
   -> "64.000"



If the argument is a list, it is passed as an array of the corresponding type to/from the JVM.

See the file "@java/test.l" in the PicoLisp distribution for more examples.

How It Works

The top-level functions java, public, interface and reflect in "@java/lib.l" call the internal workhorse jre.

The runtime Java code is in "@java/reflector.jar". It was built with

   $ (cd java; ./mkJar)

from the sources in "Reflector.java" (the actual communication with PicoLisp) and "InOut.java" (a PLIO library).

jre checks a global variable *Java. If this variable is not set, it is assumed that the JVM is not running yet. It will be started by the line

   (pipe (exec 'java "-cp" Path "Reflector" Rqst Rply))

This runs Java with the proper classpath in Path, and the bidirectional file descriptor (for the pipes to standard input and from standard output of that process) is stored in *Java, together with a fixed external symbol offset of 65535 (more below). This pipe is then used to send commands to the JVM, and receive results.

Before that, two additional pipes Rqst (request) and Rply (reply) are created as named pipes (with mkfifo), and passed to the Java-call on the command line. They are used for asynchronous requests from Java back to PicoLisp, allowing the execution of Lisp code from Java (e.g. for the interface callbacks).

A background task is started to handle these requests.

Then a cons pair of that external symbol offset (65535, in the CAR of Java) and the reflect function is added to the PicoLisp system global *Ext. This allows to receive and send Java objects like remote database objects. The highest possible file number of 65535 was chosen to avoid conflicts with possibly open local and remote databases.

Whenever data is received from Java (with rd), or sent with pr, the corresponding ext-calls make handle the proper symbol offsets.

After *Java is initialized, subsequent calls to jre will only execute

   (ext (car *Java)
      (out (cdr *Java) (pr (rest)))
      (in (cdr *Java) (rd)) ) )

i.e. send all arguments to Java with out), and receive the results with in).

https://picolisp.com/wiki/?javacode

20mar16    erik