File: reference_resolver.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 (156 lines) | stat: -rw-r--r-- 4,419 bytes parent folder | download | duplicates (11)
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
require 'rgen/array_extensions'

module RGen
  
module ModelBuilder

class ReferenceResolver
  ResolverJob = Struct.new(:receiver, :reference, :namespace, :string)
  
  class ResolverException < Exception
  end

  class ToplevelNamespace
    def initialize(ns)
      raise "Namespace must be an Enumerable" unless ns.is_a?(Enumerable)
      @ns = ns
    end
    def elements
      @ns
    end
  end
  
  def initialize
    @jobs = []
    @elementName = {}
  end

  def addJob(job)
    @jobs << job
  end
  
  def setElementName(element, name)
    @elementName[element] = name
  end

  def resolve(ns=[])
    @toplevelNamespace = ToplevelNamespace.new(ns)
    (@jobs || []).each_with_index do |job, i|
      target = resolveReference(job.namespace || @toplevelNamespace, job.string.split("."))
      raise ResolverException.new("Can not resolve reference #{job.string}") unless target
      if job.reference.many
        job.receiver.addGeneric(job.reference.name, target)
      else
        job.receiver.setGeneric(job.reference.name, target)
      end
    end
  end
  
  private
  
  # TODO: if a reference can not be fully resolved, but a prefix can be found,
  # the exception reported is that its first path element can not be found on
  # toplevel
  def resolveReference(namespace, nameParts)
    element = resolveReferenceDownwards(namespace, nameParts)
    if element.nil? && parentNamespace(namespace)
      element = resolveReference(parentNamespace(namespace), nameParts)
    end
    element
  end
  
  def resolveReferenceDownwards(namespace, nameParts)
    firstPart, *restParts = nameParts
    element = namespaceElementByName(namespace, firstPart)
    return nil unless element
    if restParts.size > 0
      resolveReferenceDownwards(element, restParts)
    else
      element
    end
  end
  
  def namespaceElementByName(namespace, name)
    @namespaceElementsByName ||= {}
    return @namespaceElementsByName[namespace][name] if @namespaceElementsByName[namespace]
    hash = {}
    namespaceElements(namespace).each do |e|
      raise ResolverException.new("Multiple elements named #{elementName(e)} found in #{nsToS(namespace)}") if hash[elementName(e)]
      hash[elementName(e)] = e if elementName(e)
    end
    @namespaceElementsByName[namespace] = hash
    hash[name]
  end
  
  def parentNamespace(namespace)
    if namespace.class.respond_to?(:ecore)
      parents = elementParents(namespace)
      raise ResolverException.new("Element #{nsToS(namespace)} has multiple parents") \
        if parents.size > 1
      parents.first || @toplevelNamespace
    else
      nil
    end
  end
  
  def namespaceElements(namespace)
    if namespace.is_a?(ToplevelNamespace)
      namespace.elements
    elsif namespace.class.respond_to?(:ecore)
      elementChildren(namespace)
    else
      raise ResolverException.new("Element #{nsToS(namespace)} can not be used as a namespace")
    end
  end
  
  def nsToS(namespace)
    if namespace.is_a?(ToplevelNamespace)
      "toplevel namespace"
    else
      result = namespace.class.name    
      result += ":\"#{elementName(namespace)}\"" if elementName(namespace)
      result
    end
  end
  
  def elementName(element)
    @elementName[element]
  end
  
  def elementChildren(element)
    @elementChildren ||= {}
    return @elementChildren[element] if @elementChildren[element]
    children = containmentRefs(element).collect do |r|
      element.getGeneric(r.name)
    end.flatten.compact
    @elementChildren[element] = children
  end
  
  def elementParents(element)
    @elementParents ||= {}
    return @elementParents[element] if @elementParents[element]
    parents = parentRefs(element).collect do |r|
      element.getGeneric(r.name)
    end.flatten.compact
    @elementParents[element] = parents
  end  
  
  def containmentRefs(element)
    @containmentRefs ||= {}
    @containmentRefs[element.class] ||= eAllReferences(element).select{|r| r.containment}
  end
  
  def parentRefs(element)
    @parentRefs ||= {}
    @parentRefs[element.class] ||= eAllReferences(element).select{|r| r.eOpposite && r.eOpposite.containment}
  end

  def eAllReferences(element)
    @eAllReferences ||= {}
    @eAllReferences[element.class] ||= element.class.ecore.eAllReferences
  end
end

end

end