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 *      http://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
019/**
020 * <p>Operations on {@code CharSet} instances.</p>
021 *
022 * <p>This class handles {@code null} input gracefully.
023 * An exception will not be thrown for a {@code null} input.
024 * Each method documents its behaviour in more detail.</p>
025 *
026 * <p>#ThreadSafe#</p>
027 * @see CharSet
028 * @since 1.0
029 */
030public class CharSetUtils {
031
032    /**
033     * <p>CharSetUtils instances should NOT be constructed in standard programming.
034     * Instead, the class should be used as {@code CharSetUtils.evaluateSet(null);}.</p>
035     *
036     * <p>This constructor is public to permit tools that require a JavaBean instance
037     * to operate.</p>
038     */
039    public CharSetUtils() {
040      super();
041    }
042
043    // Squeeze
044    //-----------------------------------------------------------------------
045    /**
046     * <p>Squeezes any repetitions of a character that is mentioned in the
047     * supplied set.</p>
048     *
049     * <pre>
050     * CharSetUtils.squeeze(null, *)        = null
051     * CharSetUtils.squeeze("", *)          = ""
052     * CharSetUtils.squeeze(*, null)        = *
053     * CharSetUtils.squeeze(*, "")          = *
054     * CharSetUtils.squeeze("hello", "k-p") = "helo"
055     * CharSetUtils.squeeze("hello", "a-e") = "hello"
056     * </pre>
057     *
058     * @see CharSet#getInstance(java.lang.String...) for set-syntax.
059     * @param str  the string to squeeze, may be null
060     * @param set  the character set to use for manipulation, may be null
061     * @return the modified String, {@code null} if null string input
062     */
063    public static String squeeze(final String str, final String... set) {
064        if (StringUtils.isEmpty(str) || deepEmpty(set)) {
065            return str;
066        }
067        final CharSet chars = CharSet.getInstance(set);
068        final StringBuilder buffer = new StringBuilder(str.length());
069        final char[] chrs = str.toCharArray();
070        final int sz = chrs.length;
071        char lastChar = chrs[0];
072        char ch = ' ';
073        Character inChars = null;
074        Character notInChars = null;
075        buffer.append(lastChar);
076        for (int i = 1; i < sz; i++) {
077            ch = chrs[i];
078            if (ch == lastChar) {
079                if (inChars != null && ch == inChars) {
080                    continue;
081                }
082                if (notInChars == null || ch != notInChars) {
083                    if (chars.contains(ch)) {
084                        inChars = ch;
085                        continue;
086                    }
087                    notInChars = ch;
088                }
089            }
090            buffer.append(ch);
091            lastChar = ch;
092        }
093        return buffer.toString();
094    }
095
096    // ContainsAny
097    //-----------------------------------------------------------------------
098    /**
099     * <p>Takes an argument in set-syntax, see evaluateSet,
100     * and identifies whether any of the characters are present in the specified string.</p>
101     *
102     * <pre>
103     * CharSetUtils.containsAny(null, *)        = false
104     * CharSetUtils.containsAny("", *)          = false
105     * CharSetUtils.containsAny(*, null)        = false
106     * CharSetUtils.containsAny(*, "")          = false
107     * CharSetUtils.containsAny("hello", "k-p") = true
108     * CharSetUtils.containsAny("hello", "a-d") = false
109     * </pre>
110     *
111     * @see CharSet#getInstance(java.lang.String...) for set-syntax.
112     * @param str  String to look for characters in, may be null
113     * @param set  String[] set of characters to identify, may be null
114     * @return whether or not the characters in the set are in the primary string
115     * @since 3.2
116     */
117    public static boolean containsAny(final String str, final String... set) {
118        if (StringUtils.isEmpty(str) || deepEmpty(set)) {
119            return false;
120        }
121        final CharSet chars = CharSet.getInstance(set);
122        for (final char c : str.toCharArray()) {
123            if (chars.contains(c)) {
124                return true;
125            }
126        }
127        return false;
128    }
129
130    // Count
131    //-----------------------------------------------------------------------
132    /**
133     * <p>Takes an argument in set-syntax, see evaluateSet,
134     * and returns the number of characters present in the specified string.</p>
135     *
136     * <pre>
137     * CharSetUtils.count(null, *)        = 0
138     * CharSetUtils.count("", *)          = 0
139     * CharSetUtils.count(*, null)        = 0
140     * CharSetUtils.count(*, "")          = 0
141     * CharSetUtils.count("hello", "k-p") = 3
142     * CharSetUtils.count("hello", "a-e") = 1
143     * </pre>
144     *
145     * @see CharSet#getInstance(java.lang.String...) for set-syntax.
146     * @param str  String to count characters in, may be null
147     * @param set  String[] set of characters to count, may be null
148     * @return the character count, zero if null string input
149     */
150    public static int count(final String str, final String... set) {
151        if (StringUtils.isEmpty(str) || deepEmpty(set)) {
152            return 0;
153        }
154        final CharSet chars = CharSet.getInstance(set);
155        int count = 0;
156        for (final char c : str.toCharArray()) {
157            if (chars.contains(c)) {
158                count++;
159            }
160        }
161        return count;
162    }
163
164    // Keep
165    //-----------------------------------------------------------------------
166    /**
167     * <p>Takes an argument in set-syntax, see evaluateSet,
168     * and keeps any of characters present in the specified string.</p>
169     *
170     * <pre>
171     * CharSetUtils.keep(null, *)        = null
172     * CharSetUtils.keep("", *)          = ""
173     * CharSetUtils.keep(*, null)        = ""
174     * CharSetUtils.keep(*, "")          = ""
175     * CharSetUtils.keep("hello", "hl")  = "hll"
176     * CharSetUtils.keep("hello", "le")  = "ell"
177     * </pre>
178     *
179     * @see CharSet#getInstance(java.lang.String...) for set-syntax.
180     * @param str  String to keep characters from, may be null
181     * @param set  String[] set of characters to keep, may be null
182     * @return the modified String, {@code null} if null string input
183     * @since 2.0
184     */
185    public static String keep(final String str, final String... set) {
186        if (str == null) {
187            return null;
188        }
189        if (str.isEmpty() || deepEmpty(set)) {
190            return StringUtils.EMPTY;
191        }
192        return modify(str, set, true);
193    }
194
195    // Delete
196    //-----------------------------------------------------------------------
197    /**
198     * <p>Takes an argument in set-syntax, see evaluateSet,
199     * and deletes any of characters present in the specified string.</p>
200     *
201     * <pre>
202     * CharSetUtils.delete(null, *)        = null
203     * CharSetUtils.delete("", *)          = ""
204     * CharSetUtils.delete(*, null)        = *
205     * CharSetUtils.delete(*, "")          = *
206     * CharSetUtils.delete("hello", "hl")  = "eo"
207     * CharSetUtils.delete("hello", "le")  = "ho"
208     * </pre>
209     *
210     * @see CharSet#getInstance(java.lang.String...) for set-syntax.
211     * @param str  String to delete characters from, may be null
212     * @param set  String[] set of characters to delete, may be null
213     * @return the modified String, {@code null} if null string input
214     */
215    public static String delete(final String str, final String... set) {
216        if (StringUtils.isEmpty(str) || deepEmpty(set)) {
217            return str;
218        }
219        return modify(str, set, false);
220    }
221
222    //-----------------------------------------------------------------------
223    /**
224     * Implementation of delete and keep
225     *
226     * @param str String to modify characters within
227     * @param set String[] set of characters to modify
228     * @param expect whether to evaluate on match, or non-match
229     * @return the modified String, not null
230     */
231    private static String modify(final String str, final String[] set, final boolean expect) {
232        final CharSet chars = CharSet.getInstance(set);
233        final StringBuilder buffer = new StringBuilder(str.length());
234        final char[] chrs = str.toCharArray();
235        for (char chr : chrs) {
236            if (chars.contains(chr) == expect) {
237                buffer.append(chr);
238            }
239        }
240        return buffer.toString();
241    }
242
243    /**
244     * Determines whether or not all the Strings in an array are
245     * empty or not.
246     *
247     * @param strings String[] whose elements are being checked for emptiness
248     * @return whether or not the String is empty
249     */
250    private static boolean deepEmpty(final String[] strings) {
251        if (strings != null) {
252            for (final String s : strings) {
253                if (StringUtils.isNotEmpty(s)) {
254                    return false;
255                }
256            }
257        }
258        return true;
259    }
260}