1 /* 2 * Copyright (c) 1997, 2021, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package jdk.javadoc.internal.tool; 27 28 import java.io.PrintStream; 29 import java.io.PrintWriter; 30 import java.lang.ref.Reference; 31 import java.lang.ref.SoftReference; 32 import java.util.EnumSet; 33 import java.util.LinkedHashMap; 34 import java.util.Locale; 35 import java.util.Map; 36 import java.util.ResourceBundle; 37 import java.util.Set; 38 39 import javax.lang.model.element.Element; 40 import javax.lang.model.element.Modifier; 41 import javax.lang.model.element.NestingKind; 42 import javax.tools.Diagnostic; 43 import javax.tools.Diagnostic.Kind; 44 import javax.tools.FileObject; 45 import javax.tools.ForwardingFileObject; 46 import javax.tools.JavaFileObject; 47 48 import jdk.javadoc.doclet.Reporter; 49 50 import com.sun.tools.javac.tree.EndPosTable; 51 import com.sun.tools.javac.util.Context.Factory; 52 import com.sun.tools.javac.util.DiagnosticSource; 53 import com.sun.source.tree.CompilationUnitTree; 54 import com.sun.source.util.DocSourcePositions; 55 import com.sun.source.util.DocTreePath; 56 import com.sun.source.util.TreePath; 57 import com.sun.tools.javac.tree.JCTree; 58 import com.sun.tools.javac.util.Context; 59 import com.sun.tools.javac.util.JCDiagnostic; 60 import com.sun.tools.javac.util.JCDiagnostic.DiagnosticFlag; 61 import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; 62 import com.sun.tools.javac.util.JCDiagnostic.DiagnosticType; 63 import com.sun.tools.javac.util.JavacMessages; 64 import com.sun.tools.javac.util.Log; 65 66 /** 67 * Class for reporting diagnostics and other messages. 68 * 69 * The class leverages the javac support for reporting diagnostics, for stylistic consistency 70 * of diagnostic messages and to avoid code duplication. 71 * 72 * The class is a subtype of javac's Log, and is primarily an adapter between 73 * javadoc method signatures and the underlying javac methods. Within this class, 74 * the methods call down to a core {@code report} method which hands off to 75 * a similar method in the superclass ({@code Log.report}, which takes care 76 * of reporting the diagnostic (unless it has been suppressed), displaying 77 * the source line and a caret to indicate the position of the issue (if appropriate), 78 * counting errors and warnings, and so on. 79 * 80 * In general, the underlying javac layer is more powerful, whereas the javadoc methods are 81 * constrained by the public {@link jdk.javadoc.doclet.Doclet} API. 82 * 83 * In the underlying javac layer, the following abstractions are used: 84 * <ul> 85 * <li>{@code DiagnosticType} -- error, warning, note, etc. 86 * <li>{@code DiagnosticSource} -- a file object and a cache of its content 87 * <li>{@code DiagnosticPosition} -- a tuple of values (start, pos, end) for the position of a diagnostic 88 * <li>{@code DiagnosticFlag} -- additional flags related to the diagnostic 89 * </ul> 90 * 91 * The javadoc layer is defined by the methods on {@code Doclet.Reporter}, and by 92 * assorted methods defined in this class for use by the javadoc tool. 93 * The primary data types are: 94 * <ul> 95 * <li>{@code Diagnostic.Kind} -- maps to {@code DiagnosticType} and {@code Set<DiagnosticFlag>} 96 * <li>{@code Element} -- maps to {@code DiagnosticSource} and {@code DiagnosticPosition} 97 * <li>{@code DocTreePath} -- maps to {@code DiagnosticSource} and {@code DiagnosticPosition} 98 * </ul> 99 * 100 * The reporting methods in the javac layer primarily take pre-localized (key, args) pairs, 101 * while the methods in the javadoc layer, especially the {@code Reporter} interface, take 102 * localized strings. To accommodate this, "wrapper" resources are used, whose value is {@code {0}}, 103 * to pass the localized string down to javac. A side-effect is that clients using a 104 * {@code DiagnosticListener} with a {@code DocumentationTask} cannot access the original resource 105 * key for the localized message. 106 * Given the limitations of the API, it is not possible to do any better. 107 * The javac Annotation Processing API has the same problem. 108 * 109 * There is a slight disparity between javac's use of streams and javadoc's use of streams. 110 * javac reports <b>all</b> diagnostics to the "error" stream, and provides a separate 111 * "output" stream for expected output, such as command-line help or the output from options 112 * like {@code -Xprint}. javadoc API, and {@code Reporter} in particular, does not specify 113 * the use of streams, and provides no support for identifying or specifying streams. JDK-8267204. 114 * The current implementation/workaround is to write errors and warnings to the "error" 115 * stream and notes to the "output" stream. 116 * 117 * 118 * <p><b>This is NOT part of any supported API. 119 * If you write code that depends on this, you do so at your own risk. 120 * This code and its internal interfaces are subject to change or 121 * deletion without notice.</b> 122 * 123 * @see java.util.ResourceBundle 124 * @see java.text.MessageFormat 125 */ 126 public class JavadocLog extends Log implements Reporter { 127 /** The overall context for the documentation run. */ 128 private final Context context; 129 130 /** The tool environment, providing access to the tool's utility classes and tables. */ 131 private ToolEnvironment toolEnv; 132 133 /** The utility class to access the positions of items in doc comments. */ 134 private DocSourcePositions sourcePositions; 135 136 /** 137 * A memory-sensitive cache of recently used {@code DiagnosticSource} objects. 138 */ 139 private final LinkedHashMap<JavaFileObject, SoftReference<DiagnosticSource>> diagSourceCache; 140 141 /** Get the current javadoc log, which is also the compiler log. */ instance0(Context context)142 public static JavadocLog instance0(Context context) { 143 Log instance = context.get(logKey); 144 if (!(instance instanceof JavadocLog l)) 145 throw new InternalError("no JavadocLog instance!"); 146 return l; 147 } 148 preRegister(Context context, final String programName)149 public static void preRegister(Context context, 150 final String programName) { 151 context.put(logKey, (Factory<Log>)c -> new JavadocLog(c, programName)); 152 } 153 preRegister(Context context, final String programName, final PrintWriter outWriter, final PrintWriter errWriter)154 public static void preRegister(Context context, final String programName, 155 final PrintWriter outWriter, final PrintWriter errWriter) { 156 context.put(logKey, (Factory<Log>)c -> new JavadocLog(c, programName, outWriter, errWriter)); 157 } 158 159 final String programName; 160 161 private Locale locale; 162 private final JavacMessages messages; 163 private final JCDiagnostic.Factory javadocDiags; 164 createPrintWriter(PrintStream ps, boolean autoflush)165 private static PrintWriter createPrintWriter(PrintStream ps, boolean autoflush) { 166 return new PrintWriter(ps, autoflush) { 167 // avoid closing system streams 168 @Override 169 public void close() { 170 super.flush(); 171 } 172 }; 173 } 174 175 /** 176 * Constructor 177 * @param programName Name of the program (for error messages). 178 */ 179 public JavadocLog(Context context, String programName) { 180 // use the current values of System.out, System.err, in case they have been redirected 181 this(context, programName, 182 createPrintWriter(System.out, false), 183 createPrintWriter(System.err, true)); 184 } 185 186 /** 187 * Constructor 188 * @param programName Name of the program (for error messages). 189 * @param outWriter Stream for notices etc. 190 * @param errWriter Stream for errors and warnings 191 */ 192 public JavadocLog(Context context, String programName, PrintWriter outWriter, PrintWriter errWriter) { 193 super(context, outWriter, errWriter); 194 messages = JavacMessages.instance(context); 195 messages.add(locale -> ResourceBundle.getBundle("jdk.javadoc.internal.tool.resources.javadoc", 196 locale)); 197 javadocDiags = new JCDiagnostic.Factory(messages, "javadoc"); 198 this.programName = programName; 199 this.context = context; 200 locale = Locale.getDefault(); 201 202 diagSourceCache = new LinkedHashMap<>() { 203 private static final int MAX_ENTRIES = 5; 204 205 @Override 206 protected boolean removeEldestEntry(Map.Entry<JavaFileObject, SoftReference<DiagnosticSource>> eldest) { 207 return size() > MAX_ENTRIES; 208 } 209 }; 210 } 211 212 @Override // Reporter 213 public PrintWriter getStandardWriter() { 214 return getWriter(Log.WriterKind.STDOUT); 215 } 216 217 @Override // Reporter 218 public PrintWriter getDiagnosticWriter() { 219 return getWriter(Log.WriterKind.STDERR); 220 } 221 222 public void setLocale(Locale locale) { 223 this.locale = locale; 224 } 225 226 /** 227 * Returns the localized string from the tool's resource bundles. 228 * 229 * @param key the resource key 230 * @param args arguments for the resource 231 */ 232 String getText(String key, Object... args) { 233 return messages.getLocalizedString(locale, key, args); 234 } 235 236 @Override // Reporter 237 public void print(Kind kind, String message) { 238 report(kind, null, null, message); 239 } 240 241 @Override // Reporter 242 public void print(Diagnostic.Kind kind, DocTreePath path, String message) { 243 DiagnosticType dt = getDiagnosticType(kind); 244 Set<DiagnosticFlag> flags = getDiagnosticFlags(kind); 245 DiagnosticSource ds = getDiagnosticSource(path); 246 DiagnosticPosition dp = getDiagnosticPosition(path); 247 report(dt, flags, ds, dp, message); 248 } 249 250 @Override // Reporter 251 public void print(Kind kind, Element element, String message) { 252 DiagnosticType dt = getDiagnosticType(kind); 253 Set<DiagnosticFlag> flags = getDiagnosticFlags(kind); 254 DiagnosticSource ds = getDiagnosticSource(element); 255 DiagnosticPosition dp = getDiagnosticPosition(element); 256 report(dt, flags, ds, dp, message); 257 } 258 259 @Override // Reporter 260 public void print(Kind kind, FileObject file, int start, int pos, int end, String message) throws IllegalArgumentException { 261 DiagnosticType dt = getDiagnosticType(kind); 262 Set<DiagnosticFlag> flags = getDiagnosticFlags(kind); 263 // Although not required to do so, it is the case that any file object returned from the 264 // javac impl of JavaFileManager will return an object that implements JavaFileObject. 265 // See PathFileObject, which provides the primary impls of (Java)FileObject. 266 JavaFileObject fo = file instanceof JavaFileObject _fo ? _fo : new WrappingJavaFileObject(file); 267 DiagnosticSource ds = new DiagnosticSource(fo, this); 268 DiagnosticPosition dp = createDiagnosticPosition(null, start, pos, end); 269 report(dt, flags, ds, dp, message); 270 } 271 272 private class WrappingJavaFileObject 273 extends ForwardingFileObject<FileObject> implements JavaFileObject { 274 275 WrappingJavaFileObject(FileObject fo) { 276 super(fo); 277 assert !(fo instanceof JavaFileObject); 278 } 279 280 @Override 281 public Kind getKind() { 282 String name = fileObject.getName(); 283 return name.endsWith(Kind.HTML.extension) 284 ? JavaFileObject.Kind.HTML 285 : JavaFileObject.Kind.OTHER; 286 } 287 288 @Override 289 public boolean isNameCompatible(String simpleName, Kind kind) { 290 return false; 291 } 292 293 @Override 294 public NestingKind getNestingKind() { 295 return null; 296 } 297 298 @Override 299 public Modifier getAccessLevel() { 300 return null; 301 } 302 } 303 304 /** 305 * Prints an error message. 306 * 307 * @param message the message 308 */ 309 public void printError(String message) { 310 report(DiagnosticType.ERROR,null, null, message); 311 } 312 313 /** 314 * Prints an error message for a given documentation tree node. 315 * 316 * @param path the path for the documentation tree node 317 * @param message the message 318 */ 319 public void printError(DocTreePath path, String message) { 320 DiagnosticSource ds = getDiagnosticSource(path); 321 DiagnosticPosition dp = getDiagnosticPosition(path); 322 report(DiagnosticType.ERROR, EnumSet.noneOf(DiagnosticFlag.class), ds, dp, message); 323 } 324 325 /** 326 * Prints an error message for a given element. 327 * 328 * @param element the element 329 * @param message the message 330 */ 331 public void printError(Element element, String message) { 332 DiagnosticSource ds = getDiagnosticSource(element); 333 DiagnosticPosition dp = getDiagnosticPosition(element); 334 report(DiagnosticType.ERROR, EnumSet.noneOf(DiagnosticFlag.class), ds, dp, message); 335 } 336 337 /** 338 * Prints an error message. 339 * 340 * @param key the resource key for the message 341 * @param args the arguments for the message 342 */ 343 public void printErrorUsingKey(String key, Object... args) { 344 printError(getText(key, args)); 345 } 346 347 /** 348 * Prints a warning message. 349 * 350 * @param message the message 351 */ 352 public void printWarning(String message) { 353 report(DiagnosticType.WARNING, null, null, message); 354 } 355 356 /** 357 * Prints a warning message for a given documentation tree node. 358 * 359 * @param path the path for the documentation tree node 360 * @param message the message 361 */ 362 public void printWarning(DocTreePath path, String message) { 363 DiagnosticSource ds = getDiagnosticSource(path); 364 DiagnosticPosition dp = getDiagnosticPosition(path); 365 report(DiagnosticType.WARNING, EnumSet.noneOf(DiagnosticFlag.class), ds, dp, message); 366 } 367 368 /** 369 * Prints a warning message for a given element. 370 * 371 * @param element the element 372 * @param message the message 373 */ 374 public void printWarning(Element element, String message) { 375 DiagnosticSource ds = getDiagnosticSource(element); 376 DiagnosticPosition dp = getDiagnosticPosition(element); 377 report(DiagnosticType.WARNING, EnumSet.noneOf(DiagnosticFlag.class), ds, dp, message); 378 } 379 380 /** 381 * Prints a warning message. 382 * 383 * @param key the resource key for the message 384 * @param args the arguments for the message 385 */ 386 public void printWarningUsingKey(String key, Object... args) { 387 printWarning(getText(key, args)); 388 } 389 390 /** 391 * Prints a warning message for an element. 392 * 393 * @param element the element 394 * @param key the resource key for the message 395 * @param args the arguments for the message 396 */ 397 public void printWarningUsingKey(Element element, String key, Object... args) { 398 printWarning(element, getText(key, args)); 399 } 400 401 /** 402 * Prints a "notice" message to the standard writer. 403 * 404 * @param key the resource key for the message 405 * @param args the arguments for the message 406 */ 407 public void noticeUsingKey(String key, Object... args) { 408 printRawLines(getStandardWriter(), getText(key, args)); 409 } 410 411 /** 412 * Prints a "notice" message to the standard writer. 413 * 414 * @param message the message 415 */ 416 public void notice(String message) { 417 printRawLines(getStandardWriter(), message); 418 } 419 420 /** 421 * Returns true if errors have been recorded. 422 */ 423 public boolean hasErrors() { 424 return nerrors != 0; 425 } 426 427 /** 428 * Returns true if warnings have been recorded. 429 */ 430 public boolean hasWarnings() { 431 return nwarnings != 0; 432 } 433 434 /** 435 * Prints the error and warning counts, if any, to the diagnostic writer. 436 */ 437 public void printErrorWarningCounts() { 438 printCount(nerrors, "main.error", "main.errors"); 439 printCount(nwarnings, "main.warning", "main.warnings"); 440 } 441 442 private void printCount(int count, String singleKey, String pluralKey) { 443 if (count > 0) { 444 String message = getText(count > 1 ? pluralKey : singleKey, count); 445 if (diagListener != null) { 446 report(DiagnosticType.NOTE, null, null, message); 447 } else { 448 printRawLines(getDiagnosticWriter(), message); 449 } 450 } 451 } 452 453 /** 454 * Reports a diagnostic message. 455 * 456 * @param kind the kind of diagnostic 457 * @param ds the diagnostic source 458 * @param dp the diagnostic position 459 * @param message the message 460 */ 461 private void report(Diagnostic.Kind kind, DiagnosticSource ds, DiagnosticPosition dp, String message) { 462 report(getDiagnosticType(kind), getDiagnosticFlags(kind), ds, dp, message); 463 } 464 465 /** 466 * Reports a diagnostic message. 467 * 468 * @param dt the diagnostic type 469 * @param ds the diagnostic source 470 * @param dp the diagnostic position 471 * @param message the message 472 */ 473 private void report(DiagnosticType dt, DiagnosticSource ds, DiagnosticPosition dp, String message) { 474 report(dt, EnumSet.noneOf(DiagnosticFlag.class), ds, dp, message); 475 } 476 477 /** 478 * Reports a diagnostic message, with diagnostic flags. 479 * For javadoc, the only flag that is used is {@code MANDATORY_WARNING}, and only 480 * because in principle the public API supports it via {@code Kind.MANDATORY_WARNING}. 481 * javadoc itself does generate mandatory warnings. 482 * 483 * This is the primary low-level wrapper around the underlying {@code Log.report}. 484 * Because we already have a localized message, we use wrapper resources (just {@code {0}}) 485 * to wrap the string. The current behavior is one wrapper per diagnostic type. 486 * We could improve this by subtyping {@code DiagnosticInfo} to modify the resource key used. 487 * 488 * {@code Log} reports all diagnostics to the corresponding writer, which defaults 489 * to the "error" stream, when using the two-stream constructor. That doesn't work 490 * for javadoc, which has historically written notes to the "output" stream, because 491 * the public API used by doclets does not provide for more detailed control. 492 * Therefore, for now, javadoc continues to use the (deprecated) three-stream 493 * constructor, with the {@code NOTE} stream set to the "output" stream. 494 * 495 * {@code Log} reports all notes with a "Note:" prefix. That's not good for the 496 * standard doclet, which uses notes to report the various "progress" messages, 497 * such as "Generating class ...". They can be written directly to the diagnostic 498 * writer, but that bypasses low-level checks about whether to suppress notes, 499 * and bypasses the diagnostic listener for API clients. 500 * Overall, it's an over-constrained problem with no obvious good solution. 501 * 502 * Note: there is an intentional difference in behavior between the diagnostic source 503 * being set to {@code null} (no source intended) and {@code NO_SOURCE} (no source available). 504 * 505 * @param dt the diagnostic type 506 * @param ds the diagnostic source 507 * @param dp the diagnostic position 508 * @param message the message 509 */ 510 private void report(DiagnosticType dt, Set<DiagnosticFlag> flags, DiagnosticSource ds, DiagnosticPosition dp, String message) { 511 report(javadocDiags.create(dt, null, flags, ds, dp, "message", message)); 512 } 513 514 /** 515 * Returns a diagnostic position for a documentation tree node. 516 * 517 * @param path the path for the documentation tree node 518 * @return the diagnostic position 519 */ 520 private DiagnosticPosition getDiagnosticPosition(DocTreePath path) { 521 DocSourcePositions posns = getSourcePositions(); 522 CompilationUnitTree compUnit = path.getTreePath().getCompilationUnit(); 523 int start = (int) posns.getStartPosition(compUnit, path.getDocComment(), path.getLeaf()); 524 int end = (int) posns.getEndPosition(compUnit, path.getDocComment(), path.getLeaf()); 525 return createDiagnosticPosition(null, start, start, end); 526 } 527 528 /** 529 * Returns a diagnostic position for an element, or {@code null} if the source 530 * file is not available. 531 * 532 * @param element the element 533 * @return the diagnostic position 534 */ 535 private DiagnosticPosition getDiagnosticPosition(Element element) { 536 ToolEnvironment toolEnv = getToolEnv(); 537 DocSourcePositions posns = getSourcePositions(); 538 TreePath tp = toolEnv.elementToTreePath.get(element); 539 if (tp == null) { 540 return null; 541 } 542 CompilationUnitTree compUnit = tp.getCompilationUnit(); 543 JCTree tree = (JCTree) tp.getLeaf(); 544 int start = (int) posns.getStartPosition(compUnit, tree); 545 int pos = tree.getPreferredPosition(); 546 int end = (int) posns.getEndPosition(compUnit, tree); 547 return createDiagnosticPosition(tree, start, pos, end); 548 } 549 550 /** 551 * Creates a diagnostic position. 552 * 553 * @param tree the tree node, or null if no tree is applicable 554 * @param start the start position 555 * @param pos the "preferred" position: this is used to position the caret in messages 556 * @param end the end position 557 * @return the diagnostic position 558 */ 559 private DiagnosticPosition createDiagnosticPosition(JCTree tree, int start, int pos, int end) { 560 return new DiagnosticPosition() { 561 @Override 562 public JCTree getTree() { 563 return tree; 564 } 565 566 @Override 567 public int getStartPosition() { 568 return start; 569 } 570 571 @Override 572 public int getPreferredPosition() { 573 return pos; 574 } 575 576 @Override 577 public int getEndPosition(EndPosTable endPosTable) { 578 return end; 579 } 580 }; 581 } 582 583 /** 584 * Returns the diagnostic type for a diagnostic kind. 585 * 586 * @param kind the diagnostic kind 587 * @return the diagnostic type 588 */ 589 private DiagnosticType getDiagnosticType(Diagnostic.Kind kind) { 590 return switch (kind) { 591 case ERROR -> DiagnosticType.ERROR; 592 case WARNING, MANDATORY_WARNING -> DiagnosticType.WARNING; 593 case NOTE -> DiagnosticType.NOTE; 594 case OTHER -> DiagnosticType.FRAGMENT; 595 }; 596 } 597 598 /** 599 * Returns the diagnostic flags for a diagnostic kind. 600 * A diagnostic kind of {@code MANDATORY_WARNING} requires the {@code MANDATORY} flag. 601 * 602 * @param kind the diagnostic kind 603 * @return the flags 604 */ 605 private Set<DiagnosticFlag> getDiagnosticFlags(Diagnostic.Kind kind) { 606 return kind == Kind.MANDATORY_WARNING 607 ? EnumSet.of(DiagnosticFlag.MANDATORY) 608 : EnumSet.noneOf(DiagnosticFlag.class); 609 } 610 611 /** 612 * Returns the diagnostic source for an documentation tree node. 613 * 614 * @param path the path for the documentation tree node 615 * @return the diagnostic source 616 */ 617 private DiagnosticSource getDiagnosticSource(DocTreePath path) { 618 return getDiagnosticSource(path.getTreePath().getCompilationUnit().getSourceFile()); 619 } 620 621 /** 622 * Returns the diagnostic source for an element, or {@code NO_SOURCE} if the 623 * source file is not known (for example, if the element was read from a class file). 624 * 625 * @param element the element 626 * @return the diagnostic source 627 */ 628 private DiagnosticSource getDiagnosticSource(Element element) { 629 TreePath tp = getToolEnv().elementToTreePath.get(element); 630 return tp == null ? DiagnosticSource.NO_SOURCE 631 : getDiagnosticSource(tp.getCompilationUnit().getSourceFile()); 632 } 633 634 /** 635 * Returns the diagnostic source for a file object. 636 * 637 * {@code DiagnosticSource} objects are moderately expensive because they maintain 638 * an internal copy of the content, to provide the line map. 639 * Therefore, we keep a small memory-sensitive cache of recently used objects. 640 * 641 * @param fo the file object 642 * @return the diagnostic source 643 */ 644 private DiagnosticSource getDiagnosticSource(JavaFileObject fo) { 645 Reference<DiagnosticSource> ref = diagSourceCache.get(fo); 646 DiagnosticSource ds = ref == null ? null : ref.get(); 647 if (ds == null) { 648 ds = new DiagnosticSource(fo, this); 649 diagSourceCache.put(fo, new SoftReference<>(ds)); 650 } 651 return ds; 652 } 653 654 /** 655 * Returns the object for computing source positions. 656 * 657 * The value is determined lazily because the tool environment is computed lazily. 658 * 659 * @return the object for computing source positions 660 */ 661 private DocSourcePositions getSourcePositions() { 662 if (sourcePositions == null) { 663 sourcePositions = getToolEnv().docTrees.getSourcePositions(); 664 } 665 return sourcePositions; 666 } 667 668 /** 669 * Returns the tool environment. 670 * 671 * The value is determined lazily, because creating it eagerly disrupts 672 * the overall initialization of objects in the context. 673 * 674 * @return the tool environment 675 */ 676 private ToolEnvironment getToolEnv() { 677 if (toolEnv == null) { 678 toolEnv = ToolEnvironment.instance(context); 679 } 680 return toolEnv; 681 } 682 } 683