Path: blob/master/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/Messager.java
41161 views
/*1* Copyright (c) 1997, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package jdk.javadoc.internal.tool;2627import java.io.PrintStream;28import java.io.PrintWriter;29import java.lang.ref.Reference;30import java.lang.ref.SoftReference;31import java.util.EnumSet;32import java.util.LinkedHashMap;33import java.util.Locale;34import java.util.Map;35import java.util.ResourceBundle;36import java.util.Set;3738import javax.lang.model.element.Element;39import javax.lang.model.element.Modifier;40import javax.lang.model.element.NestingKind;41import javax.tools.Diagnostic;42import javax.tools.Diagnostic.Kind;43import javax.tools.FileObject;44import javax.tools.ForwardingFileObject;45import javax.tools.JavaFileObject;4647import jdk.javadoc.doclet.Reporter;4849import com.sun.tools.javac.tree.EndPosTable;50import com.sun.tools.javac.util.Context.Factory;51import com.sun.tools.javac.util.DiagnosticSource;52import com.sun.source.tree.CompilationUnitTree;53import com.sun.source.util.DocSourcePositions;54import com.sun.source.util.DocTreePath;55import com.sun.source.util.TreePath;56import com.sun.tools.javac.tree.JCTree;57import com.sun.tools.javac.util.Context;58import com.sun.tools.javac.util.JCDiagnostic;59import com.sun.tools.javac.util.JCDiagnostic.DiagnosticFlag;60import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;61import com.sun.tools.javac.util.JCDiagnostic.DiagnosticType;62import com.sun.tools.javac.util.JavacMessages;63import com.sun.tools.javac.util.Log;6465/**66* Class for reporting diagnostics and other messages.67*68* The class leverages the javac support for reporting diagnostics, for stylistic consistency69* of diagnostic messages and to avoid code duplication.70*71* The class is a subtype of javac's Log, and is primarily an adapter between72* javadoc method signatures and the underlying javac methods. Within this class,73* the methods call down to a core {@code report} method which hands off to74* a similar method in the superclass ({@code Log.report}, which takes care75* of reporting the diagnostic (unless it has been suppressed), displaying76* the source line and a caret to indicate the position of the issue (if appropriate),77* counting errors and warnings, and so on.78*79* In general, the underlying javac layer is more powerful, whereas the javadoc methods are80* constrained by the public {@link jdk.javadoc.doclet.Doclet} API.81*82* In the underlying javac layer, the following abstractions are used:83* <ul>84* <li>{@code DiagnosticType} -- error, warning, note, etc.85* <li>{@code DiagnosticSource} -- a file object and a cache of its content86* <li>{@code DiagnosticPosition} -- a tuple of values (start, pos, end) for the position of a diagnostic87* <li>{@code DiagnosticFlag} -- additional flags related to the diagnostic88* </ul>89*90* The javadoc layer is defined by the methods on {@code Doclet.Reporter}, and by91* assorted methods defined in this class for use by the javadoc tool.92* The primary data types are:93* <ul>94* <li>{@code Diagnostic.Kind} -- maps to {@code DiagnosticType} and {@code Set<DiagnosticFlag>}95* <li>{@code Element} -- maps to {@code DiagnosticSource} and {@code DiagnosticPosition}96* <li>{@code DocTreePath} -- maps to {@code DiagnosticSource} and {@code DiagnosticPosition}97* </ul>98*99* The reporting methods in the javac layer primarily take pre-localized (key, args) pairs,100* while the methods in the javadoc layer, especially the {@code Reporter} interface, take101* localized strings. To accommodate this, "wrapper" resources are used, whose value is {@code {0}},102* to pass the localized string down to javac. A side-effect is that clients using a103* {@code DiagnosticListener} with a {@code DocumentationTask} cannot access the original resource104* key for the localized message.105* Given the limitations of the API, it is not possible to do any better.106* The javac Annotation Processing API has the same problem.107*108* There is a slight disparity between javac's use of streams and javadoc's use of streams.109* javac reports <b>all</b> diagnostics to the "error" stream, and provides a separate110* "output" stream for expected output, such as command-line help or the output from options111* like {@code -Xprint}. javadoc API, and {@code Reporter} in particular, does not specify112* the use of streams, and provides no support for identifying or specifying streams. JDK-8267204.113* The current implementation/workaround is to write errors and warnings to the "error"114* stream and notes to the "output" stream.115*116*117* <p><b>This is NOT part of any supported API.118* If you write code that depends on this, you do so at your own risk.119* This code and its internal interfaces are subject to change or120* deletion without notice.</b>121*122* @see java.util.ResourceBundle123* @see java.text.MessageFormat124*/125public class Messager extends Log implements Reporter {126/** The overall context for the documentation run. */127private final Context context;128129/** The tool environment, providing access to the tool's utility classes and tables. */130private ToolEnvironment toolEnv;131132/** The utility class to access the positions of items in doc comments. */133private DocSourcePositions sourcePositions;134135/**136* A memory-sensitive cache of recently used {@code DiagnosticSource} objects.137*/138private final LinkedHashMap<JavaFileObject, SoftReference<DiagnosticSource>> diagSourceCache;139140/** Get the current messager, which is also the compiler log. */141public static Messager instance0(Context context) {142Log instance = context.get(logKey);143if (!(instance instanceof Messager m))144throw new InternalError("no messager instance!");145return m;146}147148public static void preRegister(Context context,149final String programName) {150context.put(logKey, (Factory<Log>)c -> new Messager(c, programName));151}152153public static void preRegister(Context context, final String programName,154final PrintWriter outWriter, final PrintWriter errWriter) {155context.put(logKey, (Factory<Log>)c -> new Messager(c, programName, outWriter, errWriter));156}157158final String programName;159160private Locale locale;161private final JavacMessages messages;162private final JCDiagnostic.Factory javadocDiags;163164private static PrintWriter createPrintWriter(PrintStream ps, boolean autoflush) {165return new PrintWriter(ps, autoflush) {166// avoid closing system streams167@Override168public void close() {169super.flush();170}171};172}173174/**175* Constructor176* @param programName Name of the program (for error messages).177*/178public Messager(Context context, String programName) {179// use the current values of System.out, System.err, in case they have been redirected180this(context, programName,181createPrintWriter(System.out, false),182createPrintWriter(System.err, true));183}184185/**186* Constructor187* @param programName Name of the program (for error messages).188* @param outWriter Stream for notices etc.189* @param errWriter Stream for errors and warnings190*/191public Messager(Context context, String programName, PrintWriter outWriter, PrintWriter errWriter) {192super(context, outWriter, errWriter);193messages = JavacMessages.instance(context);194messages.add(locale -> ResourceBundle.getBundle("jdk.javadoc.internal.tool.resources.javadoc",195locale));196javadocDiags = new JCDiagnostic.Factory(messages, "javadoc");197this.programName = programName;198this.context = context;199locale = Locale.getDefault();200201diagSourceCache = new LinkedHashMap<>() {202private static final int MAX_ENTRIES = 5;203204@Override205protected boolean removeEldestEntry(Map.Entry<JavaFileObject, SoftReference<DiagnosticSource>> eldest) {206return size() > MAX_ENTRIES;207}208};209}210211@Override // Reporter212public PrintWriter getStandardWriter() {213return getWriter(Log.WriterKind.STDOUT);214}215216@Override // Reporter217public PrintWriter getDiagnosticWriter() {218return getWriter(Log.WriterKind.STDERR);219}220221public void setLocale(Locale locale) {222this.locale = locale;223}224225/**226* Returns the localized string from the tool's resource bundles.227*228* @param key the resource key229* @param args arguments for the resource230*/231String getText(String key, Object... args) {232return messages.getLocalizedString(locale, key, args);233}234235@Override // Reporter236public void print(Kind kind, String message) {237report(kind, null, null, message);238}239240@Override // Reporter241public void print(Diagnostic.Kind kind, DocTreePath path, String message) {242DiagnosticType dt = getDiagnosticType(kind);243Set<DiagnosticFlag> flags = getDiagnosticFlags(kind);244DiagnosticSource ds = getDiagnosticSource(path);245DiagnosticPosition dp = getDiagnosticPosition(path);246report(dt, flags, ds, dp, message);247}248249@Override // Reporter250public void print(Kind kind, Element element, String message) {251DiagnosticType dt = getDiagnosticType(kind);252Set<DiagnosticFlag> flags = getDiagnosticFlags(kind);253DiagnosticSource ds = getDiagnosticSource(element);254DiagnosticPosition dp = getDiagnosticPosition(element);255report(dt, flags, ds, dp, message);256}257258@Override // Reporter259public void print(Kind kind, FileObject file, int start, int pos, int end, String message) throws IllegalArgumentException {260DiagnosticType dt = getDiagnosticType(kind);261Set<DiagnosticFlag> flags = getDiagnosticFlags(kind);262// Although not required to do so, it is the case that any file object returned from the263// javac impl of JavaFileManager will return an object that implements JavaFileObject.264// See PathFileObject, which provides the primary impls of (Java)FileObject.265JavaFileObject fo = file instanceof JavaFileObject _fo ? _fo : new WrappingJavaFileObject(file);266DiagnosticSource ds = new DiagnosticSource(fo, this);267DiagnosticPosition dp = createDiagnosticPosition(null, start, pos, end);268report(dt, flags, ds, dp, message);269}270271private class WrappingJavaFileObject272extends ForwardingFileObject<FileObject> implements JavaFileObject {273274WrappingJavaFileObject(FileObject fo) {275super(fo);276assert !(fo instanceof JavaFileObject);277}278279@Override280public Kind getKind() {281String name = fileObject.getName();282return name.endsWith(Kind.HTML.extension)283? JavaFileObject.Kind.HTML284: JavaFileObject.Kind.OTHER;285}286287@Override288public boolean isNameCompatible(String simpleName, Kind kind) {289return false;290}291292@Override293public NestingKind getNestingKind() {294return null;295}296297@Override298public Modifier getAccessLevel() {299return null;300}301}302303/**304* Prints an error message.305*306* @param message the message307*/308public void printError(String message) {309report(DiagnosticType.ERROR,null, null, message);310}311312/**313* Prints an error message for a given documentation tree node.314*315* @param path the path for the documentation tree node316* @param message the message317*/318public void printError(DocTreePath path, String message) {319DiagnosticSource ds = getDiagnosticSource(path);320DiagnosticPosition dp = getDiagnosticPosition(path);321report(DiagnosticType.ERROR, EnumSet.noneOf(DiagnosticFlag.class), ds, dp, message);322}323324/**325* Prints an error message for a given element.326*327* @param element the element328* @param message the message329*/330public void printError(Element element, String message) {331DiagnosticSource ds = getDiagnosticSource(element);332DiagnosticPosition dp = getDiagnosticPosition(element);333report(DiagnosticType.ERROR, EnumSet.noneOf(DiagnosticFlag.class), ds, dp, message);334}335336/**337* Prints an error message.338*339* @param key the resource key for the message340* @param args the arguments for the message341*/342public void printErrorUsingKey(String key, Object... args) {343printError(getText(key, args));344}345346/**347* Prints a warning message.348*349* @param message the message350*/351public void printWarning(String message) {352report(DiagnosticType.WARNING, null, null, message);353}354355/**356* Prints a warning message for a given documentation tree node.357*358* @param path the path for the documentation tree node359* @param message the message360*/361public void printWarning(DocTreePath path, String message) {362DiagnosticSource ds = getDiagnosticSource(path);363DiagnosticPosition dp = getDiagnosticPosition(path);364report(DiagnosticType.WARNING, EnumSet.noneOf(DiagnosticFlag.class), ds, dp, message);365}366367/**368* Prints a warning message for a given element.369*370* @param element the element371* @param message the message372*/373public void printWarning(Element element, String message) {374DiagnosticSource ds = getDiagnosticSource(element);375DiagnosticPosition dp = getDiagnosticPosition(element);376report(DiagnosticType.WARNING, EnumSet.noneOf(DiagnosticFlag.class), ds, dp, message);377}378379/**380* Prints a warning message.381*382* @param key the resource key for the message383* @param args the arguments for the message384*/385public void printWarningUsingKey(String key, Object... args) {386printWarning(getText(key, args));387}388389/**390* Prints a warning message for an element.391*392* @param element the element393* @param key the resource key for the message394* @param args the arguments for the message395*/396public void printWarningUsingKey(Element element, String key, Object... args) {397printWarning(element, getText(key, args));398}399400/**401* Prints a "notice" message to the standard writer.402*403* @param key the resource key for the message404* @param args the arguments for the message405*/406public void noticeUsingKey(String key, Object... args) {407printRawLines(getStandardWriter(), getText(key, args));408}409410/**411* Prints a "notice" message to the standard writer.412*413* @param message the message414*/415public void notice(String message) {416printRawLines(getStandardWriter(), message);417}418419/**420* Returns true if errors have been recorded.421*/422public boolean hasErrors() {423return nerrors != 0;424}425426/**427* Returns true if warnings have been recorded.428*/429public boolean hasWarnings() {430return nwarnings != 0;431}432433/**434* Prints the error and warning counts, if any, to the diagnostic writer.435*/436public void printErrorWarningCounts() {437printCount(nerrors, "main.error", "main.errors");438printCount(nwarnings, "main.warning", "main.warnings");439}440441private void printCount(int count, String singleKey, String pluralKey) {442if (count > 0) {443String message = getText(count > 1 ? pluralKey : singleKey, count);444if (diagListener != null) {445report(DiagnosticType.NOTE, null, null, message);446} else {447printRawLines(getDiagnosticWriter(), message);448}449}450}451452/**453* Reports a diagnostic message.454*455* @param kind the kind of diagnostic456* @param ds the diagnostic source457* @param dp the diagnostic position458* @param message the message459*/460private void report(Diagnostic.Kind kind, DiagnosticSource ds, DiagnosticPosition dp, String message) {461report(getDiagnosticType(kind), getDiagnosticFlags(kind), ds, dp, message);462}463464/**465* Reports a diagnostic message.466*467* @param dt the diagnostic type468* @param ds the diagnostic source469* @param dp the diagnostic position470* @param message the message471*/472private void report(DiagnosticType dt, DiagnosticSource ds, DiagnosticPosition dp, String message) {473report(dt, EnumSet.noneOf(DiagnosticFlag.class), ds, dp, message);474}475476/**477* Reports a diagnostic message, with diagnostic flags.478* For javadoc, the only flag that is used is {@code MANDATORY_WARNING}, and only479* because in principle the public API supports it via {@code Kind.MANDATORY_WARNING}.480* javadoc itself does generate mandatory warnings.481*482* This is the primary low-level wrapper around the underlying {@code Log.report}.483* Because we already have a localized message, we use wrapper resources (just {@code {0}})484* to wrap the string. The current behavior is one wrapper per diagnostic type.485* We could improve this by subtyping {@code DiagnosticInfo} to modify the resource key used.486*487* {@code Log} reports all diagnostics to the corresponding writer, which defaults488* to the "error" stream, when using the two-stream constructor. That doesn't work489* for javadoc, which has historically written notes to the "output" stream, because490* the public API used by doclets does not provide for more detailed control.491* Therefore, for now, javadoc continues to use the (deprecated) three-stream492* constructor, with the {@code NOTE} stream set to the "output" stream.493*494* {@code Log} reports all notes with a "Note:" prefix. That's not good for the495* standard doclet, which uses notes to report the various "progress" messages,496* such as "Generating class ...". They can be written directly to the diagnostic497* writer, but that bypasses low-level checks about whether to suppress notes,498* and bypasses the diagnostic listener for API clients.499* Overall, it's an over-constrained problem with no obvious good solution.500*501* Note: there is an intentional difference in behavior between the diagnostic source502* being set to {@code null} (no source intended) and {@code NO_SOURCE} (no source available).503*504* @param dt the diagnostic type505* @param ds the diagnostic source506* @param dp the diagnostic position507* @param message the message508*/509private void report(DiagnosticType dt, Set<DiagnosticFlag> flags, DiagnosticSource ds, DiagnosticPosition dp, String message) {510report(javadocDiags.create(dt, null, flags, ds, dp, "message", message));511}512513/**514* Returns a diagnostic position for a documentation tree node.515*516* @param path the path for the documentation tree node517* @return the diagnostic position518*/519private DiagnosticPosition getDiagnosticPosition(DocTreePath path) {520DocSourcePositions posns = getSourcePositions();521CompilationUnitTree compUnit = path.getTreePath().getCompilationUnit();522int start = (int) posns.getStartPosition(compUnit, path.getDocComment(), path.getLeaf());523int end = (int) posns.getEndPosition(compUnit, path.getDocComment(), path.getLeaf());524return createDiagnosticPosition(null, start, start, end);525}526527/**528* Returns a diagnostic position for an element, or {@code null} if the source529* file is not available.530*531* @param element the element532* @return the diagnostic position533*/534private DiagnosticPosition getDiagnosticPosition(Element element) {535ToolEnvironment toolEnv = getToolEnv();536DocSourcePositions posns = getSourcePositions();537TreePath tp = toolEnv.elementToTreePath.get(element);538if (tp == null) {539return null;540}541CompilationUnitTree compUnit = tp.getCompilationUnit();542JCTree tree = (JCTree) tp.getLeaf();543int start = (int) posns.getStartPosition(compUnit, tree);544int pos = tree.getPreferredPosition();545int end = (int) posns.getEndPosition(compUnit, tree);546return createDiagnosticPosition(tree, start, pos, end);547}548549/**550* Creates a diagnostic position.551*552* @param tree the tree node, or null if no tree is applicable553* @param start the start position554* @param pos the "preferred" position: this is used to position the caret in messages555* @param end the end position556* @return the diagnostic position557*/558private DiagnosticPosition createDiagnosticPosition(JCTree tree, int start, int pos, int end) {559return new DiagnosticPosition() {560@Override561public JCTree getTree() {562return tree;563}564565@Override566public int getStartPosition() {567return start;568}569570@Override571public int getPreferredPosition() {572return pos;573}574575@Override576public int getEndPosition(EndPosTable endPosTable) {577return end;578}579};580}581582/**583* Returns the diagnostic type for a diagnostic kind.584*585* @param kind the diagnostic kind586* @return the diagnostic type587*/588private DiagnosticType getDiagnosticType(Diagnostic.Kind kind) {589return switch (kind) {590case ERROR -> DiagnosticType.ERROR;591case WARNING, MANDATORY_WARNING -> DiagnosticType.WARNING;592case NOTE -> DiagnosticType.NOTE;593case OTHER -> DiagnosticType.FRAGMENT;594};595}596597/**598* Returns the diagnostic flags for a diagnostic kind.599* A diagnostic kind of {@code MANDATORY_WARNING} requires the {@code MANDATORY} flag.600*601* @param kind the diagnostic kind602* @return the flags603*/604private Set<DiagnosticFlag> getDiagnosticFlags(Diagnostic.Kind kind) {605return kind == Kind.MANDATORY_WARNING606? EnumSet.of(DiagnosticFlag.MANDATORY)607: EnumSet.noneOf(DiagnosticFlag.class);608}609610/**611* Returns the diagnostic source for an documentation tree node.612*613* @param path the path for the documentation tree node614* @return the diagnostic source615*/616private DiagnosticSource getDiagnosticSource(DocTreePath path) {617return getDiagnosticSource(path.getTreePath().getCompilationUnit().getSourceFile());618}619620/**621* Returns the diagnostic source for an element, or {@code NO_SOURCE} if the622* source file is not known (for example, if the element was read from a class file).623*624* @param element the element625* @return the diagnostic source626*/627private DiagnosticSource getDiagnosticSource(Element element) {628TreePath tp = getToolEnv().elementToTreePath.get(element);629return tp == null ? DiagnosticSource.NO_SOURCE630: getDiagnosticSource(tp.getCompilationUnit().getSourceFile());631}632633/**634* Returns the diagnostic source for a file object.635*636* {@code DiagnosticSource} objects are moderately expensive because they maintain637* an internal copy of the content, to provide the line map.638* Therefore, we keep a small memory-sensitive cache of recently used objects.639*640* @param fo the file object641* @return the diagnostic source642*/643private DiagnosticSource getDiagnosticSource(JavaFileObject fo) {644Reference<DiagnosticSource> ref = diagSourceCache.get(fo);645DiagnosticSource ds = ref == null ? null : ref.get();646if (ds == null) {647ds = new DiagnosticSource(fo, this);648diagSourceCache.put(fo, new SoftReference<>(ds));649}650return ds;651}652653/**654* Returns the object for computing source positions.655*656* The value is determined lazily because the tool environment is computed lazily.657*658* @return the object for computing source positions659*/660private DocSourcePositions getSourcePositions() {661if (sourcePositions == null) {662sourcePositions = getToolEnv().docTrees.getSourcePositions();663}664return sourcePositions;665}666667/**668* Returns the tool environment.669*670* The value is determined lazily, because creating it eagerly disrupts671* the overall initialization of objects in the context.672*673* @return the tool environment674*/675private ToolEnvironment getToolEnv() {676if (toolEnv == null) {677toolEnv = ToolEnvironment.instance(context);678}679return toolEnv;680}681}682683684