1 /* 2 * Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package jdk.javadoc.internal.doclets.formats.html; 27 28 import com.sun.source.doctree.DocTree; 29 import com.sun.source.doctree.EndElementTree; 30 import com.sun.source.doctree.StartElementTree; 31 import com.sun.source.doctree.TextTree; 32 import com.sun.source.util.DocTreeFactory; 33 import com.sun.tools.doclint.HtmlTag; 34 import jdk.javadoc.internal.doclets.formats.html.markup.BodyContents; 35 import jdk.javadoc.internal.doclets.formats.html.markup.ContentBuilder; 36 import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle; 37 import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; 38 import jdk.javadoc.internal.doclets.formats.html.markup.Navigation; 39 import jdk.javadoc.internal.doclets.toolkit.Content; 40 import jdk.javadoc.internal.doclets.toolkit.DocFileElement; 41 import jdk.javadoc.internal.doclets.toolkit.DocFilesHandler; 42 import jdk.javadoc.internal.doclets.toolkit.util.DocFile; 43 import jdk.javadoc.internal.doclets.toolkit.util.DocFileIOException; 44 import jdk.javadoc.internal.doclets.toolkit.util.DocPath; 45 import jdk.javadoc.internal.doclets.toolkit.util.DocPaths; 46 import jdk.javadoc.internal.doclets.toolkit.util.DocletConstants; 47 import jdk.javadoc.internal.doclets.toolkit.util.Utils; 48 49 import javax.lang.model.element.Element; 50 import javax.lang.model.element.ModuleElement; 51 import javax.lang.model.element.PackageElement; 52 import javax.tools.FileObject; 53 import javax.tools.JavaFileManager.Location; 54 55 import java.util.ArrayList; 56 import java.util.Collections; 57 import java.util.List; 58 59 import jdk.javadoc.internal.doclets.formats.html.markup.Navigation.PageMode; 60 61 public class DocFilesHandlerImpl implements DocFilesHandler { 62 63 public final Element element; 64 public final Location location; 65 public final DocPath source; 66 public final HtmlConfiguration configuration; 67 private final HtmlOptions options; 68 private Navigation navBar; 69 70 /** 71 * Constructor to construct the DocFilesWriter object. 72 * 73 * @param configuration the configuration of this doclet. 74 * @param element the containing element of the doc-files. 75 * 76 */ 77 public DocFilesHandlerImpl(HtmlConfiguration configuration, Element element) { 78 this.configuration = configuration; 79 this.options = configuration.getOptions(); 80 this.element = element; 81 82 switch (element.getKind()) { 83 case MODULE: 84 ModuleElement mdle = (ModuleElement)element; 85 location = configuration.utils.getLocationForModule(mdle); 86 source = DocPaths.DOC_FILES; 87 break; 88 case PACKAGE: 89 PackageElement pkg = (PackageElement)element; 90 location = configuration.utils.getLocationForPackage(pkg); 91 // Note, given that we have a module-specific location, 92 // we want a module-relative path for the source, and not the 93 // standard path that may include the module directory 94 source = DocPath.create(pkg.getQualifiedName().toString().replace('.', '/')) 95 .resolve(DocPaths.DOC_FILES); 96 break; 97 default: 98 throw new AssertionError("unsupported element " + element); 99 } 100 } 101 102 /** 103 * Copy doc-files directory and its contents from the source 104 * elements directory to the generated documentation directory. 105 * 106 * @throws DocFileIOException if there is a problem while copying 107 * the documentation files 108 */ 109 110 public void copyDocFiles() throws DocFileIOException { 111 boolean first = true; 112 for (DocFile srcdir : DocFile.list(configuration, location, source)) { 113 if (!srcdir.isDirectory()) { 114 continue; 115 } 116 DocPath path = null; 117 switch (this.element.getKind()) { 118 case MODULE: 119 path = DocPaths.forModule((ModuleElement)this.element); 120 break; 121 case PACKAGE: 122 path = configuration.docPaths.forPackage((PackageElement)this.element); 123 break; 124 default: 125 throw new AssertionError("unknown kind:" + this.element.getKind()); 126 } 127 copyDirectory(srcdir, path.resolve(DocPaths.DOC_FILES), first); 128 first = false; 129 } 130 } 131 132 public List<DocPath> getStylesheets() throws DocFileIOException { 133 List<DocPath> stylesheets = new ArrayList<DocPath>(); 134 for (DocFile srcdir : DocFile.list(configuration, location, source)) { 135 for (DocFile srcFile : srcdir.list()) { 136 if (srcFile.getName().endsWith(".css")) 137 stylesheets.add(DocPaths.DOC_FILES.resolve(srcFile.getName())); 138 } 139 } 140 return stylesheets; 141 } 142 143 private void copyDirectory(DocFile srcdir, final DocPath dstDocPath, 144 boolean first) throws DocFileIOException { 145 DocFile dstdir = DocFile.createFileForOutput(configuration, dstDocPath); 146 if (srcdir.isSameFile(dstdir)) { 147 return; 148 } 149 for (DocFile srcfile: srcdir.list()) { 150 DocFile destfile = dstdir.resolve(srcfile.getName()); 151 if (srcfile.isFile()) { 152 if (destfile.exists() && !first) { 153 configuration.messages.warning("doclet.Copy_Overwrite_warning", 154 srcfile.getPath(), dstdir.getPath()); 155 } else { 156 if (Utils.toLowerCase(srcfile.getPath()).endsWith(".html")) { 157 handleHtmlFile(srcfile, dstDocPath); 158 } else { 159 configuration.messages.notice("doclet.Copying_File_0_To_Dir_1", 160 srcfile.getPath(), dstdir.getPath()); 161 destfile.copyFile(srcfile); 162 } 163 } 164 } else if (srcfile.isDirectory()) { 165 if (options.copyDocfileSubdirs 166 && !configuration.shouldExcludeDocFileDir(srcfile.getName())) { 167 DocPath dirDocPath = dstDocPath.resolve(srcfile.getName()); 168 copyDirectory(srcfile, dirDocPath, first); 169 } 170 } 171 } 172 } 173 174 private void handleHtmlFile(DocFile srcfile, DocPath dstPath) throws DocFileIOException { 175 Utils utils = configuration.utils; 176 FileObject fileObject = srcfile.getFileObject(); 177 DocFileElement dfElement = new DocFileElement(utils, element, fileObject); 178 179 DocPath dfilePath = dstPath.resolve(srcfile.getName()); 180 HtmlDocletWriter docletWriter = new DocFileWriter(configuration, dfilePath, element); 181 configuration.messages.notice("doclet.Generating_0", docletWriter.filename.getPath()); 182 183 List<? extends DocTree> localTags = getLocalHeaderTags(utils.getPreamble(dfElement)); 184 Content localTagsContent = docletWriter.commentTagsToContent(null, dfElement, localTags, false); 185 186 String title = getWindowTitle(docletWriter, dfElement).trim(); 187 HtmlTree htmlContent = docletWriter.getBody(title); 188 PackageElement pkg = dfElement.getPackageElement(); 189 this.navBar = new Navigation(element, configuration, PageMode.DOCFILE, docletWriter.path); 190 Content headerContent = new ContentBuilder(); 191 docletWriter.addTop(headerContent); 192 Content mdleLinkContent = docletWriter.getModuleLink(utils.elementUtils.getModuleOf(pkg), 193 docletWriter.contents.moduleLabel); 194 navBar.setNavLinkModule(mdleLinkContent); 195 Content pkgLinkContent = docletWriter.getPackageLink(pkg, docletWriter.contents.packageLabel); 196 navBar.setNavLinkPackage(pkgLinkContent); 197 navBar.setUserHeader(docletWriter.getUserHeaderFooter(true)); 198 headerContent.add(navBar.getContent(true)); 199 200 List<? extends DocTree> fullBody = utils.getFullBody(dfElement); 201 Content pageContent = docletWriter.commentTagsToContent(null, dfElement, fullBody, false); 202 docletWriter.addTagsInfo(dfElement, pageContent); 203 204 navBar.setUserFooter(docletWriter.getUserHeaderFooter(false)); 205 Content footer = HtmlTree.FOOTER(); 206 footer.add(navBar.getContent(false)); 207 docletWriter.addBottom(footer); 208 htmlContent.add(new BodyContents() 209 .setHeader(headerContent) 210 .addMainContent(HtmlTree.DIV(HtmlStyle.contentContainer, pageContent)) 211 .setFooter(footer) 212 .toContent()); 213 docletWriter.printHtmlDocument(Collections.emptyList(), null, localTagsContent, Collections.emptyList(), htmlContent); 214 } 215 216 217 private List<? extends DocTree> getLocalHeaderTags(List<? extends DocTree> dtrees) { 218 List<DocTree> localTags = new ArrayList<>(); 219 DocTreeFactory docTreeFactory = configuration.docEnv.getDocTrees().getDocTreeFactory(); 220 boolean inHead = false; 221 boolean inTitle = false; 222 loop: 223 for (DocTree dt : dtrees) { 224 switch (dt.getKind()) { 225 case START_ELEMENT: 226 StartElementTree startElem = (StartElementTree)dt; 227 switch (HtmlTag.get(startElem.getName())) { 228 case HEAD: 229 inHead = true; 230 break; 231 case META: 232 break; 233 case TITLE: 234 inTitle = true; 235 break; 236 default: 237 if (inHead) { 238 localTags.add(startElem); 239 localTags.add(docTreeFactory.newTextTree(DocletConstants.NL)); 240 } 241 } 242 break; 243 case END_ELEMENT: 244 EndElementTree endElem = (EndElementTree)dt; 245 switch (HtmlTag.get(endElem.getName())) { 246 case HEAD: 247 inHead = false; 248 break loop; 249 case TITLE: 250 inTitle = false; 251 break; 252 default: 253 if (inHead) { 254 localTags.add(endElem); 255 localTags.add(docTreeFactory.newTextTree(DocletConstants.NL)); 256 } 257 } 258 break; 259 case ENTITY: 260 case TEXT: 261 if (inHead && !inTitle) { 262 localTags.add(dt); 263 } 264 break; 265 } 266 } 267 return localTags; 268 } 269 270 private String getWindowTitle(HtmlDocletWriter docletWriter, Element element) { 271 List<? extends DocTree> preamble = configuration.utils.getPreamble(element); 272 StringBuilder sb = new StringBuilder(); 273 boolean titleFound = false; 274 loop: 275 for (DocTree dt : preamble) { 276 switch (dt.getKind()) { 277 case START_ELEMENT: 278 StartElementTree nodeStart = (StartElementTree)dt; 279 if (Utils.toLowerCase(nodeStart.getName().toString()).equals("title")) { 280 titleFound = true; 281 } 282 break; 283 284 case END_ELEMENT: 285 EndElementTree nodeEnd = (EndElementTree)dt; 286 if (Utils.toLowerCase(nodeEnd.getName().toString()).equals("title")) { 287 break loop; 288 } 289 break; 290 291 case TEXT: 292 TextTree nodeText = (TextTree)dt; 293 if (titleFound) 294 sb.append(nodeText.getBody()); 295 break; 296 297 default: 298 // do nothing 299 } 300 } 301 return docletWriter.getWindowTitle(sb.toString().trim()); 302 } 303 304 private static class DocFileWriter extends HtmlDocletWriter { 305 306 /** 307 * Constructor to construct the HtmlDocletWriter object. 308 * 309 * @param configuration the configuration of this doclet. 310 * @param path the file to be generated. 311 * @param e the anchoring element. 312 */ 313 public DocFileWriter(HtmlConfiguration configuration, DocPath path, Element e) { 314 super(configuration, path); 315 switch (e.getKind()) { 316 case PACKAGE: 317 case MODULE: 318 break; 319 default: 320 throw new AssertionError("unsupported element: " + e.getKind()); 321 } 322 } 323 } 324 }