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