Java in a Python body

This article was originally published in VSJ, which is now part of Developer Fusion.

Many developers like the concise coding, rapid prototyping, and loose data typing style of the Python programming language; others prefer the rigidly typed, disciplined, and formal object-oriented nature of the Java programming language. Choosing between the two languages for a specific project can often be tough. Jython eases the decision, as it implements Python in 100% Java, allowing developers to take advantage of the strengths of each programming language.

This article introduces Jython, and shows the rapid construction of a GUI application that displays data fetched from a relational database. Through a step-by-step refinement of the application, you will discover how to leverage Java libraries from Python code, call or extend Java code with Python, and invoke Python scripts from Java.

About Jython

Jython is a Python implementation created in Java. At the core of Jython is a Python interpreter that dynamically and incrementally compiles Python code into Java bytecode for execution within a Java VM. You can run Python code on any operating system platform that supports Java VM. Using a tool included in the Jython distribution called jythonc, it is also possible to compile a Python module into a static Java class file.

At the time of writing, the latest version of Jython is 2.2.1 and it supports the Python 2.2 syntax (download Jython 2.2.1). After unarchiving Jython into the directory of your choice, make sure you add the installation directory to your PATH environment variable. This enables you to invoke jython.bat or jythonc.bat from any command window.

Figure 1 shows how Python code can freely interwork with JDK libraries and/or your own custom Java classes.

Figure 1
Figure 1: How Jython works

Easy Access from Python to JDK libraries

One key feature of Jython is easy access to Java classes, and this includes the vast JDK libraries. For example, using the pawt wrapper module in Python, you can easily populate and display a Swing-based JList. The code to display such a GUI list is shown in the following simpleList.py listing.

from pawt import swing
contacts = []
contacts.append("Sing, Li")
contacts.append("Ken, Boyd")
contacts.append("Nancy, Bowers")
lst = swing.JList(contacts)
swing.test(lst)

The simpleList.py code can be executed using the command:

jython simpleList.py

…with the results shown in Figure 2.

Figure 2
Figure 2: The list resulting from running simplList.py

With very little effort, you have displayed a Python list inside a JList GUI created with JDK Swing library. To make this more interesting, and to explore another useful library, you’ll next fetch the list content from a relational database.

Standard relational database access via DB API

If you are using Jython to create business applications, you will have a need access to relational databases. The Jython distribution includes the zxJDBC package for RDBMS access. Figure 3 illustrates the architecture of zxJDBC, where zxJDBC exposes a Python DB API 2.0 compatible interface to access a relational database. The Python DB API 2.0 is the de facto standard database access API specification established by the Python community, documented here. Most Python developers are already familiar working with the DB API on some level.

Figure 3
Figure 3: The architecture of zxJDBC

Underneath the hood, zxJDBC’s access is implemented using the Java platform’s versatile JDBC library. This opens up access to the incredible range of relational databases supported by JDBC to your Python code – without the need to locate a database-specific Python-to-native-DB-access driver.

Add relational database access to simpleList.py

In the following listing, the code from simpleList.py is modified to fetch rows of data from a relational database. Database access is performed using the zxJDBC implementation of DB API. The DB API database access code is highlighted in red.

from pawt import swing
from com.ziclix.python.sql import zxJDBC
url,user,password,driver =
    dbc:mysql://localhost/vsjdb",
    vsjuser","vsjpass",
    com.mysql.jdbc.Driver"
conn = zxJDBC.connect(
    url,user,password,driver)
contacts = []
curz = conn.cursor(1)
curz.execute("select * from contacts")
while (1):
    row = curz.fetchone()
    if row == None:
    	break
    contacts.append(( "%s, %s" %
    	(row[2],row[1])))
curz.close()
conn.close()
lst = swing.JList(contacts)
swing.test(lst)

In the preceding listing, the contact information is fetched from a MySQL server using the SQL statement:

select * from contacts

It is assumed that you have a MySQL 5.0 server running and available on the local machine, and a database named vsjdb is available via the JDBC URL:

jdbc:mysql://localhost/vsjdb

You will also need to have a userid and password that have privilege to create database and tables on the MySQL server. The code uses vsjuser for user id and vsjpass for password respectively. You will need to modify it to match your user/password combination.

