package com.github.marschall.memoryfilesystem;
import static com.github.marschall.memoryfilesystem.AutoReleaseLock.autoRelease;
import static java.nio.file.attribute.AclEntryPermission.EXECUTE;
import static java.nio.file.attribute.AclEntryPermission.READ_ACL;
import static java.nio.file.attribute.AclEntryPermission.READ_DATA;
import static java.nio.file.attribute.AclEntryPermission.WRITE_ACL;
import static java.nio.file.attribute.AclEntryPermission.WRITE_DATA;
import static java.nio.file.attribute.AclEntryType.ALLOW;
import static java.nio.file.attribute.AclEntryType.DENY;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.file.AccessDeniedException;
import java.nio.file.AccessMode;
import java.nio.file.FileSystemException;
import java.nio.file.Path;
import java.nio.file.attribute.AclEntry;
import java.nio.file.attribute.AclEntryPermission;
import java.nio.file.attribute.AclEntryType;
import java.nio.file.attribute.AclFileAttributeView;
import java.nio.file.attribute.BasicFileAttributeView;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.DosFileAttributeView;
import java.nio.file.attribute.DosFileAttributes;
import java.nio.file.attribute.FileAttributeView;
import java.nio.file.attribute.FileOwnerAttributeView;
import java.nio.file.attribute.FileTime;
import java.nio.file.attribute.GroupPrincipal;
import java.nio.file.attribute.PosixFileAttributeView;
import java.nio.file.attribute.PosixFileAttributes;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.UserDefinedFileAttributeView;
import java.nio.file.attribute.UserPrincipal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
abstract class MemoryEntryAttributes {
// protected by read and write locks
private long lastModifiedTime;
private long lastAccessTime;
private long creationTime;
private final Map<String, InitializingFileAttributeView> additionalViews;
private final ReadWriteLock lock;
private final BasicFileAttributeView basicFileAttributeView;
private final MemoryFileSystem fileSystem;
MemoryEntryAttributes(EntryCreationContext context) {
this.basicFileAttributeView = this.newBasicFileAttributeView();
this.fileSystem = context.fileSystem;
this.lock = new ReentrantReadWriteLock();
long now = this.getNow();
this.lastAccessTime = now;
this.lastModifiedTime = now;
this.creationTime = now;
if (context.additionalViews.isEmpty()) {
this.additionalViews = Collections.emptyMap();
} else if (context.additionalViews.size() == 1) {
InitializingFileAttributeView view = this.instantiate(context.firstView(), context);
this.additionalViews = Collections.singletonMap(view.name(), view);
} else {
this.additionalViews = new HashMap<>(context.additionalViews.size());
for (Class<? extends FileAttributeView> viewClass : context.additionalViews) {
if (viewClass != FileOwnerAttributeView.class) {
InitializingFileAttributeView view = this.instantiate(viewClass, context);
this.additionalViews.put(view.name(), view);
if (FileOwnerAttributeView.class.isAssignableFrom(viewClass)) {
this.additionalViews.put(FileAttributeViews.OWNER, view);
}
}
}
}
}
abstract BasicFileAttributeView newBasicFileAttributeView();
AutoRelease readLock() {
return autoRelease(this.lock.readLock());
}
AutoRelease writeLock() {
return autoRelease(this.lock.writeLock());
}
FileTime lastModifiedTime() {
return FileTime.fromMillis(this.lastModifiedTime);
}
FileTime lastAccessTime() {
return FileTime.fromMillis(this.lastAccessTime);
}
FileTime creationTime() {
return FileTime.fromMillis(this.creationTime);
}
long getNow() {
return System.currentTimeMillis();
}
private UserPrincipal getCurrentUser() {
UserPrincipal user = CurrentUser.get();
if (user == null) {
return this.fileSystem.getUserPrincipalLookupService().getDefaultUser();
} else {
return user;
}
}
private GroupPrincipal getCurrentGroup() {
// TODO special case for just one user
return CurrentGroup.get();
}
private InitializingFileAttributeView instantiate(Class<? extends FileAttributeView> viewClass,
EntryCreationContext context) {
if (viewClass == PosixFileAttributeView.class) {
return new MemoryPosixFileAttributeView(this, context);
} else if (viewClass == DosFileAttributeView.class) {
return new MemoryDosFileAttributeView(this, context);
} else if (viewClass == UserDefinedFileAttributeView.class) {
return new MemoryUserDefinedFileAttributeView(this);
} else if (viewClass == AclFileAttributeView.class) {
return new MemoryAclFileAttributeView(this, context);
} else {
throw new IllegalArgumentException("unknown file attribute view: " + viewClass);
}
}
void initializeAttributes(MemoryEntryAttributes other) throws IOException {
try (AutoRelease lock = this.writeLock()) {
this.getInitializingFileAttributeView().initializeFrom(other.getBasicFileAttributeView());
for (InitializingFileAttributeView view : this.additionalViews.values()) {
view.initializeFrom(other.additionalViews);
}
}
}
void initializeRoot() {
try (AutoRelease lock = this.readLock()) {
for (InitializingFileAttributeView view : this.additionalViews.values()) {
view.initializeRoot();
}
}
}
void modified() {
// No write lock because this was to be folded in an operation with a write lock
long now = this.getNow();
this.lastAccessTime = now;
this.lastModifiedTime = now;
}
void accessed() {
// No write lock because this was to be folded in an operation with a write lock
this.lastAccessTime = this.getNow();
}
void setTimes(FileTime lastModifiedTime, FileTime lastAccessTime, FileTime createTime) throws AccessDeniedException {
try (AutoRelease lock = this.writeLock()) {
this.checkAccess(AccessMode.WRITE);
if (lastModifiedTime != null) {
this.lastModifiedTime = lastModifiedTime.toMillis();
}
if (lastAccessTime != null) {
this.lastAccessTime = lastAccessTime.toMillis();
}
if (createTime != null) {
this.creationTime = createTime.toMillis();
}
}
}
<A extends FileAttributeView> A getFileAttributeView(Class<A> type) throws AccessDeniedException {
try (AutoRelease lock = this.readLock()) {
if (type == BasicFileAttributeView.class) {
return (A) this.getBasicFileAttributeView();
} else {
String name = null;
if (type == FileOwnerAttributeView.class) {
// owner is either mapped to POSIX or ACL
// TODO POSIX and ACL?
if (this.additionalViews.containsKey(FileAttributeViews.POSIX)) {
name = FileAttributeViews.POSIX;
} else if (this.additionalViews.containsKey(FileAttributeViews.ACL)) {
name = FileAttributeViews.ACL;
}
} else {
name = FileAttributeViews.mapAttributeView(type);
}
if (name == null) {
throw new UnsupportedOperationException("file attribute view" + type + " not supported");
}
FileAttributeView view = this.additionalViews.get(name);
if (view != null) {
return (A) view;
} else {
throw new UnsupportedOperationException("file attribute view" + type + " not supported");
}
}
}
}
<A extends BasicFileAttributes> A readAttributes(Class<A> type) throws IOException {
try (AutoRelease lock = this.readLock()) {
this.checkAccess(AccessMode.READ);
if (type == BasicFileAttributes.class) {
return (A) this.getBasicFileAttributeView().readAttributes();
} else {
String viewName = FileAttributeViews.mapFileAttributes(type);
if (viewName != null) {
FileAttributeView view = this.additionalViews.get(viewName);
if (view instanceof BasicFileAttributeView) {
return (A) ((BasicFileAttributeView) view).readAttributes();
} else {
throw new UnsupportedOperationException("file attributes " + type + " not supported");
}
} else {
throw new UnsupportedOperationException("file attributes " + type + " not supported");
}
}
}
}
void checkAccess(AccessMode... modes) throws AccessDeniedException {
try (AutoRelease lock = this.readLock()) {
AccessMode unsupported = this.getUnsupported(modes);
if (unsupported != null) {
throw new UnsupportedOperationException("access mode " + unsupported + " is not supported");
}
for (Object attributeView : this.additionalViews.values()) {
if (attributeView instanceof AccessCheck) {
AccessCheck accessCheck = (AccessCheck) attributeView;
accessCheck.checkAccess(modes);
}
}
}
}
void checkAccess(AccessMode mode) throws AccessDeniedException {
try (AutoRelease lock = this.readLock()) {
AccessMode unsupported = this.getUnsupported(mode);
if (unsupported != null) {
throw new UnsupportedOperationException("access mode " + unsupported + " is not supported");
}
for (Object attributeView : this.additionalViews.values()) {
if (attributeView instanceof AccessCheck) {
AccessCheck accessCheck = (AccessCheck) attributeView;
accessCheck.checkAccess(mode);
}
}
}
}
private AccessMode getUnsupported(AccessMode... modes) {
for (AccessMode mode : modes) {
if (!(mode == AccessMode.READ || mode == AccessMode.WRITE || mode == AccessMode.EXECUTE)) {
return mode;
}
}
return null;
}
private AccessMode getUnsupported(AccessMode mode) {
if (!(mode == AccessMode.READ || mode == AccessMode.WRITE || mode == AccessMode.EXECUTE)) {
return mode;
}
return null;
}
private InitializingFileAttributeView getInitializingFileAttributeView() {
return (InitializingFileAttributeView) this.basicFileAttributeView;
}
BasicFileAttributeView getBasicFileAttributeView() {
return this.basicFileAttributeView;
}
abstract class MemoryEntryFileAttributesView implements InitializingFileAttributeView, BasicFileAttributeView {
@Override
public String name() {
return FileAttributeViews.BASIC;
}
@Override
public void initializeFrom(BasicFileAttributeView basicFileAttributeView) throws IOException {
BasicFileAttributes otherAttributes = basicFileAttributeView.readAttributes();
this.setTimes(otherAttributes.lastModifiedTime(), otherAttributes.lastAccessTime(), otherAttributes.creationTime());
}
@Override
public void initializeFrom(Map<String, ? extends FileAttributeView> additionalAttributes) {
// ignore
}
@Override
public void initializeRoot() {
// ignore
}
@Override
public void setTimes(FileTime lastModifiedTime, FileTime lastAccessTime, FileTime createTime) throws IOException {
MemoryEntryAttributes.this.setTimes(lastModifiedTime, lastAccessTime, createTime);
}
}
static abstract class MemoryEntryFileAttributes implements BasicFileAttributes {
private final FileTime lastModifiedTime;
private final FileTime lastAccessTime;
private final FileTime creationTime;
private final Object fileKey;
MemoryEntryFileAttributes(Object fileKey, FileTime lastModifiedTime, FileTime lastAccessTime, FileTime creationTime) {
this.fileKey = fileKey;
this.lastModifiedTime = lastModifiedTime;
this.lastAccessTime = lastAccessTime;
this.creationTime = creationTime;
}
@Override
public FileTime lastModifiedTime() {
return this.lastModifiedTime;
}
@Override
public FileTime lastAccessTime() {
return this.lastAccessTime;
}
@Override
public FileTime creationTime() {
return this.creationTime;
}
@Override
public Object fileKey() {
return this.fileKey;
}
}
static abstract class DelegatingFileAttributesView implements InitializingFileAttributeView {
final MemoryEntryAttributes attributes;
DelegatingFileAttributesView(MemoryEntryAttributes entry) {
this.attributes = entry;
}
public void setTimes(FileTime lastModifiedTime, FileTime lastAccessTime, FileTime createTime) throws IOException {
this.attributes.getBasicFileAttributeView().setTimes(lastModifiedTime, lastAccessTime, createTime);
}
@Override
public void initializeFrom(BasicFileAttributeView basicFileAttributeView) {
// ignore
}
@Override
public void initializeRoot() {
// ignore
}
@Override
public void initializeFrom(Map<String, ? extends FileAttributeView> additionalAttributes) {
FileAttributeView selfAttributes = additionalAttributes.get(this.name());
if (selfAttributes != null) {
this.initializeFromSelf(selfAttributes);
}
}
abstract void initializeFromSelf(FileAttributeView selfAttributes);
}
static class MemoryAclFileAttributeView extends MemoryFileOwnerAttributeView implements AclFileAttributeView, AccessCheck {
private List<AclEntry> acl;
private final Path path;
MemoryAclFileAttributeView(MemoryEntryAttributes attributes, EntryCreationContext context) {
super(attributes, context);
this.acl = Collections.emptyList();
this.path = context.path;
}
@Override
public String name() {
return FileAttributeViews.ACL;
}
@Override
void initializeFromSelf(FileAttributeView selfAttributes) {
this.acl = new ArrayList<>(((MemoryAclFileAttributeView) selfAttributes).acl);
}
@Override
public void setAcl(List<AclEntry> acl) throws IOException {
this.checkAccess(WRITE_ACL);
try (AutoRelease lock = this.attributes.writeLock()) {
this.acl = new ArrayList<>(acl); // will do null check
}
}
@Override
public List<AclEntry> getAcl() throws IOException {
this.checkAccess(READ_ACL);
try (AutoRelease lock = this.attributes.readLock()) {
return new ArrayList<>(this.acl);
}
}
public void checkAccess(AclEntryPermission mode) throws AccessDeniedException {
// TODO "OWNER@", "GROUP@", and "EVERYONE@"
UserPrincipal currentUser = this.attributes.getCurrentUser();
GroupPrincipal currentGroup = this.attributes.getCurrentGroup();
for (AclEntry entry : this.acl) {
UserPrincipal principal = entry.principal();
if (principal.equals(currentUser) || principal.equals(currentGroup)) {
Set<AclEntryPermission> permissions = entry.permissions();
boolean applies = permissions.contains(mode);
AclEntryType type = entry.type();
if (applies) {
if (type == ALLOW) {
return;
}
if (type == DENY) {
throw new AccessDeniedException(this.path.toString());
}
}
}
}
}
@Override
public void checkAccess(AccessMode mode) throws AccessDeniedException {
switch (mode) {
case READ:
this.checkAccess(READ_DATA);
break;
case WRITE:
this.checkAccess(WRITE_DATA);
break;
case EXECUTE:
this.checkAccess(EXECUTE);
break;
default:
throw new UnsupportedOperationException("access mode " + mode + " is not supported");
}
}
@Override
public void checkAccess(AccessMode[] modes) throws AccessDeniedException {
for (AccessMode mode : modes) {
this.checkAccess(mode);
}
}
}
static class MemoryDosFileAttributeView extends DelegatingFileAttributesView implements DosFileAttributeView, AccessCheck {
private boolean readOnly;
private boolean hidden;
private boolean system;
private boolean archive;
private final Path path;
MemoryDosFileAttributeView(MemoryEntryAttributes attributes, EntryCreationContext context) {
super(attributes);
this.path = context.path;
}
@Override
public String name() {
return FileAttributeViews.DOS;
}
@Override
public void initializeRoot() {
this.hidden = true;
this.system = true;
}
@Override
public DosFileAttributes readAttributes() throws IOException {
this.attributes.checkAccess(AccessMode.READ);
try (AutoRelease lock = this.attributes.readLock()) {
BasicFileAttributeView view = this.attributes.getFileAttributeView(BasicFileAttributeView.class);
return new MemoryDosFileAttributes(view.readAttributes(), this.readOnly, this.hidden, this.system, this.archive);
}
}
@Override
void initializeFromSelf(FileAttributeView selfAttributes) {
MemoryDosFileAttributeView other = (MemoryDosFileAttributeView) selfAttributes;
this.readOnly = other.readOnly;
this.hidden = other.hidden;
this.system = other.system;
this.archive = other.archive;
}
@Override
public void setReadOnly(boolean value) throws IOException {
try (AutoRelease lock = this.attributes.writeLock()) {
// don't check access
this.readOnly = value;
}
}
@Override
public void setHidden(boolean value) throws IOException{
try (AutoRelease lock = this.attributes.writeLock()) {
// don't check access
this.hidden = value;
}
}
@Override
public void setSystem(boolean value) throws IOException {
try (AutoRelease lock = this.attributes.writeLock()) {
// don't check access
this.system = value;
}
}
@Override
public void setArchive(boolean value) throws IOException {
try (AutoRelease lock = this.attributes.writeLock()) {
// don't check access
this.archive = value;
}
}
@Override
public void checkAccess(AccessMode mode) throws AccessDeniedException {
switch (mode) {
case READ:
// always fine
break;
case WRITE:
if (this.readOnly) {
throw new AccessDeniedException(this.path.toString());
}
break;
case EXECUTE:
// always fine
break;
default:
throw new UnsupportedOperationException("access mode " + mode + " is not supported");
}
}
@Override
public void checkAccess(AccessMode[] modes) throws AccessDeniedException {
for (AccessMode mode : modes) {
this.checkAccess(mode);
}
}
}
static class DelegatingAttributes implements BasicFileAttributes {
private final BasicFileAttributes delegate;
DelegatingAttributes(BasicFileAttributes delegate) {
this.delegate = delegate;
}
@Override
public FileTime lastModifiedTime() {
return this.delegate.lastModifiedTime();
}
@Override
public FileTime lastAccessTime() {
return this.delegate.lastAccessTime();
}
@Override
public FileTime creationTime() {
return this.delegate.creationTime();
}
@Override
public boolean isRegularFile() {
return this.delegate.isRegularFile();
}
@Override
public boolean isDirectory() {
return this.delegate.isDirectory();
}
@Override
public boolean isSymbolicLink() {
return this.delegate.isSymbolicLink();
}
@Override
public boolean isOther() {
return this.delegate.isOther();
}
@Override
public long size() {
return this.delegate.size();
}
@Override
public Object fileKey() {
return this.delegate.fileKey();
}
}
static class MemoryPosixFileAttributes extends DelegatingAttributes implements PosixFileAttributes {
private final UserPrincipal owner;
private final GroupPrincipal group;
private final Set<PosixFilePermission> permissions;
MemoryPosixFileAttributes(BasicFileAttributes delegate, UserPrincipal owner, GroupPrincipal group, Set<PosixFilePermission> permissions) {
super(delegate);
this.owner = owner;
this.group = group;
this.permissions = permissions;
}
@Override
public UserPrincipal owner() {
return this.owner;
}
@Override
public GroupPrincipal group() {
return this.group;
}
@Override
public Set<PosixFilePermission> permissions() {
return this.permissions;
}
}
static class MemoryDosFileAttributes extends DelegatingAttributes implements DosFileAttributes {
private final boolean readOnly;
private final boolean hidden;
private final boolean system;
private final boolean archive;
MemoryDosFileAttributes(BasicFileAttributes delegate, boolean readOnly, boolean hidden, boolean system, boolean archive) {
super(delegate);
this.readOnly = readOnly;
this.hidden = hidden;
this.system = system;
this.archive = archive;
}
@Override
public boolean isReadOnly() {
return this.readOnly;
}
@Override
public boolean isHidden() {
return this.hidden;
}
@Override
public boolean isArchive() {
return this.archive;
}
@Override
public boolean isSystem() {
return this.system;
}
}
static abstract class MemoryFileOwnerAttributeView extends DelegatingFileAttributesView implements FileOwnerAttributeView {
private UserPrincipal owner;
MemoryFileOwnerAttributeView(MemoryEntryAttributes attributes, EntryCreationContext context) {
super(attributes);
if (context.user == null) {
throw new NullPointerException("owner");
}
this.owner = context.user;
}
@Override
public UserPrincipal getOwner() {
try (AutoRelease lock = this.attributes.readLock()) {
return this.owner;
}
}
@Override
public void setOwner(UserPrincipal owner) throws IOException {
// TODO check same file system
if (owner == null) {
throw new IllegalArgumentException("owner must not be null");
}
try (AutoRelease lock = this.attributes.writeLock()) {
this.attributes.checkAccess(AccessMode.WRITE);
this.owner = owner;
}
}
}
static class MemoryPosixFileAttributeView extends MemoryFileOwnerAttributeView implements PosixFileAttributeView, AccessCheck {
private GroupPrincipal group;
private int permissions;
private final Path path;
MemoryPosixFileAttributeView(MemoryEntryAttributes attributes, EntryCreationContext context) {
super(attributes, context);
if (context.group == null) {
throw new NullPointerException("group");
}
this.group = context.group;
this.permissions = toMask(context.permissions);
this.path = context.path;
}
@Override
public String name() {
return FileAttributeViews.POSIX;
}
@Override
void initializeFromSelf(FileAttributeView selfAttributes) {
MemoryPosixFileAttributeView other = (MemoryPosixFileAttributeView) selfAttributes;
this.group = other.group;
this.permissions = other.permissions;
}
@Override
public void setGroup(GroupPrincipal group) throws IOException {
// TODO check same file system
if (group == null) {
throw new IllegalArgumentException("group must not be null");
}
try (AutoRelease lock = this.attributes.writeLock()) {
this.attributes.checkAccess(AccessMode.WRITE);
this.group = group;
}
}
@Override
public PosixFileAttributes readAttributes() throws IOException {
this.attributes.checkAccess(AccessMode.READ);
try (AutoRelease lock = this.attributes.readLock()) {
BasicFileAttributeView view = this.attributes.getFileAttributeView(BasicFileAttributeView.class);
return new MemoryPosixFileAttributes(view.readAttributes(), this.getOwner(), this.group, toSet(this.permissions));
}
}
@Override
public void setPermissions(Set<PosixFilePermission> perms) throws IOException {
if (perms == null) {
throw new IllegalArgumentException("permissions must not be null");
}
try (AutoRelease lock = this.attributes.writeLock()) {
this.assertOwner();
this.permissions = toMask(perms);
}
}
@Override
public void checkAccess(AccessMode mode) throws AccessDeniedException {
UserPrincipal user = this.attributes.getCurrentUser();
PosixFilePermission permission;
if (user == this.getOwner()) {
permission = this.translateOwnerMode(mode);
} else {
GroupPrincipal group = this.attributes.getCurrentGroup();
if (group == this.group) {
permission = this.translateGroupMode(mode);
} else {
permission = this.translateOthersMode(mode);
}
}
int flag = 1 << permission.ordinal() & this.permissions;
if (flag == 0) {
throw new AccessDeniedException(this.path.toString());
}
}
void assertOwner() throws AccessDeniedException {
UserPrincipal user = this.attributes.getCurrentUser();
if (!this.getOwner().equals(user)) {
throw new AccessDeniedException(this.path.toString());
}
}
@Override
public void checkAccess(AccessMode[] modes) throws AccessDeniedException {
for (AccessMode mode : modes) {
// TODO optimize user lookup
this.checkAccess(mode);
}
}
private PosixFilePermission translateOwnerMode(AccessMode mode) {
switch (mode) {
case READ:
return PosixFilePermission.OWNER_READ;
case WRITE:
return PosixFilePermission.OWNER_WRITE;
case EXECUTE:
return PosixFilePermission.OWNER_EXECUTE;
default:
throw new UnsupportedOperationException("access mode " + mode + " is not supported");
}
}
private PosixFilePermission translateGroupMode(AccessMode mode) {
switch (mode) {
case READ:
return PosixFilePermission.GROUP_READ;
case WRITE:
return PosixFilePermission.GROUP_WRITE;
case EXECUTE:
return PosixFilePermission.GROUP_EXECUTE;
default:
throw new UnsupportedOperationException("access mode " + mode + " is not supported");
}
}
private PosixFilePermission translateOthersMode(AccessMode mode) {
switch (mode) {
case READ:
return PosixFilePermission.OTHERS_READ;
case WRITE:
return PosixFilePermission.OTHERS_WRITE;
case EXECUTE:
return PosixFilePermission.OTHERS_EXECUTE;
default:
throw new UnsupportedOperationException("access mode " + mode + " is not supported");
}
}
}
static class MemoryUserDefinedFileAttributeView extends DelegatingFileAttributesView implements UserDefinedFileAttributeView, BasicFileAttributeView {
// can potentially be null
// try to delay instantiating as long as possible to keep per file object overhead minimal
// protected by lock of memory entry
private Map<String, byte[]> values;
MemoryUserDefinedFileAttributeView(MemoryEntryAttributes attributes) {
super(attributes);
}
@Override
public BasicFileAttributes readAttributes() throws IOException {
throw new UnsupportedOperationException("readAttributes");
}
@Override
void initializeFromSelf(FileAttributeView selfAttributes) {
MemoryUserDefinedFileAttributeView other = (MemoryUserDefinedFileAttributeView) selfAttributes;
if (other.values == null) {
this.values = null;
} else {
this.values = new HashMap<>(other.values.size());
for (Entry<String, byte[]> entry : other.values.entrySet()) {
this.values.put(entry.getKey(), entry.getValue().clone());
}
}
}
private Map<String, byte[]> getValues() {
if (this.values == null) {
this.values = new HashMap<>(3);
}
return this.values;
}
@Override
public String name() {
return FileAttributeViews.USER;
}
@Override
public List<String> list() throws IOException {
try (AutoRelease lock = this.attributes.readLock()) {
if (this.values == null) {
return Collections.emptyList();
} else {
Set<String> keys = this.getValues().keySet();
return new ArrayList<String>(keys);
}
}
}
@Override
public int size(String name) throws IOException {
try (AutoRelease lock = this.attributes.readLock()) {
byte[] value = this.getValue(name);
return value.length;
}
}
private byte[] getValue(String name) throws IOException {
if (name == null) {
throw new NullPointerException("name is null");
}
if (this.values == null) {
throw new FileSystemException(null, null, "attribute " + name + " not present");
}
byte[] value = this.values.get(name);
if (value == null) {
throw new FileSystemException(null, null, "attribute " + name + " not present");
}
return value;
}
@Override
public int read(String name, ByteBuffer dst) throws IOException {
try (AutoRelease lock = this.attributes.readLock()) {
byte[] value = this.getValue(name);
int remaining = dst.remaining();
int required = value.length;
if (remaining < required) {
throw new FileSystemException(null, null, required + " bytes in buffer required but only " + remaining + " available");
}
int startPosition = dst.position();
dst.put(value);
int endPosition = dst.position();
// TODO check if successful?
return endPosition - startPosition;
}
}
@Override
public int write(String name, ByteBuffer src) throws IOException {
try (AutoRelease lock = this.attributes.writeLock()) {
if (name == null) {
throw new NullPointerException("name is null");
}
if (src == null) {
throw new NullPointerException("buffer is null");
}
int remaining = src.remaining();
byte[] dst = new byte[remaining];
int startPosition = src.position();
src.get(dst);
int endPosition = src.position();
this.getValues().put(name, dst);
// TODO check if successful?
return endPosition - startPosition;
}
}
@Override
public void delete(String name) throws IOException {
try (AutoRelease lock = this.attributes.writeLock()) {
if (this.values != null) {
if (name == null) {
throw new NullPointerException("name is null");
}
this.values.remove(name);
}
}
}
}
class MemoryDirectoryFileAttributesView extends MemoryEntryFileAttributesView {
@Override
public BasicFileAttributes readAttributes() throws IOException {
MemoryEntryAttributes.this.checkAccess(AccessMode.READ);
try (AutoRelease lock = MemoryEntryAttributes.this.readLock()) {
FileTime creationTime = MemoryEntryAttributes.this.creationTime();
FileTime lastModifiedTime = MemoryEntryAttributes.this.lastModifiedTime();
FileTime lastAccessTime = MemoryEntryAttributes.this.lastAccessTime();
return new MemoryDirectoryFileAttributes(MemoryEntryAttributes.this, lastModifiedTime, lastAccessTime, creationTime);
}
}
}
static final class MemoryDirectoryFileAttributes extends MemoryEntryFileAttributes {
MemoryDirectoryFileAttributes(Object fileKey, FileTime lastModifiedTime, FileTime lastAccessTime, FileTime creationTime) {
super(fileKey, lastModifiedTime, lastAccessTime, creationTime);
}
@Override
public boolean isRegularFile() {
return false;
}
@Override
public boolean isDirectory() {
return true;
}
@Override
public boolean isSymbolicLink() {
return false;
}
@Override
public boolean isOther() {
return false;
}
@Override
public long size() {
// REVIEW make configurable
return -1L;
}
}
class MemorySymbolicLinkAttributesView extends MemoryEntryFileAttributesView {
@Override
public BasicFileAttributes readAttributes() throws IOException {
MemoryEntryAttributes.this.checkAccess(AccessMode.READ);
try (AutoRelease lock = MemoryEntryAttributes.this.readLock()) {
FileTime lastModifiedTime = MemoryEntryAttributes.this.lastModifiedTime();
FileTime lastAccessTime = MemoryEntryAttributes.this.lastAccessTime();
FileTime creationTime = MemoryEntryAttributes.this.creationTime();
return new MemorySymbolicLinkAttributes(MemoryEntryAttributes.this, lastModifiedTime, lastAccessTime, creationTime);
}
}
}
static final class MemorySymbolicLinkAttributes extends MemoryEntryFileAttributes {
MemorySymbolicLinkAttributes(Object fileKey, FileTime lastModifiedTime, FileTime lastAccessTime, FileTime creationTime) {
super(fileKey, lastModifiedTime, lastAccessTime, creationTime);
}
@Override
public boolean isRegularFile() {
return false;
}
@Override
public boolean isDirectory() {
return false;
}
@Override
public boolean isSymbolicLink() {
return true;
}
@Override
public boolean isOther() {
return false;
}
@Override
public long size() {
// REVIEW make configurable
return -1L;
}
}
class MemoryFileAttributesView extends MemoryEntryFileAttributesView {
@Override
public BasicFileAttributes readAttributes() throws IOException {
MemoryEntryAttributes.this.checkAccess(AccessMode.READ);
try (AutoRelease lock = MemoryEntryAttributes.this.readLock()) {
FileTime lastModifiedTime = MemoryEntryAttributes.this.lastModifiedTime();
FileTime lastAccessTime = MemoryEntryAttributes.this.lastAccessTime();
FileTime creationTime = MemoryEntryAttributes.this.creationTime();
return new MemoryFileAttributes(MemoryEntryAttributes.this, lastModifiedTime, lastAccessTime, creationTime, MemoryEntryAttributes.this.size());
}
}
}
static final class MemoryFileAttributes extends MemoryEntryFileAttributes {
private final long size;
MemoryFileAttributes(Object fileKey, FileTime lastModifiedTime, FileTime lastAccessTime, FileTime creationTime, long size) {
super(fileKey, lastModifiedTime, lastAccessTime, creationTime);
this.size = size;
}
@Override
public boolean isRegularFile() {
return true;
}
@Override
public boolean isDirectory() {
return false;
}
@Override
public boolean isSymbolicLink() {
return false;
}
@Override
public boolean isOther() {
return false;
}
@Override
public long size() {
return this.size;
}
}
static Set<PosixFilePermission> toSet(int mask) {
Set<PosixFilePermission> set = EnumSet.noneOf(PosixFilePermission.class);
for (PosixFilePermission permission : PosixFilePermission.values()) {
int flag = 1 << permission.ordinal() & mask;
if (flag != 0) {
set.add(permission);
}
}
return set;
}
abstract long size();
static int toMask(Set<PosixFilePermission> permissions) {
int mask = 0;
for (PosixFilePermission permission : permissions) {
mask |= 1 << permission.ordinal();
}
return mask;
}
}