3

I'm trying to open a jar file and execute it's main function, but jpype is throwing an error that doesn't make sense to me. Here is my code:

jpype.startJVM(jpype.getDefaultJVMPath(), '-Djava.class.path="%s"' % jar)
CommandLine = jpype.JPackage('phylonet').coalescent.CommandLine
CommandLine.main(['-i', input_file, '-o', output_file])
jpype.shutdownJVM()

I get this error: TypeError: Package phylonet.coalescent.CommandLine.main is not Callable

I've provided the absolute path to the jar file, and I've gotten the main function from META-INF/MANIFEST.MF:

cat tmp/META-INF/MANIFEST.MF | grep Main-Class
Main-Class: phylonet.coalescent.CommandLine

The jar file I'm trying to open is called astral, from here: https://github.com/smirarab/ASTRAL

Calling it like this works as expected:

java -Djava.class.path="./astral.jar"

So why not when I call it with jpype?

David Mulder
  • 7,595
  • 11
  • 45
  • 61
  • Essentially the same question as here: https://stackoverflow.com/questions/26181553/calling-a-jar-file-from-python-using-jpype-total-newbie-query except I've followed those instructions, but it doesn't work. – David Mulder Apr 16 '19 at 22:34

2 Answers2

6

First of all, I have tested your code on my own jarfile. Indeed, I was presented with such error:

TypeError: Package clip.frontend.Start.main is not Callable

Then, after reading the docs carefully, I've used another method.

import jpype

# I've used other set of parameters to JVM, and modified a bit your classpath setting.
jpype.startJVM(jpype.getDefaultJVMPath(), "-ea", "-Djava.class.path=clip.jar")

# Second difference, I decided to use JClass because it was more clear for me.
# Parameter array was kept empty.
jpype.JClass("clip.frontend.Start").main([])
jpype.shutdownJVM()

And the output was correct:

% python2 main.py
2 2
+>+[<[>>+>+<<<-]>>[<<+>>-]>[[-]>>>>>>+<<<<<<<<<[-]>[-]>>>>>>>>[<<<<<<<<+>+>>>>>>>-]
<<<<<<<[>>>>>>>+<<<<<<<-]>>>>>>>[-]<<<<<<]<<<[>>+>+<<<-]>>[<<+>>-]>[[-]>>>>>>++
[<<<<<+>>>>>>>>>>>>+<<<<<<<-]<<<<<[>>>>>+<<<<<-]>>>>>>>>>>>>>[>>]+<<[<<]>[>[>>]
<+<[<<]>-]<<<<<<<[-]++[<<<<<+>>>>>>>>>>>>+<<<<<<<-]<<<<<[>>>>>+<<<<<-]>>>>>>>>>>>>>
[>>]+<<[<<]>[>[>>]<+<[<<]>-]<<<<<<<[-]#JVM has been shutdown

Now, I decided to translate my solution to match your problem:

import jpype
jpype.startJVM(jpype.getDefaultJVMPath(), "-ea", "-Djava.class.path=astral.jar")
jpype.JClass("phylonet.coalescent.CommandLine").main([])
jpype.shutdownJVM()

And the code works correctly. More important than the actual solution is the fact, why doesn't your code work. You used wrong set of parameters and specified the classpath in the other way.

Replacing JClass with JPackage, the code still works.

import jpype
jpype.startJVM(jpype.getDefaultJVMPath(), "-ea", "-Djava.class.path=astral.jar")
jpype.JPackage('phylonet').coalescent.CommandLine.main([])
jpype.shutdownJVM()

As the way you extract classes from classpath is correct, the only possible cause is specifying invalid parameter set. After removing -ea the code still works, so mistake you made lies in this fragment of code.

'-Djava.class.path="%s"' % jar

And in fact, I've used this in opposition to my answer, and bam, the code yields this:

TypeError: Package phylonet.coalescent.CommandLine.main is not Callable

This means, the parameter contained following:

-Djava.class.path="astral.jar"

instead of following

-Djava.class.path=astral.jar

The quotes were misplaced and raised the error in result.

Kamila Szewczyk
  • 1,874
  • 1
  • 16
  • 33
2

This was a classic issue with JPype. If the jar can't be loaded then JPackage will return another JPackage which is not callable. Common causes of a failure to load include

  • The JVM loaded does not support the version of the jar (Check that getDefaultJVMPath() is not some old version)
  • A jar dependency is missing.
  • The JVM could not find the Jar as the specified path.

The previous solution was to use java.lang.Class.forName which would print the diagnostics on the jar loading. Version 0.7.0 which is currently in available as a release candidate has addressed this.

Also it is recommended that you use jpype.imports or JClass rather than JPackage when importing a class. It is much safer as it will report a more meaningful error. For example:

import jpype
import jpype.imports

jpype.startJVM()
jpype.imports.registerDomain('phylonet')  # This is required as phylonet is not a tld

from phylonet.coalescent import CommandLine

You can mark a package as being conforming (Classes start upper, packages are lower) to force an error.

Karl Nelson
  • 336
  • 2
  • 3