1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
package Torello.Java;

import java.util.regex.*;

import java.util.stream.IntStream;

import Torello.Browser.Debugger.scriptParsed;
import Torello.Java.Function.IntCharFunction;

import Torello.JavaDoc.StaticFunctional;
import Torello.JavaDoc.Excuse;
import Torello.JavaDoc.LinkJavaSource;

/**
 * A class for indenting, unindenting and trimming textual-strings.
 * 
 * <BR /><BR /><EMBED CLASS='external-html' DATA-FILE-ID=STRINDENT>
 */
@StaticFunctional
public class StrIndent
{
	private StrIndent() { }

    // This matches lines of text that contain only blank / white-space characters
    private static final Pattern EMPTY_LINE = Pattern.compile("^[ \t]+\n", Pattern.MULTILINE);

    /**
     * Replaces sub-strings that contain a newline character followed by only white-space with
     * only the new-line character itself.
     *
     * @param s Any Java {@code String}, but preferrably one which contains newline characters
     * ({@code '\n'}), and white-space characters immediately followng the newline, but not other
     * ASCII nor UNICODE.
     *
     * @return The same String with each instance of {@code ^[ \t]+\n} replaced by {@code '\n'}.
     */
    public static String trimWhiteSpaceOnlyLines(String s)
    {
        Matcher m = EMPTY_LINE.matcher(s);
        return m.replaceAll("\n");
    }

    /**
     * Will iterate through <I>each line of text</I> within the input {@code String}-parameter
     * {@code 's'}, and right-trim the lines.  "Right Trim" means to remove all white-space
     * characters that occur <I><B>after</I></B> the last non-white-space character on the line.
     * (Does not remove the new-line character ({@code '\n'}) itself).
     * 
     * <BR /><BR /><B>NOTE:</B> Any line of text which contains only white-space characters is
     * reduced to a single new-line character.
     * 
     * @param s Any Java {@code String}, preferrably one with several new-line characters.
     * 
     * @return The same text, but only after having any 'trailing white-space' characters removed
     * from each line of text.
     */
    @LinkJavaSource(handle="RightTrimAll")
    public static String rightTrimAll(String s)
    { return RightTrimAll.run(s); }

    /**
     * Will iterate through <I>each line of text</I> within the input {@code String}-parameter
     * {@code 's'}, and left-trim the lines.  "Left Trim" means to remove all white-space
     * characters that occur <I><B>before</I></B> the last non-white-space character on the line.
     * (Does not remove the new-line character ({@code '\n'}) itself).
     * 
     * <BR /><BR /><B>NOTE:</B> Any line of text which contains only white-space characters is
     * reduced to a single new-line character.
     * 
     * @param s Any Java {@code String}, preferrably one with several new-line characters.
     * 
     * @return The same text, but only after having any 'leading white-space' characters removed
     * from each line of text.
     */
    @LinkJavaSource(handle="LeftTrimAll")
    public static String leftTrimAll(String s)
    { return LeftTrimAll.run(s); }

    /**
     * This method expects to receive a method body, constructor body, or other callable body as a
     * {@code String}; it will remove the beginning and ending braces <CODE>('&#123;'</CODE> &amp;
     * <CODE>'&#125;')</CODE>, and beginning &amp; ending empty lines.  This is to prepare the
     * method for code hiliting, used internally by the package {@code Torello.JavaDoc}.
     * 
     * @param callableAsStr This should be a method body.  Make sure <B>**NOT TO INCLUDE**</B> the
     * method signature at the beginning of the method.  The first non-white-space character should
     * be the open braces character <CODE>('&#123;')</CODE>, and the last non-white-space should be
     * the closing braces character <CODE>('&amp;#125')</CODE>.
     * 
     * @return A method body {@code String} that can be hilited using the code-hiliting mechanism
     * of the "JavaDoc Package."
     * 
     * @throws CallableBodyException If the input parameter {@code String} does not begin and end
     * with the curly-braces.
     */
    @LinkJavaSource(handle="ChompCallableBraces")
    public static String chompCallableBraces(String callableAsStr)
    { return ChompCallableBraces.run(callableAsStr); }

