    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}