001package Torello.Java.Build; 002 003import Torello.Java.FileNode; 004import Torello.Java.FileRW; 005import Torello.Java.LFEC; 006import Torello.Java.Q; 007import Torello.Java.StrCmpr; 008import Torello.Java.StringParse; 009import Torello.Java.RTC; 010 011import static Torello.Java.C.*; 012 013import java.util.*; 014import java.util.regex.*; 015import java.io.*; 016 017import java.util.function.IntFunction; 018 019/** 020 * A very basic utility for ensuring that the Java Doc Comment portion of a source-file does not 021 * exceed a maximum line-length. 022 * 023 * <BR /><BR /><EMBED CLASS='external-html' DATA-FILE-ID=LINT> 024 */ 025public class Lint 026{ 027 private Lint() { } 028 029 030 // ******************************************************************************************** 031 // ******************************************************************************************** 032 // MAIN COMMAND-LINE METHOD 033 // ******************************************************************************************** 034 // ******************************************************************************************** 035 036 037 private static final String man_page = '\n' + 038 "\tjava Torello.Java.Lint 1 <Java-Source-File>\n" + 039 "\tjava Torello.Java.Lint 1 INNERCLASS <Java-Source-File>\n" + 040 "\tjava Torello.Java.Lint 2 <external-html-directory> (replaces ' * ' and {@code })\n" + 041 "\tjava Torello.Java.Lint 3 <external-html-directory> (line-length checker)\n"; 042 043 /** 044 * This class' operations are all performed via the command line. It asks questions of the 045 * user from the command line, and lints the Java Doc Comments parts of {@code '.java'} files 046 * to ensure that no line comment line is longer than 100 characters. This class is probably 047 * not too useful - <I>outside of the Java HTML Library!</I> It did help me make all of my 048 * source-code files look nice when you click on the "Hi-Lited Source Code" links. 049 */ 050 public static void main(String[] argv) throws IOException 051 { 052 if ( ((argv.length < 2) || (argv.length > 3)) 053 || StrCmpr.equalsNAND(argv[0], "1", "2", "3") 054 ) 055 System.out.println(man_page); 056 057 else if (argv[0].equals("1")) // Java Doc Linter 058 { 059 if (argv.length == 2) lint(argv[1]); 060 else 061 { 062 if (! argv[1].equals("INNERCLASS")) 063 { 064 System.out.println(man_page); 065 System.exit(0); 066 } 067 068 LONG_JD_COMMENT = LONG_JD_COMMENT_INNERCLASS; 069 COMMENT_LINE_BEGINNING = " " + COMMENT_LINE_BEGINNING; 070 lint(argv[2]); 071 } 072 } 073 074 else if (argv[0].equals("2")) // External-HTML File Linter 075 externalHTML(argv[1]); 076 077 else if (argv[0].equals("3")) // External-HTML File Linter 078 lineLengthChecker(argv[1]); 079 080 else System.out.println(man_page); 081 } 082 083 084 // ******************************************************************************************** 085 // ******************************************************************************************** 086 // Java Doc Comments Linter 087 // ******************************************************************************************** 088 // ******************************************************************************************** 089 090 091 /** Regular Expression for breaking up long lines of JavaDoc Comments */ 092 public static Pattern LONG_JD_COMMENT = Pattern.compile 093 ("^( \\* [^\\n]{0,92})\\s([^\\n]+)\n"); 094 095 /** Regular Expression for breaking up long lines of JavaDoc Comments */ 096 public static final Pattern LONG_JD_COMMENT_INNERCLASS = Pattern.compile 097 ("^( \\* [^\\n]{0,88})\\s([^\\n]+)\n"); 098 099 /** Regular Expression for matching lines that have JavaDoc Upgrade HiLite Dividers */ 100 public static final Pattern OPENING_HILITE_DIV = Pattern.compile( 101 "<DIV\\s+CLASS=['\\\"](EXAMPLE|SNIP|LOC|SHELL|HTML|REGEX|SHELLBLOCK|COMPLETE)" + 102 "(-SCROLL)?['\\\"]\\w*>\\w*\\{", 103 Pattern.CASE_INSENSITIVE); 104 105 /** Regular Expression for matching lines that have 'closing' HiLite Divider Elements. */ 106 public static final Pattern CLOSING_HILITE_DIV = Pattern.compile 107 ("\\}\\w*<\\/DIV>", Pattern.CASE_INSENSITIVE); 108 109 /** Long lines that don't require being queried by the user, because they only have text */ 110 public static final Pattern SAFE_ENDING = Pattern.compile 111 ("^.*?[\\w\\d\\s,\\-\\.]{20}$"); 112 113 /** The first 7 characters of a line of text in JavaDoc Comments portions of source files. */ 114 public static String COMMENT_LINE_BEGINNING = " * "; 115 116 private static final String[] PREV_LINE_MUST_BE_STRS = 117 { 118 "* @", 119 "* <EMBED ", 120 "* <BR /><BR />", 121 122 "* <DIV ", 123 "* <BR /><DIV ", 124 125 "* <UL ", 126 "* <BR /><UL ", 127 128 "* <OL ", 129 "* <BR /><OL ", 130 131 "* <TABLE ", 132 "* <BR /><TABLE " 133 }; 134 135 private static final String[] CANNOT_PREPEND_TO_LINE_STRS = 136 new String[11 + PREV_LINE_MUST_BE_STRS.length]; 137 static 138 { 139 System.arraycopy( 140 PREV_LINE_MUST_BE_STRS, 0, CANNOT_PREPEND_TO_LINE_STRS, 141 11, PREV_LINE_MUST_BE_STRS.length 142 ); 143 144 CANNOT_PREPEND_TO_LINE_STRS[0] = "* </UL>"; 145 CANNOT_PREPEND_TO_LINE_STRS[1] = "* </OL>"; 146 CANNOT_PREPEND_TO_LINE_STRS[2] = "* </TABLE>"; 147 CANNOT_PREPEND_TO_LINE_STRS[3] = "* <LI>"; 148 CANNOT_PREPEND_TO_LINE_STRS[4] = "* </LI>"; 149 CANNOT_PREPEND_TO_LINE_STRS[5] = "* <TD>"; 150 CANNOT_PREPEND_TO_LINE_STRS[6] = "* </TD>"; 151 CANNOT_PREPEND_TO_LINE_STRS[7] = "* <TR>"; 152 CANNOT_PREPEND_TO_LINE_STRS[8] = "* </TR>"; 153 CANNOT_PREPEND_TO_LINE_STRS[9] = "* <TH>"; 154 CANNOT_PREPEND_TO_LINE_STRS[10] = "* </TH>"; 155 } 156 157 /** 158 * Performs a 'LINT' on the input Java Source Code File. 159 * 160 * @param inFileOrDir This is any file or directory. If this is a directory, the entire 161 * directory will be scanned for {@code '.java'} source-files. If this is a file, then it 162 * will be the only file that is linted. 163 * 164 * @throws FileNotFoundException If this file or directory is not found. 165 */ 166 public static void lint(String inFileOrDir) throws IOException 167 { 168 File f = new File(inFileOrDir); 169 Vector<String> files; 170 171 if (! f.exists()) throw new FileNotFoundException 172 ("A file or directory named [" + inFileOrDir + "], was not found."); 173 174 boolean lintingDirectory = true; 175 176 if (f.isDirectory()) files = FileNode 177 .createRoot(inFileOrDir) 178 .loadTree(1, (File file, String name) -> name.endsWith(".java"), null) 179 .flattenJustFiles(RTC.FULLPATH_VECTOR()); 180 else 181 { 182 files = new Vector<>(); 183 files.add(inFileOrDir); 184 lintingDirectory = false; 185 } 186 187 for (int fNum=0; fNum < files.size(); fNum++) 188 { 189 String file = files.elementAt(fNum); 190 191 System.out.println( 192 "Linting File [" + BCYAN + (fNum+1) + " of " + files.size() + RESET + "]\n" + 193 "Visiting File: " + BYELLOW + file + RESET + "\n" 194 ); 195 196 Vector<String> javaFile = FileRW.loadFileToVector(file, true); 197 boolean insideHiLiteDIV = false; 198 int linesAdded = 0; 199 200 for (int i=1; i < (javaFile.size()-1); i++) 201 { 202 String line = javaFile.elementAt(i); 203 String prevLine = javaFile.elementAt(i-1); 204 String nextLine = javaFile.elementAt(i+1); 205 206 String lineTrimmed = line.trim(); 207 String prevLineTrimmed = prevLine.trim(); 208 String nextLineTrimmed = nextLine.trim(); 209 210 boolean lineIsComment = line.startsWith(COMMENT_LINE_BEGINNING); 211 boolean nextLineIsComment = nextLine.startsWith(COMMENT_LINE_BEGINNING); 212 boolean prevLineWasComment = prevLine.startsWith(COMMENT_LINE_BEGINNING); 213 214 boolean lineIsTooLong = line.length() > 100; 215 boolean prevLineWasBlankComment = prevLineTrimmed.equals("*"); 216 boolean nextLineIsEndingComment = nextLineTrimmed.equals("*/"); 217 boolean nthSeeTagInARow = lineTrimmed.startsWith("* @see") && 218 prevLineTrimmed.startsWith("* @see"); 219 220 boolean hasOpeningHiLiteDIV = lineIsComment && 221 OPENING_HILITE_DIV.matcher(lineTrimmed).find(); 222 223 boolean hasClosingHiLiteDIV = lineIsComment && 224 CLOSING_HILITE_DIV.matcher(lineTrimmed).find(); 225 226 boolean mustBePreceededByBlankCommentLine = 227 lineIsComment && 228 StrCmpr.startsWithXOR_CI(lineTrimmed, PREV_LINE_MUST_BE_STRS); 229 230 boolean appendToStartOfNextLineIsOK = 231 nextLineIsComment && 232 (! StrCmpr.startsWithXOR_CI(nextLineTrimmed, CANNOT_PREPEND_TO_LINE_STRS)) && 233 (! nextLineIsEndingComment); 234 235 // ************************************************************ 236 // MAIN LOOP PART 237 // ************************************************************ 238 239 if (hasOpeningHiLiteDIV) 240 insideHiLiteDIV = true; 241 else 242 { 243 if (hasClosingHiLiteDIV) 244 { insideHiLiteDIV = false; System.out.print(line); continue; } 245 246 if (insideHiLiteDIV) 247 { System.out.print(line); continue; } 248 } // tricky... 249 250 if ( mustBePreceededByBlankCommentLine 251 && (! prevLineWasBlankComment) 252 && (! nthSeeTagInARow) 253 ) 254 { 255 linesAdded++; 256 javaFile.add(i, COMMENT_LINE_BEGINNING + '\n'); 257 System.out.print(BGREEN + COMMENT_LINE_BEGINNING + RESET + '\n'); 258 } 259 else if (lineIsComment && lineIsTooLong) 260 { 261 System.out.print(BRED + line + RESET); 262 Matcher m = LONG_JD_COMMENT.matcher(line); 263 264 if (! m.find()) throw new IllegalStateException("MESSED UP, WTF?"); 265 266 String shortenedLine = StringParse.trimRight(m.group(1)); 267 String restOfThisLine = line.substring(m.end(1)).trim(); 268 269 System.out.print(shortenedLine + '\n'); 270 javaFile.setElementAt(shortenedLine + '\n', i); 271 272 if (! SAFE_ENDING.matcher(shortenedLine).find()) 273 274 while (! Q.YN( 275 "Break OK? (Currently on Line #" + i + " of " + 276 javaFile.size() + ")" 277 )) 278 { 279 int pos = shortenedLine.lastIndexOf(' '); 280 if (pos == -1) System.exit(0); 281 282 restOfThisLine = shortenedLine.substring(pos + 1).trim() + 283 ' ' + restOfThisLine; 284 285 shortenedLine = StringParse.trimRight(shortenedLine.substring(0, pos)); 286 287 System.out.print(BRED + shortenedLine + RESET + '\n'); 288 javaFile.setElementAt(shortenedLine + '\n', i); 289 } 290 291 if (restOfThisLine.length() == 0) continue; 292 293 if (appendToStartOfNextLineIsOK) 294 { 295 nextLine = COMMENT_LINE_BEGINNING + restOfThisLine + ' ' + 296 nextLine.substring(COMMENT_LINE_BEGINNING.length()); 297 298 javaFile.setElementAt(nextLine, i+1); 299 } 300 else 301 { 302 linesAdded++; 303 javaFile.add(i+1, COMMENT_LINE_BEGINNING + restOfThisLine + '\n'); 304 } 305 } 306 else System.out.print(line); 307 } 308 309 System.out.println( 310 "Finished File:\t" + BYELLOW + file + RESET + '\n' + 311 "Added [" + BCYAN + StringParse.zeroPad(linesAdded) + RESET + "] new lines " + 312 "to the file." 313 ); 314 315 for (int count=5; count > 0; count--) 316 Q.YN( 317 "Press Y/N " + BCYAN + StringParse.zeroPad(count) + RESET + 318 " more times..." 319 ); 320 321 String question = lintingDirectory 322 ? "Save and continue to next file?" 323 : "Save file?"; 324 325 if (! Q.YN(GREEN + question + RESET)) System.exit(0); 326 FileRW.writeFile_NO_NEWLINE(javaFile, file); 327 System.out.println("Wrote File:\t" + BYELLOW + file + RESET); 328 } 329 } 330 331 332 // ******************************************************************************************** 333 // ******************************************************************************************** 334 // External HTML File Converter 335 // ******************************************************************************************** 336 // ******************************************************************************************** 337 338 339 private static final Pattern CODES = Pattern.compile("\\{@code [^\\}]+\\}"); 340 private static final Pattern LINKS = Pattern.compile("\\{@link ([\\w.]+)?#?([^\\}]+)?\\}"); 341 private static final Pattern JDSTARS = Pattern.compile("^\\s+\\*( |$)", Pattern.MULTILINE); 342 343 /** 344 * This can be a really great tool for transitioning a Source-File to use External-HTML Files. 345 * This method merely scans an HTML-File for items that need to be escaped or converted to 346 * constructs that are usable in {@code '.html'} rather than {@code '.java'} files. 347 * 348 * @param directoryName The name of a directory containing {@code '.html'} files. 349 */ 350 public static void externalHTML(String directoryName) throws IOException 351 { 352 Iterator<String> externalHTMLFiles = FileNode 353 .createRoot(directoryName) 354 .loadTree(-1, (File dir, String fileName) -> fileName.endsWith(".html"), null) 355 .flattenJustFiles(RTC.FULLPATH_ITERATOR()); 356 357 while (externalHTMLFiles.hasNext()) 358 { 359 String fileName = externalHTMLFiles.next(); 360 String fileAsStr = FileRW.loadFileToString(fileName); 361 String newFileStr = fileAsStr; 362 Matcher codes = CODES.matcher(fileAsStr); 363 364 System.out.println("Linting File: " + BYELLOW + fileName + RESET); 365 366 if (! Q.YN("Shall We Lint?")) 367 { 368 if (! Q.YN("Continue to Next File? (say 'no' to Exit)")) System.exit(0); 369 else continue; 370 } 371 372 373 374 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 375 // Replace the {@code ...} 376 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 377 378 while (codes.find()) 379 { 380 String s = codes.group(); 381 382 String newS = 383 "<CODE>" + 384 s.substring(7, s.length() - 1).replace("<", "<").replace(">", ">") + 385 "</CODE>"; 386 387 System.out.println( 388 BYELLOW + s.replace("\n", "\\n") + RESET + 389 BRED + "\n ===>\n" + RESET + 390 BGREEN + newS + RESET 391 ); 392 393 if (Q.YN("Replace this one?")) 394 { 395 newFileStr = codes.replaceFirst(newS); 396 codes.reset(newFileStr); 397 } 398 } 399 400 401 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 402 // Replace the {@link ...} 403 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 404 // 405 // **NOTE: The replacements are JUST HELPERS, THEY ARE NOT COMPLETE. 406 // This does about 95% of the typing for you, but it is not 100% 407 // The relative-path string (dot-dots) has not been added. 408 409 Matcher links = LINKS.matcher(newFileStr); 410 411 while (links.find()) 412 { 413 String s = links.group(); 414 415 String file = links.group(1); 416 String rel = links.group(2); 417 418 String href = 419 ((file != null) ? (file + ".html") : "") + 420 ((rel != null) ? ("#" + rel) : ""); 421 422 String linkText = 423 ((file != null) ? (file) : "") + 424 ((rel != null) 425 ? (((file != null) ? "." : "") + rel) 426 : ""); 427 428 String newS = 429 "<B><CODE><A HREF='" + href + "'>" + linkText + "</CODE></B></A>"; 430 431 System.out.println( 432 BYELLOW + s.replace("\n", "\\n") + RESET + 433 BRED + "\n ===>\n" + RESET + 434 BGREEN + newS + RESET 435 ); 436 437 if (Q.YN("Replace this one?")) 438 { 439 newFileStr = links.replaceFirst("\n\n" + newS + "\n\n"); 440 links.reset(newFileStr); 441 } 442 } 443 444 445 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 446 // Replace the leading " * " 447 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 448 449 newFileStr = JDSTARS.matcher(newFileStr).replaceAll(""); 450 451 452 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 453 // Save the file 454 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 455 456 System.out.println( 457 newFileStr + 458 "\n************************************************************\n" 459 ); 460 461 if (Q.YN("Save this File?")) 462 { 463 FileRW.writeFile(newFileStr, fileName); 464 System.out.println("Saved File: " + BYELLOW + fileName + RESET); 465 } 466 } 467 } 468 469 470 // ******************************************************************************************** 471 // ******************************************************************************************** 472 // CHECKS FOR '.java' FILES WITH LONG LINE LENGTHS 473 // ******************************************************************************************** 474 // ******************************************************************************************** 475 476 477 /** 478 * This method will scan an entire directory for {@code '.java'} files, and then report if 479 * there are any lines in each of those files whose length is greater than 100. 480 * 481 * <BR /><BR />While not exactly a great tool for all developers, during the development of 482 * Java HTML, this has been (on occasion) extremely useful. 483 * 484 * @param directoryName The name of the directory to be scanned for {@code '.java'} files. 485 */ 486 public static void lineLengthChecker(String directoryName) throws IOException 487 { 488 Iterator<String> javaFiles = FileNode 489 .createRoot(directoryName) 490 .loadTree(-1, (File dir, String fileName) -> fileName.endsWith(".java"), null) 491 .flattenJustFiles(RTC.FULLPATH_ITERATOR()); 492 493 while (javaFiles.hasNext()) 494 { 495 String fileName = javaFiles.next(); 496 Vector<String> lines = FileRW.loadFileToVector(fileName, true); 497 boolean hadMatch = false; 498 IntFunction<String> zeroPad = (lines.size() < 999) 499 ? StringParse::zeroPad 500 : StringParse::zeroPad10e4; 501 502 System.out.println("Checking File: " + BYELLOW + fileName + RESET); 503 504 for (int i=0; i < lines.size(); i++) 505 506 if (lines.elementAt(i).length() > 100) 507 { 508 String l = lines.elementAt(i); 509 510 System.out.print( 511 "Line-Number: " + 512 513 // NOTE: add one to the line number, when printing it. The lines 514 // vector index starts at zero, not one. 515 516 '[' + BGREEN + zeroPad.apply(i + 1) + RESET + ", " + 517 "Has Length: " + BRED + l.length() + RESET + '\n' + 518 519 // Print the line 520 l 521 ); 522 523 hadMatch = true; 524 } 525 526 if (hadMatch) if (! Q.YN("continue?")) System.exit(0); 527 } 528 } 529}