    /**
     * Accepts a method body as a {@code String} and left-shifts or right-shifts each line
     * of text (each {@code 'Line of Code' - LOC}) so that the indentation is consistent with
     * the requested-indentation input-parameter.
     * 
     * <BR /><BR /><B>NOTE:</B> The lines of code contained by input-parameter {@code 'codeAsStr'}
     * must contain leading white-space <I><B STYLE='color: red;'>without any tab ({@code '\t'})
     * characters.</B></I>  The reasoning here is that tabs can be interpreted in many different
     * ways, and therefore it is required to replace them before invoking this method.  This method
     * merely adds or removes leading {@code ASCII 0x20} (space-bar characters) from the beginning
     * of each line of text.  A leading tab-character {@code ASCII 0x09} will generate an exception
     * throw.
     * 
     * @param codeAsStr A method body.  It is expected to be the internal part of a chunk of
     * source code.
     * 
     * @param requestedIndent The requested amount of indentation for the method-body.  The
     * line of code that contains the shortest amount of indentation (white-space) will be
     * calculated, and then all LOC's shall be left-shifted (or right-shifted) according to that
     * LOC which contained the least amount of leading white-space.
     * 
     * @return An updated method-body as a {@code String}.
     * 
     * @throws StringFormatException  This exception shall throw whenever a line of text contained
     * by the input {@code String} has a {@code '\t'} (tab-character) before the first 
     * non-white-space character on that line of text.  Code that contains tab-characters is
     * invariably "auto-indented" by the Code-Editor when loaded into the GUI, and the amount of
     * indentation applied for each tab-character is usually configurable.  Because there are many
     * variants of how tab's {@code '\t'} gets interpreted by the editor, it is required to replace
     * these characters first before invoking this method.
     */
    @LinkJavaSource(handle="SetCodeIndent")
    public static String setCodeIndent(String codeAsStr, int requestedIndent)
    { return SetCodeIndent.run(codeAsStr, requestedIndent); }

    /**
     * Convenience Method.
     * <BR />See Documentation: {@link #setCodeIndent(String, int)}
     * <BR />Converts: All {@code '\t'} to the specified number of spaces in parameter
     * {@code SPACES}.
     * <BR /><B STYLE='color: red'>NOTE:</B> Exception-Checking is <B STYLE='color: red'>NOT</B>
     * done on input
     */
    @LinkJavaSource(handle="SetCodeIndent")
    public static String setCodeIndent_WithTabsPolicyAbsolute
        (String codeAsStr, int requestedIndent, String SPACES)
    { return SetCodeIndent.run(codeAsStr.replace("\t", SPACES), requestedIndent); }

    /**
     * Adjusts code-indentation using a relative-sized tab-policy.  This method performs the
     * equivalent of shifting the entire text-block, proportionately, to the left or right.
     *
     * <BR /><BR />To do this, first, the number of spaces that preceed the
     * <B STYLE='color: red;'>least-indented</B> line is computed, and afterwards, every line in
     * the text is shifted by an identical number of space-characters.  The number of spaces that
     * are either added or removed from each line is dependent on whether the requested
     * indentation (parameter {@code 'requestedIndent'}) is greater-than or less-than the computed
     * least-indented line.
     * 
     * <EMBED CLASS='external-html' DATA-FILE-ID=SI_REL_TABS>
     * 
     * @param codeAsStr Any Java source-code block, as a {@code java.lang.String}
     * 
     * @param requestedIndent The number of spaces that the code should have as indentation.
     * Note, that in the JavaDoc Upgrader code, this number is always {@code '1'}.
     * 
     * @param spacesPerTab If tabs are found inside this {@code String}, then they are replaced
     * with an appropriate number of space characters, according to a relative tab-policy, as
     * described above.
     * 
     * @return A properly shifted-indented Java source-code block.
     */
    @LinkJavaSource(handle="SetCodeIndentTabsPolicy")
    public static String setCodeIndent_WithTabsPolicyRelative
        (String codeAsStr, int requestedIndent, int spacesPerTab)
    { return SetCodeIndentTabsPolicy.run(codeAsStr, requestedIndent, spacesPerTab); }

