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