001package Torello.Java;
002
003import Torello.JavaDoc.LinkJavaSource;
004
005import Torello.Java.ReadOnly.ReadOnlyArrayList;
006import Torello.Java.ReadOnly.ReadOnlyList;
007
008import Torello.Java.Function.IntTFunction;
009import static Torello.Java.C.RESET;
010
011import java.util.Calendar;
012import java.util.Locale;
013import java.util.Arrays;
014
015import java.text.DecimalFormat;
016
017/**
018 * This class provides several {@code String} printing utilities such as abbreviation and list
019 * printing.
020 */
021@Torello.JavaDoc.StaticFunctional
022public class StrPrint
023{
024    private StrPrint() { }
025
026
027    // ********************************************************************************************
028    // ********************************************************************************************
029    // HELPER & BASIC
030    // ********************************************************************************************
031    // ********************************************************************************************
032
033
034    /**
035     * <EMBED CLASS='external-html' DATA-FILE-ID=SP_NLAT_DESC>
036     * 
037     * @param s Any {@code java.lang.String}, preferably one with multiple lines of text.
038     * 
039     * @return A {@code String}, where each line of text has been "trimmed", and the two
040     * character sequence {@code "\\n"} inserted in-between each line.
041     * 
042     * @see #abbrevStartRDSF(String, int, boolean)
043     * @see #abbrevEndRDSF(String, int, boolean)
044     */
045    public static String newLinesAsText(String s)
046    {
047        return s
048            .replaceAll(
049                    // White-Space-Except-Newline, THEN newline, THEN White-SpaceExcept-Newline
050                    "[ \t\r\f\b]*\n[ \t\r\f\b]*",
051
052                    // Replace Each Occurence of that with:
053                    // == COMPILES-TO ==> "\\n" == REG-EX-READS ==> BackSlash and letter 'n'
054                    "\\\\n"
055            )
056            // == COMPILES-TO ==> "\s+" == REG-EX-READS ==> 'spaces'
057            .replaceAll("\\s+", " ")
058
059            // Don't forget about leading and trailing stuff...
060            .trim();
061    }
062
063
064    // ********************************************************************************************
065    // ********************************************************************************************
066    // Abbreviating Text, with "newLinesAsText" - Helper
067    // ********************************************************************************************
068    // ********************************************************************************************
069
070
071    /**
072     * Convenience Method.
073     * <BR /><B STYLE='color: red;'>RDSF: Remove Duplicate Spaces First</B>
074     * <BR />Invokes:    {@link StringParse#removeDuplicateSpaces(String)} 
075     * <BR />Or Invokes: {@link #newLinesAsText(String)}
076     * <BR />Finally:    {@link #abbrevStart(String, boolean, int)}
077     */
078    public static String abbrevStartRDSF
079        (String s, int maxLength, boolean seeEscapedNewLinesAsText)
080    {
081        // false is passed to 'abbrevStart' parameter 'escapeNewLines' because in both scenarios
082        // of this conditional-statement, the new-lines have already been removed by the previous
083        // method call.
084        //
085        // both 'removeDuplicateSpaces' and 'newLinesAsText' remove the new-lines
086
087        return seeEscapedNewLinesAsText
088            ? abbrevStart(newLinesAsText(s), false, maxLength)
089            : abbrevStart(StringParse.removeDuplicateSpaces(s), false, maxLength);
090    }
091
092    /**
093     * Convenience Method.
094     * <BR /><B STYLE='color: red;'>RDSF: Remove Duplicate Spaces First</B>
095     * <BR />Invokes: {@link StringParse#removeDuplicateSpaces(String)}
096     * <BR />Or Invokes: {@link #newLinesAsText(String)}
097     * <BR />Finally: {@link #abbrevEnd(String, boolean, int)}
098     */
099    public static String abbrevEndRDSF
100        (String s, int maxLength, boolean seeEscapedNewLinesAsText)
101    {
102        // false is passed to 'abbrevStart' parameter 'escapeNewLines' because in both scenarios
103        // of this conditional-statement, the new-lines have already been removed by the previous
104        // method call.
105        //
106        // both 'removeDuplicateSpaces' and 'newLinesAsText' remove the new-lines
107
108        return seeEscapedNewLinesAsText
109            ? abbrevEnd(newLinesAsText(s), false, maxLength)
110            : abbrevEnd(StringParse.removeDuplicateSpaces(s), false, maxLength);
111    }
112
113    /**
114     * Convenience Method.
115     * <BR />Passes: {@code '0'} to parameter {@code 'abbrevPos'}, forcing the abbreviation to
116     * occur at the <B>start</B> of the {@code String} (if long enough to be abbreviated)
117     * <BR />See Documentation: {@link #abbrev(String, int, boolean, String, int)}
118     */
119    @LinkJavaSource(handle="Abbrev", name="print1")
120    public static String abbrevStart(String s, boolean escNewLines, int maxLength)
121    { return Abbrev.print1(s, 0, escNewLines, null, maxLength); }
122
123    /**
124     * <EMBED CLASS='external-html' DATA-FILE-ID=SP_ABBREV_1_DESC>
125     * @param s This may be any Java (non-null) {@code String}
126     *
127     * @param abbrevPos This parameter is used to indicate where the abbreviation-{@code String}
128     * should occur - <I>if this {@code String 's'} is long enough to be abbreviated.</I>  For
129     * instance, if {@code '0'} (zero) were passed to this parameter, and {@code 's'} were longer
130     * than parameter {@code 'maxLength'}, then an ellipsis would be appended to the beginning of
131     * the returned-{@code 'String'}.  (Or, if some other {@code 'abbrevStr'} were specified, that
132     * other abbreviation would be appended to the beginning of the returned-{@code String})
133     * 
134     * @param escapeNewLines            <EMBED CLASS='external-html' DATA-FILE-ID=SP_ABBREV_ENL>
135     * @param abbrevStr                 <EMBED CLASS='external-html' DATA-FILE-ID=SP_ABBREV_STR>
136     * @param maxLength                 <EMBED CLASS='external-html' DATA-FILE-ID=SP_ABBREV_MXL>
137     * @return                          <EMBED CLASS='external-html' DATA-FILE-ID=SP_ABBREV_1_RET>
138     * @throws IllegalArgumentException <EMBED CLASS='external-html' DATA-FILE-ID=SP_ABBREV_1_IAEX>
139     */
140    @LinkJavaSource(handle="Abbrev", name="print1")
141    public static String abbrev(
142            String  s,
143            int     abbrevPos,
144            boolean escapeNewLines,
145            String  abbrevStr,
146            int     maxLength
147        )
148    { return Abbrev.print1(s, abbrevPos, escapeNewLines, abbrevStr, maxLength); }
149
150    /**
151     * Convenience Method.
152     * <BR />Parameter: {@code spaceBeforeAbbrev} set to {@code FALSE}
153     * <BR />Abbreviates: Default ellipsis ({@code '...'}) are placed at the end of the
154     * {@code String}
155     * <BR />See Documentation: {@link #abbrev(String, boolean, boolean, String, int)}
156     */
157    @LinkJavaSource(handle="Abbrev", name="print2")
158    public static String abbrevEnd(String s, boolean escapeNewLines, int maxLength)
159    { return Abbrev.print2(s, false, escapeNewLines, null, maxLength); }
160
161    /**
162     * <EMBED CLASS='external-html' DATA-FILE-ID=SP_ABBREV_2_DESC>
163     * @param s This may be any Java (non-null) {@code String}
164     *
165     * @param spaceBeforeAbbrev This ensures that for whatever variant of ellipsis being used, the
166     * space-character is inserted directly before appending the ellipsis {@code "..."} or the
167     * user-provided {@code 'abbrevStr'}.
168     *
169     * @param escapeNewLines            <EMBED CLASS='external-html' DATA-FILE-ID=SP_ABBREV_ENL>
170     * @param abbrevStr                 <EMBED CLASS='external-html' DATA-FILE-ID=SP_ABBREV_STR>
171     * @param maxLength                 <EMBED CLASS='external-html' DATA-FILE-ID=SP_ABBREV_MXL>
172     * @return                          <EMBED CLASS='external-html' DATA-FILE-ID=SP_ABBREV_2_RET>
173     * @throws IllegalArgumentException <EMBED CLASS='external-html' DATA-FILE-ID=SP_ABBREV_2_IAEX>
174     */
175    @LinkJavaSource(handle="Abbrev", name="print2")
176    public static String abbrev(
177            String  s,
178            boolean spaceBeforeAbbrev,
179            boolean escapeNewLines,
180            String  abbrevStr,
181            int     maxLength
182        )
183    { return Abbrev.print2(s, spaceBeforeAbbrev, escapeNewLines, abbrevStr, maxLength); }
184
185
186    // ********************************************************************************************
187    // ********************************************************************************************
188    // Abbreviated List Printing
189    // ********************************************************************************************
190    // ********************************************************************************************
191
192
193    /**
194     * Convenience Method.
195     * <BR />Passes: {@code Object.toString()} to {@code 'listItemPrinter'}
196     * <BR />See Documentation: {@link #printListAbbrev(Iterable, IntTFunction, int, int, boolean,
197     *  boolean, boolean)}
198     */
199    @LinkJavaSource(handle="PrintListAbbrev", name="print1")
200    public static <ELEM> String printListAbbrev(
201            Iterable<ELEM> list, int lineWidth, int indentation, boolean seeEscapedNewLinesAsText,
202            boolean printNulls, boolean showLineNumbers
203        )
204    {
205        return PrintListAbbrev.print1(
206            list, (int i, Object o) -> o.toString(), lineWidth, indentation,
207            seeEscapedNewLinesAsText, printNulls, showLineNumbers
208        );
209    }
210
211    /**
212     * <EMBED CLASS=defs DATA-LIST_TYPE=Array>
213     * <EMBED CLASS='external-html' DATA-FILE-ID=SP_PLA_DESCRIPTION>
214     * @param list Any iterable-list of Java Object's
215     * 
216     * @param listItemPrinter
217     * <EMBED CLASS=external-html DATA-FILE-ID=SP_PLA_LIST_ITEM_PR>
218     * 
219     * @param lineWidth                 <EMBED CLASS=external-html DATA-FILE-ID=SP_PLA_LINE_WIDTH>
220     * @param indentation               <EMBED CLASS=external-html DATA-FILE-ID=SP_PLA_INDENTATION>
221     * @param seeEscapedNewLinesAsText  <EMBED CLASS=external-html DATA-FILE-ID=SP_PLA_SEE_ESC_NL>
222     * @param printNulls                <EMBED CLASS=external-html DATA-FILE-ID=SP_PLA_PRINT_NULLS>
223     * @param showLineNumbers           <EMBED CLASS=external-html DATA-FILE-ID=SP_PLA_SHOW_LNUMS>
224     * @return                          <EMBED CLASS=external-html DATA-FILE-ID=SP_PLA_RETURNS>
225     * 
226     * @see #zeroPad10e2(int)
227     * @see #abbrevEndRDSF(String, int, boolean)
228     * @see StringParse#nChars(char, int)
229     * @see StringParse#trimLeft(String)
230     */
231    @LinkJavaSource(handle="PrintListAbbrev", name="print1")
232    public static <ELEM> String printListAbbrev(
233            Iterable<ELEM>                      list,
234            IntTFunction<? super ELEM, String>  listItemPrinter,
235            int                                 lineWidth,
236            int                                 indentation,
237            boolean                             seeEscapedNewLinesAsText,
238            boolean                             printNulls,
239            boolean                             showLineNumbers
240        )
241    {
242        return PrintListAbbrev.print1(
243            list, listItemPrinter, lineWidth, indentation, seeEscapedNewLinesAsText,
244            printNulls, showLineNumbers
245        );
246    }
247
248    /**
249     * Convenience Method.
250     * <BR />Passes: {@code Object.toString()} to {@code 'listItemPrinter'}
251     * <BR />See Documentation: {@link #printListAbbrev(Object[], IntTFunction, int, int, boolean,
252     *      boolean, boolean)}
253     */
254    @LinkJavaSource(handle="PrintListAbbrev", name="print2")
255    public static <ELEM> String printListAbbrev(
256            ELEM[]  arr,
257            int     lineWidth,
258            int     indentation,
259            boolean seeEscapedNewLinesAsText,
260            boolean printNulls,
261            boolean showLineNumbers
262        )
263    {
264        return PrintListAbbrev.print2(
265            arr, (int i, Object o) -> o.toString(), lineWidth, indentation,
266            seeEscapedNewLinesAsText, printNulls, showLineNumbers
267        );
268    }
269
270    /**
271     * <EMBED CLASS=defs DATA-LIST_TYPE=Array>
272     * <EMBED CLASS='external-html' DATA-FILE-ID=SP_PLA_DESCRIPTION>
273     * @param list Any iterable-list of Java Object's
274     * 
275     * @param listItemPrinter
276     * <EMBED CLASS=external-html DATA-FILE-ID=SP_PLA_LIST_ITEM_PR>
277     * 
278     * @param lineWidth                 <EMBED CLASS=external-html DATA-FILE-ID=SP_PLA_LINE_WIDTH>
279     * @param indentation               <EMBED CLASS=external-html DATA-FILE-ID=SP_PLA_INDENTATION>
280     * @param seeEscapedNewLinesAsText  <EMBED CLASS=external-html DATA-FILE-ID=SP_PLA_SEE_ESC_NL>
281     * @param printNulls                <EMBED CLASS=external-html DATA-FILE-ID=SP_PLA_PRINT_NULLS>
282     * @param showLineNumbers           <EMBED CLASS=external-html DATA-FILE-ID=SP_PLA_SHOW_LNUMS>
283     * @return                          <EMBED CLASS=external-html DATA-FILE-ID=SP_PLA_RETURNS>
284     * 
285     * @see #zeroPad10e2(int)
286     * @see #abbrevEndRDSF(String, int, boolean)
287     * @see StringParse#trimLeft(String)
288     */
289    @LinkJavaSource(handle="PrintListAbbrev", name="print2")
290    public static <ELEM> String printListAbbrev(
291            ELEM[]                              list,
292            IntTFunction<? super ELEM, String>  listItemPrinter,
293            int                                 lineWidth,
294            int                                 indentation,
295            boolean                             seeEscapedNewLinesAsText,
296            boolean                             printNulls,
297            boolean                             showLineNumbers
298        )
299    {
300        return PrintListAbbrev.print2(
301            list, listItemPrinter, lineWidth, indentation, seeEscapedNewLinesAsText,
302            printNulls, showLineNumbers
303        );
304    }
305
306
307    // ********************************************************************************************
308    // ********************************************************************************************
309    // Line(s) of Text
310    // ********************************************************************************************
311    // ********************************************************************************************
312
313
314    /**
315     * <EMBED CLASS='external-html' DATA-FILE-ID=SP_LINE_OR_DESC>
316     * 
317     * @param s This may be any valid Java {@code String}.  It ought to be some variant of a
318     * text-file. It will be searched for the nearest {@code '\n'} character - <I>in both
319     * directions, left and right</I> - from parameter {@code int 'pos'} and parameter
320     * {@code int 'len'}
321     * 
322     * @param pos This is a position in the input-parameter {@code String 's'}.  The nearest
323     * {@code '\n'} (new-line) character, <I>to the left</I> of this position, will be found and
324     * identified.  If the {@code char} at {@code s.charAt(pos)} is, itself, a {@code '\n'}
325     * (newline) character, then no left-direction search will be performed.  The left-most
326     * position of the returned substring would then be {@code pos + 1}.
327     * 
328     * @param len The search for the 'right-most' {@code '\n'} (newline-character) will begin at
329     * position {@code 'len'}.  If the character at {@code s.charAt(pos + len)} is, itself, a 
330     * new-line character, then no right-direction search will be performed.  The right-most
331     * position of the returned substring would be {@code pos + len - 1}.
332     * 
333     * @param unixColorCode If this {@code String} is null, it will be ignored.  If this
334     * {@code String} is non-null, it will be inserted before the "Matching {@code String}"
335     * indicated by the index-boundaries {@code pos} <I>TO</I> {@code pos + len}.
336     * 
337     * <BR /><BR /><DIV CLASS=JDHint>
338     * <B STYLE='color:red;'>Note:</B> No Validity Check shall be performed on this {@code String},
339     * and the user is not obligated to provide a {@link C} valid UNIX Color-Code {@code String}.
340     * Also, a closing {@code C.RESET} is inserted after the terminus of the match.
341     * </DIV>
342     * 
343     * @return The {@code String} demarcated by the first new-line character PLUS 1
344     * <I><B>BEFORE</I></B> index {@code 'pos'}, and the first new-line character MINUS 1
345     * <I><B>AFTER</I></B> index {@code pos + len}.
346     * 
347     * <EMBED CLASS='external-html' DATA-FILE-ID=SP_LINES_RET>
348     * 
349     * @throws StringIndexOutOfBoundsException If either {@code 'pos'}, or {@code 'pos + len'} are
350     * not within the bounds of the input {@code String 's'}
351     * 
352     * @throws IllegalArgumentException If the value passed to parameter {@code 'len'} is zero or
353     * negative.
354     */
355    public static String lineOrLines(String s, int pos, int len, String unixColorCode)
356    {
357        if ((pos >= s.length()) || (pos < 0)) throw new StringIndexOutOfBoundsException(
358            "The integer passed to parameter 'pos' [" + pos + "], is past the bounds of the end " +
359            "of String 's', which has length [" + s.length() + "]"
360        );
361
362        if (len <= 0) throw new IllegalArgumentException
363            ("The value passed to parameter 'len' [" + len + "], may not be negative.");
364
365        if ((pos + len) > s.length()) throw new StringIndexOutOfBoundsException(
366            "The total of parameter 'pos' [" + pos + "], and parameter 'len' [" + len + "], is: " +
367            "[" + (pos + len) + "].  Unfortunately, String parameter 's' only has length " +
368            "[" + s.length() + "]"
369        );
370
371        int linesStart, linesEnd, temp;
372
373        if (pos == 0)                                           linesStart  = 0;
374        else if (s.charAt(pos) == '\n')                         linesStart  = pos + 1; 
375        else if ((temp = StrIndexOf.left(s, pos, '\n')) != -1)  linesStart  = temp + 1;
376        else                                                    linesStart  = 0;
377
378        if ((pos + len) == s.length())                          linesEnd    = s.length();
379        else if (s.charAt(pos + len) == '\n')                   linesEnd    = pos + len;
380        else if ((temp = s.indexOf('\n', pos + len)) != -1)     linesEnd    = temp;
381        else                                                    linesEnd    = s.length();
382
383        /*
384        // VERY USEFUL FOR DEBUGGING.  DO NOT DELETE...
385        // NOTE: This method is the one that GREP uses.
386        System.out.println("s.charAt(pos)\t\t= "    + "[" + s.charAt(pos) + "]");
387        System.out.println("s.charAt(pos+len)\t= "  + "[" + s.charAt(pos+len) + "]");
388        System.out.println("s.length()\t\t= "       + s.length());
389        System.out.println("pos\t\t\t= "            + pos);
390        System.out.println("pos + len\t\t= "        + (pos + len));
391        System.out.println("linesStart\t\t= "       + linesStart);
392        System.out.println("linesEnd\t\t= "         + linesEnd);
393        */
394
395        return  (unixColorCode != null)
396            ?   s.substring(linesStart, pos) + 
397                unixColorCode + s.substring(pos, pos + len) + RESET + 
398                s.substring(pos + len, linesEnd)
399            :   s.substring(linesStart, linesEnd);
400
401                /*
402                OOPS.... For Posterity, this shall remain, here, but commented
403                s.substring(linesStart, pos) + 
404                s.substring(pos, pos + len) + 
405                s.substring(pos + len, linesEnd);
406                */
407    }
408
409    /**
410     * <EMBED CLASS='external-html' DATA-FILE-ID=SP_LINE_DESC>
411     * 
412     * @param s This may be any valid Java {@code String}.  It ought to be some variant of a
413     * text-file. It will be searched for the nearest {@code '\n'} character - <I>in both
414     * directions, left and right</I> - from parameter {@code int 'pos'}.
415     * 
416     * @param pos This is a position in the input-parameter {@code String 's'}.  The nearest
417     * new-line character both to the left of this position, and to the right, will be found and
418     * identified. If the character at {@code s.charAt(pos)} is itself a newline {@code '\n'}
419     * character, then <I>an exception shall throw</I>.
420     * 
421     * @return The {@code String} identified by the first new-line character PLUS 1
422     * <I><B>BEFORE</I></B> index {@code 'pos'}, and the first new-line character MINUS 1
423     * <I><B>AFTER</I></B> index {@code 'pos + len'}.
424     * 
425     * <EMBED CLASS='external-html' DATA-FILE-ID=SP_LINES_RET>
426     * 
427     * @throws StringIndexOutOfBoundsException If {@code 'pos'} is not within the bounds of the 
428     * input {@code String 's'}
429     * 
430     * @throws IllegalArgumentException If the character in {@code String 's'} at position
431     * {@code 'pos'} is a newline {@code '\n'}, itself.
432     */
433    public static String line(String s, int pos)
434    {
435        if ((pos > s.length()) || (pos < 0)) throw new StringIndexOutOfBoundsException(
436            "The integer passed to parameter 'pos' [" + pos + "], is past the bounds of the end of " +
437            "String 's', which has length [" + s.length() + "]"
438        );
439
440        if (s.charAt(pos) == '\n') throw  new IllegalArgumentException(
441            "The position-index for string-parameter 's' contains, itself, a new line character " +
442            "'\\n.'  This is not allowed here."
443        );
444
445        int lineStart, lineEnd;
446
447        // Prevents StrIndexOf from throwing StringINdexOutOfBounds
448        if (pos == 0) lineStart = 0;
449
450        // Also prevent lineStart equal-to '-1'
451        else if ((lineStart = StrIndexOf.left(s, pos, '\n')) == -1) lineStart = 0;
452
453        // Prevent lineEnd equal to '-1'
454        if ((lineEnd = s.indexOf('\n', pos)) == -1) lineEnd = s.length();
455
456        // if this is the first line, there was no initial '\n', so don't skip it!
457        return (lineStart == 0)
458
459            // This version returns the String from the position-0 (Pay Attention!)
460            ? s.substring(0, lineEnd)
461
462            // This version simply eliminates the '\n' that is in the directly-preceeding character
463            : s.substring(lineStart + 1, lineEnd);
464    }
465
466    /**
467     * This will retrieve the first {@code 'n'} lines of a {@code String} - where a line is defined
468     * as everything up to and including the next newline {@code '\n'} character.
469     * 
470     * @param s Any java {@code String}.
471     * 
472     * @param n This is the number of lines of text to retrieve.
473     * 
474     * @return a substring of s where the last character in the {@code String} is a {@code '\n'}.
475     * The last character should be the nth {@code '\n'} character found in s.  If there is no such
476     * character, then the original {@code String} shall be returned instead.
477     * 
478     * @throws NException This exception shall throw if parameter {@code 'n'} is less than 1, or
479     * longer than {@code s.length()}.
480     */
481    public static String firstNLines(String s, int n)
482    {
483        NException.check(n, s);
484        int pos = StrIndexOf.nth(s, n, '\n');
485
486        if (pos != -1)  return s.substring(0, pos + 1);
487        else            return s;
488    }
489
490    /**
491     * This will retrieve the last 'n' lines of a {@code String} - where a line is defined as
492     * everything up to and including the next newline {@code '\n'} character.
493     * 
494     * @param s Any java {@code String}.
495     * 
496     * @param n This is the number of lines of text to retrieve.
497     * 
498     * @return a substring of {@code 's'} where the last character in the {@code String} is a
499     * new-line character {@code '\n'}, and the first character is the character directly before
500     * the nth newline {@code '\n'} found in {@code 's'} - starting the count at the end of the
501     * {@code String}.  If there is no such substring, then the original {@code String} shall be
502     * returned.
503     * 
504     * @throws NException This exception shall throw if {@code 'n'} is less than 1, or longer
505     * {@code s.length()}.
506     */
507    public static String lastNLines(String s, int n)
508    {
509        NException.check(n, s);
510        int pos = StrIndexOf.nthFromEnd(s, n, '\n');
511
512        if (pos != -1)  return s.substring(pos + 1);
513        else            return s;
514    }
515
516    /**
517     * This is used for "trimming each line" of an input {@code String}.  Generally, when dealing
518     * with HTML there may be superfluous white-space that is useful in some places, but not
519     * necessarily when HTML is copied and pasted to other sections of a page (or to another page,
520     * altogether).  This will split a {@code String} by new-line characters, and then trim each
521     * line, and afterward rebuild the {@code String} and return it. 
522     * 
523     * <BR /><BR /><DIV CLASS=JDHint>
524     * <B STYLE='color:red;'>CRLF Issues:</B> This will only split the {@code String} using the
525     * standard {@code '\n'} character.  If the {@code String} being used uses {@code '\r'} or
526     * {@code '\n\r'}, use a different trim method.
527     * </DIV>
528     * 
529     * @param str This may be any {@code String}.  It will be split by new-line characters
530     * {@code '\n'}
531     * 
532     * @return Returns the rebuilt {@code String}, with each line having a {@code String.trim();}
533     * operation performed.
534     */
535    public static String trimEachLine(String str)
536    {
537        StringBuilder sb = new StringBuilder();
538
539        for (String s : str.split("\\n"))
540
541            if ((s = s.trim()).length() == 0)   continue;
542            else                                sb.append(s + '\n');
543
544        return sb.toString().trim();
545    }
546
547    /**
548     * Interprets an input {@code String} as one which was read out of a Text-File.  Counts the
549     * number of new-line ({@code '\n'}) characters between {@code String} indices {@code '0'} and
550     * {@code 'pos'}
551     * 
552     * <BR /><BR />This is intended be the Line-Number where {@code String}-Index parameter
553     * {@code 'pos'} is located inside the {@code 'str'} (presuming {@code 'str'} was retrieved
554     * from a Text-File).
555     * 
556     * @param str Any Java {@code String}, preferably one with multiple lines of text.
557     * @param pos Any valid {@code String}-Index that occurs ithin {@code 'str'}
558     * 
559     * @return The Line-Number within Text-File parameter {@code 'str'} which contains
560     * {@code String}-Index parameter {@code 'pos'}
561     * 
562     * @throws StringIndexOutOfBoundsException If integer-parameter {@code 'pos'} is negative or
563     * past the length of the {@code String}-Parameter {@code 'str'}.
564     * 
565     * @see #lineNumberSince(String, int, int, int)
566     */
567    public static int lineNumber(String str, int pos)
568    {
569        if (pos < 0) throw new StringIndexOutOfBoundsException
570            ("The number provided to index parameter 'pos' : [" + pos + "] is negative.");
571
572        if (pos >= str.length()) throw new StringIndexOutOfBoundsException(
573            "The number provided to index parameter 'pos' : [" + pos + "] is greater than the " +
574            "length of the input String-Parameter 'str' [" + str.length() + "]."
575        );
576
577        int lineNum = 1;
578
579        for (int i=0; i <= pos; i++) if (str.charAt(i) == '\n') lineNum++;
580
581        return lineNum;
582    }
583
584    /**
585     * <EMBED CLASS='external-html' DATA-FILE-ID=SP_LINE_SINCE_DESC>
586     * 
587     * @param str Any Java {@code String}.  This {@code String} will be interpreted as a Text-File
588     * whose newline characters ({@code '\n'} chars) represent lines of text.
589     * 
590     * @param pos Any valid {@code String}-index within {@code 'str'}
591     * 
592     * @param prevLineNum This should be the Line-Number that contains the {@code String}-index
593     * {@code 'prevPos'}
594     * 
595     * @param prevPos This may be any index contained by {@code String} parameter {@code 'str'}.
596     * It is expected that this parameter be an index that occured on Line-Number
597     * {@code 'prevLineNum'} of the Text-File {@code 'str'}
598     * 
599     * @return The Line-Number within Text-File parameter {@code 'str'} that contains
600     * {@code String}-index {@code 'pos'}
601     * 
602     * @throws IllegalArgumentException If {@code 'pos'} is less than or equal to
603     * {@code 'prevPos'}, or if {@code 'prevLineNum'} is less than zero.
604     * 
605     * @see #lineNumber(String, int)
606     */
607    public static int lineNumberSince(String str, int pos, int prevLineNum, int prevPos)
608    {
609        if (pos <= prevPos) throw new IllegalArgumentException(
610            "The number provided to index parameter 'pos' : [" + pos + "] is less than or equal " +
611            "to previous-match index-parameter prevPos : [" + prevPos + "]"
612        );
613
614        if (prevLineNum < 0) throw new IllegalArgumentException(
615            "You have provided a negative number to Line-Number parameter 'prevLineNum' : " +
616            "[" + prevLineNum + "]"
617        );
618
619        for (int i = (prevPos + 1); i <= pos; i++) if (str.charAt(i) == '\n') prevLineNum++;
620
621        return prevLineNum;
622    }
623
624
625    // ********************************************************************************************
626    // ********************************************************************************************
627    // Abbreviation: Line-Length **AND** Number of Lines
628    // ********************************************************************************************
629    // ********************************************************************************************
630
631
632    /**
633     * <EMBED CLASS='external-html' DATA-FILE-ID=SP_WHA_DESCRIPTION>
634     * 
635     * @param s                 Any Java {@code String}
636     * @param horizAbbrevStr    <EMBED CLASS='external-html' DATA-FILE-ID=SP_WHA_H_ABBREV_STR>
637     * @param maxLineLength     <EMBED CLASS='external-html' DATA-FILE-ID=SP_WHA_MAX_LINE_LEN>
638     * @param maxNumLines       <EMBED CLASS='external-html' DATA-FILE-ID=SP_WHA_MAX_NM_LINES>
639     * 
640     * @param compactConsecutiveBlankLines When this parameter is passed {@code TRUE}, any 
641     * series of Empty-Lines, or lines only containing White-Space will be compacted to a single 
642     * line of text that simply states {@code "[Compacted 10 Blank Lines]"} (or however many 
643     * White-Space-Only Lines were actually compacted, if that number isn't {@code '10'})
644     * 
645     * @return                          The modified {@code String}.
646     * @throws IllegalArgumentException <EMBED CLASS='external-html' DATA-FILE-ID=SP_WHA_IAEX>
647     */
648    @LinkJavaSource(handle="VertAndHorizAbbrev")
649    public static String widthHeightAbbrev(
650            final String    s,
651            final String    horizAbbrevStr,
652            final Integer   maxLineLength,
653            final Integer   maxNumLines,
654            final boolean   compactConsecutiveBlankLines
655        )
656    {
657        return VertAndHorizAbbrev.print
658            (s, horizAbbrevStr, maxLineLength, maxNumLines, compactConsecutiveBlankLines);
659    }
660
661
662    // ********************************************************************************************
663    // ********************************************************************************************
664    // Wrap Text to another line
665    // ********************************************************************************************
666    // ********************************************************************************************
667
668
669    /**
670     * Convenience Method.
671     * <BR />See Documentation: {@link #wrap(String, int, int)}
672     * <BR />Passes same {@code 'lineLen'} value to <B><I>BOTH</I></B> Line-Length Parameters
673     */
674    @LinkJavaSource(handle="Wrap")
675    @LinkJavaSource(handle="WrapHelpers")
676    public static String wrap(String s, int lineLen)
677    { return Wrap.wrap(s, lineLen, lineLen); }
678
679    /**
680     * <EMBED CLASS='external-html' DATA-FILE-ID=SP_WRAP_DESCRIPTION>
681     * <EMBED CLASS='external-html' DATA-FILE-ID=SP_WRAP_RIGHT_TRIM>
682     * 
683     * @param s             Any Java {@code String}; may not contain {@code '\t', '\f' or '\r'}
684     * @param firstLineLen  <EMBED CLASS='external-html' DATA-FILE-ID=SP_WRAP_1ST_LN_LEN>
685     * @param lineLen       The maximum allowable Line-Length for all other lines of text.
686     * @return              <EMBED CLASS='external-html' DATA-FILE-ID=SP_WRAP_RETURNS>
687     * 
688     * @throws StringFormatException <EMBED CLASS='external-html' DATA-FILE-ID=SP_WRAP_STR_FMT_EX>
689     * @throws IllegalArgumentException If {@code 'firstLineLen'} or {@code 'lineLen'} are
690     * zero or negative.
691     */
692    @LinkJavaSource(handle="Wrap")
693    @LinkJavaSource(handle="WrapHelpers")
694    public static String wrap(String s, int firstLineLen, int lineLen)
695    { return Wrap.wrap(s, firstLineLen, lineLen); }
696 
697
698    /**
699     * Convenience Method.
700     * <BR />See Documentation: {@link #wrapToIndentation(String, int, int)}
701     * <BR />Passes same {@code 'lineLen'} value to <B><I>BOTH</I></B> Line-Length Parameters
702     */
703    @LinkJavaSource(handle="WrapToIndentation")
704    @LinkJavaSource(handle="WrapHelpers")
705    public static String wrapToIndentation(String s, int lineLen)
706    { return WrapToIndentation.wrap(s, lineLen, lineLen); }
707
708    /**
709     * <EMBED CLASS='external-html' DATA-FILE-ID=SP_WRAP_DESCRIPTION>
710     * <EMBED CLASS='external-html' DATA-FILE-ID=SP_WRAP_RIGHT_TRIM>
711     * <EMBED CLASS='external-html' DATA-FILE-ID=SP_WRAP_TO_INDENT>
712     * 
713     * @param s             Any Java {@code String}; may not contain {@code '\t', '\f' or '\r'}
714     * @param firstLineLen  <EMBED CLASS='external-html' DATA-FILE-ID=SP_WRAP_1ST_LN_LEN>
715     * @param lineLen       The maximum allowable Line-Length for all other lines of text.
716     * @return              <EMBED CLASS='external-html' DATA-FILE-ID=SP_WRAP_RETURNS>
717     * 
718     * <BR /><BR />As already explained, the returned {@code String} shall contain "wrapped" text 
719     * which has an amount of indentation (space characters) equal to whatever line is directly
720     * above it.  Indented lines, effectively, "inherit" the amount of indentation present in the 
721     * line coming directly before them.
722     * 
723     * @throws StringFormatException <EMBED CLASS='external-html' DATA-FILE-ID=SP_WRAP_STR_FMT_EX>
724     * @throws IllegalArgumentException If {@code 'firstLineLen'} or {@code 'lineLen'} are
725     * zero or negative.
726     */
727    @LinkJavaSource(handle="WrapToIndentation")
728    @LinkJavaSource(handle="WrapHelpers")
729    public static String wrapToIndentation(String s, int firstLineLen, int lineLen)
730    { return WrapToIndentation.wrap(s, firstLineLen, lineLen); }
731
732    /**
733     * <EMBED CLASS='external-html' DATA-FILE-ID=SP_WRAP_DESCRIPTION>
734     * <EMBED CLASS='external-html' DATA-FILE-ID=SP_WRAP_RIGHT_TRIM>
735     * 
736     * @param s             Any Java {@code String}; may not contain {@code '\t', '\f' or '\r'}
737     * @param firstLineLen  <EMBED CLASS='external-html' DATA-FILE-ID=SP_WRAP_1ST_LN_LEN>
738     * @param lineLen       The maximum allowable Line-Length for all other lines of text.
739     * 
740     * @param extraSpaces   Wrapped text will get an additional amount of indentation, past 
741     *                      any and all inherited indentation
742     * @return Wrapped.
743     * 
744     * @throws StringFormatException <EMBED CLASS='external-html' DATA-FILE-ID=SP_WRAP_STR_FMT_EX>
745     * @throws IllegalArgumentException If {@code 'firstLineLen'} or {@code 'lineLen'} are
746     * zero or negative.
747     */
748    @LinkJavaSource(handle="WrapToIndentationPlus")
749    @LinkJavaSource(handle="WrapHelpers")
750    public static String wrapToIndentationPlus
751        (String s, int firstLineLen, int lineLen, int extraSpaces)
752    { return WrapToIndentationPlus.wrap(s, firstLineLen, lineLen, extraSpaces); }
753
754
755    // ********************************************************************************************
756    // ********************************************************************************************
757    // Misc Date String Functions
758    // ********************************************************************************************
759    // ********************************************************************************************
760
761
762    private static final Calendar internalCalendar = Calendar.getInstance();
763
764    /**
765     * Generates a "Date String" using the character separator {@code '.'}  
766     * @return A {@code String} in the form: {@code YYYY.MM.DD}
767     */
768    public static String dateStr() { return dateStr('.', false); }
769
770    /**
771     * Generates a "Date String" using the <I>separator</I> parameter as the separator between
772     * numbers
773     * 
774     * @param separator Any ASCII or UniCode character.
775     * 
776     * @return A {@code String} of the form: {@code YYYYcMMcDD} where {@code 'c'} is the passed
777     * {@code 'separator'} parameter.
778     */
779    public static String dateStr(char separator) { return dateStr(separator, false); }
780
781    /**
782     * Generates a "Date String" that is consistent with the directory-name file-storage locations
783     * used to store articles from {@code http://Gov.CN}.
784     * 
785     * @return The {@code String's} used for the Chinese Government Web-Portal Translation Pages
786     */
787    public static String dateStrGOVCN() { return dateStr('/', false).replaceFirst("/", "-"); }
788    // "2017-12/05"
789
790    /**
791     * This class is primary included because although Java has a pretty reasonable "general
792     * purpose" calendar class/interface, but a consistent / same {@code String} since is needed
793     * because the primary use here is for building the names of files.
794     *
795     * @param separator Any ASCII or Uni-Code character.
796     * 
797     * @param includeMonthName When <I>TRUE</I>, the English-Name of the month ({@code 'January'}
798     * ... {@code 'December'}) will be appended to the month number in the returned {@code String}.
799     * 
800     * @return The year, month, and day as a {@code String}.
801     */
802    public static String dateStr(char separator, boolean includeMonthName)
803    {
804        Calendar    c   = internalCalendar;
805        String      m   = zeroPad10e2(c.get(Calendar.MONTH) + 1); // January is month zero!
806        String      d   = zeroPad10e2(c.get(Calendar.DAY_OF_MONTH));
807
808        if (includeMonthName) m += " - " + c.getDisplayName(Calendar.MONTH, 2, Locale.US);
809
810        return (separator != 0)
811            ? (c.get(Calendar.YEAR) + "" + separator + m + separator + d)
812            : (c.get(Calendar.YEAR) + "" + m + d);
813    }
814
815
816    // ********************************************************************************************
817    // ********************************************************************************************
818    // Misc Month-Date String Functions
819    // ********************************************************************************************
820    // ********************************************************************************************
821
822
823    /** The months of the year, as an immutable list of {@code String's}. */
824    public static final ReadOnlyList<String> months = new ReadOnlyArrayList<>(
825        "January", "February", "March", "April", "May", "June",
826        "July", "August", "September", "October", "November", "December"
827    );
828
829    /**
830     * Converts an integer into a Month.
831     * @param month The month, as a number from {@code '1'} to {@code '12'}.
832     * @return A month as a {@code String} like: {@code "January"} or {@code "August"}
833     * @see #months
834     */
835    public static String monthStr(int month) { return months.get(month); }
836
837    /**
838     * Returns a {@code String} that has the year and the month (but not the day, or any other
839     * time related components).
840     *
841     * @return Returns the current year and month as a {@code String}.
842     */
843    public static String ymDateStr() { return ymDateStr('.', false); }
844
845    /**
846     * Returns a {@code String} that has the year and the month (but not the day, or other time
847     * components).
848     * 
849     * @param separator The single-character separator used between year, month and day.
850     * @return The current year and month as a {@code String}.
851     */
852    public static String ymDateStr(char separator) { return ymDateStr(separator, false); }
853
854    /**
855     * Returns a {@code String} that has the year and the month (but not the day, or other time
856     * components).
857     * 
858     * @param separator The single-character separator used between year, month and day.
859     * 
860     * @param includeMonthName When this is true, the name of the month, in English, is included
861     * with the return {@code String}.
862     * 
863     * @return {@code YYYY<separator>MM(? include-month-name)}
864     */
865    public static String ymDateStr(char separator, boolean includeMonthName)
866    {
867        Calendar    c   = internalCalendar;
868        String      m   = zeroPad10e2(c.get(Calendar.MONTH) + 1); // January is month zero!
869
870        if (includeMonthName) m += " - " + c.getDisplayName(Calendar.MONTH, 2, Locale.US);
871
872        return (separator != 0)
873            ? (c.get(Calendar.YEAR) + "" + separator + m)
874            : (c.get(Calendar.YEAR) + "" + m);
875    }
876
877
878    // ********************************************************************************************
879    // ********************************************************************************************
880    // Misc Time String Functions
881    // ********************************************************************************************
882    // ********************************************************************************************
883
884
885    /**
886     * Returns the current time as a {@code String}.
887     * 
888     * @return military time - with AM|PM (redundant) added too.
889     * Includes only Hour and Minute - separated by a colon character {@code ':'}
890     * 
891     * @see #timeStr(char)
892     */
893    public static String timeStr() { return timeStr(':'); }
894
895    /**
896     * Returns the current time as a {@code String}.
897     * @param separator The character used to separate the minute &amp; hour fields
898     * @return military time - with AM|PM added redundantly, and a separator of your choosing.
899     */
900    public static String timeStr(char separator)
901    {
902        Calendar    c   = internalCalendar;
903        int         ht  = c.get(Calendar.HOUR) + ((c.get(Calendar.AM_PM) == Calendar.AM) ? 0 : 12);
904        String      h   = zeroPad10e2((ht == 0) ? 12 : ht);  // 12:00 is represented as "0"... changes this...
905        String      m   = zeroPad10e2(c.get(Calendar.MINUTE));
906        String      p   = (c.get(Calendar.AM_PM) == Calendar.AM) ? "AM" : "PM";
907
908        return (separator != 0)
909            ? (h + separator + m + separator + p)
910            : (h + m + p);
911    }
912
913    /**
914     * Returns the current time as a {@code String}.  This method uses all time components
915     * available.
916     * 
917     * @return military time - with AM|PM added redundantly.
918     */
919    public static String timeStrComplete()
920    {
921        Calendar    c   = internalCalendar;
922        int         ht  = c.get(Calendar.HOUR) + ((c.get(Calendar.AM_PM) == Calendar.AM) ? 0 : 12);
923        String      h   = zeroPad10e2((ht == 0) ? 12 : ht);  // 12:00 is represented as "0"
924        String      m   = zeroPad10e2(c.get(Calendar.MINUTE));
925        String      s   = zeroPad10e2(c.get(Calendar.SECOND));
926        String      ms  = zeroPad(c.get(Calendar.MILLISECOND));
927        String      p   = (c.get(Calendar.AM_PM) == Calendar.AM) ? "AM" : "PM";
928
929        return h + '-' + m + '-' + p + '-' + s + '-' + ms + "ms";
930    }
931
932
933    // ********************************************************************************************
934    // ********************************************************************************************
935    // More Numeric Methods
936    // ********************************************************************************************
937    // ********************************************************************************************
938
939
940    private static final DecimalFormat formatter = new DecimalFormat("#,###");
941
942    /**
943     * Makes a {@code long} number like {@code 123456789} into a number-string such as:
944     * {@code "123,456,789"}. Java's {@code package java.text.*} is easy to use, and versatile, but
945     * the commands are not always so easy to remember.
946     *
947     * @param l Any {@code long} integer.  Comma's will be inserted for every third power of ten
948     * 
949     * @return After calling java's {@code java.text.DecimalFormat} class, a {@code String}
950     * representing this parameter will be returned.
951     */
952    public static String commas(long l)
953    { return formatter.format(l); }
954
955    /**
956     * The words "ordinal indicator" are referring to the little character {@code String} that is
957     * often used in English to make a number seem more a part of an english sentence.
958     * 
959     * @param i Any positive integer (greater than 0)
960     *
961     * @return This will return the following strings:
962     * 
963     * <TABLE CLASS=JDBriefTable>
964     * <TR><TH>Input:   </TH><TH>RETURNS:</TH></TR>
965     * <TR><TD>i = 1    </TD><TD>"st" &nbsp;(as in "1st","first")   </TD></TR>
966     * <TR><TD>i = 2    </TD><TD>"nd" &nbsp;(as in "2nd", "second") </TD></TR>
967     * <TR><TD>i = 4    </TD><TD>"th" &nbsp;(as in "4th")           </TD></TR>
968     * <TR><TD>i = 23   </TD><TD>"rd" &nbsp;(as in "23rd")          </TD></TR>
969     * </TABLE>
970     * 
971     * @throws IllegalArgumentException If i is negative, or zero
972     */
973    public static String ordinalIndicator(int i)
974    {
975        if (i < 1) throw new IllegalArgumentException
976            ("i: " + i + "\tshould be a natural number > 0.");
977
978
979        // Returns the last 2 digits of the number, or the number itself if it is less than 100.
980        // Any number greater than 100 - will not have the "text-ending" (1st, 2nd, 3rd..) affected
981        // by the digits after the first two digits.  Just analyze the two least-significant digits
982
983        i = i % 100;
984
985        // All numbers between "4th" and "19th" end with "th"
986        if ((i > 3) && (i < 20))    return "th";
987
988        // set i to be the least-significant digit of the number - if that number was 1, 2, or 3
989        i = i % 10;
990
991        // Obvious: English Rules.
992        if (i == 1) return "st";
993        if (i == 2) return "nd";
994        if (i == 3) return "rd";
995
996        // Compiler is complaining.  This statement should never be executed.
997        return "th";
998    }
999
1000
1001    // ********************************************************************************************
1002    // ********************************************************************************************
1003    // Zero Padding stuff
1004    // ********************************************************************************************
1005    // ********************************************************************************************
1006
1007
1008    /**
1009     * <EMBED CLASS='external-html' DATA-FILE-ID=SP_ZERO_PAD_DESC>
1010     * @param n Any Integer.  If {@code 'n'} is negative or greater than 1,000 - null is returned.
1011     * @return <EMBED CLASS='external-html' DATA-FILE-ID=SP_ZERO_PAD_RET>
1012     * @see #zeroPad10e2(int)
1013     * @see #zeroPad10e4(int)
1014     */
1015    public static String zeroPad(int n)
1016    {
1017        if (n < 0)      return null;
1018        if (n < 10)     return "00" + n;
1019        if (n < 100)    return "0" + n;
1020        if (n < 1000)   return "" + n;
1021        return null;
1022    }
1023
1024    /**
1025     * Pads an integer such that it contains enough leading zero's to ensure a
1026     * {@code String}-length of two.
1027     * 
1028     * @param n Must be an integer between 0 and 99, or else null will be returned
1029     * @return <EMBED CLASS='external-html' DATA-FILE-ID=SP_ZP_10E2_RET>
1030     * @see #zeroPad(int)
1031     */
1032    public static String zeroPad10e2(int n)
1033    {
1034        if (n < 0)      return null;
1035        if (n < 10)     return "0" + n;
1036        if (n < 100)    return "" + n;
1037        return null;
1038    }
1039
1040    /**
1041     * Pads an integer such that it contains enough leading zero's to ensure a String-length of
1042     * four.
1043     * 
1044     * @param n Must be an integer between 0 and 9999, or else null will be returned
1045     * @return <EMBED CLASS='external-html' DATA-FILE-ID=SP_ZP_10E4_RET>
1046     * @see #zeroPad(int)
1047     */
1048    public static String zeroPad10e4(int n)
1049    {
1050        if (n < 0)      return null;
1051        if (n < 10)     return "000" + n;
1052        if (n < 100)    return "00" + n;
1053        if (n < 1000)   return "0" + n;
1054        if (n < 10000)  return "" + n;
1055        return null;
1056    }
1057
1058    /**
1059     * <EMBED CLASS='external-html' DATA-FILE-ID=SP_ZP_POW2_DESC>
1060     * 
1061     * @param n Must be an integer between {@code '0'} and {@code '9999'} where the number of 
1062     * {@code '9'} digits is equal to the value of parameter {@code int 'powerOf10'}
1063     * 
1064     * @param powerOf10 This must be a positive integer greater than {@code '1'}.  It may not be 
1065     * larger {@code '11'}.  The largest value that any integer in Java may attain is
1066     * {@code '2,147,483, 647'}
1067     * 
1068     * @return <EMBED CLASS='external-html' DATA-FILE-ID=SP_ZP_POW2_RET>
1069     * 
1070     * @throws IllegalArgumentException if the value parameter {@code 'powerOf10'} is less than 2,
1071     * or greater than {@code 11}.
1072     */
1073    public static String zeroPad(int n, int powerOf10)
1074    {
1075        if (n < 0) return null;                 // Negative Values of 'n' not allowed
1076
1077        char[]  cArr    = new char[powerOf10];  // The String's length will be equal to 'powerOf10'
1078        String  s       = "" + n;               //       (or else 'null' would be returned)
1079        int     i       = powerOf10 - 1;        // Internal Loop variable
1080        int     j       = s.length() - 1;       // Internal Loop variable
1081
1082        Arrays.fill(cArr, '0');                 // Initially, fill the output char-array with all
1083                                                // zeros
1084
1085        while ((i >= 0) && (j >= 0))            // Now start filling that char array with the
1086            cArr[i--] = s.charAt(j--);          // actual number
1087
1088        if (j >= 0) return null;                // if all of parameter 'n' was inserted into the
1089                                                // output (number 'n' didn't fit) then powerOf10
1090                                                // was insufficient, so return null.
1091
1092        return new String(cArr);
1093    }
1094
1095}