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> -&gt; 1024000</code>
296    *    <li>
297    *       <code><js>"100M"</js> -&gt; 104857600</code>
298    *    <li>
299    *       <code><js>"100k"</js> -&gt; 1000000</code>
300    *    <li>
301    *       <code><js>"100m"</js> -&gt; 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> -&gt; 1024000</code>
343    *    <li>
344    *       <code><js>"100M"</js> -&gt; 104857600</code>
345    *    <li>
346    *       <code><js>"100k"</js> -&gt; 1000000</code>
347    *    <li>
348    *       <code><js>"100m"</js> -&gt; 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}