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