Sunday, May 08, 2005

Redundant Code
Last night at the reception I was talking to DavidM about my JNI implementation for OSX metadata. David thought that this was unnecessary, as Apple have a full set of Cocoa bindings for Java. If this were true, then the code I wrote would be redundant. While it was great practice, I'd rather that I'd provided something really useful.

BTW, the wedding went really well. No honeymoon yet, but maybe we'll try to organise something later in the year.

Tonight I started looking for documentation on Java-Cocoa, to see if David is right. It turns out that he is (mostly). Damn. :-)

Looking for documentation on Apple's site tells me that Java-Cocoa does exist, but it seemed hard to find. Looking in the Xcode documentation, I found it under:
ADC Home > Reference Library > Documentation > Cocoa > Java
Google searching also finds references to it (often in mailing lists), but typically in disparaging tones. The complaints have been that the code is buggy, and that documentation is poor.

So I went looking for it on the local system. The first place I looked was in /System/Library/Java. The Extensions subdirectory looked promising, but wasn't. However, I soon found all of the "NS" Cocoa classes in com/apple/cocoa/foundation. This included NSMetadataItem, which I thought was the exact equivalent of what I'd just written.

Oh well. I'm still glad for the coding practice. :-}

However, looking at the situation more carefully, it's not so clear cut. It seems that the NSMetadataItem has no constructor that accepts a file. Instead, these objects are instantiated as the result of a metadata query. So from what I can see, the MDItem class from Carbon allows the metadata from a given file to be retrieved, but there is no equivalent functionality in Cocoa. It may exist, but I haven't found it yet.

Once I knew what I was looking for, I started searching for the Java API for this class, but with no luck. Most other classes are available, but none of the metadata classes. Finally, I tried using the javap disassembler on the NSMetadataItem, getting this:

  Compiled from ""
public class extends{
public native java.lang.Object valueForAttribute(java.lang.String);
public native valuesForAttributes(;
public native attributes();
protected, int);
static {};
This confirms that there is no constructor which accepts a file path.

I also noted that these classes return NSDictionary and NSArray objects. This makes the wrapping a little thinner than it need be, as Java code would usually use java.util.Map and Object[] for the same purposes. At least it uses Java strings, rather than NSStringReference.

So I'm thinking I'll keep up with this code for two reasons. The first is the more convenient interfaces from java.util. The second is that having a Carbon wrapper seems to offer a couple of functions that are unavailable under Cocoa (like getting metadata from a specific file).

This extra functionality in Carbon seems to be confirmed by looking at the contents of the "mdls" command (using "nm"). The symbols it uses all indicate that the code is written in, and linked to, Objective C. However, it still uses MDItem from Core Framework. There shouldn't be a need for this, but with no support for file-specific metadata in Cocoa then it becomes essential.

I'll keep writing with this code for the time being. Given the lack of documentation, I suppose that Apple are still in the process of working on this, and will probably release something more complete as 10.4 progresses. In the meantime, I find this sort of thing fun, and everyone needs a hobby. :-)


Rob said...

Congratulations Paul!

Anonymous said...

Hey Paul,

My response is a little late, but I think this will interest you. The class dump of NSMetadataItem follows:
* Generated by class-dump 3.1.
* class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2005 by Steve Nygard.

* File: /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation
* Arch: Intel 80x86 (i386)
* Current version: 567.28.0, Compatibility version: 300.0.0

@interface NSMetadataItem : NSObject
id _item;
void *_reserved;

- (id)_init:(struct __MDItem *)fp8;
- (void)dealloc;
- (id)valueForKey:(id)fp8;
- (id)valueForAttribute:(id)fp8;
- (id)valuesForAttributes:(id)fp8;
- (id)attributes;


So, there is no constructor for an item with a path, but there is a private constructor that inits it from an MDItem. Whether you want to rely on that for production code is another matter, of course...