1 /*
   2  * Copyright (c) 2001, 2020, 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 jdk.javadoc.internal.doclets.formats.html.markup.Head;
  29 
  30 import java.io.*;
  31 import java.util.List;
  32 
  33 import javax.lang.model.element.Element;
  34 import javax.lang.model.element.ModuleElement;
  35 import javax.lang.model.element.PackageElement;
  36 import javax.lang.model.element.TypeElement;
  37 import javax.tools.FileObject;
  38 
  39 import jdk.javadoc.doclet.DocletEnvironment;
  40 import jdk.javadoc.internal.doclets.formats.html.markup.HtmlAttr;
  41 import jdk.javadoc.internal.doclets.formats.html.markup.HtmlDocument;
  42 import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle;
  43 import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTag;
  44 import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree;
  45 import jdk.javadoc.internal.doclets.formats.html.markup.StringContent;
  46 import jdk.javadoc.internal.doclets.toolkit.Content;
  47 import jdk.javadoc.internal.doclets.toolkit.Messages;
  48 import jdk.javadoc.internal.doclets.toolkit.Resources;
  49 import jdk.javadoc.internal.doclets.toolkit.util.DocFile;
  50 import jdk.javadoc.internal.doclets.toolkit.util.DocFileIOException;
  51 import jdk.javadoc.internal.doclets.toolkit.util.DocPath;
  52 import jdk.javadoc.internal.doclets.toolkit.util.DocPaths;
  53 import jdk.javadoc.internal.doclets.toolkit.util.DocletConstants;
  54 import jdk.javadoc.internal.doclets.toolkit.util.SimpleDocletException;
  55 import jdk.javadoc.internal.doclets.toolkit.util.Utils;
  56 
  57 /**
  58  * Converts Java Source Code to HTML.
  59  *
  60  *  <p><b>This is NOT part of any supported API.
  61  *  If you write code that depends on this, you do so at your own risk.
  62  *  This code and its internal interfaces are subject to change or
  63  *  deletion without notice.</b>
  64  */
  65 public class SourceToHTMLConverter {
  66 
  67     /**
  68      * The number of trailing blank lines at the end of the page.
  69      * This is inserted so that anchors at the bottom of small pages
  70      * can be reached.
  71      */
  72     private static final int NUM_BLANK_LINES = 60;
  73 
  74     /**
  75      * New line to be added to the documentation.
  76      */
  77     private static final String NEW_LINE = DocletConstants.NL;
  78 
  79     private final HtmlConfiguration configuration;
  80     private final Messages messages;
  81     private final Resources resources;
  82     private final Utils utils;
  83 
  84     private final DocletEnvironment docEnv;
  85 
  86     private final DocPath outputdir;
  87 
  88     /**
  89      * Relative path from the documentation root to the file that is being
  90      * generated.
  91      */
  92     private DocPath relativePath = DocPath.empty;
  93 
  94     private SourceToHTMLConverter(HtmlConfiguration configuration, DocletEnvironment rd,
  95                                   DocPath outputdir) {
  96         this.configuration  = configuration;
  97         this.messages = configuration.getMessages();
  98         this.resources = configuration.resources;
  99         this.utils = configuration.utils;
 100         this.docEnv = rd;
 101         this.outputdir = outputdir;
 102     }
 103 
 104     /**
 105      * Translate the TypeElements in the given DocletEnvironment to HTML representation.
 106      *
 107      * @param configuration the configuration.
 108      * @param docEnv the DocletEnvironment to convert.
 109      * @param outputdir the name of the directory to output to.
 110      * @throws DocFileIOException if there is a problem generating an output file
 111      * @throws SimpleDocletException if there is a problem reading a source file
 112      */
 113     public static void convertRoot(HtmlConfiguration configuration, DocletEnvironment docEnv,
 114                                    DocPath outputdir) throws DocFileIOException, SimpleDocletException {
 115         new SourceToHTMLConverter(configuration, docEnv, outputdir).generate();
 116     }
 117 
 118     void generate() throws DocFileIOException, SimpleDocletException {
 119         if (docEnv == null || outputdir == null) {
 120             return;
 121         }
 122         for (ModuleElement mdl : configuration.getSpecifiedModuleElements()) {
 123             // If -nodeprecated option is set and the module is marked as deprecated,
 124             // do not convert the module files to HTML.
 125             if (!(configuration.nodeprecated && utils.isDeprecated(mdl)))
 126                 convertModule(mdl, outputdir);
 127         }
 128         for (PackageElement pkg : configuration.getSpecifiedPackageElements()) {
 129             // If -nodeprecated option is set and the package is marked as deprecated,
 130             // do not convert the package files to HTML.
 131             if (!(configuration.nodeprecated && utils.isDeprecated(pkg)))
 132                 convertPackage(pkg, outputdir);
 133         }
 134         for (TypeElement te : configuration.getSpecifiedTypeElements()) {
 135             // If -nodeprecated option is set and the class is marked as deprecated
 136             // or the containing package is deprecated, do not convert the
 137             // package files to HTML.
 138             if (!(configuration.nodeprecated &&
 139                   (utils.isDeprecated(te) || utils.isDeprecated(utils.containingPackage(te)))))
 140                 convertClass(te, outputdir);
 141         }
 142     }
 143 
 144     /**
 145      * Convert the Classes in the given Package to an HTML file.
 146      *
 147      * @param pkg the Package to convert.
 148      * @param outputdir the name of the directory to output to.
 149      * @throws DocFileIOException if there is a problem generating an output file
 150      * @throws SimpleDocletException if there is a problem reading a source file
 151      */
 152     public void convertPackage(PackageElement pkg, DocPath outputdir)
 153             throws DocFileIOException, SimpleDocletException {
 154         if (pkg == null) {
 155             return;
 156         }
 157         for (Element te : utils.getAllClasses(pkg)) {
 158             // If -nodeprecated option is set and the class is marked as deprecated,
 159             // do not convert the package files to HTML. We do not check for
 160             // containing package deprecation since it is already check in
 161             // the calling method above.
 162             if (!(configuration.nodeprecated && utils.isDeprecated(te)))
 163                 convertClass((TypeElement)te, outputdir);
 164         }
 165     }
 166 
 167     /**
 168      * Convert the documented packages contained in the given module to an HTML representation.
 169      *
 170      * @param mdl the module to convert.
 171      * @param outputdir the name of the directory to output to.
 172      * @throws DocFileIOException if there is a problem generating an output file
 173      * @throws SimpleDocletException if there is a problem reading a source file
 174      */
 175     public void convertModule(ModuleElement mdl, DocPath outputdir)
 176             throws DocFileIOException, SimpleDocletException {
 177         if (mdl == null) {
 178             return;
 179         }
 180         for (Element elem : mdl.getEnclosedElements()) {
 181             if (elem instanceof PackageElement && configuration.docEnv.isIncluded(elem)
 182                     && !(configuration.nodeprecated && utils.isDeprecated(elem))) {
 183                 convertPackage((PackageElement) elem, outputdir);
 184             }
 185         }
 186     }
 187 
 188     /**
 189      * Convert the given Class to an HTML.
 190      *
 191      * @param te the class to convert.
 192      * @param outputdir the name of the directory to output to
 193      * @throws DocFileIOException if there is a problem generating the output file
 194      * @throws SimpleDocletException if there is a problem reading the source file
 195      */
 196     public void convertClass(TypeElement te, DocPath outputdir)
 197             throws DocFileIOException, SimpleDocletException {
 198         if (te == null) {
 199             return;
 200         }
 201         FileObject fo = utils.getFileObject(te);
 202         if (fo == null)
 203             return;
 204 
 205         try {
 206             Reader r = fo.openReader(true);
 207             int lineno = 1;
 208             String line;
 209             relativePath = DocPaths.SOURCE_OUTPUT
 210                     .resolve(configuration.docPaths.forPackage(te))
 211                     .invert();
 212             Content body = getHeader();
 213             Content pre = new HtmlTree(HtmlTag.PRE);
 214             try (LineNumberReader reader = new LineNumberReader(r)) {
 215                 while ((line = reader.readLine()) != null) {
 216                     addLineNo(pre, lineno);
 217                     addLine(pre, line, lineno);
 218                     lineno++;
 219                 }
 220             }
 221             addBlankLines(pre);
 222             Content div = HtmlTree.DIV(HtmlStyle.sourceContainer, pre);
 223             body.add(HtmlTree.MAIN(div));
 224             writeToFile(body, outputdir.resolve(configuration.docPaths.forClass(te)), te);
 225         } catch (IOException e) {
 226             String message = resources.getText("doclet.exception.read.file", fo.getName());
 227             throw new SimpleDocletException(message, e);
 228         }
 229     }
 230 
 231     /**
 232      * Write the output to the file.
 233      *
 234      * @param body the documentation content to be written to the file.
 235      * @param path the path for the file.
 236      */
 237     private void writeToFile(Content body, DocPath path, TypeElement te) throws DocFileIOException {
 238         Head head = new Head(path, configuration.docletVersion, configuration.startTime)
 239 //                .setTimestamp(!configuration.notimestamp) // temporary: compatibility!
 240                 .setTitle(resources.getText("doclet.Window_Source_title"))
 241 //                .setCharset(configuration.charset) // temporary: compatibility!
 242                 .setDescription(HtmlDocletWriter.getDescription("source", te))
 243                 .setGenerator(HtmlDocletWriter.getGenerator(getClass()))
 244                 .addDefaultScript(false)
 245                 .setStylesheets(configuration.getMainStylesheet(), configuration.getAdditionalStylesheets());
 246         Content htmlTree = HtmlTree.HTML(configuration.getLocale().getLanguage(),
 247                 head.toContent(), body);
 248         HtmlDocument htmlDocument = new HtmlDocument(htmlTree);
 249         messages.notice("doclet.Generating_0", path.getPath());
 250         htmlDocument.write(DocFile.createFileForOutput(configuration, path));
 251     }
 252 
 253     /**
 254      * Returns a link to the stylesheet file.
 255      *
 256      * @param head an HtmlTree to which the stylesheet links will be added
 257      */
 258     public void addStyleSheetProperties(Content head) {
 259         String filename = configuration.stylesheetfile;
 260         DocPath stylesheet;
 261         if (filename.length() > 0) {
 262             DocFile file = DocFile.createFileForInput(configuration, filename);
 263             stylesheet = DocPath.create(file.getName());
 264         } else {
 265             stylesheet = DocPaths.STYLESHEET;
 266         }
 267         DocPath p = relativePath.resolve(stylesheet);
 268         HtmlTree link = HtmlTree.LINK("stylesheet", "text/css", p.getPath(), "Style");
 269         head.add(link);
 270         addStylesheets(head);
 271     }
 272 
 273     protected void addStylesheets(Content tree) {
 274         List<String> stylesheets = configuration.additionalStylesheets;
 275         if (!stylesheets.isEmpty()) {
 276             stylesheets.forEach((ssheet) -> {
 277                 DocFile file = DocFile.createFileForInput(configuration, ssheet);
 278                 DocPath ssheetPath = DocPath.create(file.getName());
 279                 HtmlTree slink = HtmlTree.LINK("stylesheet", "text/css", relativePath.resolve(ssheetPath).getPath(),
 280                         "Style");
 281                 tree.add(slink);
 282             });
 283         }
 284     }
 285 
 286     /**
 287      * Get the header.
 288      *
 289      * @return the header content for the HTML file
 290      */
 291     private static Content getHeader() {
 292         return new HtmlTree(HtmlTag.BODY).put(HtmlAttr.CLASS, "source");
 293     }
 294 
 295     /**
 296      * Add the line numbers for the source code.
 297      *
 298      * @param pre the content tree to which the line number will be added
 299      * @param lineno The line number
 300      */
 301     private static void addLineNo(Content pre, int lineno) {
 302         HtmlTree span = new HtmlTree(HtmlTag.SPAN);
 303         span.setStyle(HtmlStyle.sourceLineNo);
 304         if (lineno < 10) {
 305             span.add("00" + Integer.toString(lineno));
 306         } else if (lineno < 100) {
 307             span.add("0" + Integer.toString(lineno));
 308         } else {
 309             span.add(Integer.toString(lineno));
 310         }
 311         pre.add(span);
 312     }
 313 
 314     /**
 315      * Add a line from source to the HTML file that is generated.
 316      *
 317      * @param pre the content tree to which the line will be added.
 318      * @param line the string to format.
 319      * @param currentLineNo the current number.
 320      */
 321     private void addLine(Content pre, String line, int currentLineNo) {
 322         if (line != null) {
 323             Content anchor = HtmlTree.SPAN_ID(
 324                     "line." + Integer.toString(currentLineNo),
 325                     new StringContent(utils.replaceTabs(line)));
 326             pre.add(anchor);
 327             pre.add(NEW_LINE);
 328         }
 329     }
 330 
 331     /**
 332      * Add trailing blank lines at the end of the page.
 333      *
 334      * @param pre the content tree to which the blank lines will be added.
 335      */
 336     private static void addBlankLines(Content pre) {
 337         for (int i = 0; i < NUM_BLANK_LINES; i++) {
 338             pre.add(NEW_LINE);
 339         }
 340     }
 341 
 342     /**
 343      * Given an element, return an anchor name for it.
 344      *
 345      * @param utils the utility class, used to get the line number of the element
 346      * @param e the element to check.
 347      * @return the name of the anchor.
 348      */
 349     public static String getAnchorName(Utils utils, Element e) {
 350         return "line." + utils.getLineNumber(e);
 351     }
 352 }