All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.outbrain.ob1k.concurrent.combiners.Combiner Maven / Gradle / Ivy

package com.outbrain.ob1k.concurrent.combiners;

import com.outbrain.ob1k.concurrent.*;
import com.outbrain.ob1k.concurrent.handlers.FutureSuccessHandler;
import com.outbrain.ob1k.concurrent.handlers.SuccessHandler;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicReferenceArray;

import static com.outbrain.ob1k.concurrent.ComposableFutures.fromValue;
import static com.outbrain.ob1k.concurrent.ComposableFutures.schedule;

/**
 * combines two or more futures into one.
 *
 * @author aronen on 9/2/14.
 */
public class Combiner {

  public static  ComposableFuture any(final Iterable> elements) {
    return ComposableFutures.build(new Producer() {
      @Override
      public void produce(final Consumer consumer) {
        final AtomicBoolean done = new AtomicBoolean();
        for (final ComposableFuture future : elements) {
          future.consume(new Consumer() {
            @Override
            public void consume(final Try result) {
              if (done.compareAndSet(false, true)) {
                consumer.consume(result);
              }
            }
          });
        }
      }
    });
  }

  public static  ComposableFuture> all(final boolean failOnError, final Iterable> elements) {
    final Map> elementsMap = new HashMap<>();
    int index = 0;
    for (final ComposableFuture element : elements) {
      elementsMap.put(index++, element);
    }

    return all(failOnError, elementsMap).continueOnSuccess(new SuccessHandler, List>() {
      @Override
      public List handle(final Map result) {
        return new ArrayList<>(result.values());
      }
    });
  }

  public static  ComposableFuture> all(final boolean failOnError, final Map> elements) {
    return first(elements, elements.size(), failOnError, null, null);
  }

  private static class Status {
    final int total;
    final int minSuccessful;

    final int results;
    final int successfulResults;
    final boolean finished;

    private Status(final int total, final int minSuccessful,
                   final int results, final int successfulResults,
                   final boolean finished) {
      this.total = total;
      this.minSuccessful = minSuccessful;
      this.results = results;
      this.successfulResults = successfulResults;
      this.finished = finished;
    }

    boolean isDone() {
      return finished || results == total || successfulResults >= minSuccessful;
    }
  }

  private static class KeyValue {
    final K key;
    final V value;

    private KeyValue(final K key, final V value) {
      this.key = key;
      this.value = value;
    }
  }

  public static  ComposableFuture> first(final Map> elements, final int numOfSuccess,
                                                         final boolean failOnError, final Long timeout, final TimeUnit timeUnit) {

    if (elements.isEmpty()) {
      final Map empty = new HashMap<>();
      return fromValue(empty);
    }

    return ComposableFutures.build(new Producer>() {
      @Override
      public void produce(final Consumer> consumer) {
        final AtomicReferenceArray> results = new AtomicReferenceArray<>(elements.size());
        final AtomicReference status = new AtomicReference<>(new Status(elements.size(), numOfSuccess, 0, 0, false));
        int counter = 0;

        if (timeout != null) {
          schedule(new Callable() {
            @Override
            public Object call() throws Exception {
              while (true) {
                final Status currentStatus = status.get();
                if (currentStatus.isDone())
                  break;

                final Status newStatus = new Status(currentStatus.total, currentStatus.minSuccessful,
                    currentStatus.results, currentStatus.successfulResults, true);

                final boolean success = status.compareAndSet(currentStatus, newStatus);
                if (success) {
                  consumer.consume(Try.fromValue(collectResults(results)));
                  break;
                }
              }
              return null;
            }
          }, timeout, timeUnit);
        }

        for (final Map.Entry> element : elements.entrySet()) {
          final ComposableFuture future = element.getValue();
          final K key = element.getKey();
          final int index = counter;
          counter++;

          future.consume(new Consumer() {
            @Override
            public void consume(final Try result) {
              if (result.isSuccess()) {
                final T element = result.getValue();
                results.set(index, new KeyValue<>(key, element));

                while (true) {
                  final Status currentStatus = status.get();
                  if (currentStatus.isDone())
                    break;

                  final Status newStatus = new Status(currentStatus.total, currentStatus.minSuccessful,
                      currentStatus.results + 1, currentStatus.successfulResults + 1, false);

                  final boolean success = status.compareAndSet(currentStatus, newStatus);
                  if (success) {
                    if (newStatus.isDone()) {
                      consumer.consume(Try.fromValue(collectResults(results)));
                    }
                    break;
                  }
                }

              } else {
                final Throwable error = result.getError();
                while (true) {
                  final Status currentStatus = status.get();
                  if (currentStatus.isDone())
                    break;

                  final Status newStatus = new Status(currentStatus.total, currentStatus.minSuccessful,
                      currentStatus.results + 1, currentStatus.successfulResults, failOnError);

                  final boolean success = status.compareAndSet(currentStatus, newStatus);
                  if (success) {
                    if (failOnError) {
                      consumer.consume(Try.>fromError(error));
                    } else {
                      if (newStatus.isDone()) {
                        consumer.consume(Try.fromValue(collectResults(results)));
                      }
                    }
                    break;
                  }
                }
              }
            }
          });
        }
      }
    });
  }

  private static  Map collectResults(final AtomicReferenceArray> elements) {
    final Map result = new HashMap<>();
    for (int i=0; i< elements.length(); i++) {
      final KeyValue element = elements.get(i);
      if (element != null && element.value != null) {
        result.put(element.key, element.value);
      }
    }

    return result;
  }

