GWT JavaScriptObjects (Javascript Overlays)
GWT has a fantastic tool for "overlaying" java (GWT) classes on JSON objects such that you don't have to write any parsing code to parse a piece of JSON. There are some nice articles about this from the GWT folks themselves, for example this one. The gist of it is, given some JSON like this:
{ "item": {
"id": 123,
"name": "cheese",
"type": "food"
}
}
we can "overlay" a java class onto that JSON, and have the GWT compiler (and the browser's native JSON parsing) do the marshalling for us.
JavaScript Overlay Types
We do that by writing a JavaScript Overlay Type class that maps the native json data back to the the world of your Java code, and looks like this:
public class Item extends JavaScriptObject
{
protected Item(){}
public final native Integer getId()/*-{
return this.id;
}-*/;
public final native String getName()/*-{
return this.name;
}-*/;
public final native String getType()/*-{
return this.id;
}-*/;
}
JSNI Methods
Those three native methods are known in GWT-land as JSNI methods (JavaScript Native Interface), and the part in comments at the end is actually pure JavaScript that you can write to directly access parts of the JSON object.
In these native JSNI sections the "this" object being referred to is the JSON object itself, and although I've only shown very simple property access here you can in fact perform more complex operations if you like.
The return values from our JSNI can be Java's primitive wrappers or String, any class that extends JavaScriptObject (JSO), or one of a number of special JSO classes GWT provides such as JsArray, JsIterable, JsArrayString, etc.
I have exclusively used jsni methods in my example above, but you can include pure java methods as well (provided they are final) - say, for example, we need to parse a date from some json:
public class Item extends JavaScriptObject
{
private static final DateTimeFormat df =
DateTimeFormat.getFormat("yyyyMMdd");
protected Item(){}
public final Date whenCreated() {
return df.parse(getCreateDateString());
}
public final native Integer getId()/*-{
return this.id;
}-*/;
public final native String getName()/*-{
return this.name;
}-*/;
public final native String getType()/*-{
return this.id;
}-*/;
public final native String getCreateDateString()/*-{
return this.createDate;
}-*/;
}
Mapping complex types
These are simple examples of course, but i've had no trouble mapping even the most complex JSON objects using JSO's. You can map arrays easily enough using the built in GWT JsArray types, like this:
public class Items extends JavaScriptObject
{
protected Items(){}
public final native JsArray getItems()/*-{
return this.items;
}-*/;
}
Which would map the following JSON:
{ items: [
{
"id": 123,
"name": "cheese",
"type": "food"
},
{
"id": 456,
"name": "ham",
"type": "food"
},
{
"id": 789,
"name": "eggs",
"type": "food"
}]
}
JavaScriptObject rules
There are a couple of basic rules to follow:
- Must have a protected no-arg constructor
- All methods must be "final"
- Must not have any instance fields
- May only implement a single Interface
JavaScriptObject "gotchas"
With regard to that last point, there's another gotcha to look out for: only one JavaScriptObject class can implement any given interface. Think about that for a minute, because its more of a restriction than you might at first realise...
The whole point of Interfaces is to use them to define a contract that implementations will adhere to, so that multiple poly-morphically interchangeable implementations can be used. With JSO's, you can only have one single JSO implementation of an interface - for example you cannot have several JSO's which implement Iterable. This is for the same reason that all JSO methods must be final - JSO's cannot use dynamic method dispatch.
If you try to implement the same interface in more than one of your JSO's you don't (currently) get any warnings from the compiler ... instead you get crazy runtime behaviour due to the wrong JSO class's methods being invoked (because one of your JSO's will receive all method invocations for the interface, regardless of which class the method should have been invoked against).
Interestingly I ran into a problem in the latest GWT release (2.4) that reported a very unhelpful error message (I'm sure the messages in earlier releases of GWT were much more helpful actually). Here's the error message I got:
java.lang.ClassFormatError: Illegal method name "$"
in class com/xxx/xxx/client/model/UserResult
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClassCond(ClassLoader.java:631)
at java.lang.ClassLoader.defineClass(ClassLoader.java:615)
at java.lang.ClassLoader.defineClass(ClassLoader.java:465)
at com.google.gwt.dev.shell.CompilingClassLoader.findClass
(CompilingClassLoader.java:1085)
at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
... as it turns out, the problem actually was that I broke one of the rules - I had a non-final method in my JavaScriptObject. Not a very helpful error message in this case, which is fairly unusual in my experience of GWT.
Comment on this postOn 'Flow' and feeling productive
I like to feel productive. Actually that's a huge understatement. If I'm not feeling productive I get quite unhappy. For me feeling productive goes hand in hand with creative output - something that produces a tangible artifact - and I find it difficult to satisfy my need for productivity with intangible things like meetings.
Even more than that, I need to be deeply involved in the creative activity - part of the group doing the creative stuff rather than managing it from a distance.
This post has been brewing for a while, and I finally began composing it last week. At the weekend I ran out of Kindle-fodder and bought @ericries 's new book The Lean Startup, and was amazed and pleased to hit upon the following quote, which said it all much better than I could:
Now I agree with almost all of that quote, except for the last sentence where I think the word "learning" is in some ways not fitting. I think Eric meant to encompass questions, process, meetings, and many other things in "learning", in which case I'd agree with him, but there certain kinds of "learning" which I find very productive.
Recently I've been exploring some new avenues in my work - straying away from spending all day coding and venturing into developing product ideas. At first it felt very uncomfortable and I didn't feel at all productive. Lately though I'm coming around to it.
For me part of the way through has been to find ways to turn what I'm thinking and doing into tangible artifacts that require some creative effort to produce - for example: drawing UI wireframes, writing up meetings and brainstorming sessions, and developing ideas in essay form.
By committing &ideas to disk I've started to feel productive again. Perhaps its just a safety blanket really, but it feels good, and the best bit is that with each new artifact, essay, description, and wireframe I can link to previous artifacts for reference, backup or explanation (all are available online in our basecamp account), and the conversation gets richer.
I've begun to wonder if some of my feeling of productivity actually comes from spending a certain amount of time each day in a state of flow), and if I don't get to spend some time each day in that state then I feel less happy and productive.
There are a few activities - mostly creative in some way - during which I find myself zoning out and really getting immersed in what i'm doing: coding; drawing; reading (novels); and - with a bit of effort - writing. Once i'm tuned out I can go on for hours without feeling time passing.
When I'm coding at home, with no distractions, I'll sit at the table in morning when my wife leaves for work and will hardly have moved by the time she comes home. I've noticed that I even realize, sometimes, that i'm in this state and have a "lucid flow" moment akin to lucid dreaming. Usually I get this when I hit a turning point in my work: when I've been working on a problem for a couple of hours and finally feel the complete shape of the solution in my head - I'm as deep into the problem as I'm going to get, I can start unwinding the mental stack, and from here on in progress is going to get quicker and quicker. Love that feeling.
Coding is the creative activity that I spend most time on, so its also the activity that most often gets me into flow, but I find the most powerful activity for flow is drawing. I suspect this has a lot to do with the difference between coding and drawing - the fact that coding requires the language centres of the brain to be active. Betty Edwards has quite a bit to say on this in Drawing on the Right Side of the Brain, which is an interesting read if you can draw reasonably well, but presumably a revelation if you've never been able to draw. |
About Me
I didn't intend to have an about me page, but it turns out that to get google to display my mugshot next to search listings I have to have one. So here it is.
This is me, the grinning idiot in the viking hat. Sadly I no longer have the hat. If you're interested, I took an (old) picture and vectorised it using Vector Magic, which allows online vectorising of a couple of images for free (thanks VectorMagic!) |
I graduated from Reading University with a BSc hons. degree in Computer Science and Cybernetics, and immediately started work as a programmer at British Aerospace, where I stayed for about a year and a half.
Next I moved on to work at a much smaller and more exciting company - KnowledgeView - where I have spent the last 11 years (oh me, oh my) developing a variety of web-based Editorial, Publishing and Syndication systems for news publishers - many based in the Middle-East and working and publishing in Arabic. We also build native and web apps for iPhone, iPad and Android devices to complement our publishing systems.
My current title is "Development Manager", though I think the job description would be better represented by the title "Chief Engineer". (A peculiar quirk of being a programmer is that you become a pedant for nomenclature).
I still code every day, for as many hours as I can. My "management style", if I have such a thing, is to lead by example. I care a lot about writing good code, using good tools, and leaving things in a better state than I found them. I try hard to enthuse my team to the same ideals.
KnowledgeView is still small, though we have a growing team in Beirut. It's also occasionally quite exciting, otherwise I wouldn't still be there. Over the years we've had many high-profile customers, including:
- News International
- CNN arabic
- Independent News and Media
- Saudi Research and Media Group
- Al-Hayat Newspaper
- Al-Quds
- ... and many more besides
When I'm not at work I divide my time between growing veg on my allotment (conveniently just outside my garden gate) and working on my personal programming projects.
I believe that programming is the most fun a person can have on their own, and I often wonder if non-programmers know what they are missing out on. I sometimes find time to draw, and wish I could find more.
Comment on this postMy first Android app (desktop widget)
Having spent quite a bit of time away from Eclipse lately I've been itching to play with some code again. During a short meeting this morning an idea hit me for a really simple Android desktop widget: a twitter search widget that you can drop on your desktop and configure with a hash-tag search.
Here's how it looks in the Android emulator:
I know, not exactly eye-candy, but that wasn't really the point :)
The important pieces of the puzzle are:
- A BroadcastReceiver that receives events telling the widget to update. Here I extended AppWidgetProvider to override onUpdate only.
- A Service that does the Twitter API request asynchronously - we don't want to use the UI thread and fail the responsiveness criteria
- The "widget info" descriptor xml
- Android manifest entries that register the Service and the broadcast receiver
- SAX ContentHandler implementation to parse the Tweet text from the atom response to the Twitter API request
Here's the code, currently wired to display a search for "#uml"
The BroadcastReceiver
public class NewsTicker extends AppWidgetProvider {
@Override
public void onUpdate(Context aContext,
AppWidgetManager aAppWidgetManager,
int[] aAppWidgetIds) {
super.onUpdate(aContext, aAppWidgetManager, aAppWidgetIds);
aContext.startService(
new Intent(aContext, NewsTickerDataService.class));
}
}
The Service
public class NewsTickerDataService extends Service {
@Override
public void onStart(Intent aIntent, int aStartId) {
super.onStart(aIntent, aStartId);
RemoteViews _views = buildUpdatedViews(this);
ComponentName _widget =
new ComponentName(this, NewsTicker.class);
AppWidgetManager _manager =
AppWidgetManager.getInstance(this);
_manager.updateAppWidget(_widget, _views);
}
@Override
public IBinder onBind(Intent aParamIntent) {
// not supporting binding
return null;
}
private RemoteViews buildUpdatedViews(Context aContext) {
List<Story> _stories = getStories();
RemoteViews _result = new RemoteViews(
aContext.getPackageName(),
R.layout.newsticker_widget
);
if (_stories.isEmpty()) {
_result.setTextViewText(R.id.title,
"Sadly there's nothing to read today.");
} else {
_result.setTextViewText(
R.id.title, _stories.get(0).getTitle());
}
return _result;
}
private List<Story> getStories() {
try {
URL _url = new URL("http://search.twitter.com" +
"/search.atom?q=%23uml&" +
"result_type=mixed&count=5"
);
InputStream _in = _url.openStream();
return parse(new InputSource(_in));
} catch (Exception anExc) {
Log.e("NewsTicker", anExc.getMessage(), anExc);
return new ArrayList<Story>();
}
}
private List<Story> parse(InputSource aSource)
throws Exception {
SAXParserFactory _f = SAXParserFactory.newInstance();
SAXParser _p = _f.newSAXParser();
XMLReader _r = _p.getXMLReader();
AbstractParser _h = new AtomParser();
_r.setContentHandler(_h);
_r.parse(aSource);
return _h.getStories();
}
}
The SAX ContentHandler
public abstract class AbstractParser extends DefaultHandler {
static interface Handler {
Handler startElement(String aName);
Handler characters(char[] aChars, int aStart, int aLength);
Handler endElement(String aName);
}
public static AbstractParser newAtomParser() {
return new AbstractParser() {
protected String getStoryElementName() {
return "entry";
}
};
}
private Handler handler;
private List<Story> stories;
public AbstractParser() {
stories = new ArrayList<Story>();
handler = newDefaultHandler();
}
protected abstract String getStoryElementName();
@Override
public void startElement(
String aUri, String aLocalName,
String aQName, Attributes aAttributes)
throws SAXException {
handler = handler.startElement(aLocalName);
}
@Override
public void characters(char[] aCh, int aStart, int aLength)
throws SAXException {
handler = handler.characters(aCh, aStart, aLength);
}
@Override
public void endElement(String aUri, String aLocalName, String aQName)
throws SAXException {
handler = handler.endElement(aLocalName);
}
public List<Story> getStories() {
return stories;
}
private Handler newDefaultHandler() {
return new Handler() {
@Override
public Handler startElement(String aName) {
if (aName.equals(getStoryElementName())) {
return newItemHandler(this);
}
return this;
}
@Override
public Handler characters(
char[] aChars, int aStart, int aLength) {
return this;
}
@Override
public Handler endElement(String aName) {
return this;
}
};
}
private Handler newItemHandler(final Handler aParent) {
stories.add(new StoryImpl());
return new Handler() {
@Override
public Handler startElement(String aName) {
if ("title".equals(aName)) {
return newTitleHandler(this);
}
return this;
}
@Override
public Handler characters(
char[] aChars, int aStart, int aLength) {
return this;
}
@Override
public Handler endElement(String aName) {
if (getStoryElementName().equals(aName)) {
return aParent;
}
return this;
}
};
}
private Handler newTitleHandler(final Handler aParent) {
final StringBuilder _title = new StringBuilder();
return new Handler() {
@Override
public Handler startElement(String aName) {
return this;
}
@Override
public Handler characters(
char[] aChars, int aStart, int aLength) {
_title.append(aChars, aStart, aLength);
return this;
}
@Override
public Handler endElement(String aName) {
if ("title".equals(aName)) {
StoryImpl _s = (StoryImpl)
stories.get(stories.size()-1);
_s.setTitle(_title.toString());
return aParent;
}
return this;
}
};
}
}
The Android Manifest
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.knowledgeview.android.ticker"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="7" />
<uses-permission android:name="android.permission.INTERNET" />
<application
android:icon="@drawable/icon"
android:label="@string/app_name">
<receiver android:name=".NewsTicker">
<intent-filter>
<action
android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/newsticker_widget_info"/>
</receiver>
<service android:name=".NewsTickerDataService"/>
</application>
</manifest>
The Widget descriptor
<appwidget-provider
xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="294dp"
android:minHeight="72dp"
android:updatePeriodMillis="86400000"
android:initialLayout="@layout/newsticker_widget">
</appwidget-provider>
The layout descriptor
<LinearLayout
android:id="@+id/linearLayout1"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
xmlns:android="http://schemas.android.com/apk/res/android">
<TextView
android:textAppearance="?android:attr/textAppearanceSmall"
android:id="@+id/title"
android:text="TextView"
android:textSize="10sp"
android:textColor="#fff"
android:layout_height="60dp"
android:layout_width="fill_parent"
android:background="#c333"
android:padding="3dp"
android:layout_margin="5dp">
</TextView>
</LinearLayout>
Complete project and source-code
The complete project and source code is available from github.
Comment on this postReplicating Java threading issues between machines with different cpu counts
Today I had a problem where a multi-threaded testcase which runs fine on my 8-core dev box failed when committed to the single-cpu build-server.
To replicate I needed to run on a single-cpu but I didn't want to commit more potentially broken tests to source-control and run repeatedly breaking builds on the build-server.
I discovered that its possible to bind an exec'd process to a single CPU in newer 'nix's using taskset. Invoking my maven build using taskset to bind the test-run to a single CPU core replicated the failure immediately, and from there it was an easy fix. Here's the cmdline to bind a maven build to a single CPU (I'm running Ubuntu, YMMV):
taskset -pc 0 mvn clean test
tasket is part of schedutils, which you may or may not have installed. It was installed on my machine already, possibly because of other things i've installed in the past. If needed you can install it with:
sudo apt-get install schedutils
It basically sets processor affinity by setting some process properties that tell the linux cpu scheduler to only allow the process to run on the stated CPU's.
Comment on this post