001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019package org.apache.hadoop.lib.lang;
020
021import org.apache.hadoop.classification.InterfaceAudience;
022import org.apache.hadoop.lib.util.Check;
023
024import java.text.MessageFormat;
025
026/**
027 * Generic exception that requires error codes and uses the a message
028 * template from the error code.
029 */
030@InterfaceAudience.Private
031public class XException extends Exception {
032
033  /**
034   * Interface to define error codes.
035   */
036  public static interface ERROR {
037
038    /**
039     * Returns the template for the error.
040     *
041     * @return the template for the error, the template must be in JDK
042     *         <code>MessageFormat</code> syntax (using {#} positional parameters).
043     */
044    public String getTemplate();
045
046  }
047
048  private ERROR error;
049
050  /**
051   * Private constructor used by the public constructors.
052   *
053   * @param error error code.
054   * @param message error message.
055   * @param cause exception cause if any.
056   */
057  private XException(ERROR error, String message, Throwable cause) {
058    super(message, cause);
059    this.error = error;
060  }
061
062  /**
063   * Creates an XException using another XException as cause.
064   * <p/>
065   * The error code and error message are extracted from the cause.
066   *
067   * @param cause exception cause.
068   */
069  public XException(XException cause) {
070    this(cause.getError(), cause.getMessage(), cause);
071  }
072
073  /**
074   * Creates an XException using the specified error code. The exception
075   * message is resolved using the error code template and the passed
076   * parameters.
077   *
078   * @param error error code for the XException.
079   * @param params parameters to use when creating the error message
080   * with the error code template.
081   */
082  @SuppressWarnings({"ThrowableResultOfMethodCallIgnored"})
083  public XException(ERROR error, Object... params) {
084    this(Check.notNull(error, "error"), format(error, params), getCause(params));
085  }
086
087  /**
088   * Returns the error code of the exception.
089   *
090   * @return the error code of the exception.
091   */
092  public ERROR getError() {
093    return error;
094  }
095
096  /**
097   * Creates a message using a error message template and arguments.
098   * <p/>
099   * The template must be in JDK <code>MessageFormat</code> syntax
100   * (using {#} positional parameters).
101   *
102   * @param error error code, to get the template from.
103   * @param args arguments to use for creating the message.
104   *
105   * @return the resolved error message.
106   */
107  private static String format(ERROR error, Object... args) {
108    String template = error.getTemplate();
109    if (template == null) {
110      StringBuilder sb = new StringBuilder();
111      for (int i = 0; i < args.length; i++) {
112        sb.append(" {").append(i).append("}");
113      }
114      template = sb.deleteCharAt(0).toString();
115    }
116    return error + ": " + MessageFormat.format(template, args);
117  }
118
119  /**
120   * Returns the last parameter if it is an instance of <code>Throwable</code>
121   * returns it else it returns NULL.
122   *
123   * @param params parameters to look for a cause.
124   *
125   * @return the last parameter if it is an instance of <code>Throwable</code>
126   *         returns it else it returns NULL.
127   */
128  private static Throwable getCause(Object... params) {
129    Throwable throwable = null;
130    if (params != null && params.length > 0 && params[params.length - 1] instanceof Throwable) {
131      throwable = (Throwable) params[params.length - 1];
132    }
133    return throwable;
134  }
135
136}