Metagnostic
home

JNIPort for Dolphin Smalltalk

Overview

Contents

Players

Layers

Examples

Configuration

InFAQ

Changes

Licence


Back to Goodies

Examples of Basic JNIPort

This section is an example of using the lowish-level interfaces of package 'CU Java Base', which are described here. We read a .ZIP file using Java's zip file handling classes.

A parallel example uses the higher-level facilities provided by automatic wrapper generation (and especially, ghost classes) to perform the same tasks.

One general point about this example: we use the low-level methods directly throughout, which is certainly not how you would use them in practice. The better way is to define wrapper classes and provide convenient Smalltalk-style methods that are built on top of the low-level API. In fact you'd probably want to use the Wrapper Wizard to help with this, rather than do it all by hand.

Ensure that a JMV is running, and open a new workspace. We start, as always, by getting a reference to the JVM object:

	jvm := JVM current.

Now we need to get a reference to the class static standing for the Java class, java.util.zip.ZipFile:

	zfClass := jvm findClass: #'java.util.zip.ZipFile'.

Now we'll open a ZIP file. For this example, we'll use the DolphinJNIHelper.zip file that should be in the Metagnostic\Extras folder. Use the file dialog to find that file:

	zfName := FileOpenDialog showModal.

Now we create a new ZipFile object from that filename, this will be equivalent to the Java code:

	String zfName = // whatever;
	java.util.zip.ZipFile zipfile = new java.util.zip.ZipFile(zfName);

At the level of this API, we have to build an argument array explicitly. First we convert the Smalltalk String to a Java String, then allocate a JNIValueArray big enough to hold 1 argument, and add the string to it. It is vitally important that we keep a separate reference to the Java string, because the argument array (which is a kind of ExternalStructure) does not do so itself. (If you start getting inexplicable NullPointerExceptions then it's quite probably because you've forgotten this point.)

	zfName := zfName asJavaString: jvm.
	args := (JNIValueArray new: 1)
			objectAt: 1 put: zfName;
			yourself.

Now we want to call the constructor. All references to Java methods (and fields, in fact) must have a “signature” string (necessary because Java allows method overloading). The signature for a constructor that takes a String argument is '(Ljava/lang/String;)V'. For an explanation of this odd construction, see the rules for forming signatures. Once we have worked out the proper signature, we can use that and the argument vector to call the constructor:

	sig := '(Ljava/lang/String;)V'.
	zipfile := zfClass callConstructorSignature: sig withArguments: args.

If there are no arguments to the constructor, then a simpler form can be used (since the signature is necessarily '()V'), the method is #callConstructor.

We may as well find out how many entries there are in the ZIP file, in this case the Java method takes no arguments and returns an int so we can use a pre-packaged shortcut to avoid messing with signatures:

	zipfile callIntMethod: 'size'.
		"--> 6"

Now we'll iterate over the elements of the ZipFile; we start by getting the Java java.util.Enumeration iterator. In this case we have to provide a signature:

	sig := '()Ljava/util/Enumeration;'.
	entries := zipfile callObjectMethod: 'entries' signature: sig.

The entries object is an instance of some Java class that implements the java.util.Enumeration interface, (for some reason, ZipFile returns a Enumeration rather than the newer Iterator); it is actually an instance of some inner class in ZipFile (so its class name is something like “java.util.zip.ZipFile$2”). It has the Java methods hasMoreElements() and nextElement() that are guaranteed by the interface. So we could iterate over the entries with a loop like:

	entries := zipfile callObjectMethod: 'entries' signature: sig.
	[entries callBooleanMethod: 'hasMoreElements'] whileTrue:
		[| next |
		next := entries callObjectMethod: 'nextElement'.
		Transcript display: next; cr].

