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
@@ -0,0 +1,67 @@
1
+ module RGen
2
+
3
+ module Util
4
+
5
+ # WARNING: the mechanism of taking timestamps of directories in order to find out if the
6
+ # content has changed doesn't work reliably across all kinds of filesystems
7
+ #
8
+ class CachedGlob
9
+
10
+ def initialize(dir_glob, file_glob)
11
+ @dir_glob = dir_glob
12
+ @file_glob = file_glob
13
+ @root_dirs = []
14
+ @dirs = {}
15
+ @files = {}
16
+ @timestamps = {}
17
+ end
18
+
19
+ # returns all files contained in directories matched by +dir_glob+ which match +file_glob+.
20
+ # +file_glob+ must be relative to +dir_glob+.
21
+ # dir_glob "*/a" with file_glob "**/*.txt" is basically equivalent with Dir.glob("*/a/**/*.txt")
22
+ # the idea is that the file glob will only be re-eavluated when the content of one of the
23
+ # directories matched by dir_glob has changed.
24
+ # this will only be faster than a normal Dir.glob if the number of dirs matched by dir_glob is
25
+ # relatively large and changes in files affect only a few of them at a time.
26
+ def glob
27
+ root_dirs = Dir.glob(@dir_glob)
28
+ (@root_dirs - root_dirs).each do |d|
29
+ remove_root_dir(d)
30
+ end
31
+ (@root_dirs & root_dirs).each do |d|
32
+ update_root_dir(d) if dir_changed?(d)
33
+ end
34
+ (root_dirs - @root_dirs).each do |d|
35
+ update_root_dir(d)
36
+ end
37
+ @root_dirs = root_dirs
38
+ @root_dirs.sort.collect{|d| @files[d]}.flatten
39
+ end
40
+
41
+ private
42
+
43
+ def dir_changed?(dir)
44
+ @dirs[dir].any?{|d| File.mtime(d) != @timestamps[dir][d]}
45
+ end
46
+
47
+ def update_root_dir(dir)
48
+ @dirs[dir] = Dir.glob(dir+"/**/")
49
+ @files[dir] = Dir.glob(dir+"/"+@file_glob)
50
+ @timestamps[dir] = {}
51
+ @dirs[dir].each do |d|
52
+ @timestamps[dir][d] = File.mtime(d)
53
+ end
54
+ end
55
+
56
+ def remove_root_dir(dir)
57
+ @dirs.delete(dir)
58
+ @files.delete(dir)
59
+ @timestamps.delete(dir)
60
+ end
61
+
62
+ end
63
+
64
+ end
65
+
66
+ end
67
+
@@ -0,0 +1,104 @@
1
+ require 'digest'
2
+ require 'fileutils'
3
+
4
+ module RGen
5
+
6
+ module Util
7
+
8
+ # Implements a cache for storing and loading data associated with arbitrary files.
9
+ # The data is stored in cache files within a subfolder of the folder where
10
+ # the associated files exists.
11
+ # The cache files are protected with a checksum and loaded data will be
12
+ # invalid in case either the associated file are the cache file has changed.
13
+ #
14
+ class FileCacheMap
15
+ # optional program version info to be associated with the cache files
16
+ # if the program version changes, cached data will also be invalid
17
+ attr_accessor :version_info
18
+
19
+ # +cache_dir+ is the name of the subfolder where cache files are created
20
+ # +postfix+ is an extension appended to the original file name for
21
+ # creating the name of the cache file
22
+ def initialize(cache_dir, postfix)
23
+ @postfix = postfix
24
+ @cache_dir = cache_dir
25
+ end
26
+
27
+ # load data associated with file +key_path+
28
+ # returns :invalid in case either the associated file or the cache file has changed
29
+ def load_data(key_path)
30
+ cf = cache_file(key_path)
31
+ return :invalid unless File.exist?(cf)
32
+ result = nil
33
+ File.open(cf, "rb") do |f|
34
+ header = f.read(41)
35
+ return :invalid unless header
36
+ checksum = header[0..39]
37
+ data = f.read
38
+ if calc_sha1(data) == checksum
39
+ if calc_sha1_keydata(key_path) == data[0..39]
40
+ result = data[41..-1]
41
+ else
42
+ result = :invalid
43
+ end
44
+ else
45
+ result = :invalid
46
+ end
47
+ end
48
+ result
49
+ end
50
+
51
+ # store data +value_data+ associated with file +key_path+
52
+ def store_data(key_path, value_data)
53
+ data = calc_sha1_keydata(key_path) + "\n" + value_data
54
+ data = calc_sha1(data) + "\n" + data
55
+ cf = cache_file(key_path)
56
+ FileUtils.mkdir(File.dirname(cf)) rescue Errno::EEXIST
57
+ File.open(cf, "wb") do |f|
58
+ f.write(data)
59
+ end
60
+ end
61
+
62
+ # remove cache files which are not associated with any file in +key_paths+
63
+ # will only remove files within +root_path+
64
+ def clean_unused(root_path, key_paths)
65
+ raise "key paths must be within root path" unless key_paths.all?{|p| p.index(root_path) == 0}
66
+ used_files = key_paths.collect{|p| cache_file(p)}
67
+ files = Dir[root_path+"/**/"+@cache_dir+"/*"+@postfix]
68
+ files.each do |f|
69
+ FileUtils.rm(f) unless used_files.include?(f)
70
+ end
71
+ end
72
+
73
+ private
74
+
75
+ def cache_file(path)
76
+ File.dirname(path) + "/"+@cache_dir+"/" + File.basename(path) + @postfix
77
+ end
78
+
79
+ def calc_sha1(data)
80
+ sha1 = Digest::SHA1.new
81
+ sha1.update(data)
82
+ sha1.hexdigest
83
+ end
84
+
85
+ def keyData(path)
86
+ File.read(path)+@version_info.to_s
87
+ end
88
+
89
+ # this method is much faster than calling +keyData+ and putting the result in +calc_sha1+
90
+ # reason is probably that there are not so many big strings being created
91
+ def calc_sha1_keydata(path)
92
+ sha1 = Digest::SHA1.new
93
+ sha1.file(path)
94
+ sha1.update(@version_info.to_s)
95
+ sha1.hexdigest
96
+ end
97
+
98
+ end
99
+
100
+ end
101
+
102
+ end
103
+
104
+
@@ -0,0 +1,78 @@
1
+ require 'digest'
2
+
3
+ module RGen
4
+
5
+ module Util
6
+
7
+ # The FileChangeDetector detects changes in a set of files.
8
+ # Changes are detected between successive calls to check_files with a give set of files.
9
+ # Changes include files being added, removed or having changed their content.
10
+ #
11
+ class FileChangeDetector
12
+
13
+ FileInfo = Struct.new(:timestamp, :digest)
14
+
15
+ # Create a FileChangeDetector, options include:
16
+ #
17
+ # :file_added
18
+ # a proc which is called when a file is added, receives the filename
19
+ #
20
+ # :file_removed
21
+ # a proc which is called when a file is removed, receives the filename
22
+ #
23
+ # :file_changed
24
+ # a proc which is called when a file is changed, receives the filename
25
+ #
26
+ def initialize(options={})
27
+ @file_added = options[:file_added]
28
+ @file_removed = options[:file_removed]
29
+ @file_changed = options[:file_changed]
30
+ @file_info = {}
31
+ end
32
+
33
+ # Checks if any of the files has changed compared to the last call of check_files.
34
+ # When called for the first time on a new object, all files will be reported as being added.
35
+ #
36
+ def check_files(files)
37
+ files_before = @file_info.keys
38
+ used_files = {}
39
+ files.each do |file|
40
+ used_files[file] = true
41
+ if @file_info[file]
42
+ if @file_info[file].timestamp != File.mtime(file)
43
+ @file_info[file].timestamp = File.mtime(file)
44
+ digest = calc_digest(file)
45
+ if @file_info[file].digest != digest
46
+ @file_info[file].digest = digest
47
+ @file_changed && @file_changed.call(file)
48
+ end
49
+ end
50
+ else
51
+ @file_info[file] = FileInfo.new
52
+ @file_info[file].timestamp = File.mtime(file)
53
+ @file_info[file].digest = calc_digest(file)
54
+ @file_added && @file_added.call(file)
55
+ end
56
+ end
57
+ files_before.each do |file|
58
+ if !used_files[file]
59
+ @file_info.delete(file)
60
+ @file_removed && @file_removed.call(file)
61
+ end
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ def calc_digest(file)
68
+ sha1 = Digest::SHA1.new
69
+ sha1.file(file)
70
+ sha1.hexdigest
71
+ end
72
+
73
+ end
74
+
75
+ end
76
+
77
+ end
78
+
@@ -1,4 +1,6 @@
1
1
  module RGen
