jnidzwetzki/bitfinex-v2-wss-api-java

View on GitHub
src/main/java/com/github/jnidzwetzki/bitfinex/v2/SequenceNumberAuditor.java

Summary

Maintainability
A
2 hrs
Test Coverage
/*******************************************************************************
 *
 *    Copyright (C) 2015-2018 Jan Kristof Nidzwetzki
 *  
 *    Licensed 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.github.jnidzwetzki.bitfinex.v2;

import org.json.JSONArray;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SequenceNumberAuditor {
    
    public enum ErrorPolicy {
        LOG_ONLY,
        RUNTIME_EXCEPTION;
    }
    
    /**
     * The public sequence
     */
    private long publicSequence;
    
    /**
     * The private sequence
     */
    private long privateSequence;
    
    /**
     * The error policy
     */
    private ErrorPolicy errorPolicy;
    
    /**
     * Was an error reported?
     */
    private boolean failed;
    
    /**
     * The Logger
     */
    private final static Logger logger = LoggerFactory.getLogger(SequenceNumberAuditor.class);
    
    public SequenceNumberAuditor() {
        this.errorPolicy = ErrorPolicy.LOG_ONLY;
        reset();
    }

    /**
     * Reset the number generator
     */
    public void reset() {
        logger.debug("Resetting sequence auditor");
        this.publicSequence = -1;
        this.privateSequence = -1;
        this.failed = false;
    }
    
    /**
     * Audit the package
     * @param jsonArray
     */
    public void auditPackage(final JSONArray jsonArray) {        
        final long channelId = jsonArray.getInt(0);
        final boolean isHeartbeat = jsonArray.optString(1, "").equals("hb");
        
        // Channel 0 uses the private and public sequence, other channels use only the public sequence
        // An exception is heartbeat of channel 0, in this case, only the public sequence is used
        if(channelId == 0) {
            if(isHeartbeat) {
                checkPublicSequence(jsonArray);
            } else {
                checkPublicAndPrivateSequence(jsonArray);
            }
        } else {
            checkPublicSequence(jsonArray);
        }
    }

    /**
     * Check the public and the private sequence
     * @param jsonArray
     */
    private void checkPublicAndPrivateSequence(final JSONArray jsonArray) {
        final long nextPublicSequnceNumber = jsonArray.getLong(jsonArray.length() - 2);
        final long nextPrivateSequnceNumber = jsonArray.getLong(jsonArray.length() - 1);

        auditPublicSequence(nextPublicSequnceNumber);
        auditPrivateSequence(nextPrivateSequnceNumber);
    }

    /** 
     * Check the public sequence
     */
    private void checkPublicSequence(final JSONArray jsonArray) {
        final long nextPublicSequnceNumber = jsonArray.getLong(jsonArray.length() - 1);
        
        auditPublicSequence(nextPublicSequnceNumber);
    }

    /**
     * Audit the public sequence
     * 
     * @param nextPublicSequnceNumber
     */
    private void auditPublicSequence(final long nextPublicSequnceNumber) {
        if(publicSequence == -1) {
            publicSequence = nextPublicSequnceNumber;
            return;
        }
        
        if(publicSequence + 1 != nextPublicSequnceNumber) {
            final String errorMessage = String.format(
                    "Got %d as next public sequence number, expected %d", 
                    publicSequence + 1, nextPublicSequnceNumber);
            
            handleError(errorMessage);
            return;
        }
        
        publicSequence++;
    }

    /**
     * Audit the private sequence
     * 
     * @param nextPublicSequnceNumber
     */
    private void auditPrivateSequence(final long nextPrivateSequnceNumber) {
        if(privateSequence == -1) {
            privateSequence = nextPrivateSequnceNumber;
            return;
        }
        
        if(privateSequence + 1 != nextPrivateSequnceNumber) {
            final String errorMessage = String.format(
                    "Got %d as next private sequence number, expected %d", 
                    privateSequence + 1, nextPrivateSequnceNumber);
            
            handleError(errorMessage);
            return;
        }
        
        privateSequence++;
    }
    
    /**
     * Handle the sequence number error
     * @param errorMessage
     */
    private void handleError(final String errorMessage) {
        
        failed = true;
        
        switch (errorPolicy) {
        case LOG_ONLY:
            logger.error(errorMessage);
            break;
            
        case RUNTIME_EXCEPTION:
            throw new RuntimeException(errorMessage);

        default:
            logger.error("Got error {} but unkown error policy {}", errorMessage, errorPolicy);
            break;
        }
    }
    
    /**
     * Get the last private sequence
     * @return
     */
    public long getPrivateSequence() {
        return privateSequence;
    }
    
    /**
     * Get the last public sequence
     * @return
     */
    public long getPublicSequence() {
        return publicSequence;
    }
    
    /**
     * Get the error policy
     * @return
     */
    public ErrorPolicy getErrorPolicy() {
        return errorPolicy;
    }
    
    /**
     * Set the error policy
     */
    public void setErrorPolicy(final ErrorPolicy errorPolicy) {
        this.errorPolicy = errorPolicy;
    }
    
    /**
     * Has the audit failed?
     * @return
     */
    public boolean isFailed() {
        return failed;
    }
}