rgen 0.5.4 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +28 -0
- data/Rakefile +3 -4
- data/lib/ea_support/uml13_ea_metamodel.rb +3 -3
- data/lib/ea_support/uml13_ea_to_uml13.rb +33 -2
- data/lib/ea_support/uml13_to_uml13_ea.rb +7 -0
- data/lib/mmgen/mm_ext/ecore_mmgen_ext.rb +4 -4
- data/lib/mmgen/templates/metamodel_generator.tpl +143 -143
- data/lib/rgen/ecore/ecore.rb +11 -1
- data/lib/rgen/ecore/ecore_interface.rb +47 -0
- data/lib/rgen/ecore/ecore_to_ruby.rb +166 -0
- data/lib/rgen/ecore/{ecore_transformer.rb → ruby_to_ecore.rb} +11 -11
- data/lib/rgen/environment.rb +15 -2
- data/lib/rgen/fragment/dump_file_cache.rb +63 -0
- data/lib/rgen/fragment/fragmented_model.rb +139 -0
- data/lib/rgen/fragment/model_fragment.rb +268 -0
- data/lib/rgen/instantiator/abstract_xml_instantiator.rb +44 -72
- data/lib/rgen/instantiator/default_xml_instantiator.rb +2 -2
- data/lib/rgen/instantiator/ecore_xml_instantiator.rb +16 -1
- data/lib/rgen/instantiator/json_instantiator.rb +16 -2
- data/lib/rgen/instantiator/nodebased_xml_instantiator.rb +118 -138
- data/lib/rgen/instantiator/qualified_name_resolver.rb +5 -1
- data/lib/rgen/instantiator/reference_resolver.rb +126 -24
- data/lib/rgen/instantiator/xmi11_instantiator.rb +6 -2
- data/lib/rgen/metamodel_builder.rb +18 -6
- data/lib/rgen/metamodel_builder/builder_extensions.rb +431 -407
- data/lib/rgen/metamodel_builder/builder_runtime.rb +8 -8
- data/lib/rgen/metamodel_builder/constant_order_helper.rb +4 -4
- data/lib/rgen/metamodel_builder/data_types.rb +5 -1
- data/lib/rgen/metamodel_builder/intermediate/feature.rb +167 -0
- data/lib/rgen/metamodel_builder/module_extension.rb +2 -2
- data/lib/rgen/model_builder.rb +10 -5
- data/lib/rgen/model_builder/builder_context.rb +17 -1
- data/lib/rgen/serializer/opposite_reference_filter.rb +18 -0
- data/lib/rgen/serializer/qualified_name_provider.rb +45 -0
- data/lib/rgen/template_language/template_container.rb +3 -1
- data/lib/rgen/{auto_class_creator.rb → util/auto_class_creator.rb} +6 -1
- data/lib/rgen/util/cached_glob.rb +67 -0
- data/lib/rgen/util/file_cache_map.rb +104 -0
- data/lib/rgen/util/file_change_detector.rb +78 -0
- data/lib/rgen/{method_delegation.rb → util/method_delegation.rb} +18 -3
- data/lib/rgen/{model_comparator.rb → util/model_comparator.rb} +17 -5
- data/lib/rgen/{model_comparator_base.rb → util/model_comparator_base.rb} +6 -1
- data/lib/rgen/{model_dumper.rb → util/model_dumper.rb} +6 -1
- data/lib/rgen/{name_helper.rb → util/name_helper.rb} +6 -1
- data/lib/rgen/util/pattern_matcher.rb +329 -0
- data/lib/transformers/uml13_to_ecore.rb +103 -60
- data/test/ecore_self_test.rb +43 -42
- data/test/json_test.rb +15 -0
- data/test/metamodel_builder_test.rb +361 -206
- data/test/metamodel_from_ecore_test.rb +45 -0
- data/test/metamodel_order_test.rb +10 -4
- data/test/metamodel_roundtrip_test.rb +2 -2
- data/test/metamodel_roundtrip_test/TestModel_Regenerated.rb +1 -1
- data/test/metamodel_roundtrip_test/houseMetamodel_Regenerated.ecore +50 -50
- data/test/method_delegation_test.rb +9 -9
- data/test/model_builder/ecore_internal.rb +19 -9
- data/test/model_builder/serializer_test.rb +1 -1
- data/test/reference_resolver_test.rb +79 -12
- data/test/rgen_test.rb +2 -0
- data/test/template_language_test.rb +7 -0
- data/test/template_language_test/templates/callback_indent_test/a.tpl +12 -0
- data/test/template_language_test/templates/callback_indent_test/b.tpl +5 -0
- data/test/testmodel/ea_testmodel_regenerated.xml +588 -583
- data/test/transformer_test.rb +3 -3
- data/test/util/file_cache_map_test.rb +91 -0
- data/test/util/file_cache_map_test/testdir/fileA +1 -0
- data/test/util_test.rb +4 -0
- data/test/xml_instantiator_test.rb +139 -135
- metadata +49 -104
- data/lib/rgen/ecore/ecore_instantiator.rb +0 -31
- data/lib/rgen/metamodel_builder/metamodel_description.rb +0 -232
- data/redist/xmlscan/ChangeLog +0 -1301
- data/redist/xmlscan/README +0 -34
- data/redist/xmlscan/THANKS +0 -11
- data/redist/xmlscan/doc/changes.html +0 -74
- data/redist/xmlscan/doc/changes.rd +0 -80
- data/redist/xmlscan/doc/en/conformance.html +0 -136
- data/redist/xmlscan/doc/en/conformance.rd +0 -152
- data/redist/xmlscan/doc/en/manual.html +0 -356
- data/redist/xmlscan/doc/en/manual.rd +0 -402
- data/redist/xmlscan/doc/ja/conformance.ja.html +0 -118
- data/redist/xmlscan/doc/ja/conformance.ja.rd +0 -134
- data/redist/xmlscan/doc/ja/manual.ja.html +0 -325
- data/redist/xmlscan/doc/ja/manual.ja.rd +0 -370
- data/redist/xmlscan/doc/src/Makefile +0 -41
- data/redist/xmlscan/doc/src/conformance.rd.src +0 -256
- data/redist/xmlscan/doc/src/langsplit.rb +0 -110
- data/redist/xmlscan/doc/src/manual.rd.src +0 -614
- data/redist/xmlscan/install.rb +0 -41
- data/redist/xmlscan/lib/xmlscan/encoding.rb +0 -311
- data/redist/xmlscan/lib/xmlscan/htmlscan.rb +0 -289
- data/redist/xmlscan/lib/xmlscan/namespace.rb +0 -352
- data/redist/xmlscan/lib/xmlscan/parser.rb +0 -299
- data/redist/xmlscan/lib/xmlscan/scanner.rb +0 -1109
- data/redist/xmlscan/lib/xmlscan/version.rb +0 -22
- data/redist/xmlscan/lib/xmlscan/visitor.rb +0 -158
- data/redist/xmlscan/lib/xmlscan/xmlchar.rb +0 -441
- data/redist/xmlscan/memo/CONFORMANCE +0 -1249
- data/redist/xmlscan/memo/PRODUCTIONS +0 -195
- data/redist/xmlscan/memo/contentspec.ry +0 -335
- data/redist/xmlscan/samples/chibixml.rb +0 -105
- data/redist/xmlscan/samples/getxmlchar.rb +0 -122
- data/redist/xmlscan/samples/rexml.rb +0 -159
- data/redist/xmlscan/samples/xmlbench.rb +0 -88
- data/redist/xmlscan/samples/xmlbench/parser/chibixml.rb +0 -22
- data/redist/xmlscan/samples/xmlbench/parser/nqxml.rb +0 -29
- data/redist/xmlscan/samples/xmlbench/parser/rexml.rb +0 -62
- data/redist/xmlscan/samples/xmlbench/parser/xmlparser.rb +0 -22
- data/redist/xmlscan/samples/xmlbench/parser/xmlscan-0.0.10.rb +0 -62
- data/redist/xmlscan/samples/xmlbench/parser/xmlscan-chibixml.rb +0 -22
- data/redist/xmlscan/samples/xmlbench/parser/xmlscan-rexml.rb +0 -22
- data/redist/xmlscan/samples/xmlbench/parser/xmlscan.rb +0 -99
- data/redist/xmlscan/samples/xmlbench/xmlbench-lib.rb +0 -116
- data/redist/xmlscan/samples/xmlconftest.rb +0 -200
- data/redist/xmlscan/test.rb +0 -7
- data/redist/xmlscan/tests/deftestcase.rb +0 -73
- data/redist/xmlscan/tests/runtest.rb +0 -47
- data/redist/xmlscan/tests/testall.rb +0 -14
- data/redist/xmlscan/tests/testencoding.rb +0 -438
- data/redist/xmlscan/tests/testhtmlscan.rb +0 -752
- data/redist/xmlscan/tests/testnamespace.rb +0 -457
- data/redist/xmlscan/tests/testparser.rb +0 -591
- data/redist/xmlscan/tests/testscanner.rb +0 -1749
- data/redist/xmlscan/tests/testxmlchar.rb +0 -143
- 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
|
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
|
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
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
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
|
+
|
@@ -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
|
+
|