2
+
3
+ module Util
2
4
 
3
5
  module MethodDelegation
4
6
 
@@ -46,7 +48,7 @@ class << self
46
48
  end
47
49
 
48
50
  def createDelegatingMethod(object, method)
49
- if object.methods.include?(method.to_s)
51
+ if hasMethod(object, method)
50
52
  object.instance_eval <<-END
51
53
  class << self
52
54
  alias #{aliasMethodName(method)} #{method}
@@ -71,7 +73,7 @@ class << self
71
73
  end
72
74
 
73
75
  def removeDelegatingMethod(object, method)
74
- if object.methods.include?(aliasMethodName(method))
76
+ if hasMethod(object, aliasMethodName(method))
75
77
  # there is an aliased original, restore it
76
78
  object.instance_eval <<-END
77
79
  class << self
@@ -89,6 +91,16 @@ class << self
89
91
  end
90
92
  end
91
93
 
94
+ def hasMethod(object, method)
95
+ # in Ruby 1.9, #methods returns symbols
96
+ if object.methods.first.is_a?(Symbol)
97
+ method = method.to_sym
98
+ else
99
+ method = method.to_s
100
+ end
101
+ object.methods.include?(method)
102
+ end
103
+
92
104
  def aliasMethodName(method)
93
105
  "#{method}_delegate_original"
