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.IOException;
29  import java.io.InputStream;
30  
31  import com.aragost.javahg.log.Logger;
32  import com.aragost.javahg.log.LoggerFactory;
33  
34  /**
35   * An input stream reading one channel block.
36   */
37  public class BlockInputStream extends InputStream {
38  
39      private static final Logger LOG = LoggerFactory.getLogger(BlockInputStream.class);
40  
41      public static final BlockInputStream EMPTY = new BlockInputStream();
42  
43      private final InputStream stream;
44  
45      /**
46       * Name of channel
47       */
48      private final char channel;
49  
50      /**
51       * Length of the block
52       */
53      private final int length;
54  
55      /**
56       * Number of bytes left in the block
57       */
58      private int countDown;
59  
60      private BlockInputStream() {
61          this.stream = null;
62          this.channel = 'o';
63          this.countDown = this.length = 0;
64      }
65  
66      /**
67       * Construct a new BlockInputStream. This will read the channel (1
68       * byte) and length (4 bytes) from the argument InputStream.
69       * 
70       * @param stream
71       * @throws IOException
72       *             from reading channel and length from the underlying
73       *             stream
74       */
75      BlockInputStream(InputStream stream) throws IOException {
76          this.stream = stream;
77          this.channel = (char) stream.read();
78          this.length = this.countDown = readInt();
79          if (this.countDown == -1) {
80              throw new InvalidStreamException();
81          }
82          if (Character.isUpperCase(this.channel)) {
83              // Uppercase means mandatory, which is assumed to be an
84              // "input" channel
85              this.countDown = 0;
86          }
87      }
88  
89      @Override
90      public int read() throws IOException {
91          if (countDown == 0) {
92              return -1;
93          } else {
94              this.countDown--;
95              int read = this.stream.read();
96              LOG.debug("stdout char: {}", (char) read);
97              return read;
98          }
99      }
100 
101     @Override
102     public int read(byte[] b, int off, int len) throws IOException {
103         if (this.countDown == 0) {
104             return -1;
105         }
106         if (len > this.countDown) {
107             len = this.countDown;
108         }
109         int result = stream.read(b, off, len);
110         if (result == -1) {
111             // The server should provide enough bytes. It told the
112             // length up front
113             // This can possible happen if the server is killed. Throw
114             // an IOException so it is properly handled by the
115             // Server#verifyServerProcess method
116             throw new IOException("Unexpected EOF");
117         }
118         this.countDown -= result;
119         if (LOG.isDebugEnabled()) {
120             LOG.debug("read {}: '{}'", "" + this.channel + result, new String(b, off, result));
121         }
122         return result;
123     }
124 
125     @Override
126     public int available() throws IOException {
127         if (this.countDown == 0) {
128             return 0;
129         }
130         int underlyingAvailable = this.stream.available();
131         return underlyingAvailable > this.countDown ? this.countDown : underlyingAvailable;
132     }
133 
134     /**
135      * @return int value (big endian) of the next 4 bytes from the
136      *         underlying stream
137      * @throws IOException
138      */
139     int readInt() throws IOException {
140         return Utils.readBigEndian(this.stream);
141     }
142 
143     char getChannel() {
144         return this.channel;
145     }
146 
147     int getLength() {
148         return length;
149     }
150 
151     int getBytesLeft() {
152         return this.countDown;
153     }
154 
155     /**
156      * Exception to indicate that channel and length could not be read
157      * from the underlying stream.
158      */
159     public static class InvalidStreamException extends RuntimeException {
160 
161         private static final long serialVersionUID = 1L;
162 
163     }
164 
165 }