package com.linkedin.parseq;

import java.util.function.Function;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.linkedin.parseq.function.Consumer3;
import com.linkedin.parseq.function.Function1;
import com.linkedin.parseq.internal.ArgumentUtil;
import com.linkedin.parseq.internal.Continuations;
import com.linkedin.parseq.promise.Promise;
import com.linkedin.parseq.promise.PromisePropagator;
import com.linkedin.parseq.promise.PromiseResolvedException;
import com.linkedin.parseq.promise.Promises;
import com.linkedin.parseq.promise.Settable;
import com.linkedin.parseq.promise.SettablePromise;
import com.linkedin.parseq.trace.Relationship;
import com.linkedin.parseq.trace.ResultType;
import com.linkedin.parseq.trace.ShallowTraceBuilder;
import com.linkedin.parseq.trace.TraceBuilder;

/**
 * FusionTask implements optimization in case when multiple synchronous
 * transformations are applied in sequence. Instead of rescheduling every transformation
 * as a separate task it is faster to apply transformations in a chain.
 *
 * This is internal class and it should not be extended or used outside ParSeq.
 * It may change or be removed at any time in a backwards incompatible way.
 *
 * @author Jaroslaw Odzga (jodzga@linkedin.com)
 *
 * @param <S> type of source
 * @param <T> type of target
 */
class FusionTask<S, T> extends BaseTask<T> {

  private static final Logger LOGGER = LoggerFactory.getLogger(FusionTask.class);

  private static final Continuations CONTINUATIONS = new Continuations();

  /* Encapsulates transformation from source to target, includes all predecessors' transformations, can't be null */
  private final Consumer3<FusionTraceContext, Promise<S>, Settable<T>> _propagator;

  /* Asynchronous task that will complete source promise, can be null in which case propagator must not depend on source */
  private final Task<S> _asyncTask;

  /* Trace builder for the predecessor, used for building trace relationships, can be null */
  private final ShallowTraceBuilder _predecessorShallowTraceBuilder;

  private FusionTask(final String desc, final Task<S> task, final PromisePropagator<S, T> propagator) {
    super(desc, TaskType.FUSION.getName());
    _propagator = completing(adaptToAcceptTraceContext(propagator));
    _asyncTask = task;
    _predecessorShallowTraceBuilder = null;
  }

  private <R> FusionTask(final String desc, final FusionTask<S, R> predecessor,
      final PromisePropagator<R, T> propagator) {
    super(desc, TaskType.FUSION.getName());
    _asyncTask = predecessor._asyncTask;
    _predecessorShallowTraceBuilder = predecessor.getShallowTraceBuilder();
    _propagator = completing(compose(predecessor._propagator, adaptToAcceptTraceContext(propagator)));
  }

  /**
   * Drops FusionTraceContext on the floor. This method adapts pure transformation to be used within FusionTask
   * that needs additional information (FusionTraceContext) to build trace.
   */
  private static <A, B> Consumer3<FusionTraceContext, Promise<A>, Settable<B>> adaptToAcceptTraceContext(
      final PromisePropagator<A, B> propagator) {
    return (traceContext, src, dest) -> propagator.accept(src, dest);
  }

  private void transitionDone(final FusionTraceContext traceContext) {
    addRelationships(traceContext);
    transitionPending();
    transitionDone();
  }

  /**
   * Has current task initiated propagation?
   */
  private boolean isPropagationInitiator(final FusionTraceContext traceContext) {
    return traceContext.getPropagationInitiator().getId().equals(getId());
  }

  /**
   * Depending on the context, returns trace builder that will contain information about the
   * transformation. This is used to avoid wrapping single transformation with "fused" parent.
   */
  private ShallowTraceBuilder getEffectiveShallowTraceBuilder(final FusionTraceContext traceContext) {
    if (isPropagationInitiator(traceContext)) {
      return traceContext.getSurrogate();
    } else {
      return _shallowTraceBuilder;
    }
  }

  private void addRelationships(final FusionTraceContext traceContext) {
    final ShallowTraceBuilder effectoveShallowTraceBuilder = getEffectiveShallowTraceBuilder(traceContext);
    TraceBuilder builder = getTraceBuilder();
    builder.addRelationship(Relationship.PARENT_OF, traceContext.getParent().getShallowTraceBuilder(), effectoveShallowTraceBuilder);
    if (_predecessorShallowTraceBuilder != null) {
      builder.addRelationship(Relationship.SUCCESSOR_OF, effectoveShallowTraceBuilder, _predecessorShallowTraceBuilder);
    }
  }

  private void addPotentialRelationships(final FusionTraceContext traceContext, final TraceBuilder builder) {
    final ShallowTraceBuilder effectoveShallowTraceBuilder = getEffectiveShallowTraceBuilder(traceContext);
    builder.addRelationship(Relationship.POTENTIAL_CHILD_OF, effectoveShallowTraceBuilder, traceContext.getParent().getShallowTraceBuilder());
    if (_predecessorShallowTraceBuilder != null) {
      builder.addRelationship(Relationship.POSSIBLE_SUCCESSOR_OF, effectoveShallowTraceBuilder, _predecessorShallowTraceBuilder);
    }
  }

  /**
   * Composes transformation with the transformation of the predecessor.
   */
  private <R> Consumer3<FusionTraceContext, Promise<S>, Settable<T>> compose(
      final Consumer3<FusionTraceContext, Promise<S>, Settable<R>> predecessor,
      final Consumer3<FusionTraceContext, Promise<R>, Settable<T>> propagator) {

    return (traceContext, src, dst) -> {

      /*
       * At this point we know that transformation chain's length > 1.
       * This code is executed during task execution, not when task is constructed.
       */
      traceContext.createSurrogate();

      predecessor.accept(traceContext, src, new Settable<R>() {

        @Override
        public void done(R value) throws PromiseResolvedException {
          try {
            /* Track start time of the transformation. End time is tracked by closure created by completing() */
            getEffectiveShallowTraceBuilder(traceContext).setStartNanos(System.nanoTime());
            propagator.accept(traceContext, Promises.value(value), dst);
          } catch (Exception e) {
            /* This can only happen if there is an internal problem. Propagators should not throw any exceptions. */
            LOGGER.error("ParSeq ingternal error. An exception was thrown by propagator", e);
          }
        }

        @Override
        public void fail(Throwable error) throws PromiseResolvedException {
          try {
            /* Track start time of the transformation. End time is tracked by closure created by completing() */
            getEffectiveShallowTraceBuilder(traceContext).setStartNanos(System.nanoTime());
            propagator.accept(traceContext, Promises.error(error), dst);
          } catch (Exception e) {
            /* This can only happen if there is an internal problem. Propagators should not throw any exceptions. */
            LOGGER.error("ParSeq ingternal error. An exception was thrown by propagator.", e);
          }
        }
      });
    };
  }

  /**
   * Adds closure to the propagator that maintains correct state of the task e.g. transitions between states,
   * adds trace etc.
   */
  private Consumer3<FusionTraceContext, Promise<S>, Settable<T>> completing(
      final Consumer3<FusionTraceContext, Promise<S>, Settable<T>> propagator) {
    return (traceContext, src, dest) -> {
      final SettablePromise<T> settable = FusionTask.this.getSettableDelegate();

      if (isPropagationInitiator(traceContext)) {
        /* BaseTask's code handles completing the parent task
         * we need to handle tracing of a surrogate task here
         */
        propagator.accept(traceContext, src, new Settable<T>() {
          @Override
          public void done(T value) throws PromiseResolvedException {
            final ShallowTraceBuilder shallowTraceBuilder = traceContext.getSurrogate();
            if (shallowTraceBuilder != null) {
              addRelationships(traceContext);
              final long endNanos = System.nanoTime();
              shallowTraceBuilder.setPendingNanos(endNanos);
              shallowTraceBuilder.setEndNanos(endNanos);
              final Function<T, String> traceValueProvider = _traceValueProvider;
              shallowTraceBuilder.setResultType(ResultType.SUCCESS);
              if (traceValueProvider != null) {
                try {
                  shallowTraceBuilder.setValue(traceValueProvider.apply(value));
                } catch (Exception e) {
                  shallowTraceBuilder.setValue(Exceptions.failureToString(e));
                }
              }
            }
            dest.done(value);
          }

          @Override
          public void fail(Throwable error) throws PromiseResolvedException {
            final ShallowTraceBuilder shallowTraceBuilder = traceContext.getSurrogate();
            if (shallowTraceBuilder != null) {
              addRelationships(traceContext);
              final long endNanos = System.nanoTime();
              shallowTraceBuilder.setPendingNanos(endNanos);
              shallowTraceBuilder.setEndNanos(endNanos);
              if (Exceptions.isEarlyFinish(error)) {
                shallowTraceBuilder.setResultType(ResultType.EARLY_FINISH);
              } else {
                shallowTraceBuilder.setResultType(ResultType.ERROR);
                shallowTraceBuilder.setValue(Exceptions.failureToString(error));
              }
            }
            dest.fail(error);
          }
        });
      } else if (transitionRun(traceContext.getParent().getTraceBuilder())) {
        markTaskStarted();
        //non-parent task executed for the first time
        traceContext.getParent().getTaskLogger().logTaskStart(this);

        Settable<T> next = new Settable<T>() {
          @Override
          public void done(final T value) throws PromiseResolvedException {
            try {
              transitionDone(traceContext);
              final Function<T, String> traceValueProvider = _traceValueProvider;
              _shallowTraceBuilder.setResultType(ResultType.SUCCESS);
              if (traceValueProvider != null) {
                try {
                  _shallowTraceBuilder.setValue(traceValueProvider.apply(value));
                } catch (Exception e) {
                  _shallowTraceBuilder.setValue(Exceptions.failureToString(e));
                }
              }
              settable.done(value);
              traceContext.getParent().getTaskLogger().logTaskEnd(FusionTask.this, _traceValueProvider);
              CONTINUATIONS.submit(() -> dest.done(value));
            } catch (Exception e) {
              CONTINUATIONS.submit(() -> dest.fail(e));
            }
          }

          @Override
          public void fail(final Throwable error) throws PromiseResolvedException {
            try {
              transitionDone(traceContext);
              traceFailure(error);
              settable.fail(error);
              traceContext.getParent().getTaskLogger().logTaskEnd(FusionTask.this, _traceValueProvider);
              CONTINUATIONS.submit(() -> dest.fail(error));
            } catch (Exception e) {
              CONTINUATIONS.submit(() -> dest.fail(e));
            }
          }
        };
        CONTINUATIONS.submit(() -> {
          try {
            propagator.accept(traceContext, src, next);
          } catch (Exception e) {
            /* This can only happen if there is an internal problem. Propagators should not throw any exceptions. */
            LOGGER.error("ParSeq ingternal error. An exception was thrown by propagator.", e);
          }
        });
      } else {
        //non-parent tasks subsequent executions
        addPotentialRelationships(traceContext, traceContext.getParent().getTraceBuilder());
        Promises.propagateResult(settable, dest);
      }
    };
  }

  /**
   * Create new FusionTask without any predecessors.
   */
  public static <S, T> FusionTask<?, T> create(final String name, final PromisePropagator<S, T> propagator) {
    return new FusionTask<S, T>(name, (Task<S>)null, propagator);
  }