94
106
  end
@@ -96,4 +108,7 @@ end
96
108
 
97
109
  end
98
110
 
99
- end
111
+ end
112
+
113
+ end
114
+
@@ -2,6 +2,8 @@ require 'rgen/ecore/ecore'
2
2
 
3
3
  module RGen
4
4
 
5
+ module Util
6
+
5
7
  module ModelComparator
6
8
 
7
9
  # This method compares to models regarding equality
@@ -27,10 +29,18 @@ def _modelEqual_internal(a, b, featureIgnoreList, path)
27
29
  puts "#{path.inspect}\n Array size differs: #{a.size} vs. #{b.size}"
28
30
  return false
29
31
  end
30
- as = a
31
- as = a.sort if a.all?{|o| o.respond_to?('<=>')}
32
- bs = b
33
- bs = b.sort if b.all?{|o| o.respond_to?('<=>')}
32
+ begin
33
+ # in Ruby 1.9 every object has the <=> operator but the default one returns
34
+ # nil and thus sorting won't work (ArgumentError)
35
+ as = a.sort
36
+ rescue ArgumentError, NoMethodError
37
+ as = a
38
+ end
39
+ begin
40
+ bs = b.sort
41
+ rescue ArgumentError, NoMethodError
42
+ bs = b
43
+ end
34
44
  a.each_index do |i|
35
45
  return false unless _modelEqual_internal(as[i], bs[i], featureIgnoreList, path+[i])
36
46
  end
@@ -53,4 +63,6 @@ end
53
63
  end
54
64
 
55
65
  end
56
-
66
+
67
+ end
68
+
@@ -1,6 +1,8 @@
1
1
  require 'andand'
2
2
 
3
3
  module RGen
4
+
5
+ module Util
4
6
 
5
7
  class ModelComparatorBase
6
8
 
@@ -134,4 +136,7 @@ class ModelComparatorBase
134
136
 
135
137
  end
136
138
 
137
- end
139
+ end
140
+
141
+ end
142
+
@@ -1,5 +1,7 @@
1
1
  module RGen
2
2
 
3
+ module Util
4
+
3
5
  module ModelDumper
