Wednesday, July 30

OSGi Text Scrambling Service

Here's most of the code from the peaberry example I mentioned in my previous post - as you can see, peaberry lets you import and export OSGi services from your Guice enabled application, without introducing a dependency on the OSGi API. In fact you can even plug your own service frameworks into peaberry.

EXPORTING BUNDLE


package examples.scrambler.impl;

import static org.ops4j.peaberry.Peaberry.registration;
import static org.ops4j.peaberry.util.TypeLiterals.export;

import com.google.inject.AbstractModule;
import com.google.inject.Key;

import examples.scrambler.Scramble;

// here's where we bind the exported service...
public class ExportModule extends AbstractModule {

@Override
protected void configure() {
// note: the service is exported at injection time
bind(export(Scramble.class)).toProvider(
registration(Key.get(ScrambleImpl.class)).export());
}
}

package examples.scrambler.impl;

import static com.google.inject.Guice.createInjector;
import static org.ops4j.peaberry.Peaberry.osgiModule;

import org.ops4j.peaberry.Export;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;

import com.google.inject.Inject;

import examples.scrambler.Scramble;

@SuppressWarnings("unused")
public class Activator implements BundleActivator {

@Inject
final Export<Scramble> handle = null;

public void start(final BundleContext ctx) throws Exception {

// apply the exported service binding
createInjector(osgiModule(ctx), new ExportModule()).injectMembers(this);
}

public void stop(final BundleContext ctx) throws Exception {

if (null != handle) {
handle.remove();
}
}
}


IMPORTING BUNDLE


package examples.scrambler.test;

import static org.ops4j.peaberry.Peaberry.service;

import com.google.inject.AbstractModule;

import examples.scrambler.Scramble;

// here's where we bind the imported service...
public class ImportModule extends AbstractModule {

@Override
protected void configure() {
bind(Scramble.class).toProvider(service(Scramble.class).single());
}
}

package examples.scrambler.test;

import static com.google.inject.Guice.createInjector;
import static org.ops4j.peaberry.Peaberry.osgiModule;

import org.ops4j.peaberry.ServiceUnavailableException;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;

import com.google.inject.Inject;

import examples.scrambler.Scramble;

@SuppressWarnings("unused")
public class Activator implements BundleActivator {

// plain text to be scrambled...
final static String TEXT = "This is a simple test of peaberry.";

@Inject
final Scramble service = null; // Guice can actually inject final fields!

Thread tester;

public void start(final BundleContext ctx) throws Exception {

// apply the imported service binding
createInjector(osgiModule(ctx), new ImportModule()).injectMembers(this);

// quick'n'dirty test thread
tester = new Thread(new Runnable() {
public void run() {
// support cooperative cancellation
while (Thread.currentThread() == tester) {
try {
System.out.println('[' + service.process(TEXT) + ']');
} catch (final ServiceUnavailableException e) {
System.err.println("No scrambler service!");
}
try {
Thread.sleep(2000);
} catch (final InterruptedException e) {
// wake-up
}
}
}
});

tester.start();
}

public void stop(final BundleContext ctx) throws Exception {

// cooperatively stop the thread
final Thread zombie = tester;
tester = null;
zombie.join();
}
}

peaberry 1.0 alpha

You may have noticed I recently uploaded an alpha build of peaberry 1.0

http://peaberry.googlecode.com/files/peaberry-1.0-alpha.zip

today I've also been working on updating the design and user guide pages:

http://code.google.com/p/peaberry/wiki/UserGuide
http://code.google.com/p/peaberry/wiki/DetailedDesign

including a small Eclipse/PDE example I put together in a matter of minutes.
(and which hopefully also works ok on other people's installations of Eclipse!)

http://peaberry.googlecode.com/files/PeaberryExample.zip

At this point I'm feeling good about the API and (unless I hear otherwise) will
concentrate on more "behind-the-scenes" work, and perhaps add a few more
utilities to help developers.

So... if you find any bugs please let me know via the issues tab :)

Tuesday, July 29

IdentityHashMap is broken in IBM JDK 5!

So there I was, happily running some tests with the latest copy of Guice when blam - I got a NullPointerException from inside the injector. Hmmm, I thought, perhaps the latest trunk code is unstable. So I pulled down a stable snapshot... and got the same exception.

Looked at location of the NPE and got even more confused:

   CreationTimeMemberInjector.java:87

where it iterates over the cached entry set of an identity hash-map. Added some debug code, and yes - the entry set is valid to begin with, but after stashing it in a list, the list elements are all null. Weird!

Why hadn't I seen this before, I've run the same test many times - then it struck me, today I happened to have JAVA_HOME set to the IBM JDK, while previously I've used the default Sun JDK.

Time to crack open the src.jar from the IBM JDK - lo and behold in IdentityHashMap.java:1142

public T[] toArray(T[] a) {
return (T[])toArray(); // !!!!
}

that is plain wrong - the specification for toArray(T[] a) clearly states that the provided array must be used if it's large enough:

Returns an array containing all of the elements in this collection; the runtime type of the returned array is that of the specified array. If the collection fits in the specified array, it is returned therein. Otherwise, a new array is allocated with the runtime type of the specified array and the size of this collection.

this behaviour is relied on in other classes from java.util, where they create a large enough array and use c.toArray(elements); to initialize the array contents. Hence the null array when creating a list based on the entry set.

I was going to report this on the "IBM Java Runtimes and SDKs" forum as the problem still exists in the latest service release, but it appears to be offline today! For those interested, here is a simple testcase to recreate the bug:

import java.util.*;

public class IdentityHashMapTest {
@SuppressWarnings("unchecked")
public static void main(String[] args) {
Map map1 = new HashMap();
Map map2 = new IdentityHashMap();
map1.put("Hello", "World");
map2.put("Hello", "World");
System.out.println("MAP 1 : " + map1.entrySet());
System.out.println("MAP 2 : " + map2.entrySet());
System.out.println("LIST 1 : " + new ArrayList(map1.entrySet()));
System.out.println("LIST 2 : " + new ArrayList(map2.entrySet()));
}
}

BTW, that comment // !!!! is actually in the original source code, which suggests someone out there knows this is wrong but couldn't be bothered to fix it...