  public static  ComposableFuture combine(final ComposableFuture left, final ComposableFuture right, final BiFunction combiner) {
    final ComposableFuture> upliftLeft = left.continueOnSuccess(new SuccessHandler>() {
      @Override
      public BiContainer handle(final T1 result) {
        return new BiContainer<>(result, null);
      }
    });

    final ComposableFuture> upliftRight = right.continueOnSuccess(new SuccessHandler>() {
      @Override
      public BiContainer handle(final T2 result) {
        return new BiContainer<>(null, result);
      }
    });

    final HashMap>> elements = new HashMap<>();
    final String leftKey = "left";
    final String rightKey = "right";
    elements.put(leftKey, upliftLeft);
    elements.put(rightKey, upliftRight);

    return all(true, elements).continueOnSuccess(new SuccessHandler>, R>() {
      @Override
      public R handle(final Map> result) throws ExecutionException {
        final BiContainer leftContainer = result.get(leftKey);
        final BiContainer rightContainer = result.get(rightKey);
        return combiner.apply(leftContainer.left, rightContainer.right);
      }
    });
  }

  public static  ComposableFuture combine(final ComposableFuture left, final ComposableFuture right, final FutureBiFunction combiner) {
    final ComposableFuture> upliftLeft = left.continueOnSuccess(new SuccessHandler>() {
      @Override
      public BiContainer handle(final T1 result) {
        return new BiContainer<>(result, null);
      }
    });

    final ComposableFuture> upliftRight = right.continueOnSuccess(new SuccessHandler>() {
      @Override
      public BiContainer handle(final T2 result) {
        return new BiContainer<>(null, result);
      }
    });

    final HashMap>> elements = new HashMap<>();
    final String leftKey = "left";
    final String rightKey = "right";
    elements.put(leftKey, upliftLeft);
    elements.put(rightKey, upliftRight);

    return all(true, elements).continueOnSuccess(new FutureSuccessHandler>, R>() {
      @Override
      public ComposableFuture handle(final Map> result) {
        final BiContainer leftContainer = result.get(leftKey);
        final BiContainer rightContainer = result.get(rightKey);
        return combiner.apply(leftContainer.left, rightContainer.right);
      }
    });
  }

  public static  ComposableFuture combine(final ComposableFuture first,
                                                            final ComposableFuture second,
                                                            final ComposableFuture third,
                                                            final TriFunction combiner) {

    final ComposableFuture> upliftFirst = first.continueOnSuccess(new SuccessHandler>() {
      @Override
      public TriContainer handle(final T1 result) {
        return new TriContainer<>(result, null, null);
      }
    });

    final ComposableFuture> upliftSecond = second.continueOnSuccess(new SuccessHandler>() {
      @Override
      public TriContainer handle(final T2 result) {
        return new TriContainer<>(null, result, null);
      }
    });

    final ComposableFuture> upliftThird = third.continueOnSuccess(new SuccessHandler>() {
      @Override
      public TriContainer handle(final T3 result) {
        return new TriContainer<>(null, null, result);
      }
    });

    final HashMap>> elements = new HashMap<>();
    final String firstKey = "first";
    final String secondKey = "second";
    final String thirdKey = "third";

    elements.put(firstKey, upliftFirst);
    elements.put(secondKey, upliftSecond);
    elements.put(thirdKey, upliftThird);

    return all(true, elements).continueOnSuccess(new SuccessHandler>, R>() {
      @Override
      public R handle(final Map> result) throws ExecutionException {
        final TriContainer firstContainer = result.get(firstKey);
        final TriContainer secondContainer = result.get(secondKey);
        final TriContainer thirdContainer = result.get(thirdKey);

        return combiner.apply(firstContainer.first, secondContainer.second, thirdContainer.third);
      }
    });

  }

  public static  ComposableFuture combine(final ComposableFuture first,
                                                            final ComposableFuture second,
                                                            final ComposableFuture third,
                                                            final FutureTriFunction combiner) {

    final ComposableFuture> upliftFirst = first.continueOnSuccess(new SuccessHandler>() {
      @Override
      public TriContainer handle(final T1 result) {
        return new TriContainer<>(result, null, null);
      }
    });

    final ComposableFuture> upliftSecond = second.continueOnSuccess(new SuccessHandler>() {
      @Override
      public TriContainer handle(final T2 result) {
        return new TriContainer<>(null, result, null);
      }
    });

    final ComposableFuture> upliftThird = third.continueOnSuccess(new SuccessHandler>() {
      @Override
      public TriContainer handle(final T3 result) {
        return new TriContainer<>(null, null, result);
      }
    });

    final HashMap>> elements = new HashMap<>();
    final String firstKey = "first";
    final String secondKey = "second";
    final String thirdKey = "third";

    elements.put(firstKey, upliftFirst);
    elements.put(secondKey, upliftSecond);
    elements.put(thirdKey, upliftThird);

    return all(true, elements).continueOnSuccess(new FutureSuccessHandler>, R>() {
      @Override
      public ComposableFuture handle(final Map> result) {
        final TriContainer firstContainer = result.get(firstKey);
        final TriContainer secondContainer = result.get(secondKey);
        final TriContainer thirdContainer = result.get(thirdKey);

        return combiner.apply(firstContainer.first, secondContainer.second, thirdContainer.third);
      }
    });

  }

}