001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2018 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 com.puppycrawl.tools.checkstyle.api.DetailNode;
023import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes;
024import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
025import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
026
027/**
028 * Checks that:
029 * <ul>
030 * <li>There is one blank line between each of two paragraphs
031 * and one blank line before the at-clauses block if it is present.</li>
032 * <li>Each paragraph but the first has &lt;p&gt; immediately
033 * before the first word, with no space after.</li>
034 * </ul>
035 *
036 * <p>The check can be specified by option allowNewlineParagraph,
037 * which says whether the &lt;p&gt; tag should be placed immediately before
038 * the first word.
039 *
040 * <p>Default configuration:
041 * </p>
042 * <pre>
043 * &lt;module name=&quot;JavadocParagraph&quot;/&gt;
044 * </pre>
045 *
046 * <p>To allow newlines and spaces immediately after the &lt;p&gt; tag:
047 * <pre>
048 * &lt;module name=&quot;JavadocParagraph&quot;&gt;
049 *      &lt;property name=&quot;allowNewlineParagraph&quot;
050 *                   value==&quot;false&quot;/&gt;
051 * &lt;/module&quot;&gt;
052 * </pre>
053 *
054 * <p>In case of allowNewlineParagraph set to false
055 * the following example will not have any violations:
056 * <pre>
057 *   /**
058 *    * &lt;p&gt;
059 *    * Some Javadoc.
060 *    *
061 *    * &lt;p&gt;  Some Javadoc.
062 *    *
063 *    * &lt;p&gt;
064 *    * &lt;pre&gt;
065 *    * Some preformatted Javadoc.
066 *    * &lt;/pre&gt;
067 *    *
068 *    *&#47;
069 * </pre>
070 *
071 */
072public class JavadocParagraphCheck extends AbstractJavadocCheck {
073
074    /**
075     * A key is pointing to the warning message text in "messages.properties"
076     * file.
077     */
078    public static final String MSG_TAG_AFTER = "javadoc.paragraph.tag.after";
079
080    /**
081     * A key is pointing to the warning message text in "messages.properties"
082     * file.
083     */
084    public static final String MSG_LINE_BEFORE = "javadoc.paragraph.line.before";
085
086    /**
087     * A key is pointing to the warning message text in "messages.properties"
088     * file.
089     */
090    public static final String MSG_REDUNDANT_PARAGRAPH = "javadoc.paragraph.redundant.paragraph";
091
092    /**
093     * A key is pointing to the warning message text in "messages.properties"
094     * file.
095     */
096    public static final String MSG_MISPLACED_TAG = "javadoc.paragraph.misplaced.tag";
097
098    /**
099     * Whether the &lt;p&gt; tag should be placed immediately before the first word.
100     */
101    private boolean allowNewlineParagraph = true;
102
103    /**
104     * Sets allowNewlineParagraph.
105     * @param value value to set.
106     */
107    public void setAllowNewlineParagraph(boolean value) {
108        allowNewlineParagraph = value;
109    }
110
111    @Override
112    public int[] getDefaultJavadocTokens() {
113        return new int[] {
114            JavadocTokenTypes.NEWLINE,
115            JavadocTokenTypes.HTML_ELEMENT,
116        };
117    }
118
119    @Override
120    public int[] getRequiredJavadocTokens() {
121        return getAcceptableJavadocTokens();
122    }
123
124    @Override
125    public void visitJavadocToken(DetailNode ast) {
126        if (ast.getType() == JavadocTokenTypes.NEWLINE && isEmptyLine(ast)) {
127            checkEmptyLine(ast);
128        }
129        else if (ast.getType() == JavadocTokenTypes.HTML_ELEMENT
130                && JavadocUtil.getFirstChild(ast).getType() == JavadocTokenTypes.P_TAG_START) {
131            checkParagraphTag(ast);
132        }
133    }
134
135    /**
136     * Determines whether or not the next line after empty line has paragraph tag in the beginning.
137     * @param newline NEWLINE node.
138     */
139    private void checkEmptyLine(DetailNode newline) {
140        final DetailNode nearestToken = getNearestNode(newline);
141        if (!isLastEmptyLine(newline) && nearestToken.getType() == JavadocTokenTypes.TEXT
142                && !CommonUtil.isBlank(nearestToken.getText())) {
143            log(newline.getLineNumber(), MSG_TAG_AFTER);
144        }
145    }
146
147    /**
148     * Determines whether or not the line with paragraph tag has previous empty line.
149     * @param tag html tag.
150     */
151    private void checkParagraphTag(DetailNode tag) {
152        final DetailNode newLine = getNearestEmptyLine(tag);
153        if (isFirstParagraph(tag)) {
154            log(tag.getLineNumber(), MSG_REDUNDANT_PARAGRAPH);
155        }
156        else if (newLine == null || tag.getLineNumber() - newLine.getLineNumber() != 1) {
157            log(tag.getLineNumber(), MSG_LINE_BEFORE);
158        }
159        if (allowNewlineParagraph && isImmediatelyFollowedByText(tag)) {
160            log(tag.getLineNumber(), MSG_MISPLACED_TAG);
161        }
162    }
163
164    /**
165     * Returns nearest node.
166     * @param node DetailNode node.
167     * @return nearest node.
168     */
169    private static DetailNode getNearestNode(DetailNode node) {
170        DetailNode tag = JavadocUtil.getNextSibling(node);
171        while (tag.getType() == JavadocTokenTypes.LEADING_ASTERISK
172                || tag.getType() == JavadocTokenTypes.NEWLINE) {
173            tag = JavadocUtil.getNextSibling(tag);
174        }
175        return tag;
176    }
177
178    /**
179     * Determines whether or not the line is empty line.
180     * @param newLine NEWLINE node.
181     * @return true, if line is empty line.
182     */
183    private static boolean isEmptyLine(DetailNode newLine) {
184        boolean result = false;
185        DetailNode previousSibling = JavadocUtil.getPreviousSibling(newLine);
186        if (previousSibling != null
187                && previousSibling.getParent().getType() == JavadocTokenTypes.JAVADOC) {
188            if (previousSibling.getType() == JavadocTokenTypes.TEXT
189                    && CommonUtil.isBlank(previousSibling.getText())) {
190                previousSibling = JavadocUtil.getPreviousSibling(previousSibling);
191            }
192            result = previousSibling != null
193                    && previousSibling.getType() == JavadocTokenTypes.LEADING_ASTERISK;
194        }
195        return result;
196    }
197
198    /**
199     * Determines whether or not the line with paragraph tag is first line in javadoc.
200     * @param paragraphTag paragraph tag.
201     * @return true, if line with paragraph tag is first line in javadoc.
202     */
203    private static boolean isFirstParagraph(DetailNode paragraphTag) {
204        boolean result = true;
205        DetailNode previousNode = JavadocUtil.getPreviousSibling(paragraphTag);
206        while (previousNode != null) {
207            if (previousNode.getType() == JavadocTokenTypes.TEXT
208                    && !CommonUtil.isBlank(previousNode.getText())
209                || previousNode.getType() != JavadocTokenTypes.LEADING_ASTERISK
210                    && previousNode.getType() != JavadocTokenTypes.NEWLINE
211                    && previousNode.getType() != JavadocTokenTypes.TEXT) {
212                result = false;
213                break;
214            }
215            previousNode = JavadocUtil.getPreviousSibling(previousNode);
216        }
217        return result;
218    }
219
220    /**
221     * Finds and returns nearest empty line in javadoc.
222     * @param node DetailNode node.
223     * @return Some nearest empty line in javadoc.
224     */
225    private static DetailNode getNearestEmptyLine(DetailNode node) {
226        DetailNode newLine = JavadocUtil.getPreviousSibling(node);
227        while (newLine != null) {
228            final DetailNode previousSibling = JavadocUtil.getPreviousSibling(newLine);
229            if (newLine.getType() == JavadocTokenTypes.NEWLINE && isEmptyLine(newLine)) {
230                break;
231            }
232            newLine = previousSibling;
233        }
234        return newLine;
235    }
236
237    /**
238     * Tests if NEWLINE node is a last node in javadoc.
239     * @param newLine NEWLINE node.
240     * @return true, if NEWLINE node is a last node in javadoc.
241     */
242    private static boolean isLastEmptyLine(DetailNode newLine) {
243        boolean result = true;
244        DetailNode nextNode = JavadocUtil.getNextSibling(newLine);
245        while (nextNode != null && nextNode.getType() != JavadocTokenTypes.JAVADOC_TAG) {
246            if (nextNode.getType() == JavadocTokenTypes.TEXT
247                    && !CommonUtil.isBlank(nextNode.getText())
248                    || nextNode.getType() == JavadocTokenTypes.HTML_ELEMENT) {
249                result = false;
250                break;
251            }
252            nextNode = JavadocUtil.getNextSibling(nextNode);
253        }
254        return result;
255    }
256
257    /**
258     * Tests whether the paragraph tag is immediately followed by the text.
259     * @param tag html tag.
260     * @return true, if the paragraph tag is immediately followed by the text.
261     */
262    private static boolean isImmediatelyFollowedByText(DetailNode tag) {
263        final DetailNode nextSibling = JavadocUtil.getNextSibling(tag);
264        return nextSibling.getType() == JavadocTokenTypes.NEWLINE
265                || nextSibling.getType() == JavadocTokenTypes.EOF
266                || CommonUtil.startsWithChar(nextSibling.getText(), ' ');
267    }
268
269}