svetlo / gwt-platform

Automatically exported from code.google.com/p/gwt-platform
0 stars 0 forks source link

Use generators to automatically create event and handler classes #69

Open GoogleCodeExporter opened 9 years ago

GoogleCodeExporter commented 9 years ago
GWT generators could be use to automatically create events and handler 
classes, saving boilerplate for applications that need a lot of events. A 
possible implementation could be:

@Events
public interface SettingsEvents {

  public interface UsernameChanged {
    public interface Event implements EventBase<String> {}
    public interface Handler implement EventHandler { 
      public void onUsernameChanged( Event e ); 
    }
  }

  public interface GroupAdded {
    public interface Event implements EventBase<Group> {}
    public interface Handler implement EventHandler{ 
      public void onPasswordChanged( Event e ); 
    }
  }

}

This is not the final interface, as some things are missing, but we could 
probably do something along these lines.

This feature is being considered. Please star and/or comment if this is 
something you'd like to see.

Original issue reported on code.google.com by philippe.beaudoin on 28 Apr 2010 at 5:31

GoogleCodeExporter commented 9 years ago
What do you think about that?

The event, maybe an abstract class so it extends GwtEvent and can be used 
outside GWTP:

public interface MyEvent extends EventBase {
  public Thing getThing();
  public String getName();
  public boolean isRemoved();
}

The handler:

public interface Handler implements EventHandler {
  public void onMyEvent(MyEvent event);
}

An interface to send event with some magical stuff:

public interface Sender implements EventSender<MyEvent> {
  // Create the event, fill its member with parameters and send it
  public void fire(HasEventBus target, Thing thing, String name, boolean removed);

  // Create the event but some member will not be set: thing will be null
  // The removed member will be set using the annotation
  @BooleanParam("removed", true)
  public void fireRemoved(HasEventBus target, String name)
}

The sender can be used like that:

GWT.create(Sender.class).fire(....);

Do you like it? I need more input...

Original comment by olivier....@free.fr on 20 Jul 2010 at 8:44

GoogleCodeExporter commented 9 years ago
I like it quite a bit! Although you probably need a way to define a static 
field somewhere for the event type, otherwise I don't see how you can register 
towards a specific event? 

Would it be possible to simplify even more for events that wrap up exactly one 
type of object. For example:

// Provided by GWTP:

public interface GenericEvent<T> {
  public T get();
}

public interface GenericHandler<T> extends EventHandler {
  public void onGenericEvent(GenericEvent<T> event);
}

public interface GenericEventSenter<T> extends EventSender<GenericEvent<T>> {
  public void fire(HasEventBus target, T object);
}

// Provided by the User:

public interface UserModifiedSender extends GenericEventSender<User> {
  public static Type<GenericEvent<User>> USER_MODIFIED_EVENT = new Type<GenericEvent<User>>();
}

And to create UserModifiedSender we could use Gin injection, maybe? It would be 
even cleaner than a GWT.create(...) and we really only need these to be 
singletons.

Original comment by philippe.beaudoin on 20 Jul 2010 at 9:03

GoogleCodeExporter commented 9 years ago
I like that ! I only have one question, what if I had more than one object 
wrapped inside my event ?

I know that in those case I should create them myself, but it would be awesome 
if we could do it !

Original comment by goudreau...@gmail.com on 20 Jul 2010 at 10:57

GoogleCodeExporter commented 9 years ago
In this case, Monaco's way would do it. I was just suggesting we provide a 
shortcut for single object events.

Original comment by philippe.beaudoin on 20 Jul 2010 at 11:15

GoogleCodeExporter commented 9 years ago
That whould be great to have both in that case.

Original comment by goudreau...@gmail.com on 20 Jul 2010 at 11:24

GoogleCodeExporter commented 9 years ago
What if instead of gwt-generators, we were use java apt (annotation 
processing).  

Then we could get rid of both the event/handler boilerplate and the 
action/result boilerplate.

Like gwt-mpv-apt, but simpler code that doesn't depend on unreleased libraries.

