/*
 * 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 com.sun.jini.constants;

import java.io.ObjectStreamException;
import java.net.NoRouteToHostException;
import java.net.PortUnreachableException;
import java.net.ProtocolException;
import java.rmi.RemoteException;
import java.rmi.ConnectIOException;
import java.rmi.NoSuchObjectException;
import java.rmi.MarshalException;
import java.rmi.UnmarshalException;
import java.rmi.UnexpectedException;
import java.rmi.UnknownHostException;
import java.rmi.ServerException;
import java.rmi.ServerError;
import net.jini.io.UnsupportedConstraintException;

/**
 * Various constants useful in processing exceptions
 *
 */
public class ThrowableConstants {
    /**
     * Value returned by <code>retryable</code> to indicate that the
     * passed <code>Throwable</code> does not provide any new
     * information on the state of the object that threw it.
     * @see #retryable
     */
    final static public int INDEFINITE = 0;

    /**
     * Value returned by <code>retryable</code> to indicate that the
     * passed <code>Throwable</code> implies that retrying the
     * operation that threw the <code>Throwable</code> with the same
     * arguments and the same expected return value would not be
     * fruitful.  
     * @see #retryable
     */
    final static public int BAD_INVOCATION = 1;

    /**
     * Value returned by <code>retryable</code> to indicate that the
     * passed <code>Throwable</code> implies that any further
     * operations on the object that threw the <code>Throwable</code>
     * would not be fruitful.  
     * @see #retryable
     */
    final static public int BAD_OBJECT = 2;

    /**
     * Value returned by <code>retryable</code> to indicate that the
     * passed <code>Throwable</code> was of a type that could not be
     * classified.
     * @see #retryable
     */
    final static public int UNCATEGORIZED = 3;

    /**
     * Attempt to classify the passed <code>Throwable</code> in terms of 
     * what it implies about the probability of success of future operations
     * on the object that threw the exception. <p>
     *
     * Note, the classification used by this method tends to assume
     * the worst. For exceptions that represent conditions that could
     * get better by themselves but probably will not, it will return
     * <code>BAD_OBJECT</code> or <code>BAD_INVOCATION</code> instead
     * of <code>INDEFINITE</code>. This makes it suitable for
     * situations where it is better to give up, fail early, and
     * notify the next layer up that something is wrong than to
     * continue silently and retry. It is probably not a good choice
     * for situations where the stakes are higher, like deciding when
     * to give up on a prepared transaction.
     *
     * @return <code>INDEFINITE</code>, <code>BAD_INVOCATION</code>,
     * or <code>BAD_OBJECT</code> if the exception is a
     * <code>RuntimeException</code>, <code>Error</code>, or
     * <code>java.rmi.RemoteException</code> depending on the details of 
     * the <code>Throwable</code>.  Otherwise return 
     * <code>UNCATEGORIZED</code>
     * @throws NullPointerException if the passed <code>Throwable</code> is
     * <code>null</code>
     */
    public static int retryable(Throwable t) {
	if (t == null) 
	    throw new NullPointerException("Must pass a non-null Throwable");

	if (t instanceof RuntimeException) {
	    return BAD_INVOCATION;
	}

	if (t instanceof Error) {
	    if ((t instanceof OutOfMemoryError) ||
		(t instanceof LinkageError))
	    {
		return INDEFINITE;
	    }

	    if (t instanceof StackOverflowError)
		return BAD_INVOCATION;

	    return BAD_OBJECT;
	}

	if (t instanceof RemoteException) {
	    final RemoteException re = (RemoteException)t;

	    if (re instanceof NoSuchObjectException ||
		re instanceof UnexpectedException ||
		re instanceof UnknownHostException) 
	    {
		return BAD_OBJECT;	    
	    }

	    final Throwable detail = re.detail;
	    if (detail == null)
		return INDEFINITE;
	    	    
	    if (re instanceof MarshalException || 
		re instanceof UnmarshalException) 
	    {
		if (detail instanceof ObjectStreamException)
		    return BAD_INVOCATION;

		final int drs = retryable(detail);

		if (drs == BAD_OBJECT || drs == BAD_INVOCATION)
		    return BAD_INVOCATION;

		return INDEFINITE;
	    }

	    if (re instanceof ConnectIOException) {
		if (detail instanceof NoRouteToHostException  ||
		    detail instanceof PortUnreachableException ||
		    detail instanceof ProtocolException)
		{
		    return BAD_OBJECT;
		}

		if (detail instanceof UnsupportedConstraintException ||
		    detail instanceof ObjectStreamException)
		{
		    return BAD_INVOCATION;
		}

		return INDEFINITE;
	    }

	    if (re instanceof ServerException) {
		final int drs = retryable(detail);
		if (drs == BAD_OBJECT)
		    return BAD_INVOCATION;

		return drs;
	    }

	    if (re instanceof ServerError) {
		return retryable(detail);
	    }

	    return INDEFINITE;
	}
	    
	return UNCATEGORIZED;

    }
}
