001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.marshall.hocon; 018 019import static org.apache.juneau.commons.utils.Shorts.*; 020import static org.apache.juneau.commons.utils.StringUtils.*; 021 022import java.util.*; 023 024/** 025 * Intermediate representation for parsed HOCON data before conversion to Java beans. 026 * 027 * <p> 028 * HOCON parsing is multi-pass: tokenize → build tree (with path expressions and object merging) → resolve substitutions → convert to bean. 029 * This tree allows handling of path expressions ({@code a.b.c = value}), object merging (duplicate keys merge), 030 * value concatenation, and substitution resolution. 031 * 032 * <h5 class='section'>See Also:</h5><ul> 033 * <li class='link'><a class="doclink" href="https://github.com/lightbend/config/blob/main/HOCON.md">HOCON Specification</a> 034 * </ul> 035 */ 036public abstract sealed class HoconValue { 037 038 /** Value type. */ 039 public enum Type { 040 /** String value. */ 041 STRING, 042 /** Numeric value. */ 043 NUMBER, 044 /** Boolean value. */ 045 BOOLEAN, 046 /** Null value. */ 047 NULL, 048 /** Object (map/bean). */ 049 OBJECT, 050 /** Array (list). */ 051 ARRAY, 052 /** Substitution reference (${var}). */ 053 SUBSTITUTION, 054 /** Optional substitution reference (${?var}). */ 055 OPT_SUBSTITUTION, 056 /** Concatenation of adjacent values. */ 057 CONCAT 058 } 059 060 /** 061 * Returns the value type. 062 * 063 * @return The type. 064 */ 065 public abstract Type getType(); 066 067 /** 068 * HOCON object (maps to Java bean or Map). 069 */ 070 public static final class HoconObject extends HoconValue { 071 private final LinkedHashMap<String,HoconValue> members = new LinkedHashMap<>(); 072 073 @Override 074 public Type getType() { 075 return Type.OBJECT; 076 } 077 078 /** 079 * Returns the members. 080 * 081 * @return The members map. 082 */ 083 @SuppressWarnings({ 084 "java:S1319" // LinkedHashMap return type intentional; HOCON preserves insertion order of object members 085 }) 086 public LinkedHashMap<String,HoconValue> getMembers() { 087 return members; 088 } 089 090 /** 091 * Merges another object into this one (HOCON object merging). 092 * 093 * @param other The other object to merge. 094 * <br>Must not be <jk>null</jk>. 095 */ 096 public void merge(HoconObject other) { 097 for (var e : other.members.entrySet()) { 098 var key = e.getKey(); 099 var existing = members.get(key); 100 var incoming = e.getValue(); 101 if (existing instanceof HoconObject existing2 && incoming instanceof HoconObject incoming2) 102 existing2.merge(incoming2); 103 else 104 members.put(key, incoming); 105 } 106 } 107 108 /** 109 * Sets a value at a dotted path (e.g., a.b.c). 110 * 111 * @param path Path components. 112 * <br>Can be <jk>null</jk> or empty (no-op). 113 * @param value The value. 114 */ 115 public void setPath(String[] path, HoconValue value) { 116 if (path == null || path.length == 0) 117 return; 118 if (path.length == 1) { 119 members.put(path[0], value); 120 return; 121 } 122 var first = path[0]; 123 var existing = members.get(first); 124 HoconObject sub; 125 if (existing instanceof HoconObject existing2) { 126 sub = existing2; 127 } else { 128 sub = new HoconObject(); 129 members.put(first, sub); 130 } 131 sub.setPath(Arrays.copyOfRange(path, 1, path.length), value); 132 } 133 134 /** 135 * Gets a value at a dotted path. 136 * 137 * @param path Path components. 138 * <br>Can be <jk>null</jk> or empty (returns <jk>null</jk>). 139 * @return The value, or <jk>null</jk> if the path is <jk>null</jk>, empty, or does not resolve to an existing 140 * value. 141 */ 142 public HoconValue getPath(String[] path) { 143 if (path == null || path.length == 0) 144 return null; 145 var val = members.get(path[0]); 146 if (path.length == 1) 147 return val; 148 if (val instanceof HoconObject val2) 149 return val2.getPath(Arrays.copyOfRange(path, 1, path.length)); 150 return null; 151 } 152 } 153 154 /** 155 * HOCON array (maps to List or array). 156 */ 157 public static final class HoconArray extends HoconValue { 158 private final List<HoconValue> elements = l(); 159 160 @Override 161 public Type getType() { 162 return Type.ARRAY; 163 } 164 165 /** 166 * Returns the elements. 167 * 168 * @return An unmodifiable copy of the elements list. 169 */ 170 public List<HoconValue> getElements() { 171 return u(cp(elements)); 172 } 173 174 /** 175 * Adds an element to this array. 176 * 177 * @param element The element to add. 178 */ 179 public void add(HoconValue element) { 180 elements.add(element); 181 } 182 183 /** 184 * Replaces the contents of this array with the specified elements. 185 * 186 * @param value The new elements. Can be <jk>null</jk> to clear the array. 187 */ 188 public void setElements(List<HoconValue> value) { 189 elements.clear(); 190 if (value != null) 191 elements.addAll(value); 192 } 193 194 /** 195 * Concatenates another array (HOCON array concatenation). 196 * 197 * @param other The other array. 198 * <br>Must not be <jk>null</jk>. 199 */ 200 public void concat(HoconArray other) { 201 elements.addAll(other.elements); 202 } 203 } 204 205 /** 206 * HOCON string value. 207 */ 208 public static final class HoconString extends HoconValue { 209 private final String value; 210 211 /** 212 * Constructor. 213 * 214 * @param value The string value. 215 */ 216 public HoconString(String value) { 217 this.value = value; 218 } 219 220 @Override 221 public Type getType() { 222 return Type.STRING; 223 } 224 225 /** 226 * Returns the string value. 227 * 228 * @return The value. 229 */ 230 public String getValue() { 231 return value; 232 } 233 } 234 235 /** 236 * HOCON number value. 237 */ 238 public static final class HoconNumber extends HoconValue { 239 private final Number value; 240 241 /** 242 * Constructor. 243 * 244 * @param value The number value. 245 */ 246 public HoconNumber(Number value) { 247 this.value = value; 248 } 249 250 @Override 251 public Type getType() { 252 return Type.NUMBER; 253 } 254 255 /** 256 * Returns the number value. 257 * 258 * @return The value. 259 */ 260 public Number getValue() { 261 return value; 262 } 263 } 264 265 /** 266 * HOCON boolean value. 267 */ 268 public static final class HoconBoolean extends HoconValue { 269 private final boolean value; 270 271 /** 272 * Constructor. 273 * 274 * @param value The boolean value. 275 */ 276 public HoconBoolean(boolean value) { 277 this.value = value; 278 } 279 280 @Override 281 public Type getType() { 282 return Type.BOOLEAN; 283 } 284 285 /** 286 * Returns the boolean value. 287 * 288 * @return The value. 289 */ 290 public boolean getValue() { 291 return value; 292 } 293 } 294 295 /** 296 * HOCON null value. 297 */ 298 public static final class HoconNull extends HoconValue { 299 @Override 300 public Type getType() { 301 return Type.NULL; 302 } 303 } 304 305 /** 306 * HOCON substitution reference (${var} or ${?var}). 307 */ 308 public static final class HoconSubstitution extends HoconValue { 309 private final String path; 310 private final boolean optional; 311 312 /** 313 * Constructor. 314 * 315 * @param path The reference path. 316 * @param optional Whether optional (${?var}). 317 */ 318 public HoconSubstitution(String path, boolean optional) { 319 this.path = path; 320 this.optional = optional; 321 } 322 323 @Override 324 public Type getType() { 325 return optional ? Type.OPT_SUBSTITUTION : Type.SUBSTITUTION; 326 } 327 328 /** 329 * Returns the reference path. 330 * 331 * @return The path. 332 */ 333 public String getPath() { 334 return path; 335 } 336 337 /** 338 * Returns whether this is an optional substitution. 339 * 340 * @return <jk>true</jk> for ${?var}. 341 */ 342 public boolean isOptional() { 343 return optional; 344 } 345 } 346 347 /** 348 * HOCON value concatenation (adjacent values concatenated). 349 */ 350 public static final class HoconConcat extends HoconValue { 351 private final List<HoconValue> parts = l(); 352 353 @Override 354 public Type getType() { 355 return Type.CONCAT; 356 } 357 358 /** 359 * Returns the concatenated parts. 360 * 361 * @return The parts list. 362 */ 363 public List<HoconValue> getParts() { 364 return u(cp(parts)); 365 } 366 367 /** 368 * Adds a part. 369 * 370 * @param part The part to add. 371 */ 372 public void add(HoconValue part) { 373 parts.add(part); 374 } 375 376 /** 377 * Returns whether this concat contains a substitution for the given path. 378 * 379 * @param path The dotted path (e.g., "path" or "a.b.c"). 380 * <br>Can be <jk>null</jk> or empty (returns <jk>false</jk>). 381 * @return <jk>true</jk> if any part is a substitution for that path. 382 */ 383 public boolean referencesPath(String path) { 384 if (isEmpty(path)) 385 return false; 386 for (var p : parts) { 387 if (p instanceof HoconSubstitution p2 && path.equals(p2.getPath())) 388 return true; 389 } 390 return false; 391 } 392 } 393}