/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.beam.sdk.coders;

/**
 * The exception thrown when a {@link CoderRegistry} or {@link CoderProvider} cannot
 * provide a {@link Coder} that has been requested.
 */
public class CannotProvideCoderException extends Exception {
  private final ReasonCode reason;

  public CannotProvideCoderException(String message) {
    this(message, ReasonCode.UNKNOWN);
  }

  public CannotProvideCoderException(String message, ReasonCode reason) {
    super(message);
    this.reason = reason;
  }

  public CannotProvideCoderException(String message, Throwable cause) {
    this(message, cause, ReasonCode.UNKNOWN);
  }

  public CannotProvideCoderException(String message, Throwable cause, ReasonCode reason) {
    super(message, cause);
    this.reason = reason;
  }

  public CannotProvideCoderException(Throwable cause) {
    this(cause, ReasonCode.UNKNOWN);
  }

  public CannotProvideCoderException(Throwable cause, ReasonCode reason) {
    super(cause);
    this.reason = reason;
  }

  /**
   * @return the reason that Coder inference failed.
   */
  public ReasonCode getReason() {
    return reason;
  }

  /**
   * Returns the inner-most {@link CannotProvideCoderException} when they are deeply nested.
   *
   * <p>For example, if a coder for {@code List<KV<Integer, Whatsit>>} cannot be provided because
   * there is no known coder for {@code Whatsit}, the root cause of the exception should be a
   * CannotProvideCoderException with details pertinent to {@code Whatsit}, suppressing the
   * intermediate layers.
   */
  public Throwable getRootCause() {
    Throwable cause = getCause();
    if (cause == null) {
      return this;
    } else if (!(cause instanceof CannotProvideCoderException)) {
      return cause;
    } else {
      return ((CannotProvideCoderException) cause).getRootCause();
    }
  }

  /**
   * Indicates the reason that {@link Coder} inference failed.
   */
  public enum ReasonCode {
    /**
     * The reason a coder could not be provided is unknown or does not have an established
     * {@link ReasonCode}.
     */
    UNKNOWN,

    /**
     * The reason a coder could not be provided is type erasure, for example when requesting
     * coder inference for a {@code List<T>} where {@code T} is unknown.
     */
    TYPE_ERASURE,

    /**
     * The reason a coder could not be provided is because the type variable {@code T} is
     * over specified with multiple incompatible coders.
     */
    OVER_SPECIFIED
  }
}
