diff options
Diffstat (limited to 'jimfs/src/main/java/com/google/common/jimfs/AttributeService.java')
-rw-r--r-- | jimfs/src/main/java/com/google/common/jimfs/AttributeService.java | 423 |
1 files changed, 423 insertions, 0 deletions
diff --git a/jimfs/src/main/java/com/google/common/jimfs/AttributeService.java b/jimfs/src/main/java/com/google/common/jimfs/AttributeService.java new file mode 100644 index 0000000..333a497 --- /dev/null +++ b/jimfs/src/main/java/com/google/common/jimfs/AttributeService.java @@ -0,0 +1,423 @@ +/* + * 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.common.jimfs; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +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.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.checkerframework.checker.nullness.compatqual.NullableDecl; + +/** + * Service providing all attribute related operations for a file store. One piece of the file store + * implementation. + * + * @author Colin Decker + */ +final class AttributeService { + + private static final String ALL_ATTRIBUTES = "*"; + + private final ImmutableMap<String, AttributeProvider> providersByName; + private final ImmutableMap<Class<?>, AttributeProvider> providersByViewType; + private final ImmutableMap<Class<?>, AttributeProvider> providersByAttributesType; + + private final ImmutableList<FileAttribute<?>> defaultValues; + + /** Creates a new attribute service using the given configuration. */ + public AttributeService(Configuration configuration) { + this(getProviders(configuration), configuration.defaultAttributeValues); + } + + /** + * Creates a new attribute service using the given providers and user provided default attribute + * values. + */ + public AttributeService( + Iterable<? extends AttributeProvider> providers, Map<String, ?> userProvidedDefaults) { + ImmutableMap.Builder<String, AttributeProvider> byViewNameBuilder = ImmutableMap.builder(); + ImmutableMap.Builder<Class<?>, AttributeProvider> byViewTypeBuilder = ImmutableMap.builder(); + ImmutableMap.Builder<Class<?>, AttributeProvider> byAttributesTypeBuilder = + ImmutableMap.builder(); + + ImmutableList.Builder<FileAttribute<?>> defaultAttributesBuilder = ImmutableList.builder(); + + for (AttributeProvider provider : providers) { + byViewNameBuilder.put(provider.name(), provider); + byViewTypeBuilder.put(provider.viewType(), provider); + if (provider.attributesType() != null) { + byAttributesTypeBuilder.put(provider.attributesType(), provider); + } + + for (Map.Entry<String, ?> entry : provider.defaultValues(userProvidedDefaults).entrySet()) { + defaultAttributesBuilder.add(new SimpleFileAttribute<>(entry.getKey(), entry.getValue())); + } + } + + this.providersByName = byViewNameBuilder.build(); + this.providersByViewType = byViewTypeBuilder.build(); + this.providersByAttributesType = byAttributesTypeBuilder.build(); + this.defaultValues = defaultAttributesBuilder.build(); + } + + private static Iterable<AttributeProvider> getProviders(Configuration configuration) { + Map<String, AttributeProvider> result = new HashMap<>(); + + for (AttributeProvider provider : configuration.attributeProviders) { + result.put(provider.name(), provider); + } + + for (String view : configuration.attributeViews) { + addStandardProvider(result, view); + } + + addMissingProviders(result); + + return Collections.unmodifiableCollection(result.values()); + } + + private static void addMissingProviders(Map<String, AttributeProvider> providers) { + Set<String> missingViews = new HashSet<>(); + for (AttributeProvider provider : providers.values()) { + for (String inheritedView : provider.inherits()) { + if (!providers.containsKey(inheritedView)) { + missingViews.add(inheritedView); + } + } + } + + if (missingViews.isEmpty()) { + return; + } + + // add any inherited views that were not listed directly + for (String view : missingViews) { + addStandardProvider(providers, view); + } + + // in case any of the providers that were added themselves have missing views they inherit + addMissingProviders(providers); + } + + private static void addStandardProvider(Map<String, AttributeProvider> result, String view) { + AttributeProvider provider = StandardAttributeProviders.get(view); + + if (provider == null) { + if (!result.containsKey(view)) { + throw new IllegalStateException("no provider found for attribute view '" + view + "'"); + } + } else { + result.put(provider.name(), provider); + } + } + + /** Implements {@link FileSystem#supportedFileAttributeViews()}. */ + public ImmutableSet<String> supportedFileAttributeViews() { + return providersByName.keySet(); + } + + /** Implements {@link FileStore#supportsFileAttributeView(Class)}. */ + public boolean supportsFileAttributeView(Class<? extends FileAttributeView> type) { + return providersByViewType.containsKey(type); + } + + /** Sets all initial attributes for the given file, including the given attributes if possible. */ + public void setInitialAttributes(File file, FileAttribute<?>... attrs) { + // default values should already be sanitized by their providers + for (int i = 0; i < defaultValues.size(); i++) { + FileAttribute<?> attribute = defaultValues.get(i); + + int separatorIndex = attribute.name().indexOf(':'); + String view = attribute.name().substring(0, separatorIndex); + String attr = attribute.name().substring(separatorIndex + 1); + file.setAttribute(view, attr, attribute.value()); + } + + for (FileAttribute<?> attr : attrs) { + setAttribute(file, attr.name(), attr.value(), true); + } + } + + /** Copies the attributes of the given file to the given copy file. */ + public void copyAttributes(File file, File copy, AttributeCopyOption copyOption) { + switch (copyOption) { + case ALL: + file.copyAttributes(copy); + break; + case BASIC: + file.copyBasicAttributes(copy); + break; + default: + // don't copy + } + } + + /** + * Gets the value of the given attribute for the given file. {@code attribute} must be of the form + * "view:attribute" or "attribute". + */ + public Object 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 attribute + * may have a ':' character. + */ + public Object getAttribute(File file, String view, String attribute) { + Object value = getAttributeInternal(file, view, attribute); + if (value == null) { + throw new IllegalArgumentException("invalid attribute for view '" + view + "': " + attribute); + } + return value; + } + + @NullableDecl + private Object getAttributeInternal(File file, String view, String attribute) { + AttributeProvider provider = providersByName.get(view); + if (provider == null) { + return null; + } + + Object value = provider.get(file, attribute); + if (value == null) { + for (String inheritedView : provider.inherits()) { + value = getAttributeInternal(file, inheritedView, attribute); + if (value != null) { + break; + } + } + } + + return value; + } + + /** Sets the value of the given attribute to the given value for the given file. */ + public void setAttribute(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) { + AttributeProvider provider = providersByName.get(view); + + if (provider != null) { + if (provider.supports(attribute)) { + provider.set(file, view, attribute, value, create); + return; + } + + for (String inheritedView : provider.inherits()) { + AttributeProvider inheritedProvider = providersByName.get(inheritedView); + if (inheritedProvider.supports(attribute)) { + inheritedProvider.set(file, view, attribute, value, create); + return; + } + } + } + + throw new UnsupportedOperationException( + "cannot set attribute '" + view + ":" + attribute + "'"); + } + + /** + * Returns an attribute view of the given type for the given file lookup callback, or {@code null} + * if the view type is not supported. + */ + @SuppressWarnings("unchecked") + @NullableDecl + public <V extends FileAttributeView> V getFileAttributeView(FileLookup lookup, Class<V> type) { + AttributeProvider provider = providersByViewType.get(type); + + if (provider != null) { + return (V) provider.view(lookup, createInheritedViews(lookup, provider)); + } + + return null; + } + + private FileAttributeView getFileAttributeView( + FileLookup lookup, + Class<? extends FileAttributeView> viewType, + Map<String, FileAttributeView> inheritedViews) { + AttributeProvider provider = providersByViewType.get(viewType); + createInheritedViews(lookup, provider, inheritedViews); + return provider.view(lookup, ImmutableMap.copyOf(inheritedViews)); + } + + private ImmutableMap<String, FileAttributeView> createInheritedViews( + FileLookup lookup, AttributeProvider provider) { + if (provider.inherits().isEmpty()) { + return ImmutableMap.of(); + } + + Map<String, FileAttributeView> inheritedViews = new HashMap<>(); + createInheritedViews(lookup, provider, inheritedViews); + return ImmutableMap.copyOf(inheritedViews); + } + + private void createInheritedViews( + FileLookup lookup, + AttributeProvider provider, + Map<String, FileAttributeView> inheritedViews) { + + for (String inherited : provider.inherits()) { + if (!inheritedViews.containsKey(inherited)) { + AttributeProvider inheritedProvider = providersByName.get(inherited); + FileAttributeView inheritedView = + getFileAttributeView(lookup, inheritedProvider.viewType(), inheritedViews); + + inheritedViews.put(inherited, inheritedView); + } + } + } + + /** 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); + } + + Map<String, Object> result = new HashMap<>(); + if (attrs.size() == 1 && attrs.contains(ALL_ATTRIBUTES)) { + // for 'view:*' format, get all keys for all providers for the view + AttributeProvider provider = providersByName.get(view); + readAll(file, provider, result); + + for (String inheritedView : provider.inherits()) { + AttributeProvider inheritedProvider = providersByName.get(inheritedView); + readAll(file, inheritedProvider, result); + } + } else { + // for 'view:attr1,attr2,etc' + for (String attr : attrs) { + result.put(attr, getAttribute(file, view, attr)); + } + } + + return ImmutableMap.copyOf(result); + } + + /** + * Returns attributes of the given file as an object of the given type. + * + * @throws UnsupportedOperationException if the given attributes type is not supported + */ + @SuppressWarnings("unchecked") + public <A extends BasicFileAttributes> A readAttributes(File file, Class<A> type) { + AttributeProvider provider = providersByAttributesType.get(type); + if (provider != null) { + return (A) provider.readAttributes(file); + } + + throw new UnsupportedOperationException("unsupported attributes type: " + type); + } + + private static void readAll(File file, AttributeProvider provider, Map<String, Object> map) { + for (String attribute : provider.attributes(file)) { + Object value = provider.get(file, attribute); + + // check for null to protect against race condition when an attribute present when + // attributes(file) was called is deleted before get() is called for that attribute + if (value != null) { + map.put(attribute, value); + } + } + } + + 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); + } + + /** Simple implementation of {@link FileAttribute}. */ + private static final class SimpleFileAttribute<T> implements FileAttribute<T> { + + private final String name; + private final T value; + + SimpleFileAttribute(String name, T value) { + this.name = checkNotNull(name); + this.value = checkNotNull(value); + } + + @Override + public String name() { + return name; + } + + @Override + public T value() { + return value; + } + } +} |