rgen 0.5.4 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. data/CHANGELOG +28 -0
  2. data/Rakefile +3 -4
  3. data/lib/ea_support/uml13_ea_metamodel.rb +3 -3
  4. data/lib/ea_support/uml13_ea_to_uml13.rb +33 -2
  5. data/lib/ea_support/uml13_to_uml13_ea.rb +7 -0
  6. data/lib/mmgen/mm_ext/ecore_mmgen_ext.rb +4 -4
  7. data/lib/mmgen/templates/metamodel_generator.tpl +143 -143
  8. data/lib/rgen/ecore/ecore.rb +11 -1
  9. data/lib/rgen/ecore/ecore_interface.rb +47 -0
  10. data/lib/rgen/ecore/ecore_to_ruby.rb +166 -0
  11. data/lib/rgen/ecore/{ecore_transformer.rb → ruby_to_ecore.rb} +11 -11
  12. data/lib/rgen/environment.rb +15 -2
  13. data/lib/rgen/fragment/dump_file_cache.rb +63 -0
  14. data/lib/rgen/fragment/fragmented_model.rb +139 -0
  15. data/lib/rgen/fragment/model_fragment.rb +268 -0
  16. data/lib/rgen/instantiator/abstract_xml_instantiator.rb +44 -72
  17. data/lib/rgen/instantiator/default_xml_instantiator.rb +2 -2
  18. data/lib/rgen/instantiator/ecore_xml_instantiator.rb +16 -1
  19. data/lib/rgen/instantiator/json_instantiator.rb +16 -2
  20. data/lib/rgen/instantiator/nodebased_xml_instantiator.rb +118 -138
  21. data/lib/rgen/instantiator/qualified_name_resolver.rb +5 -1
  22. data/lib/rgen/instantiator/reference_resolver.rb +126 -24
  23. data/lib/rgen/instantiator/xmi11_instantiator.rb +6 -2
  24. data/lib/rgen/metamodel_builder.rb +18 -6
  25. data/lib/rgen/metamodel_builder/builder_extensions.rb +431 -407
  26. data/lib/rgen/metamodel_builder/builder_runtime.rb +8 -8
  27. data/lib/rgen/metamodel_builder/constant_order_helper.rb +4 -4
  28. data/lib/rgen/metamodel_builder/data_types.rb +5 -1
  29. data/lib/rgen/metamodel_builder/intermediate/feature.rb +167 -0
  30. data/lib/rgen/metamodel_builder/module_extension.rb +2 -2
  31. data/lib/rgen/model_builder.rb +10 -5
  32. data/lib/rgen/model_builder/builder_context.rb +17 -1
  33. data/lib/rgen/serializer/opposite_reference_filter.rb +18 -0
  34. data/lib/rgen/serializer/qualified_name_provider.rb +45 -0
  35. data/lib/rgen/template_language/template_container.rb +3 -1
  36. data/lib/rgen/{auto_class_creator.rb → util/auto_class_creator.rb} +6 -1
  37. data/lib/rgen/util/cached_glob.rb +67 -0
  38. data/lib/rgen/util/file_cache_map.rb +104 -0
  39. data/lib/rgen/util/file_change_detector.rb +78 -0
  40. data/lib/rgen/{method_delegation.rb → util/method_delegation.rb} +18 -3
  41. data/lib/rgen/{model_comparator.rb → util/model_comparator.rb} +17 -5
  42. data/lib/rgen/{model_comparator_base.rb → util/model_comparator_base.rb} +6 -1
  43. data/lib/rgen/{model_dumper.rb → util/model_dumper.rb} +6 -1
  44. data/lib/rgen/{name_helper.rb → util/name_helper.rb} +6 -1
  45. data/lib/rgen/util/pattern_matcher.rb +329 -0
  46. data/lib/transformers/uml13_to_ecore.rb +103 -60
  47. data/test/ecore_self_test.rb +43 -42
  48. data/test/json_test.rb +15 -0
  49. data/test/metamodel_builder_test.rb +361 -206
  50. data/test/metamodel_from_ecore_test.rb +45 -0
  51. data/test/metamodel_order_test.rb +10 -4
  52. data/test/metamodel_roundtrip_test.rb +2 -2
  53. data/test/metamodel_roundtrip_test/TestModel_Regenerated.rb +1 -1
  54. data/test/metamodel_roundtrip_test/houseMetamodel_Regenerated.ecore +50 -50
  55. data/test/method_delegation_test.rb +9 -9
  56. data/test/model_builder/ecore_internal.rb +19 -9
  57. data/test/model_builder/serializer_test.rb +1 -1
  58. data/test/reference_resolver_test.rb +79 -12
  59. data/test/rgen_test.rb +2 -0
  60. data/test/template_language_test.rb +7 -0
  61. data/test/template_language_test/templates/callback_indent_test/a.tpl +12 -0
  62. data/test/template_language_test/templates/callback_indent_test/b.tpl +5 -0
  63. data/test/testmodel/ea_testmodel_regenerated.xml +588 -583
  64. data/test/transformer_test.rb +3 -3
  65. data/test/util/file_cache_map_test.rb +91 -0
  66. data/test/util/file_cache_map_test/testdir/fileA +1 -0
  67. data/test/util_test.rb +4 -0
  68. data/test/xml_instantiator_test.rb +139 -135
  69. metadata +49 -104
  70. data/lib/rgen/ecore/ecore_instantiator.rb +0 -31
  71. data/lib/rgen/metamodel_builder/metamodel_description.rb +0 -232
  72. data/redist/xmlscan/ChangeLog +0 -1301
  73. data/redist/xmlscan/README +0 -34
  74. data/redist/xmlscan/THANKS +0 -11
  75. data/redist/xmlscan/doc/changes.html +0 -74
  76. data/redist/xmlscan/doc/changes.rd +0 -80
  77. data/redist/xmlscan/doc/en/conformance.html +0 -136
  78. data/redist/xmlscan/doc/en/conformance.rd +0 -152
  79. data/redist/xmlscan/doc/en/manual.html +0 -356
  80. data/redist/xmlscan/doc/en/manual.rd +0 -402
  81. data/redist/xmlscan/doc/ja/conformance.ja.html +0 -118
  82. data/redist/xmlscan/doc/ja/conformance.ja.rd +0 -134
  83. data/redist/xmlscan/doc/ja/manual.ja.html +0 -325
  84. data/redist/xmlscan/doc/ja/manual.ja.rd +0 -370
  85. data/redist/xmlscan/doc/src/Makefile +0 -41
  86. data/redist/xmlscan/doc/src/conformance.rd.src +0 -256
  87. data/redist/xmlscan/doc/src/langsplit.rb +0 -110
  88. data/redist/xmlscan/doc/src/manual.rd.src +0 -614
  89. data/redist/xmlscan/install.rb +0 -41
  90. data/redist/xmlscan/lib/xmlscan/encoding.rb +0 -311
  91. data/redist/xmlscan/lib/xmlscan/htmlscan.rb +0 -289
  92. data/redist/xmlscan/lib/xmlscan/namespace.rb +0 -352
  93. data/redist/xmlscan/lib/xmlscan/parser.rb +0 -299
  94. data/redist/xmlscan/lib/xmlscan/scanner.rb +0 -1109
  95. data/redist/xmlscan/lib/xmlscan/version.rb +0 -22
  96. data/redist/xmlscan/lib/xmlscan/visitor.rb +0 -158
  97. data/redist/xmlscan/lib/xmlscan/xmlchar.rb +0 -441
  98. data/redist/xmlscan/memo/CONFORMANCE +0 -1249
  99. data/redist/xmlscan/memo/PRODUCTIONS +0 -195
  100. data/redist/xmlscan/memo/contentspec.ry +0 -335
  101. data/redist/xmlscan/samples/chibixml.rb +0 -105
  102. data/redist/xmlscan/samples/getxmlchar.rb +0 -122
  103. data/redist/xmlscan/samples/rexml.rb +0 -159
  104. data/redist/xmlscan/samples/xmlbench.rb +0 -88
  105. data/redist/xmlscan/samples/xmlbench/parser/chibixml.rb +0 -22
  106. data/redist/xmlscan/samples/xmlbench/parser/nqxml.rb +0 -29
  107. data/redist/xmlscan/samples/xmlbench/parser/rexml.rb +0 -62
  108. data/redist/xmlscan/samples/xmlbench/parser/xmlparser.rb +0 -22
  109. data/redist/xmlscan/samples/xmlbench/parser/xmlscan-0.0.10.rb +0 -62
  110. data/redist/xmlscan/samples/xmlbench/parser/xmlscan-chibixml.rb +0 -22
  111. data/redist/xmlscan/samples/xmlbench/parser/xmlscan-rexml.rb +0 -22
  112. data/redist/xmlscan/samples/xmlbench/parser/xmlscan.rb +0 -99
  113. data/redist/xmlscan/samples/xmlbench/xmlbench-lib.rb +0 -116
  114. data/redist/xmlscan/samples/xmlconftest.rb +0 -200
  115. data/redist/xmlscan/test.rb +0 -7
  116. data/redist/xmlscan/tests/deftestcase.rb +0 -73
  117. data/redist/xmlscan/tests/runtest.rb +0 -47
  118. data/redist/xmlscan/tests/testall.rb +0 -14
  119. data/redist/xmlscan/tests/testencoding.rb +0 -438
  120. data/redist/xmlscan/tests/testhtmlscan.rb +0 -752
  121. data/redist/xmlscan/tests/testnamespace.rb +0 -457
  122. data/redist/xmlscan/tests/testparser.rb +0 -591
  123. data/redist/xmlscan/tests/testscanner.rb +0 -1749
  124. data/redist/xmlscan/tests/testxmlchar.rb +0 -143
  125. data/redist/xmlscan/tests/visitor.rb +0 -34
