/* * 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 providersByName; private final ImmutableMap, AttributeProvider> providersByViewType; private final ImmutableMap, AttributeProvider> providersByAttributesType; private final ImmutableList> 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 providers, Map userProvidedDefaults) { ImmutableMap.Builder byViewNameBuilder = ImmutableMap.builder(); ImmutableMap.Builder, AttributeProvider> byViewTypeBuilder = ImmutableMap.builder(); ImmutableMap.Builder, AttributeProvider> byAttributesTypeBuilder = ImmutableMap.builder(); ImmutableList.Builder> 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 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 getProviders(Configuration configuration) { Map 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 providers) { Set 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 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 supportedFileAttributeViews() { return providersByName.keySet(); } /** Implements {@link FileStore#supportsFileAttributeView(Class)}. */ public boolean supportsFileAttributeView(Class 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 getFileAttributeView(FileLookup lookup, Class 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 viewType, Map inheritedViews) { AttributeProvider provider = providersByViewType.get(viewType); createInheritedViews(lookup, provider, inheritedViews); return provider.view(lookup, ImmutableMap.copyOf(inheritedViews)); } private ImmutableMap createInheritedViews( FileLookup lookup, AttributeProvider provider) { if (provider.inherits().isEmpty()) { return ImmutableMap.of(); } Map inheritedViews = new HashMap<>(); createInheritedViews(lookup, provider, inheritedViews); return ImmutableMap.copyOf(inheritedViews); } private void createInheritedViews( FileLookup lookup, AttributeProvider provider, Map 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 readAttributes(File file, String attributes) { String view = getViewName(attributes); List attrs = getAttributeNames(attributes); if (attrs.size() > 1 && attrs.contains(ALL_ATTRIBUTES)) { // attrs contains * and other attributes throw new IllegalArgumentException("invalid attributes: " + attributes); } Map 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 readAttributes(File file, Class 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 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 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 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 implements FileAttribute { 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; } } }