6

I'm looking for a way to get a unique file id in a Java application, and came across this:

Unique file identifier in windows

Now, I tried the answer supplied by Ashley Henderson himself (the one that asked the question), and that worked fine in C#. But I need to do this in Java, in order to have the app work across platforms.

Is there any way to port this to Java, or get to the same id some other way?

EDIT:

I almost got it working now, using the solution by eee, only I need it to be in a library, and when I compile it as a library I get an error, even though everything is working fine in a test application with everything included. But with a separate library that I try to import (no compiler errors) I get this runtime error:

debug:
Exception in thread "main" java.lang.NoClassDefFoundError: com/sun/jna/Structure
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:791)
    at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
    at java.net.URLClassLoader.defineClass(URLClassLoader.java:449)
    at java.net.URLClassLoader.access$100(URLClassLoader.java:71)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:361)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:423)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:356)
    at winfileid.FileId.getFileId(FileId.java:37)
    at testfileid.TestFileId.main(TestFileId.java:19)
Caused by: java.lang.ClassNotFoundException: com.sun.jna.Structure
    at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:423)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:356)
    ... 14 more
Java Result: 1
BUILD SUCCESSFUL (total time: 0 seconds)

I've included the jna.jar and platform.jar in the library when I compiled it... Please note again that I'm very new to Java, but what am I doing wrong?

Community
  • 1
  • 1
Anders
  • 12,556
  • 24
  • 104
  • 151
  • 1
    I suspect you would have to use JNI to get it. Why would you want this information anyways? – jontro Nov 29 '11 at 10:27
  • whats the use case - why do you need the unique id? – aishwarya Nov 29 '11 at 10:37
  • The use case is to keep track of files when they are moved or renamed, to preserve links from other files. – Anders Nov 29 '11 at 12:56
  • @AndersSvensson Since your latest edit uses my solution, it shall state like that in your edit. Otherwise, anyone can get confused with it. About the exception error, did you compile it using Eclipse/Netbean IDE or compile it from the command-line? It will help in solving the problem... – ecle Nov 30 '11 at 15:19
  • Right, sorry, edited it to indicate the edit is based on your solution. I compiled with NetBeans. – Anders Nov 30 '11 at 21:32
  • Have you added the system property `-Djna.library.path=.\lib` in VM arguments if you put `jna.jar` and `platform.jar` in the `lib` folder of your project root? I suspect your built app jar could not find those jars from the lib folder of your build – ecle Dec 01 '11 at 14:17
  • @eee Ok, thanks, I'll try that. In any case the app works well now with the code integrated, and if I don't get the library to work that's fine. So thanks again, I'll mark this as answered now! – Anders Dec 01 '11 at 22:27

4 Answers4

4

Using JNA version 3.3.0:

Kernel32.INSTANCE.GetFileInformationByHandle Test case:

package win.test;

import com.sun.jna.platform.win32.Kernel32;
import com.sun.jna.platform.win32.WinBase;
import com.sun.jna.platform.win32.WinBase.FILETIME;
import com.sun.jna.platform.win32.WinNT.HANDLE;

import win.test.Kernel32.BY_HANDLE_FILE_INFORMATION;


public class FileTest {

    /**
     * @param args
     */
    public static void main(String[] args) {
        //http://msdn.microsoft.com/en-us/library/windows/desktop/aa363858%28v=vs.85%29.aspx
        final int FILE_SHARE_READ = (0x00000001);
        //final int FILE_SHARE_WRITE = (0x00000002);
        //final int FILE_SHARE_DELETE = (0x00000004);
        final int OPEN_EXISTING = (3);
        final int GENERIC_READ = (0x80000000);
        //final int GENERIC_WRITE = (0x40000000);
        //final int FILE_FLAG_NO_BUFFERING = (0x20000000);
        //final int FILE_FLAG_WRITE_THROUGH = (0x80000000);
        //final int FILE_READ_ATTRIBUTES = (0x0080);
        //final int FILE_WRITE_ATTRIBUTES = (0x0100);
        //final int ERROR_INSUFFICIENT_BUFFER = (122);
        final int FILE_ATTRIBUTE_ARCHIVE = (0x20);

        WinBase.SECURITY_ATTRIBUTES attr = null;
        BY_HANDLE_FILE_INFORMATION lpFileInformation = new BY_HANDLE_FILE_INFORMATION();
        HANDLE hFile = null;

        hFile = Kernel32.INSTANCE.CreateFile(args[0], GENERIC_READ, FILE_SHARE_READ, attr, OPEN_EXISTING, FILE_ATTRIBUTE_ARCHIVE, null);

        System.out.println("CreateFile last error:" + Kernel32.INSTANCE.GetLastError());

        //if (hFile. != -1)
        {

            win.test.Kernel32.INSTANCE.GetFileInformationByHandle(hFile, lpFileInformation);

            System.out.println("CREATION TIME: " + FILETIME.filetimeToDate(lpFileInformation.ftCreationTime.dwHighDateTime, lpFileInformation.ftCreationTime.dwLowDateTime));

            System.out.println("VOLUME SERIAL NO.: "  + Integer.toHexString(lpFileInformation.dwVolumeSerialNumber.intValue()));

            System.out.println("FILE INDEX HIGH: "  + lpFileInformation.nFileIndexHigh);
            System.out.println("FILE INDEX LOW: "  + lpFileInformation.nFileIndexLow);


            System.out.println("GetFileInformationByHandle last error:" + Kernel32.INSTANCE.GetLastError());
        }

        Kernel32.INSTANCE.CloseHandle(hFile);

        System.out.println("CloseHandle last error:" + Kernel32.INSTANCE.GetLastError());

    }

}

