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.checks.javadoc;
021
022import java.util.ArrayList;
023import java.util.List;
024
025import com.puppycrawl.tools.checkstyle.StatelessCheck;
026import com.puppycrawl.tools.checkstyle.api.DetailNode;
027import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes;
028import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
029import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
030
031/**
032 * <p>
033 * Checks the indentation of the continuation lines in at-clauses.
034 * </p>
035 * <ul>
036 * <li>
037 * Property {@code violateExecutionOnNonTightHtml} - Control when to print violations
038 * if the Javadoc being examined by this check violates the tight html rules defined at
039 * <a href="https://checkstyle.org/writingjavadocchecks.html#Tight-HTML_rules">Tight-HTML Rules</a>.
040 * Default value is {@code false}.
041 * </li>
042 * <li>
043 * Property {@code offset} - Specify how many spaces to use for new indentation level.
044 * Default value is {@code 4}.
045 * </li>
046 * </ul>
047 * <p>
048 * To configure the default check:
049 * </p>
050 * <pre>
051 * &lt;module name="JavadocTagContinuationIndentation"/&gt;
052 * </pre>
053 * <p>
054 * To configure the check with two spaces indentation:
055 * </p>
056 * <pre>
057 * &lt;module name="JavadocTagContinuationIndentation"&gt;
058 *   &lt;property name="offset" value="2"/&gt;
059 * &lt;/module&gt;
060 * </pre>
061 *
062 * @since 6.0
063 *
064 */
065@StatelessCheck
066public class JavadocTagContinuationIndentationCheck extends AbstractJavadocCheck {
067
068    /**
069     * A key is pointing to the warning message text in "messages.properties"
070     * file.
071     */
072    public static final String MSG_KEY = "tag.continuation.indent";
073
074    /** Default tag continuation indentation. */
075    private static final int DEFAULT_INDENTATION = 4;
076
077    /**
078     * Specify how many spaces to use for new indentation level.
079     */
080    private int offset = DEFAULT_INDENTATION;
081
082    /**
083     * Setter to specify how many spaces to use for new indentation level.
084     *
085     * @param offset custom value.
086     */
087    public void setOffset(int offset) {
088        this.offset = offset;
089    }
090
091    @Override
092    public int[] getDefaultJavadocTokens() {
093        return new int[] {JavadocTokenTypes.DESCRIPTION };
094    }
095
096    @Override
097    public int[] getRequiredJavadocTokens() {
098        return getAcceptableJavadocTokens();
099    }
100
101    @Override
102    public void visitJavadocToken(DetailNode ast) {
103        if (!isInlineDescription(ast)) {
104            final List<DetailNode> textNodes = getAllNewlineNodes(ast);
105            for (DetailNode newlineNode : textNodes) {
106                final DetailNode textNode = JavadocUtil.getNextSibling(JavadocUtil
107                        .getNextSibling(newlineNode));
108                if (textNode != null && textNode.getType() == JavadocTokenTypes.TEXT) {
109                    final String text = textNode.getText();
110                    if (!CommonUtil.isBlank(text.trim())
111                            && (text.length() <= offset
112                                    || !text.substring(1, offset + 1).trim().isEmpty())) {
113                        log(textNode.getLineNumber(), MSG_KEY, offset);
114                    }
115                }
116            }
117        }
118    }
119
120    /**
121     * Finds and collects all NEWLINE nodes inside DESCRIPTION node.
122     * @param descriptionNode DESCRIPTION node.
123     * @return List with NEWLINE nodes.
124     */
125    private static List<DetailNode> getAllNewlineNodes(DetailNode descriptionNode) {
126        final List<DetailNode> textNodes = new ArrayList<>();
127        DetailNode node = JavadocUtil.getFirstChild(descriptionNode);
128        while (JavadocUtil.getNextSibling(node) != null) {
129            if (node.getType() == JavadocTokenTypes.NEWLINE) {
130                textNodes.add(node);
131            }
132            node = JavadocUtil.getNextSibling(node);
133        }
134        return textNodes;
135    }
136
137    /**
138     * Checks, if description node is a description of in-line tag.
139     * @param description DESCRIPTION node.
140     * @return true, if description node is a description of in-line tag.
141     */
142    private static boolean isInlineDescription(DetailNode description) {
143        boolean isInline = false;
144        DetailNode inlineTag = description.getParent();
145        while (inlineTag != null) {
146            if (inlineTag.getType() == JavadocTokenTypes.JAVADOC_INLINE_TAG) {
147                isInline = true;
148                break;
149            }
150            inlineTag = inlineTag.getParent();
151        }
152        return isInline;
153    }
154
155}