File: resolve_context.rb

package info (click to toggle)
ruby-hocon 1.4.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 768 kB
  • sloc: ruby: 7,903; makefile: 4
file content (254 lines) | stat: -rw-r--r-- 8,433 bytes parent folder | download
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
# encoding: utf-8

require_relative '../../hocon'
require_relative '../../hocon/config_error'
require_relative '../../hocon/impl/resolve_source'
require_relative '../../hocon/impl/resolve_memos'
require_relative '../../hocon/impl/memo_key'
require_relative '../../hocon/impl/abstract_config_value'
require_relative '../../hocon/impl/config_impl'

class Hocon::Impl::ResolveContext

  ConfigBugOrBrokenError = Hocon::ConfigError::ConfigBugOrBrokenError
  NotPossibleToResolve = Hocon::Impl::AbstractConfigValue::NotPossibleToResolve

  attr_reader :restrict_to_child

  def initialize(memos, options, restrict_to_child, resolve_stack, cycle_markers)
    @memos = memos
    @options = options
    @restrict_to_child = restrict_to_child
    @resolve_stack = resolve_stack
    @cycle_markers = cycle_markers
  end

  def self.new_cycle_markers
    # This looks crazy, but wtf else should we do with
    # return Collections.newSetFromMap(new IdentityHashMap<AbstractConfigValue, Boolean>());
    Set.new
  end

  def add_cycle_marker(value)
    if Hocon::Impl::ConfigImpl.trace_substitution_enabled
      Hocon::Impl::ConfigImpl.trace("++ Cycle marker #{value}@#{value.hash}",
                       depth)
    end
    if @cycle_markers.include?(value)
      raise ConfigBugOrBrokenError.new("Added cycle marker twice " + value)
    end
    copy = self.class.new_cycle_markers
    copy.merge(@cycle_markers)
    copy.add(value)
    self.class.new(@memos, @options, @restrict_to_child, @resolve_stack, copy)
  end

  def remove_cycle_marker(value)
    if Hocon::Impl::ConfigImpl.trace_substitution_enabled
      Hocon::Impl::ConfigImpl.trace("-- Cycle marker #{value}@#{value.hash}",
                                    depth)
    end

    copy = self.class.new_cycle_markers
    copy.merge(@cycle_markers)
    copy.delete(value)
    self.class.new(@memos, @options, @restrict_to_child, @resolve_stack, copy)
  end

  def memoize(key, value)
    changed = @memos.put(key, value)
    self.class.new(changed, @options, @restrict_to_child, @resolve_stack, @cycle_markers)
  end

  def options
    @options
  end

  def is_restricted_to_child
    @restrict_to_child != nil
  end

  def restrict(restrict_to)
    if restrict_to.equal?(@restrict_to_child)
      self
    else
      Hocon::Impl::ResolveContext.new(@memos, @options, restrict_to, @resolve_stack, @cycle_markers)
    end
  end

  def unrestricted
    restrict(nil)
  end

  def resolve(original, source)
    if Hocon::Impl::ConfigImpl.trace_substitution_enabled
      Hocon::Impl::ConfigImpl.trace(
          "resolving #{original} restrict_to_child=#{@restrict_to_child} in #{source}",
          depth)
    end
    push_trace(original).real_resolve(original, source).pop_trace
  end

  def real_resolve(original, source)
    # a fully-resolved (no restrict_to_child) object can satisfy a
    # request for a restricted object, so always check that first.
    full_key = Hocon::Impl::MemoKey.new(original, nil)
    restricted_key = nil

    cached = @memos.get(full_key)

    # but if there was no fully-resolved object cached, we'll only
    # compute the restrictToChild object so use a more limited
    # memo key
    if cached == nil && is_restricted_to_child
      restricted_key = Hocon::Impl::MemoKey.new(original, @restrict_to_child)
      cached = @memos.get(restricted_key)
    end

    if cached != nil
      if Hocon::Impl::ConfigImpl.trace_substitution_enabled
        Hocon::Impl::ConfigImpl.trace(
            "using cached resolution #{cached} for #{original} restrict_to_child #{@restrict_to_child}",
            depth)
      end
      Hocon::Impl::ResolveResult.make(self, cached)
    else
      if Hocon::Impl::ConfigImpl.trace_substitution_enabled
        Hocon::Impl::ConfigImpl.trace(
            "not found in cache, resolving #{original}@#{original.hash}",
            depth)
      end

      if @cycle_markers.include?(original)
        if Hocon::Impl::ConfigImpl.trace_substitution_enabled
          Hocon::Impl::ConfigImpl.trace(
              "Cycle detected, can't resolve; #{original}@#{original.hash}",
              depth)
        end
        raise NotPossibleToResolve.new(self)
      end

      result = original.resolve_substitutions(self, source)
      resolved = result.value

      if Hocon::Impl::ConfigImpl.trace_substitution_enabled
        Hocon::Impl::ConfigImpl.trace(
            "resolved to #{resolved}@#{resolved.hash} from #{original}@#{resolved.hash}",
            depth)
      end

      with_memo = result.context

      if resolved == nil || resolved.resolve_status == Hocon::Impl::ResolveStatus::RESOLVED
        # if the resolved object is fully resolved by resolving
        # only the restrictToChildOrNull, then it can be cached
        # under fullKey since the child we were restricted to
        # turned out to be the only unresolved thing.
        if Hocon::Impl::ConfigImpl.trace_substitution_enabled
          Hocon::Impl::ConfigImpl.trace(
              "caching #{full_key} result #{resolved}",
              depth)
        end

        with_memo = with_memo.memoize(full_key, resolved)
      else
        # if we have an unresolved object then either we did a
        # partial resolve restricted to a certain child, or we are
        # allowing incomplete resolution, or it's a bug.
        if is_restricted_to_child
          if restricted_key == nil
            raise ConfigBugOrBrokenError.new("restricted_key should not be null here")
          end
          if Hocon::Impl::ConfigImpl.trace_substitution_enabled
            Hocon::Impl::ConfigImpl.trace(
                "caching #{restricted_key} result #{resolved}",
                depth)
          end

          with_memo = with_memo.memoize(restricted_key, resolved)
        elsif @options.allow_unresolved
          if Hocon::Impl::ConfigImpl.trace_substitution_enabled
            Hocon::Impl::ConfigImpl.trace(
                "caching #{full_key} result #{resolved}",
                depth)
          end

          with_memo = with_memo.memoize(full_key, resolved)
        else
          raise ConfigBugOrBrokenError.new(
                    "resolve_substitutions did not give us a resolved object")
        end
      end
      Hocon::Impl::ResolveResult.make(with_memo, resolved)
    end
  end

  # This method is a translation of the constructor in the Java version with signature
  # ResolveContext(ConfigResolveOptions options, Path restrictToChild)
  def self.construct(options, restrict_to_child)
    context = self.new(Hocon::Impl::ResolveMemos.new,
                       options,
                       restrict_to_child,
                       [],
                       new_cycle_markers)
    if Hocon::Impl::ConfigImpl.trace_substitution_enabled
      Hocon::Impl::ConfigImpl.trace(
          "ResolveContext restrict to child #{restrict_to_child}", context.depth)
    end
    context
  end

  def trace_string
    separator = ", "
    sb = ""
    @resolve_stack.each { |value|
      if value.instance_of?(Hocon::Impl::ConfigReference)
        sb << value.expression.to_s
        sb << separator
      end
    }
    if sb.length > 0
      sb.chomp!(separator)
    end
    sb
  end

  def depth
    if @resolve_stack.size > 30
      raise Hocon::ConfigError::ConfigBugOrBrokenError.new("resolve getting too deep")
    end
    @resolve_stack.size
  end

  def self.resolve(value, root, options)
    source = Hocon::Impl::ResolveSource.new(root)
    context = construct(options, nil)
    begin
      context.resolve(value, source).value
    rescue NotPossibleToResolve => e
      # ConfigReference was supposed to catch NotPossibleToResolve
      raise ConfigBugOrBrokenError(
                "NotPossibleToResolve was thrown from an outermost resolve", e)
    end
  end

  def pop_trace
    copy = @resolve_stack.clone
    old = copy.delete_at(@resolve_stack.size - 1)
    if Hocon::Impl::ConfigImpl.trace_substitution_enabled
      Hocon::Impl::ConfigImpl.trace("popped trace #{old}", depth - 1)
    end
    Hocon::Impl::ResolveContext.new(@memos, @options, @restrict_to_child, copy, @cycle_markers)
  end

  private

  def push_trace(value)
    if Hocon::Impl::ConfigImpl.trace_substitution_enabled
      Hocon::Impl::ConfigImpl.trace("pushing trace #{value}", depth)
    end
    copy = @resolve_stack.clone
    copy << value
    Hocon::Impl::ResolveContext.new(@memos, @options, @restrict_to_child, copy, @cycle_markers)
  end
end