1 /*
2  * Copyright (c) 2020, 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.toolkit.util;
27 
28 import java.util.Objects;
29 import javax.lang.model.element.Element;
30 import javax.lang.model.element.ExecutableElement;
31 import javax.lang.model.element.ModuleElement;
32 import javax.lang.model.element.PackageElement;
33 import javax.lang.model.element.TypeElement;
34 import javax.lang.model.element.VariableElement;
35 import javax.lang.model.util.SimpleElementVisitor14;
36 
37 import com.sun.source.doctree.DocTree;
38 
39 /**
40  * An item to be included in the index pages and in interactive search.
41  *
42  * <p>
43  * Items are primarily defined by their position in the documentation,
44  * which is one of:
45  *
46  * <ul>
47  * <li>An element (module, package, type or member)
48  * <li>One of a small set of tags in the doc comment for an element:
49  *     {@code {@index ...}}, {@code {@systemProperty ...}}, etc
50  * <li>One of a small set of outliers, corresponding to summary pages:
51  *     "All Classes", "All Packages", etc
52  * </ul>
53  *
54  * <p>
55  * All items have a "label", which is the presentation string used
56  * to display the item in the list of matching choices. The
57  * label is specified when the item is created.  Items also
58  * have a "url" and a "description", which are provided by
59  * the specific doclet.
60  *
61  * <p>
62  * Each item provides details to be included in the search index files
63  * read and processed by JavaScript.
64  * Items have a "category", which is normally derived from the element
65  * kind or doc tree kind; it corresponds to the JavaScript file
66  * in which this item will be written.
67  *
68  * <p>
69  * Items for an element may have one or more of the following:
70  * "containing module", "containing package", "containing type".
71  *
72  * <p>
73  * Items for a node in a doc tree have a "holder", which is a
74  * text form of the enclosing element or page.
75  * They will typically also have a "description" derived from
76  * content in the doc tree node.
77  *
78  *
79  *  <p><b>This is NOT part of any supported API.
80  *  If you write code that depends on this, you do so at your own risk.
81  *  This code and its internal interfaces are subject to change or
82  *  deletion without notice.</b>
83  */
84 public class IndexItem {
85 
86     /**
87      * The "category" used to group items for the interactive search index.
88      * Categories correspond directly to the JavaScript files that will be generated.
89      */
90     public enum Category {
91         MODULES,
92         PACKAGES,
93         TYPES,
94         MEMBERS,
95         TAGS
96     }
97 
98     /**
99      * The presentation string for the item. It must be non-empty.
100      */
101     private final String label;
102 
103     /**
104      * The element for the item. It is only null for items for summary pages that are not
105      * associated with any specific element.
106      *
107      */
108     private final Element element;
109 
110     /**
111      * The URL pointing to the element, doc tree or page being indexed.
112      * It may be empty if the information can be determined from other fields.
113      */
114     private String url = "";
115 
116     /**
117      * The containing module, if any, for the item.
118      * It will be empty if the element is not in a package, and may be omitted if the
119      * name of the package is unique.
120      */
121     private String containingModule = "";
122 
123     /**
124      * The containing package, if any, for the item.
125      */
126     private String containingPackage = "";
127 
128     /**
129      * The containing class, if any, for the item.
130      */
131     private String containingClass = "";
132 
133     /**
134      * Creates an index item for a module element.
135      *
136      * @param moduleElement the element
137      * @param utils         the common utilities class
138      *
139      * @return the item
140      */
of(ModuleElement moduleElement, Utils utils)141     public static IndexItem of(ModuleElement moduleElement, Utils utils) {
142         return new IndexItem(moduleElement, utils.getFullyQualifiedName(moduleElement));
143     }
144 
145     /**
146      * Creates an index item for a package element.
147      *
148      * @param packageElement the element
149      * @param utils          the common utilities class
150      *
151      * @return the item
152      */
of(PackageElement packageElement, Utils utils)153     public static IndexItem of(PackageElement packageElement, Utils utils) {
154         return new IndexItem(packageElement, utils.getPackageName(packageElement));
155     }
156 
157     /**
158      * Creates an index item for a type element.
159      * Note: use {@code getElement()} to access this value, not {@code getTypeElement}.
160      *
161      * @param typeElement the element
162      * @param utils       the common utilities class
163      *
164      * @return the item
165      */
of(TypeElement typeElement, Utils utils)166     public static IndexItem of(TypeElement typeElement, Utils utils) {
167         return new IndexItem(typeElement, utils.getSimpleName(typeElement));
168     }
169 
170     /**
171      * Creates an index item for a member element.
172      * Note: the given type element may not be the same as the enclosing element of the member
173      *       in cases where the enclosing element is not visible in the documentation.
174      *
175      * @param typeElement the element that contains the member
176      * @param member      the member
177      * @param utils       the common utilities class
178      *
179      * @return the item
180      *
181      * @see #getContainingTypeElement()
182      */
of(TypeElement typeElement, Element member, Utils utils)183     public static IndexItem of(TypeElement typeElement, Element member, Utils utils) {
184         String name = utils.getSimpleName(member);
185         if (utils.isExecutableElement(member)) {
186             ExecutableElement ee = (ExecutableElement)member;
187             name += utils.flatSignature(ee, typeElement);
188         }
189         return new IndexItem(member, name) {
190             @Override
191             public TypeElement getContainingTypeElement() {
192                 return typeElement;
193             }
194         };
195     }
196 
197     /**
198      * Creates an index item for a node in the doc comment for an element.
199      * The node should only be one that gives rise to an entry in the index.
200      *
201      * @param element     the element
202      * @param docTree     the node in the doc comment
203      * @param label       the label
204      * @param holder      the holder for the comment
205      * @param description the description of the item
206      * @param link        the root-relative link to the item in the generated docs
207      *
208      * @return the item
209      */
210     public static IndexItem of(Element element, DocTree docTree, String label,
211                                String holder, String description, DocLink link) {
212         Objects.requireNonNull(element);
213         Objects.requireNonNull(holder);
214         Objects.requireNonNull(description);
215         Objects.requireNonNull(link);
216 
217         switch (docTree.getKind()) {
218             case INDEX, SYSTEM_PROPERTY -> { }
219             default -> throw new IllegalArgumentException(docTree.getKind().toString());
220         }
221 
222         return new IndexItem(element, label, link.toString()) {
223             @Override
224             public DocTree getDocTree() {
225                 return docTree;
226             }
227             @Override
228             public Category getCategory() {
229                 return getCategory(docTree);
230             }
231             @Override
232             public String getHolder() {
233                 return holder;
234             }
235             @Override
236             public String getDescription() {
237                 return description;
238             }
239         };
240     }
241 
242     /**
243      * Creates an index item for a summary page, that is not associated with any element or
244      * node in a doc comment.
245      *
246      * @param category the category for the item
247      * @param label the label for the item
248      * @param path the path for the page
249      *
250      * @return the item
251      */
252     public static IndexItem of(Category category, String label, DocPath path) {
253         Objects.requireNonNull(category);
254         return new IndexItem(null, label, path.getPath()) {
255             @Override
256             public DocTree getDocTree() {
257                 return null;
258             }
259             @Override
260             public Category getCategory() {
261                 return category;
262             }
263             @Override
264             public String getHolder() {
265                 return "";
266             }
267             @Override
268             public String getDescription() {
269                 return "";
270             }
271         };
272     }
273 
274     private IndexItem(Element element, String label) {
275         if (label.isEmpty()) {
276             throw new IllegalArgumentException();
277         }
278 
279         this.element = element;
280         this.label = label;
281     }
282 
283     private IndexItem(Element element, String label, String url) {
284         this(element, label);
285         setUrl(url);
286     }
287 
288     /**
289      * Returns the label of the item.
290      *
291      * @return the label
292      */
293     public String getLabel() {
294         return label;
295     }
296 
297     /**
298      * Returns the part of the label after the last dot, or the whole label if there are no dots.
299      *
300      * @return the simple name
301      */
302     public String getSimpleName() {
303         return label.substring(label.lastIndexOf('.') + 1);
304     }
305 
306     /**
307      * Returns the label with a fully-qualified type name.
308      * (Used to determine if labels are unique or need to be qualified.)
309      *
310      * @param utils the common utilities class
311      *
312      * @return the fully qualified name
313      */
314     public String getFullyQualifiedLabel(Utils utils) {
315         TypeElement typeElement = getContainingTypeElement();
316         if (typeElement != null) {
317             return utils.getFullyQualifiedName(typeElement) + "." + label;
318         } else if (isElementItem()) {
319             return utils.getFullyQualifiedName(element);
320         } else {
321             return label;
322         }
323     }
324 
325     /**
326      * Returns the element associate with this item, or {@code null}.
327      *
328      * @return the element
329      */
330     public Element getElement() {
331         return element;
332     }
333 
334     /**
335      * Returns the category for this item, that indicates the JavaScript file
336      * in which this item should be written.
337      *
338      * @return the category
339      */
340     public Category getCategory() {
341         return getCategory(element);
342     }
343 
344     protected Category getCategory(DocTree docTree) {
345         return switch (docTree.getKind()) {
346             case INDEX, SYSTEM_PROPERTY -> Category.TAGS;
347             default -> throw new IllegalArgumentException(docTree.getKind().toString());
348         };
349     }
350 
351     protected Category getCategory(Element element) {
352         return new SimpleElementVisitor14<Category, Void>() {
353             @Override
354             public Category visitModule(ModuleElement t, Void v) {
355                 return Category.MODULES;
356             }
357 
358             @Override
359             public Category visitPackage(PackageElement e, Void v) {
360                 return Category.PACKAGES;
361             }
362 
363             @Override
364             public Category visitType(TypeElement e, Void v) {
365                 return Category.TYPES;
366             }
367 
368             @Override
369             public Category visitVariable(VariableElement e, Void v) {
370                 return Category.MEMBERS;
371             }
372 
373             @Override
374             public Category visitExecutable(ExecutableElement e, Void v) {
375                 return Category.MEMBERS;
376             }
377 
378             @Override
379             public Category defaultAction(Element e, Void v) {
380                 throw new IllegalArgumentException(e.toString());
381             }
382         }.visit(element);
383     }
384 
385     /**
386      * Returns the type element that is documented as containing a member element,
387      * or {@code null} if this item does not represent a member element.
388      *
389      * @return the type element
390      */
391     public TypeElement getContainingTypeElement() {
392         return null;
393     }
394 
395     /**
396      * Returns the documentation tree node for this item, of {@code null} if this item
397      * does not represent a documentation tree node.
398      *
399      * @return the documentation tree node
400      */
401     public DocTree getDocTree() {
402         return null;
403     }
404 
405     /**
406      * Returns {@code true} if this index is for an element.
407      *
408      * @return {@code true} if this index is for an element
409      */
410     public boolean isElementItem() {
411         return element != null && getDocTree() == null;
412     }
413 
414     /**
415      * Returns {@code true} if this index is for a tag in a doc comment.
416      *
417      * @return {@code true} if this index is for a tag in a doc comment
418      */
419     public boolean isTagItem() {
420         return getDocTree() != null;
421     }
422 
423     /**
424      * Returns {@code true} if this index is for a specific kind of tag in a doc comment.
425      *
426      * @return {@code true} if this index is for a specific kind of tag in a doc comment
427      */
428     public boolean isKind(DocTree.Kind kind) {
429         DocTree dt = getDocTree();
430         return dt != null && dt.getKind() == kind;
431     }
432 
433     /**
434      * Sets the URL for the item, when it cannot otherwise be inferred from other fields.
435      *
436      * @param u the url
437      *
438      * @return this item
439      */
440     public IndexItem setUrl(String u) {
441         url = Objects.requireNonNull(u);
442         return this;
443     }
444 
445     /**
446      * Returns the URL for this item, or an empty string if no value has been set.
447      *
448      * @return the URL for this item, or an empty string if no value has been set
449      */
450     public String getUrl() {
451         return url;
452     }
453 
454     /**
455      * Sets the name of the containing module for this item.
456      *
457      * @param m the module
458      *
459      * @return this item
460      */
461     public IndexItem setContainingModule(String m) {
462         containingModule = Objects.requireNonNull(m);
463         return this;
464     }
465 
466     /**
467      * Sets the name of the containing package for this item.
468      *
469      * @param p the package
470      *
471      * @return this item
472      */
473     public IndexItem setContainingPackage(String p) {
474         containingPackage = Objects.requireNonNull(p);
475         return this;
476     }
477 
478     /**
479      * Sets the name of the containing class for this item.
480      *
481      * @param c the class
482      *
483      * @return this item
484      */
485     public IndexItem setContainingClass(String c) {
486         containingClass = Objects.requireNonNull(c);
487         return this;
488     }
489 
490     /**
491      * Returns a description of the element owning the documentation comment for this item,
492      * or {@code null} if this is not a item for a tag for an item in a documentation tag.
493      *
494      * @return the description of the element that owns this item
495      */
496     public String getHolder() {
497         return null;
498     }
499 
500     /**
501      * Returns a description of the tag for this item or {@code null} if this is not a item
502      * for a tag for an item in a documentation tag.
503      *
504      * @return the description of the tag
505      */
506     public String getDescription() {
507         return null;
508     }
509 
510     /**
511      * Returns a string representing this item in JSON notation.
512      *
513      * @return a string representing this item in JSON notation
514      */
515     public String toJSON() {
516         // TODO: Additional processing is required, see JDK-8238495
517         StringBuilder item = new StringBuilder();
518         Category category = getCategory();
519         switch (category) {
520             case MODULES:
521                 item.append("{")
522                         .append("\"l\":\"").append(label).append("\"")
523                         .append("}");
524                 break;
525 
526             case PACKAGES:
527                 item.append("{");
528                 if (!containingModule.isEmpty()) {
529                     item.append("\"m\":\"").append(containingModule).append("\",");
530                 }
531                 item.append("\"l\":\"").append(label).append("\"");
532                 if (!url.isEmpty()) {
533                     item.append(",\"u\":\"").append(url).append("\"");
534                 }
535                 item.append("}");
536                 break;
537 
538             case TYPES:
539                 item.append("{");
540                 if (!containingPackage.isEmpty()) {
541                     item.append("\"p\":\"").append(containingPackage).append("\",");
542                 }
543                 if (!containingModule.isEmpty()) {
544                     item.append("\"m\":\"").append(containingModule).append("\",");
545                 }
546                 item.append("\"l\":\"").append(label).append("\"");
547                 if (!url.isEmpty()) {
548                     item.append(",\"u\":\"").append(url).append("\"");
549                 }
550                 item.append("}");
551                 break;
552 
553             case MEMBERS:
554                 item.append("{");
555                 if (!containingModule.isEmpty()) {
556                     item.append("\"m\":\"").append(containingModule).append("\",");
557                 }
558                 item.append("\"p\":\"").append(containingPackage).append("\",")
559                         .append("\"c\":\"").append(containingClass).append("\",")
560                         .append("\"l\":\"").append(label).append("\"");
561                 if (!url.isEmpty()) {
562                     item.append(",\"u\":\"").append(url).append("\"");
563                 }
564                 item.append("}");
565                 break;
566 
567             case TAGS:
568                 String holder = getHolder();
569                 String description = getDescription();
570                 item.append("{")
571                         .append("\"l\":\"").append(label).append("\",")
572                         .append("\"h\":\"").append(holder).append("\",");
573                 if (!description.isEmpty()) {
574                     item.append("\"d\":\"").append(description).append("\",");
575                 }
576                 item.append("\"u\":\"").append(url).append("\"")
577                         .append("}");
578                 break;
579 
580             default:
581                 throw new AssertionError("Unexpected category: " + category);
582         }
583         return item.toString();
584     }
585 }
586