8190820: Introduce a new Head builder class
authorjjg
Thu, 16 Nov 2017 15:13:44 -0800
changeset 47849 0e38db7cf1cc
parent 47848 6b1311fbbaba
child 47850 4a28dc8a86c2
8190820: Introduce a new Head builder class Reviewed-by: bpatel, ksrini
src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/FrameOutputWriter.java
src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlConfiguration.java
src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlDocletWriter.java
src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/IndexRedirectWriter.java
src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/SourceToHTMLConverter.java
src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/Head.java
src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/HtmlDocWriter.java
src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/AbstractDoclet.java
src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/BaseConfiguration.java
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/FrameOutputWriter.java	Thu Nov 16 15:12:13 2017 -0800
+++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/FrameOutputWriter.java	Thu Nov 16 15:13:44 2017 -0800
@@ -25,7 +25,7 @@
 
 package jdk.javadoc.internal.doclets.formats.html;
 
-import jdk.javadoc.internal.doclets.formats.html.markup.Comment;
+import jdk.javadoc.internal.doclets.formats.html.markup.Head;
 import jdk.javadoc.internal.doclets.formats.html.markup.DocType;
 import jdk.javadoc.internal.doclets.formats.html.markup.HtmlAttr;
 import jdk.javadoc.internal.doclets.formats.html.markup.HtmlDocument;
@@ -130,16 +130,15 @@
     private void printFramesDocument(String title, HtmlTree body) throws DocFileIOException {
         DocType htmlDocType = DocType.forVersion(configuration.htmlVersion);
         Content htmlComment = contents.newPage;
-        Content head = new HtmlTree(HtmlTag.HEAD);
-        head.addContent(getGeneratedBy(!configuration.notimestamp));
-        Content windowTitle = HtmlTree.TITLE(title);
-        head.addContent(windowTitle);
-        Content meta = HtmlTree.META("Content-Type", CONTENT_TYPE, configuration.charset);
-        head.addContent(meta);
-        addStyleSheetProperties(configuration, head);
-        head.addContent(getFramesScript().asContent());
+        Head head = new Head(path, configuration.htmlVersion, configuration.docletVersion)
+                .setTimestamp(!configuration.notimestamp, false)
+                .setTitle(title)
+                .setCharset(configuration.charset)
+                .setStylesheets(configuration.getMainStylesheet(), configuration.getAdditionalStylesheets())
+                .addDefaultScript(false)
+                .addScript(getFramesScript());
 
-        Content htmlTree = HtmlTree.HTML(configuration.getLocale().getLanguage(), head, body);
+        Content htmlTree = HtmlTree.HTML(configuration.getLocale().getLanguage(), head.toContent(), body);
         HtmlDocument htmlDocument = new HtmlDocument(htmlDocType, htmlComment, htmlTree);
         htmlDocument.write(DocFile.createFileForOutput(configuration, path));
    }
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlConfiguration.java	Thu Nov 16 15:12:13 2017 -0800
+++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlConfiguration.java	Thu Nov 16 15:13:44 2017 -0800
@@ -27,6 +27,7 @@
 
 import java.net.*;
 import java.util.*;
+import java.util.stream.Collectors;
 
 import javax.lang.model.element.Element;
 import javax.lang.model.element.PackageElement;
