001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2020 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.Collections;
025import java.util.Deque;
026import java.util.HashMap;
027import java.util.HashSet;
028import java.util.LinkedHashSet;
029import java.util.Map;
030import java.util.Optional;
031import java.util.Set;
032import java.util.regex.Matcher;
033import java.util.regex.Pattern;
034import java.util.stream.Collectors;
035
036import javax.xml.parsers.ParserConfigurationException;
037import javax.xml.transform.TransformerException;
038
039import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
040import com.puppycrawl.tools.checkstyle.api.DetailAST;
041import com.puppycrawl.tools.checkstyle.api.DetailNode;
042import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes;
043import com.puppycrawl.tools.checkstyle.api.TokenTypes;
044import com.puppycrawl.tools.checkstyle.checks.javadoc.AbstractJavadocCheck;
045import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
046
047/**
048 * Class for scraping module metadata from the corresponding class' class-level javadoc.
049 */
050@FileStatefulCheck
051public class JavadocMetadataScraper extends AbstractJavadocCheck {
052
053    /** Module details store used for testing. */
054    private static final Map<String, ModuleDetails> MODULE_DETAILS_STORE = new HashMap<>();
055
056    /** Regular expression for property location in class-level javadocs. */
057    private static final Pattern PROPERTY_TAG = Pattern.compile("\\s*Property\\s*");
058
059    /** Regular expression for property type location in class-level javadocs. */
060    private static final Pattern TYPE_TAG = Pattern.compile("\\s.*Type is\\s.*");
061
062    /** Regular expression for property validation type location in class-level javadocs. */
063    private static final Pattern VALIDATION_TYPE_TAG =
064            Pattern.compile("\\s.*Validation type is\\s.*");
065
066    /** Regular expression for property default value location in class-level javadocs. */
067    private static final Pattern DEFAULT_VALUE_TAG = Pattern.compile("\\s*Default value is:*.*");
068
069    /** Regular expression for check example location in class-level javadocs. */
070    private static final Pattern EXAMPLES_TAG =
071            Pattern.compile("\\s*To configure the (default )?check.*");
072
073    /** Regular expression for module parent location in class-level javadocs. */
074    private static final Pattern PARENT_TAG = Pattern.compile("\\s*Parent is\\s*");
075
076    /** Regular expression for module violation messages location in class-level javadocs. */
077    private static final Pattern VIOLATION_MESSAGES_TAG =
078            Pattern.compile("\\s*Violation Message Keys:\\s*");
079
080    /** Regular expression for detecting ANTLR tokens(for e.g. CLASS_DEF). */
081    private static final Pattern TOKEN_TEXT_PATTERN = Pattern.compile("([A-Z]+_*+)+[A-Z]+");
082
083    /** Regular expression for removal of @code{-} present at the beginning of texts. */
084    private static final Pattern DESC_CLEAN = Pattern.compile("-\\s");
085
086    /** Regular expression for file separator corresponding to the host OS. */
087    private static final Pattern FILE_SEPARATOR_PATTERN =
088            Pattern.compile(Pattern.quote(System.getProperty("file.separator")));
089
090    /** Regular expression for quotes. */
091    private static final Pattern QUOTE_PATTERN = Pattern.compile("\"");
092
093    /** Java file extension. */
094    private static final String JAVA_FILE_EXTENSION = ".java";
095
096    /**
097     * This set contains faulty property default value which should not be written to the XML
098     * metadata files.
099     */
100    private static final Set<String> PROPERTIES_TO_NOT_WRITE = Collections.unmodifiableSet(
101            new HashSet<>(Arrays.asList(
102                    "null",
103                    "the charset property of the parent <a href=https://checkstyle.org/"
104                        + "config.html#Checker>Checker</a> module"
105    )));
106
107    /** ModuleDetails instance for each module AST traversal. */
108    private ModuleDetails moduleDetails;
109
110    /**
111     * Boolean variable which lets us know whether violation message section is being scraped
112     * currently.
113     */
114    private boolean scrapingViolationMessageList;
115
116    /**
117     * Boolean variable which lets us know whether we should scan and scrape the current javadoc
118     * or not. Since we need only class level javadoc, it becomes true at its root and false after
119     * encountering {@code JavadocTokenTypes.SINCE_LITERAL}.
120     */
121    private boolean toScan;
122
123    /** DetailNode pointing to the root node of the class level javadoc of the class. */
124    private DetailNode rootNode;
125
126    /**
127     * Child number of the property section node, where parent is the class level javadoc root
128     * node.
129     */
130    private int propertySectionStartIdx;
131
132    /**
133     * Child number of the example section node, where parent is the class level javadoc root
134     * node.
135     */
136    private int exampleSectionStartIdx;
137
138    /**
139     * Child number of the parent section node, where parent is the class level javadoc root
140     * node.
141     */
142    private int parentSectionStartIdx;
143
144    @Override
145    public int[] getDefaultJavadocTokens() {
146        return new int[] {
147            JavadocTokenTypes.JAVADOC,
148            JavadocTokenTypes.PARAGRAPH,
149            JavadocTokenTypes.LI,
150            JavadocTokenTypes.SINCE_LITERAL,
151        };
152    }
153
154    @Override
155    public int[] getRequiredJavadocTokens() {
156        return getAcceptableJavadocTokens();
157    }
158
159    @Override
160    public void beginJavadocTree(DetailNode rootAst) {
161        if (isTopLevelClassJavadoc()) {
162            moduleDetails = new ModuleDetails();
163            toScan = false;
164            scrapingViolationMessageList = false;
165            propertySectionStartIdx = -1;
166            exampleSectionStartIdx = -1;
167            parentSectionStartIdx = -1;
168
169            final String filePath = getFileContents().getFileName();
170            String moduleName = getModuleSimpleName();
171            final String checkModuleExtension = "Check";
172            if (moduleName.contains(checkModuleExtension)) {
173                moduleName = moduleName.substring(0, moduleName.indexOf(checkModuleExtension));
174            }
175            moduleDetails.setName(moduleName);
176            moduleDetails.setFullQualifiedName(getPackageName(filePath));
177            moduleDetails.setModuleType(getModuleType());
178        }
179    }
180
181    @Override
182    public void visitJavadocToken(DetailNode ast) {
183        if (toScan) {
184            scrapeContent(ast);
185        }
186
187        if (ast.getType() == JavadocTokenTypes.JAVADOC) {
188            final DetailAST parent = getParent(getBlockCommentAst());
189            if (parent != null && parent.getType() == TokenTypes.CLASS_DEF) {
190                rootNode = ast;
191                toScan = true;
192            }
193        }
194        else if (ast.getType() == JavadocTokenTypes.SINCE_LITERAL) {
195            toScan = false;
196        }
197    }
198
199    @Override
200    public void finishJavadocTree(DetailNode rootAst) {
201        moduleDetails.setDescription(getDescriptionText());
202        if (isTopLevelClassJavadoc()) {
203            if (getFileContents().getFileName().contains("test")) {
204                MODULE_DETAILS_STORE.put(moduleDetails.getFullQualifiedName(), moduleDetails);
205            }
206            else {
207                try {
208                    XmlMetaWriter.write(moduleDetails);
209                }
210                catch (TransformerException | ParserConfigurationException ex) {
211                    throw new IllegalStateException("Failed to write metadata into XML file for "
212                            + "module: " + getModuleSimpleName(), ex);
213                }
214            }
215        }
216    }
217
218    /**
219     * Method containing the core logic of scraping. This keeps track and decides which phase of
220     * scraping we are in, and accordingly call other subroutines.
221     *
222     * @param ast javadoc ast
223     */
224    public void scrapeContent(DetailNode ast) {
225        if (ast.getType() == JavadocTokenTypes.PARAGRAPH) {
226            if (isParentText(ast)) {
227                parentSectionStartIdx = getParentIndexOf(ast);
228                moduleDetails.setParent(getParentText(ast));
229            }
230            else if (isViolationMessagesText(ast)) {
231                scrapingViolationMessageList = true;
232            }
233            else if (exampleSectionStartIdx == -1
234                    && isExamplesText(ast)) {
235                exampleSectionStartIdx = getParentIndexOf(ast);
236            }
237        }
238        else if (ast.getType() == JavadocTokenTypes.LI) {
239            if (isPropertyList(ast)) {
240                if (propertySectionStartIdx == -1) {
241                    propertySectionStartIdx = getParentIndexOf(ast);
242                }
243                moduleDetails.addToProperties(createProperties(ast));
244            }
245            else if (scrapingViolationMessageList) {
246                moduleDetails.addToViolationMessages(getViolationMessages(ast));
247            }
248        }
249    }
250
251    /**
252     * Create the modulePropertyDetails content.
253     *
254     * @param nodeLi list item javadoc node
255     * @return modulePropertyDetail object for the corresponding property
256     */
257    private static ModulePropertyDetails createProperties(DetailNode nodeLi) {
258        final ModulePropertyDetails modulePropertyDetails = new ModulePropertyDetails();
259
260        final Optional<DetailNode> propertyNameNode = getFirstChildOfType(nodeLi,
261                JavadocTokenTypes.JAVADOC_INLINE_TAG, 0);
262        if (propertyNameNode.isPresent()) {
263            final DetailNode propertyNameTag = propertyNameNode.get();
264
265            final Optional<DetailNode> propertyTypeNode =
266                    getFirstChildOfMatchingText(nodeLi, TYPE_TAG);
267            if (propertyTypeNode.isPresent()) {
268                final DetailNode propertyType = propertyTypeNode.get();
269                final String propertyDesc = DESC_CLEAN.matcher(
270                        constructSubTreeText(nodeLi, propertyNameTag.getIndex() + 1,
271                                propertyType.getIndex() - 1))
272                        .replaceAll(Matcher.quoteReplacement(""));
273
274                modulePropertyDetails.setDescription(propertyDesc.trim());
275            }
276
277            modulePropertyDetails.setName(getTextFromTag(propertyNameTag));
278        }
279
280        final Optional<DetailNode> typeNode = getFirstChildOfMatchingText(nodeLi, TYPE_TAG);
281        if (typeNode.isPresent()) {
282            modulePropertyDetails.setType(getTagTextFromProperty(nodeLi, typeNode.get()));
283        }
284
285        final Optional<DetailNode> validationTypeNodeOpt = getFirstChildOfMatchingText(nodeLi,
286                VALIDATION_TYPE_TAG);
287        if (validationTypeNodeOpt.isPresent()) {
288            final DetailNode validationTypeNode = validationTypeNodeOpt.get();
289            modulePropertyDetails.setValidationType(getTagTextFromProperty(nodeLi,
290                    validationTypeNode));
291        }
292
293        final String defaultValue = getPropertyDefaultText(nodeLi);
294        if (!PROPERTIES_TO_NOT_WRITE.contains(defaultValue)) {
295            modulePropertyDetails.setDefaultValue(defaultValue);
296        }
297        return modulePropertyDetails;
298    }
299
300    /**
301     * Get tag text from property data.
302     *
303     * @param nodeLi javadoc li item node
304     * @param propertyMeta property javadoc node
305     * @return property metadata text
306     */
307    private static String getTagTextFromProperty(DetailNode nodeLi, DetailNode propertyMeta) {
308        final Optional<DetailNode> tagNodeOpt = getFirstChildOfType(nodeLi,
309                JavadocTokenTypes.JAVADOC_INLINE_TAG, propertyMeta.getIndex() + 1);
310        DetailNode tagNode = null;
311        if (tagNodeOpt.isPresent()) {
312            tagNode = tagNodeOpt.get();
313        }
314        return getTextFromTag(tagNode);
315    }
316
317    /**
318     * Clean up the default token text by removing hyperlinks, and only keeping token type text.
319     *
320     * @param initialText unclean text
321     * @return clean text
322     */
323    private static String cleanDefaultTokensText(String initialText) {
324        final Set<String> tokens = new LinkedHashSet<>();
325        final Matcher matcher = TOKEN_TEXT_PATTERN.matcher(initialText);
326        while (matcher.find()) {
327            tokens.add(matcher.group(0));
328        }
329        return String.join(",", tokens);
330    }
331
332    /**
333     * Performs a DFS of the subtree with a node as the root and constructs the text of that
334     * tree, ignoring JavadocToken texts.
335     *
336     * @param node root node of subtree
337     * @param childLeftLimit the left index of root children from where to scan
338     * @param childRightLimit the right index of root children till where to scan
339     * @return constructed text of subtree
340     */
341    private static String constructSubTreeText(DetailNode node, int childLeftLimit,
342                                               int childRightLimit) {
343        final StringBuilder result = new StringBuilder(1024);
344        DetailNode detailNode = node;
345
346        final Deque<DetailNode> stack = new ArrayDeque<>();
347        stack.addFirst(detailNode);
348        final Set<DetailNode> visited = new HashSet<>();
349        while (!stack.isEmpty()) {
350            detailNode = stack.getFirst();
351            stack.removeFirst();
352
353            if (!visited.contains(detailNode)) {
354                final String childText = detailNode.getText();
355                if (detailNode.getType() != JavadocTokenTypes.LEADING_ASTERISK
356                        && !TOKEN_TEXT_PATTERN.matcher(childText).matches()) {
357                    result.insert(0, detailNode.getText());
358                }
359                visited.add(detailNode);
360            }
361
362            for (DetailNode child : detailNode.getChildren()) {
363                if (child.getParent().equals(node)
364                        && (child.getIndex() < childLeftLimit
365                        || child.getIndex() > childRightLimit)) {
366                    continue;
367                }
368                if (!visited.contains(child)) {
369                    stack.addFirst(child);
370                }
371            }
372        }
373        return result.toString().trim();
374    }
375
376    /**
377     * Create the description text with starting index as 0 and ending index would be the first
378     * valid non zero index amongst in the order of {@code propertySectionStartIdx},
379     * {@code exampleSectionStartIdx} and {@code parentSectionStartIdx}.
380     *
381     * @return description text
382     */
383    private String getDescriptionText() {
384        final int descriptionEndIdx;
385        if (propertySectionStartIdx > -1) {
386            descriptionEndIdx = propertySectionStartIdx;
387        }
388        else if (exampleSectionStartIdx > -1) {
389            descriptionEndIdx = exampleSectionStartIdx;
390        }
391        else {
392            descriptionEndIdx = parentSectionStartIdx;
393        }
394        return constructSubTreeText(rootNode, 0, descriptionEndIdx - 1);
395    }
396
397    /**
398     * Create property default text, which is either normal property value or list of tokens.
399     *
400     * @param nodeLi list item javadoc node
401     * @return default property text
402     */
403    private static String getPropertyDefaultText(DetailNode nodeLi) {
404        String result = "";
405        final Optional<DetailNode> defaultValueNodeOpt = getFirstChildOfMatchingText(nodeLi,
406                DEFAULT_VALUE_TAG);
407        if (defaultValueNodeOpt.isPresent()) {
408            final DetailNode defaultValueNode = defaultValueNodeOpt.get();
409            final Optional<DetailNode> propertyDefaultValueTagNode = getFirstChildOfType(nodeLi,
410                    JavadocTokenTypes.JAVADOC_INLINE_TAG, defaultValueNode.getIndex() + 1);
411            DetailNode propertyDefaultValueTag = null;
412            if (propertyDefaultValueTagNode.isPresent()) {
413                propertyDefaultValueTag = propertyDefaultValueTagNode.get();
414            }
415            if (propertyDefaultValueTag == null) {
416                final String tokenText = constructSubTreeText(nodeLi,
417                        defaultValueNode.getIndex(), nodeLi.getChildren().length);
418                result = cleanDefaultTokensText(tokenText);
419            }
420            else {
421                result = getTextFromTag(propertyDefaultValueTag);
422            }
423        }
424        return result;
425    }
426
427    /**
428     * Get the violation message text for a specific key from the list item.
429     *
430     * @param nodeLi list item javadoc node
431     * @return violation message key text
432     */
433    private static String getViolationMessages(DetailNode nodeLi) {
434        final Optional<DetailNode> resultNode = getFirstChildOfType(nodeLi,
435                JavadocTokenTypes.JAVADOC_INLINE_TAG, 0);
436        return resultNode.map(JavadocMetadataScraper::getTextFromTag).orElse("");
437    }
438
439    /**
440     * Get text from {@code JavadocTokenTypes.JAVADOC_INLINE_TAG}.
441     *
442     * @param nodeTag target javadoc tag
443     * @return text contained by the tag
444     */
445    private static String getTextFromTag(DetailNode nodeTag) {
446        return Optional.ofNullable(nodeTag).map(JavadocMetadataScraper::getText).orElse("");
447    }
448
449    /**
450     * Returns the first child node which matches the provided {@code TokenType} and has the
451     * children index after the offset value.
452     *
453     * @param node parent node
454     * @param tokenType token type to match
455     * @param offset children array index offset
456     * @return the first child satisfying the conditions
457     */
458    private static Optional<DetailNode> getFirstChildOfType(DetailNode node, int tokenType,
459                                                            int offset) {
460        return Arrays.stream(node.getChildren())
461                .filter(child -> child.getIndex() >= offset && child.getType() == tokenType)
462                .findFirst();
463    }
464
465    /**
466     * Get joined text from all text children nodes.
467     *
468     * @param parentNode parent node
469     * @return the joined text of node
470     */
471    private static String getText(DetailNode parentNode) {
472        return Arrays.stream(parentNode.getChildren())
473                .filter(child -> child.getType() == JavadocTokenTypes.TEXT)
474                .map(node -> QUOTE_PATTERN.matcher(node.getText().trim()).replaceAll(""))
475                .collect(Collectors.joining(" "));
476    }
477
478    /**
479     * Get first child of parent node matching the provided pattern.
480     *
481     * @param node parent node
482     * @param pattern pattern to match against
483     * @return the first child node matching the condition
484     */
485    private static Optional<DetailNode> getFirstChildOfMatchingText(DetailNode node,
486                                                                    Pattern pattern) {
487        return Arrays.stream(node.getChildren())
488                .filter(child -> pattern.matcher(child.getText()).matches())
489                .findFirst();
490    }
491
492    /**
493     * Returns parent node, removing modifier/annotation nodes.
494     *
495     * @param commentBlock child node.
496     * @return parent node.
497     */
498    private static DetailAST getParent(DetailAST commentBlock) {
499        final DetailAST parentNode = commentBlock.getParent();
500        DetailAST result = null;
501        if (parentNode != null) {
502            result = parentNode;
503            if (result.getType() == TokenTypes.ANNOTATION) {
504                result = parentNode.getParent().getParent();
505            }
506            else if (result.getType() == TokenTypes.MODIFIERS) {
507                result = parentNode.getParent();
508            }
509        }
510        return result;
511    }
512
513    /**
514     * Traverse parents until we reach the root node (@code{JavadocTokenTypes.JAVADOC})
515     * child and return its index.
516     *
517     * @param node subtree child node
518     * @return root node child index
519     */
520    private static int getParentIndexOf(DetailNode node) {
521        DetailNode currNode = node;
522        while (currNode.getParent().getIndex() != -1) {
523            currNode = currNode.getParent();
524        }
525        return currNode.getIndex();
526    }
527
528    /**
529     * Get module parent text from paragraph javadoc node.
530     *
531     * @param nodeParagraph paragraph javadoc node
532     * @return parent text
533     */
534    private static String getParentText(DetailNode nodeParagraph) {
535        String result = null;
536        final Optional<DetailNode> resultNode = getFirstChildOfType(nodeParagraph,
537                JavadocTokenTypes.TEXT, 0);
538        if (resultNode.isPresent()
539                && PARENT_TAG.matcher(resultNode.get().getText()).matches()) {
540            final Optional<DetailNode> childNode = getFirstChildOfType(nodeParagraph,
541                    JavadocTokenTypes.JAVADOC_INLINE_TAG, 0);
542            if (childNode.isPresent()) {
543                result = getTextFromTag(childNode.get());
544            }
545        }
546        return result;
547    }
548
549    /**
550     * Get module type(check/filter/filefilter) based on file name.
551     *
552     * @return module type
553     */
554    private ModuleType getModuleType() {
555        final String simpleModuleName = getModuleSimpleName();
556        final ModuleType result;
557        if (simpleModuleName.endsWith("FileFilter")) {
558            result = ModuleType.FILEFILTER;
559        }
560        else if (simpleModuleName.endsWith("Filter")) {
561            result = ModuleType.FILTER;
562        }
563        else {
564            result = ModuleType.CHECK;
565        }
566        return result;
567    }
568
569    /**
570     * Extract simple file name from the whole file path name.
571     *
572     * @return simple module name
573     */
574    private String getModuleSimpleName() {
575        final String fullFileName = getFileContents().getFileName();
576        final String[] pathTokens = FILE_SEPARATOR_PATTERN.split(fullFileName);
577        final String fileName = pathTokens[pathTokens.length - 1];
578        return fileName.substring(0, fileName.length() - JAVA_FILE_EXTENSION.length());
579    }
580
581    /**
582     * Retrieve package name of module from the absolute file path.
583     *
584     * @param filePath absolute file path
585     * @return package name
586     */
587    private static String getPackageName(String filePath) {
588        final Deque<String> result = new ArrayDeque<>();
589        final String[] filePathTokens = FILE_SEPARATOR_PATTERN.split(filePath);
590        for (int i = filePathTokens.length - 1; i >= 0; i--) {
591            if ("java".equals(filePathTokens[i]) || "resources".equals(filePathTokens[i])) {
592                break;
593            }
594            result.addFirst(filePathTokens[i]);
595        }
596        final String fileName = result.removeLast();
597        result.addLast(fileName.substring(0, fileName.length() - JAVA_FILE_EXTENSION.length()));
598        return String.join(".", result);
599    }
600
601    /**
602     * Getter method for {@code moduleDetailsStore}.
603     *
604     * @return map containing module details of supplied checks.
605     */
606    public static Map<String, ModuleDetails> getModuleDetailsStore() {
607        return Collections.unmodifiableMap(MODULE_DETAILS_STORE);
608    }
609
610    /**
611     * Check if the current javadoc block comment AST corresponds to the top-level class as we
612     * only want to scrape top-level class javadoc.
613     *
614     * @return true if the current AST corresponds to top level class
615     */
616    public boolean isTopLevelClassJavadoc() {
617        final DetailAST parent = getParent(getBlockCommentAst());
618        final Optional<DetailAST> className = TokenUtil
619                .findFirstTokenByPredicate(parent, child -> {
620                    return parent.getType() == TokenTypes.CLASS_DEF
621                            && child.getType() == TokenTypes.IDENT;
622                });
623        return className.isPresent()
624                && getModuleSimpleName().equals(className.get().getText());
625    }
626
627    /**
628     * Checks whether the paragraph node corresponds to the example section.
629     *
630     * @param ast javadoc paragraph node
631     * @return true if the section matches the example section marker
632     */
633    private static boolean isExamplesText(DetailNode ast) {
634        final Optional<DetailNode> resultNode = getFirstChildOfType(ast, JavadocTokenTypes.TEXT, 0);
635        return resultNode.isPresent()
636                && EXAMPLES_TAG.matcher(resultNode.get().getText()).matches();
637    }
638
639    /**
640     * Checks whether the list item node is part of a property list.
641     *
642     * @param nodeLi {@code JavadocTokenType.LI} node
643     * @return true if the node is part of a property list
644     */
645    private static boolean isPropertyList(DetailNode nodeLi) {
646        final Optional<DetailNode> firstTextChildToken =
647                getFirstChildOfType(nodeLi, JavadocTokenTypes.TEXT, 0);
648        return firstTextChildToken.isPresent()
649                && PROPERTY_TAG.matcher(firstTextChildToken.get().getText()).matches();
650    }
651
652    /**
653     * Checks whether the {@code JavadocTokenType.PARAGRAPH} node is referring to the violation
654     * message keys javadoc segment.
655     *
656     * @param nodeParagraph paragraph javadoc node
657     * @return true if paragraph node contains the violation message keys text
658     */
659    private static boolean isViolationMessagesText(DetailNode nodeParagraph) {
660        final Optional<DetailNode> resultNode = getFirstChildOfType(nodeParagraph,
661                JavadocTokenTypes.TEXT, 0);
662        return resultNode.isPresent()
663                && VIOLATION_MESSAGES_TAG.matcher(resultNode.get().getText()).matches();
664    }
665
666    /**
667     * Checks whether the {@code JavadocTokenType.PARAGRAPH} node is referring to the parent
668     * javadoc segment.
669     *
670     * @param nodeParagraph paragraph javadoc node
671     * @return true if paragraph node contains the parent text
672     */
673    private static boolean isParentText(DetailNode nodeParagraph) {
674        final Optional<DetailNode> resultNode = getFirstChildOfType(nodeParagraph,
675                JavadocTokenTypes.TEXT, 0);
676        return resultNode.isPresent()
677                && PARENT_TAG.matcher(resultNode.get().getText()).matches();
678    }
679}