@@ -21,6 +21,8 @@ class JsonInstantiator
21
21
  # classes are looked for in metamodel package module +mm+,
22
22
  # +options+ include:
23
23
  # short_class_names: if true subpackages will be searched for unqualifed class names (default: true)
24
+ # ignore_keys: an array of json object key names which are to be ignored (default: none)
25
+ #
24
26
  # The options are also passed to the underlying QualifiedNameResolver.
25
27
  #
26
28
  def initialize(env, mm, options={})
@@ -28,6 +30,7 @@ class JsonInstantiator
28
30
  @mm = mm
29
31
  @options = options
30
32
  @short_class_names = !@options.has_key?(:short_class_names) || @options[:short_class_names]
33
+ @ignore_keys = @options[:ignore_keys] || []
31
34
  @unresolvedReferences = []
32
35
  @classes = {}
33
36
  @classes_flat = {}
@@ -42,15 +45,23 @@ class JsonInstantiator
42
45
  # Returns an array of ReferenceResolver::UnresolvedReference
43
46
  # describing the references which could not be resolved
44
47
  #
45
- def instantiate(str)
48
+ # Options:
49
+ # :root_elements: if an array is provided, it will be filled with the root elements
50
+ #
51
+ def instantiate(str, options={})
46
52
  root = @parser.parse(str)
