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