GWT, Java 8 and Future

Hello.
I think many of you know about the release of Java 8, and what innovations it brings. Unfortunately, the latest version of GWT(2.6.0) at the moment still does not support lambdas and default methods in interfaces. Because the GWT framework is quite popular, many people often have to face the development on it, I was eager to try to write in GWT using the newly added language features.

In this article we will talk about how to add support for Java 8 features in GWT, and about what, actually, is all this necessary — for example the Future. If you have ever worked with GWT, it seems all the shortcomings and inconvenience as AMI when accessing the server. At that time, as in the javascript world many are already using Future/Promise, and in some languages this concept is built into the standard library in GWT are still used callbacks in any kind of interaction between client and server, whether RemoTeServiceServlet, RPC or RequestFactory Dispatch.
So, let's start.

Sobrien GWT


After a short search, was found experimental fork of GWT. It is stated quite tolerable support for Java 8 (except for the JRE Emulation Library). In fact, it was not so. The version of jdt-core, which is used in this fork, is quite old and not able to properly lead types. Had to raise the version to 3.9.5, the benefit rule should have been a little (only changed some method signatures).
the
    the
  • so, take the source code for gwt here and the gwt-tools here.
  • the
  • After cloning, you must specify the environment variable GWT_TOOLS=path/to/gwt-tools.
  • the
  • Then go to the installation directory of the GWT source and run ant build.

Ready, in the directory of gwt-sandbox/build/lib libraries were established gwt-dev.jar, gwt-user.jar, gwt-codeserver.jar.

Run RestyGWT



For our example we will use a modified library RestyGWT.
Here is RestyGWT-enabled Future.

Now, instead
the
void makeServerRequest(MethodCallback<Void> callback);

the interaction with the server would look like this:
the
Future<Void> makeServerRequest();


It seemed to me a very attractive implementation of Future in Scala, and wanted to do something like that. Here's what happened:
Interface
public interface Future<T> {

public void onComplete(Consumer<Try<T>> consumer);

public void handle(Consumer < Throwable > errorHandler, Consumer<T> successHandler);

public void forEach(Consumer < T > consumer);

public <R> Future<R> map(Function < T, R > function);

public <R> Future<R> flatMap(Function < T, Future<R>> function);

public T get();

}


Implementation
public class FutureImpl<T> implements Future<T> {

private List<Consumer<Try<T>>> completeFunctions = new ArrayList<>();
private Option<Try<T>> result = Option.getEmpty();

public FutureImpl() {
}

@Override
public void onComplete(Consumer<Try<T>> consumer) {
result.forEach(consumer::accept);
completeFunctions.add(consumer);
}

@Override
public void handle(Consumer < Throwable > errorHandler, Consumer<T> successHandler) {
onComplete((result) -> {
if (result.isSuccess()) successHandler.accept(result.get());
else errorHandler.accept(result.getCause());
});
}

public void completeWithResult(Try<T> result) {
this.result = Option.create(result);
for (Consumer<Try<T>> completeFunction : completeFunctions) {
completeFunction.accept(result);
}
}

public void completeWithSuccess(T result) {
completeWithResult(new Success<T>(result));
}

public void completeWithFailure(Throwable ex) {
completeWithResult(new Failure<T>(ex));
}

@Override
public void forEach(Consumer < T > consumer) {
onComplete((result) -> {
if (result.isSuccess()) {
consumer.accept(result.get());
}
});
}

@Override
public <R> Future<R> map(Function < T, R > function) {
FutureImpl<R> future = new FutureImpl<R>();
onComplete((result) -> {
if (result.isSuccess()) {
future.completeWithSuccess(function.apply(result.get()));
}
});
return future;
}

@Override
public <R> FutureImpl<R> flatMap(Function < T, Future<R>> function) {
FutureImpl<R> mapped = new FutureImpl<R>();
onComplete((result) -> {

Future<R> f = function.apply(result.get());
f.onComplete(mapped::completeWithResult);
}
});
return mapped;
}

@Override
public T get() {
return result.get().get();
}

} 



Usage


What we all did? I'll try to explain what is called "fingers".
Let's say we have a service to retrieve a list of countries and regions:

