Chapter 11. Interfacing with Java

CONTENTS
  •  Using the Java APIs
  •  Java Types
  •  Java Constructors
  •  Java Arrays and jarray
  •  Java Arrays and Methods
  •  Bean Properties
  •  Properties
  •  Java Event Handling
  •  Subclassing Java Classes
  •  Advanced Topics
  •  Summary

Terms in This Chapter

  • Applet

  • AWT

  • Base class

  • Bean

  • Built-in object

  • Buffer

  • Content pane

  • Constructor

  • Coupling

  • Design pattern

  • Event

  • Event handler

  • Event listener

  • First class object

  • Frame

  • get() and set() methods

  • -h option

  • Interface

  • Java API

  • JFC

  • Object wrapper

  • Packing

  • Primitive type

  • Property

  • Public function

  • self

  • Superclass

  • Subclass

  • Typecode

  • Type signature

  • Unicode

Java's vast array of application programming interfaces (APIs) help you build GUIs, optimize network communications, create distributed objects, build components, work with databases, and design Web applications. This chapter is about how to use them with Jython.

Downloading the Java Documentation

To get the most out of this chapter, go to the JavaSoft site (http://www.java.sun.com) and download the Java API documentation.

Using the Java APIs

To see how easy it is to use the Java APIs, follow along with this interactive session. Import two classes from the Java package javax.swing: JFrame and JButton. (We'll cover these classes in depth in Chapter 13.)

>>> from javax.swing import JFrame, JButton

Create an instance of each.

>>> frame = JFrame() >>> button = JButton("Hello Java APIs")

(Note that you create Java instances and Python instances in the same way.)

Add the button instance to the content pane.

>>> pane = frame.getContentPane() # get access to the content pane >>> pane.add(button)              # add the button to the content pane javax.swing.JButton ...

Pack the components in the frame.

>>> frame.pack()

Make the frame visible.

>>> frame.visible = 1             # make the frame visible >>>    # define the function that does the event handling >>> def button_clicked(event): ... print "Hello from Java APIs!!!!" ... >>> button.actionPerformed = button_clicked   #set up the event handler

Figure 11-1 shows the output you get after clicking the button several times. Figure 11-2 shows the form we just created.

Figure 11-1. Output from the Button Event

graphics/11fig01.gif

Figure 11-2. JFrame Form

graphics/11fig02.gif

Java classes are pretty much like Python classes. Importing classes are the same in the two languages, except that Java classes come from packages whereas Python classes come from modules. There are a few other slight differences, which we'll cover later, that don't change the basic similarities.

Java Types

In this section we're going to cover how Python handles the conversion from Java types when you pass arguments to Java methods and when those methods return values. First, though, let's briefly cover the Java basic types:

  • boolean contains true or false

  • char contains Unicode characters

  • byte contains 8-bit signed integers

  • short contains 16-bit signed integers

  • int contains 32-bit signed integers

  • long contains 64-bit signed integers

  • float contains 32-bit floating-point numbers

  • double contains 64-bit floating-point numbers

  • String contains a string of Unicode characters

  • array contains arrays of objects or primitive types

Primitive versus Basic Types

I define as Java basic types all of the primitive types plus any classes that have special operators or literal notation. Thus, for example, you can add String instances together using the + operator, and you can create them using literals, so, by definition, strings are a basic Java type. I hope this terminology catches on.

Passing Arguments to Java Methods

One of the key differences between calling methods in Java and calling them in Python is that Java expects Java types. At first, this may seem complex, but Jython takes care of all of the conversion transparently. All you need to know is how a Java type maps to the corresponding Python type so that, for example, when you read the Java API documentation you know which Python type to pass to a particular method.

Java has many more basic types than Python has. For example, the Python Float is like the Java double. Python has no Double, so you can use Float whenever you need a Java double or float.

Again, Java has three ways to describe an integer byte, short, and int which take up varying levels of space. Python has just one integer type, Integer, that can take the place of any Java integer type. Fewer types mean less to worry about and remember.

Table 11-1 shows the mapping from Java types to Python types for calling Java methods. Notice that eleven Java types map to only five Python types. The Python Integer alone corresponds to four Java types (boolean, byte, short, and int). The Python String type corresponds to three Java types.

Getting Return Values from Java Methods

Jython converts the values returned from Java methods to Python types. Table 11-2 lists these mappings. The conversion also works with Java primitive-type object wrappers, as illustrated in Table 11-3.

Putting Things Together

Let's use the Java class Float to demonstrate the Python conversion of basic Java data types. Float, from the java.lang package, converts various Java basic types to a Java float.

In particular, we'll look at the following Float methods:

  • static Float valueOf(String str) converts a string to a float

  • float floatValue() gets the primitive float wrapped in a Float instance

  • static boolean isInfinite(float v) Boolean checks to see if the float is infinite and returns a primitive

Table 11-1. Java/Python Type Mapping
Java Python
char String (must have length 1)
boolean Integer (false = zero, true = nonzero)
byte, short, int, Integer
long Long (in the range of Java long or integer)
float, double Float
java.lang.String String
byte[] String
array[] Jarray

 

Table 11-2. Java/Python Type Mappings for Return Values
Java Basic Type Returned Python Type
char One-character String "a" or 'a' but not "ab"
boolean Integer (true = 1, false = 0)
byte Integer
short Integer
int Integer
long Long
float, double Float
java.lang.String String
java.lang.Class JavaClass (org.python.core.PyJavaClass)
Instance PyInstance (org.python.core.PyInstance)
ClassInstance[] Array (contains objects of class or subclass of ClassInstance)

 

Table 11-3. Java/Python Mappings for Object Wrappers
Java Object Wrapper Python Type
java.lang.Char String (with one character)
java.lang.Integer Integer
java.lang.Boolean Integer
java.lang.Byte Integer
java.lang.Short Integer
java.lang.Long Long
java.lang.Double Float
java.lang.Float Float

The static keyword specifies that a method isn't bound to a class instance; that is, it can be called without a class instance being created. Static methods in Java are thus like functions defined in Python modules. If you're confused, follow along with this quick example.

Import the Java class Float.

>>> from java.lang import Float

Demonstrate a function that returns a Float wrapper object.

>>>    #valueOf returns a java.lang.Float >>> myFloatObject = Float.valueOf("1.1") >>> type(myFloatObject) <jclass org.python.core.PyFloat at -1329353172>

Notice that the value returned from valueOf() (stored in myFloatObject) is converted to a Python Float.

Now let's use the isInfinite() method to demonstrate the return of a primitive. In this case, isInfinite() returns a primitive Boolean.

>>> myBoolean = Float.isInfinite(myFloatObject) >>> type(myBoolean) <jclass org.python.core.PyInteger at -820007380>

Before I can show you an example of floatValue(), I need to explain Java constructors.

A Reminder about Returning Types in CPython and Jython

In CPython, types are returned as follows.

Return an Int.

>>> myint = 1 >>> type(myint) <type 'int'>

Return a Float.

>>> myfloat = 1.1 >>> type(myfloat) <type 'float'>

Here's how types are returned in Jython.

Return an Int.

>>> myint = 1 >>> type(myint) <jclass org.python.core.PyInteger at -820007380>

Return a Float.

>>> myfloat=1.1 >>> type(myfloat) <jclass org.python.core.PyFloat at -1329353172>

The following will work the same in both Python and Jython. Import IntType and FloatType from the types module.

>>> from types import IntType, FloatType

Test to see if myint is IntType. If so, print "Integer type".

>>> if(type(myint)==IntType):print "Integer type" Integer type

Test to see if myfloat is FloatType; if so, print "Float type".

>>> if(type(myfloat==FloatType)):print "Float type" Float type

Java Constructors

All instances of Java classes are of type Instance (org.python.core.PyInstance). Here I use a constructor to create a date. In Java, as in Python, a constructor is a method in a class, so this example demonstrates a method returning an instance of class Date.

>>> from java.util import Date >>> date = Date() >>> type(date) <jclass org.python.core.PyInstance at -1204237921>

Constructors don't behave like regular Java methods in Jython, however. Instead, they convert PyInstances of the class even if the class is a Java primitive wrapper. (Remember, methods that return primitive wrappers are converted to the corresponding Python types.) In this context, the Integer and Float constructors return PyInstances.

Import the Java primitive wrappers Integer and Float.

>>> from java.lang import Integer, Float

Create the Java primitive wrapper Integer with the constructor, and view its type.

>>> i = Integer(1) >>> type(i) <jclass org.python.core.PyInstance at -1292653012>

Create the Java primitive wrapper Float with the constructor, and view its type.

>>> f = Float(1.1) >>> type (f) <jclass org.python.core.PyInstance at -1292653012>

As you can see, the primitive types don't come back as corresponding Python types but as PyInstances. Conversely, the valueOf() method of class Integer and class Float returns the Java primitive object wrappers Integer and Float, respectively, which are converted to corresponding Python types. In short, the rules that apply to Java methods don't apply to Java constructors.

Now back to floatValue(). Here's an example of Python converting the Java return values of int and float to Python Integer and Float:

>>> type(i.intValue()) <jclass org.python.core.PyInteger at -820007380> >>> type(f.floatValue()) <jclass org.python.core.PyFloat at -1329353172>

Convert the object wrapper instance java.lang.Float to PyFloat.

>>> type(Float.valueOf("1.1")) <jclass org.python.core.PyFloat at -1329353172>

Convert the object wrapper instance java.lang.Float to PyInteger.

>>> type(Integer.valueOf("1")) <jclass org.python.core.PyInteger at -820007380>

By the way, instances of Java classes used to be of type JavaInstance (org.python.core.PyJavaInstance), but that changed with the release of Jython 1.1.

>>> from org.python.core import PyJavaInstance >>> type(b)==PyJavaInstance 0

For Beginners: Why the Minutiae

You can program in Python without understanding how Python takes care of Java types. However, when you deal with the more advanced Java APIs, the minutiae we're dealing with here may become important. The good news is that Jython takes care of all Java type conversion transparently, so 95 percent of the time you don't need to worry. It's that other 5 percent that may get you.

Java Arrays and jarray

The closest thing to a Java array in Python is a tuple. Remember that a tuple is immutable and a list is mutable. Like a list, a Java array is mutable but, like a tuple, it's of fixed length. You may think that a tuple or a list is a likely candidate for an array, but that's not exactly the case. You see, an array is like a homogeneous tuple or list every item in it is of the same type; but a tuple is heterogeneous items in it can be of different types. A lot of Java methods have Java arrays as arguments, so when you pass an array to a Java method, Python has to guarantee that it will be homogeneously typed.

Just a note: Because they're strongly typed, Java arrays can hold Java objects or primitive types.

Jython adds PyArray class support for Java arrays org.python.core.PyArray to be exact. Instances of PyArray are returned from Java methods that return Java arrays. You need to create instances of PyArray to pass to Java methods that require arrays. Python makes creating a PyArray painless with the jarray module, which defines two public functions, described in Table 11-4.

No Built-In Java Objects

A Java object is only an instance of a class. There are no other object types, so there are no built-in Java objects as there are in Python. The closest things Java has to Python's built-in objects are strings and arrays, which require special syntax.

 

Table 11-4. jarray Public Functions
Function Description
array(sequence, type) Creates an array of a specific type with values from the sequence; the array is the same size as the sequence
zeros(length, type) Creates an empty array the length of the length parameter

The type argument for both zeros() and array() can be either a string or a Java class object. You can use a single-character string typecode to specify that the array will hold primitive values. Table 11-5 shows the typecodes and their primitive types.

Here's an example of creating arrays with the array() and zeros() methods from the jarray module.

Create a Boolean array using the array() method.

>>> from jarray import array, zeros >>> seq = (1,0,1,0,1,0) >>> booleanArray = array(seq, 'z')

Inspect its contents.

>>> booleanArray array([1, 0, 1, 0, 1, 0], boolean) >>> print `booleanArray` array([1, 1, 1, 1, 1, 0], boolean)

See what type it is.

>>> booleanArray array([1, 0, 1, 0, 1, 0], boolean) >>> print `booleanArray` array([1, 1, 1, 1, 1, 0], boolean)
Table 11-5. Typecodes and Primitive types
Typecode Primitive Type
'z' boolean
'c' char
'b' byte
'h' short
'i' int
'l' long
'f' float
'd' double

Change its values.

>>> booleanArray[1]=1 >>> booleanArray[3]=1 >>> booleanArray array([1, 1, 1, 1, 1, 0], boolean)

You can use the same operations on an array that you use on a sequence (a tuple or a list). Here's an example:

>>> for boolean in booleanArray: ...   print `boolean` ... 1 1 1 1 1 0 >>> len(booleanArray) 6

Even though an array is mutable, it's still of fixed length; that means that you can't append to it as you can a Python list. If you try, you'll get an attribute error.

>>> booleanArray.append(1) Traceback (innermost last):   File "<console>", line 1, in ? AttributeError: instance of 'org.python.core.PyArray' has no attribute 'append'

Let's create an array with the zeros() method.

>>> IntArray = zeros(10,'i') >>> print IntArray array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0], int)

As the above example shows, you can think of PyArray essentially as a Python built-in type that's specific to Jython. Like other features Jython has for integrating with Java, PyArrays are transparent to the programmer (when returned from a Java method). Creating one is easy with Jython's jarray module.

Java Arrays and Methods

Let's see some examples of using arrays to get values from and pass values to Java methods. We'll use three methods of the Java String class (from the java.lang package): valueOf(), getBytes(), and getChar(), which are described in Table 11-6.

Since the valueOf() method takes char[] as an argument, we need to create an array of primitive type char. In Java the [] notation denotes an array.

Table 11-6. Example Java String Class Methods
Method Description
static String valueOf(char[] data) Converts an array of primitive type char into a string
byte[] getBytes() Returns the byte data associated with a string
void getChars(int srcBegin, int srcEnd, char [] dst, int dstBegin) Copies characters from the string into a char array

Import java.lang.String and jarray.array.

>>> from java.lang import String >>> from jarray import array

Create a sequence whose items can be converted into primitive Java type char.

>>> seq = ('H','e','l','l', 'o', ' ', 'W', 'o', 'r', 'l' , 'd') >>> seq ('H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd')

Use the array() method to create a PyArray of primitive Java type char.

>>> data = array(seq, 'c') >>> data  #Display just to show you what is in it. array(['H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd'], char)

The valueOf() method, which takes a char[]argument (a char array), takes the array of Java primitive chars and turns it into a Java string.

>>> str = String.valueOf(data) >>> print str Hello World

Use getBytes() to demonstrate returning an array.

>>> hi = String("Hello Cruel World") >>> bytes = hi.getBytes() >>> bytes     #display what is in bytes array([72, 101, 108, 108, 111, 32, 67, 114, 117, 101, 108, 32, 87, 111, 114,     108, 100], byte) >>> chr(bytes[0]) #The chr function converts an integer to a character 'H'

In Java, you often supply the buffer (Java array), which the method fills with the output results. The getChars() method expects a character buffer (char array), but it doesn't matter what's in it because it's only for output. Thus, we can use the jarray.zeros() method to create an empty, or zero-initialized, buffer.

Import zeros() and String().

>>> from jarray import zeros >>> from java.lang import String

Create an instance of java.lang.String.

>>> hi = String("Here we are now")

Use the zeros() method to create the char array, dst.

>>> dst = zeros(10, 'c')

Use the getChars() method to fill dst.

>>> hi.getChars(0,10, dst, 0) >>> print dst array(['H', 'e', 'r', 'e', ' ', 'w', 'e', ' ', 'a', 'r'...], char)

Dealing with Java arrays in Python demonstrates how the Python language keeps things simple.

