Friday, November 2, 2007

They grow old so fast...

Two years ago, I initiated the Mule IDE project, an Eclipse plugin for the Mule ESB. It was started as a hobby project back when "Mule UMO" was mostly a (popular!) community effort with a few dedicated developers working on it.

Since then, things have really taken off for the Mule ESB, and it's now the centrepiece of MuleSource, Inc. - arguably an Open Source success story. Meanwhile, Mule IDE hasn't received too much attention since the 1.3 version from February '07 (as I have too many other (offline) things going on)

But that's all changed, since Ted and Moosa joined the Mule IDE project. And now Mule IDE 1.4.3 is out, with none of my doing whatsoever. Congratulations, I'm looking forward to seeing what's coming next!

But at the same time, I can't help feeling that it's a little bit like sending a child off to school...

Friday, September 14, 2007

On diversity and contributors

Much has been blogged recently about diversity in the project members and committer communities, and of the willingness to embrace contributions from the outside.

While I agree with what appears to be a consensus - that diversity is "A Good Thing"(TM), I would like to point out that there are other kinds of diversity than what can be calculated from the host-part of committers' email addresses.

One such diversity is that of contribution patterns. I would guess that most module owners work on their modules to fulfill the plans of both their daytime employers and the OSS project itself. Many contribute to one or more Eclipse projects on a regular basis, some may ONLY work on Eclipse itself.

And then there's people such as myself, who can hardly even qualify as software developers on "the day job". I just contribute minor stuff here and there: The odd bug report, a patch, technical clarification on some unexpected (but correct) behavior. When I get hit by a bug, and I'm able to, I'll gladly dig down to the bottom of the implementation of some plugin or other and fix it. But I rarely stay at the bottom for long - and thus I'm hardly committer material.

At any rate, I'd say that ad-hoc contributions are useful:

  • A fixed bug is a good bug, even if you didn't know it was there in the first place.
  • They sometimes add a missing piece of functionality which all downstream users may benefit from (unplanned).
  • They serve as a litmus-test for code clarity and quality. If a "total stranger" can find and fix a bug without prior knowledge of the code base, that's a good sign. People giving up after an initial glance is a bad sign.
  • They sometimes help to cross pollinate, inspiring adoption of useful code from other projects. So many software bugs come from re-inventing the wheel when it could just be imported.

The Eclipse projects I've been in contact with (mostly WTP, some EMF) welcome this kind of contribution with open arms, typically respond quickly, and give good, positive feed-back, even for rejected bugs/patches/ideas. That leads me to conclude that this kind of diversity of contribution is definitely appreciated by the projects, even if 95% of the people supporting it come from the same company.

As I see it, while the diversity of committers is crucial for the long-term survival of a project, nurturing the "satelite contributors" helps to keep the project healthy, too.

Wednesday, August 8, 2007

Monkey see, abe gør

(Abe gør = monkey do, in Danish)

I'm lazy, sure, no denying that. Sometimes I'm a bit too lazy, especially when programming -- and a while ago, I was too lazy to extract all the strings of a web application into a message bundle. Anyway, the application was only going to be in one language, so why bother?

Then, when scope crept in on me, and the inevitable second language had to be added, I suddenly had ~45 JSP/facelet pages to go through and extract all the strings. And, this being a hobby effort, I had no other developers to pass the tedius jobs on to.

The procedure is as follows:
* Go to the target JSP page
* Select the text to externalize
* Cut
* Switch to the resource bundle's text editor
* Enter the desired message key name
* Paste the text
* Copy the chosen message key name
* Switch back to the JSP page
* Enter the text output text boilerplate code and the key name, such as #{messages['LoginPage.label.userName']}

All in all, it added up to about 400 strings! No way I was going to do that by hand. Enter Eclipse Monkey, a "macro" system for Eclipse, where you can run JavaScript scripts (or other engines) within Eclipse without having to deal with making your own plugins and whatnot. But building a "localization macro" with Eclipse Monkey was harder than I thought, but then again it was my first ever script for the Monkey.

First up was the choice of which monkey DOMs I had available. A Monkey DOM makes one or more objects available to the scripting context, serving as an interaction point into Eclipse. You may also reference classes directly, but only in a few select packages (due to the access restrictions managed by OSGi). Although I was tempted to write one myself, I could make do with one of the builtin DOMs (org.eclipse.eclipsemonkey.lang.javascript), since I only needed access to the text editors, as in the variable "editors" below.

Second was the need for configurability. My Monkey script needed to know which file to copy the lozalized string to, but I have yet to find a mechanism for storing user-specific script parameters other than in the script itself. So I put the filename into a variable (destinationFileName), easy to adjust for anyone. The replacement template is specific to JBoss Seam, you can also tailor this to your need.

Third, I chose a viable keyboard shortcut, and out the file into the scripts directory in my project. The rest, as they say, is just code:


--- Came wiffling through the eclipsey wood ---
/*
* Menu: Localization > Extract String
* Key: SHIFT+F12
* Kudos: Jesper Steen Møller, Paul Colton (Aptana, Inc.)
* License: EPL 1.0
* DOM: http://download.eclipse.org/technology/dash/update/org.eclipse.eclipsemonkey.lang.javascript
*/

