View Javadoc

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          super(in);
53          this.textDecoder = textDecoder;
54      }
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          mark(1);
65          int result = read();
66          reset();
67          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          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          byte[] bytes = new byte[length];
89          int remaining = length;
90          while (remaining > 0) {
91              int n = read(bytes, length - remaining, remaining);
92              if (n == -1) {
93                  return null;
94              }
95              remaining = remaining - n;
96          }
97          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         byte[] bytes = next(length);
110         if (bytes == null) {
111             return null;
112         } else {
113             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         mark(bytes.length);
129         for (int i = 0; i < bytes.length; i++) {
130             int n = read();
131             if (n == -1 || (byte) n != bytes[i]) {
132                 reset();
133                 return false;
134             }
135         }
136         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         mark(1);
151         int n = read();
152         if (n == b) {
153             return true;
154         } else {
155             reset();
156             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         for (int i = 0; i < bytes.length; i++) {
171             mustMatch(bytes[i]);
172         }
173     }
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         int n = read();
187         if ((byte) n != b) {
188             throw new UnexpectedCommandOutputException("Got " + n + ", but expected " + b);
189         }
190     }
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         if (stop.length == 0) {
204             return new byte[0];
205         }
206         ByteArrayOutputStream byteStream = new ByteArrayOutputStream(80);
207         int stopIndex = 0;
208 
209         while (true) {
210             int b = read();
211             if (b == -1) {
212                 return null;
213                 // byteStream.write(stop, 0, stopIndex);
214                 // break;
215             }
216             if (stop[stopIndex] == (byte) b) {
217                 if (stopIndex == 0) {
218                     mark(stop.length);
219                 }
220                 stopIndex++;
221                 if (stopIndex == stop.length) {
222                     break;
223                 }
224             } else {
225                 if (stopIndex > 0) {
226                     byteStream.write(stop, 0, 1);
227                     reset();
228                     stopIndex = 0;
229                 } else {
230                     byteStream.write(b);
231                 }
232             }
233         }
234         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         ByteArrayOutputStream byteStream = new ByteArrayOutputStream(40);
249         while (true) {
250             int n = read();
251             if (n == -1) {
252                 return null;
253             }
254             if (n == stop) {
255                 break;
256             }
257             byteStream.write(n);
258         }
259         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         int index = 0;
273         int length = bytes.length;
274         if (length == 0) {
275             throw new IllegalArgumentException("Can't search for nothing");
276         }
277         while (true) {
278             int b = read();
279             if (b == -1) {
280                 return false;
281             }
282             if (bytes[index] == (byte) b) {
283                 if (index == 0) {
284                     mark(length);
285                 }
286                 index++;
287                 if (index == length) {
288                     return true;
289                 }
290             } else if (index > 0) {
291                 reset();
292                 index = 0;
293             }
294         }
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             int n = read();
307             if (n == -1) {
308                 return false;
309             }
310             if (n == b) {
311                 return true;
312             }
313         }
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         int result = 0;
328         while (true) {
329             int n = read();
330             if (n == -1 || n == stop) {
331                 return result;
332             }
333             int digit = n - '0';
334             if (digit < 0 || digit >= 10) {
335                 throw new IOException("A non-digit found: " + (char) n);
336             }
337             result = result * 10 + digit;
338         }
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         boolean somethingRead = false;
353         int result = 0;
354         while (true) {
355             mark(1);
356             int n = read();
357             if (n == -1) {
358                 break;
359             }
360             int digit = n - '0';
361             if (digit >= 0 && digit < 10) {
362                 somethingRead = true;
363                 result = 10 * result + digit;
364             } else {
365                 reset();
366                 break;
367             }
368         }
369         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         while (peek() == ' ') {
386             read();
387         }
388         if (peek() == '-') {
389             read();
390             mustMatch('1');
391             mustMatch(stop);
392             return -1;
393         } else {
394             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         long millis = 1000L * decimalIntUpTo(' ');
412         boolean negative = match('-');
413         int timezoneOffset = 1000 * decimalIntUpTo(stopByte);
414         if (negative) {
415             timezoneOffset = -timezoneOffset;
416         }
417         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         byte[] bytes = upTo(end);
432         if (bytes == null) {
433             return null;
434         } else {
435             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         byte[] bytes = upTo(b);
451         if (bytes == null) {
452             return null;
453         } else {
454             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         Utils.consumeAll(this);
466     }
467 
468     @Override
469     public String toString() {
470         // Overridden for debugability
471         StringBuilder buffer = new StringBuilder();
472 
473         buffer.append(new String(buf, 0, this.pos));
474         buffer.append(">@<");
475         buffer.append(new String(buf, this.pos, this.count));
476 
477         return buffer.toString();
478     }
479 }