Как использовать java.nio.file.Files.walkFileTree в Scala - PullRequest
5 голосов
/ 19 января 2012

Я хочу использовать новый java.nio.file.Files.walkFileTree в Scala. И я был даже успешным:

class Visitor
   extends
      java.nio.file.SimpleFileVisitor [java.nio.file.Path]
   {
   override def visitFile(
      File : java.nio.file.Path,
      Attrs : java.nio.file.attribute.BasicFileAttributes) : java.nio.file.FileVisitResult =
   {
      if (! File.toString.contains(".svn"))
      {
         System.out.println(File);
      } // if

      java.nio.file.FileVisitResult.CONTINUE;
   } // visitFile
} // Visitor

java.nio.file.Files.walkFileTree (Project_Home, new Visitor)

Но хотя этот код работает нормально, я чувствую себя немного похожим на перенос парадигм Java в Scala. Итак, вопрос к истинным Scala Gurus: могу ли я что-то улучшить или это просто так?

Ответы [ 6 ]

6 голосов
/ 19 января 2012

Посетитель действительно foreach без использования функций, поэтому давайте сделаем foreach.Метод является статическим, но он принимает в качестве первого аргумента Path, поэтому мы обогатим Path методом foreach, что делается примерно так:

import java.nio.file._
import java.nio.file.attribute.BasicFileAttributes

implicit def fromNioPath(path: Path): TraverseFiles = new TraversePath(path)

И всеelse находится внутри класса TraversePath, который выглядит примерно так:

class TraversePath(path: Path) {
  def foreach(f: (Path, BasicFileAttributes) => Unit) {
    // ...
  }
}

Этого вам достаточно, чтобы написать это:

ProjectHome foreach ((file, _) => if (!file.toString.contains(".svn")) println(File))

Конечно, на самом деле это не таксделайте что-нибудь, поэтому давайте заставим это сделать что-то:

class TraversePath(path: Path) {
  def foreach(f: (Path, BasicFileAttributes) => Unit) {
    class Visitor extends SimpleFileVisitor[Path] {
      override def visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult = try { 
        f(file, attrs)
        FileVisitResult.CONTINUE
      } catch { 
        case _ => FileVisitResult.TERMINATE
      }
    }
    Files.walkFileTree(path, new Visitor)
  }
}

Теперь эта строка будет делать то же самое, что и ваш код!Тем не менее, мы можем улучшить его дальше.Бывает, что foreach - единственный метод, необходимый для Traversable, поэтому мы можем расширить этот класс и получить все методы коллекции Scala!

Единственная проблема заключается в том, что функция Traversable.foreach принимаеттолько один аргумент, а здесь мы принимаем два.Мы можем изменить его на получение кортежа.Вот полный код:

import java.nio.file._
import java.nio.file.attribute.BasicFileAttributes
import scala.collection.Traversable

// Make it extend Traversable
class TraversePath(path: Path) extends Traversable[(Path, BasicFileAttributes)] {

  // Make foreach receive a function from Tuple2 to Unit
  def foreach(f: ((Path, BasicFileAttributes)) => Unit) {
    class Visitor extends SimpleFileVisitor[Path] {
      override def visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult = try {
        // Pass a tuple to f
        f(file -> attrs)
        FileVisitResult.CONTINUE
      } catch { 
        case _ => FileVisitResult.TERMINATE
      }
    }
    Files.walkFileTree(path, new Visitor)
  }
}

ProjectHome foreach {
  // use case to seamlessly deconstruct the tuple
  case (file, _) => if (!file.toString.contains(".svn")) println(File)
}

Отказ от ответственности: я не тестировал ни один из этого кода, потому что у меня не установлена ​​Java 7.Возможно, есть ошибки.

2 голосов
/ 29 января 2013

Взяв ответ Даниэля за основу, я немного поработал, чтобы сделать Path доступным с удобными последствиями, так как вы используются в коллекциях. Обратите внимание, что не все функции включены.

class TraversePath(path: Path) {
    def foreach(f: (Path, BasicFileAttributes) => Unit) {
        Files.walkFileTree(path, new SimpleFileVisitor[Path] {
            override def visitFile(file: Path, attrs: BasicFileAttributes) = {
                f(file, attrs)
                FileVisitResult.CONTINUE
            }
        })
    }

