1 /*
   2  * Copyright (c) 2015, 2020, 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 jdk.internal.module;
  27 
  28 import java.io.IOException;
  29 import java.io.UncheckedIOException;
  30 import java.nio.ByteBuffer;
  31 import java.nio.channels.FileChannel;
  32 import java.nio.file.Path;
  33 import java.security.MessageDigest;
  34 import java.security.NoSuchAlgorithmException;
  35 import java.util.Collections;
  36 import java.util.HashMap;
  37 import java.util.TreeMap;
  38 import java.util.Map;
  39 import java.util.Objects;
  40 import java.util.Set;
  41 
  42 /**
  43  * The result of hashing the contents of a number of module artifacts.
  44  */
  45 
  46 public final class ModuleHashes {
  47 
  48     /**
  49      * A supplier of a message digest.
  50      */
  51     public static interface HashSupplier {
  52         byte[] generate(String algorithm);
  53     }
  54 
  55     private final String algorithm;
  56     private final Map<String, byte[]> nameToHash;
  57 
  58     /**
  59      * Creates a {@code ModuleHashes}.
  60      *
  61      * @param algorithm   the algorithm used to create the hashes
  62      * @param nameToHash  the map of module name to hash value
  63      */
  64     public ModuleHashes(String algorithm, Map<String, byte[]> nameToHash) {
  65         this.algorithm = algorithm;
  66         this.nameToHash = Collections.unmodifiableMap(nameToHash);
  67     }
  68 
  69     /**
  70      * Returns the algorithm used to hash the modules ("SHA-256" for example).
  71      */
  72     public String algorithm() {
  73         return algorithm;
  74     }
  75 
  76     /**
  77      * Returns the set of module names for which hashes are recorded.
  78      */
  79     public Set<String> names() {
  80         return nameToHash.keySet();
  81     }
  82 
  83     /**
  84      * Returns the hash for the given module name, {@code null}
  85      * if there is no hash recorded for the module.
  86      */
  87     public byte[] hashFor(String mn) {
  88         return nameToHash.get(mn);
  89     }
  90 
  91     /**
  92      * Returns unmodifiable map of module name to hash
  93      */
  94     public Map<String, byte[]> hashes() {
  95         return nameToHash;
  96     }
  97 
  98     /**
  99      * Computes the hash for the given file with the given message digest
 100      * algorithm.
 101      *
 102      * @throws UncheckedIOException if an I/O error occurs
 103      * @throws RuntimeException if the algorithm is not available
 104      */
 105     public static byte[] computeHash(Path file, String algorithm) {
 106         try {
 107             MessageDigest md = MessageDigest.getInstance(algorithm);
 108 
 109             // Ideally we would just mmap the file but this consumes too much
 110             // memory when jlink is running concurrently on very large jmods
 111             try (FileChannel fc = FileChannel.open(file)) {
 112                 ByteBuffer bb = ByteBuffer.allocate(32*1024);
 113                 while (fc.read(bb) > 0) {
 114                     bb.flip();
 115                     md.update(bb);
 116                     assert bb.remaining() == 0;
 117                     bb.clear();
 118                 }
 119             }
 120 
 121             return md.digest();
 122         } catch (NoSuchAlgorithmException e) {
 123             throw new RuntimeException(e);
 124         } catch (IOException ioe) {
 125             throw new UncheckedIOException(ioe);
 126         }
 127     }
 128 
 129     /**
 130      * Computes the hash for every entry in the given map, returning a
 131      * {@code ModuleHashes} to encapsulate the result. The map key is
 132      * the entry name, typically the module name. The map value is the file
 133      * path to the entry (module artifact).
 134      *
 135      * @return ModuleHashes that encapsulates the hashes
 136      */
 137     public static ModuleHashes generate(Map<String, Path> map, String algorithm) {
 138         Map<String, byte[]> nameToHash = new TreeMap<>();
 139         for (Map.Entry<String, Path> entry: map.entrySet()) {
 140             String name = entry.getKey();
 141             Path path = entry.getValue();
 142             nameToHash.put(name, computeHash(path, algorithm));
 143         }
 144         return new ModuleHashes(algorithm, nameToHash);
 145     }
 146 
 147     /**
 148      * This is used by jdk.internal.module.SystemModules class
 149      * generated at link time.
 150      */
 151     public static class Builder {
 152         final String algorithm;
 153         final Map<String, byte[]> nameToHash;
 154 
 155         Builder(String algorithm, int initialCapacity) {
 156             this.nameToHash = new HashMap<>(initialCapacity);
 157             this.algorithm =  Objects.requireNonNull(algorithm);
 158         }
 159 
 160         /**
 161          * Sets the module hash for the given module name
 162          */
 163         public Builder hashForModule(String mn, byte[] hash) {
 164             nameToHash.put(mn, hash);
 165             return this;
 166         }
 167 
 168         /**
 169          * Builds a {@code ModuleHashes}.
 170          */
 171         public ModuleHashes build() {
 172             if (!nameToHash.isEmpty()) {
 173                 return new ModuleHashes(algorithm, nameToHash);
 174             } else {
 175                 return null;
 176             }
 177         }
 178     }
 179 }