AllCheckSummaries.java
///////////////////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
// Copyright (C) 2001-2025 the original author or authors.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
///////////////////////////////////////////////////////////////////////////////////////////////
package com.puppycrawl.tools.checkstyle.site;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
import org.apache.maven.doxia.macro.AbstractMacro;
import org.apache.maven.doxia.macro.Macro;
import org.apache.maven.doxia.macro.MacroExecutionException;
import org.apache.maven.doxia.macro.MacroRequest;
import org.apache.maven.doxia.sink.Sink;
import org.codehaus.plexus.component.annotations.Component;
import com.puppycrawl.tools.checkstyle.api.DetailNode;
import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
/**
* Macro to generate table rows for all Checkstyle modules.
* Includes every Check.java file that has a Javadoc.
* Uses href path structure based on src/site/xdoc/checks.
* Usage:
* <pre>
* <macro name="allCheckSummaries"/>
* </pre>
*
* <p>Supports optional "package" parameter to filter checks by package.
* When package parameter is provided, only checks from that package are included.
* Usage:
* <pre>
* <macro name="allCheckSummaries">
* <param name="package" value="annotation"/>
* </macro>
* </pre>
*/
@Component(role = Macro.class, hint = "allCheckSummaries")
public class AllCheckSummaries extends AbstractMacro {
/** Initial capacity for StringBuilder in wrapSummary method. */
public static final int CAPACITY = 3000;
/**
* Matches common HTML tags such as paragraph, div, span, strong, and em.
* Used to remove formatting tags from the Javadoc HTML content.
* Note: anchor tags are preserved.
*/
private static final Pattern TAG_PATTERN =
Pattern.compile("(?i)</?(?:p|div|span|strong|em)[^>]*>");
/** Whitespace regex pattern string. */
private static final String WHITESPACE_REGEX = "\\s+";
/**
* Matches one or more whitespace characters.
* Used to normalize spacing in sanitized text.
*/
private static final Pattern SPACE_PATTERN = Pattern.compile(WHITESPACE_REGEX);
/**
* Matches '&' characters that are not part of a valid HTML entity.
*/
private static final Pattern AMP_PATTERN = Pattern.compile("&(?![a-zA-Z#0-9]+;)");
/**
* Pattern to match href attributes in anchor tags.
* Captures the URL within the href attribute, including any newlines.
* DOTALL flag allows . to match newlines, making the pattern work across line breaks.
*/
private static final Pattern HREF_PATTERN =
Pattern.compile("href\\s*=\\s*['\"]([^'\"]*)['\"]",
Pattern.CASE_INSENSITIVE | Pattern.DOTALL);
/** Path component for source directory. */
private static final String SRC = "src";
/** Path component for checks directory. */
private static final String CHECKS = "checks";
/** Root path for Java check files. */
private static final Path JAVA_CHECKS_ROOT = Path.of(
SRC, "main", "java", "com", "puppycrawl", "tools", "checkstyle", CHECKS);
/** Root path for site check XML files. */
private static final Path SITE_CHECKS_ROOT = Path.of(SRC, "site", "xdoc", CHECKS);
/** XML file extension. */
private static final String XML_EXTENSION = ".xml";
/** HTML file extension. */
private static final String HTML_EXTENSION = ".html";
/** TD opening tag. */
private static final String TD_TAG = "<td>";
/** TD closing tag. */
private static final String TD_CLOSE_TAG = "</td>";
/** Package name for miscellaneous checks. */
private static final String MISC_PACKAGE = "misc";
/** Package name for annotation checks. */
private static final String ANNOTATION_PACKAGE = "annotation";
/** HTML table closing tag. */
private static final String TABLE_CLOSE_TAG = "</table>";
/** HTML div closing tag. */
private static final String DIV_CLOSE_TAG = "</div>";
/** HTML section closing tag. */
private static final String SECTION_CLOSE_TAG = "</section>";
/** HTML div wrapper opening tag. */
private static final String DIV_WRAPPER_TAG = "<div class=\"wrapper\">";
/** HTML table opening tag. */
private static final String TABLE_OPEN_TAG = "<table>";
/** HTML anchor separator. */
private static final String ANCHOR_SEPARATOR = "#";
/** Regex replacement for first capture group. */
private static final String FIRST_CAPTURE_GROUP = "$1";
/** Maximum line width for complete line including indentation. */
private static final int MAX_LINE_WIDTH_TOTAL = 100;
/** Indentation width for INDENT_LEVEL_14 (14 spaces). */
private static final int INDENT_WIDTH = 14;
/** Maximum content width excluding indentation. */
private static final int MAX_CONTENT_WIDTH = MAX_LINE_WIDTH_TOTAL - INDENT_WIDTH;
/** Closing anchor tag. */
private static final String CLOSING_ANCHOR_TAG = "</a>";
/** Pattern to match trailing spaces before closing code tags. */
private static final Pattern CODE_SPACE_PATTERN = Pattern.compile(WHITESPACE_REGEX
+ "(" + CLOSING_ANCHOR_TAG.substring(0, 2) + "code>)");
@Override
public void execute(Sink sink, MacroRequest request) throws MacroExecutionException {
final String packageFilter = (String) request.getParameter("package");
final Map<String, String> xmlHrefMap = buildXmlHtmlMap();
final Map<String, CheckInfo> infos = new TreeMap<>();
processCheckFiles(infos, xmlHrefMap, packageFilter);
final StringBuilder normalRows = new StringBuilder(4096);
final StringBuilder holderRows = new StringBuilder(512);
buildTableRows(infos, normalRows, holderRows);
sink.rawText(normalRows.toString());
if (packageFilter == null && !holderRows.isEmpty()) {
appendHolderSection(sink, holderRows);
}
else if (packageFilter != null && !holderRows.isEmpty()) {
appendFilteredHolderSection(sink, holderRows, packageFilter);
}
}
/**
* Scans Java sources and populates info map with modules having Javadoc.
*
* @param infos map of collected module info
* @param xmlHrefMap map of XML-to-HTML hrefs
* @param packageFilter optional package to filter by, null for all
* @throws MacroExecutionException if file walk fails
*/
private static void processCheckFiles(Map<String, CheckInfo> infos,
Map<String, String> xmlHrefMap,
String packageFilter)
throws MacroExecutionException {
try {
final List<Path> checkFiles = new ArrayList<>();
Files.walkFileTree(JAVA_CHECKS_ROOT, new SimpleFileVisitor<>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
if (isCheckOrHolderFile(file)) {
checkFiles.add(file);
}
return FileVisitResult.CONTINUE;
}
});
checkFiles.forEach(path -> processCheckFile(path, infos, xmlHrefMap, packageFilter));
}
catch (IOException | IllegalStateException exception) {
throw new MacroExecutionException("Failed to discover checks", exception);
}
}
/**
* Checks if a path is a Check or Holder Java file.
*
* @param path the path to check
* @return true if the path is a Check or Holder file, false otherwise
*/
private static boolean isCheckOrHolderFile(Path path) {
final Path fileName = path.getFileName();
return fileName != null
&& (fileName.toString().endsWith("Check.java")
|| fileName.toString().endsWith("Holder.java"))
&& Files.isRegularFile(path);
}
/**
* Checks if a module is a holder type.
*
* @param moduleName the module name
* @return true if the module is a holder, false otherwise
*/
private static boolean isHolder(String moduleName) {
return moduleName.endsWith("Holder");
}
/**
* Processes a single check class file and extracts metadata.
*
* @param path the check class file
* @param infos map of results
* @param xmlHrefMap map of XML hrefs
* @param packageFilter optional package to filter by, null for all
* @throws IllegalArgumentException if macro execution fails
*/
private static void processCheckFile(Path path, Map<String, CheckInfo> infos,
Map<String, String> xmlHrefMap,
String packageFilter) {
try {
final String moduleName = CommonUtil.getFileNameWithoutExtension(path.toString());
final DetailNode javadoc = SiteUtil.getModuleJavadoc(moduleName, path);
if (javadoc != null) {
String description = getDescriptionIfPresent(javadoc);
if (description != null) {
description = sanitizeAnchorUrls(description);
final String[] moduleInfo = determineModuleInfo(path, moduleName);
final String packageName = moduleInfo[1];
if (packageFilter == null || packageFilter.equals(packageName)) {
final String simpleName = moduleInfo[0];
final String summary = sanitizeAndFirstSentence(description);
final String href = resolveHref(xmlHrefMap, packageName, simpleName,
packageFilter);
infos.put(simpleName, new CheckInfo(simpleName, href, summary));
}
}
}
}
catch (MacroExecutionException exceptionThrown) {
throw new IllegalArgumentException(exceptionThrown);
}
}
/**
* Determines the simple name and package name for a check module.
*
* @param path the check class file
* @param moduleName the full module name
* @return array with [simpleName, packageName]
*/
private static String[] determineModuleInfo(Path path, String moduleName) {
String packageName = extractCategoryFromJavaPath(path);
if ("indentation".equals(packageName)) {
packageName = MISC_PACKAGE;
}
if (isHolder(moduleName)) {
packageName = ANNOTATION_PACKAGE;
}
final String simpleName;
if (isHolder(moduleName)) {
simpleName = moduleName;
}
else {
simpleName = moduleName.substring(0, moduleName.length() - "Check".length());
}
return new String[] {simpleName, packageName};
}
/**
* Returns the module description if present and non-empty.
*
* @param javadoc the parsed Javadoc node
* @return the description text, or {@code null} if not present
*/
@Nullable
private static String getDescriptionIfPresent(DetailNode javadoc) {
String result = null;
if (javadoc != null) {
try {
if (ModuleJavadocParsingUtil
.getModuleSinceVersionTagStartNode(javadoc) != null) {
final String desc = ModuleJavadocParsingUtil.getModuleDescription(javadoc);
if (!desc.isEmpty()) {
result = desc;
}
}
}
catch (IllegalStateException exception) {
result = null;
}
}
return result;
}
/**
* Builds HTML rows for both normal and holder check modules.
*
* @param infos map of collected module info
* @param normalRows builder for normal check rows
* @param holderRows builder for holder check rows
*/
private static void buildTableRows(Map<String, CheckInfo> infos,
StringBuilder normalRows,
StringBuilder holderRows) {
for (CheckInfo info : infos.values()) {
final String row = buildTableRow(info);
if (isHolder(info.simpleName)) {
holderRows.append(row);
}
else {
normalRows.append(row);
}
}
removeLeadingNewline(normalRows);
removeLeadingNewline(holderRows);
}
/**
* Builds a single table row for a check module.
*
* @param info check module information
* @return the HTML table row as a string
*/
private static String buildTableRow(CheckInfo info) {
final String ind10 = ModuleJavadocParsingUtil.INDENT_LEVEL_10;
final String ind12 = ModuleJavadocParsingUtil.INDENT_LEVEL_12;
final String ind14 = ModuleJavadocParsingUtil.INDENT_LEVEL_14;
final String ind16 = ModuleJavadocParsingUtil.INDENT_LEVEL_16;
final String cleanSummary = sanitizeAnchorUrls(info.summary);
return ind10 + "<tr>"
+ ind12 + TD_TAG
+ ind14
+ "<a href=\""
+ info.link
+ "\">"
+ ind16 + info.simpleName
+ ind14 + CLOSING_ANCHOR_TAG
+ ind12 + TD_CLOSE_TAG
+ ind12 + TD_TAG
+ ind14 + wrapSummary(cleanSummary)
+ ind12 + TD_CLOSE_TAG
+ ind10 + "</tr>";
}
/**
* Removes leading newline characters from a StringBuilder.
*
* @param builder the StringBuilder to process
*/
private static void removeLeadingNewline(StringBuilder builder) {
while (!builder.isEmpty() && Character.isWhitespace(builder.charAt(0))) {
builder.delete(0, 1);
}
}
/**
* Appends the Holder Checks HTML section.
*
* @param sink the output sink
* @param holderRows the holder rows content
*/
private static void appendHolderSection(Sink sink, StringBuilder holderRows) {
final String holderSection = ModuleJavadocParsingUtil.INDENT_LEVEL_8
+ TABLE_CLOSE_TAG
+ ModuleJavadocParsingUtil.INDENT_LEVEL_6
+ DIV_CLOSE_TAG
+ ModuleJavadocParsingUtil.INDENT_LEVEL_4
+ SECTION_CLOSE_TAG
+ ModuleJavadocParsingUtil.INDENT_LEVEL_4
+ "<section name=\"Holder Checks\">"
+ ModuleJavadocParsingUtil.INDENT_LEVEL_6
+ "<p>"
+ ModuleJavadocParsingUtil.INDENT_LEVEL_8
+ "These checks aren't normal checks and are usually"
+ ModuleJavadocParsingUtil.INDENT_LEVEL_8
+ "associated with a specialized filter to gather"
+ ModuleJavadocParsingUtil.INDENT_LEVEL_8
+ "information the filter can't get on its own."
+ ModuleJavadocParsingUtil.INDENT_LEVEL_6
+ "</p>"
+ ModuleJavadocParsingUtil.INDENT_LEVEL_6
+ DIV_WRAPPER_TAG
+ ModuleJavadocParsingUtil.INDENT_LEVEL_8
+ TABLE_OPEN_TAG
+ ModuleJavadocParsingUtil.INDENT_LEVEL_10
+ holderRows;
sink.rawText(holderSection);
}
/**
* Appends the filtered Holder Checks section for package views.
*
* @param sink the output sink
* @param holderRows the holder rows content
* @param packageName the package name
*/
private static void appendFilteredHolderSection(Sink sink, StringBuilder holderRows,
String packageName) {
final String packageTitle = getPackageDisplayName(packageName);
final String holderSection = ModuleJavadocParsingUtil.INDENT_LEVEL_8
+ TABLE_CLOSE_TAG
+ ModuleJavadocParsingUtil.INDENT_LEVEL_6
+ DIV_CLOSE_TAG
+ ModuleJavadocParsingUtil.INDENT_LEVEL_4
+ SECTION_CLOSE_TAG
+ ModuleJavadocParsingUtil.INDENT_LEVEL_4
+ "<section name=\"" + packageTitle + " Holder Checks\">"
+ ModuleJavadocParsingUtil.INDENT_LEVEL_6
+ DIV_WRAPPER_TAG
+ ModuleJavadocParsingUtil.INDENT_LEVEL_8
+ TABLE_OPEN_TAG
+ ModuleJavadocParsingUtil.INDENT_LEVEL_10
+ holderRows;
sink.rawText(holderSection);
}
/**
* Get display name for package (capitalize first letter).
*
* @param packageName the package name
* @return the capitalized package name
*/
private static String getPackageDisplayName(String packageName) {
final String result;
if (packageName == null || packageName.isEmpty()) {
result = packageName;
}
else {
result = packageName.substring(0, 1).toUpperCase(Locale.ENGLISH)
+ packageName.substring(1);
}
return result;
}
/**
* Builds map of XML file names to HTML documentation paths.
*
* @return map of lowercase check names to hrefs
*/
private static Map<String, String> buildXmlHtmlMap() {
final Map<String, String> map = new TreeMap<>();
if (Files.exists(SITE_CHECKS_ROOT)) {
try {
final List<Path> xmlFiles = new ArrayList<>();
Files.walkFileTree(SITE_CHECKS_ROOT, new SimpleFileVisitor<>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
if (isValidXmlFile(file)) {
xmlFiles.add(file);
}
return FileVisitResult.CONTINUE;
}
});
xmlFiles.forEach(path -> addXmlHtmlMapping(path, map));
}
catch (IOException ignored) {
// ignore
}
}
return map;
}
/**
* Checks if a path is a valid XML file for processing.
*
* @param path the path to check
* @return true if the path is a valid XML file, false otherwise
*/
private static boolean isValidXmlFile(Path path) {
final Path fileName = path.getFileName();
return fileName != null
&& !("index" + XML_EXTENSION).equalsIgnoreCase(fileName.toString())
&& path.toString().endsWith(XML_EXTENSION)
&& Files.isRegularFile(path);
}
/**
* Adds XML-to-HTML mapping entry to map.
*
* @param path the XML file path
* @param map the mapping to update
*/
private static void addXmlHtmlMapping(Path path, Map<String, String> map) {
final Path fileName = path.getFileName();
if (fileName != null) {
final String fileNameString = fileName.toString();
final int extensionLength = 4;
final String base = fileNameString.substring(0,
fileNameString.length() - extensionLength)
.toLowerCase(Locale.ROOT);
final Path relativePath = SITE_CHECKS_ROOT.relativize(path);
final String relativePathString = relativePath.toString();
final String rel = relativePathString
.replace('\\', '/')
.replace(XML_EXTENSION, HTML_EXTENSION);
map.put(base, CHECKS + "/" + rel);
}
}
/**
* Resolves the href for a given check module.
* When packageFilter is null, returns full path: checks/category/filename.html#CheckName
* When packageFilter is set, returns relative path: filename.html#CheckName
*
* @param xmlMap map of XML file names to HTML paths
* @param category the category of the check
* @param simpleName simple name of the check
* @param packageFilter optional package filter, null for all checks
* @return the resolved href for the check
*/
private static String resolveHref(Map<String, String> xmlMap, String category,
String simpleName, @Nullable String packageFilter) {
final String lower = simpleName.toLowerCase(Locale.ROOT);
final String href = xmlMap.get(lower);
final String result;
if (href != null) {
if (packageFilter == null) {
result = href + ANCHOR_SEPARATOR + simpleName;
}
else {
final int lastSlash = href.lastIndexOf('/');
final String filename;
if (lastSlash >= 0) {
filename = href.substring(lastSlash + 1);
}
else {
filename = href;
}
result = filename + ANCHOR_SEPARATOR + simpleName;
}
}
else {
if (packageFilter == null) {
result = String.format(Locale.ROOT, "%s/%s/%s.html%s%s",
CHECKS, category, lower, ANCHOR_SEPARATOR, simpleName);
}
else {
result = String.format(Locale.ROOT, "%s.html%s%s",
lower, ANCHOR_SEPARATOR, simpleName);
}
}
return result;
}
/**
* Extracts category path from a Java file path.
*
* @param javaPath the Java source file path
* @return the category path extracted from the Java path
*/
private static String extractCategoryFromJavaPath(Path javaPath) {
final Path rel = JAVA_CHECKS_ROOT.relativize(javaPath);
final Path parent = rel.getParent();
final String result;
if (parent == null) {
result = MISC_PACKAGE;
}
else {
result = parent.toString().replace('\\', '/');
}
return result;
}
/**
* Sanitizes URLs within anchor tags by removing whitespace from href attributes.
*
* @param html the HTML string containing anchor tags
* @return the HTML with sanitized URLs
*/
private static String sanitizeAnchorUrls(String html) {
final String result;
if (html == null || html.isEmpty()) {
result = html;
}
else {
final Matcher matcher = HREF_PATTERN.matcher(html);
final StringBuilder buffer = new StringBuilder(html.length());
while (matcher.find()) {
final String originalUrl = matcher.group(1);
final String cleanedUrl = SPACE_PATTERN.matcher(originalUrl).replaceAll("");
final String replacement = "href=\""
+ Matcher.quoteReplacement(cleanedUrl) + "\"";
matcher.appendReplacement(buffer, replacement);
}
matcher.appendTail(buffer);
result = buffer.toString();
}
return result;
}
/**
* Sanitizes HTML and extracts first sentence.
* Preserves anchor tags while removing other HTML formatting.
* Also cleans whitespace from URLs in href attributes.
*
* @param html the HTML string to process
* @return the sanitized first sentence
*/
private static String sanitizeAndFirstSentence(String html) {
final String result;
if (html == null || html.isEmpty()) {
result = "";
}
else {
String cleaned = sanitizeAnchorUrls(html);
cleaned = TAG_PATTERN.matcher(cleaned).replaceAll("");
cleaned = SPACE_PATTERN.matcher(cleaned).replaceAll(" ").trim();
cleaned = AMP_PATTERN.matcher(cleaned).replaceAll("&");
cleaned = CODE_SPACE_PATTERN.matcher(cleaned).replaceAll(FIRST_CAPTURE_GROUP);
result = extractFirstSentence(cleaned);
}
return result;
}
/**
* Extracts first sentence from plain text.
*
* @param text the text to process
* @return the first sentence extracted from the text
*/
private static String extractFirstSentence(String text) {
String result = "";
if (text != null && !text.isEmpty()) {
int end = -1;
for (int index = 0; index < text.length(); index++) {
if (text.charAt(index) == '.'
&& (index == text.length() - 1
|| Character.isWhitespace(text.charAt(index + 1))
|| text.charAt(index + 1) == '<')) {
end = index;
break;
}
}
if (end == -1) {
result = text.trim();
}
else {
result = text.substring(0, end + 1).trim();
}
}
return result;
}
/**
* Wraps long summaries to avoid exceeding line width.
* Preserves URLs in anchor tags by breaking after the opening tag's closing bracket.
*
* @param text the text to wrap
* @return the wrapped text
*/
private static String wrapSummary(String text) {
String wrapped = "";
if (text != null && !text.isEmpty()) {
final String sanitized = sanitizeAnchorUrls(text);
final String indent = ModuleJavadocParsingUtil.INDENT_LEVEL_14;
final String clean = sanitized.trim();
final StringBuilder result = new StringBuilder(CAPACITY);
int cleanIndex = 0;
final int cleanLen = clean.length();
while (cleanIndex < cleanLen) {
final int remainingChars = cleanLen - cleanIndex;
if (remainingChars <= MAX_CONTENT_WIDTH) {
result.append(indent)
.append(clean.substring(cleanIndex))
.append('\n');
break;
}
final int idealBreak = cleanIndex + MAX_CONTENT_WIDTH;
final int actualBreak = calculateBreakPoint(clean, cleanIndex, idealBreak);
result.append(indent)
.append(clean, cleanIndex, actualBreak);
cleanIndex = actualBreak;
while (cleanIndex < cleanLen && clean.charAt(cleanIndex) == ' ') {
cleanIndex++;
}
}
wrapped = result.toString().trim();
}
return wrapped;
}
/**
* Calculates the appropriate break point for text wrapping.
* Handles anchor tags specially to avoid breaking URLs.
*
* @param clean the cleaned text to process
* @param cleanIndex current position in text
* @param idealBreak ideal break position
* @return the actual break position
*/
private static int calculateBreakPoint(String clean, int cleanIndex, int idealBreak) {
final int anchorStart = clean.indexOf("<a ", cleanIndex);
final int anchorOpenEnd;
if (anchorStart == -1) {
anchorOpenEnd = -1;
}
else {
anchorOpenEnd = clean.indexOf('>', anchorStart);
}
final int actualBreak;
if (shouldBreakAfterAnchorOpen(anchorStart, anchorOpenEnd, idealBreak)) {
actualBreak = anchorOpenEnd + 1;
}
else if (shouldBreakAfterAnchorContent(anchorStart, anchorOpenEnd,
idealBreak, clean)) {
actualBreak = anchorOpenEnd + 1;
}
else {
actualBreak = findSafeBreakPoint(clean, cleanIndex, idealBreak);
}
return actualBreak;
}
/**
* Determines if break should occur after anchor opening tag.
*
* @param anchorStart start position of anchor tag
* @param anchorOpenEnd end position of anchor opening tag
* @param idealBreak ideal break position
* @return true if should break after anchor opening
*/
private static boolean shouldBreakAfterAnchorOpen(int anchorStart, int anchorOpenEnd,
int idealBreak) {
return anchorStart != -1 && anchorStart < idealBreak
&& anchorOpenEnd != -1 && anchorOpenEnd >= idealBreak;
}
/**
* Determines if break should occur after anchor content.
*
* @param anchorStart start position of anchor tag
* @param anchorOpenEnd end position of anchor opening tag
* @param idealBreak ideal break position
* @param clean the text being processed
* @return true if should break after anchor content
*/
private static boolean shouldBreakAfterAnchorContent(int anchorStart, int anchorOpenEnd,
int idealBreak, String clean) {
final boolean result;
if (anchorStart != -1 && anchorStart < idealBreak
&& anchorOpenEnd != -1 && anchorOpenEnd < idealBreak) {
final int anchorCloseStart = clean.indexOf(CLOSING_ANCHOR_TAG, anchorOpenEnd);
result = anchorCloseStart != -1 && anchorCloseStart >= idealBreak;
}
else {
result = false;
}
return result;
}
/**
* Finds a safe break point at a space character.
*
* @param text the text to search
* @param start the start index
* @param idealBreak the ideal break position
* @return the actual break position
*/
private static int findSafeBreakPoint(String text, int start, int idealBreak) {
final int actualBreak;
final int lastSpace = text.lastIndexOf(' ', idealBreak);
if (lastSpace > start && lastSpace >= start + MAX_CONTENT_WIDTH / 2) {
actualBreak = lastSpace;
}
else {
actualBreak = idealBreak;
}
return actualBreak;
}
/**
* Data holder for each Check module entry.
*/
private static final class CheckInfo {
/** Simple name of the check. */
private final String simpleName;
/** Documentation link. */
private final String link;
/** Short summary text. */
private final String summary;
/**
* Constructs an info record.
*
* @param simpleName check simple name
* @param link documentation link
* @param summary module summary
*/
private CheckInfo(String simpleName, String link, String summary) {
this.simpleName = simpleName;
this.link = link;
this.summary = summary;
}
}
}