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