/**
 *
 * 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.hadoop.hbase;

import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.ipc.RemoteException;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * An immutable class which contains a static method for handling
 * org.apache.hadoop.ipc.RemoteException exceptions.
 */
@InterfaceAudience.Private
public class RemoteExceptionHandler {
  /* Not instantiable */
  private RemoteExceptionHandler() {super();}

  /**
   * Examine passed Throwable.  See if its carrying a RemoteException. If so,
   * run {@link #decodeRemoteException(RemoteException)} on it.  Otherwise,
   * pass back <code>t</code> unaltered.
   * @param t Throwable to examine.
   * @return Decoded RemoteException carried by <code>t</code> or
   * <code>t</code> unaltered.
   */
  public static Throwable checkThrowable(final Throwable t) {
    Throwable result = t;
    if (t instanceof RemoteException) {
      try {
        result =
          RemoteExceptionHandler.decodeRemoteException((RemoteException)t);
      } catch (Throwable tt) {
        result = tt;
      }
    }
    return result;
  }

  /**
   * Examine passed IOException.  See if its carrying a RemoteException. If so,
   * run {@link #decodeRemoteException(RemoteException)} on it.  Otherwise,
   * pass back <code>e</code> unaltered.
   * @param e Exception to examine.
   * @return Decoded RemoteException carried by <code>e</code> or
   * <code>e</code> unaltered.
   */
  public static IOException checkIOException(final IOException e) {
    Throwable t = checkThrowable(e);
    return t instanceof IOException? (IOException)t: new IOException(t);
  }

  /**
   * Converts org.apache.hadoop.ipc.RemoteException into original exception,
   * if possible. If the original exception is an Error or a RuntimeException,
   * throws the original exception.
   *
   * @param re original exception
   * @return decoded RemoteException if it is an instance of or a subclass of
   *         IOException, or the original RemoteException if it cannot be decoded.
   *
   * @throws IOException indicating a server error ocurred if the decoded
   *         exception is not an IOException. The decoded exception is set as
   *         the cause.
   * @deprecated Use {@link RemoteException#unwrapRemoteException()} instead.
   * In fact we should look into deprecating this whole class - St.Ack 2010929
   */
  public static IOException decodeRemoteException(final RemoteException re)
  throws IOException {
    IOException i = re;

    try {
      Class<?> c = Class.forName(re.getClassName());

      Class<?>[] parameterTypes = { String.class };
      Constructor<?> ctor = c.getConstructor(parameterTypes);

      Object[] arguments = { re.getMessage() };
      Throwable t = (Throwable) ctor.newInstance(arguments);

      if (t instanceof IOException) {
        i = (IOException) t;
      } else {
        i = new IOException("server error");
        i.initCause(t);
        throw i;
      }

    } catch (ClassNotFoundException x) {
      // continue
    } catch (NoSuchMethodException x) {
      // continue
    } catch (IllegalAccessException x) {
      // continue
    } catch (InvocationTargetException x) {
      // continue
    } catch (InstantiationException x) {
      // continue
    }
    return i;
  }
}
