TweetFollow Us on Twitter

Combining Two Faces OF OS X: AppleScript and Java

Volume Number: 21 (2005)
Issue Number: 4
Column Tag: Programming

Combining Two Faces OF OS X: AppleScript and Java

by David Miller

Bridging the Gap

Roll Your Own Solution

Welcome

With the announcement and previews of the new version of Apple's operating system, Tiger, Mac users are starting to get excited about its new features. End users are looking forward to the conveniences brought on by features such as Spotlight and Dashboard, while developers are salivating over frameworks bundled with Tiger such as Core Data and Core Image. And while each new version of OS X has introduced enhancements that are compelling enough to convince users and developers to upgrade, a few ragged edges have also found their way into the foundation.

Two of OS X's highly-touted features are its excellent Java support, and the ease with which automated workflows are created via AppleScript. This article will focus on one of the ragged edges between these two components.

Part 1: AppleScript

AppleScript has been a part of the Mac OS since System 7, and not surprisingly, has had its share of ups and downs since its debut; the transition from OS 9 to OS X was particularly rough on it. Scripts running in the early versions of OS X often had to interact with applications nestled within both the Aqua and the "Classic" emulation layers; while this solution was not a huge obstacle for scripters, it was by no means an elegant solution.

To make matters worse, early versions of Carbon and Cocoa applications often included shoddy AppleScript support due to the amount of effort required to make the jump from OS 9 to OS X. In fact, this situation is just as relevant as it ever was, as developers are understandably more concerned about getting their applications to take advantage of OS X's newest features than to prolong the development cycle to include a "bonus" such as AppleScript support. Frameworks such as Rendezvous, WebKit, and Cocoa Bindings have allowed developers to provide more bang for their applications' buck, yet AppleScript often seems to be left behind. iTunes was an excellent example of this situation, as it did not receive substantial AppleScript support until version 3, (and many of Apple's staple applications such as Safari still don't play nicely with AppleScript).

Yet, ironically enough, OS X has become a bigger and better playground for scripters with each successive release:

  • the Script Menu item has effectively replaced the Script Runner application, and has brought with it the ability to execute scripts written in a variety of flavors besides AppleScript (Perl, Python, and a variety of shell flavors: bash, zsh, tcsh, etc.);
  • the addition of AppleScript Studio to OS X's developer tools has allowed scripters to create full-fledged Cocoa applications with AppleScript and Aqua's native widget set and, in doing so, introduced improvements to the language and AppleEvent model;
  • And Tiger will introduce Automator, an application and framework that will make scripting almost as simple as point-and-click.

AppleScript Studio, as mentioned above, was built upon an Objective-C--AppleScript bridge that allows scripts access to the methods and properties of Cocoa objects. However, this wasn't the first instance of Cocoa applications being written in a language other than Objective-C.

Part 2: Java

One of OS X's major selling points is its tight coupling with Java; heavy-weight tools such as the JBoss J2EE application server and Apache Tomcat can be installed and running on a Mac with a minimal amount of configuration. However, OS X's Java support doesn't end at the command-line. While Cocoa's "native" language is Objective-C, Cocoa's foundation classes have also been ported to Java which, in theory, gives developers the choice between two object-oriented languages for their Cocoa applications.

The above statement is qualified with "in theory" because while it is entirely possible to build a Cocoa application with Java, it is by no means a common occurrence. Cocoa's ancestor, with which it still shares many traits (including the NS prefix for class names) was created for Objective-C's object model within the NextStep environment, and then retro-fitted onto the Java object model for the release of OS X. Creating the Java-Cocoa classes was a nice gesture on Apple's part, but developers have chosen to forego Java and learn Objective-C for Cocoa hacking for several reasons:

  • The Cocoa Java classes are treated as second-class citizens when compared to their Objective-C counterparts, both in terms of their development and documentation,
  • And building Cocoa applications with Objective-C is much like placing a round peg in its round hole, whereas building them with Java is akin to forcing the round peg into a square hole.

