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> -&gt; 1024000</code>
293    *    <li>
294    *       <code><js>"100M"</js> -&gt; 104857600</code>
295    *    <li>
296    *       <code><js>"100k"</js> -&gt; 1000000</code>
297    *    <li>
298    *       <code><js>"100m"</js> -&gt; 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> -&gt; 1024000</code>
369    *    <li>
370    *       <code><js>"100M"</js> -&gt; 104857600</code>
371    *    <li>
372    *       <code><js>"100k"</js> -&gt; 1000000</code>
373    *    <li>
374    *       <code><js>"100m"</js> -&gt; 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}