/*
* Copyright 2014-present Facebook, 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.facebook.buck.io;
import com.google.common.base.Functions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Ordering;
import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.attribute.FileTime;
import java.util.Optional;
/** Utility class to list files which match a pattern, applying ordering and filtering. */
public class PathListing {
// Utility class, do not instantiate.
private PathListing() {}
/** Whether to include files which match the filter, or exclude them. */
public enum FilterMode {
INCLUDE,
EXCLUDE,
}
/** Fetches last-modified time from a path. */
public interface PathModifiedTimeFetcher {
FileTime getLastModifiedTime(Path path) throws IOException;
}
/** Uses {@code Files.getLastModifiedTime()} to get the last modified time for a Path. */
public static final PathModifiedTimeFetcher GET_PATH_MODIFIED_TIME =
path -> Files.getLastModifiedTime(path);
/** Lists matching paths in descending modified time order. */
public static ImmutableSortedSet<Path> listMatchingPaths(
Path pathToGlob, String globPattern, PathModifiedTimeFetcher pathModifiedTimeFetcher)
throws IOException {
return listMatchingPathsWithFilters(
pathToGlob,
globPattern,
pathModifiedTimeFetcher,
FilterMode.INCLUDE,
Optional.empty(),
Optional.empty());
}
/**
* Lists paths in descending modified time order, excluding any paths which bring the number of
* files over {@code maxNumPaths} or over {@code totalSizeFilter} bytes in size.
*/
public static ImmutableSortedSet<Path> listMatchingPathsWithFilters(
Path pathToGlob,
String globPattern,
PathModifiedTimeFetcher pathModifiedTimeFetcher,
FilterMode filterMode,
Optional<Integer> maxPathsFilter,
Optional<Long> totalSizeFilter)
throws IOException {
// Fetch the modification time of each path and build a map of
// (path => modification time) pairs.
ImmutableMap.Builder<Path, FileTime> pathFileTimesBuilder = ImmutableMap.builder();
try (DirectoryStream<Path> stream = Files.newDirectoryStream(pathToGlob, globPattern)) {
for (Path path : stream) {
try {
pathFileTimesBuilder.put(path, pathModifiedTimeFetcher.getLastModifiedTime(path));
} catch (NoSuchFileException e) {
// Ignore the path.
continue;
}
}
}
ImmutableMap<Path, FileTime> pathFileTimes = pathFileTimesBuilder.build();
ImmutableSortedSet<Path> paths =
ImmutableSortedSet.copyOf(
Ordering.natural()
// Order the keys of the map (the paths) by their values (the file modification times).
.onResultOf(Functions.forMap(pathFileTimes))
// If two keys of the map have the same value, fall back to key order.
.compound(Ordering.natural())
// Use descending order.
.reverse(),
pathFileTimes.keySet());
paths = applyNumPathsFilter(paths, filterMode, maxPathsFilter);
paths = applyTotalSizeFilter(paths, filterMode, totalSizeFilter);
return paths;
}
private static ImmutableSortedSet<Path> applyNumPathsFilter(
ImmutableSortedSet<Path> paths, FilterMode filterMode, Optional<Integer> maxPathsFilter) {
if (maxPathsFilter.isPresent()) {
int limitIndex = Math.min(maxPathsFilter.get(), paths.size());
paths = subSet(paths, filterMode, limitIndex);
}
return paths;
}
@SuppressWarnings("PMD.EmptyCatchBlock")
private static ImmutableSortedSet<Path> applyTotalSizeFilter(
ImmutableSortedSet<Path> paths, FilterMode filterMode, Optional<Long> totalSizeFilter)
throws IOException {
if (totalSizeFilter.isPresent()) {
int limitIndex = 0;
long totalSize = 0;
for (Path path : paths) {
try {
totalSize += Files.size(path);
} catch (NoSuchFileException e) {
// Path was deleted; ignore it.
}
if (totalSize < totalSizeFilter.get()) {
limitIndex++;
} else {
break;
}
}
paths = subSet(paths, filterMode, limitIndex);
}
return paths;
}
private static ImmutableSortedSet<Path> subSet(
ImmutableSortedSet<Path> paths, FilterMode filterMode, int limitIndex) {
// This doesn't copy the contents of the ImmutableSortedSet. We use it
// as a simple way to get O(1) access to the set's contents, as otherwise
// we would have to iterate to find the Nth element.
ImmutableList<Path> pathsList = paths.asList();
boolean fullSet = limitIndex == paths.size();
switch (filterMode) {
case INCLUDE:
// Make sure we don't call pathsList.get(pathsList.size()).
if (!fullSet) {
paths = paths.headSet(pathsList.get(limitIndex));
}
break;
case EXCLUDE:
if (fullSet) {
// Make sure we don't call pathsList.get(pathsList.size()).
paths = ImmutableSortedSet.of();
} else {
paths = paths.tailSet(pathsList.get(limitIndex));
}
break;
}
return paths;
}
}