Creating files and directories in NIO.2
Great number of applications nowadays create files or directories for very wide range of purposes. Whether it is to generate a report, export piece of configuration or simply to store some data it is important to be able to handle these tasks. Creating files and directories is one of the most heavily used functionality while working with a file system. This part of library underwent quite a modernization. Updates in this area include guarantee of atomicity of certain operations, creation of files and directories with preset file attributes, performance optimization as well as introduction of exception hierarchy that replaced boolean
returning methods from prior versions of IO library.
Checking methods
Before we get down to any code or explanation, let me take a step back and focus on something that will be essential not only to this post but also a number of posts to come. I find it important to be familiar a few methods usually called checking methods. Checking methods include all those methods used to perform various checks before calling the actual file system manipulation code. For convenience, they are all in class java.nio.file.Files
. Using these methods will help you prevent unexpected behavior of your application. Since these methods are really simple, I will skip examples dedicated to them and instead use them in later examples.
Method name | Description |
---|---|
exists(Path path, LinkOption... options) | Tests whether a file exists. |
isExecutable(Path path) | Tests whether a file is executable. |
isHidden(Path path) | Tells whether or not a file is considered hidden. |
isReadable(Path path) | Tests whether a file is readable. |
isRegularFile(Path path, LinkOption... options) | Tests whether a file is a regular file with opaque content. |
isSameFile(Path path, Path path2) | Tests if two paths locate the same file. |
isWritable(Path path) | Tests whether a file is writable. |
notExists(Path path, LinkOption... options) | Tests whether the file located by this path does not exist. |
Creating a new directory
One of the most important uses of class Files
is to create new directories using method createDirectory
. Directory creation is pretty simple and straight forward process so there is not much to explain. As usual it is always a good idea to use checking method exists
from class Files
to ensure that it is possible to create a directory with given path and also to prevent FileAlreadyExistsException
. Whole situation is presented in the following code snippet:
Path newDirectoryPath = Paths.get("/home/jstas/directory"); if (!Files.exists(newDirectoryPath)) { try { Files.createDirectory(newDirectoryPath); } catch (IOException e) { System.err.println(e); } }
The code sample is pretty simple – it creates a directory with provided path given no other file system entry resides on provided path. If we need to create whole directory hierarchy then we need to switch to method createDirectories
which behaves similarly and creates whole hierarchy defined by a path instance. Since a directory is a type of file we are able to set its own metadata (file attributes). Not only are we able to do this, we might even create metadata definition beforehand and create a directory with initial file attributes in an atomic operation preventing any inconsistencies along the way. As mentioned in my previous article, there are two supported standards for managing file system permissions: POSIX and ACL.
POSIX file permissions
First, lets look at how we can manage file system permissions on POSIX-compliant systems like Linux-based systems and Mac OS. Thanks to the fact that POSIX file permissions are rather simple to understand, library creators provide us with convenience tools such as direct translation from string representation to a set of PosixFilePermission
s or conversion tool to convert said set into FileAttribute
object. This is not the only way to create FileAttribute
object as we will see in next chapter.
Getting back to the example at hand, lets look at the following code. Using convenience method fromString
of class PosixFilePermissions
we are able to create a set of PosixFilePermission
s. Now it is necessary to create FileAttribute
instance to be passed to createDirectory
method that creates our test directory. Lets look at following snippet of code:
Path newDirectoryPath = Paths.get("/home/jstas/testPosix"); if (!Files.exists(newDirectoryPath)) { Set<PosixFilePermission> permissions = PosixFilePermissions.fromString("r-xr-----"); FileAttribute<Set<PosixFilePermission>> fileAttributes = PosixFilePermissions.asFileAttribute(permissions); try { Files.createDirectory(newDirectoryPath, fileAttributes); } catch (IOException e) { System.err.println(e); } }
It is easy to validate whether our permissions were set correctly. You can either read file attributes directly from Java code like I presented in File attributes article or do it manually. I used systems terminal to check them with following output:
dr-xr-----. 2 jstas jstas 4096 Jan 5 13:34 testPosix
ACL file permissions
Things get a little bit more complex when managing file system permissions on ACL-compliant systems such as Windows (NT, 2000, XP and newer). ACL lists can get pretty complex and robust so there are no shortcuts here like with POSIX file permissions. The key here is to use an anonymous class definition based on the interface FileAttribute
. This interface defines only two methods: name
returns the name of a file attribute and value
returns value of this attribute. When working with ACL, the name of an attribute we are interested in is ‘acl:acl’. value
method just returns list of constructed ACL entries.
Lets take a look at what’s hidden inside an ACL entry and how to create an instance of AclEntry
. First of all, ACL entry consists of several objects:
- Flags
- The flags component is a set of flags to indicate how entries are inherited and propagated
- Values:
DIRECTORY_INHERIT
,FILE_INHERIT
,INHERIT_ONLY
,NO_PROPAGATE_INHERIT
- Permissions
- The permissions component is a set of permissions
- Values:
APPEND_DATA
,DELETE
,DELETE_CHILD
,EXECUTE
,READ_ACL
,READ_ATTRIBUTES
,READ_DATA
,READ_NAMED_ATTRS
,
SYNCHRONIZE
,WRITE_ACL
,WRITE_ATTRIBUTES
,WRITE_DATA
,WRITE_NAMED_ATTRS
,WRITE_OWNER
- Type
- Principal
- The principal component, sometimes called the “who” component, is a
UserPrincipal
corresponding to the identity that the entry grants or denies access - Values retrieved using
UserPrincipalLookupService
- The principal component, sometimes called the “who” component, is a
Given the complexity of a single ACL entry, creators of NIO.2 library saw a very suitable candidate for implementation of a builder pattern. Visit following page for more information on design patterns and builder pattern. So the implementation selects appropriate flags and permissions, binds them with an user principal and sets the type of entry. Please study following code snippet to get familiar with ACL permissions:
Path newDirectoryPath = Paths.get("c:", "testACL"); if (!Files.exists(newDirectoryPath)) { FileAttribute<List<AclEntry>> fileAttributes = new FileAttribute<List<AclEntry>>() { @Override public List<AclEntry> value() { // lookup user principal FileSystem fileSystem = FileSystems.getDefault(); UserPrincipalLookupService userPrincipalLookupService = fileSystem.getUserPrincipalLookupService(); UserPrincipal userPrincipal = null; try { userPrincipal = userPrincipalLookupService.lookupPrincipalByName("JStas"); } catch (IOException e) { throw new RuntimeException(e); } // select ACL flags Set<AclEntryFlag> flags = EnumSet.of(AclEntryFlag.FILE_INHERIT, AclEntryFlag.DIRECTORY_INHERIT); // select ACL permission Set<AclEntryPermission> permissions = EnumSet.of(AclEntryPermission.READ_DATA, AclEntryPermission.WRITE_DATA, AclEntryPermission.EXECUTE); // build ACL entry Builder builder = AclEntry.newBuilder(); builder.setFlags(flags); builder.setPermissions(permissions); builder.setPrincipal(userPrincipal); builder.setType(AclEntryType.DENY); AclEntry entry = builder.build(); List<AclEntry> aclEntryList = new ArrayList<>(); aclEntryList.add(entry); return aclEntryList; } @Override public String name() { return "acl:acl"; } }; try { Files.createDirectory(newDirectoryPath, fileAttributes); } catch (IOException e) { System.err.println(e); } }
To verify successful creation of a directory and its file attributes in Windows 7, select security tab in properties of given folder and click on Advanced. Your newly created entry should be listed in presented table with detail view similar to this one:
Creating a new file
The core part of any file system related code usually involves code that creates single or more files. To create a file we need to use class Files
again and call method createFile
. Just like a directory, a file can be created with initial file attributes and same restrictions apply. Having said that I’m not going to demonstrate the work with file attributes since it is the same as in directory example. Once again this is really simple method with no catch to it so everything is presented in following example:
Path newFilePath = Paths.get("C:", "a.txt"); if (!Files.exists(newFilePath)) { try { Files.createFile(newFilePath); } catch (IOException e) { System.err.println(e); } }
Please notice the use of exists
checking method that prevents FileAlreadyExistsException
.
Reference: | Creating files and directories in NIO.2 from our JCG partner Jakub Stas at the Jakub Stas blog. |