1 /*
   2  * Copyright (c) 2017, 2018, 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.AttributeTree;
  29 import com.sun.source.doctree.AttributeTree.ValueKind;
  30 import com.sun.source.doctree.DocRootTree;
  31 import com.sun.source.doctree.DocTree;
  32 import com.sun.source.doctree.EndElementTree;
  33 import com.sun.source.doctree.LinkTree;
  34 import com.sun.source.doctree.StartElementTree;
  35 import com.sun.source.doctree.TextTree;
  36 import com.sun.source.util.SimpleDocTreeVisitor;
  37 import com.sun.tools.doclint.HtmlTag;
  38 import com.sun.tools.doclint.HtmlTag.Attr;
  39 import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree;
  40 import jdk.javadoc.internal.doclets.toolkit.Content;
  41 import jdk.javadoc.internal.doclets.toolkit.DocFileElement;
  42 import jdk.javadoc.internal.doclets.toolkit.DocFilesHandler;
  43 import jdk.javadoc.internal.doclets.toolkit.util.DocFile;
  44 import jdk.javadoc.internal.doclets.toolkit.util.DocFileIOException;
  45 import jdk.javadoc.internal.doclets.toolkit.util.DocPath;
  46 import jdk.javadoc.internal.doclets.toolkit.util.DocPaths;
  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 import java.util.Collections;
  55 import java.util.List;
  56 
  57 public class DocFilesHandlerImpl implements DocFilesHandler {
  58 
  59     public final Element element;
  60     public final Location location;
  61     public final DocPath  source;
  62     public final HtmlConfiguration configuration;
  63 
  64     /**
  65      * Constructor to construct the DocFilesWriter object.
  66      *
  67      * @param configuration the configuration of this doclet.
  68      * @param element the containing element of the doc-files.
  69      *
  70      */
  71     public DocFilesHandlerImpl(HtmlConfiguration configuration, Element element) {
  72         this.configuration = configuration;
  73         this.element = element;
  74 
  75         switch (element.getKind()) {
  76             case MODULE:
  77                 ModuleElement mdle = (ModuleElement)element;
  78                 location = configuration.utils.getLocationForModule(mdle);
  79                 source = DocPaths.DOC_FILES;
  80                 break;
  81             case PACKAGE:
  82                 PackageElement pkg = (PackageElement)element;
  83                 location = configuration.utils.getLocationForPackage(pkg);
  84                 // Note, given that we have a module-specific location,
  85                 // we want a module-relative path for the source, and not the
  86                 // standard path that may include the module directory
  87                 source = DocPath.create(pkg.getQualifiedName().toString().replace('.', '/'))
  88                         .resolve(DocPaths.DOC_FILES);
  89                 break;
  90             default:
  91                 throw new AssertionError("unsupported element " + element);
  92         }
  93     }
  94 
  95     /**
  96      * Copy doc-files directory and its contents from the source
  97      * elements directory to the generated documentation directory.
  98      *
  99      * @throws DocFileIOException if there is a problem while copying
 100      *         the documentation files
 101      */
 102 
 103     public void copyDocFiles()  throws DocFileIOException {
 104         boolean first = true;
 105         for (DocFile srcdir : DocFile.list(configuration, location, source)) {
 106             if (!srcdir.isDirectory()) {
 107                 continue;
 108             }
 109             DocPath path = null;
 110             switch (this.element.getKind()) {
 111                 case MODULE:
 112                     path = DocPaths.forModule((ModuleElement)this.element);
 113                     break;
 114                 case PACKAGE:
 115                     path = configuration.docPaths.forPackage((PackageElement)this.element);
 116                     break;
 117                 default:
 118                     throw new AssertionError("unknown kind:" + this.element.getKind());
 119             }
 120             copyDirectory(srcdir, path.resolve(DocPaths.DOC_FILES), first);
 121             first = false;
 122         }
 123     }
 124 
 125 
 126     private void copyDirectory(DocFile srcdir, final DocPath dstDocPath,
 127                                boolean first) throws DocFileIOException {
 128         DocFile dstdir = DocFile.createFileForOutput(configuration, dstDocPath);
 129         if (srcdir.isSameFile(dstdir)) {
 130             return;
 131         }
 132         for (DocFile srcfile: srcdir.list()) {
 133             DocFile destfile = dstdir.resolve(srcfile.getName());
 134             if (srcfile.isFile()) {
 135                 if (destfile.exists() && !first) {
 136                     configuration.messages.warning("doclet.Copy_Overwrite_warning",
 137                             srcfile.getPath(), dstdir.getPath());
 138                 } else {
 139                     if (Utils.toLowerCase(srcfile.getPath()).endsWith(".html")) {
 140                         if (handleHtmlFile(srcfile, dstDocPath)) {
 141                             continue;
 142                         }
 143                     }
 144                     configuration.messages.notice("doclet.Copying_File_0_To_Dir_1",
 145                             srcfile.getPath(), dstdir.getPath());
 146                     destfile.copyFile(srcfile);
 147                 }
 148             } else if (srcfile.isDirectory()) {
 149                 if (configuration.copydocfilesubdirs
 150                         && !configuration.shouldExcludeDocFileDir(srcfile.getName())) {
 151                     DocPath dirDocPath = dstDocPath.resolve(srcfile.getName());
 152                     copyDirectory(srcfile, dirDocPath, first);
 153                 }
 154             }
 155         }
 156     }
 157 
 158     private boolean handleHtmlFile(DocFile srcfile, DocPath dstPath) throws DocFileIOException {
 159         Utils utils = configuration.utils;
 160         FileObject fileObject = srcfile.getFileObject();
 161         DocFileElement dfElement = new DocFileElement(element, fileObject);
 162 
 163         if (shouldPassThrough(utils.getPreamble(dfElement))) {
 164             return false;
 165         }
 166 
 167         DocPath dfilePath = dstPath.resolve(srcfile.getName());
 168         HtmlDocletWriter docletWriter = new DocFileWriter(configuration, dfilePath, element);
 169         configuration.messages.notice("doclet.Generating_0", docletWriter.filename);
 170 
 171         String title = getWindowTitle(docletWriter, dfElement).trim();
 172         HtmlTree htmlContent = docletWriter.getBody(true, title);
 173         docletWriter.addTop(htmlContent);
 174         docletWriter.addNavLinks(true, htmlContent);
 175         List<? extends DocTree> fullBody = utils.getFullBody(dfElement);
 176         Content bodyContent = docletWriter.commentTagsToContent(null, dfElement, fullBody, false);
 177 
 178         docletWriter.addTagsInfo(dfElement, bodyContent);
 179         htmlContent.addContent(bodyContent);
 180 
 181         docletWriter.addNavLinks(false, htmlContent);
 182         docletWriter.addBottom(htmlContent);
 183         docletWriter.printHtmlDocument(Collections.emptyList(), false, htmlContent);
 184         return true;
 185     }
 186 
 187 
 188     private boolean shouldPassThrough(List<? extends DocTree> dtrees) {
 189         SimpleDocTreeVisitor<Boolean, Boolean> check = new SimpleDocTreeVisitor<Boolean, Boolean>() {
 190             @Override
 191             public Boolean visitStartElement(StartElementTree node, Boolean p) {
 192                 if (Utils.toLowerCase(node.getName().toString()).equals((Attr.STYLE.getText()))) {
 193                     return true;
 194                 }
 195                 if (Utils.toLowerCase(node.getName().toString()).equals(HtmlTag.LINK.getText())) {
 196                     for (DocTree dt : node.getAttributes()) {
 197                         if (this.visit(dt, true))
 198                             return true;
 199                     }
 200                 }
 201                 return false;
 202             }
 203 
 204             @Override
 205             public Boolean visitAttribute(AttributeTree node, Boolean p) {
 206                 if (p == null || p == false) {
 207                     return false;
 208                 }
 209                 if (Utils.toLowerCase(node.getName().toString()).equals("rel")) {
 210                     for (DocTree dt :  node.getValue()) {
 211                         Boolean found = new SimpleDocTreeVisitor<Boolean, ValueKind>() {
 212 
 213                             @Override
 214                             public Boolean visitText(TextTree node, ValueKind valueKind) {
 215                                 switch (valueKind) {
 216                                     case EMPTY:
 217                                         return false;
 218                                     default:
 219                                         return Utils.toLowerCase(node.getBody()).equals("stylesheet");
 220                                 }
 221                             }
 222 
 223                             @Override
 224                             protected Boolean defaultAction(DocTree node, ValueKind valueKind) {
 225                                 return false;
 226                             }
 227 
 228                         }.visit(dt, node.getValueKind());
 229 
 230                         if (found)
 231                             return true;
 232                     }
 233                 }
 234                 return false;
 235             }
 236 
 237             @Override
 238             protected Boolean defaultAction(DocTree node, Boolean p) {
 239                 return false;
 240             }
 241         };
 242         for (DocTree dt : dtrees) {
 243             if (check.visit(dt, false))
 244                 return true;
 245         }
 246         return false;
 247     }
 248 
 249     private String getWindowTitle(HtmlDocletWriter docletWriter, Element element) {
 250         List<? extends DocTree> preamble = configuration.utils.getPreamble(element);
 251         StringBuilder sb = new StringBuilder();
 252         boolean titleFound = false;
 253         loop:
 254         for (DocTree dt : preamble) {
 255             switch (dt.getKind()) {
 256                 case START_ELEMENT:
 257                     StartElementTree nodeStart = (StartElementTree)dt;
 258                     if (Utils.toLowerCase(nodeStart.getName().toString()).equals("title")) {
 259                         titleFound = true;
 260                     }
 261                     break;
 262 
 263                 case END_ELEMENT:
 264                     EndElementTree nodeEnd = (EndElementTree)dt;
 265                     if (Utils.toLowerCase(nodeEnd.getName().toString()).equals("title")) {
 266                         break loop;
 267                     }
 268                     break;
 269 
 270                 case TEXT:
 271                     TextTree nodeText = (TextTree)dt;
 272                     if (titleFound)
 273                         sb.append(nodeText.getBody());
 274                     break;
 275 
 276                 default:
 277                     // do nothing
 278             }
 279         }
 280         return docletWriter.getWindowTitle(sb.toString().trim());
 281     }
 282 
 283     private static class DocFileWriter extends HtmlDocletWriter {
 284 
 285         final PackageElement pkg;
 286 
 287         /**
 288          * Constructor to construct the HtmlDocletWriter object.
 289          *
 290          * @param configuration the configuruation of this doclet.
 291          * @param path          the file to be generated.
 292          * @param e             the anchoring element.
 293          */
 294         public DocFileWriter(HtmlConfiguration configuration, DocPath path, Element e) {
 295             super(configuration, path);
 296             switch (e.getKind()) {
 297                 case PACKAGE:
 298                     pkg = (PackageElement)e;
 299                     break;
 300                 default:
 301                     throw new AssertionError("unsupported element: " + e.getKind());
 302             }
 303         }
 304 
 305         /**
 306          * Get the module link.
 307          *
 308          * @return a content tree for the module link
 309          */
 310         @Override
 311         protected Content getNavLinkModule() {
 312             Content linkContent = getModuleLink(utils.elementUtils.getModuleOf(pkg),
 313                     contents.moduleLabel);
 314             Content li = HtmlTree.LI(linkContent);
 315             return li;
 316         }
 317 
 318         /**
 319          * Get this package link.
 320          *
 321          * @return a content tree for the package link
 322          */
 323         @Override
 324         protected Content getNavLinkPackage() {
 325             Content linkContent = getPackageLink(pkg,
 326                     contents.packageLabel);
 327             Content li = HtmlTree.LI(linkContent);
 328             return li;
 329         }
 330     }
 331 }