aboutsummaryrefslogtreecommitdiff
path: root/jimfs/src/main/java/com/google/common/jimfs/JimfsFileSystem.java
blob: dd72146ff0dc80af41f628200a01ae9239234dd2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
/*
 * 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.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.io.Closeable;
import java.io.IOException;
import java.net.URI;
import java.nio.file.FileStore;
import java.nio.file.FileSystem;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.nio.file.WatchService;
import java.nio.file.attribute.UserPrincipalLookupService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.checkerframework.checker.nullness.compatqual.NullableDecl;

/**
 * {@link FileSystem} implementation for Jimfs. Most behavior for the file system is implemented by
 * its {@linkplain #getDefaultView() default file system view}.
 *
 * <h3>Overview of file system design</h3>
 *
 * {@link com.google.common.jimfs.JimfsFileSystem JimfsFileSystem} instances are created by {@link
 * com.google.common.jimfs.JimfsFileSystems JimfsFileSystems} using a user-provided {@link
 * com.google.common.jimfs.Configuration Configuration}. The configuration is used to create the
 * various classes that implement the file system with the correct settings and to create the file
 * system root directories and working directory. The file system is then used to create the {@code
 * Path} objects that all file system operations use.
 *
 * <p>Once created, the primary entry points to the file system are {@link
 * com.google.common.jimfs.JimfsFileSystemProvider JimfsFileSystemProvider}, which handles calls to
 * methods in {@link java.nio.file.Files}, and {@link
 * com.google.common.jimfs.JimfsSecureDirectoryStream JimfsSecureDirectoryStream}, which provides
 * methods that are similar to those of the file system provider but which treat relative paths as
 * relative to the stream's directory rather than the file system's working directory.
 *
 * <p>The implementation of the methods on both of those classes is handled by the {@link
 * com.google.common.jimfs.FileSystemView FileSystemView} class, which acts as a view of the file
 * system with a specific working directory. The file system provider uses the file system's default
 * view, while each secure directory stream uses a view specific to that stream.
 *
 * <p>File system views make use of the file system's singleton {@link
 * com.google.common.jimfs.JimfsFileStore JimfsFileStore} which handles file creation, storage and
 * attributes. The file store delegates to several other classes to handle each of these:
 *
 * <ul>
 *   <li>{@link com.google.common.jimfs.FileFactory FileFactory} handles creation of new file
 *       objects.
 *   <li>{@link com.google.common.jimfs.HeapDisk HeapDisk} handles allocation of blocks to {@link
 *       RegularFile RegularFile} instances.
 *   <li>{@link com.google.common.jimfs.FileTree FileTree} stores the root of the file hierarchy and
 *       handles file lookup.
 *   <li>{@link com.google.common.jimfs.AttributeService AttributeService} handles file attributes,
 *       using a set of {@link com.google.common.jimfs.AttributeProvider AttributeProvider}
 *       implementations to handle each supported file attribute view.
 * </ul>
 *
 * <h3>Paths</h3>
 *
 * The implementation of {@link java.nio.file.Path} for the file system is {@link
 * com.google.common.jimfs.JimfsPath JimfsPath}. Paths are created by a {@link
 * com.google.common.jimfs.PathService PathService} with help from the file system's configured
 * {@link com.google.common.jimfs.PathType PathType}.
 *
 * <p>Paths are made up of {@link com.google.common.jimfs.Name Name} objects, which also serve as
 * the file names in directories. A name has two forms:
 *
 * <ul>
 *   <li>The <b>display form</b> is used in {@code Path} for {@code toString()}. It is also used for
 *       determining the equality and sort order of {@code Path} objects for most file systems.
 *   <li>The <b>canonical form</b> is used for equality of two {@code Name} objects. This affects
 *       the notion of name equality in the file system itself for file lookup. A file system may be
 *       configured to use the canonical form of the name for path equality (a Windows-like file
 *       system configuration does this, as the real Windows file system implementation uses
 *       case-insensitive equality for its path objects.
 * </ul>
 *
 * <p>The canonical form of a name is created by applying a series of {@linkplain PathNormalization
 * normalizations} to the original string. These normalization may be either a Unicode normalization
 * (e.g. NFD) or case folding normalization for case-insensitivity. Normalizations may also be
 * applied to the display form of a name, but this is currently only done for a Mac OS X type
 * configuration.
 *
 * <h3>Files</h3>
 *
 * All files in the file system are an instance of {@link com.google.common.jimfs.File File}. A file
 * object contains both the file's attributes and content.
 *
 * <p>There are three types of files:
 *
 * <ul>
 *   <li>{@link Directory Directory} - contains a table linking file names to {@linkplain
 *       com.google.common.jimfs.DirectoryEntry directory entries}.
 *   <li>{@link RegularFile RegularFile} - an in-memory store for raw bytes.
 *   <li>{@link com.google.common.jimfs.SymbolicLink SymbolicLink} - contains a path.
 * </ul>
 *
 * <p>{@link com.google.common.jimfs.JimfsFileChannel JimfsFileChannel}, {@link
 * com.google.common.jimfs.JimfsInputStream JimfsInputStream} and {@link
 * com.google.common.jimfs.JimfsOutputStream JimfsOutputStream} implement the standard
 * channel/stream APIs for regular files.
 *
 * <p>{@link com.google.common.jimfs.JimfsSecureDirectoryStream JimfsSecureDirectoryStream} handles
 * reading the entries of a directory. The secure directory stream additionally contains a {@code
 * FileSystemView} with its directory as the working directory, allowing for operations relative to
 * the actual directory file rather than just the path to the file. This allows the operations to
 * continue to work as expected even if the directory is moved.
 *
 * <p>A directory can be watched for changes using the {@link java.nio.file.WatchService}
 * implementation, {@link com.google.common.jimfs.PollingWatchService PollingWatchService}.
 *
 * <h3>Regular files</h3>
 *
 * {@link RegularFile RegularFile} makes use of a singleton {@link com.google.common.jimfs.HeapDisk
 * HeapDisk}. A disk is a resizable factory and cache for fixed size blocks of memory. These blocks
 * are allocated to files as needed and returned to the disk when a file is deleted or truncated.
 * When cached free blocks are available, those blocks are allocated to files first. If more blocks
 * are needed, they are created.
 *
 * <h3>Linking</h3>
 *
 * When a file is mapped to a file name in a directory table, it is <i>linked</i>. Each type of file
 * has different rules governing how it is linked.
 *
 * <ul>
 *   <li>Directory - A directory has two or more links to it. The first is the link from its parent
 *       directory to it. This link is the name of the directory. The second is the <i>self</i> link
 *       (".") which links the directory to itself. The directory may also have any number of
 *       additional <i>parent</i> links ("..") from child directories back to it.
 *   <li>Regular file - A regular file has one link from its parent directory by default. However,
 *       regular files are also allowed to have any number of additional user-created hard links,
 *       from the same directory with different names and/or from other directories with any names.
 *   <li>Symbolic link - A symbolic link can only have one link, from its parent directory.
 * </ul>
 *
 * <h3>Thread safety</h3>
 *
 * All file system operations should be safe in a multithreaded environment. The file hierarchy
 * itself is protected by a file system level read-write lock. This ensures safety of all
 * modifications to directory tables as well as atomicity of operations like file moves. Regular
 * files are each protected by a read-write lock which is obtained for each read or write operation.
 * File attributes are protected by synchronization on the file object itself.
 *
 * @author Colin Decker
 */
final class JimfsFileSystem extends FileSystem {

  private final JimfsFileSystemProvider provider;
  private final URI uri;

  private final JimfsFileStore fileStore;
  private final PathService pathService;

  private final UserPrincipalLookupService userLookupService = new UserLookupService(true);

  private final FileSystemView defaultView;

  private final WatchServiceConfiguration watchServiceConfig;

  JimfsFileSystem(
      JimfsFileSystemProvider provider,
      URI uri,
      JimfsFileStore fileStore,
      PathService pathService,
      FileSystemView defaultView,
      WatchServiceConfiguration watchServiceConfig) {
    this.provider = checkNotNull(provider);
    this.uri = checkNotNull(uri);
    this.fileStore = checkNotNull(fileStore);
    this.pathService = checkNotNull(pathService);
    this.defaultView = checkNotNull(defaultView);
    this.watchServiceConfig = checkNotNull(watchServiceConfig);
  }

  @Override
  public JimfsFileSystemProvider provider() {
    return provider;
  }

  /** Returns the URI for this file system. */
  public URI getUri() {
    return uri;
  }

  /** Returns the default view for this file system. */
  public FileSystemView getDefaultView() {
    return defaultView;
  }

  @Override
  public String getSeparator() {
    return pathService.getSeparator();
  }

