Monday, January 11, 2010

GWT and virtual toString methods

I ran into something very mysterious today with GWT 2.0. It took me quite a while to track down exactly what the root cause was, but I've narrowed it down to one specific thing.

Let's say you create an object like this:
public class TestObject {
  private Object value ; 
     
  public void setValue(Object value) { 
    this.value = value; 
  } 
     
  @Override public String toString() { 
    return value.toString(); 
  } 
} 
Then in your entry point you do this:
public class Gwt_test implements EntryPoint {
  public void onModuleLoad() { 
    RootPanel panel = RootPanel.get(); 
    HTML html = new  HTML(); 
    panel.add(html); 
        
    TestObject object = new TestObject(); 
    object.setValue(new  Object()); 
    object.setValue("Hello there"); 
        
    html.setHTML("<h1>" + object + "</h1>");
  } 
} 

You would expect when you do the GWT compile and run your server and navigate to the page, you would see "Hello there" on the screen. Well, if you use Google Chrome, you'd be right. But if you use IE, Safari, or FireFox you'll just see a blank page.

Why is this? Well, it turns out to be quite complicated, and I only understand it up to a point. But this is what I have figured out so far.

You'll notice in the onModuleLoad method of the Gwt_test class after I create an instance of TestObject I set the value to 'new Object()' and then to a String. This is there so that when the GWT compiler generates code for the TestObject toString method, it has to take into account that the 'value' field may be an Object or a String. Which means it has to do a call to toString__devirtual$ in order to generate a string for the 'value' field. If you have GWT generate readable JavaScript you can look at it. The toString method for TestObject looks like this:
function toString_7(){
  toString__devirtual$(this.value); 
} 
Now, this is the part that I don't understand. For some reason this code causes a JavaScript error in browsers other than Chrome. If you go to the page in FireFox and open Firebug, you'll see it has a JavaScript error that says, 'can't convert object to primitive type'. Kind of strange. I really don't get why this is happening, or why it works in Chrome, but I do have a fix.

The easiest way to fix this is to just change the toString method on the TestObject class. Instead of calling the toString method on the value field, just do a string concatenation. So now TestObject looks like this:
public  class  TestObject {
  private Object value ; 
     
  public void setValue(Object value) { 
    this.value = value; 
  } 
     
  @Override public String toString() { 
    return value + ""; 
  } 
} 
And the generated JavaScript for the method looks like this:
function toString_7() {
  return this.value + ''; 
} 
Now it will work in all browsers. I wish I could understand why the other code doesn't work. I looked at the toString__devirtual method, but didn't really understand where the problem was coming from. But at least I have a workaround.

1 comment: