class Session implements Closeable {

    private void next(Message message, boolean isProcessingQueuedMessages) throws FieldNotFound, RejectLogon, IncorrectDataFormat,
            IncorrectTagValue, UnsupportedMessageType, IOException, InvalidMessage {

        if (message == EventHandlingStrategy.END_OF_STREAM) {
            disconnect(ENCOUNTERED_END_OF_STREAM, false);
            return;
        }

        final Header header = message.getHeader();
        final String msgType = header.getString(MsgType.FIELD);

        // QFJ-650
        if (!header.isSetField(MsgSeqNum.FIELD)) {
            generateLogout("Received message without MsgSeqNum");
            disconnect("Received message without MsgSeqNum: " + getMessageToLog(message), true);
            return;
        }

        final String sessionBeginString = sessionID.getBeginString();
        try {
            final String beginString = header.getString(BeginString.FIELD);
            if (!beginString.equals(sessionBeginString)) {
                throw new UnsupportedVersion("Message version '" + beginString
                        + "' does not match the session version '" + sessionBeginString + "'");
            }

            if (MsgType.LOGON.equals(msgType)) {
                if (sessionID.isFIXT()) {
                    targetDefaultApplVerID.set(new ApplVerID(message
                            .getString(DefaultApplVerID.FIELD)));
                }

                // QFJ-648
                if (message.isSetField(HeartBtInt.FIELD)) {
                    if (message.getInt(HeartBtInt.FIELD) < 0) {
                        throw new RejectLogon("HeartBtInt must not be negative");
                    }
                }
            }

            if (validateIncomingMessage && dataDictionaryProvider != null) {
                final DataDictionary sessionDataDictionary = dataDictionaryProvider
                        .getSessionDataDictionary(beginString);

                final ApplVerID applVerID = header.isSetField(ApplVerID.FIELD) ? new ApplVerID(
                        header.getString(ApplVerID.FIELD)) : targetDefaultApplVerID.get();

                final DataDictionary applicationDataDictionary = MessageUtils
                        .isAdminMessage(msgType) ? dataDictionaryProvider
                        .getSessionDataDictionary(beginString) : dataDictionaryProvider
                        .getApplicationDataDictionary(applVerID);

                // related to QFJ-367 : just warn invalid incoming field/tags
                try {
                    DataDictionary.validate(message, sessionDataDictionary,
                            applicationDataDictionary);
                } catch (final IncorrectTagValue e) {
                    if (rejectInvalidMessage) {
                        throw e;
                    } else {
                        getLog().onErrorEvent("Warn: incoming message with " + e + ": " + getMessageToLog(message));
                    }
                } catch (final FieldException e) {
                    if (message.isSetField(e.getField())) {
                        if (rejectInvalidMessage) {
                            throw e;
                        } else {
                            getLog().onErrorEvent(
                                    "Warn: incoming message with incorrect field: "
                                            + message.getField(e.getField()) + ": " + getMessageToLog(message));
                        }
                    } else {
                        if (rejectInvalidMessage) {
                            throw e;
                        } else {
                            getLog().onErrorEvent(
                                    "Warn: incoming message with missing field: " + e.getField()
                                            + ": " + e.getMessage() + ": " + getMessageToLog(message));
                        }
                    }
                } catch (final FieldNotFound e) {
                    if (rejectInvalidMessage) {
                        throw e;
                    } else {
                        getLog().onErrorEvent("Warn: incoming " + e + ": " + getMessageToLog(message));
                    }
                }
            }

            switch (msgType) {
                case MsgType.LOGON:
                    nextLogon(message);
                    break;
                case MsgType.HEARTBEAT:
                    nextHeartBeat(message);
                    break;
                case MsgType.TEST_REQUEST:
                    nextTestRequest(message);
                    break;
                case MsgType.SEQUENCE_RESET:
                    nextSequenceReset(message);
                    break;
                case MsgType.LOGOUT:
                    nextLogout(message);
                    break;
                case MsgType.RESEND_REQUEST:
                    nextResendRequest(message);
                    break;
                case MsgType.REJECT:
                    nextReject(message);
                    break;
                default:
                    if (!verify(message)) {
                        return;
                    }
                    state.incrNextTargetMsgSeqNum();
                    break;
            }
        } catch (final FieldException | IncorrectDataFormat | IncorrectTagValue e) {
            if (logErrorAndDisconnectIfRequired(e, message)) {
                return;
            }
            handleExceptionAndRejectMessage(msgType, message, e);
        } catch (final FieldNotFound e) {
            if (logErrorAndDisconnectIfRequired(e, message)) {
                return;
            }
            if (sessionBeginString.compareTo(FixVersions.BEGINSTRING_FIX42) >= 0
                    && message.isApp()) {
                generateBusinessReject(message,
                        BusinessRejectReason.CONDITIONALLY_REQUIRED_FIELD_MISSING, e.field);
            } else {
                if (MsgType.LOGON.equals(msgType)) {
                    getLog().onErrorEvent("Required field missing from logon");
                    disconnect("Required field missing from logon", true);
                } else {
                    generateReject(message, SessionRejectReason.REQUIRED_TAG_MISSING, e.field);
                }
            }
        } catch (final InvalidMessage e) {
            /* InvalidMessage means a low-level error (e.g. checksum problem) and we should
               ignore the message and let the problem correct itself (optimistic approach).
               Target sequence number is not incremented, so it will trigger a ResendRequest
               on the next message that is received.
               If the message should get rejected and the seqnum get incremented,
               then setting RejectGarbledMessage=Y needs to be used. */
            if (rejectGarbledMessage) {
                getLog().onErrorEvent("Processing garbled message: " + e.getMessage());
                generateReject(message, "Message failed basic validity check");
            } else {
                getLog().onErrorEvent("Skipping invalid message: " + e + ": " + getMessageToLog(message));
                if (resetOrDisconnectIfRequired(message)) {
                    return;
                }
            }
        } catch (final RejectLogon e) {
            final String rejectMessage = e.getMessage() != null ? (": " + e) : "";
            getLog().onErrorEvent("Logon rejected" + rejectMessage);
            if (e.isLogoutBeforeDisconnect()) {
                if (e.getSessionStatus() > -1) {
                    generateLogout(e.getMessage(), new SessionStatus(e.getSessionStatus()));
                } else {
                    generateLogout(e.getMessage());
                }
            }
            // Only increment seqnum if we are at the expected seqnum
            if (getExpectedTargetNum() == header.getInt(MsgSeqNum.FIELD)) {
                state.incrNextTargetMsgSeqNum();
            }
            disconnect("Logon rejected: " + e, true);
        } catch (final UnsupportedMessageType e) {
            if (logErrorAndDisconnectIfRequired(e, message)) {
                return;
            }
            if (sessionBeginString.compareTo(FixVersions.BEGINSTRING_FIX42) >= 0) {
                generateBusinessReject(message, BusinessRejectReason.UNSUPPORTED_MESSAGE_TYPE, 0);
            } else {
                generateReject(message, "Unsupported message type");
            }
        } catch (final UnsupportedVersion e) {
            if (logErrorAndDisconnectIfRequired(e, message)) {
                return;
            }
            if (MsgType.LOGOUT.equals(msgType)) {
                nextLogout(message);
            } else {
                generateLogout("Incorrect BeginString: " + e.getMessage());
                state.incrNextTargetMsgSeqNum();
                // 1d_InvalidLogonWrongBeginString.def appears to require
                // a disconnect although the C++ didn't appear to be doing it.
                // ???
                disconnect("Incorrect BeginString: " + e, true);
            }
        } catch (final IOException e) {
            LogUtil.logThrowable(sessionID, "Error processing message: " + getMessageToLog(message), e);
            if (resetOrDisconnectIfRequired(message)) {
                return;
            }
        } catch (Throwable t) { // QFJ-572
            // If there are any other Throwables we might catch them here if desired.
            // They were most probably thrown out of fromCallback().
            if (rejectMessageOnUnhandledException) {
                getLog().onErrorEvent("Rejecting message: " + t + ": " + getMessageToLog(message));
                if (resetOrDisconnectIfRequired(message)) {
                    return;
                }
                if (!(MessageUtils.isAdminMessage(msgType))
                        && (sessionBeginString.compareTo(FixVersions.BEGINSTRING_FIX42) >= 0)) {
                    generateBusinessReject(message, BusinessRejectReason.APPLICATION_NOT_AVAILABLE,
                            0);
                } else {
                    if (MsgType.LOGON.equals(msgType)) {
                        disconnect("Problem processing Logon message", true);
                    } else {
                        generateReject(message, SessionRejectReason.OTHER, 0);
                    }
                }
            } else {
                // Re-throw as quickfix.RuntimeError to keep close to the former behaviour
                // and to have a clear notion of what is thrown out of this method.
                // Throwing RuntimeError here means that the target seqnum is not incremented
                // and a resend will be triggered by the next incoming message.
                throw new RuntimeError(t);
            }
        }

        // QFJ-788: prevent StackOverflow on large queue
        if (!isProcessingQueuedMessages) {
            nextQueued();
            if (isLoggedOn()) {
                next();
            }
        }
    }

