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 }