How do I invoke a Java method from perl6

2019-03-19 16:25发布

问题:

use java::util::zip::CRC32:from<java>;

my $crc = CRC32.new();
for 'Hello, Java'.encode('utf-8') {
    $crc.'method/update/(B)V'($_);
}
say $crc.getValue();

sadly, this does not work

Method 'method/update/(B)V' not found for invocant of class 'java.util.zip.CRC32'

This code is available at the following links. It is the only example I've been able to find

  1. Rakudo Perl 6 on the JVM (slides)
  2. Perl 6 Advent Calendar: Day 03 – Rakudo Perl 6 on the JVM

回答1:

Final answer

Combining the code cleanups explained in the Your answer cleaned up section below with Pepe Schwarz's improvements mentioned in the Expectation alert section below we get:

use java::util::zip::CRC32:from<Java>;

my $crc = CRC32.new();

for 'Hello, Java'.encode('utf-8').list { 
    $crc.update($_);
}

say $crc.getValue();

Your answer cleaned up

use v6;
use java::util::zip::CRC32:from<Java>;

my $crc = CRC32.new();

for 'Hello, Java'.encode('utf-8').list { # Appended `.list` 
    $crc.'method/update/(I)V'($_); 
}
say $crc.getValue();

One important changed bit is the appended .list.

The 'Hello, Java'.encode('utf-8') fragment returns an object, a utf8. That object returns just one value (itself) to the for statement. So the for iterates just once, passing the object to the code block with the update line in it.

Iterating just once could make sense if the update line was .'method/update/([B)V', which maps to a Java method that expects a buffer of 8 bit ints, which is essentially what a Perl 6 utf8 is. However, that would require some support Perl 6 code (presumably in the core compiler) to marshal (automagically convert) the Perl 6 utf8 into a Java buf[] and if that code ever existed/worked it sure isn't working when I test with the latest Rakudo.

But if one appends a judicious .list as shown above and changes the code block to match, things work out.

First, the .list results in the for statement iterating over a series of integers.

Second, like you, I called the Integer arg version of the Java method (.'method/update/(I)V') instead of the original buffer arg version and the code then worked correctly. (This means that the binary representation of the unsigned 8 bit integers returned from the Perl 6 utf8 object is either already exactly what the Java method expects or is automagically marshaled for you.)

Another required change is that the from<java> needs to be from<Java> per your comment below -- thanks.

Expectation alert

As of Jan 2015:

  • Merely using the JVM backend for Rakudo/NQP (i.e. running pure P6 code on a JVM) still needs more hardening before it can be officially declared ready for production use. (This is in addition to the all round hardening that the entire P6 ecosystem is expected to undergo this year.) The JVM backend will hopefully get there in 2015 -- it will hopefully be part of the initial official launch of Perl 6 being ready for production use this year -- but that's going to largely depend on demand and on there being more devs using it and contributing patches.

  • P6 code calling Java code is an additional project. Pepe Schwarz has made great progress in the last couple months in getting up to speed, learning the codebase and landing commits. He has already implemented the obviously nicer shortname calling shown at the start of this answer and completed a lot more of the marshaling logic for converting between P6 and Java types and is actively soliciting feedback and requests for specific improvements.



回答2:

The code which is responsible for this area of Java interop is found in the class org.perl6.nqp.runtime.BootJavaInterop. It suggests that the overloaded methods are identified by the string method/<name>/<descriptor>. The descriptor is computed in function org.objectweb.asm.Type#getMethodDescriptor. That jar is available through maven from http://mvnrepository.com/artifact/asm/asm.

import java.util.zip.CRC32
import org.objectweb.asm.Type

object MethodSignatures {
  def printSignature(cls: Class[_], method: String, params: Class[_]): Unit = {
    val m = cls.getMethod(method, params)
    val d = Type.getMethodDescriptor(m)
    println(m)
    println(s"\t$d")
  }
  def main(args: Array[String]) {
    val cls = classOf[CRC32]

    # see https://docs.oracle.com/javase/8/docs/api/java/util/zip/CRC32.html
    val ab = classOf[Array[Byte]]
    val i = classOf[Int]

    printSignature(cls, "update", ab)
    printSignature(cls, "update", i)
  }
}

This prints

public void java.util.zip.CRC32.update(byte[])
    ([B)V
public void java.util.zip.CRC32.update(int)
    (I)V

Since I want to call the update(int) variant of this overloaded method, the correct method invocation (on line 5 of the example program) is

$crc.'method/update/(I)V'($_);

This crashes with

This representation can not unbox to a native int

finally, for some reason I do not understand, changing the same line to

$crc.'method/update/(I)V'($_.Int);

fixes that and the example runs fine.

The final version of the code is

use v6;
use java::util::zip::CRC32:from<java>;

my $crc = CRC32.new();

for 'Hello, Java'.encode('utf-8') {
    $crc.'method/update/(I)V'($_.Int);
}
say $crc.getValue();


回答3:

I got this to work on Perl 6.c with following modification (Jan 4, 2018):

use v6;
use java::util::zip::CRC32:from<JavaRuntime>;

my $crc = CRC32.new();

for 'Hello, Java'.encode('utf-8').list { 
    $crc.update($_);
}
say $crc.getValue();

Resulting in:

% perl6-j --version
This is Rakudo version 2017.12-79-g6f36b02 built on JVM
implementing Perl 6.c.

% perl6-j crcjava.p6 
1072431491