In this section, we review how the various Objective-C entities are exposed to Java.
Arguments and return types are automatically converted by the interface.
Methods without arguments are (usually) exposed as they are: for example, the NSProcessInfo method
- (NSString *) processName;is exposed to Java as
public native String processName ();
Sometime, the name is changed. JIGS allows to do this - but the less it is done, the easier for the programmer. Anyway sometimes it is appropriate; for example, the GNUstep method description (which can be invoked on any object to return a string describing the object) is exposed to Java with the name toString () (which is the standard method for doing the equivalent job in the Java world).
In Objective-C, methods have their arguments introduced by `:', as in the following example:
- (void) addObject: (id)anObject;This method is accessible from Java as
public native void addObject (Object arg0);The only difference is that in Java the argument is enclosed in brackets.
Things get more complicated if the method has multiple arguments. In Objective-C, each argument is introduced by a little string followed by `:', as in the following example:
- (BOOL) writeToFile: (NSString *)fileName atomically: (BOOL)flag;In this example, fileName is the first argument, and flag is the second one. The method name is
writeToFile:atomically:The method is invoked as follows:
/* Objective-C example */ [array writeToFile: @"bar" atomically: YES];
In Java, these methods are (usually) exposed by keeping only the first part of the name (which is what comes before the first `:'). For example, writeToFile:atomically: is exposed as:
public native boolean writeToFile (String arg0, boolean arg1);and can be then invoked as follows:
/* Java example */ array.writeToFile ("bar", true);
It is possible that different Objective-C methods have a name which begins in the same way, but differs in the next parts. For example, imagine that an hypothetical Objective-C class LocalizedArray had both the method
- (BOOL) writeToFile: (NSString *)fileName atomically: (BOOL)flag;and the method
- (BOOL) writeToFile: (NSString *)fileName atomically: (BOOL)flag inLanguage: (NSString *)language;This second method would translate all the entries of the hypothetical array object into language, and then save it.
The methods would be exposed to Java as:
public native boolean writeToFile (String arg0, boolean arg1); public native boolean writeToFile (String arg0, boolean arg1, String arg2);which causes no conflict, because Java allows overloading of methods, and the two methods have a different Java signature. It is worth to remark here that the two methods are different in Objective-C because their names are different, while they are different in Java because their (name + Java signature)s are different. This will be important later in the discussion of selectors.
In rare cases, the conflict can't be escaped - JIGS allows the programmer doing the wrapper to resolve the conflict by exposing one of the two conflicting methods with a different name to Java.
In short, for each Objective-C init method, the Java class has a constructor.
Let us consider for example the NSArray class, which is available in Java as the gnu.gnustep.base.NSArray class.
In Objective-C, the class has the following init methods:
- (id) init; - (id) initWithArray: (NSArray*)array; - (id) initWithContentsOfFile: (NSString*)file; - (id) initWithObjects: firstObj, ...; - (id) initWithObjects: (id*)objects count: (unsigned)count;The last two methods are not exposed to Java (the first one because it takes an arbitrary number of arguments, which Java does not support; the second one because it takes a pointer argument).
The corresponding Java class has the following constructors:
public NSArray (); public NSArray (NSArray array); public NSArray (String fileName);
For example, to create an array from the contents of the array.debug file in my home directory, I would do:
/* Java Example */ NSArray array = new NSArray ("/home/nicola/array.debug");
When two Objective-C init methods have different names but arguments of the same type, it is of course impossible to expose both of them to Java. This case is rare and the only solution is to change or extend the Objective-C API (or expose only one of the two).
Primitive types are mapped automatically by the interface. For example, an Objective-C method returning a BOOL (the Objective-C type for boolean variables) will return a boolean in Java.
Strings are transparently morphed through the interface. To the end-user programmer, this simply means that Objective-C methods having as argument (or returning) Objective-C NSString objects take as argument (or return) in Java Java String objects.
Internally, when the method is called, the interface generates on the fly a GNUstep NSString object corresponding to the Java String object, and passes it to Objective-C. Viceversa, when an Objective-C method returns a NSString, a Java String object is generated on the fly from the returned string and returned to Java. This design is very comfortable for the programmer, who always uses the string objects of the environment he is programming in, but it is not very efficient. This will only have an effect on your program's performance if you pass very big strings - and (or) a huge number of them - through the interface, while normally it doesn't make any appreciable difference in terms of performance.
Numbers (instances of java.lang.Number and subclasses) are transparently morphed through the interface, in the same way as strings are. Again, this simply means that if an Objective-C method takes as argument (or returns) an Objective-C NSNumber object, the method in Java will take as argument (or return) a Java Number object; the interface will automatically take care of the conversion.
This section explains the internals of the number morphing; it's quite technical and for most people it's not really useful nor interesting, so you may safely skip it at a first reading.
Internally, the situation is more complicated than in the case of strings, because there are many public subclasses of java.lang.Number, which hold considerably different types of numbers each; and NSNumber, according to its Objective-C type, can hold very different types of numbers as well.
When the interface has to convert a Java Number object to an Objective-C NSNumber object, it will check if the Number object belongs to one of the standard Java number classes (Byte, Short, Int, Long, Float, Double); in this case, it will extract the value of the number in the appropriate precision (for example, by using byteValue for a Byte, or by using longValue for a Long), and create a corresponding NSNumber object with an Objective-C type which is big enough to hold the value. If the Number object instead is of a non-standard class (such as a class you implemented), the interface will extract the value of the number by using doubleValue, and create a NSNumber object big enough to hold this double value. This can obviously result in loss of precision or conversion problems if the Java number object is holding a value which can't be represented with a double.
When, viceversa, the interface has to convert an Objective-C NSNumber object into a Java Number object, it will check the Objective-C type of the Objective-C object (only a finite, documented number of number types are possible); it will extract the value using the appropriate method (such as longValue is the number is of type long), and create a Java Number object of a standard class which can hold the value.
Please notice that an Objective-C NSNumber object can hold a BOOL (boolean) value; this type is morphed into a small Java Number object (usually a Short) holding 0 for NO, and 1 for YES. It can't be morphed into a Java Boolean object, because in Java Boolean objects are not numbers, and the whole point of morphing is that a NSNumber is morphed exactly into a Java Number.
Exception generated during calls to the GNUstep Objective-C code are made available to Java as objects of the gnu.gnustep.base.NSException class. This is a subclass of java.lang.RuntimeException. GNUstep Objective-C exception usually have a name and a reason. For example, NSArray's objectAtIndex () method can generate an exception if you ask for an object which lies past the end of the array; in this case the name of the exception is `NSRangeException', and the reason is `Index out of bounds'. Here is an example:
/* Java Example */ NSArray array; Object object; array = new NSArray (); try { /* Raises an exception because array is empty */ object = array.objectAtIndex (5); } catch (NSException e) { System.out.println ("Name is " + e.name ()); System.out.println ("Reason is " + e.reason ()); System.out.println ("Full Description is:" + e); }The example shows how to access the information about the GNUstep exception. On my system, it prints:
Name is NSRangeException Reason is Index out of bounds Full Description is gnu.gnustep.base.NSException(NSRangeException):Index out of bounds
Many GNUstep methods can raise exceptions, even if these exceptions are not declared in the Java method declarations (contrary to the Java convention) because in Objective-C the exceptions raised by methods are declared in the documentation, not in the code - so it's important to understand that, when accessing GNUstep from Java, you could need to use an exception handler for delicate parts of your code even if your Java compiler does not force you to do so.
NSRange, NSPoint, NSSize, NSRectare exposed as the Java classes
gnu.gnustep.base.NSRange gnu.gnustep.base.NSPoint gnu.gnustep.base.NSSize gnu.gnustep.base.NSRectThe important thing to understand is that you should think of objects of these classes as C struct - that is, very lightweight objects which are not meant to be subclassed (actually, you can't subclass them at all, since they are final), and only act as a container for information which you want to access and pass around as fast as possible. This is actually why they are structs and not objects in the GNUstep Objective-C libraries in the first place.
We discuss here the case of NSPoint; the other cases are completely similar.
Whenever an Objective-C method takes a NSPoint as argument, the corresponding Java method takes a gnu.gnustep.base.NSPoint object as argument. When the method is called, JIGS internally creates a struct (allocated on the stack so it's fast) and initializes it with the x and y coordinates stored in the Java NSPoint object; it passes this struct as argument to the method call3.1.
Whenever an Objective-C method returns a NSPoint (C struct), the corresponding Java methods returns a Java NSPoint object. When the method is called, JIGS converts the returned struct by creating a corresponding Java NSPoint object, and returning this object to Java.
To create an NSPoint, you just use the constructor NSPoint (float x, float y), as follows:
/* Java Example */ NSPoint point; point = new NSPoint (1, 1);Once you have created a NSPoint, you can read and set its x and y coordinates directly, as in the following example:
/* Java Example */ point.x = 5; point.y = 4;What is extremely nice in this approach is that you read and set the coordinates of the point with exactly the same code you would use in Objective-C:
/* Objective-C Example */ point.x = 5; point.y = 4;
Some functionality which is provided by the GNUstep Base Library in functions has been exposed as methods of the class gnu.gnustep.base.NSPoint. This mainly includes:
public boolean isEqualToPoint (NSPoint aPoint); public String toString ();which provide the equivalent of the GNUstep Base Library functions
BOOL NSEqualPoints (NSPoint aPoint, NSPoint bPoint); NSString *NSStringFromPoint (NSPoint aPoint);Since gnu.gnustep.base.NSPoint is final (ie, it can't be subclassed), your Java compiler should be able to inline all the Java equivalent of these little functions, and they should be reasonably fast.