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.config; 018 019import static org.apache.juneau.commons.utils.Shorts.*; 020import static org.apache.juneau.commons.utils.StringUtils.*; 021import static org.apache.juneau.marshall.BinaryFormat.*; 022 023import java.lang.reflect.*; 024import java.util.*; 025import java.util.function.*; 026 027import org.apache.juneau.commons.utils.*; 028import org.apache.juneau.config.internal.*; 029import org.apache.juneau.marshall.*; 030import org.apache.juneau.marshall.collections.*; 031import org.apache.juneau.marshall.json.*; 032import org.apache.juneau.marshall.parser.*; 033 034/** 035 * A single entry in a {@link Config} file. 036 */ 037public class Entry { 038 039 private static boolean isArray(Type t) { 040 if (! (t instanceof Class)) 041 return false; 042 var c = (Class<?>)t; 043 return (c.isArray()); 044 } 045 private static boolean isSimpleType(Type t) { 046 if (! (t instanceof Class)) 047 return false; 048 var c = (Class<?>)t; 049 return (c == String.class || c.isPrimitive() || c.isAssignableFrom(Number.class) || c == Boolean.class || c.isEnum()); 050 } 051 private final ConfigMapEntry configEntry; 052 053 private final Config config; 054 055 private final String value; 056 057 /** 058 * Constructor. 059 * 060 * @param config The config that this entry belongs to. 061 * @param configMap The map that this belongs to. 062 * @param sectionName The section name of this entry. 063 * @param entryName The name of this entry. 064 */ 065 protected Entry(Config config, ConfigMap configMap, String sectionName, String entryName) { 066 this.configEntry = configMap.getEntry(sectionName, entryName); 067 this.config = config; 068 this.value = configEntry == null ? null : config.removeMods(configEntry.getModifiers(), configEntry.getValue()); 069 } 070 071 /** 072 * Returns this entry converted to the specified type. 073 * 074 * @param <T> The type to convert the value to. 075 * @param type The type to convert the value to. 076 * @return This entry converted to the specified type. 077 */ 078 public <T> Optional<T> as(Class<T> type) { 079 return as((Type)type); 080 } 081 082 /** 083 * Returns this entry converted to the specified type. 084 * 085 * @param <T> The type to convert the value to. 086 * @param parser The parser to use to parse the entry value. 087 * @param type The type to convert the value to. 088 * @return This entry converted to the specified type, or {@link Optional#empty()} if the entry does not exist. 089 */ 090 public <T> Optional<T> as(Parser parser, Class<T> type) { 091 return as(parser, (Type)type); 092 } 093 094 /** 095 * Same as {@link #as(Type, Type...)} but specifies the parser to use to parse the entry. 096 * 097 * @param <T> The object type to create. 098 * @param parser 099 * The parser to use to parse the entry. 100 * @param type 101 * The object type to create. 102 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 103 * @param args 104 * The type arguments of the class if it's a collection or map. 105 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 106 * <br>Ignored if the main type is not a map or collection. 107 * @return The value, or {@link Optional#empty()} if the section or key does not exist. 108 */ 109 @SuppressWarnings({ 110 "java:S3776", // Cognitive complexity acceptable for this specific logic 111 "unchecked" // Type erasure requires unchecked casts 112 }) 113 public <T> Optional<T> as(Parser parser, Type type, Type...args) { 114 if (isNull()) 115 return oe(); 116 117 try { 118 var v = toString(); 119 if (type == String.class) 120 return (Optional<T>)asString(); 121 if (type == String[].class) 122 return (Optional<T>)asStringArray(); 123 if (type == byte[].class) 124 return (Optional<T>)asBytes(); 125 if (type == int.class || type == Integer.class) 126 return (Optional<T>)asInteger(); 127 if (type == long.class || type == Long.class) 128 return (Optional<T>)asLong(); 129 if (type == JsonMap.class) 130 return (Optional<T>)asMap(); 131 if (type == JsonList.class) 132 return (Optional<T>)asList(); 133 if (isEmpty()) 134 return oe(); 135 if (isSimpleType(type)) 136 return o((T)config.marshallingSession.convertToType(v, (Class<?>)type)); 137 138 if (parser instanceof JsonParser) { 139 var s1 = firstNonWhitespaceChar(v); 140 if (isArray(type) && s1 != '[') 141 v = '[' + v + ']'; 142 else if (s1 != '[' && s1 != '{' && neq(v, "null")) 143 v = '\'' + v + '\''; 144 } 145 return o(parser.read(v, type, args)); 146 } catch (ParseException e) { 147 throw brex(e, (Class<?>)null, "Value could not be parsed."); 148 } 149 } 150 151 /** 152 * Returns this entry converted to the specified value. 153 * 154 * <p> 155 * The type can be a simple type (e.g. beans, strings, numbers) or parameterized type (collections/maps). 156 * 157 * <h5 class='section'>Examples:</h5> 158 * <p class='bjava'> 159 * Config <jv>config</jv> = Config.<jsm>create</jsm>().name(<js>"MyConfig.cfg"</js>).build(); 160 * 161 * <jc>// Parse into a linked-list of strings.</jc> 162 * List <jv>list</jv> = <jv>config</jv>.get(<js>"MySection/myListOfStrings"</js>).to(LinkedList.<jk>class</jk>, String.<jk>class</jk>); 163 * 164 * <jc>// Parse into a linked-list of beans.</jc> 165 * List <jv>list</jv> = <jv>config</jv>.get(<js>"MySection/myListOfBeans"</js>).to(LinkedList.<jk>class</jk>, MyBean.<jk>class</jk>); 166 * 167 * <jc>// Parse into a linked-list of linked-lists of strings.</jc> 168 * List <jv>list</jv> = <jv>config</jv>.get(<js>"MySection/my2dListOfStrings"</js>).to(LinkedList.<jk>class</jk>, 169 * LinkedList.<jk>class</jk>, String.<jk>class</jk>); 170 * 171 * <jc>// Parse into a map of string keys/values.</jc> 172 * Map <jv>map</jv> = <jv>config</jv>.get(<js>"MySection/myMap"</js>).to(TreeMap.<jk>class</jk>, String.<jk>class</jk>, 173 * String.<jk>class</jk>); 174 * 175 * <jc>// Parse into a map containing string keys and values of lists containing beans.</jc> 176 * Map <jv>map</jv> = <jv>config</jv>.get(<js>"MySection/myMapOfListsOfBeans"</js>).to(TreeMap.<jk>class</jk>, String.<jk>class</jk>, 177 * List.<jk>class</jk>, MyBean.<jk>class</jk>); 178 * </p> 179 * 180 * <p> 181 * <c>Collection</c> classes are assumed to be followed by zero or one objects indicating the element type. 182 * 183 * <p> 184 * <c>Map</c> classes are assumed to be followed by zero or two meta objects indicating the key and value 185 * types. 186 * 187 * <p> 188 * The array can be arbitrarily long to indicate arbitrarily complex data structures. 189 * 190 * <h5 class='section'>Notes:</h5><ul> 191 * <li class='note'> 192 * Use the {@link #as(Class)} method instead if you don't need a parameterized map/collection. 193 * </ul> 194 * 195 * @param <T> The object type to create. 196 * @param type 197 * The object type to create. 198 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 199 * @param args 200 * The type arguments of the class if it's a collection or map. 201 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 202 * <br>Ignored if the main type is not a map or collection. 203 * @return The value, or {@link Optional#empty()} if the section or key does not exist. 204 */ 205 public <T> Optional<T> as(Type type, Type...args) { 206 return as(config.parser, type, args); 207 } 208 209 /** 210 * Returns this entry as a parsed boolean. 211 * 212 * <p> 213 * Uses {@link Boolean#parseBoolean(String)} to parse value. 214 * 215 * @return The value, or {@link Optional#empty()} if the value does not exist or the value is empty. 216 */ 217 public Optional<Boolean> asBoolean() { 218 return o(isEmpty() ? null : (Boolean)Boolean.parseBoolean(toString())); 219 } 220 221 /** 222 * Returns this entry as a byte array. 223 * 224 * <p> 225 * Byte arrays are stored as encoded strings, typically BASE64, but dependent on the {@link Config.Builder#binaryFormat(BinaryFormat)} setting. 226 * 227 * @return The value, or {@link Optional#empty()} if the section or key does not exist. 228 */ 229 public Optional<byte[]> asBytes() { 230 if (isNull()) 231 return oe(); 232 var s = toString(); 233 if (s.indexOf('\n') != -1) 234 s = s.replace("\n", ""); 235 try { 236 if (config.binaryFormat == HEX) 237 return o(fromHex(s)); 238 if (config.binaryFormat == SPACED_HEX) 239 return o(fromSpacedHex(s)); 240 return o(base64Decode(s)); 241 } catch (Exception e) { 242 throw brex(e, (Class<?>)null, "Value could not be converted to a byte array."); 243 } 244 } 245 246 /** 247 * Returns this entry as a double. 248 * 249 * <p> 250 * Uses {@link Double#valueOf(String)} underneath, so any of the following number formats are supported: 251 * <ul> 252 * <li><js>"0x..."</js> 253 * <li><js>"0X..."</js> 254 * <li><js>"#..."</js> 255 * <li><js>"0..."</js> 256 * </ul> 257 * 258 * @return The value, or {@link Optional#empty()} if the value does not exist or the value is empty. 259 */ 260 public Optional<Double> asDouble() { 261 return o(isEmpty() ? null : Double.valueOf(toString())); 262 } 263 264 /** 265 * Returns this entry as a float. 266 * 267 * <p> 268 * Uses {@link Float#valueOf(String)} underneath, so any of the following number formats are supported: 269 * <ul> 270 * <li><js>"0x..."</js> 271 * <li><js>"0X..."</js> 272 * <li><js>"#..."</js> 273 * <li><js>"0..."</js> 274 * </ul> 275 * 276 * @return The value, or {@link Optional#empty()} if the value does not exist or the value is empty. 277 */ 278 public Optional<Float> asFloat() { 279 return o(isEmpty() ? null : Float.valueOf(toString())); 280 } 281 282 /** 283 * Returns this entry as an integer. 284 * 285 * <p> 286 * <js>"K"</js>, <js>"M"</js>, and <js>"G"</js> can be used to identify kilo, mega, and giga in base 2. 287 * <br><js>"k"</js>, <js>"m"</js>, and <js>"g"</js> can be used to identify kilo, mega, and giga in base 10. 288 * 289 * <h5 class='section'>Example:</h5> 290 * <ul class='spaced-list'> 291 * <li> 292 * <code><js>"100K"</js> -> 1024000</code> 293 * <li> 294 * <code><js>"100M"</js> -> 104857600</code> 295 * <li> 296 * <code><js>"100k"</js> -> 1000000</code> 297 * <li> 298 * <code><js>"100m"</js> -> 100000000</code> 299 * </ul> 300 * 301 * <p> 302 * Uses {@link Integer#decode(String)} underneath, so any of the following integer formats are supported: 303 * <ul> 304 * <li><js>"0x..."</js> 305 * <li><js>"0X..."</js> 306 * <li><js>"#..."</js> 307 * <li><js>"0..."</js> 308 * </ul> 309 * 310 * @return The value, or {@link Optional#empty()} if the value does not exist or the value is empty. 311 */ 312 public Optional<Integer> asInteger() { 313 return o(isEmpty() ? null : (Integer)parseIntWithSuffix(toString())); 314 } 315 316 /** 317 * Returns this entry as a parsed list. 318 * 319 * <p> 320 * Uses the parser registered on the {@link Config} to parse the entry. 321 * 322 * <p> 323 * If the parser is a JSON parser, the starting/trailing <js>"["</js>/<js>"]"</js> in the value are optional. 324 * 325 * @return The value, or {@link Optional#empty()} if the section or key does not exist. 326 * @throws ParseException If value could not be parsed. 327 */ 328 public Optional<JsonList> asList() throws ParseException { 329 return asList(config.parser); 330 } 331 332 /** 333 * Returns this entry as a parsed list. 334 * 335 * <p> 336 * If the parser is a JSON parser, the starting/trailing <js>"["</js>/<js>"]"</js> in the value are optional. 337 * 338 * @param parser 339 * The parser to use to parse the value. 340 * <br>Can be <jk>null</jk> to use the parser defined on the config. 341 * @return The value, or {@link Optional#empty()} if the section or key does not exist. 342 * @throws ParseException If value could not be parsed. 343 */ 344 public Optional<JsonList> asList(Parser parser) throws ParseException { 345 if (isNull()) 346 return oe(); 347 if (parser == null) 348 parser = config.parser; 349 var s = toString(); 350 if (parser instanceof JsonParser) { 351 var s1 = firstNonWhitespaceChar(s); 352 if (s1 != '[' && neq(s, "null")) 353 s = '[' + s + ']'; 354 } 355 return o(JsonList.ofString(s, parser)); 356 } 357 358 /** 359 * Returns this entry as a long. 360 * 361 * <p> 362 * <js>"K"</js>, <js>"M"</js>, <js>"G"</js>, <js>"T"</js>, and <js>"P"</js> can be used to identify kilo, mega, giga, tera, and penta in base 2. 363 * <br><js>"k"</js>, <js>"m"</js>, <js>"g"</js>, <js>"t"</js>, and <js>"p"</js> can be used to identify kilo, mega, giga, tera, and p in base 10. 364 * 365 * <h5 class='section'>Example:</h5> 366 * <ul class='spaced-list'> 367 * <li> 368 * <code><js>"100K"</js> -> 1024000</code> 369 * <li> 370 * <code><js>"100M"</js> -> 104857600</code> 371 * <li> 372 * <code><js>"100k"</js> -> 1000000</code> 373 * <li> 374 * <code><js>"100m"</js> -> 100000000</code> 375 * </ul> 376 * 377 * <p> 378 * Uses {@link Long#decode(String)} underneath, so any of the following integer formats are supported: 379 * <ul> 380 * <li><js>"0x..."</js> 381 * <li><js>"0X..."</js> 382 * <li><js>"#..."</js> 383 * <li><js>"0..."</js> 384 * </ul> 385 * 386 * @return The value, or {@link Optional#empty()} if the value does not exist or the value is empty. 387 */ 388 public Optional<Long> asLong() { 389 return o(isEmpty() ? null : (Long)parseLongWithSuffix(toString())); 390 } 391 392 /** 393 * Returns this entry as a parsed map. 394 * 395 * <p> 396 * Uses the parser registered on the {@link Config} to parse the entry. 397 * 398 * <p> 399 * If the parser is a JSON parser, the starting/trailing <js>"{"</js>/<js>"}"</js> in the value are optional. 400 * 401 * @return The value, or {@link Optional#empty()} if the section or key does not exist. 402 * @throws ParseException If value could not be parsed. 403 */ 404 public Optional<JsonMap> asMap() throws ParseException { 405 return asMap(config.parser); 406 } 407 408 /** 409 * Returns this entry as a parsed map. 410 * 411 * <p> 412 * If the parser is a JSON parser, the starting/trailing <js>"{"</js>/<js>"}"</js> in the value are optional. 413 * 414 * @param parser 415 * The parser to use to parse the value. 416 * <br>Can be <jk>null</jk> to use the parser defined on the config. 417 * @return The value, or an empty {@link Optional} if the section or key does not exist. 418 * @throws ParseException If value could not be parsed. 419 */ 420 public Optional<JsonMap> asMap(Parser parser) throws ParseException { 421 if (isNull()) 422 return oe(); 423 if (parser == null) 424 parser = config.parser; 425 var s = toString(); 426 if (parser instanceof JsonParser) { 427 var s1 = firstNonWhitespaceChar(s); 428 if (s1 != '{' && neq(s, "null")) 429 s = '{' + s + '}'; 430 } 431 return o(JsonMap.ofString(s, parser)); 432 } 433 434 /** 435 * Returns this entry as a string. 436 * 437 * @return This entry as a string, or {@link Optional#empty()} if the entry does not exist. 438 */ 439 public Optional<String> asString() { 440 return o(isPresent() ? config.varSession.resolve(value) : null); 441 } 442 443 /** 444 * Returns this entry as a string array. 445 * 446 * <p> 447 * If the value exists, splits the value on commas and returns the values as trimmed strings. 448 * 449 * @return This entry as a string array, or {@link Optional#empty()} if the entry does not exist. 450 */ 451 public Optional<String[]> asStringArray() { 452 if (! isPresent()) 453 return oe(); 454 var v = toString(); 455 var s1 = firstNonWhitespaceChar(v); 456 var s2 = lastNonWhitespaceChar(v); 457 if (s1 == '[' && s2 == ']' && config.parser instanceof JsonParser parser2) { 458 try { 459 return o(parser2.read(v, String[].class)); 460 } catch (ParseException e) { 461 throw brex(e); 462 } 463 } 464 return o(StringUtils.splita(v)); 465 } 466 467 /** 468 * Returns this entry as a string. 469 * 470 * @return This entry as a string. 471 * @throws NullPointerException if value was <jk>null</jk>. 472 */ 473 public String get() { 474 if (isNull()) 475 throw new NullPointerException("Value was null"); 476 return toString(); 477 } 478 479 /** 480 * Returns the same-line comment of this entry. 481 * 482 * @return The same-line comment of this entry, or <jk>null</jk> if this entry does not exist in the config. 483 */ 484 public String getComment() { return configEntry == null ? null : configEntry.getComment(); } 485 486 /** 487 * Returns the name of this entry. 488 * 489 * @return The name of this entry, or <jk>null</jk> if this entry does not exist in the config. 490 */ 491 public String getKey() { return configEntry == null ? null : configEntry.getKey(); } 492 493 /** 494 * Returns the modifiers for this entry. 495 * 496 * @return The modifiers for this entry, or <jk>null</jk> if it has no modifiers or this entry does not exist in the config. 497 */ 498 public String getModifiers() { return configEntry == null ? null : configEntry.getModifiers(); } 499 500 /** 501 * Returns the pre-lines of this entry. 502 * 503 * @return The pre-lines of this entry as an unmodifiable list, or <jk>null</jk> if this entry does not exist in the config. 504 */ 505 public List<String> getPreLines() { return configEntry == null ? null : configEntry.getPreLines(); } 506 507 /** 508 * Returns the raw value of this entry. 509 * 510 * @return The raw value of this entry, or <jk>null</jk> if this entry does not exist in the config. 511 */ 512 public String getValue() { return configEntry == null ? null : configEntry.getValue(); } 513 514 /** 515 * Returns <jk>true</jk> if this entry exists in the config and is not empty. 516 * 517 * @return <jk>true</jk> if this entry exists in the config and is not empty. 518 */ 519 public boolean isNotEmpty() { return ! isEmpty(); } 520 521 /** 522 * Returns <jk>true</jk> if this entry exists in the config. 523 * 524 * @return <jk>true</jk> if this entry exists in the config. 525 */ 526 public boolean isPresent() { return ! isNull(); } 527 528 /** 529 * Returns this entry converted to the specified type or returns the default value. 530 * 531 * <p> 532 * This is equivalent to calling <c>as(<jv>def</jv>.getClass()).orElse(<jv>def</jv>)</c> but is simpler and 533 * avoids the creation of an {@link Optional} object. 534 * 535 * @param def The default value to return if value does not exist. 536 * @return This entry converted to the specified type or returns the default value. 537 */ 538 public String orElse(String def) { 539 return isNull() ? def : get(); 540 } 541 542 /** 543 * Returns this entry converted to the specified type or returns the default value. 544 * 545 * <p> 546 * This is equivalent to calling <c>as(<jv>def</jv>.getClass()).orElse(<jv>def</jv>)</c> but is simpler and 547 * avoids the creation of an {@link Optional} object. 548 * 549 * @param def The default value to return if value does not exist. 550 * @return This entry converted to the specified type or returns the default value. 551 */ 552 public String orElseGet(Supplier<String> def) { 553 return isNull() ? def.get() : get(); 554 } 555 556 /** 557 * Returns this entry as a string. 558 * 559 * @return This entry as a string, or <jk>null</jk> if the entry does not exist. 560 */ 561 @Override 562 public String toString() { 563 return isPresent() ? config.varSession.resolve(value) : null; 564 } 565 566 private boolean isEmpty() { return Shorts.ie(value); } // NOAI 567 568 private boolean isNull() { return value == null; } 569}