  /**
   * Create new FusionTask with an async predecessor.
   */
  public static <S, T> FusionTask<?, T> create(final String name, final Task<S> task, final PromisePropagator<S, T> propagator) {
    return new FusionTask<S, T>(name, task, propagator);
  }

  @Override
  public <R> Task<R> apply(String desc, PromisePropagator<T, R> propagator) {
    return new FusionTask<>(desc, this, propagator);
  }

  @Override
  public Task<T> recoverWith(final String desc, final Function1<Throwable, Task<T>> func) {
    ArgumentUtil.requireNotNull(func, "function");
    final Task<T> that = this;
    return Task.async(desc, context -> {
      final SettablePromise<T> result = Promises.settable();
      context.after(that).run(() -> {
        if (that.isFailed()) {
          if (!(Exceptions.isCancellation(that.getError()))) {
            try {
              Task<T> r = func.apply(that.getError());
              if (r == null) {
                throw new RuntimeException(desc + " returned null");
              } else {
                Promises.propagateResult(r, result);
                return r;
              }
            } catch (Throwable t) {
              result.fail(t);
              return null;
            }
          } else {
            result.fail(that.getError());
            return null;
          }
        } else {
          result.done(that.get());
          return null;
        }
      } );
      context.run(that);
      return result;
    });
  }

  private void propagate(final FusionTraceContext traceContext, final SettablePromise<T> result) {
    try {
      _propagator.accept(traceContext, _asyncTask, result);
    } catch (Throwable t) {
      result.fail(t);
    }
  }

  @Override
  protected Promise<? extends T> run(final Context context) throws Throwable {
    final SettablePromise<T> result = Promises.settable();
    String baseName = getName();
    if (_asyncTask == null) {
      /* There is no async predecessor, run propagation immediately */
      FusionTraceContext traceContext = new FusionTraceContext(context,
          FusionTask.this.getShallowTraceBuilder(), baseName);
      propagate(traceContext, result);
    } else {
      /* There is async predecessor, need to run it first
         PropagationTask will actually run propagation */
      final Task<T> propagationTask = Task.async(baseName, ctx -> {
        final SettablePromise<T> fusionResult = Promises.settable();
        FusionTraceContext traceContext = new FusionTraceContext(ctx, FusionTask.this.getShallowTraceBuilder(), baseName);
        propagate(traceContext, fusionResult);
        return fusionResult;
      });
      propagationTask.getShallowTraceBuilder()
        .setHidden(_shallowTraceBuilder.getHidden())
        .setSystemHidden(_shallowTraceBuilder.getSystemHidden());
      _shallowTraceBuilder.setName("async fused");
      _shallowTraceBuilder.setSystemHidden(true);
      context.after(_asyncTask).run(propagationTask);
      context.run(_asyncTask);
      Promises.propagateResult(propagationTask, result);
    }
    return result;
  }
}


package com.linkedin.parseq.lambda;

import java.util.StringJoiner;
import java.util.regex.Pattern;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.TypeInsnNode;

class Util {

  private static final Pattern LAMBDA_NAME_PATTERN = Pattern.compile("^.*\\$\\$Lambda\\$\\d+$");

  /* package private */
  static boolean isALambdaClassByName(String name) {
    return LAMBDA_NAME_PATTERN.matcher(name.replace('/', '.')).matches();
  }

  /* package private */
  static String extractSimpleName(String fqcn, String separator) {
    if (fqcn.contains(separator)) {
      return fqcn.substring(fqcn.lastIndexOf(separator) + 1, fqcn.length());
    } else {
      return fqcn;
    }
  }

  /* package private */
  static String getArgumentsInformation(String insnDesc) {
    if (insnDesc == null) {
      return "";
    }

    Type methodType = Type.getMethodType(insnDesc);
    int argSize = methodType.getArgumentTypes().length;
    StringJoiner sj = new StringJoiner(",", "(", ")");
    for (int i = 0; i < argSize; i++) {
      sj.add("_");
    }

    return sj.toString();
  }

  /* package private */
  static String getDescriptionForMethodInsnNode(MethodInsnNode methodInstr) {
    if (methodInstr.getOpcode() == Opcodes.INVOKESPECIAL && methodInstr.name.equals("<init>")) {
      return "new " + Util.extractSimpleName(methodInstr.owner, "/") + "()";
    } else {
      Type methodType = Type.getMethodType(methodInstr.desc);
      int retSize = methodType.getArgumentsAndReturnSizes() & 0x03;
      if (retSize > 0) {
        return methodInstr.name + Util.getArgumentsInformation(methodInstr.desc);
      }
    }

    return "";
  }

  /* package private */
  static String getDescriptionForTypeInsnNode(TypeInsnNode typeInsnNode) {
    AbstractInsnNode nextInsn = typeInsnNode.getNext();
    StringJoiner argsDesc = new StringJoiner(",", "(", ")");
    while (nextInsn != null) {
      if (nextInsn instanceof LdcInsnNode) {
        argsDesc.add("_");
      } else if (nextInsn.getOpcode() == Opcodes.DUP) {
        //ignore
      } else {
        break;
      }
      nextInsn = nextInsn.getNext();
    }
    if (typeInsnNode.getOpcode() == Opcodes.NEW) {
      return "new " + Util.extractSimpleName(typeInsnNode.desc, "/") + argsDesc.toString();
    }

    return "";
  }
}


/*
 * Copyright 2012 LinkedIn, Inc
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package com.linkedin.parseq;

import com.linkedin.parseq.internal.PlanBasedRateLimiter;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;

import org.slf4j.ILoggerFactory;
import org.slf4j.LoggerFactory;

import com.linkedin.parseq.internal.ArgumentUtil;
import com.linkedin.parseq.internal.CachedLoggerFactory;
import com.linkedin.parseq.internal.PlanCompletionListener;
import com.linkedin.parseq.internal.PlanDeactivationListener;

/**
 * A configurable builder that makes {@link Engine}s.
 *

 * Minimum required configuration:
 *

 *
taskExecutor

 *
timerScheduler

 *

 *
 * @author Chris Pettitt (cpettitt@linkedin.com)
 * @author Jaroslaw Odzga (jodzga@linkedin.com)
 * @author Min Chen (mnchen@linkedin.com)
 */
public class EngineBuilder {
  private Executor _taskExecutor;
  private DelayedExecutor _timerScheduler;
  private ILoggerFactory _loggerFactory;
  private PlanDeactivationListener _planDeactivationListener;
  private PlanCompletionListener _planCompletionListener;
  private PlanBasedRateLimiter _planClassRateLimiter;
  private TaskQueueFactory _taskQueueFactory;

  private Map<String, Object> _properties = new HashMap<String, Object>();

  /**
   * Sets plan activity listener for the engine. The listener will be notified
   * when plan becomes deactivated.
   * Plan becomes deactivated when there are no tasks that can be executed e.g.
   * asynchronous operations are in progress and subsequent tasks depend on their results.
   *

   * For given plan id deactivation listener is called sequentially
   * with respect to tasks belonging to that plan. For arbitrary plan ids methods on
   * {@code PlanActivityListener} can be called in parallel.
   *
   * @param planDeactivationListener the listener that will be notified when plan
   * becomes deactivated
   */
  public void setPlanDeactivationListener(PlanDeactivationListener planDeactivationListener) {
    ArgumentUtil.requireNotNull(planDeactivationListener, "planDeactivationListener");
    _planDeactivationListener = planDeactivationListener;
    // TODO: should have returned the builder here to make it chainable.
    // Need to fix in the next major version release.
  }

  public EngineBuilder setPlanCompletionListener(PlanCompletionListener planCompletionListener) {
    ArgumentUtil.requireNotNull(planCompletionListener, "planCompletionListener");
    _planCompletionListener = planCompletionListener;
    return this;
  }

  public EngineBuilder setTaskQueueFactory(TaskQueueFactory taskQueueFactory) {
    ArgumentUtil.requireNotNull(taskQueueFactory, "taskQueueFactory");
    _taskQueueFactory = taskQueueFactory;
    return this;
  }

  public EngineBuilder setPlanClassRateLimiter(PlanBasedRateLimiter planClassRateLimiter) {
    ArgumentUtil.requireNotNull(planClassRateLimiter, "planClassRateLimiter");
    _planClassRateLimiter = planClassRateLimiter;
    return this;
  }

  /**
   * Sets the task executor for the engine.
   *

   * The lifecycle of the executor is not managed by the engine.
   *
   * @param taskExecutor the executor to use for the engine
   * @return this builder
   */
  public EngineBuilder setTaskExecutor(final Executor taskExecutor) {
    ArgumentUtil.requireNotNull(taskExecutor, "taskExecutor");
    _taskExecutor = taskExecutor;
    return this;
  }

  /**
   * Sets the timer scheduler for the engine.
   *

   * The lifecycle of the scheduler is not managed by the engine.
   *
   * @param timerScheduler the scheduler to use for the engine
   * @return this builder
   */
  public EngineBuilder setTimerScheduler(final DelayedExecutor timerScheduler) {
    ArgumentUtil.requireNotNull(timerScheduler, "timerScheduler");
    _timerScheduler = timerScheduler;
    return this;
  }

  /**
   * Sets the timer scheduler for the engine.
   *

   * The lifecycle of the scheduler is not managed by the engine.
   *
   * @param timerScheduler the scheduler to use for the engine
   * @return this builder
   */
  public EngineBuilder setTimerScheduler(final ScheduledExecutorService timerScheduler) {
    ArgumentUtil.requireNotNull(timerScheduler, "timerScheduler");
    setTimerScheduler(adaptTimerScheduler(timerScheduler));
    return this;
  }

  /**
   * Sets the logger factory that will be used by the engine. By default
   * the engine will use whatever log binding SLF4J has detected.
   *
   * @param loggerFactory the logger factory to used by the engine.
   * @return this builder
   */
  public EngineBuilder setLoggerFactory(final ILoggerFactory loggerFactory) {
    ArgumentUtil.requireNotNull(loggerFactory, "loggerFactory");
    _loggerFactory = loggerFactory;
    return this;
  }

  /**
   * Sets an engine related property on the engine.
   * That property can then be accessed by tasks via the Context.
   *
   * @param key
   * @param value
   * @return this builder
   */
  public EngineBuilder setEngineProperty(String key, Object value) {
    _properties.put(key, value);
    return this;
  }

  /**
   * Checks that the require configuration has been set and then constructs
   * and returns a new {@link Engine}.
   *
   * @return a new {@link Engine} using the configuration in this builder.
   * @throws IllegalStateException if the configuration in this builder is invalid.
   */
  public Engine build() {
    if (_taskExecutor == null) {
      throw new IllegalStateException("Task executor is required to create an Engine, but it is not set");
    }
    if (_timerScheduler == null) {
      throw new IllegalStateException("Timer scheduler is required to create an Engine, but it is not set");
    }

    return new Engine(_taskExecutor, new IndirectDelayedExecutor(_timerScheduler),
        _loggerFactory != null ? _loggerFactory : new CachedLoggerFactory(LoggerFactory.getILoggerFactory()),
        _properties, _planDeactivationListener != null ? _planDeactivationListener : planContext -> {},
        _planCompletionListener != null ? _planCompletionListener : planContext -> {},
        _taskQueueFactory, _planClassRateLimiter);
  }

