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

org.scalajs.linker.PathIRFile.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.linker

import scala.concurrent._

import java.io._
import java.nio._
import java.nio.channels._
import java.nio.charset.StandardCharsets
import java.nio.file._
import java.nio.file.attribute._

import org.scalajs.ir
import org.scalajs.linker.interface.IRFile
import org.scalajs.linker.interface.unstable.IRFileImpl

object PathIRFile {
  def apply(path: Path)(implicit ec: ExecutionContext): Future[IRFile] = {
    Future(blocking(Files.getLastModifiedTime(path)))
      .map(new PathIRFileImpl(path, _))
  }

  private[linker] def fileTimeToVersion(time: FileTime): ir.Version = {
    // FileTime.toString seems to be the only lossless way to get a byte string.
    ir.Version.fromBytes(time.toString().getBytes(StandardCharsets.US_ASCII))
  }

  private[linker] final class PathIRFileImpl(path: Path, lastModified: FileTime)
      extends IRFileImpl(path.toString, fileTimeToVersion(lastModified)) {
    def entryPointsInfo(implicit ec: ExecutionContext): Future[ir.EntryPointsInfo] = {
      def loop(chan: AsynchronousFileChannel, buf: ByteBuffer): Future[ir.EntryPointsInfo] = {
        readAsync(chan, buf).map { _ =>
          buf.flip()
          ir.Serializers.deserializeEntryPointsInfo(buf)
        }.recoverWith {
          case _: BufferUnderflowException =>
            // Reset to write again.
            buf.position(buf.limit())
            buf.limit(buf.capacity())

            val newBuf = if (buf.remaining() <= 0) {
              val newBuf = ByteBuffer.allocate(buf.capacity() * 2)
              buf.flip()
              newBuf.put(buf)
              buf
            } else {
              buf
            }

            loop(chan, newBuf)
        }
      }

      withChannel(loop(_, ByteBuffer.allocate(1024)))
    }

    def tree(implicit ec: ExecutionContext): Future[ir.Trees.ClassDef] = {
      withChannel { chan =>
        val s = chan.size()
        if (s > Int.MaxValue) {
          throw new IOException(s"$path is too big ($s bytes)")
        } else {
          val buf = ByteBuffer.allocate(s.toInt)
          def read(): Future[Unit] = readAsync(chan, buf).flatMap { _ =>
            if (buf.hasRemaining()) read()
            else Future.successful(())
          }

          read().map { _ =>
            buf.flip()
            ir.Serializers.deserialize(buf)
          }
        }
      }
    }

    private def withChannel[T](body: AsynchronousFileChannel => Future[T])(
        implicit ec: ExecutionContext): Future[T] = {
      val result = Future(AsynchronousFileChannel.open(path)).flatMap { chan =>
        body(chan).finallyWith(Future(blocking(chan.close())))
      }

      IRFileImpl.withPathExceptionContext(path.toString, result)
    }
  }

  private def readAsync(chan: AsynchronousFileChannel, buf: ByteBuffer): Future[Unit] = {
    val promise = Promise[Unit]()
    chan.read(buf, buf.position(), promise, ReadCompletionHandler)
    promise.future
  }

  private object ReadCompletionHandler extends CompletionHandler[Integer, Promise[Unit]] {
    def completed(result: Integer, attachment: Promise[Unit]): Unit = {
      if (result <= 0)
        attachment.failure(new EOFException)
      else
        attachment.success(())
    }

    def failed(exc: Throwable, attachment: Promise[Unit]): Unit =
      attachment.failure(exc)
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy