blob: d9c97387ef082f64a3bd2008818c64c220d0a8a7 [file] [log] [blame]
/*
* Copyright 2013 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.jimfs.internal;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.base.Splitter;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.jimfs.internal.attribute.AttributeProvider;
import com.google.jimfs.internal.attribute.AttributeProviderRegistry;
import com.google.jimfs.internal.file.ArrayByteStore;
import com.google.jimfs.internal.file.DirectoryTable;
import com.google.jimfs.internal.file.File;
import com.google.jimfs.internal.file.FileContent;
import com.google.jimfs.internal.path.JimfsPath;
import java.io.IOException;
import java.nio.file.FileStore;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.attribute.FileAttributeView;
import java.nio.file.attribute.FileStoreAttributeView;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import javax.annotation.Nullable;
/**
* Service for creating and copying files as well as reading and setting attributes on them.
*
* @author Colin Decker
*/
public final class JimfsFileStore extends FileStore {
private static final String ALL_ATTRIBUTES = "*";
private final AtomicLong idGenerator = new AtomicLong();
private final String name;
private final AttributeProviderRegistry attributeProviders;
/** Directory supplier with no extra file attributes. */
private final Supplier<File> defaultDirectorySupplier = new DirectorySupplier();
/** Regular file supplier with no extra file attributes. */
private final Supplier<File> defaultRegularFileSupplier = new RegularFileSupplier();
/**
* Creates a new file service using the given providers to handle file attributes.
*/
public JimfsFileStore(String name, AttributeProvider... providers) {
this(name, Arrays.asList(providers));
}
/**
* Creates a new file service using the given providers to handle file attributes.
*/
public JimfsFileStore(String name, Iterable<? extends AttributeProvider> providers) {
this.name = checkNotNull(name);
this.attributeProviders = new AttributeProviderRegistry(providers);
}
@Override
public String name() {
return name;
}
@Override
public String type() {
return "jimfs";
}
@Override
public boolean isReadOnly() {
return false;
}
@Override
public long getTotalSpace() throws IOException {
return Integer.MAX_VALUE;
}
@Override
public long getUsableSpace() throws IOException {
return Integer.MAX_VALUE;
}
@Override
public long getUnallocatedSpace() throws IOException {
return Integer.MAX_VALUE;
}
private long nextFileId() {
return idGenerator.getAndIncrement();
}
private File createFile(long id, FileContent content, FileAttribute<?>... attrs) {
File file = new File(id, content);
setInitialAttributes(file);
for (FileAttribute<?> attr : attrs) {
setAttributeInternal(file, attr.name(), attr.value(), true);
}
return file;
}
/**
* Creates a new directory and stores it. Returns the key of the new file.
*/
public File createDirectory(FileAttribute<?>... attrs) {
return createFile(nextFileId(), new DirectoryTable(), attrs);
}
/**
* Creates a new regular file and stores it. Returns the key of the new file.
*/
public File createRegularFile(FileAttribute<?>... attrs) {
return createFile(nextFileId(), new ArrayByteStore(), attrs);
}
/**
* Creates a new symbolic link referencing the given target path and stores it. Returns the key of
* the new file.
*/
public File createSymbolicLink(JimfsPath target, FileAttribute<?>... attrs) {
return createFile(nextFileId(), target, attrs);
}
/**
* Creates copies of the given file metadata and content and stores them. Returns the key of the
* new file.
*/
public File copy(File file) {
return createFile(nextFileId(), file.content().copy());
}
/**
* Returns a supplier that creates directories and sets the given attributes.
*/
public Supplier<File> directorySupplier(FileAttribute<?>... attrs) {
return attrs.length == 0 ? defaultDirectorySupplier : new DirectorySupplier(attrs);
}
/**
* Returns a supplier that creates a regular files and sets the given attributes.
*/
public Supplier<File> regularFileSupplier(FileAttribute<?>... attrs) {
return attrs.length == 0 ? defaultRegularFileSupplier : new RegularFileSupplier(attrs);
}
/**
* Returns a supplier that creates a symbolic links to the given path and sets the given
* attributes.
*/
public Supplier<File> symbolicLinkSupplier(JimfsPath target, FileAttribute<?>... attrs) {
return new SymbolicLinkSupplier(target, attrs);
}
/**
* Implements {@link FileSystem#supportedFileAttributeViews()}.
*/
public ImmutableSet<String> supportedFileAttributeViews() {
return attributeProviders.getSupportedViews();
}
/**
* Implements {@link FileStore#supportsFileAttributeView(Class)}.
*/
public boolean supportsFileAttributeView(Class<? extends FileAttributeView> type) {
return attributeProviders.getSupportedViewTypes().contains(type);
}
@Override
public boolean supportsFileAttributeView(String name) {
return supportedFileAttributeViews().contains(name);
}
/**
* Sets all initial attributes for the given file.
*/
private void setInitialAttributes(File file) {
for (AttributeProvider provider : attributeProviders.getProviders()) {
provider.setInitial(file);
}
}
/**
* Gets the value of the given attribute for the given file. {@code attribute} must be of the form
* "view:attribute" or "attribute".
*/
public <V> V getAttribute(File file, String attribute) {
String view = getViewName(attribute);
String attr = getSingleAttribute(attribute);
return getAttribute(file, view, attr);
}
/**
* Gets the value of the given attribute for the given view and file. Neither view nor file may
* have a ':' character.
*/
@SuppressWarnings("unchecked")
public <V> V getAttribute(File file, String view, String attribute) {
for (AttributeProvider provider : attributeProviders.getProviders(view)) {
if (provider.isGettable(file, attribute)) {
return (V) provider.get(file, attribute);
}
}
throw new IllegalArgumentException("attribute not found: " + attribute);
}
/**
* Sets the value of the given attribute to the given value for the given file.
*/
public void setAttribute(File file, String attribute, Object value) {
setAttributeInternal(file, attribute, value, false);
}
/**
* Sets the value of the given attribute to the given value for the given view and file.
*/
public void setAttribute(File file, String view, String attribute, Object value) {
setAttributeInternal(file, view, attribute, value, false);
}
private void setAttributeInternal(File file, String attribute, Object value, boolean create) {
String view = getViewName(attribute);
String attr = getSingleAttribute(attribute);
setAttributeInternal(file, view, attr, value, create);
}
private void setAttributeInternal(
File file, String view, String attribute, Object value, boolean create) {
for (AttributeProvider provider : attributeProviders.getProviders(view)) {
if (provider.isSettable(file, attribute)) {
if (create && !provider.isSettableOnCreate(attribute)) {
throw new UnsupportedOperationException(
"cannot set attribute '" + view + ":" + attribute + "' during file creation");
}
ImmutableSet<Class<?>> acceptedTypes = provider.acceptedTypes(attribute);
boolean validType = false;
for (Class<?> type : acceptedTypes) {
if (type.isInstance(value)) {
validType = true;
break;
}
}
if (validType) {
provider.set(file, attribute, value);
return;
} else {
Object acceptedTypeMessage = acceptedTypes.size() == 1
? acceptedTypes.iterator().next()
: "one of " + acceptedTypes;
throw new IllegalArgumentException("invalid type " + value.getClass()
+ " for attribute '" + view + ":" + attribute + "': should be "
+ acceptedTypeMessage);
}
}
}
throw new IllegalArgumentException("cannot set attribute '" + view + ":" + attribute + "'");
}
/**
* Returns an attribute view of the given type for the given file provider, or {@code null} if the
* view type is not supported.
*/
@Nullable
public <V extends FileAttributeView> V getFileAttributeView(
FileProvider fileProvider, Class<V> type) {
if (supportsFileAttributeView(type)) {
return attributeProviders.getViewProvider(type).getView(fileProvider);
}
return null;
}
/**
* Implements {@link Files#readAttributes(Path, String, LinkOption...)}.
*/
public ImmutableMap<String, Object> readAttributes(File file, String attributes) {
String view = getViewName(attributes);
List<String> attrs = getAttributeNames(attributes);
if (attrs.size() > 1 && attrs.contains(ALL_ATTRIBUTES)) {
// attrs contains * and other attributes
throw new IllegalArgumentException("invalid attributes: " + attributes);
}
ImmutableMap.Builder<String, Object> result = ImmutableMap.builder();
if (attrs.size() == 1 && attrs.contains(ALL_ATTRIBUTES)) {
// for 'view:*' format, get all keys for all providers for the view
for (AttributeProvider provider : attributeProviders.getProviders(view)) {
provider.readAll(file, result);
}
} else {
// for 'view:attr1,attr2,etc'
for (String attr : attrs) {
boolean found = false;
for (AttributeProvider provider : attributeProviders.getProviders(view)) {
if (provider.isGettable(file, attr)) {
result.put(attr, provider.get(file, attr));
found = true;
break;
}
}
if (!found) {
throw new IllegalArgumentException("invalid attribute for view '" + view + "': " + attr);
}
}
}
return result.build();
}
/**
* Returns attributes of the given file as an object of the given type.
*
* @throws UnsupportedOperationException if the given attributes type is not supported
*/
public <A extends BasicFileAttributes> A readAttributes(File file, Class<A> type) {
if (attributeProviders.getSupportedAttributesTypes().contains(type)) {
return attributeProviders.getReader(type).read(file);
}
throw new UnsupportedOperationException("unsupported attributes type: " + type);
}
private static String getViewName(String attribute) {
int separatorIndex = attribute.indexOf(':');
if (separatorIndex == -1) {
return "basic";
}
// separator must not be at the start or end of the string or appear more than once
if (separatorIndex == 0
|| separatorIndex == attribute.length() - 1
|| attribute.indexOf(':', separatorIndex + 1) != -1) {
throw new IllegalArgumentException("illegal attribute format: " + attribute);
}
return attribute.substring(0, separatorIndex);
}
private static final Splitter ATTRIBUTE_SPLITTER = Splitter.on(',');
private static ImmutableList<String> getAttributeNames(String attributes) {
int separatorIndex = attributes.indexOf(':');
String attributesPart = attributes.substring(separatorIndex + 1);
return ImmutableList.copyOf(ATTRIBUTE_SPLITTER.split(attributesPart));
}
private static String getSingleAttribute(String attribute) {
ImmutableList<String> attributeNames = getAttributeNames(attribute);
if (attributeNames.size() != 1 || ALL_ATTRIBUTES.equals(attributeNames.get(0))) {
throw new IllegalArgumentException("must specify a single attribute: " + attribute);
}
return attributeNames.get(0);
}
/**
* Returns {@code null}. This file store does not support any file store attribute views.
*/
@Override
public <V extends FileStoreAttributeView> V getFileStoreAttributeView(Class<V> type) {
return null;
}
/**
* Throws {@link UnsupportedOperationException}. This file store does not support any file store
* attributes.
*/
@Override
public Object getAttribute(String attribute) throws IOException {
throw new UnsupportedOperationException();
}
private abstract class FileSupplier implements Supplier<File> {
protected final FileAttribute<?>[] attrs;
protected FileSupplier(FileAttribute<?>[] attrs) {
this.attrs = checkNotNull(attrs);
}
}
private final class DirectorySupplier extends FileSupplier {
private DirectorySupplier(FileAttribute<?>... attrs) {
super(attrs);
}
@Override
public File get() {
return createDirectory(attrs);
}
}
private final class RegularFileSupplier extends FileSupplier {
private RegularFileSupplier(FileAttribute<?>... attrs) {
super(attrs);
}
@Override
public File get() {
return createRegularFile(attrs);
}
}
private final class SymbolicLinkSupplier extends FileSupplier {
private final JimfsPath target;
protected SymbolicLinkSupplier(JimfsPath target, FileAttribute<?>... attrs) {
super(attrs);
this.target = checkNotNull(target);
}
@Override
public File get() {
return createSymbolicLink(target, attrs);
}
}
}