  /**
   * Helper method to convert a {@link ScheduledExecutorService} to a simpler
   * {@link DelayedExecutor}.
   *
   * @param timerScheduler the scheduler to convert
   * @return the converted scheduler
   */
  private static DelayedExecutor adaptTimerScheduler(final ScheduledExecutorService timerScheduler) {
    return new DelayedExecutorAdapter(timerScheduler);
  }

  // package private. used in test only.
  PlanCompletionListener getPlanCompletionListener() {
    return _planCompletionListener;
  }
}


/*
 * Copyright 2012 LinkedIn, Inc
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package com.linkedin.parseq.promise;

import com.linkedin.parseq.function.Failure;
import com.linkedin.parseq.function.Success;
import com.linkedin.parseq.function.Try;

/**
 * This class provides a set of static helper methods that make it easier to
 * work with {@link Promise}s.
 *
 * @author Chris Pettitt (cpettitt@linkedin.com)
 * @author Jaroslaw Odzga (jodzga@linkedin.com)
 */
public class Promises {
  public static final Promise<Void> VOID = value(null);

  private Promises() {
  }

  /**
   * Creates a new promise that is already resolved with the given value.
   *
   * @param value the value for the new promise
   * @param <T> the type of the value for the promise
   * @return the promise
   */
  @SuppressWarnings("unchecked")
  public static <T> Promise<T> value(final T value) {
    if (value == null) {
      if (VOID == null) {
        return new ResolvedValue<T>(value);
      } else {
        return (Promise<T>) VOID;
      }
    }
    return new ResolvedValue<T>(value);
  }

  /**
   * Creates a new promise that is already resolved with the given error.
   *
   * @param error the error for the new promise
   * @param <T> the type of the value for the promise
   * @return the promise
   */
  public static <T> Promise<T> error(final Throwable error) {
    return new ResolvedError<T>(error);
  }

  /**
   * Returns a new promise that can have its value set at a later time.
   *
   * @param <T> the type of the value for the promise
   * @return the promise
   */
  public static <T> SettablePromise<T> settable() {
    return new SettablePromiseImpl<T>();
  }

  /**
   * Copies the value or error from the source promise to the destination
   * promise.
   *
   * @param source the source promise
   * @param dest the destination promise
   * @param <T> the value type for both promises
   */
  public static <T> void propagateResult(final Promise<T> source, final Settable<T> dest) {
    source.addListener(new TransformingPromiseListener<T, T>(dest, PromiseTransformer.identity()));
  }

  public static <T> void propagateResult(final Promise<T> source, final SettablePromise<T> dest) {
    propagateResult(source, ((Settable<T>) dest));
  }

  /**
   * Returns instance of {@link Try} that represents result a promise has completed with.
   * This method throws {@link PromiseUnresolvedException} if teh promise has not been resolved yet.
   *
   * @return instance of {@link Try} that represents result the promise has completed with
   * @see Try
   */
  public static <T> Try<T> toTry(final Promise<T> promise) {
    if (promise.isFailed()) {
      return Failure.of(promise.getError());
    } else {
      return Success.of(promise.get());
    }
  }

}


/* $Id$ */
package com.linkedin.parseq.example.domain;

import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.linkedin.parseq.Engine;
import com.linkedin.parseq.Task;
import com.linkedin.parseq.Tasks;
import com.linkedin.parseq.example.common.ExampleUtil;
import com.linkedin.parseq.function.Tuple2;

/**
 * @author Jaroslaw Odzga (jodzga@linkedin.com)
 */
public class Examples extends AbstractDomainExample {

  public static void main(String[] args) throws Exception {
    new Examples(false).runExample();
  }

  public Examples(boolean useBatching) {
    super(useBatching);
  }

  //---------------------------------------------------------------

  //create summary for a person: "<first name> <last name>"
  Task<String> createSummary(int id) {
    return fetchPerson(id).map("shortSummary", this::shortSummary);
  }

  String shortSummary(Person p) {
    return p.getFirstName() + " " + p.getLastName();
  }

  //---------------------------------------------------------------

  //handles failures delivering degraded experience
  Task<String> createResilientSummary(int id) {
    return fetchPerson(id).map(this::shortSummary).recover(e -> "Member " + id);
  }

  //---------------------------------------------------------------

  //handles failures delivering degraded experience in timely fashion
  Task<String> createResponsiveSummary(int id) {
    return fetchPerson(id).withTimeout(100, TimeUnit.MILLISECONDS).map(this::shortSummary).recover(e -> "Member " + id);
  }

  //---------------------------------------------------------------

  /** Tasks composition */

  //create extended summary for a person: "<first name> <last name> working at <company name>"
  Task<String> createExtendedSummary(int id) {
    return fetchPerson(id)
        .flatMap("createExtendedSummary", this::createExtendedSummary);
  }

  Task<String> createExtendedSummary(final Person p) {
    return fetchCompany(p.getCompanyId())
        .map("summary", company -> shortSummary(p) + " working at " + company.getName());
  }

  //---------------------------------------------------------------

  //create mailbox summary for a person: "<first name> <last name> has <X> messages"
  Task<String> createMailboxSummary(int id) {
    return Task.par(createExtendedSummary(id), fetchMailbox(id))
        .map("createMailboxSummary", (summary, mailbox) -> summary + " has " + mailbox.size() + " messages");
  }

  //create list of summaries, one per each connection
  Task<List<String>> createConnectionsSummaries(int id) {
    return fetchPerson(id).flatMap("createConnectionsSummaries", person -> createConnectionsSummaries(person.getConnections()));
  }

  Task<List<String>> createConnectionsSummaries(List<Integer> connections) {
    return Tasks.par(createConnectionsSummariesTasks(connections));
  }

  List<Task<String>> createConnectionsSummariesTasks(List<Integer> connections) {
    return connections.stream().map(this::createExtendedSummary).collect(Collectors.toList());
  }

  //---------------------------------------------------------------

  Task<Tuple2<String, List<String>>> createFullSummary(int id) {
    return Task.par(createMailboxSummary(id), createConnectionsSummaries(id));
  }

  //---------------------------------------------------------------

  /** Task collections */

  //---------------------------------------------------------------

  @Override
  protected void doRunExample(final Engine engine) throws Exception {
    Task<?> task = createExtendedSummary(1);
//    Task<?> task = Task.par(createExtendedSummary(1), createExtendedSummary(2));

    engine.run(task);

    runTaskAndPrintResults(engine, task);
  }

  private void runTaskAndPrintResults(final Engine engine, Task<?> task) throws InterruptedException {
    engine.run(task);
    task.await();
    System.out.println(task.get());
    ExampleUtil.printTracingResults(task);
  }

}


package com.linkedin.parseq;

import java.util.Collection;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

import com.linkedin.parseq.function.Tuple4;
import com.linkedin.parseq.promise.PromiseException;
import com.linkedin.parseq.promise.PromiseListener;
import com.linkedin.parseq.promise.PromiseUnresolvedException;
import com.linkedin.parseq.trace.ShallowTrace;
import com.linkedin.parseq.trace.ShallowTraceBuilder;
import com.linkedin.parseq.trace.Trace;
import com.linkedin.parseq.trace.TraceBuilder;

public class Tuple4TaskDelegate<T1, T2, T3, T4> implements Tuple4Task<T1, T2, T3, T4> {

  private final Task<Tuple4<T1, T2, T3, T4>> _task;