  @SuppressWarnings("unchecked") // safe cast of immutable set
  @Override
  public ImmutableSortedSet<Path> getRootDirectories() {
    ImmutableSortedSet.Builder<JimfsPath> builder = ImmutableSortedSet.orderedBy(pathService);
    for (Name name : fileStore.getRootDirectoryNames()) {
      builder.add(pathService.createRoot(name));
    }
    return (ImmutableSortedSet<Path>) (ImmutableSortedSet<?>) builder.build();
  }

  /** Returns the working directory path for this file system. */
  public JimfsPath getWorkingDirectory() {
    return defaultView.getWorkingDirectoryPath();
  }

  /** Returns the path service for this file system. */
  @VisibleForTesting
  PathService getPathService() {
    return pathService;
  }

  /** Returns the file store for this file system. */
  public JimfsFileStore getFileStore() {
    return fileStore;
  }

  @Override
  public ImmutableSet<FileStore> getFileStores() {
    fileStore.state().checkOpen();
    return ImmutableSet.<FileStore>of(fileStore);
  }

  @Override
  public ImmutableSet<String> supportedFileAttributeViews() {
    return fileStore.supportedFileAttributeViews();
  }

  @Override
  public JimfsPath getPath(String first, String... more) {
    fileStore.state().checkOpen();
    return pathService.parsePath(first, more);
  }

  /** Gets the URI of the given path in this file system. */
  public URI toUri(JimfsPath path) {
    fileStore.state().checkOpen();
    return pathService.toUri(uri, path.toAbsolutePath());
  }

  /** Converts the given URI into a path in this file system. */
  public JimfsPath toPath(URI uri) {
    fileStore.state().checkOpen();
    return pathService.fromUri(uri);
  }

  @Override
  public PathMatcher getPathMatcher(String syntaxAndPattern) {
    fileStore.state().checkOpen();
    return pathService.createPathMatcher(syntaxAndPattern);
  }

  @Override
  public UserPrincipalLookupService getUserPrincipalLookupService() {
    fileStore.state().checkOpen();
    return userLookupService;
  }

  @Override
  public WatchService newWatchService() throws IOException {
    return watchServiceConfig.newWatchService(defaultView, pathService);
  }

  @NullableDecl private ExecutorService defaultThreadPool;

  /**
   * Returns a default thread pool to use for asynchronous file channels when users do not provide
   * an executor themselves. (This is required by the spec of newAsynchronousFileChannel in
   * FileSystemProvider.)
   */
  public synchronized ExecutorService getDefaultThreadPool() {
    if (defaultThreadPool == null) {
      defaultThreadPool =
          Executors.newCachedThreadPool(
              new ThreadFactoryBuilder()
                  .setDaemon(true)
                  .setNameFormat("JimfsFileSystem-" + uri.getHost() + "-defaultThreadPool-%s")
                  .build());

      // ensure thread pool is closed when file system is closed
      fileStore
          .state()
          .register(
              new Closeable() {
                @Override
                public void close() {
                  defaultThreadPool.shutdown();
                }
              });
    }
    return defaultThreadPool;
  }

  /**
   * Returns {@code false}; currently, cannot create a read-only file system.
   *
   * @return {@code false}, always
   */
  @Override
  public boolean isReadOnly() {
    return false;
  }

  @Override
  public boolean isOpen() {
    return fileStore.state().isOpen();
  }

  @Override
  public void close() throws IOException {
    fileStore.state().close();
  }
}