Original comment by brendanp...@gmail.com on 21 Jul 2010 at 2:23

GoogleCodeExporter commented 9 years ago
Here is @GenEvent working using apt.  
http://code.google.com/r/brendanpdoherty-apt/source/detail?r=c8e7856028ae81dee89
9a5d3f282957eafc5e537

Two annotations, @GenEvent says take this class and make an event and a 
handler. @Order allows you to specify the order of fields (otherwise they are 
alphabetical order).  @Order only really shows itself in the constructor.  And 
not needed if only one field.

@GenEvent
public class My {
    @Order(0) Thing thing;
    @Order(1) String name;
    @Order(2) boolean removed;
}

Will generate the same event/handler as described in comment #1.

Original comment by brendanp...@gmail.com on 21 Jul 2010 at 12:51

GoogleCodeExporter commented 9 years ago
Do you produce a static .fire method ? 
And a HasXXXHandlers ?

Also, I was wondering if those custom event where UiBinder ready... Just a 
though since not every events needs to be UiBinder ready

Is just that if Eclipse don't know the type of event before compile time it's 
kinda hard to write :

@UiHandler("CustomUiWidgetYouMadeName")
void onUnitChange(UnitChangeEvent event) {
}

Same thing goes for listening to an handler. 

Am I totally thinking wrong by saying this ? 

Original comment by goudreau...@gmail.com on 21 Jul 2010 at 1:00

GoogleCodeExporter commented 9 years ago
@Phil: 
So, we have 2 type of events:
- specific (my case)
- generic (your case)

A specific event will have only one Type value. So we can hide it by providing 
a addHandler(HandlerManager, Handler) method on the Sender. This allow the 
automatically generate the static field in the way GWT do (null by default, not 
null only once an handler has registered an handler...).

The generic event is, in fact, an event like ValueChangeEvent: it has a value 
typed by generics. Maybe this case must be handled with somethink like your 
@ProxyEvent annotation and the discussion we had about it. Because we may want 
to be able to use a generic event (not a generated class) but call a specific 
on* method linked to a specific Type. Here is an example:

interface MyHandler {
  public static final Type<...> DELETE - new ...;
  public static final Type<...> CREATE - new ...;

  @EventType("DELETE")
  onDelete(ValueEvent<Item> event);
  @EventType("CREATE")
  onCreate(ValueEvent<Item> event);
}

@brendanpdoherty:
I prefere generators. I don't like two step compilation: one before writing 
code to generate some code and one after for the real compilation. Of course, 
Eclipse do it automatically but I use maven. And all automatic code generation 
I used was complicated to configure with Maven, Ant, in complex build or for 
continious integration. But if people like that, ok. 

Original comment by olivier....@free.fr on 21 Jul 2010 at 3:14

GoogleCodeExporter commented 9 years ago
@christian

From what I understand, apt generates new classes as a precompilation process. 
These classes are free to contain new methods, static methods or anything you 
like. 

@brendan

I think we should strive to handle as much stuff as possible via GWT 
generators. I believe you can generate more sensible error messages and you 
don't have to worry about things like @Order(...). That being said, I think we 
should strive to make sure gwt-mvp-apt remains compatible with GWTP for those 
who prefer this approach, and because it's the only method I can see (short of 
SpringRoo ;)) for generating dispatcher boilerplate.

@olivier

Your case is more "generic" than mine, so I would say:
- customizable (your case)
- single-object (my case)
In fact, I would implement the single-object case as a specialization of the 
customizable case. I like the "ValueEvent" name. 

Re: addHandler
The "addHandler" method would be in the EventSender interface? So you would 
inject (or GWT.create) a MyEventSender in any object that wants to listen to 
the event? I think it's a good idea... However we might want to revisit the 
name "EventSender" in that case since it's meant to both fire and listen to the 
event. What about EventConnector? (i.e. a "plug" to connect the event into a 
bus.

Re: Custom method names
Ok. Let me try to write two scenarios here.

1) The user wants many custom onXXX methods:

// The user would write:
interface ItemHandler extends EventHandler {  
  public static final Type<ValueEvent<Item>> DELETE = new ...;
  public static final Type<ValueEvent<Item>> CREATE = new ...;
  @EventType("DELETE")
  public void onDelete(ValueEvent<Item> event);
  @EventType("CREATE")
  public void onCreate(ValueEvent<Item> event);
}

interface ItemConnector extends ValueEventConnector<Item, ItemHandler> { }

// In GWTP we have:
interface ValueEventConnector< T, H extends EventHandler > {
  public void fire(HasEventBus target, T content);
  public void addHandler(HasEventBus target, Type<ValueEvent<T>> type, H handler);
}

2) The user is fine with a onValueEvent() method:

// The user would write:
interface ItemChangedConnector extends ValueEventConnector<Item, 
ValueHandler<Item>> { }

// In GWTP we have:
interface ValueHandler<T> extends EventHandler {
  public void onValueEvent(ValueEvent<T> event);  // Maybe onValueChanged?
}

Original comment by philippe.beaudoin on 21 Jul 2010 at 5:30

GoogleCodeExporter commented 9 years ago
@Phil

I'm happy to use gwt-generators for events if that works better.  
The main reason I was investigating it was to find a better way of generating 
the dispatch boiler plate (but look at events first, because it seemed simpler).

If the concepts i've used were to make into mainline for generating dispatch 
boilerplate, in eclipse all you need to do is choose to enable annotation 
processing and select the gwtp jar.
I'd much prefer to have it in gwtp rather than depend on gwt-mvp-apt, which 
seems much more complex than it needs to be, and depends on libraries that I 
don't think are released.

@olivier

Wont you have the same problem of pre and post compiling using either 
gwt-generators or java annotation processing?  I'm interested to know how it 
will work better with gwt-generators.
Perhaps http://mojo.codehaus.org/apt-maven-plugin/ will help?

@christian

Given the class below, you get this output: 
http://code.google.com/r/brendanpdoherty-apt2/source/browse/samples/MoooEvent.ja
va

@GenEvent

public class Mooo {
 @Order(0) int apple;
 @Order(4) int bob;
 boolean cats;
 @Order(3) String[] banana;
}

Original comment by brendanp...@gmail.com on 21 Jul 2010 at 6:20

GoogleCodeExporter commented 9 years ago
Some comments with issue 145, we'll be trying to be more Gwt-Like and remove 
eventBus from events.

