All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.crankuptheamps.client.BookmarkRingBuffer Maven / Gradle / Ivy

///////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2010-2024 60East Technologies Inc., All Rights Reserved.
//
// This computer software is owned by 60East Technologies Inc. and is
// protected by U.S. copyright laws and other laws and by international
// treaties.  This computer software is furnished by 60East Technologies
// Inc. pursuant to a written license agreement and may be used, copied,
// transmitted, and stored only in accordance with the terms of such
// license agreement and with the inclusion of the above copyright notice.
// This computer software or any other copies thereof may not be provided
// or otherwise made available to any other person.
//
// U.S. Government Restricted Rights.  This computer software: (a) was
// developed at private expense and is in all respects the proprietary
// information of 60East Technologies Inc.; (b) was not developed with
// government funds; (c) is a trade secret of 60East Technologies Inc.
// for all purposes of the Freedom of Information Act; and (d) is a
// commercial item and thus, pursuant to Section 12.212 of the Federal
// Acquisition Regulations (FAR) and DFAR Supplement Section 227.7202,
// Government's use, duplication or disclosure of the computer software
// is subject to the restrictions set forth by 60East Technologies Inc..
//
////////////////////////////////////////////////////////////////////////////
package com.crankuptheamps.client;

import com.crankuptheamps.client.exception.AMPSException;
import com.crankuptheamps.client.fields.BookmarkField;
import com.crankuptheamps.client.fields.Field;
import java.nio.charset.Charset;
import java.util.ArrayList;

/**
 * A ring buffer of bookmarks and activation status
 * Used by all of the bookmark stores to track state of bookmarks
 * we need to hold on to, either because they're active or because
 * they're after an active one.
 */
public class BookmarkRingBuffer
{
    /**
     * Represents a single entry in an array of bookmarks
     */
    public static class Entry
    {
        BookmarkField _bookmark;
        long _index = 0;
        boolean _active = false;

        /**
         * Initializes a new instance of the Entry class
         */
        public Entry()
        {
            _bookmark = new BookmarkField();
        }

        /**
         * Sets the bookmark field of the Entry.
         * @param f A Field object which contains the fields of the Bookmark.
         * @return This instance.
         */
        public Entry setBookmark(Field f)
        {
            _bookmark.copyFrom(f);
            return this;
        }

        /**
         * Gets the bookmark field of the Entry.
         * @return The bookmark field of the Entry.
         */
        public BookmarkField getBookmark()
        {
            return _bookmark;
        }

        /**
         * Sets the index of the Entry in the Entry array.
         * @param index The index to be set.
         * @return This instance.
         */
        public Entry setIndex(long index)
        {
            _index = index;
            return this;
        }

        /**
         * Gets the index of the Entry in the Entry array.
         * @return The index of the Entry.
         */
        public long getIndex()
        {
            return _index;
        }

        /**
         * Gets the active value of the Entry.
         * @return The active value of the Entry.
         */
        public boolean isActive()
        {
            return _active;
        }

        /**
         * Sets the active value of the Entry.
         * @param active the new value of active
         * @return Returns this instance so that various operations can be chained together.
         */
        public Entry setActive(boolean active)
        {
            _active = active;
            return this;
        }

        /**
         * WARNING: @deprecated. Do Not Use.
         * Old Doc: Gets the persisted value of the Entry.
         * @return The persisted value of the Entry.
         */
        @Deprecated
        public boolean isPersisted()
        {
            return true;
        }

        /**
         * WARNING: @deprecated. Do Not Use.
         * Old Doc: Sets the persisted value of the Entry.
         * @param persisted The persisted value of the Entry.
         * @return The persisted value of the Entry.
         */
        @Deprecated
        public Entry setPersisted(boolean persisted)
        {
            return this;
        }

        /**
         * Converts the Entry to a string.
         * @return The converted Entry.
         */
        public String toString()
        {
            return "[ " + _bookmark + ", active=" + _active
                    + ", index=" + _index + " ]";
        }
    }

