/*
 * 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.ozone.lib.lang;

import java.text.MessageFormat;
import org.apache.hadoop.hdds.annotation.InterfaceAudience;
import org.apache.ozone.lib.util.Check;

/**
 * Generic exception that requires error codes and uses the a message
 * template from the error code.
 */
@InterfaceAudience.Private
public class XException extends Exception {
  private ERROR error;

  /**
   * Private constructor used by the public constructors.
   *
   * @param error error code.
   * @param message error message.
   * @param cause exception cause if any.
   */
  private XException(ERROR error, String message, Throwable cause) {
    super(message, cause);
    this.error = error;
  }

  /**
   * Creates an XException using the specified error code. The exception
   * message is resolved using the error code template and the passed
   * parameters.
   *
   * @param error error code for the XException.
   * @param params parameters to use when creating the error message
   * with the error code template.
   */
  @SuppressWarnings({"ThrowableResultOfMethodCallIgnored"})
  public XException(ERROR error, Object... params) {
    this(Check.notNull(error, "error"),
        format(error, params),
        getCause(params));
  }

  /**
   * Returns the error code of the exception.
   *
   * @return the error code of the exception.
   */
  public ERROR getError() {
    return error;
  }

  /**
   * Creates a message using a error message template and arguments.
   * <p>
   * The template must be in JDK <code>MessageFormat</code> syntax
   * (using {#} positional parameters).
   *
   * @param error error code, to get the template from.
   * @param args arguments to use for creating the message.
   *
   * @return the resolved error message.
   */
  private static String format(ERROR error, Object... args) {
    String template = error.getTemplate();
    if (template == null) {
      StringBuilder sb = new StringBuilder();
      for (int i = 0; i < args.length; i++) {
        sb.append(" {").append(i).append('}');
      }
      template = sb.deleteCharAt(0).toString();
    }
    return error + ": " + MessageFormat.format(template, args);
  }

  /**
   * Returns the last parameter if it is an instance of <code>Throwable</code>
   * returns it else it returns NULL.
   *
   * @param params parameters to look for a cause.
   *
   * @return the last parameter if it is an instance of <code>Throwable</code>
   *         returns it else it returns NULL.
   */
  private static Throwable getCause(Object... params) {
    Throwable throwable = null;
    if (params != null && params.length > 0 && params[params.length - 1]
        instanceof Throwable) {
      throwable = (Throwable) params[params.length - 1];
    }
    return throwable;
  }

  /**
   * Interface to define error codes.
   */
  public interface ERROR {
    /**
     * Returns the template for the error.
     *
     * @return the template for the error, the template must be in JDK
     *         <code>MessageFormat</code> syntax (using {#} positional
     *         parameters).
     */
    String getTemplate();
  }
}