To create the vsjdb database, create the contacts table, and to populate the contacts table, you can use the createtbl.sql file in the code distribution.

The code for createtbl.sql is shown in the next listing. This code inserts 10 contacts into the table – spanning 4 sales regions.

create database if not exists vsjdb;
use vsjdb;
drop table if exists contacts;
create table contacts (
    id int not null,
    lastname varchar(50),
    firstname varchar(50),
    phone varchar(15),
    salesregion char(1) )
    engine = innodb;
insert into contacts values (1, 'Li',
    'Sing', '342-233-2121', 'N');
insert into contacts values (2, 'Boyd',
    'Ken', '342-233-3321', 'N');
insert into contacts values (3, 'Bowers',
    'Nancy', '422-754-2121', 'E');
insert into contacts values (4, 'Hyatt',
    'James', '342-233-2111', 'N');
insert into contacts values (5, 'Mofat',
    'Shelly', '422-754-3432', 'E');
insert into contacts values (6, 'Jones',
    'Kalid', '617-323-6721', 'W');
insert into contacts values (7, 'Nyguen',
    'Leslie', '422-453-2322', 'E');
insert into contacts values (8, 'Kent',
    'Meaghan', '617-323-3421', 'W');
insert into contacts values (9, 'Randal',
    'Roy', '342-233-2231', 'N');
insert into contacts values (10,
    'Huxley', 'Manning', '818-607-2121',
    'S');

To create and populate the table, use the MySQL command line:

mysql -hlocalhost -uvsjuser
    -pvsjpass < createtbl.sql

You may need to change the host, userid, or password on this command line depending on your MySQL server permissions.

If you don’t already have the MySQL JDBC driver installed, download it here.

To run the dbList.py Python program, you can use the following commands:

set CLASSPATH = %CLASSPATH%;
    c:\mysql-connector-java-5.1.6-bin.jar
jython dbList.py

The rundblist.bat file from the code distribution contains the preceding commands and can be used to run dbList.py. Make sure you first modify the CLASSPATH environment variable in rundblist.bat to reflect the location of your MySQL Connector/J JDBC driver.

When you execute rundblist.bat, the Swing JList is now populated with all the contacts from the MySQL database, see Figure 4.

Figure 4
Figure 4: The database

Working with customized Java classes

Software design is about using the best available tool for the problem at hand. Since the GUI presentation in Figure 4 looks a little clumsy, let’s fix it up using our own customized Swing class. This will show how to use Jython with your own customized Java classes, instead of JDK library classes.

The new class uk.co.vsj.jython.DBDisplay is a subclass of Swing’s JFrame, and knows better how to the data:

package uk.co.vsj.jython;
import java.awt.BorderLayout;
import java.awt.Dimension;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.WindowConstants;
import
javax.swing.table.DefaultTableColumnModel;
import
    javax.swing.table.DefaultTableModel;
public class DBDisplay extends JFrame {
    protected DefaultTableModel model;
    protected JTable dbtable;
    protected JScrollPane sp;
    public DBDisplay(String title){
    	super(title);
    }
    public void init(String [] names) {
    	setDefaultCloseOperation(
    		WindowConstants.EXIT_ON_CLOSE);
    	model = new DefaultTableModel();
    	model.addColumn("First name");
    	model.addColumn("Last name");
    	model.addColumn("Phone");
    	for (int i=0; i<names.length; i++){
    		String [] curData =
    			names[i].split(",");
    		model.addRow(curData);
    	}
    	dbtable = new JTable(model);
    	sp = new JScrollPane(dbtable);
dbtable.setPreferredScrollableViewportSize(
    		dbtable.getPreferredSize());
    	getContentPane().add(sp,
    		BorderLayout.CENTER);
    	setSize(640,240);
    	setVisible(true);
    }
}

Basically, it is a Swing JTable within a JScrollPane, all within a JFrame. An instance of DefaultTableModel is being used to hold the rows of data. The data is now presented in three labeled columns within the JTable, with a familiar spreadsheet look. The code takes advantage of the fact that the data passed in has its fields comma separated. Note that this is just a customized JFrame, a pure Java class, with no special coding to handle Python interfacing.

