Reference Source

js/base/collection_version.js

// NPM IMPORTS
// import assert from 'assert'
import _ from 'lodash'
import semver from 'semver'

// COMMON IMPORTS
import T              from '../utils/types'
import Instance       from './instance'
import CollectionBase from './collection_base'


// let context = 'common/base/collection_version'



/**
 * Versionned Instance objects collection
 * 
 * @author Luc BORIES
 * @license Apache-2.0
* 
* @example
* API:
* 		->set_version_getter(arg_getter):nothing - Set version getter.
* 		->get_version(arg_item):string - Get instance version.
* 		->compare_versions(arg_version1, arg_version2):integer - Compare two instance versions: v1 < v2 => -1, v1 = v2 => 0, v1 > v2 => 1.
* 		
* 		->get_latest_item(arg_name):Instance|undefined - Get latest version of item by its name.
* 		->get_latest_items():array - Get latest version of all items.
* 		->get_item_of_version(arg_name, arg_version):Instance|undefined - Get an item by its name and its version.
* 		->has_version(arg_item_name, arg_version):boolean -  Test if an item is inside the collection.
* 
* @example
* INHERITED API:
* 		->set_all(arg_items):nothing - Set all collection items. (INHERITED)
* 		->get_all(arg_types):array - Get all collection items or filter items with given type. (INHERITED)
* 		->get_all_names(arg_types):array - Get all items names with or without a filter on items types. (INHERITED)
* 		->get_all_ids():array - Get all items ids with or without a filter on items types. (INHERITED)
* 
* 		->item(arg_name):Instance - Get an item by its name. (INHERITED)
* 
* 		->get_count():number - Get all items count. (INHERITED)
* 		->get_first():object|undefined - Get first item. (INHERITED)
* 		->get_last():object|undefined - Get last item. (INHERITED)
* 
* 		->add(arg_item):nothing - Add an item to the collection. (OVERWRITTEN)
* 		->add_first(arg_item):nothing - Add an item to the collection at the first position. (INHERITED)
* 		->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 CollectionVersion extends CollectionBase
{
	/**
	 * Create a collection of versionned Instance objects.
	 * 
	 * @param {...Instance} args - variadic list of Instance.
	 * 
	 * @returns {nothing}
	 */
	constructor(...args)
	{
		super()

		/**
		 * Class type flag.
		 * @type {boolean}
		 */
		this.is_collection_version  = true

		/**
		 * Items versions names map.
		 * @type {object}
		 */
		this._items_by_name_by_version     = {}

		/**
		 * Items last version names map.
		 * @type {object}
		 */
		this._items_by_name_latest_version = {}

		/**
		 * Get version helper.
		 * @type {function}
		 */
		this.$version_getter = (item)=> item && item.get_version ? item.get_version() : undefined

		if ( args.length == 1 && T.isArray(args[0]) )
		{
			args = args[0]
		}
		
		if (args && args.length > 0)
		{
			this.set_all(args)
		}
	}



	/**
	 * Set version getter.
	 * 
	 * @param {Function} arg_getter - instance version getter.
	 * 
	 * @returns {nothing}
	 */
	set_version_getter(arg_getter)
	{
		if ( T.isFunction(arg_getter) )
		{
			this.$version_getter = arg_getter
		}
	}



	/**
	 * Get instance version.
	 * 
	 * @param {Instance} arg_item - Instance item.
	 * 
	 * @returns {string}
	 */
	get_version(arg_item)
	{
		if ( T.isObject(arg_item) && arg_item instanceof Instance )
		{
			return T.isFunction(this.$version_getter) ? this.$version_getter(arg_item) : undefined
		}

		return undefined
	}



	/**
	 * Compare two instance versions: v1 < v2 => -1, v1 = v2 => 0, v1 > v2 => 1.
	 * 
	 * @param {string} arg_version1 - requested instance 1 version.
	 * @param {string} arg_version2 - requested instance 2 version.
	 * 
	 * @returns {integer}
	 */
	compare_versions(arg_version1, arg_version2)
	{
		return semver.compare(arg_version1, arg_version2)
		// return (arg_version1 < arg_version2) ? -1 : ( (arg_version1 == arg_version2) ? 0 : 1) // TODO
	}
	


	/**
	 * Get latest version of item by its name.
	 * 
	 * @param {string} arg_name - instance name.
	 * 
	 * @returns {Instance|undefined}
	 */
	get_latest_item(arg_name)
	{
		// console.log('collection.get_latest_item:latest=' + Object.keys(this._items_by_name_latest_version).toString())
		return this._items_by_name_latest_version ? this._items_by_name_latest_version[arg_name] : undefined
	}
	


	/**
	 * Get latest version of all items.
	 * 
	 * @returns {array}
	 */
	get_latest_items()
	{
		const items = this._items_by_name_latest_version ? _.toArray(this._items_by_name_latest_version) : []
		// console.log(items.length, context + ':get_latest_items:items.length')
		return items
	}
	
	
	
	/**
	 * Get an item by its name and its version.
	 * 
	 * @param {string} arg_name - requested instance name.
	 * @param {string} arg_version - requested instance version.
	 * 
	 * @returns {Instance|undefined}
	 */
	get_item_of_version(arg_name, arg_version)
	{
		const versions = this._items_by_name_by_version ? this._items_by_name_by_version[arg_name] : undefined
		return versions ? versions[arg_version] : undefined
	}
	
	

	/**
	 * Test if an item is inside the collection.
	 * 
	 * @param {string} arg_item_name - Instance item name.
	 * @param {string} arg_version - requested instance version.
	 * 
	 * @returns {boolean}
	 */
	has_version(arg_item_name, arg_version)
	{
		if ( ! this._items_by_name_by_version[arg_item_name] )
		{
			return false
		}
		return arg_version in this._items_by_name_by_version[arg_item_name]
	}
	
	

	/**
	 * 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()
		const version = this.get_version(arg_item)

		this._items_array.push(arg_item)
		this._items_by_name[name] = arg_item
		this._items_by_id[id] = arg_item

		if (! this._items_by_name_by_version[name])
		{
			this._items_by_name_by_version[name] = {}
			this._items_by_name_latest_version[name] = undefined
		}

		this._items_by_name_by_version[name][version] = arg_item
		if ( this._items_by_name_latest_version[name] )
		{
			const prev_latest = this.get_version(this._items_by_name_latest_version[name])
			const comparison = this.compare_versions(version, prev_latest)
			if (comparison == 1)
			{
				this._items_by_name_latest_version[name] = arg_item
			}
		} else {
			this._items_by_name_latest_version[name] = 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
		}

		this._add(arg_item)

		const items = this._get_all()
		items.pop()
		this._set_all( [arg_item].concat(items) )
	}
	
	

	/**
	 * 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 version = this.get_version(arg_item)
		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]

		if ( this._items_by_name_by_version[name] )
		{
			delete this._items_by_name_by_version[name][version]
		}
		delete this._items_by_name_latest_version[name]
	}
	
	

	/**
	 * 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()
		const version = this.get_version(arg_item)
		return (name in this._items_by_name) && this.has_version(name, version)
	}
}