001package Torello.JavaDoc; 002 003import Torello.Java.Additional.Ret3; 004 005import static Torello.Java.C.*; 006 007import Torello.JDUInternal.Messager.Messager; 008import Torello.JDUInternal.Messager.Where.JDUUserAPI; 009import Torello.JDUInternal.Messager.Where.Where_Am_I; 010 011import java.util.*; 012import com.sun.source.util.*; 013import com.sun.source.tree.*; 014import com.sun.source.doctree.*; 015 016import javax.tools.JavaCompiler; 017import javax.tools.ToolProvider; 018import javax.tools.StandardJavaFileManager; 019import javax.tools.JavaFileObject; 020 021/** 022 * This is a helpful interface to retrieve and extract the utilities provided by the Java-Compiler 023 * interface provided by the JDK. This class utilizes the Tree-Classes defined in the Java Package 024 * {@code com.sun.source.tree.*} (and also {@code com.sun.source.util.*}. These AST (Tree's) work 025 * and function as an alternative to the {@code JavaParser} AST Generator. 026 */ 027public class TreeUtils 028{ 029 private static final Where_Am_I WHERE_AM_I = JDUUserAPI.TreeUtils; 030 031 032 // ******************************************************************************************** 033 // ******************************************************************************************** 034 // Static-Instances & Static-Constants 035 // ******************************************************************************************** 036 // ******************************************************************************************** 037 038 039 // javac options we pass to the compiler. We enable preview so that all preview features can 040 // be parsed. 041 042 private static final List<String> OPTIONS = 043 List.of("--enable-preview", "--release=" + getJavaMajorVersion()); 044 045 // major version of JDK such as 16, 17, 18 etc. 046 private static int getJavaMajorVersion() { return Runtime.version().feature(); } 047 048 // Standard JDK Stuff - no fancy library imports here 049 // javax.tools.JavaCompiler, javax.tools.ToolProvider 050 051 private static final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); 052 053 // javax.tools.StandardJavaFileManager 054 private static final StandardJavaFileManager fileManager = 055 compiler.getStandardFileManager(null, null, null); 056 057 058 // ******************************************************************************************** 059 // ******************************************************************************************** 060 // Public Fields 061 // ******************************************************************************************** 062 // ******************************************************************************************** 063 064 065 /** <EMBED CLASS='external-html' DATA-FILE-ID=TU_SOURCE_POS> */ 066 public final SourcePositions sourcePositions; 067 068 /** 069 * Java's Sun/Oracle Abstract Syntax Tree engine provides AST's for Java-Doc Comments, in 070 * addition to trees for the actual source-code. This reference here is merely an instance of 071 * the utility class containing tools for traversing Java-Doc Comment Syntax Trees. 072 * 073 * <BR /><BR />More information about these trees is available in the documentation for the 074 * Java package {@code com.sun.source.util.DocTrees}. 075 */ 076 public final DocTrees docTrees; 077 078 /** <EMBED CLASS='external-html' DATA-FILE-ID=TU_DOC_SOURCE_POS> */ 079 public final DocSourcePositions docSourcePositions; 080 081 /** 082 * This instance is generated by the Java-Compiler when it is asked to perform an AST (Abstract 083 * Syntax Tree) parse of a Java Source-Code File (a {@code '.java'} File). A 084 * {@code CompilationUnitTree} is essentially the root node (the 'top' of the tree) of a 085 * Source-Code AST. 086 * 087 * <BR /><BR />Please review the Sun/Oracle Documentation Page for a fuller explanation of this 088 * class' features. 089 */ 090 public final CompilationUnitTree compilationUnitTree; 091 092 /** <EMBED CLASS='external-html' DATA-FILE-ID=TU_LINE_MAP> */ 093 public final LineMap lineMap; 094 095 /** 096 * This is nothing more than a copy of the constructor-parameter by the same name. This shall 097 * contain the file-name for the source-code file that has been parsed to create an instance 098 * of this class, {@code TreeUtils}. 099 */ 100 public final String srcFileName; 101 102 /** 103 * This is also a copy of a constructor-parameter, reserved for any needed use by a reference 104 * in this class. It holds a {@code '.java'} source-code file, loaded into memory and saved as 105 * a {@code java.lang.String}. 106 */ 107 public final String srcFileAsStr; 108 109 110 // ******************************************************************************************** 111 // ******************************************************************************************** 112 // Constructor 113 // ******************************************************************************************** 114 // ******************************************************************************************** 115 116 117 /** 118 * Constructs an instance of this class. Accepts a Java Source-Code File-Name, and that file 119 * alredy fully-loaded into memory - as a {@code java.lang.String}. This constructs invokes 120 * the internal instance of the Java Compiler, and then fills in all of the public fields for 121 * this class. 122 * 123 * @param srcFileName The name of a Java Source-Code File 124 * 125 * @param srcFileAsStr The textual-content of that Source-File, loaded into a 126 * {@code java.lang.String} 127 * 128 * @see #srcFileName 129 * @see #srcFileAsStr 130 */ 131 public TreeUtils(String srcFileName, String srcFileAsStr) 132 { 133 this.srcFileName = srcFileName; 134 this.srcFileAsStr = srcFileAsStr; 135 136 137 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 138 // javax.tools.JavaFileObject 139 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 140 141 Iterable<? extends JavaFileObject> fileObjects = 142 fileManager.getJavaFileObjects(srcFileName); 143 144 Iterator<? extends JavaFileObject> iterFileObjects = fileObjects.iterator(); 145 146 if (! iterFileObjects.hasNext()) Messager.assertFailOracleParser( 147 "The JavaFileManager instance seems to have returned an empty JavaFileObject-List", 148 null, 149 WHERE_AM_I 150 ); 151 152 // The Java-Compiler actually expects the Iterator, not the single-instance. We can throw 153 // the return object of the iterator away, it isn't used at all. This is just more error 154 // checking since some of this is quite new. 155 156 iterFileObjects.next(); 157 158 if (iterFileObjects.hasNext()) Messager.assertFailOracleParser( 159 "The JavaFileManager instance has returned a JavaFileObject-List with more than one " + 160 "element", 161 null, 162 WHERE_AM_I 163 ); 164 165 166 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 167 // Running the Compiler to get a "JavacTask" 168 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 169 170 // com.sun.source.util.JavacTask 171 JavacTask task = (JavacTask) compiler.getTask 172 (null, null, null, OPTIONS, null, fileObjects); 173 174 175 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 176 // The Helper Utilities: SourcePositions, DocTrees, DocSourcePositions 177 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 178 179 // com.sun.source.util.SourcePositions 180 this.sourcePositions = Trees.instance(task).getSourcePositions(); 181 182 // com.sun.source.util.DocTrees 183 this.docTrees = DocTrees.instance(task); 184 185 // com.sun.source.util.DocSourcePositions 186 this.docSourcePositions = docTrees.getSourcePositions(); 187 188 189 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 190 // com.sun.source.tree.CompilationUnitTree 191 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 192 193 Iterable<? extends CompilationUnitTree> cutList = null; 194 195 try 196 { cutList = task.parse(); } 197 198 catch (Exception e) 199 { 200 Messager.assertFailOracleParser( 201 e, 202 "Exception thrown while parsing Source-Code File:\n" + 203 " [" + BYELLOW + srcFileName + RESET + ']', 204 null, 205 WHERE_AM_I 206 ); 207 } 208 209 Iterator<? extends CompilationUnitTree> iter = cutList.iterator(); 210 211 if (! iter.hasNext()) Messager.assertFailOracleParser( 212 "The JavacTask instance seems to have returned an empty CompilationUnitTree List", 213 null, 214 WHERE_AM_I 215 ); 216 217 // com.sun.source.tree.CompilationUnitTree 218 this.compilationUnitTree = iter.next(); 219 220 if (iter.hasNext()) Messager.assertFailOracleParser( 221 "The JavacTask instance has returned a CompilationUnitTree List with more than one " + 222 "element", 223 null, 224 WHERE_AM_I 225 ); 226 227 228 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 229 // Last, but not least: com.sun.source.tree.LineMap 230 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 231 232 this.lineMap = this.compilationUnitTree.getLineMap(); 233 } 234 235 236 // ******************************************************************************************** 237 // ******************************************************************************************** 238 // Methods 239 // ******************************************************************************************** 240 // ******************************************************************************************** 241 242 243 /** 244 * Returns the starting position within the text of the relevant Java Source-Code File for the 245 * sub-tree specified by parameter {@code 'tree'}. 246 * 247 * @param tree This may be any AST branch or leaf, but it must be sub-node that falls within 248 * the root-node {@link #compilationUnitTree}. 249 * 250 * @see #sourcePositions 251 * @see #compilationUnitTree 252 */ 253 public int startPos(Tree tree) 254 { return (int) this.sourcePositions.getStartPosition(this.compilationUnitTree, tree); } 255 256 /** 257 * Returns the ending position within the text of the relevant Java Source-Code File for the 258 * sub-tree specified by parameter {@code 'tree'}. 259 * 260 * @param tree This may be any AST branch or leaf, but it must be sub-node that falls within 261 * the root-node {@link #compilationUnitTree}. 262 * 263 * @see #sourcePositions 264 * @see #compilationUnitTree 265 */ 266 public int endPos(Tree tree) 267 { return (int) this.sourcePositions.getEndPosition(this.compilationUnitTree, tree); } 268 269 /** 270 * Returns the line-number within the text of the relevant Java Source-Code File for the 271 * <B>first line of code</B> of the element specified by parameter {@code 'tree'}. 272 * 273 * @param tree This may be any AST branch or leaf, but it must be sub-node that falls within 274 * the root-node {@link #compilationUnitTree}. 275 * 276 * @see #lineMap 277 * @see #sourcePositions 278 * @see #compilationUnitTree 279 */ 280 public int startLineNum(Tree tree) 281 { 282 return (int) this.lineMap.getLineNumber 283 (this.sourcePositions.getStartPosition(this.compilationUnitTree, tree)); 284 } 285 286 /** 287 * Returns the line-number within the text of the relevant Java Source-Code File for the 288 * <B>last line of code</B> of the element specified by parameter {@code 'tree'}. 289 * 290 * @param tree This may be any AST branch or leaf, but it must be sub-node that falls within 291 * the root-node {@link #compilationUnitTree}. 292 * 293 * @see #lineMap 294 * @see #sourcePositions 295 * @see #compilationUnitTree 296 */ 297 public int endLineNum(Tree tree) 298 { 299 return (int) this.lineMap.getLineNumber 300 (this.sourcePositions.getEndPosition(this.compilationUnitTree, tree)); 301 } 302 303 /** 304 * Performs a standard Java {@code 'substring'} operation on the Source-Code File, which has 305 * been saved as a {@code String} into internal & public field {@link #srcFileAsStr}. 306 * 307 * @param sPos The first character-postion within the text-file 308 * @param ePos The last character-postion within the text-file 309 * @return The extracted sub-string. 310 */ 311 public String substring(int sPos, int ePos) 312 { return this.srcFileAsStr.substring(sPos, ePos); } 313 314 /** 315 * Extracts the <I>exact text / character-data</I> from the original {@code '.java'} Source 316 * Code File for any node in the AST 'Parse-Tree' that was produced from that Source-File. 317 * 318 * @param tree Any node in the AST that was produced from the Source-File saved inside 319 * {@code 'this'} instance of {@code TreeUtils} 320 * 321 * @return The original Java-Source, as a {@code java.lang.String} 322 */ 323 public String substring(Tree tree) 324 { return this.srcFileAsStr.substring(startPos(tree), endPos(tree)); } 325 326 /** 327 * An instance of {@code TreePath} is a class which stores node / branch references for a node 328 * in an AST extending back to the top-level node in the Compilation-Unit. 329 * 330 * <BR /><BR />The Parse-Trees produced by the <CODE>com.sun.source.tree</CODE> classes do not 331 * provide very much in the way of parent or ancestor node pointers. This makes finding where 332 * a particular branch in an Abstract Syntax Tree a little more difficult. An instance of 333 * {@code 'TreePath'} simplifies the process. 334 * 335 * @param tree This should be a node that exists within the AST that is associated / stored 336 * inside {@code 'this'} reference of {@code TreeUtils}. 337 * 338 * @return A {@code TreePath} instance that contains the straight-line path to the root node 339 * in the Abstract Syntax Tree. 340 */ 341 public TreePath getTreePath(Tree tree) 342 { return TreePath.getPath(this.compilationUnitTree, tree); } 343 344 /** 345 * Retrieve Java-Doc Comment information (if present / if provided) for any of the entities / 346 * members of a CIET / Type. 347 * 348 * @param tree The {@link Entity} for which a Java-Doc Comment is sought. 349 * 350 * @return An instance of {@link Ret3} as follows: 351 * <BR /><BR /><UL CLASS=JDUL> 352 * 353 * <LI> {@code Ret3.a: Integer} 354 * <BR />The <B STYLE='color: red'>starting</B> character-position of the Java-Doc Comment 355 * in the original Source-Code File. 356 * <BR /><BR /></LI> 357 * 358 * <LI> {@code Ret3.b: Integer} 359 * <BR />The <B STYLE='color: red'>ending</B> character-position of the Java-Doc Comment 360 * in the original Source-Code File. 361 * <BR /><BR /></LI> 362 * 363 * <LI> {@code Ret3.b: DocCommentTree} 364 * <BR />The Parse-Tree for the Java-Doc Comment. 365 * <BR /><BR /></LI> 366 * </UL> 367 * 368 * <BR /><B STYLE='color: red;'>NOTE:</B> If the {@link Entity} / Member of the {@code 'tree'} 369 * that was passed as input to this method does not have a Java-Doc Comment written, then this 370 * method will return null instead. 371 * 372 * @see #docTrees 373 * @see #compilationUnitTree 374 * @see #docSourcePositions 375 * @see #srcFileAsStr 376 */ 377 public Ret3<Integer, Integer, DocCommentTree> getJavaDocInfoPos(Tree tree) 378 { 379 TreePath treePath = TreePath.getPath(this.compilationUnitTree, tree); 380 381 DocCommentTree dct = (treePath != null) 382 ? this.docTrees.getDocCommentTree(treePath) 383 : null; 384 385 if (dct == null) return new Ret3<>(-1, -1, null); 386 387 int sPos = (int) this.docSourcePositions.getStartPosition 388 (this.compilationUnitTree, dct, dct); 389 390 int ePos = (int) this.docSourcePositions.getEndPosition 391 (this.compilationUnitTree, dct, dct); 392 393 // This is a case where there is an "empty comment". I'm not sure at the moment if there 394 // is a "more appropriate" way to be handling this. However, while writing code, an empty 395 // comment did, indeed, generate a **NON-NULL** 'dct' - but sPos and ePos were both -1 396 // 397 // For now, I guess, we are going to pretend that a PERFECTLy-EMPTY JavaDoc-Comment (such 398 // as /** */), is identical to have NO-COMMENT at all. 399 400 if ((sPos == -1) && (ePos == -1)) return new Ret3<>(-1, -1, null); 401 402 while (sPos >= 2) 403 if (this.srcFileAsStr.charAt(sPos--) == '*') 404 if (this.srcFileAsStr.regionMatches(sPos - 1, "/**", 0, 3)) 405 { sPos--; break; } 406 407 /* 408 System.out.println( 409 "this.srcFileName: " + this.srcFileName + '\n' + 410 "sPos: " + sPos + ", ePos: " + ePos + '\n' + 411 "this.srcFileAsStr.substring(sPos, sPos+50): " + 412 this.srcFileAsStr.substring(sPos, sPos+50) 413 ); 414 415 try { System.out.println("str: " + this.srcFileAsStr.substring(sPos, ePos)); } 416 catch (Exception e) { System.out.println("e.getMessage(): " + e.getMessage()); } 417 */ 418 419 // Note that today is the "BIG DAY", I am trying to run my build on OCI rather than GCP. 420 // It is April 29th, 2024. I have just run into an issue (which never happened on whatever 421 // version of `javadoc` is running on GCP). Here, the first package to have shown the 422 // issue was in Apache.CLI.DefaultParser - which has an internal-static inner-class simply 423 // named "Builder". For "Builder" the ePos is returning '-1' 424 // 425 // Thinking about how "TreeUtils" works is not something I've been doing lately, but I 426 // just reviewed all of the sordid details of "com.sun.source.util.DocSourcePositions". 427 // 428 // These Methods: 429 // getStartPosition(CompilationUnitTree file, DocCommentTree comment, DocTree tree) 430 // getEndPosition(CompilationUnitTree file, DocCommentTree comment, DocTree tree) 431 // 432 // Look for the Position in the source file's "comment" for the specific item inside that 433 // comment, specifically item "tree". For static-inner classes, this new version of 434 // javadoc that is running on OCI (rather than GCP) is returning "-1". 435 // 436 // Fortunately, for me, there is an answer, but I think this solution could fail if the 437 // user included a "*/" Sub-String somwhere inside their comments. 438 // 439 // THIS HACK IS TO JUST SET ePos to sPos if "getEndPosition" failed and returned -1 440 // 441 // NOTE: I **THINK** - but I'm not 100% that I should switch this "hacky" like line to 442 // one that iterates through the elements in the List<DocTree> returned by this method 443 // from class DocCommentTree. I used to have a method that did just that, but I deleted it 444 // last year because iterating the character-data itself was much more efficient. However, 445 // apparently for static-inner classes, newer versions of javadoc are just failing. 446 // 447 // default List<? extends DocTree> getFullBody() 448 // Returns the entire body of a documentation comment, appearing before any block tags, 449 // including the first sentence. 450 451 if (ePos < 0) ePos = sPos; // "The Hack" 452 453 // And then this stuff below is the original code written in 2023... 454 int MAX = this.srcFileAsStr.length() - 2; 455 while (ePos < MAX) 456 if (this.srcFileAsStr.charAt(ePos++) == '*') 457 if (this.srcFileAsStr.regionMatches(ePos - 1, "*/", 0, 2)) 458 { ePos++; break; } 459 460 return new Ret3<>(sPos, ePos, dct); 461 } 462 463 /** 464 * Retrieve Java-Doc Comment information (if present / if provided) for any of the entities / 465 * members of a CIET / Type. 466 * 467 * @param tree The {@link Entity} for which a Java-Doc Comment is sought. 468 * 469 * @return An instance of {@link Ret3} as follows: 470 * <BR /><BR /><UL CLASS=JDUL> 471 * 472 * <LI> {@code Ret3.a: Integer} 473 * <BR />The <B STYLE='color: red'>starting</B> line-number of the Java-Doc Comment in the 474 * original Source-Code File. 475 * <BR /><BR /></LI> 476 * 477 * <LI> {@code Ret3.b: Integer} 478 * <BR />The <B STYLE='color: red'>ending</B> line-number of the Java-Doc Comment in the 479 * original Source-Code File. 480 * <BR /><BR /></LI> 481 * 482 * <LI> {@code Ret3.b: DocCommentTree} 483 * <BR />The Parse-Tree for the Java-Doc Comment. 484 * <BR /><BR /></LI> 485 * </UL> 486 * 487 * <BR /><B STYLE='color: red;'>NOTE:</B> If the {@link Entity} / Member of the {@code 'tree'} 488 * that was passed as input to this method does not have a Java-Doc Comment written, then this 489 * method will return null instead. 490 * 491 * @see #docTrees 492 * @see #compilationUnitTree 493 * @see #srcFileAsStr 494 * @see #docSourcePositions 495 * @see #lineMap 496 */ 497 public Ret3<Integer, Integer, DocCommentTree> getJavaDocInfoLine(Tree tree) 498 { 499 TreePath treePath = TreePath.getPath(this.compilationUnitTree, tree); 500 501 DocCommentTree dct = (treePath != null) 502 ? this.docTrees.getDocCommentTree(treePath) 503 : null; 504 505 if (dct == null) return new Ret3<>(-1, -1, null); 506 507 int sPos = (int) this.docSourcePositions.getStartPosition 508 (this.compilationUnitTree, dct, dct); 509 510 int ePos = (int) this.docSourcePositions.getEndPosition 511 (this.compilationUnitTree, dct, dct); 512 513 while (sPos >= 2) 514 if (this.srcFileAsStr.charAt(sPos--) == '*') 515 if (this.srcFileAsStr.regionMatches(sPos - 1, "/**", 0, 3)) 516 { sPos--; break; } 517 518 int MAX = this.srcFileAsStr.length() - 2; 519 while (ePos < MAX) 520 if (this.srcFileAsStr.charAt(ePos++) == '*') 521 if (this.srcFileAsStr.regionMatches(ePos - 1, "*/", 0, 2)) 522 { ePos++; break; } 523 524 return new Ret3<>( 525 (int) this.lineMap.getLineNumber(sPos), 526 (int) this.lineMap.getLineNumber(ePos), 527 dct 528 ); 529 } 530 531 /** 532 * Retrieves just the Java-Doc Comment Parse-Tree, and does not include Line-Number or 533 * Character-Position information. 534 * 535 * @param tree The {@link Entity} / Member of the Type for which the Java-Doc Comment is being 536 * sought. 537 * 538 * @return The Java-Doc Comment Parse-Tree 539 * 540 * @see #compilationUnitTree 541 * @see #docTrees 542 */ 543 public DocCommentTree getJavaDocCommentTree(Tree tree) 544 { 545 TreePath treePath = TreePath.getPath(this.compilationUnitTree, tree); 546 547 if (treePath == null) return null; 548 549 return this.docTrees.getDocCommentTree(treePath); 550 } 551}