class RGen::Fragment::ModelFragment
A model fragment is a list of root model elements associated with a location (e.g. a file). It also stores a list of unresolved references as well as a list of unresolved references which have been resolved. Using the latter, a fragment can undo reference resolution.
Optionally, an arbitrary data object may be associated with the fragment. The data object will also be stored in the cache.
If an element within the fragment changes this must be indicated to the
fragment by calling mark_changed
.
Note: the fragment knows how to resolve references
(resolve_local
, resolve_external
). However
considering a fragment a data structure, this functionality might be
removed in the future. Instead the fragment should be told about each
resolution taking place. Use method mark_resolved
for this
purpose.
Attributes
Public Class Methods
Create a model fragment
:data data object associated with this fragment :identifier_provider identifier provider to be used when resolving references it must be a proc which receives a model element and must return that element's identifier or nil if the element has no identifier
# File lib/rgen/fragment/model_fragment.rb, line 56 def initialize(location, options={}) @location = location @fragment_ref = FragmentRef.new @fragment_ref.fragment = self @data = options[:data] @resolved_refs = nil @changed = false @identifier_provider = options[:identifier_provider] end
Public Instance Methods
Builds the index of all elements within this fragment having an identifier the index is an array of 2-element arrays holding the identifier and the element
# File lib/rgen/fragment/model_fragment.rb, line 140 def build_index raise "cannot build index without an identifier provider" unless @identifier_provider @index = elements.collect { |e| ident = @identifier_provider.call(e, nil) ident && !ident.empty? ? [ident, e] : nil }.compact end
Indicates whether the fragment has been changed or not
# File lib/rgen/fragment/model_fragment.rb, line 103 def changed? @changed end
Returns all elements within this fragment
# File lib/rgen/fragment/model_fragment.rb, line 109 def elements return @elements if @elements @elements = [] @root_elements.each do |e| @elements << e all_child_elements(e, @elements) end @elements end
Returns the index of the element contained in this fragment.
# File lib/rgen/fragment/model_fragment.rb, line 121 def index build_index unless @index @index end
Must be called when any of the elements in this fragment has been changed
# File lib/rgen/fragment/model_fragment.rb, line 85 def mark_changed @changed = true @elements = nil @index = nil @unresolved_refs = nil # unresolved refs will be recalculated, no need to keep removed_urefs @removed_urefs = nil @resolved_refs = :dirty end
Marks a particular unresolved reference uref
as resolved to
target
in target_fragment
.
# File lib/rgen/fragment/model_fragment.rb, line 200 def mark_resolved(uref, target_fragment, target) @resolved_refs = {} if @resolved_refs.nil? || @resolved_refs == :dirty target_fragment ||= :unknown if target_fragment != self @resolved_refs[target_fragment] ||= [] @resolved_refs[target_fragment] << ResolvedReference.new(uref, target) end @removed_urefs ||= [] @removed_urefs << uref end
Can be used to reset the change status to unchanged.
# File lib/rgen/fragment/model_fragment.rb, line 97 def mark_unchanged @changed = false end
Resolves references to external fragments using the external_index provided. The external index must be a Hash mapping identifiers uniquely to model elements.
Options:
:fragment_provider: If a +fragment_provider+ is given, the resolve step can be reverted later on by a call to unresolve_external or unresolve_external_fragment. The fragment provider is a proc which receives a model element and must return the fragment in which it is contained. :use_target_type: reference resolver uses the expected target type to narrow the set of possible targets
# File lib/rgen/fragment/model_fragment.rb, line 178 def resolve_external(external_index, options) fragment_provider = options[:fragment_provider] resolver = RGen::Instantiator::ReferenceResolver.new( :identifier_resolver => proc {|ident| external_index[ident] }) if fragment_provider @resolved_refs = {} if @resolved_refs.nil? || @resolved_refs == :dirty on_resolve = proc { |ur, target| target_fragment = fragment_provider.call(target) target_fragment ||= :unknown raise "can not resolve local reference in resolve_external, call resolve_local first" \ if target_fragment == self @resolved_refs[target_fragment] ||= [] @resolved_refs[target_fragment] << ResolvedReference.new(ur, target) } @unresolved_refs = resolver.resolve(unresolved_refs, :on_resolve => on_resolve, :use_target_type => options[:use_target_type]) else @unresolved_refs = resolver.resolve(unresolved_refs, :use_target_type => options[:use_target_type]) end end
Resolves local references (within this fragment) as far as possible
Options:
:use_target_type: reference resolver uses the expected target type to narrow the set of possible targets
# File lib/rgen/fragment/model_fragment.rb, line 155 def resolve_local(options={}) resolver = RGen::Instantiator::ReferenceResolver.new index.each do |i| resolver.add_identifier(i[0], i[1]) end @unresolved_refs = resolver.resolve(unresolved_refs, :use_target_type => options[:use_target_type]) end
Set the root elements, normally done by an instantiator.
For optimization reasons the instantiator of the fragment may provide data explicitly which is normally derived by the fragment itself. In this case it is essential that this data is consistent with the fragment.
# File lib/rgen/fragment/model_fragment.rb, line 72 def set_root_elements(root_elements, options={}) @root_elements = root_elements @elements = options[:elements] @index = options[:index] @unresolved_refs = options[:unresolved_refs] @resolved_refs = nil # new unresolved refs, reset removed_urefs @removed_urefs = nil @changed = false end
Unresolve outgoing references to all external fragments, i.e. references which used to be represented by an unresolved reference from within this fragment. Note, that there may be more references to external fragments due to references which were represented by unresolved references from within other fragments.
# File lib/rgen/fragment/model_fragment.rb, line 216 def unresolve_external return if @resolved_refs.nil? raise "can not unresolve, missing fragment information" if @resolved_refs == :dirty || @resolved_refs[:unknown] rrefs = @resolved_refs.values.flatten @resolved_refs = {} unresolve_refs(rrefs) end
Like #unresolve_external
but only unresolve references to external fragment fragment
# File lib/rgen/fragment/model_fragment.rb, line 226 def unresolve_external_fragment(fragment) return if @resolved_refs.nil? raise "can not unresolve, missing fragment information" if @resolved_refs == :dirty || @resolved_refs[:unknown] rrefs = @resolved_refs[fragment] @resolved_refs.delete(fragment) unresolve_refs(rrefs) if rrefs end
Returns all unresolved references within this fragment, i.e. references to MMProxy objects
# File lib/rgen/fragment/model_fragment.rb, line 128 def unresolved_refs @unresolved_refs ||= collect_unresolved_refs if @removed_urefs @unresolved_refs -= @removed_urefs @removed_urefs = nil end @unresolved_refs end
Private Instance Methods
# File lib/rgen/fragment/model_fragment.rb, line 277 def all_child_elements(element, childs) containment_references(element.class).each do |r| element.getGenericAsArray(r.name).each do |c| childs << c all_child_elements(c, childs) end end end
# File lib/rgen/fragment/model_fragment.rb, line 256 def collect_unresolved_refs unresolved_refs = [] elements.each do |e| each_reference_target(e) do |r, t| if t.is_a?(RGen::MetamodelBuilder::MMProxy) unresolved_refs << RGen::Instantiator::ReferenceResolver::UnresolvedReference.new(e, r.name, t) end end end unresolved_refs end
# File lib/rgen/fragment/model_fragment.rb, line 286 def containment_references(clazz) @@containment_references_cache ||= {} @@containment_references_cache[clazz] ||= clazz.ecore.eAllReferences.select{|r| r.containment} end
# File lib/rgen/fragment/model_fragment.rb, line 269 def each_reference_target(element) non_containment_references(element.class).each do |r| element.getGenericAsArray(r.name).each do |t| yield(r, t) end end end
# File lib/rgen/fragment/model_fragment.rb, line 292 def non_containment_references(clazz) @@non_containment_references_cache ||= {} @@non_containment_references_cache[clazz] ||= clazz.ecore.eAllReferences.select{|r| !r.containment} end
Turns resolved references rrefs
back into unresolved
references
# File lib/rgen/fragment/model_fragment.rb, line 238 def unresolve_refs(rrefs) # make sure any removed_urefs have been removed, # otherwise they will be removed later even if this method actually re-added them unresolved_refs rrefs.each do |rr| ur = rr.uref refs = ur.element.getGeneric(ur.feature_name) if refs.is_a?(Array) index = refs.index(rr.target) ur.element.removeGeneric(ur.feature_name, rr.target) ur.element.addGeneric(ur.feature_name, ur.proxy, index) else ur.element.setGeneric(ur.feature_name, ur.proxy) end @unresolved_refs << ur end end