001/* 002 * Copyright (c) 1998, 2023, 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.toolkit.util; 027 028import static org.jdrupes.mdoclet.internal.doclets.toolkit.util.VisibleMemberTable.Kind.*; 029 030import java.util.*; 031import java.util.stream.Collectors; 032 033import javax.lang.model.element.Element; 034import javax.lang.model.element.ModuleElement; 035import javax.lang.model.element.PackageElement; 036import javax.lang.model.element.TypeElement; 037 038import org.jdrupes.mdoclet.internal.doclets.toolkit.BaseConfiguration; 039import org.jdrupes.mdoclet.internal.doclets.toolkit.Messages; 040 041import com.sun.source.doctree.DocTree; 042 043/** 044 * An alphabetical index of elements, search tags, and other items. 045 * Two tables are maintained: 046 * one is indexed by the first character of each items name; 047 * the other is index by the item's category, indicating the JavaScript 048 * file in which the item should be written. 049 */ 050public class IndexBuilder { 051 052 /** 053 * Sets of items keyed by the first character of the names (labels) 054 * of the items in those sets. 055 */ 056 private final Map<Character, SortedSet<IndexItem>> itemsByFirstChar; 057 058 /** 059 * Sets of items keyed by the {@link IndexItem.Category category} 060 * of the items in those sets. 061 */ 062 private final Map<IndexItem.Category, SortedSet<IndexItem>> itemsByCategory; 063 064 /** 065 * Don't generate deprecated information if true. 066 */ 067 private final boolean noDeprecated; 068 069 /** 070 * Build this index only for classes? 071 */ 072 protected final boolean classesOnly; 073 074 private final BaseConfiguration configuration; 075 private final Utils utils; 076 077 /** 078 * The comparator used for the sets in {@code itemsByFirstChar}. 079 */ 080 private final Comparator<IndexItem> mainComparator; 081 082 /** 083 * Creates a new {@code IndexBuilder}. 084 * 085 * @param configuration the current configuration of the doclet 086 * @param noDeprecated true if -nodeprecated option is used, 087 * false otherwise 088 */ 089 public IndexBuilder(BaseConfiguration configuration, 090 boolean noDeprecated) { 091 this(configuration, noDeprecated, false); 092 } 093 094 /** 095 * Creates a new {@code IndexBuilder}. 096 * 097 * @param configuration the current configuration of the doclet 098 * @param noDeprecated true if -nodeprecated option is used, 099 * false otherwise 100 * @param classesOnly include only classes in index 101 */ 102 public IndexBuilder(BaseConfiguration configuration, 103 boolean noDeprecated, 104 boolean classesOnly) { 105 this.configuration = configuration; 106 this.utils = configuration.utils; 107 108 Messages messages = configuration.getMessages(); 109 if (classesOnly) { 110 messages.notice("doclet.Building_Index_For_All_Classes"); 111 } else { 112 messages.notice("doclet.Building_Index"); 113 } 114 115 this.noDeprecated = noDeprecated; 116 this.classesOnly = classesOnly; 117 118 itemsByFirstChar = new TreeMap<>(); 119 itemsByCategory = new EnumMap<>(IndexItem.Category.class); 120 121 mainComparator 122 = classesOnly ? makeClassComparator() : makeIndexComparator(); 123 } 124 125 /** 126 * Adds all the selected modules, packages, types and their members to the index, 127 * or just the type elements if {@code classesOnly} is {@code true}. 128 */ 129 public void addElements() { 130 Set<TypeElement> classes = configuration.getIncludedTypeElements(); 131 indexTypeElements(classes); 132 if (classesOnly) { 133 return; 134 } 135 Set<PackageElement> packages 136 = configuration.getSpecifiedPackageElements(); 137 if (packages.isEmpty()) { 138 packages = classes 139 .stream() 140 .map(utils::containingPackage) 141 .filter(_package -> _package != null && !_package.isUnnamed()) 142 .collect(Collectors.toSet()); 143 } 144 packages.forEach(this::indexPackage); 145 classes.stream() 146 .filter(this::shouldIndex) 147 .forEach(this::indexMembers); 148 149 if (configuration.showModules) { 150 indexModules(); 151 } 152 } 153 154 /** 155 * Adds an individual item to the two collections of items. 156 * 157 * @param item the item to add 158 */ 159 public void add(IndexItem item) { 160 Objects.requireNonNull(item); 161 162 if (item.isElementItem() || item.isTagItem()) { 163 // don't put summary-page items in the A-Z index: 164 // they are listed separately, at the top of the index page 165 itemsByFirstChar.computeIfAbsent(keyCharacter(item.getLabel()), 166 c -> new TreeSet<>(mainComparator)) 167 .add(item); 168 } 169 170 itemsByCategory.computeIfAbsent(item.getCategory(), 171 c -> new TreeSet<>(c == IndexItem.Category.TYPES 172 ? makeTypeSearchIndexComparator() 173 : makeGenericSearchIndexComparator())) 174 .add(item); 175 } 176 177 /** 178 * Returns a sorted list of items whose names start with the 179 * provided character. 180 * 181 * @param key index key 182 * @return list of items keyed by the provided character 183 */ 184 public SortedSet<IndexItem> getItems(Character key) { 185 return itemsByFirstChar.get(key); 186 } 187 188 /** 189 * Returns a sorted list of the first characters of the labels of index items. 190 */ 191 public List<Character> getFirstCharacters() { 192 return new ArrayList<>(itemsByFirstChar.keySet()); 193 } 194 195 /** 196 * Returns a sorted list of items in a given category. 197 * 198 * @param cat the category 199 * @return list of items keyed by the provided character 200 */ 201 public SortedSet<IndexItem> getItems(IndexItem.Category cat) { 202 Objects.requireNonNull(cat); 203 return itemsByCategory.getOrDefault(cat, Collections.emptySortedSet()); 204 } 205 206 /** 207 * Returns a sorted list of items with a given kind of doc tree. 208 * 209 * @param kind the kind 210 * @return list of items keyed by the provided character 211 */ 212 public SortedSet<IndexItem> getItems(DocTree.Kind kind) { 213 Objects.requireNonNull(kind); 214 return itemsByCategory 215 .getOrDefault(IndexItem.Category.TAGS, Collections.emptySortedSet()) 216 .stream() 217 .filter(i -> i.isKind(kind)) 218 .collect( 219 Collectors.toCollection(() -> new TreeSet<>(mainComparator))); 220 } 221 222 /** 223 * Indexes all the members (fields, methods, constructors, etc.) of the 224 * provided type element. 225 * 226 * @param te TypeElement whose members are to be indexed 227 */ 228 private void indexMembers(TypeElement te) { 229 VisibleMemberTable vmt = configuration.getVisibleMemberTable(te); 230 indexMembers(te, vmt.getVisibleMembers(FIELDS)); 231 indexMembers(te, 232 vmt.getVisibleMembers(ANNOTATION_TYPE_MEMBER_OPTIONAL)); 233 indexMembers(te, 234 vmt.getVisibleMembers(ANNOTATION_TYPE_MEMBER_REQUIRED)); 235 indexMembers(te, vmt.getVisibleMembers(METHODS)); 236 indexMembers(te, vmt.getVisibleMembers(CONSTRUCTORS)); 237 indexMembers(te, vmt.getVisibleMembers(ENUM_CONSTANTS)); 238 } 239 240 /** 241 * Indexes the provided elements. 242 * 243 * @param members a collection of elements 244 */ 245 private void indexMembers(TypeElement typeElement, 246 Iterable<? extends Element> members) { 247 for (Element member : members) { 248 if (shouldIndex(member)) { 249 add(IndexItem.of(typeElement, member, utils)); 250 } 251 } 252 } 253 254 /** 255 * Index the given type elements. 256 * 257 * @param elements type elements 258 */ 259 private void indexTypeElements(Iterable<TypeElement> elements) { 260 for (TypeElement typeElement : elements) { 261 if (shouldIndex(typeElement)) { 262 add(IndexItem.of(typeElement, utils)); 263 } 264 } 265 } 266 267 /** 268 * Indexes all the modules. 269 */ 270 private void indexModules() { 271 for (ModuleElement m : configuration.modules) { 272 add(IndexItem.of(m, utils)); 273 } 274 } 275 276 /** 277 * Index the given package element. 278 * 279 * @param packageElement the package element 280 */ 281 private void indexPackage(PackageElement packageElement) { 282 if (shouldIndex(packageElement)) { 283 add(IndexItem.of(packageElement, utils)); 284 } 285 } 286 287 /** 288 * Should this element be added to the index? 289 */ 290 private boolean shouldIndex(Element element) { 291 if (utils.hasHiddenTag(element)) { 292 return false; 293 } 294 295 if (utils.isPackage(element)) { 296 // Do not add to index map if -nodeprecated option is set and the 297 // package is marked as deprecated. 298 return !(noDeprecated && utils.isDeprecated(element)); 299 } else { 300 // Do not add to index map if -nodeprecated option is set and if the 301 // element is marked as deprecated or the containing package is 302 // marked as 303 // deprecated. 304 return !(noDeprecated && 305 (utils.isDeprecated(element) || 306 utils.isDeprecated(utils.containingPackage(element)))); 307 } 308 } 309 310 private static Character keyCharacter(String s) { 311 // Use first valid java identifier start character as key, 312 // or '*' for strings that do not contain one. 313 for (int i = 0; i < s.length(); i++) { 314 if (Character.isJavaIdentifierStart(s.charAt(i))) { 315 return Character.toUpperCase(s.charAt(i)); 316 } 317 } 318 return '*'; 319 } 320 321 /** 322 * Returns a comparator for the all-classes list. 323 * @return a comparator for class element items 324 */ 325 private Comparator<IndexItem> makeClassComparator() { 326 return Comparator.comparing(IndexItem::getElement, 327 utils.comparators.makeAllClassesComparator()); 328 } 329 330 /** 331 * Returns a comparator for the {@code IndexItem}s in the index page. 332 * This is a composite comparator that must be able to compare all kinds of items: 333 * for element items, tag items, and others. 334 * 335 * @return a comparator for index page items 336 */ 337 private Comparator<IndexItem> makeIndexComparator() { 338 // We create comparators specific to element and search tag items, and a 339 // base comparator used to compare between the two kinds of items. 340 // In order to produce consistent results, it is important that the base 341 // comparator 342 // uses the same primary sort keys as both the element and search tag 343 // comparators 344 // (see JDK-8311264). 345 Comparator<Element> elementComparator 346 = utils.comparators.makeIndexElementComparator(); 347 Comparator<IndexItem> baseComparator = (ii1, ii2) -> utils 348 .compareStrings(getIndexItemKey(ii1), getIndexItemKey(ii2)); 349 Comparator<IndexItem> searchTagComparator = baseComparator 350 .thenComparing(IndexItem::getHolder) 351 .thenComparing(IndexItem::getDescription) 352 .thenComparing(IndexItem::getUrl); 353 354 return (ii1, ii2) -> { 355 // If both are element items, compare the elements 356 if (ii1.isElementItem() && ii2.isElementItem()) { 357 int d = elementComparator.compare(ii1.getElement(), 358 ii2.getElement()); 359 if (d == 0) { 360 /* 361 * Members inherited from classes with package access are 362 * documented as though they were declared in the inheriting 363 * subclass (see JDK-4780441). 364 */ 365 Element subclass1 = ii1.getContainingTypeElement(); 366 Element subclass2 = ii2.getContainingTypeElement(); 367 if (subclass1 != null && subclass2 != null) { 368 d = elementComparator.compare(subclass1, subclass2); 369 } 370 } 371 return d; 372 } 373 374 // If one is an element item, compare item keys; if equal, put 375 // element item last 376 if (ii1.isElementItem() || ii2.isElementItem()) { 377 int d = baseComparator.compare(ii1, ii2); 378 return d != 0 ? d : ii1.isElementItem() ? 1 : -1; 379 } 380 381 // Otherwise, compare labels and other fields of the items 382 return searchTagComparator.compare(ii1, ii2); 383 }; 384 } 385 386 private String getIndexItemKey(IndexItem ii) { 387 // For element items return the key used by the element comparator; 388 // for search tag items return the item's label. 389 return ii.isElementItem() 390 ? utils.comparators.getIndexElementKey(ii.getElement()) 391 : ii.getLabel(); 392 } 393 394 /** 395 * Returns a Comparator for IndexItems in the types category of the search index. 396 * Items are compared by short name, falling back to the main comparator if names are equal. 397 * 398 * @return a Comparator 399 */ 400 public Comparator<IndexItem> makeTypeSearchIndexComparator() { 401 Comparator<IndexItem> simpleNameComparator = (ii1, ii2) -> utils 402 .compareStrings(ii1.getSimpleName(), ii2.getSimpleName()); 403 return simpleNameComparator.thenComparing(mainComparator); 404 } 405 406 /** 407 * Returns a Comparator for IndexItems in the modules, packages, members, and search tags 408 * categories of the search index. 409 * Items are compared by label, falling back to the main comparator if names are equal. 410 * 411 * @return a Comparator 412 */ 413 public Comparator<IndexItem> makeGenericSearchIndexComparator() { 414 Comparator<IndexItem> labelComparator = (ii1, ii2) -> utils 415 .compareStrings(ii1.getLabel(), ii2.getLabel()); 416 return labelComparator.thenComparing(mainComparator); 417 } 418}