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