53
+ if options[:root_elements].is_a?(Array)
54
+ options[:root_elements].clear
55
+ root.each{|r| options[:root_elements] << r}
56
+ end
47
57
  resolver = QualifiedNameResolver.new(root, @options)
48
58
  resolver.resolveReferences(@unresolvedReferences)
49
59
  end
50
60
 
51
61
  def createObject(hash)
52
62
  className = hash["_class"]
53
- raise "no class information" unless className
63
+ # hashes without a _class key are returned as is
64
+ return hash unless className
54
65
  if @classes[className]
55
66
  clazz = @classes[className].instanceClass
56
67
  elsif @short_class_names && @classes_flat[className]
@@ -59,6 +70,9 @@ class JsonInstantiator
59
70
  raise "class not found: #{className}"
60
71
  end
61
72
  hash.delete("_class")
73
+ @ignore_keys.each do |k|
74
+ hash.delete(k)
75
+ end
62
76
  urefs = []
63
77
  hash.keys.each do |k|
64
78
  f = eFeature(k, clazz)
@@ -1,157 +1,137 @@
1
- $:.unshift File.join(File.dirname(__FILE__),"..","..","..","redist","xmlscan","lib")
2
-
3
1
  require 'rgen/metamodel_builder'
4
2
  require 'rgen/instantiator/abstract_instantiator'
5
- require 'xmlscan/namespace'
3
+ require 'nokogiri'
6
4
 
