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