[Java-gnome-developer] ItemFactory and GtkType and Interning
Brought to you by:
afcowie
|
From: Dan B. <da...@mi...> - 2000-11-05 02:39:31
|
I was perusing the Java-Gnome tutorial again today, and found the part
about making menus. I went through the example, and then noticed the bit at
the top about GTK actually providing a bunch of convenient stuff for making
menus, saving one the hardship of "manual" creation. So, I went off and
looked at the GTK tutorial on that topic at:
<http://www.gtk.org/tutorial/gtk_tut-13.html#ss13.3>
Well, I liked what I saw and wondered if I could do something quick to be
able to use that functionality.
Unfortunately I wasn't able to do that. However, I noticed one thing needed
that's a prerequisite for being able to use the GtkItemFactory class,
namely having the GtkType objects associated with classes in a convenient
form for use in Java. (GtkItemFactory requires a GtkType parameter in its
constructor. GtkType is an integral type, and sort of like an enumerated
type except that the enumeration is built on the fly as GTK classes get
initialized.)
As I dug into the problem, I realized it was actually a more general
issue. Here's what I ended up doing:
* I defined GtkType as an enumerated type with an empty body. I suspect that
the pre-existing GtkFundamentalType definitions may want to be moved
here, but I'm not sure about that.
* It seems that all Gnome/GTK classes have a "static method" called
"<classname>_get_type" but that the gtk.defs file didn't list any of
them. So, I added them. (I skipped a couple of the ones in gnome.defs
because of weird capitalization and insufficient cues as to the right
name--I was feeling too burnt to grovel over all the header files at that
point.)
* Tricky part #1: The approximately-standard Java way to have a type object
associated with a class is by having a static variable named TYPE (e.g.,
java.lang.Integer.TYPE et al). I figured this was the way to go, so I
added code to MethodDefinition to produce these. What it does is look to
see if it's about to spit out the method "static GtkType getType ()" and,
if so, also spits out the declaration "static final GtkType TYPE =
getType ()". This is the moral equivalent of the CLASS_NAME_GET_TYPE
macros in the gtk headers.
* Tricky part #2: There was a mismatch in the code generation between C and
Java when the return type of a method is an enumerated type. In
particular, the C side returns a raw int value (the value of the enum),
but the Java side expects an object. This caused a segfault for any such
method (such as, in particular, all those getType()s I just added, but
I also noticed a bunch of other places where this problem happened). Since
getting the right object from the C side is a bit of a monumental task
involving lots of inefficient JNI calls, I decided to make the Java side
bend backwards a bit:
* The native methods that return an enumerated type are now declared from
Java to return an int. Problem solved as far as crashing the VM is
concerned. However, that meant that Java code had to just take int
return values, and that's not very nice in terms of type safety, so...
* Every method that has this form is now split in two: There is a Java
method, with the original name (e.g., getCellType) that is declared
to return an object of the appropriate type (e.g., GtkCellType).
Then, there is a native method with the original name plus a final
digit "0" (e.g., getCellType0, patterned after how Sun writes their
code, to wit, "javap -p java.lang.ClassLoader"). The Java method
simply wraps the native return value.
* Since (1) I don't want to spew new wrapped values like there's no
tomorrow, and (2) since this wrapping has to happen all over the place,
I added a new standard method to all enumerated types, "intern()",
patterned after String.intern(). static <EnumType>.intern takes an
int and returns the canonical instance of that class for the given
value.
The native wrapper methods end up looking like this:
public EnumType methodName (Type arg, ...)
{
return EnumType.intern (methodName0 (arg, ...));
}
Classes for enumerated types end up looking like this (actual output!):
final public class GtkCellType
{
int value_;
public GtkCellType(int value) {
value_ = value;
}
public int getValue() {
return value_;
}
public int hashCode() {
return value_;
}
public boolean equals(Object other) {
return (other instanceof GtkCellType)
&& (((GtkCellType) other).value_ == value_);
}
static private HashMap theInternedExtras = null;
static private GtkCellType theSacrificialOne = new GtkCellType(0);
static public synchronized GtkCellType intern(int value) {
if (value < theInterned.length)
return theInterned[value];
theSacrificialOne.value_ = value;
if (theInternedExtras == null)
theInternedExtras = new HashMap();
GtkCellType already = (GtkCellType) theInternedExtras.get(theSacrificialOne);
if (already == null) {
already = new GtkCellType(value);
theInternedExtras.put(already, already);
}
return already;
}
static private GtkCellType[] theInterned = {
new GtkCellType(0),
new GtkCellType(1),
new GtkCellType(2),
new GtkCellType(3),
new GtkCellType(4)
};
public final static int _GTK_CELL_EMPTY = 0;
public final static GtkCellType GTK_CELL_EMPTY = theInterned[0];
public final static int _GTK_CELL_TEXT = 1;
public final static GtkCellType GTK_CELL_TEXT = theInterned[1];
public final static int _GTK_CELL_PIXMAP = 2;
public final static GtkCellType GTK_CELL_PIXMAP = theInterned[2];
public final static int _GTK_CELL_PIXTEXT = 3;
public final static GtkCellType GTK_CELL_PIXTEXT = theInterned[3];
public final static int _GTK_CELL_WIDGET = 4;
public final static GtkCellType GTK_CELL_WIDGET = theInterned[4];
}
The dynamic stuff (adding to a HashMap) is to account for classes like
GtkType, whose elements aren't all statically known, and for flag types
(the generated code looks a little different as it's sparsely populated).
If it weren't for that, it could just be simple array lookup. I added
equals() and hashCode() so that the HashMap would work. I added getValue()
because it seems like the sort of thing one ought to have in this situation
(that is, for translation in the other direction). Oh, and in case anyone
is worrying about the efficiency of "synchronized", pretty much all of the
production-quality vms do tricks that make it relatively cheap when there's
no contention for a lock.
With all that done, enumerated return values now work as expected. Those
interested in clean OO programming can use the wrapped interfaces, but
those who need that extra ounce of speed can skip the interning and just
use the native methods directly. This seems like a reasonable tradeoff to
me.
I believe I got this all working (things seem to work as well now as
before), but since (1) I don't know whether you guys think this all is good
or not, (2) since one of you might have already had this in the works in
some other way, and (3) since I don't have write access to the CVS
repository anyway, I'm just providing this stuff as a patch. Assuming
sourceforge is willing, I'll post the patch there, but you can also find it
here:
http://condensed.milk.com/~danfuzz/java-gnome/interning-diffs.txt
I've tried to be consistent with the spacing/indentation style I found,
although I probably messed up since the Java style we use at work has the
parameters "open curly on its own line" and "space after method name"
turned on.
|