7
5
  module RGen
8
6
 
9
7
  module Instantiator
10
8
 
11
9
  class NodebasedXMLInstantiator < AbstractInstantiator
12
-
13
- class << self
10
+
11
+ class << self
14
12
 
15
- # The prune level is the number of parent/children associations which
16
- # is kept when the instantiator ascents the XML tree.
17
- # If the level is 2, information for the node's children and the childrens'
18
- # children will be available as an XMLNodeDescriptor object.
19
- # If the level is 0 no pruning will take place, i.e. the whole information
20
- # is kept until the end of the instantiation process. 0 is default.
21
- def set_prune_level(level)
22
- @prune_level = level
23
- end
13
+ # The prune level is the number of parent/children associations which
14
+ # is kept when the instantiator ascents the XML tree.
15
+ # If the level is 2, information for the node's children and the childrens'
16
+ # children will be available as an XMLNodeDescriptor object.
17
+ # If the level is 0 no pruning will take place, i.e. the whole information
18
+ # is kept until the end of the instantiation process. 0 is default.
19
+ def set_prune_level(level)
20
+ @prune_level = level
21
+ end
24
22
 
25
- def prune_level # :nodoc:
26
- @prune_level ||= 0
27
- end
23
+ def prune_level # :nodoc:
24
+ @prune_level ||= 0
25
+ end
28
26
 
29
- end
27
+ end
30
28
 
