lib/xcodeproj/project/object_dictionary.rb
module Xcodeproj
class Project
# This class represents relationships to other objects stored in a
# Dictionary.
#
# It works in conjunction with the {AbstractObject} class to ensure that
# the project is not serialized with unreachable objects by updating the
# with reference count on modifications.
#
# @note To provide full support as the other classes the dictionary should:
#
# Give the following attribute:
#
# has_many_references_by_keys :project_references, {
# :project_ref => PBXFileReference,
# :product_group => PBXGroup
# }
#
# This should be possible:
#
# #=> Note the API:
# root_object.project_references.project_ref = file
#
# #=> This should raise:
# root_object.project_references.product_group = file
#
# I.e. generate setters and getters from the specification hash.
#
# Also the interface is a dirty hybrid between the
# {AbstractObjectAttribute} and the {ObjectList}.
#
# @note Concerning the mutations methods it is safe to call only those
# which are overridden to inform objects reference count. Ideally all
# the hash methods should be covered, but this is not done yet.
# Moreover it is a moving target because the methods of array
# usually are implemented in C.
#
# @todo This class should use a {Hash} as a backing store instead of
# inheriting from it. This would prevent the usage of methods which
# don't notify the objects.
#
class ObjectDictionary < Hash
# @param [Object::AbstractObjectAttribute] attribute @see #attribute
# @param [Object] owner @see #owner
#
def initialize(attribute, owner)
@attribute = attribute
@owner = owner
end
# @return [Object::AbstractObjectAttribute] The attribute that generated
# the list.
#
attr_reader :attribute
# @return [Object] The object that owns the list.
#
attr_reader :owner
# @return [Array<Symbol>] The list of the allowed keys.
#
def allowed_keys
attribute.classes_by_key.keys
end
# @return [String] A string suitable for debugging.
#
def inspect
"<ObjectDictionary attribute:`#{@attribute.name}` " \
"owner:`#{@owner.display_name}` values:#{super.inspect}>"
end
# @!group Notification enabled methods
#------------------------------------------------------------------------#
# Associates an object to the given key and updates its references count.
#
# @param [String] key
# The key.
#
# @param [AbstractObject] object
# The object to add to the dictionary.
#
# @return [AbstractObject] The given object.
#
def []=(key, object)
key = normalize_key(key)
if object
perform_additions_operations(object, key)
else
perform_deletion_operations(self[key])
end
super(key, object)
end
# Removes the given key from the dictionary and informs the object that
# is not longer referenced by the owner.
#
# @param [String] key
# The key.
#
def delete(key)
key = normalize_key(key)
object = self[key]
perform_deletion_operations(object)
super
end
# @!group AbstractObject Methods
#-----------------------------------------------------------------------#
# @return [Hash<String => String>] The plist representation of the
# dictionary where the objects are replaced by their UUIDs.
#
def to_hash
result = {}
each do |key, obj|
if obj
plist_key = Object::CaseConverter.convert_to_plist(key, nil)
result[plist_key] = Nanaimo::String.new(obj.uuid, obj.ascii_plist_annotation)
end
end
result
end
def to_ascii_plist
to_hash
end
# @return [Hash<String => String>] Returns a cascade representation of
# the object without UUIDs.
#
def to_tree_hash
result = {}
each do |key, obj|
if obj
plist_key = Object::CaseConverter.convert_to_plist(key, nil)
result[plist_key] = obj.to_tree_hash
end
end
result
end
# Removes all the references to a given object.
#
def remove_reference(object)
each { |key, obj| self[key] = nil if obj == object }
end
# Informs the objects contained in the dictionary that another object is
# referencing them.
#
def add_referrer(referrer)
values.each { |obj| obj.add_referrer(referrer) }
end
# Informs the objects contained in the dictionary that another object
# stopped referencing them.
#
def remove_referrer(referrer)
values.each { |obj| obj.remove_referrer(referrer) }
end
private
# @!group Private helpers
#------------------------------------------------------------------------#
# @return [Symbol] Normalizes a key to a symbol converting the camel case
# format with underscores.
#
# @param [String, Symbol] key
# The key to normalize.
#
def normalize_key(key)
if key.is_a?(String)
key = Object::CaseConverter.convert_to_ruby(key)
end
unless allowed_keys.include?(key)
raise "[Xcodeproj] Unsupported key `#{key}` (allowed " \
"`#{allowed_keys}`) for `#{inspect}`"
end
key
end
# Informs an object that it was added to the dictionary. In practice it
# adds the owner of the list as referrer to the objects. It also
# validates the value.
#
# @return [void]
#
def perform_additions_operations(object, key)
owner.mark_project_as_dirty!
object.add_referrer(owner)
attribute.validate_value_for_key(object, key)
end
# Informs an object that it was removed from to the dictionary, so it can
# remove it from its referrers and take the appropriate actions.
#
# @return [void]
#
def perform_deletion_operations(objects)
owner.mark_project_as_dirty!
objects.remove_referrer(owner)
end
end
end
end