| // Copyright 2014 The Bazel Authors. All rights reserved. |
| // |
| // 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.devtools.build.lib.skyframe; |
| |
| import com.google.common.base.MoreObjects; |
| import com.google.common.base.Preconditions; |
| import com.google.common.collect.Interner; |
| import com.google.common.collect.Iterables; |
| import com.google.devtools.build.lib.concurrent.BlazeInterners; |
| import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe; |
| import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec; |
| import com.google.devtools.build.lib.vfs.Dirent; |
| import com.google.devtools.build.lib.vfs.RootedPath; |
| import com.google.devtools.build.lib.vfs.Symlinks; |
| import com.google.devtools.build.skyframe.AbstractSkyKey; |
| import com.google.devtools.build.skyframe.SkyFunctionName; |
| import com.google.devtools.build.skyframe.SkyValue; |
| import java.io.IOException; |
| import java.io.Serializable; |
| import java.util.Arrays; |
| import java.util.BitSet; |
| import java.util.Collection; |
| import java.util.Comparator; |
| import java.util.Iterator; |
| import java.util.Objects; |
| import javax.annotation.Nullable; |
| |
| /** |
| * Encapsulates the filesystem operations needed to get the directory entries of a directory. |
| * |
| * <p>This class is an implementation detail of {@link DirectoryListingValue}. |
| */ |
| @AutoCodec.VisibleForSerialization |
| public final class DirectoryListingStateValue implements SkyValue { |
| |
| private final CompactSortedDirents compactSortedDirents; |
| |
| private DirectoryListingStateValue(Collection<Dirent> dirents) { |
| this.compactSortedDirents = CompactSortedDirents.create(dirents); |
| } |
| |
| @AutoCodec.Instantiator |
| public static DirectoryListingStateValue create(Collection<Dirent> dirents) { |
| return new DirectoryListingStateValue(dirents); |
| } |
| |
| public static DirectoryListingStateValue create(RootedPath dirRootedPath) throws IOException { |
| Collection<Dirent> dirents = dirRootedPath.asPath().readdir(Symlinks.NOFOLLOW); |
| return create(dirents); |
| } |
| |
| @ThreadSafe |
| public static Key key(RootedPath rootedPath) { |
| return Key.create(rootedPath); |
| } |
| |
| @AutoCodec.VisibleForSerialization |
| @AutoCodec |
| static class Key extends AbstractSkyKey<RootedPath> { |
| private static final Interner<Key> interner = BlazeInterners.newWeakInterner(); |
| |
| private Key(RootedPath arg) { |
| super(arg); |
| } |
| |
| @AutoCodec.VisibleForSerialization |
| @AutoCodec.Instantiator |
| static Key create(RootedPath arg) { |
| return interner.intern(new Key(arg)); |
| } |
| |
| @Override |
| public SkyFunctionName functionName() { |
| return SkyFunctions.DIRECTORY_LISTING_STATE; |
| } |
| } |
| |
| /** |
| * Returns the directory entries for this directory, in a stable order. |
| * |
| * <p>Symlinks are not expanded. |
| */ |
| public Dirents getDirents() { |
| return compactSortedDirents; |
| } |
| |
| @Override |
| public int hashCode() { |
| return compactSortedDirents.hashCode(); |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (this == obj) { |
| return true; |
| } |
| if (!(obj instanceof DirectoryListingStateValue)) { |
| return false; |
| } |
| DirectoryListingStateValue other = (DirectoryListingStateValue) obj; |
| return compactSortedDirents.equals(other.compactSortedDirents); |
| } |
| |
| @Override |
| public String toString() { |
| return MoreObjects.toStringHelper(this) |
| .add("dirents", Iterables.toString(getDirents())) |
| .toString(); |
| } |
| |
| /** A space-efficient, sorted, immutable dirent structure. */ |
| private static class CompactSortedDirents implements Dirents, Serializable { |
| |
| private final String[] names; |
| private final BitSet packedTypes; |
| |
| private CompactSortedDirents(String[] names, BitSet packedTypes) { |
| this.names = names; |
| this.packedTypes = packedTypes; |
| } |
| |
| public static CompactSortedDirents create(Collection<Dirent> dirents) { |
| final Dirent[] direntArray = dirents.toArray(new Dirent[dirents.size()]); |
| Integer[] indices = new Integer[dirents.size()]; |
| for (int i = 0; i < dirents.size(); i++) { |
| indices[i] = i; |
| } |
| Arrays.sort(indices, |
| new Comparator<Integer>() { |
| @Override |
| public int compare(Integer o1, Integer o2) { |
| return direntArray[o1].compareTo(direntArray[o2]); |
| } |
| }); |
| String[] names = new String[dirents.size()]; |
| BitSet packedTypes = new BitSet(dirents.size() * 2); |
| for (int i = 0; i < dirents.size(); i++) { |
| Dirent dirent = direntArray[indices[i]]; |
| names[i] = dirent.getName(); |
| packType(packedTypes, dirent.getType(), i); |
| } |
| return new CompactSortedDirents(names, packedTypes); |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (!(obj instanceof CompactSortedDirents)) { |
| return false; |
| } |
| if (this == obj) { |
| return true; |
| } |
| CompactSortedDirents other = (CompactSortedDirents) obj; |
| return Arrays.equals(names, other.names) && packedTypes.equals(other.packedTypes); |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hash(Arrays.hashCode(names), packedTypes); |
| } |
| |
| @Override |
| @Nullable |
| public Dirent maybeGetDirent(String baseName) { |
| int pos = Arrays.binarySearch(names, baseName); |
| return pos < 0 ? null : direntAt(pos); |
| } |
| |
| @Override |
| public Iterator<Dirent> iterator() { |
| return new Iterator<Dirent>() { |
| |
| private int i = 0; |
| |
| @Override |
| public boolean hasNext() { |
| return i < size(); |
| } |
| |
| @Override |
| public Dirent next() { |
| return direntAt(i++); |
| } |
| |
| @Override |
| public void remove() { |
| throw new UnsupportedOperationException(); |
| } |
| }; |
| } |
| |
| @Override |
| public int size() { |
| return names.length; |
| } |
| |
| /** Returns the type of the ith dirent. */ |
| private Dirent.Type unpackType(int i) { |
| int start = i * 2; |
| boolean upper = packedTypes.get(start); |
| boolean lower = packedTypes.get(start + 1); |
| if (!upper && !lower) { |
| return Dirent.Type.FILE; |
| } else if (!upper && lower){ |
| return Dirent.Type.DIRECTORY; |
| } else if (upper && !lower) { |
| return Dirent.Type.SYMLINK; |
| } else { |
| return Dirent.Type.UNKNOWN; |
| } |
| } |
| |
| /** Sets the type of the ith dirent. */ |
| private static void packType(BitSet bitSet, Dirent.Type type, int i) { |
| int start = i * 2; |
| switch (type) { |
| case FILE: |
| pack(bitSet, start, false, false); |
| break; |
| case DIRECTORY: |
| pack(bitSet, start, false, true); |
| break; |
| case SYMLINK: |
| pack(bitSet, start, true, false); |
| break; |
| case UNKNOWN: |
| pack(bitSet, start, true, true); |
| break; |
| default: |
| throw new IllegalStateException("Unknown dirent type: " + type); |
| } |
| } |
| |
| private static void pack(BitSet bitSet, int start, boolean upper, boolean lower) { |
| bitSet.set(start, upper); |
| bitSet.set(start + 1, lower); |
| } |
| |
| private Dirent direntAt(int i) { |
| Preconditions.checkState(i >= 0 && i < size(), "i: %s, size: %s", i, size()); |
| return new Dirent(names[i], unpackType(i)); |
| } |
| } |
| } |