001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2014  Oliver Burn
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////////////////////////////////////////////////////////////////////////////////
019package com.puppycrawl.tools.checkstyle.checks.javadoc;
020
021import com.puppycrawl.tools.checkstyle.api.DetailNode;
022import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes;
023
024/**
025 * Checks that:
026 * <ul>
027 * <li>There is one blank line between each of two paragraphs
028 * and one blank line before the at-clauses block if it is present.</li>
029 * <li>Each paragraph but the first has &lt;p&gt; immediately
030 * before the first word, with no space after.</li>
031 * </ul>
032 *
033 * <p>
034 * Default configuration:
035 * </p>
036 * <pre>
037 * &lt;module name=&quot;JavadocParagraph&quot;/&gt;
038 * </pre>
039 *
040 * @author maxvetrenko
041 *
042 */
043public class JavadocParagraphCheck extends AbstractJavadocCheck
044{
045
046    @Override
047    public int[] getDefaultJavadocTokens()
048    {
049        return new int[] {
050            JavadocTokenTypes.NEWLINE,
051            JavadocTokenTypes.HTML_ELEMENT,
052        };
053    }
054
055    @Override
056    public void visitJavadocToken(DetailNode ast)
057    {
058        if (ast.getType() == JavadocTokenTypes.NEWLINE && isEmptyLine(ast)) {
059            checkEmptyLine(ast);
060        }
061        else if (ast.getType() == JavadocTokenTypes.HTML_ELEMENT
062                && JavadocUtils.getFirstChild(ast).getType() == JavadocTokenTypes.P_TAG_OPEN)
063        {
064            checkParagraphTag(ast);
065        }
066    }
067
068    /**
069     * Determines whether or not the next line after empty line has paragraph tag in the beginning.
070     * @param newline NEWLINE node.
071     */
072    private void checkEmptyLine(DetailNode newline)
073    {
074        final DetailNode nearestToken = getNearestNode(newline);
075        if (!isLastEmptyLine(newline) && nearestToken != null
076                && nearestToken.getType() == JavadocTokenTypes.TEXT
077                && nearestToken.getChildren().length > 1)
078        {
079            log(newline.getLineNumber(), "javadoc.paragraph.tag.after");
080        }
081    }
082
083    /**
084     * Determines whether or not the line with paragraph tag has previous empty line.
085     * @param tag html tag.
086     */
087    private void checkParagraphTag(DetailNode tag)
088    {
089        final DetailNode newLine = getNearestEmptyLine(tag);
090        if (isFirstParagraph(tag)) {
091            log(tag.getLineNumber(), "javadoc.paragraph.redundant.paragraph");
092        }
093        else if (newLine == null || tag.getLineNumber() - newLine.getLineNumber() != 1) {
094            log(tag.getLineNumber(), "javadoc.paragraph.line.before");
095        }
096    }
097
098    /**
099     * Returns nearest node.
100     * @param node DetailNode node.
101     * @return nearest node.
102     */
103    private DetailNode getNearestNode(DetailNode node)
104    {
105        DetailNode tag = JavadocUtils.getNextSibling(node);
106        while (tag != null && (tag.getType() == JavadocTokenTypes.LEADING_ASTERISK
107                || tag.getType() == JavadocTokenTypes.NEWLINE))
108        {
109            tag = JavadocUtils.getNextSibling(tag);
110        }
111        return tag;
112    }
113
114    /**
115     * Determines whether or not the line is empty line.
116     * @param newLine NEWLINE node.
117     * @return true, if line is empty line.
118     */
119    private boolean isEmptyLine(DetailNode newLine)
120    {
121        DetailNode previousSibling = JavadocUtils.getPreviousSibling(newLine);
122        if (previousSibling == null
123                || previousSibling.getParent().getType() != JavadocTokenTypes.JAVADOC)
124        {
125            return false;
126        }
127        if (previousSibling.getType() == JavadocTokenTypes.TEXT
128                && previousSibling.getChildren().length == 1)
129        {
130            previousSibling = JavadocUtils.getPreviousSibling(previousSibling);
131        }
132        return previousSibling != null
133                && previousSibling.getType() == JavadocTokenTypes.LEADING_ASTERISK;
134    }
135
136    /**
137     * Determines whether or not the line with paragraph tag is first line in javadoc.
138     * @param paragraphTag paragraph tag.
139     * @return true, if line with paragraph tag is first line in javadoc.
140     */
141    private boolean isFirstParagraph(DetailNode paragraphTag)
142    {
143        DetailNode previousNode = JavadocUtils.getPreviousSibling(paragraphTag);
144        while (previousNode != null) {
145            if (previousNode.getType() == JavadocTokenTypes.TEXT
146                    && previousNode.getChildren().length > 1
147                || previousNode.getType() != JavadocTokenTypes.LEADING_ASTERISK
148                    && previousNode.getType() != JavadocTokenTypes.NEWLINE
149                    && previousNode.getType() != JavadocTokenTypes.TEXT)
150            {
151                return false;
152            }
153            previousNode = JavadocUtils.getPreviousSibling(previousNode);
154        }
155        return true;
156    }
157
158    /**
159     * Finds and returns nearest empty line in javadoc.
160     * @param node DetailNode node.
161     * @return Some nearest empty line in javadoc.
162     */
163    private DetailNode getNearestEmptyLine(DetailNode node)
164    {
165        DetailNode newLine = JavadocUtils.getPreviousSibling(node);
166        while (newLine != null) {
167            final DetailNode previousSibling = JavadocUtils.getPreviousSibling(newLine);
168            if (newLine.getType() == JavadocTokenTypes.NEWLINE && isEmptyLine(newLine))
169            {
170                break;
171            }
172            newLine = previousSibling;
173        }
174        return newLine;
175    }
176
177    /**
178     * Tests if NEWLINE node is a last node in javadoc.
179     * @param newLine NEWLINE node.
180     * @return true, if NEWLINE node is a last node in javadoc.
181     */
182    private boolean isLastEmptyLine(DetailNode newLine)
183    {
184        DetailNode nextNode = JavadocUtils.getNextSibling(newLine);
185        while (nextNode != null && nextNode.getType() != JavadocTokenTypes.JAVADOC_TAG) {
186            if (nextNode.getType() == JavadocTokenTypes.TEXT
187                    && nextNode.getChildren().length > 1
188                    || nextNode.getType() == JavadocTokenTypes.HTML_ELEMENT)
189            {
190                return false;
191            }
192            nextNode = JavadocUtils.getNextSibling(nextNode);
193        }
194        return true;
195    }
196}