Source: polyfill/EntryStore.js

// Licensed Materials - Property of IBM
//
// IBM Watson Analytics
//
// (C) Copyright IBM Corp. 2015
//
// US Government Users Restricted Rights - Use, duplication or
// disclosure restricted by GSA ADP Schedule Contract with IBM Corp.

module.exports = ( function( ObjectPolyfill )
{
"use strict";

/**
 * EntryStore provides the actual Map (and Set) implementation. Its implementation is shaped by two features:
 * 1. Attempt to achieve the O(1) lookup time of the native Map and Set types (for String and Number values).
 * 2. Support iterators according to the spec.
 *
 * Since we don't want to track iterators (as that would keep them alive indefinitely if iteration is aborted before end),
 * the EntryStore never re-uses or changes an entry index.
 * This allows iterators to keep pointing at a specific entry index, and look at the actual situation during the next call.
 * @class module:barejs/polyfill.EntryStore
 * @param {*} [_iterable] Optional: an iterable whose values will be added to the EntryStore.
 * @param {boolean} [_pair] Optional: if the EntryStore is used for a Map, this should be true
 * (iterable's values should be interpreted as key-value pairs, not as single values).
 */
function EntryStore( _iterable, _pair )
{
    this.entries = Object.create( null );
    // Note: skip index 0 so it is safe to use the entry index in boolean expressions.
    this.entries.start = this.entries.end = 1;

    this.clear();

    if ( _iterable )
    {
        // Use Array.from for iteration, as it normalizes iterables for us.
        // Only downside is that we're creating an array that is thrown away
        Array.from( _iterable, _pair === true ? this._setPair : this._setValue, this );
    }
}

ObjectPolyfill.polyfill( EntryStore, null,
/** @lends module:barejs/polyfill.EntryStore# */
{
    _setPair: function( _value )
    {
        if ( typeof _value !== "object" )
            throw new TypeError( "Iterator value " + String( _value ) + " is not an entry object" );
        this.set( _value[0], _value[1] );
    },

    _setValue: function( _value )
    {
        this.set( _value, _value );
    },

    /**
     * Find the next valid index, starting at _start
     * @param {number} _start The index to start looking at. If _start is in entries, start is returned.
     * @returns {number} The next valid index, or -1 if there is no next entry.
     */
    _nxt: function( _start )
    {
        for ( var i = Math.max( _start || 0, this.entries.start ), end = this.entries.end; i < end; ++i )
            if ( i in this.entries )
                return i;
        return -1;
    },

    /**
     * Find the entry index for a key
     * @param _key The key to get the entry index for
     * @param _key The key to get the entry index for
     * @returns {number} The entry index, or -1
     */
    indexOf: function( _key )
    {
        switch ( typeof _key )
        {
            case "string":
                return this._stringKeys[ _key ] || -1;
            case "number":
                return this._numberKeys[ String( _key ) ] || -1;
            default:
                return this._otherKeyIds[ this._otherKeys.indexOf( _key ) ] || -1;
        }
    },

    /**
     * Set the entry for a key
     * @param {*} _key The key for the value.
     * @param {*} _value The value to set.
     */
    "set": function( _key, _value )
    {
        var idx = this.indexOf( _key );

        if ( idx < 0 )
        {
            idx = this.entries.end++;
            ++this.size;

            switch ( typeof _key )
            {
                case "string":
                    this._stringKeys[ _key ] = idx;
                    break;

                case "number":
                    this._numberKeys[ String( _key ) ] = idx;
                    break;

                default:
                    this._otherKeys.push( _key );
                    this._otherKeyIds.push( idx );
                    break;
            }

            this.entries[idx] = [_key, _value];
        }
        else
        {
            this.entries[idx][1] = _value;
        }
    },

    /**
     * Remove _key from the EntryStore
     * @param {*} _key The key to remove the value for
     * @returns {boolean} True if the value for _key got removed, false otherwise.
     */
    remove: function( _key )
    {
        var idx;

        switch ( typeof _key )
        {
            case "string":
                if ( ( idx = this._stringKeys[ _key ] || -1 ) >= 0 )
                    delete this._stringKeys[ _key ];
                break;

            case "number":
                if ( ( idx = this._numberKeys[ String( _key ) ] || -1 ) >= 0 )
                    delete this._numberKeys[ String( _key ) ];
                break;

            default:
                if ( ( idx = this._otherKeys.indexOf( _key ) ) >= 0 )
                {
                    // Remove key
                    this._otherKeys.splice( idx, 1 );
                    // Remove index mapping, and update idx to the entry index
                    idx = this._otherKeyIds.splice( idx, 1 )[0];
                }
                break;
        }

        var remove = idx >= 0;
        if ( remove )
        {
            // We already moved the key mapping, but we still need to drop the entry
            // (and potentially update the start value)
            delete this.entries[idx];
            --this.size;
            if ( idx === this.entries.start )
            {
                // If we removed the "start" value, update it
                idx = this._nxt( idx + 1 );
                this.entries.start = idx < 0 ? this.entries.end : idx;
            }
        }

        return remove;
    },

    /**
     * Clear the EntryStore, removing all keys and associated values.
     */
    clear: function()
    {
        this._numberKeys = Object.create( null );
        this._stringKeys = Object.create( null );
        this._otherKeys = [];
        this._otherKeyIds = [];

        this.size = 0;

        // drop all entries
        for ( var i = this.entries.start, end = this.entries.end; i < end; ++i )
            delete this.entries[i];
        // Update start to end
        this.entries.start = this.entries.end;
    },

    /**
     * Iterate the EntryStore (on behalf of _iterated)
     * @param {module:barejs/polyfill.EntryStore} _iterated The object to report as being iterated.
     * Since the EntryStore is an internal object to be used by a Set or Map it has to report the correct object.
     * @param {function} Callback: the iterator callback to call for every entry. Called with ( &lt;key&gt;, &lt;value&gt;, _iterated ).
     * @param {object} [_thisArg] Optional: object to use as context for the callback function
     */
    forEach: function( _iterated, _callback, _thisArg )
    {
        // Do NOT cache end, adding entries during iteration is allowed
        for ( var o = ObjectPolyfill.ensureCallable( _callback, _thisArg && Object( _thisArg ) ), i = this._nxt(), entry; i >= 0; i = this._nxt( i + 1 ) )
        {
            entry = this.entries[i];
            _callback.call( o, entry[1], entry[0], _iterated );
        }
    }
}, null, "EntryStore" );

// This is NOT the native Iterator...
//jshint -W121

/**
 * @class module:barejs/polyfill.EntryStore.Iterator
 * @classdesc Base class Iterator for a class using the EntryStore
 */
function Iterator( _kind, _store )
{
    this._kind = _kind;
    this._next = _store._nxt();
    if ( this._next >= 0 )
        this._store = _store;
}

ObjectPolyfill.polyfill( Iterator, null,
/** @lends module:barejs/polyfill.EntryStore.Iterator# */
{
    /**
     * Get the next value
     * @returns {object} An object containing a done and value property.
     */
    next: function next()
    {
        var entry, // Note: entry is used to get an undefined value (minification optimisation)
            result = { value: entry, done: this._next < 0 };

        if ( !result.done )
        {
            var store = this._store,
                // use _nxt to check if there is still an entry at _next
                next = this._next = store._nxt( this._next );

            // If we're done iterating, remove the reference to _store
            if ( next < 0 )
            {
                // All entries at next (and possibly afterwards) got removed, mark the iterator done
                result.done = true;
                // Clear link to _store since we don't need it anymore
                this._store = null;
            }
            else
            {
                entry = store.entries[next];
                switch ( this._kind )
                {
                    case "key":
                        result.value = entry[0];
                        break;
                    case "value":
                        result.value = entry[1];
                        break;
                    default:
                        // Don't expose our inner array, make a copy
                        result.value = entry.slice( 0, 2 );
                        break;
                }
                this._next = store._nxt( next + 1 );
            }
        }

        return result;
    }
}, null, "Iterator" );

ObjectPolyfill.defineProperty( EntryStore, "Iterator", { value: Iterator } );

ObjectPolyfill.setIterator( Iterator.prototype, /*istanbul ignore next*/ function()
{
    //jshint validthis:true
    return this;
} );

return EntryStore;

}( require( "./Object" ) ) );