All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.ekrich.config.impl.SimpleConfig.scala Maven / Gradle / Ivy

The newest version!
/**
 *   Copyright (C) 2011-2012 Typesafe Inc. 
 */
package org.ekrich.config.impl

import java.io.ObjectStreamException
import java.io.Serializable
import java.{lang => jl}
import java.math.BigDecimal
import java.math.BigInteger
import java.time.DateTimeException
import java.time.Duration
import java.time.Period
import java.time.temporal.ChronoUnit
import java.time.temporal.TemporalAmount
import java.{util => ju}
import java.util.concurrent.TimeUnit

import scala.jdk.CollectionConverters._
import scala.util.control.Breaks._
import org.ekrich.config.Config
import org.ekrich.config.ConfigException
import org.ekrich.config.ConfigList
import org.ekrich.config.ConfigMemorySize
import org.ekrich.config.ConfigMergeable
import org.ekrich.config.ConfigObject
import org.ekrich.config.ConfigOrigin
import org.ekrich.config.ConfigResolveOptions
import org.ekrich.config.ConfigValue
import org.ekrich.config.ConfigValueType

/**
 * One thing to keep in mind in the future: as Collection-like APIs are added
 * here, including iterators or size() or anything, they should be consistent
 * with a one-level java.util.Map from paths to non-null values. Null values are
 * not "in" the map.
 */
@SerialVersionUID(1L)
object SimpleConfig {
  private def findPaths(
      entries: ju.Set[ju.Map.Entry[String, ConfigValue]],
      parent: Path,
      obj: AbstractConfigObject
  ): Unit = {
    for (entry <- obj.entrySet.asScala) {
      val elem = entry.getKey
      val v    = entry.getValue
      var path = Path.newKey(elem)
      if (parent != null) path = path.prepend(parent)
      if (v.isInstanceOf[AbstractConfigObject])
        findPaths(entries, path, v.asInstanceOf[AbstractConfigObject])
      else if (v.isInstanceOf[ConfigNull]) {
        // nothing; nulls are conceptually not in a Config
      } else
        entries.add(
          new ju.AbstractMap.SimpleImmutableEntry[String, ConfigValue](
            path.render,
            v
          )
        )
    }
  }
  private def throwIfNull(
      v: AbstractConfigValue,
      expected: ConfigValueType,
      originalPath: Path
  ): AbstractConfigValue =
    if (v.valueType eq ConfigValueType.NULL)
      throw new ConfigException.Null(
        v.origin,
        originalPath.render,
        if (expected != null) expected.name else null
      )
    else v
  private def findKey(
      self: AbstractConfigObject,
      key: String,
      expected: ConfigValueType,
      originalPath: Path
  ): AbstractConfigValue =
    throwIfNull(
      findKeyOrNull(self, key, expected, originalPath),
      expected,
      originalPath
    )

  private def findKeyOrNull(
      self: AbstractConfigObject,
      key: String,
      expected: ConfigValueType,
      originalPath: Path
  ): AbstractConfigValue = {
    var v = self.peekAssumingResolved(key, originalPath)
    if (v == null)
      throw new ConfigException.Missing(self.origin, originalPath.render)
    if (expected != null) v = DefaultTransformer.transform(v, expected)
    if (expected != null && ((v.valueType ne expected) && (v.valueType ne ConfigValueType.NULL)))
      throw new ConfigException.WrongType(
        v.origin,
        originalPath.render,
        expected.name,
        v.valueType.name
      )
    else v
  }

  private def findOrNull(
      self: AbstractConfigObject,
      path: Path,
      expected: ConfigValueType,
      originalPath: Path
  ): AbstractConfigValue =
    try {
      val key  = path.first
      val next = path.remainder
      if (next == null) findKeyOrNull(self, key, expected, originalPath)
      else {
        val o = findKey(
          self,
          key,
          ConfigValueType.OBJECT,
          originalPath.subPath(0, originalPath.length - next.length)
        ).asInstanceOf[AbstractConfigObject]
        assert(o != null) // missing was supposed to throw
        findOrNull(o, next, expected, originalPath)
      }
    } catch {
      case e: ConfigException.NotResolved =>
        throw ConfigImpl.improveNotResolved(path, e)
    }
  private def getUnits(s: String): String = {
    var i = s.length - 1
    breakable {
      while (i >= 0) {
        val c = s.charAt(i)
        if (!Character.isLetter(c)) break // break
        i -= 1
      }
    }
    return s.substring(i + 1)
  }

  /**
   * Parses a period string. If no units are specified in the string, it is
   * assumed to be in days. The returned period is in days.
   * The purpose of this function is to implement the period-related methods
   * in the ConfigObject interface.
   *
   * @param input
   *            the string to parse
   * @param originForException
   *            origin of the value being parsed
   * @param pathForException
   *            path to include in exceptions
   * @return duration in days
   * @throws ConfigException
   *             if string is invalid
   */
  def parsePeriod(
      input: String,
      originForException: ConfigOrigin,
      pathForException: String
  ) = {
    val s                  = ConfigImplUtil.unicodeTrim(input)
    val originalUnitString = getUnits(s)
    var unitString         = originalUnitString
    val numberString =
      ConfigImplUtil.unicodeTrim(s.substring(0, s.length - unitString.length))
    var units: ChronoUnit = null
    // this would be caught later anyway, but the error message
    // is more helpful if we check it here.
    if (numberString.length == 0)
      throw new ConfigException.BadValue(
        originForException,
        pathForException,
        "No number in period value '" + input + "'"
      )
    if (unitString.length > 2 && !unitString.endsWith("s"))
      unitString = unitString + "s"
    // note that this is deliberately case-sensitive
    if (unitString == "" || unitString == "d" || unitString == "days")
      units = ChronoUnit.DAYS
    else if (unitString == "w" || unitString == "weeks")
      units = ChronoUnit.WEEKS
    else if (unitString == "m" || unitString == "mo" || unitString == "months")
      units = ChronoUnit.MONTHS
    else if (unitString == "y" || unitString == "years")
      units = ChronoUnit.YEARS
    else
      throw new ConfigException.BadValue(
        originForException,
        pathForException,
        "Could not parse time unit '" + originalUnitString + "' (try d, w, mo, y)"
      )
    try periodOf(numberString.toInt, units)
    catch {
      case e: NumberFormatException =>
        throw new ConfigException.BadValue(
          originForException,
          pathForException,
          "Could not parse duration number '" + numberString + "'"
        )
    }
  }
  private def periodOf(n: Int, unit: ChronoUnit): Period = {
    if (unit.isTimeBased)
      throw new DateTimeException(
        s"$unit cannot be converted to a java.time.Period"
      )
    unit match {
      case ChronoUnit.DAYS =>
        return Period.ofDays(n)
      case ChronoUnit.WEEKS =>
        return Period.ofWeeks(n)
      case ChronoUnit.MONTHS =>
        return Period.ofMonths(n)
      case ChronoUnit.YEARS =>
        return Period.ofYears(n)
      case _ =>
        throw new DateTimeException(
          s"$unit cannot be converted to a java.time.Period"
        )
    }
  }

  /**
   * Parses a duration string. If no units are specified in the string, it is
   * assumed to be in milliseconds. The returned duration is in nanoseconds.
   * The purpose of this function is to implement the duration-related methods
   * in the ConfigObject interface.
   *
   * @param input
   *            the string to parse
   * @param originForException
   *            origin of the value being parsed
   * @param pathForException
   *            path to include in exceptions
   * @return duration in nanoseconds
   * @throws ConfigException
   *             if string is invalid
   */
  def parseDuration(
      input: String,
      originForException: ConfigOrigin,
      pathForException: String
  ): Long = {
    val s                  = ConfigImplUtil.unicodeTrim(input)
    val originalUnitString = getUnits(s)
    var unitString         = originalUnitString
    val numberString =
      ConfigImplUtil.unicodeTrim(s.substring(0, s.length - unitString.length))
    var units: TimeUnit = null

    // this would be caught later anyway, but the error message
    // is more helpful if we check it here.
    if (numberString.length == 0)
      throw new ConfigException.BadValue(
        originForException,
        pathForException,
        "No number in duration value '" + input + "'"
      )
    if (unitString.length > 2 && !unitString.endsWith("s"))
      unitString = unitString + "s"

    // note that this is deliberately case-sensitive
    if (unitString == "" || unitString == "ms" || unitString == "millis" || unitString == "milliseconds")
      units = TimeUnit.MILLISECONDS
    else if (unitString == "us" || unitString == "micros" || unitString == "microseconds")
      units = TimeUnit.MICROSECONDS
    else if (unitString == "ns" || unitString == "nanos" || unitString == "nanoseconds")
      units = TimeUnit.NANOSECONDS
    else if (unitString == "d" || unitString == "days") units = TimeUnit.DAYS
    else if (unitString == "h" || unitString == "hours") units = TimeUnit.HOURS
    else if (unitString == "s" || unitString == "seconds")
      units = TimeUnit.SECONDS
    else if (unitString == "m" || unitString == "minutes")
      units = TimeUnit.MINUTES
    else {
      throw new ConfigException.BadValue(
        originForException,
        pathForException,
        "Could not parse time unit '" + originalUnitString + "' (try ns, us, ms, s, m, h, d)"
      )
    }
    try {
      // if the string is purely digits, parse as an integer to avoid
      // possible precision loss;
      // otherwise as a double.
      if (numberString.matches("[+-]?[0-9]+")) {
        units.toNanos(jl.Long.parseLong(numberString))
      } else {
        val nanosInUnit = units.toNanos(1)
        (numberString.toDouble * nanosInUnit).toLong
      }
    } catch {
      case e: NumberFormatException =>
        throw new ConfigException.BadValue(
          originForException,
          pathForException,
          "Could not parse duration number '" + numberString + "'"
        )
    }
  }

  /**
   * Parses a size-in-bytes string. If no units are specified in the string,
   * it is assumed to be in bytes. The returned value is in bytes. The purpose
   * of this function is to implement the size-in-bytes-related methods in the
   * Config interface.
   *
   * @param input
   *            the string to parse
   * @param originForException
   *            origin of the value being parsed
   * @param pathForException
   *            path to include in exceptions
   * @return size in bytes
   * @throws ConfigException
   *             if string is invalid
   */
  def parseBytes(
      input: String,
      originForException: ConfigOrigin,
      pathForException: String
  ): Long = {
    val s          = ConfigImplUtil.unicodeTrim(input)
    val unitString = getUnits(s)
    val numberString =
      ConfigImplUtil.unicodeTrim(s.substring(0, s.length - unitString.length))
    if (numberString.length == 0)
      throw new ConfigException.BadValue(
        originForException,
        pathForException,
        "No number in size-in-bytes value '" + input + "'"
      )
    val units = MemoryUnit.parseUnit(unitString)
    if (units == null)
      throw new ConfigException.BadValue(
        originForException,
        pathForException,
        "Could not parse size-in-bytes unit '" + unitString + "' (try k, K, kB, KiB, kilobytes, kibibytes)"
      )
    try {
      var result: BigInteger = null
      // possible precision loss; otherwise as a double.
      if (numberString.matches("[0-9]+"))
        result = units.bytes.multiply(new BigInteger(numberString))
      else {
        val resultDecimal =
          new BigDecimal(units.bytes).multiply(new BigDecimal(numberString))
        result = resultDecimal.toBigInteger
      }
      if (result.bitLength < 64) result.longValue
      else
        throw new ConfigException.BadValue(
          originForException,
          pathForException,
          "size-in-bytes value is out of range for a 64-bit long: '" + input + "'"
        )
    } catch {
      case e: NumberFormatException =>
        throw new ConfigException.BadValue(
          originForException,
          pathForException,
          "Could not parse size-in-bytes number '" + numberString + "'"
        )
    }
  }
  private def addProblem(
      accumulator: ju.List[ConfigException.ValidationProblem],
      path: Path,
      origin: ConfigOrigin,
      problem: String
  ): Unit = {
    accumulator.add(
      new ConfigException.ValidationProblem(path.render, origin, problem)
    )
  }
  private def getDesc(`type`: ConfigValueType): String = {
    return `type`.name.toLowerCase
  }
  private def getDesc(refValue: ConfigValue): String = {
    if (refValue.isInstanceOf[AbstractConfigObject]) {
      val obj = refValue.asInstanceOf[AbstractConfigObject]
      if (!obj.isEmpty) return "object with keys " + obj.keySet
      else return getDesc(refValue.valueType)
    } else return getDesc(refValue.valueType)
  }
  private def addMissing(
      accumulator: ju.List[ConfigException.ValidationProblem],
      refDesc: String,
      path: Path,
      origin: ConfigOrigin
  ): Unit = {
    addProblem(
      accumulator,
      path,
      origin,
      "No setting at '" + path.render + "', expecting: " + refDesc
    )
  }
  private def addMissing(
      accumulator: ju.List[ConfigException.ValidationProblem],
      refValue: ConfigValue,
      path: Path,
      origin: ConfigOrigin
  ): Unit = {
    addMissing(accumulator, getDesc(refValue), path, origin)
  }
  // JavaBean stuff uses this
  private[impl] def addMissing(
      accumulator: ju.List[ConfigException.ValidationProblem],
      refType: ConfigValueType,
      path: Path,
      origin: ConfigOrigin
  ): Unit = {
    addMissing(accumulator, getDesc(refType), path, origin)
  }
  private def addWrongType(
      accumulator: ju.List[ConfigException.ValidationProblem],
      refDesc: String,
      actual: AbstractConfigValue,
      path: Path
  ): Unit = {
    addProblem(
      accumulator,
      path,
      actual.origin,
      "Wrong value type at '" + path.render + "', expecting: " + refDesc + " but got: " + getDesc(
        actual
      )
    )
  }
  private def addWrongType(
      accumulator: ju.List[ConfigException.ValidationProblem],
      refValue: ConfigValue,
      actual: AbstractConfigValue,
      path: Path
  ): Unit = {
    addWrongType(accumulator, getDesc(refValue), actual, path)
  }
  private def addWrongType(
      accumulator: ju.List[ConfigException.ValidationProblem],
      refType: ConfigValueType,
      actual: AbstractConfigValue,
      path: Path
  ): Unit = {
    addWrongType(accumulator, getDesc(refType), actual, path)
  }
  private def couldBeNull(v: AbstractConfigValue): Boolean = {
    return DefaultTransformer
      .transform(v, ConfigValueType.NULL)
      .valueType eq ConfigValueType.NULL
  }
  private def haveCompatibleTypes(
      reference: ConfigValue,
      value: AbstractConfigValue
  ): Boolean = {
    if (couldBeNull(reference.asInstanceOf[AbstractConfigValue])) {
      // we allow any setting to be null
      return true
    } else return haveCompatibleTypes(reference.valueType, value)
  }
  private def haveCompatibleTypes(
      referenceType: ConfigValueType,
      value: AbstractConfigValue
  ): Boolean = {
    if ((referenceType eq ConfigValueType.NULL) || couldBeNull(value))
      return true
    else if (referenceType eq ConfigValueType.OBJECT)
      if (value.isInstanceOf[AbstractConfigObject]) return true
      else return false
    else if (referenceType eq ConfigValueType.LIST) {
      // objects may be convertible to lists if they have numeric keys
      if (value.isInstanceOf[SimpleConfigList] || value
            .isInstanceOf[SimpleConfigObject]) return true
      else return false
    } else if (referenceType eq ConfigValueType.STRING) {
      // assume a string could be gotten as any non-collection type;
      // allows things like getMilliseconds including domain-specific
      // interpretations of strings
      return true
    } else if (value.isInstanceOf[ConfigString]) { // assume a string could be gotten as any non-collection type
      return true
    } else if (referenceType eq value.valueType) return true
    else return false
  }
  // path is null if we're at the root
  private def checkValidObject(
      path: Path,
      reference: AbstractConfigObject,
      value: AbstractConfigObject,
      accumulator: ju.List[ConfigException.ValidationProblem]
  ): Unit = {
    for (entry <- reference.entrySet.asScala) {
      val key = entry.getKey
      val childPath: Path =
        if (path != null) Path.newKey(key).prepend(path) else Path.newKey(key)
      val v = value.get(key)
      if (v == null)
        addMissing(accumulator, entry.getValue, childPath, value.origin)
      else checkValid(childPath, entry.getValue, v, accumulator)
    }
  }
  private def checkListCompatibility(
      path: Path,
      listRef: SimpleConfigList,
      listValue: SimpleConfigList,
      accumulator: ju.List[ConfigException.ValidationProblem]
  ): Unit = {
    if (listRef.isEmpty || listValue.isEmpty) {
      // can't verify type, leave alone
    } else {
      val refElement = listRef.get(0)
      breakable {
        for (elem <- listValue.asScala) {
          val e = elem.asInstanceOf[AbstractConfigValue]
          if (!haveCompatibleTypes(refElement, e)) {
            addProblem(
              accumulator,
              path,
              e.origin,
              "List at '" + path.render + "' contains wrong value type, expecting list of " + getDesc(
                refElement
              ) + " but got element of type " + getDesc(e)
            )
            // don't add a problem for every last array element
            break // break
          }
        }
      }
    }
  }
  // Used by the JavaBean-based validator
  private[impl] def checkValid(
      path: Path,
      referenceType: ConfigValueType,
      value: AbstractConfigValue,
      accumulator: ju.List[ConfigException.ValidationProblem]
  ): Unit = {
    if (haveCompatibleTypes(referenceType, value)) {
      if ((referenceType eq ConfigValueType.LIST) && value
            .isInstanceOf[SimpleConfigObject]) {
        // attempt conversion of indexed object to list
        val listValue =
          DefaultTransformer.transform(value, ConfigValueType.LIST)
        if (!listValue.isInstanceOf[SimpleConfigList])
          addWrongType(accumulator, referenceType, value, path)
      }
    } else {
      addWrongType(accumulator, referenceType, value, path)
    }
  }

