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}