File: fragmented_model.rb

package info (click to toggle)
ruby-rgen 0.10.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,428 kB
  • sloc: ruby: 11,344; xml: 1,368; yacc: 72; makefile: 10
file content (142 lines) | stat: -rw-r--r-- 4,478 bytes parent folder | download | duplicates (3)
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