File.java
/*
* 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.annotations.VisibleForTesting;
import com.google.common.base.MoreObjects;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Table;
import java.io.IOException;
import java.nio.file.attribute.FileTime;
import java.util.concurrent.locks.ReadWriteLock;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* A file object, containing both the file's metadata and content.
*
* @author Colin Decker
*/
public abstract class File {
private final int id;
private int links;
private FileTime creationTime;
private FileTime lastAccessTime;
private FileTime lastModifiedTime;
// null when only the basic view is used (default)
private @Nullable Table<String, String, Object> attributes;
File(int id, FileTime creationTime) {
this.id = id;
this.creationTime = creationTime;
this.lastAccessTime = creationTime;
this.lastModifiedTime = creationTime;
}
/** Returns the ID of this file. */
public int id() {
return id;
}
/**
* Returns the size, in bytes, of this file's content. Directories and symbolic links have a size
* of 0.
*/
public long size() {
return 0;
}
/** Returns whether or not this file is a directory. */
public final boolean isDirectory() {
return this instanceof Directory;
}
/** Returns whether or not this file is a regular file. */
public final boolean isRegularFile() {
return this instanceof RegularFile;
}
/** Returns whether or not this file is a symbolic link. */
public final boolean isSymbolicLink() {
return this instanceof SymbolicLink;
}
/**
* Creates a new file of the same type as this file with the given ID and creation time. Does not
* copy the content of this file unless the cost of copying the content is minimal. This is
* because this method is called with a hold on the file system's lock.
*/
abstract File copyWithoutContent(int id, FileTime creationTime);
/**
* Copies the content of this file to the given file. The given file must be the same type of file
* as this file and should have no content.
*
* <p>This method is used for copying the content of a file after copying the file itself. Does
* nothing by default.
*/
void copyContentTo(File file) throws IOException {}
/**
* Returns the read-write lock for this file's content, or {@code null} if there is no content
* lock.
*/
@Nullable ReadWriteLock contentLock() {
return null;
}
/** Called when a stream or channel to this file is opened. */
void opened() {}
/**
* Called when a stream or channel to this file is closed. If there are no more streams or
* channels open to the file and it has been deleted, its contents may be deleted.
*/
void closed() {}
/**
* Called when (a single link to) this file is deleted. There may be links remaining. Does nothing
* by default.
*/
void deleted() {}
/** Returns whether or not this file is a root directory of the file system. */
final boolean isRootDirectory() {
// only root directories have their parent link pointing to themselves
return isDirectory() && equals(((Directory) this).parent());
}
/** Returns the current count of links to this file. */
public final synchronized int links() {
return links;
}
/**
* Called when this file has been linked in a directory. The given entry is the new directory
* entry that links to this file.
*/
void linked(DirectoryEntry entry) {
checkNotNull(entry);
}
/** Called when this file has been unlinked from a directory, either for a move or delete. */
void unlinked() {}
/** Increments the link count for this file. */
final synchronized void incrementLinkCount() {
links++;
}
/** Decrements the link count for this file. */
final synchronized void decrementLinkCount() {
links--;
}
/** Gets the creation time of the file. */
public final synchronized FileTime getCreationTime() {
return creationTime;
}
/** Gets the last access time of the file. */
public final synchronized FileTime getLastAccessTime() {
return lastAccessTime;
}
/** Gets the last modified time of the file. */
public final synchronized FileTime getLastModifiedTime() {
return lastModifiedTime;
}
/** Sets the creation time of the file. */
final synchronized void setCreationTime(FileTime creationTime) {
this.creationTime = creationTime;
}
/** Sets the last access time of the file. */
final synchronized void setLastAccessTime(FileTime lastAccessTime) {
this.lastAccessTime = lastAccessTime;
}
/** Sets the last modified time of the file. */
final synchronized void setLastModifiedTime(FileTime lastModifiedTime) {
this.lastModifiedTime = lastModifiedTime;
}
/**
* Returns the names of the attributes contained in the given attribute view in the file's
* attributes table.
*/
public final synchronized ImmutableSet<String> getAttributeNames(String view) {
if (attributes == null) {
return ImmutableSet.of();
}
return ImmutableSet.copyOf(attributes.row(view).keySet());
}
/** Returns the attribute keys contained in the attributes map for the file. */
@VisibleForTesting
final synchronized ImmutableSet<String> getAttributeKeys() {
if (attributes == null) {
return ImmutableSet.of();
}
ImmutableSet.Builder<String> builder = ImmutableSet.builder();
for (Table.Cell<String, String, Object> cell : attributes.cellSet()) {
builder.add(cell.getRowKey() + ':' + cell.getColumnKey());
}
return builder.build();
}
/** Gets the value of the given attribute in the given view. */
public final synchronized @Nullable Object getAttribute(String view, String attribute) {
if (attributes == null) {
return null;
}
return attributes.get(view, attribute);
}
/** Sets the given attribute in the given view to the given value. */
public final synchronized void setAttribute(String view, String attribute, Object value) {
if (attributes == null) {
attributes = HashBasedTable.create();
}
attributes.put(view, attribute, value);
}
/** Deletes the given attribute from the given view. */
public final synchronized void deleteAttribute(String view, String attribute) {
if (attributes != null) {
attributes.remove(view, attribute);
}
}
/** Copies basic attributes (file times) from this file to the given file. */
final synchronized void copyBasicAttributes(File target) {
target.setFileTimes(creationTime, lastModifiedTime, lastAccessTime);
}
private synchronized void setFileTimes(
FileTime creationTime, FileTime lastModifiedTime, FileTime lastAccessTime) {
this.creationTime = creationTime;
this.lastModifiedTime = lastModifiedTime;
this.lastAccessTime = lastAccessTime;
}
/** Copies the attributes from this file to the given file. */
final synchronized void copyAttributes(File target) {
copyBasicAttributes(target);
target.putAll(attributes);
}
private synchronized void putAll(@Nullable Table<String, String, Object> attributes) {
if (attributes != null && this.attributes != attributes) {
if (this.attributes == null) {
this.attributes = HashBasedTable.create();
}
this.attributes.putAll(attributes);
}
}
@Override
public final String toString() {
return MoreObjects.toStringHelper(this).add("id", id()).toString();
}
}