001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.vfs2.cache;
018
019import java.util.Map;
020import java.util.concurrent.ConcurrentHashMap;
021import java.util.concurrent.ConcurrentMap;
022
023import org.apache.commons.vfs2.FileName;
024import org.apache.commons.vfs2.FileObject;
025import org.apache.commons.vfs2.FileSystem;
026
027/**
028 * A simple {@link org.apache.commons.vfs2.FilesCache FilesCache} implementation.
029 * <p>
030 * This implementation caches every file with no expire or limit. All files and file systems are hard reachable
031 * references. This implementation holds a list of file system specific {@linkplain ConcurrentHashMap ConcurrentHashMaps}
032 * in the main cache map.
033 * </p>
034 * <p>
035 * Cached {@linkplain FileObject FileObjects} as well as {@linkplain FileSystem FileSystems} are only removed when
036 * {@link #clear(FileSystem)} is called (i.e. on file system close). When the used
037 * {@link org.apache.commons.vfs2.FileSystemManager FileSystemManager} is closed, it will also {@linkplain #close()
038 * close} this cache (which frees all entries).
039 * </p>
040 * <p>
041 * Despite its name, this is not the fallback implementation used by
042 * {@link org.apache.commons.vfs2.impl.DefaultFileSystemManager#init() DefaultFileSystemManager#init()} anymore.
043 * </p>
044 */
045public class DefaultFilesCache extends AbstractFilesCache {
046
047    private static final float LOAD_FACTOR = 0.75f;
048    private static final int INITIAL_CAPACITY = 200;
049
050    /** The FileSystem cache. Keeps one Map for each FileSystem. */
051    private final ConcurrentMap<FileSystem, ConcurrentMap<FileName, FileObject>> fileSystemCache = new ConcurrentHashMap<>(10);
052
053    /**
054     * Constructs a new instance.
055     */
056    public DefaultFilesCache() {
057        // empty
058    }
059
060    @Override
061    public void clear(final FileSystem filesystem) {
062        // avoid keeping a reference to the FileSystem (key) object
063        final Map<FileName, FileObject> files = fileSystemCache.remove(filesystem);
064        if (files != null) {
065            files.clear(); // help GC
066        }
067    }
068
069    @Override
070    public void close() {
071        super.close();
072        fileSystemCache.clear();
073    }
074
075    @Override
076    public FileObject getFile(final FileSystem filesystem, final FileName name) {
077        // avoid creating filesystem entry for empty filesystem cache:
078        final Map<FileName, FileObject> files = fileSystemCache.get(filesystem);
079        if (files == null) {
080            // cache for filesystem is not known => file is not cached:
081            return null;
082        }
083        return files.get(name); // or null
084    }
085
086    /**
087     * Gets or creates a Map.
088     *
089     * @param fileSystem the key
090     * @return an existing or new Map.
091     */
092    protected ConcurrentMap<FileName, FileObject> getOrCreateFilesystemCache(final FileSystem fileSystem) {
093        ConcurrentMap<FileName, FileObject> files = fileSystemCache.get(fileSystem);
094        // we loop to make sure we never return null even when concurrent clean is called
095        while (files == null) {
096            files = fileSystemCache.computeIfAbsent(fileSystem,
097                k -> new ConcurrentHashMap<>(INITIAL_CAPACITY, LOAD_FACTOR, Math.max(2, Runtime.getRuntime().availableProcessors()) / 2));
098        }
099        return files;
100    }
101
102    @Override
103    public void putFile(final FileObject file) {
104        final Map<FileName, FileObject> files = getOrCreateFilesystemCache(file.getFileSystem());
105        files.put(file.getName(), file);
106    }
107
108    @Override
109    public boolean putFileIfAbsent(final FileObject file) {
110        final ConcurrentMap<FileName, FileObject> files = getOrCreateFilesystemCache(file.getFileSystem());
111        return files.putIfAbsent(file.getName(), file) == null;
112    }
113
114    @Override
115    public void removeFile(final FileSystem filesystem, final FileName name) {
116        // avoid creating filesystem entry for empty filesystem cache:
117        final Map<FileName, FileObject> files = fileSystemCache.get(filesystem);
118        if (files != null) {
119            files.remove(name);
120            // This would be too racey:
121            // if (files.empty()) filesystemCache.remove(filessystem);
122        }
123    }
124}