package com.google.net.stubby;

import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;
import com.google.net.stubby.transport.Transport;

import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;

/**
 * Defines the status of an operation using the canonical error space.
 */
@Immutable
public class Status {

  public static final Status OK = new Status(Transport.Code.OK);
  public static final Status CANCELLED = new Status(Transport.Code.CANCELLED);

  public static Status fromThrowable(Throwable t) {
    for (Throwable cause : Throwables.getCausalChain(t)) {
      if (cause instanceof OperationException) {
        return ((Status.OperationException) cause).getStatus();
      } else if (cause instanceof  OperationRuntimeException) {
        return ((Status.OperationRuntimeException) cause).getStatus();
      }
    }
    // Couldn't find a cause with a Status
    return new Status(Transport.Code.INTERNAL, t);
  }

  private final Transport.Code code;
  private final String description;
  private final Throwable cause;

  public Status(Transport.Code code) {
    this(code, null, null);
  }

  public Status(Transport.Code code, @Nullable String description) {
    this(code, description, null);
  }

  public Status(Transport.Code code, @Nullable Throwable cause) {
    this(code, null, cause);
  }

  public Status(Transport.Code code, @Nullable String description, @Nullable Throwable cause) {
    this.code = Preconditions.checkNotNull(code);
    this.description = description;
    this.cause = cause;
  }

  public Transport.Code getCode() {
    return code;
  }

  @Nullable
  public String getDescription() {
    return description;
  }

  @Nullable
  public Throwable getCause() {
    return cause;
  }

  public boolean isOk() {
    return OK.getCode() == getCode();
  }

  /**
   * Override this status with another if allowed.
   */
  public Status overrideWith(Status newStatus) {
    if (this.getCode() == Transport.Code.OK || newStatus.code == Transport.Code.OK) {
      return this;
    } else {
      return newStatus;
    }
  }

  public RuntimeException asRuntimeException() {
    return new OperationRuntimeException(this);
  }

  public Exception asException() {
    return new OperationException(this);
  }

  /**
   * Exception thrown by implementations while managing an operation.
   */
  public static class OperationException extends Exception {

    private final Status status;

    public OperationException(Status status) {
      super(status.getDescription(), status.getCause());
      this.status = status;
    }

    public Status getStatus() {
      return status;
    }
  }

  /**
   * Runtime exception thrown by implementations while managing an operation.
   */
  public static class OperationRuntimeException extends RuntimeException {

    private final Status status;

    public OperationRuntimeException(Status status) {
      super(status.getDescription(), status.getCause());
      this.status = status;
    }

    public Status getStatus() {
      return status;
    }
  }

  @Override
  public String toString() {
    StringBuilder builder = new StringBuilder();
    builder.append("[").append(code);
    if (description != null) {
      builder.append(";").append(description);
    }
    if (cause != null) {
      builder.append(";").append(cause);
    }
    builder.append("]");
    return builder.toString();
  }
}

@@ -2520,32 +2520,58 @@
 ) %7B%0A      super(
+status.getCode() + %22: %22 + 
 status.getDescri
@@ -2956,32 +2956,58 @@
 ) %7B%0A      super(
+status.getCode() + %22: %22 + 
 status.getDescri

Makes the Status-generated exceptions contain the status code.