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.Arrays;
028import java.util.Collection;
029import java.util.HashMap;
030import java.util.List;
031import java.util.Map;
032
033import org.apache.commons.lang3.ArrayUtils;
034import org.apache.commons.logging.Log;
035import org.apache.commons.logging.LogFactory;
036import org.apache.commons.vfs2.CacheStrategy;
037import org.apache.commons.vfs2.Capability;
038import org.apache.commons.vfs2.FileContentInfoFactory;
039import org.apache.commons.vfs2.FileName;
040import org.apache.commons.vfs2.FileObject;
041import org.apache.commons.vfs2.FileSystem;
042import org.apache.commons.vfs2.FileSystemConfigBuilder;
043import org.apache.commons.vfs2.FileSystemException;
044import org.apache.commons.vfs2.FileSystemManager;
045import org.apache.commons.vfs2.FileSystemOptions;
046import org.apache.commons.vfs2.FileType;
047import org.apache.commons.vfs2.FilesCache;
048import org.apache.commons.vfs2.NameScope;
049import org.apache.commons.vfs2.VFS;
050import org.apache.commons.vfs2.cache.SoftRefFilesCache;
051import org.apache.commons.vfs2.operations.FileOperationProvider;
052import org.apache.commons.vfs2.provider.AbstractFileName;
053import org.apache.commons.vfs2.provider.AbstractFileProvider;
054import org.apache.commons.vfs2.provider.DefaultURLStreamHandler;
055import org.apache.commons.vfs2.provider.FileProvider;
056import org.apache.commons.vfs2.provider.FileReplicator;
057import org.apache.commons.vfs2.provider.LocalFileProvider;
058import org.apache.commons.vfs2.provider.TemporaryFileStore;
059import org.apache.commons.vfs2.provider.UriParser;
060import org.apache.commons.vfs2.provider.VfsComponent;
061
062/**
063 * The default file system manager implementation.
064 */
065public class DefaultFileSystemManager implements FileSystemManager {
066
067    /**
068     * This is an internal class because it needs access to the private member providers.
069     */
070    final class VfsStreamHandlerFactory implements URLStreamHandlerFactory {
071        @Override
072        public URLStreamHandler createURLStreamHandler(final String protocol) {
073            final FileProvider provider = providers.get(protocol);
074            if (provider != null) {
075                return new DefaultURLStreamHandler(context);
076            }
077
078            // Route all other calls to the default URLStreamHandlerFactory
079            return new URLStreamHandlerProxy();
080        }
081    }
082
083    /**
084     * Mapping from URI scheme to FileProvider.
085     */
086    private final Map<String, FileProvider> providers = new HashMap<>();
087
088    /**
089     * List of the schemes of virtual file systems added.
090     */
091    private final List<String> virtualFileSystemSchemes = new ArrayList<>();
092
093    /**
094     * All components used by this manager.
095     */
096    private final ArrayList<Object> components = new ArrayList<>();
097
098    /**
099     * 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}