Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 792
lobobabysaurus Avatar asked Sep 28 '22 09:09

lobobabysaurus


1 Answers

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.

like image 157
fge Avatar answered Oct 07 '22 20:10

fge