    /**
     * Helper Method for calculating the number of space characters to be used at the beginning
     * of a line of code, all the while obeying a particular tabs-policy.
     * 
     * <BR /><BR /><B STYLE='color: red;'>IMPORTANT:</B> None of the parameters to this method
     * will be checked for errors.  This method is often used inside of a loop, and improper 
     * input should be presumed to cause indeterminate results.
     * 
     * @param code A Java source-code {@code String}, that has been converted into a Java
     * {@code char[]}-Array.  The line of code whose leading white-space is being computed may be
     * located anywhere in the array.
     * 
     * <BR /><BR /><B>NOTE:</B> These types of arrays are easily creaated by invoking the
     * {@code java.lang.String} method {@code 'toCharArray()'}
     * 
     * @param lineFirstCharacterPos The array-index to be considered as the first character of
     * non-new-line character data.
     * 
     * @param spacesPerTab The number of spaces that a tab-character ({@code '\t'}) intends to
     * represent.
     * 
     * <BR /><BR />When {@code FALSE} is passed to this parameter, a tab-character will represent
     * a {@code String} of space-characters whose length is equal to the number of space-characters
     * that remain until the next modulo-{@code spacesPerTab} boundary.
     * 
     * @return The number of space-characters ({@code ' '}) that should preceede the line of source
     * code.
     * 
     * <BR /><BR /><B STYLE='color: red'>NOTE:</B> If this line of source-code is a white-space
     * <B STYLE='color: red;'>ONLY</B> line, then {@code -1} will be returned.
     */
    public static int computeEffectiveLeadingWhiteSpace
        (char[] code, int lineFirstCharacterPos, int spacesPerTab)
    {
        int ret = 0;
        int relativeCount = 0;

        for (int i=lineFirstCharacterPos; i < code.length; i++)

            if (! Character.isWhitespace(code[i])) return ret;

            else switch (code[i])
            {
                case ' ' : 
                    ret++;
                    relativeCount = (relativeCount + 1) % spacesPerTab;
                    break;

                case '\t' :
                    ret += (spacesPerTab - relativeCount);
                    relativeCount = 0;
                    break;

                case '\r' : 
                case '\f' : break;
                case '\n' : return -1;
                default: throw new UnreachableError();
            }

        return -1;
    }

    /**
     * Replaces tab-characters ({@code '\t'}) in a single-line of source-code with a
     * relative-number of space-characters ({@code ' '}).
     * 
     * <EMBED CLASS='external-html' DATA-FILE-ID=SI_REL_TABS>
     * 
     * <BR /><BR /><B STYLE='color: red;'>IMPORTANT:</B> None of the parameters to this method
     * will be checked for errors.  This method is often used inside of a loop, and improper 
     * input should be presumed to cause indeterminate results.
     * 
     * @param code This should be the source-code, converted to a character-array.  The specific
     * line in the source-code being properly space-adjusted may be located anywhere in this
     * array.
     * 
     * <BR /><BR /><B>NOTE:</B> These types of arrays are easily creaated by invoking the
     * {@code java.lang.String} method {@code 'toCharArray()'}
     * 
     * @param numLeadingSpaces The number of spaces that have been placed before the start of this
     * line of code.  This is needed because <B STYLE='color: red;'>relative</B>-tabs are computed
     * based on integral-multiples of the tab-width ({@code 'spacesPerTab'}).
     * 
     * <BR /><BR />This method is a helper &amp; example method that may be used in conjunction 
     * with properly indenting source-code.  Note that the number of leading-spaces may not be
     * identicaly to the actual number of white-space characters in the array.  <I>After converting
     * tab-characters ({@code '\t'}) to spaces ({@code ' '}), this number will often change.</I>
     * 
     * @param fcPos This parameter should contain the location of the first source-code character
     * in the line of code.  This parameter should be an array-index that
     * <B STYLE='color: red;'>does not</B> contain white-space.
     * 
     * @param spacesPerTab The number of spaces that are used to replaces tab-characters.  Since 
     * this method performs relative tab-replacement, this constitutes the
     * <B STYLE='color: red;'>maximum</B> number of space characters that will be used to replace 
     * a tab.
     * 
     * @return A line of code, as a {@code String}, without any leading white-space, and one in
     * which all tab-characters have been replaced by spaces.
     */
    @LinkJavaSource(handle="LOCAsStr")
    public static String lineOfCodeAsStr
        (char[] code, int numLeadingSpaces, int fcPos, int spacesPerTab)
    { return LOCAsStr.run(code, numLeadingSpaces, fcPos, spacesPerTab); }

