001/* 002 * Copyright (c) 2001, 2022, Oracle and/or its affiliates. All rights reserved. 003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 004 * 005 * This code is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU General Public License version 2 only, as 007 * published by the Free Software Foundation. Oracle designates this 008 * particular file as subject to the "Classpath" exception as provided 009 * by Oracle in the LICENSE file that accompanied this code. 010 * 011 * This code is distributed in the hope that it will be useful, but WITHOUT 012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 014 * version 2 for more details (a copy is included in the LICENSE file that 015 * accompanied this code). 016 * 017 * You should have received a copy of the GNU General Public License version 018 * 2 along with this work; if not, write to the Free Software Foundation, 019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 020 * 021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 022 * or visit www.oracle.com if you need additional information or have any 023 * questions. 024 */ 025 026package org.jdrupes.mdoclet.internal.doclets.formats.html; 027 028import java.io.IOException; 029import java.io.LineNumberReader; 030import java.io.Reader; 031 032import javax.lang.model.element.Element; 033import javax.lang.model.element.ModuleElement; 034import javax.lang.model.element.PackageElement; 035import javax.lang.model.element.TypeElement; 036import javax.tools.FileObject; 037 038import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.Head; 039import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.HtmlDocument; 040import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.HtmlId; 041import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.HtmlStyle; 042import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.HtmlTree; 043import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.TagName; 044import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.Text; 045import org.jdrupes.mdoclet.internal.doclets.toolkit.Content; 046import org.jdrupes.mdoclet.internal.doclets.toolkit.Messages; 047import org.jdrupes.mdoclet.internal.doclets.toolkit.Resources; 048import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DocFile; 049import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DocFileIOException; 050import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DocPath; 051import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DocPaths; 052import org.jdrupes.mdoclet.internal.doclets.toolkit.util.SimpleDocletException; 053import org.jdrupes.mdoclet.internal.doclets.toolkit.util.Utils; 054 055/** 056 * Converts Java Source Code to HTML. 057 */ 058public class SourceToHTMLConverter { 059 060 /** 061 * The number of trailing blank lines at the end of the page. 062 * This is inserted so that anchors at the bottom of small pages 063 * can be reached. 064 */ 065 private static final int NUM_BLANK_LINES = 60; 066 067 /** 068 * New line to be added to the documentation. 069 */ 070 private static final String NEW_LINE = Text.NL; 071 072 private final HtmlConfiguration configuration; 073 private final HtmlOptions options; 074 private final Messages messages; 075 private final Resources resources; 076 private final Utils utils; 077 078 private final DocPath outputdir; 079 080 /** 081 * Relative path from the documentation root to the file that is being 082 * generated. 083 */ 084 private DocPath relativePath = DocPath.empty; 085 086 private SourceToHTMLConverter(HtmlConfiguration configuration, 087 DocPath outputdir) { 088 this.configuration = configuration; 089 this.options = configuration.getOptions(); 090 this.messages = configuration.getMessages(); 091 this.resources = configuration.docResources; 092 this.utils = configuration.utils; 093 this.outputdir = outputdir; 094 } 095 096 /** 097 * Translate the TypeElements in the given DocletEnvironment to HTML representation. 098 * 099 * @param configuration the configuration. 100 * @param outputdir the name of the directory to output to. 101 * @throws DocFileIOException if there is a problem generating an output file 102 * @throws SimpleDocletException if there is a problem reading a source file 103 */ 104 public static void convertRoot(HtmlConfiguration configuration, 105 DocPath outputdir) 106 throws DocFileIOException, SimpleDocletException { 107 new SourceToHTMLConverter(configuration, outputdir).generate(); 108 } 109 110 void generate() throws DocFileIOException, SimpleDocletException { 111 if (outputdir == null) { 112 return; 113 } 114 for (ModuleElement mdl : configuration.getSpecifiedModuleElements()) { 115 // If -nodeprecated option is set and the module is marked as 116 // deprecated, 117 // do not convert the module files to HTML. 118 if (!(options.noDeprecated() && utils.isDeprecated(mdl))) 119 convertModule(mdl, outputdir); 120 } 121 for (PackageElement pkg : configuration.getSpecifiedPackageElements()) { 122 // If -nodeprecated option is set and the package is marked as 123 // deprecated, 124 // do not convert the package files to HTML. 125 if (!(options.noDeprecated() && utils.isDeprecated(pkg))) 126 convertPackage(pkg, outputdir); 127 } 128 for (TypeElement te : configuration.getSpecifiedTypeElements()) { 129 // If -nodeprecated option is set and the class is marked as 130 // deprecated 131 // or the containing package is deprecated, do not convert the 132 // package files to HTML. 133 if (!(options.noDeprecated() && 134 (utils.isDeprecated(te) 135 || 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 (TypeElement te : utils.getAllClasses(pkg)) { 154 // If -nodeprecated option is set and the class is marked as 155 // deprecated, 156 // do not convert the package files to HTML. We do not check for 157 // containing package deprecation since it is already check in 158 // the calling method above. 159 if (!(options.noDeprecated() && utils.isDeprecated(te))) 160 convertClass(te, outputdir); 161 } 162 } 163 164 /** 165 * Convert the documented packages contained in the given module to an HTML representation. 166 * 167 * @param mdl the module to convert. 168 * @param outputdir the name of the directory to output to. 169 * @throws DocFileIOException if there is a problem generating an output file 170 * @throws SimpleDocletException if there is a problem reading a source file 171 */ 172 public void convertModule(ModuleElement mdl, DocPath outputdir) 173 throws DocFileIOException, SimpleDocletException { 174 if (mdl == null) { 175 return; 176 } 177 for (Element elem : mdl.getEnclosedElements()) { 178 if (elem instanceof PackageElement pkg 179 && configuration.docEnv.isIncluded(elem) 180 && !(options.noDeprecated() && utils.isDeprecated(elem))) { 181 convertPackage(pkg, outputdir); 182 } 183 } 184 } 185 186 /** 187 * Convert the given Class to an HTML. 188 * 189 * @param te the class to convert. 190 * @param outputdir the name of the directory to output to 191 * @throws DocFileIOException if there is a problem generating the output file 192 * @throws SimpleDocletException if there is a problem reading the source file 193 */ 194 public void convertClass(TypeElement te, DocPath outputdir) 195 throws DocFileIOException, SimpleDocletException { 196 if (te == null) { 197 return; 198 } 199 FileObject fo = utils.getFileObject(te); 200 if (fo == null) 201 return; 202 203 try { 204 Reader r = fo.openReader(true); 205 int lineno = 1; 206 String line; 207 relativePath = DocPaths.SOURCE_OUTPUT 208 .resolve(configuration.docPaths.forPackage(te)) 209 .invert(); 210 Content body = getHeader(); 211 var pre = new HtmlTree(TagName.PRE); 212 try (var reader = new LineNumberReader(r)) { 213 while ((line = reader.readLine()) != null) { 214 addLineNo(pre, lineno); 215 addLine(pre, line, lineno); 216 lineno++; 217 } 218 } 219 addBlankLines(pre); 220 var div = HtmlTree.DIV(HtmlStyle.sourceContainer, pre); 221 body.add(HtmlTree.MAIN(div)); 222 writeToFile(body, 223 outputdir.resolve(configuration.docPaths.forClass(te)), te); 224 } catch (IOException e) { 225 String message 226 = 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) 238 throws DocFileIOException { 239 Head head = new Head(path, configuration.getDocletVersion(), 240 configuration.getBuildDate()) 241// .setTimestamp(!options.notimestamp) // temporary: compatibility! 242 .setTitle(resources.getText("doclet.Window_Source_title")) 243// .setCharset(options.charset) // temporary: compatibility! 244 .setDescription(HtmlDocletWriter.getDescription("source", te)) 245 .setGenerator(HtmlDocletWriter.getGenerator(getClass())) 246 .addDefaultScript(false) 247 .setStylesheets(configuration.getMainStylesheet(), 248 configuration.getAdditionalStylesheets()); 249 var html = HtmlTree.HTML(configuration.getLocale().getLanguage(), head, 250 body); 251 HtmlDocument document = new HtmlDocument(html); 252 messages.notice("doclet.Generating_0", path.getPath()); 253 document.write(DocFile.createFileForOutput(configuration, path)); 254 } 255 256 /** 257 * Returns a link to the stylesheet file. 258 * 259 * @param head the content to which the stylesheet links will be added 260 */ 261 public void addStyleSheetProperties(Content head) { 262 String filename = options.stylesheetFile(); 263 DocPath stylesheet; 264 if (filename.length() > 0) { 265 DocFile file = DocFile.createFileForInput(configuration, filename); 266 stylesheet = DocPath.create(file.getName()); 267 } else { 268 stylesheet = DocPaths.STYLESHEET; 269 } 270 DocPath p = relativePath.resolve(stylesheet); 271 var link 272 = HtmlTree.LINK("stylesheet", "text/css", p.getPath(), "Style"); 273 head.add(link); 274 addStylesheets(head); 275 } 276 277 protected void addStylesheets(Content head) { 278 options.additionalStylesheets().forEach(css -> { 279 DocFile file = DocFile.createFileForInput(configuration, css); 280 DocPath cssPath = DocPath.create(file.getName()); 281 var slink = HtmlTree.LINK("stylesheet", "text/css", 282 relativePath.resolve(cssPath).getPath(), 283 "Style"); 284 head.add(slink); 285 }); 286 } 287 288 /** 289 * Get the header. 290 * 291 * @return the header content for the HTML file 292 */ 293 private static Content getHeader() { 294 return new HtmlTree(TagName.BODY).setStyle(HtmlStyle.sourcePage); 295 } 296 297 /** 298 * Add the line numbers for the source code. 299 * 300 * @param pre the content to which the line number will be added 301 * @param lineno The line number 302 */ 303 private static void addLineNo(Content pre, int lineno) { 304 var span = HtmlTree.SPAN(HtmlStyle.sourceLineNo); 305 if (lineno < 10) { 306 span.add("00" + lineno); 307 } else if (lineno < 100) { 308 span.add("0" + lineno); 309 } else { 310 span.add(Integer.toString(lineno)); 311 } 312 pre.add(span); 313 } 314 315 /** 316 * Add a line from source to the HTML file that is generated. 317 * 318 * @param pre the content to which the line will be added. 319 * @param line the string to format. 320 * @param currentLineNo the current number. 321 */ 322 private void addLine(HtmlTree pre, String line, int currentLineNo) { 323 if (line != null) { 324 var anchor = HtmlTree.SPAN_ID( 325 HtmlIds.forLine(currentLineNo), 326 Text.of(utils.replaceTabs(line))); 327 pre.addUnchecked(anchor); 328 pre.add(NEW_LINE); 329 } 330 } 331 332 /** 333 * Add trailing blank lines at the end of the page. 334 * 335 * @param pre the content to which the blank lines will be added. 336 */ 337 private static void addBlankLines(Content pre) { 338 for (int i = 0; i < NUM_BLANK_LINES; i++) { 339 pre.add(NEW_LINE); 340 } 341 } 342 343 /** 344 * Given an element, return an anchor name for it. 345 * 346 * @param utils the utility class, used to get the line number of the element 347 * @param e the element to check. 348 * @return the name of the anchor. 349 */ 350 public static HtmlId getAnchorName(Utils utils, Element e) { 351 return HtmlIds.forLine((int) utils.getLineNumber(e)); 352 } 353}