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 * https://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 }