And because the square hole seems to be an afterthought, it often gets overlooked when it comes time for framework releases and updates; it is no secret that the documentation and implementation for the Cocoa-Java classes is lacking; frameworks such as the Address Book have become widely accepted in Objective-C applications yet still have no Java hooks. Take for instance, the NSAppleScript class, which is used in the following (simplified) Java class:

Listing 1: NextTrack.java

NextTrack.java

A simple Java class that makes use of NSFoundation's classes for interacting with AppleScript.

Import com.apple.cocoa.foundation.*;

public class NextTrack {

  public static void main(String[] args) {
    String script = "tell application \"iTunes\"\n"
      + " next track \nend tell";
    NSAppleScript myScript =  new NSAppleScript(script);
    NSMutableDictionary errors= new NSMutableDictionary();
    NSAppleEventDescriptor results =
         myScript.execute(errors);
  }
}

The above code should accomplish the same result when run through OS X's Java interpreter as the following snippet of AppleScript when run in Script Editor:

Listing 2: NextTrack.scpt

NextTrack.scpt()
A simple script that will tell iTunes to start playing the next track.

tell application "iTunes"
   next track
end tell

...and the following Objective-C method when invoked by another class:

Listing 3: NextTrack.m

NextTrack.play()

The Objective-C equivalent to the Java code in Listing 1, and the AppleScript shown in Listing 2:

- (void) play
{
   NSAppleScript *play =
      [[NSAppleScript alloc]
         initWithSource:
   @"tell application \"iTunes\"\n next track\n end tell"];
   [play executeAndReturnError:nil];
}

And not surprisingly, the native AppleScript and Objective-C versions shown in Listings 2 and 3 execute precisely as expected; now let's see how the Java version fairs. Compiling the Java code can be accomplished through the following command in Terminal:

javac -classpath .:/System/Library/Java/
   AppleScriptTest.java

... and subsequently executed with...

java -classpath .:/System/Library/Java/ AppleScriptTest

We would expect that the next song in our iTunes playlist would begin playing after executing the above command. However, once the shell gives control to the Java interpreter to execute our program, things go downhill -- the program hangs as soon as the NSAppleScript's execute method is invoked. And even worse, the AppleScript is never executed, which leaves leaving Java developers who wish to incorporate AppleScript into their programs in a bind.

But all is not lost; Thanks to OS X's command-line interface and its osascript utility, it is possible to make a home-made solution to pass information from AppleScript to Java. Traditionally this would process would be done by using an instance of the NSAppleEventDescription class, yet we have seen that this will not work in Java. Let's see how we can overcome this bug in OS X.

Rolling Your Own Solution

Our homegrown solution has two requirements: (1) that we can execute AppleScripts from within Java's sandbox, and (2) that we are able to send the result of a script back to the sandbox. Luckily, both steps can be taken care of by the osascript command.

Included in OS X since its initial release, the osascript command executes AppleScripts in the same way that scripts from traditional languages (such as Perl, Python, and Bash) are executed: through the shell. In terms of the shell, the main difference between executing shell scripts and AppleScripts is that the former can be made executable through the use of UNIX permissions and the she-bang line (the first line of a script, which indicates which program should be interpret the script), while the osascript program must interpret AppleScripts. However, osascript follows the same rules as every other UNIX utility, namely:

  • The program will return 0 to the shell if the AppleScript was executed successfully, or 1 if the script execution terminated abnormally,
  • And the script's result will be echoed to one of stdout or stderr, depending on the return value mentioned above.

And like most other UNIX commands, all you need to know about osascript can be found out through its man page by typing man osascript. For now, all we need to know is that the command osascript test.appleScript will attempt to execute the file test.appleScript in the exact same way as though the script was executed in one of the "traditional" methods, such as through the Script Menu or executing it from within Script Editor.

The Solution, Part 1: Executing AppleScript From Java

Thus, it is relatively trivial to write a wrapper for the osascript program that will capture the script's result and store it in a String; the code shown in Listing 5 does just that.

Listing 5: AppleScript.java

AppleScript.run()

