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.filter;
018
019import java.io.File;
020import java.util.Objects;
021
022/**
023 * Enumeration of IO case sensitivity.
024 * <p>
025 * Different filing systems have different rules for case-sensitivity. Windows
026 * is case-insensitive, Unix is case-sensitive.
027 * </p>
028 * <p>
029 * This class captures that difference, providing an enumeration to control how
030 * file name comparisons should be performed. It also provides methods that use
031 * the enumeration to perform comparisons.
032 * </p>
033 * <p>
034 * Wherever possible, you should use the {@code check} methods in this
035 * class to compare file names.
036 * </p>
037 *
038 * @author This code was originally ported from Apache Commons IO File Filter
039 * @see "https://commons.apache.org/proper/commons-io/"
040 * @since 2.4
041 */
042public enum IOCase {
043
044    /**
045     * The constant for case-sensitive regardless of operating system.
046     */
047    SENSITIVE("Sensitive", true),
048
049    /**
050     * The constant for case-insensitive regardless of operating system.
051     */
052    INSENSITIVE("Insensitive", false),
053
054    /**
055     * The constant for case sensitivity determined by the current operating system.
056     * Windows is case-insensitive when comparing file names, Unix is case-sensitive.
057     * <p>
058     * <strong>Note:</strong> This only caters for Windows and Unix. Other operating
059     * systems (e.g. OSX and OpenVMS) are treated as case-sensitive if they use the
060     * Unix file separator and case-insensitive if they use the Windows file
061     * separator (see {@link File#separatorChar}).
062     * <p>
063     * If you serialize this constant on Windows, and deserialize on Unix, or vice
064     * versa, then the value of the case-sensitivity flag will change.
065     */
066    SYSTEM("System", !(File.separatorChar == '\\'));
067
068    /** Serialization version. */
069    private static final long serialVersionUID = -6343169151696340687L;
070
071    /**
072     * Factory method to create an IOCase from a name.
073     *
074     * @param name the name to find
075     * @return the IOCase object
076     * @throws IllegalArgumentException if the name is invalid
077     */
078    public static IOCase forName(final String name) {
079        for (final IOCase ioCase : values()) {
080            if (ioCase.getName().equals(name)) {
081                return ioCase;
082            }
083        }
084        throw new IllegalArgumentException("Invalid IOCase name: " + name);
085    }
086
087    /** The enumeration name. */
088    private final String name;
089
090    /** The sensitivity flag. */
091    private final transient boolean sensitive;
092
093    /**
094     * Constructs a new instance.
095     *
096     * @param name      the name
097     * @param sensitive the sensitivity
098     */
099    IOCase(final String name, final boolean sensitive) {
100        this.name = name;
101        this.sensitive = sensitive;
102    }
103
104    /**
105     * Compares two strings using the case-sensitivity rule.
106     * <p>
107     * This method mimics {@link String#compareTo} but takes case-sensitivity into
108     * account.
109     *
110     * @param str1 the first string to compare, not null
111     * @param str2 the second string to compare, not null
112     * @return true if equal using the case rules
113     * @throws NullPointerException if either string is null
114     */
115    public int checkCompareTo(final String str1, final String str2) {
116        Objects.requireNonNull(str1, "str1");
117        Objects.requireNonNull(str2, "str2");
118        return sensitive ? str1.compareTo(str2) : str1.compareToIgnoreCase(str2);
119    }
120
121    /**
122     * Checks if one string ends with another using the case-sensitivity rule.
123     * <p>
124     * This method mimics {@link String#endsWith} but takes case-sensitivity into
125     * account.
126     *
127     * @param str the string to check, not null
128     * @param end the end to compare against, not null
129     * @return true if equal using the case rules
130     * @throws NullPointerException if either string is null
131     */
132    public boolean checkEndsWith(final String str, final String end) {
133        final int endLen = end.length();
134        return str.regionMatches(!sensitive, str.length() - endLen, end, 0, endLen);
135    }
136
137    /**
138     * Compares two strings using the case-sensitivity rule.
139     * <p>
140     * This method mimics {@link String#equals} but takes case-sensitivity into
141     * account.
142     *
143     * @param str1 the first string to compare, not null
144     * @param str2 the second string to compare, not null
145     * @return true if equal using the case rules
146     * @throws NullPointerException if either string is null
147     */
148    public boolean checkEquals(final String str1, final String str2) {
149        if (str1 == null || str2 == null) {
150            throw new NullPointerException("The strings must not be null");
151        }
152        return sensitive ? str1.equals(str2) : str1.equalsIgnoreCase(str2);
153    }
154
155    /**
156     * Checks if one string contains another starting at a specific index using the
157     * case-sensitivity rule.
158     * <p>
159     * This method mimics parts of {@link String#indexOf(String, int)} but takes
160     * case-sensitivity into account.
161     *
162     * @param str           the string to check, not null
163     * @param strStartIndex the index to start at in str
164     * @param search        the start to search for, not null
165     * @return the first index of the search String, -1 if no match or {@code null}
166     *         string input
167     * @throws NullPointerException if either string is null
168     * @since 2.0
169     */
170    public int checkIndexOf(final String str, final int strStartIndex, final String search) {
171        final int endIndex = str.length() - search.length();
172        if (endIndex >= strStartIndex) {
173            for (int i = strStartIndex; i <= endIndex; i++) {
174                if (checkRegionMatches(str, i, search)) {
175                    return i;
176                }
177            }
178        }
179        return -1;
180    }
181
182    /**
183     * Checks if one string contains another at a specific index using the
184     * case-sensitivity rule.
185     * <p>
186     * This method mimics parts of
187     * {@link String#regionMatches(boolean, int, String, int, int)} but takes
188     * case-sensitivity into account.
189     *
190     * @param str           the string to check, not null
191     * @param strStartIndex the index to start at in str
192     * @param search        the start to search for, not null
193     * @return true if equal using the case rules
194     * @throws NullPointerException if either string is null
195     */
196    public boolean checkRegionMatches(final String str, final int strStartIndex, final String search) {
197        return str.regionMatches(!sensitive, strStartIndex, search, 0, search.length());
198    }
199
200    /**
201     * Checks if one string starts with another using the case-sensitivity rule.
202     * <p>
203     * This method mimics {@link String#startsWith(String)} but takes
204     * case-sensitivity into account.
205     *
206     * @param str   the string to check, not null
207     * @param start the start to compare against, not null
208     * @return true if equal using the case rules
209     * @throws NullPointerException if either string is null
210     */
211    public boolean checkStartsWith(final String str, final String start) {
212        return str.regionMatches(!sensitive, 0, start, 0, start.length());
213    }
214
215    /**
216     * Gets the name of the constant.
217     *
218     * @return the name of the constant
219     */
220    public String getName() {
221        return name;
222    }
223
224    /**
225     * Does the object represent case-sensitive comparison.
226     *
227     * @return true if case-sensitive
228     */
229    public boolean isCaseSensitive() {
230        return sensitive;
231    }
232
233    /**
234     * Replaces the enumeration from the stream with a real one. This ensures that
235     * the correct flag is set for SYSTEM.
236     *
237     * @return the resolved object
238     */
239    private Object readResolve() {
240        return forName(name);
241    }
242
243    /**
244     * Gets a string describing the sensitivity.
245     *
246     * @return a string describing the sensitivity
247     */
248    @Override
249    public String toString() {
250        return name;
251    }
252
253}