Coverage Report - com.puppycrawl.tools.checkstyle.api.FileText
 
Classes in this File Line Coverage Branch Coverage Complexity
FileText
60%
54/90
50%
13/26
2.833
 
 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.api;
 20  
 
 21  
 import java.io.BufferedReader;
 22  
 import java.io.File;
 23  
 import java.io.FileInputStream;
 24  
 import java.io.IOException;
 25  
 import java.io.InputStreamReader;
 26  
 import java.io.Reader;
 27  
 import java.io.StringReader;
 28  
 import java.io.UnsupportedEncodingException;
 29  
 import java.nio.ByteBuffer;
 30  
 import java.nio.charset.Charset;
 31  
 import java.nio.charset.CharsetDecoder;
 32  
 import java.nio.charset.CodingErrorAction;
 33  
 import java.nio.charset.UnsupportedCharsetException;
 34  
 import java.util.AbstractList;
 35  
 import java.util.ArrayList;
 36  
 import java.util.Arrays;
 37  
 import java.util.ConcurrentModificationException;
 38  
 import java.util.List;
 39  
 import java.util.regex.Matcher;
 40  
 import java.util.regex.Pattern;
 41  
 
 42  
 /**
 43  
  * Represents the text contents of a file of arbitrary plain text type.
 44  
  * <p>
 45  
  * This class will be passed to instances of class FileSetCheck by
 46  
  * Checker. It implements a string list to ensure backwards
 47  
  * compatibility, but can be extended in the future to allow more
 48  
  * flexible, more powerful or more efficient handling of certain
 49  
  * situations.
 50  
  *
 51  
  * @author Martin von Gagern
 52  
  */
 53  1191
 public final class FileText extends AbstractList<String>
 54  
 {
 55  
 
 56  
     /**
 57  
      * The number of characters to read in one go.
 58  
      */
 59  
     private static final int READ_BUFFER_SIZE = 1024;
 60  
 
 61  
     /**
 62  
      * Regular expression pattern matching all line terminators.
 63  
      */
 64  1
     private static final Pattern LINE_TERMINATOR =
 65  
         Utils.getPattern("\\n|\\r\\n?");
 66  
 
 67  
     // For now, we always keep both full text and lines array.
 68  
     // In the long run, however, the one passed at initialization might be
 69  
     // enough, while the other could be lazily created when requested.
 70  
     // This would save memory but cost CPU cycles.
 71  
 
 72  
     /**
 73  
      * The name of the file.
 74  
      * <code>null</code> if no file name is available for whatever reason.
 75  
      */
 76  
     private final File mFile;
 77  
 
 78  
     /**
 79  
      * The charset used to read the file.
 80  
      * <code>null</code> if the file was reconstructed from a list of lines.
 81  
      */
 82  
     private final Charset mCharset;
 83  
 
 84  
     /**
 85  
      * The full text contents of the file.
 86  
      */
 87  
     private final String mFullText;
 88  
 
 89  
     /**
 90  
      * The lines of the file, without terminators.
 91  
      */
 92  
     private final String[] mLines;
 93  
 
 94  
     /**
 95  
      * The first position of each line within the full text.
 96  
      */
 97  
     private int[] mLineBreaks;
 98  
 
 99  
     /**
 100  
      * Creates a new file text representation.
 101  
      *
 102  
      * The file will be read using the specified encoding, replacing
 103  
      * malformed input and unmappable characters with the default
 104  
      * replacement character.
 105  
      *
 106  
      * @param aFile the name of the file
 107  
      * @param aCharsetName the encoding to use when reading the file
 108  
      * @throws NullPointerException if the text is null
 109  
      * @throws IOException if the file could not be read
 110  
      */
 111  
     public FileText(File aFile, String aCharsetName) throws IOException
 112  798
     {
 113  798
         mFile = aFile;
 114  
 
 115  
         // We use our own decoder, to be sure we have complete control
 116  
         // about replacements.
 117  
         final CharsetDecoder decoder;
 118  
         try {
 119  798
             mCharset = Charset.forName(aCharsetName);
 120  798
             decoder = mCharset.newDecoder();
 121  798
             decoder.onMalformedInput(CodingErrorAction.REPLACE);
 122  798
             decoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
 123  
         }
 124  0
         catch (final UnsupportedCharsetException ex) {
 125  0
             final String message = "Unsupported charset: " + aCharsetName;
 126  
             final UnsupportedEncodingException ex2;
 127  0
             ex2 = new UnsupportedEncodingException(message);
 128  0
             ex2.initCause(ex);
 129  0
             throw ex2;
 130  798
         }
 131  
 
 132  798
         final char[] chars = new char[READ_BUFFER_SIZE];
 133  798
         final StringBuilder buf = new StringBuilder();
 134  798
         final FileInputStream stream = new FileInputStream(aFile);
 135  797
         final Reader reader = new InputStreamReader(stream, decoder);
 136  
         try {
 137  
             while (true) {
 138  2629
                 final int len = reader.read(chars);
 139  2629
                 if (len < 0) {
 140  797
                     break;
 141  
                 }
 142  1832
                 buf.append(chars, 0, len);
 143  1832
             }
 144  
         }
 145  
         finally {
 146  797
             Utils.closeQuietly(reader);
 147  797
         }
 148  
         // buf.trimToSize(); // could be used instead of toString().
 149  797
         mFullText = buf.toString();
 150  
 
 151  
         // Use the BufferedReader to break down the lines as this
 152  
         // is about 30% faster than using the
 153  
         // LINE_TERMINATOR.split(mFullText, -1) method
 154  797
         final ArrayList<String> lines = new ArrayList<String>();
 155  797
         final BufferedReader br =
 156  
             new BufferedReader(new StringReader(mFullText));
 157  
         for (;;) {
 158  67919
             final String l = br.readLine();
 159  67919
             if (null == l) {
 160  797
                 break;
 161  
             }
 162  67122
             lines.add(l);
 163  67122
         }
 164  797
         mLines = lines.toArray(new String[lines.size()]);
 165  797
     }
 166  
 
 167  
     /**
 168  
      * Compatibility constructor.
 169  
      *
 170  
      * This constructor reconstructs the text of the file by joining
 171  
      * lines with linefeed characters. This process does not restore
 172  
      * the original line terminators and should therefore be avoided.
 173  
      *
 174  
      * @param aFile the name of the file
 175  
      * @param aLines the lines of the text, without terminators
 176  
      * @throws NullPointerException if the lines array is null
 177  
      */
 178  
     private FileText(File aFile, List<String> aLines)
 179  0
     {
 180  0
         final StringBuilder buf = new StringBuilder();
 181  0
         for (final String line : aLines) {
 182  0
             buf.append(line).append('\n');
 183  
         }
 184  0
         buf.trimToSize();
 185  
 
 186  0
         mFile = aFile;
 187  0
         mCharset = null;
 188  0
         mFullText = buf.toString();
 189  0
         mLines = aLines.toArray(new String[aLines.size()]);
 190  0
     }
 191  
 
 192  
     /**
 193  
      * Compatibility conversion.
 194  
      *
 195  
      * This method can be used to convert the arguments passed to
 196  
      * {@link FileSetCheck#process(File,List)} to a FileText
 197  
      * object. If the list of lines already is a FileText, it is
 198  
      * returned as is. Otherwise, a new FileText is constructed by
 199  
      * joining the lines using line feed characters.
 200  
      *
 201  
      * @param aFile the name of the file
 202  
      * @param aLines the lines of the text, without terminators
 203  
      * @return an object representing the denoted text file
 204  
      */
 205  
     public static FileText fromLines(File aFile, List<String> aLines)
 206  
     {
 207  548
         return (aLines instanceof FileText)
 208  
             ? (FileText) aLines
 209  
             : new FileText(aFile, aLines);
 210  
     }
 211  
 
 212  
     /**
 213  
      * Get the name of the file.
 214  
      * @return an object containing the name of the file
 215  
      */
 216  
     public File getFile()
 217  
     {
 218  749
         return mFile;
 219  
     }
 220  
 
 221  
     /**
 222  
      * Get the character set which was used to read the file.
 223  
      * Will be <code>null</code> for a file reconstructed from its lines.
 224  
      * @return the charset used when the file was read
 225  
      */
 226  
     public Charset getCharset()
 227  
     {
 228  0
         return mCharset;
 229  
     }
 230  
 
 231  
     /**
 232  
      * Get the binary contents of the file.
 233  
      * The returned object must not be modified.
 234  
      * @return a buffer containing the bytes making up the file
 235  
      * @throws IOException if the bytes could not be read from the file
 236  
      */
 237  
     public ByteBuffer getBytes() throws IOException
 238  
     {
 239  
         // We might decide to cache file bytes in the future.
 240  0
         if (mFile == null) {
 241  0
             return null;
 242  
         }
 243  0
         if (mFile.length() > Integer.MAX_VALUE) {
 244  0
             throw new IOException("File too large.");
 245  
         }
 246  0
         byte[] bytes = new byte[(int) mFile.length() + 1];
 247  0
         final FileInputStream stream = new FileInputStream(mFile);
 248  
         try {
 249  0
             int fill = 0;
 250  
             while (true) {
 251  0
                 if (fill >= bytes.length) {
 252  
                     // shouldn't happen, but it might nevertheless
 253  0
                     final byte[] newBytes = new byte[bytes.length * 2 + 1];
 254  0
                     System.arraycopy(bytes, 0, newBytes, 0, fill);
 255  0
                     bytes = newBytes;
 256  
                 }
 257  0
                 final int len = stream.read(bytes, fill,
 258  
                                             bytes.length - fill);
 259  0
                 if (len == -1) {
 260  0
                     break;
 261  
                 }
 262  0
                 fill += len;
 263  0
             }
 264  0
             return ByteBuffer.wrap(bytes, 0, fill).asReadOnlyBuffer();
 265  
         }
 266  
         finally {
 267  0
             Utils.closeQuietly(stream);
 268  
         }
 269  
     }
 270  
 
 271  
     /**
 272  
      * Retrieve the full text of the file.
 273  
      * @return the full text of the file
 274  
      */
 275  
     public CharSequence getFullText()
 276  
     {
 277  754
         return mFullText;
 278  
     }
 279  
 
 280  
     /**
 281  
      * Returns an array of all lines.
 282  
      * {@code text.toLinesArray()} is equivalent to
 283  
      * {@code text.toArray(new String[text.size()])}.
 284  
      * @return an array of all lines of the text
 285  
      */
 286  
     public String[] toLinesArray()
 287  
     {
 288  21341
         return mLines.clone();
 289  
     }
 290  
 
 291  
     /**
 292  
      * Find positions of line breaks in the full text.
 293  
      * @return an array giving the first positions of each line.
 294  
      */
 295  
     private int[] lineBreaks()
 296  
     {
 297  12
         if (mLineBreaks == null) {
 298  4
             final int[] lineBreaks = new int[size() + 1];
 299  4
             lineBreaks[0] = 0;
 300  4
             int lineNo = 1;
 301  4
             final Matcher matcher = LINE_TERMINATOR.matcher(mFullText);
 302  792
             while (matcher.find()) {
 303  788
                 lineBreaks[lineNo++] = matcher.end();
 304  
             }
 305  4
             if (lineNo < lineBreaks.length) {
 306  0
                 lineBreaks[lineNo++] = mFullText.length();
 307  
             }
 308  4
             if (lineNo != lineBreaks.length) {
 309  0
                 throw new ConcurrentModificationException("Text changed.");
 310  
             }
 311  4
             mLineBreaks = lineBreaks;
 312  
         }
 313  12
         return mLineBreaks;
 314  
     }
 315  
 
 316  
     /**
 317  
      * Determine line and column numbers in full text.
 318  
      * @param aPos the character position in the full text
 319  
      * @return the line and column numbers of this character
 320  
      */
 321  
     public LineColumn lineColumn(int aPos)
 322  
     {
 323  12
         final int[] lineBreaks = lineBreaks();
 324  12
         int lineNo = Arrays.binarySearch(lineBreaks, aPos);
 325  12
         if (lineNo < 0) {
 326  
             // we have: lineNo = -(insertion point) - 1
 327  
             // we want: lineNo =  (insertion point) - 1
 328  9
             lineNo = -lineNo - 2;
 329  
         }
 330  12
         final int startOfLine = lineBreaks[lineNo];
 331  12
         final int columnNo = aPos - startOfLine;
 332  
         // now we have lineNo and columnNo, both starting at zero.
 333  12
         return new LineColumn(lineNo + 1, columnNo);
 334  
     }
 335  
 
 336  
     /**
 337  
      * Retrieves a line of the text by its number.
 338  
      * The returned line will not contain a trailing terminator.
 339  
      * @param aLineNo the number of the line to get, starting at zero
 340  
      * @return the line with the given number
 341  
      */
 342  
     @Override
 343  
     public String get(final int aLineNo)
 344  
     {
 345  25509
         return mLines[aLineNo];
 346  
     }
 347  
 
 348  
     /**
 349  
      * Counts the lines of the text.
 350  
      * @return the number of lines in the text
 351  
      */
 352  
     @Override
 353  
     public int size()
 354  
     {
 355  1164
         return mLines.length;
 356  
     }
 357  
 
 358  
 }