1 /*
   2  * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.oracle.ipack.util;
  27 
  28 import java.io.FilterOutputStream;
  29 import java.io.IOException;
  30 import java.io.OutputStream;
  31 import java.security.MessageDigest;
  32 import java.security.NoSuchAlgorithmException;
  33 import java.util.ArrayList;
  34 import java.util.Collections;
  35 import java.util.List;
  36 
  37 /**
  38  * Similar to HashingOutputStream, but calculates and stores a hash for each
  39  * page of the input stream.
  40  */
  41 public final class PageHashingOutputStream extends FilterOutputStream {
  42     private final MessageDigest messageDigest;
  43     private final List<byte[]> pageHashes;
  44     private final int pageSize;
  45 
  46     private int pageRemaining;
  47 
  48     public PageHashingOutputStream(final OutputStream out) {
  49         this(out, 4096);
  50     }
  51 
  52     public PageHashingOutputStream(final OutputStream out, final int pageSize) {
  53         super(out);
  54         try {
  55             messageDigest = MessageDigest.getInstance("SHA1");
  56         } catch (final NoSuchAlgorithmException e) {
  57             throw new IllegalStateException("Can't create message digest", e);
  58         }
  59         pageHashes = new ArrayList<byte[]>();
  60         this.pageSize = pageSize;
  61         this.pageRemaining = pageSize;
  62     }
  63 
  64     public List<byte[]> getPageHashes() {
  65         return Collections.unmodifiableList(pageHashes);
  66     }
  67 
  68     @Override
  69     public void write(final int byteValue) throws IOException {
  70         out.write(byteValue);
  71         messageDigest.update((byte) byteValue);
  72         --pageRemaining;
  73         if (pageRemaining == 0) {
  74             commitPageHashImpl();
  75         }
  76     }
  77 
  78     @Override
  79     public void write(final byte[] buffer, final int offset, final int length)
  80             throws IOException {
  81         out.write(buffer, offset, length);
  82 
  83         int hashRemaining = length;
  84         int hashOffset = offset;
  85         while (hashRemaining > 0) {
  86             final int chunkLength =
  87                     (pageRemaining < hashRemaining) ? pageRemaining
  88                                                     : hashRemaining;
  89 
  90             messageDigest.update(buffer, hashOffset, chunkLength);
  91 
  92             hashOffset += chunkLength;
  93             hashRemaining -= chunkLength;
  94             pageRemaining -= chunkLength;
  95 
  96             if (pageRemaining == 0) {
  97                 commitPageHashImpl();
  98             }
  99         }
 100     }
 101 
 102     /**
 103      * Creates a new page hash from the remaining data.
 104      *
 105      * If there are some data which haven't been included in the previous page
 106      * hash, this methods adds a new page hash for them even if they don't
 107      * fill up a complete page.
 108      */
 109     public void commitPageHash() {
 110         if (pageRemaining != pageSize) {
 111             // we do have some data for hashing
 112             commitPageHashImpl();
 113         }
 114     }
 115 
 116     private void commitPageHashImpl() {
 117         pageHashes.add(messageDigest.digest());
 118         pageRemaining = pageSize;
 119     }
 120 }