One of the biggest problems with pre-NIO.2 libraries was inefficient work with metadata of files and directories. Authors of NIO.2 library introduce concept of file attribute views to address this problem and also to solve couple of other issues that are dependent on this one. These problems included inability to create files with file attributes initialized at the creation time, inability to copy files with file attributes as well as problematic and inefficient way of handling file attributes. This concept was also adapted on file stores so the work with file system with regard to attributes and metadata feels seamless and consistent.
File attribute views
First thing that needs to be understood is the role of file attribute view. File system metadata is typically referred to as its file attributes. Most of these metadata can be categorized and segmented based on area of use / scope they are describing or their origin. To support this segmentation further, NIO.2 introduces file attributes views that preserve this natural grouping and provide consistent access to file system metadata via set of interfaces and concrete classes.
There are a few types of attribute views that we need to know in order to fully harness true power of this part of the library. Every standard file attribute view has a short name that is usually used for checking whether given view is supported and for quick addressing of single attribute. Lets take a look at six most important views:
BasicFileAttributeView
- View name:
basic
- As the title suggests the most basic view that must be supported by all file system implementations. Provides access to information like
lastModifiedTime
,lastAccessTime
,creationTime
,size
and few other.
- View name:
AclFileAttributeView
- View name:
acl
- View allowing programmer to control ACL settings of given file. Involves work with
AclEntry
objects.
- View name:
FileOwnerFileAttributeView
- View name:
owner
- View giving access to ownership information of a file. Allows programmer to get owners name or to set user principals. Involves work with
UserPrincipal
objects.
- View name:
DosFileAttributeView
- View name:
dos
- View providing access to four attributes that are DOS specific –
archive
,hidden
,readOnly
,system
.
- View name:
PosixFileAttributeView
- View name:
posix
- View extending
BasicFileAttributesView
that adds support for working with POSIX (Portable Operating System Interface for Unix) file attributes such asgroup
,owner
andpermisions
.
- View name:
UserDefinedFileAttributeView
- Does not have a name
- View enabling work with user-created metadata.
But how does one find out what they are and whether they are supported on their concrete file system? Well this has been all taken care of. Programmer can retrieve a list of all supported file attribute views from every single concrete file system implementation. Lets examine following snippet:
final FileSystem defaultFS = FileSystems.getDefault(); final FileSystem zipFs = getZipFS(); for (String fileAttributeView : defaultFS.supportedFileAttributeViews()) { System.out.println("Default file system supports: " + fileAttributeView); } System.out.println(); for (String fileAttributeView : zipFs.supportedFileAttributeViews()) { System.out.println("ZIP file system supports: " + fileAttributeView); }
With an output for Windows environment:
Default file system supports: acl Default file system supports: basic Default file system supports: owner Default file system supports: user Default file system supports: dos ZIP file system supports: zip ZIP file system supports: basic
and an output for Fedora environment:
Default file system supports: basic Default file system supports: owner Default file system supports: user Default file system supports: unix Default file system supports: dos Default file system supports: posix ZIP file system supports: zip ZIP file system supports: basic
As we can see supported file views are strongly implementation and platform dependent. However, all file system implementations should support basic
file attribute view. Whether certain file attribute view is supported or not lies entirely on file store implementation. Since not all file attribute views are supported it is crucial to be able to tell, whether one can be used or not. Each attribute view serves different purposes, so programmer needs to be familiar with detailed structure of the views, when designing their code. Lets take a look at the following code sample and examine the way to check for view support:
for (FileStore fs : FileSystems.getDefault().getFileStores()) { // returns true fs.supportsFileAttributeView(BasicFileAttributeView.class); }
Attribute views are always implementation and platform dependent so lets look at file system abstraction for ZIP files:
final Path zipPath = Paths.get("c:/Program Files/Java/jdk1.7.0_40/src.zip"); try (FileSystem zipFS = FileSystems.newFileSystem(zipPath, null)) { for (FileStore fs : zipFs.getFileStores()) { // returns true fs.supportsFileAttributeView(BasicFileAttributeView.class); // returns false fs.supportsFileAttributeView(DosFileAttributeView.class); // returns false fs.supportsFileAttributeView("acl"); } }
As we can see in file system example , neither acl
nor dos
views are supported. For a convenience reasons file system implementations provide method supportedFileAttributeViews
that allows us to see what range of views is available to us.
File attributes reading and modification
As mentioned above the retrieval of a single file attribute means accessing the file system. In previous versions of IO libraries the only way to retrieve a group of n attributes was to do n accesses to the file system which was not only slow but also not an elegant solution. When it comes to the way NIO.2 handles this situation, simplicity and elegance are one of key elements.
First thing I am going to demonstrate is how to get attributes from desired view. This can be done in two (not that different) ways. You either retrieve the view object where you can read attributes or you can use method for reading attributes directly. Both ways include work with class java.nio.file.Files
. To get a file attribute view just call method getFileAttributes
with a path of the file and class of desired view. Then just call readAttributes
to receive attributes object that you can query for supported attributes. On the other hand programmer can call readAttributes
directly on Files
class and get the same output, as demonstrated in following snippet:
final Path path = Paths.get("/usr/java/jdk1.7.0_25/src.zip"); final BasicFileAttributeView view = Files.getFileAttributeView(path, BasicFileAttributeView.class); final BasicFileAttributes attrs = view.readAttributes(); final BasicFileAttributes attrsDirect = Files.readAttributes(path, BasicFileAttributes.class); System.out.println("View attribute 'creationTime': " + attrs.creationTime() + " - " + attrsDirect.creationTime()); System.out.println("View attribute 'isRegularFile': " + attrs.isRegularFile() + " - " + attrsDirect.isRegularFile()); System.out.println("View attribute 'isSymbolicLink': " + attrs.isSymbolicLink() + " - " + attrsDirect.isSymbolicLink());
With an output:
View attribute 'creationTime': 2013-06-06T04:03:54Z - 2013-06-06T04:03:54Z View attribute 'isRegularFile': true - true View attribute 'isSymbolicLink': false - false
Why are there two ways of doing this and not just one you ask? Well, there is big difference between file attribute views and their attributes objects that I have not described yet. Difference becomes apparent when we examine both classes closely. While attributes object is sort of transfer object that stores supported attributes for given view and provides us only getter methods to read them, view object is more powerful and informative. Views give us the ability to get attributes objects to get and set attribute values and some other (more complex) setter methods. For example PosixFileAttributeView
has methods for setting owner group or file permissions. In this way each view is unique and may provide more advanced functionality than attributes objects.
However, there are situations when programmer does not need to work with group of file attributes and may want to get one particular attribute. Files
class has a method to do just that. The only thing that changes is the syntax used in method getAttribute
that specifies concrete attribute. There is also a setter method setAttribute
that follows same rules. Each attribute view provides a table of all supported attributes with their names as well as the name of this view in its javadoc. Look at the BasicFileAttributeView
example from current version of its javadoc:
Name | Type |
---|---|
lastModifiedTime | FileTime |
lastAccessTime | FileTime |
creationTime | FileTime |
size | Long |
isRegularFile | Boolean |
isDirectory | Boolean |
isSymbolicLink | Boolean |
isOther | Boolean |
fileKey | Object |
These names are used to specify what attribute you want to work with. To specify what attribute to work with use the following scheme: <viewName>:<attributeName>
. BasicFileAttributeView
is special in a way that you can declare only the name of desired attribute without the need to specify view. All non-prefixed attributes are assumed to be from basic file attribute view. Lets look at the following code snippet that provides an example of this:
final Path path = Paths.get("/usr/java/jdk1.7.0_25/src.zip"); final Boolean isDirectoryAttr = (Boolean) Files.getAttribute(path, "isDirectory", LinkOption.NOFOLLOW_LINKS); final Long sizeAttr = (Long) Files.getAttribute(path, "basic:size", LinkOption.NOFOLLOW_LINKS); final UserPrincipal ownerAttr = (UserPrincipal) Files.getAttribute(path, "posix:owner", LinkOption.NOFOLLOW_LINKS); System.out.println("Attribute 'isDirectory': " + isDirectoryAttr); System.out.println("Attribute 'size': " + sizeAttr); System.out.println("Attribute 'owner': " + ownerAttr.getName());
With an output:
Attribute 'isDirectory': false Attribute 'size': 19852231 Attribute 'owner': root
One can clearly see one major disadvantage of this approach. In order to use this technique properly programmer needs to be aware of the type of returned attributes value, whether by heart or by consulting the javadoc. Since we are using an explicit downcast we loose type safety of Java and open doors for unwanted runtime exceptions.
Now that we can read file attributes lets try to modify them. To demonstrate this I decided to change the owner of a file on ext4 file system. In order to do so, first look at class UserPrincipalLookupService
and UserPrincipal
from package java.nio.file.attribute
. UserPrincipal
represents an identity that is used to determine access rights to file system objects. UserPrincipalLookupService
is an object that allows programmer to look up and use UserPrincipal
objects in code. In order to modify the owner attribute I am going to use the direct approach of setting one attribute via Files
class since it is the only one attribute I am interested in. You can observe the use of posix:owner syntax mentioned in previous paragraph for setting a non-basic file attribute. Following code snippet toggles the owner from a user to root
and reverse.
final UserPrincipalLookupService userPrincipalLookupService = FileSystems.getDefault().getUserPrincipalLookupService(); final UserPrincipal me = userPrincipalLookupService.lookupPrincipalByName("jstas"); final UserPrincipal root = userPrincipalLookupService.lookupPrincipalByName("root"); final Path path = Paths.get("/usr/java/jdk1.7.0_25/src.zip"); final UserPrincipal ownerOld = (UserPrincipal) Files.getAttribute(path, "posix:owner"); if (ownerOld.equals(me)) { Files.setAttribute(path, "posix:owner", root); } else { Files.setAttribute(path, "posix:owner", me); } final UserPrincipal ownerNew = (UserPrincipal) Files.getAttribute(path, "posix:owner"); System.out.println("Attribute 'owner' before: " + ownerOld); System.out.println("Attribute 'owner' after: " + ownerNew);
*Please note that in case of this kind of modification the process must be run with an appropriate user privileges (in this case by root
)
With an output:
Attribute 'owner' before: root Attribute 'owner' after: jstas
Another great example of file attribute modification is changing of access permissions for a given file. This code modifies current access permissions and demonstrates the use of view object when an attribute object just will not cut it. Before running this code I changed permissions of the file via system terminal by running:
chmod 777 /usr/java/jdk1.7.0_25/src.zip
Now lets look at the code itself:
final Path path = Paths.get("/usr/java/jdk1.7.0_25/src.zip"); // get permissions from posix view final PosixFileAttributeView posixView = Files.getFileAttributeView(path, PosixFileAttributeView.class); final PosixFileAttributes attrs = posixView.readAttributes(); final Set permessions = attrs.permissions(); // display all the permissions System.out.println("\nPermissions before modification:"); for (PosixFilePermission permission : permessions) { System.out.println(permission); } // clear all the permissions permessions.clear(); // set own permissions permessions.add(PosixFilePermission.OWNER_READ); permessions.add(PosixFilePermission.OWNER_WRITE); permessions.add(PosixFilePermission.OWNER_EXECUTE); // modife file attributes posixView.setPermissions(permessions); System.out.println("\nPermissions after modification:"); // verify results final Set permessionsNew = posixView.readAttributes().permissions(); for (PosixFilePermission permission : permessionsNew) { System.out.println(permission); }
*Please note that in case of this kind of modification the process must be run with an appropriate user privileges (in this case by root
)
With an output:
Permissions before modification: GROUP_EXECUTE OTHERS_WRITE OWNER_EXECUTE OWNER_WRITE OTHERS_READ GROUP_READ GROUP_WRITE OTHERS_EXECUTE OWNER_READ Permissions after modification: OWNER_EXECUTE OWNER_WRITE OWNER_READ
These simple steps are universal to working with any type of view or attributes object. Feel free to experiment and try new ways to modify file attributes on your own.
User defined file attributes
Last chapter of this post is dedicated to creation of custom file attributes. To allow this, creators of NIO.2 library defined an extra view called UserDefinedFileAttributeView
that allows programmer to create, read, update and delete user defined metadata for given file. This view is located in java.nio.file.attribute
package. Attributes are saved as a key-value pairs. First of all, lets look at the way of reading all user defined attributes which follows standard procedure for reading standard attributes with a small change. First we need to get a view instance using Files
class. UserDefinedFileAttributeView
provides method list
that lists names of all user defined attributes currently present on the file. Sample code to read these could look as follows:
private static void showCurrentStateOfTheFile(String suffix) throws IOException { final UserDefinedFileAttributeView view = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class); System.out.println("\nList of all user defined attributes " + suffix + ":\n"); for (String attribute : view.list()) { System.out.println(attribute + ": " + getUserDefinedAttribute(view, attribute)); } System.out.println("\nFile size: " + Files.getAttribute(path, "size") + " B\n\n"); }
When it comes to creation of such an attributes there is simple way to do so using write
method on your view instance. Since all attribute values are stored as strings we need to do conversion of desired value. Keep in mind that this string value should not be encoded using default charset. Always use one of the standard charsets available in class StandardCharsets
from package java.nio.charset
and be consistent in your choice throughout the application. And definition of a new user defined attribute is done in following way:
private static void createUserDefinedAttribute(String name, String value) throws IOException { final UserDefinedFileAttributeView view = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class); if (!view.list().contains(name)) { view.write(name, StandardCharsets.UTF_8.encode(value)); } }
There is no special method for updating the value of user defined attribute so we, once again, use write
method to do the job (which makes the use of if statement in previous example obsolete – only prevents updating of the attribute).
However, when it comes to reading an user defined attribute, this is the part where things get a little complicated. In order to read this type of attribute developer needs to use a byte buffer to do charset decoding of the value. Since every user defined attribute contains information about its size we need to get this value to allocate our ByteBuffer
instance properly. Then ask the view to fill this buffer with data and flip it. Flipping is done by calling flip
method on the buffer itself. This sets the limit to current position in the buffer and then the position is set to zero. This way there is no problem to decode its contents using standard charset. Whole situation is shown in the following code snippet:
private static String getUserDefinedAttribute(UserDefinedFileAttributeView view, String attributeName) throws IOException { if (view.list().contains(attributeName)) { ByteBuffer buffer = ByteBuffer.allocateDirect(view.size(attributeName)); view.read(attributeName, buffer); buffer.flip(); return StandardCharsets.UTF_8.decode(buffer).toString(); } else { return ""; } }
Finally, deleting the user defined attribute is pretty straightforward task. Just get the name of attribute you want to delete and pass it to delete method of view instance.
private static void deleteUserDefinedAttributes() throws IOException { final UserDefinedFileAttributeView view = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class); for (String attributeName : view.list()) { view.delete(attributeName); } }
To see the whole process of reading, creating, modifying and deleting user defined attributes please follow this next code snippet. Notice that even though I added three file attributes size of the file remains constant. This behavior is platform dependent so consult documentation for your particular file system.
showCurrentStateOfTheFile("before any modification"); createUserDefinedAttributes(); showCurrentStateOfTheFile("after adding user defined attributes"); modifyUserDefinedAttributes(); showCurrentStateOfTheFile("after modifying user defined attributes"); deleteUserDefinedAttributes(); showCurrentStateOfTheFile("after removing user defined attributes");
With an output:
List of all user defined attributes before any modification: File size: 19852231 B List of all user defined attributes after adding user defined attributes: revision number: 17 revision state: draft revision committed: 08.10.2013 File size: 19852231 B List of all user defined attributes after modifying user defined attributes: revision number: 18 revision state: published revision committed: 08.10.2013 File size: 19852231 B List of all user defined attributes after removing user defined attributes: File size: 19852231 B
This was a very good article. Thank you!
Good article, helpful… but not for mac programmers as a mac system only supports ‘owner’, ‘basic’,’posix’, and a ‘unix’ – Macs do not support user defined file attributes nor acl so i am having a real hard time setting those extended file attributes (like author for example) on a mac… I can write the code to do it on a PC in my sleep…. really wish i could find a good mac example of how to do this…
I feel your pain mate, but I have never owned a Mac, so there is no way for me to really comment or help here. Sorry.