001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2025 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.meta; 021 022import java.util.ArrayDeque; 023import java.util.Arrays; 024import java.util.Deque; 025import java.util.HashSet; 026import java.util.Optional; 027import java.util.Set; 028import java.util.regex.Matcher; 029import java.util.regex.Pattern; 030 031import com.puppycrawl.tools.checkstyle.api.DetailNode; 032import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes; 033 034/** 035 * Class for scraping module metadata from the corresponding class' class-level javadoc. 036 */ 037public final class JavadocMetadataScraperUtil { 038 039 /** Regular expression for detecting ANTLR tokens(for e.g. CLASS_DEF). */ 040 private static final Pattern TOKEN_TEXT_PATTERN = Pattern.compile("([A-Z_]{2,})+"); 041 042 /** 043 * Private utility constructor. 044 */ 045 private JavadocMetadataScraperUtil() { 046 } 047 048 /** 049 * Performs a DFS of the subtree with a node as the root and constructs the text of that 050 * tree, ignoring JavadocToken texts. 051 * 052 * @param node root node of subtree 053 * @param childLeftLimit the left index of root children from where to scan 054 * @param childRightLimit the right index of root children till where to scan 055 * @return constructed text of subtree 056 */ 057 public static String constructSubTreeText(DetailNode node, int childLeftLimit, 058 int childRightLimit) { 059 DetailNode detailNode = node; 060 061 final Deque<DetailNode> stack = new ArrayDeque<>(); 062 stack.addFirst(detailNode); 063 final Set<DetailNode> visited = new HashSet<>(); 064 final StringBuilder result = new StringBuilder(1024); 065 while (!stack.isEmpty()) { 066 detailNode = stack.removeFirst(); 067 068 if (visited.add(detailNode) && isContentToWrite(detailNode)) { 069 String childText = detailNode.getText(); 070 071 if (detailNode.getParent().getType() == JavadocTokenTypes.JAVADOC_INLINE_TAG) { 072 childText = adjustCodeInlineTagChildToHtml(detailNode); 073 } 074 075 result.insert(0, childText); 076 } 077 078 for (DetailNode child : detailNode.getChildren()) { 079 if (child.getParent().equals(node) 080 && (child.getIndex() < childLeftLimit 081 || child.getIndex() > childRightLimit)) { 082 continue; 083 } 084 if (!visited.contains(child)) { 085 stack.addFirst(child); 086 } 087 } 088 } 089 return result.toString().trim(); 090 } 091 092 /** 093 * Checks whether selected Javadoc node is considered as something to write. 094 * 095 * @param detailNode javadoc node to check. 096 * @return whether javadoc node is something to write. 097 */ 098 private static boolean isContentToWrite(DetailNode detailNode) { 099 100 return detailNode.getType() != JavadocTokenTypes.LEADING_ASTERISK 101 && (detailNode.getType() == JavadocTokenTypes.TEXT 102 || !TOKEN_TEXT_PATTERN.matcher(detailNode.getText()).matches()); 103 } 104 105 /** 106 * Adjusts certain child of {@code @code} Javadoc inline tag to its analogous html format. 107 * 108 * @param codeChild {@code @code} child to convert. 109 * @return converted {@code @code} child element, otherwise just the original text. 110 */ 111 public static String adjustCodeInlineTagChildToHtml(DetailNode codeChild) { 112 113 return switch (codeChild.getType()) { 114 case JavadocTokenTypes.JAVADOC_INLINE_TAG_END -> "</code>"; 115 case JavadocTokenTypes.WS -> ""; 116 case JavadocTokenTypes.CODE_LITERAL -> codeChild.getText().replace("@", "") + ">"; 117 case JavadocTokenTypes.JAVADOC_INLINE_TAG_START -> "<"; 118 default -> codeChild.getText(); 119 }; 120 } 121 122 /** 123 * Returns the first child node which matches the provided {@code TokenType} and has the 124 * children index after the offset value. 125 * 126 * @param node parent node 127 * @param tokenType token type to match 128 * @param offset children array index offset 129 * @return the first child satisfying the conditions 130 */ 131 private static Optional<DetailNode> getFirstChildOfType(DetailNode node, int tokenType, 132 int offset) { 133 return Arrays.stream(node.getChildren()) 134 .filter(child -> child.getIndex() >= offset && child.getType() == tokenType) 135 .findFirst(); 136 } 137 138 /** 139 * Checks whether the first child {@code JavadocTokenType.TEXT} node matches given pattern. 140 * 141 * @param ast parent javadoc node 142 * @param pattern pattern to match 143 * @return true if one of child text nodes matches pattern 144 */ 145 public static boolean isChildNodeTextMatches(DetailNode ast, Pattern pattern) { 146 return getFirstChildOfType(ast, JavadocTokenTypes.TEXT, 0) 147 .map(DetailNode::getText) 148 .map(pattern::matcher) 149 .map(Matcher::matches) 150 .orElse(Boolean.FALSE); 151 } 152}