MacDevCenter    
 Published on MacDevCenter (http://www.macdevcenter.com/)
 See this if you're having trouble printing code examples


AppleScripting Mac OS X Controlling Your Mac with AppleScript and Java

by Scott D.W. Rankin
02/25/2003

Editor's note -- Even though there isn't a lot of formal support for Java in AppleScript, you can execute AppleScripts from Java. Why would you want to do this? Because by combining these technologies, you can easily interact with and control your Mac from remote locations. What follows is a look at how to set that up.

With the introduction of Mac OS X, Apple has made the Java language a first-class citizen in the Mac development world. The Cocoa framework ships by default with bindings for both Objective-C (a very well-designed language, started in the NeXT world) and Java. Developers who want to write a native OS X application can use either or both, for that matter, and they will look and feel the exact same way.

Apple has given Java developers total access to the Cocoa libraries and frameworks. This means that Java developers are no longer limited to the Sun-specified APIs for interacting with the operating system; Apple has opened up a whole new world of functionality to us.

Another incredibly powerful tool in the Mac OS X arsenal is AppleScript. Virtually every Apple-produced application, including the Finder, is controllable through AppleScript. iTunes, iPhoto, Mail: they're all scriptable. But AppleScript, while a fantastic language for running scripts locally, has minimal support for doing anything else, like sockets or serving web pages. This is where Cocoa and Java come in.

Cocoa has a class, NSAppleScript, which allows a Cocoa application to interact with scriptable applications. So now we can combine the power of Java with the power of AppleScript to make a remote control center for any AppleScriptable application on your computer. In this article, we'll examine the Cocoa classes that deal with AppleScript, and then we'll create a simple web-based application that enables you to control iTunes from any computer on your network.

NSAppleScript -- Executing a Script from Java

There are two primary Cocoa/Java classes that we will be using when interacting with AppleScripts: NSAppleScript and NSAppleEventDescriptor. For dealing with AppleScripts that send only, NSAppleScript is all you need. Using NSAppleScript is extremely simple: in its constructor, it takes a String containing the text of the AppleScript that you wish to execute. You then can call the execute() method and the AppleScript gets executed.

AppleScript in a Nutshell

Related Reading

AppleScript in a Nutshell
By Bruce W. Perry


Read Online--Safari
Search this book on Safari:
 

Code Fragments only

Before executing a script in Java, it's often helpful to try out the script in ScriptEditor first. The ScriptEditor application is found in /Applications/AppleScript/ScriptEditor; launch it and you will get a window that looks like this:

Screen shot.
Apple's ScriptEditor application.

You can type the AppleScript in the top and any results will come back at the bottom. Here's a simple AppleScript that will make a new folder on your desktop:

tell application "Finder" 
    make new folder at desktop 
end tell

Type this into the ScriptEditor, and press Run. You should see a new folder named "untitled folder" appear on your desktop:

Screen shot.
The result of the first script: a new folder.

Now let's see how to do this in Java. The first thing we want to do is create an empty Java class as a framework. Open up your favorite text editor. If you use Apple's Text Edit, be sure to go to the Format menu and choose "Make Plain Text" or it will not let you save it as a .java file. Here is the framework of the Java class:

import com.apple.cocoa.application.NSApplication;
import com.apple.cocoa.foundation.*;

public class AppleScriptTest
{
     public static void main(String[] args)
     {
         // Our code will go here
         System.out.println("Yay AppleScript!");
     }
}

Copy or type this in to your file, and save it as AppleScriptTest.java. Next we need to compile and run our framework. Do this by opening up Terminal (if you're using Terminal to edit the file, press Command-N to open up a new terminal window) and changing to the directory where you have AppleScriptTest.java. Type the following command at the prompt (all on one line):

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

This will compile your class. To run it, type the following:

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

It should output "Yay AppleScript!".

Screen shot.
This is what you should see in the Terminal window

Now we need to add the code to execute the AppleScript that we wrote above. When writing AppleScripts in Java, you should keep in mind that AppleScript needs the quotes and line breaks to understand the script. So we must use Java's escape characters for quotes (\") and newlines (\n) to make sure the script goes through properly. Here is the code to insert that will run a simple AppleScript:

// This line of code is necessary because
// of a change introduced with QuickTime 6.3
// This line loads the Cocoa libraries.
NSApplication.sharedApplication();

// This is the text of the AppleScript
String script = "tell application \"Finder\" \n"
   + " make new folder at desktop \n"
   + "end tell";

// This creates a new NSAppleScript
// object to execute the script
NSAppleScript myScript =
     new NSAppleScript(script);

// This dictionary holds any errors that are
// encountered during script execution
NSMutableDictionary errors =
     new NSMutableDictionary();

// Execute the script!
myScript.execute(errors);

You should compile and run your script again using the javac and java commands from above. Not only should you see "Yay AppleScript" in the Terminal, but if you look at your Desktop, you should see another new folder, too.

Screen shot.
The script has run, and created a second new folder, this time from Java.

Next we turn to reading AppleScript replies.

NSAppleEventDescriptor: Listening to Replies

In the code that we just wrote, we executed a script that performed an action. The call to execute() actually did return a reply, but we ignored it, because it was not important to our script.

Now we want to be able to execute scripts and do things with the output. The easiest replies to deal with are the ones that are just one single string. For example, if you type this script into ScriptEditor, and run it, you will get the name of the first disk on your desktop:

tell application "Finder"
    get the name of the first item in the desktop
end tell

Mine returns "Greater Scott".

Screen shot.
The script reads the name of the first item on your desktop with a reply.

Now, let's do this in Java. We can use the same file, with a few tweaks. Change the script string to be

String script = "tell application \"Finder\" \n"
    + " get the name of the first item "
    + " in the desktop \n" + "end tell";

In order to get the reply back, we need to assign a variable to the value that execute() returns. Make the line with execute() read as follows:

NSAppleEventDescriptor results = myScript.execute(errors);

This will capture the AppleScript result, and allow us to access it via Java. Add these lines after the call to execute():

String resultString = results.stringValue();
System.out.println("The first item is:"
                   + resultString);

Compile and run your application, and you should see it tell you the name of the first item on your desktop. Congratulations! Your Mac is talking, and you are listening!

Multiple Replies

Right now we can ask AppleScript to return one value, and we can read that value. But that's pretty limiting. AppleScript has the concept of lists, so it stands to reason that we would want to be able to get back a list of things from AppleScript. Continuing on with the Finder interaction, we will now get a list of every file that sits on your desktop.

In ScriptEditor, write the following script:

tell application "Finder"
    get the name of every item in the desktop
end tell

This should return you a list of everything (disks, files, folders, etc.) on your desktop. Now let's see how to parse this reply using Java.

The NSAppleEventDescriptor is a catch-all class that represents how AppleEvents are represented internally. They can contain a multitude of different types: enumerations, lists, strings, numbers, you name it. There are several methods in the NSAppleEventDescriptor class that allow you to determine the type of descriptor; for now, we're going to just assume that it's either a list or a string.

Edit your AppleScriptTest.java file, and change the script variable to be the script from above:

String script = "tell application \"Finder\" \n"
    + " get the name of every item "
    + " in the desktop \n" + "end tell";

Edit the lines after the execute() method. We are no longer interested in the string value of the descriptor. Instead we want to get the sub-descriptors. These are accessible using the method descriptorAtIndex(). You can tell the number of sub-descriptors using the numberOfItems() method. Note that descriptors are indexed starting at index 1, not at 0 as Java programmers are used to. Using these two methods, we can iterate over the subdescriptors and print out the list of everything on the desktop. Your entire main method should now look like this:

S// This line of code is necessary because
// of a change introduced with QuickTime 6.3
// This line loads the Cocoa libraries.
NSApplication.sharedApplication();

String script = "tell application \"Finder\" \n"
     + " get the name of every item "
     + " in the desktop \n" + "end tell";

// This creates a new NSAppleScript object
// to execute the script
NSAppleScript myScript =
     new NSAppleScript(script);

// This dictionary holds any errors
// that are encountered during script execution
NSMutableDictionary errors =
     new NSMutableDictionary();

// Execute the script!
NSAppleEventDescriptor results =
     myScript.execute(errors);

// Print out everything on your desktop
System.out.println("Starting list of items "
                    + "on the desktop: ");

int numberOfDesktopItems = results.numberOfItems();
for(int i = 1; i <= numberOfDesktopItems; i++)
{
     NSAppleEventDescriptor subDescriptor =
         results.descriptorAtIndex(i);
     System.out.println("    " +
                       subDescriptor.stringValue());
}

System.out.println("Yay AppleScript!");

On my desktop, here are the results:

Screen shot.
The program lists all the items on my desktop.

As you can see, for scripts that return a list of items, those items are contained as subdescriptors underneath the result descriptor. Depending on the complexity of the script, descriptors could be nested many levels deep.

Now you should be able to see some of the potential of combining AppleScript and Java. AppleScript gives you the fantastic power to interact with your applications in a way that is unmaginable when using plain Java, and Java gives you all the power of an enterprise-quality, full-fledged programming language.

Used together, you can set up your Mac as a sort of command center, and use Java to access it from anywhere. Think about editing your iCal calendars from your office, or controlling iTunes from another room in your house. Almost anything that you can do on your Mac, now you can do from somewhere else.

Scott D.W. Rankin is a Senior Software Engineer with Back Bay Technologies and a die-hard Mac fan(atic).


Read more AppleScripting Mac OS X columns.

Return to the Mac DevCenter.

Copyright © 2009 O'Reilly Media, Inc.