@@ -82,12 +83,6 @@
 public class HtmlConfiguration extends BaseConfiguration {
 
     /**
-     * The build date.  Note: For now, we will use
-     * a version number instead of a date.
-     */
-    public static final String BUILD_DATE = System.getProperty("java.version");
-
-    /**
      * Argument for command line option "-header".
      */
     public String header = "";
@@ -249,8 +244,9 @@
     protected Messages messages;
 
     /**
-     * Constructor. Initializes resource for the
-     * {@link jdk.javadoc.internal.tool.Messager Messager}.
+     * Creates an object to hold the configuration for a doclet.
+     *
+     * @param doclet the doclet
      */
     public HtmlConfiguration(Doclet doclet) {
         super(doclet);
@@ -260,30 +256,28 @@
 
         messages = new Messages(this);
         contents = new Contents(this);
+
+        String v;
+        try {
+            ResourceBundle rb = ResourceBundle.getBundle(versionBundleName, getLocale());
+            try {
+                v = rb.getString("release");
+            } catch (MissingResourceException e) {
+                v = defaultDocletVersion;
+            }
+        } catch (MissingResourceException e) {
+            v = defaultDocletVersion;
+        }
+        docletVersion = v;
     }
 
-    private final String versionRBName = "jdk.javadoc.internal.tool.resources.version";
-    private ResourceBundle versionRB;
+    private static final String versionBundleName = "jdk.javadoc.internal.tool.resources.version";
+    private static final String defaultDocletVersion = System.getProperty("java.version");
+    public final String docletVersion;
 
-    /**
-     * Return the build date for the doclet.
-     * @return the build date
-     */
     @Override
-    public String getDocletSpecificBuildDate() {
-        if (versionRB == null) {
-            try {
-                versionRB = ResourceBundle.getBundle(versionRBName, getLocale());
-            } catch (MissingResourceException e) {
-                return BUILD_DATE;
-            }
-        }
-
-        try {
-            return versionRB.getString("release");
-        } catch (MissingResourceException e) {
-            return BUILD_DATE;
-        }
+    public String getDocletVersion() {
+        return docletVersion;
     }
 
     @Override
@@ -480,6 +474,16 @@
         return null;
     }
 
+    public DocFile getMainStylesheet() {
+        return stylesheetfile.isEmpty() ? null : DocFile.createFileForInput(this, stylesheetfile);
+    }
+
+    public List<DocFile> getAdditionalStylesheets() {
+        return additionalStylesheets.stream()
+                .map(ssf -> DocFile.createFileForInput(this, ssf))
+                .collect(Collectors.toList());
+    }
+
     /**
      * {@inheritDoc}
      */
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlDocletWriter.java	Thu Nov 16 15:12:13 2017 -0800
+++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlDocletWriter.java	Thu Nov 16 15:13:44 2017 -0800
@@ -25,9 +25,9 @@
 
 package jdk.javadoc.internal.doclets.formats.html;
 
+import jdk.javadoc.internal.doclets.formats.html.markup.Head;
 import jdk.javadoc.internal.doclets.formats.html.markup.TableHeader;
 
-import java.text.SimpleDateFormat;
 import java.util.*;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -412,28 +412,15 @@
             Content body) throws DocFileIOException {
         DocType htmlDocType = DocType.forVersion(configuration.htmlVersion);
         Content htmlComment = contents.newPage;
-        Content head = new HtmlTree(HtmlTag.HEAD);
-        head.addContent(getGeneratedBy(!configuration.notimestamp));
-        head.addContent(HtmlTree.TITLE(winTitle));
-        Content meta = HtmlTree.META("Content-Type", CONTENT_TYPE, configuration.charset);
-        head.addContent(meta);
-        if (!configuration.notimestamp) {
-            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
-            meta = HtmlTree.META(configuration.isOutputHtml5()
-                    ? "dc.created"
-                    : "date", dateFormat.format(new Date()));
-            head.addContent(meta);
-        }
-        if (metakeywords != null) {
-            for (String metakeyword : metakeywords) {
-                meta = HtmlTree.META("keywords", metakeyword);
-                head.addContent(meta);
-            }
-        }
-        addStyleSheetProperties(head);
-        addScriptProperties(head);
+        Head head = new Head(path, configuration.htmlVersion, configuration.docletVersion)
+                .setTimestamp(!configuration.notimestamp)
+                .setTitle(winTitle)
+                .setCharset(configuration.charset)
+                .addKeywords(metakeywords)
+                .setStylesheets(configuration.getMainStylesheet(), configuration.getAdditionalStylesheets())
+                .setIndex(configuration.createindex, mainBodyScript);
 
-        Content htmlTree = HtmlTree.HTML(configuration.getLocale().getLanguage(), head, body);
+        Content htmlTree = HtmlTree.HTML(configuration.getLocale().getLanguage(), head.toContent(), body);
         HtmlDocument htmlDocument = new HtmlDocument(htmlDocType, htmlComment, htmlTree);
         htmlDocument.write(DocFile.createFileForOutput(configuration, path));
     }
