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"
068     * file.
069     */
070    public static final String MSG_WELL_KNOWN_API = "javadoc.wellKnownApi";
071
072    /**
073     * A key is pointing to the warning message text in "messages.properties"
074     * file.
075     */
076    public static final String MSG_WELL_KNOWN_PACKAGE = "javadoc.wellKnownPackage";
077
078    /**
079     * Dot.
080     */
081    private static final char DOT = '.';
082
083    /**
084     * Package names whose fully qualified API references should not be linked.
085     */
086    private Set<String> wellKnownQualifiedPackages = Set.of("java.lang");
087
088    /**
089     * Simple API names that should not be linked.
090     */
091    private Set<String> wellKnownSimpleNames = Set.of("String");
092
093    /**
094     * Creates a new {@code JavadocLinkWellKnownApiCheck} instance.
095     */
096    public JavadocLinkWellKnownApiCheck() {
097        // no code by default
098    }
099
100    @Override
101    public int[] getDefaultJavadocTokens() {
102        return getRequiredJavadocTokens();
103    }
104
105    @Override
106    public int[] getRequiredJavadocTokens() {
107        return new int[] {
108            JavadocCommentsTokenTypes.LINK_INLINE_TAG,
109            JavadocCommentsTokenTypes.LINKPLAIN_INLINE_TAG,
110        };
111    }
112
113    /**
114     * Setter to specify package names whose fully qualified API references should not be
115     * linked.
116     *
117     * @param values user's values.
118     * @since 14.1.0
119     */
120    public final void setWellKnownQualifiedPackages(String... values) {
121        wellKnownQualifiedPackages = Arrays.stream(values).collect(Collectors.toUnmodifiableSet());
122    }
123
124    /**
125     * Setter to specify simple API names that should not be linked.
126     *
127     * @param values user's values.
128     * @since 14.1.0
129     */
130    public final void setWellKnownSimpleNames(String... values) {
131        wellKnownSimpleNames = Arrays.stream(values).collect(Collectors.toUnmodifiableSet());
132    }
133
134    @Override
135    public void visitJavadocToken(DetailNode ast) {
136        final DetailNode referenceNode = JavadocUtil.findFirstToken(ast,
137                JavadocCommentsTokenTypes.REFERENCE);
138        if (JavadocUtil.findFirstToken(referenceNode,
139                JavadocCommentsTokenTypes.MEMBER_REFERENCE) == null) {
140            final String apiName = referenceNode.getFirstChild().getText();
141            if (isWellKnownQualified(apiName)) {
142                log(ast, MSG_WELL_KNOWN_PACKAGE, apiName);
143            }
144            else if (wellKnownSimpleNames.contains(apiName)) {
145                log(ast, MSG_WELL_KNOWN_API, apiName);
146            }
147        }
148    }
149
150    /**
151     * Checks whether the given API name belongs to a well-known qualified package.
152     *
153     * @param apiName the API name to check
154     * @return true if the API name belongs to a well-known qualified package
155     */
156    private boolean isWellKnownQualified(String apiName) {
157        boolean result = false;
158        for (String packageName : wellKnownQualifiedPackages) {
159            final String prefix = packageName + DOT;
160            final int prefixLength = prefix.length();
161            if (apiName.startsWith(prefix)
162                    && apiName.length() > prefixLength
163                    && Character.isUpperCase(apiName.charAt(prefixLength))
164                    && apiName.indexOf(DOT, prefixLength) == -1) {
165                result = true;
166                break;
167            }
168        }
169        return result;
170    }
171
172}