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