001package Torello.Java; 002 003import java.util.regex.*; 004 005import java.util.stream.IntStream; 006import Torello.Java.Function.IntCharFunction; 007 008import Torello.JavaDoc.StaticFunctional; 009import Torello.JavaDoc.Excuse; 010 011/** 012 * A class for indenting, unindenting and trimming textual-strings. 013 * 014 * <BR /><BR /><EMBED CLASS='external-html' DATA-FILE-ID=STRINDENT> 015 */ 016@StaticFunctional(Excused="INDENTATION_COUNTER", Excuses=Excuse.DEBUGGING) 017public class StrIndent 018{ 019 private StrIndent() { } 020 021 // the indent(String, n, tabOrSpace) method uses this array as a parameter to StrReplace 022 private static final char[] cArrIndent = { '\n' }; 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 // The 'setCodeIndent' uses all of these (but *ONLY* if "DEBUGGING_INDENTATION" is set to TRUE) 028 private static int INDENTATION_COUNTER = 0; 029 private static final boolean DEBUGGING_INDENTATION = false; 030 private static final String STR_FORMAT_EX_MESSAGE = 031 "One of the lines of code for the method-body as-a-string that was passed contained " + 032 "a tab '\\t' character. Source-Code String's that are passed to this method must have" + 033 "been de-tabified."; 034 035 // Used by 'setCodeIndent_WithTabsPolicyRelative' 036 // This needs to be a long-array, because there might be lines with lots of initial indentation. 037 038 private static final char[] SPACES = new char[200]; 039 040 static { java.util.Arrays.fill(SPACES, ' '); } 041 042 /** 043 * Replaces sub-strings that contain a newline character followed by only white-space with 044 * only the new-line character itself. 045 * 046 * @param s Any Java {@code String}, but preferrably one which contains newline characters 047 * ({@code '\n'}), and white-space characters immediately followng the newline, but not other 048 * ASCII nor UNICODE. 049 * 050 * @return The same String with each instance of {@code ^[ \t]+\n} replaced by {@code '\n'}. 051 */ 052 public static String trimWhiteSpaceOnlyLines(String s) 053 { 054 Matcher m = EMPTY_LINE.matcher(s); 055 return m.replaceAll("\n"); 056 } 057 058 /** 059 * Will iterate through <I>each line of text</I> within the input {@code String}-parameter 060 * {@code 's'}, and right-trim the lines. "Right Trim" means to remove all white-space 061 * characters that occur <I><B>after</I></B> the last non-white-space character on the line. 062 * (Does not remove the new-line character ({@code '\n'}) itself). 063 * 064 * <BR /><BR /><B>NOTE:</B> Any line of text which contains only white-space characters is 065 * reduced to a single new-line character. 066 * 067 * @param s Any Java {@code String}, preferrably one with several new-line characters. 068 * 069 * @return The same text, but only after having any 'trailing white-space' characters removed 070 * from each line of text. 071 */ 072 public static String rightTrimAll(String s) 073 { 074 // SPECIAL-CASE: There is 075 if (s.length() == 0) return s; 076 077 char[] cArr = s.toCharArray(); 078 int targetPos = 0; 079 int nlPos = 0; 080 081 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 082 // Set up the Loop Variables 'nlPos' and 'targetPos' 083 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 084 085 // SKIP: Skip past all leading new-lines. They don't need to be moved at all in the loop! 086 for (nlPos=0; (nlPos < cArr.length) && (cArr[nlPos] == '\n'); nlPos++); 087 088 // SPECIAL-CASE: The String had **ONLY** new-lines in it... 089 if (nlPos == cArr.length) return s; 090 091 // IF THERE WERE LEADING NEWLINES: 092 // AFTER-LOOP: 'nlPos' will be pointing at the character IMMEDIATELY-AFTER the last 093 // leading newline. 094 // NOTE: IF there WERE NOT leading newlines, 'nlPos' is ZERO, 095 // which is just fine for assigning to 'targetPos' anyway! 096 targetPos = nlPos; 097 098 // Continue to initialize 'nlPos' and 'targetPos' 099 // PART ONE: Find the FIRST new-line AFTER the first CONTENT-CONTAINING line. 100 // PART TWO: Set 'targetPos' to the last character that is non-white-space. 'targetPos' 101 // will be incremented-by-one later to point to white-space. 102 while ((++nlPos < cArr.length) && (cArr[nlPos] != '\n')) 103 if (! Character.isWhitespace(cArr[nlPos])) 104 targetPos = nlPos; 105 106 // Now increment 'targetPos' because in MOST-CASES it will be pointing to the last 107 // non-white-space character in the first line. (and we *CANNOT* clobber that character!) 108 // HOWEVER: if 'targetPos' is still pointing at White-Space (after the previous loop), 109 // then it must be that the ENTIRE-FIRST-LINE was BLANK! 110 if (! Character.isWhitespace(cArr[targetPos])) targetPos++; 111 112 // SPECIAL-CASE: The first CONTENT-FUL LINE is the ONLY-LINE in the text. 113 // ===> Return the input-String. 114 // **BUT** make sure to right-trim that CONTENT-FUL line. 115 if (nlPos == cArr.length) return s.substring(0, targetPos); 116 117 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 118 // Now do the 'Shifting Loop' 119 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 120 121 while (nlPos < cArr.length) 122 { 123 int nextNLPos = nlPos + 1; 124 int lastNonWhiteSpacePos = nlPos; 125 int sourcePos = nlPos; 126 127 // Compute 'nextNLPos' and 'lastNonWhiteSpacePos': In the Example *SUBSTRING* Below: 128 // "...\nHello, How are you? \n..." 129 // 130 // NEW-LINE-POS was set to the first '\n' you see in the above-String 131 // The '?' is the LAST-NON-WHITE-SPACE 132 // The '\n' after that is the NEXT-NEWLINE-POS 133 while ((nextNLPos < cArr.length) && (cArr[nextNLPos] != '\n')) 134 { 135 if (! Character.isWhitespace(cArr[nextNLPos])) 136 lastNonWhiteSpacePos = nextNLPos; 137 nextNLPos++; 138 } 139 140 // Shift all characters BEGINNING with the OLD new-line position (since 'sourcePos') 141 // is initiliazed with 'nlPos') ... 142 // 143 // AGAIN: Shift all characters beginning with 'nlPos' UP-TO-AND-INCLUDING 144 // 'lastNonWhiteSpacePos' for the NEXT line of text to the appropriate position. 145 while (sourcePos <= lastNonWhiteSpacePos) cArr[targetPos++] = cArr[sourcePos++]; 146 147 // The next loop-iteration will start with the next line in the text. 148 nlPos = nextNLPos; 149 } 150 151 return new String(cArr, 0, targetPos); 152 } 153 154 /** 155 * Will iterate through <I>each line of text</I> within the input {@code String}-parameter 156 * {@code 's'}, and left-trim the lines. "Left Trim" means to remove all white-space 157 * characters that occur <I><B>before</I></B> the last non-white-space character on the line. 158 * (Does not remove the new-line character ({@code '\n'}) itself). 159 * 160 * <BR /><BR /><B>NOTE:</B> Any line of text which contains only white-space characters is 161 * reduced to a single new-line character. 162 * 163 * @param s Any Java {@code String}, preferrably one with several new-line characters. 164 * 165 * @return The same text, but only after having any 'leading white-space' characters removed 166 * from each line of text. 167 */ 168 public static String leftTrimAll(String s) 169 { 170 char[] cArr = s.toCharArray(); 171 172 // LEFT TRIM is easier on the mind. There are only two variables needed for this one. 173 int targetPos = 0; 174 int sourcePos = 0; 175 176 // Make sure to skip completely any and all leading new-line characters. 177 while ((targetPos < cArr.length) && (cArr[targetPos] == '\n')) targetPos++; 178 179 // If there were **ONLY** leading new-line characters, return the original string 180 if (targetPos == cArr.length) return s; 181 182 // Re-initialize 'sourcePos' 183 sourcePos = targetPos; 184 185 while (sourcePos < cArr.length) 186 { 187 // When this loop begins, sourcePos is pointing at the first character of text 188 // in the very-next line-of-text to process. 189 // 190 // NORMAL EXECUTION: This loop advances 'sourcePos' to the first non-white-space 191 // character in the line. 192 // WS-ONLY LINES CASE: This loop advances 'sourcePos' to the next line ('\n') 193 // LAST-LINE CASE: Advances 'sourcePos' to cArr.length 194 while ( (sourcePos < cArr.length) 195 && Character.isWhitespace(cArr[sourcePos]) 196 && (cArr[sourcePos] != '\n') 197 ) 198 sourcePos++; 199 200 // Left Shift the String to 'erase' all leading white-space characters in the 201 // current line of text. 202 while ((sourcePos < cArr.length) && (cArr[sourcePos] != '\n')) 203 cArr[targetPos++] = cArr[sourcePos++]; 204 205 // The loop that is directly above this statement BREAKS when '\n' is reached, 206 // so unless the end of the String has been reached, shift one more of the characters 207 // NOTE: If a character is shifted, below, it will always be the '\n' character 208 if (sourcePos < cArr.length) cArr[targetPos++] = cArr[sourcePos++]; 209 } 210 211 return new String(cArr, 0, targetPos); 212 } 213 214 /** 215 * This method expects to receive a method body, constructor body, or other callable body as a 216 * {@code String}; it will remove the beginning and ending braces <CODE>('{'</CODE> & 217 * <CODE>'}')</CODE>, and beginning & ending empty lines. This is to prepare the 218 * method for code hiliting, used internally by the package {@code Torello.JavaDoc}. 219 * 220 * @param callableAsStr This should be a method body. Make sure <B>**NOT TO INCLUDE**</B> the 221 * method signature at the beginning of the method. The first non-white-space character should 222 * be the open braces character <CODE>('{')</CODE>, and the last non-white-space should be 223 * the closing braces character <CODE>('&#125')</CODE>. 224 * 225 * @return A method body {@code String} that can be hilited using the code-hiliting mechanism 226 * of the "JavaDoc Package." 227 * 228 * @throws CallableBodyException If the input parameter {@code String} does not begin and end 229 * with the curly-braces. 230 */ 231 public static String chompCallableBraces(String callableAsStr) 232 { 233 callableAsStr = callableAsStr.trim(); 234 235 if (callableAsStr.charAt(0) != '{') throw new CallableBodyException( 236 "Passed callable does not begin with a squiggly-brace '{', but rather a: '" + 237 callableAsStr.charAt(0) + "'\n" + callableAsStr 238 ); 239 240 if (callableAsStr.charAt(callableAsStr.length() - 1) != '}') throw new CallableBodyException( 241 "Passed callable does not end with a squiggly-brace '}', but rather a: '" + 242 callableAsStr.charAt(0) + "'\n"+ callableAsStr 243 ); 244 245 // This Version does a single "String.substring(...)", meaning it is more efficient 246 // because it is not doing any extra String copies at all. 247 // 248 // NOTE: There is an auto-pre-increment (and pre-decrement), in both of the while loops. 249 // Therefore, sPos starts at 'zero' - even though the open-curly-brace is the 250 // character at position 0, and the closed-curly-brace is at position length-1. 251 252 int sPos = 0; 253 int ePos = callableAsStr.length() - 1; 254 int first = 1; // The character after the '{' (open-curly-brace) 255 char tempCh = 0; // temp-var 256 257 // If the callable-braces are on their own line, skip that first line. 258 // If they are **NOTE** on their own first line, the first character returned will be 259 // the first character of source-code. 260 261 while ((sPos < ePos) && Character.isWhitespace(tempCh = callableAsStr.charAt(++sPos))) 262 263 if (tempCh == '\n') 264 { 265 first = sPos + 1; 266 break; 267 } 268 269 // Check for the "bizarre case" that the method body just doesn't contain any code. 270 // This means that *EVERY CHARACTER* that was checked was white-space. Return a single 271 // space character, and be done with it. 272 273 if (sPos == ePos) return " "; 274 275 // When this loop terminates, 'ePos' will be pointing to the first non-white-space 276 // character at the tail end of the source-code / String (callableAsStr is a String of 277 // source-code used in the JavaDoc package) 278 279 while ((ePos > sPos) && Character.isWhitespace(callableAsStr.charAt(--ePos))); 280 281 // Starts at the 'first-white-space' character in the first line of code, and ends at the 282 // last non-white-space character in source-code String 'callableAsStr' 283 284 return callableAsStr.substring(first, ePos + 1); 285 } 286 287 /** 288 * Accepts a method body as a {@code String} and left-shifts or right-shifts each line 289 * of text (each {@code 'Line of Code' - LOC}) so that the indentation is consistent with 290 * the requested-indentation input-parameter. 291 * 292 * <BR /><BR /><B>NOTE:</B> The lines of code contained by input-parameter {@code 'codeAsStr'} 293 * must contain leading white-space <I><B STYLE='color: red;'>without any tab ({@code '\t'}) 294 * characters.</B></I> The reasoning here is that tabs can be interpreted in many different 295 * ways, and therefore it is required to replace them before invoking this method. This method 296 * merely adds or removes leading {@code ASCII 0x20} (space-bar characters) from the beginning 297 * of each line of text. A leading tab-character {@code ASCII 0x09} will generate an exception 298 * throw. 299 * 300 * @param codeAsStr A method body. It is expected to be the internal part of a chunk of 301 * source code. 302 * 303 * @param requestedIndent The requested amount of indentation for the method-body. The 304 * line of code that contains the shortest amount of indentation (white-space) will be 305 * calculated, and then all LOC's shall be left-shifted (or right-shifted) according to that 306 * LOC which contained the least amount of leading white-space. 307 * 308 * @return An updated method-body as a {@code String}. 309 * 310 * @throws StringFormatException This exception shall throw whenever a line of text contained 311 * by the input {@code String} has a {@code '\t'} (tab-character) before the first 312 * non-white-space character on that line of text. Code that contains tab-characters is 313 * invariably "auto-indented" by the Code-Editor when loaded into the GUI, and the amount of 314 * indentation applied for each tab-character is usually configurable. Because there are many 315 * variants of how tab's {@code '\t'} gets interpreted by the editor, it is required to replace 316 * these characters first before invoking this method. 317 */ 318 public static String setCodeIndent(String codeAsStr, int requestedIndent) 319 { 320 if (requestedIndent < 0) throw new IllegalArgumentException 321 ("The requested indent passed to this method was a negative value: " + requestedIndent); 322 323 else if (requestedIndent > 80) throw new IllegalArgumentException 324 ("The requested indent passed to this method was greater than 80: " + requestedIndent); 325 326 // Source-Code-String to char-array 327 char[] cArr = codeAsStr.toCharArray(); 328 329 // Code "Starts With New Line '\n'" 330 boolean swNL = cArr[0] == '\n'; 331 332 // Code "Ends With New Line" 333 boolean ewNL = cArr[cArr.length - 1] == '\n'; 334 335 // Location of each '\n' in the code 336 int[] nlPosArr = StrIndexOf.all(codeAsStr, '\n'); 337 338 // Unless the first character in the code is '\n', numLines - num '\n' + 1 339 int numLines = nlPosArr.length + (swNL ? 0 : 1); 340 341 // Length of "Leading White Space" for each line of code 342 int[] wsLenArr = new int[numLines]; 343 344 // TRUE / FALSE for "only white-space" lines of code 345 boolean[] isOnlyWSArr = new boolean[numLines]; 346 347 // Amount of White-Space for the LEAST-INDENTED Line of Code 348 int minIndent = Integer.MAX_VALUE; 349 350 // These three variables are loop-control and temporary variables. 351 int wsCount = 0; // "White Space Count" - amount of WS on each line 352 int outArrPos = 0; // There are two parallel "Output Arrays", this the index 353 int lastPos = 0; // Temp Var for the last-index in a line of code 354 355 int i; // Simple Loop Control Variable 356 int j; // Simple Loop Control Variable 357 358 359 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 360 // Compute amount of "leading white space" (indentation) for the first LOC in input-String 361 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 362 363 // Count the leading white-space in the first line of text - unless the first character 364 // in the code was a '\n' (newline) 365 366 if (! swNL) 367 { 368 // The array-index in array cArr of the last character in the first line of text. 369 // If the input code-String is just a single line of code without any newline '\n' 370 // characters, then this value is the length of the code-string. Otherwise, this 371 // value is assigned the cArr index/position of the first newline '\n' character. 372 373 lastPos = (nlPosArr.length > 0) ? nlPosArr[0] : cArr.length; 374 375 // The loop iterate until we reach the end of the first line of code/text, or 376 // we reach a character that is not white-space. 377 378 for (i=0; (i < lastPos) && Character.isWhitespace(cArr[i]); i++) 379 if (cArr[i] == '\t') throw new StringFormatException(STR_FORMAT_EX_MESSAGE); 380 else wsCount++; 381 382 // Amount of "Leading" white-space (indentation) for first LOC 383 wsLenArr[0] = wsCount; 384 385 // Was the first line only white-space? 386 isOnlyWSArr[0] = (i == lastPos); 387 388 // 'minIndent' was initialized to Integer.MAX_VALUE 389 minIndent = wsCount; 390 391 outArrPos++; 392 } 393 394 395 // **************************************************************************************** 396 // Compute the amount of "leading white space" (indentation) for each LOC in input-String 397 // **************************************************************************************** 398 // 399 // This loop will iterate each line of code inside the input source-code character-array 400 // The length (number of characters) for the "leading white-space" (Which may also be called 401 // indentation) for each LOC is stored in an integer-array called "wsLenArr" 402 // When this loop encounters a line that is blank (contains only white-space), it is noted 403 // in a boolean array "isOnlyWSArr" 404 // 405 // NOTE: The previous loop did the *EXACT SAME THING*, but ONLY for the first line of code 406 // in the input-string. This is because the loop-control variables are slightly 407 // different for the first line of code. 408 409 for (i=0; i < nlPosArr.length; i++) 410 { 411 wsCount = 0; 412 lastPos = (i < (nlPosArr.length-1)) ? nlPosArr[i+1] : cArr.length; 413 414 for (j = (nlPosArr[i]+1); (j < lastPos) && Character.isWhitespace(cArr[j]); j++) 415 if (cArr[j] == '\t') throw new StringFormatException(STR_FORMAT_EX_MESSAGE); 416 else wsCount++; 417 418 // Amount of "Leading" white-space (indentation) for current LOC 419 wsLenArr[outArrPos] = wsCount; 420 421 // Is the current LOC only white-space? 422 isOnlyWSArr[outArrPos] = (j == lastPos); 423 424 // Check if this line is the "reigning champion" of minimum of white-space indentation 425 // Blank lines (lines with 'only white-space') cannot be factored into the 426 // "minimum indentation" computation 427 428 if (wsCount < minIndent) if (! isOnlyWSArr[outArrPos]) minIndent = wsCount; 429 430 outArrPos++; 431 } 432 433 434 // **************************************************************************************** 435 // Now we will shorten or extend the amount of indentation for the input code snippet. 436 // **************************************************************************************** 437 // 438 // *** Keep Here for Reference *** 439 // int[] nlPosArr // Location of each '\n' in the code 440 // int[] wsLenArr // Length of "Leading White Space" for each line of code 441 // boolean[] isOnlyWSArr // TRUE / FALSE for "only white-space" lines of code 442 443 int diff = requestedIndent - minIndent; 444 int delta = 0; // Intended to store the change in "Source Code as a String" LENGTH 445 // after performing the indentation changes 446 int nextNLPos = 0; // Position of the next newline 447 int srcPos = 0; // index/pointer to the input "Source Code as a String" char-array 448 int destPos = 0; // index/pointer to (output) indentation-change output char-array 449 int nlPosArrPos = 0; // index/pointer to the "New Line Position Array" 450 int otherArrPos = 0; // index/pointer to the other 2 position arrays 451 // a.k.a: "White-Space-Length Array" and the 452 // "Is Only White-Space Array" 453 454 if (diff == 0) return codeAsStr; 455 456 for (i=0; i < wsLenArr.length; i++) if (! isOnlyWSArr[i]) delta += diff; 457 458 char[] outArr = new char[cArr.length + delta]; 459 460 if (diff > 0) 461 return indent(codeAsStr, diff, true, true); 462 else 463 { 464 // We are removing white-space, start at end, work backwards 465 srcPos = cArr.length - 1; 466 467 // The "output array", therefore, also starts at end of char-array 468 destPos = outArr.length - 1; 469 470 // The "Where are the newlines array" index-pointer 471 nlPosArrPos = nlPosArr.length - 1; 472 473 otherArrPos = wsLenArr.length - 1; 474 // The number of "lines of text" and "number of new-lines" 475 // are *NOT NECESSARILY* identical. The former might be 476 // longer by *PRECISELY ONE* array-element. 477 478 for (; otherArrPos >= 0; otherArrPos--) 479 { 480 // Check if the first character in the Source-Code String is a newline. 481 nextNLPos = (nlPosArrPos >= 0) ? nlPosArr[nlPosArrPos--] : -1; 482 483 // Lines of Source Code that are only white-space shall simply be copied to 484 // the destination/output char-array. 485 if (isOnlyWSArr[otherArrPos]) 486 while (srcPos >= nextNLPos) outArr[destPos--] = cArr[srcPos--]; 487 488 else 489 { 490 // Copy the line of source code 491 int numChars = srcPos - nextNLPos - wsLenArr[otherArrPos]; 492 while (numChars-- > 0) outArr[destPos--] = cArr[srcPos--]; 493 494 // Insert the exact amount of space-characters indentation 495 numChars = wsLenArr[otherArrPos] + diff; 496 while (numChars-- > 0) outArr[destPos--] = ' '; 497 498 // Skip over the original indentation (white-space) from the input line 499 // of source-code. 500 srcPos -= (wsLenArr[otherArrPos] + 1); 501 502 // Make sure to insert a new-line (since this character WASN'T copied) 503 if (destPos >= 0) outArr[destPos--] = '\n'; 504 } 505 } 506 } 507 508 509 // **************************************************************************************** 510 // Debug Println - This code isn't executed without the little debug-flag being set. 511 // **************************************************************************************** 512 513 // The returned code block is ready; convert to a String 514 String ret = new String(outArr); 515 516 // Do not delete. Writing the debugging information takes a lot of thought. 517 // If there is ever an error, this code is quite important. 518 519 if (! DEBUGGING_INDENTATION) return ret; 520 521 try 522 { 523 StringBuilder sb = new StringBuilder(); 524 525 sb.append( 526 "swNL:\t\t\t\t" + swNL + '\n' + 527 "ewNL:\t\t\t\t" + ewNL + '\n' + 528 "numLines:\t\t\t" + numLines + '\n' + 529 "minIndent:\t\t\t" + minIndent + '\n' + 530 "requestedIndent:\t" + requestedIndent + '\n' + 531 "diff:\t\t\t\t" + diff + '\n' + 532 "delta:\t\t\t\t" + delta + '\n' + 533 "nlPosArr:\n\t" 534 ); 535 536 for (i=0; i < nlPosArr.length; i++) sb.append(nlPosArr[i] + ", "); 537 sb.append("\nwsLenArr:\n\t"); 538 for (i=0; i < wsLenArr.length; i++) sb.append(wsLenArr[i] + ", "); 539 sb.append("\nisOnlyWSArr:\n\t"); 540 for (i=0; i < isOnlyWSArr.length; i++) sb.append(isOnlyWSArr[i] + ", "); 541 sb.append("\n"); 542 543 FileRW.writeFile( 544 sb.toString() + "\n\n****************************************************\n\n" + 545 codeAsStr + "\n\n****************************************************\n\n" + 546 ret, 547 "TEMP/method" + StringParse.zeroPad10e2(++INDENTATION_COUNTER) + ".txt" 548 ); 549 550 if (INDENTATION_COUNTER == 5) System.exit(0); 551 552 } catch (Exception e) 553 { 554 e.printStackTrace(); 555 System.out.println(e + "\n\nFatal Error. Exiting."); 556 System.exit(0); 557 } 558 559 return ret; 560 } 561 562 /** 563 * Convenience Method. 564 * <BR />Invokes: {@link #setCodeIndent(String, int)} 565 * <BR />Converts: All {@code '\t'} to the specified number of spaces in parameter 566 * {@code SPACES}. 567 * <BR /><B STYLE='color: red'>NOTE:</B> Exception-Checking is <B STYLE='color: red'>NOT</B> 568 * done on input 569 */ 570 public static String setCodeIndent_WithTabsPolicyAbsolute 571 (String codeAsStr, int requestedIndent, String SPACES) 572 { return setCodeIndent(codeAsStr.replace("\t", SPACES), requestedIndent); } 573 574 /** 575 * Adjusts code-indentation using a relative-sized tab-policy. This method performs the 576 * equivalent of shifting the entire text-block, proportionately, to the left or right. 577 * 578 * <BR /><BR />To do this, first, the number of spaces that preceed the 579 * <B STYLE='color: red;'>least-indented</B> line is computed, and afterwards, every line in 580 * the text is shifted by an identical number of space-characters. The number of spaces that 581 * are either added or removed from each line is dependent on whether the requested 582 * indentation (parameter {@code 'requestedIndent'}) is greater-than or less-than the computed 583 * least-indented line. 584 * 585 * <EMBED CLASS='external-html' DATA-FILE-ID=SI_REL_TABS> 586 * 587 * @param codeAsStr Any Java source-code block, as a {@code java.lang.String} 588 * 589 * @param requestedIndent The number of spaces that the code should have as indentation. 590 * Note, that in the JavaDoc Upgrader code, this number is always {@code '1'}. 591 * 592 * @param spacesPerTab If tabs are found inside this {@code String}, then they are replaced 593 * with an appropriate number of space characters, according to a relative tab-policy, as 594 * described above. 595 * 596 * @return A properly shifted-indented Java source-code block. 597 */ 598 public static String setCodeIndent_WithTabsPolicyRelative 599 (String codeAsStr, int requestedIndent, int spacesPerTab) 600 { 601 char[] code = codeAsStr.toCharArray(); 602 IntStream.Builder b = IntStream.builder(); 603 604 605 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 606 // First find all of the line-breaks / new-lines. 607 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 608 // 609 // NOTE: If the first character is not a new-line, then the first line is presumed to begin 610 // at String-index '-1' 611 // 612 // Afterwards, convert the Stream to 'nlPos' array. Build two other arrays 613 614 if (code[0] != '\n') b.accept(-1); 615 616 for (int i=0; i < code.length; i++) if (code[i] == '\n') b.accept(i); 617 618 int[] nlPos = b.build().toArray(); 619 int[] wsLen = new int[nlPos.length]; 620 int[] fcPos = new int[nlPos.length]; 621 int maxIndent = 0; 622 int minIndent = Integer.MAX_VALUE; 623 624 625 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 626 // Compute how much white-space is currently at the start of each line 627 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 628 // 629 // NOTE: Since this method calculates what the user is looking at in his code-editor, the 630 // tabs-policy needs to be included in the calculation. 631 // 632 // Once the amount of white-space at the start of each line is computed, it will be easy 633 // to shift the entire source-code left or right. Note that in the JavaDoc Upgrader Tool, 634 // this is always shifted until the **LEAST INDENTED** line is indented by 1... 635 // 636 // REMEMBER: Shifting everything left must be a shift of an **EQUAL NUMBER OF SPACES** for 637 // each line that is shifted. 638 639 for (int i=0; i < wsLen.length; i++) 640 { 641 int END = (i == (nlPos.length - 1)) ? code.length : nlPos[i+1]; 642 boolean hasCode = false; 643 644 INNER: 645 for (int j = (nlPos[i] + 1); j < END; j++) 646 647 if (! Character.isWhitespace(code[j])) 648 { 649 fcPos[i] = j; 650 hasCode = true; 651 652 break INNER; 653 } 654 655 if (! hasCode) fcPos[i] = wsLen[i] = -1; 656 657 else 658 { 659 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 660 // wsLen[i] = computeEffectiveLeadingWhiteSpace(code, nlPos[i] + 1, spacesPerTab); 661 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 662 // 663 // NOTE: The contents of everything within this 'else' branch is nothing more than 664 // an inline block-copy of the method named in the comment above. Hopefully 665 // inlining the method will speed this up a little bit. 666 // 667 // The values that would be passed before the method was inlined, here, are also 668 // noted in the above comment. 669 // 670 // ALSO: The 'i' loop-variable was changed to a 'j' (to avoid conflicting with the 671 // outer-loop 'i'). The "ret" was changed to "whiteSpaceChars" 672 673 int whiteSpaceChars = 0; 674 int relativeCount = 0; 675 676 wsLen[i] = -1; 677 678 EFFECTIVE_LEADING_WS: 679 for (int j = (nlPos[i] + 1) /* lineFirstCharacterPos */; j < code.length; j++) 680 681 if (! Character.isWhitespace(code[j])) 682 { 683 wsLen[j] = whiteSpaceChars; // return ret; 684 break EFFECTIVE_LEADING_WS; 685 } 686 687 else switch (code[j]) 688 { 689 case ' ' : 690 whiteSpaceChars++; 691 relativeCount = (relativeCount + 1) % spacesPerTab; 692 break; 693 694 case '\t' : 695 whiteSpaceChars += (spacesPerTab - relativeCount); 696 relativeCount = 0; 697 break; 698 699 case '\r' : 700 case '\f' : break; 701 case '\n' : break EFFECTIVE_LEADING_WS; // return -1; 702 default: throw new UnreachableError(); 703 } 704 705 // return -1; <== Not needed, the array-location is initialized to -1 706 } 707 708 if (wsLen[i] == -1) continue; 709 if (wsLen[i] > maxIndent) maxIndent = wsLen[i]; 710 if (wsLen[i] < minIndent) minIndent = wsLen[i]; 711 } 712 713 // This is the amount of space to shift each line. 714 int delta = requestedIndent - minIndent; 715 716 717 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 718 // NOW: Rebuild the source-code string, making sure to shift each line. 719 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 720 721 StringBuilder sb = new StringBuilder(); 722 723 for (int i=0; i < wsLen.length; i++) 724 725 // The array "White-Space-Length" will have a '-1' if the entire line is nothing but 726 // white-space. In such cases, simply append a '\n' - there is no reason to add extra 727 // spaces. The code hilited just ignores it. 728 729 if (wsLen[i] == -1) sb.append('\n'); 730 731 // Otherwise append the leading white-space, and then the line-of-code. 732 else 733 { 734 // First append the white-space at the beginning of the line. 735 int numSpaces = wsLen[i] + delta; 736 737 sb.append(SPACES, 0, numSpaces); 738 739 // Now append the line of code. Since there may be tabs after the first 740 // non-white-space character, this is a little complicated... 741 // 742 // NOTE: This could be inlined, but this method just does too much... 743 // 744 // The char[]-Array 'code' has the code. The text of the source-code begins at 745 // array-index 'First-Character-Position' (fcPos). This method needs the parameter 746 // 'numSpaces' to make sure the tabs stay properly-relativised... 747 748 sb.append(lineOfCodeAsStr(code, numSpaces, fcPos[i], spacesPerTab)); 749 } 750 751 752 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 753 // FINISHED: Return the Source-Code String 754 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 755 756 return sb.toString(); 757 } 758 759 /** 760 * Helper Method for calculating the number of space characters to be used at the beginning 761 * of a line of code, all the while obeying a particular tabs-policy. 762 * 763 * <BR /><BR /><B STYLE='color: red;'>IMPORTANT:</B> None of the parameters to this method 764 * will be checked for errors. This method is often used inside of a loop, and improper 765 * input should be presumed to cause indeterminate results. 766 * 767 * @param code A Java source-code {@code String}, that has been converted into a Java 768 * {@code char[]}-Array. The line of code whose leading white-space is being computed may be 769 * located anywhere in the array. 770 * 771 * <BR /><BR /><B>NOTE:</B> These types of arrays are easily creaated by invoking the 772 * {@code java.lang.String} method {@code 'toCharArray()'} 773 * 774 * @param lineFirstCharacterPos The array-index to be considered as the first character of 775 * non-new-line character data. 776 * 777 * @param spacesPerTab The number of spaces that a tab-character ({@code '\t'}) intends to 778 * represent. 779 * 780 * <BR /><BR />When {@code FALSE} is passed to this parameter, a tab-character will represent 781 * a {@code String} of space-characters whose length is equal to the number of space-characters 782 * that remain until the next modulo-{@code spacesPerTab} boundary. 783 * 784 * @return The number of space-characters ({@code ' '}) that should preceede the line of source 785 * code. 786 * 787 * <BR /><BR /><B STYLE='color: red'>NOTE:</B> If this line of source-code is a white-space 788 * <B STYLE='color: red;'>ONLY</B> line, then {@code -1} will be returned. 789 */ 790 public static int computeEffectiveLeadingWhiteSpace 791 (char[] code, int lineFirstCharacterPos, int spacesPerTab) 792 { 793 int ret = 0; 794 int relativeCount = 0; 795 796 for (int i=lineFirstCharacterPos; i < code.length; i++) 797 798 if (! Character.isWhitespace(code[i])) return ret; 799 800 else switch (code[i]) 801 { 802 case ' ' : 803 ret++; 804 relativeCount = (relativeCount + 1) % spacesPerTab; 805 break; 806 807 case '\t' : 808 ret += (spacesPerTab - relativeCount); 809 relativeCount = 0; 810 break; 811 812 case '\r' : 813 case '\f' : break; 814 case '\n' : return -1; 815 default: throw new UnreachableError(); 816 } 817 818 return -1; 819 } 820 821 /** 822 * Replaces tab-characters ({@code '\t'}) in a single-line of source-code with a 823 * relative-number of space-characters ({@code ' '}). 824 * 825 * <EMBED CLASS='external-html' DATA-FILE-ID=SI_REL_TABS> 826 * 827 * <BR /><BR /><B STYLE='color: red;'>IMPORTANT:</B> None of the parameters to this method 828 * will be checked for errors. This method is often used inside of a loop, and improper 829 * input should be presumed to cause indeterminate results. 830 * 831 * @param code This should be the source-code, converted to a character-array. The specific 832 * line in the source-code being properly space-adjusted may be located anywhere in this 833 * array. 834 * 835 * <BR /><BR /><B>NOTE:</B> These types of arrays are easily creaated by invoking the 836 * {@code java.lang.String} method {@code 'toCharArray()'} 837 * 838 * @param numLeadingSpaces The number of spaces that have been placed before the start of this 839 * line of code. This is needed because <B STYLE='color: red;'>relative</B>-tabs are computed 840 * based on integral-multiples of the tab-width ({@code 'spacesPerTab'}). 841 * 842 * <BR /><BR />This method is a helper & example method that may be used in conjunction 843 * with properly indenting source-code. Note that the number of leading-spaces may not be 844 * identicaly to the actual number of white-space characters in the array. <I>After converting 845 * tab-characters ({@code '\t'}) to spaces ({@code ' '}), this number will often change.</I> 846 * 847 * @param fcPos This parameter should contain the location of the first source-code character 848 * in the line of code. This parameter should be an array-index that 849 * <B STYLE='color: red;'>does not</B> contain white-space. 850 * 851 * @param spacesPerTab The number of spaces that are used to replaces tab-characters. Since 852 * this method performs relative tab-replacement, this constitutes the 853 * <B STYLE='color: red;'>maximum</B> number of space characters that will be used to replace 854 * a tab. 855 * 856 * @return A line of code, as a {@code String}, without any leading white-space, and one in 857 * which all tab-characters have been replaced by spaces. 858 */ 859 public static String lineOfCodeAsStr 860 (char[] code, int numLeadingSpaces, int fcPos, int spacesPerTab) 861 { 862 StringBuilder sb = new StringBuilder(); 863 864 // Loop Control Variables 865 int possibleEndingWhiteSpaceCount = 0; 866 int relativePos = numLeadingSpaces % spacesPerTab; 867 int i = fcPos; 868 869 while ((i < code.length) && (code[i] != '\n')) 870 { 871 if (code[i] == '\t') 872 while (relativePos < 4) 873 { sb.append(' '); relativePos++; possibleEndingWhiteSpaceCount++; } 874 875 else if ((code[i] == ' ') || (code[i] == '\r') || (code[i] == '\f')) 876 { sb.append(' '); relativePos++; possibleEndingWhiteSpaceCount++; } 877 878 else 879 { sb.append(code[i]); relativePos++; possibleEndingWhiteSpaceCount=0;} 880 881 i++; 882 relativePos %= 4; 883 } 884 885 if (i < code.length) 886 { 887 if (possibleEndingWhiteSpaceCount > 0) 888 { 889 sb.setCharAt(sb.length() - possibleEndingWhiteSpaceCount, '\n'); 890 return sb.substring(0, sb.length() - possibleEndingWhiteSpaceCount + 1); 891 } 892 else return sb.append('\n').toString(); 893 } 894 895 return (possibleEndingWhiteSpaceCount > 0) 896 ? sb.substring(0, sb.length() - possibleEndingWhiteSpaceCount) 897 : sb.toString(); 898 } 899 900 /** 901 * This performs a variation of "indentation" on a Java {@code String} - simply put - it 902 * replaces each new-line character ({@code '\n'}) with a {@code String} that begins with a 903 * new-line, and is followed by {@code 'n'} blank white-space characters {@code ' '}. 904 * 905 * If the input {@code String} parameter {@code 's'} is of zero-length, then the zero-length 906 * {@code String} is returned. If the final character in the input {@code String} is a 907 * new-line, that new-line is not padded. 908 * 909 * @param s Any {@code java.lang.String} - preferably one that contains new-line characters. 910 * 911 * @param n The number of white-space characters to use when pre-pending white-space to each 912 * line of text in input-parameter {@code 's'} 913 * 914 * @return A new {@code java.lang.String} where each line of text has been indented by 915 * {@code 'n'} blank white-space characters. If the text ends with a new-line, that line of 916 * text is not indented. 917 * 918 * @throws NException If parameter {@code 'n'} is less than one. 919 */ 920 public static String indent(String s, int n) 921 { 922 if (n < 1) throw new NException( 923 "The value passed to parameter 'n' was [" + n + "], but this is expected to be an " + 924 "integer greater than or equal to 1." 925 ); 926 927 if (s.length() == 0) return ""; 928 929 String padding = String.format("%1$" + n + "s", " "); 930 boolean lastIsNewLine = s.charAt(s.length() - 1) == '\n'; 931 932 s = padding + s.replace("\n", "\n" + padding); 933 934 return lastIsNewLine 935 ? s.substring(0, s.length() - padding.length()) 936 : s; 937 } 938 939 /** 940 * Identical to {@link #indent(String, int)}, but pre-pends a {@code TAB} character {@code 'n'} 941 * times, rather than a space-character {@code ' '}. 942 * 943 * @param s Any {@code java.lang.String} - preferably one that contains new-line characters. 944 * 945 * @param n The number of tab ({@code '\t'}) characters to use when pre-pending to each line of 946 * text within input-parameter {@code 's'} 947 * 948 * @return A new {@code java.lang.String} where each line of text has been indented by 949 * {@code 'n'} tab characters. If the text ends with a new-line, that line of text is not 950 * indented. 951 * 952 * @throws NException If parameter {@code 'n'} is less than one. 953 */ 954 public static String indentTabs(String s, int n) 955 { 956 if (n < 1) throw new NException( 957 "The value passed to parameter 'n' was [" + n + "], but this is expected to be an " + 958 "integer greater than or equal to 1." 959 ); 960 961 if (s.length() == 0) return ""; 962 963 String padding = String.format("%1$"+ n + "s", "\t"); 964 boolean lastIsNewLine = s.charAt(s.length() - 1) == '\n'; 965 966 s = padding + s.replace("\n", "\n" + padding); 967 968 return lastIsNewLine 969 ? s.substring(0, s.length() - padding.length()) 970 : s; 971 } 972 973 /** 974 * This method replaces all '\n' characters with pre-pended white-space indentation, similar 975 * to the other two methods of the same name. The difference, here, and the other two 976 * functions is this one <I>does not indent lines of text that only contain white-space!</I> 977 * 978 * <DIV CLASS=EXAMPLE>{@code 979 * // ***************************** 980 * // VERSION 1: Identical Output 981 * String s1 = "Hello World!"; 982 * 983 * System.out.println(indent(s1, 4)); 984 * System.out.println(indent(s1, 4, true, true)); 985 * // Both Print: " Hello World!" 986 * 987 * // ***************************** 988 * // VERSION 2: Output's differ 989 * String s2 = "Hello World!\n\nSincerely,\n\nCorporate Headquarters.\n"; 990 * 991 * System.out.println(indent(s2, 4)); 992 * // Prints: " Hello World!\n \n Sincerely,\n \n Corporate Headquarters.\n" 993 * 994 * System.out.println(indent(s2, 4, true, true)); 995 * // Prints: " Hello World!\n\n Sincerely,\n\n Corporate Headquarters.\n" 996 * // NOTICE: Blank lines are not indented. 997 * }</DIV> 998 * 999 * @param s Any {@code java.lang.String} - preferably one that contains new-line characters. 1000 * 1001 * @param n The number of tab ({@code '\t'}) characters to use when pre-pending to each line of 1002 * text in input-parameter {@code 's'} 1003 * 1004 * @param spaceOrTab When this parameter is passed <B>{@code TRUE}</B>, the space character 1005 * {@code ' '} is used for indentation. When <B>{@code FALSE}</B>, the tab-character 1006 * {@code '\t'} is used. 1007 * 1008 * @return A new {@code java.lang.String} where each line of text has been indented by 1009 * {@code 'n'} characters of spaces or tabs, dependent upon the value of parameter 1010 * {@code 'spaceOrTab'}. 1011 * 1012 * <BR /><BR />The returned {@code String} differs from the returns of the other two 1013 * {@code 'indent'} methods in that any new-line that contains only white-space, or any 1014 * new-line that is empty and is immediately-followed-by another newline, 1015 * <I>is not idented</I>. Succinctly, only lines containing non-white-space characters are 1016 * actually indented. 1017 * 1018 * @throws NException If parameter {@code 'n'} is less than one. 1019 */ 1020 public static String indent 1021 (final String s, int n, boolean spaceOrTab, boolean trimBlankLines) 1022 { 1023 if (n < 1) throw new NException( 1024 "The value passed to parameter 'n' was [" + n + "], but this is expected to be an " + 1025 "integer greater than or equal to 1." 1026 ); 1027 1028 if (s.length() == 0) return ""; 1029 1030 final String padding = String.format("%1$"+ n + "s", spaceOrTab ? " " : "\t"); 1031 1032 // This replacement-function does a 'look-ahead'. If the next character after a newline 1033 // character '\n' is *also* a '\n', then the first '\n' is left alone (not indented) 1034 IntCharFunction<String> replFunc = (int i, char c) -> 1035 { 1036 while (i < (s.length() - 1)) 1037 { 1038 c = s.charAt(++i); 1039 if (c == '\n') return "\n"; 1040 if ((c != ' ') && (c != '\t')) return "\n" + padding; 1041 } 1042 1043 return "\n"; 1044 }; 1045 1046 // NOTE: private static final char[] cArrIndent = { '\n' }; 1047 String ret = StrReplace.r(s, cArrIndent, replFunc); 1048 1049 if (trimBlankLines) ret = trimWhiteSpaceOnlyLines(ret); 1050 1051 // Indent the first line of text - insert the padding before the final returned string. 1052 // NOTE: This is somewhat inefficient, because the whole array needs to be copied again. 1053 // Perhaps switching to RegEx and matching '^' is better (because of this reason). 1054 // Special Case: The first character, itself, is a new-line. 1055 return (ret.charAt(0) != '\n') ? (padding + ret) : ret; 1056 } 1057 1058 /** 1059 * Throws a new {@code ToDoException} 1060 * 1061 * @return Will (one day) return an unindented String. 1062 */ 1063 public static String unIndent( 1064 String s, int n, 1065 boolean trimBlankLines, 1066 boolean rightTrimLines, 1067 boolean throwOnTab, 1068 boolean throwOnNotEnough, 1069 boolean dontThrowOnWhiteSpaceOnlyLines 1070 ) 1071 { 1072 if (n < 1) throw new NException( 1073 "The value that was passed to parameter 'n' was [" + n + "], but unfortunately this " + 1074 "expected to be a positive integer, greater than zero." 1075 ); 1076 1077 char[] cArr = s.toCharArray(); 1078 throw new ToDoException(); 1079 } 1080 1081 /** 1082 * Performs an indenting of {@code String} of text, but does not indent the first line. This 1083 * is used quit frequently by code-generators that need to assign or invoke something, and want 1084 * to make sure that subsequent lines of piece of code are indented (after the first line of 1085 * text). 1086 * 1087 * @param s Any instance of {@code java.lang.String}. 1088 * @param n The number of space-characters to insert after each newline {@code '\n'} character. 1089 * @param spaceOrTab When {@code TRUE}, then there shall be {@code 'n'}-number of space 1090 * characters ({@code ' '}) inserted at the beginning of each line of text. When 1091 * {@code FALSE}, then this function will insert {@code 'n'} tab characters. 1092 * @param trimBlankLines When {@code TRUE}, requests that blank lines be trimmed to only 1093 * a single newline ({@code '\n'}) character. 1094 * @return The indented {@code String} 1095 */ 1096 public static String indentAfter2ndLine 1097 (String s, int n, boolean spaceOrTab, boolean trimBlankLines) 1098 { 1099 int pos = s.indexOf('\n'); // If there are no newlines, then return the original string. 1100 if (pos == -1) return s; 1101 1102 pos++; 1103 return // return the first string, as is, and then indent subsequent lines. 1104 s.substring(0, pos) + 1105 indent(s.substring(pos), n, spaceOrTab, trimBlankLines); 1106 } 1107 1108 /** 1109 * This will replaced leading tabs for each line of text in a source code file with a specified 1110 * number of spaces. If tabs are supposed to represent {@code 4} spaces, then if a line in a 1111 * source-code file had three leading tab-characters, then those three leading 1112 * {@code char '\t'} would be replaced with {@code 12} leading space characters {@code ' '}. 1113 * 1114 * @param javaSrcFileAsStr A Java Source-Code File, loaded as a {@code String}. 1115 * 1116 * @param numSpacesPerTab This identifies the number of spaces a {@code char '\t'} is supposed 1117 * to represent in any source-code editor's settings. In Google Cloud Server, the default 1118 * value is '4' spaces. 1119 */ 1120 public static String tabsToSpace(String javaSrcFileAsStr, int numSpacesPerTab) 1121 { 1122 String spaces = StringParse.nChars(' ', numSpacesPerTab); 1123 String[] lines = javaSrcFileAsStr.split("\n"); 1124 1125 for (String line:lines) System.out.println("LINE: " + line); 1126 1127 for (int i=0; i < lines.length; i++) 1128 { 1129 int numTabs = 0; 1130 1131 while ((numTabs < lines[i].length()) && (lines[i].charAt(numTabs) == '\t')) 1132 numTabs++; 1133 1134 if (numTabs == 0) 1135 lines[i] = lines[i] + '\n'; 1136 else 1137 lines[i] = StringParse.nStrings(spaces, numTabs) + 1138 lines[i].substring(numTabs) + '\n'; 1139 } 1140 1141 StringBuilder sb = new StringBuilder(); 1142 1143 for (String line : lines) sb.append(line); 1144 1145 return sb.toString(); 1146 } 1147}