function err = handleJavaException(obj, caughtException)
; %#ok Undocumented
    % function to return an appropriate error message for a variety of java
    % exceptions that the proxy layer could throw.  This function does
    % nothing if the exception is not one of the ones it recognizes.
    % objectType should be either 'jobmanager' or 'worker' as appropriate.

% Copyright 2004-2018 The MathWorks, Inc.

nExpectArgs = 2;
assert(nargin == nExpectArgs, ...
       'parallel:internal:cluster:HandleJavaExceptionIncorrectNargin', ...
       'handleJavaException was given %d args instead of %d.', nargin, nExpectArgs);
   
    % Filter out all non-Java errors accidentally passed to this method
    if ~isa(caughtException, 'matlab.exception.JavaException')
        err = caughtException;
        return;
    end
    
    % The Exception object in Java that caused the error
    exceptionObject = caughtException.ExceptionObject;
    
    % Now get the type of the exception for processing
    exceptionType = class(exceptionObject);
    
    % Our own CallableExecutionException wraps the original exception, so unwrap it here
    if isa(exceptionObject, 'com.mathworks.toolbox.distcomp.util.CallableExecutionException')
        cause = exceptionObject.getCause();
        if ~isempty(cause)
            exceptionObject = cause;
            exceptionType = class(cause);
        end
        originalExceptionWasCallableExecutionException = true;
    else
        originalExceptionWasCallableExecutionException = false;
    end
    
    % java.rmi.ServerException and java.util.concurrent.ExecutionException
    % wrap the original exception, so unwrap the original exception before processing
    if isa(exceptionObject, 'java.rmi.ServerException')
        cause = exceptionObject.getCause();
        if ~isempty(cause)
            exceptionObject = cause;
            exceptionType = class(cause);
        end
    end
    
    if isa(exceptionObject, 'java.util.concurrent.ExecutionException')
        cause = exceptionObject.getCause();
        if ~isempty(cause)
            exceptionObject = cause;
            exceptionType = class(cause);
        end
    end

    % Our own DistcompException may also wrap the original exception, so unwrap it here
    if isa(exceptionObject, 'com.mathworks.toolbox.distcomp.mjs.DistcompException')
        cause = exceptionObject.getCause();
        if ~isempty(cause)
            exceptionObject = cause;
            exceptionType = class(cause);
        end
    end
    
    % TransferableMJSException and TransferableIOException wrap most exceptions for backwards compatibility.
    % In this case we would like the original exception's class name to preserve the old error message behaviour.
    % The stack trace should only ever be printed to the logs, not to the output window
    isTransferableMJSException = isa(exceptionObject, 'com.mathworks.toolbox.distcomp.mjs.TransferableMJSException');
    isTransferableIOException = isa(exceptionObject, 'com.mathworks.toolbox.distcomp.mjs.TransferableIOException');
    isTransferableException = isTransferableMJSException || isTransferableIOException;
    
    if isTransferableException
        exceptionType = char(exceptionObject.getOriginalExceptionClass());
        stackTrace = char(exceptionObject.getOriginalExceptionStackTrace());
        
        % We might wrap a DistcompException in a Transferable*Exception. In this case, try to parse out
        % the original exception type from the stack trace. This code is for legacy behaviour only.
        % strcmp needed because we have lost access to the exception object
        if strcmp(exceptionType, 'com.mathworks.toolbox.distcomp.mjs.DistcompException')
            % dct_getJavaExceptionCause() is legacy code for extracting the cause
            [hasCause, causeExceptionType] = dct_getJavaExceptionCause(stackTrace);
            if hasCause
                exceptionType = causeExceptionType;
            end
        end
    else
        % If the exception is not wrapped in a Transferable*Exception, its message should be a stack trace.
        % Take the stack trace from the top-most exception (i.e. caughtException)
        stackTrace = caughtException.message;
    end
    
    % Create an MException from the (user-friendly) error message and log the stack trace
    msg = iFindErrorMessage(obj, exceptionType);
    loggingMessage = char(exceptionObject.getMessage());
    
    % Perform the old error message behaviour, if possible
    if ~isempty(msg)
        err = MException(msg);
    
    % Deserialize exception due to a backwards-incompatible class. If we do our job right we should never be here, but mistakes happen
    elseif isa(exceptionObject, 'java.rmi.UnmarshalException') || isa(exceptionObject, 'java.lang.ClassNotFoundException') || isa(exceptionObject, 'java.io.InvalidClassException')
        err = MException(message('parallel:cluster:MJSExceptionTransferFail', char(exceptionObject.getLocalizedMessage())));
        
    % From 2016a onwards, all exceptions occurring on the server (including our own MatlabIdentified ones, 3rd party exceptions, unchecked Java exceptions, etc.)
    % should be wrapped in a TransferableMJSException or TransferableIOException to ensure backwards compatibility. If not, an exception thrown on the server
    % that does not exist on the client will cause a java.rmi.UnmarshalException, java.lang.ClassNotFoundException, or a java.io.InvalidClassException.
    % See http://inside.mathworks.com/wiki/MJS_long-term_multi-version_support_TransferableMJSException for more details
    elseif isTransferableException
        originalExceptionMessageID = char(exceptionObject.getOriginalExceptionMessageID());
        if strcmp(originalExceptionMessageID, 'parallel:cluster:UnidentifiedJavaException')
            % Include the transferable exception prefix message when the wrapped exception is not a MatlabIdentified one
            err = MException(message('parallel:cluster:UnidentifiedJavaException', char(exceptionObject.getLocalizedMessage())));
        else
            % Otherwise behave as if the exception was never wrapped in the first place
            err = MException(originalExceptionMessageID, '%s', char(exceptionObject.getLocalizedMessage()));
        end
    elseif originalExceptionWasCallableExecutionException
        if (isa(exceptionObject, 'com.mathworks.util.MatlabIdentified'))
            err = MException(char(exceptionObject.getMessageID()), '%s', char(exceptionObject.getLocalizedMessage()));
        else
            err = MException(message('parallel:cluster:UnidentifiedJavaException', char(exceptionObject.getLocalizedMessage())));
        end
    else
        % It is an unidentified Java exception that we don't know how to handle
        err = MException(caughtException.identifier, '%s', caughtException.message);
    end
    
    % Log the stack trace
    dctSchedulerMessage(0, 'Caught a %s. Original exception message: %s\n%s', class(exceptionObject), loggingMessage, stackTrace);

