aboutsummaryrefslogtreecommitdiff
path: root/jimfs/src/main/java/com/google/common/jimfs/Jimfs.java
blob: a04ce46d9fd410cd79fa9f3e854f3e5e88c123a1 (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
/*
 * 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.checkArgument;
import static com.google.common.jimfs.SystemJimfsFileSystemProvider.FILE_SYSTEM_KEY;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.ProviderNotFoundException;
import java.nio.file.spi.FileSystemProvider;
import java.util.ServiceConfigurationError;
import java.util.ServiceLoader;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.checkerframework.checker.nullness.compatqual.NullableDecl;

/**
 * Static factory methods for creating new Jimfs file systems. File systems may either be created
 * with a basic configuration matching the current operating system or by providing a specific
 * {@link Configuration}. Basic {@linkplain Configuration#unix() UNIX}, {@linkplain
 * Configuration#osX() Mac OS X} and {@linkplain Configuration#windows() Windows} configurations are
 * provided.
 *
 * <p>Examples:
 *
 * <pre>
 *   // A file system with a configuration similar to the current OS
 *   FileSystem fileSystem = Jimfs.newFileSystem();
 *
 *   // A file system with paths and behavior generally matching that of Windows
 *   FileSystem windows = Jimfs.newFileSystem(Configuration.windows());  </pre>
 *
 * <p>Additionally, various behavior of the file system can be customized by creating a custom
 * {@link Configuration}. A modified version of one of the existing default configurations can be
 * created using {@link Configuration#toBuilder()} or a new configuration can be created from
 * scratch with {@link Configuration#builder(PathType)}. See {@link Configuration.Builder} for what
 * can be configured.
 *
 * <p>Examples:
 *
 * <pre>
 *   // Modify the default UNIX configuration
 *   FileSystem fileSystem = Jimfs.newFileSystem(Configuration.unix()
 *       .toBuilder()
 *       .setAttributeViews("basic", "owner", "posix", "unix")
 *       .setWorkingDirectory("/home/user")
 *       .setBlockSize(4096)
 *       .build());
 *
 *   // Create a custom configuration
 *   Configuration config = Configuration.builder(PathType.windows())
 *       .setRoots("C:\\", "D:\\", "E:\\")
 *       // ...
 *       .build();  </pre>
 *
 * @author Colin Decker
 */
public final class Jimfs {

  /** The URI scheme for the Jimfs file system ("jimfs"). */
  public static final String URI_SCHEME = "jimfs";

  private static final Logger LOGGER = Logger.getLogger(Jimfs.class.getName());

  private Jimfs() {}

  /**
   * Creates a new in-memory file system with a {@linkplain Configuration#forCurrentPlatform()
   * default configuration} appropriate to the current operating system.
   *
   * <p>More specifically, if the operating system is Windows, {@link Configuration#windows()} is
   * used; if the operating system is Mac OS X, {@link Configuration#osX()} is used; otherwise,
   * {@link Configuration#unix()} is used.
   */
  public static FileSystem newFileSystem() {
    return newFileSystem(newRandomFileSystemName());
  }

  /**
   * Creates a new in-memory file system with a {@linkplain Configuration#forCurrentPlatform()
   * default configuration} appropriate to the current operating system.
   *
   * <p>More specifically, if the operating system is Windows, {@link Configuration#windows()} is
   * used; if the operating system is Mac OS X, {@link Configuration#osX()} is used; otherwise,
   * {@link Configuration#unix()} is used.
   *
   * <p>The returned file system uses the given name as the host part of its URI and the URIs of
   * paths in the file system. For example, given the name {@code my-file-system}, the file system's
   * URI will be {@code jimfs://my-file-system} and the URI of the path {@code /foo/bar} will be
   * {@code jimfs://my-file-system/foo/bar}.
   */
  public static FileSystem newFileSystem(String name) {
    return newFileSystem(name, Configuration.forCurrentPlatform());
  }

  /** Creates a new in-memory file system with the given configuration. */
  public static FileSystem newFileSystem(Configuration configuration) {
    return newFileSystem(newRandomFileSystemName(), configuration);
  }

  /**
   * Creates a new in-memory file system with the given configuration.
   *
   * <p>The returned file system uses the given name as the host part of its URI and the URIs of
   * paths in the file system. For example, given the name {@code my-file-system}, the file system's
   * URI will be {@code jimfs://my-file-system} and the URI of the path {@code /foo/bar} will be
   * {@code jimfs://my-file-system/foo/bar}.
   */
  public static FileSystem newFileSystem(String name, Configuration configuration) {
    try {
      URI uri = new URI(URI_SCHEME, name, null, null);
      return newFileSystem(uri, configuration);
    } catch (URISyntaxException e) {
      throw new IllegalArgumentException(e);
    }
  }

  @VisibleForTesting
  static FileSystem newFileSystem(URI uri, Configuration config) {
    checkArgument(
        URI_SCHEME.equals(uri.getScheme()), "uri (%s) must have scheme %s", uri, URI_SCHEME);

    try {
      // Create the FileSystem. It uses JimfsFileSystemProvider as its provider, as that is
      // the provider that actually implements the operations needed for Files methods to work.
      JimfsFileSystem fileSystem =
          JimfsFileSystems.newFileSystem(JimfsFileSystemProvider.instance(), uri, config);

      /*
       * Now, call FileSystems.newFileSystem, passing it the FileSystem we just created. This
       * allows the system-loaded SystemJimfsFileSystemProvider instance to cache the FileSystem
       * so that methods like Paths.get(URI) work.
       * We do it in this awkward way to avoid issues when the classes in the API (this class
       * and Configuration, for example) are loaded by a different classloader than the one that
       * loads SystemJimfsFileSystemProvider using ServiceLoader. See
       * https://github.com/google/jimfs/issues/18 for gory details.
       */
      try {
        ImmutableMap<String, ?> env = ImmutableMap.of(FILE_SYSTEM_KEY, fileSystem);
        FileSystems.newFileSystem(uri, env, SystemJimfsFileSystemProvider.class.getClassLoader());
      } catch (ProviderNotFoundException | ServiceConfigurationError ignore) {
        // See the similar catch block below for why we ignore this.
        // We log there rather than here so that there's only typically one such message per VM.
      }

      return fileSystem;
    } catch (IOException e) {
      throw new AssertionError(e);
    }
  }

  /**
   * The system-loaded instance of {@code SystemJimfsFileSystemProvider}, or {@code null} if it
   * could not be found or loaded.
   */
  @NullableDecl static final FileSystemProvider systemProvider = getSystemJimfsProvider();

  /**
   * Returns the system-loaded instance of {@code SystemJimfsFileSystemProvider} or {@code null} if
   * it could not be found or loaded.
   *
   * <p>Like {@link FileSystems#newFileSystem(URI, Map, ClassLoader)}, this method first looks in
   * the list of {@linkplain FileSystemProvider#installedProviders() installed providers} and if not
   * found there, attempts to load it from the {@code ClassLoader} with {@link ServiceLoader}.
   *
   * <p>The idea is that this method should return an instance of the same class (i.e. loaded by the
   * same class loader) as the class whose static cache a {@code JimfsFileSystem} instance will be
   * placed in when {@code FileSystems.newFileSystem} is called in {@code Jimfs.newFileSystem}.
   */
  @NullableDecl
  private static FileSystemProvider getSystemJimfsProvider() {
    try {
      for (FileSystemProvider provider : FileSystemProvider.installedProviders()) {
        if (provider.getScheme().equals(URI_SCHEME)) {
          return provider;
        }
      }

      /*
       * Jimfs.newFileSystem passes SystemJimfsFileSystemProvider.class.getClassLoader() to
       * FileSystems.newFileSystem so that it will fall back to loading from that classloader if
       * the provider isn't found in the installed providers. So do the same fallback here to ensure
       * that we can remove file systems from the static cache on SystemJimfsFileSystemProvider if
       * it gets loaded that way.
       */
      ServiceLoader<FileSystemProvider> loader =
          ServiceLoader.load(
              FileSystemProvider.class, SystemJimfsFileSystemProvider.class.getClassLoader());
      for (FileSystemProvider provider : loader) {
        if (provider.getScheme().equals(URI_SCHEME)) {
          return provider;
        }
      }
    } catch (ProviderNotFoundException | ServiceConfigurationError e) {
      /*
       * This can apparently (https://github.com/google/jimfs/issues/31) occur in an environment
       * where services are not loaded from META-INF/services, such as JBoss/Wildfly. In this
       * case, FileSystems.newFileSystem will most likely fail in the same way when called from
       * Jimfs.newFileSystem above, and there will be no way to make URI-based methods like
       * Paths.get(URI) work. Rather than making the user completly unable to use Jimfs, just
       * log this exception and continue.
       *
       * Note: Catching both ProviderNotFoundException, which would occur if no provider matching
       * the "jimfs" URI scheme is found, and ServiceConfigurationError, which can occur if the
       * ServiceLoader finds the META-INF/services entry for Jimfs (or some other
       * FileSystemProvider!) but is then unable to load that class.
       */
      LOGGER.log(
          Level.INFO,
          "An exception occurred when attempting to find the system-loaded FileSystemProvider "
              + "for Jimfs. This likely means that your environment does not support loading "
              + "services via ServiceLoader or is not configured correctly. This does not prevent "
              + "using Jimfs, but it will mean that methods that look up via URI such as "
              + "Paths.get(URI) cannot work.",
          e);
    }

    return null;
  }

  private static String newRandomFileSystemName() {
    return UUID.randomUUID().toString();
  }
}