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 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266
|
require 'set'
require 'rgen/ecore/ecore'
module RGen
module ECore
# ECoreToRuby can turn ECore models into their Ruby metamodel representations
class ECoreToRuby
def initialize
@modules = {}
@classifiers = {}
@features_added = {}
@reserved = Set.new(Object.methods).merge(Kernel.methods)
end
# Create a Ruby module representing +epackage+.
# This includes all nested modules/packages, classes and enums.
#
# If a parent module is provided with the "under" parameter,
# the new module will be nested under the parent module.
#
# If the parent module has a non-temporary name,
# (more precisely: a non-temporary classpath) i.e. if it is reachable
# via a path of constant names from the root, then the nested
# modules and classes will also have non-temporary names.
# In particular, this means that they will keep their names even
# if they are assigned to new constants.
#
# If no parent module is provided or the parent module has a
# temporary name by itself, then the nested modules and classes will
# also have temporary names. This means that their name will stay
# 'volatile' until they are assigned to constants reachable from
# the root and the Module#name method is called for the first time.
#
# While the second approach is more flexible, it can come with a major
# performance impact. The reason is that Ruby searches the space of
# all known non-temporary classes/modules every time the name
# of a class/module with a temporary name is queried.
#
def create_module(epackage, under=Module.new)
with_empty_constant_order_helper do
temp = under.to_s.start_with?("#")
mod = create_module_internal(epackage, under, temp)
epackage.eAllClassifiers.each do |c|
if c.is_a?(RGen::ECore::EClass)
create_class(c, temp)
elsif c.is_a?(RGen::ECore::EEnum)
create_enum(c)
end
end
load_classes_with_reserved_keywords(epackage)
mod
end
end
private
def load_classes_with_reserved_keywords(epackage)
epackage.eAllClassifiers.each do |eclass|
# we early load classes which have ruby reserved keywords
if eclass.is_a?(RGen::ECore::EClass)
reserved_used = eclass.eStructuralFeatures.any? { |f| @reserved.include?(f.name.to_sym) }
add_features(eclass) if reserved_used
end
end
end
def create_module_internal(epackage, under, temp)
return @modules[epackage] if @modules[epackage]
if temp
mod = Module.new do
extend RGen::MetamodelBuilder::ModuleExtension
end
under.const_set(epackage.name, mod)
else
under.module_eval <<-END
module #{epackage.name}
extend RGen::MetamodelBuilder::ModuleExtension
end
END
mod = under.const_get(epackage.name)
end
@modules[epackage] = mod
epackage.eSubpackages.each{|p| create_module_internal(p, mod, temp)}
mod._set_ecore_internal(epackage)
mod
end
def create_class(eclass, temp)
return @classifiers[eclass] if @classifiers[eclass]
mod = @modules[eclass.ePackage]
if temp
cls = Class.new(super_class(eclass, temp)) do
abstract if eclass.abstract
class << self
attr_accessor :_ecore_to_ruby
end
end
mod.const_set(eclass.name, cls)
else
mod.module_eval <<-END
class #{eclass.name} < #{super_class(eclass, temp)}
#{eclass.abstract ? 'abstract' : ''}
class << self
attr_accessor :_ecore_to_ruby
end
end
END
cls = mod.const_get(eclass.name)
end
class << eclass
attr_accessor :instanceClass
def instanceClassName
instanceClass.to_s
end
end
eclass.instanceClass = cls
cls::ClassModule.module_eval do
alias _method_missing method_missing
def method_missing(m, *args)
if self.class._ecore_to_ruby.add_features(self.class.ecore)
send(m, *args)
else
_method_missing(m, *args)
end
end
alias _respond_to respond_to?
def respond_to?(m, include_all=false)
self.class._ecore_to_ruby.add_features(self.class.ecore)
_respond_to(m)
end
end
@classifiers[eclass] = cls
cls._set_ecore_internal(eclass)
cls._ecore_to_ruby = self
cls
end
def create_enum(eenum)
return @classifiers[eenum] if @classifiers[eenum]
e = RGen::MetamodelBuilder::DataTypes::Enum.new(eenum.eLiterals.collect{|l| l.name.to_sym})
@classifiers[eenum] = e
@modules[eenum.ePackage].const_set(eenum.name, e)
e
end
class FeatureWrapper
def initialize(efeature, classifiers)
@efeature = efeature
@classifiers = classifiers
end
def value(prop)
return false if prop == :containment && @efeature.is_a?(RGen::ECore::EAttribute)
@efeature.send(prop)
end
def many?
@efeature.many
end
def reference?
@efeature.is_a?(RGen::ECore::EReference)
end
def opposite
@efeature.eOpposite
end
def impl_type
etype = @efeature.eType
if etype.is_a?(RGen::ECore::EClass) || etype.is_a?(RGen::ECore::EEnum)
@classifiers[etype]
else
ic = etype.instanceClass
if ic
ic
else
raise "unknown type: #{etype.name}"
end
end
end
end
def super_class(eclass, temp)
super_types = eclass.eSuperTypes
if temp
case super_types.size
when 0
RGen::MetamodelBuilder::MMBase
when 1
create_class(super_types.first, temp)
else
RGen::MetamodelBuilder::MMMultiple(*super_types.collect{|t| create_class(t, temp)})
end
else
case super_types.size
when 0
"RGen::MetamodelBuilder::MMBase"
when 1
create_class(super_types.first, temp).name
else
"RGen::MetamodelBuilder::MMMultiple(" +
super_types.collect{|t| create_class(t, temp).name}.join(",") + ")"
end
end
end
class EmptyConstantOrderHelper
def classCreated(c); end
def moduleCreated(m); end
def enumCreated(e); end
end
def with_empty_constant_order_helper
orig_coh = RGen::MetamodelBuilder::ConstantOrderHelper
RGen::MetamodelBuilder.instance_eval { remove_const(:ConstantOrderHelper) }
RGen::MetamodelBuilder.const_set(:ConstantOrderHelper, EmptyConstantOrderHelper.new)
begin
result = yield
ensure
RGen::MetamodelBuilder.instance_eval { remove_const(:ConstantOrderHelper) }
RGen::MetamodelBuilder.const_set(:ConstantOrderHelper, orig_coh)
end
result
end
public
def add_features(eclass)
return false if @features_added[eclass]
c = @classifiers[eclass]
eclass.eStructuralFeatures.each do |f|
w1 = FeatureWrapper.new(f, @classifiers)
w2 = FeatureWrapper.new(f.eOpposite, @classifiers) if f.is_a?(RGen::ECore::EReference) && f.eOpposite
c.module_eval do
if w1.many?
_build_many_methods(w1, w2)
else
_build_one_methods(w1, w2)
end
end
end
@features_added[eclass] = true
eclass.eSuperTypes.each do |t|
add_features(t)
end
true
end
end
end
end
|