@@ -2071,78 +2058,6 @@
         return text;
     }
 
-    static final Set<String> blockTags = new HashSet<>();
-    static {
-        for (HtmlTag t: HtmlTag.values()) {
-            if (t.blockType == HtmlTag.BlockType.BLOCK)
-                blockTags.add(t.value);
-        }
-    }
-
-    /**
-     * Add a link to the stylesheet file.
-     *
-     * @param head the content tree to which the files will be added
-     */
-    public void addStyleSheetProperties(Content head) {
-        String stylesheetfile = configuration.stylesheetfile;
-        DocPath stylesheet;
-        if (stylesheetfile.isEmpty()) {
-            stylesheet = DocPaths.STYLESHEET;
-        } else {
-            DocFile file = DocFile.createFileForInput(configuration, stylesheetfile);
-            stylesheet = DocPath.create(file.getName());
-        }
-        HtmlTree link = HtmlTree.LINK("stylesheet", "text/css",
-                pathToRoot.resolve(stylesheet).getPath(),
-                "Style");
-        head.addContent(link);
-        addStylesheets(configuration, head);
-        if (configuration.createindex) {
-            HtmlTree jq_link = HtmlTree.LINK("stylesheet", "text/css",
-                    pathToRoot.resolve(DocPaths.JQUERY_FILES.resolve(DocPaths.JQUERY_STYLESHEET_FILE)).getPath(),
-                    "Style");
-            head.addContent(jq_link);
-        }
-    }
-
-    /**
-     * Add a link to the JavaScript file.
-     *
-     * @param head the content tree to which the files will be added
-     */
-    public void addScriptProperties(Content head) {
-        HtmlTree javascript = HtmlTree.SCRIPT(pathToRoot.resolve(DocPaths.JAVASCRIPT).getPath());
-        head.addContent(javascript);
-        if (configuration.createindex) {
-            if (pathToRoot != null && mainBodyScript != null) {
-                String ptrPath = pathToRoot.isEmpty() ? "." : pathToRoot.getPath();
-                mainBodyScript.append("var pathtoroot = ")
-                        .appendStringLiteral(ptrPath + "/")
-                        .append(";loadScripts(document, \'script\');");
-            }
-            addJQueryFile(head, DocPaths.JSZIP_MIN);
-            addJQueryFile(head, DocPaths.JSZIPUTILS_MIN);
-            head.addContent(new RawHtml("<!--[if IE]>"));
-            addJQueryFile(head, DocPaths.JSZIPUTILS_IE_MIN);
-            head.addContent(new RawHtml("<![endif]-->"));
-            addJQueryFile(head, DocPaths.JQUERY_JS_1_10);
-            addJQueryFile(head, DocPaths.JQUERY_JS);
-        }
-    }
-
-    /**
-     * Add a link to the JQuery javascript file.
-     *
-     * @param head the content tree to which the files will be added
-     * @param filePath the DocPath of the file that needs to be added
-     */
-    private void addJQueryFile(Content head, DocPath filePath) {
-        HtmlTree jqueryScriptFile = HtmlTree.SCRIPT(
-                pathToRoot.resolve(DocPaths.JQUERY_FILES.resolve(filePath)).getPath());
-        head.addContent(jqueryScriptFile);
-    }
-
     /**
      * According to
      * <cite>The Java&trade; Language Specification</cite>,
@@ -2188,7 +2103,7 @@
     }
 
     /**
-     * Adds the annotatation types for the given element.
+     * Adds the annotation types for the given element.
      *
      * @param element the package to write annotations for
      * @param htmltree the content tree to which the annotation types will be added
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/IndexRedirectWriter.java	Thu Nov 16 15:12:13 2017 -0800
+++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/IndexRedirectWriter.java	Thu Nov 16 15:13:44 2017 -0800
@@ -25,7 +25,7 @@
 
 package jdk.javadoc.internal.doclets.formats.html;
 
-import jdk.javadoc.internal.doclets.formats.html.markup.Comment;
+import jdk.javadoc.internal.doclets.formats.html.markup.Head;
 import jdk.javadoc.internal.doclets.formats.html.markup.ContentBuilder;
 import jdk.javadoc.internal.doclets.formats.html.markup.DocType;
 import jdk.javadoc.internal.doclets.formats.html.markup.HtmlAttr;
@@ -68,33 +68,27 @@
     void generateIndexFile() throws DocFileIOException {
         DocType htmlDocType = DocType.forVersion(configuration.htmlVersion);
         Content htmlComment = contents.newPage;
-        Content head = new HtmlTree(HtmlTag.HEAD);
-        head.addContent(getGeneratedBy(!configuration.notimestamp));
+        Head head = new Head(path, configuration.htmlVersion, configuration.docletVersion)
+                .setTimestamp(true, false)
+                .addDefaultScript(false);
 
         String title = (configuration.windowtitle.length() > 0)
                 ? configuration.windowtitle
                 : resources.getText("doclet.Generated_Docs_Untitled");
 
-        Content windowTitle = HtmlTree.TITLE(title);
-        head.addContent(windowTitle);
-        Content metaContentType = HtmlTree.META("Content", CONTENT_TYPE, configuration.charset);
-        head.addContent(metaContentType);
+        head.setTitle(title)
+                .setCharset(configuration.charset);
 
         String topFilePath = configuration.topFile.getPath();
         Script script = new Script("window.location.replace(")
                 .appendStringLiteral(topFilePath, '\'')
                 .append(")");
-        head.addContent(script.asContent());
-        HtmlTree metaRefresh = new HtmlTree(HtmlTag.META);
-        metaRefresh.addAttr(HtmlAttr.HTTP_EQUIV, "Refresh");
-        metaRefresh.addAttr(HtmlAttr.CONTENT, "0;" + topFilePath);
-        if (configuration.isOutputHtml5()) {
-            head.addContent(HtmlTree.NOSCRIPT(metaRefresh));
-        } else {
-            head.addContent(metaRefresh);
-        }
-
-        addStyleSheetProperties(configuration, head);
+        HtmlTree metaRefresh = new HtmlTree(HtmlTag.META)
+                .addAttr(HtmlAttr.HTTP_EQUIV, "Refresh")
+                .addAttr(HtmlAttr.CONTENT, "0;" + topFilePath);
+        head.addContent(
+                script.asContent(),
+                configuration.isOutputHtml5() ? HtmlTree.NOSCRIPT(metaRefresh) : metaRefresh);
 
         ContentBuilder bodyContent = new ContentBuilder();
         bodyContent.addContent(HtmlTree.NOSCRIPT(
@@ -110,7 +104,7 @@
             body.addContent(bodyContent);
         }
 
-        Content htmlTree = HtmlTree.HTML(configuration.getLocale().getLanguage(), head, body);
+        Content htmlTree = HtmlTree.HTML(configuration.getLocale().getLanguage(), head.toContent(), body);
         HtmlDocument htmlDocument = new HtmlDocument(htmlDocType, htmlComment, htmlTree);
         htmlDocument.write(DocFile.createFileForOutput(configuration, path));
     }
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/SourceToHTMLConverter.java	Thu Nov 16 15:12:13 2017 -0800
+++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/SourceToHTMLConverter.java	Thu Nov 16 15:13:44 2017 -0800
@@ -25,6 +25,8 @@
 
 package jdk.javadoc.internal.doclets.formats.html;
 
+import jdk.javadoc.internal.doclets.formats.html.markup.Head;
+
 import java.io.*;
 import java.util.List;
 
@@ -209,11 +211,14 @@
      */
     private void writeToFile(Content body, DocPath path) throws DocFileIOException {
         DocType htmlDocType = DocType.forVersion(configuration.htmlVersion);
-        Content head = new HtmlTree(HtmlTag.HEAD);
-        head.addContent(HtmlTree.TITLE(resources.getText("doclet.Window_Source_title")));
-        addStyleSheetProperties(head);
+        Head head = new Head(path, configuration.htmlVersion, configuration.docletVersion)
+//                .setTimestamp(!configuration.notimestamp) // temporary: compatibility!
+                .setTitle(resources.getText("doclet.Window_Source_title"))
+//                .setCharset(configuration.charset) // temporary: compatibility!
+                .addDefaultScript(false)
+                .setStylesheets(configuration.getMainStylesheet(), configuration.getAdditionalStylesheets());
         Content htmlTree = HtmlTree.HTML(configuration.getLocale().getLanguage(),
-                head, body);
+                head.toContent(), body);
         HtmlDocument htmlDocument = new HtmlDocument(htmlDocType, htmlTree);
         messages.notice("doclet.Generating_0", path.getPath());
         htmlDocument.write(DocFile.createFileForOutput(configuration, path));
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/Head.java	Thu Nov 16 15:13:44 2017 -0800
@@ -0,0 +1,327 @@
+/*
+ * Copyright (c) 2003, 2017, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package jdk.javadoc.internal.doclets.formats.html.markup;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.List;
+import java.util.TimeZone;
+
+import jdk.javadoc.internal.doclets.toolkit.Content;
+import jdk.javadoc.internal.doclets.toolkit.util.DocFile;
+import jdk.javadoc.internal.doclets.toolkit.util.DocPath;
+import jdk.javadoc.internal.doclets.toolkit.util.DocPaths;
+
+/**
+ * A builder for HTML HEAD elements.
+ *
+ * Many methods return the current object, to facilitate fluent builder-style usage.
+ *
+ *  <p><b>This is NOT part of any supported API.
+ *  If you write code that depends on this, you do so at your own risk.
+ *  This code and its internal interfaces are subject to change or
+ *  deletion without notice.</b>
+ */
+public class Head {
+    private final HtmlVersion htmlVersion;
+    private final String docletVersion;
+    private final DocPath pathToRoot;
+    private String title;
+    private String charset;
+    private final List<String> keywords;
+    private boolean showTimestamp;
+    private boolean showGeneratedBy;    // temporary: for compatibility
+    private boolean showMetaCreated;    // temporary: for compatibility
+    private DocFile mainStylesheetFile;
+    private List<DocFile> additionalStylesheetFiles = Collections.emptyList();
+    private boolean index;
+    private Script mainBodyScript;
+    private final List<Script> scripts;
+    private final List<Content> extraContent;
+    boolean addDefaultScript = true;
+
+    private static final Calendar calendar = new GregorianCalendar(TimeZone.getDefault());
+
+    /**
+     * Creates a {@code Head} object, for a given file and HTML version.
+     * The file is used to help determine the relative paths to stylesheet and script files.
+     * The HTML version is used to determine the the appropriate form of a META element
+     * recording the time the file was created.
+     * The doclet version should also be provided for recording in the file.
+     * @param path the path for the file that will include this HEAD element
+     * @param htmlVersion the HTML version
+     * @param docletVersion a string identifying the doclet version
+     */
+    public Head(DocPath path, HtmlVersion htmlVersion, String docletVersion) {
+        this.htmlVersion = htmlVersion;
+        this.docletVersion = docletVersion;
+        pathToRoot = path.parent().invert();
+        keywords = new ArrayList<>();
+        scripts = new ArrayList<>();
+        extraContent = new ArrayList<>();
+    }
+
+    /**
+     * Sets the title to appear in the TITLE element.
+     *
+     * @param title the title
+     * @return this object
+     */
+    public Head setTitle(String title) {
+        this.title = title;
+        return this;
+    }
+
+    /**
+     * Sets the charset to be declared in a META [@code Content-TYPE} element.
+     *
+     * @param charset the charset
+     * @return this object
+     */
+    // For temporary compatibility, this is currently optional.
+    // Eventually, this should be a required call.
+    public Head setCharset(String charset) {
+        this.charset = charset;
+        return this;
+    }
+
+    /**
+     * Adds a list of keywords to appear in META [@code keywords} elements.
+     *
+     * @param keywords the list of keywords, or null if none need to be added
+     * @return this object
+     */
+    public Head addKeywords(List<String> keywords) {
+        if (keywords != null) {
+            this.keywords.addAll(keywords);
+        }
+        return this;
+    }
+
+    /**
+     * Sets whether or not timestamps should be recorded in the HEAD element.
+     * The timestamp will be recorded in a comment, and in an appropriate META
+     * element, depending on the HTML version specified when this object was created.
+     *
+     * @param timestamp true if timestamps should be be added.
+     * @return this object
+     */
+    // For temporary backwards compatibiility, if this method is not called,
+    // no 'Generated by javadoc' comment will be added.
+    public Head setTimestamp(boolean timestamp) {
+        showTimestamp = timestamp;
+        showGeneratedBy = true;
+        showMetaCreated = timestamp;
+        return this;
+    }
+
+    /**
+     * Sets whether or not timestamps should be recorded in the HEAD element.
+     * The timestamp will be recorded in a comment, and possibly in an appropriate META
+     * element, depending on the HTML version specified when this object was created.
+     *
+     * @param timestamp true if timestamps should be be added.
+     * @param metaCreated  true if a META element should be added containing the timestamp
+     * @return this object
+     */
+    // This method is for temporary compatibility. In time, all clients should use
+    // {@code setTimestamp(boolean)}.
+    public Head setTimestamp(boolean timestamp, boolean metaCreated) {
+        showTimestamp = timestamp;
+        showGeneratedBy = true;
+        showMetaCreated = metaCreated;
+        return this;
+    }
+
+    /**
+     * Sets the main and any additional stylesheets to be listed in the HEAD element.
+     *
+     * @param main the main stylesheet, or null to use the default
+     * @param additional a list of any additional stylesheets to be included
+     * @return  this object
+     */
+    public Head setStylesheets(DocFile main, List<DocFile> additional) {
+        this.mainStylesheetFile = main;
+        this.additionalStylesheetFiles = additional;
+        return this;
+    }
+
+    /**
+     * Sets whether or not to include the supporting scripts and stylesheets for the
+     * "search" feature.
+     * If the feature is enabled, a {@code Script} must be provided into which some
+     * JavaScript code will be injected, to be executed during page loading. The value
+     * will be ignored if the feature is not enabled.
+     *
+     * @param index true if the supporting files are to be included
+     * @param mainBodyScript the {@code Script} object, or null
+     * @return this object
+     */
+    public Head setIndex(boolean index, Script mainBodyScript) {
+        this.index = index;
+        this.mainBodyScript = mainBodyScript;
+        return this;
+    }
+
+    /**
+     * Adds a script to be included in the HEAD element.
+     *
+     * @param script the script
+     * @return this object
+     */
+    public Head addScript(Script script) {
+        scripts.add(script);
+        return this;
+    }
+
+    /**
+     * Specifies whether or not to add a reference to a default script to be included
+     * in the HEAD element.
+     * The default script will normally be included; this method may be used to prevent that.
+     *
+     * @param addDefaultScript whether or not a default script will be included
+     * @return this object
+     */
+    public Head addDefaultScript(boolean addDefaultScript) {
+        this.addDefaultScript = addDefaultScript;
+        return this;
+    }
+
+    /**
+     * Adds additional content to be included in the HEAD element.
+     *
+     * @param contents the content
+     * @return this object
+     */
+    public Head addContent(Content... contents) {
+        extraContent.addAll(Arrays.asList(contents));
+        return this;
+    }
+
+    /**
+     * Returns the HTML for the HEAD element.
+     *
+     * @return the HTML
+     */
+    public Content toContent() {
+        Date now = showTimestamp ? calendar.getTime() : null;
+
+        HtmlTree tree = new HtmlTree(HtmlTag.HEAD);
+        if (showGeneratedBy) {
+            tree.addContent(getGeneratedBy(showTimestamp, now));
+        }
+        tree.addContent(HtmlTree.TITLE(title));
+
+        if (charset != null) { // compatibility; should this be allowed?
+            tree.addContent(HtmlTree.META("Content-Type", "text/html", charset));
+        }
+
+        if (showMetaCreated) {
+            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
+            tree.addContent(HtmlTree.META(
+                    (htmlVersion == HtmlVersion.HTML5) ? "dc.created" : "date",
+                    dateFormat.format(now)));
+        }
+
+        for (String k : keywords) {
+            tree.addContent(HtmlTree.META("keywords", k));
+        }
+
+        for (Content c : extraContent) {
+            tree.addContent(c);
+        }
+
+        addStylesheets(tree);
+        addScripts(tree);
+
+        return tree;
+    }
+
+    private Comment getGeneratedBy(boolean timestamp, Date now) {
+        String text = "Generated by javadoc"; // marker string, deliberately not localized
+        if (timestamp) {
+            text += " ("+ docletVersion + ") on " + now;
+        }
+        return new Comment(text);
+    }
+
+    private void addStylesheets(HtmlTree tree) {
+        DocPath mainStylesheet;
+        if (mainStylesheetFile == null) {
+            mainStylesheet = DocPaths.STYLESHEET;
+        } else {
+            mainStylesheet = DocPath.create(mainStylesheetFile.getName());
+        }
+        addStylesheet(tree, mainStylesheet);
+
+        for (DocFile file : additionalStylesheetFiles) {
+            addStylesheet(tree, DocPath.create(file.getName()));
+        }
+
+        if (index) {
+            addStylesheet(tree, DocPaths.JQUERY_FILES.resolve(DocPaths.JQUERY_STYLESHEET_FILE));
+        }
+    }
+
+    private void addStylesheet(HtmlTree tree, DocPath stylesheet) {
+        tree.addContent(HtmlTree.LINK("stylesheet", "text/css",
+                pathToRoot.resolve(stylesheet).getPath(), "Style"));
+    }
+
+    private void addScripts(HtmlTree tree) {
+        if (addDefaultScript) {
+            tree.addContent(HtmlTree.SCRIPT(pathToRoot.resolve(DocPaths.JAVASCRIPT).getPath()));
+        }
+        if (index) {
+            if (pathToRoot != null && mainBodyScript != null) {
+                String ptrPath = pathToRoot.isEmpty() ? "." : pathToRoot.getPath();
+                mainBodyScript.append("var pathtoroot = ")
+                        .appendStringLiteral(ptrPath + "/")
+                        .append(";loadScripts(document, \'script\');");
+            }
+            addJQueryFile(tree, DocPaths.JSZIP_MIN);
+            addJQueryFile(tree, DocPaths.JSZIPUTILS_MIN);
+            tree.addContent(new RawHtml("<!--[if IE]>"));
+            addJQueryFile(tree, DocPaths.JSZIPUTILS_IE_MIN);
+            tree.addContent(new RawHtml("<![endif]-->"));
+            addJQueryFile(tree, DocPaths.JQUERY_JS_1_10);
+            addJQueryFile(tree, DocPaths.JQUERY_JS);
+        }
+        for (Script script : scripts) {
+            tree.addContent(script.asContent());
+        }
+    }
+
+    private void addJQueryFile(HtmlTree tree, DocPath filePath) {
+        DocPath jqueryFile = pathToRoot.resolve(DocPaths.JQUERY_FILES.resolve(filePath));
+        tree.addContent(HtmlTree.SCRIPT(jqueryFile.getPath()));
+    }
+}
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/HtmlDocWriter.java	Thu Nov 16 15:12:13 2017 -0800
+++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/HtmlDocWriter.java	Thu Nov 16 15:13:44 2017 -0800
@@ -25,8 +25,6 @@
 
 package jdk.javadoc.internal.doclets.formats.html.markup;
 
