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