We can use the osascript command to run an AppleScript file via the shell. The output of the script will be echoed to standard out if successful, or sent to standard error if not. Either way, it can be trapped by using an InputStream and then inspected as required.

public String run(File script) throws AppleScriptException,
   IOException,
   FileNotFoundException,
   java.lang.InterruptedException {

ArrayList cmd = new ArrayList(); 
cmd.add("/usr/bin/osascript"); 

// add necessary command-line switches here...

// create an array to store the parameters
cmd.add(script.getPath());
String[] cmdArray = (String[]) cmd.toArray(new String[0]);

// run the script
Process result = Runtime.getRuntime().exec(cmdArray);
result.waitFor();

String line;
StringBuffer output = new StringBuffer();

/* if something bad happened while trying to run the script, throw an AppleScriptException 
   letting the user know what the problem was */
if (result.exitValue() != 0) {
   
  // read in the description of the error
   BufferedReader err = new BufferedReader(new
       InputStreamReader(result.getErrorStream()));
   while ((line = err.readLine()) != null) {
       output.append(line + "\n");
   }
   
   // and throw an exception describing what the problem was throw new 
      AppleScriptException(output.toString().trim());
         
// otherwise the script ran successfully
} else {

   /* read in the output */
   BufferedReader out = new BufferedReader(new
       InputStreamReader(result.getInputStream()));
   while ((line = out.readLine()) != null) {
      output.append(line + "\n");
   }
}

The Solution, Part 2: Returning AppleScript Results to Java

Now you may be asking yourself, "What is the result of an AppleScript?" A script's result is defined to be the value of the last statement in the script. For example, the result of the following script will be a reference to the track that is currently playing iTunes:

Listing 4: GetCurrentTrack.scpt

GetCurrentTrack.scpt

A simple AppleScript used to retrieve the current track in iTunes.

tell application "iTunes"
   set mytrack to current track
end tell

If executed through Script Editor, the result of the above script will be an instance of iTunes' Track class, which contains all of the information retained by iTunes (such as its artist, album, rating, etc.) for the track that is currently playing. However, if the above script is executed through the osascript command after saving it to a file, the following result will be echoed back to the Terminal:

Listing 4: Sample Result

The result of converting an AppleScript object to a textual representation.

"class cFlT" id 105

The two listings included above are merely different representations of the same result. Listing 4 is the result of converting an AppleScript object to text for echoing to the shell; and in the process of converting the object all information about the track that we were hoping to use in our Java program is lost. However, there is a way around this problem: by changing our script so that its result can be parsed as text, we can then capture that information without changing our Java code.

For example, we can retrieve the information for the currently playing track in iTunes and echo it to the shell by passing the following file to osascript :

Listing 5:GetCurrentTrack.scpt

GetCurrentTrack.txt

The following script will result in the information for the current track being echoed out to standard out with the fields delimited by a tab character (\t).

set tab to "\t"
tell application "iTunes"
   try
      set r to current track
      set myvalue to (album of r & tab & artist of r & tab & bit rate of r & 
         tab & comment of r & tab & compilation of r & tab & composer of r & tab & 
         database ID of r & tab & (date added of r as string) & tab & disc count 
         of r & tab & disc number of r & tab & duration of r & tab & enabled of r 
         & tab & (EQ of r as string) & tab & (genre of r as string) & tab & (kind of 
         r as string) & tab & (modification date of r as string) & tab & played 
         count of r & tab & (played date of r as string) & tab & rating of r & tab & 
         sample rate of r & tab & size of r & tab & track count of r & tab & track 
         number of r & tab & year of r & tab & name of r)
   on error
      (* do nothing *)
   end try
end tell

As can be seen from the listing, the script will return the fields of the current track in a tab-delimited format. The Java wrapper method will store this information as a String, which can then be parsed to extract the relevant fields of the Track that we are looking for. The following code illustrates how to do exactly that:

Listing 6: CurrentTrack.java

CurrentTrack.getCurrentTrack()

The following method will return an object containing all of the information for the track that is currently playing in iTunes. After the tab-delimited result is stored, it is broken up into its individual fields, which are used to set the fields of the Track instance. The Calendar.parseAppleScriptDate method is used to convert a textual representation of an AppleScript date field to a java.util.Date object; and numbers must be parsed as Integers before setting our track's attributes.

public static Track getCurrentTrack() throws AppleScriptException {

   try {
      
      File script = new File();

      // <snip>
      // store a get a reference to a file containing the
    // AppleScript shown in Listing 5
      // </snip>

      AppleScript as = new AppleScript(script);
      String result = as.run();

      // split the output on the tab character (\t) to 
      // break it into the individual fields
      String[] bits = result.split("\t");
      
      // if we only have one item, then no track was returned
      if (bits.length <= 1) {
         throw new AppleScriptException("No current track");
      }

      // the object used to store all of the info
      Track current = new Track();
      
      // loop through all fields and set the corresponding
      // attributes of the Track object...
      current.setAlbum(bits[0]);
      current.setArtist(bits[1]);
      current.setBitRate(Integer.parseInt(bits[2]));
      current.setComment(bits[3]);
      current.setCompilation(
         new Boolean(bits[4]).booleanValue());
      current.setComposer(bits[5]);
      current.setId(Integer.parseInt(bits[6]));            
      current.setDateAdded(
         Calendar.parseAppleScriptDate(bits[7]));

      // <snip>other modifiers go here</snip>

      current.setTrackCount(Integer.parseInt(bits[21]));
      current.setTrackNumber(Integer.parseInt(bits[22]));
      current.setYear(Integer.parseInt(bits[23]));
      current.setName(bits[24]);

      return current;

   // if the script doesn't execute properly, then throw
   // an exception to propagate the error
   } catch (Exception e) {
      throw new AppleScriptException(e);
   }
}

It should be noted that our solution does require more code than would be required if Apple's NS* Java classes worked as intended. But, oddly enough, the extra code is AppleScript, not Java, due to the process of manually flattening AppleScript objects into a single string rather than using an NSAppleEventDescriptor to contain the object's individual fields. While it is by no means an elegant solution, it gets the job done reliably and proves to be an interesting exercise in making the different faces of OS X talk to each other in ways that weren't originally intended.

Wrapping Up

Mac OS X has proven to be a healthy and productive platform for developers, and given Apple's recent track record there are no signs that this situation will change in the near future. However, with all of the new technical demos shown in Tiger's previews intended for Objective-C, one hopes that Java developers will also have something to play with upon booting up Apple's new operating system. Or, if not something new, then at least a working copy of what was promised years ago when the first version of OS X was being previewed to developers years ago.

About the Code

The class and supporting JAR files can be found on the CD, which also includes an ANT build file to take care of the heavy lifting; see the accompanying documentation for more information on how to build the class. There are several classes used in the above listing that are free to download and use:

  • AppleScript.java and AppleScriptException.java are part of the com.fivevoltlogic.tools.orchard package,
  • And Track.java is part of the com.fivevoltlogic.mytunes package.

Copies are included on the CD and can also be downloaded from http://www.fivevoltlogic.com/code/.


David Miller is a developer based in Calgary, AB, Canada. You can reach him by sending an email to davidfmiller@gmail.com or pointing your browser to http://www.fivevoltlogic.com.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

pwSafe 4.1 - Secure password management...
pwSafe provides simple and secure password management across devices and computers. pwSafe uses iCloud to keep your password databases backed-up and synced between Macs and iOS devices. It is... Read more
Kodi 15.0.rc1 - Powerful media center to...
Kodi (was XBMC) is an award-winning free and open-source (GPL) software media player and entertainment hub that can be installed on Linux, OS X, Windows, iOS, and Android, featuring a 10-foot user... Read more
Coda 2.5.11 - One-window Web development...
Coda is a powerful Web editor that puts everything in one place. An editor. Terminal. CSS. Files. With Coda 2, we went beyond expectations. With loads of new, much-requested features, a few surprises... Read more
Bookends 12.5.7 - Reference management a...
Bookends is a full-featured bibliography/reference and information-management system for students and professionals. Access the power of Bookends directly from Mellel, Nisus Writer Pro, or MS Word (... Read more
Maya 2016 - Professional 3D modeling and...
Maya is an award-winning software and powerful, integrated 3D modeling, animation, visual effects, and rendering solution. Because Maya is based on an open architecture, all your work can be scripted... Read more
RapidWeaver 6.2.3 - Create template-base...
RapidWeaver is a next-generation Web design application to help you easily create professional-looking Web sites in minutes. No knowledge of complex code is required, RapidWeaver will take care of... Read more
MacFamilyTree 7.5.2 - Create and explore...
MacFamilyTree gives genealogy a facelift: it's modern, interactive, incredibly fast, and easy to use. We're convinced that generations of chroniclers would have loved to trade in their genealogy... Read more
Paragraphs 1.0.1 - Writing tool just for...
Paragraphs is an app just for writers. It was built for one thing and one thing only: writing. It gives you everything you need to create brilliant prose and does away with the rest. Everything in... Read more
BlueStacks App Player 0.9.21 - Run Andro...
BlueStacks App Player lets you run your Android apps fast and fullscreen on your Mac. Version 0.9.21: Note: Now requires OS X 10.8 or later running on a 64-bit Intel processor. Initial stable... Read more
Tweetbot 2.0.2 - Popular Twitter client....
Tweetbot is a full-featured OS X Twitter client with a lot of personality. Whether it's the meticulously-crafted interface, sounds and animation, or features like multiple timelines and column views... Read more

Rage of Bahamut is Giving Almost All of...
The App Store isn't what it used to be back in 2012, so it's not unexpected to see some games changing their structures with the times. Now we can add Rage of Bahamut to that list with the recent announcement that the game is severely cutting back... | Read more »
Adventures of Pip (Games)
Adventures of Pip 1.0 Device: iOS iPhone Category: Games Price: $4.99, Version: 1.0 (iTunes) Description: ** ONE WEEK ONLY — 66% OFF! *** “Adventures of Pip is a delightful little platformer full of charm, challenge and impeccable... | Read more »
Divide By Sheep - Tips, Tricks, and Stre...
Who would have thought splitting up sheep could be so involved? Anyone who’s played Divide by Sheep, that’s who! While we’re not about to give you complete solutions to everything (because that’s just cheating), we will happily give you some... | Read more »
NaturalMotion and Zynga Have Started Tea...
An official sequel to 2012's CSR Racing is officially on the way, with Zynga and NaturalMotion releasing a short teaser trailer to get everyone excited. Well, as excited as one can get from a trailer with no gameplay footage, anyway. [Read more] | Read more »
Grab a Friend and Pick up Overkill 3, Be...
Overkill 3 is a pretty enjoyable third-person shooter that was sort of begging for some online multiplayer. Fortunately the begging can stop, because its newest update has added an online co-op mode. [Read more] | Read more »
Scanner Pro's Newest Update Adds Au...
Scanner Pro is one of the most popular document scanning apps on iOS, thanks in no small part to its near-constant updates, I'm sure. Now we're up to update number six, and it adds some pretty handy new features. [Read more] | Read more »
Heroki (Games)
Heroki 1.0 Device: iOS Universal Category: Games Price: $7.99, Version: 1.0 (iTunes) Description: CLEAR THE SKIES FOR A NEW HERO!The peaceful sky village of Levantia is in danger! The dastardly Dr. N. Forchin and his accomplice,... | Read more »
Wars of the Roses (Games)
Wars of the Roses 1.0 Device: iOS Universal Category: Games Price: $4.99, Version: 1.0 (iTunes) Description: | Read more »
TapMon Battle (Games)
TapMon Battle 1.0 Device: iOS Universal Category: Games Price: $.99, Version: 1.0 (iTunes) Description: It's time to battle!Tap! Tap! Tap! Try tap a egg to hatch a Tapmon!Do a battle with another tapmons using your hatched tapmons! *... | Read more »
Alchemic Dungeons (Games)
Alchemic Dungeons 1.0 Device: iOS Universal Category: Games Price: $.99, Version: 1.0 (iTunes) Description: ### Release Event! ### 2.99$->0.99$ for limited time! ### Roguelike Role Playing Game! ### Alchemic Dungeons is roguelike... | Read more »

Price Scanner via MacPrices.net

Canon PIXMA MG3620 Wireless Inkjet All-in-One...
Canon U.S.A., Inc. has announced the PIXMA MG3620 Wireless (1) Inkjet All-in-One (AIO) printer for high-quality photo and document printing. Built with convenience in mind for the everyday home user... Read more
July 4th Holiday Weekend 13-inch MacBook Pro...
Save up to $150 on the purchase of a new 2015 13″ Retina MacBook Pro at the following resellers this weekend. Shipping is free with each model: 2.7GHz/128GB MSRP $1299 2.7GHz/... Read more
27-inch 3.5GHz 5K iMac on sale for $2149, sav...
Best Buy has the 27″ 3.5GHz 5K iMac on sale for $2149.99. Choose free shipping or free local store pickup (if available). Sale price for online orders only, in-store prices may vary. Their price is $... Read more
Apple now offering refurbished 2015 11-inch...
The Apple Store is now offering Apple Certified Refurbished 2015 11″ MacBook Airs as well as 13″ MacBook Airs (the latest models), available for up to $180 off the cost of new models. An Apple one-... Read more
15-inch 2.5GHz Retina MacBook Pro on sale for...
Amazon.com has the 15″ 2.5GHz Retina MacBook Pro on sale for $2274 including free shipping. Their price is $225 off MSRP, and it’s the lowest price available for this model. Read more
Finally Safe To Upgrade To Yosemite’?
The reason I’ve held back from upgrading my MacBook Air from OS X 10.9 Mavericks to 10.10 Yosemite for nearly a year isn’t just procrastination. Among other bugs reported, there have been persistent... Read more
Logo Pop Free Vector Logo Design App For OS X...
128bit Technologies has released of Logo Pop Free 1.2 for Mac OS X, a vector based, full-fledged, logo design app available exclusively on the Mac App Store for the agreeable price of absolutely free... Read more
21-inch 1.4GHz iMac on sale for $999, save $1...
B&H Photo has new 21″ 1.4GHz iMac on sale for $999 including free shipping plus NY sales tax only. Their price is $100 off MSRP. Best Buy has the 21″ 1.4GHz iMac on sale for $999.99 on their... Read more
16GB iPad mini 3 on sale for $339, save $60
B&H Photo has the 16GB iPad mini 3 WiFi on sale for $339 including free shipping plus NY tax only. Their price is $60 off MSRP. Read more
Save up to $40 on iPad Air 2, NY tax only, fr...
B&H Photo has iPad Air 2s on sale for up to $40 off MSRP including free shipping plus NY sales tax only: - 16GB iPad Air 2 WiFi: $489 $10 off - 64GB iPad Air 2 WiFi: $559 $40 off - 128GB iPad Air... Read more

Jobs Board

Global Deployment Project Manager, *Apple*...
…international landscape is paramount to drive innovation, compliance, competition of Apple 's strengths, and talent planning. Manages the process, logistics, and systems Read more
*Apple* MAC Support Services Subject Matter...
Title: Apple MAC Support Services Subject Matter Expert Location: Pleasanton, CA Type of position: Temporary Contract for approximately 6 weeks Tasks The tasks for the Read more
*Apple* MAC Support Administrator - Net2Sour...
…solutions customized to client needs including staffing, training and technology Title Apple MAC Support Administrator Location Belmont, CA Duration 6+ Month Job Read more
*Apple* Certified Mac Technician - Updated 6...
…and friendly, hands-on technical support to customers troubleshooting and repairing Apple /Mac products with courtesy, speed and skill. Use your problem-solving skills Read more
*Apple* MAC Support Services Subject Matter...
…the best talent to create a competitive advantage. Currently, we are seeking an Apple MAC Support Services Subject Matter Expert for a long term contract in Pleasanton, Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.