  public Tuple4TaskDelegate(Task<Tuple4<T1, T2, T3, T4>> task) {
    _task = task;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean cancel(Exception reason) {
    return _task.cancel(reason);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Tuple4<T1, T2, T3, T4> get() throws PromiseException {
    return _task.get();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Throwable getError() throws PromiseUnresolvedException {
    return _task.getError();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Tuple4<T1, T2, T3, T4> getOrDefault(Tuple4<T1, T2, T3, T4> defaultValue) throws PromiseUnresolvedException {
    return _task.getOrDefault(defaultValue);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public String getName() {
    return _task.getName();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public int getPriority() {
    return _task.getPriority();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean setPriority(int priority) {
    return _task.setPriority(priority);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void await() throws InterruptedException {
    _task.await();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void contextRun(Context context, Task<?> parent, Collection<Task<?>> predecessors) {
    _task.contextRun(context, parent, predecessors);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean await(long time, TimeUnit unit) throws InterruptedException {
    return _task.await(time, unit);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public ShallowTrace getShallowTrace() {
    return _task.getShallowTrace();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void addListener(PromiseListener<Tuple4<T1, T2, T3, T4>> listener) {
    _task.addListener(listener);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Trace getTrace() {
    return _task.getTrace();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isDone() {
    return _task.isDone();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isFailed() {
    return _task.isFailed();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Long getId() {
    return _task.getId();
  }

  @Override
  public void setTraceValueSerializer(Function<Tuple4<T1, T2, T3, T4>, String> serializer) {
    _task.setTraceValueSerializer(serializer);
  }

  @Override
  public ShallowTraceBuilder getShallowTraceBuilder() {
    return _task.getShallowTraceBuilder();
  }

  @Override
  public TraceBuilder getTraceBuilder() {
    return _task.getTraceBuilder();
  }

}


package com.linkedin.parseq.lambda;

import com.ea.agentloader.AgentLoader;
import com.ea.agentloader.ClassPathUtils;
import com.linkedin.parseq.TaskDescriptor;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.Field;
import java.security.ProtectionDomain;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicBoolean;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Opcodes;

/**
 * An ASM based implementation of {@link TaskDescriptor} to provide description for generated Lambda class.
 * Description of Lambda expression includes source code location of lambda, function call or method reference
 * within lambda.
 */
public class ASMBasedTaskDescriptor implements TaskDescriptor {

  private static ConcurrentMap<String, String> _names = new ConcurrentHashMap<>();

  static {
    if (ASMBasedTaskDescriptor.Agent.class.getClassLoader() != ClassLoader.getSystemClassLoader()) {
      try {
        // Agent needs to be loaded through system class loader
        // This piece of code adds Agent to system class path and then loads the Agent
        ClassPathUtils.appendToSystemPath(ClassPathUtils.getClassPathFor(ASMBasedTaskDescriptor.Agent.class));
        AgentLoader.loadAgentClass(ASMBasedTaskDescriptor.Agent.class.getName(), null, null, true, true, false);

        // Reference the names field to names field of instance loaded through system class loader
        // Its the system class loaded instance names field which is populated with lambda descriptions
        // because agent is loaded through system class loader
        Class<?> systemClazz = ClassLoader.getSystemClassLoader().loadClass(ASMBasedTaskDescriptor.class.getName());
        Object _systemClassDescriptor = systemClazz.newInstance();

        Field field = systemClazz.getDeclaredField("_names");
        field.setAccessible(true);

        ConcurrentMap<String, String> systemsNamesMap = (ConcurrentMap<String, String>) field.get(_systemClassDescriptor);
        _names = systemsNamesMap;
      } catch (Throwable e) {
        System.err.println("Unable to refer to names map of ASMBasedTaskDescriptor loaded from system classpath");
      }
    } else {
      try {
        //for cases such as test executions which have only one class loader.
        AgentLoader.loadAgentClass(ASMBasedTaskDescriptor.Agent.class.getName(), null);
      } catch (Throwable e) {
        // ignore this
        // in case of multiple class loaders, this can throw an error as SystemClassLoaded loaded it already
        // look above (ClassLoader.getSystemClassLoader().loadClass(ASMBasedTaskDescriptor.class.getName());)
      }
    }
  }

  @Override
  public String getDescription(String className) {
    Optional<String> lambdaClassDescription = getLambdaClassDescription(className);
    if (lambdaClassDescription.isPresent()) {
      return lambdaClassDescription.get();
    } else {
      return className;
    }
  }

  /*package private */ Optional<String> getLambdaClassDescription(String className) {
    int slashIndex = className.lastIndexOf('/');
    if (slashIndex > 0) {
      String name = className.substring(0, slashIndex);
      String desc = _names.get(name);
      if (desc == null || desc.isEmpty()) {
        return Optional.empty();
      } else {
        return Optional.of(desc);
      }
    }

    return Optional.empty();
  }

  private static void add(String lambdaClassName, String description) {
    _names.put(lambdaClassName, description);
  }

  public static class Agent {

    private static final AtomicBoolean _initialized = new AtomicBoolean(false);

    public static void agentmain(String agentArgs, Instrumentation instrumentation) {
      if (_initialized.compareAndSet(false, true)) {
        instrumentation.addTransformer(new Analyzer());
      }
    }

    private static class Analyzer implements ClassFileTransformer {

      @Override
      public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
          ProtectionDomain protectionDomain, byte[] classfileBuffer)
          throws IllegalClassFormatException {
        if (className == null && loader != null) {
          analyze(classfileBuffer, loader);
        }
        return classfileBuffer;
      }

      private void analyze(byte[] byteCode, ClassLoader loader) {
        ClassReader reader = new ClassReader(byteCode);
        LambdaClassLocator cv = new LambdaClassLocator(Opcodes.ASM5, loader);

        reader.accept(cv, 0);

        if (cv.isLambdaClass()) {
          LambdaClassDescription lambdaClassDescription = cv.getLambdaClassDescription();
          add(lambdaClassDescription.getClassName(), lambdaClassDescription.getDescription());
        }
      }
    }
  }
}


package com.linkedin.parseq;

import java.util.Collection;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

import com.linkedin.parseq.function.Tuple2;
import com.linkedin.parseq.promise.PromiseException;
import com.linkedin.parseq.promise.PromiseListener;
import com.linkedin.parseq.promise.PromiseUnresolvedException;
import com.linkedin.parseq.trace.ShallowTrace;
import com.linkedin.parseq.trace.ShallowTraceBuilder;
import com.linkedin.parseq.trace.Trace;
import com.linkedin.parseq.trace.TraceBuilder;

public class Tuple2TaskDelegate<T1, T2> implements Tuple2Task<T1, T2> {

  private final Task<Tuple2<T1, T2>> _task;

  public Tuple2TaskDelegate(Task<Tuple2<T1, T2>> task) {
    _task = task;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean cancel(Exception reason) {
    return _task.cancel(reason);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Tuple2<T1, T2> get() throws PromiseException {
    return _task.get();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Throwable getError() throws PromiseUnresolvedException {
    return _task.getError();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Tuple2<T1, T2> getOrDefault(Tuple2<T1, T2> defaultValue) throws PromiseUnresolvedException {
    return _task.getOrDefault(defaultValue);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public String getName() {
    return _task.getName();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public int getPriority() {
    return _task.getPriority();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean setPriority(int priority) {
    return _task.setPriority(priority);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void await() throws InterruptedException {
    _task.await();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void contextRun(Context context, Task<?> parent, Collection<Task<?>> predecessors) {
    _task.contextRun(context, parent, predecessors);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean await(long time, TimeUnit unit) throws InterruptedException {
    return _task.await(time, unit);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public ShallowTrace getShallowTrace() {
    return _task.getShallowTrace();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void addListener(PromiseListener<Tuple2<T1, T2>> listener) {
    _task.addListener(listener);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Trace getTrace() {
    return _task.getTrace();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isDone() {
    return _task.isDone();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isFailed() {
    return _task.isFailed();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Long getId() {
    return _task.getId();
  }

  @Override
  public void setTraceValueSerializer(Function<Tuple2<T1, T2>, String> serializer) {
    _task.setTraceValueSerializer(serializer);
  }

  @Override
  public ShallowTraceBuilder getShallowTraceBuilder() {
    return _task.getShallowTraceBuilder();
  }

  @Override
  public TraceBuilder getTraceBuilder() {
    return _task.getTraceBuilder();
  }

}


/*
 * Copyright 2012 LinkedIn, Inc
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package com.linkedin.parseq;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;

/**
 * This class provides a set of factory methods for create common
 * {@link Task}s.
 *
 * @author Chris Pettitt (cpettitt@linkedin.com)
 * @author Chi Chan (ckchan@linkedin.com)
 */
public class Tasks {
  private Tasks() {
  }

  /**
   * Creates a new {@link Task} that have a value of type Void. Because the
   * returned task has no value, it is typically used to produce side-effects.
   *
   * @deprecated  As of 2.0.0, replaced by {@link Task#action(String, com.linkedin.parseq.function.Action) Task.action}
   * @param name a name that describes the action
   * @param runnable the action that will be executed when the task is run
   * @return the new task
   * @see Task#action(String, com.linkedin.parseq.function.Action) Task.action
   */
  @Deprecated
  public static Task<Void> action(final String name, final Runnable runnable) {
    return Task.action(name, runnable::run);
  }

  /**
   * Creates a new {@link Task} that's value will be set to the value returned
   * from the supplied callable. This task is useful when doing basic
   * computation that does not require asynchrony. It is not appropriate for
   * long running or blocking tasks.
   *
   * @deprecated  As of 2.0.0, replaced by {@link Task#callable(String, Callable) Task.callable}
   * @param name a name that describes the action
   * @param callable the callable to execute when this task is run
   * @param <T> the type of the return value for this task
   * @return the new task
   * @see Task#callable(String, Callable) Task.callable
   */
  @Deprecated
  public static <T> Task<T> callable(final String name, final Callable<? extends T> callable) {
    return Task.callable(name, () -> callable.call());
  }

  /**
   * Creates a new {@link Task} that's value will be set to the value returned
   * from the supplied callable. This task is useful when doing basic
   * computation that does not require asynchrony. It is not appropriate for
   * long running or blocking tasks.
   *
   * @deprecated  As of 2.0.0, replaced by {@link Task#callable(String, Callable) Task.callable}
   * @param name a name that describes the action
   * @param callable the callable to execute when this task is run
   * @param <T> the type of the return value for this task
   * @return the new task
   * @see Task#callable(String, Callable) Task.callable
   */
  @Deprecated
  public static <T> Task<T> callable(final String name, final ThrowableCallable<? extends T> callable) {
    return new CallableTask<T>(name, callable);
  }

  /**
   * Creates a new task that will run the given tasks sequentially (e.g.
   * task1 will be finished before task2 starts). The value of the seq task will
   * be the result of the last task in the sequence.
   *
   * @deprecated  As of 2.0.0, replaced by {@link Task#map(String, com.linkedin.parseq.function.Function1) Task.map},
   * {@link Task#flatMap(String, com.linkedin.parseq.function.Function1) Task.flatMap},
   * {@link Task#andThen(String, Task) Task.andThen} and other methods in {@link Task}.
   * @see Task#map(String, com.linkedin.parseq.function.Function1) Task.map
   * @see Task#flatMap(String, com.linkedin.parseq.function.Function1) Task.flatMap
   * @see Task#andThen(String, Task) Task.andThen
   * @see Task
   */
  @Deprecated
  public static <T> Task<T> seq(Task<?> task1, Task<T> task2) {
    return vaseq(task1, task2);
  }

  /**
   * @deprecated  As of 2.0.0, replaced by {@link Task#map(String, com.linkedin.parseq.function.Function1) Task.map},
   * {@link Task#flatMap(String, com.linkedin.parseq.function.Function1) Task.flatMap},
   * {@link Task#andThen(String, Task) Task.andThen} and other methods in {@link Task}.
   * @see Task#map(String, com.linkedin.parseq.function.Function1) Task.map
   * @see Task#flatMap(String, com.linkedin.parseq.function.Function1) Task.flatMap
   * @see Task#andThen(String, Task) Task.andThen
   * @see Task
   */
  @Deprecated
  public static <T> Task<T> seq(Task<?> task1, Task<?> task2, Task<T> task3) {
    return vaseq(task1, task2, task3);
  }

  /**
   * @deprecated  As of 2.0.0, replaced by {@link Task#map(String, com.linkedin.parseq.function.Function1) Task.map},
   * {@link Task#flatMap(String, com.linkedin.parseq.function.Function1) Task.flatMap},
   * {@link Task#andThen(String, Task) Task.andThen} and other methods in {@link Task}.
   * @see Task#map(String, com.linkedin.parseq.function.Function1) Task.map
   * @see Task#flatMap(String, com.linkedin.parseq.function.Function1) Task.flatMap
   * @see Task#andThen(String, Task) Task.andThen
   * @see Task
   */
  @Deprecated
  public static <T> Task<T> seq(Task<?> task1, Task<?> task2, Task<?> task3, Task<T> task4) {
    return vaseq(task1, task2, task3, task4);
  }

  /**
   * @deprecated  As of 2.0.0, replaced by {@link Task#map(String, com.linkedin.parseq.function.Function1) Task.map},
   * {@link Task#flatMap(String, com.linkedin.parseq.function.Function1) Task.flatMap},
   * {@link Task#andThen(String, Task) Task.andThen} and other methods in {@link Task}.
   * @see Task#map(String, com.linkedin.parseq.function.Function1) Task.map
   * @see Task#flatMap(String, com.linkedin.parseq.function.Function1) Task.flatMap
   * @see Task#andThen(String, Task) Task.andThen
   * @see Task
   */
  @Deprecated
  public static <T> Task<T> seq(Task<?> task1, Task<?> task2, Task<?> task3, Task<?> task4, Task<T> task5) {
    return vaseq(task1, task2, task3, task4, task5);
  }

  /**
   * @deprecated  As of 2.0.0, replaced by {@link Task#map(String, com.linkedin.parseq.function.Function1) Task.map},
   * {@link Task#flatMap(String, com.linkedin.parseq.function.Function1) Task.flatMap},
   * {@link Task#andThen(String, Task) Task.andThen} and other methods in {@link Task}.
   * @see Task#map(String, com.linkedin.parseq.function.Function1) Task.map
   * @see Task#flatMap(String, com.linkedin.parseq.function.Function1) Task.flatMap
   * @see Task#andThen(String, Task) Task.andThen
   * @see Task
   */
  @Deprecated
  public static <T> Task<T> seq(Task<?> task1, Task<?> task2, Task<?> task3, Task<?> task4, Task<?> task5,
      Task<T> task6) {
    return vaseq(task1, task2, task3, task4, task5, task6);
  }

  /**
   * @deprecated  As of 2.0.0, replaced by {@link Task#map(String, com.linkedin.parseq.function.Function1) Task.map},
   * {@link Task#flatMap(String, com.linkedin.parseq.function.Function1) Task.flatMap},
   * {@link Task#andThen(String, Task) Task.andThen} and other methods in {@link Task}.
   * @see Task#map(String, com.linkedin.parseq.function.Function1) Task.map
   * @see Task#flatMap(String, com.linkedin.parseq.function.Function1) Task.flatMap
   * @see Task#andThen(String, Task) Task.andThen
   * @see Task
   */
  @Deprecated
  public static <T> Task<T> seq(Task<?> task1, Task<?> task2, Task<?> task3, Task<?> task4, Task<?> task5,
      Task<?> task6, Task<T> task7) {
    return vaseq(task1, task2, task3, task4, task5, task6, task7);
  }

  /**
   * @deprecated  As of 2.0.0, replaced by {@link Task#map(String, com.linkedin.parseq.function.Function1) Task.map},
   * {@link Task#flatMap(String, com.linkedin.parseq.function.Function1) Task.flatMap},
   * {@link Task#andThen(String, Task) Task.andThen} and other methods in {@link Task}.
   * @see Task#map(String, com.linkedin.parseq.function.Function1) Task.map
   * @see Task#flatMap(String, com.linkedin.parseq.function.Function1) Task.flatMap
   * @see Task#andThen(String, Task) Task.andThen
   * @see Task
   */
  @Deprecated
  public static <T> Task<T> seq(Task<?> task1, Task<?> task2, Task<?> task3, Task<?> task4, Task<?> task5,
      Task<?> task6, Task<?> task7, Task<T> task8) {
    return vaseq(task1, task2, task3, task4, task5, task6, task7, task8);
  }

  /**
   * @deprecated  As of 2.0.0, replaced by {@link Task#map(String, com.linkedin.parseq.function.Function1) Task.map},
   * {@link Task#flatMap(String, com.linkedin.parseq.function.Function1) Task.flatMap},
   * {@link Task#andThen(String, Task) Task.andThen} and other methods in {@link Task}.
   * @see Task#map(String, com.linkedin.parseq.function.Function1) Task.map
   * @see Task#flatMap(String, com.linkedin.parseq.function.Function1) Task.flatMap
   * @see Task#andThen(String, Task) Task.andThen
   * @see Task
   */
  @Deprecated
  public static <T> Task<T> seq(Task<?> task1, Task<?> task2, Task<?> task3, Task<?> task4, Task<?> task5,
      Task<?> task6, Task<?> task7, Task<?> task8, Task<T> task9) {
    return vaseq(task1, task2, task3, task4, task5, task6, task7, task8, task9);
  }

  /**
   * @deprecated  As of 2.0.0, replaced by {@link Task#map(String, com.linkedin.parseq.function.Function1) Task.map},
   * {@link Task#flatMap(String, com.linkedin.parseq.function.Function1) Task.flatMap},
   * {@link Task#andThen(String, Task) Task.andThen} and other methods in {@link Task}.
   * @see Task#map(String, com.linkedin.parseq.function.Function1) Task.map
   * @see Task#flatMap(String, com.linkedin.parseq.function.Function1) Task.flatMap
   * @see Task#andThen(String, Task) Task.andThen
   * @see Task
   */
  @Deprecated
  public static <T> Task<T> seq(Task<?> task1, Task<?> task2, Task<?> task3, Task<?> task4, Task<?> task5,
      Task<?> task6, Task<?> task7, Task<?> task8, Task<?> task9, Task<T> task10) {
    return vaseq(task1, task2, task3, task4, task5, task6, task7, task8, task9, task10);
  }

  /**
   * Creates a new task that, when run, will run each of the supplied tasks in
   * order. The value of this new task is the value of the last task in the
   * sequence. This method is not type-safe.
   *
   * @param tasks the tasks to run sequentially
   * @param <T> the result value for the sequence of tasks
   * @return a new task that will run the given tasks sequentially
   */
  @SuppressWarnings("deprecation")
  public static <T> Task<T> seq(final Iterable<? extends Task<?>> tasks) {
    return new SeqTask<T>("seq", tasks);
  }

  /**
   * Creates a task that will run another task as a side effect once the primary task
   * completes successfully. The side effect will not be run if the primary task fails or
   * is canceled. The entire task is marked done once the base task completes, even if
   * the side effect has not been run.
   *
   * @param parent the primary task.
   * @param sideEffect the side effect of the primary task.
   * @param <T> the result value of the parent task, and the resulting task.
   * @return a new task that will be done once parent completes, but has the given side effect.
   * @deprecated  As of 2.0.0, replaced by {@link Task#withSideEffect(String, com.linkedin.parseq.function.Function1) Task.withSideEffect}
   * @see Task#withSideEffect(String, com.linkedin.parseq.function.Function1) Task.withSideEffect
   */
  @Deprecated
  public static <T> Task<T> withSideEffect(final Task<T> parent, final Task<?> sideEffect) {
    return parent.withSideEffect(t -> sideEffect);
  }

  /**
   * Creates a new task that will run the given tasks in parallel (e.g. task1
   * can be executed at the same time as task2). When all tasks complete
   * successfully, you can use {@link com.linkedin.parseq.ParTask#get()} to
   * get a list of the results. If at least one task failed, then this task will
   * also be marked as failed. Use {@link com.linkedin.parseq.ParTask#getTasks()}
   * or {@link com.linkedin.parseq.ParTask#getSuccessful()} to get results in
   * this case.
   * @deprecated  As of 2.0.0, replaced by {@link Task#par(Task, Task) Task.par}
   * @see Task#par(Task, Task) Task.par
   */
  @Deprecated
  public static <T> ParTask<T> par(Task<? extends T> task1, Task<? extends T> task2) {
    return vapar(task1, task2);
  }

  /**
   * @deprecated  As of 2.0.0, replaced by {@link Task#par(Task, Task) Task.par}
   * @see Task#par(Task, Task) Task.par
   */
  @Deprecated
  public static <T> ParTask<T> par(Task<? extends T> task1, Task<? extends T> task2, Task<? extends T> task3) {
    return vapar(task1, task2, task3);
  }

  /**
   * @deprecated  As of 2.0.0, replaced by {@link Task#par(Task, Task) Task.par}
   * @see Task#par(Task, Task) Task.par
   */
  @Deprecated
  public static <T> ParTask<T> par(Task<? extends T> task1, Task<? extends T> task2, Task<? extends T> task3,
      Task<? extends T> task4) {
    return vapar(task1, task2, task3, task4);
  }

  /**
   * @deprecated  As of 2.0.0, replaced by {@link Task#par(Task, Task) Task.par}
   * @see Task#par(Task, Task) Task.par
   */
  @Deprecated
  public static <T> ParTask<T> par(Task<? extends T> task1, Task<? extends T> task2, Task<? extends T> task3,
      Task<? extends T> task4, Task<? extends T> task5) {
    return vapar(task1, task2, task3, task4, task5);
  }

  /**
   * @deprecated  As of 2.0.0, replaced by {@link Task#par(Task, Task) Task.par}
   * @see Task#par(Task, Task) Task.par
   */
  @Deprecated
  public static <T> ParTask<T> par(Task<? extends T> task1, Task<? extends T> task2, Task<? extends T> task3,
      Task<? extends T> task4, Task<? extends T> task5, Task<? extends T> task6) {
    return vapar(task1, task2, task3, task4, task5, task6);
  }

  /**
   * @deprecated  As of 2.0.0, replaced by {@link Task#par(Task, Task) Task.par}
   * @see Task#par(Task, Task) Task.par
   */
  @Deprecated
  public static <T> ParTask<T> par(Task<? extends T> task1, Task<? extends T> task2, Task<? extends T> task3,
      Task<? extends T> task4, Task<? extends T> task5, Task<? extends T> task6, Task<? extends T> task7) {
    return vapar(task1, task2, task3, task4, task5, task6, task7);
  }

  /**
   * @deprecated  As of 2.0.0, replaced by {@link Task#par(Task, Task) Task.par}
   * @see Task#par(Task, Task) Task.par
   */
  @Deprecated
  public static <T> ParTask<T> par(Task<? extends T> task1, Task<? extends T> task2, Task<? extends T> task3,
      Task<? extends T> task4, Task<? extends T> task5, Task<? extends T> task6, Task<? extends T> task7,
      Task<? extends T> task8) {
    return vapar(task1, task2, task3, task4, task5, task6, task7, task8);
  }

  /**
   * @deprecated  As of 2.0.0, replaced by {@link Task#par(Task, Task) Task.par}
   * @see Task#par(Task, Task) Task.par
   */
  @Deprecated
  public static <T> ParTask<T> par(Task<? extends T> task1, Task<? extends T> task2, Task<? extends T> task3,
      Task<? extends T> task4, Task<? extends T> task5, Task<? extends T> task6, Task<? extends T> task7,
      Task<? extends T> task8, Task<? extends T> task9) {
    return vapar(task1, task2, task3, task4, task5, task6, task7, task8, task9);
  }

  /**
   * @deprecated  As of 2.0.0, replaced by {@link Task#par(Task, Task) Task.par}
   * @see Task#par(Task, Task) Task.par
   */
  @Deprecated
  public static <T> ParTask<T> par(Task<? extends T> task1, Task<? extends T> task2, Task<? extends T> task3,
      Task<? extends T> task4, Task<? extends T> task5, Task<? extends T> task6, Task<? extends T> task7,
      Task<? extends T> task8, Task<? extends T> task9, Task<? extends T> task10) {
    return vapar(task1, task2, task3, task4, task5, task6, task7, task8, task9, task10);
  }

  /**
   * @deprecated  As of 2.0.0, replaced by {@link Task#par(Iterable) Task.par}
   * as a safer alternative that does not throw an IllegalArgumentException for
   * an empty Iterable of tasks.
   * @see Task#par(Iterable) Task.par
   */
  @Deprecated
  public static <T> ParTask<T> par(final Iterable<? extends Task<? extends T>> tasks) {
    return new ParTaskImpl<T>("par", tasks);
  }

  /**
   * Creates a new task that wraps the given task. If the given task finishes
   * before the timeout occurs then this task takes on the value of the task.
   * If the task does not complete in the given time then this task will
   * have a TimeoutException. The wrapped task may be cancelled when a timeout
   * occurs.
   *
   * @param time the time to wait before timing out
   * @param unit the units for the time
   * @param task the task to wrap
   * @param <T> the value type for the task
   * @return the new timeout task
   * @deprecated  As of 2.0.0, replaced by {@link Task#withTimeout(long, TimeUnit) Task.withTimeout}.
   * @see Task#withTimeout(long, TimeUnit) Task.withTimeout
   */
  @Deprecated
  public static <T> Task<T> timeoutWithError(final long time, final TimeUnit unit, final Task<T> task) {
    return task.withTimeout(time, unit);
  }

  /**
   * This method is similar to {@link #timeoutWithError(long, java.util.concurrent.TimeUnit, Task)},
   * but it also takes a name that will be set on the returned task.
   *
   * @param name the name of this task
   * @param time the time to wait before timing out
   * @param unit the units for the time
   * @param task the task to wrap
   * @param <T> the value type for the task
   * @return the new timeout task
   * @deprecated  As of 2.0.0, replaced by {@link Task#withTimeout(long, TimeUnit) Task.withTimeout}.
   * @see Task#withTimeout(long, TimeUnit) Task.withTimeout
   */
  @Deprecated
  public static <T> Task<T> timeoutWithError(final String name, final long time, final TimeUnit unit,
      final Task<T> task) {
    return task.withTimeout(time, unit);
  }

  /**
   * Creates a new task that will run the given array of tasks sequentially
   * (e.g. tasks[0] will be finished before tasks[1] is run). This
   * method is not type-safe - prefer one of the {@code seq} options
   * when possible. The value of this new task is the value of the last task in
   * the sequence.
   *
   * @param tasks the tasks to run sequentially
   * @param <T> the result value for the sequence of tasks
   * @return a new task that will run the given tasks sequentially
   */
  @SuppressWarnings("deprecation")
  private static <T> Task<T> vaseq(final Task<?>... tasks) {
    return new SeqTask<T>("seq", Arrays.asList(tasks));
  }

  /**
   * Creates a new task that will run each of the supplied tasks in parallel (e.g.
   * tasks[0] can be run at the same time as task2). This method is not
   * type-safe - prefer one of the {@code par} options when possible.
   *

   * When all tasks complete successfully, you can use
   * {@link com.linkedin.parseq.ParTask#get()} to get a list of the results. If
   * at least one task failed, then this task will also be marked as failed. Use
   * {@link com.linkedin.parseq.ParTask#getTasks()} or
   * {@link com.linkedin.parseq.ParTask#getSuccessful()} to get results in this
   * case.
   *
   * @param tasks the tasks to run in parallel
   * @return a new task that will run the given tasks in parallel
   */
  private static <T> ParTask<T> vapar(final Task<?>... tasks) {
    final List<Task<T>> taskList = new ArrayList<Task<T>>(tasks.length);
    for (Task<?> task : tasks) {
      @SuppressWarnings("unchecked")
      final Task<T> typedTask = (Task<T>) task;
      taskList.add(typedTask);
    }
    return par(taskList);
  }
}


/*
 * Copyright 2016 LinkedIn Crop.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package com.linkedin.parseq.zk.client;

import java.util.List;
import java.util.stream.Collectors;
import org.apache.zookeeper.KeeperException;

/**
 * @author Ang Xu
 */
public final class ZKUtil {

  private ZKUtil() {}

  public static String normalizeZKPath(String basePath, String child) {
    // normalization
    while (basePath.endsWith("/")) {
      basePath = basePath.substring(0, basePath.length() - 2);
    }
    while(child.startsWith("/")) {
      child = child.substring(1);
    }
    return basePath + "/" + child;
  }

  /**
   * Decides whether the given {@link Throwable throwable} is recoverable or not.
   * Recoverable exceptions are defined as such:
   * http://wiki.apache.org/hadoop/ZooKeeper/ErrorHandling
   *
   * @param t the {@link Throwable}
   * @return  {@code true} if {@code t} is recoverable and {@code false} otherwise.
   */
  public static boolean isRecoverable(Throwable t) {
    if (t instanceof KeeperException) {
      KeeperException ex = (KeeperException) t;
      if (ex.code() == KeeperException.Code.CONNECTIONLOSS ||
          ex.code() == KeeperException.Code.OPERATIONTIMEOUT) {
        return true;
      }
    }
    return false;
  }

  public static String findNodeWithUUID(List<String> children, String uuid) {
    for (String child : children) {
      if (child.startsWith(uuid)) {
        return child;
      }
    }
    return null;
  }

  /**
   * Finds node with next lowest sequence number than the given node within
   * the given list of nodes.
   *
   * @param children list of children
   * @param node the given node to compare sequence number with
   * @return the node with next lowest sequence number
   */
  public static String findNodeWithNextLowestSN(List<String> children, String node) {
    List<String> sortedChildren = children.stream()
        .sorted((String o1, String o2) ->
            Long.compare(getSequenceNumber(o1), getSequenceNumber(o2)))
        .collect(Collectors.toList());

    int index = sortedChildren.indexOf(node);
    if (index > 0) {
      return sortedChildren.get(index-1);
    } else if (index == 0) {
      return node;
    } else {
      return null;
    }
  }

  /**
   * Gets 10 digit sequence number from the given znode per:
   * http://zookeeper.apache.org/doc/trunk/zookeeperProgrammers.html#Sequence+Nodes+--+Unique+Naming
   */
  private static long getSequenceNumber(String node) {
    final int length = node.length();
    try {
      String seqNumStr = node.substring(length - 10, length);
      return Long.valueOf(seqNumStr);
    } catch (IndexOutOfBoundsException | NumberFormatException ex) {
      throw new IllegalArgumentException(String.format("znode %s doesn't have sequence number", node));
    }
  }
}


/*
 * Copyright 2012 LinkedIn, Inc
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package com.linkedin.parseq;

import java.util.Collection;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.function.Supplier;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.linkedin.parseq.internal.IdGenerator;
import com.linkedin.parseq.internal.TaskLogger;
import com.linkedin.parseq.promise.DelegatingPromise;
import com.linkedin.parseq.promise.Promise;
import com.linkedin.parseq.promise.Promises;
import com.linkedin.parseq.promise.SettablePromise;
import com.linkedin.parseq.trace.Relationship;
import com.linkedin.parseq.trace.ResultType;
import com.linkedin.parseq.trace.ShallowTrace;
import com.linkedin.parseq.trace.ShallowTraceBuilder;
import com.linkedin.parseq.trace.Trace;
import com.linkedin.parseq.trace.TraceBuilder;

/**
 * An abstract base class that can be used to build implementations of
 * {@link Task}.
 *
 * @author Chris Pettitt (cpettitt@linkedin.com)
 * @author Chi Chan (ckchan@linkedin.com)
 */
public abstract class BaseTask<T> extends DelegatingPromise<T>implements Task<T> {

  private static final int TASK_NAME_MAX_LENGTH = 1024;

  static final Logger LOGGER = LoggerFactory.getLogger(BaseTask.class);
  private static final String CANONICAL_NAME = BaseTask.class.getCanonicalName();

  private static enum StateType {
    // The initial state of the task.
    //
    // A task in this state can be cancelled and have its priority changed.
    INIT,

    // The task is currently executing. That is, a thread is in the run()
    // method for the TaskDef.
    //
    // A task in this state in not cancellable and cannot have its priority
    // changed.
    RUN,

    // The task has finished running, but the result has not yet been set.
    // This occurs for Tasks with AsyncTaskDefs.
    //
    // A task in this state in cancellable, and cannot have its priority
    // changed.
    PENDING,

    // The task is resolved.
    //
    // A task in this state in not cancellable and cannot have its priority
    // changed.
    DONE
  }

  private final Long _id = IdGenerator.getNextId();
  private final AtomicReference<State> _stateRef;
  private final String _name;
  protected final ShallowTraceBuilder _shallowTraceBuilder;

  protected volatile Function<T, String> _traceValueProvider;

  private volatile TraceBuilder _traceBuilder;

  private final Throwable _taskStackTraceHolder;

  /**
   * Constructs a base task without a specified name. The name for this task
   * will be the {@link #toString} representation for this instance. It is
   * usually best to use {@link BaseTask#BaseTask(String)}.
   */
  public BaseTask() {
    this(null);
  }

  /**
   * Constructs a base task with a name.
   *
   * @param name the name for this task.
   */
  public BaseTask(final String name) {
    this(name, null);
  }

  /**
   * Constructs a base task with a name and type of task
   *
   * @param name the name for this task.
   * @param taskType the type of the task
   */
  public BaseTask(final String name, final String taskType) {
    super(Promises.settable());
    _name = truncate(name);
    final State state = State.INIT;
    _shallowTraceBuilder = new ShallowTraceBuilder(_id);
    _shallowTraceBuilder.setName(getName());
    _shallowTraceBuilder.setResultType(ResultType.UNFINISHED);
    if (taskType != null) {
      _shallowTraceBuilder.setTaskType(taskType);
    }
    _stateRef = new AtomicReference<>(state);

    if (ParSeqGlobalConfiguration.isCrossThreadStackTracesEnabled()) {
      _taskStackTraceHolder = new Throwable();
    } else {
      _taskStackTraceHolder = null;
    }
  }

  private String truncate(String name) {
    if (name == null || name.length() <= TASK_NAME_MAX_LENGTH) {
      return name;
    } else {
      return name.substring(0, TASK_NAME_MAX_LENGTH);
    }
  }

  @Override
  public Long getId() {
    return _id;
  }

  @Override
  public int getPriority() {
    return _stateRef.get().getPriority();
  }

  @Override
  public boolean setPriority(final int priority) {
    if (priority < Priority.MIN_PRIORITY || priority > Priority.MAX_PRIORITY) {
      throw new IllegalArgumentException("Priority out of bounds: " + priority);
    }

    State state;
    State newState;
    do {
      state = _stateRef.get();
      if (state.getType() != StateType.INIT) {
        return false;
      }

      newState = new State(state.getType(), priority);
    } while (!_stateRef.compareAndSet(state, newState));

    return true;
  }

  @Override
  public TraceBuilder getTraceBuilder() {
    return _traceBuilder;
  }

  @Override
  public final void contextRun(final Context context, final Task<?> parent, final Collection<Task<?>> predecessors) {
    final TaskLogger taskLogger = context.getTaskLogger();
    final TraceBuilder traceBuilder = context.getTraceBuilder();
    if (transitionRun(traceBuilder)) {
      markTaskStarted();
      final Promise<T> promise;
      try {
        if (parent != null) {
          traceBuilder.addRelationship(Relationship.CHILD_OF, getShallowTraceBuilder(),
              parent.getShallowTraceBuilder());
        }
        for (Task<?> predecessor : predecessors) {
          traceBuilder.addRelationship(Relationship.SUCCESSOR_OF, getShallowTraceBuilder(),
              predecessor.getShallowTraceBuilder());
        }

        taskLogger.logTaskStart(this);
        try {
          final Context wrapperContext = new WrappedContext(context);
          promise = doContextRun(wrapperContext);
        } finally {
          transitionPending();
        }

        promise.addListener(resolvedPromise -> {
          if (resolvedPromise.isFailed()) {
            fail(resolvedPromise.getError(), taskLogger);
          } else {
            done(resolvedPromise.get(), taskLogger);
          }
        } );
      } catch (Throwable t) {
        fail(t, taskLogger);
      }
    } else {
      //this is possible when task was cancelled or has been executed multiple times
      //e.g. task has multiple paths it can be executed with or has been completed by
      //a FusionTask
      if (parent != null) {
        traceBuilder.addRelationship(Relationship.POTENTIAL_CHILD_OF, getShallowTraceBuilder(),
            parent.getShallowTraceBuilder());
      }
      for (Task<?> predecessor : predecessors) {
        traceBuilder.addRelationship(Relationship.POSSIBLE_SUCCESSOR_OF, getShallowTraceBuilder(),
            predecessor.getShallowTraceBuilder());
      }
    }
  }

  @SuppressWarnings("unchecked")
  private Promise<T> doContextRun(final Context context) throws Throwable {
    return (Promise<T>) run(context);
  }

  /**
   * Returns the name of this task. If no name was set during construction
   * this method will return the value of {@link #toString()}. In most
   * cases it is preferable to explicitly set a name.
   *
   * @return the name of this task
   */
  @Override
  public String getName() {
    return _name == null ? toString() : _name;
  }

  @Override
  public boolean cancel(final Exception rootReason) {
    if (transitionCancel(rootReason)) {
      final Exception reason = new CancellationException(rootReason);
      traceFailure(reason);
      getSettableDelegate().fail(reason);
      return true;
    }
    return false;
  }

  protected void traceFailure(final Throwable reason) {
    if (Exceptions.isEarlyFinish(reason)) {
      _shallowTraceBuilder.setResultType(ResultType.EARLY_FINISH);
    } else {
      _shallowTraceBuilder.setResultType(ResultType.ERROR);
      _shallowTraceBuilder.setValue(Exceptions.failureToString(reason));
    }
  }

  @Override
  public ShallowTraceBuilder getShallowTraceBuilder() {
    return _shallowTraceBuilder;
  }

  @Override
  public ShallowTrace getShallowTrace() {
    return _shallowTraceBuilder.build();
  }

  @Override
  public void setTraceValueSerializer(final Function<T, String> traceValueProvider) {
    _traceValueProvider = traceValueProvider;
  }

  @Override
  public Trace getTrace() {
    TraceBuilder traceBuilder = getTraceBuilder();
    if (traceBuilder != null) {
      return traceBuilder.build();
    } else {
      return Trace.single(getShallowTrace(), "none", 0L);
    }
  }

  /**
   * This template method is invoked when the task is run.
   *
   * @param context the context to use while running this task
   * @return a promise that will have its value set when this task is finished
   * @throws Exception if an error occurs while running the task
   */
  protected abstract Promise<? extends T> run(final Context context) throws Throwable;

  private void traceDone(final T value) {
    _shallowTraceBuilder.setResultType(ResultType.SUCCESS);
    final Function<T, String> traceValueProvider = _traceValueProvider;
    if (traceValueProvider != null) {
      try {
        _shallowTraceBuilder.setValue(traceValueProvider.apply(value));
      } catch (Exception e) {
        _shallowTraceBuilder.setValue(Exceptions.failureToString(e));
      }
    }
  }

  private void done(final T value, final TaskLogger taskLogger) {
    if (transitionDone()) {
      traceDone(value);
      getSettableDelegate().done(value);
      taskLogger.logTaskEnd(BaseTask.this, _traceValueProvider);
    }
  }

  private void fail(final Throwable error, final TaskLogger taskLogger) {
    if (transitionDone()) {
      appendTaskStackTrace(error);
      traceFailure(error);
      getSettableDelegate().fail(error);
      taskLogger.logTaskEnd(BaseTask.this, _traceValueProvider);
    }
  }

  // Concatenate stack traces if kept the original stack trace from the task creation
  private void appendTaskStackTrace(final Throwable error) {
    StackTraceElement[] taskStackTrace = _taskStackTraceHolder != null ? _taskStackTraceHolder.getStackTrace() : null;

    // At a minimum, any stack trace should have at least 3 stack frames (caller + BaseTask + getStackTrace).
    // So if there are less than 3 stack frames available then there's something fishy and it's better to ignore them.
    if (!ParSeqGlobalConfiguration.isCrossThreadStackTracesEnabled() || error == null ||
        taskStackTrace == null || taskStackTrace.length <= 2) {
      return;
    }

    StackTraceElement[] errorStackTrace = error.getStackTrace();
    if (errorStackTrace.length <= 2) {
      return;
    }

    // Skip stack frames up to the BaseTask (useless java.util.concurrent stuff)
    int skipErrorFrames = 1;
    while (skipErrorFrames < errorStackTrace.length) {
      int index = errorStackTrace.length - 1 - skipErrorFrames;
      if (!errorStackTrace[index].getClassName().equals(CANONICAL_NAME) &&
          errorStackTrace[index + 1].getClassName().equals(CANONICAL_NAME)) {
        break;
      }
      skipErrorFrames++;
    }

    // Safeguard against accidentally removing entire stack trace
    if (skipErrorFrames == errorStackTrace.length) {
      skipErrorFrames = 0;
    }

    // Skip stack frames up to the BaseTask (useless Thread.getStackTrace stuff)
    int skipTaskFrames = 1;
    while (skipTaskFrames < taskStackTrace.length) {
      if (!taskStackTrace[skipTaskFrames].getClassName().equals(CANONICAL_NAME) &&
          taskStackTrace[skipTaskFrames - 1].getClassName().equals(CANONICAL_NAME)) {
        break;
      }
      skipTaskFrames++;
    }

    // Safeguard against accidentally removing entire stack trace
    if (skipTaskFrames == taskStackTrace.length) {
      skipTaskFrames = 0;
    }

    int combinedLength = errorStackTrace.length - skipErrorFrames + taskStackTrace.length - skipTaskFrames;
    if (combinedLength <= 0) {
      return;
    }

    StackTraceElement[] concatenatedStackTrace = new StackTraceElement[combinedLength + 1];
    System.arraycopy(errorStackTrace, 0, concatenatedStackTrace,
        0, errorStackTrace.length - skipErrorFrames);
    concatenatedStackTrace[errorStackTrace.length - skipErrorFrames] =
        new StackTraceElement("********** Task \"" + getName() + "\" (above) was instantiated as following (below): **********", "",null, 0);
    System.arraycopy(taskStackTrace, skipTaskFrames, concatenatedStackTrace,
        errorStackTrace.length - skipErrorFrames + 1, taskStackTrace.length - skipTaskFrames);
    error.setStackTrace(concatenatedStackTrace);
  }

  protected boolean transitionRun(final TraceBuilder traceBuilder) {
    State state;
    State newState;
    do {
      state = _stateRef.get();
      if (state.getType() != StateType.INIT) {
        return false;
      }
      newState = state.transitionRun();
    } while (!_stateRef.compareAndSet(state, newState));
    _traceBuilder = traceBuilder;
    traceBuilder.addShallowTrace(_shallowTraceBuilder);
    return true;
  }

  protected void markTaskStarted() {
    _shallowTraceBuilder.setStartNanos(System.nanoTime());
  }

  protected void transitionPending() {
    State state;
    State newState;
    do {
      state = _stateRef.get();
      if (state.getType() != StateType.RUN) {
        return;
      }
      newState = state.transitionPending();
    } while (!_stateRef.compareAndSet(state, newState));
    markTaskPending();
  }

  protected void markTaskPending() {
    _shallowTraceBuilder.setPendingNanos(System.nanoTime());
  }

  protected boolean transitionCancel(final Exception reason) {
    State state;
    State newState;
    do {
      state = _stateRef.get();
      final StateType type = state.getType();
      if (type == StateType.RUN || type == StateType.DONE) {
        return false;
      }
      newState = state.transitionDone();
    } while (!_stateRef.compareAndSet(state, newState));
    return true;
  }

  protected boolean transitionDone() {
    State state;
    State newState;
    do {
      state = _stateRef.get();
      if (state.getType() == StateType.DONE) {
        return false;
      }
      newState = state.transitionDone();
    } while (!_stateRef.compareAndSet(state, newState));
    return true;
  }

  protected SettablePromise<T> getSettableDelegate() {
    return (SettablePromise<T>) super.getDelegate();
  }

  protected static class State {
    private final StateType _type;
    private final int _priority;

    private State(final StateType type, final int priority) {
      _type = type;
      _priority = priority;
    }

    public StateType getType() {
      return _type;
    }

    public int getPriority() {
      return _priority;
    }

    public State transitionRun() {
      return new State(StateType.RUN, _priority);
    }

    public State transitionPending() {
      return new State(StateType.PENDING, _priority);
    }

    public State transitionDone() {
      return new State(StateType.DONE, _priority);
    }

    public static final State INIT = new State(StateType.INIT, Priority.DEFAULT_PRIORITY) {
      @Override
      final public State transitionDone() {
        return DONE;
      };

      @Override
      final public State transitionRun() {
        return RUN;
      }

      @Override
      final public State transitionPending() {
        return PENDING;
      }
    };

    public static final State RUN = new State(StateType.RUN, Priority.DEFAULT_PRIORITY) {
      @Override
      final public State transitionDone() {
        return DONE;
      };

      @Override
      final public State transitionRun() {
        return RUN;
      }

      @Override
      final public State transitionPending() {
        return PENDING;
      }
    };

    public static final State PENDING = new State(StateType.PENDING, Priority.DEFAULT_PRIORITY) {
      @Override
      final public State transitionDone() {
        return DONE;
      };

      @Override
      final public State transitionRun() {
        return RUN;
      }

      @Override
      final public State transitionPending() {
        return PENDING;
      }
    };

    public static final State DONE = new State(StateType.DONE, Priority.DEFAULT_PRIORITY) {
      @Override
      final public State transitionDone() {
        return DONE;
      };

      @Override
      final public State transitionRun() {
        return RUN;
      }

      @Override
      final public State transitionPending() {
        return PENDING;
      }
    };
  }

  private class WrappedContext implements Context {
    private final Context _context;

    public WrappedContext(final Context context) {
      _context = context;
    }

    @Override
    public Cancellable createTimer(final long time, final TimeUnit unit, final Task<?> task) {
      final Cancellable cancellable = _context.createTimer(time, unit, task);
      getTraceBuilder().addRelationship(Relationship.POTENTIAL_PARENT_OF, getShallowTraceBuilder(),
          task.getShallowTraceBuilder());
      return cancellable;
    }

    @Override
    public void run(final Task<?>... tasks) {
      _context.run(tasks);
      for (Task<?> task : tasks) {
        getTraceBuilder().addRelationship(Relationship.POTENTIAL_PARENT_OF, getShallowTraceBuilder(),
            task.getShallowTraceBuilder());
      }
    }

    @Override
    public After after(final Promise<?>... promises) {
      return new WrappedAfter(_context.after(promises));
    }

    @Override
    public Object getEngineProperty(String key) {
      return _context.getEngineProperty(key);
    }

    @Override
    public TraceBuilder getTraceBuilder() {
      return _context.getTraceBuilder();
    }

    private class WrappedAfter implements After {
      private final After _after;

      public WrappedAfter(final After after) {
        _after = after;
      }

      @Override
      public void run(final Task<?> task) {
        _after.run(task);
        getTraceBuilder().addRelationship(Relationship.POTENTIAL_PARENT_OF, getShallowTraceBuilder(),
            task.getShallowTraceBuilder());
      }

      @Override
      public void run(Supplier<Task<?>> taskSupplier) {
        _after.run(() -> {
          Task<?> task = taskSupplier.get();
          if (task != null) {
            getTraceBuilder().addRelationship(Relationship.POTENTIAL_PARENT_OF, getShallowTraceBuilder(),
                task.getShallowTraceBuilder());
          }
          return task;
        } );
      }
    }

    @Override
    public ShallowTraceBuilder getShallowTraceBuilder() {
      return _context.getShallowTraceBuilder();
    }

    @Override
    public Long getPlanId() {
      return _context.getPlanId();
    }

    @Override
    public Long getTaskId() {
      return _context.getTaskId();
    }

    @Override
    public TaskLogger getTaskLogger() {
      return _context.getTaskLogger();
    }

    @Override
    public void runSideEffect(Task<?>... tasks) {
      _context.runSideEffect(tasks);
      for (Task<?> task : tasks) {
        getTraceBuilder().addRelationship(Relationship.POTENTIAL_PARENT_OF, getShallowTraceBuilder(),
            task.getShallowTraceBuilder());
      }
    }

    @Override
    public String getPlanClass() {
      return _context.getPlanClass();
    }
  }

  @Override
  public String toString() {
    return "Task [id=" + _id + ", name=" + _name + "]";
  }

}


/*
 * Copyright 2017 LinkedIn, Inc
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package com.linkedin.parseq.internal;

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.function.Consumer;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.event.Level;

/**
 * This class allows active monitoring of ParSeq threads in order to detect situations where
 * due to programming error task is running for a very long time e.g. due to deadlock, blocking etc.
 *

 * When monitoring is enabled each ParSeq thread has a {@code ThreadLocal<ExecutionMonitorState>} which
 * is updated whenever a {@code Runnable} is started and completed. This class starts a daemon monitoring thread
 * which checks status of all actively monitored threads. When thread is executing a {@code Runnable} for
 * a long time (as specified by {@code durationThresholdNano} parameter a log is emitted at specified log level.
 * Log contains full thread dump as well as state of all actively monitored threads before and after thread dump.
 *

 * The intention of this class is to catch and help solving programming error, not problems related to a java
 * platform or underlying resources e.g. we try to avoid reporting long STW GCs. In order to achieve it the
 * monitoring thread tries to detect significant hiccups (situations when it was not woken up on time) and use it
 * as a signal that stall was caused by external factor such as long STW GC.
 *

 * Monitoring mechanism implemented by this class tries to minimize overhead of ParSeq threads.
 *
 * @author Jaroslaw Odzga (jodzga@linkedin.com)
 *
 */
public class ExecutionMonitor {

  private static final Logger LOG = LoggerFactory.getLogger(ExecutionMonitor.class);
  private static final ThreadDumper THREAD_DUMPER = new ThreadDumper();
  private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("#.###");

  private final ThreadLocal<ExecutionMonitorState> LOCAL_MONITOR = new ThreadLocal<ExecutionMonitorState>() {
    @Override
    protected ExecutionMonitorState initialValue() {
      ExecutionMonitorState state = new ExecutionMonitorState(Thread.currentThread().getId());
      return state;
    };
  };

  private final ConcurrentLinkedQueue<ExecutionMonitorState> _addedMonitors =
      new ConcurrentLinkedQueue<ExecutionMonitorState>();

  private final int _maxMonitors;
  private final long _durationThresholdNano;
  private final long _checkIntervalNano;
  private final long _idleDurationNano;
  private final long _loggingIntervalNano;
  private final long _minStallNano;
  private final int _stallsHistorySize;
  private final Consumer<String> _logger;
  private final Clock _clock;
  private volatile boolean _stopped = false;

  private final Set<ExecutionMonitorState> _monitors = new HashSet<>();
  private final TreeMap<Long, Long> _stalls = new TreeMap<>();
  private final Thread _monitoringThread;

  private long _lastMonitoringStep;
  private long _nextAllowedLogging;
  private long _shortestObservedDelta = Long.MAX_VALUE;

  /**
   * Creates an instance of ExecutionMonitor and starts monitoring daemon thread.
   *
   * @param maxMonitors maximum number of monitored threads. It should be greater than expected number of threads
   * in ParSeq thread pool. When number of active threads exceeds this number then warning is logged and some threads
   * will be ignored.
   * @param durationThresholdNano specifies the duration of execution that is considered as exceedingly long. Thread
   * executing {@code Runnable} longer than this value will trigger log event containing thread dump and state of
   * all actively monitored threads.
   * @param checkIntervalNano interval at which monitoring thread checks for long running tasks.
   * @param idleDurationNano specifies amount of time after which thread is removed from a list of actively monitored
   * threads if it has not been running any tasks. This allows detection of dead and inactive threads.
   * @param loggingIntervalNano specifies minimum amount of time between two log events generated by this class. This
   * is to avoid excessive logging because thread dumps can be large.
   * @param minStallNano specifies minimum amount of time which is considered a significant stall. In order to avoid
   * triggering thread dumps on events such as long STW GCs this class tries to identify stalls cause by external
   * factors by measuring the difference between time at which monitoring thread was woken up and scheduled wake up time.
   * The difference is considered to be a stall if it is larger than this parameter.
   * @param stallsHistorySize size of the maximum stalls history to be kept in memory
   * @param level level at which log events containing state of monitored threads and thread dump is generated.
   * @param clock clock implementation to be used.
   */
  public ExecutionMonitor(int maxMonitors, long durationThresholdNano, long checkIntervalNano, long idleDurationNano,
      long loggingIntervalNano, long minStallNano, int stallsHistorySize, Level level, Clock clock) {
    _maxMonitors = maxMonitors;
    _durationThresholdNano = durationThresholdNano;
    _checkIntervalNano = checkIntervalNano;
    _idleDurationNano = idleDurationNano;
    _loggingIntervalNano = loggingIntervalNano;
    _minStallNano = minStallNano;
    _stallsHistorySize = stallsHistorySize;
    _clock = clock;

    switch(level) {
      case INFO:
        _logger = LOG::info;
        break;
      case DEBUG:
        _logger = LOG::debug;
        break;
      case ERROR:
        _logger = LOG::error;
        break;
      case TRACE:
        _logger = LOG::trace;
        break;
      case WARN:
        _logger = LOG::warn;
        break;
      default:
        _logger = LOG::warn;
        break;
    }
    _monitoringThread = new Thread(this::monitor);
    _monitoringThread.setDaemon(true);
    _monitoringThread.setName("ParSeqExecutionMonitor");
    _monitoringThread.start();
  }

  ExecutionMonitorState getLocalMonitorState() {
    return LOCAL_MONITOR.get();
  }

  void shutdown() {
    _stopped = true;
  }

  /**
   * Main loop of monitoring thread.
   */
  private void monitor() {
    _lastMonitoringStep = _clock.nanoTime();
    _nextAllowedLogging = _lastMonitoringStep;
    while(!_stopped) {
      try {
        _clock.sleepNano(_checkIntervalNano);
      } catch (InterruptedException e) {
        break;
      }
      monitorStep();
    }
  }

  private void monitorStep() {
    long currentTime = _clock.nanoTime();

    checkForStall(currentTime);

    drainAddedMonitorsQueue();

    List<ExecutionMonitorState> toRemove = new ArrayList<>();

    long oldestTimestamp = currentTime;
    boolean thereAreLongRunningThreads = false;

    for (ExecutionMonitorState m : _monitors) {
      if (m._isActive) {
        //we can't write if (m._lastUpdate < oldestTimestamp) because of an overflow of signed long
        if (m._lastUpdate - oldestTimestamp < 0) {
          oldestTimestamp = m._lastUpdate;
        }

        /*
         * stallTime represents time that we assume thread was not responsible for.
         * This is a heuristic that allows us avoid reporting situations such as a long STW GC.
         */
        long stallTime = getStallsSince(m._lastUpdate);
        //
        long activeTime = (currentTime - m._lastUpdate) - stallTime;
        if (activeTime > _durationThresholdNano) {
          thereAreLongRunningThreads = true;
        }
      } else {
        if (currentTime - m._lastUpdate > _idleDurationNano) {
          toRemove.add(m);
        }
      }
    }

    for (ExecutionMonitorState m : toRemove) {
      _monitors.remove(m);
    }

    //generate log events if there was a long running thread and we don't exceed allowed logging frequency
    if (thereAreLongRunningThreads && currentTime - _nextAllowedLogging >= 0) {
      _nextAllowedLogging = currentTime + _loggingIntervalNano;
      logMonitoredThreads(_monitors);
    }

    //remove stalls that will not be used anymore
    trimStalls(oldestTimestamp);

    _lastMonitoringStep = _clock.nanoTime();
  }

  /**
   * Check how much we missed scheduled wake up and if it is larger than _minStallNano
   * then consider it a stall and remember it.
   */
  private void checkForStall(long currentTime) {
    long delta = currentTime - _lastMonitoringStep;
    if (delta < _shortestObservedDelta) {
      _shortestObservedDelta = delta;
    }
    long stall = Math.max(0, delta - _shortestObservedDelta);
    if (stall > _minStallNano) {
      _stalls.put(_lastMonitoringStep, stall);
      if (_stalls.size() > _stallsHistorySize) {
        _stalls.pollFirstEntry();
      }
    }
  }

  private void drainAddedMonitorsQueue() {
    ExecutionMonitorState monitor = null;
    do {
      monitor = _addedMonitors.poll();
      if (monitor != null) {
        if (_monitors.size() < _maxMonitors) {
          _monitors.add(monitor);
        } else {
          LOG.warn("Exceeded number of maximum monitored threads, thread with Id=" + monitor._threadId
              + " will not be monitored");
        }
      }
    } while(monitor != null);
  }

  private void logMonitoredThreads(Set<ExecutionMonitorState> monitoredThreads) {
    StringBuilder sb = new StringBuilder();

    sb.append("Found ParSeq threads running longer than ")
      .append(DECIMAL_FORMAT.format(((double) _durationThresholdNano) / 1000000))
      .append("ms.\n\nMonitored ParSeq threads before thread dump: \n");

    logMonitoredThreads(monitoredThreads, _clock.nanoTime(), sb);

    sb.append("\nThread dump:\n\n");

    THREAD_DUMPER.threadDump(sb);

    sb.append("Monitored ParSeq threads after thread dump: \n");

    logMonitoredThreads(monitoredThreads, _clock.nanoTime(), sb);

    _logger.accept(sb.toString());

  }

  private void logMonitoredThreads(Set<ExecutionMonitorState> monitoredThreads, long currentTime, StringBuilder sb) {
    for (ExecutionMonitorState m : monitoredThreads) {
      long runTime = Math.max(0,  currentTime - m._lastUpdate);
      if (runTime > _durationThresholdNano && m._isActive) {
        sb.append("(!) ");
      } else {
        sb.append("    ");
      }
      sb.append("Thread Id=")
      .append(m._threadId)
      .append(m._isActive ? " busy for " : " idle for ")
      .append(DECIMAL_FORMAT.format(((double)runTime) / 1000000))
      .append("ms\n");
    }
  }

  private void trimStalls(long oldestTimestamp) {
    while (!_stalls.isEmpty() && _stalls.firstKey() < oldestTimestamp) {
      _stalls.remove(_stalls.firstKey());
    }
  }

  long getStallsSince(long lastUpdate) {
    long stall = 0;
    Entry<Long, Long> entry = _stalls.ceilingEntry(lastUpdate);
    while(entry != null) {
      stall += entry.getValue();
      entry = _stalls.higherEntry(entry.getKey());
    }
    return stall;
  }

  class ExecutionMonitorState {

    private final long _threadId;
    private volatile long _lastUpdate = 0;
    private volatile boolean _isActive = false;
    private volatile boolean _isMonitored = false;

    public ExecutionMonitorState(long threadId) {
      _threadId = threadId;
    }

    public void activate() {
      _lastUpdate = _clock.nanoTime();
      _isActive = true;
      if (!_isMonitored) {
        _isMonitored = true;
        _addedMonitors.add(this);
      }
    }

    public void deactivate() {
      _lastUpdate = _clock.nanoTime();
      _isActive = false;
    }

    @Override
    public int hashCode() {
      final int prime = 31;
      int result = 1;
      result = prime * result + (int) (_threadId ^ (_threadId >>> 32));
      return result;
    }

    @Override
    public boolean equals(Object obj) {
      if (this == obj)
        return true;
      if (obj == null)
        return false;
      if (getClass() != obj.getClass())
        return false;
      ExecutionMonitorState other = (ExecutionMonitorState) obj;
      if (_threadId != other._threadId)
        return false;
      return true;
    }
  }
}