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