function askKey(oldKey, targetString) {
dialog = new Packages.org.eclipse.jface.dialogs.InputDialog(
window.getShell(),
"Localization Key",
"Enter the localization key for string '" + targetString + "' ?",
oldKey, null)
result = dialog.open()
if (result == Packages.org.eclipse.jface.window.Window.OK) {
return dialog.getValue()
}
}
// Simple info dialog
function show(text) {
Packages.org.eclipse.jface.dialogs.MessageDialog.openInformation(
window.getShell(),
"Extract String",
text
)
}
// Simple error dialog
function showError(text) {
Packages.org.eclipse.jface.dialogs.MessageDialog.openError(
window.getShell(),
"Extract String",
text
)
}

function main() {
// Change this for using a different editor source
var destinationFileName = "messages_da.properties";
var destinationEditor = undefined;
var sourceEditor = editors.activeEditor;
var valid = false;
for each( editor in editors.all ) {
if (editor.title == destinationFileName) {
destinationEditor = editor;
valid = true;
}
}
if (! valid) {
showError("No editor is open for " + destinationFileName);
}
if (valid && (sourceEditor == null)) {
valid = false;
showError("No active editor");
}
// make sure we have an editor
if (valid && (sourceEditor === undefined)) {
valid = false;
showError("No active editor");
}
// insert replacement
if (valid) {
var range = sourceEditor.selectionRange;
var offset = range.startingOffset;
var deleteLength = range.endingOffset - range.startingOffset;
var source = sourceEditor.source;
var replacement = source.substr(range.startingOffset, deleteLength);

replacement = replacement.replace(/(\r)?\n/g, "\\n\\" + destinationEditor.lineDelimiter + " ");

var keyLine = findKeyLineNo(destinationEditor);
var oldKeyName = "key" + keyLine;

if (keyLine >=0 ) {
var theLine = lineContents(destinationEditor, keyLine);
oldKeyName = theLine.substring(0, theLine.indexOf('='));
}

var key = askKey(oldKeyName, replacement);
if (! (key === undefined)) {
var text = "#{messages['" + key + "']}";

// apply edit and reveal in editor
sourceEditor.applyEdit(offset, deleteLength, text);
sourceEditor.selectAndReveal(offset, text.length);

// now copy the replacement text into the property file
var curLine = destinationEditor.getLineAtOffset(destinationEditor.selectionRange.endingOffset);
var replacementOffset = destinationEditor.getOffsetAtLine(curLine+1);
if (replacementOffset < 1) replacementOffset = destinationEditor.sourceLength;
var replacementText = key + "=" + replacement + destinationEditor.lineDelimiter;
destinationEditor.applyEdit(replacementOffset, 0, replacementText);
destinationEditor.selectAndReveal(replacementOffset, replacementText.length);
}
}
}
// Get the string contents of a line
function lineContents(anEditor, lineNo) {
var firstOffset = anEditor.getOffsetAtLine(lineNo);
var lastOffset = anEditor.getOffsetAtLine(lineNo + 1);
var s = anEditor.source.substr(firstOffset, lastOffset - firstOffset);
//alert("Contents of line " + lineNo + ":" + s + " (" + firstOffset + "," + lastOffset + ")");
return s;
}
// Finds the nearest line which starts a key=value
function findKeyLineNo(anEditor) {
var beginLine = anEditor.getLineAtOffset(anEditor.selectionRange.startingOffset);

// Search up...
var i = beginLine;
while (i >= 0) {
if (lineContents(anEditor, i).indexOf('=') > 0) return i;
--i;
}
var lastLineNo = anEditor.getLineAtOffset(anEditor.sourceLength);
var i = beginLine + 1;
while (i < lastLineNo) {
if (lineContents(anEditor, i).indexOf('=') > 0) return i;
++i;
}
return -1;
}
--- And burbled as it ran! ---


The funny header and footer makes it easy to paste the script into you Eclipse workspace, just mark everything (including the headers) and choose Scripts > Paste New Script, and you're flying.

The morale is: Look for script that do similar things, and experiment from there.

Wednesday, July 25, 2007

Bug day is tomorrow...

... but I must honor all schema locations today

I was bitten by this bug so I fixed it, although it may not make it into the next WTP maintenance release since it contains more or less "gratuitous" UI changes/additions.

The underlying problem is that of XML schemas that directly or indirectly import several schema files from different locations, like this:

A.xsd defines element nsA:A in nsA and imports B1.xsd and B2.xsd
B1.xsd defines element nsB:B1
B2.xsd defines element nsB:B2
A-instance.xml contains an nsA:A which contains a nsB:B1 and a nsB:B2.

(confused? This is a simplification of an example derived from a simplification of the real-world problem)

Now, the XML Schema spec says this is undefined and that a processor is free to ignore the schemaLocation attribute of the subsequent imports. Nevertheless, the Danish government office for IT standardisation and such has decided in ~2003 to mandate such use in their guidelines (OIOXML), and if you want to design XML interfaces to work in the public sector in Denmark, you'd better play along. Sigh. Many implementations, such as Microsoft's, did the sensible thing and ignored the extra schemaLocation, some even with a warning.

Now, WTP's XSD editor supports this in general (due to elaborate 'best effort' lookups in the EMF model for XML Schema), but the validation framework uses the Xerces schema functionality, not the EMF model, for validation. This validation drives the annotations you see in the Problems view and the as-you-type squiggly lines in the editor.

Fortunately, the problem was mostly solved in WTP a while back, possibly in 1.0.1, by introducing the "Honour all schema locations" checkbox in the XML Schema preferences.
However, this only works for the validation of the schema files themselves, not for XML files which use such schemas.

Now at least there's a solution, if the patch is accepted.



Next question is: Which bug to pick for bugday?