The Python code can now be modified to use this new DBDisplay class, instead of the stock JList. dbJavaDisplayer.py is an updated version of dbList.py for this purpose. The code for dbJavaDisplayer.py is shown in the following listing, with the very minor changes highlighted.

from uk.co.vsj.jython import DBDisplay
from com.ziclix.python.sql import zxJDBC
url,user,password,driver =
    "jdbc:mysql://localhost/vsjdb",
    "vsjuser","vsjpass",
    "com.mysql.jdbc.Driver"
conn =
 zxJDBC.connect(url,user,password,driver)
contacts = []
curz = conn.cursor(1)
curz.execute("select * from contacts")
while (1):
    row = curz.fetchone()
    if row == None:
    	break
    contacts.append(
    	( "%s, %s, %s" %
    		(row[2],row[1],row[3])))
curz.close()
conn.close()
lst = DBDisplay(
    "Custom Java Display Class")
lst.init(contacts)

Run dbJavaDisplayer.py using the commands:

set CLASSPATH=%CLASSPATH%;c:\mysql
    -connector-java-5.1.6-
    bin.jar;..\vsjjava\bin
jython dbJavaDisplayer.py

The rundbjavadisplayer.bat file in the code distribution contains the preceding commands and can be used to run dbJavaDisplayer.py.

You need to customize the location of the MySQL driver. Figure 5 shows dbJavaDisplayer.py running, using our newly customized Swing subclass.

Figure 5
Figure 5: Using the newly customised subclass

Calling your own customized Java classes from Python code is as simple as calling regular Python modules, there is no syntax difference.

Embedding the Jython interpreter in Java applications

Just as Python code can access customized Java class directly in Jython, Java code access to Python is also quite straightforward. First, let us take a look at the case where you want to execute an existing Python module from Java. In this case, the Python code already resides on a file of its own.

The uk.co.vsj.fromjava.PythonRunner class shown in the following listing starts a Jython interpreter instance and uses it to execute the dbJavaDisplayer.py module.

package uk.co.vsj.fromjava;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import org.python.util.PythonInterpreter;
import org.python.core.*;
public class PythonRunner {
    public static void main(String []args)
    	throws Exception
    	{
    	PythonInterpreter interp =
    		new PythonInterpreter();

    	File src = new File(
    	"d:\\eworkspace\\vsjjython2\\
    		dbJavaDisplayer.py");
    	FileInputStream is =
    		new FileInputStream(src);
    	interp.execfile(is);
    	is.close();
    	}
    }

This code basically creates an instance of the PythonInterpreter class and passes the content of the dbJavaDisplayer.py file for it to execute. The PythonInterpreter class has methods that enable you to parse Python code a-line-at-a-time for interactive execution, and perform other parsing tasks. Table 1 describes several of these methods.

The doc directory of the Jython 2.2.1 distribution contains the javadoc of the API and you can discover all the available methods available for PythonInterpreter. Other interesting classes to study in the javadoc are PyObject and the PySystemState class; the PythonInterpreter uses PyObject to hold pre-evaluated Python code and PySystemState to maintain state.

To execute PythonRunner, you need to set up the Java classpath to point to:

