2

In my experience and after repeated tests I've done and deep web researches, I've found that major java libraries (either "Apache Commons" or Google.coomons or Jcifs) doesn't predict the case of “cyclic copy” of a file onto a destination differently mapped (denoted with different RootPath according with newer java.nio package Path Class) that,at last end of mapping cycle,resolves into the itself origin file.

That's a situation of data losing, because Outputsream method nor jnio's GetChannel method prevents itself this case:the origin file and the destination file are in reality "the same file" and the result of these methods is that the file become lost, better said the size o file become 0 length.

How can one avoid this without get off at a lower filesystem level or even surrender to a more safe Runtime.exec, delegating the stuff at the underlying S.O.

Should I have to lock the destination file (the above methods not allowing this), perhaps with the aid of the oldest RandomAccessFile Class ?

You can test using those cited major libraries with a common "CopyFile(File origin,File dest)" method after having done:

1) the origin folder of file c:\tmp\test.txt mapped to to x: virtual drive via a cmd's [SUBST x: c:\tmp] thus trying to copy onto x:\test.txt

2) Similar case if the local folder c:\tmp has been shared via Windows share mechanism and the destination is represented as a UNC path ending with the same file name

3) Other similar network situations ...

I think there must be another better solution, but my experience of java is fairly few and so I ask for this to you all. Thanks in advance if interested in this “real world” discussion.

4 Answers4

0

Your question is interesting, never thought about that. Look at this question: Determine Symbolic Links. You should detect the cycle before copying.

Community
  • 1
  • 1
Stephan
  • 4,395
  • 3
  • 26
  • 49
0

Perhaps you can try to approach this problem slightly differently and try to detect that source and destination files are the same by comparing file's metadata (name, size, date, etc) and perhaps even calculate hash of the files content as well. This would of course slow processing down.

If you have enough permissions you could also write 'marker' file with random name in destination and try to read it at the source to detect that they're pointing to the same place. Or try to check that file already exist at destination before copying.

maximdim
  • 8,041
  • 3
  • 33
  • 48
  • I thought something like this implementing a getChannel().lock() on a new RandomAccessFile on filedest with "rw" flags an then initiate reading with fileorig.getChannel().read(ByteBuffer.allocate(1)), just to see if it throws a "file already in use" exceptions ; if that is the case we return in the classical S.O. behaviour, we close gracefully the open handle and it's done. But I can't believe that such a critical checks weren't implemented over the years in a clean basical java.io method. There must be one that the common java developer can safely use ... – Sebastian Dainotti Feb 14 '12 at 16:00
  • I would argue that it's not that often you need to copy file from shared locations in java :) and handling all different ways you could map these locations, with symlinks and what not could be tricky. Btw in Java 7 perhaps it could be addressed with 'pluggable file system'. – maximdim Feb 14 '12 at 18:23
0

I agree that it is unusual situations, but you will agree that files are a critical base of every IT system. I disagree that manipulating files in java is unusual: in my case I have to attach image files of products through FileChooser and copy them in ordered way to a repository ... but real world users (call them customers who buy your product) may fall in such situations and if it happens, one can not 'blame the devil of bad luck if your product does something "less" than expected. It is a good practice learning from experience and try to avoid what one of Murphy's Laws says, more' or less: "if something CAN go wrong, it WILL go wrong sooner or later. Is perhaps also for one of those a reason I believe the Java team at Sun and Oracle has enhanced the old java.io package for to the newest java.nio. I'm analyzing a the new java.nio.Files Class which I had escaped to attention, and soon I believe I've found the solution I wanted and expected. See you later.

0

Thank for the address from other experienced members of the community,and thanks also to a young member of my team, Tindaro, who helped me in the research, I've found the real solution in Jdk 1.7, which is made by reliable, fast, simple and almost definitively will spawn a pity veil on older java.io solutions. Despite the web is still plenty full of examples of copying files in java using In/out Streams I'll warmely suggest everyone to use a simple method : java.nio.Files.copy(Path origin, Path destination) with optional parameters for replacing destination,migrate metadata file attributes and even try a transactional move of files (if permitted by the underlying O.S.). That's a really good Job, waited for so long! You can easily convert code from copy(File file1, File file2) by appending a ".toPath()" to the File instance (e.g. file1.toPath(), file2.toPath(). Note also that the boolean method "isSameFile(file1.toPath(), file2.toPath())", is already used inside the above copy method but easily usable in every case you want. For every case you can't upgrade to 1.7 using community libraries from Apache or Google is still suggested, but for reliable purpose, permit me to suggest the temporary workaround I've found before:

public static boolean isTheSameFile(File f1, File f2) {//throws Exception{
    // minimum prerequisites !
    if(f1.length()!=f2.length()) return false; 
    if (!file1.exists() || !file2.exists()) { return false; }
    if (file1.isDirectory() || file2.isDirectory()){ return false; }
    //if (file1.getCanonicalFile().equals(file2.getCanonicalFile())); //don't rely in this ! can even still fail 
    //new FileInputStream(f2).getChannel().lock();//exception, can lock only on OutputStream
    RandomAccessFile rf1=null,rf2=null; //the only practicable solution on my own ... better than parse entire files
    try {
        rf1 = new RandomAccessFile(f1, "r");
        rf2=new RandomAccessFile(f2, "rw");
    } catch (FileNotFoundException e) {
        e.printStackTrace();
        return false;
    }

    try {
        rf2.getChannel().lock();
    } catch (IOException e) {
        return false;
    }

    try {
        rf1.getChannel().read(ByteBuffer.allocate(1));//reads 1 only byte
    } catch (IOException e) {
        //e.printStackTrace(); // if and if only the same file, the O.S. will throw an IOException with reason "file already in use"
        try {rf2.close();} catch (IOException e1) {}
        return true;
    }
    //close the still opened resources ...
    if (rf1.getChannel().isOpen())
        try {rf1.getChannel().close();} catch (IOException e) {}

    try {
        rf2.close();
    } catch (IOException e) {
        return false;
    }
    // done, files differs
    return false;
}