// 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 ( <key>, <value>, _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" ) ) );