## 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 apps, 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 apps into self-contained jar files runnable on most JVM's. Consider the development benefits of prototyping apps 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 app. 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.

##### Listing 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.1\jythonc 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\to\jikes # 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\to\jikes 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 \path\jython.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:\WINDOWS\Desktop\ch8examples\guess.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. ##### Listing 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.1\jython.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\to\jython.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:\WINDOWS\Desktop\ch8examples\paths.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.1\Lib") # 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 app is moved to another machine, paths become troublesome and functionality across platforms suffers.

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 app 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 app 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 app Freezing an app 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 app 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 app 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 app (from Listing 8.2) should be four class files: • paths.class • paths$_PyInner.class

• random.class

##### Listing 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 Screenshot should appear.

##### Screenshot 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 , "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.

##### Listing 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\to\jython.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 app 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.

##### Listing 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.

##### Listing 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\to\jython.jar test" # dos "java -cp .:/path/to/jython.jar test" # *nix: printing THIS from static method printing THAT from static method