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 = (Map) node;
137 for (int i = 1; i < keys.length - 1; i++) {
138 node = map.get(keys[i]);
139 if (node instanceof Map) {
140 map = (Map) node;
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 = (Map) node;
216 for (int i = 1; i < keys.length - 1; i++) {
217 node = map.get(keys[i]);
218 if (node instanceof Map) {
219 map = (Map) node;
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 (T) config.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 = (Map) node;
251 } else {
252 return defaultValue;
253 }
254 }
255 Object value = config.get(keys[keys.length - 1]);
256 return value != null ? (T) value : 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 (T) value;
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 ? (T) node : 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 = (Map) node;
296 for (int i = 1; i < keys.length - 1; i++) {
297 node = map.get(keys[i]);
298 if (node instanceof Map) {
299 map = (Map) node;
300 } else {
301 return defaultValue;
302 }
303 }
304 Object value = map.get(keys[keys.length - 1]);
305 return value != null ? (T) value : 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 key) throws 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 (T) config.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 = (Map) node;
332 } else {
333 throw missingResource(type, key);
334 }
335 }
336 Object value = config.get(keys[keys.length - 1]);
337 if (value != null) {
338 return (T) value;
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 key) throws 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 (T) value;
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 (T) node;
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 (T) value;
395 }
396 throw missingResource(type, key);
397 }
398
399 private static MissingResourceException missingResource(String classname, String key) throws 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(value) : null;
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 }
|