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    
019    package org.apache.hadoop.lib.lang;
020    
021    import org.apache.hadoop.classification.InterfaceAudience;
022    import org.apache.hadoop.lib.util.Check;
023    
024    import 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
031    public 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    }