001/*
002 * Copyright (c) 2021, 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.util.List;
029import java.util.Locale;
030import java.util.Map;
031import java.util.Set;
032import javax.lang.model.element.Element;
033import javax.lang.model.element.ExecutableElement;
034import javax.lang.model.element.PackageElement;
035import javax.lang.model.element.TypeElement;
036import javax.lang.model.element.VariableElement;
037import javax.lang.model.type.ArrayType;
038import javax.lang.model.type.DeclaredType;
039import javax.lang.model.type.TypeMirror;
040import javax.lang.model.type.TypeVariable;
041import javax.lang.model.util.SimpleTypeVisitor9;
042
043import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.HtmlId;
044import org.jdrupes.mdoclet.internal.doclets.toolkit.util.SummaryAPIListBuilder;
045import org.jdrupes.mdoclet.internal.doclets.toolkit.util.Utils;
046import org.jdrupes.mdoclet.internal.doclets.toolkit.util.VisibleMemberTable;
047
048/**
049 * Centralized constants and factory methods for HTML ids.
050 *
051 * <p>To ensure consistency, these constants and methods should be used
052 * both when declaring ids (for example, {@code HtmlTree.setId})
053 * and creating references (for example, {@code Links.createLink}).
054 *
055 * <p>Most ids are mostly for internal use within the pages of a documentation
056 * bundle. However, the ids for member declarations may be referred to
057 * from other documentation using {@code {@link}}, and should not be
058 * changed without due consideration for the compatibility impact.
059 *
060 * <p>The use of punctuating characters is inconsistent and could be improved.
061 *
062 * <p>Constants and methods are {@code static} where possible.
063 * However, some methods require access to {@code utils} and are
064 * better provided as instance methods.
065 */
066public class HtmlIds {
067    private final HtmlConfiguration configuration;
068    private final Utils utils;
069
070    static final HtmlId ALL_CLASSES_TABLE = HtmlId.of("all-classes-table");
071    static final HtmlId ALL_MODULES_TABLE = HtmlId.of("all-modules-table");
072    static final HtmlId ALL_PACKAGES_TABLE = HtmlId.of("all-packages-table");
073    static final HtmlId ANNOTATION_TYPE_ELEMENT_DETAIL
074        = HtmlId.of("annotation-interface-element-detail");
075    static final HtmlId ANNOTATION_TYPE_OPTIONAL_ELEMENT_SUMMARY
076        = HtmlId.of("annotation-interface-optional-element-summary");
077    static final HtmlId ANNOTATION_TYPE_REQUIRED_ELEMENT_SUMMARY
078        = HtmlId.of("annotation-interface-required-element-summary");
079    static final HtmlId CLASS_DESCRIPTION = HtmlId.of("class-description");
080    static final HtmlId CLASS_SUMMARY = HtmlId.of("class-summary");
081    static final HtmlId CONSTRUCTOR_DETAIL = HtmlId.of("constructor-detail");
082    static final HtmlId CONSTRUCTOR_SUMMARY = HtmlId.of("constructor-summary");
083    static final HtmlId ENUM_CONSTANT_DETAIL
084        = HtmlId.of("enum-constant-detail");
085    static final HtmlId ENUM_CONSTANT_SUMMARY
086        = HtmlId.of("enum-constant-summary");
087    static final HtmlId EXTERNAL_SPECS = HtmlId.of("external-specs");
088    static final HtmlId FIELD_DETAIL = HtmlId.of("field-detail");
089    static final HtmlId FIELD_SUMMARY = HtmlId.of("field-summary");
090    static final HtmlId FOR_REMOVAL = HtmlId.of("for-removal");
091    static final HtmlId HELP_NAVIGATION = HtmlId.of("help-navigation");
092    static final HtmlId HELP_PAGES = HtmlId.of("help-pages");
093    static final HtmlId METHOD_DETAIL = HtmlId.of("method-detail");
094    static final HtmlId METHOD_SUMMARY = HtmlId.of("method-summary");
095    static final HtmlId METHOD_SUMMARY_TABLE
096        = HtmlId.of("method-summary-table");
097    static final HtmlId MODULES = HtmlId.of("modules-summary");
098    static final HtmlId MODULE_DESCRIPTION = HtmlId.of("module-description");
099    static final HtmlId NAVBAR_SUB_LIST = HtmlId.of("navbar-sub-list");
100    static final HtmlId NAVBAR_TOGGLE_BUTTON
101        = HtmlId.of("navbar-toggle-button");
102    static final HtmlId NAVBAR_TOP = HtmlId.of("navbar-top");
103    static final HtmlId NAVBAR_TOP_FIRSTROW = HtmlId.of("navbar-top-firstrow");
104    static final HtmlId NESTED_CLASS_SUMMARY
105        = HtmlId.of("nested-class-summary");
106    static final HtmlId PACKAGES = HtmlId.of("packages-summary");
107    static final HtmlId PACKAGE_DESCRIPTION = HtmlId.of("package-description");
108    static final HtmlId PACKAGE_SUMMARY_TABLE
109        = HtmlId.of("package-summary-table");
110    static final HtmlId PROPERTY_DETAIL = HtmlId.of("property-detail");
111    static final HtmlId PROPERTY_SUMMARY = HtmlId.of("property-summary");
112    static final HtmlId RELATED_PACKAGE_SUMMARY
113        = HtmlId.of("related-package-summary");
114    static final HtmlId RESET_BUTTON = HtmlId.of("reset-button");
115    static final HtmlId SEARCH_INPUT = HtmlId.of("search-input");
116    static final HtmlId SERVICES = HtmlId.of("services-summary");
117    static final HtmlId SKIP_NAVBAR_TOP = HtmlId.of("skip-navbar-top");
118    static final HtmlId UNNAMED_PACKAGE_ANCHOR = HtmlId.of("unnamed-package");
119
120    private static final String ENUM_CONSTANTS_INHERITANCE
121        = "enum-constants-inherited-from-class-";
122    private static final String FIELDS_INHERITANCE
123        = "fields-inherited-from-class-";
124    private static final String METHODS_INHERITANCE
125        = "methods-inherited-from-class-";
126    private static final String NESTED_CLASSES_INHERITANCE
127        = "nested-classes-inherited-from-class-";
128    private static final String PROPERTIES_INHERITANCE
129        = "properties-inherited-from-class-";
130
131    /**
132     * Creates a factory for element-specific ids.
133     *
134     * @param configuration the configuration
135     */
136    HtmlIds(HtmlConfiguration configuration) {
137        this.configuration = configuration;
138        this.utils = configuration.utils;
139    }
140
141    /**
142     * Returns an id for a package.
143     *
144     * @param element the package
145     *
146     * @return the id
147     */
148    HtmlId forPackage(PackageElement element) {
149        return element == null || element.isUnnamed()
150            ? UNNAMED_PACKAGE_ANCHOR
151            : HtmlId.of(element.getQualifiedName().toString());
152    }
153
154    /**
155     * Returns an id for a package name.
156     *
157     * @param pkgName the package name
158     *
159     * @return the id
160     */
161    HtmlId forPackageName(String pkgName) {
162        return pkgName.isEmpty()
163            ? UNNAMED_PACKAGE_ANCHOR
164            : HtmlId.of(pkgName);
165    }
166
167    /**
168     * Returns an id for a class or interface.
169     *
170     * @param element the class or interface
171     *
172     * @return the id
173     */
174    HtmlId forClass(TypeElement element) {
175        return HtmlId.of(utils.getFullyQualifiedName(element));
176    }
177
178    /**
179     * Returns an id for an executable element, suitable for use when the
180     * simple name and argument list will be unique within the page, such as
181     * in the page for the declaration of the enclosing class or interface.
182     *
183     * @param element the element
184     *
185     * @return the id
186     */
187    HtmlId forMember(ExecutableElement element) {
188        String a = element.getSimpleName()
189            + utils.makeSignature(element, null, true, true);
190        // utils.makeSignature includes spaces
191        return HtmlId.of(a.replaceAll("\\s", ""));
192    }
193
194    /**
195     * Returns an id for an executable element, including the context
196     * of its documented enclosing class or interface.
197     *
198     * @param typeElement the enclosing class or interface
199     * @param member      the element
200     *
201     * @return the id
202     */
203    HtmlId forMember(TypeElement typeElement, ExecutableElement member) {
204        return HtmlId.of(
205            utils.getSimpleName(member) + utils.signature(member, typeElement));
206    }
207
208    /**
209     * Returns an id for a field, suitable for use when the simple name
210     * will be unique within the page, such as in the page for the
211     * declaration of the enclosing class or interface.
212     *
213     * <p>Warning: the name may not be unique if a property with the same
214     * name is also being documented in the same class.
215     *
216     * @param element the element
217     *
218     * @return the id
219     *
220     * @see #forProperty(ExecutableElement)
221     */
222    HtmlId forMember(VariableElement element) {
223        return HtmlId.of(element.getSimpleName().toString());
224    }
225
226    /**
227     * Returns an id for a field, including the context
228     * of its documented enclosing class or interface.
229     *
230     * @param typeElement the enclosing class or interface
231     * @param member the element
232     *
233     * @return the id
234     */
235    HtmlId forMember(TypeElement typeElement, VariableElement member) {
236        return HtmlId
237            .of(typeElement.getQualifiedName() + "." + member.getSimpleName());
238    }
239
240    /**
241     * Returns an id for the erasure of an executable element,
242     * or {@code null} if there are no type variables in the signature.
243     *
244     * For backward compatibility, include an anchor using the erasures of the
245     * parameters.  NOTE:  We won't need this method anymore after we fix
246     * {@code @see} tags so that they use the type instead of the erasure.
247     *
248     * @param executableElement the element to anchor to
249     * @return the 1.4.x style anchor for the executable element
250     */
251    protected HtmlId forErasure(ExecutableElement executableElement) {
252        final StringBuilder buf
253            = new StringBuilder(executableElement.getSimpleName().toString());
254        buf.append("(");
255        List<? extends VariableElement> parameters
256            = executableElement.getParameters();
257        boolean foundTypeVariable = false;
258        for (int i = 0; i < parameters.size(); i++) {
259            if (i > 0) {
260                buf.append(",");
261            }
262            TypeMirror t = parameters.get(i).asType();
263            SimpleTypeVisitor9<Boolean, Void> stv = new SimpleTypeVisitor9<>() {
264                boolean foundTypeVariable = false;
265
266                @Override
267                public Boolean visitArray(ArrayType t, Void p) {
268                    visit(t.getComponentType());
269                    buf.append(utils.getDimension(t));
270                    return foundTypeVariable;
271                }
272
273                @Override
274                public Boolean visitTypeVariable(TypeVariable t, Void p) {
275                    buf.append(
276                        utils.asTypeElement(t).getQualifiedName().toString());
277                    foundTypeVariable = true;
278                    return foundTypeVariable;
279                }
280
281                @Override
282                public Boolean visitDeclared(DeclaredType t, Void p) {
283                    buf.append(utils.getQualifiedTypeName(t));
284                    return foundTypeVariable;
285                }
286
287                @Override
288                protected Boolean defaultAction(TypeMirror e, Void p) {
289                    buf.append(e);
290                    return foundTypeVariable;
291                }
292            };
293
294            boolean isTypeVariable = stv.visit(t);
295            if (!foundTypeVariable) {
296                foundTypeVariable = isTypeVariable;
297            }
298        }
299        buf.append(")");
300        return foundTypeVariable ? HtmlId.of(buf.toString()) : null;
301    }
302
303    /**
304     * Returns an id for a property, suitable for use when the simple name
305     * will be unique within the page, such as in the page for the
306     * declaration of the enclosing class or interface.
307     *
308     * <p>Warning: the name may not be unique if a field with the same
309     * name is also being documented in the same class.
310     *
311     * @param element the element
312     *
313     * @return the id
314     *
315     * @see #forMember(VariableElement)
316     */
317    HtmlId forProperty(ExecutableElement element) {
318        return HtmlId.of(element.getSimpleName().toString());
319    }
320
321    /**
322     * Returns an id for the list of classes and interfaces inherited from
323     * a class or interface.
324     *
325     * <p>Note: the use of {@code utils} may not be strictly necessary.
326     *
327     * @param element the class or interface
328     *
329     * @return the id
330     */
331    HtmlId forInheritedClasses(TypeElement element) {
332        return HtmlId.of(
333            NESTED_CLASSES_INHERITANCE + utils.getFullyQualifiedName(element));
334    }
335
336    /**
337     * Returns an id for the list of fields inherited from a class or interface.
338     *
339     * @param element the class or interface
340     *
341     * @return the id
342     */
343    HtmlId forInheritedFields(TypeElement element) {
344        return forInherited(FIELDS_INHERITANCE, element);
345    }
346
347    /**
348     * Returns an id for the list of enum constants inherited from a class or interface.
349     *
350     * @param element the class or interface
351     *
352     * @return the id
353     */
354    HtmlId forInheritedEnumConstants(TypeElement element) {
355        return forInherited(ENUM_CONSTANTS_INHERITANCE, element);
356    }
357
358    /**
359     * Returns an id for the list of methods inherited from a class or interface.
360     *
361     * @param element the class or interface
362     *
363     * @return the id
364     */
365    HtmlId forInheritedMethods(TypeElement element) {
366        return forInherited(METHODS_INHERITANCE, element);
367    }
368
369    /**
370     * Returns an id for the list of properties inherited from a class or interface.
371     *
372     * @param element the class or interface
373     *
374     * @return the id
375     */
376    HtmlId forInheritedProperties(TypeElement element) {
377        return forInherited(PROPERTIES_INHERITANCE, element);
378    }
379
380    // Note: the use of {@code configuration} may not be strictly necessary as
381    // compared to just using the fully qualified name, but would be a change in
382    // the value.
383    private HtmlId forInherited(String prefix, TypeElement element) {
384        return HtmlId.of(prefix + configuration.getClassName(element));
385    }
386
387    /**
388     * Returns an id for a character on the A-Z Index page.
389     *
390     * @param character the character
391     *
392     * @return the id
393     */
394    static HtmlId forIndexChar(char character) {
395        return HtmlId.of("I:" + character);
396    }
397
398    /**
399     * Returns an id for a line in a source-code listing.
400     *
401     * @param lineNumber the line number
402     *
403     * @return the id
404     */
405    static HtmlId forLine(int lineNumber) {
406        return HtmlId.of("line-" + lineNumber);
407    }
408
409    /**
410     * Returns an id for a parameter, such as a component of a record.
411     *
412     * <p>Warning: this may not be unique on the page if used when there are
413     * other like-named parameters.
414     *
415     * @param paramName the parameter name
416     *
417     * @return the id
418     */
419    static HtmlId forParam(String paramName) {
420        return HtmlId.of("param-" + paramName);
421    }
422
423    /**
424     * Returns an id for a fragment of text, such as in an {@code @index} tag,
425     * using a map of counts to ensure the id is unique.
426     *
427     * @param text the text
428     * @param counts the map of counts
429     *
430     * @return the id
431     */
432    static HtmlId forText(String text, Map<String, Integer> counts) {
433        String base = text.replaceAll("\\s+", "");
434        int count = counts.compute(base, (k, v) -> v == null ? 0 : v + 1);
435        return HtmlId.of(count == 0 ? base : base + "-" + count);
436    }
437
438    /**
439     * Returns an id for one of the kinds of section in the pages for item group summaries.
440     *
441     * <p>Note: while the use of simple names (that are not keywords)
442     * may seem undesirable, they cannot conflict with the unqualified names
443     * of fields and properties, which should not also appear on the same page.
444     *
445     * @param kind the kind of deprecated items in the section
446     *
447     * @return the id
448     */
449    static HtmlId
450            forSummaryKind(SummaryAPIListBuilder.SummaryElementKind kind) {
451        return HtmlId.of(switch (kind) {
452        case MODULE -> "module";
453        case PACKAGE -> "package";
454        case INTERFACE -> "interface";
455        case CLASS -> "class";
456        case ENUM -> "enum-class";
457        case EXCEPTION_CLASS -> "exception-class";
458        case ANNOTATION_TYPE -> "annotation-interface";
459        case FIELD -> "field";
460        case METHOD -> "method";
461        case CONSTRUCTOR -> "constructor";
462        case ENUM_CONSTANT -> "enum-constant";
463        case ANNOTATION_TYPE_MEMBER -> "annotation-interface-member";
464        case RECORD_CLASS -> "record-class";
465        });
466    }
467
468    /**
469     * Returns an id for the member summary table of the given {@code kind} in a class page.
470     *
471     * @param kind the kind of member
472     *
473     * @return the id
474     */
475    static HtmlId forMemberSummary(VisibleMemberTable.Kind kind) {
476        return switch (kind) {
477        case NESTED_CLASSES -> NESTED_CLASS_SUMMARY;
478        case ENUM_CONSTANTS -> ENUM_CONSTANT_SUMMARY;
479        case FIELDS -> FIELD_SUMMARY;
480        case CONSTRUCTORS -> CONSTRUCTOR_SUMMARY;
481        case METHODS -> METHOD_SUMMARY;
482        // We generate separate summaries for optional and required annotation
483        // members
484        case ANNOTATION_TYPE_MEMBER -> throw new IllegalArgumentException(
485            "unsupported member kind");
486        case ANNOTATION_TYPE_MEMBER_OPTIONAL -> ANNOTATION_TYPE_OPTIONAL_ELEMENT_SUMMARY;
487        case ANNOTATION_TYPE_MEMBER_REQUIRED -> ANNOTATION_TYPE_REQUIRED_ELEMENT_SUMMARY;
488        case PROPERTIES -> PROPERTY_SUMMARY;
489        };
490    }
491
492    /**
493     * Returns an id for a "tab" in a table.
494     *
495     * @param tableId the id for the table
496     * @param tabIndex the index of the tab
497     *
498     * @return the id
499     */
500    public static HtmlId forTab(HtmlId tableId, int tabIndex) {
501        return HtmlId.of(tableId.name() + "-tab" + tabIndex);
502    }
503
504    /**
505     * Returns an id for the "tab panel" in a table.
506     *
507     * @param tableId the id for the table
508     *
509     * @return the id
510     */
511    public static HtmlId forTabPanel(HtmlId tableId) {
512        return HtmlId.of(tableId.name() + ".tabpanel");
513    }
514
515    /**
516     * Returns an id for the "preview" section for an element.
517     *
518     * @param el the element
519     *
520     * @return the id
521     */
522    public HtmlId forPreviewSection(Element el) {
523        return HtmlId.of("preview-" + switch (el.getKind()) {
524        case CONSTRUCTOR, METHOD -> forMember((ExecutableElement) el).name();
525        case PACKAGE -> forPackage((PackageElement) el).name();
526        default -> utils.getFullyQualifiedName(el, false);
527        });
528    }
529
530    /**
531     * Returns an id for the entry on the HELP page for a kind of generated page.
532     *
533     * @param page the kind of page
534     *
535     * @return the id
536     */
537    public HtmlId forPage(Navigation.PageMode page) {
538        return HtmlId
539            .of(page.name().toLowerCase(Locale.ROOT).replace("_", "-"));
540    }
541
542    /**
543     * Returns an id for a heading in a doc comment. The id value is derived from the contents
544     * of the heading with additional checks to make it unique within its containing page.
545     *
546     * @param headingText the text contained by the heading
547     * @param headingIds the set of heading ids already generated for the current page
548     * @return a unique id value for the heading
549     */
550    public HtmlId forHeading(CharSequence headingText, Set<String> headingIds) {
551        String idValue = headingText.toString()
552            .toLowerCase(Locale.ROOT)
553            .trim()
554            .replaceAll("[^\\w_-]+", "-");
555        // Make id value unique
556        idValue = idValue + "-heading";
557        if (!headingIds.add(idValue)) {
558            int counter = 1;
559            while (!headingIds.add(idValue + counter)) {
560                counter++;
561            }
562            idValue = idValue + counter;
563        }
564        return HtmlId.of(idValue);
565    }
566}