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 Navigation navBar;
  68 
  69     /**
  70      * Constructor to construct the DocFilesWriter object.
  71      *
  72      * @param configuration the configuration of this doclet.
  73      * @param element the containing element of the doc-files.
  74      *
  75      */
  76     public DocFilesHandlerImpl(HtmlConfiguration configuration, Element element) {
  77         this.configuration = configuration;
  78         this.element = element;
  79 
  80         switch (element.getKind()) {
  81             case MODULE:
  82                 ModuleElement mdle = (ModuleElement)element;
  83                 location = configuration.utils.getLocationForModule(mdle);
  84                 source = DocPaths.DOC_FILES;
  85                 break;
  86             case PACKAGE:
  87                 PackageElement pkg = (PackageElement)element;
  88                 location = configuration.utils.getLocationForPackage(pkg);
  89                 // Note, given that we have a module-specific location,
  90                 // we want a module-relative path for the source, and not the
  91                 // standard path that may include the module directory
  92                 source = DocPath.create(pkg.getQualifiedName().toString().replace('.', '/'))
  93                         .resolve(DocPaths.DOC_FILES);
  94                 break;
  95             default:
  96                 throw new AssertionError("unsupported element " + element);
  97         }
  98     }
  99 
 100     /**
 101      * Copy doc-files directory and its contents from the source
 102      * elements directory to the generated documentation directory.
 103      *
 104      * @throws DocFileIOException if there is a problem while copying
 105      *         the documentation files
 106      */
 107 
 108     public void copyDocFiles()  throws DocFileIOException {
 109         boolean first = true;
 110         for (DocFile srcdir : DocFile.list(configuration, location, source)) {
 111             if (!srcdir.isDirectory()) {
 112                 continue;
 113             }
 114             DocPath path = null;
 115             switch (this.element.getKind()) {
 116                 case MODULE:
 117                     path = DocPaths.forModule((ModuleElement)this.element);
 118                     break;
 119                 case PACKAGE:
 120                     path = configuration.docPaths.forPackage((PackageElement)this.element);
 121                     break;
 122                 default:
 123                     throw new AssertionError("unknown kind:" + this.element.getKind());
 124             }
 125             copyDirectory(srcdir, path.resolve(DocPaths.DOC_FILES), first);
 126             first = false;
 127         }
 128     }
 129 
 130     public List<DocPath> getStylesheets() throws DocFileIOException {
 131         List<DocPath> stylesheets = new ArrayList<DocPath>();
 132         for (DocFile srcdir : DocFile.list(configuration, location, source)) {
 133             for (DocFile srcFile : srcdir.list()) {
 134                 if (srcFile.getName().endsWith(".css"))
 135                     stylesheets.add(DocPaths.DOC_FILES.resolve(srcFile.getName()));
 136             }
 137         }
 138         return stylesheets;
 139     }
 140 
 141     private void copyDirectory(DocFile srcdir, final DocPath dstDocPath,
 142                                boolean first) throws DocFileIOException {
 143         DocFile dstdir = DocFile.createFileForOutput(configuration, dstDocPath);
 144         if (srcdir.isSameFile(dstdir)) {
 145             return;
 146         }
 147         for (DocFile srcfile: srcdir.list()) {
 148             DocFile destfile = dstdir.resolve(srcfile.getName());
 149             if (srcfile.isFile()) {
 150                 if (destfile.exists() && !first) {
 151                     configuration.messages.warning("doclet.Copy_Overwrite_warning",
 152                             srcfile.getPath(), dstdir.getPath());
 153                 } else {
 154                     if (Utils.toLowerCase(srcfile.getPath()).endsWith(".html")) {
 155                         handleHtmlFile(srcfile, dstDocPath);
 156                     } else {
 157                         configuration.messages.notice("doclet.Copying_File_0_To_Dir_1",
 158                                 srcfile.getPath(), dstdir.getPath());
 159                         destfile.copyFile(srcfile);
 160                     }
 161                 }
 162             } else if (srcfile.isDirectory()) {
 163                 if (configuration.copydocfilesubdirs
 164                         && !configuration.shouldExcludeDocFileDir(srcfile.getName())) {
 165                     DocPath dirDocPath = dstDocPath.resolve(srcfile.getName());
 166                     copyDirectory(srcfile, dirDocPath, first);
 167                 }
 168             }
 169         }
 170     }
 171 
 172     private void handleHtmlFile(DocFile srcfile, DocPath dstPath) throws DocFileIOException {
 173         Utils utils = configuration.utils;
 174         FileObject fileObject = srcfile.getFileObject();
 175         DocFileElement dfElement = new DocFileElement(utils, element, fileObject);
 176 
 177         DocPath dfilePath = dstPath.resolve(srcfile.getName());
 178         HtmlDocletWriter docletWriter = new DocFileWriter(configuration, dfilePath, element);
 179         configuration.messages.notice("doclet.Generating_0", docletWriter.filename.getPath());
 180 
 181         List<? extends DocTree> localTags = getLocalHeaderTags(utils.getPreamble(dfElement));
 182         Content localTagsContent = docletWriter.commentTagsToContent(null, dfElement, localTags, false);
 183 
 184         String title = getWindowTitle(docletWriter, dfElement).trim();
 185         HtmlTree htmlContent = docletWriter.getBody(title);
 186         PackageElement pkg = dfElement.getPackageElement();
 187         this.navBar = new Navigation(element, configuration, PageMode.DOCFILE, docletWriter.path);
 188         Content headerContent = new ContentBuilder();
 189         docletWriter.addTop(headerContent);
 190         Content mdleLinkContent = docletWriter.getModuleLink(utils.elementUtils.getModuleOf(pkg),
 191                 docletWriter.contents.moduleLabel);
 192         navBar.setNavLinkModule(mdleLinkContent);
 193         Content pkgLinkContent = docletWriter.getPackageLink(pkg, docletWriter.contents.packageLabel);
 194         navBar.setNavLinkPackage(pkgLinkContent);
 195         navBar.setUserHeader(docletWriter.getUserHeaderFooter(true));
 196         headerContent.add(navBar.getContent(true));
 197 
 198         List<? extends DocTree> fullBody = utils.getFullBody(dfElement);
 199         Content pageContent = docletWriter.commentTagsToContent(null, dfElement, fullBody, false);
 200         docletWriter.addTagsInfo(dfElement, pageContent);
 201 
 202         navBar.setUserFooter(docletWriter.getUserHeaderFooter(false));
 203         Content footer = HtmlTree.FOOTER();
 204         footer.add(navBar.getContent(false));
 205         docletWriter.addBottom(footer);
 206         htmlContent.add(new BodyContents()
 207                 .setHeader(headerContent)
 208                 .addMainContent(HtmlTree.DIV(HtmlStyle.contentContainer, pageContent))
 209                 .setFooter(footer)
 210                 .toContent());
 211         docletWriter.printHtmlDocument(Collections.emptyList(), null, localTagsContent, Collections.emptyList(), htmlContent);
 212     }
 213 
 214 
 215     private List<? extends DocTree> getLocalHeaderTags(List<? extends DocTree> dtrees) {
 216         List<DocTree> localTags = new ArrayList<>();
 217         DocTreeFactory docTreeFactory = configuration.docEnv.getDocTrees().getDocTreeFactory();
 218         boolean inHead = false;
 219         boolean inTitle = false;
 220         loop:
 221         for (DocTree dt : dtrees) {
 222             switch (dt.getKind()) {
 223                 case START_ELEMENT:
 224                     StartElementTree startElem = (StartElementTree)dt;
 225                     switch (HtmlTag.get(startElem.getName())) {
 226                         case HEAD:
 227                             inHead = true;
 228                             break;
 229                         case META:
 230                             break;
 231                         case TITLE:
 232                             inTitle = true;
 233                             break;
 234                         default:
 235                             if (inHead) {
 236                                 localTags.add(startElem);
 237                                 localTags.add(docTreeFactory.newTextTree(DocletConstants.NL));
 238                             }
 239                     }
 240                     break;
 241                 case END_ELEMENT:
 242                     EndElementTree endElem = (EndElementTree)dt;
 243                     switch (HtmlTag.get(endElem.getName())) {
 244                         case HEAD:
 245                             inHead = false;
 246                             break loop;
 247                         case TITLE:
 248                             inTitle = false;
 249                             break;
 250                         default:
 251                             if (inHead) {
 252                                 localTags.add(endElem);
 253                                 localTags.add(docTreeFactory.newTextTree(DocletConstants.NL));
 254                             }
 255                     }
 256                     break;
 257                 case ENTITY:
 258                 case TEXT:
 259                     if (inHead && !inTitle) {
 260                         localTags.add(dt);
 261                     }
 262                     break;
 263             }
 264         }
 265         return localTags;
 266     }
 267 
 268     private String getWindowTitle(HtmlDocletWriter docletWriter, Element element) {
 269         List<? extends DocTree> preamble = configuration.utils.getPreamble(element);
 270         StringBuilder sb = new StringBuilder();
 271         boolean titleFound = false;
 272         loop:
 273         for (DocTree dt : preamble) {
 274             switch (dt.getKind()) {
 275                 case START_ELEMENT:
 276                     StartElementTree nodeStart = (StartElementTree)dt;
 277                     if (Utils.toLowerCase(nodeStart.getName().toString()).equals("title")) {
 278                         titleFound = true;
 279                     }
 280                     break;
 281 
 282                 case END_ELEMENT:
 283                     EndElementTree nodeEnd = (EndElementTree)dt;
 284                     if (Utils.toLowerCase(nodeEnd.getName().toString()).equals("title")) {
 285                         break loop;
 286                     }
 287                     break;
 288 
 289                 case TEXT:
 290                     TextTree nodeText = (TextTree)dt;
 291                     if (titleFound)
 292                         sb.append(nodeText.getBody());
 293                     break;
 294 
 295                 default:
 296                     // do nothing
 297             }
 298         }
 299         return docletWriter.getWindowTitle(sb.toString().trim());
 300     }
 301 
 302     private static class DocFileWriter extends HtmlDocletWriter {
 303 
 304         /**
 305          * Constructor to construct the HtmlDocletWriter object.
 306          *
 307          * @param configuration the configuration of this doclet.
 308          * @param path          the file to be generated.
 309          * @param e             the anchoring element.
 310          */
 311         public DocFileWriter(HtmlConfiguration configuration, DocPath path, Element e) {
 312             super(configuration, path);
 313             switch (e.getKind()) {
 314                 case PACKAGE:
 315                 case MODULE:
 316                     break;
 317                 default:
 318                     throw new AssertionError("unsupported element: " + e.getKind());
 319             }
 320         }
 321     }
 322 }