    /**
     * This performs a variation of "indentation" on a Java {@code String} - simply put - it
     * replaces each new-line character ({@code '\n'}) with a {@code String} that begins with a
     * new-line, and is followed by {@code 'n'} blank white-space characters {@code ' '}.
     * 
     * If the input {@code String} parameter {@code 's'} is of zero-length, then the zero-length
     * {@code String} is returned.  If the final character in the input {@code String} is a 
     * new-line, that new-line is not padded.
     * 
     * @param s Any {@code java.lang.String} - preferably one that contains new-line characters.
     * 
     * @param n The number of white-space characters to use when pre-pending white-space to each
     * line of text in input-parameter {@code 's'}
     * 
     * @return A new {@code java.lang.String} where each line of text has been indented by
     * {@code 'n'} blank white-space characters.  If the text ends with a new-line, that line of
     * text is not indented.
     * 
     * @throws NException If parameter {@code 'n'} is less than one.
     */
    public static String indent(String s, int n)
    {
        if (n < 1) throw new NException(
            "The value passed to parameter 'n' was [" + n + "], but this is expected to be an " +
            "integer greater than or equal to 1."
        );

        if (s.length() == 0) return "";

        String  padding         = String.format("%1$" + n + "s", " ");
        boolean lastIsNewLine   = s.charAt(s.length() - 1) == '\n';

        s = padding + s.replace("\n", "\n" + padding);

        return lastIsNewLine
            ? s.substring(0, s.length() - padding.length())
            : s;
    }

    /**
     * Identical to {@link #indent(String, int)}, but pre-pends a {@code TAB} character {@code 'n'}
     * times, rather than a space-character {@code ' '}.
     * 
     * @param s Any {@code java.lang.String} - preferably one that contains new-line characters.
     * 
     * @param n The number of tab ({@code '\t'}) characters to use when pre-pending to each line of
     * text within input-parameter {@code 's'}
     * 
     * @return A new {@code java.lang.String} where each line of text has been indented by
     * {@code 'n'} tab characters.  If the text ends with a new-line, that line of text is not
     * indented.
     * 
     * @throws NException If parameter {@code 'n'} is less than one.
     */
    public static String indentTabs(String s, int n)
    {
        if (n < 1) throw new NException(
            "The value passed to parameter 'n' was [" + n + "], but this is expected to be an " +
            "integer greater than or equal to 1."
        );

        if (s.length() == 0) return "";

        String  padding         = String.format("%1$"+ n + "s", "\t");
        boolean lastIsNewLine   = s.charAt(s.length() - 1) == '\n';

        s = padding + s.replace("\n", "\n" + padding);

        return lastIsNewLine
            ? s.substring(0, s.length() - padding.length())
            : s;
    }

