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

import java.util.stream.IntStream;

class SetCodeIndentTabsPolicy
{
    // Used by 'setCodeIndent_WithTabsPolicyRelative'
    // This needs to be a long-array, because there might be lines with lots of initial indentation.

    private static final char[] SPACES = new char[200];

    static { java.util.Arrays.fill(SPACES, ' '); }


    static String run
        (String codeAsStr, int requestedIndent, int spacesPerTab)
    {
        char[]              code    = codeAsStr.toCharArray();
        IntStream.Builder   b       = IntStream.builder();


        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
        // First find all of the line-breaks / new-lines.
        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
        //
        // NOTE: If the first character is not a new-line, then the first line is presumed to begin
        //       at String-index '-1'
        //
        // Afterwards, convert the Stream to 'nlPos' array.  Build two other arrays

        if (code[0] != '\n') b.accept(-1);

        for (int i=0; i < code.length; i++) if (code[i] == '\n') b.accept(i);

        int[]   nlPos       = b.build().toArray();
        int[]   wsLen       = new int[nlPos.length];
        int[]   fcPos       = new int[nlPos.length];
        int     maxIndent   = 0;
        int     minIndent   = Integer.MAX_VALUE;


        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
        // Compute how much white-space is currently at the start of each line
        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
        //
        // NOTE: Since this method calculates what the user is looking at in his code-editor, the
        //       tabs-policy needs to be included in the calculation.
        //
        // Once the amount of white-space at the start of each line is computed, it will be easy
        // to shift the entire source-code left or right.  Note that in the JavaDoc Upgrader Tool,
        // this is always shifted until the **LEAST INDENTED** line is indented by 1...
        //
        // REMEMBER: Shifting everything left must be a shift of an **EQUAL NUMBER OF SPACES** for
        //           each line that is shifted.

        for (int i=0; i < wsLen.length; i++)
        {
            int END = (i == (nlPos.length - 1))
                ? code.length
                : nlPos[i+1];

            boolean hasCode = false;

            INNER:
            for (int j = (nlPos[i] + 1); j < END; j++)

                if (! Character.isWhitespace(code[j]))
                {
                    fcPos[i]    = j;
                    hasCode     = true;

                    break INNER;
                }


            if (! hasCode) fcPos[i] = wsLen[i] = -1;

            else
            {
                // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
                // wsLen[i] = computeEffectiveLeadingWhiteSpace(code, nlPos[i] + 1, spacesPerTab);
                // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
                //
                // NOTE: The contents of everything within this 'else' branch is nothing more than
                //       an inline block-copy of the method named in the comment above.  Hopefully
                //       inlining the method will speed this up a little bit.
                //
                // The values that would be passed before the method was inlined, here, are also
                // noted in the above comment.
                //
                // ALSO: The 'i' loop-variable was changed to a 'j' (to avoid conflicting with the
                //       outer-loop 'i').  The "ret" was changed to "whiteSpaceChars"

                int whiteSpaceChars = 0;
                int relativeCount   = 0;

                wsLen[i] = -1;

                EFFECTIVE_LEADING_WS:
                for (int j = (nlPos[i] + 1) /* lineFirstCharacterPos */; j < code.length; j++)

                    if (! Character.isWhitespace(code[j])) 
                    {
                        wsLen[j] = whiteSpaceChars; // return ret;
                        break EFFECTIVE_LEADING_WS;
                    }

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

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

                        case '\r' : 
                        case '\f' : break;
                        case '\n' : break EFFECTIVE_LEADING_WS; // return -1;

                        default: throw new UnreachableError();
                    }

                // return -1;  <== Not needed, the array-location is initialized to -1
            }

            if (wsLen[i] == -1)        continue;
            if (wsLen[i] > maxIndent)  maxIndent = wsLen[i];
            if (wsLen[i] < minIndent)  minIndent = wsLen[i];
        }

        // This is the amount of space to shift each line.
        int delta = requestedIndent - minIndent;


        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
        // NOW: Rebuild the source-code string, making sure to shift each line.
        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***

        StringBuilder sb = new StringBuilder();

        for (int i=0; i < wsLen.length; i++)


            // The array "White-Space-Length" will have a '-1' if the entire line is nothing but
            // white-space.  In such cases, simply append a '\n' - there is no reason to add extra
            // spaces.  The code hilited just ignores it.

            if (wsLen[i] == -1) sb.append('\n');

            // Otherwise append the leading white-space, and then the line-of-code.
            else
            {
                // First append the white-space at the beginning of the line.
                int numSpaces = wsLen[i] + delta;

                if (numSpaces > SPACES.length) throw new InternalError 
                    ("A Line of Code has more than 200 characters of leading white space");

                sb.append(SPACES, 0, numSpaces);


                // Now append the line of code.  Since there may be tabs after the first
                // non-white-space character, this is a little complicated...
                //
                // NOTE: This could be inlined, but this method just does too much...
                //
                // The char[]-Array 'code' has the code.  The text of the source-code begins at
                // array-index 'First-Character-Position' (fcPos).  This method needs the parameter
                // 'numSpaces' to make sure the tabs stay properly-relativised...

                sb.append(LOCAsStr.run(code, numSpaces, fcPos[i], spacesPerTab));
            }


        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
        // FINISHED: Return the Source-Code String
        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***

        return sb.toString();
    }    
}