1

I want to create an Annotation Processor which replaces the call to GWT.create.

With an annotation processor, you'd have to generate both classes and then dynamically (at runtime) select among them, depending on the context (you could generate a factory to help doing that, but you'd still have to somehow feed the factory with the current context, e.g. the current locale).

Source: https://stackoverflow.com/a/29915793/116472

I got my Annotation Processor running it generated the classes very well. The part I do not know is the runtime selection part.

How can I do this runtime selection?

Thomas Broyer
  • 64,353
  • 7
  • 91
  • 164
Michael
  • 32,527
  • 49
  • 210
  • 370

2 Answers2

4

I'll assume that you've got the code generation side covered, and only focus on how we might pick the correct implementation in GWT:

but you'd still have to somehow feed the factory with the current context, e.g. the current locale).

We could do this at runtime, as you suggest, but as of the recently added support for System.getProperty you could also do this at compile time.

Step one, of course, is to generate the code for each implementation you might want to have access to. Taking locales as an example, you might have Foo_en.java, Foo_es.java, Foo_de.java, etc.

Next, we need a consistent way to get any one implementation - perhaps a generated FooFactory, with a method like this:

public static Foo getFoo(String locale) {
  if ("en".equals(locale)) {
    return new Foo_en();
  } else if /*...
  ...*/

  throw new IllegalArgumentException("Locale " + locale + " is not supported");
}

If you ask the user at runtime which locale they wanted, you could pass that value in to this factory method to get the implementation you wanted. Likewise, if you could read that value at runtime from something, you could again get the right instance and proceed forward.


But what if you actually want to make the compiler pick for you? Lets keep this code generated with the annotation processor, but move the phase of selecting a locale to the compiler and its permutations.

As in existing GWT code, specify a property for locale, and several values. Then, instead of asking the user or deciding at runtime in your own Java code which locale you want, use the same selection script wiring as GWT typically uses (checks the url, a cookie, meta tags, the user agent itself, etc) - you can construct your own property-provider for this if you wanted.

As before, we can use getFoo(locale), but now we use System.getProperty to read out the property that we created in our .gwt.xml file. This will be statically compiled out to the correct constant for each permutation. But rather than calling FooFactory.getFoo(System.getProperty("locale")) each time we need an instance, lets make yet another method in our generated FooFactory:

public static Foo getFoo() {
  return getFoo(System.getProperty("locale"));
}

Now we can just call FooFactory.getFoo(), and we will get the correct class for our current permutation.


Dagger question: Thomas is likely far better suited to address this, but no, Dagger2 doesn't do bind(Foo).to(Bar).in(Scope) like Guice does, since that would require running the code to resolve the bindings, whereas Dagger operates only from what it can see by reflecting on the types, and not actually running the code. This means you end up with a lot of these @Provides methods or annotating your actual types with details of what should be used to implement what.

FWIW so far I haven't adopted Dagger due to a few quirks that would require me to rethink a few bits, and I haven't taken the time to do that rethinking yet:

Community
  • 1
  • 1
Colin Alworth
  • 17,801
  • 2
  • 26
  • 39
  • _runtime_ is all relative ;-) From my PoV, `System.getProperty()` is _at runtime_ because you implement the selection in code, and the fact that the code is ultimately optimized into a constant and single code branch at compile-time is an implementation detail (using soft-permutations, it'll actually select the implementation at _runtime_, `System.getProperty()` then being replaced by a function resolving the current runtime value) – Thomas Broyer Apr 29 '15 at 15:43
  • By that logic, it wouldn't be a huge step to say that generators are runtime too... ;) Yes, fair point, though to my credit, the question specifically asked about runtime - the fact that the compiler can make the runtime decision at compile time is just an added bonus! – Colin Alworth Apr 29 '15 at 15:50
  • Generators run in the compiler VM, just like annotation processors run in the javac VM. That's definitely compile-time. – Thomas Broyer Apr 29 '15 at 18:02
  • 1
    Sorry, yes, obviously annotation processors run at Java compile time, and clearly a GWT generator will resolve its values at *gwt* compile-time, but likewise anything using the GWT compile may be resolved at gwt compile time, especially magic methods like GWT.foo or System.foo. I'm not suggesting anyone consider a generator to be runtime code, I'm just trying to draw a distinction that they are just as compile-time as a replace-with binding or any other constant code. Compiler can do it, and soft perms limit this, but only limits to a single runtime check on startup, not per-invocation. – Colin Alworth Apr 29 '15 at 18:06
  • @ColinAlworth Great answer. Please have a look at my edit. Do you have any idea on that? – Michael Apr 29 '15 at 20:36
4

First, you're not forced to replace your GWT.create() call. Because a GWT generator does 2 things, you could possibly (this is not always possible) replace the code-generation part with an annotation processor, and the "selection" part with simple <replace-with> rules (targeting classes that have been generated by the annotation processor).
Also note that GWT.create() is actually available outside a GWT context (e.g. on the server-side) using the ServerGwtBridge, which let's you register class instantiators. The annotation processor could generate such an instantiator, or you could code it using reflection.

If you do want to replace your GWT.create() calls, then you have to use a factory (either directly at the call point, or wrapped into a facade / proxy). That factory can possibly be generated by the annotation processor too.

As with any such factory, the code would be an if…else cascade or a switch…case, to ultimately new the appropriate (generated) class; but the conditions are very specific to your needs.

In a GWT context, you could use GWT.create(UserAgent.class).getCompileTimeValue() to access the user.agent binding property value, and thus replace the <when-property-is name="user.agent" value"…" /> of your *.gwt.xml with a switch…case (note that you'd then have to handle fallbacks yourself; e.g. ie9 falls back to ie8 when no rule specifically matches ie9). Other notable values would be LocaleInfo.getCurrentLocale() as a replacement for the locale binding property (but it's unlikely that your annotation processor knows all the possible values of that property, unless your pass them as options or the processor reads the GWT Module file – whose name you'd likely have to pass as an option too). LocaleInfo.getCurrentLocale().isRTL() is more interesting. You could also use things like GWT.isClient() or properties of Window.Navigator; and starting with GWT 2.8 you can use System.getProperty() to access your configuration and binding properties (e.g. System.getProperty("user.agent", "unknown")). Finally, of course, there's JSNI to do detect features supported by the current browser; but then that code cannot be optimized out by the GWT compiler and all the generated classes will be compiled in the final JS.

When you can't use such static values, then you can still pass the relevant context as arguments to the factory.

Thomas Broyer
  • 64,353
  • 7
  • 91
  • 164