Search code examples
javafilemetadatanio

Issues writing file metadata with Java nio


I'm looking to add a custom metadata tag to any type of file using functionality from java.nio.file.Files. I have been able to read metadata correctly, but am having issues whenever I try to set metadata.

I've tried to set a custom metadata element with a plain string using Files.setAttribute with the following

    Path photo = Paths.get("C:\\Users\\some\\picture\\path\\2634.jpeg");
    try{
        BasicFileAttributes attrs = Files.readAttributes(photo, BasicFileAttributes.class);
        Files.setAttribute(photo, "user:tags", "test");
        String attribute = Files.getAttribute(photo, "user:tags").toString();
        System.out.println(attribute);
    }
    catch (IOException ioex){
        ioex.printStackTrace();
    }

but end up with the following error :

Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.nio.ByteBuffer

if I try to cast that string to a ByteBuffer like so

    Path photo = Paths.get("C:\\Users\\some\\picture\\path\\2634.jpeg");
    try{
        BasicFileAttributes attrs = Files.readAttributes(photo, BasicFileAttributes.class);
        Files.setAttribute(photo, "user:tags", ByteBuffer.wrap(("test").getBytes("UTF-8")));
        String attribute = Files.getAttribute(photo, "user:tags").toString();
        System.out.println(attribute);
    }
    catch (IOException ioex){
        ioex.printStackTrace();
    }

instead of outputting the text 'test', it outputs the strange character string '[B@14e3f41'

What is the proper way to convert a String to a bytebuffer and have it be convertable back into a string, and is there a more customizable way to modify metadata on a File using java?


Solution

  • User defined attributes, that is any attribute defined by UserDefinedFileAttributeView (provided that your FileSystem supports them!), are readable/writable from Java as byte arrays; if a given attribute contains text content, it is then process dependent what the encoding will be for the string in question.

    Now, you are using the .{get,set}Attribute() methods, which means that you have two options to write user attributes:

    • either using a ByteBuffer like you did; or
    • using a plain byte array.

    What you will read out of it however is a byte array, always.

    From the javadoc link above (emphasis mine):

    Where dynamic access to file attributes is required, the getAttribute method may be used to read the attribute value. The attribute value is returned as a byte array (byte[]). The setAttribute method may be used to write the value of a user-defined attribute from a buffer (as if by invoking the write method), or byte array (byte[]).

    So, in your case:

    • in order to write the attribute, obtain a byte array with the requested encoding from your string:

      final Charset utf8 = StandardCharsets.UTF_8;
      final String myAttrValue = "Mémé dans les orties";
      final byte[] userAttributeValue = myAttrValue.getBytes(utf8);
      Files.setAttribute(photo, "user:tags", userAttributeValue);
      
    • in order to read the attribute, you'll need to cast the result of .getAttribute() to a byte array, and then obtain a string out of it, again using the correct encoding:

      final Charset utf8 = StandardCharsets.UTF_8;
      final byte[] userAttributeValue 
          = (byte[]) Files.readAttribute(photo, "user:tags");
      final String myAttrValue = new String(userAttributeValue, utf8);
      

    A peek into the other solution, just in case...

    As already mentioned, what you want to deal with is a UserDefinedFileAttributeView. The Files class allows you to obtain any FileAttributeView implementation using this method:

    final UserDefinedFileAttributeView view
        = Files.getFileAttributeView(photo, UserDefinedFileAttributeView.class);
    

    Now, once you have this view at your disposal, you may read from, or write to, it.

    For instance, here is how you would read your particular attribute; note that here we only use the attribute name, since the view (with name "user") is already there:

    final Charset utf8 = StandardCharsets.UTF_8;
    final int attrSize = view.size("tags");
    final ByteBuffer buf = ByteBuffer.allocate(attrSize);
    view.read("tags", buf);
    return new String(buf.array(), utf8);
    

    In order to write, you'll need to wrap the byte array into a ByteBuffer:

    final Charset utf8 = StandardCharsets.UTF_8;
    final int array = tagValue.getBytes(utf8);
    final ByteBuffer buf = ByteBuffer.wrap(array);
    view.write("tags", buf);
    

    Like I said, it gives you more control, but is more involved.

    Final note: as the name pretty much dictates, user defined attributes are user defined; a given attribute for this view may, or may not, exist. It is your responsibility to correctly handle errors if an attribute does not exist etc; the JDK offers no such thing as NoSuchAttributeException for this kind of scenario.