4
6
 
5
7
  def dump(obj=nil)
@@ -21,4 +23,7 @@ module ModelDumper
21
23
 
22
24
  end
23
25
 
24
- end
26
+ end
27
+
28
+ end
29
+
@@ -3,6 +3,8 @@
3
3
 
4
4
  module RGen
5
5
 
6
+ module Util
7
+
6
8
  module NameHelper
7
9
 
8
10
  def normalize(name)
@@ -34,4 +36,7 @@ module NameHelper
34
36
  end
35
37
  end
36
38
 
37
- end
39
+ end
40
+
41
+ end
42
+
@@ -0,0 +1,329 @@
1
+ module RGen
2
+
3
+ module Util
4
+
5
+ # A PatternMatcher can be used to find, insert and remove patterns on a given model.
6
+ #
7
+ # A pattern is specified by means of a block passed to the add_pattern method.
8
+ # The block must take an Environment as first parameter and at least one model element
9
+ # as connection point as further parameter. The pattern matches if it can be found
10
+ # in a given environment and connected to the given connection point elements.
11
+ #
12
+ class PatternMatcher
13
+
14
+ Match = Struct.new(:root, :elements, :bound_values)
15
+ attr_accessor :debug
16
+
17
+ def initialize
18
+ @patterns = {}
19
+ @insert_mode = false
20
+ @debug = false
21
+ end
22
+
23
+ def add_pattern(name, &block)
24
+ raise "a pattern needs at least 2 block parameters: " +
25
+ "an RGen environment and a model element as connection point" \
26
+ unless block.arity >= 2
27
+ @patterns[name] = block
28
+ end
29
+
30
+ def find_pattern(env, name, *connection_points)
31
+ match = find_pattern_internal(env, name, *connection_points)
32
+ end
33
+
34
+ def insert_pattern(env, name, *connection_points)
35
+ @insert_mode = true
36
+ root = evaluate_pattern(name, env, connection_points)
37
+ @insert_mode = false
38
+ root
39
+ end
40
+
41
+ def remove_pattern(env, name, *connection_points)
42
+ match = find_pattern_internal(env, name, *connection_points)
43
+ if match
44
+ match.elements.each do |e|
45
+ disconnect_element(e)
46
+ env.delete(e)
47
+ end
48
+ match
49
+ else
50
+ nil
51
+ end
52
+ end
53
+
54
+ def lazy(&block)
55
+ if @insert_mode
56
+ block.call
57
+ else
58
+ Lazy.new(&block)
59
+ end
60
+ end
61
+
62
+ class Lazy < RGen::MetamodelBuilder::MMGeneric
63
+ def initialize(&block)
64
+ @block = block
65
+ end
66
+ def _eval
67
+ @block.call
68
+ end
69
+ end
70
+
71
+ private
72
+
73
+ class Proxy < RGen::MetamodelBuilder::MMProxy
74
+ attr_reader :_target
75
+ def initialize(target)
76
+ @_target = target
77
+ end
78
+ def method_missing(m, *args)
79
+ result = @_target.send(m, *args)
80
+ if result.is_a?(Array)
81
+ result.collect do |e|
82
+ if e.is_a?(RGen::MetamodelBuilder::MMBase)
83
+ Proxy.new(e)
84
+ else
85
+ e
86
+ end
87
+ end
88
+ else
89
+ if result.is_a?(RGen::MetamodelBuilder::MMBase)
90
+ Proxy.new(result)
91
+ else
92
+ result
93
+ end
94
+ end
95
+ end
96
+ end
97
+
98
+ class Bindable < RGen::MetamodelBuilder::MMGeneric
99
+ # by being an Enumerable, Bindables can be used for many-features as well
100
+ include Enumerable
101
+ def initialize
102
+ @bound = false
103
+ @value = nil
104
+ @many = false
105
+ end
106
+ def _bound?
107
+ @bound
108
+ end
109
+ def _many?
110
+ @many
111
+ end
112
+ def _bind(value)
113
+ @value = value
114
+ @bound = true
115
+ end
116
+ def _value
117
+ @value
118
+ end
119
+ def to_s
120
+ @value.to_s
121
+ end
122
+ # pretend this is an enumerable which contains itself, so the bindable can be
123
+ # inserted into many-features, when this is done the bindable is marked as a many-bindable
124
+ def each
125
+ @many = true
126
+ yield(self)
127
+ end
128
+ def method_missing(m, *args)
129
+ raise "bindable not bound" unless _bound?
130
+ @value.send(m, *args)
131
+ end
132
+ end
133
+
134
+ TempEnv = RGen::Environment.new
135
+ class << TempEnv
136
+ def <<(el); end
137
+ end
138
+
139
+ def find_pattern_internal(env, name, *connection_points)
140
+ proxied_args = connection_points.collect{|a|
141
+ a.is_a?(RGen::MetamodelBuilder::MMBase) ? Proxy.new(a) : a }
142
+ bindables = create_bindables(name, connection_points)
143
+ pattern_root = evaluate_pattern(name, TempEnv, proxied_args+bindables)
144
+ candidates = candidates_via_connection_points(pattern_root, connection_points)
145
+ candidates ||= env.find(:class => pattern_root.class)
146
+ candidates.each do |e|
147
+ # create new bindables for every try, otherwise they can could be bound to old values
148
+ bindables = create_bindables(name, connection_points)
149
+ pattern_root = evaluate_pattern(name, TempEnv, proxied_args+bindables)
150
+ matched = match(pattern_root, e)
151
+ return Match.new(e, matched, bindables.collect{|b| b._value}) if matched
152
+ end
153
+ nil
154
+ end
155
+
156
+ def create_bindables(pattern_name, connection_points)
157
+ (1..(num_pattern_variables(pattern_name) - connection_points.size)).collect{|i| Bindable.new}
158
+ end
159
+
160
+ def candidates_via_connection_points(pattern_root, connection_points)
161
+ @candidates_via_connection_points_refs ||= {}
162
+ refs = (@candidates_via_connection_points_refs[pattern_root.class] ||=
163
+ pattern_root.class.ecore.eAllReferences.reject{|r| r.derived || r.many || !r.eOpposite})
164
+ candidates = nil
165
+ refs.each do |r|
166
+ t = pattern_root.getGeneric(r.name)
167
+ cp = t.is_a?(Proxy) && connection_points.find{|cp| cp.object_id == t._target.object_id}
168
+ if cp
169
+ elements = cp.getGenericAsArray(r.eOpposite.name)
170
+ candidates = elements if candidates.nil? || elements.size < candidates.size
171
+ end
172
+ end
173
+ candidates
174
+ end
175
+
176
+ def match(pat_element, test_element)
177
+ visited = {}
178
+ check_later = []
179
+ return false unless match_internal(pat_element, test_element, visited, check_later)
180
+ while cl = check_later.shift
181
+ pv, tv = cl.lazy._eval, cl.value
182
+ if cl.feature.is_a?(RGen::ECore::EAttribute)
183
+ unless pv == tv
184
+ match_failed(cl.feature, "wrong attribute value (lazy): #{pv} vs. #{tv}")
185
+ return false
186
+ end
187
+ else
188
+ if pv.is_a?(Proxy)
189
+ unless pv._target.object_id == tv.object_id
190
+ match_failed(f, "wrong target object")
191
+ return false
192
+ end
193
+ else
194
+ unless (pv.nil? && tv.nil?) || (!pv.nil? && !tv.nil? && match_internal(pv, tv, visited, check_later))
195
+ return false
196
+ end
197
+ end
198
+ end
199
+ end
200
+ visited.keys
201
+ end
202
+
203
+ CheckLater = Struct.new(:feature, :lazy, :value)
204
+ def match_internal(pat_element, test_element, visited, check_later)
205
+ return true if visited[test_element]
206
+ visited[test_element] = true
207
+ unless pat_element.class == test_element.class
208
+ match_failed(nil, "wrong class: #{pat_element.class} vs #{test_element.class}")
209
+ return false
210
+ end
211
+ all_structural_features(pat_element).each do |f|
212
+ pat_values = pat_element.getGeneric(f.name)
213
+ # nil values must be kept to support size check with Bindables
214
+ pat_values = [ pat_values ] unless pat_values.is_a?(Array)
215
+ test_values = test_element.getGeneric(f.name)
216
+ test_values = [ test_values] unless test_values.is_a?(Array)
217
+ if pat_values.size == 1 && pat_values.first.is_a?(Bindable) && pat_values.first._many?
218
+ unless match_many_bindable(pat_values.first, test_values)
219
+ return false
220
+ end
221
+ else
222
+ unless pat_values.size == test_values.size
223
+ match_failed(f, "wrong size #{pat_values.size} vs. #{test_values.size}")
224
+ return false
225
+ end
226
+ pat_values.each_with_index do |pv,i|
227
+ tv = test_values[i]
228
+ if pv.is_a?(Lazy)
229
+ check_later << CheckLater.new(f, pv, tv)
230
+ elsif pv.is_a?(Bindable)
231
+ if pv._bound?
232
+ unless pv._value == tv
233
+ match_failed(f, "value does not match bound value #{pv._value.class}:#{pv._value.object_id} vs. #{tv.class}:#{tv.object_id}")
234
+ return false
235
+ end
236
+ else
237
+ pv._bind(tv)
238
+ end
239
+ else
240
+ if f.is_a?(RGen::ECore::EAttribute)
241
+ unless pv == tv
242
+ match_failed(f, "wrong attribute value")
243
+ return false
244
+ end
245
+ else
246
+ if pv.is_a?(Proxy)
247
+ unless pv._target.object_id == tv.object_id
248
+ match_failed(f, "wrong target object")
249
+ return false
250
+ end
251
+ else
252
+ unless both_nil_or_match(pv, tv, visited, check_later)
253
+ return false
254
+ end
255
+ end
256
+ end
257
+ end
258
+ end
259
+ end
260
+ end
261
+ true
262
+ end
263
+
264
+ def match_many_bindable(bindable, test_values)
265
+ if bindable._bound?
266
+ bindable._value.each_with_index do |pv,i|
267
+ tv = test_values[i]
268
+ if f.is_a?(RGen::ECore::EAttribute)
269
+ unless pv == tv
270
+ match_failed(f, "wrong attribute value")
271
+ return false
272
+ end
273
+ else
274
+ unless both_nil_or_match(pv, tv, visited, check_later)
275
+ return false
276
+ end
277
+ end
278
+ end
279
+ else
280
+ bindable._bind(test_values.dup)
281
+ end
282
+ true
283
+ end
284
+
285
+ def both_nil_or_match(pv, tv, visited, check_later)
286
+ (pv.nil? && tv.nil?) || (!pv.nil? && !tv.nil? && match_internal(pv, tv, visited, check_later))
287
+ end
288
+
289
+ def match_failed(f, msg)
290
+ puts "match failed #{f&&f.eContainingClass.name}##{f&&f.name}: #{msg}" if @debug
291
+ end
292
+
293
+ def num_pattern_variables(name)
294
+ prok = @patterns[name]
295
+ prok.arity - 1
296
+ end
297
+
298
+ def evaluate_pattern(name, env, connection_points)
299
+ prok = @patterns[name]
300
+ raise "unknown pattern #{name}" unless prok
301
+ raise "wrong number of arguments, expected #{prok.arity-1} connection points)" \
302
+ unless connection_points.size == prok.arity-1
303
+ prok.call(env, *connection_points)
304
+ end
305
+
306
+ def disconnect_element(element)
307
+ return if element.nil?
308
+ all_structural_features(element).each do |f|
309
+ if f.many
310
+ element.setGeneric(f.name, [])
311
+ else
312
+ element.setGeneric(f.name, nil)
313
+ end
314
+ end
315
+ end
316
+
317
+ def all_structural_features(element)
318
+ @all_structural_features ||= {}
319
+ return @all_structural_features[element.class] if @all_structural_features[element.class]
320
+ @all_structural_features[element.class] =
321
+ element.class.ecore.eAllStructuralFeatures.reject{|f| f.derived}
322
+ end
323
+
324
+ end
325
+
326
+ end
327
+
328
+ end
329
+