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