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