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> -&gt; 1024000</code>
291    *    <li>
292    *       <code><js>"100M"</js> -&gt; 104857600</code>
293    *    <li>
294    *       <code><js>"100k"</js> -&gt; 1000000</code>
295    *    <li>
296    *       <code><js>"100m"</js> -&gt; 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> -&gt; 1024000</code>
365    *    <li>
366    *       <code><js>"100M"</js> -&gt; 104857600</code>
367    *    <li>
368    *       <code><js>"100k"</js> -&gt; 1000000</code>
369    *    <li>
370    *       <code><js>"100m"</js> -&gt; 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}