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