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