View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.vfs2.impl;
18  
19  import java.io.File;
20  import java.lang.reflect.Constructor;
21  import java.net.URI;
22  import java.net.URISyntaxException;
23  import java.net.URL;
24  import java.net.URLStreamHandler;
25  import java.net.URLStreamHandlerFactory;
26  import java.util.ArrayList;
27  import java.util.Arrays;
28  import java.util.Collection;
29  import java.util.HashMap;
30  import java.util.List;
31  import java.util.Map;
32  
33  import org.apache.commons.lang3.ArrayUtils;
34  import org.apache.commons.logging.Log;
35  import org.apache.commons.logging.LogFactory;
36  import org.apache.commons.vfs2.CacheStrategy;
37  import org.apache.commons.vfs2.Capability;
38  import org.apache.commons.vfs2.FileContentInfoFactory;
39  import org.apache.commons.vfs2.FileName;
40  import org.apache.commons.vfs2.FileObject;
41  import org.apache.commons.vfs2.FileSystem;
42  import org.apache.commons.vfs2.FileSystemConfigBuilder;
43  import org.apache.commons.vfs2.FileSystemException;
44  import org.apache.commons.vfs2.FileSystemManager;
45  import org.apache.commons.vfs2.FileSystemOptions;
46  import org.apache.commons.vfs2.FileType;
47  import org.apache.commons.vfs2.FilesCache;
48  import org.apache.commons.vfs2.NameScope;
49  import org.apache.commons.vfs2.VFS;
50  import org.apache.commons.vfs2.cache.SoftRefFilesCache;
51  import org.apache.commons.vfs2.operations.FileOperationProvider;
52  import org.apache.commons.vfs2.provider.AbstractFileName;
53  import org.apache.commons.vfs2.provider.AbstractFileProvider;
54  import org.apache.commons.vfs2.provider.DefaultURLStreamHandler;
55  import org.apache.commons.vfs2.provider.FileProvider;
56  import org.apache.commons.vfs2.provider.FileReplicator;
57  import org.apache.commons.vfs2.provider.LocalFileProvider;
58  import org.apache.commons.vfs2.provider.TemporaryFileStore;
59  import org.apache.commons.vfs2.provider.UriParser;
60  import org.apache.commons.vfs2.provider.VfsComponent;
61  
62  /**
63   * The default file system manager implementation.
64   */
65  public class DefaultFileSystemManager implements FileSystemManager {
66  
67      /**
68       * This is an internal class because it needs access to the private member providers.
69       */
70      final class VfsStreamHandlerFactory implements URLStreamHandlerFactory {
71          @Override
72          public URLStreamHandler createURLStreamHandler(final String protocol) {
73              final FileProvider provider = providers.get(protocol);
74              if (provider != null) {
75                  return new DefaultURLStreamHandler(context);
76              }
77  
78              // Route all other calls to the default URLStreamHandlerFactory
79              return new URLStreamHandlerProxy();
80          }
81      }
82  
83      /**
84       * Mapping from URI scheme to FileProvider.
85       */
86      private final Map<String, FileProvider> providers = new HashMap<>();
87  
88      /**
89       * List of the schemes of virtual file systems added.
90       */
91      private final List<String> virtualFileSystemSchemes = new ArrayList<>();
92  
93      /**
94       * All components used by this manager.
95       */
96      private final ArrayList<Object> components = new ArrayList<>();
97  
98      /**
99       * The context to pass to providers.
100      */
101     private final DefaultVfsComponentContext context = new DefaultVfsComponentContext(this);
102 
103     /**
104      * Operations providers added to this manager.
105      */
106     private final Map<String, List<FileOperationProvider>> operationProviders = new HashMap<>();
107 
108     /**
109      * Mappings of file types.
110      */
111     private final FileTypeMap typeMap = new FileTypeMap();
112 
113     /**
114      * The provider for local files.
115      */
116     private LocalFileProvider localFileProvider;
117 
118     /**
119      * The default provider.
120      */
121     private FileProvider defaultProvider;
122 
123     /**
124      * The file replicator to use.
125      */
126     private FileReplicator fileReplicator;
127 
128     /**
129      * The base file to use for relative URI.
130      */
131     private FileObject baseFile;
132 
133     /**
134      * The files cache
135      */
136     private FilesCache filesCache;
137 
138     /**
139      * The cache strategy
140      */
141     private CacheStrategy fileCacheStrategy;
142     /**
143      * Class which decorates all returned fileObjects
144      */
145     private Class<?> fileObjectDecorator;
146 
147     /**
148      * Reflection constructor extracted from {@link #fileObjectDecorator}
149      */
150     private Constructor<?> fileObjectDecoratorConst;
151 
152     /**
153      * The class to use to determine the content-type (mime-type)
154      */
155     private FileContentInfoFactory fileContentInfoFactory;
156 
157     /**
158      * The logger to use. Default implementation.
159      */
160     private Log log = LogFactory.getLog(getClass());
161 
162     /**
163      * The temporary file store to use.
164      */
165     private TemporaryFileStore tempFileStore;
166 
167     /**
168      * The virtual file provider.
169      */
170     private VirtualFileProvider vfsProvider;
171 
172     /**
173      * Flag, if manager is initialized (after init() and before close()).
174      */
175     private boolean init;
176 
177     /**
178      * Constructs a new instance.
179      */
180     public DefaultFileSystemManager() {
181         // empty
182     }
183 
184     /**
185      * Closes the given file system.
186      * <p>
187      * If you use VFS as singleton it is VERY dangerous to call this method
188      * </p>
189      *
190      * @param fileSystem The FileSystem to close.
191      */
192     public void _closeFileSystem(final FileSystem fileSystem) {
193         final FileProvider provider = providers.get(fileSystem.getRootName().getScheme());
194         if (provider != null) {
195             ((AbstractFileProvider) provider).closeFileSystem(fileSystem);
196         } else if (fileSystem instanceof VirtualFileSystem) {
197             // vfsProvider does not implement AbstractFileProvider
198             vfsProvider.closeFileSystem(fileSystem);
199         }
200     }
201 
202     /**
203      * Adds a file name extension mapping.
204      *
205      * @param extension The file name extension.
206      * @param scheme The scheme to use for files with this extension.
207      */
208     public void addExtensionMap(final String extension, final String scheme) {
209         typeMap.addExtension(extension, scheme);
210     }
211 
212     /**
213      * Adds a mime type mapping.
214      *
215      * @param mimeType The mime type.
216      * @param scheme The scheme to use for files with this mime type.
217      */
218     public void addMimeTypeMap(final String mimeType, final String scheme) {
219         typeMap.addMimeType(mimeType, scheme);
220     }
221 
222     /**
223      * Adds the specified FileOperationProvider for the specified scheme. Several FileOperationProvider's might be
224      * registered for the same scheme. For example, for "file" scheme we can register SvnWsOperationProvider and
225      * CvsOperationProvider.
226      *
227      * @param scheme The scheme the provider should be registered for.
228      * @param operationProvider The FileOperationProvider.
229      * @throws FileSystemException if an error occurs adding the provider.
230      */
231     @Override
232     public void addOperationProvider(final String scheme, final FileOperationProvider operationProvider)
233             throws FileSystemException {
234         addOperationProvider(new String[] {scheme}, operationProvider);
235     }
236 
237     /**
238      * @see FileSystemManager#addOperationProvider(String, org.apache.commons.vfs2.operations.FileOperationProvider)
239      * @param schemes The array of schemes the provider should apply to.
240      * @param operationProvider The FileOperationProvider.
241      * @throws FileSystemException if an error occurs.
242      */
243     @Override
244     public void addOperationProvider(final String[] schemes, final FileOperationProvider operationProvider)
245             throws FileSystemException {
246         for (final String scheme : schemes) {
247             final List<FileOperationProvider> providers = operationProviders.computeIfAbsent(scheme, k -> new ArrayList<>());
248 
249             if (providers.contains(operationProvider)) {
250                 throw new FileSystemException("vfs.operation/operation-provider-already-added.error", scheme);
251             }
252 
253             setupComponent(operationProvider);
254 
255             providers.add(operationProvider);
256         }
257     }
258 
259     /**
260      * Adds a file system provider.
261      * <p>
262      * The manager takes care of all lifecycle management. A provider may be registered multiple times. The first
263      * {@link LocalFileProvider} added will be remembered for {@link #getLocalFileProvider()}.
264      * </p>
265      *
266      * @param urlScheme The scheme the provider will handle.
267      * @param provider The provider.
268      * @throws FileSystemException if an error occurs adding the provider.
269      */
270     public void addProvider(final String urlScheme, final FileProvider provider) throws FileSystemException {
271         addProvider(new String[] {urlScheme}, provider);
272     }
273 
274     /**
275      * Adds a file system provider.
276      * <p>
277      * The manager takes care of all lifecycle management. A provider may be registered multiple times. The first
278      * {@link LocalFileProvider} added will be remembered for {@link #getLocalFileProvider()}.
279      * </p>
280      *
281      * @param urlSchemes The schemes the provider will handle.
282      * @param provider The provider.
283      * @throws FileSystemException if an error occurs adding the provider.
284      */
285     public void addProvider(final String[] urlSchemes, final FileProvider provider) throws FileSystemException {
286         // fail duplicate schemes
287         for (final String scheme : urlSchemes) {
288             if (providers.containsKey(scheme)) {
289                 throw new FileSystemException("vfs.impl/multiple-providers-for-scheme.error", scheme);
290             }
291         }
292 
293         // Contextualise the component (if not already)
294         setupComponent(provider);
295 
296         // Add to map
297         Arrays.stream(urlSchemes).forEach(scheme -> providers.put(scheme, provider));
298 
299         if (provider instanceof LocalFileProvider && localFileProvider == null) {
300             localFileProvider = (LocalFileProvider) provider;
301         }
302     }
303 
304     /**
305      * Adds a scheme.
306      *
307      * @param rootUri the URI containing the scheme to add.
308      */
309     protected void addVirtualFileSystemScheme(String rootUri) {
310         if (rootUri.indexOf(':') != -1) {
311             rootUri = rootUri.substring(0, rootUri.indexOf(':'));
312         }
313         virtualFileSystemSchemes.add(rootUri);
314     }
315 
316     /**
317      * Tests if a layered file system can be created for a given file.
318      *
319      * @param file The file to check for.
320      * @return true if the FileSystem can be created.
321      * @throws FileSystemException if an error occurs.
322      */
323     @Override
324     public boolean canCreateFileSystem(final FileObject file) throws FileSystemException {
325         return typeMap.getScheme(file) != null;
326     }
327 
328     /**
329      * Closes the manager.
330      * <p>
331      * This will close all providers (all files), it will also close all managed components including temporary files,
332      * replicator, file cache and file operations.
333      * </p>
334      * <p>
335      * The manager is in uninitialized state after this method.
336      * </p>
337      */
338     @Override
339     public void close() {
340         if (!init) {
341             return;
342         }
343 
344         // make sure all discovered components in
345         // org.apache.commons.vfs2.impl.StandardFileSystemManager.configure(Element)
346         // are closed here
347 
348         // Close the file system providers.
349         providers.values().forEach(this::closeComponent);
350 
351         // Close the other components
352         closeComponent(vfsProvider);
353         closeComponent(fileReplicator);
354         closeComponent(tempFileStore);
355         closeComponent(defaultProvider);
356 
357         // unregister all providers here, so if any components have local file references
358         // they can still resolve against the supported schemes
359         providers.clear();
360 
361         // FileOperations are components, too
362         operationProviders.values().forEach(opProviders -> opProviders.forEach(this::closeComponent));
363 
364         // unregister all
365         operationProviders.clear();
366 
367         // collections with add()
368         typeMap.clear();
369 
370         // Close cache last.
371         closeComponent(filesCache);
372 
373         // should not happen, but make debugging easier:
374         if (!components.isEmpty()) {
375             log.warn("DefaultFilesystemManager.close: not all components are closed: " + components);
376         }
377         components.clear();
378 
379         // managed components
380         vfsProvider = null;
381 
382         // virtual schemas
383         virtualFileSystemSchemes.clear();
384 
385         // setters and derived state
386         defaultProvider = null;
387         baseFile = null;
388         fileObjectDecorator = null;
389         fileObjectDecoratorConst = null;
390         localFileProvider = null;
391         fileReplicator = null;
392         tempFileStore = null;
393         // setters with init() defaults
394         filesCache = null;
395         fileCacheStrategy = null;
396         fileContentInfoFactory = null;
397 
398         init = false;
399     }
400 
401     /**
402      * Closes a component, if it has not already been closed.
403      *
404      * @param component The component to close.
405      */
406     private void closeComponent(final Object component) {
407         if (component != null && components.contains(component)) {
408             if (component instanceof VfsComponent) {
409                 ((VfsComponent) component).close();
410             }
411             components.remove(component);
412         }
413     }
414 
415     /**
416      * Closes the given file system.
417      * <p>
418      * If you use VFS as singleton it is VERY dangerous to call this method.
419      * </p>
420      *
421      * @param fileSystem The FileSystem to close.
422      */
423     @Override
424     public void closeFileSystem(final FileSystem fileSystem) {
425         // inform the cache ...
426         getFilesCache().clear(fileSystem);
427 
428         // just in case the cache didn't call _closeFileSystem
429         _closeFileSystem(fileSystem);
430     }
431 
432     /**
433      * Creates a layered file system.
434      *
435      * @param file The FileObject to use.
436      * @return The layered FileObject.
437      * @throws FileSystemException if an error occurs.
438      */
439     @Override
440     public FileObject createFileSystem(final FileObject file) throws FileSystemException {
441         final String scheme = typeMap.getScheme(file);
442         FileSystemException.requireNonNull(scheme, "vfs.impl/no-provider-for-file.error", file);
443         return createFileSystem(scheme, file);
444     }
445 
446     /**
447      * Creates a layered file system.
448      *
449      * @param scheme The scheme to use.
450      * @param file The FileObject.
451      * @return The layered FileObject.
452      * @throws FileSystemException if an error occurs.
453      */
454     @Override
455     public FileObject createFileSystem(final String scheme, final FileObject file) throws FileSystemException {
456         final FileProvider provider = providers.get(scheme);
457         FileSystemException.requireNonNull(provider, "vfs.impl/unknown-provider.error", scheme, file);
458         return provider.createFileSystem(scheme, file, file.getFileSystem().getFileSystemOptions());
459     }
460 
461     /**
462      * Creates a virtual file system.
463      *
464      * @param rootFile The FileObject to use.
465      * @return The FileObject in the VirtualFileSystem.
466      * @throws FileSystemException if an error occurs creating the file.
467      */
468     @Override
469     public FileObject createVirtualFileSystem(final FileObject rootFile) throws FileSystemException {
470         final FileObject fileObject = vfsProvider.createFileSystem(rootFile);
471         addVirtualFileSystemScheme(rootFile.getName().getScheme());
472         return fileObject;
473     }
474 
475     /**
476      * Creates an empty virtual file system.
477      *
478      * @param rootUri The URI to use as the root of the FileSystem.
479      * @return A FileObject in the virtual FileSystem.
480      * @throws FileSystemException if an error occurs.
481      */
482     @Override
483     public FileObject createVirtualFileSystem(final String rootUri) throws FileSystemException {
484         final FileObject fileObject = vfsProvider.createFileSystem(rootUri);
485         addVirtualFileSystemScheme(rootUri);
486         return fileObject;
487     }
488 
489     /**
490      * Frees all unused resources created by this manager.
491      */
492     public void freeUnusedResources() {
493         if (!init) {
494             return;
495         }
496 
497         // Close the providers.
498         providers.values().forEach(fileProvider -> {
499             if (fileProvider instanceof AbstractFileProvider) {
500                 ((AbstractFileProvider) fileProvider).freeUnusedResources();
501             }
502         });
503         // vfsProvider does not need to free resources
504     }
505 
506     /**
507      * Gets the base file used to resolve relative URI.
508      *
509      * @return The FileObject that represents the base file.
510      * @throws FileSystemException if an error occurs.
511      */
512     @Override
513     public FileObject getBaseFile() throws FileSystemException {
514         return baseFile;
515     }
516 
517     /**
518      * Gets the cache strategy used.
519      *
520      * @return The CacheStrategy.
521      */
522     @Override
523     public CacheStrategy getCacheStrategy() {
524         return fileCacheStrategy;
525     }
526 
527     /**
528      * Gets the fileContentInfoFactory used to determine the infos of a file content.
529      *
530      * @return The FileContentInfoFactory.
531      */
532     @Override
533     public FileContentInfoFactory getFileContentInfoFactory() {
534         return fileContentInfoFactory;
535     }
536 
537     /**
538      * Gets the file object decorator used.
539      *
540      * @return The decorator.
541      */
542     @Override
543     public Class<?> getFileObjectDecorator() {
544         return fileObjectDecorator;
545     }
546 
547     /**
548      * Gets the constructor associated to the fileObjectDecorator. We cache it here for performance reasons.
549      *
550      * @return The decorator's Constructor.
551      */
552     @Override
553     public Constructor<?> getFileObjectDecoratorConst() {
554         return fileObjectDecoratorConst;
555     }
556 
557     /**
558      * Gets the filesCache implementation used to cache files.
559      *
560      * @return The FilesCache.
561      */
562     @Override
563     public FilesCache getFilesCache() {
564         return filesCache;
565     }
566 
567     /**
568      * Gets the configuration builder for the given scheme.
569      *
570      * @param scheme The scheme to locate.
571      * @return The FileSystemConfigBuilder for the scheme.
572      * @throws FileSystemException if the given scheme is not known
573      */
574     @Override
575     public FileSystemConfigBuilder getFileSystemConfigBuilder(final String scheme) throws FileSystemException {
576         final FileProvider provider = providers.get(scheme);
577         FileSystemException.requireNonNull(provider, "vfs.impl/unknown-scheme.error", scheme);
578         return provider.getConfigBuilder();
579     }
580 
581     /**
582      * Gets the local file provider.
583      * <p>
584      * The local file provider is the first {@linkplain #addProvider(String[], FileProvider) provider added}
585      * implementing {@link LocalFileProvider}.
586      * </p>
587      *
588      * @return The LocalFileProvider.
589      * @throws FileSystemException if no local file provider was set.
590      */
591     private LocalFileProvider getLocalFileProvider() throws FileSystemException {
592         return FileSystemException.requireNonNull(localFileProvider, "vfs.impl/no-local-file-provider.error");
593     }
594 
595     /**
596      * Gets the logger used by this manager.
597      *
598      * @return the Logger.
599      */
600     protected Log getLogger() {
601         return log;
602     }
603 
604     /**
605      * @param scheme the scheme for which we want to get the list af registered providers.
606      * @return the registered FileOperationProviders for the specified scheme. If there were no providers registered for
607      *         the scheme, it returns null.
608      *
609      * @throws FileSystemException if an error occurs.
610      */
611     @Override
612     public FileOperationProvider[] getOperationProviders(final String scheme) throws FileSystemException {
613 
614         final List<?> providers = operationProviders.get(scheme);
615         if (providers == null || providers.isEmpty()) {
616             return null;
617         }
618         return providers.toArray(FileOperationProvider.EMPTY_ARRAY);
619     }
620 
621     /**
622      * Gets the capabilities for a given scheme.
623      *
624      * @param scheme The scheme to locate.
625      * @return A Collection of capabilities.
626      * @throws FileSystemException if the given scheme is not known
627      */
628     @Override
629     public Collection<Capability> getProviderCapabilities(final String scheme) throws FileSystemException {
630         final FileProvider provider = providers.get(scheme);
631         FileSystemException.requireNonNull(provider, "vfs.impl/unknown-scheme.error", scheme);
632         return provider.getCapabilities();
633     }
634 
635     /**
636      * Gets the file replicator.
637      *
638      * @return The file replicator. Never returns null.
639      * @throws FileSystemException if there is no FileReplicator.
640      */
641     public FileReplicator getReplicator() throws FileSystemException {
642         return FileSystemException.requireNonNull(fileReplicator, "vfs.impl/no-replicator.error");
643     }
644 
645     /**
646      * Gets the schemes currently available.
647      *
648      * @return The array of scheme names.
649      */
650     @Override
651     public String[] getSchemes() {
652         final List<String> schemes = new ArrayList<>(providers.size() + virtualFileSystemSchemes.size());
653         schemes.addAll(providers.keySet());
654         schemes.addAll(virtualFileSystemSchemes);
655         return schemes.toArray(ArrayUtils.EMPTY_STRING_ARRAY);
656     }
657 
658     /**
659      * Gets the temporary file store.
660      *
661      * @return The file store. Never returns null.
662      * @throws FileSystemException if there is no TemporaryFileStore.
663      */
664     public TemporaryFileStore getTemporaryFileStore() throws FileSystemException {
665         return FileSystemException.requireNonNull(tempFileStore, "vfs.impl/no-temp-file-store.error");
666     }
667 
668     /**
669      * Gets the URLStreamHandlerFactory.
670      *
671      * @return The URLStreamHandlerFactory.
672      */
673     @Override
674     public URLStreamHandlerFactory getURLStreamHandlerFactory() {
675         return new VfsStreamHandlerFactory();
676     }
677 
678     /**
679      * Tests if this manager has a provider for a particular scheme.
680      *
681      * @param scheme The scheme to check.
682      * @return true if a provider is configured for this scheme, false otherwise.
683      */
684     @Override
685     public boolean hasProvider(final String scheme) {
686         return providers.containsKey(scheme);
687     }
688 
689     /**
690      * Initializes this manager.
691      * <p>
692      * If no value for the following properties was specified, it will use the following defaults:
693      * </p>
694      * <ul>
695      * <li>fileContentInfoFactory = new FileContentInfoFilenameFactory()</li>
696      * <li>filesCache = new SoftRefFilesCache()</li>
697      * <li>fileCacheStrategy = CacheStrategy.ON_RESOLVE</li>
698      * </ul>
699      *
700      * @throws FileSystemException if an error occurs during initialization.
701      */
702     public void init() throws FileSystemException {
703         if (fileContentInfoFactory == null) {
704             fileContentInfoFactory = new FileContentInfoFilenameFactory();
705         }
706 
707         if (filesCache == null) {
708             filesCache = new SoftRefFilesCache();
709         }
710         if (fileCacheStrategy == null) {
711             fileCacheStrategy = CacheStrategy.ON_RESOLVE;
712         }
713         setupComponent(filesCache);
714 
715         vfsProvider = new VirtualFileProvider();
716         setupComponent(vfsProvider);
717 
718         init = true;
719     }
720 
721     /**
722      * Unregisters a file system provider.
723      *
724      * @param urlScheme The scheme of the provider.
725      * @since 2.8.0
726      */
727     public void removeProvider(final String urlScheme) {
728         final FileProvider provider = providers.remove(urlScheme);
729         // check whether the same instance is not used somewhere else
730         if (provider != null && !providers.containsValue(provider)) {
731             closeComponent(provider);
732         }
733     }
734 
735     /**
736      * Resolves a URI, relative to base file.
737      * <p>
738      * Uses the {@linkplain #getLocalFileProvider() local file provider} to locate the system file.
739      * </p>
740      *
741      * @param baseFile The base File to use to locate the file.
742      * @param uri The URI of the file to locate.
743      * @return The FileObject for the located file.
744      * @throws FileSystemException if the file cannot be located or an error occurs.
745      */
746     @Override
747     public FileObject resolveFile(final File baseFile, final String uri) throws FileSystemException {
748         return resolveFile(getLocalFileProvider().findLocalFile(baseFile), uri);
749     }
750 
751     /**
752      * Resolves a URI, relative to a base file.
753      *
754      * @param baseFile The base FileObject to use to locate the file.
755      * @param uri The URI of the file to locate.
756      * @return The FileObject for the located file.
757      * @throws FileSystemException if the file cannot be located or an error occurs.
758      */
759     @Override
760     public FileObject resolveFile(final FileObject baseFile, final String uri) throws FileSystemException {
761         return resolveFile(baseFile, uri, baseFile == null ? null : baseFile.getFileSystem().getFileSystemOptions());
762     }
763 
764     /**
765      * Resolves a URI, relative to a base file with specified FileSystem configuration.
766      *
767      * @param baseFile The base file.
768      * @param uri The file name. May be a fully qualified or relative path or an url.
769      * @param fileSystemOptions Options to pass to the file system.
770      * @return A FileObject representing the target file.
771      * @throws FileSystemException if an error occurs accessing the file.
772      */
773     public FileObject resolveFile(final FileObject baseFile, final String uri,
774             final FileSystemOptions fileSystemOptions) throws FileSystemException {
775         final FileObject realBaseFile;
776         if (baseFile != null && VFS.isUriStyle() && baseFile.getName().isFile()) {
777             realBaseFile = baseFile.getParent();
778         } else {
779             realBaseFile = baseFile;
780         }
781         // TODO: use resolveName and use this name to resolve the fileObject
782 
783         UriParser.checkUriEncoding(uri);
784 
785         if (uri == null) {
786             throw new IllegalArgumentException();
787         }
788 
789         // Extract the scheme
790         final String scheme = UriParser.extractScheme(getSchemes(), uri);
791         if (scheme != null) {
792             // An absolute URI - locate the provider
793             final FileProvider provider = providers.get(scheme);
794             if (provider != null) {
795                 return provider.findFile(realBaseFile, uri, fileSystemOptions);
796             }
797             // Otherwise, assume a local file
798         }
799 
800         // Handle absolute file names
801         if (localFileProvider != null && localFileProvider.isAbsoluteLocalName(uri)) {
802             return localFileProvider.findLocalFile(uri);
803         }
804 
805         if (scheme != null) {
806             // An unknown scheme - hand it to the default provider
807             FileSystemException.requireNonNull(defaultProvider, "vfs.impl/unknown-scheme.error", scheme, uri);
808             return defaultProvider.findFile(realBaseFile, uri, fileSystemOptions);
809         }
810 
811         // Assume a relative name - use the supplied base file
812         FileSystemException.requireNonNull(realBaseFile, "vfs.impl/find-rel-file.error", uri);
813 
814         return realBaseFile.resolveFile(uri);
815     }
816 
817     /**
818      * Locates a file by URI.
819      *
820      * @param uri The URI of the file to locate.
821      * @return The FileObject for the located file.
822      * @throws FileSystemException if the file cannot be located or an error occurs.
823      */
824     @Override
825     public FileObject resolveFile(final String uri) throws FileSystemException {
826         return resolveFile(getBaseFile(), uri);
827     }
828 
829     /**
830      * Resolves a file by URI, use the FileSystemOptions for file-system creation.
831      *
832      * @param uri The URI of the file to locate.
833      * @param fileSystemOptions The options for the FileSystem.
834      * @return The FileObject for the located file.
835      * @throws FileSystemException if the file cannot be located or an error occurs.
836      */
837 
838     @Override
839     public FileObject resolveFile(final String uri, final FileSystemOptions fileSystemOptions)
840             throws FileSystemException {
841         // return resolveFile(baseFile, uri, fileSystemOptions);
842         return resolveFile(getBaseFile(), uri, fileSystemOptions);
843     }
844 
845     /**
846      * Resolves a URI into a {@link FileObject}.
847      *
848      * @param uri The URI to convert.
849      * @return The {@link FileObject} that represents the URI. Never returns null.
850      * @throws FileSystemException On error converting the URI.
851      * @since 2.1
852      */
853     @Override
854     public FileObject resolveFile(final URI uri) throws FileSystemException {
855         // TODO Push the URI deeper into VFS
856         return resolveFile(baseFile, uri.toString(), null);
857     }
858 
859     /**
860      * Resolves a URL into a {@link FileObject}.
861      *
862      * @param url The URL to convert.
863      * @return The {@link FileObject} that represents the URL. Never returns null.
864      * @throws FileSystemException On error converting the URL.
865      * @since 2.1
866      */
867     @Override
868     public FileObject resolveFile(final URL url) throws FileSystemException {
869         try {
870             return this.resolveFile(url.toURI());
871         } catch (final URISyntaxException e) {
872             throw new FileSystemException(e);
873         }
874     }
875 
876     /**
877      * Resolves a name, relative to the file. If the supplied name is an absolute path, then it is resolved relative to
878      * the root of the file system that the file belongs to. If a relative name is supplied, then it is resolved
879      * relative to this file name.
880      *
881      * @param root The base FileName.
882      * @param path The path to the file relative to the base FileName or an absolute path.
883      * @return The constructed FileName.
884      * @throws FileSystemException if an error occurs constructing the FileName.
885      */
886     @Override
887     public FileName resolveName(final FileName root, final String path) throws FileSystemException {
888         return resolveName(root, path, NameScope.FILE_SYSTEM);
889     }
890 
891     /**
892      * Resolves a name, relative to the root.
893      *
894      * @param base the base file name
895      * @param name the name
896      * @param scope the {@link NameScope}
897      * @return The FileName of the file.
898      * @throws FileSystemException if an error occurs.
899      */
900     @Override
901     public FileName resolveName(final FileName base, final String name, final NameScope scope)
902             throws FileSystemException {
903         FileSystemException.requireNonNull(base, "Invalid base FileName.");
904         FileSystemException.requireNonNull(name, "Invalid name FileName.");
905         final FileName realBase;
906         if (VFS.isUriStyle() && base.isFile()) {
907             realBase = base.getParent();
908         } else {
909             realBase = base;
910         }
911 
912         final StringBuilder buffer = new StringBuilder(name);
913 
914         // Adjust separators
915         UriParser.fixSeparators(buffer);
916         String scheme = UriParser.extractScheme(getSchemes(), buffer.toString());
917 
918         // Determine whether to prepend the base path
919         if (name.isEmpty() || scheme == null && buffer.charAt(0) != FileName.SEPARATOR_CHAR) {
920             // Supplied path is not absolute
921             if (!VFS.isUriStyle()) {
922                 // when using URIs the parent already do have the trailing "/"
923                 buffer.insert(0, FileName.SEPARATOR_CHAR);
924             }
925             buffer.insert(0, realBase.getPath());
926         }
927 
928         // Normalise the path
929         final FileType fileType = UriParser.normalisePath(buffer);
930 
931         // Check the name is ok
932         final String resolvedPath = buffer.toString();
933         if (!AbstractFileName.checkName(realBase.getPath(), resolvedPath, scope)) {
934             throw new FileSystemException("vfs.provider/invalid-descendent-name.error", name);
935         }
936 
937         // Reappend the removed trailing / in case of a FOLDER, so that the following calls to
938         // 'provider.parseUri(realBase, fullPath)' can determine the correct FileType
939         // otherwise the resulting FileType is always fileType.FILE
940         final String trailingPathPart = fileType == FileType.FOLDER ? FileName.SEPARATOR : "";
941 
942         final String fullPath;
943         if (scheme != null) {
944             fullPath = resolvedPath + trailingPathPart;
945         } else {
946             scheme = realBase.getScheme();
947             fullPath = realBase.getRootURI() + resolvedPath + trailingPathPart;
948         }
949         final FileProvider provider = providers.get(scheme);
950         if (provider != null) {
951             // TODO: extend the file name parser to be able to parse
952             // only a pathname and take the missing informations from
953             // the base. Then we can get rid of the string operation.
954             // // String fullPath = base.getRootURI() +
955             // resolvedPath.substring(1);
956 
957             return provider.parseUri(realBase, fullPath);
958         }
959 
960         // An unknown scheme - hand it to the default provider - if possible
961         if (scheme != null && defaultProvider != null) {
962             return defaultProvider.parseUri(realBase, fullPath);
963         }
964 
965         // TODO: avoid fallback to this point
966         // this happens if we have a virtual filesystem (no provider for scheme)
967         return ((AbstractFileName) realBase).createName(resolvedPath, fileType);
968     }
969 
970     /**
971      * Resolve the uri to a file name.
972      *
973      * @param uri The URI to resolve.
974      * @return The FileName of the file.
975      * @throws FileSystemException if an error occurs.
976      */
977     @Override
978     public FileName resolveURI(final String uri) throws FileSystemException {
979         UriParser.checkUriEncoding(uri);
980 
981         if (uri == null) {
982             throw new IllegalArgumentException();
983         }
984 
985         // Extract the scheme
986         final String scheme = UriParser.extractScheme(getSchemes(), uri);
987         if (scheme != null) {
988             // An absolute URI - locate the provider
989             final FileProvider provider = providers.get(scheme);
990             if (provider != null) {
991                 return provider.parseUri(null, uri);
992             }
993 
994             // Otherwise, assume a local file
995         }
996 
997         // Handle absolute file names
998         if (localFileProvider != null && localFileProvider.isAbsoluteLocalName(uri)) {
999             return localFileProvider.parseUri(null, uri);
1000         }
1001 
1002         if (scheme != null) {
1003             // An unknown scheme - hand it to the default provider
1004             FileSystemException.requireNonNull(defaultProvider, "vfs.impl/unknown-scheme.error", scheme, uri);
1005             return defaultProvider.parseUri(null, uri);
1006         }
1007 
1008         // Assume a relative name - use the supplied base file
1009         FileSystemException.requireNonNull(baseFile, "vfs.impl/find-rel-file.error", uri);
1010 
1011         return resolveName(baseFile.getName(), uri, NameScope.FILE_SYSTEM);
1012     }
1013 
1014     /**
1015      * Sets the base file to use when resolving relative URI.
1016      *
1017      * @param baseFile The new base FileObject.
1018      * @throws FileSystemException if an error occurs.
1019      */
1020     public void setBaseFile(final File baseFile) throws FileSystemException {
1021         this.baseFile = getLocalFileProvider().findLocalFile(baseFile);
1022     }
1023 
1024     /**
1025      * Sets the base file to use when resolving relative URI.
1026      *
1027      * @param baseFile The new base FileObject.
1028      */
1029     public void setBaseFile(final FileObject baseFile) {
1030         this.baseFile = baseFile;
1031     }
1032 
1033     /**
1034      * Sets the cache strategy to use when dealing with file object data.
1035      * <p>
1036      * Can only be set before the FileSystemManager is initialized.
1037      * </p>
1038      * <p>
1039      * The default is {@link CacheStrategy#ON_RESOLVE}.
1040      * </p>
1041      *
1042      * @param fileCacheStrategy The CacheStrategy to use.
1043      * @throws FileSystemException if this is not possible. e.g. it is already set.
1044      */
1045     public void setCacheStrategy(final CacheStrategy fileCacheStrategy) throws FileSystemException {
1046         if (init) {
1047             throw new FileSystemException("vfs.impl/already-inited.error");
1048         }
1049 
1050         this.fileCacheStrategy = fileCacheStrategy;
1051     }
1052 
1053     /**
1054      * Sets the default provider. This is the provider that will handle URI with unknown schemes. The manager takes care
1055      * of all lifecycle management.
1056      *
1057      * @param provider The FileProvider.
1058      * @throws FileSystemException if an error occurs setting the provider.
1059      */
1060     public void setDefaultProvider(final FileProvider provider) throws FileSystemException {
1061         setupComponent(provider);
1062         defaultProvider = provider;
1063     }
1064 
1065     /**
1066      * Sets the fileContentInfoFactory used to determine the infos of a file content.
1067      * <p>
1068      * Can only be set before the FileSystemManager is initialized.
1069      * </p>
1070      *
1071      * @param fileContentInfoFactory The FileContentInfoFactory.
1072      * @throws FileSystemException if an error occurs setting the FileContentInfoFactory.
1073      */
1074     public void setFileContentInfoFactory(final FileContentInfoFactory fileContentInfoFactory)
1075             throws FileSystemException {
1076         if (init) {
1077             throw new FileSystemException("vfs.impl/already-inited.error");
1078         }
1079 
1080         this.fileContentInfoFactory = fileContentInfoFactory;
1081     }
1082 
1083     /**
1084      * Sets a fileObject decorator to be used for ALL returned file objects.
1085      * <p>
1086      * Can only be set before the FileSystemManager is initialized.
1087      * </p>
1088      *
1089      * @param fileObjectDecorator must be inherited from {@link DecoratedFileObject} a has to provide a constructor with
1090      *            a single {@link FileObject} as argument
1091      * @throws FileSystemException if an error occurs setting the decorator.
1092      */
1093     public void setFileObjectDecorator(final Class<?> fileObjectDecorator) throws FileSystemException {
1094         if (init) {
1095             throw new FileSystemException("vfs.impl/already-inited.error");
1096         }
1097         if (!DecoratedFileObject.class.isAssignableFrom(fileObjectDecorator)) {
1098             throw new FileSystemException("vfs.impl/invalid-decorator.error", fileObjectDecorator.getName());
1099         }
1100 
1101         try {
1102             fileObjectDecoratorConst = fileObjectDecorator.getConstructor(FileObject.class);
1103         } catch (final NoSuchMethodException e) {
1104             throw new FileSystemException("vfs.impl/invalid-decorator.error", fileObjectDecorator.getName(), e);
1105         }
1106 
1107         this.fileObjectDecorator = fileObjectDecorator;
1108     }
1109 
1110     /**
1111      * Sets the filesCache implementation used to cache files.
1112      * <p>
1113      * Can only be set before the FileSystemManager is initialized.
1114      * </p>
1115      * <p>
1116      * The manager takes care of the lifecycle. If none is set, a default is picked in {@link #init()}.
1117      * </p>
1118      *
1119      * @param filesCache The FilesCache.
1120      * @throws FileSystemException if an error occurs setting the cache.
1121      */
1122     public void setFilesCache(final FilesCache filesCache) throws FileSystemException {
1123         if (init) {
1124             throw new FileSystemException("vfs.impl/already-inited.error");
1125         }
1126         this.filesCache = filesCache;
1127     }
1128 
1129     /**
1130      * Sets the logger to use.
1131      * <p>
1132      * This overwrites the default logger for this manager and is not reset in {@link #close()}.
1133      * </p>
1134      *
1135      * @param log The Logger to use.
1136      */
1137     @Override
1138     public void setLogger(final Log log) {
1139         this.log = log;
1140     }
1141 
1142     /**
1143      * Sets the file replicator to use.
1144      * <p>
1145      * The manager takes care of all lifecycle management.
1146      * </p>
1147      *
1148      * @param replicator The FileReplicator.
1149      * @throws FileSystemException if an error occurs setting the replicator.
1150      */
1151     public void setReplicator(final FileReplicator replicator) throws FileSystemException {
1152         setupComponent(replicator);
1153         fileReplicator = replicator;
1154     }
1155 
1156     /**
1157      * Sets the temporary file store to use.
1158      * <p>
1159      * The manager takes care of all lifecycle management.
1160      * </p>
1161      *
1162      * @param tempFileStore The temporary FileStore.
1163      * @throws FileSystemException if an error occurs adding the file store.
1164      */
1165     public void setTemporaryFileStore(final TemporaryFileStore tempFileStore) throws FileSystemException {
1166         setupComponent(tempFileStore);
1167         this.tempFileStore = tempFileStore;
1168     }
1169 
1170     /**
1171      * Initializes a component, if it has not already been initialized.
1172      *
1173      * @param component The component to set up.
1174      * @throws FileSystemException if an error occurs.
1175      */
1176     private void setupComponent(final Object component) throws FileSystemException {
1177         if (!components.contains(component)) {
1178             if (component instanceof VfsComponent) {
1179                 final VfsComponent vfsComponent = (VfsComponent) component;
1180                 vfsComponent.setLogger(getLogger());
1181                 vfsComponent.setContext(context);
1182                 vfsComponent.init();
1183             }
1184             components.add(component);
1185         }
1186     }
1187 
1188     /**
1189      * Converts a local file into a {@link FileObject}.
1190      *
1191      * @param file The input File.
1192      * @return the created FileObject
1193      * @throws FileSystemException if an error occurs creating the file.
1194      */
1195     @Override
1196     public FileObject toFileObject(final File file) throws FileSystemException {
1197         return getLocalFileProvider().findLocalFile(file);
1198     }
1199 
1200 }