  1. the bin directory of the vsjfromjava directory, where uk.co.vsj.fromjava.PythonRunner can be found
  2. the location of the MySQL Connector/J JDBC driver JAR file
  3. the location of the jython.jar file from the Jython distribution, where the PythonInterpreter class can be located

You can modify vsjfromdata\runfromjava.bat file in the code distribution to run PythonRunner. This batch file contains the following commands:

set CLASSPATH=c:\mysql-connector-
    	java-5.1.6-bin.jar;
    	c:\javabin\jython2.2.1\jython.jar;
    		..\vsjjava\bin
java -classpath
    	"bin;%CLASSPATH%" uk.co.vsj.
    		fromjava.PythonRunner

When you run PythonRunner, you are executing a Jython script from Java code – dbJavaDisplayer.py. The dbJavaDisplayer.py script, in turn, takes advantage of the rich Java platform library support to do its work. This shows programming logic that weaves from Java to Python, back to Java. It is precisely this flexibility that makes Jython a highly versatile tool to have around.

Fine grained Java-Python interactions

The final example in this article shows fine-grained integration between Java and Python within Jython. Instead of having Java running Python code as a complete script file, you will see Python code implementing a Java interface (in the same way that Python code can subclass a Java abstract class), and a Java event source firing an event to be handled by a Python-based event listener.

Interactive Java GUI with Python RDBS updates

In this example, we create a Java subclass of the DBDisplay GUI class, called DBSelectDisplay. This subclass adds a set of radio button to select the sales region of the contacts. Recall that our contacts data actually contains the sales region information (North, South, East or West). Figure 6 shows the final application running.

Figure 6
Figure 6: The completed Jython application

When a user clicks on one of the regions, only contacts from that region will be shown in the table. A query similar to the following is issued to MySQL whenever a region is selected:

select * from contacts where
    salesregion='N'

The saleregion value changes depending on the radio button clicked. This sort of filtered data table display is required frequently in real-world applications. What makes this example really interesting, however, is not in the GUI details; it is the fact that Python code will be used to query the database in a loosely coupled fashion. The flow of code follows the observer pattern, from a Java event source to a registered event Python listener.

First, take a look at the GUI display code of uk.co.vsj.jython.DBSelectDisplay shown in the following listing. The highlighted code shows methods used for listener registration and event handling.

package uk.co.vsj.jython;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.AbstractButton;
import javax.swing.ButtonGroup;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class DBSelectDisplay extends
    DBDisplay implements ActionListener {
    protected static final String
    	EAST_REGION = "East";
    protected static final String
    	SOUTH_REGION = "South";
    protected static final String
    	WEST_REGION = "West";
    protected static final String
    	NORTH_REGION = "North";
    protected ButtonGroup regions;
    protected String selectedRegion =
    	EAST_REGION;
    private ChangeListener
    	regionChangedListener = null;
    public DBSelectDisplay(String title){
    	super(title);
    }
    public void init(String [] contacts) {
    	super.init(contacts);
    	JRadioButton eastButton =
    		new JRadioButton(EAST_REGION);
    	JRadioButton westButton =
    		new JRadioButton(WEST_REGION);
    	JRadioButton northButton =
    		new JRadioButton(NORTH_REGION);
    	JRadioButton southButton =
    		new JRadioButton(SOUTH_REGION);
    	eastButton.addActionListener(this);
    	westButton.addActionListener(this);
    northButton.addActionListener(this);
    southButton.addActionListener(this);
    	regions = new ButtonGroup();
    	regions.add(eastButton);
    	regions.add(southButton);
    	regions.add(westButton);
    	regions.add(northButton);
    	eastButton.setSelected(true);
    	JPanel regionPanel = new JPanel();
    	regionPanel.add(
    		new JLabel("Regions: "));
    	regionPanel.add(eastButton);
    	regionPanel.add(southButton);
    	regionPanel.add(westButton);
    	regionPanel.add(northButton);
    	getContentPane().add(regionPanel,
    		BorderLayout.SOUTH);
    	validate();
    }
public void update(String [] contacts) {
    	// clear the rows in the model
    	int curRowCount =
    		model.getRowCount();
    	if (curRowCount > 0) {
    		for (int i=curRowCount - 1;
    			i >= 0; i--) {
    		model.removeRow(i);
    		}
    	}
    // fill it up with the new set of rows
    	for (int i=0; i< contacts.length;
    		i++) {
    		String [] curData =
    			contacts[i].split(",");
    		model.addRow(curData);
    	}
    }
    public void addRegionChangedListener(
    		ChangeListener lis) {
    	regionChangedListener = lis;
    }

    public void actionPerformed(
    	ActionEvent e) {
    	AbstractButton tmpButton =
    		(AbstractButton) e.getSource();
    	selectedRegion =
    		tmpButton.getText();
    if (regionChangedListener != null ) {
    	regionChangedListener.stateChanged(
    		new ChangeEvent(selectedRegion));
    	}
    }
    public String getSelectedRegion() {
    	return selectedRegion;
    }
}

In this class, the init() method creates and displays the additional JRadioButtons and bundles them into a ButtonGroup for mutually exclusive selection. Each JRadioButton’s ActionListener is set to be the instance of DBSelectDisplay itself; this means that the ActionPerformed() method will be called anytime one of the button is clicked.

An update() method is added to facilitate the update of the displayed table content, replacing the list of contacts displayed whenever the region is changed. Here, the existing rows in the DefaultTableModel instance are deleted, and a new set of rows is added – displaying the new regional contacts.

The DBSelectDisplay class itself maintains a String indicating the currently selectedRegion, and a single ChangeListener instance named regionChangedListener. In the ActionPerformed() method, when a button is clicked, the new value of selectedRegion is set and the stateChanged() method on the regionChangedListener. A listener can be registered by calling the addRegionChangedListener() method. In our case, this listener is actually coded in Python.

Adding a Python ChangeListener to a Java component

The Python script that runs the application is a modified version of dbJavaDisplayer.py, named dbJavaSelector.py.

The code for dbJavaSelector.py is shown in the following listing, the highlighted part showing how to add a Python listener to the DBSelectDisplay Java class.

from uk.co.vsj.jython
    import DBSelectDisplay
from com.ziclix.python.sql
    	import zxJDBC
from javax.swing.event
    	import ChangeListener
class RegionChanged(ChangeListener):
    def stateChanged(self, event):
    	strtext = str(event.getSource())
    	conn = zxJDBC.connect(url,
    		user,password,driver)
    		contacts = []
    		curz = conn.cursor(1)
    		stmt = "select * from contacts
    			where salesregion=
    				'%s'" % strtext[0]
    			curz.execute(stmt)
    		while (1):
    			row = curz.fetchone()
    			if row == None:
    				break
    			contacts.append(( "%s, %s, %s" %
    			(row[2],row[1],row[3])))
    			curz.close()
    			conn.close()
    			lst.update(contacts)
url,user,password,driver = "jdbc:mysql:
    		//localhost/vsjdb",
    	"vsjuser","vsjpass",
    		"com.mysql.jdbc.Driver"
lst = DBSelectDisplay(
    	"Custom Java Selector")
contacts=[]
lst.init(contacts)
changelistener = RegionChanged()
lst.addRegionChangedListener(
    changelistener)

Essentially, the last two lines of the preceding listing are responsible for registering the RegionChanged Python class as the Java ChangedListener that the DBSelectDisplay instance will notify whenever region is changed.

If you look back at the code for DBSelectDisplay, you will find that the event source supplied is actually a String indicating the region of the button clicked (actually the button’s label). The RegionChanged Python class takes advantage of this when formulating the SQL query, obtaining the event source by calling event.getSournce() and stripping only the first character of the region to use as SQL query criteria for the salesregion field.

Implementing a Java Interface from Python code

In order to add the RegionChanged Python class as ChangeListener for the DBSelectDisplay class, it must first implement the javax.swing.event.ChangeListener interface. This same technique can be used to extend Java classes in Python code.

The steps required are:

  1. import the parent interface/class in the Python code
  2. create a Python class using the imported Java interface/class as a superclass
  3. the body of the Python class, you can implement the interface’s method (or override any of the super class’s public/protected methods)

The following except shows how the Python RegionChanged class is coded to implement the Java ChangeListener interface. The notification method, stateChanged(), is defined in the RegionChanged class to update the list of contacts whenever the region has changed in the GUI.

from uk.co.vsj.jython
    import DBSelectDisplay
from com.ziclix.python.sql
    import zxJDBC
from javax.swing.event
    import ChangeListener
class RegionChanged(ChangeListener):
def stateChanged(self, event):
strtext = str(event.getSource())
...

Conclusions

Combining the flexible and concise scripting nature of Python with the formal strongly typed nature of Java, Jython delivers the best of both worlds for today’s complex multi-faceted IT projects. Python script can take full advantage of the extremely rich libraries provided by Java SE 6; while Java systems can now embed a full Python interpreter – allowing users to easily create script to customize behaviour of the system and Java-based components. Jython is set to become another useful blade in the Swiss army knife of modern day Java developer.


Sing Li has been writing software, and writing about software for twenty plus years. His specialities include scalable distributed computing systems and peer-to-peer technologies. He now spends a lot of time working with open source Java technologies.

You might also like...

Comments

About the author

Sing Li United States

Sing Li has been writing software, and writing about software for twenty plus years. His specialities include scalable distributed computing systems and peer-to-peer technologies. He now spends ...

Interested in writing for us? Find out more.

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law.”