| /* |
| * 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; |
| } |
| } |
| } |