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     * 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        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, not null
168     * @param str2  the second string to compare, not null
169     * @return true if equal using the case rules
170     * @throws NullPointerException if either string is null
171     */
172    public boolean checkEquals(final String str1, final String str2) {
173        Objects.requireNonNull(str1, "str1");
174        Objects.requireNonNull(str2, "str2");
175        return sensitive ? str1.equals(str2) : str1.equalsIgnoreCase(str2);
176    }
177
178    /**
179     * Checks if one string contains another starting at a specific index using the
180     * case-sensitivity rule.
181     * <p>
182     * This method mimics parts of {@link String#indexOf(String, int)}
183     * but takes case-sensitivity into account.
184     * </p>
185     *
186     * @param str  the string to check, not null
187     * @param strStartIndex  the index to start at in str
188     * @param search  the start to search for, not null
189     * @return the first index of the search String,
190     *  -1 if no match or {@code null} string input
191     * @throws NullPointerException if either string is null
192     * @since 2.0
193     */
194    public int checkIndexOf(final String str, final int strStartIndex, final String search) {
195        final int endIndex = str.length() - search.length();
196        if (endIndex >= strStartIndex) {
197            for (int i = strStartIndex; i <= endIndex; i++) {
198                if (checkRegionMatches(str, i, search)) {
199                    return i;
200                }
201            }
202        }
203        return -1;
204    }
205
206    /**
207     * Checks if one string contains another at a specific index using the case-sensitivity rule.
208     * <p>
209     * This method mimics parts of {@link String#regionMatches(boolean, int, String, int, int)}
210     * but takes case-sensitivity into account.
211     * </p>
212     *
213     * @param str  the string to check, not null
214     * @param strStartIndex  the index to start at in str
215     * @param search  the start to search for, not null
216     * @return true if equal using the case rules
217     * @throws NullPointerException if either string is null
218     */
219    public boolean checkRegionMatches(final String str, final int strStartIndex, final String search) {
220        return str.regionMatches(!sensitive, strStartIndex, search, 0, search.length());
221    }
222
223    /**
224     * Checks if one string starts with another using the case-sensitivity rule.
225     * <p>
226     * This method mimics {@link String#startsWith(String)} but takes case-sensitivity
227     * into account.
228     * </p>
229     *
230     * @param str  the string to check
231     * @param start  the start to compare against
232     * @return true if equal using the case rules, false if either input is null
233     */
234    public boolean checkStartsWith(final String str, final String start) {
235        return str != null && start != null && str.regionMatches(!sensitive, 0, start, 0, start.length());
236    }
237
238    /**
239     * Gets the name of the constant.
240     *
241     * @return the name of the constant
242     */
243    public String getName() {
244        return name;
245    }
246
247    /**
248     * Does the object represent case-sensitive comparison.
249     *
250     * @return true if case-sensitive
251     */
252    public boolean isCaseSensitive() {
253        return sensitive;
254    }
255
256    /**
257     * Replaces the enumeration from the stream with a real one.
258     * This ensures that the correct flag is set for SYSTEM.
259     *
260     * @return the resolved object
261     */
262    private Object readResolve() {
263        return forName(name);
264    }
265
266    /**
267     * Gets a string describing the sensitivity.
268     *
269     * @return a string describing the sensitivity
270     */
271    @Override
272    public String toString() {
273        return name;
274    }
275
276}