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 = (Map) node;
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 = (Map) node;
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 = (Map) node;
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 = (Map) node;
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 (T) config.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 = (Map) node;
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 ? (T) value : 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 (T) value;
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 ? (T) node : 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 = (Map) node;
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 = (Map) node;
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 ? (T) value : 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 key) throws 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 (T) config.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 = (Map) node;
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 (T) value;
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 key) throws 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 (T) value;
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 (T) node;
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 (T) value;
439 }
440 throw missingResource(type, key);
441 }
442
443 private static MissingResourceException missingResource(String classname, String key) throws 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(value) : null;
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 }
|