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