8267204: Expose access to underlying streams in Reporter

Reviewed-by: prappo
This commit is contained in:
Jonathan Gibbons 2021-06-05 00:05:17 +00:00
parent 76b54a1995
commit 6ff978ac16
10 changed files with 512 additions and 122 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1998, 2016, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1998, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -25,41 +25,124 @@
package jdk.javadoc.doclet;
import java.io.PrintWriter;
import java.util.Locale;
import javax.lang.model.element.Element;
import javax.tools.Diagnostic;
import javax.tools.FileObject;
import com.sun.source.util.DocTreePath;
/**
* This interface provides error, warning and notice reporting.
* Interface for reporting diagnostics and other messages.
*
* <p>Diagnostics consist of a {@link Diagnostic.Kind diagnostic kind} and a message,
* and may additionally be associated with an {@link Element element},
* a {@link DocTreePath tree node} in a documentation comment,
* or an arbitrary position in a given {@link FileObject file}.
* Other messages may be written directly to one of two streams that are informally
* for use by "standard output" and "diagnostic output", where "standard output"
* means the output that is the expected result of executing some operation,
* such as the command-line help that is generated when using a {@code --help} option,
* and "diagnostic output" refers to any errors, warnings and other output that is
* a side-effect of executing the operation.
*
* <p>The exact manner in which diagnostics are output is unspecified and depends
* on the enclosing context. For example:
* <ul>
* <li>The {@link javax.tools.DocumentationTool} API allows a client to specify a
* {@link javax.tools.DiagnosticListener} to which diagnostics will be
* {@link javax.tools.DiagnosticListener#report reported}. If no listener is specified,
* diagnostics will be written to a given stream, or to {@code System.err} if no such
* stream is provided.
* <li>The {@link java.util.spi.ToolProvider} API allows a client to specify
* the streams to be used for reporting standard and diagnostic output.
* </ul>
*
* @since 9
*/
public interface Reporter {
/**
* Print error message and increment error count.
* Prints a diagnostic message.
*
* @param kind specify the diagnostic kind
* @param msg message to print
* @param kind the kind of diagnostic
* @param message the message to be printed
*/
void print(Diagnostic.Kind kind, String msg);
void print(Diagnostic.Kind kind, String message);
/**
* Print an error message and increment error count.
* Prints a diagnostic message related to a tree node in a documentation comment.
*
* @param kind specify the diagnostic kind
* @param path the DocTreePath of item where the error occurs
* @param msg message to print
* @param kind the kind of diagnostic
* @param path the path for the tree node
* @param message the message to be printed
*/
void print(Diagnostic.Kind kind, DocTreePath path, String msg);
void print(Diagnostic.Kind kind, DocTreePath path, String message);
/**
* Print an error message and increment error count.
* Prints a diagnostic message related to an element.
*
* @param kind specify the diagnostic kind
* @param e the Element for which the error occurs
* @param msg message to print
* @param kind the kind of diagnostic
* @param element the element
* @param message the message to be printed
*/
void print(Diagnostic.Kind kind, Element e, String msg);
void print(Diagnostic.Kind kind, Element element, String message);
/**
* Prints a diagnostic message related to a position within a range of characters in a file.
* The positions are all 0-based character offsets from the beginning of content of the file.
* The positions should satisfy the relation {@code start <= pos <= end}.
*
* @param kind the kind of diagnostic
* @param file the file
* @param start the beginning of the enclosing range
* @param pos the position
* @param end the end of the enclosing range
* @param message the message to be printed
*
* @since 17
*/
void print(Diagnostic.Kind kind, FileObject file, int start, int pos, int end, String message);
/**
* Returns a writer that can be used to write non-diagnostic output,
* or {@code null} if no such writer is available.
*
* @apiNote
* The value may or may not be the same as that returned by {@link #getDiagnosticWriter()}.
*
* @implSpec
* This implementation returns {@code null}.
* The implementation provided by the {@code javadoc} tool to
* {@link Doclet#init(Locale, Reporter) initialize} a doclet
* always returns a non-{@code null} value.
*
* @return the writer
* @since 17
*/
default PrintWriter getStandardWriter() {
return null;
}
/**
* Returns a writer that can be used to write diagnostic output,
* or {@code null} if no such writer is available.
*
* @apiNote
* The value may or may not be the same as that returned by {@link #getStandardWriter()}.
*
* @implSpec
* This implementation returns {@code null}.
* The implementation provided by the {@code javadoc} tool to
* {@link Doclet#init(Locale, Reporter) initialize} a doclet
* always returns a non-{@code null} value.
*
* @return the writer
* @since 17
*/
default PrintWriter getDiagnosticWriter() {
return null;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2016, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -26,6 +26,7 @@ package jdk.javadoc.internal.doclets.toolkit;
import javax.lang.model.element.Element;
import javax.tools.Diagnostic;
import javax.tools.FileObject;
import com.sun.source.util.DocTreePath;
import jdk.javadoc.doclet.Reporter;
@ -76,8 +77,8 @@ public class Messages {
/**
* Reports an error message to the doclet's reporter.
*
* @param key the name of a resource containing the message to be printed
* @param args optional arguments to be replaced in the message.
* @param key the name of a resource containing the message to be printed
* @param args optional arguments to be replaced in the message
*/
public void error(String key, Object... args) {
report(ERROR, resources.getText(key, args));
@ -86,22 +87,35 @@ public class Messages {
/**
* Reports an error message to the doclet's reporter.
*
* @param path a path identifying the position to be included with
* the message
* @param key the name of a resource containing the message to be printed
* @param args optional arguments to be replaced in the message.
* @param path a path identifying the position to be included with the message
* @param key the name of a resource containing the message to be printed
* @param args optional arguments to be replaced in the message
*/
public void error(DocTreePath path, String key, Object... args) {
report(ERROR, path, resources.getText(key, args));
}
/**
* Reports an error message to the doclet's reporter.
*
* @param fo the file object to be associated with the message
* @param start the start of a range of characters to be associated with the message
* @param pos the position to be associated with the message
* @param end the end of a range of characters to be associated with the message
* @param key the name of a resource containing the message to be printed
* @param args optional arguments to be replaced in the message
*/
public void error(FileObject fo, int start, int pos, int end, String key, Object... args) {
report(ERROR, fo, start, pos, end, resources.getText(key, args));
}
// ***** Warnings *****
/**
* Reports a warning message to the doclet's reporter.
*
* @param key the name of a resource containing the message to be printed
* @param args optional arguments to be replaced in the message.
* @param key the name of a resource containing the message to be printed
* @param args optional arguments to be replaced in the message
*/
public void warning(String key, Object... args) {
report(WARNING, resources.getText(key, args));
@ -110,10 +124,9 @@ public class Messages {
/**
* Reports a warning message to the doclet's reporter.
*
* @param path a path identifying the position to be included with
* the message
* @param key the name of a resource containing the message to be printed
* @param args optional arguments to be replaced in the message.
* @param path a path identifying the position to be included with the message
* @param key the name of a resource containing the message to be printed
* @param args optional arguments to be replaced in the message
*/
public void warning(DocTreePath path, String key, Object... args) {
if (configuration.showMessage(path, key)) {
@ -124,10 +137,9 @@ public class Messages {
/**
* Reports a warning message to the doclet's reporter.
*
* @param e an element identifying the declaration whose position should
* to be included with the message
* @param key the name of a resource containing the message to be printed
* @param args optional arguments to be replaced in the message.
* @param e an element identifying the position to be included with the message
* @param key the name of a resource containing the message to be printed
* @param args optional arguments to be replaced in the message
*/
public void warning(Element e, String key, Object... args) {
if (configuration.showMessage(e, key)) {
@ -135,17 +147,33 @@ public class Messages {
}
}
/**
* Reports a warning message to the doclet's reporter.
*
* @param fo the file object to be associated with the message
* @param start the start of a range of characters to be associated with the message
* @param pos the position to be associated with the message
* @param end the end of a range of characters to be associated with the message
* @param key the name of a resource containing the message to be printed
* @param args optional arguments to be replaced in the message
*/
public void warning(FileObject fo, int start, int pos, int end, String key, Object... args) {
report(WARNING, fo, start, pos, end, resources.getText(key, args));
}
// ***** Notices *****
/**
* Reports an informational notice to the doclet's reporter.
* The message is written directly to the reporter's diagnostic stream.
*
* @param key the name of a resource containing the message to be printed
* @param args optional arguments to be replaced in the message.
* @param key the name of a resource containing the message to be printed
* @param args optional arguments to be replaced in the message
*/
public void notice(String key, Object... args) {
if (!configuration.getOptions().quiet()) {
report(NOTE, resources.getText(key, args));
// Note: we do not use report(NOTE, ...) which would prefix the output with "Note:"
reporter.getDiagnosticWriter().println(resources.getText(key, args));
}
}
@ -162,4 +190,8 @@ public class Messages {
private void report(Diagnostic.Kind k, Element e, String msg) {
reporter.print(k, e, msg);
}
private void report(Diagnostic.Kind k, FileObject fo, int start, int pos, int end, String msg) {
reporter.print(k, fo, start, pos, end, msg);
}
}

View File

@ -25,6 +25,7 @@
package jdk.javadoc.internal.tool;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
@ -36,8 +37,12 @@ import java.util.ResourceBundle;
import java.util.Set;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.NestingKind;
import javax.tools.Diagnostic;
import javax.tools.Diagnostic.Kind;
import javax.tools.FileObject;
import javax.tools.ForwardingFileObject;
import javax.tools.JavaFileObject;
import jdk.javadoc.doclet.Reporter;
@ -157,17 +162,25 @@ public class Messager extends Log implements Reporter {
private final JavacMessages messages;
private final JCDiagnostic.Factory javadocDiags;
/** The default writer for notes. */
private static final PrintWriter defaultOutWriter = new PrintWriter(System.out);
/** The default writer for errors and warnings. */
private static final PrintWriter defaultErrWriter = new PrintWriter(System.err);
private static PrintWriter createPrintWriter(PrintStream ps, boolean autoflush) {
return new PrintWriter(ps, autoflush) {
// avoid closing system streams
@Override
public void close() {
super.flush();
}
};
}
/**
* Constructor
* @param programName Name of the program (for error messages).
*/
public Messager(Context context, String programName) {
this(context, programName, defaultOutWriter, defaultErrWriter);
// use the current values of System.out, System.err, in case they have been redirected
this(context, programName,
createPrintWriter(System.out, false),
createPrintWriter(System.err, true));
}
/**
@ -176,9 +189,8 @@ public class Messager extends Log implements Reporter {
* @param outWriter Stream for notices etc.
* @param errWriter Stream for errors and warnings
*/
@SuppressWarnings("deprecation")
public Messager(Context context, String programName, PrintWriter outWriter, PrintWriter errWriter) {
super(context, errWriter, errWriter, outWriter);
super(context, outWriter, errWriter);
messages = JavacMessages.instance(context);
messages.add(locale -> ResourceBundle.getBundle("jdk.javadoc.internal.tool.resources.javadoc",
locale));
@ -197,6 +209,16 @@ public class Messager extends Log implements Reporter {
};
}
@Override // Reporter
public PrintWriter getStandardWriter() {
return getWriter(Log.WriterKind.STDOUT);
}
@Override // Reporter
public PrintWriter getDiagnosticWriter() {
return getWriter(Log.WriterKind.STDERR);
}
public void setLocale(Locale locale) {
this.locale = locale;
}
@ -234,6 +256,51 @@ public class Messager extends Log implements Reporter {
report(dt, flags, ds, dp, message);
}
@Override // Reporter
public void print(Kind kind, FileObject file, int start, int pos, int end, String message) throws IllegalArgumentException {
DiagnosticType dt = getDiagnosticType(kind);
Set<DiagnosticFlag> flags = getDiagnosticFlags(kind);
// Although not required to do so, it is the case that any file object returned from the
// javac impl of JavaFileManager will return an object that implements JavaFileObject.
// See PathFileObject, which provides the primary impls of (Java)FileObject.
JavaFileObject fo = file instanceof JavaFileObject _fo ? _fo : new WrappingJavaFileObject(file);
DiagnosticSource ds = new DiagnosticSource(fo, this);
DiagnosticPosition dp = createDiagnosticPosition(null, start, pos, end);
report(dt, flags, ds, dp, message);
}
private class WrappingJavaFileObject
extends ForwardingFileObject<FileObject> implements JavaFileObject {
WrappingJavaFileObject(FileObject fo) {
super(fo);
assert !(fo instanceof JavaFileObject);
}
@Override
public Kind getKind() {
String name = fileObject.getName();
return name.endsWith(Kind.HTML.extension)
? JavaFileObject.Kind.HTML
: JavaFileObject.Kind.OTHER;
}
@Override
public boolean isNameCompatible(String simpleName, Kind kind) {
return false;
}
@Override
public NestingKind getNestingKind() {
return null;
}
@Override
public Modifier getAccessLevel() {
return null;
}
}
/**
* Prints an error message.
*
@ -332,46 +399,22 @@ public class Messager extends Log implements Reporter {
}
/**
* Prints a "notice" message.
*
* @param message the message
*/
public void printNotice(String message) {
report(DiagnosticType.NOTE, null, null, message);
}
/**
* Prints a "notice" message for a given documentation tree node.
*
* @param path the path for the documentation tree node
* @param message the message
*/
public void printNotice(DocTreePath path, String message) {
DiagnosticSource ds = getDiagnosticSource(path);
DiagnosticPosition dp = getDiagnosticPosition(path);
report(DiagnosticType.NOTE, EnumSet.noneOf(DiagnosticFlag.class), ds, dp, message);
}
/**
* Prints a "notice" message for a given element.
*
* @param element the element
* @param message the message
*/
public void printNotice(Element element, String message) {
DiagnosticSource ds = getDiagnosticSource(element);
DiagnosticPosition dp = getDiagnosticPosition(element);
report(DiagnosticType.NOTE, EnumSet.noneOf(DiagnosticFlag.class), ds, dp, message);
}
/**
* Prints a "notice" message.
* Prints a "notice" message to the standard writer.
*
* @param key the resource key for the message
* @param args the arguments for the message
*/
public void notice(String key, Object... args) {
printNotice(getText(key, args));
public void noticeUsingKey(String key, Object... args) {
printRawLines(getStandardWriter(), getText(key, args));
}
/**
* Prints a "notice" message to the standard writer.
*
* @param message the message
*/
public void notice(String message) {
printRawLines(getStandardWriter(), message);
}
/**
@ -389,16 +432,21 @@ public class Messager extends Log implements Reporter {
}
/**
* Prints the error and warning counts, if any.
* Prints the error and warning counts, if any, to the diagnostic writer.
*/
public void printErrorWarningCounts() {
if (nerrors > 0) {
notice((nerrors > 1) ? "main.errors" : "main.error",
"" + nerrors);
}
if (nwarnings > 0) {
notice((nwarnings > 1) ? "main.warnings" : "main.warning",
"" + nwarnings);
printCount(nerrors, "main.error", "main.errors");
printCount(nwarnings, "main.warning", "main.warnings");
}
private void printCount(int count, String singleKey, String pluralKey) {
if (count > 0) {
String message = getText(count > 1 ? pluralKey : singleKey, count);
if (diagListener != null) {
report(DiagnosticType.NOTE, null, null, message);
} else {
printRawLines(getDiagnosticWriter(), message);
}
}
}
@ -446,9 +494,10 @@ public class Messager extends Log implements Reporter {
*
* {@code Log} reports all notes with a "Note:" prefix. That's not good for the
* standard doclet, which uses notes to report the various "progress" messages,
* such as "Generating class ...". Therefore, for now, we detect and report those
* messages directly. (A better solution would be to expose access to the output
* and error streams via {@code Reporter}).
* such as "Generating class ...". They can be written directly to the diagnostic
* writer, but that bypasses low-level checks about whether to suppress notes,
* and bypasses the diagnostic listener for API clients.
* Overall, it's an over-constrained problem with no obvious good solution.
*
* Note: there is an intentional difference in behavior between the diagnostic source
* being set to {@code null} (no source intended) and {@code NO_SOURCE} (no source available).
@ -459,12 +508,7 @@ public class Messager extends Log implements Reporter {
* @param message the message
*/
private void report(DiagnosticType dt, Set<DiagnosticFlag> flags, DiagnosticSource ds, DiagnosticPosition dp, String message) {
if (dt == DiagnosticType.NOTE && ds == null && !hasDiagnosticListener()) {
printRawLines(WriterKind.STDOUT, message);
getWriter(WriterKind.STDOUT).flush();
} else {
report(javadocDiags.create(dt, null, flags, ds, dp, "message", message));
}
report(javadocDiags.create(dt, null, flags, ds, dp, "message", message));
}
/**

View File

@ -195,7 +195,7 @@ public class Start {
}
private void showUsage(String headerKey, ToolOption.Kind kind, String footerKey) {
messager.notice(headerKey);
messager.noticeUsingKey(headerKey);
showToolOptions(kind);
// let doclet print usage information
@ -205,11 +205,11 @@ public class Start {
: Option.Kind.STANDARD);
}
if (footerKey != null)
messager.notice(footerKey);
messager.noticeUsingKey(footerKey);
}
private void showVersion(String labelKey, String value) {
messager.notice(labelKey, messager.programName, value);
messager.noticeUsingKey(labelKey, messager.programName, value);
}
private void showToolOptions(ToolOption.Kind kind) {
@ -252,7 +252,7 @@ public class Start {
if (options.isEmpty()) {
return;
}
messager.notice("main.doclet.usage.header", name);
messager.noticeUsingKey("main.doclet.usage.header", name);
Comparator<Doclet.Option> comp = new Comparator<Doclet.Option>() {
final Collator collator = Collator.getInstance(Locale.US);
@ -307,22 +307,22 @@ public class Start {
if (synopses.length() < DEFAULT_SYNOPSIS_WIDTH
&& !description.contains("\n")
&& (SMALL_INDENT.length() + DEFAULT_SYNOPSIS_WIDTH + 1 + description.length() <= DEFAULT_MAX_LINE_LENGTH)) {
messager.printNotice(String.format(COMPACT_FORMAT, synopses, description));
messager.notice(String.format(COMPACT_FORMAT, synopses, description));
return;
}
// If option synopses fit on a single line of reasonable length, show that;
// otherwise, show 1 per line
if (synopses.length() <= DEFAULT_MAX_LINE_LENGTH) {
messager.printNotice(SMALL_INDENT + synopses);
messager.notice(SMALL_INDENT + synopses);
} else {
for (String name: names) {
messager.printNotice(SMALL_INDENT + name + parameters);
messager.notice(SMALL_INDENT + name + parameters);
}
}
// Finally, show the description
messager.printNotice(LARGE_INDENT + description.replace("\n", "\n" + LARGE_INDENT));
messager.notice(LARGE_INDENT + description.replace("\n", "\n" + LARGE_INDENT));
}
@ -560,7 +560,7 @@ public class Start {
// We're done.
if (options.verbose()) {
long elapsedMillis = (System.nanoTime() - startNanos) / 1_000_000;
messager.notice("main.done_in", Long.toString(elapsedMillis));
messager.noticeUsingKey("main.done_in", Long.toString(elapsedMillis));
}
return returnStatus;

View File

@ -199,7 +199,7 @@ public class ToolEnvironment {
if (quiet) {
return;
}
messager.notice(key);
messager.noticeUsingKey(key);
}
/**
@ -212,7 +212,7 @@ public class ToolEnvironment {
if (quiet) {
return;
}
messager.notice(key, a1);
messager.noticeUsingKey(key, a1);
}
TreePath getTreePath(JCCompilationUnit tree) {

View File

@ -70,7 +70,7 @@ public class TestDiagsLineCaret extends JavadocTester {
error: This is a error
warning: This is a warning
warning: This is a mandatory_warning
This is a note
Note: This is a note
MyClass.java:5: error: This is a error for MyClass
public class MyClass { }
^

View File

@ -0,0 +1,220 @@
/*
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @bug 8267204
* @summary Expose access to underlying streams in Reporter
* @library /tools/lib ../../lib
* @modules jdk.javadoc/jdk.javadoc.internal.tool
* @build toolbox.ToolBox javadoc.tester.*
* @run main TestReporterStreams
*/
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Collections;
import java.util.Locale;
import java.util.Set;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;
import com.sun.source.doctree.DocCommentTree;
import com.sun.source.doctree.SinceTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.util.DocSourcePositions;
import com.sun.source.util.DocTreePath;
import com.sun.source.util.DocTrees;
import com.sun.source.util.TreePath;
import javadoc.tester.JavadocTester;
import jdk.javadoc.doclet.Doclet;
import jdk.javadoc.doclet.DocletEnvironment;
import jdk.javadoc.doclet.Reporter;
import toolbox.ToolBox;
public class TestReporterStreams extends JavadocTester {
public static void main(String... args) throws Exception {
TestReporterStreams tester = new TestReporterStreams();
tester.runTests(m -> new Object[]{Path.of(m.getName())});
}
ToolBox tb = new ToolBox();
TestReporterStreams() throws IOException {
tb.writeJavaFiles(Path.of("."), """
/**
* Comment.
* @since 0
*/
public class C { }""");
}
/**
* Tests the entry point used by the DocumentationTool API and JavadocTester, in which
* all output is written to a single specified writer.
*/
@Test
public void testSingleStream(Path base) throws IOException {
test(base, false, Output.OUT, Output.OUT);
}
/**
* Tests the entry point used by the launcher, in which output is written to
* writers that wrap {@code System.out} and {@code System.err}.
*/
@Test
public void testStandardStreams(Path base) throws IOException {
test(base, true, Output.STDOUT, Output.STDERR);
}
void test(Path base, boolean useStdStreams, Output stdOut, Output stdErr) throws IOException {
String testClasses = System.getProperty("test.classes");
setOutputDirectoryCheck(DirectoryCheck.NONE);
setUseStandardStreams(useStdStreams);
javadoc("-docletpath", testClasses,
"-doclet", MyDoclet.class.getName(),
"C.java" // avoid using a directory, to avoid path separator issues in expected output
);
checkExit(Exit.ERROR);
checkOutput(stdOut, true,
"Writing to the standard writer");
checkOutput(stdErr, true,
"Writing to the diagnostic writer");
checkOutput(stdErr, true,
"""
error: This is a ERROR with no position
C.java:5: error: This is a ERROR for an element
public class C { }
^
C.java:2: error: This is a ERROR for a doc tree path
* Comment.
^
C.java:3: error: This is a ERROR for a file position
* @since 0
^
warning: This is a WARNING with no position
C.java:5: warning: This is a WARNING for an element
public class C { }
^
C.java:2: warning: This is a WARNING for a doc tree path
* Comment.
^
C.java:3: warning: This is a WARNING for a file position
* @since 0
^
warning: This is a MANDATORY_WARNING with no position
C.java:5: warning: This is a MANDATORY_WARNING for an element
public class C { }
^
C.java:2: warning: This is a MANDATORY_WARNING for a doc tree path
* Comment.
^
C.java:3: warning: This is a MANDATORY_WARNING for a file position
* @since 0
^
Note: This is a NOTE with no position
C.java:5: Note: This is a NOTE for an element
public class C { }
^
C.java:2: Note: This is a NOTE for a doc tree path
* Comment.
^
C.java:3: Note: This is a NOTE for a file position
* @since 0
^
""");
}
public static class MyDoclet implements Doclet {
private Locale locale;
private Reporter reporter;
@Override
public void init(Locale locale, Reporter reporter) {
this.locale = locale;
this.reporter = reporter;
}
@Override
public String getName() {
return "MyDoclet";
}
@Override
public Set<? extends Option> getSupportedOptions() {
return Collections.emptySet();
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public boolean run(DocletEnvironment environment) {
// Write directly to the given streams
reporter.getStandardWriter().println("Writing to the standard writer");
reporter.getDiagnosticWriter().println("Writing to the diagnostic writer");
// the following is little more than a null check for the locale
reporter.print(Diagnostic.Kind.NOTE, "The locale is " + locale.getDisplayName());
// Write different kinds of diagnostics using the different overloads
// for printing diagnostics
Set<? extends Element> specElems = environment.getSpecifiedElements();
Element e = specElems.iterator().next();
DocTrees trees = environment.getDocTrees();
TreePath tp = trees.getPath(e);
DocCommentTree dct = trees.getDocCommentTree(e);
DocTreePath dtp = new DocTreePath(tp, dct);
CompilationUnitTree cut = tp.getCompilationUnit();
JavaFileObject fo = cut.getSourceFile();
SinceTree st = (SinceTree) dct.getBlockTags().get(0);
DocSourcePositions sp = trees.getSourcePositions();
int start = (int) sp.getStartPosition(cut, dct, st);
int pos = (int) sp.getStartPosition(cut, dct, st.getBody().get(0));
int end = (int) sp.getEndPosition(cut, dct, st);
for (Diagnostic.Kind k : Diagnostic.Kind.values()) {
if (k == Diagnostic.Kind.OTHER) {
continue;
}
reporter.print(k, "This is a " + k + " with no position");
reporter.print(k, e, "This is a " + k + " for an element");
reporter.print(k, dtp, "This is a " + k + " for a doc tree path");
reporter.print(k, fo, start, pos, end, "This is a " + k + " for a file position");
}
return true;
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2002, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2002, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -236,6 +236,7 @@ public abstract class JavadocTester {
private boolean automaticCheckAccessibility = true;
private boolean automaticCheckLinks = true;
private boolean automaticCheckUniqueOUT = true;
private boolean useStandardStreams = false;
/** The current subtest number. Incremented when checking(...) is called. */
private int numTestsRun = 0;
@ -338,7 +339,7 @@ public abstract class JavadocTester {
cs = charsetArg;
}
} else {
cs = docencodingArg;
cs = docencodingArg;
}
try {
charset = Charset.forName(cs);
@ -351,7 +352,7 @@ public abstract class JavadocTester {
outputDirectoryCheck.check(outputDir);
// This is the sole stream used by javadoc
// This is the sole stream normally used by javadoc
WriterOutput outOut = new WriterOutput();
// These are to catch output to System.out and System.err,
@ -360,7 +361,9 @@ public abstract class JavadocTester {
StreamOutput sysErr = new StreamOutput(System.err, System::setErr);
try {
exitCode = jdk.javadoc.internal.tool.Main.execute(args, outOut.pw);
exitCode = useStandardStreams
? jdk.javadoc.internal.tool.Main.execute(args) // use sysOut, sysErr
: jdk.javadoc.internal.tool.Main.execute(args, outOut.pw); // default
} finally {
outputMap.put(Output.STDOUT, sysOut.close());
outputMap.put(Output.STDERR, sysErr.close());
@ -418,6 +421,16 @@ public abstract class JavadocTester {
automaticCheckUniqueOUT = b;
}
/**
* Sets whether to use standard output streams (stdout and stderr)
* instead of a single temporary stream.
* Tests using standard streams should generally take care to avoid
* conflicting use of stdout and stderr.
*/
public void setUseStandardStreams(boolean b) {
useStandardStreams = b;
}
/**
* The exit codes returned by the javadoc tool.
* @see jdk.javadoc.internal.tool.Main.Result
@ -1105,4 +1118,4 @@ public abstract class JavadocTester {
// Support classes for checkLinks
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -109,8 +109,8 @@ public class EnsureNewOldDoclet extends TestRunner {
setArgs("-classpath", ".", // insulates us from ambient classpath
testSrc.toString());
Task.Result tr = task.run(Task.Expect.SUCCESS);
List<String> out = tr.getOutputLines(Task.OutputKind.STDOUT);
checkOutput(testName, out, NEW_HEADER);
List<String> err = tr.getOutputLines(Task.OutputKind.STDERR);
checkOutput(testName, err, NEW_HEADER);
}
// input: new doclet and new taglet
@ -128,8 +128,8 @@ public class EnsureNewOldDoclet extends TestRunner {
Task.Result tr = task.run(Task.Expect.SUCCESS);
List<String> out = tr.getOutputLines(Task.OutputKind.STDOUT);
List<String> err = tr.getOutputLines(Task.OutputKind.STDERR);
checkOutput(testName, out, NEW_HEADER);
checkOutput(testName, out, NEW_TAGLET_MARKER);
checkOutput(testName, err, NEW_HEADER);
checkOutput(testName, err, NEW_TAGLET_MARKER);
}
void setArgs(String... args) {

View File

@ -54,9 +54,8 @@ public class GetTask_DiagListenerTest extends APITest {
/**
* Verify that a diagnostic listener can be specified.
* Note that messages from the tool and doclet are imperfectly modeled
* because the DocErrorReporter API works in terms of localized strings
* and file:line positions. Therefore, messages reported via DocErrorReporter
* and simply wrapped and passed through.
* because the Reporter API works in terms of localized strings.
* Therefore, messages reported via Reporter are simply wrapped and passed through.
*/
@Test
public void testDiagListener() throws Exception {
@ -77,7 +76,6 @@ public class GetTask_DiagListenerTest extends APITest {
diagCodes.add(d.getCode());
}
List<String> expect = Arrays.asList(
"javadoc.note.message", // Loading source file
"compiler.err.expected4", // class, interface, enum, or record expected
"javadoc.note.message"); // 1 error
if (!diagCodes.equals(expect))