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