    // Constant representing the initial count of entries.
    static final int INITIAL_ENTRY_COUNT = 1000;
    /**
     * Constant representing an unset index value.
     */
    public final static int UNSET_INDEX = -1;
    Entry _entries[] = new Entry[INITIAL_ENTRY_COUNT];
    int _start = 1, _end = 1;
    long _index = 1, _indexOfStart = 0;
    BookmarkField _lastDiscarded = new BookmarkField();
    boolean _empty = true;
    BookmarkStoreResizeHandler _resizeHandler;
    BookmarkStore _store;
    Field _subId;
    volatile boolean _canResize = false;

    /**
     * Initializes the underlying array, and sets
     * the "last discarded" value to something reasonable.
     */
    public BookmarkRingBuffer()
    {
        _lastDiscarded.set("0".getBytes(Charset.defaultCharset()));
        for(int i = 0; i < _entries.length; ++i)
        {
            _entries[i] = new Entry();
        }
    }

    /**
     * Resets this bookmark ring buffer for reuse by clearing out its state.
     */
    public void reset()
    {
        for(int i = 0; i < _entries.length; ++i)
        {
            _entries[i].setActive(false).setIndex(0)
                .getBookmark().reset();
        }
        _start = 1;
        _end = 1;
        _index = 1;
        _indexOfStart = 0;
        _lastDiscarded.set("0".getBytes(Charset.defaultCharset()));
        _empty = true;
        _resizeHandler = null;
        _store = null;
        _subId = null;
        _canResize = false;
    }

    /**
     * Size of underlying array.
     * @return Current capacity of self.
     */
    public int capacity()
    {
        return _entries.length;
    }

    /**
     * Retrieves an Entry given an index.
     * @param index An index returned by .getIndex(), getStartIndex(),
     *              getEndIndex(), log, etc.
     * @return The entry at that index, or null if not found.
     */
    public Entry getByIndex(long index)
    {
        // The index must be higher than the lowest we have stored, which is
        // _start + _indexOfStart, and less than _index
        int offsetFromStart = 0;
        if (index < _indexOfStart + _start || index >= _index)
            return null;
        offsetFromStart = (int)(index-_indexOfStart) % _entries.length;
        return _entries[offsetFromStart];
    }

    /**
     * Returns the "last discarded" bookmark. This may be a
     * newer bookmark than the one you last passed to discard(),
     * if you're discarding out of order.
     *
     * @return The last Bookmark discarded.
     */
    public BookmarkField getLastDiscarded()
    {
        return _lastDiscarded;
    }

    /**
     * Returns if the buffer is currently empty.
     * @return True if self is empty, false otherwise.
     */
    public boolean isEmpty()
    {
        return _empty;
    }

    /**
     * Returns the index value associated with the first valid
     * Entry in self.
     * @return Index of first valid Entry.
     */
    public long getStartIndex()
    {
        if (_empty)
            return UNSET_INDEX;
        return _indexOfStart + _start;
    }

    /**
     * Returns the index value one greater than the last valid
     * Entry in self.
     * @return Index 1 greater than last valid entry.
     */
    public long getEndIndex()
    {
        if (_empty) return UNSET_INDEX;
        return _index;
    }

    /**
     * Logs the bookmark by allocating an Entry, setting
     * the Entry to active, copying the bookmark value
     * to that entry, and returning the index
     * of that entry.
     * @param bookmark The Bookmark to log.
     * @return The index of the new Entry.
     */
    public long log(BookmarkField bookmark)
    {
        if (_end == _start && !_empty)
        {
            if (!_canResize) return 0;
            resize();
        }
        _empty = false;
        int writeIndex = _end++;
        _entries[writeIndex].setBookmark(bookmark).setActive(true).setIndex(_index);
        if(_end == _entries.length) _end = 0;
        return _index++;
    }