end

function msg = iFindErrorMessage(obj, exceptionType)
    msg = [];
    % Some error messages need to tell the user which JVM this is (either worker or local) so
    % define a string based on isdmlworker
    if system_dependent('isdmlworker')
        jvmType = 'worker';
    else
        jvmType = 'local';
    end

    if isa(obj, 'distcomp.jobmanager') || isa( obj, 'parallel.Cluster' )
        switch exceptionType
            case 'com.mathworks.toolbox.distcomp.mjs.storage.JobNotFoundException'
                msg = message('parallel:job:MJSJobNotFound');
            case 'com.mathworks.toolbox.distcomp.mjs.jobmanager.SavePausedStateException'
                msg = message('parallel:cluster:MJSPausedStateNotSaved');
            case 'com.mathworks.toolbox.distcomp.mjs.jobmanager.SaveRunningStateException'
                msg = message('parallel:cluster:MJSRunningStateNotSaved');
            case 'com.mathworks.toolbox.distcomp.util.PortUnavailableException'
                msg = message('parallel:cluster:MJSPortUnavailable');
            case 'java.net.ConnectException' 
                msg = message('parallel:cluster:MJSUnreachable'); 
            case 'java.rmi.ConnectException' 
                msg = message('parallel:cluster:MJSUnreachable'); 
            case 'java.rmi.RemoteException'
                msg = message('parallel:cluster:MJSUnreachable'); 
            case 'java.rmi.ConnectIOException'
                msg = message('parallel:cluster:MJSUnreachable'); 
            case 'java.rmi.NoSuchObjectException'
                msg = message('parallel:cluster:MJSUnreachable'); 
            case 'com.mathworks.toolbox.distcomp.mjs.peerrmi.ClientOutputGroupCreationException'
                msg = message('parallel:cluster:MJSUnreachable');
            case 'com.mathworks.toolbox.distcomp.mjs.peerrmi.RemoteMethodInvocationHandler$MethodCallTimeoutException'
                msg = message('parallel:cluster:MJSUnreachable');
            case 'com.mathworks.toolbox.distcomp.mjs.peerrmi.RemoteMethodInvocationHandler$NoReturnMessageException'
                msg = message('parallel:cluster:MJSUnreachable');
            case 'com.mathworks.toolbox.distcomp.mjs.peerrmi.RemoteMethodInvocationHandler$FailedToSendInvocationException'
                msg = message('parallel:cluster:MJSUnreachable');
            case 'com.mathworks.toolbox.distcomp.mjs.peerrmi.PeerRmiDispatcher$NoSuchObjectInRegistryException'
                msg = message('parallel:cluster:MJSUnreachable');                
            case 'com.mathworks.toolbox.distcomp.mjs.DistcompProxy$SerializeProxyException'
                msg = message('parallel:cluster:MJSReachableInvalidData');
           case 'com.mathworks.toolbox.distcomp.mjs.storage.CredentialStorageException'
               msg = message('parallel:cluster:MJSCredentialStorageFailed');
           case 'com.mathworks.toolbox.distcomp.mjs.jobmanager.LogRetrievalException'
               msg = message('parallel:cluster:MJSLogRetrievalFailed');
           case 'com.mathworks.toolbox.distcomp.mjs.datastore.NullLargeDataInvokerException'
               msg = message('parallel:cluster:MJSNoLargeDataInvoker');
       end
    elseif isa(obj, 'distcomp.worker') || isa( obj, 'parallel.cluster.MJSWorker' )
        switch exceptionType
            case 'java.net.ConnectException' 
                msg = message('parallel:cluster:MJSWorkerUnavailable'); 
            case 'java.rmi.ConnectException' 
                msg = message('parallel:cluster:MJSWorkerUnavailable'); 
            case 'java.rmi.RemoteException'
                msg = message('parallel:cluster:MJSWorkerUnavailable'); 
            case 'java.rmi.ConnectIOException'
                msg = message('parallel:cluster:MJSWorkerUnavailable'); 
            case 'java.rmi.NoSuchObjectException'
                msg = message('parallel:cluster:MJSWorkerUnavailable'); 
        end
    elseif isa(obj, 'distcomp.task') || isa( obj, 'parallel.Task' )
        switch exceptionType
            case 'com.mathworks.toolbox.distcomp.mjs.storage.TaskNotFoundException'
                msg = message('parallel:task:MJSTaskNotFound');
            case 'com.mathworks.toolbox.distcomp.mjs.storage.JobNotFoundException'
                msg = message('parallel:job:MJSJobNotFound');
            case 'com.mathworks.toolbox.distcomp.mjs.datastore.MaxDataLimitExceededException'
                msg = message('parallel:cluster:MJSHeadnodeNotEnoughJavaMemory');
            case 'com.mathworks.toolbox.distcomp.mjs.datastore.CallerDataStoreExceededException'
                storeSize = iGetDataStoreSizeInMB(obj);
                msg = message('parallel:cluster:MJSNotEnoughJavaMemoryToTransfer', jvmType, storeSize);
            case 'com.mathworks.toolbox.distcomp.mjs.datastore.DataTransferException'
                msg = message('parallel:cluster:MJSNotEnoughJavaMemoryToAllocate', jvmType);
            case 'java.lang.OutOfMemoryError'
                msg = message('parallel:cluster:MJSNotEnoughJavaMemoryToAllocate', jvmType);
            case 'com.mathworks.toolbox.distcomp.mjs.datastore.NullLargeDataInvokerException'
                msg = message('parallel:cluster:MJSNoLargeDataInvoker');
            case 'com.mathworks.toolbox.distcomp.mjs.datastore.DataStoreException'
                port = iGetDataStoreExportPort(obj);
                % if the port is 0 then there is no RMI server on the
                % client. parallel:cluster:MJSCannotContactMATLAB's advice 
                % will not make sense.
                if port==0
                    msg = message('parallel:cluster:MJSCannotContactMATLABNetworkDrop');
                else
                    msg = message('parallel:cluster:MJSCannotContactMATLAB', ...
                        char(java.lang.System.getProperty('java.rmi.server.hostname')), port, ...
                        char(java.lang.System.getProperty('java.rmi.server.hostname')), port);
                end
            case 'com.mathworks.toolbox.distcomp.util.PortUnavailableException'
                msg = message('parallel:cluster:MJSPortUnavailable');
            case 'java.rmi.ServerException'
                msg = message('parallel:cluster:MJSCannotContactClient');
            case 'com.mathworks.toolbox.distcomp.mjs.peerrmi.RemoteMethodInvocationHandler$NoReturnMessageException'
                msg = message('parallel:cluster:MJSUnreachable');
            case 'java.net.ConnectException' 
                msg = message('parallel:cluster:MJSUnreachable'); 
            case 'java.rmi.ConnectException' 
                msg = message('parallel:cluster:MJSUnreachable'); 
            case 'java.rmi.RemoteException'
                msg = message('parallel:cluster:MJSUnreachable'); 
            case 'java.rmi.ConnectIOException'
                msg = message('parallel:cluster:MJSUnreachable'); 
            case 'java.rmi.NoSuchObjectException'
                msg = message('parallel:cluster:MJSUnreachable'); 
            case 'com.mathworks.toolbox.distcomp.util.UnavailableEphemeralPortsException'
                msg = message('parallel:cluster:MJSUnavailableEphemeralPorts');
            case 'com.mathworks.toolbox.distcomp.mjs.storage.DataStorageException'
                msg = message('parallel:task:MJSDataStorageFailed');
        end           
    elseif isa(obj, 'distcomp.job') || isa( obj, 'parallel.Job' )
        switch exceptionType
            case 'com.mathworks.toolbox.distcomp.mjs.storage.TaskNotFoundException'
                msg = message('parallel:task:MJSTaskNotFound');
            case 'com.mathworks.toolbox.distcomp.mjs.storage.JobNotFoundException'
                msg = message('parallel:job:MJSJobNotFound');
            case 'com.mathworks.toolbox.distcomp.mjs.datastore.MaxDataLimitExceededException'
                msg = message('parallel:cluster:MJSHeadnodeNotEnoughJavaMemory');
            case 'com.mathworks.toolbox.distcomp.mjs.datastore.CallerDataStoreExceededException'
                storeSize = iGetDataStoreSizeInMB(obj);
                msg = message('parallel:cluster:MJSNotEnoughJavaMemoryToTransfer', jvmType, storeSize);
            case 'com.mathworks.toolbox.distcomp.mjs.datastore.DataTransferException'
                msg = message('parallel:cluster:MJSNotEnoughJavaMemoryToAllocate', jvmType);
            case 'java.lang.OutOfMemoryError'
                msg = message('parallel:cluster:MJSNotEnoughJavaMemoryToAllocate', jvmType);
            case 'com.mathworks.toolbox.distcomp.mjs.datastore.NullLargeDataInvokerException'
                msg = message('parallel:cluster:MJSNoLargeDataInvoker');
            case 'com.mathworks.toolbox.distcomp.mjs.datastore.DataStoreException'
                msg = message('parallel:cluster:MJSCannotContactMATLAB',  ...
                    char(java.lang.System.getProperty('java.rmi.server.hostname')), iGetDataStoreExportPort(obj), ...
                    char(java.lang.System.getProperty('java.rmi.server.hostname')), iGetDataStoreExportPort(obj));
            case 'com.mathworks.toolbox.distcomp.util.PortUnavailableException'
                msg = message('parallel:cluster:MJSPortUnavailable');
            case 'java.rmi.ServerException'
                msg = message('parallel:cluster:MJSCannotContactClient');
            case 'com.mathworks.toolbox.distcomp.mjs.peerrmi.RemoteMethodInvocationHandler$NoReturnMessageException'
                msg = message('parallel:cluster:MJSUnreachable');
            case 'com.mathworks.toolbox.distcomp.mjs.peerrmi.ClientOutputGroupCreationException'
                msg = message('parallel:cluster:MJSUnreachable');
            case 'java.net.ConnectException' 
                msg = message('parallel:cluster:MJSUnreachable'); 
            case 'java.rmi.ConnectException' 
                msg = message('parallel:cluster:MJSUnreachable'); 
            case 'java.rmi.RemoteException'
                msg = message('parallel:cluster:MJSUnreachable'); 
            case 'java.rmi.ConnectIOException'
                msg = message('parallel:cluster:MJSUnreachable'); 
            case 'java.rmi.NoSuchObjectException'
                msg = message('parallel:cluster:MJSUnreachable'); 
            case 'com.mathworks.toolbox.distcomp.util.UnavailableEphemeralPortsException'
                msg = message('parallel:cluster:MJSUnavailableEphemeralPorts');
            case 'com.mathworks.toolbox.distcomp.mjs.storage.DataStorageException'
                msg = message('parallel:job:MJSDataStorageFailed');
        end
    else
        switch exceptionType
            case 'com.mathworks.toolbox.distcomp.util.PortUnavailableException'
                msg = message('parallel:cluster:MJSPortUnavailable');
            case 'com.mathworks.toolbox.distcomp.util.LookupDiscoveryException'
                msg = message('parallel:cluster:MJSMulticastLookupFailed');
            case 'java.net.MalformedURLException'
                msg = message('parallel:cluster:MalformedLookupURL');
        end        
    end        
end

function storeSize = iGetDataStoreSizeInMB(workUnit)
    dataStoreInfo = workUnit.pGetDataStoreInfo;
    storeSize =  fix(dataStoreInfo.DataStoreSize / (1024 * 1024));
end

function exportPort = iGetDataStoreExportPort(workUnit)
    dataStoreInfo = workUnit.pGetDataStoreInfo;
    exportPort = dataStoreInfo.DataStoreExportPort;
end
