001package Torello.Java;
002
003import java.util.regex.*;
004
005import java.util.stream.IntStream;
006import Torello.Java.Function.IntCharFunction;
007
008import Torello.JavaDoc.StaticFunctional;
009import Torello.JavaDoc.Excuse;
010
011/**
012 * A class for indenting, unindenting and trimming textual-strings.
013 * 
014 * <BR /><BR /><EMBED CLASS='external-html' DATA-FILE-ID=STRINDENT>
015 */
016@StaticFunctional(Excused="INDENTATION_COUNTER", Excuses=Excuse.DEBUGGING)
017public class StrIndent
018{
019    private StrIndent() { }
020
021    // the indent(String, n, tabOrSpace) method uses this array as a parameter to StrReplace
022    private static final char[] cArrIndent = { '\n' };
023
024    // This matches lines of text that contain only blank / white-space characters
025    private static final Pattern EMPTY_LINE = Pattern.compile("^[ \t]+\n", Pattern.MULTILINE);
026
027    // The 'setCodeIndent' uses all of these (but *ONLY* if "DEBUGGING_INDENTATION" is set to TRUE)
028    private static int              INDENTATION_COUNTER     = 0;
029    private static final boolean    DEBUGGING_INDENTATION   = false;
030    private static final String     STR_FORMAT_EX_MESSAGE   = 
031        "One of the lines of code for the method-body as-a-string that was passed contained " +
032        "a tab '\\t' character.  Source-Code String's that are passed to this method must have" +
033        "been de-tabified.";
034
035    // Used by 'setCodeIndent_WithTabsPolicyRelative'
036    // This needs to be a long-array, because there might be lines with lots of initial indentation.
037
038    private static final char[] SPACES = new char[200];
039
040    static { java.util.Arrays.fill(SPACES, ' '); }
041    
042    /**
043     * Replaces sub-strings that contain a newline character followed by only white-space with
044     * only the new-line character itself.
045     *
046     * @param s Any Java {@code String}, but preferrably one which contains newline characters
047     * ({@code '\n'}), and white-space characters immediately followng the newline, but not other
048     * ASCII nor UNICODE.
049     *
050     * @return The same String with each instance of {@code ^[ \t]+\n} replaced by {@code '\n'}.
051     */
052    public static String trimWhiteSpaceOnlyLines(String s)
053    {
054        Matcher m = EMPTY_LINE.matcher(s);
055        return m.replaceAll("\n");
056    }
057
058    /**
059     * Will iterate through <I>each line of text</I> within the input {@code String}-parameter
060     * {@code 's'}, and right-trim the lines.  "Right Trim" means to remove all white-space
061     * characters that occur <I><B>after</I></B> the last non-white-space character on the line.
062     * (Does not remove the new-line character ({@code '\n'}) itself).
063     * 
064     * <BR /><BR /><B>NOTE:</B> Any line of text which contains only white-space characters is
065     * reduced to a single new-line character.
066     * 
067     * @param s Any Java {@code String}, preferrably one with several new-line characters.
068     * 
069     * @return The same text, but only after having any 'trailing white-space' characters removed
070     * from each line of text.
071     */
072    public static String rightTrimAll(String s)
073    {
074        // SPECIAL-CASE: There is
075        if (s.length() == 0) return s;
076
077        char[]  cArr        = s.toCharArray();
078        int     targetPos   = 0;
079        int     nlPos       = 0;
080
081        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
082        // Set up the Loop Variables 'nlPos' and 'targetPos'
083        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
084
085        // SKIP: Skip past all leading new-lines.  They don't need to be moved at all in the loop!
086        for (nlPos=0; (nlPos < cArr.length) && (cArr[nlPos] == '\n'); nlPos++);
087
088        // SPECIAL-CASE: The String had **ONLY** new-lines in it...
089        if (nlPos == cArr.length) return s;
090
091        // IF THERE WERE LEADING NEWLINES:
092        // AFTER-LOOP:  'nlPos' will be pointing at the character IMMEDIATELY-AFTER the last
093        //              leading newline.
094        // NOTE:        IF there WERE NOT leading newlines, 'nlPos' is ZERO,
095        //              which is just fine for assigning to 'targetPos' anyway!
096        targetPos = nlPos;
097
098        // Continue to initialize 'nlPos' and 'targetPos'
099        // PART ONE: Find the FIRST new-line AFTER the first CONTENT-CONTAINING line.
100        // PART TWO: Set 'targetPos' to the last character that is non-white-space.  'targetPos'
101        //           will be incremented-by-one later to point to white-space.
102        while ((++nlPos < cArr.length) && (cArr[nlPos] != '\n'))
103            if (! Character.isWhitespace(cArr[nlPos]))
104                targetPos = nlPos;
105
106        // Now increment 'targetPos' because in MOST-CASES it will be pointing to the last
107        // non-white-space character in the first line.  (and we *CANNOT* clobber that character!)
108        // HOWEVER: if 'targetPos' is still pointing at White-Space (after the previous loop),
109        //          then it must be that the ENTIRE-FIRST-LINE was BLANK!
110        if (! Character.isWhitespace(cArr[targetPos])) targetPos++;
111
112        // SPECIAL-CASE: The first CONTENT-FUL LINE is the ONLY-LINE in the text.
113        //          ===> Return the input-String.
114        //               **BUT** make sure to right-trim that CONTENT-FUL line.
115        if (nlPos == cArr.length) return s.substring(0, targetPos);
116
117        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
118        // Now do the 'Shifting Loop'
119        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
120
121        while (nlPos < cArr.length)
122        {
123            int nextNLPos               = nlPos + 1;
124            int lastNonWhiteSpacePos    = nlPos;
125            int sourcePos               = nlPos;
126
127            // Compute 'nextNLPos' and 'lastNonWhiteSpacePos': In the Example *SUBSTRING* Below:
128            // "...\nHello, How are you?   \n..."
129            //
130            // NEW-LINE-POS was set to the first '\n' you see in the above-String
131            // The '?' is the LAST-NON-WHITE-SPACE
132            // The '\n' after that is the NEXT-NEWLINE-POS
133            while ((nextNLPos < cArr.length) && (cArr[nextNLPos] != '\n'))
134            {
135                if (! Character.isWhitespace(cArr[nextNLPos]))
136                    lastNonWhiteSpacePos = nextNLPos;
137                nextNLPos++;
138            }
139
140            // Shift all characters BEGINNING with the OLD new-line position (since 'sourcePos')
141            // is initiliazed with 'nlPos') ... 
142            // 
143            // AGAIN: Shift all characters beginning with 'nlPos' UP-TO-AND-INCLUDING
144            // 'lastNonWhiteSpacePos' for the NEXT line of text to the appropriate position.
145            while (sourcePos <= lastNonWhiteSpacePos) cArr[targetPos++] = cArr[sourcePos++];
146
147            // The next loop-iteration will start with the next line in the text.
148            nlPos = nextNLPos;
149        }
150
151        return new String(cArr, 0, targetPos);
152    }
153
154    /**
155     * Will iterate through <I>each line of text</I> within the input {@code String}-parameter
156     * {@code 's'}, and left-trim the lines.  "Left Trim" means to remove all white-space
157     * characters that occur <I><B>before</I></B> the last non-white-space character on the line.
158     * (Does not remove the new-line character ({@code '\n'}) itself).
159     * 
160     * <BR /><BR /><B>NOTE:</B> Any line of text which contains only white-space characters is
161     * reduced to a single new-line character.
162     * 
163     * @param s Any Java {@code String}, preferrably one with several new-line characters.
164     * 
165     * @return The same text, but only after having any 'leading white-space' characters removed
166     * from each line of text.
167     */
168    public static String leftTrimAll(String s)
169    {
170        char[] cArr = s.toCharArray();
171
172        // LEFT TRIM is easier on the mind.  There are only two variables needed for this one.
173        int targetPos = 0;
174        int sourcePos = 0;
175
176        // Make sure to skip completely any and all leading new-line characters.
177        while ((targetPos < cArr.length) && (cArr[targetPos] == '\n')) targetPos++;
178
179        // If there were **ONLY** leading new-line characters, return the original string
180        if (targetPos == cArr.length) return s;
181
182        // Re-initialize 'sourcePos'
183        sourcePos = targetPos;
184
185        while (sourcePos < cArr.length)
186        {
187            // When this loop begins, sourcePos is pointing at the first character of text
188            // in the very-next line-of-text to process.
189            //
190            // NORMAL EXECUTION:    This loop advances 'sourcePos' to the first non-white-space
191            //                      character in the line.
192            // WS-ONLY LINES CASE:  This loop advances 'sourcePos' to the next line ('\n')
193            // LAST-LINE CASE:      Advances 'sourcePos' to cArr.length
194            while (     (sourcePos < cArr.length)
195                    &&  Character.isWhitespace(cArr[sourcePos])
196                    &&  (cArr[sourcePos] != '\n')
197                )
198                sourcePos++;
199
200            // Left Shift the String to 'erase' all leading white-space characters in the
201            // current line of text.
202            while ((sourcePos < cArr.length) && (cArr[sourcePos] != '\n'))
203                cArr[targetPos++] = cArr[sourcePos++];
204
205            // The loop that is directly above this statement BREAKS when '\n' is reached,
206            // so unless the end of the String has been reached, shift one more of the characters
207            // NOTE: If a character is shifted, below, it will always be the '\n' character
208            if (sourcePos < cArr.length) cArr[targetPos++] = cArr[sourcePos++];
209        }
210
211        return new String(cArr, 0, targetPos);
212    }
213
214    /**
215     * This method expects to receive a method body, constructor body, or other callable body as a
216     * {@code String}; it will remove the beginning and ending braces <CODE>('&#123;'</CODE> &amp;
217     * <CODE>'&#125;')</CODE>, and beginning &amp; ending empty lines.  This is to prepare the
218     * method for code hiliting, used internally by the package {@code Torello.JavaDoc}.
219     * 
220     * @param callableAsStr This should be a method body.  Make sure <B>**NOT TO INCLUDE**</B> the
221     * method signature at the beginning of the method.  The first non-white-space character should
222     * be the open braces character <CODE>('&#123;')</CODE>, and the last non-white-space should be
223     * the closing braces character <CODE>('&amp;#125')</CODE>.
224     * 
225     * @return A method body {@code String} that can be hilited using the code-hiliting mechanism
226     * of the "JavaDoc Package."
227     * 
228     * @throws CallableBodyException If the input parameter {@code String} does not begin and end
229     * with the curly-braces.
230     */
231    public static String chompCallableBraces(String callableAsStr)
232    {
233        callableAsStr = callableAsStr.trim();
234
235        if (callableAsStr.charAt(0) != '{') throw new CallableBodyException(
236            "Passed callable does not begin with a squiggly-brace '{', but rather a: '" + 
237            callableAsStr.charAt(0) + "'\n" + callableAsStr
238        );
239
240        if (callableAsStr.charAt(callableAsStr.length() - 1) != '}') throw new CallableBodyException(
241            "Passed callable does not end with a squiggly-brace '}', but rather a: '" + 
242            callableAsStr.charAt(0) + "'\n"+ callableAsStr
243        );
244
245        // This Version does a single "String.substring(...)", meaning it is more efficient
246        // because it is not doing any extra String copies at all.
247        //
248        // NOTE: There is an auto-pre-increment (and pre-decrement), in both of the while loops.
249        //       Therefore, sPos starts at 'zero' - even though the open-curly-brace is the 
250        //       character at position 0, and the closed-curly-brace is at position length-1.
251
252        int     sPos    = 0;
253        int     ePos    = callableAsStr.length() - 1;
254        int     first   = 1;    // The character after the '{' (open-curly-brace)
255        char    tempCh  = 0;    // temp-var
256
257        // If the callable-braces are on their own line, skip that first line.
258        // If they are **NOTE** on their own first line, the first character returned will be
259        // the first character of source-code.
260
261        while ((sPos < ePos) && Character.isWhitespace(tempCh = callableAsStr.charAt(++sPos)))
262
263            if (tempCh == '\n')
264            {
265                first = sPos + 1;
266                break;
267            }
268
269        // Check for the "bizarre case" that the method body just doesn't contain any code.
270        // This means that *EVERY CHARACTER* that was checked was white-space.  Return a single
271        // space character, and be done with it.
272
273        if (sPos == ePos) return " ";
274
275        // When this loop terminates, 'ePos' will be pointing to the first non-white-space
276        // character at the tail end of the source-code / String (callableAsStr is a String of
277        // source-code used in the JavaDoc package)
278
279        while ((ePos > sPos) && Character.isWhitespace(callableAsStr.charAt(--ePos)));
280        
281        // Starts at the 'first-white-space' character in the first line of code, and ends at the
282        // last non-white-space character in source-code String 'callableAsStr'
283
284        return callableAsStr.substring(first, ePos + 1);
285    }
286
287    /**
288     * Accepts a method body as a {@code String} and left-shifts or right-shifts each line
289     * of text (each {@code 'Line of Code' - LOC}) so that the indentation is consistent with
290     * the requested-indentation input-parameter.
291     * 
292     * <BR /><BR /><B>NOTE:</B> The lines of code contained by input-parameter {@code 'codeAsStr'}
293     * must contain leading white-space <I><B STYLE='color: red;'>without any tab ({@code '\t'})
294     * characters.</B></I>  The reasoning here is that tabs can be interpreted in many different
295     * ways, and therefore it is required to replace them before invoking this method.  This method
296     * merely adds or removes leading {@code ASCII 0x20} (space-bar characters) from the beginning
297     * of each line of text.  A leading tab-character {@code ASCII 0x09} will generate an exception
298     * throw.
299     * 
300     * @param codeAsStr A method body.  It is expected to be the internal part of a chunk of
301     * source code.
302     * 
303     * @param requestedIndent The requested amount of indentation for the method-body.  The
304     * line of code that contains the shortest amount of indentation (white-space) will be
305     * calculated, and then all LOC's shall be left-shifted (or right-shifted) according to that
306     * LOC which contained the least amount of leading white-space.
307     * 
308     * @return An updated method-body as a {@code String}.
309     * 
310     * @throws StringFormatException  This exception shall throw whenever a line of text contained
311     * by the input {@code String} has a {@code '\t'} (tab-character) before the first 
312     * non-white-space character on that line of text.  Code that contains tab-characters is
313     * invariably "auto-indented" by the Code-Editor when loaded into the GUI, and the amount of
314     * indentation applied for each tab-character is usually configurable.  Because there are many
315     * variants of how tab's {@code '\t'} gets interpreted by the editor, it is required to replace
316     * these characters first before invoking this method.
317     */
318    public static String setCodeIndent(String codeAsStr, int requestedIndent)
319    {
320        if (requestedIndent < 0) throw new IllegalArgumentException
321            ("The requested indent passed to this method was a negative value: " + requestedIndent);
322
323        else if (requestedIndent > 80) throw new IllegalArgumentException
324            ("The requested indent passed to this method was greater than 80: " + requestedIndent);
325
326        // Source-Code-String to char-array
327        char[] cArr = codeAsStr.toCharArray();
328
329        // Code "Starts With New Line '\n'"
330        boolean swNL = cArr[0] == '\n';
331
332        // Code "Ends With New Line"            
333        boolean ewNL = cArr[cArr.length - 1] == '\n';
334
335        // Location of each '\n' in the code
336        int[] nlPosArr = StrIndexOf.all(codeAsStr, '\n');
337
338        // Unless the first character in the code is '\n', numLines - num '\n' + 1
339        int numLines = nlPosArr.length + (swNL ? 0 : 1);
340
341        // Length of "Leading White Space" for each line of code
342        int[] wsLenArr = new int[numLines];
343
344        // TRUE / FALSE for "only white-space" lines of code
345        boolean[] isOnlyWSArr = new boolean[numLines];
346
347        // Amount of White-Space for the LEAST-INDENTED Line of Code
348        int minIndent = Integer.MAX_VALUE;
349
350        // These three variables are loop-control and temporary variables.
351        int wsCount     = 0;    // "White Space Count" - amount of WS on each line
352        int outArrPos   = 0;    // There are two parallel "Output Arrays", this the index
353        int lastPos     = 0;    // Temp Var for the last-index in a line of code
354
355        int i;                  // Simple Loop Control Variable
356        int j;                  // Simple Loop Control Variable
357
358
359        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
360        // Compute amount of "leading white space" (indentation) for the first LOC in input-String
361        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
362
363        // Count the leading white-space in the first line of text - unless the first character
364        // in the code was a '\n' (newline)
365
366        if (! swNL)
367        {
368            // The array-index in array cArr of the last character in the first line of text.
369            // If the input code-String is just a single line of code without any newline '\n'
370            // characters, then this value is the length of the code-string.  Otherwise, this
371            // value is assigned the cArr index/position of the first newline '\n' character.
372
373            lastPos = (nlPosArr.length > 0) ? nlPosArr[0] : cArr.length;
374
375            // The loop iterate until we reach the end of the first line of code/text, or
376            // we reach a character that is not white-space.
377
378            for (i=0; (i < lastPos) && Character.isWhitespace(cArr[i]); i++)
379                if (cArr[i] == '\t')    throw new StringFormatException(STR_FORMAT_EX_MESSAGE);
380                else                    wsCount++;
381
382            // Amount of "Leading" white-space (indentation) for first LOC
383            wsLenArr[0] = wsCount;
384
385            // Was the first line only white-space?
386            isOnlyWSArr[0] = (i == lastPos);
387
388            // 'minIndent' was initialized to Integer.MAX_VALUE
389            minIndent = wsCount;
390
391            outArrPos++;
392        }
393
394
395        // ****************************************************************************************
396        // Compute the amount of "leading white space" (indentation) for each LOC in input-String
397        // ****************************************************************************************
398        //
399        // This loop will iterate each line of code inside the input source-code character-array
400        // The length (number of characters) for the "leading white-space" (Which may also be called
401        // indentation) for each LOC is stored in an integer-array called "wsLenArr"
402        // When this loop encounters a line that is blank (contains only white-space), it is noted
403        // in a boolean array "isOnlyWSArr"
404        //
405        // NOTE: The previous loop did the *EXACT SAME THING*, but ONLY for the first line of code
406        //       in the input-string.  This is because the loop-control variables are slightly
407        //       different for the first line of code.
408
409        for (i=0; i < nlPosArr.length; i++)
410        {
411            wsCount = 0;
412            lastPos = (i < (nlPosArr.length-1)) ? nlPosArr[i+1] : cArr.length;
413
414            for (j = (nlPosArr[i]+1); (j < lastPos) && Character.isWhitespace(cArr[j]); j++)
415                if (cArr[j] == '\t')    throw new StringFormatException(STR_FORMAT_EX_MESSAGE);
416                else                    wsCount++;
417
418            // Amount of "Leading" white-space (indentation) for current LOC
419            wsLenArr[outArrPos] = wsCount;
420
421            // Is the current LOC only white-space?
422            isOnlyWSArr[outArrPos] = (j == lastPos);
423
424            // Check if this line is the "reigning champion" of minimum of white-space indentation
425            // Blank lines (lines with 'only white-space') cannot be factored into the
426            // "minimum indentation" computation
427
428            if (wsCount < minIndent) if (! isOnlyWSArr[outArrPos]) minIndent = wsCount;
429            
430            outArrPos++;
431        }
432
433
434        // ****************************************************************************************
435        // Now we will shorten or extend the amount of indentation for the input code snippet.
436        // ****************************************************************************************
437        //
438        // *** Keep Here for Reference ***
439        // int[]        nlPosArr    // Location of each '\n' in the code
440        // int[]        wsLenArr    // Length of "Leading White Space" for each line of code
441        // boolean[]    isOnlyWSArr // TRUE / FALSE for "only white-space" lines of code
442
443        int diff        = requestedIndent - minIndent;
444        int delta       = 0;    // Intended to store the change in "Source Code as a String" LENGTH
445                                // after performing the indentation changes
446        int nextNLPos   = 0;    // Position of the next newline
447        int srcPos      = 0;    // index/pointer to the input "Source Code as a String" char-array
448        int destPos     = 0;    // index/pointer to (output) indentation-change output char-array
449        int nlPosArrPos = 0;    // index/pointer to the "New Line Position Array"
450        int otherArrPos = 0;    // index/pointer to the other 2 position arrays
451                                // a.k.a:   "White-Space-Length Array" and the
452                                //          "Is Only White-Space Array"
453
454        if (diff == 0) return codeAsStr;
455
456        for (i=0; i < wsLenArr.length; i++) if (! isOnlyWSArr[i]) delta += diff;
457
458        char[]  outArr  = new char[cArr.length + delta];
459
460        if (diff > 0)
461            return indent(codeAsStr, diff, true, true);
462        else
463        {
464            // We are removing white-space, start at end, work backwards
465            srcPos = cArr.length - 1;
466
467            // The "output array", therefore, also starts at end of char-array
468            destPos     = outArr.length - 1;
469
470            // The "Where are the newlines array" index-pointer
471            nlPosArrPos = nlPosArr.length - 1;
472
473            otherArrPos = wsLenArr.length - 1;
474                // The number of "lines of text" and "number of new-lines"
475                // are *NOT NECESSARILY* identical.  The former might be
476                // longer by *PRECISELY ONE* array-element.
477
478            for (; otherArrPos >= 0; otherArrPos--)
479            {
480                // Check if the first character in the Source-Code String is a newline.
481                nextNLPos = (nlPosArrPos >= 0) ? nlPosArr[nlPosArrPos--] : -1;
482
483                // Lines of Source Code that are only white-space shall simply be copied to
484                // the destination/output char-array.
485                if (isOnlyWSArr[otherArrPos])
486                    while (srcPos >= nextNLPos) outArr[destPos--] = cArr[srcPos--];
487
488                else
489                {
490                    // Copy the line of source code
491                    int numChars = srcPos - nextNLPos - wsLenArr[otherArrPos];
492                    while (numChars-- > 0) outArr[destPos--] = cArr[srcPos--];
493
494                    // Insert the exact amount of space-characters indentation
495                    numChars = wsLenArr[otherArrPos] + diff;
496                    while (numChars-- > 0) outArr[destPos--] = ' ';
497
498                    // Skip over the original indentation (white-space) from the input line
499                    // of source-code.
500                    srcPos -= (wsLenArr[otherArrPos] + 1);
501
502                    // Make sure to insert a new-line (since this character WASN'T copied)
503                    if (destPos >= 0) outArr[destPos--] = '\n';
504                }
505            }
506        }
507
508
509        // ****************************************************************************************
510        // Debug Println - This code isn't executed without the little debug-flag being set.
511        // ****************************************************************************************
512
513        // The returned code block is ready; convert to a String
514        String ret = new String(outArr);
515
516        // Do not delete.  Writing the debugging information takes a lot of thought.
517        // If there is ever an error, this code is quite important.
518
519        if (! DEBUGGING_INDENTATION) return ret;
520
521        try
522        {
523            StringBuilder sb = new StringBuilder();
524
525            sb.append(
526                "swNL:\t\t\t\t"         + swNL              + '\n' +
527                "ewNL:\t\t\t\t"         + ewNL              + '\n' +
528                "numLines:\t\t\t"       + numLines          + '\n' +
529                "minIndent:\t\t\t"      + minIndent         + '\n' +
530                "requestedIndent:\t"    + requestedIndent   + '\n' +
531                "diff:\t\t\t\t"         + diff              + '\n' +
532                "delta:\t\t\t\t"        + delta             + '\n' +
533                "nlPosArr:\n\t"
534            );
535
536            for (i=0; i < nlPosArr.length; i++) sb.append(nlPosArr[i] + ", ");
537            sb.append("\nwsLenArr:\n\t");
538            for (i=0; i < wsLenArr.length; i++) sb.append(wsLenArr[i] + ", ");
539            sb.append("\nisOnlyWSArr:\n\t");
540            for (i=0; i < isOnlyWSArr.length; i++) sb.append(isOnlyWSArr[i] + ", ");
541            sb.append("\n");
542
543            FileRW.writeFile(
544                sb.toString() + "\n\n****************************************************\n\n" + 
545                codeAsStr + "\n\n****************************************************\n\n" +
546                ret,
547                "TEMP/method" + StringParse.zeroPad10e2(++INDENTATION_COUNTER) + ".txt"
548            );
549
550            if (INDENTATION_COUNTER == 5) System.exit(0);
551
552        } catch (Exception e)
553        {
554            e.printStackTrace();
555            System.out.println(e + "\n\nFatal Error. Exiting.");
556            System.exit(0);
557        }
558
559        return ret;
560    }
561
562    /**
563     * Convenience Method.
564     * <BR />Invokes: {@link #setCodeIndent(String, int)}
565     * <BR />Converts: All {@code '\t'} to the specified number of spaces in parameter
566     * {@code SPACES}.
567     * <BR /><B STYLE='color: red'>NOTE:</B> Exception-Checking is <B STYLE='color: red'>NOT</B>
568     * done on input
569     */
570    public static String setCodeIndent_WithTabsPolicyAbsolute
571        (String codeAsStr, int requestedIndent, String SPACES)
572    { return setCodeIndent(codeAsStr.replace("\t", SPACES), requestedIndent); }
573
574    /**
575     * Adjusts code-indentation using a relative-sized tab-policy.  This method performs the
576     * equivalent of shifting the entire text-block, proportionately, to the left or right.
577     *
578     * <BR /><BR />To do this, first, the number of spaces that preceed the
579     * <B STYLE='color: red;'>least-indented</B> line is computed, and afterwards, every line in
580     * the text is shifted by an identical number of space-characters.  The number of spaces that
581     * are either added or removed from each line is dependent on whether the requested
582     * indentation (parameter {@code 'requestedIndent'}) is greater-than or less-than the computed
583     * least-indented line.
584     * 
585     * <EMBED CLASS='external-html' DATA-FILE-ID=SI_REL_TABS>
586     * 
587     * @param codeAsStr Any Java source-code block, as a {@code java.lang.String}
588     * 
589     * @param requestedIndent The number of spaces that the code should have as indentation.
590     * Note, that in the JavaDoc Upgrader code, this number is always {@code '1'}.
591     * 
592     * @param spacesPerTab If tabs are found inside this {@code String}, then they are replaced
593     * with an appropriate number of space characters, according to a relative tab-policy, as
594     * described above.
595     * 
596     * @return A properly shifted-indented Java source-code block.
597     */
598    public static String setCodeIndent_WithTabsPolicyRelative
599        (String codeAsStr, int requestedIndent, int spacesPerTab)
600    {
601        char[]              code    = codeAsStr.toCharArray();
602        IntStream.Builder   b       = IntStream.builder();
603
604
605        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
606        // First find all of the line-breaks / new-lines.
607        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
608        //
609        // NOTE: If the first character is not a new-line, then the first line is presumed to begin
610        //       at String-index '-1'
611        //
612        // Afterwards, convert the Stream to 'nlPos' array.  Build two other arrays
613
614        if (code[0] != '\n') b.accept(-1);
615
616        for (int i=0; i < code.length; i++) if (code[i] == '\n') b.accept(i);
617
618        int[]   nlPos       = b.build().toArray();
619        int[]   wsLen       = new int[nlPos.length];
620        int[]   fcPos       = new int[nlPos.length];
621        int     maxIndent   = 0;
622        int     minIndent   = Integer.MAX_VALUE;
623
624
625        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
626        // Compute how much white-space is currently at the start of each line
627        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
628        //
629        // NOTE: Since this method calculates what the user is looking at in his code-editor, the
630        //       tabs-policy needs to be included in the calculation.
631        //
632        // Once the amount of white-space at the start of each line is computed, it will be easy
633        // to shift the entire source-code left or right.  Note that in the JavaDoc Upgrader Tool,
634        // this is always shifted until the **LEAST INDENTED** line is indented by 1...
635        //
636        // REMEMBER: Shifting everything left must be a shift of an **EQUAL NUMBER OF SPACES** for
637        //           each line that is shifted.
638
639        for (int i=0; i < wsLen.length; i++)
640        {
641            int     END     = (i == (nlPos.length - 1)) ? code.length : nlPos[i+1];
642            boolean hasCode = false;
643
644            INNER:
645            for (int j = (nlPos[i] + 1); j < END; j++)
646
647                if (! Character.isWhitespace(code[j]))
648                {
649                    fcPos[i]    = j;
650                    hasCode     = true;
651
652                    break INNER;
653                }
654
655            if (! hasCode) fcPos[i] = wsLen[i] = -1;
656
657            else
658            {
659                // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
660                // wsLen[i] = computeEffectiveLeadingWhiteSpace(code, nlPos[i] + 1, spacesPerTab);
661                // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
662                //
663                // NOTE: The contents of everything within this 'else' branch is nothing more than
664                //       an inline block-copy of the method named in the comment above.  Hopefully
665                //       inlining the method will speed this up a little bit.
666                //
667                // The values that would be passed before the method was inlined, here, are also
668                // noted in the above comment.
669                //
670                // ALSO: The 'i' loop-variable was changed to a 'j' (to avoid conflicting with the
671                //       outer-loop 'i').  The "ret" was changed to "whiteSpaceChars"
672
673                int whiteSpaceChars = 0;
674                int relativeCount   = 0;
675
676                wsLen[i] = -1;
677
678                EFFECTIVE_LEADING_WS:
679                for (int j = (nlPos[i] + 1) /* lineFirstCharacterPos */; j < code.length; j++)
680
681                    if (! Character.isWhitespace(code[j])) 
682                    {
683                        wsLen[j] = whiteSpaceChars; // return ret;
684                        break EFFECTIVE_LEADING_WS;
685                    }
686
687                    else switch (code[j])
688                    {
689                        case ' ' : 
690                            whiteSpaceChars++;
691                            relativeCount = (relativeCount + 1) % spacesPerTab;
692                            break;
693
694                        case '\t' :
695                            whiteSpaceChars += (spacesPerTab - relativeCount);
696                            relativeCount = 0;
697                            break;
698
699                        case '\r' : 
700                        case '\f' : break;
701                        case '\n' : break EFFECTIVE_LEADING_WS; // return -1;
702                        default: throw new UnreachableError();
703                    }
704
705                // return -1;  <== Not needed, the array-location is initialized to -1
706            }
707
708            if (wsLen[i] == -1)        continue;
709            if (wsLen[i] > maxIndent)  maxIndent = wsLen[i];
710            if (wsLen[i] < minIndent)  minIndent = wsLen[i];
711        }
712
713        // This is the amount of space to shift each line.
714        int delta = requestedIndent - minIndent;
715
716
717        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
718        // NOW: Rebuild the source-code string, making sure to shift each line.
719        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
720
721        StringBuilder sb = new StringBuilder();
722
723        for (int i=0; i < wsLen.length; i++)
724
725            // The array "White-Space-Length" will have a '-1' if the entire line is nothing but
726            // white-space.  In such cases, simply append a '\n' - there is no reason to add extra
727            // spaces.  The code hilited just ignores it.
728
729            if (wsLen[i] == -1) sb.append('\n');
730
731            // Otherwise append the leading white-space, and then the line-of-code.
732            else
733            {
734                // First append the white-space at the beginning of the line.
735                int numSpaces = wsLen[i] + delta;
736
737                sb.append(SPACES, 0, numSpaces);
738
739                // Now append the line of code.  Since there may be tabs after the first
740                // non-white-space character, this is a little complicated...
741                //
742                // NOTE: This could be inlined, but this method just does too much...
743                //
744                // The char[]-Array 'code' has the code.  The text of the source-code begins at
745                // array-index 'First-Character-Position' (fcPos).  This method needs the parameter
746                // 'numSpaces' to make sure the tabs stay properly-relativised...
747
748                sb.append(lineOfCodeAsStr(code, numSpaces, fcPos[i], spacesPerTab));
749            }
750
751
752        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
753        // FINISHED: Return the Source-Code String
754        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
755
756        return sb.toString();
757    }
758
759    /**
760     * Helper Method for calculating the number of space characters to be used at the beginning
761     * of a line of code, all the while obeying a particular tabs-policy.
762     * 
763     * <BR /><BR /><B STYLE='color: red;'>IMPORTANT:</B> None of the parameters to this method
764     * will be checked for errors.  This method is often used inside of a loop, and improper 
765     * input should be presumed to cause indeterminate results.
766     * 
767     * @param code A Java source-code {@code String}, that has been converted into a Java
768     * {@code char[]}-Array.  The line of code whose leading white-space is being computed may be
769     * located anywhere in the array.
770     * 
771     * <BR /><BR /><B>NOTE:</B> These types of arrays are easily creaated by invoking the
772     * {@code java.lang.String} method {@code 'toCharArray()'}
773     * 
774     * @param lineFirstCharacterPos The array-index to be considered as the first character of
775     * non-new-line character data.
776     * 
777     * @param spacesPerTab The number of spaces that a tab-character ({@code '\t'}) intends to
778     * represent.
779     * 
780     * <BR /><BR />When {@code FALSE} is passed to this parameter, a tab-character will represent
781     * a {@code String} of space-characters whose length is equal to the number of space-characters
782     * that remain until the next modulo-{@code spacesPerTab} boundary.
783     * 
784     * @return The number of space-characters ({@code ' '}) that should preceede the line of source
785     * code.
786     * 
787     * <BR /><BR /><B STYLE='color: red'>NOTE:</B> If this line of source-code is a white-space
788     * <B STYLE='color: red;'>ONLY</B> line, then {@code -1} will be returned.
789     */
790    public static int computeEffectiveLeadingWhiteSpace
791        (char[] code, int lineFirstCharacterPos, int spacesPerTab)
792    {
793        int ret = 0;
794        int relativeCount = 0;
795
796        for (int i=lineFirstCharacterPos; i < code.length; i++)
797
798            if (! Character.isWhitespace(code[i])) return ret;
799
800            else switch (code[i])
801            {
802                case ' ' : 
803                    ret++;
804                    relativeCount = (relativeCount + 1) % spacesPerTab;
805                    break;
806
807                case '\t' :
808                    ret += (spacesPerTab - relativeCount);
809                    relativeCount = 0;
810                    break;
811
812                case '\r' : 
813                case '\f' : break;
814                case '\n' : return -1;
815                default: throw new UnreachableError();
816            }
817
818        return -1;
819    }
820
821    /**
822     * Replaces tab-characters ({@code '\t'}) in a single-line of source-code with a
823     * relative-number of space-characters ({@code ' '}).
824     * 
825     * <EMBED CLASS='external-html' DATA-FILE-ID=SI_REL_TABS>
826     * 
827     * <BR /><BR /><B STYLE='color: red;'>IMPORTANT:</B> None of the parameters to this method
828     * will be checked for errors.  This method is often used inside of a loop, and improper 
829     * input should be presumed to cause indeterminate results.
830     * 
831     * @param code This should be the source-code, converted to a character-array.  The specific
832     * line in the source-code being properly space-adjusted may be located anywhere in this
833     * array.
834     * 
835     * <BR /><BR /><B>NOTE:</B> These types of arrays are easily creaated by invoking the
836     * {@code java.lang.String} method {@code 'toCharArray()'}
837     * 
838     * @param numLeadingSpaces The number of spaces that have been placed before the start of this
839     * line of code.  This is needed because <B STYLE='color: red;'>relative</B>-tabs are computed
840     * based on integral-multiples of the tab-width ({@code 'spacesPerTab'}).
841     * 
842     * <BR /><BR />This method is a helper &amp; example method that may be used in conjunction 
843     * with properly indenting source-code.  Note that the number of leading-spaces may not be
844     * identicaly to the actual number of white-space characters in the array.  <I>After converting
845     * tab-characters ({@code '\t'}) to spaces ({@code ' '}), this number will often change.</I>
846     * 
847     * @param fcPos This parameter should contain the location of the first source-code character
848     * in the line of code.  This parameter should be an array-index that
849     * <B STYLE='color: red;'>does not</B> contain white-space.
850     * 
851     * @param spacesPerTab The number of spaces that are used to replaces tab-characters.  Since 
852     * this method performs relative tab-replacement, this constitutes the
853     * <B STYLE='color: red;'>maximum</B> number of space characters that will be used to replace 
854     * a tab.
855     * 
856     * @return A line of code, as a {@code String}, without any leading white-space, and one in
857     * which all tab-characters have been replaced by spaces.
858     */
859    public static String lineOfCodeAsStr
860        (char[] code, int numLeadingSpaces, int fcPos, int spacesPerTab)
861    {
862        StringBuilder sb = new StringBuilder();
863
864        // Loop Control Variables
865        int possibleEndingWhiteSpaceCount   = 0;
866        int relativePos                     = numLeadingSpaces % spacesPerTab;
867        int i                               = fcPos;
868
869        while ((i < code.length) && (code[i] != '\n'))
870        {
871            if (code[i] == '\t')
872                while (relativePos < 4)
873                    { sb.append(' '); relativePos++; possibleEndingWhiteSpaceCount++; }
874
875            else if ((code[i] == ' ') || (code[i] == '\r') || (code[i] == '\f'))
876            { sb.append(' '); relativePos++; possibleEndingWhiteSpaceCount++; }
877
878            else
879            { sb.append(code[i]); relativePos++; possibleEndingWhiteSpaceCount=0;}
880
881            i++;
882            relativePos %= 4;
883        }
884
885        if (i < code.length)
886        {
887            if (possibleEndingWhiteSpaceCount > 0)
888            {
889                sb.setCharAt(sb.length() - possibleEndingWhiteSpaceCount, '\n');
890                return sb.substring(0, sb.length() - possibleEndingWhiteSpaceCount + 1);
891            }
892            else return sb.append('\n').toString();
893        }
894
895        return (possibleEndingWhiteSpaceCount > 0)
896            ? sb.substring(0, sb.length() - possibleEndingWhiteSpaceCount)
897            : sb.toString();
898    }
899
900    /**
901     * This performs a variation of "indentation" on a Java {@code String} - simply put - it
902     * replaces each new-line character ({@code '\n'}) with a {@code String} that begins with a
903     * new-line, and is followed by {@code 'n'} blank white-space characters {@code ' '}.
904     * 
905     * If the input {@code String} parameter {@code 's'} is of zero-length, then the zero-length
906     * {@code String} is returned.  If the final character in the input {@code String} is a 
907     * new-line, that new-line is not padded.
908     * 
909     * @param s Any {@code java.lang.String} - preferably one that contains new-line characters.
910     * 
911     * @param n The number of white-space characters to use when pre-pending white-space to each
912     * line of text in input-parameter {@code 's'}
913     * 
914     * @return A new {@code java.lang.String} where each line of text has been indented by
915     * {@code 'n'} blank white-space characters.  If the text ends with a new-line, that line of
916     * text is not indented.
917     * 
918     * @throws NException If parameter {@code 'n'} is less than one.
919     */
920    public static String indent(String s, int n)
921    {
922        if (n < 1) throw new NException(
923            "The value passed to parameter 'n' was [" + n + "], but this is expected to be an " +
924            "integer greater than or equal to 1."
925        );
926
927        if (s.length() == 0) return "";
928
929        String  padding         = String.format("%1$" + n + "s", " ");
930        boolean lastIsNewLine   = s.charAt(s.length() - 1) == '\n';
931
932        s = padding + s.replace("\n", "\n" + padding);
933
934        return lastIsNewLine
935            ? s.substring(0, s.length() - padding.length())
936            : s;
937    }
938
939    /**
940     * Identical to {@link #indent(String, int)}, but pre-pends a {@code TAB} character {@code 'n'}
941     * times, rather than a space-character {@code ' '}.
942     * 
943     * @param s Any {@code java.lang.String} - preferably one that contains new-line characters.
944     * 
945     * @param n The number of tab ({@code '\t'}) characters to use when pre-pending to each line of
946     * text within input-parameter {@code 's'}
947     * 
948     * @return A new {@code java.lang.String} where each line of text has been indented by
949     * {@code 'n'} tab characters.  If the text ends with a new-line, that line of text is not
950     * indented.
951     * 
952     * @throws NException If parameter {@code 'n'} is less than one.
953     */
954    public static String indentTabs(String s, int n)
955    {
956        if (n < 1) throw new NException(
957            "The value passed to parameter 'n' was [" + n + "], but this is expected to be an " +
958            "integer greater than or equal to 1."
959        );
960
961        if (s.length() == 0) return "";
962
963        String  padding         = String.format("%1$"+ n + "s", "\t");
964        boolean lastIsNewLine   = s.charAt(s.length() - 1) == '\n';
965
966        s = padding + s.replace("\n", "\n" + padding);
967
968        return lastIsNewLine
969            ? s.substring(0, s.length() - padding.length())
970            : s;
971    }
972
973    /**
974     * This method replaces all '\n' characters with pre-pended white-space indentation, similar
975     * to the other two methods of the same name.  The difference, here, and the other two
976     * functions is this one <I>does not indent lines of text that only contain white-space!</I>
977     * 
978     * <DIV CLASS=EXAMPLE>{@code
979     * // *****************************
980     * // VERSION 1: Identical Output
981     * String s1 = "Hello World!";
982     * 
983     * System.out.println(indent(s1, 4));
984     * System.out.println(indent(s1, 4, true, true));
985     * // Both Print: "    Hello World!"
986     * 
987     * // *****************************
988     * // VERSION 2: Output's differ
989     * String s2 = "Hello World!\n\nSincerely,\n\nCorporate Headquarters.\n";
990     *
991     * System.out.println(indent(s2, 4));
992     * // Prints: "    Hello World!\n    \n    Sincerely,\n    \n    Corporate Headquarters.\n"
993     *
994     * System.out.println(indent(s2, 4, true, true));
995     * // Prints: "    Hello World!\n\n    Sincerely,\n\n    Corporate Headquarters.\n"
996     * // NOTICE: Blank lines are not indented.
997     * }</DIV>
998     * 
999     * @param s Any {@code java.lang.String} - preferably one that contains new-line characters.
1000     * 
1001     * @param n The number of tab ({@code '\t'}) characters to use when pre-pending to each line of
1002     * text in input-parameter {@code 's'}
1003     * 
1004     * @param spaceOrTab When this parameter is passed <B>{@code TRUE}</B>, the space character
1005     * {@code ' '} is used for indentation.  When <B>{@code FALSE}</B>, the tab-character
1006     * {@code '\t'} is used.
1007     * 
1008     * @return A new {@code java.lang.String} where each line of text has been indented by
1009     * {@code 'n'} characters of spaces or tabs, dependent upon the value of parameter 
1010     * {@code 'spaceOrTab'}.
1011     * 
1012     * <BR /><BR />The returned {@code String} differs from the returns of the other two
1013     * {@code 'indent'} methods in that any new-line that contains only white-space, or any
1014     * new-line that is empty and is immediately-followed-by another newline,
1015     * <I>is not idented</I>.  Succinctly, only lines containing non-white-space characters are
1016     * actually indented.
1017     * 
1018     * @throws NException If parameter {@code 'n'} is less than one.
1019     */
1020    public static String indent
1021        (final String s, int n, boolean spaceOrTab, boolean trimBlankLines)
1022    {
1023        if (n < 1) throw new NException(
1024            "The value passed to parameter 'n' was [" + n + "], but this is expected to be an " +
1025            "integer greater than or equal to 1."
1026        );
1027
1028        if (s.length() == 0) return "";
1029
1030        final String padding = String.format("%1$"+ n + "s", spaceOrTab ? " " : "\t");
1031
1032        // This replacement-function does a 'look-ahead'.  If the next character after a newline
1033        // character '\n' is *also* a '\n', then the first '\n' is left alone (not indented)
1034        IntCharFunction<String> replFunc = (int i, char c) ->
1035        {
1036            while (i < (s.length() - 1))
1037            {
1038                c = s.charAt(++i);
1039                if (c == '\n')                  return "\n";
1040                if ((c != ' ') && (c != '\t'))  return "\n" + padding;
1041            }
1042
1043            return "\n";
1044        };
1045
1046        // NOTE: private static final char[] cArrIndent = { '\n' };
1047        String ret = StrReplace.r(s, cArrIndent, replFunc);
1048
1049        if (trimBlankLines) ret = trimWhiteSpaceOnlyLines(ret);
1050
1051        // Indent the first line of text - insert the padding before the final returned string.
1052        // NOTE: This is somewhat inefficient, because the whole array needs to be copied again.
1053        //       Perhaps switching to RegEx and matching '^' is better (because of this reason).
1054        // Special Case:    The first character, itself, is a new-line.
1055        return (ret.charAt(0) != '\n') ? (padding + ret) : ret;
1056    }
1057
1058    /**
1059     * Throws a new {@code ToDoException}
1060     * 
1061     * @return Will (one day) return an unindented String.
1062     */
1063    public static String unIndent(
1064            String s, int n,
1065            boolean trimBlankLines,
1066            boolean rightTrimLines,
1067            boolean throwOnTab, 
1068            boolean throwOnNotEnough,
1069            boolean dontThrowOnWhiteSpaceOnlyLines
1070        )
1071    {
1072        if (n < 1) throw new NException(
1073            "The value that was passed to parameter 'n' was [" + n + "], but unfortunately this " +
1074            "expected to be a positive integer, greater than zero."
1075        );
1076
1077        char[] cArr = s.toCharArray();
1078        throw new ToDoException();
1079    }
1080
1081    /**
1082     * Performs an indenting of {@code String} of text, but does not indent the first line.  This
1083     * is used quit frequently by code-generators that need to assign or invoke something, and want
1084     * to make sure that subsequent lines of piece of code are indented (after the first line of
1085     * text).
1086     * 
1087     * @param s Any instance of {@code java.lang.String}.
1088     * @param n The number of space-characters to insert after each newline {@code '\n'} character.
1089     * @param spaceOrTab When {@code TRUE}, then there shall be {@code 'n'}-number of space
1090     * characters ({@code ' '}) inserted at the beginning of each line of text.  When
1091     * {@code FALSE}, then this function will insert {@code 'n'} tab characters.
1092     * @param trimBlankLines When {@code TRUE}, requests that blank lines be trimmed to only
1093     * a single newline ({@code '\n'}) character.
1094     * @return The indented {@code String}
1095     */
1096    public static String indentAfter2ndLine
1097        (String s, int n, boolean spaceOrTab, boolean trimBlankLines)
1098    {
1099        int pos = s.indexOf('\n'); // If there are no newlines, then return the original string.
1100        if (pos == -1) return s;
1101
1102        pos++;
1103        return // return the first string, as is, and then indent subsequent lines.
1104            s.substring(0, pos) + 
1105            indent(s.substring(pos), n, spaceOrTab, trimBlankLines);
1106    }
1107
1108    /**
1109     * This will replaced leading tabs for each line of text in a source code file with a specified
1110     * number of spaces.  If tabs are supposed to represent {@code 4} spaces, then if a line in a
1111     * source-code file had three leading tab-characters, then those three leading
1112     * {@code char '\t'} would be replaced with {@code 12} leading space characters {@code ' '}.
1113     * 
1114     * @param javaSrcFileAsStr A Java Source-Code File, loaded as a {@code String}.
1115     * 
1116     * @param numSpacesPerTab This identifies the number of spaces a {@code char '\t'} is supposed
1117     * to represent in any source-code editor's settings.  In Google Cloud Server, the default
1118     * value is '4' spaces.
1119     */
1120    public static String tabsToSpace(String javaSrcFileAsStr, int numSpacesPerTab)
1121    {
1122        String spaces   = StringParse.nChars(' ', numSpacesPerTab);
1123        String[] lines  = javaSrcFileAsStr.split("\n");
1124
1125        for (String line:lines) System.out.println("LINE: " + line);
1126
1127        for (int i=0; i < lines.length; i++)
1128        {
1129            int numTabs = 0;
1130
1131            while ((numTabs < lines[i].length()) && (lines[i].charAt(numTabs) == '\t'))
1132                numTabs++;
1133
1134            if (numTabs == 0)
1135                lines[i] = lines[i] + '\n';
1136            else
1137                lines[i] = StringParse.nStrings(spaces, numTabs) +
1138                lines[i].substring(numTabs) + '\n';
1139        }
1140
1141        StringBuilder sb = new StringBuilder();
1142
1143        for (String line : lines) sb.append(line);
1144
1145        return sb.toString();
1146    }
1147}