    /**
     * Discards an entry by index. If the discard is completed,
     * lastDiscarded will change, otherwise the discard is cached
     * in the entry, and the getLastDiscarded() value is unchanged.
     *
     * @param index The index of the Entry to be discarded.
     * @return True if this caused lastDiscarded to change.
     */
    public boolean discard(long index)
    {
        boolean retVal = false;

        if (index < (_indexOfStart + _start) || index >= _index)
        {
            return retVal;
        }

        int offsetFromStart = (int)(index - _indexOfStart);
        int indexInArray = offsetFromStart % _entries.length;

        _entries[indexInArray].setActive(false);
        if(_start == indexInArray)
        {
            Entry lastDiscardedEntry = null;
            while(!_entries[_start].isActive()
                  && !_entries[_start].getBookmark().isNull()
                  && (_start != _end || !_empty))
            {
                if (lastDiscardedEntry != null)
                {
                    lastDiscardedEntry.getBookmark().reset();
                }
                lastDiscardedEntry = _entries[_start];
                if(++_start == _entries.length)
                {
                    _start = 0;
                    _indexOfStart += _entries.length;
                }
                _empty = (_start == _end);
            }
            if(lastDiscardedEntry != null)
            {
                BookmarkField bookmark = lastDiscardedEntry.getBookmark();
                if (bookmark != null && !bookmark.isNull()) {
                    _lastDiscarded.copyFrom(bookmark);
                    bookmark.reset();
                }
                retVal = true;
            }
        }
        return retVal;
    }

    /**
     * Finds an entry in the bookmark ring buffer with a specified bookmark field.
     * 
     * @param field The bookmark that you are searching for.
     * @return The entry with the specified bookmark field if found, or null if the
     *         Entry was not found.
     */
    public Entry find(BookmarkField field)
    {
        if (_empty) return null;
        long index = _indexOfStart + _start;
        int i = _start;
        do
        {
            if(!_entries[i].getBookmark().isNull() &&
               _entries[i].getBookmark().equals(field))
            {
                // hooray! we found it.
                return _entries[i].setIndex(index);
            }
            ++index;
            i = (i+1)%_entries.length;
        } while(i!=_end);
        return null;
    }

    /**
     * WARNING: @deprecated. Do Not Use.
     * Old Doc: Mark all records up to the and including the provided bookmark as safe for discard and discard all appropriate
     * bookmarks. Applies to server versions 3.8x.
     * @param bookmark The latest bookmark that can be safely disposed.
     */
    @Deprecated
    public void persisted(BookmarkField bookmark)
    {
        return;
    }

    /**
     * WARNING: @deprecated. Do Not Use.
     * Old Doc: Called internally by the Client to record the last persisted message in the transaction log  of the connected AMPS server.
     * @param bookmark The bookmark containing the message.
     */
    @Deprecated
    public void persisted(long bookmark)
    {
        return;
    }

    /**
     * Called to check if resizing of the BookmarkRingBuffer is possible.
     */
    public void checkResize()
    {
        int oldLength = _entries.length;
        _canResize = (_resizeHandler == null ||
                      _resizeHandler.invoke(_store, _subId,
                                            (int)(oldLength*1.5)));
    }

    /**
     * Called when self is full, assumes all entries are valid.
     */
    private void resize()
    {
        _canResize = false;
        int oldLength = _entries.length;
        // allocate a new array (1.5x resize factor)
        // copy over the data.
        Entry[] newEntries = new Entry[(int) (oldLength * 1.5)];

        int start = _start;
        System.arraycopy(_entries, start, newEntries, 0, oldLength - start);
        if(start > 0)
        {
            System.arraycopy(_entries, 0, newEntries, oldLength - start, start);
        }
        _indexOfStart += _start;
        _start = 0;
        _end = oldLength;
        for(int i = oldLength; i< newEntries.length; ++i)
        {
            newEntries[i] = new Entry();
        }
        _entries = newEntries;
    }

    /**
     * Call this when you want to set a resize handler that is invoked when a store needs a resize.
     * @param handler The handler to invoke for the resize.
     * @param store The store to be resized.
     */
    public void setResizeHandler(BookmarkStoreResizeHandler handler, BookmarkStore store)
    {
        _resizeHandler = handler;
        _store = store;
    }

