1 // Copyright 2016 Google Inc. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package com.google.archivepatcher.generator; 16 17 import java.io.IOException; 18 import java.io.InputStream; 19 import java.io.OutputStream; 20 21 /** 22 * A simple {@link OutputStream} that requires all bytes that are written to match bytes from a 23 * specified corresponding {@link InputStream}. Any length or content mismatch results in the stream 24 * being closed and an error being raised. 25 * <p> 26 * Every call to one of the write(...) methods results in reading the same total number of bytes 27 * from the {@link InputStream}. The bytes in both streams must match exactly. 28 */ 29 public class MatchingOutputStream extends OutputStream { 30 31 /** 32 * The bytes to match against. 33 */ 34 private final InputStream expectedBytesStream; 35 36 /** 37 * The buffer for reading bytes from the input stream for matching. 38 */ 39 private final byte[] buffer; 40 41 /** 42 * Constructs a new stream that will match against the the specified {@link InputStream}. 43 * @param expectedBytesStream stream of bytes to expect to see 44 * @param matchBufferSize the number of bytes to reserve for matching against the specified 45 * {@link InputStream}. This 46 */ MatchingOutputStream(InputStream expectedBytesStream, int matchBufferSize)47 public MatchingOutputStream(InputStream expectedBytesStream, int matchBufferSize) { 48 if (matchBufferSize < 1) { 49 throw new IllegalArgumentException("buffer size must be >= 1"); 50 } 51 this.expectedBytesStream = expectedBytesStream; 52 this.buffer = new byte[matchBufferSize]; 53 } 54 55 @Override write(int b)56 public void write(int b) throws IOException { 57 int expected = expectedBytesStream.read(); 58 if (expected == -1) { 59 throw new MismatchException("EOF reached in expectedBytesStream"); 60 } 61 if (expected != b) { 62 throw new MismatchException("Data does not match"); 63 } 64 } 65 66 @Override write(byte[] b)67 public void write(byte[] b) throws IOException { 68 write(b, 0, b.length); 69 } 70 71 @Override write(byte[] dataToWrite, int offset, int length)72 public void write(byte[] dataToWrite, int offset, int length) throws IOException { 73 int numReadSoFar = 0; 74 while (numReadSoFar < length) { 75 int maxToRead = Math.min(buffer.length, length - numReadSoFar); 76 int numReadThisLoop = expectedBytesStream.read(buffer, 0, maxToRead); 77 if (numReadThisLoop == -1) { 78 throw new MismatchException("EOF reached in expectedBytesStream"); 79 } 80 for (int matchCount = 0; matchCount < numReadThisLoop; matchCount++) { 81 if (buffer[matchCount] != dataToWrite[offset + numReadSoFar + matchCount]) { 82 throw new MismatchException("Data does not match"); 83 } 84 } 85 numReadSoFar += numReadThisLoop; 86 } 87 } 88 89 @Override close()90 public void close() throws IOException { 91 expectedBytesStream.close(); 92 } 93 94 /** 95 * Expects the end-of-file to be reached in the associated {@link InputStream}. 96 * @throws IOException if the end-of-file has not yet been reached in the associated 97 * {@link InputStream} 98 */ expectEof()99 public void expectEof() throws IOException { 100 if (expectedBytesStream.read() != -1) { 101 throw new MismatchException("EOF not reached in expectedBytesStream"); 102 } 103 } 104 } 105