31
- class XMLNodeDescriptor
32
- attr_reader :namespace, :qtag, :prefix, :tag, :parent, :attributes, :chardata
33
- attr_accessor :object, :children
34
-
35
- def initialize(ns, qtag, prefix, tag, parent, children, attributes)
36
- @namespace, @qtag, @prefix, @tag, @parent, @children, @attributes =
37
- ns, qtag, prefix, tag, parent, children, attributes
38
- @parent.children << self if @parent
39
- @chardata = []
40
- end
41
- end
42
-
43
- class XMLScanVisitor
44
- attr_reader :namespaces
45
-
46
- include XMLScan::NSVisitor
47
-
48
- def initialize(inst)
49
- @current_attributes = {}
50
- @instantiator = inst
51
- end
52
-
53
- def on_attribute_ns(qname, prefix, localpart)
54
- @current_attr_name = qname
55
- end
56
-
57
- def on_attr_value(str)
58
- @current_attributes[@current_attr_name] = str
59
- end
60
-
61
- def on_stag_end_ns(qname, namespaces)
62
- @namespaces = namespaces
63
- if qname =~ /^([^:]+):([^:]+)$/
64
- prefix, tag = $1, $2
65
- else
66
- prefix, tag = nil, qname
67
- end
68
- @instantiator.start_element(namespaces[prefix], qname, prefix, tag, @current_attributes)
69
- @current_attributes = {}
70
- end
71
-
72
- def on_stag_end_empty_ns(qname, namespaces)
73
- on_stag_end_ns(qname, namespaces)
74
- @instantiator.end_element
75
- end
76
-
77
- def on_etag(name)
78
- @instantiator.end_element
79
- end
80
-
81
- def on_chardata(str)
82
- @instantiator.on_chardata(str)
83
- end
84
- end
85
-
86
- def initialize(env)
87
- super
88
- @env = env
89
- @stack = []
90
- end
91
-
92
- def instantiate_file(file)
93
- File.open(file) { |f| parse(f.read)}
94
- resolve
95
- end
96
-
97
- def instantiate(text)
98
- parse(text)
99
- resolve
100
- end
101
-
102
- def parse(src)
103
- @visitor = XMLScanVisitor.new(self)
104
- parser = XMLScan::XMLParserNS.new(@visitor)
105
- parser.parse(src)
106
- @visitor = nil
107
- end
108
-
109
- def start_element(ns, qtag, prefix, tag, attributes)
110
- node = XMLNodeDescriptor.new(ns, qtag, prefix, tag, @stack[-1], [], attributes)
111
- @stack.push node
112
- on_descent(node)
113
- end
114
-
115
- def end_element
116
- node = @stack.pop
117
- on_ascent(node)
118
- prune_children(node, self.class.prune_level - 1) if self.class.prune_level > 0
119
- end
120
-
121
- def on_chardata(str)
122
- node = @stack.last
123
- node.chardata << str
124
- end
29
+ class XMLNodeDescriptor
30
+ attr_reader :namespace, :qtag, :prefix, :tag, :parent, :attributes, :chardata
31
+ attr_accessor :object, :children
32
+
33
+ def initialize(ns, qtag, prefix, tag, parent, children, attributes)
34
+ @namespace, @qtag, @prefix, @tag, @parent, @children, @attributes =
35
+ ns, qtag, prefix, tag, parent, children, attributes
36
+ @parent.children << self if @parent
37
+ @chardata = []
38
+ end
39
+ end
40
+
41
+ class Visitor < Nokogiri::XML::SAX::Document
42
+ attr_reader :namespaces
43
+
44
+ def initialize(inst)
45
+ @instantiator = inst
46
+ @namespaces = {}
47
+ end
48
+
49
+ def start_element_namespace(tag, attributes, prefix, uri, ns)
50
+ ns.each{|n| @namespaces[n[0]] = n[1]}
51
+ attrs = {}
52
+ attributes.each{|a| attrs[a.prefix ? a.prefix+":"+a.localname : a.localname] = a.value}
53
+ qname = prefix ? prefix+":"+tag : tag
54
+ @instantiator.start_element(uri, qname, prefix, tag, attrs)
55
+ end
56
+
57
+ def end_element(name)
58
+ @instantiator.end_element
59
+ end
60
+
61
+ def characters(str)
62
+ @instantiator.on_chardata(str)
63
+ end
64
+ end
65
+
66
+ def initialize(env)
67
+ super
68
+ @env = env
69
+ @stack = []
70
+ end
71
+
72
+ def instantiate_file(file)
73
+ File.open(file) { |f| parse(f.read)}
74
+ resolve
75
+ end
76
+
77
+ def instantiate(text)
78
+ parse(text)
79
+ resolve
80
+ end
81
+
82
+ def parse(src)
83
+ @visitor = Visitor.new(self)
84
+ parser = Nokogiri::XML::SAX::Parser.new(@visitor)
85
+ parser.parse(src)
86
+ @visitor = nil
87
+ end
88
+
89
+ def start_element(ns, qtag, prefix, tag, attributes)
90
+ node = XMLNodeDescriptor.new(ns, qtag, prefix, tag, @stack[-1], [], attributes)
91
+ @stack.push node
92
+ on_descent(node)
93
+ end
94
+
95
+ def end_element
96
+ node = @stack.pop
97
+ on_ascent(node)
98
+ prune_children(node, self.class.prune_level - 1) if self.class.prune_level > 0
99
+ end
100
+
101
+ def on_chardata(str)
102
+ node = @stack.last
103
+ node.chardata << str
104
+ end
125
105
 
126
- # This method is called when the XML parser goes down the tree.
127
- # An XMLNodeDescriptor +node+ describes the current node.
128
- # Implementing classes must overwrite this method.
129
- def on_descent(node)
130
- raise "Overwrite this method !"
131
- end
132
-
133
- # This method is called when the XML parser goes up the tree.
134
- # An XMLNodeDescriptor +node+ describes the current node.
135
- # Implementing classes must overwrite this method.
136
- def on_ascent(node)
137
- raise "Overwrite this method !"
138
- end
106
+ # This method is called when the XML parser goes down the tree.
107
+ # An XMLNodeDescriptor +node+ describes the current node.
108
+ # Implementing classes must overwrite this method.
109
+ def on_descent(node)
110
+ raise "Overwrite this method !"
111
+ end
112
+
113
+ # This method is called when the XML parser goes up the tree.
114
+ # An XMLNodeDescriptor +node+ describes the current node.
115
+ # Implementing classes must overwrite this method.
116
+ def on_ascent(node)
117
+ raise "Overwrite this method !"
118
+ end
139
119
 
140
- def namespaces
141
- @visitor.namespaces if @visitor
142
- end
120
+ def namespaces
121
+ @visitor.namespaces if @visitor
122
+ end
143
123
 