    /**
     * Sets the Subscription Identifier for the bookmark.
     * @param sub The Field object to copy as the new SubId.
     */
    public void setSubId(Field sub)
    {
        _subId = sub.copy();
    }

    /**
     * Gets a list of recovery entries from the BookmarkRingBuffer.
     * 
     * @param entryList_ The list to fill with the recovery entries.
     */
    public void getRecoveryEntries(ArrayList entryList_)
    {
        for (int idx = _start; idx != _end; idx = (++idx%_entries.length))
        {
            entryList_.add(_entries[idx]);
        }
    }

    /**
     * WARNING: @deprecated. Do Not Use. The notion of a separate "recovered bookmark"
     * section within the ring buffer and the ability to re-log/re-order those entries
     * into the main/active ring buffer section is an obsolete concept that hasn't been
     * needed since the AMPS server stopped providing persisted acks for every message
     * delivered on a replay (i.e. not needed since AMPS 4.0). The bookmark ring buffer
     * has been simplified to remove this concept since a bug was discovered in it (AC-953).
     *
     * Old Docs:
     *
     * If there are presently no recovery entries in the ring buffer, this will convert all of the entries in the 
     * buffer into recovery entries.
     * @return The index of the first recovery Entry.
     */
    @Deprecated
    public long setRecovery()
    {
        return _start + _indexOfStart;
    }

    /**
     * WARNING: @deprecated. Do Not Use. The notion of a separate "recovered bookmark"
     * section within the ring buffer and the ability to re-log/re-order those entries
     * into the main/active ring buffer section is an obsolete concept that hasn't been
     * needed since the AMPS server stopped providing persisted acks for every message
     * delivered on a replay (i.e. not needed since AMPS 4.0). The bookmark ring buffer
     * has been simplified to remove this concept since a bug was discovered in it (AC-953).
     *
     * Old Docs:
     *
     * Gets all of the recovery entries in a newly allocated ArrayList. If there are no recovery entries,
     * this method returns null.
     * @return The ArrayList of entries or null if none.
     * @see #setRecovery()
     */
    @Deprecated
    public ArrayList getRecoveryEntries()
    {
        return null;
    }

    /**
     * WARNING: @deprecated. Do Not Use. The notion of a separate "recovered bookmark"
     * section within the ring buffer and the ability to re-log/re-order those entries
     * into the main/active ring buffer section is an obsolete concept that hasn't been
     * needed since the AMPS server stopped providing persisted acks for every message
     * delivered on a replay (i.e. not needed since AMPS 4.0). The bookmark ring buffer
     * has been simplified to remove this concept since a bug was discovered in it (AC-953).
     *
     * Old Docs:
     *
     * Re-logs the bookmark by copying an Entry to a new location
     * and returning the index of that new location.
     * @param oldIndex The index that the Entry was previously at.
     * @param bookmark The index to move.
     * @return The index of the new Entry.
     */
    @Deprecated
    public long relog(long oldIndex, BookmarkField bookmark)
    {
        return oldIndex;
    }

    /**
     * Returns a debugging string representing ring buffer state.
     */
    public String toString()
    {
        StringBuilder sb = new StringBuilder(300 + (int) (getEndIndex() - getStartIndex()) * 100);
        sb.append(String.format(
            "_entries.length = %d%n"
            + "_start = %d%n"
            + "_end = %d%n"
            + "_index = %s%n"
            + "_lastDiscarded = %s%n"
            + "_subId = %s%n"
            + "_canResize = %s%n"
            + "_resizeHandler = %s%n"
            + "_store = %s%n"
            + "%n"
            + "Entries:%n%n",
            _entries.length, _start, _end, _index,
            _lastDiscarded, _subId,
            _canResize, _resizeHandler, _store
            ));
        for (int idx = _start; idx != _end; idx = (++idx%_entries.length))
        {
            sb.append(String.format("%5d: %s%n", idx, _entries[idx]));
        }
        return sb.toString();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy