ConfigUtils.java
001 /*
002  * Copyright 2008-2016 the original author or authors.
003  *
004  * Licensed under the Apache License, Version 2.0 (the "License");
005  * you may not use this file except in compliance with the License.
006  * You may obtain a copy of the License at
007  *
008  *     http://www.apache.org/licenses/LICENSE-2.0
009  *
010  * Unless required by applicable law or agreed to in writing, software
011  * distributed under the License is distributed on an "AS IS" BASIS,
012  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013  * See the License for the specific language governing permissions and
014  * limitations under the License.
015  */
016 package griffon.util;
017 
018 import javax.annotation.Nonnull;
019 import javax.annotation.Nullable;
020 import java.util.LinkedHashSet;
021 import java.util.Map;
022 import java.util.MissingResourceException;
023 import java.util.ResourceBundle;
024 import java.util.Set;
025 
026 import static griffon.util.GriffonNameUtils.requireNonBlank;
027 import static griffon.util.TypeUtils.castToBoolean;
028 import static griffon.util.TypeUtils.castToDouble;
029 import static griffon.util.TypeUtils.castToFloat;
030 import static griffon.util.TypeUtils.castToInt;
031 import static griffon.util.TypeUtils.castToLong;
032 import static griffon.util.TypeUtils.castToNumber;
033 import static java.util.Collections.unmodifiableSet;
034 import static java.util.Objects.requireNonNull;
035 
036 /**
037  * Utility class for reading configuration properties.
038  *
039  @author Andres Almiray
040  */
041 public final class ConfigUtils {
042     private static final String ERROR_CONFIG_NULL = "Argument 'config' must not be null";
043     private static final String ERROR_KEY_BLANK = "Argument 'key' must not be blank";
044 
045     private ConfigUtils() {
046         // prevent instantiation
047     }
048 
049     /**
050      * Returns true if there's a non-null value for the specified key.
051      *
052      @param config the configuration object to be searched upon
053      @param key    the key to be searched
054      @return true if there's a value for the specified key, false otherwise
055      @since 2.2.0
056      */
057     @SuppressWarnings("unchecked")
058     public static boolean containsKey(@Nonnull Map<String, Object> config, @Nonnull String key) {
059         requireNonNull(config, ERROR_CONFIG_NULL);
060         requireNonBlank(key, ERROR_KEY_BLANK);
061 
062         if (config.containsKey(key)) {
063             return true;
064         }
065 
066         String[] keys = key.split("\\.");
067         for (int i = 0; i < keys.length - 1; i++) {
068             if (config != null) {
069                 Object node = config.get(keys[i]);
070                 if (node instanceof Map) {
071                     config = (Map<String, Object>node;
072                 else {
073                     return false;
074                 }
075             else {
076                 return false;
077             }
078         }
079         return config != null && config.containsKey(keys[keys.length - 1]);
080     }
081 
082     /**
083      * Returns true if there's a non-null value for the specified key.
084      *
085      @param config the configuration object to be searched upon
086      @param key    the key to be searched
087      @return true if there's a value for the specified key, false otherwise
088      @since 2.2.0
089      */
090     @SuppressWarnings("unchecked")
091     public static boolean containsKey(@Nonnull ResourceBundle config, @Nonnull String key) {
092         requireNonNull(config, ERROR_CONFIG_NULL);
093         requireNonBlank(key, ERROR_KEY_BLANK);
094 
095         String[] keys = key.split("\\.");
096 
097         try {
098             if (config.containsKey(key)) {
099                 return true;
100             }
101         catch (MissingResourceException mre) {
102             // OK
103         }
104 
105         if (keys.length == 1) {
106             return config.containsKey(keys[0]);
107         }
108 
109         Object node = config.getObject(keys[0]);
110         if (!(node instanceof Map)) {
111             return false;
112         }
113 
114         Map<String, Object> map = (Mapnode;
115         for (int i = 1; i < keys.length - 1; i++) {
116             if (map != null) {
117                 node = map.get(keys[i]);
118                 if (node instanceof Map) {
119                     map = (Mapnode;
120                 else {
121                     return false;
122                 }
123             else {
124                 return false;
125             }
126         }
127         return map != null && map.containsKey(keys[keys.length - 1]);
128     }
129 
130     /**
131      * Returns true if there's a non-null value for the specified key.
132      *
133      @param config the configuration object to be searched upon
134      @param key    the key to be searched
135      @return true if there's a value for the specified key, false otherwise
136      */
137     @SuppressWarnings("unchecked")
138     public static boolean isValueDefined(@Nonnull Map<String, Object> config, @Nonnull String key) {
139         requireNonNull(config, ERROR_CONFIG_NULL);
140         requireNonBlank(key, ERROR_KEY_BLANK);
141 
142         if (config.containsKey(key)) {
143             return true;
144         }
145 
146         String[] keys = key.split("\\.");
147         for (int i = 0; i < keys.length - 1; i++) {
148             if (config != null) {
149                 Object node = config.get(keys[i]);
150                 if (node instanceof Map) {
151                     config = (Map<String, Object>node;
152                 else {
153                     return false;
154                 }
155             else {
156                 return false;
157             }
158         }
159         if (config == nullreturn false;
160         Object value = config.get(keys[keys.length - 1]);
161         return value != null;
162     }
163 
164     /**
165      * Returns true if there's a non-null value for the specified key.
166      *
167      @param config the configuration object to be searched upon
168      @param key    the key to be searched
169      @return true if there's a value for the specified key, false otherwise
170      */
171     @SuppressWarnings("unchecked")
172     public static boolean isValueDefined(@Nonnull ResourceBundle config, @Nonnull String key) {
173         requireNonNull(config, ERROR_CONFIG_NULL);
174         requireNonBlank(key, ERROR_KEY_BLANK);
175 
176         String[] keys = key.split("\\.");
177 
178         try {
179             Object value = config.getObject(key);
180             if (value != null) {
181                 return true;
182             }
183         catch (MissingResourceException mre) {
184             // OK
185         }
186 
187         if (keys.length == 1) {
188             try {
189                 Object node = config.getObject(keys[0]);
190                 return node != null;
191             catch (MissingResourceException mre) {
192                 return false;
193             }
194         }
195 
196         Object node = config.getObject(keys[0]);
197         if (!(node instanceof Map)) {
198             return false;
199         }
200 
201         Map<String, Object> map = (Mapnode;
202         for (int i = 1; i < keys.length - 1; i++) {
203             if (map != null) {
204                 node = map.get(keys[i]);
205                 if (node instanceof Map) {
206                     map = (Mapnode;
207                 else {
208                     return false;
209                 }
210             else {
211                 return false;
212             }
213         }
214         if (map == nullreturn false;
215         Object value = map.get(keys[keys.length - 1]);
216         return value != null;
217     }
218 
219     /**
220      * Returns the value for the specified key with an optional default value if no match is found.
221      *
222      @param config       the configuration object to be searched upon
223      @param key          the key to be searched
224      @param defaultValue the value to send back if no match is found
225      @return the value of the key or the default value if no match is found
226      */
227     @Nullable
228     @SuppressWarnings({"unchecked""ConstantConditions"})
229     public static <T> T getConfigValue(@Nonnull Map<String, Object> config, @Nonnull String key, @Nullable T defaultValue) {
230         requireNonNull(config, ERROR_CONFIG_NULL);
231         requireNonBlank(key, ERROR_KEY_BLANK);
232 
233         if (config.containsKey(key)) {
234             return (Tconfig.get(key);
235         }
236 
237         String[] keys = key.split("\\.");
238         for (int i = 0; i < keys.length - 1; i++) {
239             if (config != null) {
240                 Object node = config.get(keys[i]);
241                 if (node instanceof Map) {
242                     config = (Mapnode;
243                 else {
244                     return defaultValue;
245                 }
246             else {
247                 return defaultValue;
248             }
249         }
250         if (config == nullreturn defaultValue;
251         Object value = config.get(keys[keys.length - 1]);
252         return value != null (Tvalue : defaultValue;
253     }
254 
255     /**
256      * Returns the value for the specified key with an optional default value if no match is found.
257      *
258      @param config       the configuration object to be searched upon
259      @param key          the key to be searched
260      @param defaultValue the value to send back if no match is found
261      @return the value of the key or the default value if no match is found
262      */
263     @Nullable
264     @SuppressWarnings({"unchecked""ConstantConditions"})
265     public static <T> T getConfigValue(@Nonnull ResourceBundle config, @Nonnull String key, @Nullable T defaultValue) {
266         requireNonNull(config, ERROR_CONFIG_NULL);
267         requireNonBlank(key, ERROR_KEY_BLANK);
268 
269         String[] keys = key.split("\\.");
270 
271         try {
272             Object value = config.getObject(key);
273             if (value != null) {
274                 return (Tvalue;
275             }
276         catch (MissingResourceException mre) {
277             // OK
278         }
279 
280         if (keys.length == 1) {
281             Object node = config.getObject(keys[0]);
282             return node != null (Tnode : defaultValue;
283         }
284 
285         Object node = config.getObject(keys[0]);
286         if (!(node instanceof Map)) {
287             return defaultValue;
288         }
289 
290         Map<String, Object> map = (Mapnode;
291         for (int i = 1; i < keys.length - 1; i++) {
292             if (map != null) {
293                 node = map.get(keys[i]);
294                 if (node instanceof Map) {
295                     map = (Mapnode;
296                 else {
297                     return defaultValue;
298                 }
299             else {
300                 return defaultValue;
301             }
302         }
303         if (map == nullreturn defaultValue;
304         Object value = map.get(keys[keys.length - 1]);
305         return value != null (Tvalue : defaultValue;
306     }
307 
308     /**
309      * Returns the value for the specified key with an optional default value if no match is found.
310      *
311      @param config the configuration object to be searched upon
312      @param key    the key to be searched
313      @return the value of the key or the default value if no match is found
314      */
315     @Nullable
316     @SuppressWarnings({"unchecked""ConstantConditions"})
317     public static <T> T getConfigValue(@Nonnull Map<String, Object> config, @Nonnull String keythrows MissingResourceException {
318         requireNonNull(config, ERROR_CONFIG_NULL);
319         requireNonBlank(key, ERROR_KEY_BLANK);
320         String type = config.getClass().getName();
321 
322         if (config.containsKey(key)) {
323             return (Tconfig.get(key);
324         }
325 
326         String[] keys = key.split("\\.");
327         for (int i = 0; i < keys.length - 1; i++) {
328             if (config != null) {
329                 Object node = config.get(keys[i]);
330                 if (node instanceof Map) {
331                     config = (Mapnode;
332                 else {
333                     throw missingResource(type, key);
334                 }
335             else {
336                 throw missingResource(type, key);
337             }
338         }
339         if (config == null) {
340             throw missingResource(type, key);
341         }
342         Object value = config.get(keys[keys.length - 1]);
343         if (value != null) {
344             return (Tvalue;
345         }
346         throw missingResource(type, key);
347     }
348 
349     /**
350      * Returns the value for the specified key with an optional default value if no match is found.
351      *
352      @param config the configuration object to be searched upon
353      @param key    the key to be searched
354      @return the value of the key or the default value if no match is found
355      */
356     @Nullable
357     @SuppressWarnings({"unchecked""ConstantConditions"})
358     public static <T> T getConfigValue(@Nonnull ResourceBundle config, @Nonnull String keythrows MissingResourceException {
359         requireNonNull(config, ERROR_CONFIG_NULL);
360         requireNonBlank(key, ERROR_KEY_BLANK);
361         String type = config.getClass().getName();
362 
363         String[] keys = key.split("\\.");
364 
365         try {
366             Object value = config.getObject(key);
367             if (value != null) {
368                 return (Tvalue;
369             }
370         catch (MissingResourceException mre) {
371             // OK
372         }
373 
374         if (keys.length == 1) {
375             Object node = config.getObject(keys[0]);
376             if (node != null) {
377                 return (Tnode;
378             }
379             throw missingResource(type, key);
380         }
381 
382         Object node = config.getObject(keys[0]);
383         if (!(node instanceof Map)) {
384             throw missingResource(type, key);
385         }
386 
387         Map<String, Object> map = (Map<String, Object>node;
388         for (int i = 1; i < keys.length - 1; i++) {
389             if (map != null) {
390                 node = map.get(keys[i]);
391                 if (node instanceof Map) {
392                     map = (Map<String, Object>node;
393                 else {
394                     throw missingResource(type, key);
395                 }
396             else {
397                 throw missingResource(type, key);
398             }
399         }
400         if (map == null) {
401             throw missingResource(type, key);
402         }
403 
404         Object value = map.get(keys[keys.length - 1]);
405         if (value != null) {
406             return (Tvalue;
407         }
408         throw missingResource(type, key);
409     }
410 
411     private static MissingResourceException missingResource(String classname, String keythrows MissingResourceException {
412         return new MissingResourceException("Can't find resource for bundle " + classname + ", key " + key, classname, key);
413     }
414 
415     /**
416      * Returns the value for the specified key coerced to a boolean.
417      *
418      @param config the configuration object to be searched upon
419      @param key    the key to be searched
420      @return the value of the key. Returns {@code false} if no match.
421      */
422     public static boolean getConfigValueAsBoolean(@Nonnull Map<String, Object> config, @Nonnull String key) {
423         return getConfigValueAsBoolean(config, key, false);
424     }
425 
426     /**
427      * Returns the value for the specified key with an optional default value if no match is found.
428      *
429      @param config       the configuration object to be searched upon
430      @param key          the key to be searched
431      @param defaultValue the value to send back if no match is found
432      @return the value of the key or the default value if no match is found
433      */
434     public static boolean getConfigValueAsBoolean(@Nonnull Map<String, Object> config, @Nonnull String key, boolean defaultValue) {
435         Object value = getConfigValue(config, key, defaultValue);
436         return castToBoolean(value);
437     }
438 
439     /**
440      * Returns the value for the specified key coerced to an int.
441      *
442      @param config the configuration object to be searched upon
443      @param key    the key to be searched
444      @return the value of the key. Returns {@code 0} if no match.
445      */
446     public static int getConfigValueAsInt(@Nonnull Map<String, Object> config, @Nonnull String key) {
447         return getConfigValueAsInt(config, key, 0);
448     }
449 
450     /**
451      * Returns the value for the specified key with an optional default value if no match is found.
452      *
453      @param config       the configuration object to be searched upon
454      @param key          the key to be searched
455      @param defaultValue the value to send back if no match is found
456      @return the value of the key or the default value if no match is found
457      */
458     public static int getConfigValueAsInt(@Nonnull Map<String, Object> config, @Nonnull String key, int defaultValue) {
459         Object value = getConfigValue(config, key, defaultValue);
460         return castToInt(value);
461     }
462 
463     /**
464      * Returns the value for the specified key coerced to a long.
465      *
466      @param config the configuration object to be searched upon
467      @param key    the key to be searched
468      @return the value of the key. Returns {@code 0L} if no match.
469      */
470     public static long getConfigValueAsLong(@Nonnull Map<String, Object> config, @Nonnull String key) {
471         return getConfigValueAsLong(config, key, 0L);
472     }
473 
474     /**
475      * Returns the value for the specified key with an optional default value if no match is found.
476      *
477      @param config       the configuration object to be searched upon
478      @param key          the key to be searched
479      @param defaultValue the value to send back if no match is found
480      @return the value of the key or the default value if no match is found
481      */
482     public static long getConfigValueAsLong(@Nonnull Map<String, Object> config, @Nonnull String key, long defaultValue) {
483         Object value = getConfigValue(config, key, defaultValue);
484         return castToLong(value);
485     }
486 
487     /**
488      * Returns the value for the specified key coerced to a double.
489      *
490      @param config the configuration object to be searched upon
491      @param key    the key to be searched
492      @return the value of the key. Returns {@code 0d} if no match.
493      */
494     public static double getConfigValueAsDouble(@Nonnull Map<String, Object> config, @Nonnull String key) {
495         return getConfigValueAsDouble(config, key, 0d);
496     }
497 
498     /**
499      * Returns the value for the specified key with an optional default value if no match is found.
500      *
501      @param config       the configuration object to be searched upon
502      @param key          the key to be searched
503      @param defaultValue the value to send back if no match is found
504      @return the value of the key or the default value if no match is found
505      */
506     public static double getConfigValueAsDouble(@Nonnull Map<String, Object> config, @Nonnull String key, double defaultValue) {
507         Object value = getConfigValue(config, key, defaultValue);
508         return castToDouble(value);
509     }
510 
511     /**
512      * Returns the value for the specified key coerced to a float.
513      *
514      @param config the configuration object to be searched upon
515      @param key    the key to be searched
516      @return the value of the key. Returns {@code 0f} if no match.
517      */
518     public static float getConfigValueAsFloat(@Nonnull Map<String, Object> config, @Nonnull String key) {
519         return getConfigValueAsFloat(config, key, 0f);
520     }
521 
522     /**
523      * Returns the value for the specified key with an optional default value if no match is found.
524      *
525      @param config       the configuration object to be searched upon
526      @param key          the key to be searched
527      @param defaultValue the value to send back if no match is found
528      @return the value of the key or the default value if no match is found
529      */
530     public static float getConfigValueAsFloat(@Nonnull Map<String, Object> config, @Nonnull String key, float defaultValue) {
531         Object value = getConfigValue(config, key, defaultValue);
532         return castToFloat(value);
533     }
534 
535     /**
536      * Returns the value for the specified key coerced to a Number.
537      *
538      @param config the configuration object to be searched upon
539      @param key    the key to be searched
540      @return the value of the key. Returns {@code null} if no match.
541      */
542     @Nullable
543     public static Number getConfigValueAsNumber(@Nonnull Map<String, Object> config, @Nonnull String key) {
544         return getConfigValueAsNumber(config, key, null);
545     }
546 
547     /**
548      * Returns the value for the specified key with an optional default value if no match is found.
549      *
550      @param config       the configuration object to be searched upon
551      @param key          the key to be searched
552      @param defaultValue the value to send back if no match is found
553      @return the value of the key or the default value if no match is found
554      */
555     @Nullable
556     public static Number getConfigValueAsNumber(@Nonnull Map<String, Object> config, @Nonnull String key, @Nullable Number defaultValue) {
557         Object value = getConfigValue(config, key, defaultValue);
558         return castToNumber(value);
559     }
560 
561     /**
562      * Returns the value for the specified key converted to a String.
563      *
564      @param config the configuration object to be searched upon
565      @param key    the key to be searched
566      @return the value of the key. Returns {@code ""} if no match.
567      */
568     @Nullable
569     public static String getConfigValueAsString(@Nonnull Map<String, Object> config, @Nonnull String key) {
570         return getConfigValueAsString(config, key, "");
571     }
572 
573     /**
574      * Returns the value for the specified key with an optional default value if no match is found.
575      *
576      @param config       the configuration object to be searched upon
577      @param key          the key to be searched
578      @param defaultValue the value to send back if no match is found
579      @return the value of the key or the default value if no match is found
580      */
581     @Nullable
582     public static String getConfigValueAsString(@Nonnull Map<String, Object> config, @Nonnull String key, @Nullable String defaultValue) {
583         Object value = getConfigValue(config, key, defaultValue);
584         return value != null ? String.valueOf(valuenull;
585     }
586 
587     // the following taken from SpringFramework::org.springframework.util.StringUtils
588 
589     /**
590      * Extract the filename extension from the given path,
591      * e.g. "mypath/myfile.txt" -> "txt".
592      *
593      @param path the file path (may be <code>null</code>)
594      @return the extracted filename extension, or <code>null</code> if none
595      */
596     public static String getFilenameExtension(String path) {
597         if (path == null) {
598             return null;
599         }
600         int extIndex = path.lastIndexOf(".");
601         if (extIndex == -1) {
602             return null;
603         }
604         int folderIndex = path.lastIndexOf("/");
605         if (folderIndex > extIndex) {
606             return null;
607         }
608         return path.substring(extIndex + 1);
609     }
610 
611     /**
612      * Strip the filename extension from the given path,
613      * e.g. "mypath/myfile.txt" -> "mypath/myfile".
614      *
615      @param path the file path (may be <code>null</code>)
616      @return the path with stripped filename extension,
617      * or <code>null</code> if none
618      */
619     public static String stripFilenameExtension(String path) {
620         if (path == null) {
621             return null;
622         }
623         int extIndex = path.lastIndexOf(".");
624         if (extIndex == -1) {
625             return path;
626         }
627         int folderIndex = path.lastIndexOf("/");
628         if (folderIndex > extIndex) {
629             return path;
630         }
631         return path.substring(0, extIndex);
632     }
633 
634     @Nonnull
635     public static Set<String> collectKeys(@Nonnull Map<String, Object> map) {
636         requireNonNull(map, "Argument 'map' must not be null");
637 
638         Set<String> keys = new LinkedHashSet<>();
639         for (Map.Entry<String, Object> entry : map.entrySet()) {
640             String key = entry.getKey();
641             Object value = entry.getValue();
642             doCollectKeys(key, value, keys);
643         }
644 
645         return unmodifiableSet(keys);
646     }
647 
648     @SuppressWarnings("unchecked")
649     private static void doCollectKeys(String key, Object value, Set<String> keys) {
650         if (value instanceof Map) {
651             Map<String, Object> map = (Map<String, Object>value;
652             for (Map.Entry<String, Object> entry : map.entrySet()) {
653                 doCollectKeys(key + "." + entry.getKey(), entry.getValue(), keys);
654             }
655         else {
656             keys.add(key);
657         }
658     }
659 }