    /**
     * foreach that takes FileVisitResult instead of Unit
     */
    def foreach2(f: (Path, BasicFileAttributes) => FileVisitResult) {
        Files.walkFileTree(path, new SimpleFileVisitor[Path] {
            override def visitFile(file: Path, attrs: BasicFileAttributes) = f(file, attrs)
        })
    }

    def foldLeft[T](t: T)(f: (T, Path) => T) = {
        var current = t
        foreach((p, _) => current = f(current, p))
        current
    }

    def forall(f: Path => Boolean) = {
        var ret = true
        foreach2((p, _) =>
            if ( !f(path) ) {
                ret = false
                FileVisitResult.TERMINATE
            }
            else
                FileVisitResult.CONTINUE
        )
        ret
    }

    def exists(f: Path => Boolean) = {
        var ret = false
        foreach2((p, _) =>
            if ( f(path) ) {
                ret = true
                FileVisitResult.TERMINATE
            }
            else
                FileVisitResult.CONTINUE
        )
    }

    /**
     * Directly modifies the underlying path.
     */
    def mapReal(f: Path => Path) = foreach((p, _) => Files.move(p, f(p)))

    /**
     * @param f map function
     * @return a left-folded list with the map function applied to each element
     */
    def map(f: Path => Path) = foldLeft(Nil: List[Path]) {
        case (xs, p) => xs ::: f(p) :: Nil
    }

    def find(f: Path => Boolean) = {
        var k = None: Option[Path]
        foreach2((p, _) =>
            if ( f(p) ) {
                k = Some(p)
                FileVisitResult.TERMINATE
            } else FileVisitResult.CONTINUE
        )
        k
    }
}

implicit def fromNioPath(path: Path) = new TraversePath(path)

API java.nio чрезвычайно мощен и, IMHO, очень достаточен для использования с Scala. С этими последствиями (и более того, если вы хотите написать некоторые функции) очень просто выполнять еще более сложные задачи.

Вы можете использовать это сейчас, написав что-то вроде этого:

val path1 = Paths.get(sys.props("user.home"), "workspace")

val path2 = Paths.get(sys.props("user.home"), "workspace2")

val list1 = path1.foldLeft(Nil: List[Path]) {
    (xs, p) => xs ::: path1.relativize(p) :: Nil
}
val list2 = path2.foldLeft(Nil: List[Path]) {
    (xs, p) => xs ::: path2.relativize(p) :: Nil
}
(list1 diff list2) foreach println

С уважением,
Danyel

2 голосов
/ 25 апреля 2012

Вот сценарий Дэниела, скомпилированный:

import java.nio.file._
import java.nio.file.attribute.BasicFileAttributes
import scala.collection.Traversable

// Make it extend Traversable
class TraversePath(path: Path) extends Traversable[(Path, BasicFileAttributes)] {

  // Make foreach receive a function from Tuple2 to Unit
  def foreach[U](f: ((Path, BasicFileAttributes)) => U) {
    class Visitor extends SimpleFileVisitor[Path] {
      override def visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult = try {
        // Pass a tuple to f
        f(file -> attrs)
        FileVisitResult.CONTINUE
      } catch { 
        case _ => FileVisitResult.TERMINATE
      }
    }
    Files.walkFileTree(path, new Visitor)
  }
}
val projectHome = new TraversePath(Paths.get("."))

projectHome foreach { 
  // use case to seamlessly deconstruct the tuple
  case (file:Path, attr:BasicFileAttributes) => if (!file.toString.contains(".svn")) println(file)
}
2 голосов
/ 19 января 2012

Вы можете сделать свой код немного более симпатичным, но в конце концов он все равно будет выглядеть как обычный старый шаблон посетителей.

0 голосов
/ 13 января 2017

Распространение идей других постов.Мне нравится решение, в котором мы можем сопоставить классы дел.Следующий код просто возвращает коллекцию строк для различных событий, которые посетитель должен был бы вызвать.

FileWalker(java.nio.file.Paths.get(" your dir ")).map({
    case PreVisitDirectory(dir, atts) => s"about to visit dir ${dir}"
    case PostVisitDirectory(dir, exc) => s"have visited dir ${dir}"
    case VisitFile(file, attrs) => s"visiting file ${file}"
    case VisitFileFailed(file, exc) => s"failed to visit ${file}"
})

