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