What goes on underneath Overlay Types

Posted by Andrew Bowers - Tuesday, November 04, 2008 at 1:35:00 PM

Recently I was giving an introductory talk on JavaScript Overlay Types in GWT. A question came up about what I meant by the following:

final native void foo(int x, int y) /*-{ ... }-*/;

can magically become

final static native void foo(MyJSO this_, int x, int y) /*-{ ... }-*/;

It was a great question, and merited explaining why Overlay Types have certain constraints on them and what goes on during the compile process. If you are brand new to Overlay Types, you'll want to look over the earlier blog post or docs first.

As the earlier post explained, in Overlay Types you can augment the Java type without disturbing the underlying JavaScript object. For example, you can have getters and setters in the Java class that don't exist on the underlying JavaScript object. At the same time, your Java class is providing this richer functionality without GWT having to modify the underlying JavaScript object instance or its prototype.

This is possible because by design we disallow polymorphic calls on instance methods. All methods must be 'final' and/or 'private'. Consequently, every method on an overlay type is statically resolvable by the compiler, so there is never a need for dynamic dispatch at runtime. We can either inline the method body or create a global function external to the object itself.

This all makes more sense if we look at a concrete example, so let's do that. Here we have a JavaScript snippet embedded in an HTML page. This snippet defines a native JavaScript object of type 'nativeCustomer' and creates a instance called 'fred'.

//JavaScript in an HTML page
<script>
  function nativeCustomer () {
    this.FirstName = "";
    this.LastName = "";
  }
 
  var fred = new nativeCustomer();
  fred.FirstName = "Fred";
  fred.LastName = "Wilson";
</script>

Now we'll overlay that native JavaScript object with an Overlay Type in GWT.

// An overlay type in GWT, which must extend JavaScriptObject
class Customer extends JavaScriptObject {

  // Overlay types always have protected, zero-arg constructors
  protected Customer() { }
   
  // Typically, methods on overlay types are JSNI
  public final native String getFirstName() /*-{ return this.FirstName; }-*/;
  public final native String getLastName()  /*-{ return this.LastName;  }-*/;
  
    public final String thankCustomer(String thanksType){
       if (thanksType.equals("buying")){
         return "Dear " + getFirstName() + ", thanks for buying our product";
       } else {
         return "Dear " + getFirstName() + ", thanks for referring your friend";
       }
     }
}

For good measure, I'll add a JSNI method to get the native JavaScript object from the wild, returning the Customer overlay type above.

private native Customer getNativeCustomer() /*-{
  return $wnd.fred;
}-*/;

Now that we have our native JavaScript object, a way to access it, and an Overlay Type, we'll want to use it. Below I have some GWT code that simply gets the JavaScript object from the wild as an Overlay type, then calls some methods on it.

Customer customer = getNativeCustomer();
Window.alert(customer.getFirstName() + " " + customer.getLastName());
Window.alert(customer.getFullName());

In this blog post we're interested in what's happening underneath - what the compiled output looks like. You'll notice that the methods in our Overlay Type don't exist on the actual JavaScript object. Where do they go? As we said above, where possible we try to just get rid of them. Yep, gone. The compiler will actually inline the augmented method bodies, so that the above code looks like this:

var customer;
customer = $wnd.fred;
$wnd.alert(customer.FirstName + ' ' + customer.LastName);
$wnd.alert(customer.FirstName + ' ' + customer.LastName);

You'll notice that we're accessing the underlying object's attributes (FirstName, LastName) directly. Pretty cool.

Sometimes it isn't possible to inline the augmented Java method, in which case a global function is created. Take the method thankCustomer() in the Customer class above. If we can't inline the method body, then we need to create a function somewhere. However, we don't want to add it to the underlying object or its prototype, so we create a global function that takes the underlying object as an argument. We get the same effect, but without the overhead of indirection.

So the 'thankCustomer' method above becomes

function $thankCustomer(this$static, thanksType){
  if ($equals_0(thanksType, 'buying')) {
    return 'Dear ' + this$static.FirstName + ', thanks for buying our product';
  }
   else {
    return 'Dear ' + this$static.FirstName + ', thanks for referring your friend';
  }
}

which means a Java source code line like

Window.alert(customer.thankCustomer("buying"));

in the end compiles to a call to global function $thankCustomer with the customer object passed as an argument.

$wnd.alert($thankCustomer(customer, 'buying'));

And that takes us full circle back to the code snippet at the beginning of this post. When we say that

final native void foo(int x, int y) /*-{ ... }-*/;
        can magically become
final static native void foo(MyJSO this_, int x, int y) /*-{ ... }-*/;

We're showing that if foo can't be inlined, then it will become a global function with the object passed as an argument rather than a method on the object itself.

You may also now see why we don't allow attributes on an Overlay Type -- we'd have nowhere to store them in this model. However, as you've seen we can allow augmented Java methods through either inlining the method away, or creating a global function where the first argument is the object itself.

No comments: