OCP Question 1, Explanation

Given that /fish.txt and /cats/tiger.txt are accessible, and the code fragment:

Path source = Paths.get("/fish.txt");
Path target = Paths.get("/cats/tiger.txt");
Files.move(source, target, StandardCopyOption.ATOMIC_MOVE);
Files.delete(source);

Which statement is true?

A. The fish.txt file content is replaced by the tiger.txt file content and the tiger.txt file is deleted.
B. The tiger.txt file content is replaced by the fish.txt file content and an exception is thrown.
C. The file fish.txt is moved to the /cats directory.
D. A FileAlreadyExistsException is thrown at runtime.

The correct answer is option B.

I can offer you two kinds of explanations for this question: a short, down-to-the-point version and a verbose one, with such emotionally laden words as ‛nasty’, ‛ambiguity’ and even ‛hate’. Take your pick.

The short version:

Since the target file already exists, the Files.move() method is supposed to throw an IOException because the StandardCopyOption.REPLACE_EXISTING arg hasn’t been mentioned in the call. However, the code did specify the StandardCopyOption.ATOMIC_MOVE arg, which makes absence or presence of any other StandardCopyOption constant simply irrelevant. The file fish.txt will be successfully renamed to /cats/tiger.txt, and then a NoSuchFileException gets thrown because the last LOC wants to delete the source file, which has already been erased after the renaming operation.

And here’s a full-scale exposé (click to expand)

Yes, this little marvel appears entirely innocent but if you stare at it hard enough, it’ll reveal a nasty ambiguity, which actually makes it impossible to choose between two equally valid options. I’d venture as far as to say that the question is badly formulated and has no right to exist on our exam… Unfortunately to us, exam takers, it belongs to the ‛Core’ block of the questions, and you’ll meet it, guaranteed. So brace yourselves.

This said, let’s start by analyzing ‛ideology’ of the question, its code’s intentions. Apparently, the question wants to move some file. OK, and how do we generally move files? The source file is first copied to destination under a different name and then the system deletes the source, right? Observe, however, that the last LOC in our code also wants to delete the source file on its own, but by this moment the source file will be no longer there. Which leads, of course, to an exception; we can even predict that since the problem is related to file access, it’s going to be an IOException or one of its subtypes.

Alright, so which options mention an exception? B and D. Good; we’ve managed to slash the number of choices by half. Now let’s dig deeper and analyze the code itself; we’ll be also needing to run it to check our conclusions.

First off, notice that the paths apparently belong to a *nix-based environment and are absolute because they start with a forward slash. Now is a good time to recall that Windows-based systems use the backslash \ character. Keep in mind, though, that many programming languages and file systems support both types of slashes when writing path statements, so it is also possible to play with this question in its unmodified form on a Windows rig, although you will most likely run into an ‛access denied’ error because you’ll be needing admin-level privileges to create fish.txt in the root directory. Here’s the full workflow for this case (assuming that the Test class lives in the C:\Try_Java\host\igor dir):

  • Code the class:
package host.igor;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;

class Test{
  public static void main(String[] args) throws IOException{
    Path source = Paths.get("/fish.txt");
    Path target = Paths.get("/cats/tiger.txt");
    Files.move(source, target, StandardCopyOption.ATOMIC_MOVE);
    Files.delete(source);
  }
}

Please note that we had to specify a throws clause because both Files.move() and Files.delete() methods throw an IOException, which is a ChE. As an additional exercise, you may wish to re-code the class to utilize a try-catch block; just make sure to output stack trace to see what this question is all about. And as you probably remember from my book on 1Z0-808, I always recommend to stay away from ‛wildcarded’ imports and write fully qualified names, instead, until you pass the exam.

One more remark: To be absolute, a path must include the root directory. Under Windows, however, it does not mean paths with the root component are necessary absolute:

Path p1 = Paths.get("C:/home");
System.out.println(p1.isAbsolute()); // true
System.out.println(p1.getRoot());    // C:\

Path p2 = Paths.get("/home");
System.out.println(p2.isAbsolute()); // false
System.out.println(p2.getRoot());    // \

In the above example, both Paths do contain a root component (because getRoot() would’ve returned null otherwise) but the 2nd path isn’t absolute.

  • Now click the Start button, type cmd in the search field and right-click cmd.exe when it appears in the list of search results, select Run as administrator, create both .txt files in proper places, and then actually compile and run the class:

If you don’t want to run it as an admin, then either remove the leftmost slashes and create those .txt files inside the Try_java folder, or change the source object’s instantiation LOC to something like:

Path source = Paths.get("/not_cats/fish.txt");

Alright, so what happens when we run this program? We can clearly see that the code throws an exception (namely, java.nio.file.NoSuchFileException, which is a grandchild of IOException and, as such, is a ChE) but why?

It’s because the Files.move() method erases the source object’s underlying file after it has been renamed to \cats\tiger.txt, so the Files.delete() has nothing to work with. Should we comment out the last LOC, the run would succeed. The command

type c:\cats\tiger.txt

confirms that this file is containing what fish.txt used to have. So in line with our observations, the answer is option B.

And now let me tell you what I hate about this question: it’s its option D. Oh boy, if only it weren’t there…

Please recall that the Files.move() method throws “FileAlreadyExistsException if the target file exists but cannot be replaced because the REPLACE_EXISTING option is not specified” (ref.to javadoc on the java.nio.file.Files class). Now I’m asking you: did the code specify this option? Nope. So why on earth FileAlreadyExistsException hasn’t been thrown, eh?

That’s the problem. Which originates with the StandardCopyOption.ATOMIC_MOVE arg. When we specify it in the call, “…the move is performed as an atomic file system operation and all other options are ignored” (this is again from javadoc on Files.) As a result, our code doesn’t even need REPLACE_EXISTING, hence in the absence of both A_M and R_E args the program indeed should throw FileAlreadyExistsException.

But it doesn’t! And you know why? Because when we specify ATOMIC_MOVE, “…if the target file exists then it is implementation specific if the existing file is replaced or this method fails by throwing an IOException“. Implementation specific, dammit!

I tried passing ATOMIC_MOVE on Windows 7, RHEL Server 6, Solaris 10 and Raspbian, they all replaced the target object’s file without throwing FAEE. But there still remains at least a theoretical possibility that on some exotic systems the correct answer will be option D…

Takeaway: ATOMIC_MOVE is implementation-specific; it may either replace an existing file or throw an IOE. I answered B on the exam, and apparently it was accepted as correct.

And finally, one more annoying thing about this question is its sloppiness with wording when it says “The tiger.txt file content is replaced by the fish.txt file content”. I agree, it does appear as if the code replaces the tiger.txt file content – but in reality it’s the file itself that gets replaced. Would you like to see a glaring proof? Try to delete the freshly renamed c:\cats\tiger.txt without having admin rights. See? The system suddenly doesn’t let you do that anymore! It’s because fish.txt actually replaced the original tiger.txt together with all its attributes, and fish.txt was owned by admin…

Leave Comment

Your email address will not be published. Required fields are marked *