After reading, and playing with some stub code based on these ideas, I come
to the conclusion than 'native' is almost perfect from the point of view of
a gwt developer.
We want pure java syntax and simplicity, and any other conventions like
DSLs or magic interface names, etc, could obscure the understanding
existing code, or trying to figure out how to implement something.
For java developers 'native' means that the platform knows how to deal with
this method, and in the case of gwt this is true and intuitive, meaning
that 'this is native because it's implemented natively in JS'.
The compiler ability to automatically resolve native methods, could be very
useful not only for JsInterop but for many other cases where we use them.
Hence any getter or setter could be automatically written by the compiler
if it does not have the jsni fragment. For instance in
c.g.g.d.c.StyleElement we could get rid of all of the jsni fragments and
just write methods declaration
public final native void setCssText(String cssText);
If the compiler does this, we could save thousands of line of code in jsni
fragments in the gwt core, also in elemental, and in several other 3 party
libraries.
Even, we could have an annotation to deal with simple JSNI code instead of
using c++ comment blocks to change the default implementation:
@Jsni("return $doc.head || $doc.getElementsByTagName('head')[0]")
public final native void getHead();
Also it would be nice if the compiler is able to deal directly with type
conversion and it is able to box returned values: java arrays to jsArrays,
js numbers to java types, etc. This will make gwt developer experience
better.
Related with testing, I think that because we already use the GWTTestCase
to run JRE tests (returning module null), it should be not difficult to
modify it and return proxies of classes implementing native methods ant
throwing sensible messages to the user telling what methods are used, etc.
Coming back to JsInterop, I agree that native methods is an easy solution,
probably not so difficult to implement, and definitively solves many
problems like inheritance, static methods, constructors, etc.
On Sat, Nov 1, 2014 at 6:52 AM, 'Goktug Gokdogan' via GWT Contributors <
[email protected]> wrote:
> I finally had some time to think about JsInterop and how to abstract
> browser APIs.
>
> I will try to summarize my thought process and different options that I
> have played with so that you could get a better understanding where I'm
> coming from and I hope it will provide good documentation for future.
>
> So I started by playing with the static functions, constructors and how to
> represent that with the current JsInterop semantics.
>
> First I started with the original approach: use @JsType on an interface
> and use some simple conventions to generate the rest:
>
> @JsType(prototype = "Object")interface JsObject extends Auto_JsObject {
>
> static JsObject of(Object obj) {
> return obj instanceof JsObject ? (JsObject) obj : null;
> }
>
> interface Static {
> String[] keys(JsObject obj);
> JsObject defineProperties(JsObject obj, JsObject props);
> }
>
> interface constructors {
> void constructor(String param);
> }
>
> boolean hasOwnProperty(String prop);
> boolean isPrototypeOf(JsObject prop);
> }
>
>
> then with the annotation processor following code will be generated:
>
> interface Auto_JsObject {
>
> public static JsObject newInstance(String param) {
> return js("new Object($0)", param);
> }
>
> public static String[] keys(JsObject obj) {
> return js("Object.keys($0)", obj);
> }
>
> public static JsObject defineProperties(JsObject obj, JsObject props) {
> ...
> }
>
> @PrototypeOfJsType(JsObject.class)
> public static class prototype {
> JsObject constructor(String param) { return null}
> boolean hasOwnProperty(String prop) { return false; };
> boolean isPrototypeOf(JsObject prop) { return false; };
> }
> }
>
>
>
> And it looks like following when used:
>
> MyObject extends JsObject.prototype {}
>
> JsObject.keys( ... );
>
> JsObject.newInstance( ... );
>
> JsObject.of(new Object());
>
>
> One of the advantages of this is; I can additionally generate a special
> method named statics() that returns an instance of JsObject.Static where
> we forward all calls to the static methods. This is pretty useful for
> people who care about pure unit testing.
>
> Although this solution is not ideal due to need for extending a separate
> class (i.e. JsObject.prototype), it looks reasonably good at first sight.
> But there is one major gotcha: it doesn't work!
>
> The reason is, according to here
> <http://docs.oracle.com/javase/tutorial/java/IandI/override.html> unlike
> static methods in real classes, static methods in interfaces are not
> 'inherited' by subclasses.
>
> So I stepped backed and started questioning what we are doing here.
>
> We basically creating a DSL but this DSL is trying to enhance the very
> same class where it is used. This model isn't directly supported with
> annotation processors (that's why we do the trick of extending generated
> class) and we already decided not to use Lombok hacks so it looks like we
> can't do much here.
>
> So I'm thinking; as we are already defining a DSL why not make it more
> explicit in a separate class and let it generate the actual API?
>
> Here we go:
>
> @JsApi("Object") // A new annotation for the DSL, not required to be in the
> SDKinterface __JsObject { // Used underscore so it doesn't show up in code
> completion
>
> String[] keys(JsObject obj);
>
> JsObject defineProperties(JsObject obj, JsObject props);
>
> static JsObject of(Object obj) {
> return obj instanceof JsObject ? (JsObject) obj : null;
> }
>
> interface prototype {
> boolean hasOwnProperty(String prop);
> boolean isPrototypeOf(JsObject prop);
> }
>
> interface constructors {
> void constructor(String param);
> }
> }
>
>
> generates following:
>
> @JsType(prototype = "Object")interface JsObject {
> static JsObject newInstance(String param) {
> return js("new Object($0)", param);
> }
>
> static String[] keys(JsObject obj) {
> return js("Object.keys($0)", obj);
> };
>
> static JsObject defineProperties(JsObject obj, JsObject props) {
> ...
> }
>
> static JsObject of(Object obj) {
> return __JsObject.of(obj);
> }
>
> boolean hasOwnProperty(String prop);
> boolean isPrototypeOf(JsObject prop);
>
> public static JsObject defineProperties(JsObject obj, JsObject props) {
> ...
> }
>
> @PrototypeOfJsType(JsObject.class)
> public static class prototype {
> JsObject constructor(String param) { return null;}
> boolean hasOwnProperty(String prop) { return false; };
> boolean isPrototypeOf(JsObject prop) { return false; };
> }
>
> }
>
>
> So this is basically a working version of the previous one.
>
> One may argue that if we are committing to use some kind of DSL why not
> use something else like IDL or xml etc. There are 3 main reasons to use
> java + APT instead of others:
>
> First, our user base already familiar with java so they don't need to
> learn anything new other than a few simple naming conventions. Also
> developers need to able to write java code snippets for helper functions
> (see JsObject.of) which plays well with this approach.
>
> Second, IDE will give free syntax highlighting, checks and auto completion
> for whole process.
>
> Third, logistics for APT based code generation is already provided by
> IDEs, build systems and very likely will be provided by superdev in the
> future.
>
>
> Long story short; this is quite feasible and viable option but there are
> still some stuff I don't like about it:
>
> - JsObject.protoype is still an annoying artifact that all API consumers
> need to know about.
> - I don't like the compiler silently ignoring some method bodies (i.e.
> JsObject.prototype::*).
> - There is plenty of code generation going on which causes extra
> indirections.
>
>
> So I stepped back one more time and decided give a short break to my
> strong prejudice on native methods and created a solution around native
> methods:
>
> @JsType(prototype="Object")public class JsObject {
>
> @JsConstructor
> public static native JsObject newInstance(String param);
>
> public static native String[] keys(JsObject obj);
>
> public static native JsObject defineProperties(JsObject obj, JsObject
> props);
>
> public static JsObject of(Object obj) {
> return obj instanceof JsObject ? (JsObject) obj : null;
> }
>
> public native boolean hasOwnProperty(String prop);
> public native boolean isPrototypeOf(JsObject prop);
> }
>
>
> This solution is perfect from a purist point of view. No code generation
> involved; we basically utilize the host language's extension point created
> solely for this purpose. Here we are saying that this methods exist in the
> runtime and implemented natively by the platform.
>
> Pros:
> It is intuitive for java developers. There is no magic prototype class to
> extend from, no conventions or DSL, no code generators. It is more explicit
> and it is very clear where the native boundary is when you look at the
> code. Probably it is even more pleasing to implement in the compiler.
>
> All the cons are around the testability aspect. For JRE testing, native
> methods are a headache. Also we no longer generate an interface for static
> methods. We are basically going back to square one with respect to
> testability.
>
> But on the other hand it is not horrible. For people who insists on JRE
> testing; we can provide a classloader that stubs all native methods or they
> can use a more powerful mocking library like powermock or gwt-mockito.
>
> I would like to first get high level feedback here before discussing this
> internally. I'll update the design doc if we conclude on something.
>
> - Goktug
>
> --
> You received this message because you are subscribed to the Google Groups
> "GWT Contributors" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to [email protected].
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/google-web-toolkit-contributors/CAN%3DyUA2DvnfqtL1F4zd_ydM4KdWx9bBXCdtY7RGs_vaVvh88%2Bg%40mail.gmail.com
> <https://groups.google.com/d/msgid/google-web-toolkit-contributors/CAN%3DyUA2DvnfqtL1F4zd_ydM4KdWx9bBXCdtY7RGs_vaVvh88%2Bg%40mail.gmail.com?utm_medium=email&utm_source=footer>
> .
> For more options, visit https://groups.google.com/d/optout.
>
--
You received this message because you are subscribed to the Google Groups "GWT
Contributors" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/google-web-toolkit-contributors/CAM28XAtxB%3DZwRGfKxGVN-qa-%3DKk1jSZAMsq-0zsKTGRWuqzFZw%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.