001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      https://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.lang3;
018
019import org.apache.commons.lang3.stream.Streams;
020
021/**
022 * Operations on {@link CharSet} instances.
023 *
024 * <p>
025 * This class handles {@code null} input gracefully.
026 * An exception will not be thrown for a {@code null} input.
027 * Each method documents its behavior in more detail.
028 * </p>
029 *
030 * <p>
031 * #ThreadSafe#
032 * </p>
033 *
034 * @see CharSet
035 * @since 1.0
036 */
037public class CharSetUtils {
038
039    /**
040     * Takes an argument in set-syntax, see evaluateSet,
041     * and identifies whether any of the characters are present in the specified string.
042     *
043     * <pre>
044     * CharSetUtils.containsAny(null, *)        = false
045     * CharSetUtils.containsAny("", *)          = false
046     * CharSetUtils.containsAny(*, null)        = false
047     * CharSetUtils.containsAny(*, "")          = false
048     * CharSetUtils.containsAny("hello", "k-p") = true
049     * CharSetUtils.containsAny("hello", "a-d") = false
050     * </pre>
051     *
052     * @see CharSet#getInstance(String...) for set-syntax.
053     * @param str  String to look for characters in, may be null
054     * @param set  String[] set of characters to identify, may be null
055     * @return whether or not the characters in the set are in the primary string
056     * @since 3.2
057     */
058    public static boolean containsAny(final String str, final String... set) {
059        if (isEmpty(str, set)) {
060            return false;
061        }
062        final CharSet chars = CharSet.getInstance(set);
063        for (final char c : str.toCharArray()) {
064            if (chars.contains(c)) {
065                return true;
066            }
067        }
068        return false;
069    }
070
071    /**
072     * Takes an argument in set-syntax, see evaluateSet,
073     * and returns the number of characters present in the specified string.
074     *
075     * <pre>
076     * CharSetUtils.count(null, *)        = 0
077     * CharSetUtils.count("", *)          = 0
078     * CharSetUtils.count(*, null)        = 0
079     * CharSetUtils.count(*, "")          = 0
080     * CharSetUtils.count("hello", "k-p") = 3
081     * CharSetUtils.count("hello", "a-e") = 1
082     * </pre>
083     *
084     * @see CharSet#getInstance(String...) for set-syntax.
085     * @param str  String to count characters in, may be null
086     * @param set  String[] set of characters to count, may be null
087     * @return The character count, zero if null string input
088     */
089    public static int count(final String str, final String... set) {
090        if (isEmpty(str, set)) {
091            return 0;
092        }
093        final CharSet chars = CharSet.getInstance(set);
094        int count = 0;
095        for (final char c : str.toCharArray()) {
096            if (chars.contains(c)) {
097                count++;
098            }
099        }
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}