Coverage Report - com.puppycrawl.tools.checkstyle.checks.javadoc.TagParser
 
Classes in this File Line Coverage Branch Coverage Complexity
TagParser
100%
81/81
100%
58/58
2.667
TagParser$Point
100%
6/6
N/A
2.667
 
 1  
 ////////////////////////////////////////////////////////////////////////////////
 2  
 // checkstyle: Checks Java source code for adherence to a set of rules.
 3  
 // Copyright (C) 2001-2017 the original author or authors.
 4  
 //
 5  
 // This library is free software; you can redistribute it and/or
 6  
 // modify it under the terms of the GNU Lesser General Public
 7  
 // License as published by the Free Software Foundation; either
 8  
 // version 2.1 of the License, or (at your option) any later version.
 9  
 //
 10  
 // This library is distributed in the hope that it will be useful,
 11  
 // but WITHOUT ANY WARRANTY; without even the implied warranty of
 12  
 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 13  
 // Lesser General Public License for more details.
 14  
 //
 15  
 // You should have received a copy of the GNU Lesser General Public
 16  
 // License along with this library; if not, write to the Free Software
 17  
 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 18  
 ////////////////////////////////////////////////////////////////////////////////
 19  
 
 20  
 package com.puppycrawl.tools.checkstyle.checks.javadoc;
 21  
 
 22  
 import java.util.LinkedList;
 23  
 import java.util.List;
 24  
 
 25  
 /**
 26  
  * <p>
 27  
  * Helper class used to parse HTML tags or generic type identifiers
 28  
  * from a single line of text. Just the beginning of the HTML tag
 29  
  * is located.  No attempt is made to parse out the complete tag,
 30  
  * particularly since some of the tag parameters could be located
 31  
  * on the following line of text.  The {@code hasNextTag} and
 32  
  * {@code nextTag} methods are used to iterate through the HTML
 33  
  * tags or generic type identifiers that were found on the line of text.
 34  
  * </p>
 35  
  *
 36  
  * <p>
 37  
  * This class isn't really specific to HTML tags. Currently the only HTML
 38  
  * tag that this class looks specifically for is the HTML comment tag.
 39  
  * This class helps figure out if a tag exists and if it is well-formed.
 40  
  * It does not know whether it is valid HTML.  This class is also used for
 41  
  * generics types which looks like opening HTML tags ex: {@code <T>, <E>, <V>,
 42  
  * <MY_FOO_TYPE>}, etc. According to this class they are valid tags.
 43  
  * </p>
 44  
  *
 45  
  * @author Chris Stillwell
 46  
  */
 47  
 class TagParser {
 48  
     /** List of HtmlTags found on the input line of text. */
 49  209
     private final List<HtmlTag> tags = new LinkedList<>();
 50  
 
 51  
     /**
 52  
      * Constructs a TagParser and finds the first tag if any.
 53  
      * @param text the line of text to parse.
 54  
      * @param lineNo the source line number.
 55  
      */
 56  209
     TagParser(String[] text, int lineNo) {
 57  209
         parseTags(text, lineNo);
 58  209
     }
 59  
 
 60  
     /**
 61  
      * Returns the next available HtmlTag.
 62  
      * @return a HtmlTag or {@code null} if none available.
 63  
      * @throws IndexOutOfBoundsException if there are no HtmlTags
 64  
      *         left to return.
 65  
      */
 66  
     public HtmlTag nextTag() {
 67  207
         return tags.remove(0);
 68  
     }
 69  
 
 70  
     /**
 71  
      * Indicates if there are any more HtmlTag to retrieve.
 72  
      * @return {@code true} if there are more tags.
 73  
      */
 74  
     public boolean hasNextTag() {
 75  411
         return !tags.isEmpty();
 76  
     }
 77  
 
 78  
     /**
 79  
      * Performs lazy initialization on the internal tags List
 80  
      * and adds the tag.
 81  
      * @param tag the HtmlTag to add.
 82  
      */
 83  
     private void add(HtmlTag tag) {
 84  207
         tags.add(tag);
 85  207
     }
 86  
 
 87  
     /**
 88  
      * Parses the text line for any HTML tags and adds them to the internal
 89  
      * List of tags.
 90  
      * @param text the source line to parse.
 91  
      * @param lineNo the source line number.
 92  
      */
 93  
     private void parseTags(String[] text, int lineNo) {
 94  209
         final int nLines = text.length;
 95  209
         Point position = findChar(text, '<', new Point(0, 0));
 96  432
         while (position.getLineNo() < nLines) {
 97  
             // if this is html comment then skip it
 98  223
             if (isCommentTag(text, position)) {
 99  8
                 position = skipHtmlComment(text, position);
 100  
             }
 101  215
             else if (isTag(text, position)) {
 102  207
                 position = parseTag(text, lineNo, nLines, position);
 103  
             }
 104  
             else {
 105  8
                 position = getNextCharPos(text, position);
 106  
             }
 107  223
             position = findChar(text, '<', position);
 108  
         }
 109  209
     }
 110  
 
 111  
     /**
 112  
      * Parses the tag and return position after it.
 113  
      * @param text the source line to parse.
 114  
      * @param lineNo the source line number.
 115  
      * @param nLines line length
 116  
      * @param position start position for parsing
 117  
      * @return position after tag
 118  
      */
 119  
     private Point parseTag(String[] text, int lineNo, final int nLines, Point position) {
 120  
         // find end of tag
 121  207
         final Point endTag = findChar(text, '>', position);
 122  207
         final boolean incompleteTag = endTag.getLineNo() >= nLines;
 123  
         // get tag id (one word)
 124  
         final String tagId;
 125  
 
 126  207
         if (incompleteTag) {
 127  5
             tagId = "";
 128  
         }
 129  
         else {
 130  202
             tagId = getTagId(text, position);
 131  
         }
 132  
         // is this closed tag
 133  207
         final boolean closedTag =
 134  207
                 endTag.getLineNo() < nLines
 135  202
                  && text[endTag.getLineNo()]
 136  202
                  .charAt(endTag.getColumnNo() - 1) == '/';
 137  
         // add new tag
 138  414
         add(new HtmlTag(tagId,
 139  207
                         position.getLineNo() + lineNo,
 140  207
                         position.getColumnNo(),
 141  
                         closedTag,
 142  
                         incompleteTag,
 143  207
                         text[position.getLineNo()]));
 144  207
         return endTag;
 145  
     }
 146  
 
 147  
     /**
 148  
      * Checks if the given position is start one for HTML tag.
 149  
      * @param javadocText text of javadoc comments.
 150  
      * @param pos position to check.
 151  
      * @return {@code true} some HTML tag starts from given position.
 152  
      */
 153  
     private static boolean isTag(String[] javadocText, Point pos) {
 154  215
         final int column = pos.getColumnNo() + 1;
 155  215
         final String text = javadocText[pos.getLineNo()];
 156  
 
 157  
         //Character.isJavaIdentifier... may not be a valid HTML
 158  
         //identifier but is valid for generics
 159  430
         return column < text.length()
 160  206
                 && (Character.isJavaIdentifierStart(text.charAt(column))
 161  59
                     || text.charAt(column) == '/')
 162  17
                 || column >= text.length();
 163  
     }
 164  
 
 165  
     /**
 166  
      * Parse tag id.
 167  
      * @param javadocText text of javadoc comments.
 168  
      * @param tagStart start position of the tag
 169  
      * @return id for given tag
 170  
      */
 171  
     private static String getTagId(String[] javadocText, Point tagStart) {
 172  202
         String tagId = "";
 173  202
         int column = tagStart.getColumnNo() + 1;
 174  202
         String text = javadocText[tagStart.getLineNo()];
 175  202
         if (column < text.length()) {
 176  
 
 177  198
             if (text.charAt(column) == '/') {
 178  51
                 column++;
 179  
             }
 180  
 
 181  198
             text = text.substring(column).trim();
 182  198
             int position = 0;
 183  
 
 184  
             //Character.isJavaIdentifier... may not be a valid HTML
 185  
             //identifier but is valid for generics
 186  835
             while (position < text.length()
 187  831
                     && (Character.isJavaIdentifierStart(text.charAt(position))
 188  219
                         || Character.isJavaIdentifierPart(text.charAt(position)))) {
 189  637
                 position++;
 190  
             }
 191  
 
 192  198
             tagId = text.substring(0, position);
 193  
         }
 194  202
         return tagId;
 195  
     }
 196  
 
 197  
     /**
 198  
      * If this is a HTML-comments.
 199  
      * @param text text of javadoc comments
 200  
      * @param pos position to check
 201  
      * @return {@code true} if HTML-comments
 202  
      *         starts form given position.
 203  
      */
 204  
     private static boolean isCommentTag(String[] text, Point pos) {
 205  223
         return text[pos.getLineNo()].startsWith("<!--", pos.getColumnNo());
 206  
     }
 207  
 
 208  
     /**
 209  
      * Skips HTML comments.
 210  
      * @param text text of javadoc comments.
 211  
      * @param fromPoint start position of HTML-comments
 212  
      * @return position after HTML-comments
 213  
      */
 214  
     private static Point skipHtmlComment(String[] text, Point fromPoint) {
 215  8
         Point toPoint = fromPoint;
 216  8
         toPoint = findChar(text, '>', toPoint);
 217  11
         while (!text[toPoint.getLineNo()]
 218  11
                .substring(0, toPoint.getColumnNo() + 1).endsWith("-->")) {
 219  3
             toPoint = findChar(text, '>', getNextCharPos(text, toPoint));
 220  
         }
 221  8
         return toPoint;
 222  
     }
 223  
 
 224  
     /**
 225  
      * Finds next occurrence of given character.
 226  
      * @param text text to search
 227  
      * @param character character to search
 228  
      * @param from position to start search
 229  
      * @return position of next occurrence of given character
 230  
      */
 231  
     private static Point findChar(String[] text, char character, Point from) {
 232  650
         Point curr = new Point(from.getLineNo(), from.getColumnNo());
 233  14688
         while (curr.getLineNo() < text.length
 234  14474
                && text[curr.getLineNo()].charAt(curr.getColumnNo()) != character) {
 235  14038
             curr = getNextCharPos(text, curr);
 236  
         }
 237  
 
 238  650
         return curr;
 239  
     }
 240  
 
 241  
     /**
 242  
      * Returns position of next comment character, skips
 243  
      * whitespaces and asterisks.
 244  
      * @param text to search.
 245  
      * @param from location to search from
 246  
      * @return location of the next character.
 247  
      */
 248  
     private static Point getNextCharPos(String[] text, Point from) {
 249  14049
         int line = from.getLineNo();
 250  14049
         int column = from.getColumnNo() + 1;
 251  15069
         while (line < text.length && column >= text[line].length()) {
 252  
             // go to the next line
 253  1020
             line++;
 254  1020
             column = 0;
 255  1020
             if (line < text.length) {
 256  
                 //skip beginning spaces and stars
 257  811
                 final String currentLine = text[line];
 258  8286
                 while (column < currentLine.length()
 259  8041
                        && (Character.isWhitespace(currentLine.charAt(column))
 260  1382
                            || currentLine.charAt(column) == '*')) {
 261  7475
                     column++;
 262  7475
                     if (column < currentLine.length()
 263  7421
                         && currentLine.charAt(column - 1) == '*'
 264  800
                         && currentLine.charAt(column) == '/') {
 265  
                         // this is end of comment
 266  191
                         column = currentLine.length();
 267  
                     }
 268  
                 }
 269  811
             }
 270  
         }
 271  
 
 272  14049
         return new Point(line, column);
 273  
     }
 274  
 
 275  
     /**
 276  
      * Represents current position in the text.
 277  
      * @author o_sukholsky
 278  
      */
 279  
     private static final class Point {
 280  
         /** Line number. */
 281  
         private final int lineNo;
 282  
         /** Column number.*/
 283  
         private final int columnNo;
 284  
 
 285  
         /**
 286  
          * Creates new {@code Point} instance.
 287  
          * @param lineNo line number
 288  
          * @param columnNo column number
 289  
          */
 290  14908
         Point(int lineNo, int columnNo) {
 291  14908
             this.lineNo = lineNo;
 292  14908
             this.columnNo = columnNo;
 293  14908
         }
 294  
 
 295  
         /**
 296  
          * Getter for line number.
 297  
          * @return line number of the position.
 298  
          */
 299  
         public int getLineNo() {
 300  45974
             return lineNo;
 301  
         }
 302  
 
 303  
         /**
 304  
          * Getter for column number.
 305  
          * @return column number of the position.
 306  
          */
 307  
         public int getColumnNo() {
 308  30233
             return columnNo;
 309  
         }
 310  
     }
 311  
 }