Search code examples
javajavascriptgwtgwt2jsni

GWT, JSNI: Implementing an overlay object with fully nullable methods?


I'm struggling with JSNI method return types to ensure that one of my DTO object can really have nullable properties. This was the very first and obviously unsuccessful try for the overlay DTO:

public class Dto extends JavaScriptObject {

    protected Dto() {
    }

    private native Integer getValue1() /*-{ return this.value1; }-*/;

    // no longs are allowed
    private native Double getValue2() /*-{ return this.value2; }-*/;

    // TODO migrate to Java 8
    public static final Function<Dto, DomainObject> dtoToDomainObject = new Function<Dto, DomainObject>() {
        @Override
        public DomainObject apply(Dto dto) {
            return new DomainObject(
                    dto.getValue1(),
                    (long) dto.getValue2()
            );
        }
    };

}

As per the documentation, the outgoing types are just String, boolean, Java numeric primitive (with certain restrictions), JavaScriptObject, Object, and arrays. And this restriction does not seem to fit the nullable semantics, because of various runtime exceptions that prevent passing JavaScript "primitive" via a Number box or vice versa. This is somewhat strange, but I believe there were solid technical arguments for it. Anyway. So far after getting a lot of exceptions during my experiments, I've gained the following way:

public class Dto extends JavaScriptObject {

    protected Dto() {
    }

    private native boolean hasValue1() /*-{ return typeof(this.value1) != "undefined" && this.value1 != null; }-*/;

    private native int getValue1() /*-{ return this.value1; }-*/;

    private native boolean hasValue2() /*-{ return typeof(this.value2) != "undefined" && this.value2 != null; }-*/;

    // no longs are allowed
    private native double getValue2() /*-{ return this.value2; }-*/;

    // TODO migrate to Java 8
    public static final Function<Dto, DomainObject> dtoToDomainObject = new Function<Dto, DomainObject>() {
        @Override
        public DomainObject apply(Dto dto) {
            return new DomainObject(
                    dto.hasValue1() ? dto.getValue1() : null,
                    dto.hasValue2() ? (long) dto.getValue2() : null
            );
        }
    };

}

The second implementation finally works as I expect letting me distinguish between real values and nulls. But this is a very huge boilerplate with at least the following disadvantages:

  • satellite has-methods;
  • the implementation of the satellite methods, at least two checks for nullability relying on JavaScript type checks (it can be extracted into a single JavaScript method, but that is not always easy), not typo-bulletproof;
  • ternary operator while accessing the nullable methods, not typo-bulletproof allowing to compose an "alien" satellite method that belongs to another accessor;
  • maybe something else that I don't see so far.

I'm wondering: how can I use/implement complete nullable semantics for JSNI methods in a more elegant way without that huge boilerplate?


Solution

  • You won't cut down much boilerplate (though you could have simplified your code above). The code would look like:

    private native Integer getValue1() /*-{
      return this.value1 == null ? null : @java.lang.Integer::valueOf(I)(this.value1);
    }-*/;
    
    private native Long getValue2() /*-{
      return this.value2 == null ? null : @my.app.client.Dto::doubleToLong(D)(this.value2);
    }-*/;
    
    private static Long doubleToLong(double d) {
      return Long.valueOf((long) d);
    }
    

    BTW, your double-to-long code in the first snippet is wrong, it would throw an NPE if the value is null. You'd have to check for nulls before casting to long (or getting longValue())