Building Cocoa-Java Apps with Eclipse
Pages: 1, 2
Eclipse provides an excellent Java debugger with features that Xcode can only dream of at the moment. Luckily, it only requires a couple of VMOptions in the Info.plist to make use of this debugger.
The VMOptions we need to provide are the same options that appear in the debugger console when the Xcode debugger is first launched.
-Xdebug -Xnoagent -Xrunjdwp:transport=dt_socket,server=y,address=8000
These options tell the VM running the Cocoa-Java app to wait for a debugger on a particular socket. Once the VM is waiting, we just have to tell the Eclipse bugger to go talk to that socket.
Getting Started
Add the VMOptions to the Info.plist file. *4
<key>Java</key> <dict> <key>VMOptions</key> <string> -Xdebug -Xnoagent -Xrunjdwp:transport=dt_socket,server=y address=8000 </string> </dict>Add a breakpoint to the first line of
applicationDidFinishLaunching()in Controller.java.Perform a clean build and then run the application. If the options have been added correctly, the app will just bounce in the dock indefinitely as it waits for the debugger.
From the main menu bar in Eclipse, choose "Run -> Debug...". This will bring up a dialog allowing you to generate and save a debug configuration.
Choose Remote Java Application from the list of configurations and click on New.
Enter "TextEdit" (or whatever you like) as the name of the configuration.
In the Connect options tab, enter "TextEdit" as the project. The defaults for Host and Port should be fine at localhost and 8000.
Screenshot of the "Debug..." dialogIf you'd like to have easy access to this debugger configuration, switch to the Common tab and check the option "Display in favorites menu".
Finally, click on the Debug button. The app should stop bouncing in the dock, Eclipse should switch to the Java perspective, and the whole thing should come to halt at the breakpoint that we set earlier in Controller.java.
Automating Build-And-Debug
Obviously, it wouldn't be much fun going through all of those steps every time we wanted to debug our application. Let's extend the build file to automatically add the VMOptions for us.
The Info.plist file is simply an XML file; it's not particularly well designed, but it's still an XML file. The easiest way to manipulate XML is with XSLT--a language specifically designed for this purpose. Don't worry if you are not familiar with XSLT; there's plenty of help freely available on the internet and we'll keep the script relatively simple.
First, we'll add a couple of new targets to the build file.
<target name="buildAndDebug"
depends="build, addDebugInfo, run"
description="--> Build a debugable version">
</target>
This is the main public target that will be used to run a debug-friendly version of TextEdit. It builds upon the results of the normal build target, but goes a little further by invoking the two new targets, addDebugInfo and run.
<target name="addDebugInfo" >
<xslt style="addDebugInfo.xsl"
in="Info.plist"
out="${app.info.plist}" />
</target>
This is the target that gets most of the work done. It passes the original clean Info.plist file through an XSLT script called addDebugInfo.xsl, which is stored in the root directory of the project. The output of the XSLT script overwrites the Info.plist file inside of the application bundle.
<target name="run" >
<exec executable="open">
<arg value="-a"/>
<arg value="${app.path}"/>
</exec>
</target>
Finally, the run target. This simple target uses the command-line tool open to launch the application that we've just built.
That's the build file complete. Now let's take a look at the XSLT script. First, some background on XSL. XSL stands for Extensible Stylesheet Language. It is made up of XSLT, which performs the transformation of an XML document to another format; XPath, for referencing sections of an XML document; and XSL-FO for describing display information. XSL, unlike C or Java, is not a procedural language. An XSLT script consists of a series of rules defining how particular patterns should be dealt with. When the script is invoked, the XML tree of the document being transformed is traversed and the most appropriate rule is applied to each node of the tree.
For the purpose of adding debug options to the Info.plist file, we want to perform the following rules. It's worth noting that these rules do not cover every possible situation, but they do provide a good starting point.
All of the original entries should be copied exactly.
If the VMOptions entry does not already exist, then add it.
Existing VMOption entries should be preserved, and debug options added if not already present.
The completed XSL script can be found here and what follows is a description of the templates that directly perform the rules described above.
All of the original entries should be copied exactly.
<xsl:template match="node()"> <xsl:copy> <xsl:copy-of select="@*" /> <xsl:apply-templates /> </xsl:copy> </xsl:template>This is a common operation in XSL scripts. The template (or rule) above can be applied to any node (
match=node()) that doesn't have a more specific rule defined for it. The template makes a copy of the current node (xsl:copy) along with all of its attributes (xsl:copy-of select="@*"). The flow of the script continues on to process each of its children via thexsl:apply-templates.If the VMOptions entry does not already exist, then add it.
<xsl:template match="dict"> <xsl:copy> <xsl:copy-of select="@*" /> <xsl:if test = "generate-id() = $firstDictID and false() = (//key='VMOptions')"> <key>Java</key> <dict> <key>VMOptions</key> <string><xsl:value-of select="$debugCommand"/></string> </dict> </xsl:if> <xsl:apply-templates/> </xsl:copy> </xsl:template>Each Info.plist file contains a
<dict>element as a child of the root<plist>element. Thisdictelement ultimately holds all of the information in the Info.plist.The template above copies
dictelements (match="dict") in the same manner as the first template. However, it also checks to see if the current node is the rootdictelement (if test="generate-id() = $firstDictID). If it is the rootdictelement, a check is made to see if there are any<key>elements whose value isVMOptions(and false() = (//key='VMOptions')"). If no VMOption can be found, then the block of code that we previously added by hand (<key>Java</key>...) is inserted into the Info.plist file.Existing VMOption entries should be preserved, and debug options added if not already present.
<xsl:template match="string"> <xsl:copy> <xsl:value-of select="."/> <xsl:if test="preceding-sibling::*[1] = 'VMOptions' and false = contains(., '-Xdebug' )"> <xsl:text> </xsl:text> <xsl:value-of select="$debugCommand"/> </xsl:if> </xsl:copy> </xsl:template>The value for VMOptions is stored in a
<string>. For normal string elements, this final template copies the string (xsl:copy) and its contents (xsl:value-of select=".").
If the preceding key element had a value ofVMOptions(if test="preceding-sibling::*[1] = 'VMOptions'), then this node is the string node associated with a VMOptions key--so try to add the debugger options. However, the debugger options only need to be added if they are not already present, so first a quick check to see if the-Xdebugoption is already present(and false() = contains(., '-Xdebug' )). If-Xdebugwasn't found, then the debugger options are appended to the existing options (xsl:value-of select="$debugCommand"), separated by a single space (<xsl:text> </xsl:text>).
And that's it! After the XSL script has been downloaded, and the new build targets added to build.xml, perform a clean before running the buildAndDebug target. As before, the TextEdit icon should just bounce in the dock as it waits for the debugger to connect. Now, as TextEdit was the last thing to be debugged, you can simply click the Debug icon
in the main toolbar to re-run the TextEdit Remote-Java Debugger configuration.
Final Thoughts
To sum up, we've covered building a Cocoa-Java app in Eclipse using Ant. Once the build file is in place, it takes little or no maintenance effort and can be used to build other Cocoa-Java apps just by editing the properties file.
We've also covered how to debug a Cocoa-Java application using Eclipse's excellent Java debugger. Again, the XSL script is not tied to the sample project and can be used to work with most Cocoa-Java apps.
If you haven't tried Eclipse before, you'll be amazed at how enjoyable it is. Try it, and you'll never want to go back!
Notes
*1: Actually, it's much more than just an IDE, but that's beyond the scope of this article.
*2: If you build the project without
the Java files and try to use the Info.plist generated, now
it won't be as useful. As soon as you remove all of the
Java files, Xcode will mark the Info.plist as not needing
Java. You can add the missing fields by hand or by setting
the Needs Java, Root Directory, and Path options of
the Cocoa Java Specific section of the target.
*3: The property file should simply be stored in the same folder as the build file.
*4 : If you are using an intelligent XML editor to edit the Info.plist file, it is imperative that the debug options do not get automatically wrapped onto a second line, as they may not be interpreted correctly.
Mike Butler worked for 7 years as a software developer with Apple and has just recently launched a Mac Localisation Tools Development company called TripleSpin.
Return to MacDevCenter.com.
You must be logged in to the O'Reilly Network to post a talkback.
Showing messages 1 through 13 of 13.
-
Having a problem setting up Debugging
2006-07-17 19:55:16 meclyma [Reply | View]
Mike,
I have enjoyed your article.
I have run into a problem when I started to implement the Debugging section of the article. Under the headings of page 2: "Debugging Cocoa-Java Applications with Eclipse" and then the heading: "Getting Started". Item #2 "Add a breakpoint to the first line of applicationDidFinishLaunching() in Controller.java." Where can "Controller.java" be found? It must be a file that can be viewed in Eclipse because you need to set a breakpoint. But I can't find it. Do you have any helpful suggestions?
Thanks.
Mark -
Having a problem setting up Debugging
2006-07-19 13:20:17 mikebutler1 [Reply | View]
Hi Mark,
Glad you liked the article - hopefully we can sort out what's going on...
If you follow the link from the first part of the article you will find the TextEdit sample project. Controller.java is simply one of the files used in that project. (file:///Developer/Examples/Java/AppKit/TextEdit/Controller.java
)
When you follow the steps outlined in the "Getting Started with Eclipse" section, you should see all of the java files in the eclipse Navigator panel ( or the Package Explorer if you prefer it :-) So you should be able to see Controller.java there.
If it's not showing up, make sure it's actually there on disk in the first place and hasn't been accidentally deleted at some point!
Let me know how it goes for you,
Mike. -
Having a problem setting up Debugging
2006-07-20 19:52:42 meclyma [Reply | View]
Mike,
I found the original Controller.java file. I copied the code into a new file under the TextEdit folder in Eclipse (it was placed under a default package). Then I received several errors in Controller.java. Here are a few of the lines with errors:
- Under applicationShouldTerminate method:
Document document = Document.documentForWindow(window);
The error is on "Document"
Preferences.saveDefaults();
The error is on "Preferences"
- Under applicationOpenFile method:
return Document.openDocumentWithURL(NSPathUtilities.URLWithPath(filename),
The error is on "Document" (there are other errors on this line that I won't list here).
There are more errors in Controller.java that I will not list here because they are similar errors. I am not sure if all of these errors are part of JAVA classes or Cocoa classes (I suspect Cocoa classes). Do I need to add more "import"'s to Controller.java? Or do I need to tell Eclipse where to find the Cocoa library? If the latter, is this because I created a new Java file and copied the code into a new file that Eclipse is confused on (and obviously this developer too)? In your article you showed us how to tell Eclipse where to find the Cocoa classes by creating a- new
BTW, I forgot to mention that I am using: Eclipse 3.2.0, MacOS X 10.4.6, Xcode 2.3, and JAVA 1.5.
Thanks.
Mark -
Having a problem setting up Debugging
2006-07-21 02:40:02 mikebutler1 [Reply | View]
Hi Mark,
It looks like you're nearly there ( but why did you have to copy the code from Controller.java? Why not just use Controller.java? ). It just looks like you haven't told Eclipse where to find all of the Cocoa classes.
If you follow steps 5 and 6 of "Tell Eclipse where to find the Cocoa classes" on page one, then it should sort it out for you. Those steps describe how to get Eclipse to add an external class folder - in this case /System/Library/Java where all of the Cocoa classes are stored.
Hope that helps,
Mike.
-
Small Issues
2005-05-30 17:44:46 johndwill [Reply | View]
Good article, but I ran into a couple problems that I thought I would mention.
After moving all the resource files into the Resources directory I had to go back into the xcode project and update all the references to the resource files. Without doing that the xcode project wouldn't compile.
Also, the properties file seems to be missing the definition for "app.resources.dir" which is referenced in the ant file.
Once I dealt with those everything built.
-
Unclear from the beginning
2005-04-28 07:50:29 davidvoo [Reply | View]
Hi there,
I assumed this article cover XCode 1.5. At first, after build the project, I assumed the said "build" directory (build/TextEdit.app/Contents/Info.plist) refers to Targets > TextEdit > Bundle Resources > InfoPlist.strings?
Thanks
davidvoo -
Sorry, my apology
2005-04-28 08:44:16 davidvoo [Reply | View]
Hi there,
Eventually I am mistaken the location of the file.
Now when I tried to run Debug as shown to catch the breakpoint, I hit error of:
Failed to connect to remote VM, connection refused.
Whats wrong?
-
Unclear from the beginning
2005-04-28 08:31:37 mikebutler1 [Reply | View]
Hi david,
It refers to the actual Info.plist file which is inside the Contents folder of the newly built application.
"Instead of writing an Info.plist by hand, simply build the project and take the Info.plist from the newly produced app in the build folder (build/TextEdit.app/Contents/Info.plist)"
This simply means, build TextEdit with XCode. Then using the Finder, go inside the Contents folder of the newly built application bundle. There you will find a completed Info.plist which you can take a copy of - rather than trying to write it by hand.
-
Great article
2005-04-23 03:19:57 helgegrimm [Reply | View]
One of the best articles that I've read on Macdevcenter. I would never have tried that myself. Thanks! -
Great article
2005-04-23 21:01:26 bfancher [Reply | View]
Eclipse is a great tool, but the real question isn't how do I use it for writing Cocoa-Java apps, but why on earth anyone would use Java for writing a Cocoa application, when Cocoa's native language of Objective-C is vastly superior? Is it because you want to your program to run slower (thanks to the Objective-C to Java bridge)? Maybe you want it to be harder to read (because of Java's idiotic unnamed method arguments)? Or is it that you just like having typecasts littered all through your code like rodent turds?
-
Great article
2005-04-24 09:30:43 ebelin [Reply | View]
Sounds like a die-hard bigotry to me. One might want to use Java to write any ( including Cocoa ) app becuase of the tools available. I've seen a lot and I have to say that Eclipse ( along with IDEaJ, from where Eclipse "borrows" many features ) has simply the best ( overall in industry ):
1. debugger
2. code assist
3. code navigation and search
4. code refactoring
5. interface to CVS ( via SSH )
6. Java has obligotary exception handling ( missed exceptions are caught at compile time )
7. Background compilation
8. Many other things that take mundane parts out of everyday programming away.
9. Oh, and hey, real grabage collection, for a change
This is a few why's. Now, you maybe a super-programmer and do not use any of those things, but some of us are "normal" people and every once in a while ( heck, every day, actually ) use all of those features.
I feel sorry for people like yourself. Languages do not matter anymore - libraries do. It takes about 2 hours to grasp Objective-C ( language ) and even write leak-free code. It takes much longer to learn the guts of Cocoa libraries and how to use them.
Not to say that Java is perfect though. Microsoft realized the shortcomings of Java and created JavaNT *cough*.NET*cough*. At the moment they are lacking in the tool department too. As soon as they catch up - we will have even more choice.
Remember, no tool ( language ) is perfect for all jobs. And every tool ( language ) is perfect for some job. I am not going to participate in this discussion. I have figured most things for myself, so this is just a note to folks who might be misguided by the "shored" commentors.
Interesting article, by the way. For one, I would have never tried it myself. It is a pity that Apple does not embrace Eclipse as the "IDE of choice for MacOSX" and does not adopt Objective-C to be well supported in that great IDE. If they did, they would have passed Microsoft in developer convenince for a couple of years, which is quite a lot... -
Great article
2005-04-24 13:19:12 otto [Reply | View]
It is a pity that Apple does not embrace Eclipse as the "IDE of choice for MacOSX"
I agree. The first step would have to be improving Eclipse's performance on OS X.
and does not adopt Objective-C to be well supported in that great IDE
Well, as much as Xcode is a Obj-C IDE at heart, Eclipse is a Java IDE through and through.
I'd love to have a IDE that handles all languages well, but it sounds too much like a silver bullet to be true.






I'm an old timer around Unix and new to OS X and Java programming. When I follow your steps I think I lose continuity at some points.
Get the Xcode Side...
1. OK
2. OK
3. OK (I match the graphic)
4. OK
5. In which folder do I create Resources and aren't you asking me to move some of the files I just deleted into this new folder?
Getting Started...
1. OK
2. OK
3. Is this the same or another TextEdit folder? I deleted all the .java files in 3 (above) and now you say Eclipse sees them.
Sorry if I am missing something obvious that is implied. Are we dealing with two folders, an original and a copy, or just one.
Thanks!