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> -&gt; 1024000</code>
302    *    <li>
303    *       <code><js>"100M"</js> -&gt; 104857600</code>
304    *    <li>
305    *       <code><js>"100k"</js> -&gt; 1000000</code>
306    *    <li>
307    *       <code><js>"100m"</js> -&gt; 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> -&gt; 1024000</code>
349    *    <li>
350    *       <code><js>"100M"</js> -&gt; 104857600</code>
351    *    <li>
352    *       <code><js>"100k"</js> -&gt; 1000000</code>
353    *    <li>
354    *       <code><js>"100m"</js> -&gt; 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}