3

Here in short is the situation:

for(element : array)
{
   execute something that adds new elements to the array;
}

It's obvious that the newly added elements will not be processed. Is there a technique to get every elements processed?

Here is an example of this problem: I want to, given a folder, go through its depth and move all files - either under it's children or under it's children's children - to directly under the given folder. After that delete all empty folders:

Parent Folder
 - Level1Folder1
    - file1_1
 - Level1Folder2
    - Level2Folder1
        - file2_1
        - file2_2
    - file1_2

will become:

Parent Folder
 - file1_1
 - file1_2
 - file2_1
 - file2_2

Here's that potion of my code:

public static void moveUpOneFolder(Path parent) {

    try (DirectoryStream<Path> ds1 = Files.newDirectoryStream(parent)) {
        for (Path p1 : ds1) {
            //if this node is a dir, traverse its content
            if (Files.isDirectory(p1)) {
                moveUpOneFolder(p1);
            }
            //if this node is a file, move it up one level
            else {
                Path newFileName = parent.getParent().resolve(p1.getName(p1.getNameCount() - 2) + "_" + p1.getFileName());
                Files.move(p1, newFileName);
            }
            Files.delete(p1);
        }

    } catch (IOException e) {
        e.printStackTrace();
    }
}

This recursion does not work because when execution reaches Level2Folder1, it moves file2_1 and file 2_2 up to Level1Folder2, then continue to move file1_2 to Parent Folder, ignoring file2_1 and file2_2, the newly added elements to the folder. This happens because ds1 is already initialized for the for loop, the new elements are not added to this array/stream and so are ignored.

I imagine this will not be difficult with an experienced coder, but I'm really stuck. :-)

2
  • I don't think you are allowed to add things to an array you are iterating over using a for-each. It will probably throw a ConcurrentModificationException. Commented Aug 26, 2013 at 22:48
  • Try two passes? ideone.com/qdrnvs Not sure about the details, though. Commented Aug 26, 2013 at 23:08

4 Answers 4

6

The Java Collections reject this problem by throwing ConcurrentModificationException. ;)

In this particular case, I'd tend to recommend a queue-type structure; rather than using a for loop, repeatedly dequeue elements and add more elements to the back of the queue.

Sign up to request clarification or add additional context in comments.

Comments

2

Why do you need to move the files up one directory at a time? I think this is the root of your problem, you're creating an overly complex solution. If instead you come at the problem a slightly different way, and split the component pieces of behavior into parts, you can avoid the issue you're running into. In pseudo-java-code:

public void moveAllFiles(File fromDir, File toDir) {
  for(File f : getAllFilesAndDirs(fromDir)) {
    if(f.isDirectory()) {
      moveAllFiles(f, toDir);
      f.delete();
    } else {
      move(f, toDir);
    }
  }
}

Notice a few things:

  1. We logically separate fromDir and toDir, there's no need to require they be the same location, and this lets us use the call recursively.
  2. We get all the children of fromDir immediately, and as long as no other programs modify the directory, we no longer care if the directory changes later, avoiding the concurrent modification issue.
  3. By using recursion, we're able to trust that either we're looking at a file or a (now) empty directory.

Comments

1

You don't need to iterate over an array while adding elements to it. That would throw all sorts of ConcurrentModificationException.

With Java's NIO, it isn't too hard to flatten your directory structure. Copy all files to a temporary directory, deleting as you go. Then copy from that temporary directory back to your root directory.

final Path root = Paths.get("/root");
final Path temp = Files.createTempDirectory(null);
Files.walkFileTree(root, new SimpleFileVisitor<Path>() {

    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
        if (attrs.isRegularFile()) {
            Files.copy(file, temp.resolve(file.getFileName()));
            Files.delete(file);
        }
        return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
        if (!dir.equals(root))
            Files.delete(dir);
        return FileVisitResult.CONTINUE;
    }           
});

Files.walkFileTree(temp, new SimpleFileVisitor<Path>() {
    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
        if (attrs.isRegularFile()) {
            Files.copy(file, root.resolve(file.getFileName()));
            Files.delete(file);
        }
        return FileVisitResult.CONTINUE;
    }
});

This will flatten your directory structure, keeping only files and discarding directories.

Comments

1

I suggest using a Stack or a Queue instead of an array. This means that you continue to loop until the data structure is empty. During the loop, you add elements which need to be processed (i.e. directories containing files that should be moved).

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.