Chapter 8. Compiling Jython with jythonc

Compiling Jython with jythonc

Jython excels because of its comprehensive integration of Python and Java. Most multi-level language combinations have a semantic gap between the languages that discourages truly comprehensive integration. CPython, for example, cannot use C libraries without special steps, and writing C extensions must following specific guidelines unique to CPython. Jython, on the other hand, enables the seamless usage of arbitrary Java classes in a Python environment without requiring extra steps, modification, or special treatment in writing the Java class. That’s only one side of the coin, however. To truly be comprehensive, Jython must also allow Java to seamlessly use Python. Jython meets this latter requirement, even if not fully transparently, thanks to jythonc.

jythonc allows you to compile Jython modules into Java *.class files. Jythonc can create runnable classes—those that Java can execute but still rely on Jython modules. jythonc can also create fully self-contained and runnable applications, meaning those that Java can execute and that have all dependant modules located and compiled. Additionally, jythonc can compile Jython classes that indistinguishably masquerade as Java classes—those that Java code can import and call.

To pique your interest, consider the advantages of being able to compile Jython applications into self-contained jar files runnable on most JVM’s. Consider the development benefits of prototyping applications in Jython and later converting portions to Java—but only as needed. Consider the ease of writing servlets, applets and beans in Jython, and having them work within Java frameworks without regard for their original language. Consider the flexibility of composing, subclassing, and extending Java and Jython classes interchangeably. These rare abilities are the functionality supplied by jythonc.

What Is jythonc?

jythonc is a tool that generates Java code from Jython modules. jythonc can also create jar files, track dependencies, freeze associated modules, and more depending on the options supplied. Within the same directory as the Jython shell script (or batch file) should be another script called jythonc (or jythonc.bat). This script is really a wrapper around the file sys.prefix/Tools/jythonc/jythonc.py that does the work of creating a java file and calling a java compiler to create *.class files. This means you must have an appropriate Java compiler such as Sun’s javac, available at http://www.javasoft.com/j2se/, or IBM’s jikes, currently available at http://www10.software.ibm.com/developerworks/ opensource/jikes/, to use in conjunction with jythonc. The Java Runtime Environmenta (JRE) is insufficient in itself because it does not include a compiler. You will need the Java Development Kit (JDK) or Jikes compiler. Note that Microsoft’s jvc compiler is currently not a good choice for compiling Jython code.

Compiled Jython has no performance advantage over interpreted Python. It is often such an immediate concern for those new to Jython that it seems worth mentioning early. The current implementation produces compiled Jython code that performs similarly to using Jython itself to execute a module.

Compiling a Module with jythonc

If you run jython A.py it means that the module A runs as the top-level, or __main__, script. If you compile A.py with jythonc, you can instead use Java directly to execute the application. Listing 8.1 is a simple Jython module called guess.py. It will serve as a test subject for compiling a Jython module into runnable Java class files. The examples below assume that the Java compiler, javac, is in your path. If it is not, or if jythonc has difficulty locating the Java compiler, you can use jythonc’s −C switch to specify the compiler. See the jythonc Options section later in this chapter for more on the −C switch. Additionally, you may wish to exclude jython.jar from your classpath environment variable before running jythonc because some versions of jythonc fail when jython.jar is already in the classpath.

Example 8.1. A Jython Guessing Module for Compiling into Runnable Classes

#file: guess.py 
import random 

print "I'm thinking of a number between 1 and 100. Guess what it is." 
num = random.randint(1, 100) 

while 1: 
    guess = int(raw_input("Enter guess: ")) 
    if guess==num: 
        print "Correct, the number was %i" %% num 
        break 
    elif abs(guess - num) < 3: 
        print "Close enough. The number was %i" % num 
        break 
    elif guess > num: 
        print "Too high." 
    else: print "Too low" 

The shell command irequired to compile Listing 8.1 is this:

> jythonc guess.py 

The preceding command assumes jythonc is in your path. If it is not, you must give the full path to jythonc—something like this:

c:jython-2.1jythonc guess.py 

After running the jythonc command, you should see output similar to this:

processing guess 

Required packages: 

Creating adapters: 

Creating .java files: 
guess module 

Compiling .java to .class... 
Compiling with args: ['javac', '-classpath', 
'"C:\jython\jython.jar;.\jpywork;;C:\jython\Tools\jythonc;C:\ch8 
examples\.;C:\jython\Lib;C:\jython"', '.\jpywork\guess.java'] 
0 

You may also receive the following warning depending on the version of the JDK you are iusing:

Note: Some input files use or override a deprecated API. 
Note: Recompile with -deprecation for details. 

Examining the output, we see that jythonc created a guess.java file from the guess.py module, and then called javac to compile the guess.java file into class files. The full command used to compile guess.java into classes is shown as a list (think .join(args) to get the actual command). We know jythonc called javac to do the compiling because javac is the first item in the args list. It is important to note that the generated Java file may have code specific to the JVM version you are using. This means that using the −target option may be insufficient as the generated code may contain references to unsupported classes. To compile Jython code for a specific JVM version, you should use that version of the JVM when running jythonc. This seems to be most often necessary for applets.

If javac is not the Java compiler you use, then you can specify a different compiler in the registry or on the command line. The Jython registry key python.jythonc.compiler and the −C command-line switch both specify which Java compiler to use. The appropriate registry line to use jikes instead of javac would be as follows:

python.jythonc.compiler = /path/to/jikes # for *nix users 
python.jythonc.compiler = c:path	ojikes # for win users 

Specifying the jikes compiler with the -C option would appear as follows:

bash> jythonc −C /path/to/jikes guess.py 
dos> jythonc −C c:path	ojikes guess.py 

The results of jythonc guess.py is a directory called jpywork, which contains the files guess.java, guess.class, and guess$_PyInner.class. The guess.java file is what jythonc generates, and the two class files are the results of compiling guess.java. Compiling Jython code should result in at least two class files: one with the module’s name and an associated $_PyInner class.

Running jythonc -compiled classes with a JVM executable requires that the jython.jar file and both compiled class files (guess.class and guess$_PyInner.class) are in the classpath. If you are within the same directory as the two class files generated from compiling listing 8.1, and you are using the Java executable from Sun’s JDK, this would be the command to run the compiled guess files:

bash> java −cp /path/to/jython.jar:. guess 
dos> java −cp pathjython.jar;. guess 

You should be prompted to guess a number. Sample output from running this program is as follows:

I'm thinking of a number between 1 and 100. Guess what it is. 
Enter guess: 50 
Too high. 
Enter guess: 25 
Close enough. The number was 23 

It seems a somewhat common error to forget to include the appropriate directories in the classpath when first running jythonc -compiled files. Because jythonc -compiled files are often used within existing Java frameworks, the compiled class files are often moved into specific directories for that framework. This step seems prone to errors such as not copying all the required files. Remember that jythonc creates at least two files. Without the inner file (guess$_PyInner.class in our case) in the classpath, you should receive the following error message:

Error running main. Can't find: guess$_PyInner 

Additionally, jythonc -compiled classes required the classes found in the jython.jar file. These can be included in an archive by using special jythonc options, as we will see later. For now, we must ensure that the jython.jar file is in the classpath. If the jython.jar file is not included in the classpath, you should receive the following error message:

Exception in thread "main" java.lang.NoClassDefFoundError: 
org/python/core/PyObject 

The compiled version of Listing 8.1 still relies on the random module from the sys.prefix/Lib directory. If the sys.prefix or sys.path variables are wrong, you will receive the following error:

Java Traceback: 
... 
Traceback (innermost last): 
  File "C:WINDOWSDesktopch8examplesguess.py", line 0, in main 
ImportError: no module named random 

Paths and Compiled Jython

Jython depends on a Java property called python.home to locate and read the Jython registry file as well as establish the sys.prefix, and thus certain sys.path entries. Jython also creates a cache directory in the python.home or sys.prefix directory, which is aptly named cachedir by default. Information discovered about Java packages is stored in this directory as an essential optimization, but this can be troublesome if python.home or sys.prefix have unexpected values. The python.home property is set as part of jython’s startup script, but compiled Jython doesn’t have this convenience.

This introduces issues concerning Jython’s paths. A Jython module compiled into Java classes requires special steps to ensure it can find Jython modules and packages it depends on and make use of a specific cachedir. What follows is a list of options you can choose from to do so.

Set the python.home Property in the JVM

Sun’s Java executables allow the setting of properties with the −D command-line option. Listing 8.2 is a Jython module designed to expose path problems.

Example 8.2. Jython Module for Clarifying Paths and Properties

# file: paths.py 

import sys 
import java 

print "sys.path=", sys.path 
print "sys.prefix=", sys.prefix 
print "python.home=", java.lang.System.getProperty("python.home") 

# print a random number just so this depends on a 
# module in the sys.prefix/Lib directory 
import random 
print "My lucky number is", random.randint(1,100) 

Compile Listing 8.2 with jythonc paths.py. Compiling it this way creates class files dubbed runnable in Jython lingo. The word runnable infers that Jython code compiled so that the Java executable can execute it, but it still relies on Jython libraries and follows the standard Jython interpreter behavior. Executing a runnable class should act just like running a Jython script— meaning it would cache Java package information at startup and use the sys.path to locate modules. This means setting the python.home property or at least specifying library paths is required.

To continue testing Listing 8.2, change directories in a command shell to where the paths.class and paths$_PyInner.class files reside. Then execute paths.class using the −D option to set the python.home property. The command should look similar to that below only using the directories unique to your installation (the command “wraps” because of the long line, but is meant as a single command):

bash> java −Dpython.home="/usr/local/jython-2.1" −cp /usr/local/jython.jar:. 
paths 

dos> java −Dpython.home="c:jython-2.1" −cp c:jython-2.1jython.jar;. paths 

The results should appear similar to this:

sys.path= ['.', 'c:\jython 2.1\Lib'] 
sys.prefix= c:\jython 2.1 
python.home= c:\jython 2.1 
My lucky number is 96 

This means Jython’s registry file is found, the sys.prefix value is accurate, and modules, such as random in the sys.prefix/Lib directory, can be found and loaded.

What happens without the −D option? Without it, the command would look like this:

bash> java −cp /path/to/jython.jar:. paths 
dos> java −cp c:path	ojython.jar;. paths 

If the jython.jar file is still in the Jython installation directory, the results should be similar to that below (replace / with your platform-specific directory separator):

sys.path= ['.', '/usr/local/jython-2.1/Lib'] 
sys.prefix= /usr/local/jython 2.1a1/ 
python.home= None 
My lucky number is 97 

How does Jython know the correct sys.prefix value without having a proper python.home value? It’s not part of Jython’s documented behavior, but currently, if jython.jar is not in the current directory, the sys.prefix value becomes the directory in which the jython.jar file was found. If you copy the jython.jar file into the same directory as the paths.class file, the results would be different:

dos> java -cp jython.jar;. paths 
sys.path= ['.', '\Lib'] 
sys.prefix=. 
python.home= None 
Java Traceback: 

        at org.python.core.Py.ImportError(Py.java:180) 
... 
        at paths.main(paths.java:72) 
Traceback (innermost last): 
  File "C:WINDOWSDesktopch8examplespaths.py", line 0, in main 
ImportError: no module named random 

The result in this last case is a python.home equal to None. This means that the Jython registry file is not located, and that modules in Jython’s Lib directory (random) cannot be found. This can be resolved by moving the random module into the same directory as well, because sys.path will automatically include the current directory, or ..

Explicitly Add Directories to sys.path Within Modules

It can be easiest at times to explicitly append library paths to the sys.path variable within the module you intend to compile. This ensures modules within a certain directory are accessible. If you wanted to ensure that Listing 8.2 could find the random module in the /usr/local/jython-2.1/Lib directory, you can add the following line to paths.py (after importing sys of course):

sys.path.append("/usr/local/jython-2.1/Lib") # *nix 
sys.path.append("c:jython-2.1Lib") # dos 

With this being part of the compiled class, modules in that directory will always be found no matter where the python.home property and sys.prefix variable point. However, this lacks the flexibility of Java’s classpath. If an application is moved to another machine, paths become troublesome and functionality across platforms suffers.

Add to the python.path or python.prepath Property

Just as you can set the python.home property with Java’s −D switch, you can also set the registry’s python.path or python.prepath property with the −D switch. Assume you have compiled Listing 8.2 with jythonc paths.py, and you have moved the jython.jar file into the same directory as the paths.class and paths$_PyInner.class files. You can run the application with a command similar to this:

dos>java −Dpython.path="c:\jython-2.1\Lib" −cp jython.jar;. paths 

In this case, the sys.path list appends the python.path value as part its initialization, and therefore before loading modules. Our example application would locate and load the random module appropriately. Setting path properties in the command line allows batch files or scripts to dynamically determine this value—an improvement over placing static paths within code.

Freeze an Application

Freezing an application infers compiling a Jython module in a way that tracks all dependencies during compilation and results in class files for all required modules. A frozen application is self-contained: It does not depend on Jython’s sys.path directories, it locates modules on Java’s classpath, and it does not cache package information at startup. Freezing an application requires using jythonc ’s −d or −−deep option. To freeze Listing 8.2, use a command like the following:

dos> jythonc −d paths.py 
dos> jythonc −−deep paths.py 

The compilation process will now include the random module. jythonc tracks dependencies, such as the random module, and compiles those modules to Java as well. The results of freezing the paths application (from Listing 8.2) should be four class files:

  • paths.class

  • paths$_PyInner.class

  • random.class

  • random$_PyInner.class

These class files, plus the classes found in jython.jar, are all that is required to run the paths application.

If you are at a command prompt, and you are in the same directory as the class files created from compiling Listing 8.2, you can execute the frozen paths program with the following command:

bash> java −cp /usr/local/jython-2.1/jython.jar:. paths 
dos> java −cp c:jython-2.1jython.jar;. paths 

Note that the classes within jython.jar are still required and must appear in the classpath .

It does not work to compile paths.py then random.py separately. jythonc must know if dependencies are to be Java classes or Jython modules at com-pile-time. This class/module dichotomy introduces another important point. The class files created from compiling a module with jythonc cannot be used within Jython as if they are modules. jythonc has made them Java classes and to revert them back into modules is tricky.

Write a Custom __import__() Hook

Writing your own import tools allows greater control but also has some limitations. With a custom import, you can control the location from which modules load. This allows you to use zip files, jar files, classpath resources, remote URL resources, and more as the source for modules. The limitation, however, is the current difficulty in dealing with pre-compiled modules. The duality of Python modules (* .py files) and Py classes (modules that are compiled into $py.class files) makes loading pre-compiled modules troublesome in Jython. The ability to load a compiled Jython module ($py.class) without having to freeze all module dependencies is a desired feature dubbed poor man freezing, but it is not yet implemented.

Listing 8.3 is a simple implementation of custom module loading that loads modules (*. py files) from the classpath. This allows access to *. py files that are packaged within jar files—something often desired for delivering applications. In Listing 8.3, loadmodule is the custom import function, and the myTest.py module is the simple companion module loaded with loadmodule.

The important aspects of the loadmodule function are as follows:

  • The classloader

  • Reading the module

  • Creating a module instance with the new module

  • The exec function

When examining the contents of the sys module, you will see three functions related to class loaders: classLoader, getClassLoader, and setClassLoader. Currently sys.classLoader is only set in a jythonc -compiled class. Listing 8.3 includes two extra lines for working with uncompiled code (they are commented out in the listing).

After acquiring a class loader, the loadmodule function begins loading the desired module. The steps to reading the module are as follows:

  1. Create a byte [] buffer with the jarray module to use as an argument to the stream’s read() method.

  2. Read from the buffer.

  3. Append data to the module string until the read() method reports the end of file (-1).

The new module enables the dynamic creation of classes, functions, methods, instances, and modules; the loadmodule function makes use of its dynamic module creation to create a module object. This module should be registered with the list of all modules loaded, which is held in the PyStringMap sys.modules, but also needs a reference in the current namespace, so both sys.modules[name] and mod are assigned the new module instance.

The remaining work in setting up the module is executing it, but it must be executed in the namespace of the new module instance created— mod (otherwise it executes in the top-level environment, or __main__). in mod.__dict__ is added after the exec statement to force bindings resulting from the execution to appear in the mod.__dict__ mapping object.

Example 8.3. Custom Module Import

# file: customimp.py 
import sys 
import new 
import jarray 
import java 

def loadmodule(name, bufferSize=1024): 
    filename = name + ".py" 
    cl = sys.getClassLoader() 
    if cl == None: 
        cl = java.lang.ClassLoader.getSystemClassLoader() 
    r = cl.getResourceAsStream(filename) 
    if not r: 
        raise ImportError, "No module named %s" % name 
    moduleString = "" 
    buf = jarray.zeros(bufferSize, "b") 
    while 1: 
        readsize = r.read(buf) 
        if readsize==-1: break 
        moduleString += str(java.lang.String(buf[0:readsize])) 
    sys.modules[name] == mod = new.module(name) 
    exec(moduleString) in mod.__dict__ 
    return mod 

myTest = loadmodule("myTest") 
print myTest.test() 

The simple myTest module used to test the custom loading is as follows:

# file: myTest.py 
def test(): 
    return "It worked" 

Listing 8.3 is a nice compromise because even if you freeze the application, you can still have flexibility with the *.py files that you load with loadmodule. Any required *.py files can be updated, added, or removed from a resource without having to recompile the anything.

To freeze Listing 8.3 and include required Jython classes all in a single jar file, use the following jythonc command:

jythonc −all −−jar myapp.jar customimp.py 

You can now add the myTest.py file to myapp.jar with the following:

jar −uf myapp.jar myTest.py 

The myapp.jar file contains all you need to run the application, but you can easily update the myTest.py file without having to recompile anything. The following command runs the customimp module:

>java −cp myapp.jar customimp 
It worked 

jythonc Options

jythonc has a number of options that allow special treatment of the compiled files, dependencies, compiler options, and more. jythonc’s syntax requires that options are specified first, followed by the modules to be compiled:

jythonc options modules 

The modules can be full paths to python modules, such as the following:

jythonc /usr/local/jython2.1/Lib/ftplib.py 

Or, modules in the sys.path may be listed by just the module’s name as it would be used in an import statement:

jythonc ftplib 

Table 8.1 shows each of jythonc’s options and what it does. Notice that each option has a long and short form that may be used interchangeably. Some of the jythonc options are also available as registry keys. Equivalent registry keys are noted where applicable.

Table 8.1. jythonc Options

Option

Description

−− deep

-d

The −− deep option locates and compiles all dependencies required for an application. It creates what is called a “frozen,” or self-contained, application. A frozen application can run without access to the Jython Lib directory and does not cache Java package information at startup.

−− jar jarfile

-j jarfile

The −− jar option automatically applies jarfile the −− deep option and it places the results of the compilation in the specified jar file. A name jar file must appear with the −jar option. This does not update existing jar files, but instead over writes if a jar file of the specified name already exists.

−− core

-c

The −− core option automatically applies the −− deep option and adds the core Java classes to the jar file specified with the −− jar option. The −− core option is designed to be used in conjunction with the −− jar option. If no −− jar option appears, the −− core option is ignored.

−− all

-a

The −− all automatically applies the −− deep option and it adds the classes from the org.python.core, org.python.parser, and org.python.compiler packages to the jar file specified with the − -jar option. The resulting jar file contains all required files to run the Jython application. The −− all option is designed to be used in conjunction with the −− jar option. If no −− jar option appears, the −− all option is ignored.

−− addpackages packages

-A

The −− addpackages option adds the specified Java packages to the jar file specified with the −− jar option. The current implementation adds class files that are in package hierarchies (directories), but not the contents of jar files.

−− workingdir directory

-w directory

jythonc uses a directory called jpywork by default to create java files and compile to classes. The −− workingdir option allows you to specify another directory for this to occur.

−− skip modules

-s modules

The −− skip option requires a comma-sep arated list of module names. All module names in list are excluded from the compilation.

−− compiler compilerName

-C complierName

The −− compiler option allows you to specify which Java compiler to use. If the desired compiler is in the path, its name is sufficient; otherwise the path and name are required. You can use a −− compiler option of NONE to have jythonc stop after the creation of the java class. Jython’s registry file also includes the key python.jythonc.compiler, which can be used to set the default compiler.

−− compileropts options

-J

The −− compileropts options allows you to specify options that are passed to the Java compiler used. A −− compileropts −O option would pass the optimize (-O) option to javac (if that is the compiler used).

−− package packageName

-p packageName

The −− package option places the compiled code into the specified Java package. Accessing the compiled code then requires the package name to qualify the classes. Compiling module A with −p test means future references to A require test.A. The −− package option is most useful in conjunction with the −− deep option.

−−bean jarfile

-b jarfile

The −bean option creates a jar file jarfile that includes the appropriate bean manifest file. This is useful if the Jython class being compiled follows bean conventions and you wish to use the jar file created from within a bean box.

−−falsenames names

-f names

The −−falsenames option sets a comma-separated list of names to false.

−−help

-h

The −−help option prints usage information to the screen

Table 8.2 shows sample usages of jythonc coupled with explanations of how the options specified affect the compilation.

Table 8.2. Usages of jythonc

Command

Result

jythonc test.p

This compiles test.py into runnable class files that will still potentially depend on Jython modules.

jythonc −d −A utest

−j A.jar test.py

This compiles test.py into a frozen application. The −d (deep) option tracks all dependencies. The −A utest option adds java classes from the utest package to the resulting jar file. The -j A.jar option tells jythonc to put all resulting files into the file A.jar.

jythonc −p unit

−j test.jar *.py

This compiles all modules in the current directory into Java class files. All class files will be part of the java package unit because of the −p option, and will be placed in the test.jar file because of the −j option.

jythonc −b myBean.jar

namebean.py

This compiles the namebean.py module and places the resulting class files into the myBean.jar file. This also adds the appropriate bean manifest to the myBean.jar file.

jythonc −−core −j

test.jar test.py

This compiles the test.py into a frozen application. All dependencies are tracked down and compiled because the −−core option implies the −−deep option. Additionaly, Jython’s core Java classes are also added to the jar file.

Java-Compatible Classes

A compiled Jython class can behave like a native Java class if it is Java-compatible. To make a Jython class Java-compatible, you must consider the following three guidelines:

  • The Jython class must subclass a Java class or interface.

  • The Jython class may include Java method signature hints called sig-strings.

  • The Jython class must be within a module of the same name. The Java-compatible class A should be within the file A.py.

The first guideline is fairly simple: Subclass a Java class or interface. If you do not need the functionality from a specific Java class, subclass java.lang.Object. You may still inherit from one or more Jython classes— jythonc does not alter Jython’s inheritance rules, it’s just an additional requirement for those Jython classes used within Java. This doesn’t apply to runnable or frozen applications, it is only when you require a compiled Jython class to masquerade as a Java class.

The second guideline is the use of @sig strings. A @sig string is really a doc string with a specific format. The format is the literal @sig followed by a valid Java method signature. Suppose you have the following Jython method:

def greet(self, name): 
    return "Hello " + name 

The same method with an added @sig string would appear as follows:

def greet(self, name): 
    "@sig public String greet(String name)" 
    return "Hello " + name 

The @sig in this case specifies a public method with a String object as a return value and a String object as the parameter name. From Java, this method now acts as if it were Java code with the signature specified in the @sig string.

Not all methods require @sig strings. If you are overriding a method that appears in the Java superclass (see requirement #1), you need not specify the method signature. jythonc is smart enough to get this information from the Java superclass. In addition, if you never intend to call a certain method from Java, you need not specify a @sig string. In other words, if method A calls method B, and method A has a @sig string, then method A is callable from Java, while method B is not; however, method B is still callable from method A. Intra-class methods that are not accessible from Java are still accessible from Jython. This acts in a sense as enforced abstraction.

The third requirement is that the name of the class and the module that contains it must match. Listing 8.4 is a Jython module called message.py. Listing 8.4 also contains a class called message. Note the matching class and module name meet the third requirement for Java-compatible classes.

An Example Java-Compatible Jython Class

The message class in Listing 8.4 has two methods: __init__ and showMessage, of which, the showMessage method has a @sig string. The __init__ method does not, but it is still accessible from Java. Why? jythonc is able to determine this information from the Java superclass. The catch is that of the three constructors in the superclass, all three now point to the single __init__ method in the subclass. The subclass chooses to implement only the one signature, and fortunately, the number of parameters makes it clear which one. Remember that only methods that do not override base class methods require @sig strings. You may choose to supply a @sig string even where it is not required—there is no harm in that, but there is no advantage.

Example 8.4. Java-Compatible Jython Class

# file: message.py 
import java 

class message(java.awt.Frame): 
    def __init__(self, name): 
        self.setTitle(name) 
        self.label = java.awt.Label("", java.awt.Label.CENTER) 
        action = lambda x: java.lang.System.exit(0) 
        button = java.awt.Button('OK', actionPerformed=action) 
        p = java.awt.Panel(java.awt.GridLayout(2,1,2,2)) 
        p.add(self.label) 
        p.add(button) 
        self.add(p) 

    def showMessage(self, text): 
        "@sig public void showMessage(String text)" 
        self.label.setText(text) 
        self.pack() 
        self.show() 

if __name__ == '__main__': 
    m = message("Test Message") 
    m.showMessage("I look like Java, but I'm not") 

To use the message class in Listing 8.4 from within Java, it must be compiled with jythonc. No special options are required, so this is as simple as jythonc message.py (assuming you are in the same directory as message.py and jythonc is in your path). Again note that depending on your compiler version, you may receive a warning message concerning the use of a deprecated API. The output from running jythonc message.py should look similar to this:

processing message 

Required packages: 
  java.lang 
  java.awt 

Creating adapters: 
  java.awt.event.ActionListener used in message 

Creating .java files: 
  message module 
    message extends java.awt.Frame 

Compiling .java to .class... 
Compiling with args: ['C:\JDK1.3.1\bin\javac', '-classpath', '"C:\jython 
2.1a1\jython.jar;;.\jpywork;;C:\jython 
2.1a1\Tools\jythonc;C:\WINDOWS\Desktop\ch8examples\.;C:\jython 
2.1a1\Lib;C:\jython 2.1a1;c:\jython 2.1a1\Lib\site-python"', 
'.\jpywork\message.java'] 
0 

The messages you receive from jythonc are valuable. What is particularly valuable when compiling Java-compatible classes such as message.py is the information printed when jythonc is creating the *.java files:

Creating .java files: 
  message module 
    message extends java.awt.Frame 

It is especially important to watch for the appropriate extends message, which ensures that you have met the first of the Java-compatible requirements.

Now that you have message.class and message$_PyInner.class, how do you use them from Java? The only requirement is placing both generated class files and the jython.jar file in the classpath. Otherwise, it acts no different than any other Java class. Listing 8.5 is a simple Java class that uses the message class as if it were native Java.

Example 8.5. Using a Compiled Jython Class in Java

//file MessageTest.java 
import message; 

public class MessageTest {
    public static void main(String[] args)) {
        message m = new message("Test"); 
        m.showMessage("I look like Java, but I'm not"); 
    } 
} 

To compile Listing 8.5, include jython.jar and the message classes in the classpath. From within the directory containing message.class and message$_PyInner.class, and using javac from the Sun jdk, the command is as follows:

javac −classpath .;/path/to/jython.jar MessageTest.java 

Running the MessageTest class also requires that the jython.jar and both message class files be in the classpath. If the classes message.class, message$_PyInner.class, and MessageTest.class are in the same directory, run MessageTest with the following command:

java −cp .;/path/to/jython.jar MessageTest 

The small message box similar to that in Figure 8.1 should appear.

A Jython Message Box.

Figure 8.1.  A Jython Message Box.

Providing a specific method signature in a @sig string does not automatically allow for overloaded methods. Yes, a @sig string specifies a unique signature, but as noted in Chapter 6, “Classes, Instances, and Inheritance,” if Jython overrides a Java method, it must handle all overloaded methods of that name. You cannot add a @sig string in hopes of circumventing this generalization. Like-named methods in a Java superclass cannot automatically handle parameter types that differ from the @sig string. Nor can you define multiple, like-named methods in a Jython subclass to handle differing parameter types based solely on @sig strings.

Listing 8.6 demonstrates with three classes called Base, sub, and App.

Example 8.6. Overloaded Methods and jythonc

// file: Base.java 

public class Base {
    public String getSomething(String s) {
        return "string parameter. Base class."; 
    } 

    public String getSomething(int i) {
        return "int parameter. Base class."; 
    } 

    public String getSomething(float f) {
        return "float parameter. Base class."; 
    } 
} 
#- - - - - - -
#file: Sub.py 

import Base 

class Sub(Base): 
    def getSomething(self, var): 
        "@sig public String getSomething(String var)" 
        return str(type(var)) + " Jython subclass.." 

//- - - - - - - -
//file: App.java 

import Sub; 
public class App {
    public static void main(String args[]) {
        Sub s = new Sub(); 
        System.out.println(s.getSomething("A string")); 
        System.out.println(s.getSomething(1)); 
        System.out.println(s.getSomething((float)1.1)); 
    } 
} 

The base Java class called Base has an overloaded method called getSomething. Three versions of this method collectively allow for String, int, and float parameter types. The Jython subclass, called Sub, overrides the getSomething method, but has a @sig string specifying only the String parameter type. This does not restrict it to overriding that specific method signature, however. Method signature information is also gleaned from the superclass, so all methods in the superclass with the same name, but different signatures, are automatically directed to the single method of that name in the Jython subclass.

To compile the files in Listing 8.6, place them all in the same directory, change the console’s working directory to that where they reside, then compile with the following commands (in the specified order):

javac Base.java 
jythonc −w . Sub.py 
javac −classpath . App.java 

Execute App.java with the following command from within the same directory:

dos>java −cp .;path	ojython.jar App 
*nix>java −c; .:/path/to/jython.jar App 

The results should appear similar to this:

org.python.core.PyString Jython subclass. 
org.python.core.PyInteger Jython subclass. 
org.python.core.PyFloat Jython subclass. 

You can see that jythonc has automatically intercepted each of the Base class’s overloaded methods with the name getSomething. The single Jython method by this name is expected to handle each of these conditions, and trying to circumvent this generalization with a @sig string gained nothing.

Module-Global Objects and Java-Compatible Classes

The rigidity of adding Java method signatures can feel contradictory to Jython’s dynamic nature. Many common patterns that occur in Jython depend on dynamic special methods like __getattr__ and __setattr__, or other mechanisms that don’t easily translate into compile-time checks. Additionally, module-global objects might be unclear when using jythonc to create Java-compatible classes. The focus on the class shadows the value of module-global identifiers: those objects defined in a module, but not within the java-compatible class. Although a Jython class and those methods with @sig strings are callable from Java, module-global objects are not. However, the Jython class may use module-level code, and module-level code may even alter the details of a class used by Java (as in Listing 8.8). Listing 8.8 uses a bit of Jython’s dynamicism in a compiled class as well as lends clarity to module-global objects.

To set the stage for Listing 8.8, imagine prototyping a Java application with compiled Jython classes. What do you do about static methods? Jythonc doesn’t allow you to specify static in a @sig string, so Listing 8.8 was invented to create the illusion of static methods in a compiled Jython class. It doesn’t create a legitimate static method—the word “static” doesn’t appear in it’s signature, but it does emulate one. It does so with the use of other module-global code that is not callable from Java. It’s useful to remember that Java may call only specific methods in the compiled Jython class, but Jython code has no restrictions. The generated method signatures are a hook into Jython and need not alter its flexibility.

Listing 8.8 defines a class called JavaPrototype, which contains one function definition: test. The test function is empty except for the @sig string. All this test function does is make jythonc generate a desired Java method signature; it does not have anything to do with the run-time functionality. Why? Because the last line of the module assigns a callable instance of another class to test. The staticfunctor class in Listing 8.8 defines __call__, and an instance of this class is what provides the desired method functionality. If you were wondering why the arg identifier appears in the test method’s @sig string, it is because we are actually trying to generate a signature for the __call__ method in the second class. Following is a summary of what happens in Listing 8.8:

  1. Create a phony method and @sig string to trick jythonc into adding the desired Java method signature.

  2. Define a class that implements __call__. The __call__ definition is the actual functionality desired so the method signature (step 1) should fit its needs.

  3. Assign the identifier from the phony method to an instance of a class containing the desired __call__ method.

Example 8.7. Creating a Static-Like Method in Compiled Jython

# file: JavaPrototype.py 
import java.lang.Object 

class JavaPrototype(java.lang.Object): 
    def test(self): # This will become static-like 
        "@sig public void test(String arg)" 
class staticfunctor(java.lang.Object): 
    def __call__(self, arg): 
        print "printing %s from static method" %% arg 

JavaPrototype.__dict__['test'] = staticfunctor() 

How can it be static if the @sig string doesn’t specify static? This is a stretch, but it acts static in the Jython sense. If you use the JavaPrototype module within Jython, you can test to see that multiple instances of the class share a single, common instance of the test function:

>>> import JavaPrototype 
>>> p1 = JavaPrototype.JavaPrototype() 
>>> p2 = JavaPrototype.JavaPrototype() 
>>> p1.test == p2.test 
1 
>>> p1.test is p2.test 
1 
>>> print id(p1.test), id(p2.test) 
5482965 5482965 

The interactive example above uses the JavaPrototype module, not the compiled classes. This invites a warning about compiled Jython classes. A compiled Jython class is designed to work in Java, not in Jython. You should use modules in Jython and reserve jythonc -compiled classes for use within Java.

Listing 8.8 is a small Java test class that imports and uses the compiled JavaPrototype class to confirm that it behaves properly as a Java class as well. This assumes that you have used jythonc to compile the JavaPrototype module into class files with jythonc JavaPrototype.

Example 8.8. Using the JavaPrototype Class from Within Java

// file: Test.java 
import JavaPrototype; 

public class Test {
    public static void main(String[] args)) {
        JavaPrototype i1 = new JavaPrototype(); 
        JavaPrototype i2 = new JavaPrototype(); 
        i1.test("THIS"); 
        i2.test("THAT"); 
    } 
} 

Place in the same direcotry as JavaPrototype.class and 
JavaPrototype$_PyInner.class, then compile with: 
javac −classpath . test.java 
Output from running "java "cp .;path	ojython.jar test" # dos 
                    "java −cp .:/path/to/jython.jar test" # *nix: 
printing THIS from static method 
printing THAT from static method 
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset