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

coursier.version.VersionConstraint.scala Maven / Gradle / Ivy

There is a newer version: 0.5.0
Show newest version
package coursier.version

import dataclass.data

import scala.annotation.tailrec

sealed abstract class VersionConstraint extends Product with Serializable with Ordered[VersionConstraint] {
  def asString: String
  def interval: VersionInterval

  /**
   * Preferred version, if any
   */
  def preferred: Option[Version]

  def latest: Option[Latest]

  def generateString: String =
    VersionConstraint.generateString(interval, preferred, latest)

  private lazy val compareKey = preferred.headOption.orElse(interval.from).getOrElse(Version.zero)
  def compare(other: VersionConstraint): Int =
    compareKey.compare(other.compareKey)

  def isValid: Boolean =
    interval.isValid && preferred.forall { v =>
      interval.contains(v) ||
        interval.to.forall { to =>
          val cmp = v.compare(to)
          cmp < 0 || (cmp == 0 && interval.toIncluded)
        }
    }

  def withLatest(latestOpt: Option[Latest]): VersionConstraint
}

object VersionConstraint {
  def apply(version: String): VersionConstraint =
    Lazy(version)
  def from(interval: VersionInterval, preferred: Option[Version], latest: Option[Latest]): VersionConstraint =
    Eager(
      generateString(interval, preferred, latest),
      interval,
      preferred,
      latest
    )

  def empty: VersionConstraint =
    empty0

  private def generateString(interval: VersionInterval, preferred: Option[Version], latest: Option[Latest]): String = {

    val nonIntervalPartOpt =
      if (preferred.isEmpty && latest.isEmpty) None
      else Some((preferred.iterator.map(_.asString) ++ latest.iterator.map(_.asString)).mkString(";"))

    if (interval == VersionInterval.zero)
      nonIntervalPartOpt.getOrElse("")
    else
      (Iterator(interval.repr) ++ nonIntervalPartOpt.iterator).mkString("&")
  }

  def fromVersion(version: Version): VersionConstraint =
    Eager(
      version.repr,
      VersionInterval.zero,
      Some(version),
      None
    )

  def merge(constraints: VersionConstraint*): Option[VersionConstraint] =
    if (constraints.isEmpty) Some(empty)
    else if (constraints.lengthCompare(1) == 0) Some(constraints.head).filter(_.isValid)
    else {
      val intervals = constraints.map(_.interval)

      val intervalOpt =
        intervals.foldLeft(Option(VersionInterval.zero)) {
          case (acc, itv) =>
            acc.flatMap(_.merge(itv))
        }

      val constraintOpt = intervalOpt.map { interval =>
        val preferreds = {
          val allPreferred = constraints.flatMap(_.preferred)
          interval.from match {
            case Some(from) =>
              allPreferred.filter { v =>
                val cmp = from.compare(v)
                cmp < 0 || (cmp == 0 && interval.fromIncluded)
              }
            case None =>
              allPreferred
          }
        }
        val latests = constraints.flatMap(_.latest)
        from(
          interval,
          Some(preferreds).filter(_.nonEmpty).map(_.max),
          Some(latests).filter(_.nonEmpty).map(_.max)
        )
      }

      constraintOpt.filter(_.isValid)
    }

  // 1. sort constraints in ascending order.
  // 2. from the right, merge them two-by-two with the merge method above
  // 3. return the last successful merge
  def relaxedMerge(constraints: VersionConstraint*): VersionConstraint =
    constraints.toList.sorted.reverse match {
      case Nil      => VersionConstraint.empty
      case h :: Nil => h
      case h :: t   =>

        @tailrec
        def mergeByTwo(head: VersionConstraint, rest: List[VersionConstraint]): VersionConstraint =
          rest match {
            case next :: xs =>
              merge(head, next) match {
                case Some(success) => mergeByTwo(success, xs)
                case _             => head
              }
            case Nil => head
          }

        val latestOpt = {
          val it = constraints.iterator.flatMap(_.latest.iterator)
          if (it.hasNext) Some(it.max) else None
        }

        val merged = mergeByTwo(h, t)

        if (merged.latest == latestOpt) merged
        else merged.withLatest(latestOpt)
    }


  private[version] def fromPreferred(input: String, version: Version): Eager =
    Eager(input, VersionInterval.zero, Some(version), None)
  private[version] def fromInterval(input: String, interval: VersionInterval): Eager =
    Eager(input, interval, None, None)
  private[version] def fromLatest(input: String, latest: Latest): Eager =
    Eager(input, VersionInterval.zero, None, Some(latest))

  private[version] val empty0 = Eager("", VersionInterval.zero, None, None)

  private[coursier] val parsedValueAsToString: ThreadLocal[Boolean] = new ThreadLocal[Boolean] {
    override protected def initialValue(): Boolean =
      false
  }

  @data class Lazy(asString: String) extends VersionConstraint {
    private var parsed0: Eager = null
    private def parsed = {
      if (parsed0 == null)
        parsed0 = VersionParse.eagerVersionConstraint(asString)
      parsed0
    }
    def interval: VersionInterval = parsed.interval
    def preferred: Option[Version] = parsed.preferred
    def latest: Option[Latest] = parsed.latest

    override def toString: String =
      if (parsedValueAsToString.get()) asString
      else
        s"VersionConstraint.Lazy($asString, ${if (parsed0 == null) "[unparsed]" else s"$interval, $preferred, $latest"})"
    override def hashCode(): Int =
      (VersionConstraint, asString).hashCode()
    override def equals(obj: Any): Boolean =
      obj.isInstanceOf[VersionConstraint] && {
        val other = obj.asInstanceOf[VersionConstraint]
        asString == other.asString
      }

    def withLatest(latestOpt: Option[Latest]): VersionConstraint =
      parsed.withLatest(latestOpt)
  }
  @data class Eager(
    asString: String,
    interval: VersionInterval,
    preferred: Option[Version],
    latest: Option[Latest]
  ) extends VersionConstraint {
    override def toString: String =
      if (parsedValueAsToString.get()) asString
      else
        s"VersionConstraint.Eager($asString, $interval, $preferred, $latest)"
    override def hashCode(): Int =
      (VersionConstraint, asString).hashCode()
    override def equals(obj: Any): Boolean =
      obj.isInstanceOf[VersionConstraint] && {
        val other = obj.asInstanceOf[VersionConstraint]
        asString == other.asString
      }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy