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