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.lib.util.Check;
022    
023    import java.text.MessageFormat;
024    
025    /**
026     * Generic exception that requires error codes and uses the a message
027     * template from the error code.
028     */
029    public class XException extends Exception {
030    
031      /**
032       * Interface to define error codes.
033       */
034      public static interface ERROR {
035    
036        /**
037         * Returns the template for the error.
038         *
039         * @return the template for the error, the template must be in JDK
040         *         <code>MessageFormat</code> syntax (using {#} positional parameters).
041         */
042        public String getTemplate();
043    
044      }
045    
046      private ERROR error;
047    
048      /**
049       * Private constructor used by the public constructors.
050       *
051       * @param error error code.
052       * @param message error message.
053       * @param cause exception cause if any.
054       */
055      private XException(ERROR error, String message, Throwable cause) {
056        super(message, cause);
057        this.error = error;
058      }
059    
060      /**
061       * Creates an XException using another XException as cause.
062       * <p/>
063       * The error code and error message are extracted from the cause.
064       *
065       * @param cause exception cause.
066       */
067      public XException(XException cause) {
068        this(cause.getError(), cause.getMessage(), cause);
069      }
070    
071      /**
072       * Creates an XException using the specified error code. The exception
073       * message is resolved using the error code template and the passed
074       * parameters.
075       *
076       * @param error error code for the XException.
077       * @param params parameters to use when creating the error message
078       * with the error code template.
079       */
080      @SuppressWarnings({"ThrowableResultOfMethodCallIgnored"})
081      public XException(ERROR error, Object... params) {
082        this(Check.notNull(error, "error"), format(error, params), getCause(params));
083      }
084    
085      /**
086       * Returns the error code of the exception.
087       *
088       * @return the error code of the exception.
089       */
090      public ERROR getError() {
091        return error;
092      }
093    
094      /**
095       * Creates a message using a error message template and arguments.
096       * <p/>
097       * The template must be in JDK <code>MessageFormat</code> syntax
098       * (using {#} positional parameters).
099       *
100       * @param error error code, to get the template from.
101       * @param args arguments to use for creating the message.
102       *
103       * @return the resolved error message.
104       */
105      private static String format(ERROR error, Object... args) {
106        String template = error.getTemplate();
107        if (template == null) {
108          StringBuilder sb = new StringBuilder();
109          for (int i = 0; i < args.length; i++) {
110            sb.append(" {").append(i).append("}");
111          }
112          template = sb.deleteCharAt(0).toString();
113        }
114        return error + ": " + MessageFormat.format(template, args);
115      }
116    
117      /**
118       * Returns the last parameter if it is an instance of <code>Throwable</code>
119       * returns it else it returns NULL.
120       *
121       * @param params parameters to look for a cause.
122       *
123       * @return the last parameter if it is an instance of <code>Throwable</code>
124       *         returns it else it returns NULL.
125       */
126      private static Throwable getCause(Object... params) {
127        Throwable throwable = null;
128        if (params != null && params.length > 0 && params[params.length - 1] instanceof Throwable) {
129          throwable = (Throwable) params[params.length - 1];
130        }
131        return throwable;
132      }
133    
134    }