Which is ugly, even though we can take advantage of the shortcut method #callObjectMethod: which “knows” that the signature is '()Ljava/lang/Object'. JNIPort comes with a better way to iterate over a Enumeration (or an Iterator). Sending #asAnEnumeration (or #asAnIterator) to the entries object will answer an adaptor that implements the basics of the <ReadableStream> protocol. You can then use the common Smalltalk iteration style:

	entries := (zipfile callObjectMethod: 'entries' signature: sig)
			asAnEnumeration.
	[entries atEnd] whileFalse:
		[Transcript display: entries next; cr].

or, if you prefer:

	entries := zipfile callObjectMethod: 'entries' signature: sig.
	entries asAnEnumeration do:
		[:each | Transcript display: each; cr].

which is marginally more efficient than the explicit loops, since it avoids repeatedly looking up the method names/signatures.

By the way, the adaptor object only implements a small part of the Smalltalk <ReadableStream> protocol. You can get a more complete implementation by either writing the other wrapper methods yourself <grin> or by asking for a further level of adaptor by sending #asReadStream.

Now to read one of the files from inside the ZipFile. We start by getting the appropriate ZipEntry, this will be the equivalent of the Java code:

	java.util.zip.ZipEntry entry
		= zipfile getEntry("DolphinJNIHelper/DolphinJNIHelper.h");

The signature in this case is '(Ljava/lang/String;)Ljava/util/zip/ZipEntry;', and we also have to build another argument array, so the code is:

	sig := '(Ljava/lang/String;)Ljava/util/zip/ZipEntry;'.
	name := 'DolphinJNIHelper/DolphinJNIHelper.h' asJavaString: jvm.
	args := (JNIValueArray new: 1)
			objectAt: 1 put: name;
			yourself.
	entry := zipfile
			callObjectMethod: 'getEntry'
			signature: sig
			withArguments: args.

By now, you should have a very clear idea of why I wrote the wrapper generation features.

If you check the Java documentation then you'll see that you can ask the ZipEntry for a fair raft of data. What you can't do is get the contents of the file, for that you have to go back to the ZipFile (that's just the way the Java class is designed), so:

	sig := '(Ljava/util/zip/ZipEntry;)Ljava/io/InputStream;'.
	args := (JNIValueArray new: 1)
			objectAt: 1 put:entry;
			yourself.
	stream := zipfile
			callObjectMethod: 'getInputStream'
			signature: sig
			withArguments: args.

This answers an object that is of some subclass of java.io.InputStream. Because there is a registered wrapper class for InputStream, actually JavaIoInputStream, the stream object will be an instance of some subclass of that. The mismatch between Java's IO design and Smalltalk's is quite bad, and I haven't been able to create a completely Smalltalk-flavoured interface to Java streams, however you can ask the stream for its #upToEnd (you can't use #contents because that requires a positionable stream and Java's streams aren't arbitrarily seekable). Since the stream in question is binary, the answered collection will be a ByteArray. So we can get the contents of the file by saying:

	bytes := stream upToEnd.
	Transcript nextPutAll: bytes asString; flush.

That's almost the end of the example. We may as well clean up properly, though:

	zipfile callVoidMethod: 'close'.

Finally, to see an example of what happens when Java code throws exceptions, try getting a ZipEntry again now that the ZipFile has been closed:

	sig := '(Ljava/lang/String;)Ljava/util/zip/ZipEntry;'.
	name := 'xxx' asJavaString: jvm.
	args := (JNIValueArray new: 1)
			objectAt: 1 put: name;
			yourself.
	entry := zipfile
			callObjectMethod: 'getEntry'
			signature: sig
			withArguments: args.

Which should give a normal Smalltalk walkback. You can trap the error in various ways, one is:

	[zipfile callObjectMethod: 'getEntry' signature: sig withArguments: args]
		on: JavaException
		do: [:err | err notify].

Another:

	exClass := jvm findClass: #'java.lang.IllegalStateException'.
	[zipfile callObjectMethod: 'getEntry' signature: sig withArguments: args]
		on: exClass
		do: [:err | Transcript display: 'Too bad'; cr].

Copyright © Chris Uppal, 2003-2005

Java, JNI (probably), JVM (possibly), and God knows what else, are trademarks of Sun Microsystems, Inc.