001package Torello.Java;
002
003import java.util.regex.*;
004
005import java.util.stream.IntStream;
006
007import Torello.Browser.Debugger.scriptParsed;
008import Torello.Java.Function.IntCharFunction;
009
010import Torello.JavaDoc.StaticFunctional;
011import Torello.JavaDoc.Excuse;
012import Torello.JavaDoc.LinkJavaSource;
013
014/**
015 * A class for indenting, unindenting and trimming textual-strings.
016 * 
017 * <BR /><BR /><EMBED CLASS='external-html' DATA-FILE-ID=STRINDENT>
018 */
019@StaticFunctional
020public class StrIndent
021{
022    private StrIndent() { }
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    /**
028     * Replaces sub-strings that contain a newline character followed by only white-space with
029     * only the new-line character itself.
030     *
031     * @param s Any Java {@code String}, but preferrably one which contains newline characters
032     * ({@code '\n'}), and white-space characters immediately followng the newline, but not other
033     * ASCII nor UNICODE.
034     *
035     * @return The same String with each instance of {@code ^[ \t]+\n} replaced by {@code '\n'}.
036     */
037    public static String trimWhiteSpaceOnlyLines(String s)
038    {
039        Matcher m = EMPTY_LINE.matcher(s);
040        return m.replaceAll("\n");
041    }
042
043    /**
044     * Will iterate through <I>each line of text</I> within the input {@code String}-parameter
045     * {@code 's'}, and right-trim the lines.  "Right Trim" means to remove all white-space
046     * characters that occur <I><B>after</I></B> the last non-white-space character on the line.
047     * (Does not remove the new-line character ({@code '\n'}) itself).
048     * 
049     * <BR /><BR /><B>NOTE:</B> Any line of text which contains only white-space characters is
050     * reduced to a single new-line character.
051     * 
052     * @param s Any Java {@code String}, preferrably one with several new-line characters.
053     * 
054     * @return The same text, but only after having any 'trailing white-space' characters removed
055     * from each line of text.
056     */
057    @LinkJavaSource(handle="RightTrimAll")
058    public static String rightTrimAll(String s)
059    { return RightTrimAll.run(s); }
060
061    /**
062     * Will iterate through <I>each line of text</I> within the input {@code String}-parameter
063     * {@code 's'}, and left-trim the lines.  "Left Trim" means to remove all white-space
064     * characters that occur <I><B>before</I></B> the last non-white-space character on the line.
065     * (Does not remove the new-line character ({@code '\n'}) itself).
066     * 
067     * <BR /><BR /><B>NOTE:</B> Any line of text which contains only white-space characters is
068     * reduced to a single new-line character.
069     * 
070     * @param s Any Java {@code String}, preferrably one with several new-line characters.
071     * 
072     * @return The same text, but only after having any 'leading white-space' characters removed
073     * from each line of text.
074     */
075    @LinkJavaSource(handle="LeftTrimAll")
076    public static String leftTrimAll(String s)
077    { return LeftTrimAll.run(s); }
078
079    /**
080     * This method expects to receive a method body, constructor body, or other callable body as a
081     * {@code String}; it will remove the beginning and ending braces <CODE>('&#123;'</CODE> &amp;
082     * <CODE>'&#125;')</CODE>, and beginning &amp; ending empty lines.  This is to prepare the
083     * method for code hiliting, used internally by the package {@code Torello.JavaDoc}.
084     * 
085     * @param callableAsStr This should be a method body.  Make sure <B>**NOT TO INCLUDE**</B> the
086     * method signature at the beginning of the method.  The first non-white-space character should
087     * be the open braces character <CODE>('&#123;')</CODE>, and the last non-white-space should be
088     * the closing braces character <CODE>('&amp;#125')</CODE>.
089     * 
090     * @return A method body {@code String} that can be hilited using the code-hiliting mechanism
091     * of the "JavaDoc Package."
092     * 
093     * @throws CallableBodyException If the input parameter {@code String} does not begin and end
094     * with the curly-braces.
095     */
096    @LinkJavaSource(handle="ChompCallableBraces")
097    public static String chompCallableBraces(String callableAsStr)
098    { return ChompCallableBraces.run(callableAsStr); }
099
100    /**
101     * Accepts a method body as a {@code String} and left-shifts or right-shifts each line
102     * of text (each {@code 'Line of Code' - LOC}) so that the indentation is consistent with
103     * the requested-indentation input-parameter.
104     * 
105     * <BR /><BR /><B>NOTE:</B> The lines of code contained by input-parameter {@code 'codeAsStr'}
106     * must contain leading white-space <I><B STYLE='color: red;'>without any tab ({@code '\t'})
107     * characters.</B></I>  The reasoning here is that tabs can be interpreted in many different
108     * ways, and therefore it is required to replace them before invoking this method.  This method
109     * merely adds or removes leading {@code ASCII 0x20} (space-bar characters) from the beginning
110     * of each line of text.  A leading tab-character {@code ASCII 0x09} will generate an exception
111     * throw.
112     * 
113     * @param codeAsStr A method body.  It is expected to be the internal part of a chunk of
114     * source code.
115     * 
116     * @param requestedIndent The requested amount of indentation for the method-body.  The
117     * line of code that contains the shortest amount of indentation (white-space) will be
118     * calculated, and then all LOC's shall be left-shifted (or right-shifted) according to that
119     * LOC which contained the least amount of leading white-space.
120     * 
121     * @return An updated method-body as a {@code String}.
122     * 
123     * @throws StringFormatException  This exception shall throw whenever a line of text contained
124     * by the input {@code String} has a {@code '\t'} (tab-character) before the first 
125     * non-white-space character on that line of text.  Code that contains tab-characters is
126     * invariably "auto-indented" by the Code-Editor when loaded into the GUI, and the amount of
127     * indentation applied for each tab-character is usually configurable.  Because there are many
128     * variants of how tab's {@code '\t'} gets interpreted by the editor, it is required to replace
129     * these characters first before invoking this method.
130     */
131    @LinkJavaSource(handle="SetCodeIndent")
132    public static String setCodeIndent(String codeAsStr, int requestedIndent)
133    { return SetCodeIndent.run(codeAsStr, requestedIndent); }
134
135    /**
136     * Convenience Method.
137     * <BR />See Documentation: {@link #setCodeIndent(String, int)}
138     * <BR />Converts: All {@code '\t'} to the specified number of spaces in parameter
139     * {@code SPACES}.
140     * <BR /><B STYLE='color: red'>NOTE:</B> Exception-Checking is <B STYLE='color: red'>NOT</B>
141     * done on input
142     */
143    @LinkJavaSource(handle="SetCodeIndent")
144    public static String setCodeIndent_WithTabsPolicyAbsolute
145        (String codeAsStr, int requestedIndent, String SPACES)
146    { return SetCodeIndent.run(codeAsStr.replace("\t", SPACES), requestedIndent); }
147
148    /**
149     * Adjusts code-indentation using a relative-sized tab-policy.  This method performs the
150     * equivalent of shifting the entire text-block, proportionately, to the left or right.
151     *
152     * <BR /><BR />To do this, first, the number of spaces that preceed the
153     * <B STYLE='color: red;'>least-indented</B> line is computed, and afterwards, every line in
154     * the text is shifted by an identical number of space-characters.  The number of spaces that
155     * are either added or removed from each line is dependent on whether the requested
156     * indentation (parameter {@code 'requestedIndent'}) is greater-than or less-than the computed
157     * least-indented line.
158     * 
159     * <EMBED CLASS='external-html' DATA-FILE-ID=SI_REL_TABS>
160     * 
161     * @param codeAsStr Any Java source-code block, as a {@code java.lang.String}
162     * 
163     * @param requestedIndent The number of spaces that the code should have as indentation.
164     * Note, that in the JavaDoc Upgrader code, this number is always {@code '1'}.
165     * 
166     * @param spacesPerTab If tabs are found inside this {@code String}, then they are replaced
167     * with an appropriate number of space characters, according to a relative tab-policy, as
168     * described above.
169     * 
170     * @return A properly shifted-indented Java source-code block.
171     */
172    @LinkJavaSource(handle="SetCodeIndentTabsPolicy")
173    public static String setCodeIndent_WithTabsPolicyRelative
174        (String codeAsStr, int requestedIndent, int spacesPerTab)
175    { return SetCodeIndentTabsPolicy.run(codeAsStr, requestedIndent, spacesPerTab); }
176
177    /**
178     * Helper Method for calculating the number of space characters to be used at the beginning
179     * of a line of code, all the while obeying a particular tabs-policy.
180     * 
181     * <BR /><BR /><B STYLE='color: red;'>IMPORTANT:</B> None of the parameters to this method
182     * will be checked for errors.  This method is often used inside of a loop, and improper 
183     * input should be presumed to cause indeterminate results.
184     * 
185     * @param code A Java source-code {@code String}, that has been converted into a Java
186     * {@code char[]}-Array.  The line of code whose leading white-space is being computed may be
187     * located anywhere in the array.
188     * 
189     * <BR /><BR /><B>NOTE:</B> These types of arrays are easily creaated by invoking the
190     * {@code java.lang.String} method {@code 'toCharArray()'}
191     * 
192     * @param lineFirstCharacterPos The array-index to be considered as the first character of
193     * non-new-line character data.
194     * 
195     * @param spacesPerTab The number of spaces that a tab-character ({@code '\t'}) intends to
196     * represent.
197     * 
198     * <BR /><BR />When {@code FALSE} is passed to this parameter, a tab-character will represent
199     * a {@code String} of space-characters whose length is equal to the number of space-characters
200     * that remain until the next modulo-{@code spacesPerTab} boundary.
201     * 
202     * @return The number of space-characters ({@code ' '}) that should preceede the line of source
203     * code.
204     * 
205     * <BR /><BR /><B STYLE='color: red'>NOTE:</B> If this line of source-code is a white-space
206     * <B STYLE='color: red;'>ONLY</B> line, then {@code -1} will be returned.
207     */
208    public static int computeEffectiveLeadingWhiteSpace
209        (char[] code, int lineFirstCharacterPos, int spacesPerTab)
210    {
211        int ret = 0;
212        int relativeCount = 0;
213
214        for (int i=lineFirstCharacterPos; i < code.length; i++)
215
216            if (! Character.isWhitespace(code[i])) return ret;
217
218            else switch (code[i])
219            {
220                case ' ' : 
221                    ret++;
222                    relativeCount = (relativeCount + 1) % spacesPerTab;
223                    break;
224
225                case '\t' :
226                    ret += (spacesPerTab - relativeCount);
227                    relativeCount = 0;
228                    break;
229
230                case '\r' : 
231                case '\f' : break;
232                case '\n' : return -1;
233                default: throw new UnreachableError();
234            }
235
236        return -1;
237    }
238
239    /**
240     * Replaces tab-characters ({@code '\t'}) in a single-line of source-code with a
241     * relative-number of space-characters ({@code ' '}).
242     * 
243     * <EMBED CLASS='external-html' DATA-FILE-ID=SI_REL_TABS>
244     * 
245     * <BR /><BR /><B STYLE='color: red;'>IMPORTANT:</B> None of the parameters to this method
246     * will be checked for errors.  This method is often used inside of a loop, and improper 
247     * input should be presumed to cause indeterminate results.
248     * 
249     * @param code This should be the source-code, converted to a character-array.  The specific
250     * line in the source-code being properly space-adjusted may be located anywhere in this
251     * array.
252     * 
253     * <BR /><BR /><B>NOTE:</B> These types of arrays are easily creaated by invoking the
254     * {@code java.lang.String} method {@code 'toCharArray()'}
255     * 
256     * @param numLeadingSpaces The number of spaces that have been placed before the start of this
257     * line of code.  This is needed because <B STYLE='color: red;'>relative</B>-tabs are computed
258     * based on integral-multiples of the tab-width ({@code 'spacesPerTab'}).
259     * 
260     * <BR /><BR />This method is a helper &amp; example method that may be used in conjunction 
261     * with properly indenting source-code.  Note that the number of leading-spaces may not be
262     * identicaly to the actual number of white-space characters in the array.  <I>After converting
263     * tab-characters ({@code '\t'}) to spaces ({@code ' '}), this number will often change.</I>
264     * 
265     * @param fcPos This parameter should contain the location of the first source-code character
266     * in the line of code.  This parameter should be an array-index that
267     * <B STYLE='color: red;'>does not</B> contain white-space.
268     * 
269     * @param spacesPerTab The number of spaces that are used to replaces tab-characters.  Since 
270     * this method performs relative tab-replacement, this constitutes the
271     * <B STYLE='color: red;'>maximum</B> number of space characters that will be used to replace 
272     * a tab.
273     * 
274     * @return A line of code, as a {@code String}, without any leading white-space, and one in
275     * which all tab-characters have been replaced by spaces.
276     */
277    @LinkJavaSource(handle="LOCAsStr")
278    public static String lineOfCodeAsStr
279        (char[] code, int numLeadingSpaces, int fcPos, int spacesPerTab)
280    { return LOCAsStr.run(code, numLeadingSpaces, fcPos, spacesPerTab); }
281
282    /**
283     * This performs a variation of "indentation" on a Java {@code String} - simply put - it
284     * replaces each new-line character ({@code '\n'}) with a {@code String} that begins with a
285     * new-line, and is followed by {@code 'n'} blank white-space characters {@code ' '}.
286     * 
287     * If the input {@code String} parameter {@code 's'} is of zero-length, then the zero-length
288     * {@code String} is returned.  If the final character in the input {@code String} is a 
289     * new-line, that new-line is not padded.
290     * 
291     * @param s Any {@code java.lang.String} - preferably one that contains new-line characters.
292     * 
293     * @param n The number of white-space characters to use when pre-pending white-space to each
294     * line of text in input-parameter {@code 's'}
295     * 
296     * @return A new {@code java.lang.String} where each line of text has been indented by
297     * {@code 'n'} blank white-space characters.  If the text ends with a new-line, that line of
298     * text is not indented.
299     * 
300     * @throws NException If parameter {@code 'n'} is less than one.
301     */
302    public static String indent(String s, int n)
303    {
304        if (n < 1) throw new NException(
305            "The value passed to parameter 'n' was [" + n + "], but this is expected to be an " +
306            "integer greater than or equal to 1."
307        );
308
309        if (s.length() == 0) return "";
310
311        String  padding         = String.format("%1$" + n + "s", " ");
312        boolean lastIsNewLine   = s.charAt(s.length() - 1) == '\n';
313
314        s = padding + s.replace("\n", "\n" + padding);
315
316        return lastIsNewLine
317            ? s.substring(0, s.length() - padding.length())
318            : s;
319    }
320
321    /**
322     * Identical to {@link #indent(String, int)}, but pre-pends a {@code TAB} character {@code 'n'}
323     * times, rather than a space-character {@code ' '}.
324     * 
325     * @param s Any {@code java.lang.String} - preferably one that contains new-line characters.
326     * 
327     * @param n The number of tab ({@code '\t'}) characters to use when pre-pending to each line of
328     * text within input-parameter {@code 's'}
329     * 
330     * @return A new {@code java.lang.String} where each line of text has been indented by
331     * {@code 'n'} tab characters.  If the text ends with a new-line, that line of text is not
332     * indented.
333     * 
334     * @throws NException If parameter {@code 'n'} is less than one.
335     */
336    public static String indentTabs(String s, int n)
337    {
338        if (n < 1) throw new NException(
339            "The value passed to parameter 'n' was [" + n + "], but this is expected to be an " +
340            "integer greater than or equal to 1."
341        );
342
343        if (s.length() == 0) return "";
344
345        String  padding         = String.format("%1$"+ n + "s", "\t");
346        boolean lastIsNewLine   = s.charAt(s.length() - 1) == '\n';
347
348        s = padding + s.replace("\n", "\n" + padding);
349
350        return lastIsNewLine
351            ? s.substring(0, s.length() - padding.length())
352            : s;
353    }
354
355    /**
356     * <EMBED CLASS='external-html' DATA-FILE-ID=SI_INDENT_DESC>
357     * 
358     * @param s Any {@code java.lang.String} - preferably one that contains new-line characters.
359     * 
360     * @param n The number of tab ({@code '\t'}) characters to use when pre-pending to each line of
361     * text in input-parameter {@code 's'}
362     * 
363     * @param spaceOrTab When this parameter is passed <B>{@code TRUE}</B>, the space character
364     * {@code ' '} is used for indentation.  When <B>{@code FALSE}</B>, the tab-character
365     * {@code '\t'} is used.
366     * 
367     * @return <EMBED CLASS='external-html' DATA-FILE-ID=SI_INDENT_RET>
368     * @throws NException If parameter {@code 'n'} is less than one.
369     */
370    @LinkJavaSource(handle="Indent")
371    public static String indent
372        (final String s, int n, boolean spaceOrTab, boolean trimBlankLines)
373    { return Indent.run(s, n, spaceOrTab, trimBlankLines); }
374
375    /**
376     * Throws a new {@code ToDoException}
377     * 
378     * @return Will (one day) return an unindented String.
379     */
380    public static String unIndent(
381            String s, int n,
382            boolean trimBlankLines,
383            boolean rightTrimLines,
384            boolean throwOnTab, 
385            boolean throwOnNotEnough,
386            boolean dontThrowOnWhiteSpaceOnlyLines
387        )
388    {
389        if (n < 1) throw new NException(
390            "The value that was passed to parameter 'n' was [" + n + "], but unfortunately this " +
391            "expected to be a positive integer, greater than zero."
392        );
393
394        char[] cArr = s.toCharArray();
395        throw new ToDoException();
396    }
397
398    /**
399     * Performs an indenting of {@code String} of text, but does not indent the first line.  This
400     * is used quit frequently by code-generators that need to assign or invoke something, and want
401     * to make sure that subsequent lines of piece of code are indented (after the first line of
402     * text).
403     * 
404     * @param s Any instance of {@code java.lang.String}.
405     * @param n The number of space-characters to insert after each newline {@code '\n'} character.
406     * @param spaceOrTab When {@code TRUE}, then there shall be {@code 'n'}-number of space
407     * characters ({@code ' '}) inserted at the beginning of each line of text.  When
408     * {@code FALSE}, then this function will insert {@code 'n'} tab characters.
409     * @param trimBlankLines When {@code TRUE}, requests that blank lines be trimmed to only
410     * a single newline ({@code '\n'}) character.
411     * @return The indented {@code String}
412     */
413    public static String indentAfter2ndLine
414        (String s, int n, boolean spaceOrTab, boolean trimBlankLines)
415    {
416        int pos = s.indexOf('\n'); // If there are no newlines, then return the original string.
417        if (pos == -1) return s;
418
419        pos++;
420        return // return the first string, as is, and then indent subsequent lines.
421            s.substring(0, pos) + 
422            indent(s.substring(pos), n, spaceOrTab, trimBlankLines);
423    }
424
425    /**
426     * This will replaced leading tabs for each line of text in a source code file with a specified
427     * number of spaces.  If tabs are supposed to represent {@code 4} spaces, then if a line in a
428     * source-code file had three leading tab-characters, then those three leading
429     * {@code char '\t'} would be replaced with {@code 12} leading space characters {@code ' '}.
430     * 
431     * @param javaSrcFileAsStr A Java Source-Code File, loaded as a {@code String}.
432     * 
433     * @param numSpacesPerTab This identifies the number of spaces a {@code char '\t'} is supposed
434     * to represent in any source-code editor's settings.  In Google Cloud Server, the default
435     * value is '4' spaces.
436     */
437    public static String tabsToSpace(String javaSrcFileAsStr, int numSpacesPerTab)
438    {
439        String spaces   = StringParse.nChars(' ', numSpacesPerTab);
440        String[] lines  = javaSrcFileAsStr.split("\n");
441
442        for (String line:lines) System.out.println("LINE: " + line);
443
444        for (int i=0; i < lines.length; i++)
445        {
446            int numTabs = 0;
447
448            while ((numTabs < lines[i].length()) && (lines[i].charAt(numTabs) == '\t'))
449                numTabs++;
450
451            if (numTabs == 0)
452                lines[i] = lines[i] + '\n';
453            else
454                lines[i] = StringParse.nStrings(spaces, numTabs) +
455                lines[i].substring(numTabs) + '\n';
456        }
457
458        StringBuilder sb = new StringBuilder();
459
460        for (String line : lines) sb.append(line);
461
462        return sb.toString();
463    }
464}