js/base/collection_base.js
// NPM IMPORTS
import assert from 'assert'
import _ from 'lodash'
// COMMON IMPORTS
import T from '../utils/types'
import Errorable from './errorable'
import Instance from './instance'
/**
* Contextual constant for this file logs.
* @private
*/
const context = 'common/base/collection_base'
/**
* Base class for all collections classes.
* @abstract
*
* @author Luc BORIES
* @license Apache-2.0
*
* @example
API:
* ->set_all(arg_items):nothing - Set all collection items.
* ->get_all(arg_types):array - Get all collection items or filter items with given type.
* ->get_all_names(arg_types):array - Get all items names with or without a filter on items types.
* ->get_all_ids():array - Get all items ids with or without a filter on items types.
*
* ->item(arg_name):Instance - Get an item by its name.
*
* ->get_count():number - Get all items count.
* ->get_first():object|undefined - Get first item.
* ->get_last():object|undefined - Get last item.
*
* ->add(arg_item):nothing - Add an item to the collection.
* ->add_first(arg_item):nothing - Add an item to the collection at the first position.
* ->remove(arg_item):nothing - Remove an item from the collection.
* ->has(arg_item):boolean - Test if an item is inside the collection.
*
* ->find_by_name(arg_name):Instance|undefined - Find an item by its name into the collection.
* ->find_by_id(arg_id):Instance|undefined - Find an item by its id into the collection.
* ->find_by_attr(arg_attr_name, arg_attr_value):Instance|undefined - Find an item by one of its attributes into the collection.
* ->find_by_filter(arg_filter_function):Instance|undefined - Find an item by a filter function.
*
* ->filter_by_attr(arg_attr_name, arg_attr_value):array - Filter items by one of theirs attributes into the collection.
* ->filter_by_filter(arg_filter_function):array - Filter items by a filter function.
*
* ->get_accepted_types():array - Get all collection accepted types.
* ->set_accepted_types(arg_types):nothing - Set all collection accepted types.
* ->add_accepted_type(arg_type):nothing - Add one collection accepted type.
* ->has_accepted_type(arg_type):boolean - Test if collection has given accepted type.
*
* ->forEach(arg_cb):nothing - forEach wrapper on ordered items.
*/
export default class CollectionBase extends Errorable
{
/**
* Create a collection of Instance objects.
*
* @returns {nothing}
*/
constructor()
{
super(context, undefined)
/**
* Class type flag.
* @type {boolean}
*/
this.is_collection_base = true
/**
* Items array.
* @type {array}
*/
this._items_array = []
/**
* Items names map.
* @type {object}
*/
this._items_by_name = {}
/**
* Items ids map.
* @type {object}
*/
this._items_by_id = {}
/**
* Accepted types array.
* @type {array}
*/
this._accepted_types = ['*']
}
/**
* Format string dump.
*
* @returns{string}
*/
toString()
{
let str = '['
_.forEach(this._items_array, (item, index)=>(index > 0 ? ',' : '') + item.get_name() )
return str + ']'
}
/**
* Set all collection items.
*
* @param {Instance|array} arg_items - collection items: one or many Instance objects.
*
* @returns {nothing}
*/
set_all(arg_items)
{
// DEBUG
let str = '['
_.forEach(arg_items, (item, index)=> str += (index > 0 ? ',' : '') + (item.get_name ? item.get_name() : 'bad item of type ' + (typeof item) ) )
str += ']'
console.log('set_all', str, typeof arg_items)
// RESET STORES
this._items_array = []
this._items_by_name = {}
this._items_by_id = {}
// ONE INSTANCE IS GIVEN
if ( T.isObject(arg_items) && arg_items instanceof Instance )
{
this._add(arg_items)
return
}
// AN OBJECT OR AN ARRAY IS GIVEN
if ( T.isObject(arg_items) || T.isArray(arg_items) )
{
_.forEach(arg_items,
(item)=>{
if ( T.isObject(item) && item instanceof Instance )
{
this._add(item)
}
}
)
return
}
console.error(context + '::bad given items type (not an Instance, object, array)')
}
/**
* Set all collection items.
* @private
*
* @param {array} arg_items - Instance objects array.
*
* @returns {nothing}
*/
_set_all(arg_items)
{
this._items_array = arg_items
}
/**
* Get all collection items.
* @private
*
* @returns {array}
*/
_get_all()
{
return this._items_array
}
/**
* Get all collection items or filter items with given type.
*
* @param {array|string|nothing} arg_types - type or types for items filtering.
*
* @returns {array} - all or filtered items, empty array if not found.
*/
get_all(arg_types)
{
// NO TYPE FILTER
if (! arg_types)
{
return _.toArray( this._items_array )
}
// ONE TYPE FILTER
if ( T.isString(arg_types) )
{
return _.filter(this._items_array, item => item.get_types() == arg_types )
}
// MANY TYPES FILTER
if ( T.isArray(arg_types) )
{
return _.filter(this._items_array, item => arg_types.indexOf( item.get_types() ) >= 0 )
}
return []
}
/**
* Get all items names with or without a filter on items types.
*
* @param {array|string|nothing} arg_types - type or types for items filtering.
*
* @returns {array} - all or filtered items names, empty array if not found.
*/
get_all_names(arg_types)
{
// NO TYPE FILTER
if (! arg_types)
{
return _.map(this._items_array, (item) => item.get_name() )
}
// ONE TYPE FILTER
if ( T.isString(arg_types) )
{
return _.filter( this._items_array, item => item.get_types() == arg_types ).map( (item) => item.get_name() )
}
// MANY TYPES FILTER
if ( T.isArray(arg_types) )
{
return _.filter( this._items_array, item => arg_types.indexOf( item.get_types() ) >= 0 ).map( (item) => item.get_name() )
}
return []
}
/**
* Get all items ids with or without a filter on items types.
*
* @returns {array} - all items ids.
*/
get_all_ids()
{
return _.map(this._items_array, (item) => item.get_id() )
}
/**
* Get an item by its name.
*
* @param {string} arg_name - instance name.
*
* @returns {Instance|undefined}
*/
item(arg_name)
{
return this._items_by_name ? this._items_by_name[arg_name] : undefined
}
/**
* Get an item by its name.
*
* @param {string} arg_name - instance name.
*
* @returns {Instance|undefined}
*/
get(arg_name)
{
return this._items_by_name ? this._items_by_name[arg_name] : undefined
}
/**
* Default iterator operator.
*/
// * [Symbol.iterator]() {
// for (let item of this.$items)
// {
// yield item
// }
// }
// [Symbol.iterator]()
// {
// let step = 0
// const count = this.$items.length
// const iterator = {
// next()
// {
// if (step < count)
// {
// const item = this.$items[step]
// step++
// return { value:item, done:false }
// }
// return { value:undefined, done:true }
// }
// }
// return iterator
// }
// NOT COMPATIBLE WITH NODE 0.10
// [Symbol.iterator]()
// {
// return this.$items.iterator()
// }
/**
* Get all items count.
*
* @returns {number} - all items count.
*/
get_count()
{
return _.size(this._items_array)
}
/**
* Get first item.
*
* @returns {object|undefined} - first collection items or undefined if collection is empty.
*/
get_first()
{
return _.first(this._items_array)
}
/**
* Get last item.
*
* @returns {object|undefined} - last collection items or null if collection is empty.
*/
get_last()
{
return _.last(this._items_array)
}
/**
* Add an item to the collection.
*
* @param {Instance} arg_item - Instance item.
*
* @returns {nothing}
*/
add(arg_item)
{
if ( T.isObject(arg_item) && arg_item instanceof Instance )
{
if ( this.has_accepted_type('*') || this.has_accepted_type( arg_item.get_type() ) )
{
this._add(arg_item)
return
}
this.error('not accepted type [' + arg_item.get_type() + '] for instance [' + arg_item.get_name() + ']')
return
}
this.error('bad item: not an instance object')
}
/**
* Add an item to the collection at the first position.
*
* @param {Instance} arg_item - Instance item.
*
* @returns {nothing}
*/
add_first(arg_item)
{
if ( T.isObject(arg_item) && arg_item instanceof Instance )
{
if ( this.has_accepted_type('*') || this.has_accepted_type(arg_item.$type) )
{
this._add_first(arg_item)
return
}
this.error('not accepted type [' + arg_item.$type + '] for instance [' + arg_item.$name + ']')
return
}
this.error('bad item: not an instance object')
}
/**
* Remove an item from the collection.
*
* @param {Instance} arg_item - Instance item.
*
* @returns {nothing}
*/
remove(arg_item)
{
if ( T.isObject(arg_item) && arg_item instanceof Instance )
{
const name = arg_item.get_name()
if (name in this._items_by_name)
{
this._remove(arg_item)
return
}
}
this.error('bad item: not an instance object or not found')
}
/**
* Test if an item is inside the collection.
*
* @param {Instance} arg_item - Instance item.
*
* @returns {boolean}
*/
has(arg_item)
{
if ( T.isObject(arg_item) && arg_item instanceof Instance )
{
return this._has(arg_item)
}
return false
}
/**
* Add an item to the collection without type checks (unsafe).
* @private
*
* @param {Instance} arg_item - Instance item.
*
* @returns {nothing}
*/
_add(arg_item)
{
if( this._has(arg_item) )
{
return
}
const name = arg_item.get_name()
const id = arg_item.get_id()
this._items_array.push(arg_item)
this._items_by_name[name] = arg_item
this._items_by_id[id] = arg_item
}
/**
* Add an item to the collection without type checks at first position (unsafe).
* @private
*
* @param {Instance} arg_item - Instance item.
*
* @returns {nothing}
*/
_add_first(arg_item)
{
if( this._has(arg_item) )
{
return
}
const name = arg_item.get_name()
const id = arg_item.get_id()
this._items_array = [arg_item].concat(this._items_array)
this._items_by_name[name] = arg_item
this._items_by_id[id] = arg_item
}
/**
* Remove an item from the collection without type checks (unsafe).
* @private
*
* @param {Instance} arg_item - Instance item.
*
* @returns {nothing}
*/
_remove(arg_item)
{
const name = arg_item.get_name()
const id = arg_item.get_id()
const index = this._items_array.indexOf(arg_item)
this._items_array.splice(index, 1)
delete this._items_by_name[name]
delete this._items_by_id[id]
}
/**
* Test if an item is inside the collection without type checks (unsafe).
* @private
*
* @param {Instance} arg_item - Instance item.
*
* @returns {boolean}
*/
_has(arg_item)
{
const name = arg_item.get_name()
return (name in this._items_by_name)
}
/**
* Find an item by its name into the collection.
*
* TODO: optimize with a map index.
*
* @param {string} arg_name - instance name.
*
* @returns {Instance|undefined}
*/
find_by_name(arg_name)
{
return this._items_by_name[arg_name]
}
/**
* Find an item by its id into the collection.
*
* @param {string} arg_id - instance id.
*
* @returns {Instance|undefined}
*/
find_by_id(arg_id)
{
return this._items_by_id[arg_id]
}
/**
* Find an item by one of its attributes into the collection.
*
* @param {string} arg_attr_name - instance attribute name.
* @param {any} arg_attr_value - instance attribute value.
*
* @returns {Instance|undefined}
*/
find_by_attr(arg_attr_name, arg_attr_value)
{
return _.find(this._items_array, item => (arg_attr_name in item) && item[arg_attr_name] == arg_attr_value)
}
/**
* Find an item by a filter function.
*
* @param {string} arg_filter_function - function to apply on instance, returns a boolean.
*
* @returns {Instance|undefined}
*/
find_by_filter(arg_filter_function)
{
return _.find(this._items_array, item => arg_filter_function(item) )
}
/**
* Filter items by one of theirs attributes into the collection.
*
* @param {string} arg_attr_name - instance attribute name.
* @param {any} arg_attr_value - instance attribute value.
*
* @returns {array}
*/
filter_by_attr(arg_attr_name, arg_attr_value)
{
return _.filter(this._items_array, item => (arg_attr_name in item) && item[arg_attr_name] == arg_attr_value)
}
/**
* Filter items by a filter function.
*
* @param {string} arg_filter_function - function to apply on instance, returns a boolean.
*
* @returns {array}
*/
filter_by_filter(arg_filter_function)
{
return _.filter(this._items_array, item => arg_filter_function(item) )
}
/**
* Get all collection accepted types.
*
* @returns {array} - array of types strings.
*/
get_accepted_types()
{
this._accepted_types
}
/**
* Set all collection accepted types.
*
* @param {array} arg_types - accepted types strings array.
*
* @returns {nothing}
*/
set_accepted_types(arg_types)
{
assert(T.isArray(arg_types), context + ':bad accepted types array')
this._accepted_types = arg_types
}
/**
* Add one collection accepted type.
*
* @param {string} arg_type - accepted types string.
*
* @returns {nothing}
*/
add_accepted_type(arg_type)
{
assert(T.isString(arg_type), context + ':bad accepted type string')
this._accepted_types.push(arg_type)
}
/**
* Test if collection has given accepted type.
*
* @param {string} arg_type - accepted types string.
*
* @returns {boolean}
*/
has_accepted_type(arg_type)
{
return this._accepted_types.indexOf(arg_type) > -1
}
/**
* forEach wrapper on ordered items.
*
* @param {function} arg_cb - callback to call on each item.
*
* @returns {nothing}
*/
forEach(arg_cb)
{
_.forEach(this._items_array, arg_cb)
}
}