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.lang3;
18
19 import org.apache.commons.lang3.stream.Streams;
20
21 /**
22 * Operations on {@link CharSet} instances.
23 *
24 * <p>
25 * This class handles {@code null} input gracefully.
26 * An exception will not be thrown for a {@code null} input.
27 * Each method documents its behavior in more detail.
28 * </p>
29 *
30 * <p>
31 * #ThreadSafe#
32 * </p>
33 *
34 * @see CharSet
35 * @since 1.0
36 */
37 public class CharSetUtils {
38
39 /**
40 * Takes an argument in set-syntax, see evaluateSet,
41 * and identifies whether any of the characters are present in the specified string.
42 *
43 * <pre>
44 * CharSetUtils.containsAny(null, *) = false
45 * CharSetUtils.containsAny("", *) = false
46 * CharSetUtils.containsAny(*, null) = false
47 * CharSetUtils.containsAny(*, "") = false
48 * CharSetUtils.containsAny("hello", "k-p") = true
49 * CharSetUtils.containsAny("hello", "a-d") = false
50 * </pre>
51 *
52 * @see CharSet#getInstance(String...) for set-syntax.
53 * @param str String to look for characters in, may be null
54 * @param set String[] set of characters to identify, may be null
55 * @return whether or not the characters in the set are in the primary string
56 * @since 3.2
57 */
58 public static boolean containsAny(final String str, final String... set) {
59 if (isEmpty(str, set)) {
60 return false;
61 }
62 final CharSet chars = CharSet.getInstance(set);
63 for (final char c : str.toCharArray()) {
64 if (chars.contains(c)) {
65 return true;
66 }
67 }
68 return false;
69 }
70
71 /**
72 * Takes an argument in set-syntax, see evaluateSet,
73 * and returns the number of characters present in the specified string.
74 *
75 * <pre>
76 * CharSetUtils.count(null, *) = 0
77 * CharSetUtils.count("", *) = 0
78 * CharSetUtils.count(*, null) = 0
79 * CharSetUtils.count(*, "") = 0
80 * CharSetUtils.count("hello", "k-p") = 3
81 * CharSetUtils.count("hello", "a-e") = 1
82 * </pre>
83 *
84 * @see CharSet#getInstance(String...) for set-syntax.
85 * @param str String to count characters in, may be null
86 * @param set String[] set of characters to count, may be null
87 * @return The character count, zero if null string input
88 */
89 public static int count(final String str, final String... set) {
90 if (isEmpty(str, set)) {
91 return 0;
92 }
93 final CharSet chars = CharSet.getInstance(set);
94 int count = 0;
95 for (final char c : str.toCharArray()) {
96 if (chars.contains(c)) {
97 count++;
98 }
99 }
100 return count;
101 }
102
103 /**
104 * Determines whether or not all the Strings in an array are
105 * empty or not.
106 *
107 * @param strings String[] whose elements are being checked for emptiness
108 * @return whether or not the String is empty
109 */
110 private static boolean deepEmpty(final String[] strings) {
111 return Streams.of(strings).allMatch(StringUtils::isEmpty);
112 }
113
114 /**
115 * Takes an argument in set-syntax, see evaluateSet,
116 * and deletes any of characters present in the specified string.
117 *
118 * <pre>
119 * CharSetUtils.delete(null, *) = null
120 * CharSetUtils.delete("", *) = ""
121 * CharSetUtils.delete(*, null) = *
122 * CharSetUtils.delete(*, "") = *
123 * CharSetUtils.delete("hello", "hl") = "eo"
124 * CharSetUtils.delete("hello", "le") = "ho"
125 * </pre>
126 *
127 * @see CharSet#getInstance(String...) for set-syntax.
128 * @param str String to delete characters from, may be null
129 * @param set String[] set of characters to delete, may be null
130 * @return The modified String, {@code null} if null string input
131 */
132 public static String delete(final String str, final String... set) {
133 if (isEmpty(str, set)) {
134 return str;
135 }
136 return modify(str, set, false);
137 }
138
139 private static boolean isEmpty(final String str, final String... set) {
140 return StringUtils.isEmpty(str) || deepEmpty(set);
141 }
142
143 /**
144 * Takes an argument in set-syntax, see evaluateSet,
145 * and keeps any of characters present in the specified string.
146 *
147 * <pre>
148 * CharSetUtils.keep(null, *) = null
149 * CharSetUtils.keep("", *) = ""
150 * CharSetUtils.keep(*, null) = ""
151 * CharSetUtils.keep(*, "") = ""
152 * CharSetUtils.keep("hello", "hl") = "hll"
153 * CharSetUtils.keep("hello", "le") = "ell"
154 * </pre>
155 *
156 * @see CharSet#getInstance(String...) for set-syntax.
157 * @param str String to keep characters from, may be null
158 * @param set String[] set of characters to keep, may be null
159 * @return The modified String, {@code null} if null string input
160 * @since 2.0
161 */
162 public static String keep(final String str, final String... set) {
163 if (str == null) {
164 return null;
165 }
166 if (str.isEmpty() || deepEmpty(set)) {
167 return StringUtils.EMPTY;
168 }
169 return modify(str, set, true);
170 }
171
172 /**
173 * Implements delete and keep.
174 *
175 * @param str String to modify characters within
176 * @param set String[] set of characters to modify
177 * @param expect whether to evaluate on match, or non-match
178 * @return The modified String, not null
179 */
180 private static String modify(final String str, final String[] set, final boolean expect) {
181 final CharSet chars = CharSet.getInstance(set);
182 final StringBuilder buffer = new StringBuilder(str.length());
183 final char[] chrs = str.toCharArray();
184 for (final char chr : chrs) {
185 if (chars.contains(chr) == expect) {
186 buffer.append(chr);
187 }
188 }
189 return buffer.toString();
190 }
191
192 /**
193 * Squeezes any repetitions of a character that is mentioned in the
194 * supplied set.
195 *
196 * <pre>
197 * CharSetUtils.squeeze(null, *) = null
198 * CharSetUtils.squeeze("", *) = ""
199 * CharSetUtils.squeeze(*, null) = *
200 * CharSetUtils.squeeze(*, "") = *
201 * CharSetUtils.squeeze("hello", "k-p") = "helo"
202 * CharSetUtils.squeeze("hello", "a-e") = "hello"
203 * </pre>
204 *
205 * @see CharSet#getInstance(String...) for set-syntax.
206 * @param str The string to squeeze, may be null
207 * @param set The character set to use for manipulation, may be null
208 * @return The modified String, {@code null} if null string input
209 */
210 public static String squeeze(final String str, final String... set) {
211 if (isEmpty(str, set)) {
212 return str;
213 }
214 final CharSet chars = CharSet.getInstance(set);
215 final StringBuilder buffer = new StringBuilder(str.length());
216 final char[] chrs = str.toCharArray();
217 final int sz = chrs.length;
218 char lastChar = chrs[0];
219 char ch;
220 Character inChars = null;
221 Character notInChars = null;
222 buffer.append(lastChar);
223 for (int i = 1; i < sz; i++) {
224 ch = chrs[i];
225 if (ch == lastChar) {
226 if (inChars != null && ch == inChars) {
227 continue;
228 }
229 if (notInChars == null || ch != notInChars) {
230 if (chars.contains(ch)) {
231 inChars = ch;
232 continue;
233 }
234 notInChars = ch;
235 }
236 }
237 buffer.append(ch);
238 lastChar = ch;
239 }
240 return buffer.toString();
241 }
242
243 /**
244 * CharSetUtils instances should NOT be constructed in standard programming.
245 * Instead, the class should be used as {@code CharSetUtils.evaluateSet(null);}.
246 *
247 * <p>
248 * This constructor is public to permit tools that require a JavaBean instance
249 * to operate.
250 * </p>
251 *
252 * @deprecated TODO Make private in 4.0.
253 */
254 @Deprecated
255 public CharSetUtils() {
256 }
257 }