8222793: Javadoc tool ignores "-locale" param and uses default locale for all messages and texts

Reviewed-by: prappo
This commit is contained in:
Jonathan Gibbons 2020-02-05 11:01:05 -08:00
parent c0f23a8604
commit 98f5d98a88
6 changed files with 278 additions and 64 deletions
src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets
test/langtools/jdk/javadoc/tool/testLocaleOption

@ -39,6 +39,9 @@ import com.sun.source.util.DocTreePath;
import jdk.javadoc.doclet.Doclet;
import jdk.javadoc.doclet.DocletEnvironment;
import jdk.javadoc.doclet.Reporter;
import jdk.javadoc.doclet.StandardDoclet;
import jdk.javadoc.doclet.Taglet;
import jdk.javadoc.internal.doclets.toolkit.BaseConfiguration;
import jdk.javadoc.internal.doclets.toolkit.DocletException;
import jdk.javadoc.internal.doclets.toolkit.Messages;
@ -113,13 +116,24 @@ public class HtmlConfiguration extends BaseConfiguration {
private final HtmlOptions options;
/**
* Creates an object to hold the configuration for a doclet.
* Constructs the full configuration needed by the doclet, including
* the format-specific part, defined in this class, and the format-independent
* part, defined in the supertype.
*
* @param doclet the doclet
* @apiNote The {@code doclet} parameter is used when
* {@link Taglet#init(DocletEnvironment, Doclet) initializing tags}.
* Some doclets (such as the {@link StandardDoclet}), may delegate to another
* (such as the {@link HtmlDoclet}). In such cases, the primary doclet (i.e
* {@code StandardDoclet}) should be provided here, and not any internal
* class like {@code HtmlDoclet}.
*
* @param doclet the doclet for this run of javadoc
* @param locale the locale for the generated documentation
* @param reporter the reporter to use for console messages
*/
public HtmlConfiguration(Doclet doclet) {
super(doclet);
resources = new Resources(this,
public HtmlConfiguration(Doclet doclet, Locale locale, Reporter reporter) {
super(doclet, locale, reporter);
resources = new Resources(locale,
BaseConfiguration.sharedResourceBundleName,
"jdk.javadoc.internal.doclets.formats.html.resources.standard");

@ -55,8 +55,17 @@ import jdk.javadoc.internal.doclets.toolkit.util.IndexBuilder;
*/
public class HtmlDoclet extends AbstractDoclet {
public HtmlDoclet(Doclet parent) {
configuration = new HtmlConfiguration(parent);
/**
* Creates a doclet to generate HTML documentation,
* specifying the "initiating doclet" to be used when
* initializing any taglets for this doclet.
* An initiating doclet is one that delegates to
* this doclet.
*
* @param initiatingDoclet the initiating doclet
*/
public HtmlDoclet(Doclet initiatingDoclet) {
this.initiatingDoclet = initiatingDoclet;
}
@Override // defined by Doclet
@ -65,20 +74,31 @@ public class HtmlDoclet extends AbstractDoclet {
}
/**
* The global configuration information for this run.
* The initiating doclet, to be specified when creating
* the configuration.
*/
private final HtmlConfiguration configuration;
private final Doclet initiatingDoclet;
/**
* The global configuration information for this run.
* Initialized in {@link #init(Locale, Reporter)}.
*/
private HtmlConfiguration configuration;
/**
* Object for generating messages and diagnostics.
*/
private Messages messages;
/**
* Base path for resources for this doclet.
*/
private static final DocPath DOCLET_RESOURCES = DocPath
.create("/jdk/javadoc/internal/doclets/formats/html/resources");
@Override // defined by Doclet
public void init(Locale locale, Reporter reporter) {
configuration.reporter = reporter;
configuration.locale = locale;
configuration = new HtmlConfiguration(initiatingDoclet, locale, reporter);
messages = configuration.getMessages();
}

@ -148,9 +148,9 @@ public abstract class BaseConfiguration {
*/
public Extern extern;
public Reporter reporter;
public final Reporter reporter;
public Locale locale;
public final Locale locale;
public abstract Messages getMessages();
@ -202,20 +202,23 @@ public abstract class BaseConfiguration {
public PropertyUtils propertyUtils = null;
/**
* Constructs the configurations needed by the doclet.
* Constructs the format-independent configuration needed by the doclet.
*
* @apiNote
* The {@code doclet} parameter is used when {@link Taglet#init(DocletEnvironment, Doclet)
* initializing tags}.
* Some doclets (such as the {@link StandardDoclet), may delegate to another
* @apiNote The {@code doclet} parameter is used when
* {@link Taglet#init(DocletEnvironment, Doclet) initializing tags}.
* Some doclets (such as the {@link StandardDoclet}), may delegate to another
* (such as the {@link HtmlDoclet}). In such cases, the primary doclet (i.e
* {@code StandardDoclet}) should be provided here, and not any internal
* class like {@code HtmlDoclet}.
*
* @param doclet the doclet for this run of javadoc
* @param doclet the doclet for this run of javadoc
* @param locale the locale for the generated documentation
* @param reporter the reporter to use for console messages
*/
public BaseConfiguration(Doclet doclet) {
public BaseConfiguration(Doclet doclet, Locale locale, Reporter reporter) {
this.doclet = doclet;
this.locale = locale;
this.reporter = reporter;
}
public abstract BaseOptions getOptions();

@ -45,7 +45,7 @@ import static javax.tools.Diagnostic.Kind.*;
public class Messages {
private final BaseConfiguration configuration;
private final Resources resources;
private Reporter reporter;
private final Reporter reporter;
/**
* Creates a {@code Messages} object to provide standardized access to
@ -58,6 +58,7 @@ public class Messages {
public Messages(BaseConfiguration configuration) {
this.configuration = configuration;
resources = configuration.getResources();
reporter = configuration.getReporter();
}
// ***** Errors *****
@ -140,25 +141,14 @@ public class Messages {
// ***** Internal support *****
private void report(Diagnostic.Kind k, String msg) {
initReporter();
reporter.print(k, msg);
}
private void report(Diagnostic.Kind k, DocTreePath p, String msg) {
initReporter();
reporter.print(k, p, msg);
}
private void report(Diagnostic.Kind k, Element e, String msg) {
initReporter();
reporter.print(k, e, msg);
}
// Lazy init the reporter for now, until we can fix/improve
// the init of HtmlConfiguration in HtmlDoclet (and similar.)
private void initReporter() {
if (reporter == null) {
reporter = configuration.reporter;
}
}
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2016, 2020, 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
@ -41,9 +41,6 @@ import java.util.ResourceBundle;
public class Resources {
public final String annotationTypeSummary;
public final String classSummary;
private final BaseConfiguration configuration;
private final String commonBundleName;
private final String docletBundleName;
public final String enumSummary;
public final String errorSummary;
public final String exceptionSummary;
@ -55,21 +52,21 @@ public class Resources {
protected ResourceBundle docletBundle;
/**
* Creates a {@code Resources} to provide access the resource
* Creates a {@code Resources} object to provide access the resource
* bundles used by a doclet.
*
* @param configuration the configuration for the doclet,
* to provide access the locale to be used when accessing the
* names resource bundles.
* @param locale the locale to be used when accessing the
* resource bundles.
* @param commonBundleName the name of the bundle containing the strings
* common to all output formats
* common to all output formats
* @param docletBundleName the name of the bundle containing the strings
* specific to a particular format
* specific to a particular format
*/
public Resources(BaseConfiguration configuration, String commonBundleName, String docletBundleName) {
this.configuration = configuration;
this.commonBundleName = commonBundleName;
this.docletBundleName = docletBundleName;
public Resources(Locale locale, String commonBundleName, String docletBundleName) {
this.commonBundle = ResourceBundle.getBundle(commonBundleName, locale);
this.docletBundle = ResourceBundle.getBundle(docletBundleName, locale);
this.annotationTypeSummary = getText("doclet.Annotation_Types_Summary");
this.classSummary = getText("doclet.Class_Summary");
this.enumSummary = getText("doclet.Enum_Summary");
@ -81,7 +78,7 @@ public class Resources {
}
/**
* Gets the string for the given key from one of the doclet's
* Returns the string for the given key from one of the doclet's
* resource bundles.
*
* The more specific bundle is checked first;
@ -90,18 +87,16 @@ public class Resources {
* @param key the key for the desired string
* @return the string for the given key
* @throws MissingResourceException if the key is not found in either
* bundle.
* bundle.
*/
public String getText(String key) throws MissingResourceException {
initBundles();
if (docletBundle.containsKey(key))
return docletBundle.getString(key);
return commonBundle.getString(key);
}
/**
* Gets the string for the given key from one of the doclet's
* Returns the string for the given key from one of the doclet's
* resource bundles, substituting additional arguments into
* into the resulting string with {@link MessageFormat#format}.
*
@ -117,16 +112,4 @@ public class Resources {
public String getText(String key, Object... args) throws MissingResourceException {
return MessageFormat.format(getText(key), args);
}
/**
* Lazily initializes the bundles. This is (currently) necessary because
* this object may be created before the locale to be used is known.
*/
protected void initBundles() {
if (commonBundle == null) {
Locale locale = configuration.getLocale();
this.commonBundle = ResourceBundle.getBundle(commonBundleName, locale);
this.docletBundle = ResourceBundle.getBundle(docletBundleName, locale);
}
}
}

@ -0,0 +1,204 @@
/*
* Copyright (c) 2020, 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 8222793
* @summary Javadoc tool ignores "-locale" param and uses default locale for
* all messages and texts
* @library /tools/lib
* @modules jdk.javadoc/jdk.javadoc.internal.api
* jdk.javadoc/jdk.javadoc.internal.tool
* jdk.javadoc/jdk.javadoc.internal.tool.resources:open
* jdk.javadoc/jdk.javadoc.internal.doclets.toolkit.resources:open
* jdk.javadoc/jdk.javadoc.internal.doclets.formats.html.resources:open
* @build toolbox.JavadocTask toolbox.ToolBox
* @run main TestLocaleOption
*/
import java.io.File;
import java.io.Writer;
import java.util.Enumeration;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Properties;
import java.util.ResourceBundle;
import java.nio.file.Files;
import java.nio.file.Path;
import jdk.javadoc.internal.tool.Main;
import toolbox.JavadocTask;
import toolbox.Task;
import toolbox.TestRunner;
import toolbox.ToolBox;
/**
* Tests the {@code -locale} option.
*
* The test generates a set of resources files for javadoc and the doclet
* that can be used to "simulate" a non-default locale. These resource files
* have to be patched into the {@code jdk.javadoc} module, which means that
* the tool must be run in a separate VM, meaning that we cannot use the
* standard {@code JavadocTester} framework. Instead, we fall back on ToolBox.
*/
public class TestLocaleOption extends TestRunner {
public static void main(String... args) throws Exception {
Locale.setDefault(Locale.US);
TestLocaleOption t = new TestLocaleOption();
t.run();
}
private ToolBox tb = new ToolBox();
private Path patchDir;
private Path srcDir;
/** Locale for the generated resource files with uppercase values. */
private static final String LOCALE = "en_GB_ALLCAPS";
TestLocaleOption() {
super(System.err);
}
public void run() throws Exception {
patchDir = Path.of("patch");
generateBundle(patchDir, "jdk.javadoc.internal.tool.resources.javadoc");
generateBundle(patchDir, "jdk.javadoc.internal.doclets.toolkit.resources.doclets");
generateBundle(patchDir, "jdk.javadoc.internal.doclets.formats.html.resources.standard");
srcDir = Path.of("src");
tb.writeJavaFiles(srcDir,
"package p;\n"
+ "public class HelloWorld {\n"
+ " public static void main(String... args) {\n"
+ " System.out.println(\"Hello World!\");\n"
+ " }\n"
+ "}\n");
runTests(m -> new Object[] { Path.of(m.getName()) });
}
@Test
public void testHelpDefault(Path base) {
String stdOut = javadoc(patchDir, "-help")
.writeAll()
.getOutput(Task.OutputKind.STDOUT);
checkContains(stdOut,
"Usage:\n"
+" javadoc [options] [packagenames] [sourcefiles] [@files]");
}
@Test
public void testHelpLocale(Path base) {
String stdOut = javadoc(patchDir, "-locale", LOCALE, "-help")
.writeAll()
.getOutput(Task.OutputKind.STDOUT);
checkContains(stdOut,
"USAGE:\n"
+" JAVADOC [OPTIONS] [PACKAGENAMES] [SOURCEFILES] [@FILES]");
}
@Test
public void testHelloWorldDefault(Path base) throws Exception {
Path apiDir = base.resolve("api");
String stdOut = javadoc(patchDir,
"-sourcepath", srcDir.toString(),
"-d", apiDir.toString(),
"p")
.writeAll()
.getOutput(Task.OutputKind.STDOUT);
checkContains(stdOut,
"Loading source files for package p...\n"
+ "Constructing Javadoc information...");
String hw = Files.readString(apiDir.resolve("p/HelloWorld.html"));
checkContains(hw,
"<h2>Method Summary</h2>",
"<th class=\"colFirst\" scope=\"col\">Modifier and Type</th>",
"<th class=\"colSecond\" scope=\"col\">Method</th>",
"<th class=\"colLast\" scope=\"col\">Description</th>");
}
@Test
public void testHelloWorldLocale(Path base) throws Exception {
Path apiDir = base.resolve("api");
String stdOut = javadoc(patchDir,
"-locale", LOCALE,
"-sourcepath", srcDir.toString(),
"-d", apiDir.toString(),
"p")
.writeAll()
.getOutput(Task.OutputKind.STDOUT);
checkContains(stdOut,
"LOADING SOURCE FILES FOR PACKAGE p...\n"
+ "CONSTRUCTING JAVADOC INFORMATION...");
String hw = Files.readString(apiDir.resolve("p/HelloWorld.html"));
checkContains(hw,
"<h2>METHOD SUMMARY</h2>",
"<th class=\"colFirst\" scope=\"col\">MODIFIER AND TYPE</th>",
"<th class=\"colSecond\" scope=\"col\">METHOD</th>",
"<th class=\"colLast\" scope=\"col\">DESCRIPTION</th>");
}
private void generateBundle(Path dir, String name) throws Exception {
Module m = Main.class.getModule();
ResourceBundle rb = ResourceBundle.getBundle(name, m);
Properties p = new Properties();
Enumeration<String> e = rb.getKeys();
while (e.hasMoreElements()) {
String key = e.nextElement();
String value = rb.getString(key);
p.put(key, value.toUpperCase(Locale.US));
}
Path outPath = dir.resolve(name.replace(".", File.separator) + "_" + LOCALE + ".properties");
Files.createDirectories(outPath.getParent());
try (Writer out = Files.newBufferedWriter(outPath)) {
p.store(out, "Generated by TestLocaleOption");
System.err.println("wrote: " + outPath);
}
}
private Task.Result javadoc(Path patchDir, String... args) {
List<String> options = new ArrayList<>();
options.add("-J--patch-module=jdk.javadoc=" + patchDir);
options.addAll(List.of(args));
return new JavadocTask(tb, Task.Mode.EXEC)
.options(options)
.run();
}
private String NL = System.lineSeparator();
private void checkContains(String found, String... expect) {
for (String e : expect) {
String e2 = e.replace("\n", NL);
if (!found.contains(e2)) {
error("expected string not found: '" + e2 + "'");
}
}
}
}