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     @SuppressWarnings("preview")
352     protected Category getCategory(Element element) {
353         return new SimpleElementVisitor14<Category, Void>() {
354             @Override
355             public Category visitModule(ModuleElement t, Void v) {
356                 return Category.MODULES;
357             }
358 
359             @Override
360             public Category visitPackage(PackageElement e, Void v) {
361                 return Category.PACKAGES;
362             }
363 
364             @Override
365             public Category visitType(TypeElement e, Void v) {
366                 return Category.TYPES;
367             }
368 
369             @Override
370             public Category visitVariable(VariableElement e, Void v) {
371                 return Category.MEMBERS;
372             }
373 
374             @Override
375             public Category visitExecutable(ExecutableElement e, Void v) {
376                 return Category.MEMBERS;
377             }
378 
379             @Override
380             public Category defaultAction(Element e, Void v) {
381                 throw new IllegalArgumentException(e.toString());
382             }
383         }.visit(element);
384     }
385 
386     /**
387      * Returns the type element that is documented as containing a member element,
388      * or {@code null} if this item does not represent a member element.
389      *
390      * @return the type element
391      */
392     public TypeElement getContainingTypeElement() {
393         return null;
394     }
395 
396     /**
397      * Returns the documentation tree node for this item, of {@code null} if this item
398      * does not represent a documentation tree node.
399      *
400      * @return the documentation tree node
401      */
402     public DocTree getDocTree() {
403         return null;
404     }
405 
406     /**
407      * Returns {@code true} if this index is for an element.
408      *
409      * @return {@code true} if this index is for an element
410      */
411     public boolean isElementItem() {
412         return element != null && getDocTree() == null;
413     }
414 
415     /**
416      * Returns {@code true} if this index is for a tag in a doc comment.
417      *
418      * @return {@code true} if this index is for a tag in a doc comment
419      */
420     public boolean isTagItem() {
421         return getDocTree() != null;
422     }
423 
424     /**
425      * Returns {@code true} if this index is for a specific kind of tag in a doc comment.
426      *
427      * @return {@code true} if this index is for a specific kind of tag in a doc comment
428      */
429     public boolean isKind(DocTree.Kind kind) {
430         DocTree dt = getDocTree();
431         return dt != null && dt.getKind() == kind;
432     }
433 
434     /**
435      * Sets the URL for the item, when it cannot otherwise be inferred from other fields.
436      *
437      * @param u the url
438      *
439      * @return this item
440      */
441     public IndexItem setUrl(String u) {
442         url = Objects.requireNonNull(u);
443         return this;
444     }
445 
446     /**
447      * Returns the URL for this item, or an empty string if no value has been set.
448      *
449      * @return the URL for this item, or an empty string if no value has been set
450      */
451     public String getUrl() {
452         return url;
453     }
454 
455     /**
456      * Sets the name of the containing module for this item.
457      *
458      * @param m the module
459      *
460      * @return this item
461      */
462     public IndexItem setContainingModule(String m) {
463         containingModule = Objects.requireNonNull(m);
464         return this;
465     }
466 
467     /**
468      * Sets the name of the containing package for this item.
469      *
470      * @param p the package
471      *
472      * @return this item
473      */
474     public IndexItem setContainingPackage(String p) {
475         containingPackage = Objects.requireNonNull(p);
476         return this;
477     }
478 
479     /**
480      * Sets the name of the containing class for this item.
481      *
482      * @param c the class
483      *
484      * @return this item
485      */
486     public IndexItem setContainingClass(String c) {
487         containingClass = Objects.requireNonNull(c);
488         return this;
489     }
490 
491     /**
492      * Returns a description of the element owning the documentation comment for this item,
493      * or {@code null} if this is not a item for a tag for an item in a documentation tag.
494      *
495      * @return the description of the element that owns this item
496      */
497     public String getHolder() {
498         return null;
499     }
500 
501     /**
502      * Returns a description of the tag for this item or {@code null} if this is not a item
503      * for a tag for an item in a documentation tag.
504      *
505      * @return the description of the tag
506      */
507     public String getDescription() {
508         return null;
509     }
510 
511     /**
512      * Returns a string representing this item in JSON notation.
513      *
514      * @return a string representing this item in JSON notation
515      */
516     public String toJSON() {
517         // TODO: Additional processing is required, see JDK-8238495
518         StringBuilder item = new StringBuilder();
519         Category category = getCategory();
520         switch (category) {
521             case MODULES:
522                 item.append("{")
523                         .append("\"l\":\"").append(label).append("\"")
524                         .append("}");
525                 break;
526 
527             case PACKAGES:
528                 item.append("{");
529                 if (!containingModule.isEmpty()) {
530                     item.append("\"m\":\"").append(containingModule).append("\",");
531                 }
532                 item.append("\"l\":\"").append(label).append("\"");
533                 if (!url.isEmpty()) {
534                     item.append(",\"u\":\"").append(url).append("\"");
535                 }
536                 item.append("}");
537                 break;
538 
539             case TYPES:
540                 item.append("{");
541                 if (!containingPackage.isEmpty()) {
542                     item.append("\"p\":\"").append(containingPackage).append("\",");
543                 }
544                 if (!containingModule.isEmpty()) {
545                     item.append("\"m\":\"").append(containingModule).append("\",");
546                 }
547                 item.append("\"l\":\"").append(label).append("\"");
548                 if (!url.isEmpty()) {
549                     item.append(",\"u\":\"").append(url).append("\"");
550                 }
551                 item.append("}");
552                 break;
553 
554             case MEMBERS:
555                 item.append("{");
556                 if (!containingModule.isEmpty()) {
557                     item.append("\"m\":\"").append(containingModule).append("\",");
558                 }
559                 item.append("\"p\":\"").append(containingPackage).append("\",")
560                         .append("\"c\":\"").append(containingClass).append("\",")
561                         .append("\"l\":\"").append(label).append("\"");
562                 if (!url.isEmpty()) {
563                     item.append(",\"u\":\"").append(url).append("\"");
564                 }
565                 item.append("}");
566                 break;
567 
568             case TAGS:
569                 String holder = getHolder();
570                 String description = getDescription();
571                 item.append("{")
572                         .append("\"l\":\"").append(label).append("\",")
573                         .append("\"h\":\"").append(holder).append("\",");
574                 if (!description.isEmpty()) {
575                     item.append("\"d\":\"").append(description).append("\",");
576                 }
577                 item.append("\"u\":\"").append(url).append("\"")
578                         .append("}");
579                 break;
580 
581             default:
582                 throw new AssertionError("Unexpected category: " + category);
583         }
584         return item.toString();
585     }
586 }
587