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

class RemoveGeneric
{
    static String remove(String typeAsStr)
    {
        int leftPos = typeAsStr.indexOf('<');

        if (leftPos == -1)
        {
            int pos = typeAsStr.indexOf('>');

            if (pos == -1) return typeAsStr.trim();

            throw REM_GENERIC_ERROR_MSG(typeAsStr, pos);
        }

        char[]  cArr    = typeAsStr.toCharArray();
        int     count   = 1;            // The number of OPENING-CLOSING tags (same as Inclusive)
        int     END     = cArr.length;  // This is the location JUST-AFTER the last USEABLE-char
        int     delta   = 0;            // How many characters have been deleted already.
                                        // NOTE: This is zero, because the loop hasn't started.
                                        //       If there is a "Shift" this will be PRECISELY-EQUAL
                                        //       to the size of the last generic parameter-expression.
                                        // ALSO: The only purpose of this is for error-reporting.

        // check for a closing '>' before the first opening '<'
        for (int j=0; j < leftPos; j++)
            if (cArr[j] == '>') throw REM_GENERIC_ERROR_MSG(typeAsStr, j);


        // Check for in-valid characters
        // 
        // This is a lot of lines of code, but these methods are extremely short, and the input
        // string (for all VALID) input will be very short.  This is peace of mind.  It checks...

        for (int pos=0; pos < cArr.length; pos++)
        {
            char c = cArr[pos];

            if (! Character.isJavaIdentifierPart(c))
                if (! Character.isIdentifierIgnorable(c))
                    if (! Character.isWhitespace(c))
                        if (
                                (c != '[') && (c != ']') && (c != '?') && (c != '<') &&
                                (c != '>') && (c != ',') && (c != '.')
                        )
                            throw REM_GENERIC_ERROR_MSG(typeAsStr, pos);
        }

        do
        {
            // Keeps a count on the number of "Opening Braces" and "Closing Braces" 
            // This is the same thing as the whole "Inclusive" deal, but with braces instead.
            //
            // count: At loop start, count is '1'  If it ever reaches 0, the loop exits.
            // leftPos: The location of the '<' that has been found.

            int i = leftPos + 1;
    
            while ((count > 0) && (i < END))
            {
                if      (cArr[i] == '<')    count++;
                else if (cArr[i] == '>')    count--;

                if (count > 0) i++;
            }

            // The '<' and the '>' didn't match up.  Better to throw exception, than ignore it.
            if ((count != 0) && (i == END))
                throw REM_GENERIC_ERROR_MSG(typeAsStr, leftPos);

            int rightPos = i; // 'i' is currently pointing to the '>'

            // Erase the most recently found <...> expression
            int     sourcePos       = rightPos + 1; // Pointing at first VALID / NEED-TO-COPY char
            int     destPos         = leftPos;      // Pointing at '<'
            boolean possiblyAnother = false;

            while (sourcePos < END)
            {
                // The next character to copy... check it first to see if it is valid!
                char c = cArr[sourcePos]; 

                // continue to shift all the characters left to erase the expression.
                cArr[destPos] = c;

                if (! possiblyAnother) // Haven't found an opening '<'
                {
                    // If there is a '>' - ***AND NO '<' HAS BEEN FOUND***, this is an error.    
                    if (c == '>')
                        throw REM_GENERIC_ERROR_MSG(typeAsStr, delta + sourcePos);

                    // If there is another '<', then it is possible another expression awaits us
                    if (c == '<')
                    {
                        // Reset the outer-loop variables for the next iteration.  There is going
                        // to be another iteration - guaranteed.
                        //
                        // NOTE: Delta is supposed to hold how many characters are being deleted.
                        //       This is used for proper error-reporting (only)
                        // 
                        // This is how many chars are in the current <...> expression

                        delta   = rightPos - leftPos + 1;

                        leftPos = destPos;  // Now pointing at the next open '<' char (just found!)
                        count   = 1;        // There was a new-unclosed '>', prepares for next loop

                        // You know it
                        possiblyAnother = true;
                    }
                }

                sourcePos++; destPos++;
            }


            // Completed without errors, and without another expression being found.
            // NOTE: This used to be a one-line return call.
            // ADDED: This now does a String.trim().   These little loops skip leading and 
            //        trailing white-space BEFORE returning the String
            //
            // WORKS-NO-TRIM: return new String(cArr, 0, destPos);
            //                replace loop-body with the above line to get rid of trim()

            if (! possiblyAnother)
            {
                int sPos    = 0;
                int len     = destPos;  // REMEMBER:    new String(char[], int OFFSET, int COUNT)
                                        // NOT:         new String(char[], int SPOS, int EPOS)

                // Skip LEADING-WHITESPACE
                while ((sPos < cArr.length) && (destPos > 0) && Character.isWhitespace(cArr[sPos]))
                { sPos++; destPos--; } // Advance start, *AND* shorten "count"

                // Skip TRAILING WHITE-SPACE
                while ((destPos > 1) && Character.isWhitespace(cArr[sPos + destPos-1]))
                    destPos--; // Shorten length *ONLY*

                return new String(cArr, sPos, destPos);
            }
            
            END = destPos;  // Pointing at the first invalid / unused / ALREADY-MOVED char
        }
        while (true);
    }


    private static StringFormatException REM_GENERIC_ERROR_MSG(String s, int charPos)
    { 
        return new StringFormatException(
            /*
            "The opening '<' and closing '>' symbols in the type-string have not been " +
            "properly placed.\n" +
            */
            "Generic Type-String Error, Beginning at Noted Location:\n" +
            StrSource.caretBeneath(s, charPos)
        );
    }

}