1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 package org.apache.commons.io; 18 19 import java.io.File; 20 import java.util.Objects; 21 import java.util.stream.Stream; 22 23 /** 24 * Enumeration of IO case sensitivity. 25 * <p> 26 * Different filing systems have different rules for case-sensitivity. 27 * Windows is case-insensitive, UNIX is case-sensitive. 28 * </p> 29 * <p> 30 * This class captures that difference, providing an enumeration to 31 * control how file name comparisons should be performed. It also provides 32 * methods that use the enumeration to perform comparisons. 33 * </p> 34 * <p> 35 * Wherever possible, you should use the {@code check} methods in this 36 * class to compare file names. 37 * </p> 38 * 39 * @since 1.3 40 */ 41 public enum IOCase { 42 43 /** 44 * The constant for case-sensitive regardless of operating system. 45 */ 46 SENSITIVE("Sensitive", true), 47 48 /** 49 * The constant for case-insensitive regardless of operating system. 50 */ 51 INSENSITIVE("Insensitive", false), 52 53 /** 54 * The constant for case sensitivity determined by the current operating system. 55 * Windows is case-insensitive when comparing file names, UNIX is case-sensitive. 56 * <p> 57 * <strong>Note:</strong> This only caters for Windows and Unix. Other operating 58 * systems (e.g. OSX and OpenVMS) are treated as case-sensitive if they use the 59 * UNIX file separator and case-insensitive if they use the Windows file separator 60 * (see {@link File#separatorChar}). 61 * </p> 62 * <p> 63 * If you serialize this constant on Windows, and deserialize on Unix, or vice 64 * versa, then the value of the case-sensitivity flag will change. 65 * </p> 66 */ 67 SYSTEM("System", FileSystem.getCurrent().isCaseSensitive()); 68 69 /** Serialization version. */ 70 private static final long serialVersionUID = -6343169151696340687L; 71 72 /** 73 * Looks up an IOCase by name. 74 * 75 * @param name the name to find 76 * @return the IOCase object 77 * @throws IllegalArgumentException if the name is invalid 78 */ 79 public static IOCase forName(final String name) { 80 return Stream.of(values()).filter(ioCase -> ioCase.getName().equals(name)).findFirst() 81 .orElseThrow(() -> new IllegalArgumentException("Illegal IOCase name: " + name)); 82 } 83 84 /** 85 * Tests for cases sensitivity in a null-safe manner. 86 * 87 * @param ioCase an IOCase. 88 * @return true if the input is non-null and {@link #isCaseSensitive()}. 89 * @since 2.10.0 90 */ 91 public static boolean isCaseSensitive(final IOCase ioCase) { 92 return ioCase != null && ioCase.isCaseSensitive(); 93 } 94 95 /** 96 * Returns the given value if not-null, the defaultValue if null. 97 * 98 * @param value the value to test. 99 * @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 }