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