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