blob: 6fc4c819dbd328685e1bf4977e115e081775a26e [file] [log] [blame]
/*
* Copyright (c) 2003, 2018, 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 boolean useModuleDirectories;
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;
private boolean addDefaultScript = true;
private DocPath canonicalLink;
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 the module directories should be used. This is used to set the JavaScript variable.
*
* @param useModuleDirectories true if the module directories should be used
* @return this object
*/
public Head setUseModuleDirectories(boolean useModuleDirectories) {
this.useModuleDirectories = useModuleDirectories;
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;
}
/**
* Specifies a value for a
* <a href="https://en.wikipedia.org/wiki/Canonical_link_element">canonical link</a>
* in the {@code <head>} element.
* @param link
*/
public void setCanonicalLink(DocPath link) {
this.canonicalLink = link;
}
/**
* 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);
}
if (canonicalLink != null) {
HtmlTree link = new HtmlTree(HtmlTag.LINK);
link.addAttr(HtmlAttr.REL, "canonical");
link.addAttr(HtmlAttr.HREF, canonicalLink.getPath());
tree.addContent(link);
}
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(";\n")
.append("var useModuleDirectories = " + useModuleDirectories + ";\n")
.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_3_3);
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()));
}
}