1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
|
require 'rgen/instantiator/reference_resolver'
module RGen
module Fragment
# A FragmentedModel represents a model which consists of fragments (ModelFragment).
#
# The main purpose of this class is to resolve references across fragments and
# to keep the references consistent while fragments are added or removed.
# This way it also plays an important role in keeping the model fragments consistent
# and thus ModelFragment objects should only be accessed via this interface.
# Overall unresolved references after the resolution step are also maintained.
#
# A FragmentedModel can also keep an RGen::Environment object up to date while fragments
# are added or removed. The environment must be registered with the constructor.
#
# Reference resolution is based on arbitrary identifiers. The identifiers must be
# provided in the fragments' indices. The FragmentedModel takes care to maintain
# the overall index.
#
class FragmentedModel
attr_reader :fragments
attr_reader :environment
# Creates a fragmented model. Options:
#
# :env
# environment which will be updated as model elements are added and removed
#
def initialize(options={})
@environment = options[:env]
@fragments = []
@index = nil
@fragment_change_listeners = []
@fragment_index = {}
end
# Adds a proc which is called when a fragment is added or removed
# The proc receives the fragment and one of :added, :removed
#
def add_fragment_change_listener(listener)
@fragment_change_listeners << listener
end
def remove_fragment_change_listener(listener)
@fragment_change_listeners.delete(listener)
end
# Add a fragment.
#
def add_fragment(fragment)
invalidate_cache
@fragments << fragment
fragment.elements.each{|e| @environment << e} if @environment
@fragment_change_listeners.each{|l| l.call(fragment, :added)}
end
# Removes the fragment. The fragment will be unresolved using unresolve_fragment.
#
def remove_fragment(fragment)
raise "fragment not part of model" unless @fragments.include?(fragment)
invalidate_cache
@fragments.delete(fragment)
@fragment_index.delete(fragment)
unresolve_fragment(fragment)
fragment.elements.each{|e| @environment.delete(e)} if @environment
@fragment_change_listeners.each{|l| l.call(fragment, :removed)}
end
# Resolve references between fragments.
# It is assumed that references within fragments have already been resolved.
# This method can be called several times. It will update the overall unresolved references.
#
# Options:
#
# :fragment_provider:
# Only if a +fragment_provider+ is given, the resolve step can be reverted later on
# by a call to unresolve_fragment. The fragment provider is a proc which receives a model
# element and must return the fragment in which the element is contained.
#
# :use_target_type:
# reference resolver uses the expected target type to narrow the set of possible targets
#
def resolve(options={})
local_index = index
@fragments.each do |f|
f.resolve_external(local_index, options)
end
end
# Remove all references between this fragment and all other fragments.
# The references will be replaced with unresolved references (MMProxy objects).
#
def unresolve_fragment(fragment)
fragment.unresolve_external
@fragments.each do |f|
if f != fragment
f.unresolve_external_fragment(fragment)
end
end
end
# Returns the overall unresolved references.
#
def unresolved_refs
@fragments.collect{|f| f.unresolved_refs}.flatten
end
# Returns the overall index.
# This is a Hash mapping identifiers to model elements accessible via the identifier.
#
def index
# Invalidate the cache when any fragment's local index changes.
# Assumption: If the local index content changes, there is a new index object.
fragments.each do |f|
if !@fragment_index[f] || (@fragment_index[f].object_id != f.index.object_id)
@fragment_index[f] = f.index
invalidate_cache
end
end
return @index if @index
@index = {}
fragments.each do |f|
f.index.each do |i|
(@index[i[0]] ||= []) << i[1]
end
end
@index
end
private
def invalidate_cache
@index = nil
end
end
end
end
|