001/* 002 * Copyright (c) 2017, 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 com.sun.source.doctree.DocTree; 029import com.sun.source.doctree.EndElementTree; 030import com.sun.source.doctree.StartElementTree; 031import com.sun.source.util.DocTreeFactory; 032 033import javax.lang.model.element.Element; 034import javax.lang.model.element.ModuleElement; 035import javax.lang.model.element.PackageElement; 036import javax.tools.FileObject; 037import javax.tools.JavaFileManager.Location; 038 039import org.jdrupes.mdoclet.internal.doclets.formats.html.Navigation.PageMode; 040import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.BodyContents; 041import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.HtmlTree; 042import org.jdrupes.mdoclet.internal.doclets.toolkit.Content; 043import org.jdrupes.mdoclet.internal.doclets.toolkit.DocFileElement; 044import org.jdrupes.mdoclet.internal.doclets.toolkit.DocFilesHandler; 045import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DocFile; 046import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DocFileIOException; 047import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DocPath; 048import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DocPaths; 049import org.jdrupes.mdoclet.internal.doclets.toolkit.util.Utils; 050import org.jdrupes.mdoclet.internal.doclint.HtmlTag; 051 052import java.net.URI; 053import java.net.URISyntaxException; 054import java.util.ArrayList; 055import java.util.List; 056 057public class DocFilesHandlerImpl implements DocFilesHandler { 058 059 public final Element element; 060 public final Location location; 061 public final DocPath source; 062 public final HtmlConfiguration configuration; 063 private final HtmlOptions options; 064 065 /** 066 * Constructor to construct the DocFilesWriter object. 067 * 068 * @param configuration the configuration of this doclet. 069 * @param element the containing element of the doc-files. 070 * 071 */ 072 public DocFilesHandlerImpl(HtmlConfiguration configuration, 073 Element element) { 074 this.configuration = configuration; 075 this.options = configuration.getOptions(); 076 this.element = element; 077 078 switch (element.getKind()) { 079 case MODULE -> { 080 ModuleElement mdle = (ModuleElement) element; 081 location = configuration.utils.getLocationForModule(mdle); 082 source = DocPaths.DOC_FILES; 083 } 084 085 case PACKAGE -> { 086 PackageElement pkg = (PackageElement) element; 087 location = configuration.utils.getLocationForPackage(pkg); 088 // Note, given that we have a module-specific location, 089 // we want a module-relative path for the source, and not the 090 // standard path that may include the module directory 091 source = DocPath 092 .create(pkg.getQualifiedName().toString().replace('.', '/')) 093 .resolve(DocPaths.DOC_FILES); 094 } 095 096 default -> throw new AssertionError("unsupported element " + element); 097 } 098 } 099 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 @Override 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 = switch (this.element.getKind()) { 115 case MODULE -> DocPaths.forModule((ModuleElement) this.element); 116 case PACKAGE -> configuration.docPaths 117 .forPackage((PackageElement) this.element); 118 default -> throw new AssertionError( 119 "unknown kind:" + this.element.getKind()); 120 }; 121 copyDirectory(srcdir, path.resolve(DocPaths.DOC_FILES), first); 122 first = false; 123 } 124 } 125 126 @Override 127 public List<DocPath> getStylesheets() throws DocFileIOException { 128 var stylesheets = new ArrayList<DocPath>(); 129 for (DocFile srcdir : DocFile.list(configuration, location, source)) { 130 for (DocFile srcFile : srcdir.list()) { 131 if (srcFile.getName().endsWith(".css")) 132 stylesheets 133 .add(DocPaths.DOC_FILES.resolve(srcFile.getName())); 134 } 135 } 136 return stylesheets; 137 } 138 139 private void copyDirectory(DocFile srcdir, final DocPath dstDocPath, 140 boolean first) throws DocFileIOException { 141 DocFile dstdir = DocFile.createFileForOutput(configuration, dstDocPath); 142 if (srcdir.isSameFile(dstdir)) { 143 return; 144 } 145 for (DocFile srcfile : srcdir.list()) { 146 // ensure that the name is a valid component in an eventual full 147 // path 148 // and so avoid an equivalent check lower down in the file manager 149 // that throws IllegalArgumentException 150 if (!isValidFilename(srcfile)) { 151 configuration.messages.warning("doclet.Copy_Ignored_warning", 152 srcfile.getPath()); 153 continue; 154 } 155 156 DocFile destfile = dstdir.resolve(srcfile.getName()); 157 if (srcfile.isFile()) { 158 if (destfile.exists() && !first) { 159 configuration.messages.warning( 160 "doclet.Copy_Overwrite_warning", 161 srcfile.getPath(), dstdir.getPath()); 162 } else { 163 if (Utils.toLowerCase(srcfile.getPath()) 164 .endsWith(".html")) { 165 handleHtmlFile(srcfile, dstDocPath); 166 } else { 167 configuration.messages.notice( 168 "doclet.Copying_File_0_To_Dir_1", 169 srcfile.getPath(), dstdir.getPath()); 170 destfile.copyFile(srcfile); 171 } 172 } 173 } else if (srcfile.isDirectory()) { 174 if (options.copyDocfileSubdirs() 175 && !configuration 176 .shouldExcludeDocFileDir(srcfile.getName())) { 177 DocPath dirDocPath = dstDocPath.resolve(srcfile.getName()); 178 copyDirectory(srcfile, dirDocPath, first); 179 } 180 } 181 } 182 } 183 184 private boolean isValidFilename(DocFile f) { 185 try { 186 String n = f.getName(); 187 URI u = new URI(n); 188 return u.getPath().equals(n); 189 } catch (URISyntaxException e) { 190 return false; 191 } 192 } 193 194 private void handleHtmlFile(DocFile srcfile, DocPath dstPath) 195 throws DocFileIOException { 196 Utils utils = configuration.utils; 197 FileObject fileObject = srcfile.getFileObject(); 198 DocFileElement dfElement 199 = new DocFileElement(utils, element, fileObject); 200 201 DocPath dfilePath = dstPath.resolve(srcfile.getName()); 202 PackageElement pkg = dfElement.getPackageElement(); 203 204 HtmlDocletWriter docletWriter 205 = new DocFileWriter(configuration, dfilePath, element, pkg); 206 207 List<? extends DocTree> localTags 208 = getLocalHeaderTags(utils.getPreamble(dfElement)); 209 Content localTagsContent 210 = docletWriter.commentTagsToContent(dfElement, localTags, false); 211 212 String title = getWindowTitle(docletWriter, dfElement).trim(); 213 HtmlTree htmlContent = docletWriter.getBody(title); 214 215 List<? extends DocTree> fullBody = utils.getFullBody(dfElement); 216 Content pageContent 217 = docletWriter.commentTagsToContent(dfElement, fullBody, false); 218 docletWriter.addTagsInfo(dfElement, pageContent); 219 220 htmlContent.add(new BodyContents() 221 .setHeader(docletWriter.getHeader(PageMode.DOC_FILE, element)) 222 .addMainContent(pageContent) 223 .setFooter(docletWriter.getFooter())); 224 docletWriter.printHtmlDocument(List.of(), null, localTagsContent, 225 List.of(), htmlContent); 226 } 227 228 private List<? extends DocTree> 229 getLocalHeaderTags(List<? extends DocTree> dtrees) { 230 List<DocTree> localTags = new ArrayList<>(); 231 DocTreeFactory docTreeFactory 232 = configuration.docEnv.getDocTrees().getDocTreeFactory(); 233 boolean inHead = false; 234 boolean inTitle = false; 235 loop: for (DocTree dt : dtrees) { 236 switch (dt.getKind()) { 237 case START_ELEMENT: 238 StartElementTree startElem = (StartElementTree) dt; 239 switch (HtmlTag.get(startElem.getName())) { 240 case HEAD: 241 inHead = true; 242 break; 243 case META: 244 break; 245 case TITLE: 246 inTitle = true; 247 break; 248 default: 249 if (inHead) { 250 localTags.add(startElem); 251 localTags.add(docTreeFactory.newTextTree("\n")); 252 } 253 } 254 break; 255 case END_ELEMENT: 256 EndElementTree endElem = (EndElementTree) dt; 257 switch (HtmlTag.get(endElem.getName())) { 258 case HEAD: 259 inHead = false; 260 break loop; 261 case TITLE: 262 inTitle = false; 263 break; 264 default: 265 if (inHead) { 266 localTags.add(endElem); 267 localTags.add(docTreeFactory.newTextTree("\n")); 268 } 269 } 270 break; 271 case ENTITY: 272 case TEXT: 273 if (inHead && !inTitle) { 274 localTags.add(dt); 275 } 276 break; 277 } 278 } 279 return localTags; 280 } 281 282 private String getWindowTitle(HtmlDocletWriter docletWriter, 283 Element element) { 284 String t = configuration.utils.getHTMLTitle(element); 285 return docletWriter.getWindowTitle(t); 286 } 287 288 private static class DocFileWriter extends HtmlDocletWriter { 289 private final PackageElement pkg; 290 291 /** 292 * Constructor to construct the HtmlDocletWriter object. 293 * 294 * @param configuration the configuration of this doclet 295 * @param path the file to be generated 296 * @param e the anchoring element 297 * @param pkg the package containing the doc file 298 */ 299 public DocFileWriter(HtmlConfiguration configuration, DocPath path, 300 Element e, PackageElement pkg) { 301 super(configuration, path); 302 switch (e.getKind()) { 303 case PACKAGE: 304 case MODULE: 305 break; 306 default: 307 throw new AssertionError("unsupported element: " + e.getKind()); 308 } 309 this.pkg = pkg; 310 } 311 312 @Override 313 protected Navigation getNavBar(PageMode pageMode, Element element) { 314 Content mdleLinkContent 315 = getModuleLink(utils.elementUtils.getModuleOf(element), 316 contents.moduleLabel); 317 Content pkgLinkContent = getPackageLink(pkg, contents.packageLabel); 318 return super.getNavBar(pageMode, element) 319 .setNavLinkModule(mdleLinkContent) 320 .setNavLinkPackage(pkgLinkContent); 321 } 322 } 323}