Реализация FileWalker:

import java.io.IOException
import java.nio.file.{FileVisitResult, Files, Path, SimpleFileVisitor}
import java.nio.file.attribute.BasicFileAttributes

trait FileVisitEvent
case class PreVisitDirectory(path: Path, atts: BasicFileAttributes) extends FileVisitEvent
case class PostVisitDirectory(dir: Path, exc: IOException) extends FileVisitEvent
case class VisitFile(file: Path, attrs: BasicFileAttributes) extends FileVisitEvent
case class VisitFileFailed(file: Path, exc: IOException) extends FileVisitEvent

/**
  * Scala style walker for a directory tree
  *
  * Is a treversable over the tree which traverses different event types extending {{FileVisitEvent}}
  *
  * @param from
  */
class FileWalker(from: Path) extends Traversable[FileVisitEvent] {
  // just to simplify error handling
  def wrapper(x: => Unit): FileVisitResult = try {
    x
    FileVisitResult.CONTINUE
  }
  catch {
    case _ : Throwable => FileVisitResult.TERMINATE
  }

  override def foreach[U](f: (FileVisitEvent) => U): Unit = {
    Files.walkFileTree(from, new SimpleFileVisitor[Path] {
      override def preVisitDirectory(dir: Path, atts: BasicFileAttributes): FileVisitResult =
        wrapper( f(PreVisitDirectory(dir, atts)))

      override def postVisitDirectory(dir: Path, exc: IOException): FileVisitResult =
        wrapper(f(PostVisitDirectory(dir, exc)))

      override def visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult =
        wrapper(f(VisitFile( file, attrs ) ))

      override def visitFileFailed(file: Path, exc: IOException): FileVisitResult =
        wrapper( f(VisitFileFailed( file, exc ) ))
    })
  }
}

object FileWalker {
  def apply( from : Path ) = new FileWalker( from )
}
0 голосов
/ 06 июня 2014

Пример FIles.walkFileTree для сравнения двух каталогов / синхронизации двух каталогов по разнице файлов

private static void compareDirectories(String srcPath, String destPath) throws IOException, InterruptedException {
    System.out.println("sync. started....");
    final Path mainDir = Paths.get(srcPath);
    final Path otherDir = Paths.get(destPath);

    // Walk thru mainDir directory
    Files.walkFileTree(mainDir, new FileVisitor<Path>() {
        @Override
        public FileVisitResult preVisitDirectory(Path path,
                BasicFileAttributes atts) throws IOException {
            return visitFile(path, atts);
        }

        @Override
        public FileVisitResult visitFile(Path path, BasicFileAttributes mainAtts)
                throws IOException {
            // I've seen two implementations on windows and MacOSX. One has passed the relative path, one the absolute path.
            // This works in both cases
            Path relativePath = mainDir.relativize(mainDir.resolve(path));
            File tmpFile = new File(otherDir+"/"+relativePath);

                if(tmpFile.exists()) {
                    BasicFileAttributes otherAtts = Files.readAttributes(otherDir.resolve(relativePath), BasicFileAttributes.class);
                    // Do your comparison logic here: we are skipping directories as all directories are traversed automatically
                    if(!new File(path.toString()).isDirectory()) {
                                                    //write your logic for comparing files
                        compareEntries(mainDir, otherDir, relativePath, mainAtts, otherAtts);
                    }
                    else {
                        File src = new File(path.toString());

                                                   //write your logic here for comparing directories
                                                   compareDirectories(src,tmpFile.toPath().toString()+"/"+s);
                    }
                }
                else {
                                            //this function will copy missing files in destPath from srcPath recursive function till depth of directory structure
                    copyFolderOrFiles(new File(path.toString()), tmpFile);
                }
            return FileVisitResult.CONTINUE;
        }

        @Override
        public FileVisitResult postVisitDirectory(Path path,
                IOException exc) throws IOException {
            return FileVisitResult.CONTINUE;
        }

        @Override
        public FileVisitResult visitFileFailed(Path path, IOException exc)
                throws IOException {
            exc.printStackTrace();
            // If the root directory has failed it makes no sense to continue
            return (path.equals(mainDir))? FileVisitResult.TERMINATE:FileVisitResult.CONTINUE;
        }
    });
}
...