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