001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2026 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018///////////////////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle.checks.javadoc;
021
022import java.util.Arrays;
023import java.util.Set;
024import java.util.stream.Collectors;
025
026import com.puppycrawl.tools.checkstyle.StatelessCheck;
027import com.puppycrawl.tools.checkstyle.api.DetailNode;
028import com.puppycrawl.tools.checkstyle.api.JavadocCommentsTokenTypes;
029import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
030
031/**
032 * <div>
033 * Checks that Javadoc comments avoid unnecessary {@code {@link}} and {@code {@linkplain}} tags
034 * for APIs that are considered well-known. Linking well-known APIs can make comments harder to
035 * read without adding much value for the reader.
036 * </div>
037 *
038 * <p>
039 * This check reports {@code {@link}} references to configured well-known APIs.
040 * Two properties are supported:
041 * {@code wellKnownQualifiedPackages} and {@code wellKnownSimpleNames}.
042 * </p>
043 *
044 * <p>
045 * Both properties are needed because Checkstyle does not resolve Javadoc link targets.
046 * For example, {@code java.lang.String} contains the package name, so it can be
047 * matched through {@code wellKnownQualifiedPackages}. However, {@code String}
048 * only contains the simple name {@code String}, so it needs to be matched through
049 * {@code wellKnownSimpleNames}. Resolution of imports is not a solution since
050 * {@code java.lang} is implicitly imported.
051 * </p>
052 *
053 * <p>
054 * For {@code wellKnownQualifiedPackages}, only references to classes that are
055 * direct members of a well-known package are reported. References to a member
056 * (for example, {@code String#length()}), a nested class (for example,
057 * {@code System.Logger}), a subpackage (for example, {@code java.lang.ref.WeakReference}),
058 * and a package itself (for example, {@code java.lang.ref}) are not reported.
059 * </p>
060 *
061 * @since 14.1.0
062 */
063@StatelessCheck
064public class JavadocLinkWellKnownApiCheck extends AbstractJavadocCheck {
065
066    /**
067     * A key is pointing to the warning message text in "messages.properties" file.
068     */
069    public static final String MSG_WELL_KNOWN_API = "javadoc.wellKnownApi";
070
071    /**
072     * A key is pointing to the warning message text in "messages.properties" file.
073     */
074    public static final String MSG_WELL_KNOWN_PACKAGE = "javadoc.wellKnownPackage";
075
076    /**
077     * Dot.
078     */
079    private static final char DOT = '.';
080
081    /**
082     * Package names whose fully qualified API references should not be linked.
083     */
084    private Set<String> wellKnownQualifiedPackages = Set.of("java.lang");
085
086    /**
087     * Simple API names that should not be linked.
088     */
089    private Set<String> wellKnownSimpleNames = Set.of("String");
090
091    /**
092     * Creates a new {@code JavadocLinkWellKnownApiCheck} instance.
093     */
094    public JavadocLinkWellKnownApiCheck() {
095        // no code by default
096    }
097
098    @Override
099    public int[] getDefaultJavadocTokens() {
100        return getRequiredJavadocTokens();
101    }
102
103    @Override
104    public int[] getRequiredJavadocTokens() {
105        return new int[] {
106            JavadocCommentsTokenTypes.LINK_INLINE_TAG,
107            JavadocCommentsTokenTypes.LINKPLAIN_INLINE_TAG,
108        };
109    }
110
111    /**
112     * Setter to specify package names whose fully qualified API references should not be linked.
113     *
114     * @param values user's values.
115     * @since 14.1.0
116     */
117    public final void setWellKnownQualifiedPackages(String... values) {
118        wellKnownQualifiedPackages = Arrays.stream(values).collect(Collectors.toUnmodifiableSet());
119    }
120
121    /**
122     * Setter to specify simple API names that should not be linked.
123     *
124     * @param values user's values.
125     * @since 14.1.0
126     */
127    public final void setWellKnownSimpleNames(String... values) {
128        wellKnownSimpleNames = Arrays.stream(values).collect(Collectors.toUnmodifiableSet());
129    }
130
131    @Override
132    public void visitJavadocToken(DetailNode ast) {
133        final DetailNode referenceNode = JavadocUtil.findFirstToken(ast,
134                JavadocCommentsTokenTypes.REFERENCE);
135        if (JavadocUtil.findFirstToken(referenceNode,
136                JavadocCommentsTokenTypes.MEMBER_REFERENCE) == null) {
137            final String apiName = referenceNode.getFirstChild().getText();
138            if (isWellKnownQualified(apiName)) {
139                log(ast, MSG_WELL_KNOWN_PACKAGE, apiName);
140            }
141            else if (wellKnownSimpleNames.contains(apiName)) {
142                log(ast, MSG_WELL_KNOWN_API, apiName);
143            }
144        }
145    }
146
147    /**
148     * Checks whether the given API name belongs to a well-known qualified package.
149     *
150     * @param apiName the API name to check
151     * @return true if the API name belongs to a well-known qualified package
152     */
153    private boolean isWellKnownQualified(String apiName) {
154        boolean result = false;
155        for (String packageName : wellKnownQualifiedPackages) {
156            final String prefix = packageName + DOT;
157            final int prefixLength = prefix.length();
158            if (apiName.startsWith(prefix)
159                    && apiName.length() > prefixLength
160                    && Character.isUpperCase(apiName.charAt(prefixLength))
161                    && apiName.indexOf(DOT, prefixLength) == -1) {
162                result = true;
163                break;
164            }
165        }
166        return result;
167    }
168
169}