package com.github.marschall.memoryfilesystem;
import java.io.IOException;
import java.net.URI;
import java.nio.file.FileSystem;
import java.nio.file.FileSystemAlreadyExistsException;
import java.nio.file.FileSystems;
import java.nio.file.attribute.BasicFileAttributeView;
import java.nio.file.attribute.DosFileAttributeView;
import java.nio.file.attribute.FileAttributeView;
import java.nio.file.attribute.PosixFileAttributeView;
import java.nio.file.attribute.PosixFilePermission;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
/**
* <a href="https://en.wikipedia.org/wiki/Builder_pattern">Builder</a>
* for conveniently creating create memory file system instances.
*
* <p>The builder takes care of creating the environment and selecting
* the correct class loader to pass to {@link FileSystems#newFileSystem(URI, Map, ClassLoader)}.</p>
*/
public final class MemoryFileSystemBuilder {
private final List<String> roots;
private final Set<String> users;
private final Set<String> groups;
private final Set<Character> forbiddenCharacters;
private final Set<String> additionalFileAttributeViews;
private Set<PosixFilePermission> umask = Collections.emptySet();
private String separator;
private String currentWorkingDirectory;
private StringTransformer storeTransformer;
private StringTransformer lookUpTransformer;
private StringTransformer principalTransformer;
private Collator collator;
private Locale locale;
private MemoryFileSystemBuilder() {
this.roots = new ArrayList<>();
this.users = new LinkedHashSet<>();
this.groups = new LinkedHashSet<>();
this.additionalFileAttributeViews = new HashSet<>();
this.forbiddenCharacters = new HashSet<>();
}
/**
* Add a file system root.
*
* <p>This method is intended to be used in Windows mode to add a drive
* letter eg.:</p>
*
* <pre><code>builder.addRoot("D:\\")</code></pre>
*
* @param root the file system root
* @return the current builder object
*/
public MemoryFileSystemBuilder addRoot(String root) {
this.roots.add(root);
return this;
}
public MemoryFileSystemBuilder setSeprator(String separator) {
this.separator = separator;
return this;
}
public MemoryFileSystemBuilder addForbiddenCharacter(char c) {
this.forbiddenCharacters.add(c);
return this;
}
/**
* Adds a user and a group to the file systems
* {@link java.nio.file.attribute.UserPrincipalLookupService}.
*
* @param userName the name of the user to add
* @return the current builder object
*/
public MemoryFileSystemBuilder addUser(String userName) {
this.users.add(userName);
this.addGroup(userName);
return this;
}
/**
* Adds a group to the file systems
* {@link java.nio.file.attribute.UserPrincipalLookupService}.
*
* @param groupName the name of the group to add
* @return the current builder object
*/
public MemoryFileSystemBuilder addGroup(String groupName) {
this.groups.add(groupName);
return this;
}
/**
* Sets the permissions that will be applied to new files.
*
* @param umask the permissions that will be applied to new files
* @return the receiver
*/
public MemoryFileSystemBuilder setUmask(Set<PosixFilePermission> umask) {
this.umask = umask;
return this;
}
// can't add "owner" directly"
public MemoryFileSystemBuilder addFileAttributeView(String fileAttributeViewName) {
if (FileAttributeViews.isSupported(fileAttributeViewName)) {
if (!FileAttributeViews.BASIC.equals(fileAttributeViewName)) {
// ignore "basic", always supported
this.additionalFileAttributeViews.add(fileAttributeViewName);
}
} else {
throw new IllegalArgumentException("file attribute view \"" + fileAttributeViewName + "\" is not supported");
}
return this;
}
// can't add FileOwnerAttributeView directly
public MemoryFileSystemBuilder addFileAttributeView(Class<? extends FileAttributeView> fileAttributeView) {
return this.addFileAttributeView(FileAttributeViews.mapAttributeView(fileAttributeView));
}
public MemoryFileSystemBuilder setCurrentWorkingDirectory(String currentWorkingDirectory) {
this.currentWorkingDirectory = currentWorkingDirectory;
return this;
}
public MemoryFileSystemBuilder setStoreTransformer(StringTransformer storeTransformer) {
this.storeTransformer = storeTransformer;
return this;
}
public MemoryFileSystemBuilder setLocale(Locale locale) {
this.locale = locale;
return this;
}
private Locale getLocale() {
if (this.locale == null) {
return Locale.getDefault();
} else {
return this.locale;
}
}
public MemoryFileSystemBuilder setCaseSensitive(boolean caseSensitive) {
if (caseSensitive) {
this.lookUpTransformer = StringTransformers.IDENTIY;
this.collator = MemoryFileSystemProperties.caseSensitiveCollator(this.getLocale(), false);
} else {
this.lookUpTransformer = StringTransformers.caseInsensitive(this.getLocale());
this.collator = MemoryFileSystemProperties.caseInsensitiveCollator(this.getLocale());
}
return this;
}
public MemoryFileSystemBuilder setLookUpTransformer(StringTransformer lookUpTransformer) {
this.lookUpTransformer = lookUpTransformer;
return this;
}
public MemoryFileSystemBuilder setCollator(Collator collator) {
this.collator = collator;
return this;
}
/**
* Creates a builder for a very basic file system.
*
* <p>The file system does not support permissions and only supports
* {@link BasicFileAttributeView}. It is UNIX-like in the sense that
* is uses {@literal "/"} as a separator, has a single root and is
* case sensitive and case preserving.<p>
*
* @return the builder
*/
public static MemoryFileSystemBuilder newEmpty() {
return new MemoryFileSystemBuilder();
}
public static MemoryFileSystemBuilder newLinux() {
return new MemoryFileSystemBuilder()
.addRoot(MemoryFileSystemProperties.UNIX_ROOT)
.setSeprator(MemoryFileSystemProperties.UNIX_SEPARATOR)
.addUser(getSystemUserName())
.addGroup(getSystemUserName())
.addFileAttributeView(PosixFileAttributeView.class)
.setCurrentWorkingDirectory("/home/" + getSystemUserName())
.setStoreTransformer(StringTransformers.IDENTIY)
.setCaseSensitive(true)
.addForbiddenCharacter((char) 0);
}
public static MemoryFileSystemBuilder newMacOs() {
// new JVMs use NFC instead of the native NFD
MemoryFileSystemBuilder builder = new MemoryFileSystemBuilder();
return builder
.addRoot(MemoryFileSystemProperties.UNIX_ROOT)
.setSeprator(MemoryFileSystemProperties.UNIX_SEPARATOR)
.addUser(getSystemUserName())
.addGroup(getSystemUserName())
.addFileAttributeView(PosixFileAttributeView.class)
.setCurrentWorkingDirectory("/Users/" + getSystemUserName())
.setCollator(MemoryFileSystemProperties.caseSensitiveCollator(builder.getLocale(), true))
.setLookUpTransformer(StringTransformers.caseInsensitiveMacOSJvm(builder.getLocale()))
.setStoreTransformer(StringTransformers.NFC)
.addForbiddenCharacter((char) 0);
}
public static MemoryFileSystemBuilder newMacOsOldJvm() {
// old JVMs used the native NFC
MemoryFileSystemBuilder builder = new MemoryFileSystemBuilder();
return builder
.addRoot(MemoryFileSystemProperties.UNIX_ROOT)
.setSeprator(MemoryFileSystemProperties.UNIX_SEPARATOR)
.addUser(getSystemUserName())
.addGroup(getSystemUserName())
.addFileAttributeView(PosixFileAttributeView.class)
.setCurrentWorkingDirectory("/Users/" + getSystemUserName())
.setCollator(MemoryFileSystemProperties.caseSensitiveCollator(builder.getLocale(), false))
.setLookUpTransformer(StringTransformers.caseInsensitiveMacOSNative(builder.getLocale()))
.setStoreTransformer(StringTransformers.NFD)
.addForbiddenCharacter((char) 0);
}
public static MemoryFileSystemBuilder newWindows() {
return new MemoryFileSystemBuilder()
.addRoot("C:\\")
.setSeprator(MemoryFileSystemProperties.WINDOWS_SEPARATOR)
.addUser(getSystemUserName())
.addGroup(getSystemUserName())
.addFileAttributeView(DosFileAttributeView.class)
.setCurrentWorkingDirectory("C:\\Users\\" + getSystemUserName())
.setStoreTransformer(StringTransformers.IDENTIY)
.setCaseSensitive(false)
// TODO forbid
// CON, PRN, AUX, CLOCK$, NULL
// COM1, COM2, COM3, COM4, COM5, COM6, COM7, COM8, COM9
// LPT1, LPT2, LPT3, LPT4, LPT5, LPT6, LPT7, LPT8, and LPT9
// TODO forbid
// $Mft, $MftMirr, $LogFile, $Volume, $AttrDef, $Bitmap, $Boot, $BadClus, $Secure,
// $Upcase, $Extend, $Quota, $ObjId and $Reparse
// TODO check for 0x00
.addForbiddenCharacter('\\')
.addForbiddenCharacter('/')
.addForbiddenCharacter(':')
.addForbiddenCharacter('*')
.addForbiddenCharacter('?')
.addForbiddenCharacter('"')
.addForbiddenCharacter('<')
.addForbiddenCharacter('>')
.addForbiddenCharacter('|');
}
static String getSystemUserName() {
return System.getProperty("user.name");
}
/**
* Creates the new file system instance.
*
* @param name the name, must be unique otherwise a
* {@link FileSystemAlreadyExistsException} will be thrown
* @return the file system
* @throws IOException if the file system can't be created
* @see FileSystems#newFileSystem(URI, Map, ClassLoader)
*/
public FileSystem build(String name) throws IOException {
Map<String, ?> env = this.buildEnvironment();
URI uri = URI.create("memory:".concat(name));
ClassLoader classLoader = MemoryFileSystemBuilder.class.getClassLoader();
return FileSystems.newFileSystem(uri, env, classLoader);
}
/**
* Builds an environment to pass to {@link FileSystems#newFileSystem(URI, Map)}.
*
* @return the environment
*/
public Map<String, ?> buildEnvironment() {
Map<String, Object> env = new HashMap<>();
if (!this.roots.isEmpty()) {
env.put(MemoryFileSystemProperties.ROOTS_PROPERTY, this.roots);
}
if (this.separator != null) {
env.put(MemoryFileSystemProperties.DEFAULT_NAME_SEPARATOR_PROPERTY, this.separator);
}
if (this.currentWorkingDirectory != null) {
env.put(MemoryFileSystemProperties.CURRENT_WORKING_DIRECTORY_PROPERTY, this.currentWorkingDirectory);
}
if (this.storeTransformer != null) {
env.put(MemoryFileSystemProperties.PATH_STORE_TRANSFORMER_PROPERTY, this.storeTransformer);
}
if (this.lookUpTransformer != null) {
env.put(MemoryFileSystemProperties.PATH_LOOKUP_TRANSFORMER_PROPERTY, this.lookUpTransformer);
}
if (this.principalTransformer != null) {
env.put(MemoryFileSystemProperties.PRINCIPAL_TRANSFORMER_PROPERTY, this.principalTransformer);
}
if (this.collator != null) {
env.put(MemoryFileSystemProperties.COLLATOR_PROPERTY, this.collator);
}
if (this.additionalFileAttributeViews != null) {
env.put(MemoryFileSystemProperties.FILE_ATTRIBUTE_VIEWS_PROPERTY, this.additionalFileAttributeViews);
}
if (this.umask != null) {
env.put(MemoryFileSystemProperties.UMASK_PROPERTY, this.umask);
}
if (this.forbiddenCharacters != null) {
env.put(MemoryFileSystemProperties.FORBIDDEN_CHARACTERS, this.forbiddenCharacters);
}
if (!this.users.isEmpty()) {
env.put(MemoryFileSystemProperties.USERS_PROPERTY, new ArrayList<>(this.users));
}
if (!this.groups.isEmpty()) {
env.put(MemoryFileSystemProperties.GROUPS_PROPERTY, new ArrayList<>(this.groups));
}
return env;
}
}