package com.github.marschall.memoryfilesystem;
import static com.github.marschall.memoryfilesystem.AutoReleaseLock.autoRelease;
import static java.nio.file.AccessMode.WRITE;
import static java.nio.file.StandardOpenOption.CREATE;
import static java.nio.file.StandardOpenOption.CREATE_NEW;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.nio.file.AccessMode;
import java.nio.file.CopyOption;
import java.nio.file.DirectoryStream;
import java.nio.file.DirectoryStream.Filter;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.FileStore;
import java.nio.file.FileSystem;
import java.nio.file.FileSystemException;
import java.nio.file.FileSystemLoopException;
import java.nio.file.LinkOption;
import java.nio.file.NoSuchFileException;
import java.nio.file.NotDirectoryException;
import java.nio.file.NotLinkException;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.nio.file.WatchService;
import java.nio.file.attribute.BasicFileAttributeView;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.DosFileAttributes;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.attribute.FileAttributeView;
import java.nio.file.attribute.FileOwnerAttributeView;
import java.nio.file.attribute.GroupPrincipal;
import java.nio.file.attribute.PosixFileAttributes;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.PosixFilePermissions;
import java.nio.file.attribute.UserPrincipal;
import java.nio.file.spi.FileSystemProvider;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.regex.Pattern;
import javax.annotation.PreDestroy;
class MemoryFileSystem extends FileSystem {
private static final Set<String> UNSUPPORTED_INITIAL_ATTRIBUES;
private static final Set<OpenOption> NO_OPEN_OPTIONS = Collections.emptySet();
private static final FileAttribute<?>[] NO_FILE_ATTRIBUTES = new FileAttribute<?>[0];
private final String key;
private final String separator;
private final MemoryFileSystemProvider provider;
private final MemoryFileStore store;
private final Iterable<FileStore> stores;
private final ClosedFileSystemChecker checker;
private volatile Map<Root, MemoryDirectory> roots;
private volatile Map<String, Root> rootByKey;
private final ConcurrentMap<AbsolutePath, List<MemoryWatchKey>> watchKeys;
private volatile AbstractPath defaultPath;
private final MemoryUserPrincipalLookupService userPrincipalLookupService;
private final PathParser pathParser;
private final EmptyPath emptyPath;
// computes the file name to be stored of a file
private final StringTransformer storeTransformer;
// computes the look up key of a file name
private final StringTransformer lookUpTransformer;
private final Collator collator;
private final Set<Class<? extends FileAttributeView>> additionalViews;
private final Set<String> supportedFileAttributeViews;
/**
* Operations involving multiple paths (copy and move) need to use
* lock ordering to avoid deadlocks. During the lock acquisition phase
* no changes must be made that might affect the ordering. This involves
* deleting and creating files (with a different capitalization) or links.
* We assume deleting is less common so we block this operation.
*/
private final ReadWriteLock pathOrderingLock;
private final Set<PosixFilePermission> umask;
static {
Set<String> unsupported = new HashSet<>(3);
unsupported.add("lastAccessTime");
unsupported.add("creationTime");
unsupported.add("lastModifiedTime");
UNSUPPORTED_INITIAL_ATTRIBUES = Collections.unmodifiableSet(unsupported);
}
MemoryFileSystem(String key, String separator, PathParser pathParser, MemoryFileSystemProvider provider, MemoryFileStore store,
MemoryUserPrincipalLookupService userPrincipalLookupService, ClosedFileSystemChecker checker, StringTransformer storeTransformer,
StringTransformer lookUpTransformer, Collator collator, Set<Class<? extends FileAttributeView>> additionalViews,
Set<PosixFilePermission> umask) {
this.key = key;
this.separator = separator;
this.pathParser = pathParser;
this.provider = provider;
this.store = store;
this.userPrincipalLookupService = userPrincipalLookupService;
this.checker = checker;
this.storeTransformer = storeTransformer;
this.lookUpTransformer = lookUpTransformer;
this.collator = collator;
this.additionalViews = additionalViews;
this.umask = umask;
this.stores = Collections.<FileStore>singletonList(store);
this.watchKeys = new ConcurrentHashMap<>(1);
this.emptyPath = new EmptyPath(this);
this.supportedFileAttributeViews = this.buildSupportedFileAttributeViews(additionalViews);
this.pathOrderingLock = new ReentrantReadWriteLock();
}
private Set<String> buildSupportedFileAttributeViews(Set<Class<? extends FileAttributeView>> additionalViews) {
if (additionalViews.isEmpty()) {
return Collections.singleton(FileAttributeViews.BASIC);
} else {
Set<String> views = new HashSet<>(additionalViews.size() + 2);
views.add(FileAttributeViews.BASIC);
for (Class<? extends FileAttributeView> viewClass : additionalViews) {
if (FileOwnerAttributeView.class.isAssignableFrom(viewClass)) {
views.add(FileAttributeViews.OWNER);
}
if (viewClass != FileOwnerAttributeView.class) {
views.add(FileAttributeViews.mapAttributeView(viewClass));
}
}
return Collections.unmodifiableSet(views);
}
}
String getKey() {
return this.key;
}
Set<PosixFilePermission> getUmask() {
return this.umask;
}
EntryCreationContext newEntryCreationContext(Path path, FileAttribute<?>[] attributes) throws IOException {
Set<PosixFilePermission> permissions = EnumSet.allOf(PosixFilePermission.class);
for (FileAttribute<?> attribute: attributes) {
if (attribute instanceof PosixFileAttributes) {
permissions = ((PosixFileAttributes) attribute).permissions();
break;
}
}
UserPrincipal user = this.getCurrentUser();
GroupPrincipal group = this.getGroupOf(user);
return new EntryCreationContext(this.additionalViews, permissions, user, group, this, path);
}
private UserPrincipal getCurrentUser() {
UserPrincipal currentUser = CurrentUser.get();
if (currentUser != null) {
return currentUser;
} else {
return this.userPrincipalLookupService.getDefaultUser();
}
}
private GroupPrincipal getGroupOf(UserPrincipal user) throws IOException {
// TODO is this always true?
return this.userPrincipalLookupService.lookupPrincipalByGroupName(user.getName());
}
EmptyPath getEmptyPath() {
return this.emptyPath;
}
/**
* Sets the root directories.
*
* <p>This is a bit annoying.</p>
*
* @param rootDirectories the root directories, not {@code null},
* should not be modified, no defensive copy will be made
*/
void setRootDirectories(Map<Root, MemoryDirectory> rootDirectories) {
this.roots = rootDirectories;
this.rootByKey = this.buildRootsByKey(rootDirectories.keySet());
}
private Map<String, Root> buildRootsByKey(Collection<Root> rootDirectories) {
if (rootDirectories.isEmpty()) {
throw new IllegalArgumentException("a file system root must be present");
} else if (rootDirectories.size() == 1) {
Root root = rootDirectories.iterator().next();
String key = this.lookUpTransformer.transform(root.getKey());
return Collections.singletonMap(key, root);
} else {
Map<String, Root> map = new HashMap<>(rootDirectories.size());
for (Root root : rootDirectories) {
String key = this.lookUpTransformer.transform(root.getKey());
map.put(key, root);
}
return map;
}
}
/**
* Sets the current working directory.
*
* <p>This is used to resolve relative paths. This has to be set
* after {@link #setRootDirectories(Map)}</p>.
*
* @param currentWorkingDirectory the default directory path
*/
void setCurrentWorkingDirectory(String currentWorkingDirectory) {
this.defaultPath = this.getPath(currentWorkingDirectory);
if (!this.defaultPath.isAbsolute()) {
throw new IllegalArgumentException("current working directory must be absolute");
}
}
AbstractPath getDefaultPath() {
return this.defaultPath;
}
BlockChannel newFileChannel(AbstractPath path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
this.checker.check();
MemoryFile file = this.getFile(path, options, attrs);
return file.newChannel(options, path);
}
InputStream newInputStream(AbstractPath path, OpenOption... options) throws IOException {
this.checker.check();
Set<OpenOption> optionsSet;
if (options == null || options.length == 0) {
optionsSet = Collections.emptySet();
} else {
optionsSet = new HashSet<>(options.length);
for (OpenOption option : options) {
optionsSet.add(option);
}
}
MemoryFile file = this.getFile(path, optionsSet);
return file.newInputStream(optionsSet, path);
}
OutputStream newOutputStream(AbstractPath path, OpenOption... options) throws IOException {
this.checker.check();
Set<OpenOption> optionsSet;
if (options == null || options.length == 0) {
optionsSet = DefaultOpenOptions.INSTANCE;
} else {
optionsSet = new HashSet<>(options.length);
for (OpenOption option : options) {
optionsSet.add(option);
}
}
MemoryFile file = this.getFile(path, optionsSet);
return file.newOutputStream(optionsSet, path);
}
private static void checkSupportedInitialAttributes(FileAttribute<?>... attrs) {
if (attrs != null) {
for (FileAttribute<?> attribute : attrs) {
String attributeName = attribute.name();
if (UNSUPPORTED_INITIAL_ATTRIBUES.contains(attributeName)) {
throw new UnsupportedOperationException("'" + attributeName + "' not supported as initial attribute");
}
}
}
}
private GetFileResult getFile(final AbstractPath path, final Set<? extends OpenOption> options, FileAttribute<?>[] attrs, final boolean followSymLinks, final Set<MemorySymbolicLink> encounteredSymlinks) throws IOException {
final FileAttribute<?>[] newAttributes = this.applyUmask(attrs); // TODO lazy
final AbstractPath absolutePath = (AbstractPath) path.toAbsolutePath().normalize();
if (absolutePath.isRoot()) {
throw new FileSystemException(path.toString(), null, "is not a file");
}
final ElementPath elementPath = (ElementPath) absolutePath;
MemoryDirectory rootDirectory = this.getRootDirectory(absolutePath);
final AbstractPath parent = (AbstractPath) absolutePath.getParent();
return this.withWriteLockOnLastDo(rootDirectory, parent, followSymLinks, encounteredSymlinks, new MemoryDirectoryBlock<GetFileResult>() {
@Override
public GetFileResult value(MemoryDirectory directory) throws IOException {
boolean isCreateNew = options.contains(CREATE_NEW);
String fileName = elementPath.getLastNameElement();
String key = MemoryFileSystem.this.lookUpTransformer.transform(fileName);
EntryCreationContext creationContext = MemoryFileSystem.this.newEntryCreationContext(absolutePath, newAttributes);
if (isCreateNew) {
String name = MemoryFileSystem.this.storeTransformer.transform(fileName);
MemoryFile file = new MemoryFile(name, creationContext);
checkSupportedInitialAttributes(newAttributes);
AttributeAccessors.setAttributes(file, newAttributes);
directory.checkAccess(WRITE);
// will throw an exception if already present
directory.addEntry(key, file);
return new GetFileResult(file);
} else {
MemoryEntry storedEntry = directory.getEntry(key);
if (storedEntry == null) {
boolean isCreate = options.contains(CREATE);
if (isCreate) {
String name = MemoryFileSystem.this.storeTransformer.transform(fileName);
MemoryFile file = new MemoryFile(name, creationContext);
checkSupportedInitialAttributes(newAttributes);
AttributeAccessors.setAttributes(file, newAttributes);
directory.checkAccess(WRITE);
directory.addEntry(key, file);
return new GetFileResult(file);
} else {
throw new NoSuchFileException(path.toString());
}
}
if (storedEntry instanceof MemoryFile) {
return new GetFileResult((MemoryFile) storedEntry);
} else if (storedEntry instanceof MemorySymbolicLink && followSymLinks) {
MemorySymbolicLink link = (MemorySymbolicLink) storedEntry;
if (!encounteredSymlinks.add(link)) {
throw new FileSystemLoopException(path.toString());
}
AbstractPath linkTarget = link.getTarget();
if (linkTarget.isAbsolute()) {
return new GetFileResult(linkTarget);
} else {
return new GetFileResult((AbstractPath) parent.resolve(linkTarget));
}
} else {
throw new FileSystemException(absolutePath.toString(), null, "file is a directory");
}
}
}
});
}
static final class GetFileResult {
final MemoryFile file;
final AbstractPath linkTarget;
GetFileResult(MemoryFile file) {
this.file = file;
this.linkTarget = null;
}
GetFileResult(AbstractPath linkTarget) {
this.file = null;
this.linkTarget = linkTarget;
}
}
private MemoryFile getFile(final AbstractPath path, final Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
boolean followSymLinks = Options.isFollowSymLinks(options);
Set<MemorySymbolicLink> encounteredSymlinks;
if (followSymLinks) {
// we don't expect to encounter many symlinks so we initialize to a lower than the default value of 16
// TODO optimized set
encounteredSymlinks = new HashSet<>(4);
} else {
encounteredSymlinks = Collections.emptySet();
}
GetFileResult result = this.getFile(path, options, attrs, followSymLinks, encounteredSymlinks);
while (result.file == null) {
result = this.getFile(result.linkTarget, options, attrs, followSymLinks, encounteredSymlinks);
}
return result.file;
}
private FileAttribute<?>[] applyUmask(FileAttribute<?>[] attributes) {
if (this.umask.isEmpty()) {
return attributes;
}
int length = attributes.length;
boolean changed = false;
FileAttribute<?>[] copy = null;
for (int i = 0; i < length; i++) {
FileAttribute<?> attribute = attributes[i];
if (changed) {
copy[i] = attribute;
continue;
}
if ("posix:permissions".equals(attribute.name())) {
copy = new FileAttribute[length];
changed = true;
if (i > 0) {
System.arraycopy(attributes, 0, copy, 0, i - 1);
}
Set<PosixFilePermission> perms = (Set<PosixFilePermission>) attribute.value();
Set<PosixFilePermission> newPerms = EnumSet.copyOf(perms);
newPerms.removeAll(this.umask);
copy[i] = PosixFilePermissions.asFileAttribute(newPerms);
continue;
}
}
if (changed) {
return copy;
} else {
// umask is set so we can reasonably sure posix permissions are supported
// add permissions and set the to the umask
FileAttribute<?>[] withPermissions = new FileAttribute[length + 1];
System.arraycopy(attributes, 0, withPermissions, 0, length);
EnumSet<PosixFilePermission> permissions = EnumSet.allOf(PosixFilePermission.class);
permissions.removeAll(this.umask);
withPermissions[length] = PosixFilePermissions.asFileAttribute(permissions);
return withPermissions;
}
}
DirectoryStream<Path> newDirectoryStream(final AbstractPath abstractPath, final Filter<? super Path> filter) throws IOException {
final AbstractPath absolutePath = (AbstractPath) abstractPath.toAbsolutePath().normalize();
MemoryDirectory root = this.getRootDirectory(absolutePath);
return this.withReadLockDo(root, absolutePath, false, new MemoryEntryBlock<DirectoryStream<Path>>() {
@Override
public DirectoryStream<Path> value(MemoryEntry entry) throws IOException {
if (!(entry instanceof MemoryDirectory)) {
throw new NotDirectoryException(abstractPath.toString());
}
MemoryDirectory directory = (MemoryDirectory) entry;
return directory.newDirectoryStream(abstractPath, filter);
}
});
}
void createDirectory(final AbstractPath path, final FileAttribute<?>... attrs) throws IOException {
final FileAttribute<?>[] masked = this.applyUmask(attrs);
this.createFile(path, new MemoryEntryCreator() {
@Override
public MemoryEntry create(String name) throws IOException {
EntryCreationContext context = MemoryFileSystem.this.newEntryCreationContext(path, masked);
MemoryDirectory directory = new MemoryDirectory(name, context);
AttributeAccessors.setAttributes(directory, masked);
return directory;
}
});
}
void createSymbolicLink(final AbstractPath link, final AbstractPath target, final FileAttribute<?>... attrs) throws IOException {
final FileAttribute<?>[] masked = this.applyUmask(attrs);
this.createFile(link, new MemoryEntryCreator() {
@Override
public MemoryEntry create(String name) throws IOException {
EntryCreationContext context = MemoryFileSystem.this.newEntryCreationContext(link, masked);
MemorySymbolicLink symbolicLink = new MemorySymbolicLink(name, target, context);
AttributeAccessors.setAttributes(symbolicLink, masked);
return symbolicLink;
}
});
}
void createLink(final AbstractPath link, AbstractPath existing) throws IOException {
final MemoryFile existingFile = this.getFile(existing);
if (existingFile == null) {
throw new FileSystemException(link.toString(), existing.toString(), "hard links are only supported for regular files");
}
this.createFile(link, new MemoryEntryCreator() {
@Override
public MemoryFile create(String name) throws IOException {
EntryCreationContext context = MemoryFileSystem.this.newEntryCreationContext(link, NO_FILE_ATTRIBUTES);
return existingFile.createLink(name, context);
}
});
}
boolean isSameFile(AbstractPath path, AbstractPath path2) throws IOException {
final MemoryFile file = this.getFile(path);
if (file == null) {
return false;
}
final MemoryFile file2 = this.getFile(path2);
if (file2 == null) {
return false;
}
return file.hasSameInodeAs(file2);
}
private MemoryFile getFile(AbstractPath existing) throws IOException {
return this.accessFileReading(existing, true, new MemoryEntryBlock<MemoryFile>() {
@Override
public MemoryFile value(MemoryEntry entry) throws IOException {
if (entry instanceof MemoryFile) {
return (MemoryFile) entry;
}
return null;
}
});
}
private void createFile(final AbstractPath path, final MemoryEntryCreator creator) throws IOException {
this.checker.check();
AbstractPath absolutePath = (AbstractPath) path.toAbsolutePath().normalize();
if (absolutePath.isRoot()) {
throw new FileSystemException(path.toString(), null, "can not create root");
}
final ElementPath elementPath = (ElementPath) absolutePath;
MemoryDirectory rootDirectory = this.getRootDirectory(elementPath);
this.withWriteLockOnLastDo(rootDirectory, (AbstractPath) elementPath.getParent(), true, new MemoryDirectoryBlock<Void>() {
@Override
public Void value(MemoryDirectory directory) throws IOException {
String name = MemoryFileSystem.this.storeTransformer.transform(elementPath.getLastNameElement());
MemoryEntry newEntry = creator.create(name);
String key = MemoryFileSystem.this.lookUpTransformer.transform(newEntry.getOriginalName());
directory.checkAccess(WRITE);
directory.addEntry(key, newEntry);
return null;
}
});
}
AbstractPath toRealPath(AbstractPath abstractPath, LinkOption... options) throws IOException {
this.checker.check();
AbstractPath absolutePath = (AbstractPath) abstractPath.toAbsolutePath().normalize();
boolean followSymLinks = Options.isFollowSymLinks(options);
Set<MemorySymbolicLink> encounteredSymlinks;
if (followSymLinks) {
// we don't expect to encounter many symlinks so we initialize to a lower than the default value of 16
// TODO optimized set
encounteredSymlinks = new HashSet<>(4);
} else {
encounteredSymlinks = Collections.emptySet();
}
MemoryDirectory root = this.getRootDirectory(absolutePath);
return this.toRealPath(root, absolutePath, encounteredSymlinks, followSymLinks);
}
private AbstractPath toRealPath(MemoryDirectory root, AbstractPath path, Set<MemorySymbolicLink> encounteredLinks, boolean followSymLinks) throws IOException {
if (path.isRoot()) {
return (AbstractPath) path.getRoot();
} else if (path instanceof ElementPath) {
ElementPath elementPath = (ElementPath) path;
List<String> nameElements = elementPath.getNameElements();
int pathElementCount = nameElements.size();
List<String> realPath = new ArrayList<>(pathElementCount);
List<AutoRelease> locks = new ArrayList<>(pathElementCount + 1);
try {
locks.add(root.readLock());
MemoryDirectory parent = root;
for (int i = 0; i < pathElementCount; ++i) {
String fileName = nameElements.get(i);
String key = this.lookUpTransformer.transform(fileName);
MemoryEntry current = parent.getEntryOrException(key, path);
locks.add(current.readLock());
realPath.add(current.getOriginalName());
if (followSymLinks && current instanceof MemorySymbolicLink) {
MemorySymbolicLink link = (MemorySymbolicLink) current;
if (!encounteredLinks.add(link)) {
throw new FileSystemLoopException(path.toString());
}
Path symLinkTarget = link.getTarget().toAbsolutePath();
Path newLookUpPath = symLinkTarget;
for (int j = i + 1; j < pathElementCount; ++j) {
newLookUpPath = newLookUpPath.resolve(nameElements.get(j));
}
return this.toRealPath(root, (AbstractPath) newLookUpPath, encounteredLinks, followSymLinks);
}
if (current instanceof MemoryDirectory) {
parent = (MemoryDirectory) current;
} else if (i < (pathElementCount - 1)) {
// all except last must be a directory
throw new NotDirectoryException(path.toString());
}
}
} finally {
for (int i = locks.size() - 1; i >= 0; --i) {
AutoRelease lock = locks.get(i);
lock.close();
}
}
return AbsolutePath.createAboslute(this, (Root) path.getRoot(), realPath);
} else {
throw new IllegalArgumentException("unknown path type" + path);
}
}
void checkAccess(AbstractPath path, final AccessMode... modes) throws IOException {
this.checker.check();
// java.nio.file.spi.FileSystemProvider#checkAccess(Path, AccessMode...)
// says we should follow symbolic links
this.accessFileReading(path, true, new MemoryEntryBlock<Void>() {
@Override
public Void value(MemoryEntry entry) throws IOException {
entry.checkAccess(modes);
return null;
}
});
}
<A extends BasicFileAttributes> A readAttributes(AbstractPath path, final Class<A> type, LinkOption... options) throws IOException {
this.checker.check();
return this.accessFileReading(path, Options.isFollowSymLinks(options), new MemoryEntryBlock<A>() {
@Override
public A value(MemoryEntry entry) throws IOException {
return entry.readAttributes(type);
}
});
}
<V extends FileAttributeView> V getLazyFileAttributeView(AbstractPath path, Class<V> type, LinkOption... options) {
if (type != BasicFileAttributeView.class && !this.additionalViews.contains(type)) {
// unsupported view, specification requires null
return null;
}
InvocationHandler handler = new LazyFileAttributeView<>(path, type, options);
Object proxy = Proxy.newProxyInstance(MemoryFileSystem.class.getClassLoader(), new Class<?>[]{type}, handler);
return type.cast(proxy);
}
<V extends FileAttributeView> V getFileAttributeView(AbstractPath path, final Class<V> type, LinkOption... options) throws IOException {
return this.accessFileReading(path, Options.isFollowSymLinks(options), new MemoryEntryBlock<V>() {
@Override
public V value(MemoryEntry entry) throws IOException {
return entry.getFileAttributeView(type);
}
});
}
Map<String, Object> readAttributes(AbstractPath path, final String attributes, LinkOption... options) throws IOException {
this.checker.check();
return this.accessFileReading(path, Options.isFollowSymLinks(options), new MemoryEntryBlock<Map<String, Object>>() {
@Override
public Map<String, Object> value(MemoryEntry entry) throws IOException {
return AttributeAccessors.readAttributes(entry, attributes);
}
});
}
void setAttribute(AbstractPath path, final String attribute, final Object value, LinkOption... options) throws IOException {
this.checker.check();
this.accessFileWriting(path, Options.isFollowSymLinks(options), new MemoryEntryBlock<Void>() {
@Override
public Void value(MemoryEntry entry) throws IOException {
// TODO write lock?
AttributeAccessors.setAttribute(entry, attribute, value);
return null;
}
});
}
private <R> R accessFileReading(AbstractPath path, boolean followSymLinks, MemoryEntryBlock<? extends R> callback) throws IOException {
return this.accessFile(path, followSymLinks, LockType.READ, callback);
}
private <R> R accessFileWriting(AbstractPath path, boolean followSymLinks, MemoryEntryBlock<? extends R> callback) throws IOException {
return this.accessFile(path, followSymLinks, LockType.WRITE, callback);
}
private <R> R accessFile(AbstractPath path, boolean followSymLinks, LockType lockType, MemoryEntryBlock<? extends R> callback) throws IOException {
this.checker.check();
AbstractPath absolutePath = (AbstractPath) path.toAbsolutePath().normalize();
MemoryDirectory directory = this.getRootDirectory(absolutePath);
if (lockType == LockType.READ) {
return this.withReadLockDo(directory, absolutePath, followSymLinks, callback);
} else {
MemoryDirectory rootDirectory = this.getRootDirectory(absolutePath);
if (absolutePath.isRoot()) {
try (AutoRelease autoRelease = rootDirectory.writeLock()) {
return callback.value(rootDirectory);
}
}
ElementPath elementPath = (ElementPath) absolutePath;
Set<MemorySymbolicLink> encounteredSymlinks;
if (followSymLinks) {
encounteredSymlinks = new HashSet<>(4);
} else {
encounteredSymlinks = Collections.emptySet();
}
return this.withLockDo(rootDirectory, elementPath, encounteredSymlinks, followSymLinks, LockType.WRITE, callback);
}
}
private <R> R withWriteLockOnLastDo(MemoryDirectory root, final AbstractPath path, boolean followSymLinks, final MemoryDirectoryBlock<R> callback) throws IOException {
Set<MemorySymbolicLink> encounteredSymlinks;
if (followSymLinks) {
encounteredSymlinks = new HashSet<>(4);
} else {
encounteredSymlinks = Collections.emptySet();
}
return this.withWriteLockOnLastDo(root, path, followSymLinks, encounteredSymlinks, callback);
}
private <R> R withWriteLockOnLastDo(MemoryDirectory root, final AbstractPath path, boolean followSymLinks, Set<MemorySymbolicLink> encounteredSymlinks, final MemoryDirectoryBlock<R> callback) throws IOException {
return this.withLockDo(root, path, encounteredSymlinks, followSymLinks, LockType.WRITE, new MemoryEntryBlock<R>() {
@Override
public R value(MemoryEntry entry) throws IOException {
if (!(entry instanceof MemoryDirectory)) {
throw new NotDirectoryException(path.toString());
}
return callback.value((MemoryDirectory) entry);
}
});
}
private <R> R withReadLockDo(MemoryDirectory root, AbstractPath path, boolean followSymLinks, MemoryEntryBlock<? extends R> callback) throws IOException {
Set<MemorySymbolicLink> encounteredSymlinks;
if (followSymLinks) {
encounteredSymlinks = new HashSet<>(4);
} else {
encounteredSymlinks = Collections.emptySet();
}
return this.withLockDo(root, path, encounteredSymlinks, followSymLinks, LockType.READ, callback);
}
private <R> R withLockDo(MemoryDirectory root, AbstractPath path, Set<MemorySymbolicLink> encounteredLinks, boolean followSymLinks, LockType lockType, MemoryEntryBlock<? extends R> callback) throws IOException {
if (path.isRoot()) {
try (AutoRelease lock = root.readLock()) {
return callback.value(root);
}
} else if (path instanceof ElementPath) {
R result = null;
Path symLinkTarget = null;
ElementPath elementPath = (ElementPath) path;
List<String> nameElements = elementPath.getNameElements();
int nameElementsSize = nameElements.size();
List<AutoRelease> locks = new ArrayList<>(nameElementsSize + 1);
try {
locks.add(root.readLock());
MemoryDirectory parent = root;
for (int i = 0; i < nameElementsSize; ++i) {
String fileName = nameElements.get(i);
String key = this.lookUpTransformer.transform(fileName);
MemoryEntry current = parent.getEntryOrException(key, path);
boolean isLast = i == nameElementsSize - 1;
if (isLast && lockType == LockType.WRITE) {
locks.add(current.writeLock());
} else {
locks.add(current.readLock());
}
if (followSymLinks && current instanceof MemorySymbolicLink) {
MemorySymbolicLink link = (MemorySymbolicLink) current;
if (!encounteredLinks.add(link)) {
throw new FileSystemLoopException(path.toString());
}
symLinkTarget = link.getTarget();
break;
}
if (isLast) {
result = callback.value(current);
} else if (current instanceof MemoryDirectory) {
parent = (MemoryDirectory) current;
} else {
throw new NotDirectoryException(path.toString());
}
}
} finally {
for (int i = locks.size() - 1; i >= 0; --i) {
AutoRelease lock = locks.get(i);
lock.close();
}
}
if (symLinkTarget == null) {
return result;
} else {
return this.withLockDo(root, (AbstractPath) symLinkTarget, encounteredLinks, followSymLinks, lockType, callback);
}
} else {
throw new IllegalArgumentException("unknown path type" + path);
}
}
private MemoryDirectory getRootDirectory(AbstractPath path) throws IOException {
Path root = path.getRoot();
MemoryDirectory directory = this.roots.get(root);
if (directory == null) {
throw new NoSuchFileException(path.toString(), null, "the root doesn't exist");
}
return directory;
}
void copyOrMove(AbstractPath source, AbstractPath target, TwoPathOperation operation, CopyOption... options) throws IOException {
try (AutoRelease autoRelease = autoRelease(this.pathOrderingLock.writeLock())) {
EndPointCopyContext sourceContext = this.buildEndpointCopyContext(source);
EndPointCopyContext targetContext = this.buildEndpointCopyContext(target);
int order = this.orderPaths(sourceContext, targetContext);
final CopyContext copyContext = buildCopyContext(sourceContext, targetContext, operation, options, order);
AbstractPath firstParent = copyContext.first.parent;
final AbstractPath secondParent = copyContext.second.parent;
if (firstParent == null && secondParent == null) {
// both of the involved paths is a root
// simply ignore
return;
}
if (firstParent == null || secondParent == null) {
// only one of the involved paths is a root
throw new FileSystemException(toStringOrNull(firstParent), toStringOrNull(secondParent), "can't copy or move root directory");
}
MemoryDirectory firstRoot = this.getRootDirectory(copyContext.first.path);
final MemoryDirectory secondRoot = this.getRootDirectory(copyContext.second.path);
this.withWriteLockOnLastDo(firstRoot, firstParent, copyContext.firstFollowSymLinks, new MemoryDirectoryBlock<Void>() {
@Override
public Void value(final MemoryDirectory firstDirectory) throws IOException {
MemoryFileSystem.this.withWriteLockOnLastDo(secondRoot, secondParent, copyContext.secondFollowSymLinks, new MemoryDirectoryBlock<Void>() {
@Override
public Void value(MemoryDirectory secondDirectory) throws IOException {
handleTwoPathOperation(copyContext, firstDirectory, secondDirectory);
return null;
}
});
return null;
}
});
}
}
static void copyOrMoveBetweenFileSystems(MemoryFileSystem sourceFileSystem, MemoryFileSystem targetFileSystem, AbstractPath source, AbstractPath target, TwoPathOperation operation, CopyOption... options) throws IOException {
EndPointCopyContext sourceContext = sourceFileSystem.buildEndpointCopyContext(source);
EndPointCopyContext targetContext = targetFileSystem.buildEndpointCopyContext(target);
int order = orderFileSystems(sourceContext, targetContext);
final CopyContext copyContext = buildCopyContext(sourceContext, targetContext, operation, options, order);
AbstractPath firstParent = copyContext.first.parent;
final AbstractPath secondParent = copyContext.second.parent;
if (firstParent == null || secondParent == null) {
throw new FileSystemException(toStringOrNull(firstParent), toStringOrNull(secondParent), "can't move ore copy the file system root");
}
MemoryDirectory firstRoot = sourceFileSystem.getRootDirectory(copyContext.first.path);
final MemoryDirectory secondRoot = targetFileSystem.getRootDirectory(copyContext.second.path);
copyContext.first.path.getMemoryFileSystem().withWriteLockOnLastDo(firstRoot, firstParent, copyContext.firstFollowSymLinks, new MemoryDirectoryBlock<Void>() {
@Override
public Void value(final MemoryDirectory firstDirectory) throws IOException {
copyContext.second.path.getMemoryFileSystem().withWriteLockOnLastDo(secondRoot, secondParent, copyContext.secondFollowSymLinks, new MemoryDirectoryBlock<Void>() {
@Override
public Void value(MemoryDirectory secondDirectory) throws IOException {
handleTwoPathOperation(copyContext, firstDirectory, secondDirectory);
return null;
}
});
return null;
}
});
}
private static String toStringOrNull(AbstractPath path) {
return Objects.toString(path, null);
}
private int orderPaths(EndPointCopyContext source, EndPointCopyContext target) {
int parentOrder;
if (source.parent == null) {
parentOrder = target.parent == null ? 0 : -1;
} else if (target.parent == null) {
parentOrder = 1;
} else {
parentOrder = source.parent.compareTo(target.parent);
}
if (parentOrder != 0) {
return parentOrder;
} else {
if (source.elementName == null) {
return target.elementName == null ? 0 : -1;
} else if (target.elementName == null) {
return 1;
} else {
return MemoryFileSystem.this.collator.compare(source.elementName, target.elementName);
}
}
}
private static int orderFileSystems(EndPointCopyContext source, EndPointCopyContext target) {
MemoryFileSystem sourceFileSystem = source.path.getMemoryFileSystem();
MemoryFileSystem targetFileSystem = target.path.getMemoryFileSystem();
String sourceKey = sourceFileSystem.getKey();
String targetKey = targetFileSystem.getKey();
int comparison = sourceKey.compareTo(targetKey);
if (comparison != 0) {
return comparison;
} else {
throw new AssertionError("the two file system keys " + sourceKey + " and " + targetKey + " compare equal.");
}
}
private EndPointCopyContext buildEndpointCopyContext(AbstractPath path) {
AbstractPath absolutePath = (AbstractPath) path.toAbsolutePath().normalize();
if (absolutePath.isRoot()) {
return new EndPointCopyContext(absolutePath, null, null);
} else {
ElementPath elementPath = (ElementPath) absolutePath;
AbstractPath parent = (AbstractPath) elementPath.getParent();
String elementName = elementPath.getLastNameElement();
return new EndPointCopyContext(elementPath, parent, elementName);
}
}
static final class EndPointCopyContext {
final AbstractPath path;
final AbstractPath parent;
final String elementName;
EndPointCopyContext(AbstractPath path, AbstractPath parent, String elementName) {
this.path = path;
this.parent = parent;
this.elementName = elementName;
}
}
private static CopyContext buildCopyContext(EndPointCopyContext source, EndPointCopyContext target, TwoPathOperation operation, CopyOption[] options, int order) {
boolean followSymLinks = Options.isFollowSymLinks(options);
boolean replaceExisting = Options.isReplaceExisting(options);
boolean copyAttribues = Options.isCopyAttribues(options);
EndPointCopyContext first;
EndPointCopyContext second;
boolean firstFollowSymLinks;
boolean secondFollowSymLinks;
boolean inverted;
if (order <= 0) {
first = source;
second = target;
firstFollowSymLinks = followSymLinks;
secondFollowSymLinks = false;
inverted = false;
} else {
first = target;
second = source;
firstFollowSymLinks = false;
secondFollowSymLinks = followSymLinks;
inverted = true;
}
return new CopyContext(operation, source, target, first, second, firstFollowSymLinks, secondFollowSymLinks,
inverted, replaceExisting, copyAttribues);
}
static final class CopyContext {
final EndPointCopyContext source;
final EndPointCopyContext target;
final EndPointCopyContext first;
final EndPointCopyContext second;
final boolean firstFollowSymLinks;
final boolean secondFollowSymLinks;
private final boolean inverted;
final boolean replaceExisting;
final boolean copyAttribues;
final TwoPathOperation operation;
CopyContext(TwoPathOperation operation, EndPointCopyContext source, EndPointCopyContext target, EndPointCopyContext first, EndPointCopyContext second,
boolean firstFollowSymLinks, boolean secondFollowSymLinks, boolean inverted, boolean replaceExisting, boolean copyAttribues) {
this.operation = operation;
this.source = source;
this.target = target;
this.first = first;
this.second = second;
this.firstFollowSymLinks = firstFollowSymLinks;
this.secondFollowSymLinks = secondFollowSymLinks;
this.inverted = inverted;
this.replaceExisting = replaceExisting;
this.copyAttribues = copyAttribues;
}
boolean isSourceFollowSymLinks() {
if (this.inverted) {
return this.secondFollowSymLinks;
} else {
return this.firstFollowSymLinks;
}
}
MemoryDirectory getSourceParent(MemoryDirectory firstDirectory, MemoryDirectory secondDirectory) {
if (!this.inverted) {
return firstDirectory;
} else {
return secondDirectory;
}
}
MemoryDirectory getTargetParent(MemoryDirectory firstDirectory, MemoryDirectory secondDirectory) {
if (!this.inverted) {
return secondDirectory;
} else {
return firstDirectory;
}
}
}
void delete(final AbstractPath abstractPath) throws IOException {
try (AutoRelease autoRelease = autoRelease(this.pathOrderingLock.readLock())) {
AbstractPath absolutePath = (AbstractPath) abstractPath.toAbsolutePath().normalize();
if (absolutePath.isRoot()) {
throw new FileSystemException(abstractPath.toString(), null, "can not delete root");
}
final ElementPath elementPath = (ElementPath) absolutePath;
MemoryDirectory rootDirectory = this.getRootDirectory(elementPath);
this.withWriteLockOnLastDo(rootDirectory, (AbstractPath) elementPath.getParent(), true, new MemoryDirectoryBlock<Void>() {
@Override
public Void value(MemoryDirectory directory) throws IOException {
String fileName = elementPath.getLastNameElement();
String key = MemoryFileSystem.this.lookUpTransformer.transform(fileName);
MemoryEntry child = directory.getEntryOrException(key, abstractPath);
try (AutoRelease lock = child.writeLock()) {
if (child instanceof MemoryDirectory) {
MemoryDirectory childDirectory = (MemoryDirectory) child;
childDirectory.checkEmpty(abstractPath);
}
if (child instanceof MemoryFile) {
MemoryFile file = (MemoryFile) child;
if (file.openCount() > 0) {
throw new FileSystemException(abstractPath.toString(), null, "file still open");
}
file.markForDeletion();
}
directory.checkAccess(WRITE);
directory.removeEntry(key);
}
return null;
}
});
}
}
@Override
public FileSystemProvider provider() {
this.checker.check();
return this.provider;
}
@Override
@PreDestroy
public void close() {
// avoid throws IOException
// https://github.com/marschall/memoryfilesystem/issues/76
if (this.checker.close()) {
// closing twice is explicitly allowed by the contract
this.checker.close();
this.provider.close(this);
}
}
@Override
public boolean isOpen() {
return this.checker.isOpen();
}
@Override
public boolean isReadOnly() {
this.checker.check();
return this.store.isReadOnly();
}
@Override
public String getSeparator() {
this.checker.check();
return this.separator;
}
@Override
public Iterable<Path> getRootDirectories() {
this.checker.check();
// this is fine because the iterator does not support modification
return (Iterable<Path>) ((Object) this.roots.keySet());
}
@Override
public Iterable<FileStore> getFileStores() {
this.checker.check();
return this.stores;
}
@Override
public Set<String> supportedFileAttributeViews() {
this.checker.check();
return this.supportedFileAttributeViews;
}
@Override
public AbstractPath getPath(String first, String... more) {
this.checker.check();
// TODO check for maximum length
return this.pathParser.parse(this.rootByKey, first, more);
}
AbstractPath getPathFromUri(String uri) {
this.checker.check();
// TODO check for maximum length
return this.pathParser.parseUri(this.rootByKey, uri);
}
@Override
public PathMatcher getPathMatcher(String syntaxAndPattern) {
this.checker.check();
int colonIndex = syntaxAndPattern.indexOf(':');
if (colonIndex <= 0 || colonIndex == syntaxAndPattern.length() - 1) {
throw new IllegalArgumentException("syntaxAndPattern must have form \"syntax:pattern\" but was \"" + syntaxAndPattern + "\"");
}
String syntax = syntaxAndPattern.substring(0, colonIndex);
String pattern = syntaxAndPattern.substring(colonIndex + 1);
if (syntax.equalsIgnoreCase(GlobPathMatcher.name())) {
return this.pathParser.parseGlob(pattern);
}
if (syntax.equalsIgnoreCase(RegexPathMatcher.name())) {
Pattern regex = Pattern.compile(pattern);
return new RegexPathMatcher(regex);
}
throw new UnsupportedOperationException("unsupported syntax \"" + syntax + "\"");
}
@Override
public MemoryUserPrincipalLookupService getUserPrincipalLookupService() {
this.checker.check();
return this.userPrincipalLookupService;
}
@Override
public WatchService newWatchService() throws IOException {
this.checker.check();
// TODO make configurable
if (true) {
throw new UnsupportedOperationException();
}
return new MemoryFileSystemWatchService(this);
}
void register(MemoryWatchKey watchKey) {
this.checker.check();
AbsolutePath absolutePath = (AbsolutePath) watchKey.watchable().toAbsolutePath();
List<MemoryWatchKey> keys = this.watchKeys.get(watchKey);
if (keys == null) {
keys = new CopyOnWriteArrayList<>();
List<MemoryWatchKey> previous = this.watchKeys.putIfAbsent(absolutePath, keys);
if (previous != null) {
keys = previous;
}
}
keys.add(watchKey);
}
FileStore getFileStore() {
return this.store;
}
Collator getCollator() {
return this.collator;
}
boolean isHidden(AbstractPath abstractPath) throws IOException {
// Posix seems to check only the file name
// TODO write test
return this.accessFileReading(abstractPath, false, new MemoryEntryBlock<Boolean>(){
@Override
public Boolean value(MemoryEntry entry) throws IOException {
Set<String> supportedFileAttributeViews = MemoryFileSystem.this.supportedFileAttributeViews();
if (supportedFileAttributeViews.contains(FileAttributeViews.POSIX)) {
String originalName = entry.getOriginalName();
return !originalName.isEmpty() && originalName.charAt(0) == '.';
} else if (supportedFileAttributeViews.contains(FileAttributeViews.DOS)) {
return entry.readAttributes(DosFileAttributes.class).isHidden();
} else {
return false;
}
}
});
}
private MemoryEntry copyEntry(Path absoluteTargetPath, MemoryEntry sourceEntry, String targetElementName) throws IOException {
if (sourceEntry instanceof MemoryFile) {
MemoryFile sourceFile = (MemoryFile) sourceEntry;
try (AutoRelease lock = sourceFile.readLock()) {
EntryCreationContext context = this.newEntryCreationContext(absoluteTargetPath, NO_FILE_ATTRIBUTES);
return new MemoryFile(targetElementName, context, sourceFile);
}
} else {
if (sourceEntry instanceof MemoryDirectory) {
MemoryDirectory sourceDirectory = (MemoryDirectory) sourceEntry;
try (AutoRelease lock = sourceDirectory.readLock()) {
sourceDirectory.checkEmpty(absoluteTargetPath);
EntryCreationContext context = MemoryFileSystem.this.newEntryCreationContext(absoluteTargetPath, NO_FILE_ATTRIBUTES);
return new MemoryDirectory(targetElementName, context);
}
} else if (sourceEntry instanceof MemorySymbolicLink) {
MemorySymbolicLink sourceLink = (MemorySymbolicLink) sourceEntry;
try (AutoRelease lock = sourceLink.readLock()) {
EntryCreationContext context = MemoryFileSystem.this.newEntryCreationContext(absoluteTargetPath, NO_FILE_ATTRIBUTES);
return new MemorySymbolicLink(targetElementName, sourceLink.getTarget(), context);
}
} else {
throw new AssertionError("unknown entry type:" + sourceEntry);
}
}
}
Path readSymbolicLink(final AbstractPath path) throws IOException {
// look up the parent following symlinks
// then look up the child not following symlinks
AbstractPath parent = (AbstractPath) path.toAbsolutePath().getParent();
return this.accessFileReading(parent, true, new MemoryEntryBlock<Path>() {
@Override
public Path value(MemoryEntry parentEntry) throws IOException {
if (!(parentEntry instanceof MemoryDirectory)) {
throw new FileSystemException(path.toString(), null, "parent is not a directory");
}
MemoryDirectory directory = (MemoryDirectory) parentEntry;
return MemoryFileSystem.this.withReadLockDo(directory, (AbstractPath) path.getFileName(), false, new MemoryEntryBlock<Path>() {
@Override
public Path value(MemoryEntry entry) throws IOException {
if (!(entry instanceof MemorySymbolicLink)) {
throw new NotLinkException("file is not a symbolic link");
}
return ((MemorySymbolicLink) entry).getTarget();
}
});
}
});
}
static void handleTwoPathOperation(CopyContext copyContext, MemoryDirectory firstDirectory, MemoryDirectory secondDirectory) throws IOException {
EndPointCopyContext sourceContext = copyContext.source;
EndPointCopyContext targetContext = copyContext.target;
MemoryDirectory sourceParent = copyContext.getSourceParent(firstDirectory, secondDirectory);
MemoryDirectory targetParent = copyContext.getTargetParent(firstDirectory, secondDirectory);
StringTransformer sourceTransformer = sourceContext.path.getMemoryFileSystem().lookUpTransformer;
String sourceElementName = sourceTransformer.transform(sourceContext.elementName);
MemoryEntry sourceEntry = sourceParent.getEntryOrException(sourceElementName, sourceContext.path);
StringTransformer targetTransformer = targetContext.path.getMemoryFileSystem().lookUpTransformer;
String targetElementName = targetTransformer.transform(targetContext.elementName);
MemoryEntry targetEntry = targetParent.getEntry(targetElementName);
if (sourceEntry == targetEntry) {
// source and target are the same, do nothing
// the way I read Files#copy this is the intention of the spec
return;
}
if (sourceEntry == targetParent) {
// copy or move "folder" -> "folder/sub"
throw new FileSystemException(sourceContext.path.toString(), targetContext.path.toString(), "invalid argument");
}
// have to check permission first
targetParent.checkAccess(WRITE);
if (copyContext.operation.isMove()) {
sourceParent.checkAccess(WRITE);
}
if (targetEntry != null) {
if (!copyContext.replaceExisting) {
throw new FileAlreadyExistsException(targetContext.path.toString());
}
if (targetEntry instanceof MemoryDirectory) {
MemoryDirectory targetDirectory = (MemoryDirectory) targetEntry;
try (AutoRelease lock = targetDirectory.readLock()) {
targetDirectory.checkEmpty(targetContext.path);
}
}
if (targetEntry instanceof MemorySymbolicLink && !copyContext.operation.isMove() && !copyContext.replaceExisting) {
MemorySymbolicLink link = (MemorySymbolicLink) targetEntry;
link.setTarget(copyContext.source.path);
if (copyContext.copyAttribues) {
MemoryEntry toCopy = getCopySource(copyContext, sourceEntry);
targetEntry.initializeAttributes(toCopy);
}
return;
}
// TODO target should become symlink
targetParent.removeEntry(targetElementName);
}
if (copyContext.operation.isMove()) {
sourceParent.removeEntry(sourceElementName);
targetParent.addEntry(targetElementName, sourceEntry);
String newOriginalName = targetContext.path.getMemoryFileSystem().storeTransformer.transform(targetContext.elementName);
sourceEntry.setOriginalName(newOriginalName);
} else {
MemoryEntry toCopy = getCopySource(copyContext, sourceEntry);
MemoryEntry copy = targetContext.path.getMemoryFileSystem().copyEntry(targetContext.path, toCopy, targetElementName);
if (copyContext.copyAttribues) {
copy.initializeAttributes(toCopy);
}
targetParent.addEntry(targetElementName, copy);
}
}
private static MemoryEntry getCopySource(CopyContext copyContext, MemoryEntry sourceEntry) throws IOException {
MemoryEntry toCopy;
if (sourceEntry instanceof MemorySymbolicLink && copyContext.isSourceFollowSymLinks()) {
AbstractPath linkTarget = ((MemorySymbolicLink) sourceEntry).getTarget();
// TODO requires reentrant lock, should build return value object
MemoryFileSystem sourceFileSystem = copyContext.source.path.getFileSystem();
if (linkTarget.isAbsolute()) {
toCopy = sourceFileSystem.getFile(linkTarget, NO_OPEN_OPTIONS, NO_FILE_ATTRIBUTES);
} else {
toCopy = sourceFileSystem.getFile((AbstractPath) copyContext.source.parent.resolve(linkTarget), NO_OPEN_OPTIONS, NO_FILE_ATTRIBUTES);
}
} else {
toCopy = sourceEntry;
}
return toCopy;
}
@Override
public String toString() {
return MemoryFileSystem.class.getSimpleName() + '[' + this.key + ']';
}
class LazyFileAttributeView<V extends FileAttributeView> implements InvocationHandler {
private final AbstractPath path;
private final LinkOption[] options;
private final Class<V> type;
private final AtomicReference<V> attributeView;
LazyFileAttributeView(AbstractPath path, Class<V> type, LinkOption... options) {
this.path = path;
this.options = options;
this.type = type;
this.attributeView = new AtomicReference<>();
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
switch (methodName) {
case "name":
if (args != null && args.length > 0) {
throw new AssertionError("#name() not expected to have any arguments");
}
return FileAttributeViews.mapAttributeView(this.type);
case "toString":
if (args != null && args.length > 0) {
throw new AssertionError("#toString() not expected to have any arguments");
}
return this.type.toString();
case "equals":
if (args == null || args.length != 1) {
throw new AssertionError("#equals() expected to exactly one argument");
}
return proxy == args[0];
case "hashCode":
if (args != null && args.length > 0) {
throw new AssertionError("#hashCode() not expected to have any arguments");
}
return System.identityHashCode(proxy);
default:
try {
return method.invoke(this.getView(), args);
} catch (InvocationTargetException e) {
throw e.getCause();
}
}
}
private V getView() throws IOException {
V v = this.attributeView.get();
if (v != null) {
return v;
} else {
V newValue = MemoryFileSystem.this.getFileAttributeView(this.path, this.type, this.options);
boolean successful = this.attributeView.compareAndSet(null, newValue);
if (successful) {
return newValue;
} else {
return this.attributeView.get();
}
}
}
}
interface MemoryEntryBlock<R> {
R value(MemoryEntry entry) throws IOException;
}
interface MemoryDirectoryBlock<R> {
R value(MemoryDirectory entry) throws IOException;
}
interface MemoryEntryCreator {
MemoryEntry create(String name) throws IOException;
}
enum LockType {
READ,
WRITE;
}
}