  private def checkValid(
      path: Path,
      reference: ConfigValue,
      value: AbstractConfigValue,
      accumulator: ju.List[ConfigException.ValidationProblem]
  ): Unit = {
    // Unmergeable is supposed to be impossible to encounter in here
    // because we check for resolve status up front.
    if (haveCompatibleTypes(reference, value)) {
      if (reference.isInstanceOf[AbstractConfigObject] && value
            .isInstanceOf[AbstractConfigObject]) {
        checkValidObject(
          path,
          reference.asInstanceOf[AbstractConfigObject],
          value.asInstanceOf[AbstractConfigObject],
          accumulator
        )
      } else if (reference.isInstanceOf[SimpleConfigList] && value
                   .isInstanceOf[SimpleConfigList]) {
        val listRef   = reference.asInstanceOf[SimpleConfigList]
        val listValue = value.asInstanceOf[SimpleConfigList]
        checkListCompatibility(path, listRef, listValue, accumulator)
      } else if (reference.isInstanceOf[SimpleConfigList] && value
                   .isInstanceOf[SimpleConfigObject]) {
        val listRef = reference.asInstanceOf[SimpleConfigList]
        val listValue =
          DefaultTransformer.transform(value, ConfigValueType.LIST)
        if (listValue.isInstanceOf[SimpleConfigList])
          checkListCompatibility(
            path,
            listRef,
            listValue.asInstanceOf[SimpleConfigList],
            accumulator
          )
        else
          addWrongType(accumulator, reference, value, path)
      }
    } else {
      addWrongType(accumulator, reference, value, path)
    }
  }
}

