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

scala.scalanative.io.VirtualDirectory.scala Maven / Gradle / Ivy

package scala.scalanative
package io

import java.io.Writer
import java.net.URI
import java.nio.ByteBuffer
import java.nio.file._
import java.nio.channels._
import java.util.HashMap
import scalanative.util.{Scope, acquire, defer}

sealed trait VirtualDirectory {

  /** A unique identifier for this directory */
  def uri: URI

  /** Java NIO Path pointing to underlying directory */
  def path: Path

  /** Check if file with given path is in the directory. */
  def contains(path: Path): Boolean =
    files.contains(path)

  /** Reads a contents of file with given path. */
  def read(path: Path): ByteBuffer

  /** Reads up to len bytes from the file with the given path. */
  def read(path: Path, len: Int): ByteBuffer

  /** Replaces contents of file with given value. */
  def write(path: Path, buffer: ByteBuffer): Unit

  /** Replaces contents of file using given writer. */
  def write(path: Path)(fn: Writer => Unit): Path

  /** List all files in this directory. */
  def files: Seq[Path]

  /** Merges content of source paths into single file in target */
  def merge(sources: Seq[Path], target: Path): Path

  /** Returns a Java NIO path matcher for given pattern based on underlying file
   *  system
   */
  def pathMatcher(pattern: String): PathMatcher
}

object VirtualDirectory {

  /** Real, non-virtual directory on local file system. */
  def local(file: Path): VirtualDirectory = {
    def absolute = file.toAbsolutePath
    assert(Files.exists(file), s"Local directory doesn't exist: $absolute")
    assert(Files.isDirectory(file), s"Not a directory: $absolute")

    new LocalDirectory(file)
  }

  /** Virtual directory that represents contents of the jar file. */
  def jar(file: Path)(implicit in: Scope): VirtualDirectory = {
    val absolute = file.toAbsolutePath
    assert(Files.exists(file), s"Jar doesn't exist: $absolute")
    assert(absolute.toString.endsWith(".jar"), s"Not a jar: $absolute")

    new JarDirectory(file)
  }

  /** Virtual directory based on either local directory or a jar. */
  def real(file: Path)(implicit in: Scope): VirtualDirectory =
    if (Files.isDirectory(file)) {
      local(file)
    } else if (file.toString.endsWith(".jar")) {
      jar(file)
    } else {
      throw new UnsupportedOperationException(
        "Neither a jar, nor a directory: " + file
      )
    }

  /** Empty directory that contains no files. */
  val empty: VirtualDirectory = EmptyDirectory

  private trait NioDirectory extends VirtualDirectory {
    protected def resolve(path: Path): Path = path

    protected def open(path: Path) =
      FileChannel.open(
        path,
        StandardOpenOption.CREATE,
        StandardOpenOption.WRITE,
        StandardOpenOption.TRUNCATE_EXISTING
      )

    override def read(path: Path): ByteBuffer = {
      val bytes = Files.readAllBytes(resolve(path))
      val buffer = ByteBuffer.wrap(bytes)
      buffer
    }

    override def read(path: Path, len: Int): ByteBuffer = {
      val stream = Files.newInputStream(resolve(path))
      try {
        val bytes = new Array[Byte](len)
        val read = stream.read(bytes)
        ByteBuffer.wrap(bytes, 0, read)
      } finally stream.close()
    }

    override def write(path: Path)(fn: Writer => Unit): Path = {
      val fullPath = resolve(path)
      Files.createDirectories(fullPath.getParent())
      val writer = Files.newBufferedWriter(fullPath)
      try fn(writer)
      finally writer.close()
      fullPath
    }

    override def write(path: Path, buffer: ByteBuffer): Unit = {
      val fullPath = resolve(path)
      Files.createDirectories(fullPath.getParent())
      val channel = open(fullPath)
      try channel.write(buffer)
      finally channel.close
    }

    override def merge(sources: Seq[Path], target: Path): Path = {
      val outputPath = resolve(target)
      Files.createDirectories(outputPath.getParent())
      val output = FileChannel.open(
        outputPath,
        StandardOpenOption.CREATE,
        StandardOpenOption.WRITE,
        StandardOpenOption.APPEND
      )
      try {
        sources.foreach { path =>
          val input = FileChannel.open(
            resolve(path),
            StandardOpenOption.READ,
            StandardOpenOption.DELETE_ON_CLOSE
          )
          try {
            input.transferTo(0, input.size(), output)
          } finally input.close()
        }
      } finally output.close()
      outputPath
    }
  }

  private final class LocalDirectory(override val path: Path)
      extends NioDirectory {

    def uri: URI = path.toUri

    override protected def resolve(path: Path): Path =
      this.path.resolve(path)

    override def files: Seq[Path] =
      jIteratorToSeq {
        Files
          .walk(path, Integer.MAX_VALUE, FileVisitOption.FOLLOW_LINKS)
          .iterator()
      }
        .filter(Files.isRegularFile(_))
        .map(fp => path.relativize(fp))

    override def pathMatcher(pattern: String): PathMatcher =
      path.getFileSystem().getPathMatcher(pattern)
  }

  private final class JarDirectory(override val path: Path)(implicit in: Scope)
      extends NioDirectory {
    def uri: URI = URI.create(s"jar:${path.toUri}")
    private val fileSystem: FileSystem =
      acquire {
        try {
          val params = new HashMap[String, String]()
          params.put("create", "false")
          FileSystems.newFileSystem(uri, params)
        } catch {
          case e: FileSystemAlreadyExistsException =>
            FileSystems.getFileSystem(uri)
        }
      }

    override def pathMatcher(pattern: String): PathMatcher =
      fileSystem.getPathMatcher(pattern)

    override def files: Seq[Path] = {
      val roots = jIteratorToSeq(fileSystem.getRootDirectories.iterator())

      roots
        .flatMap { path =>
          jIteratorToSeq {
            Files
              .walk(path, Integer.MAX_VALUE, FileVisitOption.FOLLOW_LINKS)
              .iterator()
          }.filter(Files.isRegularFile(_))
        }
    }
  }

  private object EmptyDirectory extends VirtualDirectory {
    override def path: Path = Paths.get("")
    val uri: URI = URI.create("")

    override def files = Seq.empty

    override def read(path: Path): ByteBuffer =
      throw new UnsupportedOperationException(
        "Can't read from empty directory."
      )

    override def read(path: Path, len: Int): ByteBuffer = read(path)

    override def write(path: Path)(fn: Writer => Unit): Path =
      throw new UnsupportedOperationException("Can't write to empty directory.")

    override def write(path: Path, buffer: ByteBuffer): Unit =
      throw new UnsupportedOperationException("Can't write to empty directory.")

    override def merge(sources: Seq[Path], target: Path): Path =
      throw new UnsupportedOperationException("Can't merge in empty directory.")

    override def pathMatcher(pattern: String): PathMatcher =
      throw new UnsupportedOperationException("Can't match in empty directory.")
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy