Coverage Report - com.puppycrawl.tools.checkstyle.checks.javadoc.TagParser
 
Classes in this File Line Coverage Branch Coverage Complexity
TagParser
98%
60/61
92%
59/64
3.071
TagParser$Point
100%
6/6
N/A
3.071
 
 1  
 ////////////////////////////////////////////////////////////////////////////////
 2  
 // checkstyle: Checks Java source code for adherence to a set of rules.
 3  
 // Copyright (C) 2001-2014  Oliver Burn
 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  
 package com.puppycrawl.tools.checkstyle.checks.javadoc;
 20  
 
 21  
 import com.google.common.collect.Lists;
 22  
 import java.util.List;
 23  
 
 24  
 /**
 25  
  * <p>
 26  
  * Helper class used to parse HTML tags or generic type identifiers
 27  
  * from a single line of text. Just the beginning of the HTML tag
 28  
  * is located.  No attempt is made to parse out the complete tag,
 29  
  * particularly since some of the tag parameters could be located
 30  
  * on the following line of text.  The <code>hasNextTag</code> and
 31  
  * <code>nextTag</code> methods are used to iterate through the HTML
 32  
  * tags or generic type identifiers that were found on the line of text.
 33  
  * </p>
 34  
  *
 35  
  * <p>
 36  
  * This class isn't really specific to HTML tags. Currently the only HTML
 37  
  * tag that this class looks specifically for is the HTML comment tag.
 38  
  * This class helps figure out if a tag exists and if it is well-formed.
 39  
  * It does not know whether it is valid HTML.  This class is also used for
 40  
  * generics types which looks like opening HTML tags ex: <T>, <E>, <V>,
 41  
  * <MY_FOO_TYPE>, etc. According to this class they are valid tags.
 42  
  * </p>
 43  
  * @author Chris Stillwell
 44  
  */
 45  
 class TagParser
 46  
 {
 47  
     /** List of HtmlTags found on the input line of text. */
 48  154
     private final List<HtmlTag> mTags = Lists.newLinkedList();
 49  
 
 50  
     /**
 51  
      * Constructs a TagParser and finds the first tag if any.
 52  
      * @param aText the line of text to parse.
 53  
      * @param aLineNo the source line number.
 54  
      */
 55  
     public TagParser(String[] aText, int aLineNo)
 56  154
     {
 57  154
         parseTags(aText, aLineNo);
 58  154
     }
 59  
 
 60  
     /**
 61  
      * Returns the next available HtmlTag.
 62  
      * @return a HtmlTag or <code>null</code> if none available.
 63  
      * @throws IndexOutOfBoundsException if there are no HtmlTags
 64  
      *         left to return.
 65  
      */
 66  
     public HtmlTag nextTag()
 67  
     {
 68  166
         return mTags.remove(0);
 69  
     }
 70  
 
 71  
     /**
 72  
      * Indicates if there are any more HtmlTag to retrieve.
 73  
      * @return <code>true</code> if there are more tags.
 74  
      */
 75  
     public boolean hasNextTag()
 76  
     {
 77  315
         return !mTags.isEmpty();
 78  
     }
 79  
 
 80  
     /**
 81  
      * Performs lazy initialization on the internal tags List
 82  
      * and adds the tag.
 83  
      * @param aTag the HtmlTag to add.
 84  
      */
 85  
     private void add(HtmlTag aTag)
 86  
     {
 87  166
         mTags.add(aTag);
 88  166
     }
 89  
 
 90  
     /**
 91  
      * Parses the text line for any HTML tags and adds them to the internal
 92  
      * List of tags.
 93  
      * @param aText the source line to parse.
 94  
      * @param aLineNo the source line number.
 95  
      */
 96  
     private void parseTags(String[] aText, int aLineNo)
 97  
     {
 98  154
         final int nLines = aText.length;
 99  154
         Point position = new Point(0, 0);
 100  
 
 101  154
         position = findChar(aText, '<', position);
 102  331
         while (position.getLineNo() < nLines) {
 103  
             // if this is html comment then skip it
 104  177
             if (isCommentTag(aText, position)) {
 105  3
                 position = skipHtmlComment(aText, position);
 106  
             }
 107  174
             else if (!isTag(aText, position)) {
 108  8
                 position = getNextCharPos(aText, position);
 109  
             }
 110  
             else {
 111  
                 // find end of tag
 112  166
                 final Point endTag = findChar(aText, '>', position);
 113  166
                 final boolean incompleteTag = (endTag.getLineNo() >= nLines);
 114  
                 // get tag id (one word)
 115  166
                 final String tagId =
 116  
                     (incompleteTag ? "" : getTagId(aText, position));
 117  
                 // is this closed tag
 118  166
                 final boolean closedTag =
 119  
                     ((endTag.getLineNo() < nLines) && (endTag.getColumnNo() > 0)
 120  
                      && (aText[endTag.getLineNo()]
 121  
                      .charAt(endTag.getColumnNo() - 1) == '/'));
 122  
                 // add new tag
 123  166
                 add(new HtmlTag(tagId,
 124  
                                 position.getLineNo() + aLineNo,
 125  
                                 position.getColumnNo(),
 126  
                                 closedTag,
 127  
                                 incompleteTag,
 128  
                                 aText[position.getLineNo()]));
 129  166
                 position = endTag;
 130  
             }
 131  177
             position = findChar(aText, '<', position);
 132  
         }
 133  154
     }
 134  
 
 135  
     /**
 136  
      * Checks if the given position is start one for HTML tag.
 137  
      * @param aText text of javadoc comments.
 138  
      * @param aPos position to check.
 139  
      * @return <code>true</code> some HTML tag starts from given position.
 140  
      */
 141  
     private boolean isTag(String[] aText, Point aPos)
 142  
     {
 143  174
         final int column = aPos.getColumnNo() + 1;
 144  174
         final String text = aText[aPos.getLineNo()];
 145  
 
 146  
         //Character.isJavaIdentifier... may not be a valid HTML
 147  
         //identifier but is valid for generics
 148  174
         return ((column < text.length())
 149  
                 && (Character.isJavaIdentifierStart(text.charAt(column))
 150  
                     || Character.isJavaIdentifierPart(text.charAt(column))
 151  
                     || text.charAt(column) == '/')
 152  
                 || (column >= text.length()));
 153  
     }
 154  
 
 155  
     /**
 156  
      * Parse tag id.
 157  
      * @param aText text of javadoc comments.
 158  
      * @param aTagStart start position of the tag
 159  
      * @return id for given tag
 160  
      */
 161  
     private String getTagId(String[] aText, Point aTagStart)
 162  
     {
 163  161
         int column = aTagStart.getColumnNo() + 1;
 164  161
         String text = aText[aTagStart.getLineNo()];
 165  161
         if (column >= text.length()) {
 166  0
             return "";
 167  
         }
 168  
 
 169  161
         if (text.charAt(column) == '/') {
 170  43
             column++;
 171  
         }
 172  
 
 173  161
         text = text.substring(column).trim();
 174  161
         column = 0;
 175  
 
 176  
         //Character.isJavaIdentifier... may not be a valid HTML
 177  
         //identifier but is valid for generics
 178  
         while (column < text.length()
 179  723
             && (Character.isJavaIdentifierStart(text.charAt(column))
 180  
                 || Character.isJavaIdentifierPart(text.charAt(column))))
 181  
         {
 182  562
             column++;
 183  
         }
 184  
 
 185  161
         return text.substring(0, column);
 186  
     }
 187  
 
 188  
     /**
 189  
      * If this is a HTML-comments.
 190  
      * @param aText text of javadoc comments
 191  
      * @param aPos position to check
 192  
      * @return <code>true</code> if HTML-comments
 193  
      *         starts form given position.
 194  
      */
 195  
     private boolean isCommentTag(String[] aText, Point aPos)
 196  
     {
 197  177
         return aText[aPos.getLineNo()].startsWith("<!--", aPos.getColumnNo());
 198  
     }
 199  
 
 200  
     /**
 201  
      * Skips HTML comments.
 202  
      * @param aText text of javadoc comments.
 203  
      * @param aFrom start position of HTML-comments
 204  
      * @return position after HTML-comments
 205  
      */
 206  
     private Point skipHtmlComment(String[] aText, Point aFrom)
 207  
     {
 208  3
         Point to = aFrom;
 209  3
         to = findChar(aText, '>', to);
 210  
         while ((to.getLineNo() < aText.length)
 211  9
                && !aText[to.getLineNo()]
 212  
                .substring(0, to.getColumnNo()).endsWith("-->"))
 213  
         {
 214  6
             to = findChar(aText, '>', getNextCharPos(aText, to));
 215  
         }
 216  3
         return to;
 217  
     }
 218  
 
 219  
     /**
 220  
      * Finds next occurrence of given character.
 221  
      * @param aText text to search
 222  
      * @param aChar character to search
 223  
      * @param aFrom position to start search
 224  
      * @return position of next occurrence of given character
 225  
      */
 226  
     private Point findChar(String[] aText, char aChar, Point aFrom)
 227  
     {
 228  506
         Point curr = new Point(aFrom.getLineNo(), aFrom.getColumnNo());
 229  
         while ((curr.getLineNo() < aText.length)
 230  11936
                && (aText[curr.getLineNo()].charAt(curr.getColumnNo()) != aChar))
 231  
         {
 232  11430
             curr = getNextCharPos(aText, curr);
 233  
         }
 234  
 
 235  506
         return curr;
 236  
     }
 237  
 
 238  
     /**
 239  
      * Returns position of next comment character, skips
 240  
      * whitespaces and asterisks.
 241  
      * @param aText to search.
 242  
      * @param aFrom location to search from
 243  
      * @return location of the next character.
 244  
      */
 245  
     private Point getNextCharPos(String[] aText, Point aFrom)
 246  
     {
 247  11444
         int line = aFrom.getLineNo();
 248  11444
         int column = aFrom.getColumnNo() + 1;
 249  12214
         while ((line < aText.length) && (column >= aText[line].length())) {
 250  
             // go to the next line
 251  770
             line++;
 252  770
             column = 0;
 253  770
             if (line < aText.length) {
 254  
                 //skip beginning spaces and stars
 255  616
                 final String currentLine = aText[line];
 256  
                 while ((column < currentLine.length())
 257  6134
                        && (Character.isWhitespace(currentLine.charAt(column))
 258  
                            || (currentLine.charAt(column) == '*')))
 259  
                 {
 260  5518
                     column++;
 261  5518
                     if ((column < currentLine.length())
 262  
                         && (currentLine.charAt(column - 1) == '*')
 263  
                         && (currentLine.charAt(column) == '/'))
 264  
                     {
 265  
                         // this is end of comment
 266  136
                         column = currentLine.length();
 267  
                     }
 268  
                 }
 269  616
             }
 270  
         }
 271  
 
 272  11444
         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  
     {
 281  
         /** line number. */
 282  
         private final int mLine;
 283  
         /** column number.*/
 284  
         private final int mColumn;
 285  
 
 286  
         /**
 287  
          * Creates new <code>Point</code> instance.
 288  
          * @param aLineNo line number
 289  
          * @param aColumnNo column number
 290  
          */
 291  
         public Point(int aLineNo, int aColumnNo)
 292  12104
         {
 293  12104
             mLine = aLineNo;
 294  12104
             mColumn = aColumnNo;
 295  12104
         }
 296  
 
 297  
         /**
 298  
          * Getter for line number.
 299  
          * @return line number of the position.
 300  
          */
 301  
         public int getLineNo()
 302  
         {
 303  37343
             return mLine;
 304  
         }
 305  
 
 306  
         /**
 307  
          * Getter for column number.
 308  
          * @return column number of the position.
 309  
          */
 310  
         public int getColumnNo()
 311  
         {
 312  24730
             return mColumn;
 313  
         }
 314  
     }
 315  
 }