      Session(Application application, MessageStoreFactory messageStoreFactory, SessionID sessionID,
            DataDictionaryProvider dataDictionaryProvider, SessionSchedule sessionSchedule, LogFactory logFactory,
            MessageFactory messageFactory, int heartbeatInterval);
      Session(Application application, MessageStoreFactory messageStoreFactory, SessionID sessionID,
            DataDictionaryProvider dataDictionaryProvider, SessionSchedule sessionSchedule,
            LogFactory logFactory, MessageFactory messageFactory, int heartbeatInterval,
            boolean checkLatency, int maxLatency, UtcTimestampPrecision timestampPrecision,
            boolean resetOnLogon, boolean resetOnLogout, boolean resetOnDisconnect,
            boolean refreshOnLogon, boolean checkCompID,
            boolean redundantResentRequestsAllowed, boolean persistMessages,
            boolean useClosedRangeForResend, double testRequestDelayMultiplier,
            DefaultApplVerID senderDefaultApplVerID, boolean validateSequenceNumbers,
            int[] logonIntervals, boolean resetOnError, boolean disconnectOnError,
            boolean disableHeartBeatCheck, boolean rejectGarbledMessage, boolean rejectInvalidMessage,
            boolean rejectMessageOnUnhandledException, boolean requiresOrigSendingTime,
            boolean forceResendWhenCorruptedStore, Set<InetAddress> allowedRemoteAddresses,
            boolean validateIncomingMessage, int resendRequestChunkSize,
            boolean enableNextExpectedMsgSeqNum, boolean enableLastMsgSeqNumProcessed,
            boolean validateChecksum, List<StringField> logonTags, double heartBeatTimeoutMultiplier);

}