the
@Path("../service")
@Consumes(MediaType.APPLICATION_JSON)
public interface CallbackCountryService extends RestService {

@GET
@Path("/countires/")
public void getCountries(MethodCallback<List<Country>> callback);

@GET
@Path("/regions/{countryId}/")
public void getRegions(@PathParam("countryId") Integer countryId, MethodCallback<List<Region>> callback);

}


Here are a few examples of usage of this service with the use of the Future without him:
    the
  1. With a simple example. We want to take a list of countries and display it in our View:
    No Future:

    the
    countryService.getCountries(new MethodCallback<List<Country>>() {
    
    @Override
    public void onFailure(Method Method, Throwable exception) {
    
    }
    
    @Override
    public void onSuccess(Method Method, List<Country> response) {
    view.displayCountries(response);
    }
    });
    


    With Future:

    the
    countryService.getCountries().forEach(view::displayCountries); 
    

    The forEach method is a kind of onSuccess as a. That is, if successful, will be called method displayCountries from View.

  2. a more complex Example. Suppose we want to handle the exception and display it.
    No Future:

    the

    countryService.getCountries(new MethodCallback<List<Country>>() {
    
    @Override
    public void onFailure(Method Method, Throwable exception) {
    view.displayError(exception.getMessage());
    }
    
    @Override
    public void onSuccess(Method Method, List<Country> response) {
    view.displayCountries(response);
    }
    });
    


    With Future:

    the
    countryService.getCountries().handle(t -> view.displayError(t.getMessage()), view::displayCountries);
    

    In the method Future.handle we pass two functions. One is responsible for error processing, the second processing success result.

    the
  3. We need to take the first country from the list and display the list of regions for her:
    No Future:

    the
    countryService.getCountries(new MethodCallback<List<Country>>() {
    @Override
    public void onFailure(Method Method, Throwable exception) {
    view.displayError(exception.getMessage());
    }
    
    @Override
    public void onSuccess(Method Method, List<Country> response) {
    countryService.getRegions(response.get(0).getId(), new MethodCallback<List<Region>>() {
    
    @Override
    public void onFailure(Method Method, Throwable exception) {
    view.displayError(exception.getMessage());
    }
    
    @Override
    public void onSuccess(Method Method, List<Region> response) {
    view.displayRegions(response);
    }
    }); 
    }
    });
    

    Using the Future:

    the
    countryService.getCountries()
    .map(countries - > countries.get(0).getId())
    .flatMap(countryService::getRegions)
    .handle(err -> view.displayError(err.getMessage()), view::displayRegions);
    

    First we convert aFuture<List> in the Future, it will return countryId if it succeeds. Then we get Future with a list of regions. Finally, the processed result.


    We need to get all regions of all countries.
    The solution of this problem without the use of the Future is rather cumbersome. So here is a second option.

    Break down the task into several stages:

    the
    Future<List<Future<List<Region>>>> regionFutures = countryService.getCountries()
    .map(
    countries ->
    countries.map(country -> countryService.getRegions(country.getId()))
    );
    

    Here we get a list of Future all regions.

    The following transformation should allow the
    List<Future> Future<List>. That is, our Future will be executed when all of the Future within the list will be completed.

    Future<Future<List<List<Region>>>> regions = regionFutures.map(FutureUtils::toFutureOfList);


    And finally, given
    Future<Future> to Future and transform a list of lists to a one-dimensional list:

    FutureUtils .flatten(regions) .map(ListUtils::flatten) .handle(err -> view.displayError(err.getMessage()), view::displayRegions());


    The disadvantage of the last example is that it is quite difficult to understand to a person who was not involved in writing this code. However, the solution is quite compact and has a right to exist.



    PS For those who want to try Java 8 in GWT, prepared sample project with examples from the article. The project laveneziana, it can be run using mvn jetty:run-exploded.
    It should be understood that all of the supplied libraries is better not to use in real projects. Support Future in RestyGWT is quite crude, not yet tested and not yet able to work, for example with JSONP requests. The support for default interfaces and lambda works quite confidently, although the compilation does not always go when you use lambdas in static methods.
Article based on information from habrahabr.ru

Комментарии

Популярные сообщения из этого блога

Car navigation in detail

PostgreSQL: Analytics for DBA

Google has launched an online training course advanced search