This line:
public static void fire(EventBus eventBus, int apple, java.lang.String[] 
banana, int bob, boolean cats) {

would become:
public static <T extends HasEventHandler> void fire(T source, int apple, 
java.lang.String[] banana, int bob, boolean cats) {

You could use HasHandlers instead in the meanwhile we implement HasEventHandler.

As for the output that's hell of a nice way to avoid boiler plate code !

Original comment by goudreau...@gmail.com on 21 Jul 2010 at 6:29

GoogleCodeExporter commented 9 years ago
Christian, I've commented in Issue 145 so as not to detract the thread here. 
Let's discuss it there.

Original comment by philippe.beaudoin on 21 Jul 2010 at 6:38

GoogleCodeExporter commented 9 years ago
@brendan
With apt, I write a class (eg My) and it generates a new class (eg MyEvent). I 
need MyEvent to write my code so I have to generate MyEvent from My before 
writing my code. This is the first step. The second, the real compilation, 
takes place after I wrote my code.

Using Eclipse it's seem natural because Eclipse build continually. But if you 
write code without Eclipse, you have to run the two pass. And when you change 
My, you must think to re-run the generation. It's a good way, for me, to commit 
bugs ("oops, I forgot the test my code with the new generated MyEvent before 
committing").

With generator, your code must compile before the generation takes place. Of 
course, this means you generate the code each time you compile. But your code 
is always well-formed for the compiler.

So, generators seems even more magic then apt ;).

Original comment by olivier....@free.fr on 21 Jul 2010 at 8:52

GoogleCodeExporter commented 9 years ago
I still don't get difference between apt and gwt-generators, but I'd like to 
understand it, as it's the only method for generating dispatch boilerplate (gwt 
generators wont be able to do it).

Am i correct that both apt and gwt-generator need to run at compile time?

Is the problem that maven is not automatically running apt, but maven is 
automatically running the gwt-generator?

Would the problem be solved by using apt-maven-plugin and adding the following 
xml to your pom?

<project>
  ...
    <build>
      ...
      <plugins>
        ...
        <plugin>
          <groupId>org.codehaus.mojo</groupId>
          <artifactId>apt-maven-plugin</artifactId>
          <version>1.0-alpha-3</version>
          <configuration>
            <factory>com.gwtplatform.annotation.processor.GenDispatchAptFactory</factory>
          </configuration>
          <dependencies>
            <dependency>
              <groupId>com.acme</groupId>
              <artifactId>com.gwtplatform</artifactId>

              <version>1.0</version>
            </dependency>
          </dependencies>
        </plugin>
        ...
      </plugins>
      ...
    </build>
  ...
</project>

Original comment by brendanp...@gmail.com on 21 Jul 2010 at 10:11

GoogleCodeExporter commented 9 years ago
@brendan
We must continue our discussion here:
http://groups.google.com/group/gwt-platform/browse_frm/thread/c15c7991c25c9776

Original comment by olivier....@free.fr on 21 Jul 2010 at 10:28

GoogleCodeExporter commented 9 years ago
I'm lost ;)... Let me try to merge all ideas.

First we need an event. The event class can be:

(1) A single-object/generic event. This event, ValuedEvent<T>, is an 
"hard-coded" event class (not generated). The only problem is the "dispath" 
method but we will see it later.

(2) A customizable/specific event. The developer must define an abstract class 
extending GwtEvent, to keep us in the GWT-way. All abstract methods of this 
class must be getters and will be the data members of this event.

public abstract class MyEvent extends GwtEvent<...> {
  public abstract Thing getThing();
  public abstract String getName();
}

Next, we need an handler and a type. One type for one handler method.

(3) We can use the same way as GWT. But one event will have only one type and 
so one handler method. I don't like that

(4) We can link the type with the handler. For simple handler, the developer 
define the following class (XXX for any name, can be all different):

public interface MySimpleHandler {
  public static final Type<...> XXX = ...;
  public void onXXX(XXXEvent event);
}

For handler with multiple possible method for one event, we can use names:

public interface MyComplexHandler {
  public static final Type<...> YYY = ... ;
  public static final Type<...> ZZZ = ... ;

  @EventType("ZZZ")
  public void onUUU(XXXEvent event);

  @EventType("YYY")
  public void onVVV(XXXEvent event);
}

With this approach, the dispath method of the event can be implemented using 
many ways. We can link each event with one handler. This is not good because we 
can do (1) and the developer can't reuse events of (2). The best is to use a 
dispatcher to call the good handler method. This dispatcher is included in the 
event and used by the dispath method.

This approach allow us to forget the addHandler because each event type can be 
directly accessed by the developer to register handlers.

Now how to send an event?

(5) To send a ValuedEvent (1), we can include the following method in the event 
class:

public static void fire(HasHandlers source, Type<...> type, T value);

(6) To send a customizable event (2), we need a sender. So, the developer 
define it like that:

public interface MySender {
  public void fire(HasHandlers source, Type<...> type, ...); 
}

The final "..." are each member of MyEvent. And we can include all stuff I told 
in comment #1.

What do you think? Do I forgot something?

Original comment by olivier....@free.fr on 21 Jul 2010 at 10:29

GoogleCodeExporter commented 9 years ago
It's not good. The event can't link a type to an handler method. We need some 
code generation.

Maybe, (1) must be an abstract class with the dispatch method not implemented. 
The developer must create it's own class to link ValuedEvent to an handler:

public abstract class MyEvent extends ValuedEvent<Thing, MyHandler> {}

The problem is we need also a sender (6) for ValuedEvent. The (5) can't now be 
done.

For (2), the event must be linked to an handler (it's the "..." of GwtEvent):

public abstract class MyEvent extends GwtEvent<...> {
  public abstract Thing getThing();
  public abstract String getName();
}

However, we can't now reuse events.

Original comment by olivier....@free.fr on 21 Jul 2010 at 10:46

GoogleCodeExporter commented 9 years ago
Actually, Brandan's way create the class in a real usable file... rendering 
them visible for you to use in your code directly ! And that's what I like 
about it the most !

Original comment by goudreau...@gmail.com on 21 Jul 2010 at 10:52

GoogleCodeExporter commented 9 years ago
Oh and in the generated event, there was a static fire method !

Original comment by goudreau...@gmail.com on 21 Jul 2010 at 10:53

GoogleCodeExporter commented 9 years ago
I'm stupid, the sender is the dispatcher... I forgot that ;).

So (1) must use a sender, (5) can't be used. In my code sample for MySender, I 
forgot the "extends...":

public interface MySender extends Sender<MyHandler> {...}

The sender is generated "using" Gin (as a singleton). When a fire method is 
called. The sender create the event, sets the data members and the type, add 
itself as the dispatcher and fire the event to source.

The dispath method of the event call a dispath (default-visible) method which 
choose the right method depending on the type.

So we can do (1), (2) and (4) as-is. (3) is to avoid. (5) can't be done. (6) 
must follow this comment.

Original comment by olivier....@free.fr on 21 Jul 2010 at 10:53

GoogleCodeExporter commented 9 years ago
Instead of a Sender we could have a Connector that would have both a .fire() 
and a .addHandler() method. See Comment 10 above for details on how I would do 
this.

In this case, the type doesn't have to be provided by the user (unless he wants 
to use type-injected event. That is, a single event class with different event 
types, which we could support also).

I will be away for the next 2 weeks, but this issue is a priority for me, so if 
you want to start working on it, go ahead!

Original comment by philippe.beaudoin on 23 Jul 2010 at 10:46

GoogleCodeExporter commented 9 years ago

Original comment by olivier....@free.fr on 27 Jul 2010 at 3:31

GoogleCodeExporter commented 9 years ago
I've done a little and minimalist prototype.

http://codereview.appspot.com/1942045

Currently I've only done generators for the event and for an interface I 
currently calling Sender. Now there is many things possibles.

1) I've choose to define a @EventClass annotation. So one sender can send many 
different events. Is it a good choice?

