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