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.Serializable;
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 filename 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 filenames.
033 *
034 * @version $Id: IOCase.java 1307459 2012-03-30 15:11:44Z ggregory $
035 * @since 1.3
036 */
037public final class IOCase implements Serializable {
038
039    /**
040     * The constant for case sensitive regardless of operating system.
041     */
042    public static final IOCase SENSITIVE = new IOCase("Sensitive", true);
043    
044    /**
045     * The constant for case insensitive regardless of operating system.
046     */
047    public static final IOCase INSENSITIVE = new IOCase("Insensitive", false);
048    
049    /**
050     * The constant for case sensitivity determined by the current operating system.
051     * Windows is case-insensitive when comparing filenames, Unix is case-sensitive.
052     * <p>
053     * <strong>Note:</strong> This only caters for Windows and Unix. Other operating
054     * systems (e.g. OSX and OpenVMS) are treated as case sensitive if they use the
055     * Unix file separator and case-insensitive if they use the Windows file separator
056     * (see {@link java.io.File#separatorChar}).
057     * <p>
058     * If you derialize this constant of Windows, and deserialize on Unix, or vice
059     * versa, then the value of the case-sensitivity flag will change.
060     */
061    public static final IOCase SYSTEM = new IOCase("System", !FilenameUtils.isSystemWindows());
062
063    /** Serialization version. */
064    private static final long serialVersionUID = -6343169151696340687L;
065
066    /** The enumeration name. */
067    private final String name;
068    
069    /** The sensitivity flag. */
070    private final transient boolean sensitive;
071
072    //-----------------------------------------------------------------------
073    /**
074     * Factory method to create an IOCase from a name.
075     * 
076     * @param name  the name to find
077     * @return the IOCase object
078     * @throws IllegalArgumentException if the name is invalid
079     */
080    public static IOCase forName(String name) {
081        if (IOCase.SENSITIVE.name.equals(name)){
082            return IOCase.SENSITIVE;
083        }
084        if (IOCase.INSENSITIVE.name.equals(name)){
085            return IOCase.INSENSITIVE;
086        }
087        if (IOCase.SYSTEM.name.equals(name)){
088            return IOCase.SYSTEM;
089        }
090        throw new IllegalArgumentException("Invalid IOCase name: " + name);
091    }
092
093    //-----------------------------------------------------------------------
094    /**
095     * Private constructor.
096     * 
097     * @param name  the name
098     * @param sensitive  the sensitivity
099     */
100    private IOCase(String name, boolean sensitive) {
101        this.name = name;
102        this.sensitive = sensitive;
103    }
104
105    /**
106     * Replaces the enumeration from the stream with a real one.
107     * This ensures that the correct flag is set for SYSTEM.
108     * 
109     * @return the resolved object
110     */
111    private Object readResolve() {
112        return forName(name);
113    }
114
115    //-----------------------------------------------------------------------
116    /**
117     * Gets the name of the constant.
118     * 
119     * @return the name of the constant
120     */
121    public String getName() {
122        return name;
123    }
124
125    /**
126     * Does the object represent case sensitive comparison.
127     * 
128     * @return true if case sensitive
129     */
130    public boolean isCaseSensitive() {
131        return sensitive;
132    }
133
134    //-----------------------------------------------------------------------
135    /**
136     * Compares two strings using the case-sensitivity rule.
137     * <p>
138     * This method mimics {@link String#compareTo} but takes case-sensitivity
139     * into account.
140     * 
141     * @param str1  the first string to compare, not null
142     * @param str2  the second string to compare, not null
143     * @return true if equal using the case rules
144     * @throws NullPointerException if either string is null
145     */
146    public int checkCompareTo(String str1, 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
157     * into 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(String str1, 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 case-sensitivity
175     * 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(String str, 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
190     * into 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(String str, String end) {
198        int 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)} 
207     * but takes 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,
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(String str, int strStartIndex, String search) {
218        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     * 
235     * @param str  the string to check, not null
236     * @param strStartIndex  the index to start at in str
237     * @param search  the start to search for, not null
238     * @return true if equal using the case rules
239     * @throws NullPointerException if either string is null
240     */
241    public boolean checkRegionMatches(String str, int strStartIndex, String search) {
242        return str.regionMatches(!sensitive, strStartIndex, search, 0, search.length());
243    }
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}