-import java.util.*;
-
 import javax.lang.model.element.ModuleElement;
 import javax.lang.model.element.PackageElement;
 import javax.lang.model.element.TypeElement;
@@ -56,10 +54,7 @@
  */
 public abstract class HtmlDocWriter {
 
-    public static final String CONTENT_TYPE = "text/html";
-
     private final HtmlConfiguration configuration;
-    private final DocPath pathToRoot;
 
     /**
      * Constructor.
@@ -69,7 +64,6 @@
      */
     public HtmlDocWriter(HtmlConfiguration configuration, DocPath filename) {
         this.configuration = configuration;
-        this.pathToRoot = filename.parent().invert();
         Messages messages = configuration.getMessages();
         messages.notice("doclet.Generating_0",
             DocFile.createFileForOutput(configuration, filename).getPath());
@@ -304,49 +298,4 @@
         PackageElement encl = configuration.utils.containingPackage(te);
         return (encl.isUnnamed()) ? "" : (encl.getQualifiedName() + ".");
     }
-
-    /**
-     * Returns a link to the stylesheet file.
-     *
-     * @param configuration the configuration for this doclet
-     * @param head HtmlTree to which the stylesheet links will be added
-     */
-    public void addStyleSheetProperties(HtmlConfiguration configuration, Content head) {
-        String stylesheetfile = configuration.stylesheetfile;
-        DocPath stylesheet;
-        if (stylesheetfile.isEmpty()) {
-            stylesheet = DocPaths.STYLESHEET;
-        } else {
-            DocFile file = DocFile.createFileForInput(configuration, stylesheetfile);
-            stylesheet = DocPath.create(file.getName());
-        }
-        HtmlTree link = HtmlTree.LINK("stylesheet", "text/css",
-                pathToRoot.resolve(stylesheet).getPath(),
-                "Style");
-        head.addContent(link);
-        addStylesheets(configuration, head);
-    }
-
-    protected void addStylesheets(HtmlConfiguration configuration, Content tree) {
-        List<String> stylesheets = configuration.additionalStylesheets;
-        if (!stylesheets.isEmpty()) {
-            stylesheets.forEach((ssheet) -> {
-                DocFile file = DocFile.createFileForInput(configuration, ssheet);
-                DocPath ssheetPath = DocPath.create(file.getName());
-                HtmlTree slink = HtmlTree.LINK("stylesheet", "text/css", pathToRoot.resolve(ssheetPath).getPath(),
-                        "Style");
-                tree.addContent(slink);
-            });
-        }
-    }
-
-    protected Comment getGeneratedBy(boolean timestamp) {
-        String text = "Generated by javadoc"; // marker string, deliberately not localized
-        if (timestamp) {
-            Calendar calendar = new GregorianCalendar(TimeZone.getDefault());
-            Date today = calendar.getTime();
-            text += " ("+ configuration.getDocletSpecificBuildDate() + ") on " + today;
-        }
-        return new Comment(text);
-    }
 }
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/AbstractDoclet.java	Thu Nov 16 15:12:13 2017 -0800
+++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/AbstractDoclet.java	Thu Nov 16 15:13:44 2017 -0800
@@ -201,7 +201,7 @@
             return;
         }
         messages.notice("doclet.build_version",
-            configuration.getDocletSpecificBuildDate());
+            configuration.getDocletVersion());
         ClassTree classtree = new ClassTree(configuration, configuration.nodeprecated);
 
         generateClassFiles(docEnv, classtree);
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/BaseConfiguration.java	Thu Nov 16 15:12:13 2017 -0800
+++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/BaseConfiguration.java	Thu Nov 16 15:13:44 2017 -0800
@@ -307,11 +307,11 @@
     public abstract Resources getResources();
 
     /**
-     * Return the build date for the doclet.
+     * Returns a string identifying the version of the doclet.
      *
-     * @return the build date
+     * @return a version string
      */
-    public abstract String getDocletSpecificBuildDate();
+    public abstract String getDocletVersion();
 
     /**
      * This method should be defined in all those doclets (configurations),