001package Torello.Java;
002
003import java.util.*;
004import java.util.regex.*;
005import java.io.*;
006import java.util.stream.*;
007import java.util.function.*;
008
009import java.text.DecimalFormat;
010import java.net.URL;
011
012import Torello.JavaDoc.IntoHTMLTable;
013import Torello.JavaDoc.LinkJavaSource;
014
015import static Torello.JavaDoc.Entity.FIELD;
016import static Torello.JavaDoc.IntoHTMLTable.Background.BlueDither;
017import static Torello.JavaDoc.IntoHTMLTable.Background.GreenDither;
018
019/**
020 * A plethora of extensions to Java's {@code String} class.
021 * <EMBED CLASS='external-html' DATA-FILE-ID=STRING_PARSE>
022 */
023@Torello.JavaDoc.StaticFunctional
024public class StringParse
025{
026    private StringParse() { }
027
028
029    // ********************************************************************************************
030    // ********************************************************************************************
031    // Constants
032    // ********************************************************************************************
033    // ********************************************************************************************
034
035
036    /**
037     * This regular expression simply matches white-space found in a java {@code String}.
038     * @see #removeWhiteSpace(String)
039     */
040    public static final Pattern WHITE_SPACE_REGEX = Pattern.compile("\\s+");
041
042    /**
043     * This {@code Predicate<String>} checks whether the contents of a {@code java.lang.String}
044     * are comprised of only White-Space Characters.
045     * 
046     * <BR /><BR />Java's {@code 'asMatchPredicate'} is very similar to appending the Reg-Ex
047     * Control-Characters {@code '^'} and {@code '$'} to the beginning and ending of a
048     * {@code String}.
049     * 
050     * <BR /><BR /><B CLASS=JDDescLabel>Important:</B> Since the Regular Expression used is the
051     * one defined, above, as {@code \w+} - <I>using the {@code '+'}, rather than the
052     * {@code '*'}</I> - this {@code Predicate} will return {@code FALSE} when a Zero-Length
053     * {@code String} is passed as input.
054     * 
055     * @see #WHITE_SPACE_REGEX
056     * @see #onlyWhiteSpace_OrZeroLen
057     */
058    public static final Predicate<String> onlyWhiteSpace =
059        WHITE_SPACE_REGEX.asMatchPredicate();
060
061    /**
062     * This is a {@code Predicate} that works in an identical manner to {@link #onlyWhiteSpace},
063     * with the minor addded stipulation that a Zero-Length {@code String} will generate a 
064     * {@code TRUE} / pass result from the {@code Predicate.test(String)} method.
065     * 
066     * @see #onlyWhiteSpace
067     */
068    public static final Predicate<String> onlyWhiteSpace_OrZeroLen =
069        Pattern.compile("^\\s*$").asPredicate();
070
071    /**
072     * This regular expression simply matches the comma.  The only reason for including this here
073     * is because the java {@code class 'Pattern'} contains a method called
074     * {@code Stream<String> 'splitAsStream(CharSequence)'} which is used for the CSV method
075     * further below
076     * 
077     * @see StrCSV#CSV(String, boolean, boolean)
078     * @see FileRW#readDoublesFromFile(String, boolean, boolean)
079     * @see FileRW#readLongsFromFile(String, boolean, boolean, int)
080     */
081    public static final Pattern COMMA_REGEX = Pattern.compile(",");
082
083    /**
084     * This regular expression is used for integer and floating-point numbers that use the
085     * comma ({@code ','}) between the digits that comprise the number.  For example, this
086     * Regular Expression would match the {@code String} {@code "900,800,75.00"}.
087     * 
088     * @see FileRW#readIntsFromFile(String, boolean, boolean, int)
089     */
090    public static final Pattern NUMBER_COMMMA_REGEX = Pattern.compile("(\\d),(\\d)");
091
092    /**
093     * This represents any version of the new-line character.  Note that the {@code '\r\n'} version
094     * comes before the single {@code '\r'} version in the regular-expression, to guarantee that
095     * if both are present, they are treated as a single newline.
096     */
097    public static final Pattern NEWLINEP = Pattern.compile("\\r\\n|\\r|\\n");
098
099    /**
100     * Predicate for new-line characters
101     * @see #NEWLINEP
102     */
103    public static final Predicate<String> newLinePred = NEWLINEP.asPredicate();
104
105    /** This is the list of characters that need to be escaped for a regular expression */
106    public static final String REG_EX_ESCAPE_CHARS = "\\/()[]{}$^+*?-.";
107
108    /** Alpha-Numeric RegEx */
109    public static final Pattern ALPHA_NUMERIC = Pattern.compile("^[\\d\\w]*$");
110
111    /**
112     * Alpha-Numeric {@code String} Predicate.
113     * @see #ALPHA_NUMERIC
114     */
115    public static final Predicate<String> alphaNumPred = ALPHA_NUMERIC.asPredicate();
116
117    /** An empty {@code String} array. */
118    public static final String[] EMPTY_STR_ARRAY = {};
119
120
121    // ********************************************************************************************
122    // ********************************************************************************************
123    // methods
124    // ********************************************************************************************
125    // ********************************************************************************************
126
127
128    /**
129     * Trims any white-space {@code Characters} from the end of a {@code String}.
130     * 
131     * <BR /><TABLE CLASS=JDBriefTable>
132     * <TR><TH>Input String:</TH><TH>Output String:</TH></TR>
133     * <TR><TD>{@code "A Quick Brown Fox\n \t"}</TD><TD>{@code "A Quick Brown Fox"}</TD></TR>
134     * <TR><TD>{@code "\tA Lazy Dog."}</TD><TD>{@code "\tA Lazy Dog."}</TD></TR>
135     * <TR><TD>{@code "   "  (only white-space)}</TD><TD>{@code ""}</TD></TR>
136     * <TR><TD>{@code "" (empty-string)}</TD><TD>{@code ""}</TD></TR>
137     * <TR><TD>{@code null}</TD><TD>throws {@code NullPointerException}</TD></TR>
138     * </TABLE>
139     * 
140     * @param s Any Java {@code String}
141     * 
142     * @return A copy of the same {@code String} - <I>but all characters that matched Java
143     * method {@code java.lang.Character.isWhitespace(char)}</I> and were at the end of the 
144     * {@code String} will not be included in the returned {@code String}.
145     * 
146     * <BR /><BR />If the {@code zero-length String} is passed to parameter {@code 's'}, it
147     * shall be returned immediately.
148     * 
149     * <BR /><BR />If the resultant-{@code String} has zero-length, it is returned, without
150     * exception.
151     */
152    public static String trimRight(String s)
153    {
154        if (s.length() == 0) return s;
155
156        int pos = s.length();
157
158        while ((pos > 0) && Character.isWhitespace(s.charAt(--pos)));
159
160        if (pos == 0) if (Character.isWhitespace(s.charAt(0))) return "";
161
162        return s.substring(0, pos + 1);
163    }
164
165    /**
166     * Trims any white-space {@code Characters} from the beginning of a {@code String}.
167     * 
168     * <TABLE CLASS=JDBriefTable>
169     * <TR><TH>Input String:</TH><TH>Output String:</TH></TR>
170     * <TR><TD>{@code "\t  A Quick Brown Fox"}</TD><TD>{@code "A Quick Brown Fox"}</TD></TR>
171     * <TR><TD>{@code "A Lazy Dog. \n\r\t"}</TD><TD>{@code "A Lazy Dog. \n\r\t"}</TD></TR>
172     * <TR><TD>{@code "   "  (only white-space)}</TD><TD>{@code ""}</TD></TR>
173     * <TR><TD>{@code ""  (empty-string)}</TD><TD>{@code ""}</TD></TR>
174     * <TR><TD>{@code null}</TD><TD>throws {@code NullPointerException}</TD></TR>
175     * </TABLE>
176     * 
177     * @param s Any Java {@code String}
178     * 
179     * @return A copy of the same {@code String} - <I>but all characters that matched Java
180     * method {@code java.lang.Character.isWhitespace(char)}</I> and were at the start of the 
181     * {@code String} will not be included in the returned {@code String}.
182     * 
183     * <BR /><BR />If the {@code zero-length String} is passed to parameter {@code 's'}, it
184     * shall be returned immediately.
185     * 
186     * <BR /><BR />If the resultant-{@code String} has zero-length, it is returned, without
187     * exception.
188     */
189    public static String trimLeft(String s)
190    {
191        int pos = 0;
192        int len = s.length();
193
194        if (len == 0) return s;
195
196        while ((pos < len) && Character.isWhitespace(s.charAt(pos++)));
197
198        if (pos == len) if (Character.isWhitespace(s.charAt(len-1))) return "";
199
200        return s.substring(pos - 1);
201    }
202
203    /**
204     * <EMBED CLASS='external-html' DATA-FILE-ID=STRP_SPC_PAD_L_DESC>
205     * @param s This may be any {@code java.lang.String}
206     * @param totalStringLength
207     * <EMBED CLASS='external-html' DATA-PREPOST=pre DATA-FILE-ID=STRP_SPC_PAD_TOTAL_LEN>
208     * @throws IllegalArgumentException If {@code totalStringLength} is zero or negative.
209     * @see #rightSpacePad(String, int)
210     */
211    public static String leftSpacePad(String s, int totalStringLength)
212    {
213        CHECK_NEGATIVE(totalStringLength);
214
215        return (s.length() >= totalStringLength) 
216            ? s 
217            : String.format("%1$" + totalStringLength + "s", s);
218    }
219
220    /**
221     * <EMBED CLASS='external-html' DATA-FILE-ID=STRP_SPC_PAD_R_DESC>
222     * @param s This may be any {@code java.lang.String}
223     * @param totalStringLength
224     * <EMBED CLASS='external-html' DATA-PREPOST=post DATA-FILE-ID=STRP_SPC_PAD_TOTAL_LEN>
225     * @throws IllegalArgumentException If {@code totalStringLength} is zero or negative.
226     * @see #leftSpacePad(String, int)
227     */
228    public static String rightSpacePad(String s, int totalStringLength)
229    {
230        CHECK_NEGATIVE(totalStringLength);
231
232        return (s.length() >= totalStringLength) 
233            ? s 
234            : String.format("%1$-" + totalStringLength + "s", s);
235    }
236
237    private static void CHECK_NEGATIVE(int totalStringLength)
238    {
239        if (totalStringLength <= 0) throw new IllegalArgumentException(
240            "totalString length was '" + totalStringLength + ", " +
241            "however it is expected to be a positive integer."
242        );
243    }
244
245    /**
246     * Runs a Regular-Expression over a {@code String} to retrieve all matches that occur between
247     * input {@code String} parameter {@code 's'} and Regular-Expression {@code 'regEx'}.
248     * 
249     * @param s Any Java {@code String}
250     * @param regEx Any Java Regular-Expression
251     * 
252     * @param eliminateOverlappingMatches When this parameter is passed {@code 'TRUE'}, successive
253     * matches that have portions which overlap each-other are eliminated.
254     * 
255     * @return An array of all {@code MatchResult's} (from package {@code 'java.util.regex.*'}) that
256     * were produced by iterating the {@code Matcher's} {@code 'find()'} method.
257     */
258    public static MatchResult[] getAllMatches
259        (String s, Pattern regEx, boolean eliminateOverlappingMatches)
260    {
261        Stream.Builder<MatchResult> b       = Stream.builder();
262        Matcher                     m       = regEx.matcher(s);
263        int                         prevEnd = 0;
264
265        while (m.find())
266        {
267            MatchResult matchResult = m.toMatchResult();
268
269            // This skip any / all overlapping matches - if the user has requested it
270            if (eliminateOverlappingMatches) if (matchResult.start() < prevEnd) continue;
271
272            b.accept(matchResult);
273
274            prevEnd = matchResult.end();
275        }
276
277        // Convert the Java-Stream into a Java-Array and return the result
278        return b.build().toArray(MatchResult[]::new);
279    }
280
281
282    // ********************************************************************************************
283    // ********************************************************************************************
284    // Helper set & get for strings
285    // ********************************************************************************************
286    // ********************************************************************************************
287
288
289    /**
290     * This sets a character in a {@code String} to a new value, and returns a result
291     * @param str Any java {@code String}
292     * @param i An index into the underlying character array of that {@code String}.
293     * @param c A new character to be placed at the <I>i'th position</I> of this {@code String}.
294     * 
295     * @return a new java {@code String}, with the appropriate index into the {@code String}
296     * substituted using character parameter {@code 'c'}.
297     */
298    public static String setChar(String str, int i, char c)
299    {
300        return ((i + 1) < str.length())
301            ? (str.substring(0, i) + c + str.substring(i + 1))
302            : (str.substring(0, i) + c);
303    }
304
305    /**
306     * This removes a character from a {@code String}, and returns a new {@code String} as a
307     * result.
308     * 
309     * @param str Any Java-{@code String}.
310     * 
311     * @param i This is the index into the underlying java {@code char}-array whose character will
312     * be removed from the return {@code String}.
313     * 
314     * @return Since Java {@code String}'s are all immutable, this {@code String} that is returned
315     * is completely new, with the character that was originally at index 'i' removed.
316     */
317    public static String delChar(String str, int i)
318    {
319        if ((i + 1) < str.length())
320            return str.substring(0, i) + str.substring(i + 1);
321        else
322            return str.substring(0, i);
323    }
324
325    /**
326     * Returns the same {@code String} is input, but trims all spaces down to a single space.
327     * Each and every <I>lone / independent or contiguous</I> white-space character is reduced
328     * to a single space-character.
329     * 
330     * <TABLE CLASS=JDBriefTable>
331     * <TR><TH>Input String</TH><TH>Output String</TH></TR>
332     * <TR><TD><PRE>{@code "This   has   extra   spaces\n"}</PRE></TD>
333     *     <TD>{@code "This has extra spaces "}</TD>
334     * </TR>
335     * <TR><TD>{@code "This does not"}</TD>
336     *     <TD>{@code "This does not"}</TD>
337     * </TR>
338     * <TR><TD>{@code "\tThis\nhas\ttabs\nand\tnewlines\n"}</TD>
339     *     <TD>{@code " This has tabs and newlines "}</TD>
340     * </TR>
341     * </TABLE>
342     *
343     * @param s Any Java {@code String}
344     * 
345     * @return A {@code String} where all white-space is compacted to a single space.  This is
346     * generally how HTML works, when it is displayed in a browser.
347     */
348    public static String removeDuplicateSpaces(String s)
349    { return StringParse.WHITE_SPACE_REGEX.matcher(s).replaceAll(" "); }
350
351    /**
352     * This string-modify method simply removes any and all white-space matches found within a
353     * java-{@code String}.
354     * 
355     * <TABLE CLASS=JDBriefTable>
356     * <TR><TH>Input String</TH><TH>Output String</TH></TR>
357     * <TR><TD><PRE>{@code "This   Has   Extra   Spaces\n"}</PRE></TD>
358     *     <TD>{@code "ThisHasExtraSpaces"}</TD>
359     * </TR>
360     * <TR><TD>{@code "This Does Not"}</TD>
361     *     <TD>{@code "ThisDoesNot"}</TD>
362     * </TR>
363     * <TR><TD>{@code "\tThis\nHas\tTabs\nAnd\tNewlines\n"}</TD>
364     *     <TD>{@code "ThisHasTabsAndNewlines"}</TD>
365     * </TR>
366     * </TABLE>
367     * 
368     * @param s Any {@code String}, but if it has any white-space (space that matches
369     * regular-expression: {@code \w+}) then those character-blocks will be removed
370     * 
371     * @return A new {@code String} without any {@code \w} (RegEx for 'whitespace')
372     * 
373     * @see #WHITE_SPACE_REGEX
374     */
375    public static String removeWhiteSpace(String s)
376    { return WHITE_SPACE_REGEX.matcher(s).replaceAll(""); }
377
378    /**
379     * Generates a {@code String} that contains {@code n} copies of character {@code c}.
380     * @return {@code n} copies of {@code c}, as a {@code String}.
381     * @throws IllegalArgumentException If the value passed to parameter {@code 'n'} is negative
382     * @see StrSource#caretBeneath(String, int)
383     */
384    public static String nChars(char c, int n)
385    {
386        if (n < 0) throw new IllegalArgumentException("Value of parameter 'n' is negative: " + n);
387
388        char[] cArr = new char[n];
389        Arrays.fill(cArr, c);
390        return new String(cArr);
391    }
392
393    /**
394     * Generates a {@code String} that contains {@code n} copies of {@code s}.
395     * @return {@code n} copies of {@code s} as a {@code String}.
396     * @throws NException if the value provided to parameter {@code 'n'} is negative.
397     */
398    public static String nStrings(String s, int n)
399    {
400        if (n < 0) throw new NException("A negative value was passed to 'n' [" + n + ']');
401
402        StringBuilder sb = new StringBuilder();
403
404        for (int i=0; i < n; i++) sb.append(s);
405
406        return sb.toString();
407    }
408
409    /**
410     * This method checks whether or not a java-{@code String} has white-space.
411     * 
412     * @param s Any Java-{@code String}.  If this {@code String} has any white-space, this method
413     * will return {@code TRUE}
414     * 
415     * @return {@code TRUE} If there is any white-space in this method, and {@code FALSE} otherwise.
416     * 
417     * @see #WHITE_SPACE_REGEX
418     */
419    public static boolean hasWhiteSpace(String s)
420    { return WHITE_SPACE_REGEX.matcher(s).find(); }
421
422    /**
423     * Counts the number of instances of character input {@code char c} contained by the
424     * input {@code String s}
425     * 
426     * @param s Any {@code String} containing any combination of ASCII/UniCode characters
427     * 
428     * @param c Any ASCII/UniCode character.
429     * 
430     * @return The number of times {@code char c} occurs in {@code String s}
431     */
432    public static int countCharacters(String s, char c)
433    {
434        int count = 0;
435        int pos   = 0;
436        while ((pos = s.indexOf(c, pos + 1)) != -1) count++;
437        return count;
438    }
439
440
441    /**
442     * If the {@code String} passed to this method contains a single-quote on both sides of the
443     * {@code String}, or if it contains a double-quote on both sides of this {@code String}, then
444     * this method shall return a new {@code String} that is shorter in length by 2, and leaves off
445     * the first and last characters of the input parameter {@code String}.
446     * 
447     * <BR /><BR /><B>HOPEFULLY,</B> The name of this method explains clearly what this method does
448     *
449     * @param s This may be any java {@code String}.  Only {@code String's} whose first and last
450     * characters are not only quotation marks (single or double), but also they are <B>the same,
451     * identical, quotation marks on each side.</B>
452     * 
453     * @return A new {@code String} that whose first and last quotation marks are gone - if they
454     * were there when this method began.
455     */
456    public static String ifQuotesStripQuotes(String s)
457    {
458        if (s == null)      return null;
459        if (s.length() < 2) return s;
460
461        int lenM1 = s.length() - 1; // Position of the last character in the String
462
463        if (    ((s.charAt(0) == '\"')  && (s.charAt(lenM1) == '\"'))       // String has Double-Quotation-Marks
464                                        ||                                  //            ** or ***
465                ((s.charAt(0) == '\'')  && (s.charAt(lenM1) == '\''))  )    // String has Single-Quotation-Marks
466            return s.substring(1, lenM1);
467        else  
468            return s;
469    }
470
471    /**
472     * Counts the number of lines of text inside of a Java {@code String}.
473     * 
474     * @param text This may be any text, as a {@code String}.
475     * 
476     * @return Returns the number of lines of text.  The integer returned shall be precisely
477     * equal to the number of {@code '\n'} characters <B><I>plus one!</I></B>
478     */
479    public static int numLines(String text)
480    {
481        if (text.length() == 0) return 0;
482
483        int pos     = -1;
484        int count   = 0;
485
486        do
487        {
488            pos = text.indexOf('\n', pos + 1);
489            count++;
490        }
491        while (pos != -1);
492
493        return count;
494    }
495
496
497
498    // ********************************************************************************************
499    // ********************************************************************************************
500    // Find / Front Last-Front-Slash
501    // ********************************************************************************************
502    // ********************************************************************************************
503
504
505    /**
506     * This function finds the position of the last "front-slash" character {@code '/'} in a
507     * java-{@code String}
508     * 
509     * @param urlOrDir This is any java-{@code String}, but preferably one that is a
510     * {@code URL}, or directory.
511     * 
512     * @return The {@code String}-index of the last 'front-slash' {@code '/'} position in a
513     * {@code String}, or {@code -1} if there are not front-slashes.
514     */
515    public static int findLastFrontSlashPos(String urlOrDir)
516    { return urlOrDir.lastIndexOf('/'); }
517
518    /**
519     * This returns the contents of a {@code String}, after the last front-slash found.
520     * 
521     * <BR /><BR /><B>NOTE:</B> If not front-slash {@code '/'} character is found, then the
522     * original {@code String} is returned.
523     *
524     * @param urlOrDir This is any java-{@code String}, but preferably one that is a
525     * {@code URL}, or directory.
526     * 
527     * @return the portion of the {@code String} after the final front-slash {@code '/'} character.
528     * If there are no front-slash characters found in this {@code String}, then the original
529     * {@code String} shall be returned.
530     */
531    public static String fromLastFrontSlashPos(String urlOrDir)
532    {
533        int pos = urlOrDir.lastIndexOf('/');
534        if (pos == -1) return urlOrDir;
535        return urlOrDir.substring(pos + 1);
536    }
537
538    /**
539     * This returns the contents of a {@code String}, before the last front-slash found (including
540     * the front-slash {@code '/'} itself).
541     * 
542     * <BR /><BR /><B>NOTE:</B> If no front-slash {@code '/'} character is found, then null is
543     * returned.
544     *
545     * @param urlOrDir This is any java-{@code String}, but preferably one that is a
546     * {@code URL}, or directory.
547     * 
548     * @return the portion of the {@code String} <I><B>before and including</B></I> the final
549     * front-slash {@code '/'} character.  If there are no front-slash characters found in this 
550     * {@code String}, then null.
551     */
552    public static String beforeLastFrontSlashPos(String urlOrDir)
553    {
554        int pos = urlOrDir.lastIndexOf('/');
555        if (pos == -1) return null;
556        return urlOrDir.substring(0, pos + 1);
557    }
558
559
560    // ********************************************************************************************
561    // ********************************************************************************************
562    // Find / From Last-File-Separator
563    // ********************************************************************************************
564    // ********************************************************************************************
565
566
567    /**
568     * This function finds the position of the last {@code 'java.io.File.separator'} character in a
569     * java-{@code String}. In UNIX-based systems, this is a forward-slash {@code '/'} character,
570     * but in Windows-MSDOS, this is a back-slash {@code '\'} character.  Identifying which of the
571     * two is used is obtained by "using" Java's {@code File.separator} class and field.
572     *
573     * @param fileOrDir This may be any Java-{@code String}, but preferably one that represents a
574     * file or directory.
575     * 
576     * @return The {@code String}-index of the last 'file-separator' position in a {@code String},
577     * or {@code -1} if there are no such file-separators.
578     */
579    public static int findLastFileSeparatorPos(String fileOrDir)
580    { return fileOrDir.lastIndexOf(File.separator.charAt(0)); }
581
582    /**
583     * This returns the contents of a {@code String}, after the last
584     * {@code 'java.io.File.separator'} found. 
585     * 
586     * <BR /><BR /><B>NOTE:</B> If no {@code 'java.io.File.separator'} character is found, then
587     * the original {@code String} is returned.
588     *
589     * @param fileOrDir This is any java-{@code String}, but preferably one that is a filename or
590     * directory-name
591     * 
592     * @return the portion of the {@code String} after the final  {@code 'java.io.File.separator'}
593     * character.  If there are no such characters found, then the original {@code String} shall
594     * be returned.
595     */
596    public static String fromLastFileSeparatorPos(String fileOrDir)
597    {
598        int pos = fileOrDir.lastIndexOf(File.separator.charAt(0));
599        if (pos == -1) return fileOrDir;
600        return fileOrDir.substring(pos + 1);
601    }
602
603    /**
604     * This returns the contents of a {@code String}, before the last
605     * {@code 'java.io.File.separator'} (including the separator itself).
606     * 
607     * <BR /><BR /><B>NOTE:</B> If no {@code 'java.io.File.separator'} character is found,
608     * then null is returned.
609     *
610     * @param urlOrDir This is any java-{@code String}, but preferably one that is a
611     * {@code URL}, or directory.
612     * 
613     * @return the portion of the {@code String} <I><B>before and including</B></I> the final
614     * {@code 'java.io.File.separator'} character.  If there are no such characters found in this 
615     * {@code String}, then null is returned.
616     */
617    public static String beforeLastFileSeparatorPos(String urlOrDir)
618    {
619        int pos = urlOrDir.lastIndexOf(File.separator.charAt(0));
620        if (pos == -1) return null;
621        return urlOrDir.substring(0, pos + 1);
622    }
623
624
625    // ********************************************************************************************
626    // ********************************************************************************************
627    // Find / From File-Extension
628    // ********************************************************************************************
629    // ********************************************************************************************
630
631
632    /**
633     * This method swaps the ending 'File Extension' with another, parameter-provided, extension.
634     * @param fileNameOrURLWithExtension Any file-name (or {@code URL}) that has an extension.
635     * @param newExtension <EMBED CLASS='external-html' DATA-FILE-ID=STRP_SWAP_EXT_NEWEX>
636     * @return The new file-name or {@code URL} having the substituted extension.
637     * @throws StringFormatException 
638     */
639    public static String swapExtension(String fileNameOrURLWithExtension, String newExtension)
640    {
641        final int dotPos = fileNameOrURLWithExtension.lastIndexOf('.');
642
643        if (dotPos == -1) throw new StringFormatException(
644            "The file-name provided\n[" + fileNameOrURLWithExtension + "]\n" +
645            "does not have a file-extension"
646        );
647
648        if (newExtension.length() == 0) throw new StringFormatException(
649            "The new file-name extension has length 0.  " +
650            " To remove an extension, use 'StringParse.removeFileExtension(fileName)'"
651        );
652
653        return (newExtension.charAt(0) == '.')
654            ? fileNameOrURLWithExtension.substring(0, dotPos) + newExtension
655            : fileNameOrURLWithExtension.substring(0, dotPos) + '.' + newExtension;
656    }
657
658    /**
659     * <EMBED CLASS='external-html' DATA-FILE-ID=STRP_REM_EXT_DESC>
660     * @param fileNameOrURL Any file-name or {@code URL}, as a {@code String}.
661     * @return <EMBED CLASS='external-html' DATA-FILE-ID=STRP_REM_EXT_RET>
662     */
663    public static String removeExtension(String fileNameOrURL)
664    {
665        final int dotPos = fileNameOrURL.lastIndexOf('.');
666
667        return (dotPos == -1)
668            ? fileNameOrURL
669            : fileNameOrURL.substring(0, dotPos);
670    }
671
672    /**
673     * <EMBED CLASS='external-html' DATA-FILE-ID=STRP_FIND_EXT_DESC>
674     * @param file This may be any Java-{@code String}, but preferably one that represents a file.
675     * @param includeDot <EMBED CLASS='external-html' DATA-FILE-ID=STRP_FIND_EXT_INCL>
676     * @return <EMBED CLASS='external-html' DATA-FILE-ID=STRP_FIND_EXT_RET>
677     */
678    public static int findExtension(String file, boolean includeDot)
679    {
680        int pos = file.lastIndexOf('.');
681
682        if (pos == -1)                  return -1;
683        else if (includeDot)            return pos;
684        else if (++pos < file.length()) return pos;
685        else                            return -1;
686    }
687
688    /**
689     * <EMBED CLASS='external-html' DATA-FILE-ID=STRP_FROM_EXT_DESC>
690     * @param file This is any java-{@code String}, but preferably one that is a filename.
691     * 
692     * @param includeDot Indicates whether the period {@code '.'} is to be included in the
693     * returned-{@code String}.
694     * 
695     * @return <EMBED CLASS='external-html' DATA-FILE-ID=STRP_FROM_EXT_RET>
696     */
697    public static String fromExtension(String file, boolean includeDot)
698    {
699        final int pos = findExtension(file, includeDot);
700
701        return (pos == -1)
702            ? null
703            : file.substring(pos);
704    }
705
706    /**
707     * <EMBED CLASS='external-html' DATA-FILE-ID=STRP_BEFORE_EXT_DESC>
708     * @param file This is any java-{@code String}, but preferably one that is a filename.
709     * @return <EMBED CLASS='external-html' DATA-FILE-ID=STRP_BEFORE_EXT_RET>
710     */
711    public static String beforeExtension(String file)
712    {
713        final int pos = file.lastIndexOf('.');
714
715        return (pos == -1)
716            ? file
717            : file.substring(0, pos);
718    }
719
720    /**
721     * <EMBED CLASS='external-html' DATA-FILE-ID=STRP_BEFORE_EXT_DESC>
722     * @param url A URL as a {@code String} - like {@code http://domain/directory/[file]}
723     * @return substring(0, index of last front-slash ({@code '/'}) in {@code String})
724     */
725    public static String findURLRoot(String url)
726    {
727        final int pos = findLastFrontSlashPos(url);
728
729        return (pos == -1)
730            ? null
731            : url.substring(0, pos + 1);
732    }
733
734    /**
735     * String text that is situated before the first white-space character in the string.
736     * @return After breaking the {@code String} by white-space, this returns the first 'chunk'
737     * before the first whitespace.
738     */
739    public static String firstWord(String s)
740    {
741        final int pos = s.indexOf(" ");
742
743        return (pos == -1)
744            ? s
745            :  s.substring(0, pos);
746    }
747
748
749    // ********************************************************************************************
750    // ********************************************************************************************
751    // Removing parts of a string
752    // ********************************************************************************************
753    // ********************************************************************************************
754
755
756    /**
757     * This function will remove any pairs of Brackets within a {@code String}, and returned the
758     * paired down {@code String}
759     * 
760     * @param s <EMBED CLASS='external-html' DATA-FILE-ID=STRP_RMV_BRACKETS_S>
761     * @return The same {@code String}, but with any bracket-pairs removed.
762     */
763    public static String removeBrackets(String s) { return remove_(s, '[', ']'); }
764
765    /**
766     * <EMBED CLASS='external-html' DATA-FILE-ID=STRP_RMV_BRACES_DESC>
767     * @param s <EMBED CLASS='external-html' DATA-FILE-ID=STRP_RMV_BRACES_S>
768     * @return The same {@code String}, but with any curly-brace-pairs removed.
769     */
770    public static String removeBraces(String s) { return remove_(s, '{', '}'); }
771
772    /**
773     * Removes Parenthesis, similar to other parenthetical removing functions.
774     * @param s <EMBED CLASS='external-html' DATA-FILE-ID=STRP_RMV_PARENS_S>
775     * @return The same {@code String}, but with any parenthesis removed.
776     */
777    public static String removeParens(String s) { return remove_(s, '(', ')'); }
778
779    /**
780     * Removes all parenthetical notations.  Calls all <I><B>remove functions</B></I>
781     * @param s Any valid string
782     * @return The same string, but with all parenthesis, curly-brace &amp; bracket pairs removed.
783     * @see #removeParens(String)
784     * @see #removeBraces(String)
785     * @see #removeBrackets(String)
786     */
787    public static String removeAllParenthetical(String s)
788    { return removeParens(removeBraces(removeBrackets(s))); }
789    
790    private static String remove_(String s, char left, char right)
791    {
792        int p = s.indexOf(left);
793        if (p == -1) return s;
794
795        String ret = s.substring(0, p).trim();
796
797        for (++p; (s.charAt(p) != right) && (p < s.length()); p++);
798
799        if (p >= (s.length() - 1)) return ret;
800
801        ret += " " + s.substring(p + 1).trim();
802
803        if (ret.indexOf(left) != -1)    return remove_(ret.trim(), left, right);
804        else                            return ret.trim();
805    }
806
807
808
809    // ********************************************************************************************
810    // ********************************************************************************************
811    // Base-64 Encoded Java Objects
812    // ********************************************************************************************
813    // ********************************************************************************************
814
815
816    /**
817     * <EMBED CLASS='external-html' DATA-FILE-ID=STRP_OBJ_2_B64_STR_DESC>
818     * @param o Any java {@code java.lang.Object}.  This object must be Serializable, or throws.
819     * @return <EMBED CLASS='external-html' DATA-FILE-ID=STRP_OBJ_2_B64_STR_RET>
820     * @see #b64StrToObj(String)
821     */
822    @LinkJavaSource(handle="B64", name="objToB64Str")
823    public static String objToB64Str(Object o) throws IOException
824    { return B64.objToB64Str(o); }
825
826    /**
827     * <EMBED CLASS='external-html' DATA-FILE-ID=STRP_B64_STR_2_OBJ_DESC>
828     * @param str <EMBED CLASS='external-html' DATA-FILE-ID=STRP_B64_STR_2_OBJ_STR>
829     * @return A de-compressed {@code java.lang.Object} converted back from a B64-{@code String}
830     * @see #objToB64Str(Object)
831     */
832    @LinkJavaSource(handle="B64", name="b64StrToObj")
833    public static Object b64StrToObj(String str) throws IOException
834    { return B64.b64StrToObj(str); }
835
836    /**
837     * <EMBED CLASS='external-html' DATA-FILE-ID=STRP_O_2_B64_MSTR_DESC>
838     * @param o <EMBED CLASS='external-html' DATA-FILE-ID=STRP_O_2_B64_MSTR_O>
839     * @return A Base-64 MIME-Encoded {@code String} of any serializable {@code java.lang.Object}
840     * @see #objToB64Str(Object)
841     * @see #b64MimeStrToObj(String)
842     */
843    @LinkJavaSource(handle="B64", name="objToB64MimeStr")
844    public static String objToB64MimeStr(Object o) throws IOException
845    { return B64.objToB64MimeStr(o); }
846
847    /**
848     * <EMBED CLASS='external-html' DATA-FILE-ID=STRP_B64_MSTR_2_O_DESC>
849     * @return <EMBED CLASS='external-html' DATA-FILE-ID=STRP_B64_MSTR_2_O_RET>
850     * @see #b64StrToObj(String)
851     * @see #objToB64MimeStr(Object)
852     */
853    @LinkJavaSource(handle="B64", name="b64MimeStrToObj")
854    public static Object b64MimeStrToObj(String str) throws IOException
855    { return B64.b64MimeStrToObj(str); }
856
857
858    // ********************************************************************************************
859    // ********************************************************************************************
860    // '../' (Parent Directory)
861    // ********************************************************************************************
862    // ********************************************************************************************
863
864
865    /**
866     * Computes a "relative {@code URL String}".
867     * @param fileName This is a fileName whose ancestor directory needs to be <I>relative-ized</I>
868     * @param ancestorDirectory This is an ancestor (container) directory.
869     * @param separator The separator character used to separate file-system directory names.
870     * @return                          <EMBED CLASS='external-html' DATA-FILE-ID=STRP_DOTDOT_RET>
871     * @throws IllegalArgumentException <EMBED CLASS='external-html' DATA-FILE-ID=STRP_DOTDOT_IAEX>
872     */
873    @LinkJavaSource(handle="DotDots", name="specifiedAncestor")
874    public static String dotDots(String fileName, String ancestorDirectory, char separator)
875    { return DotDots.specifiedAncestor(fileName, ancestorDirectory, separator); }
876
877
878    /**
879     * <BR>See Docs: Method {@link #dotDotParentDirectory(String, char, short)}
880     * <BR>Converts: Input {@code URL} to a {@code String} - eliminates non-essential 
881     * {@code URI}-information (Such as: {@code Query-Strings, and others as well})
882     * <BR>Passes: Character {@code char '/'}, the separator character used in {@code URL's}
883     * <BR>Passes: A {@code '1'} to parameter {@code 'nLevels'} - only going up one directory
884     */
885    @IntoHTMLTable(background=BlueDither, title="Rerieve the Parent-Directory of a URL")
886    @LinkJavaSource(handle="DotDots", name="parentDir")
887    public static String dotDotParentDirectory(URL url)
888    {
889        final String urlStr = url.getProtocol() + "://" + url.getHost() + url.getPath();
890        return DotDots.parentDir(urlStr, '/', (short) 1);
891    }
892
893
894    /**
895     * <BR>See Docs: Method {@link #dotDotParentDirectory(String, char, short)}
896     * <BR>Passes: {@code char '/'}, the separator character used in {@code URL's}
897     * <BR>Passes: {@code '1'} to parameter {@code 'nLevels'} - only going up on directory
898     */
899    @IntoHTMLTable(
900        background=GreenDither,
901        title="Rerieve the Parent-Directory of a URL as a String"
902    )
903    @LinkJavaSource(handle="DotDots", name="parentDir")
904    public static String dotDotParentDirectory(String urlAsStr)
905    { return DotDots.parentDir(urlAsStr, '/', (short) 1); }
906
907
908    /**
909     * <BR>See Docs: Method {@link #dotDotParentDirectory(String, char, short)}
910     * <BR>Converts: {@code URL} to {@code String}, eliminates non-essential
911     * {@code URI}-information (such as Query-Strings, etc.)
912     * <BR>Passes: A Separator-Character {@code char '/'} - the separator used in {@code URL's}
913     */
914    @IntoHTMLTable(
915        background=BlueDither,
916        title="Rerieve the n<SUP>th</SUP> Ancestory-Directory of a URL"
917    )
918    @LinkJavaSource(handle="DotDots", name="parentDir")
919    public static String dotDotParentDirectory(URL url, short nLevels)
920    {
921        final String urlStr = url.getProtocol() + "://" + url.getHost() + url.getPath();
922        return DotDots.parentDir(urlStr, '/', nLevels);
923    }
924
925
926    /** 
927     * <BR>See Docs: Method {@link #dotDotParentDirectory(String, char, short)}
928     * <BR>Passes: {@code char '/'}, the separator character used in {@code URL's}
929     */
930    @IntoHTMLTable(
931        background=GreenDither,
932        title="Rerieve the n<SUP>th</SUP> Ancestory-Directory of a URL as a String"
933    )
934    @LinkJavaSource(handle="DotDots", name="parentDir")
935    public static String dotDotParentDirectory(String urlAsStr, short nLevels)
936    { return DotDots.parentDir(urlAsStr, '/', nLevels); }
937
938
939    /**
940     * <BR>See Docs: Method {@link #dotDotParentDirectory(String, char, short)}
941     * <BR>Passes: {@code '1'} to parameter {@code nLevels} - only going up one directory.
942     */ 
943    @IntoHTMLTable(
944        background=BlueDither,
945        title="Rerieve the Parent-Directory from String, use a Specified Separator-Char"
946    )
947    @LinkJavaSource(handle="DotDots", name="parentDir")
948    public static String dotDotParentDirectory(String directoryStr, char dirSeparator)
949    { return DotDots.parentDir(directoryStr, dirSeparator, (short) 1); }
950
951
952    /**
953     * <EMBED CLASS='external-html' DATA-FILE-ID=STRP_DOTDOT_PD_DESC>
954     * @param directoryStr  <EMBED CLASS='external-html' DATA-FILE-ID=STRP_DOTDOT_PD_DSTR> 
955     * @param separator     <EMBED CLASS='external-html' DATA-FILE-ID=STRP_DOTDOT_PD_SEP>
956     * @param nLevels       <EMBED CLASS='external-html' DATA-FILE-ID=STRP_DOTDOT_PD_NLVL> 
957     * @return              <EMBED CLASS='external-html' DATA-FILE-ID=STRP_DOTDOT_PD_RET> 
958     * 
959     * @throws IllegalArgumentException
960     * <EMBED CLASS='external-html' DATA-FILE-ID=STRP_DOTDOT_PD_IAEX> 
961     */
962    @LinkJavaSource(handle="DotDots", name="parentDir")
963    public static String dotDotParentDirectory(String directoryStr, char separator, short nLevels)
964    { return DotDots.parentDir(directoryStr, separator, nLevels); }
965
966
967    // ********************************************************************************************
968    // ********************************************************************************************
969    // Quick 'isNumber' methods
970    // ********************************************************************************************
971    // ********************************************************************************************
972
973
974    /**
975     * <EMBED CLASS='external-html' DATA-FILE-ID=SPA_IS_INTEGER_DESC>
976     * @param s Any java {@code String}
977     * @return  <EMBED CLASS='external-html' DATA-FILE-ID=SPA_IS_INTEGER_RET>
978     * @see     #isInt(String)
979     */
980    @LinkJavaSource(handle="IOPT", name="isInteger")
981    public static boolean isInteger(String s)
982    { return IsOfPrimitiveType.isInteger(s); }
983
984
985    /** <BR>Passes: The ASCII characters that comprise {@code Integer.MIN_VALUE} */
986    @IntoHTMLTable(
987        background=BlueDither,
988        title="Check if a Java-String Comprises a Valid Integer"
989    )
990    @LinkJavaSource(handle="IOPT", entity=FIELD, name="INT_MIN_VALUE_DIGITS_AS_CHARS")
991    @LinkJavaSource(handle="IOPT", name="check")
992    public static boolean isInt(String s)
993    { return IsOfPrimitiveType.check(s, IsOfPrimitiveType.INT_MIN_VALUE_DIGITS_AS_CHARS); }
994
995
996    /** <BR>Passes: The ASCII characters that comprise {@code Long.MIN_VALUE} */
997    @IntoHTMLTable(
998        background=GreenDither,
999        title="Check if a Java-String Comprises a Valid Long-Integer"
1000    )
1001    @LinkJavaSource(handle="IOPT", entity=FIELD, name="LONG_MIN_VALUE_DIGITS_AS_CHARS")
1002    @LinkJavaSource(handle="IOPT", name="check")
1003    public static boolean isLong(String s)
1004    { return IsOfPrimitiveType.check(s, IsOfPrimitiveType.LONG_MIN_VALUE_DIGITS_AS_CHARS); }
1005
1006
1007    /** <BR>Passes: ASCII characters that comprise {@code Byte.MIN_VALUE} */
1008    @IntoHTMLTable(
1009        background=BlueDither,
1010        title="Check if a Java-String Comprises a Valid Byte"
1011    )
1012    @LinkJavaSource(handle="IOPT", entity=FIELD, name="BYTE_MIN_VALUE_DIGITS_AS_CHARS")
1013    @LinkJavaSource(handle="IOPT", name="check")
1014    public static boolean isByte(String s)
1015    { return IsOfPrimitiveType.check(s, IsOfPrimitiveType.BYTE_MIN_VALUE_DIGITS_AS_CHARS); }
1016
1017
1018    /** <BR>Passes: ASCII characters that comprise {@code Short.MIN_VALUE} */
1019    @IntoHTMLTable(
1020        background=GreenDither,
1021        title="Check if a Java-String Comprises a Valid Short-Integer"
1022    )
1023    @LinkJavaSource(handle="IOPT", entity=FIELD, name="SHORT_MIN_VALUE_DIGITS_AS_CHARS")
1024    @LinkJavaSource(handle="IOPT", name="check")
1025    public static boolean isShort(String s)
1026    { return IsOfPrimitiveType.check(s, IsOfPrimitiveType.SHORT_MIN_VALUE_DIGITS_AS_CHARS); }
1027
1028
1029    /**
1030     * <EMBED CLASS='external-html' DATA-FILE-ID=SPA_IS_PRMTYPE_DESC>
1031     * @param s Any Java <CODE>String</CODE>
1032     * @param minArr    <EMBED CLASS='external-html' DATA-FILE-ID=SPA_IS_PRMTYPE_MARR>
1033     * @return          <EMBED CLASS='external-html' DATA-FILE-ID=SPA_IS_PRMTYPE_RET>
1034     * @see #isInteger(String)
1035     * @see #isInt(String)
1036     * @see #isByte(String)
1037     * @see #isLong(String)
1038     * @see #isShort(String)
1039     */
1040    @LinkJavaSource(handle="IOPT", name="check")
1041    protected static boolean isOfPrimitiveType(String s, char[] minArr)
1042    { return IsOfPrimitiveType.check(s, minArr); }
1043
1044
1045    /**
1046     * <EMBED CLASS='external-html' DATA-FILE-ID=STRP_FLOAT_PT_REGEX>
1047     * <EMBED CLASS='external-html' DATA-FILE-ID=STRP_D_VALUEOF>
1048     * @see #floatingPointPred
1049     * @see #isDouble(String)
1050     */
1051    @LinkJavaSource(handle="IOPT", entity=FIELD, name="FLOATING_POINT_REGEX")
1052    public static final Pattern FLOATING_POINT_REGEX = IsOfPrimitiveType.FLOATING_POINT_REGEX;
1053
1054
1055    /**
1056     * This is the floating-point regular-expression, simply converted to a predicate.
1057     * @see #FLOATING_POINT_REGEX
1058     * @see #isDouble(String)
1059     */
1060    @LinkJavaSource(handle="IOPT", entity=FIELD, name="FLOATING_POINT_REGEX")
1061    public static final Predicate<String> floatingPointPred =
1062        IsOfPrimitiveType.FLOATING_POINT_REGEX.asPredicate();
1063
1064
1065    /**
1066     * Tests whether an input-{@code String} can be parsed into a {@code double}
1067     * @return <EMBED CLASSS='external-html' DATA-FILE-ID=SPA_IS_DOUBLE_RET>
1068     * @see #FLOATING_POINT_REGEX
1069     * @see #floatingPointPred
1070     */
1071    @LinkJavaSource(handle="IOPT", entity=FIELD, name="FLOATING_POINT_REGEX")
1072    public static boolean isDouble(String s)
1073    { return floatingPointPred.test(s); }
1074}