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

java.util.jar.Manifest.scala Maven / Gradle / Ivy

package java.util.jar

// Ported from Apache Harmony

import java.io.{ByteArrayOutputStream, IOException, InputStream, OutputStream}
import java.nio.{ByteBuffer, CharBuffer}
import java.nio.charset.{CharsetEncoder, CoderResult, StandardCharsets}
import java.util.{HashMap, Map}

class Manifest() extends Cloneable {

  private var mainAttributes: Attributes = new Attributes()
  private var entries: Map[String, Attributes] =
    new HashMap[String, Attributes]
  private var chunks: Map[String, Manifest.Chunk] = null
  private var im: InitManifest                    = null
  private var mainEnd: Int                        = 0

  def this(is: InputStream) = {
    this()
    read(is)
  }

  def this(man: Manifest) = {
    this()
    mainAttributes = man.mainAttributes.clone().asInstanceOf[Attributes]
    entries = man
      .getEntries()
      .asInstanceOf[HashMap[String, Attributes]]
      .clone()
      .asInstanceOf[HashMap[String, Attributes]]
  }

  private[jar] def this(is: InputStream, readChunks: Boolean) = {
    this()
    if (readChunks) {
      chunks = new HashMap[String, Manifest.Chunk]
    }
    read(is)
  }

  def clear(): Unit = {
    im = null
    entries.clear()
    mainAttributes.clear()
  }

  def getAttributes(name: String): Attributes =
    getEntries().get(name)

  def getEntries(): Map[String, Attributes] =
    entries

  def getMainAttributes(): Attributes =
    mainAttributes

  override def clone(): Object =
    new Manifest(this)

  def write(os: OutputStream): Unit =
    Manifest.write(this, os)

  def read(is: InputStream): Unit = {
    val buf: Array[Byte] = Manifest.readFully(is)
    if (buf.length != 0) {
      // replace EOF and NUL with another new line
      val b = buf(buf.length - 1)
      if (0 == b || 26 == b) {
        buf(buf.length - 1) = '\n'
      }

      // Attributes.Name.MANIFEST_VERSION is not used for
      // the second parameter for RI compatibility
      im = new InitManifest(buf, mainAttributes, null)
      mainEnd = im.getPos()
      // FIXME (from Apache Harmony, no more details)
      im.initEntries(entries, chunks)
      im = null
    }
  }

  override def hashCode(): Int =
    mainAttributes.hashCode() ^ getEntries().hashCode()

  override def equals(o: Any): Boolean =
    o match {
      case other: Manifest =>
        mainAttributes == other.mainAttributes && getEntries() == other
          .getEntries()
      case _ =>
        false
    }

  private[jar] def getChunk(name: String): Manifest.Chunk =
    chunks.get(name)

  private[jar] def removeChunks(): Unit =
    chunks = null

  private[jar] def getMainAttributesEnd(): Int =
    mainEnd
}

object Manifest {
  private[jar] class Chunk(val start: Int, val end: Int)

  private[jar] final val LINE_LENGTH_LIMIT: Int  = 72
  private final val LINE_SEPARATOR: Array[Byte]  = Array[Byte]('\r', '\n')
  private final val VALUE_SEPARATOR: Array[Byte] = Array[Byte](':', ' ')
  private final val NAME_ATTRIBUTE: Attributes.Name =
    new Attributes.Name("Name")

  private[jar] def write(manifest: Manifest, out: OutputStream): Unit = {
    val encoder = StandardCharsets.UTF_8.newEncoder()
    val buffer  = ByteBuffer.allocate(4096)

    val version =
      manifest.mainAttributes.getValue(Attributes.Name.MANIFEST_VERSION)
    if (version != null) {
      writeEntry(out,
                 Attributes.Name.MANIFEST_VERSION,
                 version,
                 encoder,
                 buffer)
      val entries = manifest.mainAttributes.keySet().iterator()
      while (entries.hasNext()) {
        val name = entries.next().asInstanceOf[Attributes.Name]
        if (name != Attributes.Name.MANIFEST_VERSION) {
          writeEntry(out,
                     name,
                     manifest.mainAttributes.getValue(name),
                     encoder,
                     buffer)
        }
      }
    }
    out.write(LINE_SEPARATOR)
    val i = manifest.getEntries.keySet.iterator
    while (i.hasNext) {
      val key = i.next()
      writeEntry(out, NAME_ATTRIBUTE, key, encoder, buffer)
      val attrib  = manifest.entries.get(key)
      val entries = attrib.keySet().iterator()
      while (entries.hasNext()) {
        val name = entries.next().asInstanceOf[Attributes.Name]
        writeEntry(out, name, attrib.getValue(name), encoder, buffer)
      }
      out.write(LINE_SEPARATOR)
    }
  }

  private def writeEntry(os: OutputStream,
                         name: Attributes.Name,
                         value: String,
                         encoder: CharsetEncoder,
                         bBuf: ByteBuffer): Unit = {
    val out = name.getBytes()
    if (out.length > LINE_LENGTH_LIMIT) {
      throw new IOException(
        s"""A length of the encoded header name "$name" exceeded maximum length $LINE_LENGTH_LIMIT""")
    } else {
      os.write(out)
      os.write(VALUE_SEPARATOR)

      encoder.reset()
      bBuf.clear().limit(LINE_LENGTH_LIMIT - out.length - 2)

      val cBuf = CharBuffer.wrap(value)
      var done = false
      while (!done) {
        var r = encoder.encode(cBuf, bBuf, true)
        if (CoderResult.UNDERFLOW == r) {
          r = encoder.flush(bBuf)
        }
        os.write(bBuf.array(), bBuf.arrayOffset(), bBuf.position())
        os.write(LINE_SEPARATOR)
        if (CoderResult.UNDERFLOW == r) {
          done = true
        } else {
          os.write(' ')
          bBuf.clear().limit(LINE_LENGTH_LIMIT - 1)
        }
      }
    }
  }

  private def readFully(is: InputStream): Array[Byte] = {
    // Initial read
    val buffer   = new Array[Byte](4096)
    var count    = is.read(buffer)
    val nextByte = is.read()

    // Did we get it all in one read?
    if (nextByte == -1) {
      val dest = new Array[Byte](count)
      System.arraycopy(buffer, 0, dest, 0, count)
      dest
    } else {
      // Does it look like a manifest?
      if (!containsLine(buffer, count)) {
        throw new IOException("Manifest is too long")
      } else {
        val baos = new ByteArrayOutputStream(count * 2)
        var done = false
        baos.write(buffer, 0, count)
        baos.write(nextByte)
        while (!done) {
          count = is.read(buffer)
          if (count == -1) {
            done = true
          } else {
            baos.write(buffer, 0, count)
          }
        }
        baos.toByteArray()
      }
    }
  }

  private def containsLine(buffer: Array[Byte], length: Int) = {
    var i      = 0
    var result = false
    while (!result && i < length) {
      if (buffer(i) == 0x0A || buffer(i) == 0x0D) {
        result = true
      }
      i += 1
    }
    result
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy