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    /**
068     * Tests for cases sensitivity in a null-safe manner.
069     * 
070     * @param caseSensitivity an IOCase.
071     * @return true if the input is non-null and {@link #isCaseSensitive()}.
072     * @since 2.10.0
073     */
074    public static boolean isCaseSensitive(final IOCase caseSensitivity) {
075        return caseSensitivity != null && !caseSensitivity.isCaseSensitive();
076    }
077
078    /** Serialization version. */
079    private static final long serialVersionUID = -6343169151696340687L;
080
081    /** The enumeration name. */
082    private final String name;
083
084    /** The sensitivity flag. */
085    private final transient boolean sensitive;
086
087    /**
088     * Factory method to create an IOCase from a name.
089     *
090     * @param name  the name to find
091     * @return the IOCase object
092     * @throws IllegalArgumentException if the name is invalid
093     */
094    public static IOCase forName(final String name) {
095        for (final IOCase ioCase : IOCase.values()) {
096            if (ioCase.getName().equals(name)) {
097                return ioCase;
098            }
099        }
100        throw new IllegalArgumentException("Invalid IOCase name: " + name);
101    }
102
103    /**
104     * Constructs a new instance.
105     *
106     * @param name  the name
107     * @param sensitive  the sensitivity
108     */
109    IOCase(final String name, final boolean sensitive) {
110        this.name = name;
111        this.sensitive = sensitive;
112    }
113
114    /**
115     * Replaces the enumeration from the stream with a real one.
116     * This ensures that the correct flag is set for SYSTEM.
117     *
118     * @return the resolved object
119     */
120    private Object readResolve() {
121        return forName(name);
122    }
123
124    /**
125     * Gets the name of the constant.
126     *
127     * @return the name of the constant
128     */
129    public String getName() {
130        return name;
131    }
132
133    /**
134     * Does the object represent case sensitive comparison.
135     *
136     * @return true if case sensitive
137     */
138    public boolean isCaseSensitive() {
139        return sensitive;
140    }
141
142    /**
143     * Compares two strings using the case-sensitivity rule.
144     * <p>
145     * This method mimics {@link String#compareTo} but takes case-sensitivity
146     * into account.
147     * </p>
148     *
149     * @param str1  the first string to compare, not null
150     * @param str2  the second string to compare, not null
151     * @return true if equal using the case rules
152     * @throws NullPointerException if either string is null
153     */
154    public int checkCompareTo(final String str1, final String str2) {
155        Objects.requireNonNull(str1, "str1");
156        Objects.requireNonNull(str2, "str2");
157        return sensitive ? str1.compareTo(str2) : str1.compareToIgnoreCase(str2);
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 starts with another using the case-sensitivity rule.
180     * <p>
181     * This method mimics {@link String#startsWith(String)} but takes case-sensitivity
182     * into account.
183     * </p>
184     *
185     * @param str  the string to check
186     * @param start  the start to compare against
187     * @return true if equal using the case rules, false if either input is null
188     */
189    public boolean checkStartsWith(final String str, final String start) {
190        return str != null && start != null && str.regionMatches(!sensitive, 0, start, 0, start.length());
191    }
192
193    /**
194     * Checks if one string ends with another using the case-sensitivity rule.
195     * <p>
196     * This method mimics {@link String#endsWith} but takes case-sensitivity
197     * into account.
198     * </p>
199     *
200     * @param str  the string to check
201     * @param end  the end to compare against
202     * @return true if equal using the case rules, false if either input is null
203     */
204    public boolean checkEndsWith(final String str, final String end) {
205        if (str == null || end == null) {
206            return false;
207        }
208        final int endLen = end.length();
209        return str.regionMatches(!sensitive, str.length() - endLen, end, 0, endLen);
210    }
211
212    /**
213     * Checks if one string contains another starting at a specific index using the
214     * case-sensitivity rule.
215     * <p>
216     * This method mimics parts of {@link String#indexOf(String, int)}
217     * but takes case-sensitivity into account.
218     * </p>
219     *
220     * @param str  the string to check, not null
221     * @param strStartIndex  the index to start at in str
222     * @param search  the start to search for, not null
223     * @return the first index of the search String,
224     *  -1 if no match or {@code null} string input
225     * @throws NullPointerException if either string is null
226     * @since 2.0
227     */
228    public int checkIndexOf(final String str, final int strStartIndex, final String search) {
229        final int endIndex = str.length() - search.length();
230        if (endIndex >= strStartIndex) {
231            for (int i = strStartIndex; i <= endIndex; i++) {
232                if (checkRegionMatches(str, i, search)) {
233                    return i;
234                }
235            }
236        }
237        return -1;
238    }
239
240    /**
241     * Checks if one string contains another at a specific index using the case-sensitivity rule.
242     * <p>
243     * This method mimics parts of {@link String#regionMatches(boolean, int, String, int, int)}
244     * but takes case-sensitivity into account.
245     * </p>
246     *
247     * @param str  the string to check, not null
248     * @param strStartIndex  the index to start at in str
249     * @param search  the start to search for, not null
250     * @return true if equal using the case rules
251     * @throws NullPointerException if either string is null
252     */
253    public boolean checkRegionMatches(final String str, final int strStartIndex, final String search) {
254        return str.regionMatches(!sensitive, strStartIndex, search, 0, search.length());
255    }
256
257    /**
258     * Gets a string describing the sensitivity.
259     *
260     * @return a string describing the sensitivity
261     */
262    @Override
263    public String toString() {
264        return name;
265    }
266
267}