Sample output:

CreateFile last error:0
CREATION TIME: Tue Nov 29 22:24:04 SGT 2011
VOLUME SERIAL NO.: 900c0655
FILE INDEX HIGH: 1769472
FILE INDEX LOW: 286306
GetFileInformationByHandle last error:0
CloseHandle last error:0

Kernel32 JNA instance class:

package win.test;

import java.util.HashMap;
import java.util.Map;

import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.Structure;
import com.sun.jna.WString;
import com.sun.jna.platform.win32.WinBase.FILETIME;
import com.sun.jna.platform.win32.WinDef.DWORD;
import com.sun.jna.platform.win32.WinNT.HANDLE;
import com.sun.jna.win32.StdCallLibrary;
import com.sun.jna.win32.W32APIFunctionMapper;
import com.sun.jna.win32.W32APITypeMapper;

public interface Kernel32 extends StdCallLibrary {
    final static Map<String, Object> WIN32API_OPTIONS = new HashMap<String, Object>() {
        private static final long serialVersionUID = 1L;
        {
            put(Library.OPTION_FUNCTION_MAPPER, W32APIFunctionMapper.UNICODE);
            put(Library.OPTION_TYPE_MAPPER, W32APITypeMapper.UNICODE);
        }
    };

    public Kernel32 INSTANCE = (Kernel32) Native.loadLibrary("Kernel32", Kernel32.class, WIN32API_OPTIONS);

    public int GetLastError();

    /**
    typedef struct _BY_HANDLE_FILE_INFORMATION {
          DWORD    dwFileAttributes;
          FILETIME ftCreationTime;
          FILETIME ftLastAccessTime;
          FILETIME ftLastWriteTime;
          DWORD    dwVolumeSerialNumber;
          DWORD    nFileSizeHigh;
          DWORD    nFileSizeLow;
          DWORD    nNumberOfLinks;
          DWORD    nFileIndexHigh;
          DWORD    nFileIndexLow;
        } BY_HANDLE_FILE_INFORMATION, *PBY_HANDLE_FILE_INFORMATION;
     */

    public class BY_HANDLE_FILE_INFORMATION extends Structure {
        public DWORD    dwFileAttributes;
        public FILETIME ftCreationTime;
        public FILETIME ftLastAccessTime;
        public FILETIME ftLastWriteTime;
        public DWORD    dwVolumeSerialNumber;
        public DWORD    nFileSizeHigh;
        public DWORD    nFileSizeLow;
        public DWORD    nNumberOfLinks;
        public DWORD    nFileIndexHigh;
        public DWORD    nFileIndexLow;
        public static class ByReference extends BY_HANDLE_FILE_INFORMATION implements Structure.ByReference {

        };
        public static class ByValue extends BY_HANDLE_FILE_INFORMATION implements Structure.ByValue {

        };        
    }; 

