001/* 002 * Copyright (c) 1998, 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.Writer; 030import java.util.HashMap; 031import java.util.Map; 032import java.util.SortedSet; 033import javax.lang.model.element.Element; 034import javax.lang.model.element.ExecutableElement; 035import javax.lang.model.element.TypeElement; 036 037import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.HtmlTree; 038import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.Links; 039import org.jdrupes.mdoclet.internal.doclets.toolkit.Resources; 040import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DocFile; 041import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DocFileIOException; 042import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DocPath; 043import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DocPaths; 044import org.jdrupes.mdoclet.internal.doclets.toolkit.util.IndexBuilder; 045import org.jdrupes.mdoclet.internal.doclets.toolkit.util.IndexItem; 046import org.jdrupes.mdoclet.internal.doclets.toolkit.util.Utils; 047 048/** 049 * Extensions to {@code IndexBuilder} to fill in remaining fields 050 * in index items: {@code containingModule}, {@code containingPackage}, 051 * {@code containingClass}, and {@code url}, and to write out the 052 * JavaScript files. 053 */ 054public class HtmlIndexBuilder extends IndexBuilder { 055 private final HtmlConfiguration configuration; 056 057 private final Resources resources; 058 private final Utils utils; 059 private final HtmlIds htmlIds; 060 061 /** 062 * Creates a new {@code HtmlIndexBuilder}. 063 * 064 * @param configuration the current configuration of the doclet 065 */ 066 HtmlIndexBuilder(HtmlConfiguration configuration) { 067 super(configuration, configuration.getOptions().noDeprecated()); 068 this.configuration = configuration; 069 resources = configuration.docResources; 070 utils = configuration.utils; 071 htmlIds = configuration.htmlIds; 072 } 073 074 /** 075 * {@inheritDoc} 076 * 077 * After the initial work to add the element items, the remaining fields in 078 * the items are also initialized. 079 */ 080 public void addElements() { 081 super.addElements(); 082 if (classesOnly) { 083 return; 084 } 085 086 Map<String, Integer> duplicateLabelCheck = new HashMap<>(); 087 for (Character ch : getFirstCharacters()) { 088 for (IndexItem item : getItems(ch)) { 089 duplicateLabelCheck.compute(item.getFullyQualifiedLabel(utils), 090 (k, v) -> v == null ? 1 : v + 1); 091 } 092 } 093 094 for (Character ch : getFirstCharacters()) { 095 for (IndexItem item : getItems(ch)) { 096 if (item.isElementItem()) { 097 boolean addModuleInfo = duplicateLabelCheck 098 .get(item.getFullyQualifiedLabel(utils)) > 1; 099 addContainingInfo(item, addModuleInfo); 100 } 101 } 102 } 103 } 104 105 private void addContainingInfo(IndexItem item, boolean addModuleInfo) { 106 Element element = item.getElement(); 107 switch (element.getKind()) { 108 case MODULE: 109 break; 110 111 case PACKAGE: 112 if (configuration.showModules) { 113 item.setContainingModule(utils 114 .getFullyQualifiedName(utils.containingModule(element))); 115 } 116 break; 117 118 case CLASS: 119 case ENUM: 120 case RECORD: 121 case ANNOTATION_TYPE: 122 case INTERFACE: 123 item.setContainingPackage( 124 utils.getPackageName(utils.containingPackage(element))); 125 if (configuration.showModules && addModuleInfo) { 126 item.setContainingModule(utils 127 .getFullyQualifiedName(utils.containingModule(element))); 128 } 129 break; 130 131 case CONSTRUCTOR: 132 case METHOD: 133 case FIELD: 134 case ENUM_CONSTANT: 135 TypeElement containingType = item.getContainingTypeElement(); 136 item.setContainingPackage( 137 utils.getPackageName(utils.containingPackage(containingType))); 138 item.setContainingClass(utils.getSimpleName(containingType)); 139 if (configuration.showModules && addModuleInfo) { 140 item.setContainingModule(utils 141 .getFullyQualifiedName(utils.containingModule(element))); 142 } 143 if (utils.isExecutableElement(element)) { 144 String url = HtmlTree.encodeURL( 145 htmlIds.forMember((ExecutableElement) element).name()); 146 if (!url.equals(item.getLabel())) { 147 item.setUrl(url); 148 } 149 } 150 break; 151 152 default: 153 throw new Error(); 154 } 155 } 156 157 /** 158 * Generates the set of index files used by interactive search. 159 * 160 * @throws DocFileIOException if there is a problem creating any of the search index files 161 */ 162 public void createSearchIndexFiles() throws DocFileIOException { 163 // add last-minute items 164 if (!configuration.packages.isEmpty()) { 165 IndexItem item = IndexItem.of(IndexItem.Category.PACKAGES, 166 resources.getText("doclet.All_Packages"), 167 DocPaths.ALLPACKAGES_INDEX); 168 add(item); 169 } 170 IndexItem item = IndexItem.of(IndexItem.Category.TYPES, 171 resources.getText("doclet.All_Classes_And_Interfaces"), 172 DocPaths.ALLCLASSES_INDEX); 173 add(item); 174 175 for (IndexItem.Category category : IndexItem.Category.values()) { 176 DocPath file; 177 String varName; 178 switch (category) { 179 case MODULES -> { 180 file = DocPaths.MODULE_SEARCH_INDEX_JS; 181 varName = "moduleSearchIndex"; 182 } 183 case PACKAGES -> { 184 file = DocPaths.PACKAGE_SEARCH_INDEX_JS; 185 varName = "packageSearchIndex"; 186 } 187 case TYPES -> { 188 file = DocPaths.TYPE_SEARCH_INDEX_JS; 189 varName = "typeSearchIndex"; 190 } 191 case MEMBERS -> { 192 file = DocPaths.MEMBER_SEARCH_INDEX_JS; 193 varName = "memberSearchIndex"; 194 } 195 case TAGS -> { 196 file = DocPaths.TAG_SEARCH_INDEX_JS; 197 varName = "tagSearchIndex"; 198 } 199 default -> throw new Error(); 200 } 201 202 createSearchIndexFile(file, getItems(category), varName); 203 } 204 } 205 206 /** 207 * Creates a search index file. 208 * 209 * @param searchIndexJS the file for the JavaScript to be generated 210 * @param indexItems the search index items 211 * @param varName the variable name to write in the JavaScript file 212 * 213 * @throws DocFileIOException if there is a problem creating the search index file 214 */ 215 private void createSearchIndexFile(DocPath searchIndexJS, 216 SortedSet<IndexItem> indexItems, 217 String varName) 218 throws DocFileIOException { 219 // The file needs to be created even if there are no searchIndex items 220 DocFile jsFile 221 = DocFile.createFileForOutput(configuration, searchIndexJS); 222 try (Writer wr = jsFile.openWriter()) { 223 wr.write(varName); 224 wr.write(" = ["); 225 boolean first = true; 226 for (IndexItem item : indexItems) { 227 if (first) { 228 first = false; 229 } else { 230 wr.write(","); 231 } 232 wr.write(item.toJSON()); 233 } 234 wr.write("];"); 235 wr.write("updateSearchResults();"); 236 } catch (IOException ie) { 237 throw new DocFileIOException(jsFile, DocFileIOException.Mode.WRITE, 238 ie); 239 } 240 } 241}