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