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;
018
019import java.util.Arrays;
020import java.util.Iterator;
021import java.util.Map;
022import java.util.SortedMap;
023import java.util.TreeMap;
024
025/**
026 * Configures file systems individually with these options.
027 * <p>
028 * To configure a file system, you set properties on a {@link FileSystemOptions} object. Most file systems provide a
029 * {@link FileSystemConfigBuilder} with specific options for that file system.
030 * <p>
031 * To use the options, pass them to {@link FileSystemManager#resolveFile(String,FileSystemOptions)}. From there, the
032 * options apply to all files that are resolved relative to that file.
033 *
034 * @see org.apache.commons.vfs2.impl.DefaultFileSystemConfigBuilder
035 * @see org.apache.commons.vfs2.provider.ftp.FtpFileSystemConfigBuilder
036 * @see org.apache.commons.vfs2.provider.ftps.FtpsFileSystemConfigBuilder
037 * @see org.apache.commons.vfs2.provider.hdfs.HdfsFileSystemConfigBuilder
038 * @see org.apache.commons.vfs2.provider.http.HttpFileSystemConfigBuilder
039 * @see org.apache.commons.vfs2.provider.webdav.WebdavFileSystemConfigBuilder
040 * @see org.apache.commons.vfs2.provider.ram.RamFileSystemConfigBuilder
041 * @see org.apache.commons.vfs2.provider.res.ResourceFileSystemConfigBuilder
042 * @see org.apache.commons.vfs2.provider.sftp.SftpFileSystemConfigBuilder
043 *
044 */
045public final class FileSystemOptions implements Cloneable {
046    /** The options */
047    private final Map<FileSystemOptionKey, Object> options;
048
049    /**
050     * Creates a new instance.
051     */
052    public FileSystemOptions() {
053        this(new TreeMap<FileSystemOptionKey, Object>());
054    }
055
056    protected FileSystemOptions(final Map<FileSystemOptionKey, Object> options) {
057        this.options = options;
058    }
059
060    /**
061     * Keys in the options Map.
062     */
063    private static final class FileSystemOptionKey implements Comparable<FileSystemOptionKey> {
064        /** Constant used to create hashcode */
065        private static final int HASH = 29;
066
067        /** The FileSystem class */
068        private final Class<? extends FileSystem> fileSystemClass;
069
070        /** The option name */
071        private final String name;
072
073        // TODO: the parameter name suggests that the class should only be a
074        // a FileSystem, however some of the tests pass in DefaultFileSystemConfigBuilder
075        private FileSystemOptionKey(final Class<? extends FileSystem> fileSystemClass, final String name) {
076            this.fileSystemClass = fileSystemClass;
077            this.name = name;
078        }
079
080        @Override
081        public int compareTo(final FileSystemOptionKey o) {
082            final int ret = fileSystemClass.getName().compareTo(o.fileSystemClass.getName());
083            if (ret != 0) {
084                return ret;
085            }
086            return name.compareTo(o.name);
087        }
088
089        @Override
090        public boolean equals(final Object o) {
091            if (this == o) {
092                return true;
093            }
094            if (o == null || getClass() != o.getClass()) {
095                return false;
096            }
097
098            final FileSystemOptionKey that = (FileSystemOptionKey) o;
099
100            if (!fileSystemClass.equals(that.fileSystemClass)) {
101                return false;
102            }
103            if (!name.equals(that.name)) {
104                return false;
105            }
106
107            return true;
108        }
109
110        @Override
111        public int hashCode() {
112            int result;
113            result = fileSystemClass.hashCode();
114            result = HASH * result + name.hashCode();
115            return result;
116        }
117
118        @Override
119        public String toString() {
120            return fileSystemClass.getName() + "." + name;
121        }
122    }
123
124    void setOption(final Class<? extends FileSystem> fileSystemClass, final String name, final Object value) {
125        options.put(new FileSystemOptionKey(fileSystemClass, name), value);
126    }
127
128    Object getOption(final Class<? extends FileSystem> fileSystemClass, final String name) {
129        final FileSystemOptionKey key = new FileSystemOptionKey(fileSystemClass, name);
130        return options.get(key);
131    }
132
133    boolean hasOption(final Class<? extends FileSystem> fileSystemClass, final String name) {
134        final FileSystemOptionKey key = new FileSystemOptionKey(fileSystemClass, name);
135        return options.containsKey(key);
136    }
137
138    public int compareTo(final FileSystemOptions other) {
139        if (this == other) {
140            // the same instance
141            return 0;
142        }
143
144        final int propsSz = options == null ? 0 : options.size();
145        final int propsFkSz = other.options == null ? 0 : other.options.size();
146        if (propsSz < propsFkSz) {
147            return -1;
148        }
149        if (propsSz > propsFkSz) {
150            return 1;
151        }
152        if (propsSz == 0) {
153            // props empty
154            return 0;
155        }
156
157        // ensure proper sequence of options
158        final SortedMap<FileSystemOptionKey, Object> myOptions = options instanceof SortedMap
159                ? (SortedMap<FileSystemOptionKey, Object>) options
160                : new TreeMap<>(options);
161        final SortedMap<FileSystemOptionKey, Object> theirOptions = other.options instanceof SortedMap
162                ? (SortedMap<FileSystemOptionKey, Object>) other.options
163                : new TreeMap<>(other.options);
164        final Iterator<FileSystemOptionKey> optKeysIter = myOptions.keySet().iterator();
165        final Iterator<FileSystemOptionKey> otherKeysIter = theirOptions.keySet().iterator();
166        while (optKeysIter.hasNext()) {
167            final int comp = optKeysIter.next().compareTo(otherKeysIter.next());
168            if (comp != 0) {
169                return comp;
170            }
171        }
172
173        final Object[] array = new Object[propsSz];
174        final int hash = Arrays.deepHashCode(myOptions.values().toArray(array));
175        final int hashFk = Arrays.deepHashCode(theirOptions.values().toArray(array));
176        if (hash < hashFk) {
177            return -1;
178        }
179        if (hash > hashFk) {
180            return 1;
181        }
182
183        // TODO: compare Entry by Entry ??
184        return 0;
185    }
186
187    @Override
188    public int hashCode() {
189        final int prime = 31;
190        int result = 1;
191        if (options == null) {
192            result = prime * result;
193        } else {
194            final SortedMap<FileSystemOptionKey, Object> myOptions = options instanceof SortedMap
195                    ? (SortedMap<FileSystemOptionKey, Object>) options
196                    : new TreeMap<>(options);
197            result = prime * result + myOptions.keySet().hashCode();
198            result = prime * result + Arrays.deepHashCode(myOptions.values().toArray(new Object[options.size()]));
199        }
200        return result;
201    }
202
203    @Override
204    public boolean equals(final Object obj) {
205        if (this == obj) {
206            return true;
207        }
208        if (obj == null) {
209            return false;
210        }
211        if (getClass() != obj.getClass()) {
212            return false;
213        }
214        final FileSystemOptions other = (FileSystemOptions) obj;
215        return compareTo(other) == 0;
216    }
217
218    /**
219     * {@inheritDoc}
220     *
221     * @since 2.0
222     */
223    @Override
224    public Object clone() {
225        return new FileSystemOptions(new TreeMap<>(options));
226    }
227
228    @Override
229    public String toString() {
230        return options.toString();
231    }
232}