require 'forwardable' require 'compo/composite' module Compo # Implementation of Composite that stores its children in an Array. # # IDs for items entering a ListComposite must be numeric, and will change if # an item with an ID less than the item in question is deleted or inserted. # This means the ID function for objects in a ListComposite may report # different values at different times. # # Adding an object at an occupied ID moves the occupant and those at # successive IDs up by one. class ArrayComposite include Composite extend Forwardable # Initialises an ArrayComposite # # @api public # @example Initializes an ArrayComposite. # comp = ArrayComposite.new def initialize @children = [] end # Returns the ArrayComposite's children, as a Hash # # @api public # @example Gets the children of an empty ArrayComposite. # comp.children # #=> {} # @example Gets the children of a populated ArrayComposite. # comp.children # #=> {0: :first, 1: :second} # # @return [Hash] The Hash mapping the IDs of children to their values. def children Hash[(0...@children.size).zip(@children)] end private def_delegator :@children, :delete, :remove! def_delegator :@children, :delete_at, :remove_id! # Inserts a child into the ArrayComposite with the given ID # # You probably want to use #add instead. # # @api private # # @param id [Object] The ID under which the child is to be added. # @param child [Object] The child to add to the ArrayComposite. # # @return [Object] The newly added child, or nil if the ID was invalid. def add!(id, child) valid_id?(id) ? do_insert(id, child) : nil end # Checks to see if the given ID is valid # # A valid ID for ArrayComposites is a number between 0 and the current # size of the children list. # # @api private # # @param id [Object] The candidate ID. # # @return [Boolean] True if the ID is valid; false if not. def valid_id?(id) id.is_a?(Numeric) && (0..@children.size).cover?(id) end # Actually performs the insertion of an item into the array # # @api private # # @param id [Numeric] The index into the array at which the child is to be # inserted. # @param child [Object] The object to insert into the children array. # # @return [Object] The inserted child. def do_insert(id, child) @children.insert(id, child) child end # Creates an ID function for the given child # # The returned proc is O(n), as it checks the child array at each call to # find the current ID of the child. # # @api private # # @param child [Object] The child whose ID is to be returned by the proc. # # @return [Proc] A proc returning the child's ID. def id_function(object) proc { @children.index(object) } end end end