Bean Properties

With Jython, you can use JavaBeans properties like instance attributes, which makes it easy to interact with Java classes. You can also use them as keyword arguments to Java class constructors (like Tcl/Tk).

About JavaBeans

JavaBeans have properties and events, much like ActiveX controls, which are a lot like OLE controls (OCXs), which are like Visual Basic controls (VBX). To make a short story long, JavaBeans allow IDE (Integrated Development Environments) to do mundane things that you used to have to write code for.

In Java, you need the set() and get() methods to define a property. Let's say that you create a property called name that you want to be both read and write. You can define two methods for it.

public void setName(String name) public String getName()

(By the way, void in Java means that the method doesn't return anything.) If you want name to be read-only, define only the getName() method.

In Python, you can access the name property as follows (assuming you defined the methods in a class called myJavaClass).

>>> instance = myJavaClass() >>> instance.name = "Rick" >>> print instance.name Rick

You can also set name when you call the constructor.

>>> instance = myJavaClass(name= "Rick") >>> print instance.name Rick

We'll show some real examples of this later in the chapter.

Properties

Sometimes properties are more complex than basic Java types for example, a property can be a type of class. You can set properties that expect class instances with tuples that correspond to the constructor of the property type. (This only works for things like java.awt.Dimension.)

An example of this is the setSize() method of javax.swing.JFrame. The size property expects an instance of class Size, whose constructor expects width and height integer arguments.

The following code recaps what we've learned so far about using properties. We'll show how to create a frame and set its size property with a tuple, and we'll show the frame's visible property. Don't worry that you don't know JFC/Swing. I'll walk you through it.

Import the JFrame class.

>>> from javax.swing import JFrame

Create a JFrame instance.

>>> f = JFrame()

What happens? Nothing. That's because the default for creating a JFrame instance is that it be invisible. If we want it to be visible, we can pass it the visible property keyword argument as follows, or we can just set the property like any other instance attribute.

Set the visible property in the constructor.

>>> f = JFrame (visible=1)

Set the visible property as a property.

>>> f.visible = 0 >>> f.visible = 1

As you can see, even though visible, the frame isn't very big. We can set the frame size by passing it a size parameter.

>>> f = JFrame(size=(250,250), visible=1)

Now we have a big, blank frame, which we can make even bigger.

>>> f.size = 500,500 >>> f.background = 0,255,255 >>> f.background = 255,0,255 >>> f.background = 0,255,255 >>> f.background = 255,255,0 >>> f.background = 0,255,0 >>> f.background = 255,0,0 >>> f.background = 0,0,255

No, I didn't forget to tell you what the above code does. I didn't want to tell you. I want to shame those of you who aren't doing the examples into putting down the book and starting to program.

Event Properties

You can think of event properties as properties that take methods as arguments. As a Java programmer, you can think of them as a way to implement Java events. In Python, you set an event property so you can be notified when an event happens. Notification comes when the class instance calls the method you passed to the property when the event occurred.

For example, JButton has an event property called actionPerformed, which represents the button being pressed. Whenever this happens, the method passed to actionPerformed is called. This is much easier to explain with code.

Import the JButton and JFrame classes from the javax.swing package, and create an instance of JFrame. Set its size at 200,200 pixels, and make it visible.

>>> from javax.swing import JButton, JFrame >>> f = JFrame(visible=1, size=(200,200))

Define an event handler for JButton.

>>> def Hello(event): ...     print "Hello World" ...

Create an instance of JButton called button, and pass the event handler as the actionPerformed property.

>>> button = JButton("Hello World", actionPerformed=Hello)

Add the button to the frame, and pack the frame with the components.

>>> f.getContentPane().add(button) javax.swing.JButton[,0,0,0x0,invalid,layout=javax.swing.OverlayLayout,     alignmentX ... >>> f.pack()

If you hit the "Hello World" button, you'll see "Hello World" print to the console. Note that the argument the event handler needs is the event object passed to it.

If you program in Java 1.1 or later, you know that AWT (and JFC) components are handled by passing an instance of a class that implements a specific event handler interface. In Jython, things are much easier because functions and methods are considered first class objects.

Java Event Handling

Here's an example of Java event handling in Jython. It does the same thing the example in the previous section did but in the Java (Jython) way.

>>> from javax.swing import JFrame, JButton >>> from java.awt.event import ActionListener >>> class hello(ActionListener): ...   def actionPerformed(self, event): ...           print "Hello" ... >>> f = JFrame() >>> b = JButton("Hello from Java Events") >>> hi = hello() >>> b.addActionListener(hi) >>> f.getContentPane().add(b) >>> f.pack() >>> f.visible = 1

I think you'll prefer the more Pythonesque way of doing things because it's brief and easy. Unfortunately, however, you need to know the Java way to understand the Jython way. For example, the only way to know that JButton supports an event property is to know that it supports the method addActionListener(), and that addActionListener() takes an instance of the ActionListener interface (an interface is a totally abstract class), and that ActionListener has a method called actionPerformed(). In other words, the inventors of Jython expect you to know the Java event model.

So then, Jython isn't really for beginners. To help in this regard, I've created a module to let you inspect the Java event model, and I've added a section explaining it in detail so that when you read the Java APIs you'll know how the events map.

The jinfo Module

I've created a module called jinfo that allows you to inspect Java class event properties. Let's look at JButton (javax.swing.JButton).

Import the jinfo module and the JButton class.

>>> from jinfo import * >>> from javax.swing import JButton

Use the getEventsInfo() method to view the events associated with JButton.

>>> getEventsInfo(JButton, 1) >>>

Nothing happened here because the events associated with JButton are defined in JButton's superclass. Thus, we need to find out what that superclass is.

>>> JButton.superclass <jclass javax.swing.AbstractButton at 1250890579>

Now we can query its events.

>>> from javax.swing import AbstractButton >>> getEventsInfo(AbstractButton) [<beanEventProperty itemStateChanged for event interface java.awt.event.ItemListener at -1327819950>,  <beanEventProperty actionPerformed for event interface jav a.awt.event.ActionListener at -1284828334>, <beanEventProperty stateChanged for event interface javax.swing.event.ChangeListener at -1331489966>]

The code above is a little hard to read, so I'll show you another way to use the getEventsInfo() information.

>>> events = getEventsInfo(AbstractButton) >>> len (events) 3 >>> printEventProperty(events[0]) Event Property:           itemListener Defined in:               java.awt.event.ItemListener Event:                    java.awt.event.ItemEvent Event properties for java.awt.event.ItemEvent:      itemSelectable       Type: org.python.core.PyBeanProperty      stateChange          Type: org.python.core.PyBeanProperty      item                 Type: org.python.core.PyBeanProperty Public Event fields for java.awt.event.ItemEvent:      static final ITEM_FIRST                   Type: int      static final ITEM_LAST             Type: int      static final ITEM_STATE_CHANGED    Type: int      static final SELECTED              Type: int      static final DESELECTED            Type: int

Now you can look up the event information in the class's java.awt.event.ItemListener interface and java.awt.event.ItemEvent class in the Java API documentation. The properties that the event passes to itemListener are also shown.

Let's do another example. We're familiar with this event because we've used it in many of the previous examples.

>>> printEventProperty(events[1]) Event Property:           actionListener Defined in:               java.awt.event.ActionListener Event:                    java.awt.event.ActionEvent Event properties for java.awt.event.ActionEvent:     modifiers             Type: org.python.core.PyBeanProperty     actionCommand        Type: org.python.core.PyBeanProperty Public Event fields for java.awt.event.ActionEvent:     static final SHIFT_MASK             Type: int     static final CTRL_MASK              Type: int     static final META_MASK              Type: int     static final ALT_MASK               Type: int     static final ACTION_FIRST           Type: int     static final ACTION_LAST            Type: int     static final ACTION_PERFORMED       Type: int

We can use this information to determine which classes to look up in the documentation: ActionListener and ActionEvent. We can use the fields from ActionEvent in our event handler. The following code uses this information to display the Modifiers and ActionCommand of ActionEvent:

>>> from javax.swing import JButton, JFrame >>> f = JFrame(visible=1) >>> b = JButton("Hello") >>> def hello(event): ...     print "Modifiers: " + `event.modifiers` ...     print "ActionCommand: " + `event.actionCommand` ... >>> b.actionPerformed = hello >>> f.contentPane.add(b) javax.swing.JButton[,0,0,0x0,invalid,layout=javax.swing.OverlayLayout,`     alignmentX ... ... >>> f.pack()

Try hitting the button a few times. You'll see that you can print out the attributes of the event based on the information obtained using getEventsInfo() and printEventProperty(). If you want to know what the properties for the event were used for, look them up under java.awt.event.ActionEvent in the Java documentation.

Advanced Topic: The JavaBeans Event Model in Detail

The Java 1.1 event model was introduced so that you would no longer have to subclass a GUI component in order to handle events, as you did with the Java 1.0 version. JFC, AWT, and JavaBeans use the version 1.1 event model, which defines the following:

  • Source the origin of the event

  • Listener a class that listens for an event, that is, the event handler

  • Event information about the event

In this model, to define an event you have to subclass a class from EventObject, which contains event information. One piece of information is the source property, which provides the event's origin. In our last example, we had a JButton instance called b, which was the source of the ActionEvent instance passed to the event method handler (in the last example this method was called hello()).

An event listener registers itself with the event source, which notifies the listener when the event happens by calling a method on it. All listeners must implement the method the event source needs to call. To do so they have to implement a listener interface.

An interface is like an abstract Java class, so it can't be instantiated. It defines a contract between the event source and the event listener. Classes that want to listen for ActionEvents, for example, must implement the ActionListener interface. All listener interfaces must extend the java.util.EventListener interface.

Classes that act as event sources must define methods that allow event listeners to register themselves. These methods typically take the form addXXListener (XXListener listener) and removeXXListener(XXListener listener), where XX is the event name. The class maintains a list of listeners registered for that event. When the event occurs, the event source class has the code to notify them.

The individual methods in the listener interface need only one argument, of type Event. For example, XXListener's methods take one argument of XXEvent. Let's say you've written a class that monitors stock prices (we'll call it StockPriceChecker), which acts as an event source for an event called StockPrice. As the stock price rises and falls above or below certain levels, the class sends out StockPrice events to all classes that are registered to receive them.

The StockPriceChecker class has the following methods for registering StockPriceEvent listener objects:

  • addStockPriceListener(StockPriceListener listener)

  • removeStockPriceListener(StockPriceListener listener)

The StockPriceEvent class might have the following methods that provide data on the StockPrice event:

  • getPrice() returns a float

  • getName() returns the name of the stock

Contained in StockPriceEvent is the data that StockPriceChecker sends out to the StockPrice event listeners.

The StockPriceListener interface can have the following methods, which define specific occurrences of events:

  • priceDropped(StockPriceEvent event)

  • priceIncreased(StockPriceEvent event)

Any class implementing the interface can register with the event source, StockPriceChecker, to receive StockPrice events.

Design Patterns

Java is chockful of design patterns. These are ways to organize classes and objects to solve recurring software engineering problems. The Java event model is an example of the Observer/Observable design pattern, also known as Publish and Subscribe. Its purpose is to reduce coupling of the source and the sink (the listener).

Once you've mastered Python programming and read Object-Oriented Analysis and Design with Applications (Booch, 1994), make sure to read Design Patterns (Gamma et al., 1995).

Now, if you use the StockPriceChecker class in Python, you don't have to implement the StockPriceListener interface and register an instance with StockPriceChecker. StockPriceChecker has two event properties corresponding to the methods in the interface of StockPriceListener priceDropped and priceIncreased so you can pass an event handler function that has one argument, which is passed as StockEvent.

If you're confused at this point, fear not. We'll do lots of examples using both Python and Java event handling when we cover GUI programming in Chapter 13.

Technical Tip: Bean Introspection

Jython uses the bean introspection features of Java, which provide the ability to inspect a bean's properties, events, and methods. Any Java class can be a bean if it follows certain design patterns in method, interface, and class-naming conventions. Also, instead of naming conventions, developers can define BeanInfo classes that describe the methods corresponding to actions such as event registration.

Subclassing Java Classes

You can subclass Java classes like Python classes. Here's an example of subclassing the Java class java.util.Date (from MyDate.py):

from java.util import Date class MyDate(Date):        def __init__(self, num=None):              if num is None:                     Date.__init__(self)              else:                     Date.__init__(self, num)        def __str__(self):              str = Date.toString(self)              str = "MyDate: " + str              return str

Working with Java Constructors

When you subclass a Java class, you call its superclass's constructor, just as you do in regular Python. As we saw, the MyDate.py example subclassed the Date class from java.lang and defined two constructors that called Date's base class constructors.

As an exercise, look up the constructors of java.lang.Date in the Java API documentation. You'll find them in a section called Constructor Summary.

The constructor of Date is defined in Java as

Date ()

You call it from Python like this:

Date.__init__(self)

The second constructor of Date is defined in Java as

Date (long date)

You call it from Python like this:

Date.__init__(self, num)

Here's how to call both Date constructors:

class MyDate(Date):        def __init__(self, num=None):              if num is None:                     Date.__init__(self)              else:                     Date.__init__(self, num)

In Java, constructors are special language constructs that always have the same name as that of their class. In Python, the constructor is always a method called __init__. You call Java constructors in Python just as you do Python constructors; however, you can only do so in your subclass's constructor.

Working with Java Methods

When subclassing a Java class, you can call all methods from a Java base class that you can from a Python base class. In our MyDate.py example, we subclass Date and add a __str__ method.

def __str__(self):       str = Date.toString(self)       str = "MyDate: " + str       return str

Unlike Python methods, Java methods can be overloaded, which essentially means that a Java class can have several methods with the same name. An example of this is java.io.OutputStream, which defines three write() methods:

  • abstract void write(int b)

  • void write(byte[] b)

  • void write (byte[] b, int off, int len)

As you can see, these methods can be called with different argument types.

To subclass OutputStream, we need a way to override any or all of the write() methods. The following example shows how to do this. (Before we get started, however, read up on the OutputStream class in the Java API documentation.)

from java.io import OutputStream import types class OutputScreen (OutputStream):       def write (self, b=None, off=None, len=None):              if (type(b) is types.IntType):                     print b              elif (off is None):                     print b              else:                     print b[off:off+len]

This code imports OutputStream, a Java class from the java.io package. OutputScreen subclasses OutputStream and overrides its write() methods (all three versions). Thus, each clause of the if statement in write() mimics a different overloaded signature of OutputStream's write()s.

Overloading

Strongly typed languages like Java, C++, and Delphi allow method overloading. In other words, they allow more than one method with the same name but different signatures. The type of the method's arguments defines the signature. With overloaded methods, the signature determines which method is actually being invoked.

Python doesn't allow method overloading based on type signatures because it isn't a strongly typed language. (Some may say it's loosely typed; Pythonians prefer the term "dynamically typed."). To override a method from a subclass in Python, the class must define a method with the same name.

To illustrate, the following code mimics the functionality of void write(int b) by determining that the first argument, b, is of type Integer.

if (type(b) is types.IntType):       print b

This code mimics the functionality of void write(byte[]b) by determining that off wasn't passed (and that the first type was not Integer).

elif (off is None):        print b

This mimics the functionality of void write (byte[]b, int off, int len) by a process of elimination.

else:        print b[off:off+len]

Let's test each of these scenarios. Start the overload.py module from Jython with the i option and try the following statements in an interactive session.

from jarray import array screenOut = OutputScreen() screenOut.write(33) seq = [0,1,2,3,4,5,6,7,8,9,0xA,0xB,0xC,0xD,0xE,0xF] bytearray = array(seq, 'b') screenOut.write(bytearray) screenOut.write(bytearray, 5, 14)

Essentially, we're calling all three write() methods. Here's the code step by step.

Import the array() function from jarray to create Java byte arrays (byte[]).

from jarray import array

Create an OutputScreen instance called ScreenOut.

screenOut = OutputScreen()

Invoke the first form of the write() method, void write(int b).

screenOut.write(33)

Create a byte array for an argument for the second and third forms of write().

seq = [0,1,2,3,4,5,6,7,8,9,0xA,0xB,0xC,0xD,0xE,0xF] bytearray = array(seq, 'b')

Invoke the second form of write(), void write(byte[] b).

screenOut.write(bytearray)

Invoke the last form of write(), void write (byte[]b, int off, int len).

screenOut.write(bytearray, 5, 14)

The output should be

33 array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], byte) array([5, 6, 7, 8, 9, 10], byte)

Advanced Topics

To understand what I'm going to talk about here, you should have some Java background and have read the API documentation for OutputStream.

Overriding write()

Another way to override all of write()'s forms is to override only the abstract version and then just call the others. You can also use a variable number of arguments for write() and then just use the apply() method to execute the nonabstract forms. Let me show you what I mean. (The following code is from Overload.py.)

class OutputScreen2 (OutputStream):      def write(self, *args):                    # If the args length is greater than one or the first                    # argument is not type IntType, use the apply method.             if len(args) > 1 or not type(args[0]) is types.IntType:                    # Use the apply method to call other forms:                    #       void write(byte[] b)                    #       void write(byte[] b, int off, int len)                    apply(OutputStream.write, (self,)+args)                    # Otherwise just print the first argument                    # , i.e., write(int b).             else:                    print args[0]

To determine which form of write() is being invoked, OutputScreen2's write() method checks if the length of the args tuple is greater than 1 or if the first argument isn't an integer. If either is the case, void write(byte[] b) or void write(byte [] b int off, int len) is being called. Otherwise, write(int b) is being called.

The code to test this (Overload.py) looks like the code to test OutputScreen2, although the output is much different.

print "OutScreen2" screenOut = OutputScreen2() screenOut.write(33) seq = [0,1,2,3,4,5,6,7,8,9,0xA,0xB,0xC,0xD,0xE,0xF] bytearray = array(seq, 'b') print "void write(byte[])" screenOut.write(bytearray) print "void write(byte[], int off, int len)" screenOut.write(bytearray, 5, 5)

The Java API documentation states, "Applications that need to define a subclass of OutputStream must always provide at least a method that writes one byte of output." Basically this means that, if you define void write (int b), the default implementation of the other two write() forms calls void write (int b), which is why the output is different. Essentially, write() is called for each item in the byte array passed to void write(byte[]), and for each byte in the range within the byte array for void write(byte[], int off, int len).

Compiling Java Classes

To build applets, JavaBeans, and servlets, or to subclass Python classes in Java, you need to compile the classes into *.class files. The Jython distribution includes a utility called jythonc to do this.

If you execute jythonc from the command line with the h option, it will describe the command-line options for the compiler. Go ahead and try it. (If you're a Jython user, you can type jythonc instead of jpythonc.)

The following options are used with jythonc:

  • -p the package option; puts the compiled classes in the specified package

  • -j the jarfile option; puts the compiled classes in a jar file

  • -d the deep option; compiles Python dependencies of the compiled classes (necessary for creating applets that will run on most browsers, including Internet Explorer)

  • -c the core option; compiles all dependencies and includes the core Jython libraries (necessary for creating applets that will run in Netscape Navigator)

  • -a the all option; everything in the core plus the compiler and parser

  • -b the bean option; puts classes in the jar file and adds a manifest file for the bean (necessary for creating JavaBeans)

  • -w the workdir option; specifies where to compile the code (the default puts the code in a directory under the current directory called jpywork)

We'll cover creating a JavaBean and an applet later. For now, using Overload.py, let's create a Python class that can be subclassed in Java. Before we can do this with jythonc, we have to put the class in a module with the same name, and the class has to subclass a Java class or interface. To compile the OutputScreen class, we need to put it in a file called OutputScreen.py. OutputScreen already subclasses a Java class called OutputStream.

Here's an exercise using the OutputScreen class:

from java.io import OutputStream import types       # Class OutputScreen is a subclass of Java class OutputStream. class OutputScreen (OutputStream):       def write(self, *args):                    # If the args length is greater than one or the first                    # argument is not type IntType, use the apply method.             if len(args) > 1 or not type(args[0]) is types.IntType:                           # Use the apply method to call other forms:                           #       void write(byte[] b)                           #       void write(byte[] b, int off, int len)                    apply(OutputStream.write, (self,)+args)             else:                           # Overide the write (int) method.                    print args[0]

Go to the DOS prompt where the source code is located, and then enter pythonc d OutputScreen at the command line.

C:\jpybook\chap11>jythonc -d OutputScreen.py

jythonc creates a directory called jpywork, in which it places the Java source code and the compiled Java class.

C:\jpybook\chap11\jpywork>dir Directory of C:\jpybook\chap11\jpywork .              <DIR>        08-05-99 5:46p . ..             <DIR>        08-05-99 5:46p .. OUTPUT~1 JAV         6,221  08-05-99 5:46p OutputScreen.java OUTPUT~1 CLA         3,305  08-05-99 5:46p OutputScreen.class OUTPUT~2 CLA         3,224  08-05-99 5:46p OutputScreen$_PyInner.class          3 file(s)         12,750 bytes          2 dir(s)        2,432.67 MB free

There should be one source file called OutputScreen.java and two class files.

The following listing shows part of the OutputScreen class.

import org.python.core.*; public class OutputScreen extends java.io.OutputStream implements org.python.core.PyProxy { ... ...              //The overloaded write method public void write(int arg0) throws java.io.IOException {         PyObject inst = Py.jgetattr(this, "write");         try {             inst._jcallexc(new Object[] {Py.newInteger(arg0)} );         }       ...       ... }       //The write method public void write(byte[] arg0, int arg1, int arg2) throws java.io.IOException {       PyObject inst = Py.jfindattr(this, "write");       if (inst != null) try {                inst._jcallexc(new Object[] {arg0, Py.newInteger(arg1),                   Py.newInteger(arg2)} );       }       ... ...       else super.write(arg0, arg1, arg2); }              //The write method public void write(byte[] arg0) throws java.io.IOException {         PyObject inst = Py.jfindattr(this, "write");         if (inst != null) try {             inst._jcallexc(new Object[] {arg0} );         } ... ...         else super.write(arg0); }

The code for the write() method that takes only one argument has the Python instance of OutputScreen and gets the write attribute from it. Then it executes the method.

First it defines void write(int).

public void write(int arg0) throws java.io.IOException {

Next it gets the write attribute from the Python instance of OutputScreen, called Py. The attribute is stored in inst.

PyObject inst = Py.jgetattr(this, "write");

Then it executes the attribute.

inst._jcallexc(new Object[] {Py.newInteger(arg0)} );

Now the code has multiple arguments. The forms that were not overridden check to see if the Python instance of OutputScreen has a write() method. If it doesn't, the superclass OutputStream is called; if it does, the write() method of the Python instance is invoked, and the arguments are passed to it.

Next the code defines void write(byte[] arg0, int arg1, int arg2).

public void write(byte[] arg0, int arg1, int arg2) throws  java.io.IOException {

Then it attempts to get the write() method from the Py instance.

PyObject inst = Py.jfindattr(this, "write");

If successful, it invokes the method with the following arguments:

if (inst != null) try {     inst._jcallexc(new Object[] {arg0, Py.newInteger(arg1),                          Py.newInteger(arg2)} ); }

If not successful, it invokes the superclass's (OutputStream's) write() method.

else super.write(arg0, arg1, arg2);

Now that we've explained a little bit of how compiled Python code works underneath, let's create some Java that uses it.

Java Code That Uses a Jython Class

Here's testOutScreen.java, which treats OutputScreen like any other Java class:

public class testOutScreen{        public static void main(String[] args){               OutputScreen os = new OutputScreen();               byte [] bytes = {0,1,2,3,4,5,6,7,8,9,0xa,0xb,0xC} ;               try{                     os.write(1);                     os.write(bytes, 5, 5);                     os.write(bytes);               }               catch(java.io.IOException e){                     // do something useful with this exception               }        } }

The preceding code needs little explanation, as all it does is create an instance of OutputScreen and call all three forms of the overloaded write() method. To compile and run it, do this:

C:\jpybook\chap11\jpywork>javac testOutScreen.java C:\jpybook\chap11\jpywork>java testOutScreen

The Java Default Package

Both OutputScreen and testOutScreen are in the Java default package, which is okay for illustration. However, any experienced Java programmer knows that leaving anything there is impolite. To put OutputScreen in its own package use the p option.

C:\jpybook\chap11>jythonc -d  p examples.chap11 OutputScreen.py

Summary

In this chapter, we covered how to integrate Java and Python. Essentially, Jython makes this integration easy and, most of the time, transparent. The mappings from Java types to Python types for passing arguments and returning values from Java methods are straightforward.

Jython has added the jarray module, which allows the creation of the built-in array type that maps to Java arrays. jarray allows you to create both empty arrays and arrays initialized with the contents of a sequence. A Java array is like a homogeneously typed Python sequence with a fixed length, so you can use a Python sequence operation on it.

CONTENTS


Python Programming with the JavaT Class Libraries. A Tutorial for Building Web and Enterprise Applications with Jython
Python Programming with the Javaв„ў Class Libraries: A Tutorial for Building Web and Enterprise Applications with Jython
ISBN: 0201616165
EAN: 2147483647
Year: 2001
Pages: 25

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net