package org.jacorb.orb;

/*
 *        JacORB - a free Java ORB
 *
 *   Copyright (C) 1997-2014 Gerald Brose / The JacORB Team.
 *
 *   This library is free software; you can redistribute it and/or
 *   modify it under the terms of the GNU Library General Public
 *   License as published by the Free Software Foundation; either
 *   version 2 of the License, or (at your option) any later version.
 *
 *   This library is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *   Library General Public License for more details.
 *
 *   You should have received a copy of the GNU Library General Public
 *   License along with this library; if not, write to the Free
 *   Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import org.jacorb.config.Configurable;
import org.jacorb.config.Configuration;
import org.jacorb.config.ConfigurationException;
import org.jacorb.ir.RepositoryID;
import org.jacorb.orb.giop.ReplyInputStream;
import org.jacorb.util.ObjectUtil;
import org.omg.CORBA.BAD_PARAM;
import org.omg.CORBA.ExceptionList;
import org.omg.CORBA.INTERNAL;
import org.omg.CORBA.NO_IMPLEMENT;
import org.omg.CORBA.TypeCode;
import org.omg.CORBA.UserException;
import org.omg.GIOP.ReplyStatusType_1_2;
import org.slf4j.Logger;

/**
 * JacORB-specific implementation of
 * <code>org.omg.Messaging.ExceptionHolder</code>.  An instance of this
 * type is used to pass an exception to a reply handler.
 *
 * @author Andre Spiegel {@literal <spiegel@gnu.org>}
 */
public class ExceptionHolderImpl
    extends org.omg.Messaging.ExceptionHolder
    implements Configurable
{
    private Logger logger = null;
    private final ORB orb;

    /**
     * No-arg constructor for demarshaling.
     */
    public ExceptionHolderImpl(ORB orb)
    {
        super();

        this.orb = orb;

        try
        {
           configure (orb.getConfiguration ());
        }
        catch (ConfigurationException ex)
        {
           throw new INTERNAL ("Caught configuration exception." + ex);
        }
    }

    /**
     * Constructs an ExceptionHolderImpl object from an input stream.
     * It is assumed that the reply status of this input stream is
     * either USER_EXCEPTION or SYSTEM_EXCEPTION.  If it has another
     * status, a RuntimeException is thrown.
     */
    public ExceptionHolderImpl(ORB orb, ReplyInputStream inputStream )
    {
        this(orb);

        int status = inputStream.getStatus().value();
        if ( status == ReplyStatusType_1_2._USER_EXCEPTION )
        {
            is_system_exception = false;
        }
        else if ( status == ReplyStatusType_1_2._SYSTEM_EXCEPTION )
        {
            is_system_exception = true;
        }
        else
        {
            throw new BAD_PARAM( "attempt to create ExceptionHolder " +
                                        "for non-exception reply" );
        }
        byte_order          = inputStream.getLittleEndian();
        marshaled_exception = inputStream.getBody();
    }

    public ExceptionHolderImpl(ORB orb, org.omg.CORBA.SystemException exception)
    {
        this(orb);

        is_system_exception = true;
        byte_order          = false;

        final CDROutputStream out = new CDROutputStream(orb);

        try
        {
            SystemExceptionHelper.write(out, exception);
            marshaled_exception = out.getBufferCopy();
        }
        finally
        {
            out.close();
        }
    }

    @Override
    public void configure(Configuration configuration)
        throws ConfigurationException
    {
        logger =
            configuration.getLogger("org.jacorb.orb.exc_holder");
    }


    @Override
    public void raise_exception_with_list (TypeCode[] exc_list) throws UserException
    {
        throw new NO_IMPLEMENT ("NYI");
    }

    @Override
    public void raise_exception()
        throws UserException
    {
        final CDRInputStream input =
            new CDRInputStream (orb, marshaled_exception, byte_order);

        try
        {
            if ( is_system_exception )
            {
                throw SystemExceptionHelper.read( input );
            }

            input.mark( 0 );
            String id = input.read_string();

            try
            {
                input.reset();
            }
            catch( IOException e )
            {
                logger.error( "Unexpected IOException: ", e);

                throw new INTERNAL("Unexpected IOException: " + e);
            }

            final UserException result;
            try
            {
                result = exceptionFromHelper( id, input );
            }
            catch( Exception e )
            {
                logger.error("error reading exception", e);

                throw new org.omg.CORBA.UnknownUserException();
            }
            throw result;
        }
        finally
        {
            input.close();
        }
    }

    public void raise_exception_with_list( ExceptionList exc_list )
        throws UserException
    {
        throw new org.omg.CORBA.NO_IMPLEMENT(
           "raise_exception_with_list not yet implemented" );
    }

    /**
     * For testing.
     */
    @Override
    public String toString()
    {
        StringBuffer result = new StringBuffer();
        for (int i=0; i<marshaled_exception.length; i++)
        {
            result.append (marshaled_exception[i]);
            result.append ('(');
            result.append ((char)marshaled_exception[i]);
            result.append (")  ");
        }
        return result.toString();
    }

    /**
     * Given a repository id, tries to find a helper for the corresponding
     * class and uses it to unmarshal an instance of this class from
     * the given InputStream.
     */
    public org.omg.CORBA.UserException exceptionFromHelper
                                ( String repositoryID,
                                  org.omg.CORBA.portable.InputStream input )
        throws ClassNotFoundException,
               NoSuchMethodException,
               IllegalAccessException,
               InvocationTargetException
    {
        final String helperClassName = RepositoryID.className(repositoryID, "Helper", null);

        try
        {
            final Class<?> helperClazz = ObjectUtil.classForName (helperClassName);

            return exceptionFromHelper(input, helperClazz);
        }
        catch (ClassNotFoundException e)
        {
            final String repositoryIDWithoutPragmaPrefix = stripPragmaPrefix(repositoryID);

            if ( ! repositoryIDWithoutPragmaPrefix.equals (repositoryID))
            {
                if (logger.isDebugEnabled())
                {
                    logger.debug("unable to locate class " + helperClassName + " for repository ID " + repositoryID + ". Retrying without pragma prefix: " + repositoryIDWithoutPragmaPrefix);
                }
                return exceptionFromHelper(repositoryIDWithoutPragmaPrefix, input);
            }
            else
            {
                // if class doesn't exist, let exception propagate
                throw e;
            }
        }
    }


    /**
     * try to strip off pragma prefix information.
     * returns the unmodified string if not possible
     */
    private String stripPragmaPrefix(String original)
    {
        final int index = original.indexOf('.', 4);
        final int versionIndex = original.lastIndexOf(':');

        if (index > 0 && index < versionIndex)
        {
            return "IDL:" + original.substring(original.indexOf('/') + 1);
        }
        return original;
    }


    private org.omg.CORBA.UserException exceptionFromHelper(org.omg.CORBA.portable.InputStream input, Class<?> helperClazz) throws NoSuchMethodException, ClassNotFoundException, IllegalAccessException, InvocationTargetException
    {
        // helper must not be null from here on

        // get read method from helper and invoke it,
        // i.e. read the object from the stream
        final Method readMethod =
        helperClazz.getMethod( "read", new Class[]{ org.omg.CORBA.portable.InputStream.class });

        return ( org.omg.CORBA.UserException ) readMethod.invoke( null, new java.lang.Object[]{ input } );
    }

    /**
     * Marshals this object into a new buffer and returns that buffer.
     */
    public byte[] marshal()
    {
         final CDROutputStream out = new CDROutputStream( orb );
         try
         {
             out.write_value( this, "IDL:omg.org/Messaging/ExceptionHolder:1.0" );
             return out.getBufferCopy();
         }
         finally
         {
             out.close();
         }
    }
}