@SerialVersionUID(1L)
final class SimpleConfig private[impl] (val confObj: AbstractConfigObject)
    extends Config
    with MergeableValue
    with Serializable {
  override def root: AbstractConfigObject = confObj
  override def origin: ConfigOrigin       = confObj.origin
  override def resolve(): SimpleConfig    = resolve(ConfigResolveOptions.defaults)
  override def resolve(options: ConfigResolveOptions): SimpleConfig =
    resolveWith(this, options)
  override def resolveWith(source: Config): SimpleConfig =
    resolveWith(source, ConfigResolveOptions.defaults)
  override def resolveWith(
      source: Config,
      options: ConfigResolveOptions
  ): SimpleConfig = {
    val resolved = ResolveContext.resolve(
      confObj,
      source.asInstanceOf[SimpleConfig].confObj,
      options
    )
    if (resolved eq confObj) this
    else new SimpleConfig(resolved.asInstanceOf[AbstractConfigObject])
  }
  private def hasPathPeek(pathExpression: String) = {
    val path                        = Path.newPath(pathExpression)
    var peeked: AbstractConfigValue = null
    try peeked = confObj.peekPath(path)
    catch {
      case e: ConfigException.NotResolved =>
        throw ConfigImpl.improveNotResolved(path, e)
    }
    peeked
  }
  override def hasPath(pathExpression: String): Boolean = {
    val peeked = hasPathPeek(pathExpression)
    peeked != null && (peeked.valueType ne ConfigValueType.NULL)
  }
  override def hasPathOrNull(path: String): Boolean = {
    val peeked = hasPathPeek(path)
    peeked != null
  }
  override def isEmpty: Boolean = confObj.isEmpty
  override def entrySet: ju.Set[ju.Map.Entry[String, ConfigValue]] = {
    val entries = new ju.HashSet[ju.Map.Entry[String, ConfigValue]]
    SimpleConfig.findPaths(entries, null, confObj)
    entries
  }
  private[impl] def find(
      pathExpression: Path,
      expected: ConfigValueType,
      originalPath: Path
  ): AbstractConfigValue =
    SimpleConfig.throwIfNull(
      SimpleConfig.findOrNull(confObj, pathExpression, expected, originalPath),
      expected,
      originalPath
    )
  private[impl] def find(
      pathExpression: String,
      expected: ConfigValueType
  ): AbstractConfigValue = {
    val path = Path.newPath(pathExpression)
    find(path, expected, path)
  }
  private def findOrNull(
      pathExpression: Path,
      expected: ConfigValueType,
      originalPath: Path
  ): AbstractConfigValue =
    SimpleConfig.findOrNull(confObj, pathExpression, expected, originalPath)
  private def findOrNull(
      pathExpression: String,
      expected: ConfigValueType
  ): AbstractConfigValue = {
    val path = Path.newPath(pathExpression)
    findOrNull(path, expected, path)
  }
  override def getValue(path: String): AbstractConfigValue = find(path, null)
  override def getIsNull(path: String): Boolean = {
    val v = findOrNull(path, null)
    v.valueType eq ConfigValueType.NULL
  }
  override def getBoolean(path: String): Boolean = {
    val v = find(path, ConfigValueType.BOOLEAN)
    v.unwrapped.asInstanceOf[Boolean]
  }
  private def getConfigNumber(path: String): ConfigNumber = {
    val v = find(path, ConfigValueType.NUMBER)
    v.asInstanceOf[ConfigNumber]
  }
  override def getNumber(path: String): Number =
    getConfigNumber(path).unwrapped
  override def getInt(path: String): Int = {
    val n = getConfigNumber(path)
    n.intValueRangeChecked(path)
  }
  override def getLong(path: String): Long     = getNumber(path).longValue
  override def getDouble(path: String): Double = getNumber(path).doubleValue
  override def getString(path: String): String = {
    val v = find(path, ConfigValueType.STRING)
    v.unwrapped.asInstanceOf[String]
  }
  def getEnum[T <: jl.Enum[T]](enumClass: Class[T], path: String): T = {
    val v = find(path, ConfigValueType.STRING)
    getEnumValue(path, enumClass, v)
  }
  override def getList(path: String): ConfigList = {
    val v = find(path, ConfigValueType.LIST)
    v.asInstanceOf[ConfigList]
  }
  override def getObject(path: String): AbstractConfigObject = {
    val obj =
      find(path, ConfigValueType.OBJECT).asInstanceOf[AbstractConfigObject]
    obj
  }
  override def getConfig(path: String): SimpleConfig = getObject(path).toConfig
  override def getAnyRef(path: String): AnyRef = {
    val v = find(path, null)
    v.unwrapped
  }
  override def getBytes(path: String): jl.Long = {
    var size: jl.Long = null
    try size = getLong(path)
    catch {
      case e: ConfigException.WrongType =>
        val v = find(path, ConfigValueType.STRING)
        size = SimpleConfig.parseBytes(
          v.unwrapped.asInstanceOf[String],
          v.origin,
          path
        )
    }
    size
  }
  override def getMemorySize(path: String): ConfigMemorySize =
    ConfigMemorySize.ofBytes(getBytes(path))
  override def getDuration(path: String, unit: TimeUnit): Long = {
    val v = find(path, ConfigValueType.STRING)
    val result = unit.convert(
      SimpleConfig
        .parseDuration(v.unwrapped.asInstanceOf[String], v.origin, path),
      TimeUnit.NANOSECONDS
    )
    result
  }
  override def getDuration(path: String): Duration = {
    val v = find(path, ConfigValueType.STRING)
    val nanos = SimpleConfig.parseDuration(
      v.unwrapped.asInstanceOf[String],
      v.origin,
      path
    )
    Duration.ofNanos(nanos)
  }
  override def getPeriod(path: String): Period = {
    val v = find(path, ConfigValueType.STRING)
    SimpleConfig.parsePeriod(v.unwrapped.asInstanceOf[String], v.origin, path)
  }
  override def getTemporal(path: String): TemporalAmount =
    try getDuration(path)
    catch {
      case e: ConfigException.BadValue =>
        getPeriod(path)
    }
  @SuppressWarnings(Array("unchecked"))
  private def getHomogeneousUnwrappedList[T](
      path: String,
      expected: ConfigValueType
  ): ju.List[T] = {
    val l    = new ju.ArrayList[T]
    val list = getList(path)
    for (cv <- list.asScala) {
      // variance would be nice, but stupid cast will do
      var v = cv.asInstanceOf[AbstractConfigValue]
      if (expected != null)
        v = DefaultTransformer.transform(v, expected)
      if (v.valueType ne expected)
        throw new ConfigException.WrongType(
          v.origin,
          path,
          "list of " + expected.name,
          "list of " + v.valueType.name
        )
      l.add(v.unwrapped.asInstanceOf[T])
    }
    l
  }
  override def getBooleanList(path: String): ju.List[jl.Boolean] =
    getHomogeneousUnwrappedList(path, ConfigValueType.BOOLEAN)
  override def getNumberList(path: String): ju.List[jl.Number] =
    getHomogeneousUnwrappedList(path, ConfigValueType.NUMBER)
  override def getIntList(path: String): ju.List[jl.Integer] = {
    val l = new ju.ArrayList[Integer]
    val numbers = getHomogeneousWrappedList(path, ConfigValueType.NUMBER)
      .asInstanceOf[ju.List[ConfigNumber]]
    for (v <- numbers.asScala) {
      l.add(v.asInstanceOf[ConfigNumber].intValueRangeChecked(path))
    }
    l
  }
  override def getLongList(path: String): ju.List[jl.Long] = {
    val l       = new ju.ArrayList[jl.Long]
    val numbers = getNumberList(path)
    for (n <- numbers.asScala) {
      l.add(n.longValue)
    }
    l
  }
  override def getDoubleList(path: String): ju.List[jl.Double] = {
    val l       = new ju.ArrayList[jl.Double]
    val numbers = getNumberList(path)
    for (n <- numbers.asScala) {
      l.add(n.doubleValue)
    }
    l
  }
  override def getStringList(path: String): ju.List[String] =
    getHomogeneousUnwrappedList(path, ConfigValueType.STRING)

  def getEnumList[T <: jl.Enum[T]](
      enumClass: Class[T],
      path: String
  ): ju.List[T] = {
    val enumNames = getHomogeneousWrappedList(path, ConfigValueType.STRING)
      .asInstanceOf[ju.List[ConfigString]]
    val enumList = new ju.ArrayList[T]

    for (enumName <- enumNames.asScala) {
      enumList.add(getEnumValue(path, enumClass, enumName))
    }
    enumList
  }

  private def getEnumValue[T <: jl.Enum[T]](
      path: String,
      enumClass: Class[T],
      enumConfigValue: ConfigValue
  ): T = {
    val enumName = enumConfigValue.unwrapped.asInstanceOf[String]
    try {
      jl.Enum.valueOf(enumClass, enumName)
    } catch {
      case e: IllegalArgumentException =>
        val enumNames     = new ju.ArrayList[String]
        val enumConstants = enumClass.getEnumConstants
        if (enumConstants != null) for (enumConstant <- enumConstants) {
          enumNames.add(enumConstant.name)
        }
        throw new ConfigException.BadValue(
          enumConfigValue.origin,
          path,
          String.format(
            "The enum class %s has no constant of the name '%s' (should be one of %s.)",
            enumClass.getSimpleName,
            enumName,
            enumNames
          )
        )
    }
  }

  private def getHomogeneousWrappedList[T <: ConfigValue](
      path: String,
      expected: ConfigValueType
  ): ju.List[T] = {
    val l    = new ju.ArrayList[T]
    val list = getList(path)
    for (cv <- list.asScala) {
      var v = cv.asInstanceOf[AbstractConfigValue]
      if (expected != null) v = DefaultTransformer.transform(v, expected)
      if (v.valueType ne expected)
        throw new ConfigException.WrongType(
          v.origin,
          path,
          "list of " + expected.name,
          "list of " + v.valueType.name
        )
      l.add(v.asInstanceOf[T])
    }
    l
  }
  override def getObjectList(path: String): ju.List[ConfigObject] =
    getHomogeneousWrappedList(path, ConfigValueType.OBJECT)
  override def getConfigList(path: String): ju.List[_ <: Config] = {
    val objects = getObjectList(path)
    val l       = new ju.ArrayList[Config]
    for (o <- objects.asScala) {
      l.add(o.toConfig)
    }
    l
  }
  override def getAnyRefList(path: String): ju.List[_ <: AnyRef] = {
    val l    = new ju.ArrayList[AnyRef]
    val list = getList(path)
    for (v <- list.asScala) {
      l.add(v.unwrapped)
    }
    l
  }
  override def getBytesList(path: String): ju.List[jl.Long] = {
    val l    = new ju.ArrayList[jl.Long]
    val list = getList(path)
    for (v <- list.asScala) {
      if (v.valueType eq ConfigValueType.NUMBER) {
        l.add(v.unwrapped.asInstanceOf[Number].longValue)
      } else if (v.valueType eq ConfigValueType.STRING) {
        val s = v.unwrapped.asInstanceOf[String]
        val n = SimpleConfig.parseBytes(s, v.origin, path)
        l.add(n)
      } else {
        throw new ConfigException.WrongType(
          v.origin,
          path,
          "memory size string or number of bytes",
          v.valueType.name
        )
      }
    }
    l
  }
  override def getMemorySizeList(path: String): ju.List[ConfigMemorySize] = {
    val list    = getBytesList(path)
    val builder = new ju.ArrayList[ConfigMemorySize]
    for (v <- list.asScala) {
      builder.add(ConfigMemorySize.ofBytes(v))
    }
    builder
  }
  override def getDurationList(
      path: String,
      unit: TimeUnit
  ): ju.List[jl.Long] = {
    val l    = new ju.ArrayList[jl.Long]
    val list = getList(path)
    for (v <- list.asScala) {
      if (v.valueType eq ConfigValueType.NUMBER) {
        val n = unit.convert(
          v.unwrapped.asInstanceOf[Number].longValue,
          TimeUnit.MILLISECONDS
        )
        l.add(n)
      } else if (v.valueType eq ConfigValueType.STRING) {
        val s = v.unwrapped.asInstanceOf[String]
        val n = unit.convert(
          SimpleConfig.parseDuration(s, v.origin, path),
          TimeUnit.NANOSECONDS
        )
        l.add(n)
      } else {
        throw new ConfigException.WrongType(
          v.origin,
          path,
          "duration string or number of milliseconds",
          v.valueType.name
        )
      }
    }
    l
  }
  override def getDurationList(path: String): ju.List[Duration] = {
    val l       = getDurationList(path, TimeUnit.NANOSECONDS)
    val builder = new ju.ArrayList[Duration](l.size)
    for (value <- l.asScala) {
      builder.add(Duration.ofNanos(value))
    }
    builder
  }
  override def toFallbackValue: AbstractConfigObject = confObj
  override def withFallback(other: ConfigMergeable): SimpleConfig = { // this can return "this" if the withFallback doesn't need a new
    // ConfigObject
    confObj.withFallback(other).toConfig
  }
  override final def equals(other: Any): Boolean =
    if (other.isInstanceOf[SimpleConfig])
      confObj == other.asInstanceOf[SimpleConfig].confObj
    else false

  override final def hashCode
      : Int = { // we do the "41*" just so our hash code won't match that of the
    // underlying object. there's no real reason it can't match, but
    // making it not match might catch some kinds of bug.
    41 * confObj.hashCode
  }
  override def toString: String                         = "Config(" + confObj.toString + ")"
  private def peekPath(path: Path): AbstractConfigValue = root.peekPath(path)
  override def isResolved: Boolean =
    root.resolveStatus eq ResolveStatus.RESOLVED

  // This method in Config uses @varargs so it can be called from Java with varargs
  // See https://github.com/scala/bug/issues/10658
  // originally: @Override public void checkValid(Config reference, String... restrictToPaths)
  // Now the code goes through the Scala varargs method but we need this one for Java
  def checkValid(reference: Config, restrictToPaths: Array[String]): Unit = {
    checkValid(reference, restrictToPaths.toIndexedSeq: _*)
  }

  override def checkValid(reference: Config, restrictToPaths: String*): Unit = {
    val ref = reference.asInstanceOf[SimpleConfig]
    // unresolved reference config is a bug in the caller of checkValid
    if (ref.root.resolveStatus ne ResolveStatus.RESOLVED)
      throw new ConfigException.BugOrBroken(
        "do not call checkValid() with an unresolved reference config, call Config#resolve(), see Config#resolve() API docs"
      )
    // unresolved config under validation is a bug in something,
    // NotResolved is a more specific subclass of BugOrBroken
    if (root.resolveStatus ne ResolveStatus.RESOLVED)
      throw new ConfigException.NotResolved(
        "need to Config#resolve() each config before using it, see the API docs for Config#resolve()"
      )
    // Now we know that both reference and this config are resolved
    val problems = new ju.ArrayList[ConfigException.ValidationProblem]
    if (restrictToPaths.length == 0)
      SimpleConfig.checkValidObject(null, ref.root, root, problems)
    else
      for (p <- restrictToPaths) {
        val path     = Path.newPath(p)
        val refValue = ref.peekPath(path)
        if (refValue != null) {
          val child = peekPath(path)
          if (child != null)
            SimpleConfig.checkValid(path, refValue, child, problems)
          else SimpleConfig.addMissing(problems, refValue, path, origin)
        }
      }
    if (!problems.isEmpty) throw new ConfigException.ValidationFailed(problems)
  }
  override def withOnlyPath(pathExpression: String): SimpleConfig = {
    val path = Path.newPath(pathExpression)
    new SimpleConfig(root.withOnlyPath(path))
  }
  override def withoutPath(pathExpression: String): SimpleConfig = {
    val path = Path.newPath(pathExpression)
    new SimpleConfig(root.withoutPath(path))
  }
  override def withValue(
      pathExpression: String,
      v: ConfigValue
  ): SimpleConfig = {
    val path = Path.newPath(pathExpression)
    new SimpleConfig(root.withValue(path, v))
  }
  private[impl] def atKey(origin: ConfigOrigin, key: String) =
    root.atKey(origin, key)
  override def atKey(key: String): SimpleConfig = root.atKey(key)
  override def atPath(path: String): Config     = root.atPath(path)
  // serialization all goes through SerializedConfigValue
  @throws[ObjectStreamException]
  private def writeReplace(): AnyRef = new SerializedConfigValue(this)
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy