Recipe 26.4 Marrying Java and Perl


Problem

You want to make use of your existing Perl code base from a Java application, or vice versa.

Solution

Use the Perl Inline::Java module.

Discussion

Perl is often called a "glue language" that can be used to bring together diverse parts of the software world. But, in addition, it is a full-blown language in its own right for creating software. A wealth of extension modules provide ready-to-run solutions for quite diverse problems, and most of these modules are available for free from CPAN, the Comprehensive Perl Archive Network (http://www.cpan.org/). Also, as a scripting language, it is ideally suited for rapid prototyping. On the other hand, while building graphical user interfaces is definitely possible in Perl, it is not exactly one of the language's strengths. So you might want to construct your GUI using Java Swing, and, at the same time, reuse business logic implemented in Perl.

Fortunately, among the many CPAN modules, Inline::Java makes the integration of Perl and Java a breeze. Let's assume first that you want to call into Java from Perl. For business logic, I have picked a CPAN module that measures the similarity of two strings (the so-called Levenshtein edit distance). Example 26-7 shows the complete source. You need at least Version 0.44 of the module Inline::Java; previous versions did not support threaded applications properly, so use of Swing wasn't possible.

Example 26-7. Swinging.pl
#! /usr/bin/perl # Calling Java from Perl, and back again use strict; use warnings; use Text::Levenshtein qw( );   # Perl module from CPAN to measure string similarity use Inline 0.44 "JAVA" => "DATA";  # pointer to the Inline java source use Inline::Java qw(caught);  # helper function to determine exception type my $show = new Showit;     # construct Java object using Perl syntax $show->show("Just another Perl hacker");            # call method on that object eval {   # Call a method that will call back to Perl;   # catch exceptions, if any.   print "matcher: ", $show->match("Japh", shift||"Java"), " (displayed from Perl)\n"; }; if ($@) {   print STDERR "Caught:", caught($@), "\n";   die $@ unless caught("java.lang.Exception");   print STDERR $@->getMessage( ), "\n"; } __END_  _ __JAVA_  _ // Java starts here import javax.swing.*; import org.perl.inline.java.*; class Showit extends InlineJavaPerlCaller {   // extension only neeeded if calling back into Perl   /** Simple Java class to be called from Perl, and to call back to Perl    */   public Showit( ) throws InlineJavaException { }   /** Simple method */   public void show(String str) {     System.out.println(str + " inside Java");   }   /** Method calling back into Perl */   public int match(String target, String pattern)       throws InlineJavaException, InlineJavaPerlException {     // Calling a function residing in a Perl Module     String str = (String)CallPerl("Text::Levenshtein", "distance",           new Object [] {target, pattern});     // Show result     JOptionPane.showMessageDialog(null, "Edit distance between '" + target +         "' and '" + pattern + "' is " + str,         "Swinging Perl", JOptionPane.INFORMATION_MESSAGE);     return Integer.parseInt(str);   } }

In simple cases like this, you don't even need to write a separate Java source file: you combine all the code, Perl and Java alike, in one single file. You do not need to compile anything, either; just execute it by typing:

perl Swinging.pl

(You can also add a string argument.) After a little churning, a Java message box pops up, telling you that the distance between "Japh" and "Java" is 2. At the same time, your console shows the string "Just another Perl hacker inside Java." When you close the message box, you get the final result "matcher: 2 (displayed from Perl)."

In between, your Perl program has created an instance of the Java class Showit by calling its constructor. It then called that object's show( ) method to display a string from within Java. It then proceeded to call the match( ) method, but this time, something more complicated happens: the Java code calls back into Perl, accessing method distance of module Text::Levenshtein and passing it two strings as arguments. It receives the result, displays it in a message box, and finally, for good measure, returns it to the Perl main program that it had been called from.

Incidentally, the eval { } block around the method call is the Perlish way of catching exceptions. In this case, the exception is thrown from within Java.

If you restart the program, you will notice that startup time is much shorter, which is always good news. Why is that so? On the first call, Inline::Java took the input apart, precompiled the Java part, and saved it to disk (usually, in a subdirectory called _Inline). On subsequent calls, it just makes sure that the Java source has not changed and then calls the class file that is already on disk. (Of course, if you surreptitiously changed the Java code, it is recompiled just as automagically.) Behind the scenes, even stranger things are going on, however. When the Perl script is executed, a Java server is constructed and started unbeknownst to the user, and the Perl part and the Java bits communicate through a TCP socket (see Chapter 17).

Let's look at the other way around now: you conceive of your Java code as the "main program," and you want to call some Perl code. As the Perl folks have it, "TMTOWTDI" (for the uninitiated: "There's more than one way to do it"), but I think it is easiest to actually start from Perl, using a very simple stub that, besides supplying all the Perl business logic you need, basically just starts up your Java code and hands control over to Java.

StringDistance.java is a fairly short Swing application that displays the dialog shown in Figure 26-3.

Figure 26-3. StringDistance.java/.pl in action
figs/jcb2_2603.gif


The full source is included online; Example 26-8 shows just the essential part that deals with Perl. This time, since it is a separate source file, you have to compile it yourself.

Example 26-8. StringDistance.java (extract)
// requires classpath to include InlineJavaServer.jar; usually something like // .;<perldir>/site/lib/Inline/Java/InlineJavaServer.jar public class StringDistance extends InlineJavaPerlCaller {   JFrame frame;           // visual container   JTextField tf[], dist;  // text input fields, result output field   JButton go, exit;       // action buttons   /* The constructor with possibly 2 initial strings */   public StringDistance(String[] strs) throws InlineJavaException {     // omitted from printed version   }   /** The central interface function to Perl. */   public int match(String s0, String s1) {     try {       String str = (String)CallPerl("Text::Levenshtein", "distance",                                     new Object [] {s0, s1});       return Integer.parseInt(str);     } catch (InlineJavaPerlException e) {       System.err.println("Inline Java Perl Exception: " + e);     } catch (InlineJavaException e) {       System.err.println("Inline Java Exception: " + e);     }     return 0;   } }

Example 26-9 shows the full Perl code that is needed for getting things running just 12 lines of code.

Example 26-9. StringDistance.pl
#! /usr/bin/perl # Perl main program acting as a stub for callbacks from Java use strict; use warnings; # all modules called from either Perl or from Java must go here: use Text::Levenshtein qw( ); use Inline "Java"  => "STUDY",            # glean interface from Java class file            "AUTOSTUDY" => 1,              # glean more interfaces, too, just in case            "STUDY" => ["StringDistance"], # name of our main Java class            "CLASSPATH" => ".",            # needed in order to find main Java class            ; my $sd = StringDistance->new(\@ARGV);     # construct instance of main Java class $sd->show( );                              # call routine to show it $sd->StartCallbackLoop( );                 # prepare to listen for threaded callbacks

Marrying two platform-independent languages, like Perl and Java, in a portable way skirts many portability problems. When distributing inlined applications, be sure to supply not just the source files but also the contents of the _Inline directory. (It is advisable to purge that directory and to rebuild everything just before distribution time; otherwise, old compiled versions left lying around might make it into the distribution.) Each target machine needs to repeat the magic steps of Inline::Java , which requires a Java compiler. In any case, the Inline::Java module must be installed.

Since Perl has Inline modules for a number of other languages (ordinary languages like C, but others as exotic as Befunge), one might even consider using Perl as glue for interoperation between those other languages, jointly or separately, and Java. I am sure many happy hours can be spent working out the intricacies of such interactions.

See Also

Full information on Inline::Java can be found on CPAN (http://search.cpan.org/) or in the POD (plain old documentation) that is installed along with the module itself.



Java Cookbook
Java Cookbook, Second Edition
ISBN: 0596007019
EAN: 2147483647
Year: 2003
Pages: 409
Authors: Ian F Darwin

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