package com.github.marschall.memoryfilesystem;
import java.io.IOException;
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.FileAttribute;
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.UserPrincipal;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
final class AttributeAccessors {
private static final int DOS_ATTRIBUTE_COUNT = 4;
private static final int OWNER_ATTRIBUTE_COUNT = 1;
private static final int BASIC_ATTRIBUTE_COUNT = 9;
private static final Map<String, Map<String, AttributeAccessor>> ACCESSORS;
static {
ACCESSORS = new HashMap<>(4);
Map<String, AttributeAccessor> basicFileAttributesMap = buildBasicFileAttributesMap();
Map<String, AttributeAccessor> ownerAttributesMap = buildOwnerAttributesMap();
Map<String, AttributeAccessor> dosAttributesMap = buildDosAttributesMap();
Map<String, AttributeAccessor> posixAttributesMap = buildPosixAttributesMap();
dosAttributesMap.putAll(basicFileAttributesMap);
posixAttributesMap.putAll(ownerAttributesMap);
posixAttributesMap.putAll(basicFileAttributesMap);
ACCESSORS.put(FileAttributeViews.BASIC, basicFileAttributesMap);
ACCESSORS.put(FileAttributeViews.OWNER, ownerAttributesMap);
ACCESSORS.put(FileAttributeViews.DOS, dosAttributesMap);
ACCESSORS.put(FileAttributeViews.POSIX, posixAttributesMap);
}
AttributeAccessors() {
throw new AssertionError("not instantiable");
}
private static Map<String, AttributeAccessor> buildOwnerAttributesMap() {
return Collections.<String, AttributeAccessor>singletonMap("owner", new OwnerAccessor());
}
private static Map<String, AttributeAccessor> buildDosAttributesMap() {
Map<String, AttributeAccessor> map = new HashMap<>(DOS_ATTRIBUTE_COUNT + BASIC_ATTRIBUTE_COUNT);
map.put("readonly", new IsReadOnlyAccessor());
map.put("hidden", new IsHiddenAccessor());
map.put("archive", new IsArchiveAccessor());
map.put("system", new IsSymbolicLinkAccessor());
return map;
}
private static Map<String, AttributeAccessor> buildPosixAttributesMap() {
Map<String, AttributeAccessor> map = new HashMap<>(OWNER_ATTRIBUTE_COUNT + 2 + BASIC_ATTRIBUTE_COUNT);
// owner excluded because it's inherited
map.put("group", new GroupAccessor());
map.put("permissions", new PermissionsAccessor());
return map;
}
private static Map<String, AttributeAccessor> buildBasicFileAttributesMap() {
Map<String, AttributeAccessor> map = new HashMap<>(BASIC_ATTRIBUTE_COUNT);
map.put("lastModifiedTime", new LastModifiedTimeAccessor());
map.put("lastAccessTime", new LastAccessTimeAccessor());
map.put("creationTime", new CreationTimeAccessor());
map.put("isRegularFile", new IsRegularFileAccessor());
map.put("isDirectory", new IsDirectoryAccessor());
map.put("isSymbolicLink", new IsSymbolicLinkAccessor());
map.put("isOther", new IsOtherAccessor());
map.put("size", new SizeAccessor());
map.put("fileKey", new FileKeyAccessor());
return map;
}
static final class LastModifiedTimeAccessor implements AttributeAccessor {
@Override
public Object readAttribute(AtributeReadContext context) throws IOException {
return context.getBasicFileAttributes().lastModifiedTime();
}
@Override
public void writeAttribute(Object value, MemoryEntry entry) throws IOException {
entry.getBasicFileAttributeView().setTimes((FileTime) value, null, null);
}
}
static final class LastAccessTimeAccessor implements AttributeAccessor {
@Override
public Object readAttribute(AtributeReadContext context) throws IOException {
return context.getBasicFileAttributes().lastAccessTime();
}
@Override
public void writeAttribute(Object value, MemoryEntry entry) throws IOException {
entry.getBasicFileAttributeView().setTimes(null, (FileTime) value, null);
}
}
static final class CreationTimeAccessor implements AttributeAccessor {
@Override
public Object readAttribute(AtributeReadContext context) throws IOException {
return context.getBasicFileAttributes().creationTime();
}
@Override
public void writeAttribute(Object value, MemoryEntry entry) throws IOException {
entry.getBasicFileAttributeView().setTimes(null, null, (FileTime) value);
}
}
static final class IsRegularFileAccessor implements AttributeAccessor {
@Override
public Object readAttribute(AtributeReadContext context) throws IOException {
return context.getBasicFileAttributes().isRegularFile();
}
@Override
public void writeAttribute(Object value, MemoryEntry entry) throws IOException {
throw new IllegalArgumentException("\"isRegularFile\" can not be written");
}
}
static final class IsDirectoryAccessor implements AttributeAccessor {
@Override
public Object readAttribute(AtributeReadContext context) throws IOException {
return context.getBasicFileAttributes().isDirectory();
}
@Override
public void writeAttribute(Object value, MemoryEntry entry) throws IOException {
throw new IllegalArgumentException("\"isDirectory\" can not be written");
}
}
static final class IsOtherAccessor implements AttributeAccessor {
@Override
public Object readAttribute(AtributeReadContext context) throws IOException {
return context.getBasicFileAttributes().isOther();
}
@Override
public void writeAttribute(Object value, MemoryEntry entry) throws IOException {
throw new IllegalArgumentException("\"isOther\" can not be written");
}
}
static final class IsSymbolicLinkAccessor implements AttributeAccessor {
@Override
public Object readAttribute(AtributeReadContext context) throws IOException {
return context.getBasicFileAttributes().isSymbolicLink();
}
@Override
public void writeAttribute(Object value, MemoryEntry entry) throws IOException {
throw new IllegalArgumentException("\"isSymbolicLink\" can not be written");
}
}
static final class SizeAccessor implements AttributeAccessor {
@Override
public Object readAttribute(AtributeReadContext context) throws IOException {
return context.getBasicFileAttributes().size();
}
@Override
public void writeAttribute(Object value, MemoryEntry entry) throws IOException {
throw new IllegalArgumentException("\"size\" can not be written");
}
}
static final class FileKeyAccessor implements AttributeAccessor {
@Override
public Object readAttribute(AtributeReadContext context) throws IOException {
return context.getBasicFileAttributes().fileKey();
}
@Override
public void writeAttribute(Object value, MemoryEntry entry) throws IOException {
throw new IllegalArgumentException("\"fileKey\" can not be written");
}
}
static final class OwnerAccessor implements AttributeAccessor {
@Override
public Object readAttribute(AtributeReadContext context) throws IOException {
return context.getFileOwnerAttributeView().getOwner();
}
@Override
public void writeAttribute(Object value, MemoryEntry entry) throws IOException {
entry.getFileAttributeView(FileOwnerAttributeView.class).setOwner((UserPrincipal) value);
}
}
static final class GroupAccessor implements AttributeAccessor {
@Override
public Object readAttribute(AtributeReadContext context) throws IOException {
return context.getPosixFileAttributes().group();
}
@Override
public void writeAttribute(Object value, MemoryEntry entry) throws IOException {
entry.getFileAttributeView(PosixFileAttributeView.class).setGroup((GroupPrincipal) value);
}
}
static final class PermissionsAccessor implements AttributeAccessor {
@Override
public Object readAttribute(AtributeReadContext context) throws IOException {
return context.getPosixFileAttributes().permissions();
}
@Override
public void writeAttribute(Object value, MemoryEntry entry) throws IOException {
entry.getFileAttributeView(PosixFileAttributeView.class).setPermissions((Set<PosixFilePermission>) value);
}
}
static final class IsHiddenAccessor implements AttributeAccessor {
@Override
public Object readAttribute(AtributeReadContext context) throws IOException {
return context.getDosFileAttributes().isHidden();
}
@Override
public void writeAttribute(Object value, MemoryEntry entry) throws IOException {
entry.getFileAttributeView(DosFileAttributeView.class).setHidden((Boolean) value);
}
}
static final class IsReadOnlyAccessor implements AttributeAccessor {
@Override
public Object readAttribute(AtributeReadContext context) throws IOException {
return context.getDosFileAttributes().isReadOnly();
}
@Override
public void writeAttribute(Object value, MemoryEntry entry) throws IOException {
entry.getFileAttributeView(DosFileAttributeView.class).setReadOnly((Boolean) value);
}
}
static final class IsSystemAccessor implements AttributeAccessor {
@Override
public Object readAttribute(AtributeReadContext context) throws IOException {
return context.getDosFileAttributes().isSystem();
}
@Override
public void writeAttribute(Object value, MemoryEntry entry) throws IOException {
entry.getFileAttributeView(DosFileAttributeView.class).setSystem((Boolean) value);
}
}
static final class IsArchiveAccessor implements AttributeAccessor {
@Override
public Object readAttribute(AtributeReadContext context) throws IOException {
return context.getDosFileAttributes().isArchive();
}
@Override
public void writeAttribute(Object value, MemoryEntry entry) throws IOException {
entry.getFileAttributeView(DosFileAttributeView.class).setArchive((Boolean) value);
}
}
static void setAttribute(MemoryEntry entry, String attribute, Object value) throws IOException {
getAccessor(attribute).writeAttribute(value, entry);
}
static void setAttributes(MemoryEntry entry, FileAttribute<?>... attrs) throws IOException {
for (FileAttribute<?> attribute : attrs) {
getAccessor(attribute.name()).writeAttribute(attribute.value(), entry);
}
}
static Map<String, Object> readAttributes(MemoryEntry entry, String viewAndAttribute) throws IOException {
int colonIndex = viewAndAttribute.indexOf(':');
if (colonIndex == viewAndAttribute.length() - 1) {
throw new IllegalArgumentException("\"" + viewAndAttribute + "\" is missing attribute name");
}
String view;
String attribute;
if (colonIndex == -1) {
view = FileAttributeViews.BASIC;
attribute = viewAndAttribute;
} else {
view = viewAndAttribute.substring(0, colonIndex);
attribute = viewAndAttribute.substring(colonIndex + 1, viewAndAttribute.length());
}
Map<String, AttributeAccessor> viewMap = ACCESSORS.get(view);
if (viewMap == null) {
throw new UnsupportedOperationException("view \"" + view + "\" is not supported");
}
if (attribute.equals("*")) {
return readAllAttributes(entry, viewMap);
} else if (attribute.indexOf(',') != -1) {
String[] attributes = attribute.split(","); // should end up in the fast path
return readAttributes(entry, attributes, viewMap);
} else {
return readSingleAttribute(entry, attribute, viewMap);
}
}
private static Map<String, Object> readSingleAttribute(MemoryEntry entry, String attribute, Map<String, AttributeAccessor> viewMap) throws IOException {
AttributeAccessor accessor = viewMap.get(attribute);
if (accessor == null) {
throw new IllegalArgumentException("attribute \"" + attribute + "\" is not supported");
}
AtributeReadContext context = new AtributeReadContext(entry);
Object value = accessor.readAttribute(context);
return Collections.singletonMap(attribute, value);
}
private static Map<String, Object> readAttributes(MemoryEntry entry, String[] attributes, Map<String, AttributeAccessor> viewMap) throws IOException {
Map<String, Object> values = new HashMap<>(attributes.length);
AtributeReadContext context = new AtributeReadContext(entry);
for (String each : attributes) {
AttributeAccessor accessor = viewMap.get(each);
if (accessor == null) {
throw new IllegalArgumentException("attribute \"" + each + "\" is not supported");
}
values.put(each, accessor.readAttribute(context));
}
return values;
}
private static Map<String, Object> readAllAttributes(MemoryEntry entry, Map<String, AttributeAccessor> viewMap) throws IOException {
Map<String, Object> values = new HashMap<>(viewMap.size());
AtributeReadContext context = new AtributeReadContext(entry);
for (Entry<String, AttributeAccessor> each : viewMap.entrySet()) {
AttributeAccessor accessor = each.getValue();
values.put(each.getKey(), accessor.readAttribute(context));
}
return values;
}
private static AttributeAccessor getAccessor(String viewAndAttribute) {
int colonIndex = viewAndAttribute.indexOf(':');
if (colonIndex == viewAndAttribute.length() - 1) {
throw new IllegalArgumentException("\"" + viewAndAttribute + "\" is missing attribute name");
}
String view;
String attribute;
if (colonIndex == -1) {
view = FileAttributeViews.BASIC;
attribute = viewAndAttribute;
} else {
view = viewAndAttribute.substring(0, colonIndex);
attribute = viewAndAttribute.substring(colonIndex + 1, viewAndAttribute.length());
}
Map<String, AttributeAccessor> viewMap = ACCESSORS.get(view);
if (viewMap == null) {
throw new UnsupportedOperationException("view \"" + view + "\" is not supported");
}
AttributeAccessor accessor = viewMap.get(attribute);
if (accessor == null) {
throw new IllegalArgumentException("view \"" + view + "\" is not supported");
}
return accessor;
}
static final class AtributeReadContext {
private final MemoryEntry entry;
private BasicFileAttributes basicFileAttributes;
private DosFileAttributes dosFileAttributes;
private PosixFileAttributes posixFileAttributes;
private FileOwnerAttributeView fileOwnerAttributeView;
AtributeReadContext(MemoryEntry entry) {
this.entry = entry;
}
BasicFileAttributes getBasicFileAttributes() throws IOException {
if (this.basicFileAttributes == null) {
this.basicFileAttributes = this.entry.getFileAttributeView(BasicFileAttributeView.class).readAttributes();
}
return this.basicFileAttributes;
}
public DosFileAttributes getDosFileAttributes() throws IOException {
if (this.dosFileAttributes == null) {
this.dosFileAttributes = this.entry.getFileAttributeView(DosFileAttributeView.class).readAttributes();
}
return this.dosFileAttributes;
}
public PosixFileAttributes getPosixFileAttributes() throws IOException {
if (this.posixFileAttributes == null) {
this.posixFileAttributes = this.entry.getFileAttributeView(PosixFileAttributeView.class).readAttributes();
}
return this.posixFileAttributes;
}
public FileOwnerAttributeView getFileOwnerAttributeView() throws IOException {
if (this.fileOwnerAttributeView == null) {
this.fileOwnerAttributeView = this.entry.getFileAttributeView(FileOwnerAttributeView.class);
}
return this.fileOwnerAttributeView;
}
}
interface AttributeAccessor {
Object readAttribute(AtributeReadContext context) throws IOException;
void writeAttribute(Object value, MemoryEntry entry) throws IOException;
}
}