com.google.jimfs.AttributeService.java Source code

Java tutorial

Introduction

Here is the source code for com.google.jimfs.AttributeService.java

Source

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

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 javax.annotation.Nullable;

/**
 * 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 file times of the given file to the given copy file.
     */
    public void copyBasicAttributes(File file, File copy) {
        file.copyBasicAttributes(copy);
    }

    /**
     * Copies the attributes of the given file to the given copy file.
     */
    public void copyAttributes(File file, File copy) {
        file.copyAttributes(copy);
    }

    /**
     * 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 attribute
     * may have a ':' character.
     */
    @SuppressWarnings("unchecked")
    public <V> V 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 (V) value;
    }

    @Nullable
    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 IllegalArgumentException("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")
    @Nullable
    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 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);
            }
        }
    }

    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));
    }

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

    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);
            }
        }
    }

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