Metagnostic
home

JNIPort for Dolphin Smalltalk

Overview

Contents

Players

Layers

Examples

Configuration

InFAQ

Changes

Licence


Back to Goodies

Static Wrapper Classes

The basic layer of JNIPort provides proxies for Java objects and classes, the proxies allow you to invoke the underlying Java objects' methods and to access their fields. But they are not, in themselves, particularly easy to use. Invoking an object's toString() method, for instance, involves Smalltalk code like:

	aJavaObject
		callObjectMethod: 'toString'
		signature: '()Ljava/lang/String;'
		withArguments: nil.

which is neither convenient nor readable.

The next step towards real usability is that JNIPort allows you to specify custom wrapper classes. These are subclasses of the generic proxy classes that are associated with specific Java classes. Whenever JNIPort creates a proxy for an instance of a Java class, it checks to see if there is a special wrapper class registered for that case. If there is then it creates an instance of the custom class rather than the generic one. JNIPort follows a similar pattern when it is creating class statics to stand for Java classes. You can add methods to the custom subclass that expose the Java object's (or class's) members directly, or that build on them to create a higher-level interface. Those methods would have to use the low-level features like #callObjectMethod:signature:withArguments: internally, but they can provide a much nicer interface to the users of the proxy.

This section is about the custom wrapper classes, and how to set them up and use them. The Wrapper Wizard is able to do the grunt-work of generating code like the call to toString(), above, so this section is mostly background for understanding what it does and how to use the results.

Please note: this description has become slightly out of date. It is still accurate as far as it goes, but it should be updated to discuss how the new “supplementary classloaders” fit into the picture.

JNIPort establishes the mapping between Java classes and their designated wrappers during the phase of initialisation called bootstrapping. At this time, it scans the subclasses of JavaObject looking for classes that announce themselves to be wrappers. A class states that it is a wrapper by answering true to #isWrapperClass, which by default is implemented as a test that the class has a class-side method (not inherited) named #javaClassName. If it does then subclasses of JavaInstance are registered as the prefered instance wrapper class for the Java class named by #javaClassName, and subclasses of JavaStatic are registered as the preferred class for the corresponding class static. In fact the class static is created at this time, and it remembers the preferred instance class.

In normal operation, all the mappings are established during bootstrapping, but the Wrapper Wizard has the option of registering any new wrapper classes that it builds. Also ghost classes are created and installed using very much the same mechanism.

When JNIPort encounters a reference to a class it has not seen before (which can happen in many ways; e.g. when it sees a reference to an instance of that class) it creates a class static for the class. It decides what the Smalltalk class of the class static will be by climbing the inheritance ladder of the Java class until it finds a class that it already knows about (which may turn out to be java.lang.Object) It then uses the same class for the new class static as it did for the ancestor it has just found. The effect of this is that any registered class-side wrapper (a subclass of JavaStatic, and in fact of JavaClassStatic) will be used for its own Java class and any subclasses. For instance JNIPort uses instances of StaticJavaLangClassLoader as the class statics for the Java class java.lang.ClassLoader, and for all of its subclasses.

JNIPort uses the same technique to find the right instance-side wrapper class, so that, for example, all references to instances of java.lang.Throwable will be proxied by instances of JavaLangThrowable. You can, of course, add more specialised subclasses; indeed the 'CU Java Additional Wrappers' package does add two specialised sub-classes of JavaLangThrowable that are used for instances of java.lang.Error and java.lang.Exception and their further subclasses.

JNIPort uses class statics for Java interfaces too. The mechanism is the same as for normal classes, with the slight difference that, since Java interfaces don't inherit from java.lang.Object, the interface wrappers are subclasses from JavaInterfaceStatic.

JNIPort can also create proxies for Java objects that are known to implement some interface. This case is really rather different from the normal proxies, see here for details, but the mechanism is very similar. Subclasses of JavaInterfaceInstance announce themselves to be wrappers for the Java interfaces named by their #javaClassName methods in the same way.

A convenient way to create and use wrapper classes is to start with the methods generated by the Wrapper Wizard, and then embellish the wrapper class with further methods that build on the generated methods, but which have more natural names and, perhaps, a higher-level of functionality than is provided by the Java class itself. (As an aside: Java classes tend to have very narrow interfaces, sometimes to the point of anorexia.)

As an example of this, the package 'CU Java Additional Wrappers' contains instance wrapper classes for java.io.OutputStream and java.io.Writer. The wrapper classes were both generated by the Wrapper Wizard, and so they have methods with names like #write_charArray:int:int: and #write_byteArray:int:int: which simply forward to the corresponding Java methods. The methods are named by following JNIPort's mechanical rules for the names of the generated methods. The names are not very Smalltalk-ish, and the interpretation of the Integer parameters follows Java's 0-based indexing convention. Therefore the Additional Wrappers package embellishes the raw Java methods with Smalltalk methods like #writeChars:from:to:, which follow the normal Smalltalk conventions, and #nextPutAll: which allow the proxies to understand the Smalltalk output stream protocols.

One important point about this particular example is that most of the embellishments are implemented in a common superclass for the two wrapper classes. JavaIoOutputStream and JavaIoWriter both inherit from JavaWriteStream, which is where methods like #cr and #space are implemented. JavaWriteStream is not itself a wrapper class, but it collects some useful commonality between the two Java classes, even though they are not related in the Java hierarchy. JNIPort contains several such cases where “informal” similarities between Java classes are exploited as subclass relationships within the hierarchy of wrapper classes.

One last point is about ghost classes. JNIPort generates and populates ghost classes on the fly. When it generates a ghost class, it always ensures that the ghost inherits from the most applicable custom wrapper class, which means that it will also understand any embellishments that are defined by the wrapper class. For instance, in the case of a java.Io.Writer, the proxy will be an instance of a dynamically-generated subclass of JavaIoWriter, and so will understand #nextPutAll:.

The Wrapper Wizard generates methods with the same names and meanings as the ghost classes have, but the ghost implementation is faster. So, when a ghost is derived from a class that was initially populated by the Wrapper Wizard, the ghost methods will override the Wizard-generated methods with faster versions. Because #nextPutAll: is implemented on top of the methods that the Wrapper Wizard-generated (rather than calling the methods of 'Java Base' directly) it will pick up the faster implementation if ghosts are in use.


Copyright © Chris Uppal, 2003-2005

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