Coverage Report - com.aragost.javahg.internals.OutputChannelInputStream
 
Classes in this File Line Coverage Branch Coverage Complexity
OutputChannelInputStream
80%
65/81
70%
19/27
7.571
 
 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.ByteArrayOutputStream;
 29  
 import java.io.IOException;
 30  
 import java.io.InputStream;
 31  
 
 32  
 import com.aragost.javahg.commands.CancelledExecutionException;
 33  
 import com.aragost.javahg.internals.AbstractCommand.State;
 34  
 import com.aragost.javahg.log.Logger;
 35  
 import com.aragost.javahg.log.LoggerFactory;
 36  
 
 37  
 /**
 38  
  * An input stream that will make everything written to the output
 39  
  * channel for one command available as an input stream.
 40  
  * <p>
 41  
  * Reading from this stream will add to the
 42  
  * {@link ByteArrayOutputStream} for the other channels, as blocks for
 43  
  * other channels are detected.
 44  
  * <p>
 45  
  * The stream will indicate EOF when the 'r'esult block has been read.
 46  
  * <p>
 47  
  * If a 'L' block is read it will also indicate EOF. After a respond
 48  
  * to the 'L' block is sent to the server, you can call reopen() and
 49  
  * read the rest of the input.
 50  
  */
 51  
 public class OutputChannelInputStream extends InputStream {
 52  
 
 53  1
     private static final Logger LOG = LoggerFactory.getLogger(OutputChannelInputStream.class);
 54  
 
 55  
     private final InputStream channelStream;
 56  
 
 57  
     private final AbstractCommand cmd;
 58  
 
 59  
     private final Server server;
 60  
 
 61  
     /**
 62  
      * The current 'o' channel. If this is {@code null} then eof has
 63  
      * been reached.
 64  
      */
 65  3808
     private BlockInputStream currentOutputChannelBlock = BlockInputStream.EMPTY;
 66  
 
 67  
     /**
 68  
      * 
 69  
      * @param channelStream
 70  
      * @param cmd
 71  
      * @throws IOException
 72  
      */
 73  3808
     OutputChannelInputStream(InputStream channelStream, Server server, AbstractCommand cmd) throws IOException {
 74  3808
         this.channelStream = channelStream;
 75  3808
         this.cmd = cmd;
 76  3808
         this.server = server;
 77  
         try {
 78  3808
             findNextOutputChannelBlock();
 79  18
         } catch (RuntimeException e) {
 80  18
             verifyServerProcess(e);
 81  17
             throw e;
 82  11
         } catch (IOException e) {
 83  11
             verifyServerProcess(e);
 84  0
             throw e;
 85  3779
         }
 86  3779
     }
 87  
 
 88  
     @Override
 89  
     public int read() throws IOException {
 90  1
         if (this.currentOutputChannelBlock == null) {
 91  0
             return -1;
 92  
         }
 93  
         try {
 94  1
             int ch = this.currentOutputChannelBlock.read();
 95  0
             if (ch == -1) {
 96  0
                 findNextOutputChannelBlock();
 97  0
                 return read();
 98  
             }
 99  0
             return ch;
 100  0
         } catch (RuntimeException e) {
 101  0
             verifyServerProcess(e);
 102  0
             throw e;
 103  1
         } catch (IOException e) {
 104  1
             verifyServerProcess(e);
 105  1
             throw e;
 106  
         }
 107  
     }
 108  
 
 109  
     @Override
 110  
     public int read(byte[] b, int off, int len) throws IOException {
 111  16118
         if (this.currentOutputChannelBlock == null) {
 112  5634
             return -1;
 113  
         }
 114  
         try {
 115  10484
             int n = this.currentOutputChannelBlock.read(b, off, len);
 116  10482
             if (n > 0) {
 117  5916
                 return n;
 118  4566
             } else if (n == -1) {
 119  4566
                 findNextOutputChannelBlock();
 120  4563
                 return read(b, off, len);
 121  
             } else {
 122  0
                 return 0;
 123  
             }
 124  3
         } catch (RuntimeException e) {
 125  3
             verifyServerProcess(e);
 126  3
             throw e;
 127  2
         } catch (IOException e) {
 128  2
             verifyServerProcess(e);
 129  1
             throw e;
 130  
         }
 131  
     }
 132  
 
 133  
     @Override
 134  
     public int available() throws IOException {
 135  5592
         if (this.currentOutputChannelBlock == null) {
 136  0
             return 0;
 137  
         } else {
 138  5592
             return this.currentOutputChannelBlock.available();
 139  
         }
 140  
     }
 141  
 
 142  
     /**
 143  
      * Find the next output channel block.
 144  
      * <p>
 145  
      * The method will handle other channels while looking for an
 146  
      * o-channel block.
 147  
      * 
 148  
      * @throws IOException
 149  
      */
 150  
     private void findNextOutputChannelBlock() throws IOException {
 151  8377
         if (this.currentOutputChannelBlock == null) {
 152  0
             throw new IllegalStateException();
 153  
         }
 154  8377
         if (this.currentOutputChannelBlock.getBytesLeft() > 0) {
 155  0
             throw new IllegalStateException("Still bytes available in currentOutputChannelBlock");
 156  
         }
 157  8377
         this.currentOutputChannelBlock = null;
 158  8377
         BlockInputStream blockInputStream = null;
 159  
         while (true) {
 160  8769
             if (cmd.getState() == State.CANCELING) {
 161  1
                 throw new CancelledExecutionException(cmd);
 162  
             }
 163  
 
 164  8768
             blockInputStream = new BlockInputStream(this.channelStream);
 165  8756
             char channel = blockInputStream.getChannel();
 166  8756
             switch (channel) {
 167  
             case 'o':
 168  4570
                 this.currentOutputChannelBlock = blockInputStream;
 169  4570
                 return;
 170  
             case 'e':
 171  391
                 this.cmd.addToError(blockInputStream);
 172  391
                 break;
 173  
             case 'r':
 174  3789
                 if (blockInputStream.getLength() != 4) {
 175  0
                     throw new IllegalStateException("Length 4 expected for channel 'r'");
 176  
                 }
 177  3789
                 int returnCode = blockInputStream.readInt();
 178  3789
                 LOG.debug("Command '{}' gave return code: {}", this.cmd.getCommandName(), returnCode);
 179  3789
                 this.cmd.handleReturnCode(returnCode);
 180  3772
                 return;
 181  
             case 'L':
 182  3
                 this.cmd.setLineChannelLength(blockInputStream.getLength());
 183  3
                 return;
 184  
             default:
 185  3
                 if (Character.isLowerCase(channel)) {
 186  
                     // Ignore an unrecognized channel
 187  1
                     Utils.consumeAll(blockInputStream);
 188  
                 } else {
 189  
                     // Unrecognized but mandatory channel
 190  2
                     throw new IllegalStateException("Unknown channel: " + channel);
 191  
                 }
 192  
             }
 193  392
         }
 194  
     }
 195  
 
 196  
     /**
 197  
      * Reopen this stream to read pending 'o' blocks.
 198  
      * <p>
 199  
      * The stream is indicating EOF when an 'L' block is encountered.
 200  
      * With this method the rest of the rest of the 'o' blocks can be
 201  
      * read.
 202  
      */
 203  
     public void reopen() {
 204  3
         this.currentOutputChannelBlock = BlockInputStream.EMPTY;
 205  
         try {
 206  3
             findNextOutputChannelBlock();
 207  0
         } catch (IOException e) {
 208  0
             throw new RuntimeIOException(e);
 209  3
         }
 210  3
     }
 211  
 
 212  
     /**
 213  
      * @param e
 214  
      */
 215  
     private void verifyServerProcess(Exception e) {
 216  35
         this.server.verifyServerProcess(e);
 217  22
     }
 218  
 }