    /**
     * <EMBED CLASS='external-html' DATA-FILE-ID=SI_INDENT_DESC>
     * 
     * @param s Any {@code java.lang.String} - preferably one that contains new-line characters.
     * 
     * @param n The number of tab ({@code '\t'}) characters to use when pre-pending to each line of
     * text in input-parameter {@code 's'}
     * 
     * @param spaceOrTab When this parameter is passed <B>{@code TRUE}</B>, the space character
     * {@code ' '} is used for indentation.  When <B>{@code FALSE}</B>, the tab-character
     * {@code '\t'} is used.
     * 
     * @return <EMBED CLASS='external-html' DATA-FILE-ID=SI_INDENT_RET>
     * @throws NException If parameter {@code 'n'} is less than one.
     */
    @LinkJavaSource(handle="Indent")
    public static String indent
        (final String s, int n, boolean spaceOrTab, boolean trimBlankLines)
    { return Indent.run(s, n, spaceOrTab, trimBlankLines); }

    /**
     * Throws a new {@code ToDoException}
     * 
     * @return Will (one day) return an unindented String.
     */
    public static String unIndent(
            String s, int n,
            boolean trimBlankLines,
            boolean rightTrimLines,
            boolean throwOnTab, 
            boolean throwOnNotEnough,
            boolean dontThrowOnWhiteSpaceOnlyLines
        )
    {
        if (n < 1) throw new NException(
            "The value that was passed to parameter 'n' was [" + n + "], but unfortunately this " +
            "expected to be a positive integer, greater than zero."
        );

        char[] cArr = s.toCharArray();
        throw new ToDoException();
    }

    /**
     * Performs an indenting of {@code String} of text, but does not indent the first line.  This
     * is used quit frequently by code-generators that need to assign or invoke something, and want
     * to make sure that subsequent lines of piece of code are indented (after the first line of
     * text).
     * 
     * @param s Any instance of {@code java.lang.String}.
     * @param n The number of space-characters to insert after each newline {@code '\n'} character.
     * @param spaceOrTab When {@code TRUE}, then there shall be {@code 'n'}-number of space
     * characters ({@code ' '}) inserted at the beginning of each line of text.  When
     * {@code FALSE}, then this function will insert {@code 'n'} tab characters.
     * @param trimBlankLines When {@code TRUE}, requests that blank lines be trimmed to only
     * a single newline ({@code '\n'}) character.
     * @return The indented {@code String}
     */
    public static String indentAfter2ndLine
        (String s, int n, boolean spaceOrTab, boolean trimBlankLines)
    {
        int pos = s.indexOf('\n'); // If there are no newlines, then return the original string.
        if (pos == -1) return s;

        pos++;
        return // return the first string, as is, and then indent subsequent lines.
            s.substring(0, pos) + 
            indent(s.substring(pos), n, spaceOrTab, trimBlankLines);
    }

    /**
     * This will replaced leading tabs for each line of text in a source code file with a specified
     * number of spaces.  If tabs are supposed to represent {@code 4} spaces, then if a line in a
     * source-code file had three leading tab-characters, then those three leading
     * {@code char '\t'} would be replaced with {@code 12} leading space characters {@code ' '}.
     * 
     * @param javaSrcFileAsStr A Java Source-Code File, loaded as a {@code String}.
     * 
     * @param numSpacesPerTab This identifies the number of spaces a {@code char '\t'} is supposed
     * to represent in any source-code editor's settings.  In Google Cloud Server, the default
     * value is '4' spaces.
     */
    public static String tabsToSpace(String javaSrcFileAsStr, int numSpacesPerTab)
    {
        String spaces   = StringParse.nChars(' ', numSpacesPerTab);
        String[] lines  = javaSrcFileAsStr.split("\n");

        for (String line:lines) System.out.println("LINE: " + line);

        for (int i=0; i < lines.length; i++)
        {
            int numTabs = 0;

            while ((numTabs < lines[i].length()) && (lines[i].charAt(numTabs) == '\t'))
                numTabs++;

            if (numTabs == 0)
                lines[i] = lines[i] + '\n';
            else
                lines[i] = StringParse.nStrings(spaces, numTabs) +
                lines[i].substring(numTabs) + '\n';
        }

        StringBuilder sb = new StringBuilder();

        for (String line : lines) sb.append(line);

        return sb.toString();
    }
}