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