144
- private
145
-
146
- def prune_children(node, level)
147
- if level == 0
148
- node.children = nil
149
- else
150
- node.children.each { |c| prune_children(c, level-1) }
151
- end
152
- end
124
+ private
125
+
126
+ def prune_children(node, level)
127
+ if level == 0
128
+ node.children = nil
129
+ else
130
+ node.children.each { |c| prune_children(c, level-1) }
131
+ end
132
+ end
153
133
  end
154
134
 
155
135
  end
156
136
 
157
- end
137
+ end
@@ -6,7 +6,6 @@ module Instantiator
6
6
 
7
7
  # This is a resolver resolving element identifiers which are qualified names.
8
8
  class QualifiedNameResolver
9
- include ReferenceResolver
10
9
 
11
10
  attr_reader :nameAttribute
12
11
  attr_reader :separator
@@ -20,6 +19,7 @@ class QualifiedNameResolver
20
19
  @elementByQName = {}
21
20
  @visitedQName = {}
22
21
  @childReferences = {}
22
+ @resolverDelegate = ReferenceResolver.new(:identifier_resolver => method(:resolveIdentifier))
23
23
  end
24
24
 
25
25
  def resolveIdentifier(qualifiedName)
@@ -57,6 +57,10 @@ class QualifiedNameResolver
57
57
  @elementByQName[qualifiedName] ||= nil
58
58
  end
59
59
 
60
+ def resolveReferences(unresolvedReferences, problems=[])
61
+ @resolverDelegate.resolve(unresolvedReferences, :problems => problems)
62
+ end
63
+
60
64
  private
61
65
 
62
66
  def allNamedChildren(element)
@@ -2,41 +2,143 @@ module RGen
2
2
 
3
3
  module Instantiator
4
4
 
5
- # This module is meant to be mixed into a resolver class providing the method +resolveIdentifier+
6
- module ReferenceResolver
5
+ # The ReferenceResolver can be used to resolve unresolved references, i.e. instances
6
+ # of class UnresolvedReference
7
+ #
8
+ # There are two ways how this can be used:
9
+ # 1. the identifiers and associated model elements are added upfront using +add_identifier+
10
+ # 2. register an :identifier_resolver with the constructor, which will be invoked
11
+ # for every unresolved identifier
12
+ #
13
+ class ReferenceResolver
7
14
 
8
15
  # Instances of this class represent information about not yet resolved references.
9
- # This consists of the +element+ and metamodel +featureName+ which hold/is to hold the reference
10
- # and the proxy object which is the placeholder for the reference.
11
- UnresolvedReference = Struct.new(:element, :featureName, :proxy)
12
-
13
- # tries to resolve the given +unresolvedReferences+
14
- # if resolution is successful, the proxy object will be removed
15
- # otherwise there will be an error description in +problems+
16
- # returns an array of the references which are still unresolved
17
- def resolveReferences(unresolvedReferences, problems=[])
18
- stillUnresolvedReferences = []
19
- unresolvedReferences.each do |ur|
20
- target = resolveIdentifier(ur.proxy.targetIdentifier)
21
- if target && !target.is_a?(Array)
22
- if ur.element.hasManyMethods(ur.featureName)
23
- ur.element.removeGeneric(ur.featureName, ur.proxy)
24
- ur.element.addGeneric(ur.featureName, target)
16
+ # This consists of the +element+ and metamodel +feature_name+ which hold/is to hold the
17
+ # reference and the +proxy+ object which is the placeholder for the reference.
18
+ # If the reference could not be resolved because the target type does not match the
19
+ # feature type, the flag +target_type_error+ will be set.
20
+ #
21
+ class UnresolvedReference
22
+ attr_reader :feature_name, :proxy
23
+ attr_accessor :element, :target_type_error
24
+ def initialize(element, feature_name, proxy)
25
+ @element = element
26
+ @feature_name = feature_name
27
+ @proxy = proxy
28
+ end
29
+ end
30
+
31
+ # Create a reference resolver, options:
32
+ #
33
+ # :identifier_resolver:
34
+ # a proc which is called with an identifier and which should return the associated element
35
+ # in case the identifier is not uniq, the proc may return multiple values
36
+ # default: lookup element in internal map
37
+ #
38
+ def initialize(options={})
39
+ @identifier_resolver = options[:identifier_resolver]
40
+ @identifier_map = {}
41
+ end
42
+
43
+ # Add an +identifer+ / +element+ pair which will be used for looking up unresolved identifers
44
+ def add_identifier(ident, element)
45
+ map_entry = @identifier_map[ident]
46
+ if map_entry
47
+ if map_entry.is_a?(Array)
48
+ map_entry << element
49
+ else
50
+ @identifier_map[ident] = [map_entry, element]
51
+ end
52
+ else
53
+ @identifier_map[ident] = element
54
+ end
55
+ end
56
+
57
+ # Tries to resolve the given +unresolved_refs+. If resolution is successful, the proxy object
58
+ # will be removed, otherwise there will be an error description in the problems array.
59
+ # In case the resolved target element's type is not valid for the given feature, the
60
+ # +target_type_error+ flag will be set on the unresolved reference.
61
+ # Returns an array of the references which are still unresolved. Options:
62
+ #
63
+ # :problems
64
+ # an array to which problems will be appended
65
+ #
66
+ # :on_resolve
67
+ # a proc which will be called for every sucessful resolution, receives the unresolved
68
+ # reference as well as to new target element
69
+ #
70
+ # :use_target_type
71
+ # use the expected target type to narrow the set of possible targets
72
+ # (i.e. ignore targets with wrong type)
73
+ #
74
+ def resolve(unresolved_refs, options={})
75
+ problems = options[:problems] || []
76
+ still_unresolved_refs = []
77
+ unresolved_refs.each do |ur|
78
+ if @identifier_resolver
79
+ target = @identifier_resolver.call(ur.proxy.targetIdentifier)
80
+ else
81
+ target = @identifier_map[ur.proxy.targetIdentifier]
82
+ end
83
+ target = [target].compact unless target.is_a?(Array)
84
+ if options[:use_target_type]
85
+ feature = ur.element.class.ecore.eAllReferences.find{|r| r.name == ur.feature_name}
86
+ target = target.select{|e| e.is_a?(feature.eType.instanceClass)}
87
+ end
88
+ if target.size == 1
89
+ refs = ur.element.getGeneric(ur.feature_name)
90
+ if refs.is_a?(Array)
91
+ index = refs.index(ur.proxy)
92
+ ur.element.removeGeneric(ur.feature_name, ur.proxy)
93
+ begin
94
+ ur.element.addGeneric(ur.feature_name, target[0], index)
95
+ options[:on_resolve] && options[:on_resolve].call(ur, target[0])
96
+ rescue StandardError => e
97
+ if is_type_error?(e)
98
+ ur.element.addGeneric(ur.feature_name, ur.proxy, index)
99
+ ur.target_type_error = true
100
+ problems << type_error_message(target[0])
101
+ still_unresolved_refs << ur
102
+ else
103
+ raise
104
+ end
105
+ end
25
106
  else
26
- # this will replace the proxy
27
- ur.element.setGeneric(ur.featureName, target)
107
+ begin
108
+ # this will replace the proxy
109
+ ur.element.setGeneric(ur.feature_name, target[0])
110
+ options[:on_resolve] && options[:on_resolve].call(ur, target[0])
111
+ rescue StandardError => e
112
+ if is_type_error?(e)
113
+ ur.target_type_error = true
114
+ problems << type_error_message(target[0])
115
+ still_unresolved_refs << ur
116
+ else
117
+ raise
118
+ end
119
+ end
28
120
  end
29
- elsif target
121
+ elsif target.size > 1
30
122
  problems << "identifier #{ur.proxy.targetIdentifier} not uniq"
31
- stillUnresolvedReferences << ur
123
+ still_unresolved_refs << ur
32
124
  else
33
125
  problems << "identifier #{ur.proxy.targetIdentifier} not found"
34
- stillUnresolvedReferences << ur
126
+ still_unresolved_refs << ur
35
127
  end
36
128
  end
37
- stillUnresolvedReferences
129
+ still_unresolved_refs
38
130
  end
39
131
 
132
+ private
133
+
134
+ def is_type_error?(e)
135
+ e.message =~ /Can not use a .* where a .* is expected/
136
+ end
137
+
138
+ def type_error_message(target)
139
+ "invalid target type #{target.class}"
140
+ end
141
+
40
142
  end
41
143
 
42
144
  end