    /**
    BOOL WINAPI GetFileInformationByHandle(
              __in   HANDLE hFile,
              __out  LPBY_HANDLE_FILE_INFORMATION lpFileInformation
            );
    */
    boolean GetFileInformationByHandle(
              HANDLE hFile,
              BY_HANDLE_FILE_INFORMATION lpFileInformation
            );
}
ecle
  • 3,952
  • 1
  • 18
  • 22
  • Wow, thanks for that detailed answer...! As I mentioned, though, this is a bit above my head, and I'm completely new to Java to boot, having only programmed in C# before. But I have to give this a try now that you provided such a detailed example... But I don't understand where you specify the file to be checked? It's probably in there, but it's hard for me to decode this. Also, what is the FILE INDEX HIGH vs FILE INDEX LOW? In the example I used (from the link) there was only one index value, so which should I use here to identify the file? – Anders Nov 29 '11 at 15:35
  • BTW, since JNA seems to be for accessing native code in dlls, there isn't a way to do this simply by compiling the code in the example link (which I already have in C# working) into a dll, and then call that with JNA? If it is I would really appreciate a simple example... I was just hoping that might be simpler. – Anders Nov 29 '11 at 15:38
  • @AndersSvensson yes, you can do that but it is a little bit difficult since you start with C# DLL rather than C/C++ DLL which the latter is the preferred DLL calling convention in JNA. You need to wrap your C# DLL into C/C++ DLL first before you can use it with JNA. That's what I can understand with JNA. – ecle Nov 29 '11 at 17:21
  • @AndersSvensson Oh, `args[0]` is from the program command argument which you need to pass into the program; for example: `FileTest "test.txt"` where `"test.txt"` will be the value of `args[0]` of `FileTest` program – ecle Nov 29 '11 at 17:25
  • @AndersSvensson To run the program successfully, you need to download JNA platform library (`jna.jar` and `platform.jar`), add them as referenced libraries and set the system property `-Djna.library.path=` [download here](http://java.net/projects/jna/downloads/directory/3.3.0) – ecle Nov 29 '11 at 17:28
  • @AndersSvensson Refer to MSDN link to the `BY_HANDLE_FILE_INFORMATION` structure, it says that, `The identifier (low and high parts) and the volume serial number uniquely identify a file on a single computer. To determine whether two open handles represent the same file, combine the identifier and the volume serial number for each file and compare them.` [link](http://msdn.microsoft.com/en-us/library/windows/desktop/aa363788%28v=vs.85%29.aspx) – ecle Nov 29 '11 at 17:34
  • Thanks, but I get a compiler exception for the import win32.wininet.Kernel32.BY_HANDLE_FILE_INFORMATION; It says it doesn't exist, and in fact I have no idea where it would get it from. I read on the JNA web page that jna would take care of loading the native libraries itself, but nonetheless I get the error, both in the IDE and when I try to run the program... – Anders Nov 29 '11 at 21:31
  • @AndersSvensson Oh, sorry it's my mistake when changing the package name. "win32.wininet" should be "win.test". I will rectify the error in the codes above.. – ecle Nov 29 '11 at 23:02
  • Thanks! Got it working now in a test application. This is a superb answer, and I'll credit you for sure, but one last question, please see my edit about getting it to work in a separate library to import... – Anders Nov 30 '11 at 14:18
1

In Java you would need JNI, native C compilation, both for Windows (using the C# code), and for Unix/Linux (using the inode of a file). Honestly I do not think this is very safe.

Joop Eggen
  • 107,315
  • 7
  • 83
  • 138
  • +1 The filesystem in use is also important, further making portability more difficult -- good luck on FAT, for instance :) –  Nov 29 '11 at 10:45
  • Yes, I looked briefly into that, and was hoping there would be a simpler way. But I was hoping not to have to compile this and use it as is in Java, but rather if there was a way to rewrite it in Java to get to the same id... Getting a file id in *nix is much simpler, but I can't find a way to do it for Windows except this example in C#. BTW, FAT is not an issue. It's going to be NTFS in Windows, and Mac OS X is the other OS I need to consider. – Anders Nov 29 '11 at 12:58
0

There can be multiple paths to the same file through mounted volumes and various types of links/reparse points. The unique file id API is the only way I know of to determine whether the several paths to a seemingly identical file refer to unique files or the same file through different paths.

That being said, I agree that the only java generic way would involve C or C++ code and JNI. Done right though this could probably provide solutions that work on Linux and Windows using different back-end libraries with a common presentation to the hosting java code.

0

Can't you use the file path as its unique id ?!

Full file path is unique enough ...

refaelos
  • 7,927
  • 7
  • 36
  • 55
  • 3
    Yes, it's unique - but it's not preserved when you move a file to another directory, which seems to be the intention behind the file ID. – Anthony Grist Nov 29 '11 at 10:33
  • Right, the point is to be able to move the files around and rename them freely, so file paths are actually what I need to find out after such moves or renames. – Anders Nov 29 '11 at 13:00