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
|