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

org.scalajs.ir.Version.scala Maven / Gradle / Ivy

/*
 * Scala.js (https://www.scala-js.org/)
 *
 * Copyright EPFL.
 *
 * Licensed under Apache License 2.0
 * (https://www.apache.org/licenses/LICENSE-2.0).
 *
 * See the NOTICE file distributed with this work for
 * additional information regarding copyright ownership.
 */

package org.scalajs.ir

import java.util.Arrays
import java.io.OutputStream
import java.nio.ByteBuffer

/** A version of a thing
 *
 *  Versions are always optional, [[Version.Unversioned]] being the sentinel.
 *
 *  The remaining versions come in two fundamentally different flavors:
 *  - Hashes: They are stable in serialized form, [[Serializers]] will write
 *        them to IR files. The only way to create these versions is via
 *        [[Hashers]].
 *  - Non hashes: Not guaranteed to be stable / collision free across different
 *        programs. Never written to IR files.
 */
final class Version private (private val v: Array[Byte]) extends AnyVal {
  import Version.Type

  /** Checks whether two versions are known to be the same.
   *
   *  Returns false if either of the versions is [[Version.Unversioned]]
   */
  def sameVersion(that: Version): Boolean = {
    if (!this.isVersioned || !that.isVersioned) false
    else Arrays.equals(this.v, that.v)
  }

  private[ir] def isHash: Boolean = isVersioned && v(0) == Type.Hash

  private[ir] def writeHash(out: OutputStream): Unit = {
    require(isHash)
    out.write(v, 1, 20)
  }

  @inline
  private def isVersioned: Boolean = v != null

  // For debugging purposes
  override def toString(): String = {
    if (v == null) {
      "Unversioned"
    } else {
      val typeByte = v(0)
      val otherBytesStr = v.iterator.drop(1).map(b => "%02x".format(b & 0xff)).mkString
      s"Version($typeByte, $otherBytesStr)"
    }
  }
}

object Version {
  private object Type {
    val Hash: Byte = 0x00
    val Ephemeral: Byte = 0x02
    val Combined: Byte = 0x03
  }

  val Unversioned: Version = new Version(null)

  /** Create a non-hash version from the given bytes.
   *
   *  Guaranteed to differ from:
   *  - all hash versions.
   *  - versions returned from [[combine]].
   *  - versions with different bytes.
   */
  def fromBytes(bytes: Array[Byte]): Version =
    make(Type.Ephemeral, bytes)

  /** Create a non-hash version from an Int.
   *
   *  Strictly equivalent to (but potentially more efficient):
   *  {{{
   *  fromBytes(ByteBuffer.allocate(4).putInt(i).array())
   *  }}}
   */
  def fromInt(i: Int): Version = {
    val buf = ByteBuffer.allocate(5)
    buf.put(Type.Ephemeral)
    buf.putInt(i)
    new Version(buf.array())
  }

  /** Create a non-hash version from a Long.
   *
   *  Strictly equivalent to (but potentially more efficient):
   *  {{{
   *  fromBytes(ByteBuffer.allocate(8).putLong(i).array())
   *  }}}
   */
  def fromLong(l: Long): Version = {
    val buf = ByteBuffer.allocate(9)
    buf.put(Type.Ephemeral)
    buf.putLong(l)
    new Version(buf.array())
  }

  /** Create a non-hash version from the given [[UTF8String]].
   *
   *  Strictly equivalent to (but potentially more efficient):
   *  {{{
   *  fromBytes(Array.tabulate(utf8String.length)(utf8String(_)))
   *  }}}
   */
  def fromUTF8String(utf8String: UTF8String): Version =
    make(Type.Ephemeral, utf8String.bytes)

  /** Create a combined, non-hash version from the given bytes.
   *
   *  Returns [[Unversioned]] if at least one of versions is [[Unversioned]].
   *
   *  The returned version is to differ from:
   *  - all hash versions.
   *  - all non-hash versions created with `from` methods.
   *  - combined versions created with different (ordered) version lists
   *    (including the empty list).
   *
   *  @note This can be used to create tagged versions (for alternatives):
   *    {{{
   *    Versions.combine(Versions.fromInt(0), underlying)
   *    }}}
   */
  def combine(versions: Version*): Version = {
    if (versions.forall(_.isVersioned)) {
      val buf = ByteBuffer.allocate(1 + 4 + versions.map(_.v.length + 4).sum)

      buf.put(Type.Combined)
      buf.putInt(versions.length)

      for (version <- versions) {
        buf.putInt(version.v.length)
        buf.put(version.v)
      }

      new Version(buf.array())
    } else {
      Unversioned
    }
  }

  private[ir] def fromHash(hash: Array[Byte]): Version = {
    require(hash.length == 20)
    make(Type.Hash, hash)
  }

  private def make(tpe: Byte, bytes: Array[Byte]): Version = {
    val len = bytes.length
    val v = new Array[Byte](len + 1)
    v(0) = tpe

    System.arraycopy(bytes, 0, v, 1, len)
    new Version(v)
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy