001package Torello.Java;
002
003import java.util.TreeSet;
004
005/**
006 * Class String-Character provides an exhaustive-combinatoric suite of methods that extend the
007 * basic Java <CODE>String</CODE> methods <CODE>equals, contains, startsWith</CODE> and
008 * <CODE>endsWith</CODE>, using Java {@code char} primitives as the comparator element.
009 * 
010 * <BR /><BR /><EMBED CLASS="external-html" DATA-FILE-ID="STRCH">
011 */
012@Torello.HTML.Tools.JavaDoc.StaticFunctional
013public class StrCh
014{
015    private StrCh() { }
016
017
018    // ********************************************************************************************
019    // ********************************************************************************************
020    // CONTAINS
021    // ********************************************************************************************
022    // ********************************************************************************************
023
024
025    /**
026     * <EMBED CLASS=defs DATA-DESC='contains at least one' DATA-CI='is'>
027     * <EMBED CLASS='external-html' DATA-FILE-ID=STRCHDESC>
028     * @param srcStr Any non-null instance of {@code java.lang.String}
029     * @param compareChar The {@code char's} used in the comparison against {@code 'srcStr'}
030     * @return <EMBED CLASS='external-html' DATA-FILE-ID=STRCHRET>
031     * @see #CONTAINS_NAND_OR(boolean, byte, String, char[])
032     */
033    public static boolean containsOR(String srcStr, char... compareChar)
034    { return CONTAINS_NAND_OR(false, OR, srcStr, compareChar); }
035
036    /**
037     * <EMBED CLASS=defs DATA-DESC='contains every one' DATA-CI='is'>
038     * <EMBED CLASS='external-html' DATA-FILE-ID=STRCHDESC>
039     * @param srcStr Any non-null instance of {@code java.lang.String}
040     * @param compareChar The {@code char's} used in the comparison against {@code 'srcStr'}
041     * @return <EMBED CLASS='external-html' DATA-FILE-ID=STRCHRET>
042     * @see #CONTAINS_XOR_AND(boolean, byte, String, char[])
043     */
044    public static boolean containsAND(String srcStr, char... compareChar)
045    { return CONTAINS_XOR_AND(false, AND, srcStr, compareChar); }
046
047    /**
048     * <EMBED CLASS=defs DATA-DESC='contains exactly one' DATA-CI='is'>
049     * <EMBED CLASS='external-html' DATA-FILE-ID=STRCHDESC>
050     * @param srcStr Any non-null instance of {@code java.lang.String}
051     * @param compareChar The {@code char's} used in the comparison against {@code 'srcStr'}
052     * @return <EMBED CLASS='external-html' DATA-FILE-ID=STRCHRET>
053     * <EMBED CLASS='external-html' DATA-FILE-ID=STRCHXORNOTE>
054     * @see #CONTAINS_XOR_AND(boolean, byte, String, char[])
055     */
056    public static boolean containsXOR(String srcStr, char... compareChar)
057    { return CONTAINS_XOR_AND(false, XOR, srcStr, compareChar); }
058
059    /**
060     * <EMBED CLASS=defs DATA-DESC='does not contain any' DATA-CI='is'>
061     * <EMBED CLASS='external-html' DATA-FILE-ID=STRCHDESC>
062     * @param srcStr Any non-null instance of {@code java.lang.String}
063     * @param compareChar The {@code char's} used in the comparison against {@code 'srcStr'}
064     * @return <EMBED CLASS='external-html' DATA-FILE-ID=STRCHRET>
065     * @see #CONTAINS_NAND_OR(boolean, byte, String, char[])
066     */
067    public static boolean containsNAND(String srcStr, char... compareChar)
068    { return CONTAINS_NAND_OR(false, NAND, srcStr, compareChar); }
069
070    /**
071     * <EMBED CLASS=defs DATA-DESC='contains at least one' DATA-CI='is not'>
072     * <EMBED CLASS='external-html' DATA-FILE-ID=STRCHDESC>
073     * @param srcStr Any non-null instance of {@code java.lang.String}
074     * @param compareChar The {@code char's} used in the comparison against {@code 'srcStr'}
075     * @return <EMBED CLASS='external-html' DATA-FILE-ID=STRCHRET>
076     * @see #CONTAINS_NAND_OR(boolean, byte, String, char[])
077     */
078    public static boolean containsOR_CI(String srcStr, char... compareChar)
079    { return CONTAINS_NAND_OR(true, OR, srcStr, compareChar); }
080
081    /**
082     * <EMBED CLASS=defs DATA-DESC='contains every one' DATA-CI='is not'>
083     * <EMBED CLASS='external-html' DATA-FILE-ID=STRCHDESC>
084     * @param srcStr Any non-null instance of {@code java.lang.String}
085     * @param compareChar The {@code char's} used in the comparison against {@code 'srcStr'}
086     * @return <EMBED CLASS='external-html' DATA-FILE-ID=STRCHRET>
087     * @see #CONTAINS_XOR_AND(boolean, byte, String, char[])
088     */
089    public static boolean containsAND_CI(String srcStr, char... compareChar)
090    { return CONTAINS_XOR_AND(true, AND, srcStr, compareChar); }
091
092    /**
093     * <EMBED CLASS=defs DATA-DESC='contains exactly one' DATA-CI='is not'>
094     * <EMBED CLASS='external-html' DATA-FILE-ID=STRCHDESC>
095     * @param srcStr Any non-null instance of {@code java.lang.String}
096     * @param compareChar The {@code char's} used in the comparison against {@code 'srcStr'}
097     * @return <EMBED CLASS='external-html' DATA-FILE-ID=STRCHRET>
098     * <EMBED CLASS='external-html' DATA-FILE-ID=STRCHXORNOTE>
099     * @see #CONTAINS_XOR_AND(boolean, byte, String, char[])
100     */
101    public static boolean containsXOR_CI(String srcStr, char... compareChar)
102    { return CONTAINS_XOR_AND(true, XOR, srcStr, compareChar); }
103
104    /**
105     * <EMBED CLASS=defs DATA-DESC='does not contain any' DATA-CI='is not'>
106     * <EMBED CLASS='external-html' DATA-FILE-ID=STRCHDESC>
107     * @param srcStr Any non-null instance of {@code java.lang.String}
108     * @param compareChar The {@code char's} used in the comparison against {@code 'srcStr'}
109     * @return <EMBED CLASS='external-html' DATA-FILE-ID=STRCHRET>
110     * @see #CONTAINS_NAND_OR(boolean, byte, String, char[])
111     */
112    public static boolean containsNAND_CI(String srcStr, char... compareChar)
113    { return CONTAINS_NAND_OR(true, NAND, srcStr, compareChar); }
114
115
116    // ********************************************************************************************
117    // ********************************************************************************************
118    // STARTS-WITH, ENDS-WITH
119    // ********************************************************************************************
120    // ********************************************************************************************
121
122
123    /**
124     * <EMBED CLASS=defs DATA-DESC='ends with at least one' DATA-CI='is'>
125     * <EMBED CLASS='external-html' DATA-FILE-ID=STRCHDESC>
126     * @param srcStr Any non-null instance of {@code java.lang.String}
127     * @param compareChar The {@code char's} used in the comparison against {@code 'srcStr'}
128     * @return <EMBED CLASS='external-html' DATA-FILE-ID=STRCHRET>
129     */
130    public static boolean endsWithOR(String srcStr, char... compareChar)
131    {
132        char c = srcStr.charAt(srcStr.length() - 1);
133        for (char c2 : compareChar) if (c2 == c) return true;
134        return false;
135    }
136
137    /**
138     * <EMBED CLASS=defs DATA-DESC='does not end with any' DATA-CI='is'>
139     * <EMBED CLASS='external-html' DATA-FILE-ID=STRCHDESC>
140     * @param srcStr Any non-null instance of {@code java.lang.String}
141     * @param compareChar The {@code char's} used in the comparison against {@code 'srcStr'}
142     * @return <EMBED CLASS='external-html' DATA-FILE-ID=STRCHRET>
143     */
144    public static boolean endsWithNAND(String srcStr, char... compareChar)
145    {
146        char c = srcStr.charAt(srcStr.length() - 1);
147        for (char c2 : compareChar) if (c2 == c) return false;
148        return true;
149    }
150
151    /**
152     * <EMBED CLASS=defs DATA-DESC='starts with at least one' DATA-CI='is'>
153     * <EMBED CLASS='external-html' DATA-FILE-ID=STRCHDESC>
154     * @param srcStr Any non-null instance of {@code java.lang.String}
155     * @param compareChar The {@code char's} used in the comparison against {@code 'srcStr'}
156     * @return <EMBED CLASS='external-html' DATA-FILE-ID=STRCHRET>
157     */
158    public static boolean startsWithOR(String srcStr, char... compareChar)
159    {
160        char c = srcStr.charAt(0);
161        for (char c2 : compareChar) if (c2 == c) return true;
162        return false;
163    }
164
165    /**
166     * <EMBED CLASS=defs DATA-DESC='does not start with any' DATA-CI='is'>
167     * <EMBED CLASS='external-html' DATA-FILE-ID=STRCHDESC>
168     * @param srcStr Any non-null instance of {@code java.lang.String}
169     * @param compareChar The {@code char's} used in the comparison against {@code 'srcStr'}
170     * @return <EMBED CLASS='external-html' DATA-FILE-ID=STRCHRET>
171     */
172    public static boolean startsWithNAND(String srcStr, char... compareChar)
173    {
174        char c = srcStr.charAt(0);
175        for (char c2 : compareChar) if (c2 == c) return false;
176        return true;
177    }
178
179
180    // ********************************************************************************************
181    // ********************************************************************************************
182    // STARTS-WITH, ENDS-WITH - CASE INSENSITIVE
183    // ********************************************************************************************
184    // ********************************************************************************************
185
186
187    /**
188     * <EMBED CLASS=defs DATA-DESC='ends with at least one' DATA-CI='is not'>
189     * <EMBED CLASS='external-html' DATA-FILE-ID=STRCHDESC>
190     * @param srcStr Any non-null instance of {@code java.lang.String}
191     * @param compareChar The {@code char's} used in the comparison against {@code 'srcStr'}
192     * @return <EMBED CLASS='external-html' DATA-FILE-ID=STRCHRET>
193     */
194    public static boolean endsWithOR_CI(String srcStr, char... compareChar)
195    {
196        char c = Character.toLowerCase(srcStr.charAt(srcStr.length() - 1));
197        for (char c2 : compareChar) if (Character.toLowerCase(c2) == c) return true;
198        return false;
199    }
200
201    /**
202     * <EMBED CLASS=defs DATA-DESC='does not end with any' DATA-CI='is not'>
203     * <EMBED CLASS='external-html' DATA-FILE-ID=STRCHDESC>
204     * @param srcStr Any non-null instance of {@code java.lang.String}
205     * @param compareChar The {@code char's} used in the comparison against {@code 'srcStr'}
206     * @return <EMBED CLASS='external-html' DATA-FILE-ID=STRCHRET>
207     */
208    public static boolean endsWithNAND_CI(String srcStr, char... compareChar)
209    {
210        char c = Character.toLowerCase(srcStr.charAt(srcStr.length() - 1));
211        for (char c2 : compareChar) if (Character.toLowerCase(c2) == c) return false;
212        return true;
213    }
214
215    /**
216     * <EMBED CLASS=defs DATA-DESC='starts at least one' DATA-CI='is not'>
217     * <EMBED CLASS='external-html' DATA-FILE-ID=STRCHDESC>
218     * @param srcStr Any non-null instance of {@code java.lang.String}
219     * @param compareChar The {@code char's} used in the comparison against {@code 'srcStr'}
220     * @return <EMBED CLASS='external-html' DATA-FILE-ID=STRCHRET>
221     */
222    public static boolean startsWithOR_CI(String srcStr, char... compareChar)
223    {
224        char c = Character.toLowerCase(srcStr.charAt(0));
225        for (char c2 : compareChar) if (Character.toLowerCase(c2) == c) return true;
226        return false;
227    }
228
229    /**
230     * <EMBED CLASS=defs DATA-DESC='does not start with any' DATA-CI='is not'>
231     * <EMBED CLASS='external-html' DATA-FILE-ID=STRCHDESC>
232     * @param srcStr Any non-null instance of {@code java.lang.String}
233     * @param compareChar The {@code char's} used in the comparison against {@code 'srcStr'}
234     * @return <EMBED CLASS='external-html' DATA-FILE-ID=STRCHRET>
235     */
236    public static boolean startsWithNAND_CI(String srcStr, char ... compareChar)
237    {
238        char c = Character.toLowerCase(srcStr.charAt(0));
239        for (char c2 : compareChar) if (Character.toLowerCase(c2) == c) return false;
240        return true;
241    }
242
243
244    // ********************************************************************************************
245    // ********************************************************************************************
246    // Single char-primitive methods
247    // ********************************************************************************************
248    // ********************************************************************************************
249
250
251    /**
252     * Checks whether or not {@code 'srcStr'} <B STYLE='color: red;'>starts-with</B>
253     * {@code 'compareChar'}.
254     * 
255     * <BR /><BR />This method <B STYLE='color: red;'>** is not**</B> case-sensitive.
256     * 
257     * @param srcStr Any non-null instance of {@code java.lang.String}
258     * @param compareChar The {@code char} used in the comparison against {@code 'srcStr'}
259     * @return {@code TRUE} if {@code 'srcStr'} begins with {@code 'compareChar'}, ignoring case.
260     */
261    public static boolean startsWithIgnoreCase(String srcStr, char compareChar)
262    { return Character.toLowerCase(srcStr.charAt(0)) == Character.toLowerCase(compareChar); }
263
264    /**
265     * Checks whether or not {@code 'srcStr'} <B STYLE='color: red;'>ends-with</B>
266     * {@code 'compareChar'}.
267     * 
268     * <BR /><BR />This method <B STYLE='color: red;'>** is not**</B> case-sensitive.
269     * 
270     * @param srcStr Any non-null instance of {@code java.lang.String}
271     * @param compareChar The {@code char} used in the comparison against {@code 'srcStr'}
272     * @return {@code TRUE} if {@code 'srcStr'} ends with {@code 'compareChar'}, ignoring case.
273     */
274    public static boolean endsWithIgnoreCase(String srcStr, char compareChar)
275    {
276        return Character.toLowerCase(srcStr.charAt(srcStr.length() - 1)) == 
277            Character.toLowerCase(compareChar); 
278    }
279
280    /**
281     * Checks whether or not {@code 'srcStr'} <B STYLE='color: red;'>contains</B>
282     * {@code 'compareChar'}.
283     * 
284     * <BR /><BR />This method <B STYLE='color: red;'>** is not**</B> case-sensitive.
285     * 
286     * @param srcStr Any non-null instance of {@code java.lang.String}
287     * @param compareChar The {@code char} used in the comparison against {@code 'srcStr'}
288     * @return {@code TRUE} if {@code 'srcStr'} begins with {@code 'compareChar'}, ignoring case.
289     */
290    public static boolean containsIgnoreCase(String srcStr, char compareChar)
291    {
292        compareChar = Character.toLowerCase(compareChar);
293
294        int len = srcStr.length();
295
296        for (int i=0; i < len; i++)
297            if (Character.toLowerCase(srcStr.charAt(i)) == compareChar)
298                return true;
299
300        return false;
301    }
302
303
304    // ********************************************************************************************
305    // ********************************************************************************************
306    // PROTECTED CONTAINS HELPER
307    // ********************************************************************************************
308    // ********************************************************************************************
309
310
311    /**
312     * Signifies that an {@code 'AND'} operation is required, but only for methods that implement
313     * one of the {@code 'contains'} variants.
314     */
315    protected static final byte AND = 0;
316
317    /**
318     * Signifies that an {@code 'AND'} operation is required, but only for methods that implement
319     * one of the {@code 'contains'} variants.
320     */
321    protected static final byte OR = 1;
322
323    /**
324     * Signifies that an {@code 'AND'} operation is required, but only for methods that implement
325     * one of the {@code 'contains'} variants.
326     */
327    protected static final byte NAND = 2;
328
329    /**
330     * Signifies that an {@code 'AND'} operation is required, but only for methods that implement
331     * one of the {@code 'contains'} variants.
332     */
333    protected static final byte XOR = 3;
334
335    /**
336     * A protected helper method for <B>{@code NAND}</B> and <B>{@code OR}</B> 'contains' methods.
337     * 
338     * <BR /><BR /><B STYLE='color: red;'>NOTE:</B> Does not error check the value passed to
339     * {@code 'booleanOperation'}.
340     * 
341     * @param ignoreCase Whether the comparisons performed should ignore case.
342     * @param booleanOperation Accepts only {@link StrCh#NAND} and {@link StrCh#OR}.
343     * @param srcStr Any non-null instance of {@code java.lang.String}
344     * @param compareChar The {@code char} used in the comparison against {@code 'srcStr'}
345     */
346    protected static boolean CONTAINS_NAND_OR
347        (boolean ignoreCase, byte booleanOperation, String srcStr, char[] compareChar)
348    {
349        if (ignoreCase)
350        {
351            char[] temp = new char[compareChar.length];
352
353            for (int i=0; i < compareChar.length; i++)
354                temp[i] = Character.toLowerCase(compareChar[i]);
355
356            compareChar = temp;
357        }
358
359        int len = srcStr.length();
360
361        for (int i=0; i < len; i++)
362        {
363            char c = ignoreCase ? Character.toLowerCase(srcStr.charAt(i)) : srcStr.charAt(i);
364
365            for (char c2 : compareChar)
366
367                if (c2 == c)
368
369                    switch(booleanOperation)
370                    {
371                        case NAND:  return false;
372                        case OR:    return true;
373                    }
374        }
375
376        switch (booleanOperation)
377        {
378            case NAND:  return true;
379            case OR:    return false;
380        }
381
382        throw new UnreachableError();
383    }
384
385    /**
386     * A protected helper method for <B>{@code XOR}</B> and <B>{@code AND}</B> 'contains' methods.
387     * 
388     * <BR /><BR /><B STYLE='color: red;'>NOTE:</B> Does not error check the value passed to
389     * {@code 'booleanOperation'}.
390     * 
391     * @param ignoreCase Whether the comparisons performed should ignore case.
392     * @param booleanOperation Accepts only {@link StrCh#XOR} and {@link StrCh#AND}.
393     * @param srcStr Any non-null instance of {@code java.lang.String}
394     * @param compareChar The {@code char} used in the comparison against {@code 'srcStr'}
395     */
396    protected static boolean CONTAINS_XOR_AND
397        (boolean ignoreCase, byte booleanOperation, String srcStr, char[] compareChar)
398    {
399        int count = 0;
400
401        // There is a TreeSet so that multiple instances of the same character ARE NOT CHECKED.
402        // Once a character has been found, it is more efficient to stop testing for it at all.
403
404        TreeSet<Character> ts = new TreeSet<>();
405
406        // When building the TreeSet of characters to check, if 'ignoreCase' has been requested,
407        // then doing the case-conversion before entering the loop is more efficient.
408
409        if (ignoreCase) for (char c : compareChar) ts.add(Character.toLowerCase(c));
410        else            for (char c : compareChar) ts.add(c);
411
412        // Iterate each character in the passed 'srcStr'
413        int len = srcStr.length();
414
415        for (int i=0; i < len; i++)
416        {
417            char c = ignoreCase ? Character.toLowerCase(srcStr.charAt(i)) : srcStr.charAt(i);
418
419            // Iterate each of the remaining compare-characters in the TreeSet.  These characters
420            // are removed when matches are found.
421
422            INNER:
423            for (char c2 : ts)
424
425                if (c2 == c)
426                {
427                    ts.remove(c2);
428
429                    switch (booleanOperation)
430                    {
431                        case AND: if (--count == 0) return true; else break INNER;
432                        case XOR: if (++count > 1)  return false; else break INNER;
433                    }
434                }
435        }
436
437        switch (booleanOperation)
438        {
439            // If the count had reached zero, then true would ALREADY have been returned.
440            case AND: return false;
441
442            // Whether or not a match has been found is all that is left to check.
443            case XOR: return count == 1;
444        }
445
446        throw new UnreachableError();
447    }
448}