macdevcenter.com
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Building Cocoa-Java Apps with Eclipse
Pages: 1, 2

Debugging Cocoa-Java Applications with Eclipse

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

  1. 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>
  2. Add a breakpoint to the first line of applicationDidFinishLaunching() in Controller.java.

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

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

  5. Choose Remote Java Application from the list of configurations and click on New.

  6. Enter "TextEdit" (or whatever you like) as the name of the configuration.

  7. 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...' Dialog Screenshot of the "Debug..." dialog
  8. If you'd like to have easy access to this debugger configuration, switch to the Common tab and check the option "Display in favorites menu".

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

  1. All of the original entries should be copied exactly.

  2. If the VMOptions entry does not already exist, then add it.

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

  1. 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 the xsl:apply-templates.

  2. 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. This dict element ultimately holds all of the information in the Info.plist.

    The template above copies dict elements (match="dict") in the same manner as the first template. However, it also checks to see if the current node is the root dict element (if test="generate-id() = $firstDictID). If it is the root dict element, a check is made to see if there are any <key> elements whose value is VMOptions ( 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.

  3. 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 of VMOptions (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 -Xdebug option is already present(and false() = contains(., '-Xdebug' )). If -Xdebug wasn'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 Debug Icon in the Toolbar 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.