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