Coverage Report - com.aragost.javahg.internals.HgInputStream
 
Classes in this File Line Coverage Branch Coverage Complexity
HgInputStream
84%
122/145
80%
63/78
3.905
 
 1  
 /*
 2  
  * #%L
 3  
  * JavaHg
 4  
  * %%
 5  
  * Copyright (C) 2011 aragost Trifork ag
 6  
  * %%
 7  
  * Permission is hereby granted, free of charge, to any person obtaining a copy
 8  
  * of this software and associated documentation files (the "Software"), to deal
 9  
  * in the Software without restriction, including without limitation the rights
 10  
  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 11  
  * copies of the Software, and to permit persons to whom the Software is
 12  
  * furnished to do so, subject to the following conditions:
 13  
  * 
 14  
  * The above copyright notice and this permission notice shall be included in
 15  
  * all copies or substantial portions of the Software.
 16  
  * 
 17  
  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 18  
  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 19  
  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 20  
  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 21  
  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 22  
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 23  
  * THE SOFTWARE.
 24  
  * #L%
 25  
  */
 26  
 package com.aragost.javahg.internals;
 27  
 
 28  
 import java.io.BufferedInputStream;
 29  
 import java.io.ByteArrayOutputStream;
 30  
 import java.io.IOException;
 31  
 import java.io.InputStream;
 32  
 import java.nio.charset.CharsetDecoder;
 33  
 
 34  
 import com.aragost.javahg.DateTime;
 35  
 
 36  
 /**
 37  
  * An InputStream that has some methods that make it convenient for
 38  
  * JavaHg to read the stdout from the command server.
 39  
  */
 40  
 public class HgInputStream extends BufferedInputStream {
 41  
 
 42  
     private CharsetDecoder textDecoder;
 43  
 
 44  
     /**
 45  
      * @param in
 46  
      *            the byte stream.
 47  
      * @param textDecoder
 48  
      *            the decoder used when Strings are extracted from the
 49  
      *            byte stream.
 50  
      */
 51  
     public HgInputStream(InputStream in, CharsetDecoder textDecoder) {
 52  3816
         super(in);
 53  3816
         this.textDecoder = textDecoder;
 54  3816
     }
 55  
 
 56  
     /**
 57  
      * Return the next byte from the stream without forwarding the
 58  
      * position.
 59  
      * 
 60  
      * @return the byte read or -1 if EOF is reached.
 61  
      * @throws IOException
 62  
      */
 63  
     public int peek() throws IOException {
 64  4187
         mark(1);
 65  4187
         int result = read();
 66  4187
         reset();
 67  4187
         return result;
 68  
     }
 69  
 
 70  
     /**
 71  
      * @return true if no more bytes can be read from the stream
 72  
      * @throws IOException
 73  
      */
 74  
     public boolean isEof() throws IOException {
 75  3888
         return peek() == -1;
 76  
     }
 77  
 
 78  
     /**
 79  
      * Get the next {@code length} bytes from the stream.
 80  
      * 
 81  
      * @param length
 82  
      *            the number of bytes to read.
 83  
      * @return the bytes read or {@code null} if EOF is reached before
 84  
      *         all the bytes have been read.
 85  
      * @throws IOException
 86  
      */
 87  
     public byte[] next(int length) throws IOException {
 88  330
         byte[] bytes = new byte[length];
 89  330
         int remaining = length;
 90  660
         while (remaining > 0) {
 91  330
             int n = read(bytes, length - remaining, remaining);
 92  330
             if (n == -1) {
 93  0
                 return null;
 94  
             }
 95  330
             remaining = remaining - n;
 96  330
         }
 97  330
         return bytes;
 98  
     }
 99  
 
 100  
     /**
 101  
      * Get the next {@code length} bytes from the stream, and return
 102  
      * it as a String
 103  
      * 
 104  
      * @param length
 105  
      * @return the decoded String
 106  
      * @throws IOException
 107  
      */
 108  
     public String nextAsText(int length) throws IOException {
 109  220
         byte[] bytes = next(length);
 110  220
         if (bytes == null) {
 111  0
             return null;
 112  
         } else {
 113  220
             return Utils.decodeBytes(bytes, this.textDecoder);
 114  
         }
 115  
     }
 116  
 
 117  
     /**
 118  
      * Look for a fixed set of bytes in the stream. The current
 119  
      * position is advanced by the length of the bytes to look for if
 120  
      * they are found, otherwise it is left unchanged.
 121  
      * 
 122  
      * @param bytes
 123  
      *            the bytes to look for
 124  
      * @return true if the bytes were found at the current position.
 125  
      * @throws IOException
 126  
      */
 127  
     public boolean match(byte[] bytes) throws IOException {
 128  346
         mark(bytes.length);
 129  2840
         for (int i = 0; i < bytes.length; i++) {
 130  2725
             int n = read();
 131  2725
             if (n == -1 || (byte) n != bytes[i]) {
 132  231
                 reset();
 133  231
                 return false;
 134  
             }
 135  
         }
 136  115
         return true;
 137  
     }
 138  
 
 139  
     /**
 140  
      * Look for a fixed byte in the stream. The current position is
 141  
      * advanced by 1 if the byte is found, otherwise it is left
 142  
      * unchanged.
 143  
      * 
 144  
      * @param b
 145  
      *            the byte to look for.
 146  
      * @return true if the byte was found at the current position.
 147  
      * @throws IOException
 148  
      */
 149  
     public boolean match(int b) throws IOException {
 150  148
         mark(1);
 151  148
         int n = read();
 152  148
         if (n == b) {
 153  146
             return true;
 154  
         } else {
 155  2
             reset();
 156  2
             return false;
 157  
         }
 158  
     }
 159  
 
 160  
     /**
 161  
      * Verifies that the next bytes in the stream matches the
 162  
      * specified bytes.
 163  
      * 
 164  
      * @param bytes
 165  
      * @throws IOException
 166  
      * @throws UnexpectedCommandOutputException
 167  
      *             if the stream doesn't match the specified bytes
 168  
      */
 169  
     public void mustMatch(byte[] bytes) throws IOException, UnexpectedCommandOutputException {
 170  838
         for (int i = 0; i < bytes.length; i++) {
 171  796
             mustMatch(bytes[i]);
 172  
         }
 173  42
     }
 174  
 
 175  
     /**
 176  
      * Verifies that the next byte in the stream matches the specified
 177  
      * byte.
 178  
      * 
 179  
      * @param b
 180  
      *            the next byte
 181  
      * @throws IOException
 182  
      * @throws UnexpectedCommandOutputException
 183  
      *             if the stream doesn't match the specified bytes
 184  
      */
 185  
     public void mustMatch(int b) throws IOException, UnexpectedCommandOutputException {
 186  946
         int n = read();
 187  946
         if ((byte) n != b) {
 188  0
             throw new UnexpectedCommandOutputException("Got " + n + ", but expected " + b);
 189  
         }
 190  946
     }
 191  
 
 192  
     /**
 193  
      * Read from the stream until a fixed set of bytes are found. The
 194  
      * current position is left after the stop bytes.
 195  
      * 
 196  
      * @param stop
 197  
      *            the bytes to look for.
 198  
      * @return the bytes read while looking for the stop bytes. This
 199  
      *         does not include the stop bytes themselves.
 200  
      * @throws IOException
 201  
      */
 202  
     public byte[] upTo(byte[] stop) throws IOException {
 203  22
         if (stop.length == 0) {
 204  0
             return new byte[0];
 205  
         }
 206  22
         ByteArrayOutputStream byteStream = new ByteArrayOutputStream(80);
 207  22
         int stopIndex = 0;
 208  
 
 209  
         while (true) {
 210  1087
             int b = read();
 211  1087
             if (b == -1) {
 212  1
                 return null;
 213  
                 // byteStream.write(stop, 0, stopIndex);
 214  
                 // break;
 215  
             }
 216  1086
             if (stop[stopIndex] == (byte) b) {
 217  820
                 if (stopIndex == 0) {
 218  39
                     mark(stop.length);
 219  
                 }
 220  820
                 stopIndex++;
 221  820
                 if (stopIndex == stop.length) {
 222  21
                     break;
 223  
                 }
 224  
             } else {
 225  266
                 if (stopIndex > 0) {
 226  17
                     byteStream.write(stop, 0, 1);
 227  17
                     reset();
 228  17
                     stopIndex = 0;
 229  
                 } else {
 230  249
                     byteStream.write(b);
 231  
                 }
 232  
             }
 233  1065
         }
 234  21
         return byteStream.toByteArray();
 235  
     }
 236  
 
 237  
     /**
 238  
      * Read from the stream until a fixed byte is found. The current
 239  
      * position is left after the stop byte.
 240  
      * 
 241  
      * @param stop
 242  
      *            the byte to look for.
 243  
      * @return the bytes read while looking for the stop byte. This
 244  
      *         does not include the stop byte.
 245  
      * @throws IOException
 246  
      */
 247  
     public byte[] upTo(int stop) throws IOException {
 248  1719
         ByteArrayOutputStream byteStream = new ByteArrayOutputStream(40);
 249  
         while (true) {
 250  21199
             int n = read();
 251  21199
             if (n == -1) {
 252  0
                 return null;
 253  
             }
 254  21199
             if (n == stop) {
 255  1719
                 break;
 256  
             }
 257  19480
             byteStream.write(n);
 258  19480
         }
 259  1719
         return byteStream.toByteArray();
 260  
     }
 261  
 
 262  
     /**
 263  
      * Search for the specified bytes in the stream. If found then the
 264  
      * position in the stream is just after the bytes. If not found,
 265  
      * the stream is positioned at EOF.
 266  
      * 
 267  
      * @param bytes
 268  
      * @return true if the bytes were found, false otherwise
 269  
      * @throws IOException
 270  
      */
 271  
     public boolean find(byte[] bytes) throws IOException {
 272  111
         int index = 0;
 273  111
         int length = bytes.length;
 274  111
         if (length == 0) {
 275  2
             throw new IllegalArgumentException("Can't search for nothing");
 276  
         }
 277  
         while (true) {
 278  12428
             int b = read();
 279  12428
             if (b == -1) {
 280  23
                 return false;
 281  
             }
 282  12405
             if (bytes[index] == (byte) b) {
 283  1940
                 if (index == 0) {
 284  320
                     mark(length);
 285  
                 }
 286  1940
                 index++;
 287  1940
                 if (index == length) {
 288  86
                     return true;
 289  
                 }
 290  10465
             } else if (index > 0) {
 291  234
                 reset();
 292  234
                 index = 0;
 293  
             }
 294  12319
         }
 295  
     }
 296  
 
 297  
     /**
 298  
      * Read from stream until the specified byte is read.
 299  
      * 
 300  
      * @param b
 301  
      * @return true if the byte was found, otherwise false
 302  
      * @throws IOException
 303  
      */
 304  
     public boolean find(int b) throws IOException {
 305  
         while (true) {
 306  0
             int n = read();
 307  0
             if (n == -1) {
 308  0
                 return false;
 309  
             }
 310  0
             if (n == b) {
 311  0
                 return true;
 312  
             }
 313  0
         }
 314  
 
 315  
     }
 316  
 
 317  
     /**
 318  
      * Read a non-negative integer from the stream until a fixed byte
 319  
      * is found. The current position is left after the stop byte.
 320  
      * 
 321  
      * @param stop
 322  
      *            the byte to look for.
 323  
      * @return the integer read.
 324  
      * @throws IOException
 325  
      */
 326  
     public int decimalIntUpTo(int stop) throws IOException {
 327  339
         int result = 0;
 328  
         while (true) {
 329  1980
             int n = read();
 330  1980
             if (n == -1 || n == stop) {
 331  339
                 return result;
 332  
             }
 333  1641
             int digit = n - '0';
 334  1641
             if (digit < 0 || digit >= 10) {
 335  0
                 throw new IOException("A non-digit found: " + (char) n);
 336  
             }
 337  1641
             result = result * 10 + digit;
 338  1641
         }
 339  
     }
 340  
 
 341  
     /**
 342  
      * Read a non-negative integer from the stream.
 343  
      * 
 344  
      * All characters that are a valid digit is read.
 345  
      * 
 346  
      * @return null if the next character is a non-digit, otherwise
 347  
      *         return the integer value of the digit characters read
 348  
      *         from stream
 349  
      * @throws IOException
 350  
      */
 351  
     public Integer readDecimal() throws IOException {
 352  49
         boolean somethingRead = false;
 353  49
         int result = 0;
 354  
         while (true) {
 355  103
             mark(1);
 356  103
             int n = read();
 357  103
             if (n == -1) {
 358  2
                 break;
 359  
             }
 360  101
             int digit = n - '0';
 361  101
             if (digit >= 0 && digit < 10) {
 362  54
                 somethingRead = true;
 363  54
                 result = 10 * result + digit;
 364  
             } else {
 365  47
                 reset();
 366  47
                 break;
 367  
             }
 368  54
         }
 369  49
         return somethingRead ? Integer.valueOf(result) : null;
 370  
     }
 371  
 
 372  
     /**
 373  
      * Read a revision number from the stream until a fixed byte is
 374  
      * found. A revision number is an integer greater than or equal to
 375  
      * -1. The current position is left after the stop byte.
 376  
      * <p>
 377  
      * Initial spaces in the stream is skipped until a '-' or a digit is found.
 378  
      * 
 379  
      * @param stop
 380  
      *            the byte to look for.
 381  
      * @return the integer read.
 382  
      * @throws IOException
 383  
      */
 384  
     public int revisionUpTo(int stop) throws IOException {
 385  119
         while (peek() == ' ') {
 386  2
             read();
 387  
         }
 388  117
         if (peek() == '-') {
 389  0
             read();
 390  0
             mustMatch('1');
 391  0
             mustMatch(stop);
 392  0
             return -1;
 393  
         } else {
 394  117
             return decimalIntUpTo(stop);
 395  
         }
 396  
     }
 397  
 
 398  
     /**
 399  
      * Read a Mercurial date from the stream, stopping when a fixed
 400  
      * byte is met. A Mercurial date is produced with the "hgdate"
 401  
      * template filter and consist of two integers, the first is the
 402  
      * number of seconds since 1970 and the second is the time zone
 403  
      * offset.
 404  
      * 
 405  
      * @param stopByte
 406  
      *            the stop byte
 407  
      * @return a parsed date
 408  
      * @throws IOException
 409  
      */
 410  
     public DateTime dateTimeUpTo(int stopByte) throws IOException {
 411  110
         long millis = 1000L * decimalIntUpTo(' ');
 412  110
         boolean negative = match('-');
 413  110
         int timezoneOffset = 1000 * decimalIntUpTo(stopByte);
 414  110
         if (negative) {
 415  108
             timezoneOffset = -timezoneOffset;
 416  
         }
 417  110
         return new DateTime(millis, timezoneOffset);
 418  
     }
 419  
 
 420  
     /**
 421  
      * Read from the stream until {@code end} is found, return the
 422  
      * read portion as a String. The current position is left after
 423  
      * the {@code end} marker.
 424  
      * 
 425  
      * @param end
 426  
      *            the stop marker.
 427  
      * @return the decoded bytes
 428  
      * @throws IOException
 429  
      */
 430  
     public String textUpTo(byte[] end) throws IOException {
 431  7
         byte[] bytes = upTo(end);
 432  7
         if (bytes == null) {
 433  0
             return null;
 434  
         } else {
 435  7
             return Utils.decodeBytes(bytes, this.textDecoder);
 436  
         }
 437  
     }
 438  
 
 439  
     /**
 440  
      * Read from the stream until the byte {@code b} is found, return
 441  
      * the read portion as a String. The current position is left
 442  
      * after the {@code b} marker.
 443  
      * 
 444  
      * @param b
 445  
      *            the stop marker.
 446  
      * @return the decoded bytes
 447  
      * @throws IOException
 448  
      */
 449  
     public String textUpTo(int b) throws IOException {
 450  1454
         byte[] bytes = upTo(b);
 451  1454
         if (bytes == null) {
 452  0
             return null;
 453  
         } else {
 454  1454
             return Utils.decodeBytes(bytes, this.textDecoder);
 455  
         }
 456  
 
 457  
     }
 458  
 
 459  
     /**
 460  
      * Read until EOF and discard the bytes read
 461  
      * 
 462  
      * @throws IOException
 463  
      */
 464  
     public void consumeAll() throws IOException {
 465  44
         Utils.consumeAll(this);
 466  44
     }
 467  
 
 468  
     @Override
 469  
     public String toString() {
 470  
         // Overridden for debugability
 471  0
         StringBuilder buffer = new StringBuilder();
 472  
 
 473  0
         buffer.append(new String(buf, 0, this.pos));
 474  0
         buffer.append(">@<");
 475  0
         buffer.append(new String(buf, this.pos, this.count));
 476  
 
 477  0
         return buffer.toString();
 478  
     }
 479  
 }