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
|
# encoding: utf-8
require_relative '../../hocon/impl'
require_relative '../../hocon/impl/abstract_config_value'
require_relative '../../hocon/impl/abstract_config_object'
require_relative '../../hocon/impl/simple_config_list'
require_relative '../../hocon/config_object'
require_relative '../../hocon/impl/unmergeable'
require_relative '../../hocon/impl/simple_config_origin'
require_relative '../../hocon/impl/config_string'
require_relative '../../hocon/impl/container'
class Hocon::Impl::ConfigConcatenation
include Hocon::Impl::Unmergeable
include Hocon::Impl::Container
include Hocon::Impl::AbstractConfigValue
SimpleConfigList = Hocon::Impl::SimpleConfigList
ConfigObject = Hocon::ConfigObject
ConfigString = Hocon::Impl::ConfigString
ResolveStatus = Hocon::Impl::ResolveStatus
Unmergeable = Hocon::Impl::Unmergeable
SimpleConfigOrigin = Hocon::Impl::SimpleConfigOrigin
ConfigBugOrBrokenError = Hocon::ConfigError::ConfigBugOrBrokenError
ConfigNotResolvedError = Hocon::ConfigError::ConfigNotResolvedError
ConfigWrongTypeError = Hocon::ConfigError::ConfigWrongTypeError
attr_reader :pieces
def initialize(origin, pieces)
super(origin)
@pieces = pieces
if pieces.size < 2
raise ConfigBugOrBrokenError, "Created concatenation with less than 2 items: #{self}"
end
had_unmergeable = false
pieces.each do |p|
if p.is_a?(Hocon::Impl::ConfigConcatenation)
raise ConfigBugOrBrokenError, "ConfigConcatenation should never be nested: #{self}"
end
if p.is_a?(Unmergeable)
had_unmergeable = true
end
end
unless had_unmergeable
raise ConfigBugOrBrokenError, "Created concatenation without an unmergeable in it: #{self}"
end
end
def value_type
raise not_resolved
end
def unwrapped
raise not_resolved
end
def new_copy(new_origin)
self.class.new(new_origin, @pieces)
end
def ignores_fallbacks?
# we can never ignore fallbacks because if a child ConfigReference
# is self-referential we have to look lower in the merge stack
# for its value.
false
end
def unmerged_values
[self]
end
#
# Add left and right, or their merger, to builder
#
def self.join(builder, orig_right)
left = builder[builder.size - 1]
right = orig_right
# check for an object which can be converted to a list
# (this will be an object with numeric keys, like foo.0, foo.1)
if (left.is_a?(ConfigObject)) && (right.is_a?(SimpleConfigList))
left = Hocon::Impl::DefaultTransformer.transform(left, Hocon::ConfigValueType::LIST)
elsif (left.is_a?(SimpleConfigList)) && (right.is_a?(ConfigObject))
right = Hocon::Impl::DefaultTransformer.transform(right, Hocon::ConfigValueType::LIST)
end
# Since this depends on the type of two instances, I couldn't think
# of much alternative to an instanceof chain. Visitors are sometimes
# used for multiple dispatch but seems like overkill.
joined = nil
if (left.is_a?(ConfigObject)) && (right.is_a?(ConfigObject))
joined = right.with_fallback(left)
elsif (left.is_a?(SimpleConfigList)) && (right.is_a?(SimpleConfigList))
joined = left.concatenate(right)
elsif (left.is_a?(SimpleConfigList) || left.is_a?(ConfigObject)) &&
is_ignored_whitespace(right)
joined = left
# it should be impossible that left is whitespace and right is a list or object
elsif (left.is_a?(Hocon::Impl::ConfigConcatenation)) ||
(right.is_a?(Hocon::Impl::ConfigConcatenation))
raise ConfigBugOrBrokenError, "unflattened ConfigConcatenation"
elsif (left.is_a?(Unmergeable)) || (right.is_a?(Unmergeable))
# leave joined=null, cannot join
else
# handle primitive type or primitive type mixed with object or list
s1 = left.transform_to_string
s2 = right.transform_to_string
if s1.nil? || s2.nil?
raise ConfigWrongTypeError.new(left.origin,
"Cannot concatenate object or list with a non-object-or-list, #{left} " +
"and #{right} are not compatible", nil)
else
joined_origin = SimpleConfigOrigin.merge_origins([left.origin, right.origin])
joined = Hocon::Impl::ConfigString::Quoted.new(joined_origin, s1 + s2)
end
end
if joined.nil?
builder.push(right)
else
builder.pop
builder.push(joined)
end
end
def self.consolidate(pieces)
if pieces.length < 2
pieces
else
flattened = []
pieces.each do |v|
if v.is_a?(Hocon::Impl::ConfigConcatenation)
flattened.concat(v.pieces)
else
flattened.push(v)
end
end
consolidated = []
flattened.each do |v|
if consolidated.empty?
consolidated.push(v)
else
join(consolidated, v)
end
end
consolidated
end
end
def self.concatenate(pieces)
consolidated = consolidate(pieces)
if consolidated.empty?
nil
elsif consolidated.length == 1
consolidated[0]
else
merged_origin = SimpleConfigOrigin.merge_value_origins(consolidated)
Hocon::Impl::ConfigConcatenation.new(merged_origin, consolidated)
end
end
def resolve_substitutions(context, source)
if Hocon::Impl::ConfigImpl.trace_substitution_enabled
indent = context.depth + 2
Hocon::Impl::ConfigImpl.trace("concatenation has #{@pieces.size} pieces",
indent - 1)
count = 0
@pieces.each { |v|
Hocon::Impl::ConfigImpl.trace("#{count}: #{v}", count)
count += 1
}
end
# Right now there's no reason to pushParent here because the
# content of ConfigConcatenation should not need to replaceChild,
# but if it did we'd have to do this.
source_with_parent = source
new_context = context
resolved = []
@pieces.each { |p|
# to concat into a string we have to do a full resolve,
# so unrestrict the context, then put restriction back afterward
restriction = new_context.restrict_to_child
result = new_context.unrestricted
.resolve(p, source_with_parent)
r = result.value
new_context = result.context.restrict(restriction)
if Hocon::Impl::ConfigImpl.trace_substitution_enabled
Hocon::Impl::ConfigImpl.trace("resolved concat piece to #{r}",
context.depth)
end
if r
resolved << r
end
# otherwise, it was optional ... omit
}
# now need to concat everything
joined = self.class.consolidate(resolved)
# if unresolved is allowed we can just become another
# ConfigConcatenation
if joined.size > 1 and context.options.allow_unresolved
Hocon::Impl::ResolveResult.make(new_context, Hocon::Impl::ConfigConcatenation.new(origin, joined))
elsif joined.empty?
# we had just a list of optional references using ${?}
Hocon::Impl::ResolveResult.make(new_context, nil)
elsif joined.size == 1
Hocon::Impl::ResolveResult.make(new_context, joined[0])
else
raise ConfigBugOrBrokenError.new(
"Bug in the library; resolved list was joined to too many values: #{joined}")
end
end
def resolve_status
ResolveStatus::UNRESOLVED
end
def replace_child(child, replacement)
new_pieces = replace_child_in_list(@pieces, child, replacement)
if new_pieces == nil
nil
else
self.class.new(origin, new_pieces)
end
end
def has_descendant?(descendant)
has_descendant_in_list?(@pieces, descendant)
end
# when you graft a substitution into another object,
# you have to prefix it with the location in that object
# where you grafted it; but save prefixLength so
# system property and env variable lookups don 't get
# broken.
def relativized(prefix)
new_pieces = []
@pieces.each { |p|
new_pieces << p.relativized(prefix)
}
self.class.new(origin, new_pieces)
end
def can_equal(other)
other.is_a? Hocon::Impl::ConfigConcatenation
end
def ==(other)
if other.is_a? Hocon::Impl::ConfigConcatenation
can_equal(other) && @pieces == other.pieces
else
false
end
end
def hash
# note that "origin" is deliberately NOT part of equality
@pieces.hash
end
def render_value_to_sb(sb, indent, at_root, options)
@pieces.each do |piece|
piece.render_value_to_sb(sb, indent, at_root, options)
end
end
private
def not_resolved
ConfigNotResolvedError.new("need to Config#resolve(), see the API docs for Config#resolve(); substitution not resolved: #{self}")
end
def self.is_ignored_whitespace(value)
return value.is_a?(ConfigString) && !value.was_quoted?
end
end
|