001/*
002 * Copyright 2023 the original author or authors.
003 * <p>
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 * <p>
008 * https://www.apache.org/licenses/LICENSE-2.0
009 * <p>
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package de.cuioss.tools.io;
017
018/**
019 * Enumeration of IO case sensitivity.
020 * <p>
021 * Different filing systems have different rules for case-sensitivity. Windows
022 * is case-insensitive, Unix is case-sensitive.
023 * <p>
024 * This class captures that difference, providing an enumeration to control how
025 * filename comparisons should be performed. It also provides methods that use
026 * the enumeration to perform comparisons.
027 * <p>
028 * Wherever possible, you should use the <code>check</code> methods in this
029 * class to compare filenames.
030 *
031 * @author commons.io:org.apache.commons.io.IOCase
032 */
033public enum IOCase {
034
035    /**
036     * The constant for case-sensitive regardless of operating system.
037     */
038    SENSITIVE("Sensitive", true),
039
040    /**
041     * The constant for case-insensitive regardless of operating system.
042     */
043    INSENSITIVE("Insensitive", false),
044
045    /**
046     * The constant for case sensitivity determined by the current operating system.
047     * Windows is case-insensitive when comparing filenames, Unix is case-sensitive.
048     * <p>
049     * <strong>Note:</strong> This only caters for Windows and Unix. Other operating
050     * systems (e.g. OSX and OpenVMS) are treated as case-sensitive if they use the
051     * Unix file separator and case-insensitive if they use the Windows file
052     * separator (see {@link java.io.File#separatorChar}).
053     * <p>
054     * If you serialize this constant on Windows, and deserialize on Unix, or vice
055     * versa, then the value of the case-sensitivity flag will change.
056     */
057    SYSTEM("System", !FilenameUtils.isSystemWindows());
058
059    /**
060     * The enumeration name.
061     */
062    private final String name;
063
064    /**
065     * The sensitivity flag.
066     */
067    private final transient boolean sensitive;
068
069    // -----------------------------------------------------------------------
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 : IOCase.values()) {
080            if (ioCase.getName().equals(name)) {
081                return ioCase;
082            }
083        }
084        throw new IllegalArgumentException("Invalid IOCase name: " + name);
085    }
086
087    // -----------------------------------------------------------------------
088
089    /**
090     * Private constructor.
091     *
092     * @param name      the name
093     * @param sensitive the sensitivity
094     */
095    IOCase(final String name, final boolean sensitive) {
096        this.name = name;
097        this.sensitive = sensitive;
098    }
099
100    /**
101     * Replaces the enumeration from the stream with a real one. This ensures that
102     * the correct flag is set for SYSTEM.
103     *
104     * @return the resolved object
105     */
106    Object readResolve() {
107        return forName(name);
108    }
109
110    // -----------------------------------------------------------------------
111
112    /**
113     * Gets the name of the constant.
114     *
115     * @return the name of the constant
116     */
117    public String getName() {
118        return name;
119    }
120
121    /**
122     * Does the object represent case-sensitive comparison.
123     *
124     * @return true if case-sensitive
125     */
126    public boolean isCaseSensitive() {
127        return sensitive;
128    }
129
130    // -----------------------------------------------------------------------
131
132    /**
133     * Compares two strings using the case-sensitivity rule.
134     * <p>
135     * This method mimics {@link String#compareTo} but takes case-sensitivity into
136     * account.
137     *
138     * @param str1 the first string to compare, not null
139     * @param str2 the second string to compare, not null
140     * @return the value 0 if the argument string is equal to this string; a value
141     *         less than 0 if this string is lexicographically less than the string
142     *         argument; and a value greater than 0 if this string is
143     *         lexicographically greater than the string argument.
144     * @throws NullPointerException if either string is null
145     */
146    public int checkCompareTo(final String str1, final String str2) {
147        if (str1 == null || str2 == null) {
148            throw new NullPointerException("The strings must not be null");
149        }
150        return sensitive ? str1.compareTo(str2) : str1.compareToIgnoreCase(str2);
151    }
152
153    /**
154     * Compares two strings using the case-sensitivity rule.
155     * <p>
156     * This method mimics {@link String#equals} but takes case-sensitivity into
157     * account.
158     *
159     * @param str1 the first string to compare, not null
160     * @param str2 the second string to compare, not null
161     * @return true if equal using the case rules
162     * @throws NullPointerException if either string is null
163     */
164    public boolean checkEquals(final String str1, final String str2) {
165        if (str1 == null || str2 == null) {
166            throw new NullPointerException("The strings must not be null");
167        }
168        return sensitive ? str1.equals(str2) : str1.equalsIgnoreCase(str2);
169    }
170
171    /**
172     * Checks if one string starts with another using the case-sensitivity rule.
173     * <p>
174     * This method mimics {@link String#startsWith(String)} but takes
175     * case-sensitivity into account.
176     *
177     * @param str   the string to check, not null
178     * @param start the start to compare against, not null
179     * @return true if equal using the case rules
180     * @throws NullPointerException if either string is null
181     */
182    public boolean checkStartsWith(final String str, final String start) {
183        return str.regionMatches(!sensitive, 0, start, 0, start.length());
184    }
185
186    /**
187     * Checks if one string ends with another using the case-sensitivity rule.
188     * <p>
189     * This method mimics {@link String#endsWith} but takes case-sensitivity into
190     * account.
191     *
192     * @param str the string to check, not null
193     * @param end the end to compare against, not null
194     * @return true if equal using the case rules
195     * @throws NullPointerException if either string is null
196     */
197    public boolean checkEndsWith(final String str, final String end) {
198        final var endLen = end.length();
199        return str.regionMatches(!sensitive, str.length() - endLen, end, 0, endLen);
200    }
201
202    /**
203     * Checks if one string contains another starting at a specific index using the
204     * case-sensitivity rule.
205     * <p>
206     * This method mimics parts of {@link String#indexOf(String, int)} but takes
207     * case-sensitivity into account.
208     *
209     * @param str           the string to check, not null
210     * @param strStartIndex the index to start at in str
211     * @param search        the start to search for, not null
212     * @return the first index of the search String, -1 if no match or {@code null}
213     *         string input
214     * @throws NullPointerException if either string is null
215     */
216    public int checkIndexOf(final String str, final int strStartIndex, final String search) {
217        final var endIndex = str.length() - search.length();
218        if (endIndex >= strStartIndex) {
219            for (var i = strStartIndex; i <= endIndex; i++) {
220                if (checkRegionMatches(str, i, search)) {
221                    return i;
222                }
223            }
224        }
225        return -1;
226    }
227
228    /**
229     * Checks if one string contains another at a specific index using the
230     * case-sensitivity rule.
231     * <p>
232     * This method mimics parts of
233     * {@link String#regionMatches(boolean, int, String, int, int)} but takes
234     * case-sensitivity into account.
235     *
236     * @param str           the string to check, not null
237     * @param strStartIndex the index to start at in str
238     * @param search        the start to search for, not null
239     * @return true if equal using the case rules
240     * @throws NullPointerException if either string is null
241     */
242    public boolean checkRegionMatches(final String str, final int strStartIndex, final String search) {
243        return str.regionMatches(!sensitive, strStartIndex, search, 0, search.length());
244    }
245
246    // -----------------------------------------------------------------------
247
248    /**
249     * Gets a string describing the sensitivity.
250     *
251     * @return a string describing the sensitivity
252     */
253    @Override
254    public String toString() {
255        return name;
256    }
257
258}