2) Currently, the Sender interface have only fire methods. I've choose to use 
HasHandlers (and not EventBus or HasEventBus) so this code can be used with 
standard events. Is it ok?

3) If the Sender interface use HasHandler, it can't include an addHandler 
method. A such method needs a HandlerManager (or access to protected methods 
like the one in Widget class). That's why it's not a Connector for instance.

4) The type is currently defined on the event class. Maybe we can remove this 
need and require the user to define a getType method on Sender interface. With 
(1), this will result in something like:

public interface MySender {
  @EventClass(DeleteEvent.class)
  public Type<DeleteHandler> getDeleteType();

  @Eventclass(DeleteEvent.class)
  public void fireDelete(HasHandlers source...);

  @EventClass(CreateEvent.class)
  public Type<CreateHandler> getCreateType();

  @Eventclass(CreateEvent.class)
  public void fireCreate(HasHandlers source...);
  ...
}

Using such way, we can keep the GWT optimization of really firing an event only 
if getType was called.

Note that the generators are not safe (no error check...) and I also used it to 
find a way to rework the proxy generator.

Original comment by olivier....@free.fr on 18 Aug 2010 at 9:35

GoogleCodeExporter commented 9 years ago

Original comment by philippe.beaudoin on 22 Sep 2010 at 1:35

GoogleCodeExporter commented 9 years ago

Original comment by philippe.beaudoin on 16 Jun 2011 at 1:21