Did you know? Programmers convert coffee to code.

If you like my articles, sponsor me a coffee.

JaPy — calling Python from Java

What about this post? I recently mentioned to call Python from a Java application to use the Dropbox API and promised to write about this topic.

The time has come for this. Now. Or at least for the first article.

Why?

This is the most frequent question. Why would somebody call from programming language A a code snippet or a library written in programming language B?

The simple answer is: DRtW (Don’t Reinvent the Wheel). If someone has already developed something you will / should / have to / could use then use it. Currently we have no time for reprogramming code parts in other languages.

Another reason can be that the other module can something which cannot be implemented in your language. For example keyboard logging (better known as creating a key logger). I won’t start a discussion about key logging. That’s out of the question. However I’ve worked on projects where the customers wanted a Java application which monitors the users of a computer (taking a screenshot from the desktop, counting the words he/she tips per minute, number of key presses and mouse clicks, etc). This is nice but Java cannot keep track of global keyboard and mouse events. But there exist some C/C++ libraries which can accomplish this and Java has the Native Interface (JNI) which you can use to call those libraries.

A third reason could be the above mentioned problem what you can face if you need to bulk-undelete some Dropbox files using the Java API. This is currently an almost impossible mission (it requires some spare time and open source commitment to implement this feature). In this case you can now a) implement the functionality to handle deleted files via the Java API b) use my implementation (which does the variation mentioned under “a)” ) or c) DRtW and use another library which can work with deleted files.

Calling Python from Java

Why the Python API? Because I had a working Python application which is capable of working with deleted files. I didn’t want to spend more time to look through the APIs which can handle deleted files too. That’s why Python.

Which options do you have if you want to call some Python code from your Java Application?

Runtime and ProcessBuilder

Runtime and ProcessBuilder are some, so to say, old-school methods calling external scripts from your Java application. The idea behind these solutions is to create a Python script as a file and call it at runtime. However they can be used to execute anything — so eventually you could use it to create Java classes from your Java application.

As an example I’ll write a simple Java application which creates a Python script which adds up some numbers and displays the result. And I’ll do the same with a Java application just to show how it can be done.

Runtime and ProcessBuilder are doing quite the same thing: they execute a process. The difference is how they do it.

Runtime returns the current runtime associated with the JVM and can execute a string command with given environment variables and a specified working directory. You have a simple convenience method for only one String statement to execute.

The ProcessBuilder creates a process with the program to execute and the arguments — each is a separate String object. Then you can start the process (the program with the variables you specified at build time).

I included two sample Java classes in the oldschool folder: OldSchoolCallingPython creates a simple Python script wich adds up the numbers given as system arguments (with Runtime and with ProcessBuilder); OldSchoolCallingJava creates, compiles and executes a Java class which does the same.

import java.io.*;

/**
 * This class creates a Python script and executes it first with Runtime and then with ProcessBuilder.
 *
 * @author GHajba
 */
public class OldSchoolCallingPython {

    public static final String FILE_NAME = "testScript.py";

    public static void main(String... args) {
        try {
            createPythonScript();

            String execution = "python " + FILE_NAME + " " + 13 + " " + 1 + " " + 20 + " " + 8;

            Process process1 = Runtime.getRuntime().exec(execution);
            getExecutionResult(process1);
            cleanUp();

            Process process2 = new ProcessBuilder("python", FILE_NAME, "" + 13, "" + 1, "" + 20, "" + 8).start();
            getExecutionResult(process2);
            cleanUp();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void cleanUp() {
        new File(FILE_NAME).delete();
    }

    private static void getExecutionResult(Process process1) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(process1.getInputStream()));
        System.out.println("the result is: " + reader.readLine());
        reader.close();
    }

    private static void createPythonScript() throws IOException {
        BufferedWriter pythonWriter = new BufferedWriter(new FileWriter(FILE_NAME));
        StringBuilder pythonApplication = new StringBuilder();
        pythonApplication.append("import sys");
        pythonApplication.append("\n");
        pythonApplication.append("sum = 0");
        pythonApplication.append("\n");
        pythonApplication.append("for i in range(1, len(sys.argv)):");
        pythonApplication.append("\n");
        pythonApplication.append("    sum += int(sys.argv[i])");
        pythonApplication.append("\n");
        pythonApplication.append("print(sum)");
        pythonApplication.append("\n");
        pythonWriter.write(pythonApplication.toString());
        pythonWriter.flush();
        pythonWriter.close();
    }
}
import java.io.*;

/**
 * This class demonstrates a simple Java program creating and writing another Java program
 *
 * @author GHajba
 */
public class OldSchoolCallingJava {
    public static final String FILE_NAME = "TestClass.java";

    public static void main(String... args) {
        try {
            createJavaClass();

            Runtime.getRuntime().exec("javac " + FILE_NAME);
            Process execute1 = Runtime.getRuntime().exec("java TestClass 13 1 20 8");
            getExecutionResult(execute1);
            cleanUp();

            new ProcessBuilder("javac", FILE_NAME).start();
            Process execute2 = new ProcessBuilder("java", "TestClass", "" + 13, "" + 1, "" + 20, "" + 8).start();
            getExecutionResult(execute2);
            cleanUp();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void cleanUp() {
        new File(FILE_NAME).delete();
    }

    private static void getExecutionResult(Process process1) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(process1.getInputStream()));
        System.out.println("the result is: " + reader.readLine());
        reader.close();
    }

    private static void createJavaClass() throws IOException {
        BufferedWriter javaWriter = new BufferedWriter(new FileWriter(FILE_NAME));
        StringBuilder javaApplication = new StringBuilder();
        javaApplication.append("public class TestClass {");
        javaApplication.append("\n");
        javaApplication.append("    public static void main(String... args) {");
        javaApplication.append("\n");
        javaApplication.append("        int sum = 0;");
        javaApplication.append("\n");
        javaApplication.append("        for(String arg : args) {");
        javaApplication.append("\n");
        javaApplication.append("            sum += new Integer(arg);");
        javaApplication.append("\n");
        javaApplication.append("        }");
        javaApplication.append("\n");
        javaApplication.append("        System.out.println(sum);");
        javaApplication.append("\n");
        javaApplication.append("    }");
        javaApplication.append("\n");
        javaApplication.append("}");
        javaWriter.write(javaApplication.toString());
        javaWriter.flush();
        javaWriter.close();
    }
}

As I mentioned previously there is one problem with this approach: you have to have the script already on the machine if you do not have write permissions at the system your Java class is executing.

Jython

Jython (for Java and Python) is a two way method. You can call Python from Java and call Java from Python (or you can write Python compiled to Java).

For the example I created a maven project to automatically include the Jython library (because I’m lazy to add it to the class path, and I do not want you to fight with it). This example does the same as the previous ones: it sums up the numbers given as arguments.

import org.python.core.PyObject;
import org.python.util.PythonInterpreter;

/**
 * This class calls a Python script with Jython.
 *
 * @author GHajba
 */
public class PythonCalling {
    public static void main(String... args) {
        PythonInterpreter pythonInterpreter = new PythonInterpreter();

        int count = 0;
        for(String arg : args) {
            try {
                count++;
                pythonInterpreter.set("number" + count, new Integer(arg));
            }
            catch (NumberFormatException e) {
                System.err.println(arg + " is not a number!");
                count--;
            }
        }
        pythonInterpreter.set("sum", 0);
        for(int i = 1; i <= count; i++) {
            pythonInterpreter.exec(String.format("sum += %s", "number"+i));
        }
        PyObject sum = pythonInterpreter.get("sum");
        System.out.println("The result is: " + sum.toString());
    }
}

The difference between the old-school techniques and Jython is that Jython works as a Python interpreter and you do not have to have a Python script along with your Java class (and do not need to write one either).

You can call an existing Python script with Jython too.

Naturally you have to have the Jython JAR on your class path to use the PythonInterpreter class.

Important notice: the current stable version of Jython is 2.5.3 which works with Python 2.5.3. Not a good thing, is it? However around 6 weeks ago the 2.7-b2 (beta) version has been released. It works with Python 2.7 🙂

JSR-223

With JSR-223 (starting with Java 6) there is another method, the ScriptEngine to call dynamic languages in Java. It works as Jython however you do not have to import external classes in your Java code.

Important note: You need both the Jython Engine and the Jython standalone JAR files to be able to use the ScriptEngine of javax.

The sample application is the same as with Jython: the only difference is the method calls (put instead of set; eval instead of exec).

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

/**
 * This class demonstrates how to call Python from Java with JSR-223
 *
 * @author GHajba
 */
public class JSR223 {

    public static void main(String[] args) throws ScriptException {
        ScriptEngine scriptEngine = new ScriptEngineManager().getEngineByName("python");
        if(scriptEngine == null) {
            System.err.println("Could not find script engine for python.");
            return;
        }
        int count = 0;
        for(String arg : args) {
            try {
                count++;
                scriptEngine.put("number" + count, new Integer(arg));
            }
            catch (NumberFormatException e) {
                System.err.println(arg + " is not a number!");
                count--;
            }
        }
        scriptEngine.put("sum", 0);
        for(int i = 1; i <= count; i++) {
            scriptEngine.eval(String.format("sum += %s", "number" + i));
        }
        Object sum = scriptEngine.get("sum");
        System.out.println("The result is: " + sum.toString());
    }
}

Calling the Python  Dropbox API

This is the most interesting part (in my opinion) where I’ll call the Python Dropbox API from a Java class.

The only problem with this approach is: you cannot pick some modules out of the API to use — you have to establish the connection object in Python too to use it because you have to set up a connection and call the various operations on this connection.

So for the old-school variants the only solution is to write an application in Python (for example my undoutils) and call this script from java — providing the necessary parameters. There is no way to mix up Java and Python that narrowly that you could use code parts dealing with various objects (not strings and integers so basic types).

For the other variants (Jython and JSR-223) the solutions is almost the same. Here you do not need a script but it is useful. Because you have to establish the connection (as described above) and so to have objects you can call. And this would be same time consuming to write in Java: a lot of eval or exec calls object providing here and there.

Besides this if you embed Jython into your Java project (for example with Maven) you have a basic interpreter which you cannot extend with the required packages (oauth and dropbox). This means that you cannot use the required modules using the interpreters.

So I humbly admit that the Dropbox API was a bad-bad idea to be an example for the Java-Python integration.

However, as I mentioned some sentences above, ProcessBuilder and Runtime can be used to execute an existing Python script — so the already existing undelete.py could be executed from Java. Naturally as a requirement the installed (and default) Python should contain the required modules oauth and dropbox.

End of the first part

This whole post is bigger than most of my posts. So I decided to add the other part (PyJa — calling Java from Python) as a separate article, published next week. But it will be a big topic to, so I’ll take two weeks and give you two articles on the topic.

The source code

For this week’s article you’ll find the sources in the same GitHub repository, in the “java” subfolder. The “python” subfolder will contain the examples for the next articles.

GHajba
 

Senior developer, consultant, author, mentor, apprentice.

I love to share my knowledge and insights what I achieve through my daily work which is not trivial — at least not for me.

Click Here to Leave a Comment Below 1 comments
%d bloggers like this: