content: package com.termux.shared.settings.properties;
  
  import android.content.Context;
  import android.widget.Toast;
  
  import androidx.annotation.NonNull;
  import androidx.annotation.Nullable;
  
  import com.google.common.collect.BiMap;
  import com.google.common.collect.ImmutableBiMap;
  import com.google.common.primitives.Primitives;
  import com.termux.shared.file.FileUtils;
  import com.termux.shared.file.filesystem.FileType;
  import com.termux.shared.logger.Logger;
  
  import java.io.File;
  import java.io.FileInputStream;
  import java.io.InputStreamReader;
  import java.nio.charset.StandardCharsets;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.Properties;
  import java.util.Set;
  
  /**
   * An implementation similar to android's {@link android.content.SharedPreferences} interface for
   * reading and writing to and from ".properties" files which also maintains an in-memory cache for
   * the key/value pairs when an instance object is used. Operations are done under
   * synchronization locks and should be thread safe.
   *
   * If {@link SharedProperties} instance object is used, then two types of in-memory cache maps are
   * maintained, one for the literal {@link String} values found in the file for the keys and an
   * additional one that stores (near) primitive {@link Object} values for internal use by the caller.
   *
   * The {@link SharedProperties} also provides static functions that can be used to read properties
   * from files or individual key values or even their internal values. An automatic mapping to a
   * boolean as internal value can also be done. An in-memory cache is not maintained, nor are locks used.
   *
   * This currently only has read support, write support can/will be added later if needed. Check android's
   * SharedPreferencesImpl class for reference implementation.
   *
   * https://cs.android.com/android/platform/superproject/+/android-11.0.0_r3:frameworks/base/core/java/android/app/SharedPreferencesImpl.java
   */
  public class SharedProperties {
  
      /**
       * The {@link Properties} object that maintains an in-memory cache of values loaded from the
       * {@link #mPropertiesFile} file. The key/value pairs are of any keys that are found in the file
       * against their literal values in the file.
       */
      private Properties mProperties;
  
      /**
       * The {@link HashMap<>} object that maintains an in-memory cache of internal values for the values
       * loaded from the {@link #mPropertiesFile} file. The key/value pairs are of any keys defined by
       * {@link #mPropertiesList} that are found in the file against their internal {@link Object} values
       * returned by the call to
       * {@link SharedPropertiesParser#getInternalPropertyValueFromValue(Context, String, String)} interface.
       */
      private Map<String, Object> mMap;
  
      private final Context mContext;
      private final File mPropertiesFile;
      private final Set<String> mPropertiesList;
      private final SharedPropertiesParser mSharedPropertiesParser;
  
      private final Object mLock = new Object();
  
      /** Defines the bidirectional map for boolean values and their internal values  */
      public static final ImmutableBiMap<String, Boolean> MAP_GENERIC_BOOLEAN =
          new ImmutableBiMap.Builder<String, Boolean>()
              .put("true", true)
              .put("false", false)
              .build();
  
      /** Defines the bidirectional map for inverted boolean values and their internal values  */
      public static final ImmutableBiMap<String, Boolean> MAP_GENERIC_INVERTED_BOOLEAN =
          new ImmutableBiMap.Builder<String, Boolean>()
              .put("true", false)
              .put("false", true)
              .build();
  
      private static final String LOG_TAG = "SharedProperties";
  
      /**
       * Constructor for the SharedProperties class.
       *
       * @param context The Context for operations.
       * @param propertiesFile The {@link File} object to load properties from.
       * @param propertiesList The {@link Set<String>} object that defined which properties to load.
       *                       If this is set to {@code null}, then all properties that exist in
       *                       {@code propertiesFile} will be read by {@link #loadPropertiesFromDisk()}
       * @param sharedPropertiesParser The implementation of the {@link SharedPropertiesParser} interface.
       */
      public SharedProperties(@NonNull Context context, @Nullable File propertiesFile, Set<String> propertiesList, @NonNull SharedPropertiesParser sharedPropertiesParser) {
          mContext = context.getApplicationContext();
          mPropertiesFile = propertiesFile;
          mPropertiesList = propertiesList;
          mSharedPropertiesParser = sharedPropertiesParser;
  
          mProperties = new Properties();
          mMap = new HashMap<>();
      }
  
      /**
       * Load the properties defined by {@link #mPropertiesList} or all properties if its {@code null}
       * from the {@link #mPropertiesFile} file to update the {@link #mProperties} and {@link #mMap}
       * in-memory cache.
       * Properties are not loading automatically when constructor is called and must be manually called.
       */
      public void loadPropertiesFromDisk() {
          synchronized (mLock) {
              // Get properties from mPropertiesFile
              Properties properties = getProperties(false);
  
              // We still need to load default values into mMap, so we assume no properties defined if
              // reading from mPropertiesFile failed
              if (properties == null)
                  properties = new Properties();
  
              HashMap<String, Object> map = new HashMap<>();
              Properties newProperties = new Properties();
  
              Set<String> propertiesList = mPropertiesList;
              if (propertiesList == null)
                  propertiesList = properties.stringPropertyNames();
  
              String value;
              Object internalValue;
              for (String key : propertiesList) {
                  value = properties.getProperty(key); // value will be null if key does not exist in propertiesFile
                  // Logger.logVerbose(LOG_TAG, key + " : " + value);
  
                  // Call the {@link SharedPropertiesParser#getInternalPropertyValueFromValue(Context,String,String)}
                  // interface method to get the internal value to store in the {@link #mMap}.
                  internalValue = mSharedPropertiesParser.getInternalPropertyValueFromValue(mContext, key, value);
  
                  // If the internal value was successfully added to map, then also add value to newProperties
                  // We only store values in-memory defined by propertiesList
                  if (putToMap(map, key, internalValue)) { // null internalValue will be put into map
                      putToProperties(newProperties, key, value); // null value will **not** be into properties
                  }
              }
  
              mMap = map;
              mProperties = newProperties;
          }
      }
  
      /**
       * Get the {@link Properties} object for the {@link #mPropertiesFile}. The {@link Properties}
       * object will also contain properties not defined by the {@link #mPropertiesList} if cache
       * value is {@code false}.
       *
       * @param cached If {@code true}, then the {@link #mProperties} in-memory cache is returned. Otherwise
       *               the {@link Properties} object is directly read from the {@link #mPropertiesFile}.
       * @return Returns the {@link Properties} object if read from file, otherwise a copy of {@link #mProperties}.
       */
      public Properties getProperties(boolean cached) {
          synchronized (mLock) {
              if (cached) {
                  if (mProperties == null) mProperties = new Properties();
                  return getPropertiesCopy(mProperties);
              } else {
                  return getPropertiesFromFile(mContext, mPropertiesFile, mSharedPropertiesParser);
              }
          }
      }
  
      /**
       * Get the {@link String} value for the key passed from the {@link #mPropertiesFile}.
       *
       * @param key The key to read from the {@link Properties} object.
       * @param cached If {@code true}, then the value is returned from the {@link #mProperties} in-memory cache.
       *               Otherwise the {@link Properties} object is read directly from the {@link #mPropertiesFile}
       *               and value is returned from it against the key.
       * @return Returns the {@link String} object. This will be {@code null} if key is not found.
       */
      public String getProperty(String key, boolean cached) {
          synchronized (mLock) {
              return (String) getProperties(cached).get(key);
          }
      }
  
      /**
       * Get the {@link #mMap} object for the {@link #mPropertiesFile}. A call to
       * {@link #loadPropertiesFromDisk()} must be made before this.
       *
       * @return Returns a copy of {@link #mMap} object.
       */
      public Map<String, Object> getInternalProperties() {
          synchronized (mLock) {
              if (mMap == null) mMap = new HashMap<>();
              return getMapCopy(mMap);
          }
      }
  
      /**
       * Get the internal {@link Object} value for the key passed from the {@link #mPropertiesFile}.
       * The value is returned from the {@link #mMap} in-memory cache, so a call to
       * {@link #loadPropertiesFromDisk()} must be made before this.
       *
       * @param key The key to read from the {@link #mMap} object.
       * @return Returns the {@link Object} object. This will be {@code null} if key is not found or
       * if object was {@code null}. Use {@link HashMap#containsKey(Object)} to detect the later.
       * situation.
       */
      public Object getInternalProperty(String key) {
          synchronized (mLock) {
              // null keys are not allowed to be stored in mMap
              if (key != null)
                  return getInternalProperties().get(key);
              else
                  return null;
          }
      }
  
  
  
  
  
      /**
       * A static function to get the {@link Properties} object for the propertiesFile. A lock is not
       * taken when this function is called.
       *
       * @param context The {@link Context} to use to show a flash if an exception is raised while
       *                reading the file. If context is {@code null}, then flash will not be shown.
       * @param propertiesFile The {@link File} to read the {@link Properties} from.
       * @return Returns the {@link Properties} object. It will be {@code null} if an exception is
       * raised while reading the file.
       */
      public static Properties getPropertiesFromFile(Context context, File propertiesFile, @Nullable SharedPropertiesParser sharedPropertiesParser) {
          Properties properties = new Properties();
  
          if (propertiesFile == null) {
              Logger.logWarn(LOG_TAG, "Not loading properties since file is null");
              return properties;
          }
  
          try {
              try (FileInputStream in = new FileInputStream(propertiesFile)) {
                  Logger.logVerbose(LOG_TAG, "Loading properties from \"" + propertiesFile.getAbsolutePath() + "\" file");
                  properties.load(new InputStreamReader(in, StandardCharsets.UTF_8));
              }
          } catch (Exception e) {
              if (context != null)
                  Toast.makeText(context, "Could not open properties file \"" + propertiesFile.getAbsolutePath() + "\": " + e.getMessage(), Toast.LENGTH_LONG).show();
              Logger.logStackTraceWithMessage(LOG_TAG, "Error loading properties file \"" + propertiesFile.getAbsolutePath() + "\"", e);
              return null;
          }
  
          if (sharedPropertiesParser != null && context != null)
              return sharedPropertiesParser.preProcessPropertiesOnReadFromDisk(context, properties);
          else
              return properties;
      }
  
      /** Returns the first {@link File} found in
       * {@code propertiesFilePaths} from which app properties can be loaded. If the {@link File} found
       * is not a regular file or is not readable, then {@code null} is returned. Symlinks **will not**
       * be followed for potential security reasons.
       *
       * @param propertiesFilePaths The {@link List<String>} containing properties file paths.
       * @param logTag If log tag to use for logging errors.
       * @return Returns the {@link File} object for Termux:Float app properties.
       */
      public static File getPropertiesFileFromList(List<String> propertiesFilePaths, @NonNull String logTag) {
          if (propertiesFilePaths == null || propertiesFilePaths.size() == 0)
              return null;
  
          for(String propertiesFilePath : propertiesFilePaths) {
              File propertiesFile = new File(propertiesFilePath);
  
              // Symlinks **will not** be followed.
              FileType fileType = FileUtils.getFileType(propertiesFilePath, false);
              if (fileType == FileType.REGULAR) {
                  if (propertiesFile.canRead())
                      return propertiesFile;
                  else
                      Logger.logWarn(logTag, "Ignoring properties file at \"" + propertiesFilePath + "\" since it is not readable");
              } else if (fileType != FileType.NO_EXIST) {
                  Logger.logWarn(logTag, "Ignoring properties file at \"" + propertiesFilePath + "\" of type: \"" + fileType.getName() + "\"");
              }
          }
  
          Logger.logDebug(logTag, "No readable properties file found at: " + propertiesFilePaths);
          return null;
      }
  
  
  
      public static String getProperty(Context context, File propertiesFile, String key, String def) {
          return getProperty(context, propertiesFile, key, def, null);
      }
  
      /**
       * A static function to get the {@link String} value for the {@link Properties} key read from
       * the propertiesFile file.
       *
       * @param context The {@link Context} for the {@link #getPropertiesFromFile(Context,File,SharedPropertiesParser)} call.
       * @param propertiesFile The {@link File} to read the {@link Properties} from.
       * @param key The key to read.
       * @param def The default value.
       * @param sharedPropertiesParser The implementation of the {@link SharedPropertiesParser} interface.
       * @return Returns the {@link String} object. This will be {@code null} if key is not found.
       */
      public static String getProperty(Context context, File propertiesFile, String key, String def, @Nullable SharedPropertiesParser sharedPropertiesParser) {
          return (String) getDefaultIfNull(getDefaultIfNull(getPropertiesFromFile(context, propertiesFile, sharedPropertiesParser), new Properties()).get(key), def);
      }
  
      /**
       * A static function to get the internal {@link Object} value for the {@link String} value for
       * the {@link Properties} key read from the propertiesFile file.
       *
       * @param context The {@link Context} for the {@link #getPropertiesFromFile(Context,File,SharedPropertiesParser)} call.
       * @param propertiesFile The {@link File} to read the {@link Properties} from.
       * @param key The key to read.
       * @param sharedPropertiesParser The implementation of the {@link SharedPropertiesParser} interface.
       * @return Returns the {@link String} Object returned by the call to
       * {@link SharedPropertiesParser#getInternalPropertyValueFromValue(Context,String,String)}.
       */
      public static Object getInternalProperty(Context context, File propertiesFile, String key, @NonNull SharedPropertiesParser sharedPropertiesParser) {
          String value = (String) getDefaultIfNull(getPropertiesFromFile(context, propertiesFile, sharedPropertiesParser), new Properties()).get(key);
  
          // Call the {@link SharedPropertiesParser#getInternalPropertyValueFromValue(Context,String,String)}
          // interface method to get the internal value to return.
          return sharedPropertiesParser.getInternalPropertyValueFromValue(context, key, value);
      }
  
  
      public static boolean isPropertyValueTrue(Context context, File propertiesFile, String key, boolean logErrorOnInvalidValue) {
          return isPropertyValueTrue(context, propertiesFile, key, logErrorOnInvalidValue, null);
      }
  
      /**
       * A static function to check if the value is {@code true} for {@link Properties} key read from
       * the propertiesFile file.
       *
       * @param context The {@link Context} for the {@link #getPropertiesFromFile(Context,File,SharedPropertiesParser)} call.
       * @param propertiesFile The {@link File} to read the {@link Properties} from.
       * @param key The key to read.
       * @param logErrorOnInvalidValue If {@code true}, then an error will be logged if key value
       *                               was found in {@link Properties} but was invalid.
       * @param sharedPropertiesParser The implementation of the {@link SharedPropertiesParser} interface.
       * @return Returns the {@code true} if the {@link Properties} key {@link String} value equals "true",
       * regardless of case. If the key does not exist in the file or does not equal "true", then
       * {@code false} will be returned.
       */
      public static boolean isPropertyValueTrue(Context context, File propertiesFile, String key, boolean logErrorOnInvalidValue, @Nullable SharedPropertiesParser sharedPropertiesParser) {
          return (boolean) getBooleanValueForStringValue(key, (String) getProperty(context, propertiesFile, key, null, sharedPropertiesParser), false, logErrorOnInvalidValue, LOG_TAG);
      }
  
  
      public static boolean isPropertyValueFalse(Context context, File propertiesFile, String key, boolean logErrorOnInvalidValue) {
          return isPropertyValueFalse(context, propertiesFile, key, logErrorOnInvalidValue, null);
      }
  
      /**
       * A static function to check if the value is {@code false} for {@link Properties} key read from
       * the propertiesFile file.
       *
       * @param context The {@link Context} for the {@link #getPropertiesFromFile(Context,File,SharedPropertiesParser)} call.
       * @param propertiesFile The {@link File} to read the {@link Properties} from.
       * @param key The key to read.
       * @param logErrorOnInvalidValue If {@code true}, then an error will be logged if key value
       *                               was found in {@link Properties} but was invalid.
       * @param sharedPropertiesParser The implementation of the {@link SharedPropertiesParser} interface.
       * @return Returns the {@code true} if the {@link Properties} key {@link String} value equals "false",
       * regardless of case. If the key does not exist in the file or does not equal "false", then
       * {@code true} will be returned.
       */
      public static boolean isPropertyValueFalse(Context context, File propertiesFile, String key, boolean logErrorOnInvalidValue, @Nullable SharedPropertiesParser sharedPropertiesParser) {
          return (boolean) getInvertedBooleanValueForStringValue(key, (String) getProperty(context, propertiesFile, key, null, sharedPropertiesParser), true, logErrorOnInvalidValue, LOG_TAG);
      }
  
  
  
  
  
      /**
       * Put a value in a {@link #mMap}.
       * The key cannot be {@code null}.
       * Only {@code null}, primitive or their wrapper classes or String class objects are allowed to be added to
       * the map, although this limitation may be changed.
       *
       * @param map The {@link Map} object to add value to.
       * @param key The key for which to add the value to the map.
       * @param value The {@link Object} to add to the map.
       * @return Returns {@code true} if value was successfully added, otherwise {@code false}.
       */
      public static boolean putToMap(HashMap<String, Object> map, String key, Object value) {
  
          if (map == null) {
              Logger.logError(LOG_TAG, "Map passed to SharedProperties.putToProperties() is null");
              return false;
          }
  
          // null keys are not allowed to be stored in mMap
          if (key == null) {
              Logger.logError(LOG_TAG, "Cannot put a null key into properties map");
              return false;
          }
  
          boolean put = false;
          if (value != null) {
              Class<?> clazz = value.getClass();
              if (clazz.isPrimitive() || Primitives.isWrapperType(clazz) || value instanceof String) {
                  put = true;
              }
          } else {
              put = true;
          }
  
          if (put) {
              map.put(key, value);
              return true;
          } else {
              Logger.logError(LOG_TAG, "Cannot put a non-primitive value for the key \"" + key + "\" into properties map");
              return false;
          }
      }
  
      /**
       * Put a value in a {@link Map}.
       * The key cannot be {@code null}.
       * Passing {@code null} as the value argument is equivalent to removing the key from the
       * properties.
       *
       * @param properties The {@link Properties} object to add value to.
       * @param key The key for which to add the value to the properties.
       * @param value The {@link String} to add to the properties.
       * @return Returns {@code true} if value was successfully added, otherwise {@code false}.
       */
      public static boolean putToProperties(Properties properties, String key, String value) {
  
          if (properties == null) {
              Logger.logError(LOG_TAG, "Properties passed to SharedProperties.putToProperties() is null");
              return false;
          }
  
          // null keys are not allowed to be stored in mMap
          if (key == null) {
              Logger.logError(LOG_TAG, "Cannot put a null key into properties");
              return false;
          }
  
          if (value != null) {
              properties.put(key, value);
              return true;
          } else {
              properties.remove(key);
          }
  
          return true;
      }
  
      public static Properties getPropertiesCopy(Properties inputProperties) {
          if (inputProperties == null) return null;
  
          Properties outputProperties = new Properties();
          for (String key : inputProperties.stringPropertyNames()) {
              outputProperties.put(key, inputProperties.get(key));
          }
  
          return outputProperties;
      }
  
      public static Map<String, Object> getMapCopy(Map<String, Object> map) {
          if (map == null) return null;
          return new HashMap<>(map);
      }
  
  
  
  
  
  
      /**
       * Get the boolean value for the {@link String} value.
       *
       * @param value The {@link String} value to convert.
       * @return Returns {@code true} or {@code false} if value is the literal string "true" or "false" respectively,
       * regardless of case. Otherwise returns {@code null}.
       */
      public static Boolean getBooleanValueForStringValue(String value) {
          return MAP_GENERIC_BOOLEAN.get(toLowerCase(value));
      }
  
      /**
       * Get the boolean value for the {@link String} value.
       *
       * @param value The {@link String} value to convert.
       * @param def The default {@link boolean} value to return.
       * @param logErrorOnInvalidValue If {@code true}, then an error will be logged if {@code value}
       *                               was not {@code null} and was invalid.
       * @param logTag If log tag to use for logging errors.
       * @return Returns {@code true} or {@code false} if value is the literal string "true" or "false" respectively,
       * regardless of case. Otherwise returns default value.
       */
      public static boolean getBooleanValueForStringValue(String key, String value, boolean def, boolean logErrorOnInvalidValue, String logTag) {
          return (boolean) getDefaultIfNotInMap(key, MAP_GENERIC_BOOLEAN, toLowerCase(value), def, logErrorOnInvalidValue, logTag);
      }
  
      /**
       * Get the inverted boolean value for the {@link String} value.
       *
       * @param value The {@link String} value to convert.
       * @param def The default {@link boolean} value to return.
       * @param logErrorOnInvalidValue If {@code true}, then an error will be logged if {@code value}
       *                               was not {@code null} and was invalid.
       * @param logTag If log tag to use for logging errors.
       * @return Returns {@code true} or {@code false} if value is the literal string "false" or "true" respectively,
       * regardless of case. Otherwise returns default value.
       */
      public static boolean getInvertedBooleanValueForStringValue(String key, String value, boolean def, boolean logErrorOnInvalidValue, String logTag) {
          return (boolean) getDefaultIfNotInMap(key, MAP_GENERIC_INVERTED_BOOLEAN, toLowerCase(value), def, logErrorOnInvalidValue, logTag);
      }
  
      /**
       * Get the value for the {@code inputValue} {@link Object} key from a {@link BiMap<>}, otherwise
       * default value if key not found in {@code map}.
       *
       * @param key The shared properties {@link String} key value for which the value is being returned.
       * @param map The {@link BiMap<>} value to get the value from.
       * @param inputValue The {@link Object} key value of the map.
       * @param defaultOutputValue The default {@link boolean} value to return if {@code inputValue} not found in map.
       *            The default value must exist as a value in the {@link BiMap<>} passed.
       * @param logErrorOnInvalidValue If {@code true}, then an error will be logged if {@code inputValue}
       *                               was not {@code null} and was not found in the map.
       * @param logTag If log tag to use for logging errors.
       * @return Returns the value for the {@code inputValue} key from the map if it exists. Otherwise
       * returns default value.
       */
      public static Object getDefaultIfNotInMap(String key, @NonNull BiMap<?, ?> map, Object inputValue, Object defaultOutputValue, boolean logErrorOnInvalidValue, String logTag) {
          Object outputValue = map.get(inputValue);
          if (outputValue == null) {
              Object defaultInputValue = map.inverse().get(defaultOutputValue);
              if (defaultInputValue == null)
                  Logger.logError(LOG_TAG, "The default output value \"" + defaultOutputValue + "\" for the key \"" + key + "\" does not exist as a value in the BiMap passed to getDefaultIfNotInMap(): " + map.values());
  
              if (logErrorOnInvalidValue && inputValue != null) {
                  if (key != null)
                      Logger.logError(logTag, "The value \"" + inputValue + "\" for the key \"" + key + "\" is invalid. Using default value \"" + defaultInputValue + "\" instead.");
                  else
                      Logger.logError(logTag, "The value \"" + inputValue + "\" is invalid. Using default value \"" + defaultInputValue + "\" instead.");
              }
  
              return defaultOutputValue;
          } else {
              return outputValue;
          }
      }
  
      /**
       * Get the {@code int} {@code value} as is if between {@code min} and {@code max} (inclusive), otherwise
       * return default value.
       *
       * @param key The shared properties {@link String} key value for which the value is being returned.
       * @param value The {@code int} value to check.
       * @param def The default {@code int} value if {@code value} not in range.
       * @param min The min allowed {@code int} value.
       * @param max The max allowed {@code int} value.
       * @param logErrorOnInvalidValue If {@code true}, then an error will be logged if {@code value}
       *                               not in range.
       * @param ignoreErrorIfValueZero If logging error should be ignored if value equals 0.
       * @param logTag If log tag to use for logging errors.
       * @return Returns the {@code value} as is if within range. Otherwise returns default value.
       */
      public static int getDefaultIfNotInRange(String key, int value, int def, int min, int max, boolean logErrorOnInvalidValue, boolean ignoreErrorIfValueZero, String logTag) {
          if (value < min || value > max) {
              if (logErrorOnInvalidValue && (!ignoreErrorIfValueZero || value != 0)) {
                  if (key != null)
                      Logger.logError(logTag, "The value \"" + value + "\" for the key \"" + key + "\" is not within the range " + min +  "-" + max +  " (inclusive). Using default value \"" + def + "\" instead.");
                  else
                      Logger.logError(logTag, "The value \"" + value + "\" is not within the range " + min +  "-" + max +  " (inclusive). Using default value \"" + def + "\" instead.");
              }
              return def;
          } else {
              return value;
          }
      }
  
      /**
       * Get the {@code float} {@code value} as is if between {@code min} and {@code max} (inclusive), otherwise
       * return default value.
       *
       * @param key The shared properties {@link String} key value for which the value is being returned.
       * @param value The {@code float} value to check.
       * @param def The default {@code float} value if {@code value} not in range.
       * @param min The min allowed {@code float} value.
       * @param max The max allowed {@code float} value.
       * @param logErrorOnInvalidValue If {@code true}, then an error will be logged if {@code value}
       *                               not in range.
       * @param ignoreErrorIfValueZero If logging error should be ignored if value equals 0.
       * @param logTag If log tag to use for logging errors.
       * @return Returns the {@code value} as is if within range. Otherwise returns default value.
       */
      public static float getDefaultIfNotInRange(String key, float value, float def, float min, float max, boolean logErrorOnInvalidValue, boolean ignoreErrorIfValueZero, String logTag) {
          if (value < min || value > max) {
              if (logErrorOnInvalidValue && (!ignoreErrorIfValueZero || value != 0)) {
                  if (key != null)
                      Logger.logError(logTag, "The value \"" + value + "\" for the key \"" + key + "\" is not within the range " + min +  "-" + max +  " (inclusive). Using default value \"" + def + "\" instead.");
                  else
                      Logger.logError(logTag, "The value \"" + value + "\" is not within the range " + min +  "-" + max +  " (inclusive). Using default value \"" + def + "\" instead.");
              }
              return def;
          } else {
              return value;
          }
      }
  
      /**
       * Get the object itself if it is not {@code null}, otherwise default.
       *
       * @param object The {@link Object} to check.
       * @param def The default {@link Object}.
       * @return Returns {@code object} if it is not {@code null}, otherwise returns {@code def}.
       */
      public static <T> T getDefaultIfNull(@Nullable T object, @Nullable T def) {
          return (object == null) ? def : object;
      }
  
      /**
       * Get the {@link String} object itself if it is not {@code null} or empty, otherwise default.
       *
       * @param object The {@link String} to check.
       * @param def The default {@link String}.
       * @return Returns {@code object} if it is not {@code null}, otherwise returns {@code def}.
       */
      public static String getDefaultIfNullOrEmpty(@Nullable String object, @Nullable String def) {
          return (object == null || object.isEmpty()) ? def : object;
      }
  
      /**
       * Covert the {@link String} value to lowercase.
       *
       * @param value The {@link String} value to convert.
       * @return Returns the lowercased value.
       */
      public static String toLowerCase(String value) {
          if (value == null) return null; else return value.toLowerCase();
      }
  
  }
  