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.util.Objects; 20 import java.util.stream.Stream; 21 22 /** 23 * Enumeration of IO case sensitivity. 24 * <p> 25 * Different filing systems have different rules for case-sensitivity. 26 * Windows is case-insensitive, Unix is case-sensitive. 27 * </p> 28 * <p> 29 * This class captures that difference, providing an enumeration to 30 * control how file name comparisons should be performed. It also provides 31 * methods that use the enumeration to perform comparisons. 32 * </p> 33 * <p> 34 * Wherever possible, you should use the {@code check} methods in this 35 * class to compare file names. 36 * </p> 37 * 38 * @since 1.3 39 */ 40 public enum IOCase { 41 42 /** 43 * The constant for case-sensitive regardless of operating system. 44 */ 45 SENSITIVE("Sensitive", true), 46 47 /** 48 * The constant for case-insensitive regardless of operating system. 49 */ 50 INSENSITIVE("Insensitive", false), 51 52 /** 53 * The constant for case sensitivity determined by the current operating system. 54 * Windows is case-insensitive when comparing file names, Unix is case-sensitive. 55 * <p> 56 * <strong>Note:</strong> This only caters for Windows and Unix. Other operating 57 * systems (e.g. OSX and OpenVMS) are treated as case-sensitive if they use the 58 * Unix file separator and case-insensitive if they use the Windows file separator 59 * (see {@link java.io.File#separatorChar}). 60 * </p> 61 * <p> 62 * If you serialize this constant on Windows, and deserialize on Unix, or vice 63 * versa, then the value of the case-sensitivity flag will change. 64 * </p> 65 */ 66 SYSTEM("System", FileSystem.getCurrent().isCaseSensitive()); 67 68 /** Serialization version. */ 69 private static final long serialVersionUID = -6343169151696340687L; 70 71 /** 72 * Factory method to create an IOCase from a name. 73 * 74 * @param name the name to find 75 * @return the IOCase object 76 * @throws IllegalArgumentException if the name is invalid 77 */ forName(final String name)78 public static IOCase forName(final String name) { 79 return Stream.of(IOCase.values()).filter(ioCase -> ioCase.getName().equals(name)).findFirst() 80 .orElseThrow(() -> new IllegalArgumentException("Illegal IOCase name: " + name)); 81 } 82 83 /** 84 * Tests for cases sensitivity in a null-safe manner. 85 * 86 * @param ioCase an IOCase. 87 * @return true if the input is non-null and {@link #isCaseSensitive()}. 88 * @since 2.10.0 89 */ isCaseSensitive(final IOCase ioCase)90 public static boolean isCaseSensitive(final IOCase ioCase) { 91 return ioCase != null && ioCase.isCaseSensitive(); 92 } 93 94 /** 95 * Returns the given value if not-null, the defaultValue if null. 96 * 97 * @param value the value to test. 98 * @param defaultValue the default value. 99 * @return the given value if not-null, the defaultValue if null. 100 * @since 2.12.0 101 */ value(final IOCase value, final IOCase defaultValue)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 */ IOCase(final String name, final boolean sensitive)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 */ checkCompareTo(final String str1, final String str2)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 */ checkEndsWith(final String str, final String end)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 */ checkEquals(final String str1, final String str2)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 */ checkIndexOf(final String str, final int strStartIndex, final String search)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 */ checkRegionMatches(final String str, final int strStartIndex, final String search)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 */ checkStartsWith(final String str, final String start)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 */ getName()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 */ isCaseSensitive()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 */ readResolve()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 toString()272 public String toString() { 273 return name; 274 } 275 276 } 277