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