File: abstract_config_value.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 (383 lines) | stat: -rw-r--r-- 11,555 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
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
# encoding: utf-8

require_relative '../../hocon/impl'
require 'stringio'
require_relative '../../hocon/config_render_options'
require_relative '../../hocon/config_object'
require_relative '../../hocon/impl/resolve_status'
require_relative '../../hocon/impl/resolve_result'
require_relative '../../hocon/impl/unmergeable'
require_relative '../../hocon/impl/config_impl_util'
require_relative '../../hocon/config_error'
require_relative '../../hocon/config_value'

##
## Trying very hard to avoid a parent reference in config values; when you have
## a tree like this, the availability of parent() tends to result in a lot of
## improperly-factored and non-modular code. Please don't add parent().
##
module Hocon::Impl::AbstractConfigValue
  include Hocon::ConfigValue

  ConfigImplUtil = Hocon::Impl::ConfigImplUtil
  ConfigBugOrBrokenError = Hocon::ConfigError::ConfigBugOrBrokenError
  ResolveStatus = Hocon::Impl::ResolveStatus

  def initialize(origin)
    @origin = origin
  end

  attr_reader :origin

  # This exception means that a value is inherently not resolveable, at the
  # moment the only known cause is a cycle of substitutions. This is a
  # checked exception since it's internal to the library and we want to be
  # sure we handle it before passing it out to public API. This is only
  # supposed to be thrown by the target of a cyclic reference and it's
  # supposed to be caught by the ConfigReference looking up that reference,
  # so it should be impossible for an outermost resolve() to throw this.
  #
  # Contrast with ConfigException.NotResolved which just means nobody called
  # resolve().
  class NotPossibleToResolve < Exception
    def initialize(context)
      super("was not possible to resolve")
      @trace_string = context.trace_string
    end

    attr_reader :trace_string
  end

  # Called only by ResolveContext.resolve
  #
  # @param context
  #            state of the current resolve
  # @param source
  #            where to look up values
  # @return a new value if there were changes, or this if no changes
  def resolve_substitutions(context, source)
    Hocon::Impl::ResolveResult.make(context, self)
  end

  def resolve_status
    Hocon::Impl::ResolveStatus::RESOLVED
  end

  def self.replace_child_in_list(list, child, replacement)
    i = 0
    while (i < list.size) && (! list[i].equal?(child))
      i += 1
    end
    if (i == list.size)
      raise ConfigBugOrBrokenError, "tried to replace #{child} which is not in #{list}"
    end

    new_stack = list.clone
    if ! replacement.nil?
      new_stack[i] = replacement
    else
      new_stack.delete(i)
    end

    if new_stack.empty?
      nil
    else
      new_stack
    end
  end

  def self.has_descendant_in_list?(list, descendant)
    list.each do |v|
      if v.equal?(descendant)
        return true
      end
    end
    # now the expensive traversal
    list.each do |v|
      if v.is_a?(Hocon::Impl::Container) && v.has_descendant?(descendant)
        return true
      end
    end
    false
  end

  # This is used when including one file in another; the included file is
  # relativized to the path it's included into in the parent file. The point
  # is that if you include a file at foo.bar in the parent, and the included
  # file as a substitution ${a.b.c}, the included substitution now needs to
  # be ${foo.bar.a.b.c} because we resolve substitutions globally only after
  # parsing everything.
  #
  # @param prefix
  # @return value relativized to the given path or the same value if nothing
  #         to do
  def relativized(prefix)
    self
  end

  module NoExceptionsModifier
    def modify_child_may_throw(key_or_nil, v)
      begin
        modify_child(key_or_nil, v)
      rescue Hocon::ConfigError => e
        raise e
      end
    end
  end

  def to_fallback_value
    self
  end

  def new_copy(origin)
    raise ConfigBugOrBrokenError, "subclasses of AbstractConfigValue should provide their own implementation of `new_copy` (#{self.class})"
  end

  # this is virtualized rather than a field because only some subclasses
  # really need to store the boolean, and they may be able to pack it
  # with another boolean to save space.
  def ignores_fallbacks?
    # if we are not resolved, then somewhere in this value there's
    # a substitution that may need to look at the fallbacks.
    resolve_status == Hocon::Impl::ResolveStatus::RESOLVED
  end

  def with_fallbacks_ignored
    if ignores_fallbacks?
      self
    else
      raise ConfigBugOrBrokenError, "value class doesn't implement forced fallback-ignoring #{self}"
    end
  end

  # the withFallback() implementation is supposed to avoid calling
  # mergedWith* if we're ignoring fallbacks.
  def require_not_ignoring_fallbacks
    if ignores_fallbacks?
      raise ConfigBugOrBrokenError, "method should not have been called with ignoresFallbacks=true #{self.class.name}"
    end
  end

  def construct_delayed_merge(origin, stack)
    # TODO: this might not work because ConfigDelayedMerge inherits
    # from this class, so we can't `require` it from this file
    require_relative '../../hocon/impl/config_delayed_merge'
    Hocon::Impl::ConfigDelayedMerge.new(origin, stack)
  end

  def merged_stack_with_the_unmergeable(stack, fallback)
    require_not_ignoring_fallbacks

    # if we turn out to be an object, and the fallback also does,
    # then a merge may be required; delay until we resolve.
    new_stack = stack.clone
    new_stack.concat(fallback.unmerged_values)
    # TODO: this might not work because AbstractConfigObject inherits
    # from this class, so we can't `require` it from this file
    construct_delayed_merge(Hocon::Impl::AbstractConfigObject.merge_origins(new_stack), new_stack)
  end

  def delay_merge(stack, fallback)
    # if we turn out to be an object, and the fallback also does,
    # then a merge may be required.
    # if we contain a substitution, resolving it may need to look
    # back to the fallback
    new_stack = stack.clone
    new_stack << fallback
    # TODO: this might not work because AbstractConfigObject inherits
    # from this class, so we can't `require` it from this file
    construct_delayed_merge(Hocon::Impl::AbstractConfigObject.merge_origins(new_stack), new_stack)
  end

  def merged_stack_with_object(stack, fallback)
    require_not_ignoring_fallbacks

    # TODO: this might not work because AbstractConfigObject inherits
    # from this class, so we can't `require` it from this file
    if self.is_a?(Hocon::Impl::AbstractConfigObject)
      raise ConfigBugOrBrokenError, "Objects must reimplement merged_with_object"
    end

    merged_stack_with_non_object(stack, fallback)
  end

  def merged_stack_with_non_object(stack, fallback)
    require_not_ignoring_fallbacks

    if resolve_status == ResolveStatus::RESOLVED
      # falling back to a non-object doesn't merge anything, and also
      # prohibits merging any objects that we fall back to later.
      # so we have to switch to ignoresFallbacks mode.
      with_fallbacks_ignored
    else
      # if unresolved we may have to look back to fallbacks as part of
      # the resolution process, so always delay
      delay_merge(stack, fallback)
    end
  end

  def merged_with_the_unmergeable(fallback)
    require_not_ignoring_fallbacks
    merged_stack_with_the_unmergeable([self], fallback)
  end

  def merged_with_object(fallback)
    require_not_ignoring_fallbacks
    merged_stack_with_object([self], fallback)
  end

  def merged_with_non_object(fallback)
    require_not_ignoring_fallbacks
    merged_stack_with_non_object([self], fallback)
  end

  def with_origin(origin)
    if @origin.equal?(origin)
      self
    else
      new_copy(origin)
    end
  end

  def with_fallback(mergeable)
    if ignores_fallbacks?
      self
    else
      other = mergeable.to_fallback_value
      if other.is_a?(Hocon::Impl::Unmergeable)
        merged_with_the_unmergeable(other)
        # TODO: this probably isn't going to work because AbstractConfigObject inherits
        # from this class, so we can't `require` it from this file
      elsif other.is_a?(Hocon::Impl::AbstractConfigObject)
        merged_with_object(other)
      else
        merged_with_non_object(other)
      end
    end
  end

  def can_equal(other)
    other.is_a?(Hocon::Impl::AbstractConfigValue)
  end

  def ==(other)
    # note that "origin" is deliberately NOT part of equality
    if other.is_a?(Hocon::Impl::AbstractConfigValue)
      can_equal(other) &&
          value_type == other.value_type &&
          ConfigImplUtil.equals_handling_nil?(unwrapped, other.unwrapped)
    else
      false
    end
  end

  def hash
    # note that "origin" is deliberately NOT part of equality
    unwrapped_value = unwrapped
    if unwrapped_value.nil?
      0
    else
      unwrapped_value.hash
    end
  end

  def to_s
    sb = StringIO.new
    render_to_sb(sb, 0, true, nil, Hocon::ConfigRenderOptions.concise)
    "#{self.class.name.split('::').last}(#{sb.string})"
  end

  def inspect
    to_s
  end

  def self.indent(sb, indent_size, options)
    if options.formatted?
      remaining = indent_size
      while remaining > 0
        sb << "    "
        remaining -= 1
      end
    end
  end

  def render_to_sb(sb, indent, at_root, at_key, options)
    if !at_key.nil?
      rendered_key =
          if options.json?
            ConfigImplUtil.render_json_string(at_key)
          else
            ConfigImplUtil.render_string_unquoted_if_possible(at_key)
          end

      sb << rendered_key

      if options.json?
        if options.formatted?
          sb << ": "
        else
          sb << ":"
        end
      else
        case options.key_value_separator
          when :colon
            sb << ": "
          else
            sb << "="
        end      end
    end
    render_value_to_sb(sb, indent, at_root, options)
  end

  # to be overridden by subclasses
  def render_value_to_sb(sb, indent, at_root, options)
    u = unwrapped
    sb << u.to_s
  end

  def render(options = Hocon::ConfigRenderOptions.defaults)
    sb = StringIO.new
    render_to_sb(sb, 0, true, nil, options)
    # We take a substring that ends at sb.pos, because we've been decrementing
    # sb.pos at various points in the code as a means to remove characters from
    # the end of the StringIO
    sb.string[0, sb.pos]
  end

  # toString() is a debugging-oriented string but this is defined
  # to create a string that would parse back to the value in JSON.
  # It only works for primitive values (that would be a single
  # which are auto-converted to strings when concatenating with
  # other strings or by the DefaultTransformer.
  def transform_to_string
    nil
  end

  def at_key(key)
    at_key_with_origin(Hocon::Impl::SimpleConfigOrigin.new_simple("at_key(#{key})"), key)
  end

  # Renamed this to be consistent with the other at_key* overloaded methods
  def at_key_with_origin(origin, key)
    m = {key=>self}
    Hocon::Impl::SimpleConfigObject.new(origin, m).to_config
  end

  # In java this is an overloaded version of atPath
  def at_path_with_origin(origin, path)
    parent = path.parent
    result = at_key_with_origin(origin, path.last)
    while not parent.nil? do
      key = parent.last
      result = result.at_key_with_origin(origin, key)
      parent = parent.parent
    end
    result
  end

  def at_path(path_expression)
    origin = Hocon::Impl::SimpleConfigOrigin.new_simple("at_path(#{path_expression})")
    at_path_with_origin(origin, Hocon::Impl::Path.new_path(path_expression))
  end

end