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
package Torello.Java;

class SetCodeIndent
{
    private static final String STR_FORMAT_EX_MESSAGE = 
        "One of the lines of code for the method-body as-a-string that was passed contained " +
        "a tab '\\t' character.  Source-Code String's that are passed to this method must have" +
        "been de-tabified.";

    private static int              INDENTATION_COUNTER     = 0;
    private static final boolean    DEBUGGING_INDENTATION   = false;

    static String run(String codeAsStr, int requestedIndent)
    {
        if (requestedIndent < 0) throw new IllegalArgumentException
            ("The requested indent passed to this method was a negative value: " + requestedIndent);

        else if (requestedIndent > 80) throw new IllegalArgumentException
            ("The requested indent passed to this method was greater than 80: " + requestedIndent);

        // Source-Code-String to char-array
        char[] cArr = codeAsStr.toCharArray();

        // Code "Starts With New Line '\n'"
        boolean swNL = cArr[0] == '\n';

        // Code "Ends With New Line"            
        boolean ewNL = cArr[cArr.length - 1] == '\n';

        // Location of each '\n' in the code
        int[] nlPosArr = StrIndexOf.all(codeAsStr, '\n');

        // Unless the first character in the code is '\n', numLines - num '\n' + 1
        int numLines = nlPosArr.length + (swNL ? 0 : 1);

        // Length of "Leading White Space" for each line of code
        int[] wsLenArr = new int[numLines];

        // TRUE / FALSE for "only white-space" lines of code
        boolean[] isOnlyWSArr = new boolean[numLines];

        // Amount of White-Space for the LEAST-INDENTED Line of Code
        int minIndent = Integer.MAX_VALUE;

        // These three variables are loop-control and temporary variables.
        int wsCount     = 0;    // "White Space Count" - amount of WS on each line
        int outArrPos   = 0;    // There are two parallel "Output Arrays", this the index
        int lastPos     = 0;    // Temp Var for the last-index in a line of code

        int i;                  // Simple Loop Control Variable
        int j;                  // Simple Loop Control Variable


        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
        // Compute amount of "leading white space" (indentation) for the first LOC in input-String
        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
        // 
        // Count the leading white-space in the first line of text - unless the first character
        // in the code was a '\n' (newline)

        if (! swNL)
        {
            // The array-index in array cArr of the last character in the first line of text.
            // If the input code-String is just a single line of code without any newline '\n'
            // characters, then this value is the length of the code-string.  Otherwise, this
            // value is assigned the cArr index/position of the first newline '\n' character.

            lastPos = (nlPosArr.length > 0) ? nlPosArr[0] : cArr.length;


            // The loop iterate until we reach the end of the first line of code/text, or
            // we reach a character that is not white-space.

            for (i=0; (i < lastPos) && Character.isWhitespace(cArr[i]); i++)

                if (cArr[i] == '\t')    throw new StringFormatException(STR_FORMAT_EX_MESSAGE);
                else                    wsCount++;

            // Amount of "Leading" white-space (indentation) for first LOC
            wsLenArr[0] = wsCount;

            // Was the first line only white-space?
            isOnlyWSArr[0] = (i == lastPos);

            // 'minIndent' was initialized to Integer.MAX_VALUE
            minIndent = wsCount;

            outArrPos++;
        }


        // ****************************************************************************************
        // Compute the amount of "leading white space" (indentation) for each LOC in input-String
        // ****************************************************************************************
        //
        // This loop will iterate each line of code inside the input source-code character-array
        // The length (number of characters) for the "leading white-space" (Which may also be called
        // indentation) for each LOC is stored in an integer-array called "wsLenArr"
        // When this loop encounters a line that is blank (contains only white-space), it is noted
        // in a boolean array "isOnlyWSArr"
        //
        // NOTE: The previous loop did the *EXACT SAME THING*, but ONLY for the first line of code
        //       in the input-string.  This is because the loop-control variables are slightly
        //       different for the first line of code.

        for (i=0; i < nlPosArr.length; i++)
        {
            wsCount = 0;
            lastPos = (i < (nlPosArr.length-1)) ? nlPosArr[i+1] : cArr.length;

            for (j = (nlPosArr[i]+1); (j < lastPos) && Character.isWhitespace(cArr[j]); j++)

                if (cArr[j] == '\t')    throw new StringFormatException(STR_FORMAT_EX_MESSAGE);
                else                    wsCount++;


            // Amount of "Leading" white-space (indentation) for current LOC
            wsLenArr[outArrPos] = wsCount;

            // Is the current LOC only white-space?
            isOnlyWSArr[outArrPos] = (j == lastPos);


            // Check if this line is the "reigning champion" of minimum of white-space indentation
            // Blank lines (lines with 'only white-space') cannot be factored into the
            // "minimum indentation" computation

            if (wsCount < minIndent) if (! isOnlyWSArr[outArrPos]) minIndent = wsCount;
            
            outArrPos++;
        }


        // ****************************************************************************************
        // Now we will shorten or extend the amount of indentation for the input code snippet.
        // ****************************************************************************************
        //
        // *** Keep Here for Reference ***
        // int[]        nlPosArr    // Location of each '\n' in the code
        // int[]        wsLenArr    // Length of "Leading White Space" for each line of code
        // boolean[]    isOnlyWSArr // TRUE / FALSE for "only white-space" lines of code

        int diff        = requestedIndent - minIndent;
        int delta       = 0;    // Intended to store the change in "Source Code as a String" LENGTH
                                // after performing the indentation changes

        int nextNLPos   = 0;    // Position of the next newline
        int srcPos      = 0;    // index/pointer to the input "Source Code as a String" char-array
        int destPos     = 0;    // index/pointer to (output) indentation-change output char-array
        int nlPosArrPos = 0;    // index/pointer to the "New Line Position Array"
        int otherArrPos = 0;    // index/pointer to the other 2 position arrays
                                // a.k.a:   "White-Space-Length Array" and the
                                //          "Is Only White-Space Array"

        if (diff == 0) return codeAsStr;

        for (i=0; i < wsLenArr.length; i++) if (! isOnlyWSArr[i]) delta += diff;

        char[]  outArr  = new char[cArr.length + delta];

        if (diff > 0)
            return Indent.run(codeAsStr, diff, true, true);

        else
        {
            // We are removing white-space, start at end, work backwards
            srcPos = cArr.length - 1;

            // The "output array", therefore, also starts at end of char-array
            destPos     = outArr.length - 1;

            // The "Where are the newlines array" index-pointer
            nlPosArrPos = nlPosArr.length - 1;

            otherArrPos = wsLenArr.length - 1;
                // The number of "lines of text" and "number of new-lines"
                // are *NOT NECESSARILY* identical.  The former might be
                // longer by *PRECISELY ONE* array-element.

            for (; otherArrPos >= 0; otherArrPos--)
            {
                // Check if the first character in the Source-Code String is a newline.
                nextNLPos = (nlPosArrPos >= 0) ? nlPosArr[nlPosArrPos--] : -1;


                // Lines of Source Code that are only white-space shall simply be copied to
                // the destination/output char-array.

                if (isOnlyWSArr[otherArrPos])
                    while (srcPos >= nextNLPos) outArr[destPos--] = cArr[srcPos--];

                else
                {
                    // Copy the line of source code
                    int numChars = srcPos - nextNLPos - wsLenArr[otherArrPos];

                    while (numChars-- > 0) outArr[destPos--] = cArr[srcPos--];


                    // Insert the exact amount of space-characters indentation
                    numChars = wsLenArr[otherArrPos] + diff;

                    while (numChars-- > 0) outArr[destPos--] = ' ';


                    // Skip over the original indentation (white-space) from the input line
                    // of source-code.

                    srcPos -= (wsLenArr[otherArrPos] + 1);

                    // Make sure to insert a new-line (since this character WASN'T copied)
                    if (destPos >= 0) outArr[destPos--] = '\n';
                }
            }
        }


        // ****************************************************************************************
        // Debug Println - This code isn't executed without the little debug-flag being set.
        // ****************************************************************************************

        // The returned code block is ready; convert to a String
        String ret = new String(outArr);

        // Do not delete.  Writing the debugging information takes a lot of thought.
        // If there is ever an error, this code is quite important.

        if (! DEBUGGING_INDENTATION) return ret;

        try
        {
            StringBuilder sb = new StringBuilder();

            sb.append(
                "swNL:\t\t\t\t"         + swNL              + '\n' +
                "ewNL:\t\t\t\t"         + ewNL              + '\n' +
                "numLines:\t\t\t"       + numLines          + '\n' +
                "minIndent:\t\t\t"      + minIndent         + '\n' +
                "requestedIndent:\t"    + requestedIndent   + '\n' +
                "diff:\t\t\t\t"         + diff              + '\n' +
                "delta:\t\t\t\t"        + delta             + '\n' +
                "nlPosArr:\n\t"
            );

            for (i=0; i < nlPosArr.length; i++) sb.append(nlPosArr[i] + ", ");

            sb.append("\nwsLenArr:\n\t");
            for (i=0; i < wsLenArr.length; i++) sb.append(wsLenArr[i] + ", ");

            sb.append("\nisOnlyWSArr:\n\t");
            for (i=0; i < isOnlyWSArr.length; i++) sb.append(isOnlyWSArr[i] + ", ");

            sb.append("\n");

            FileRW.writeFile(
                sb.toString() + "\n\n****************************************************\n\n" + 
                codeAsStr + "\n\n****************************************************\n\n" +
                ret,
                "TEMP/method" + StringParse.zeroPad10e2(++INDENTATION_COUNTER) + ".txt"
            );

            if (INDENTATION_COUNTER == 5) System.exit(0);

        }

        catch (Exception e)
        {
            e.printStackTrace();
            System.out.println(e + "\n\nFatal Error. Exiting.");
            System.exit(0);
        }

        return ret;
    }
}