Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions hash-utils/src/main/java/net/minecraftforge/util/hash/Adler32.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright (c) Forge Development LLC
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.util.hash;

import java.util.zip.Checksum;

final class Adler32 extends ChecksumHashFunction {
static final Adler32 INSTANCE = new Adler32();

@Override
protected Checksum getHasher() {
return new java.util.zip.Adler32();
}

@Override
public String extension() {
return "alder32";
}
}
21 changes: 21 additions & 0 deletions hash-utils/src/main/java/net/minecraftforge/util/hash/CRC32.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright (c) Forge Development LLC
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.util.hash;

import java.util.zip.Checksum;

final class CRC32 extends ChecksumHashFunction {
static final CRC32 INSTANCE = new CRC32();

@Override
protected Checksum getHasher() {
return new java.util.zip.CRC32();
}

@Override
public String extension() {
return "crc32";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright (c) Forge Development LLC
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.util.hash;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Locale;
import java.util.zip.Checksum;

abstract class ChecksumHashFunction extends HashFunction {
private static final String PADDING = String.format(Locale.ENGLISH, "%032d", 0);

protected abstract Checksum getHasher();

@Override
public final String hash(Iterable<File> files) throws IOException {
Checksum hasher = getHasher();
byte[] buffer = new byte[8192];

for (File file : files) {
if (!file.exists())
continue;

try (FileInputStream fin = new FileInputStream(file)) {
int count = -1;
while ((count = fin.read(buffer)) != -1)
hasher.update(buffer, 0, count);
}
}
return pad(Long.toHexString(hasher.getValue()));
}

@Override
public final String hash(InputStream inputStream) throws IOException {
Checksum hasher = getHasher();
byte[] buffer = new byte[8192];
int count = -1;
while ((count = inputStream.read(buffer)) != -1)
hasher.update(buffer, 0, count);
return pad(Long.toHexString(hasher.getValue()));
}

@Override
public final String hash(byte[] bytes) {
Checksum hasher = getHasher();
hasher.update(bytes, 0, bytes.length);
return pad(Long.toHexString(hasher.getValue()));
}

@Override
public final String pad(String hash) {
return (PADDING + hash).substring(hash.length());
}
}
167 changes: 53 additions & 114 deletions hash-utils/src/main/java/net/minecraftforge/util/hash/HashFunction.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,150 +8,89 @@
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Locale;

public enum HashFunction {
MD5 ("md5", 32),
SHA1 ("SHA-1", 40),
SHA256("SHA-256", 64),
SHA512("SHA-512", 128);

private final String algo;
private final String pad;
private final String ext;
private Boolean supported;

HashFunction(String algo, int length) {
this.algo = algo;
this.pad = String.format(Locale.ENGLISH, "%0" + length + "d", 0);
this.ext = this.name().toLowerCase(Locale.ENGLISH);
public abstract class HashFunction implements Iterable<HashFunction> {
public abstract String extension();

public final String hash(File file) throws IOException {
try (FileInputStream fin = new FileInputStream(file)) {
return hash(fin);
}
}

public String extension() {
return this.ext;
public final String hash(File... files) throws IOException {
return hash(Arrays.asList(files));
}

public static HashFunction find(String name) {
String cleaned = name.toUpperCase(Locale.ENGLISH);
for (HashFunction func : values()) {
if (cleaned.equals(func.name()))
return func;
}
public abstract String hash(Iterable<File> files) throws IOException;

return null;
public final String hash(String data) {
return hash(data == null ? new byte[0] : data.getBytes());
}

public static HashFunction findByHash(String hash) {
int len = hash.length();
for (HashFunction func : values()) {
if (func.pad.length() == len)
return func;
}
public abstract String hash(InputStream inputStream) throws IOException;
public abstract String hash(byte[] bytes);

return null;
}
public abstract String pad(String hash);

public boolean supported() {
if (supported == null) {
try {
MessageDigest.getInstance(algo);
supported = true;
} catch (NoSuchAlgorithmException e) {
supported = false;
}
}
return supported;
public static HashFunction alder32() {
return Adler32.INSTANCE;
}

public MessageDigest get() {
try {
return MessageDigest.getInstance(algo);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
public static HashFunction crc32() {
return CRC32.INSTANCE;
}

public String hash(File file) throws IOException {
try (FileInputStream fin = new FileInputStream(file)) {
return hash(fin);
}
public static HashFunction md5() {
return MD5.INSTANCE;
}

public String sneakyHash(File file) {
try {
return hash(file);
} catch (IOException e) {
return HashUtils.sneak(e);
}
public static HashFunction sha1() {
return SHA1.INSTANCE;
}

public String hash(File... files) throws IOException {
return hash(Arrays.asList(files));
public static HashFunction sha256() {
return SHA256.INSTANCE;
}

public String sneakyHash(File... files) {
try {
return hash(Arrays.asList(files));
} catch (IOException e) {
return HashUtils.sneak(e);
}
public static HashFunction sha512() {
return SHA512.INSTANCE;
}

public String hash(Iterable<File> files) throws IOException {
MessageDigest hash = get();
byte[] buf = new byte[1024];

for (File file : files) {
if (!file.exists())
continue;

try (FileInputStream fin = new FileInputStream(file)) {
int count = -1;
while ((count = fin.read(buf)) != -1)
hash.update(buf, 0, count);
}
public static HashFunction byName(String extension) {
switch (extension.toLowerCase(Locale.ROOT)) {
case "adler32": return Adler32.INSTANCE;
case "crc32": return CRC32.INSTANCE;
case "md5": return MD5.INSTANCE;
case "sha1": return SHA1.INSTANCE;
case "sha256": return SHA256.INSTANCE;
case "sha512": return SHA512.INSTANCE;
default: throw new UnsupportedOperationException("Unknown hash extension: " + extension);
}
return pad(new BigInteger(1, hash.digest()).toString(16));
}

public String hash(String data) {
return hash(data == null ? new byte[0] : data.getBytes(StandardCharsets.UTF_8));
public static HashFunction[] values() {
return new HashFunction[] { Adler32.INSTANCE, CRC32.INSTANCE, MD5.INSTANCE, SHA1.INSTANCE, SHA256.INSTANCE, SHA512.INSTANCE };
}

public String hash(InputStream stream) throws IOException {
MessageDigest hash = get();
byte[] buf = new byte[1024];
int count = -1;
while ((count = stream.read(buf)) != -1)
hash.update(buf, 0, count);
return pad(new BigInteger(1, hash.digest()).toString(16));
}
@Override
public Iterator<HashFunction> iterator() {
return new Iterator<HashFunction>() {
private final HashFunction[] values = values();
private int index = 0;

public String hash(byte[] data) {
return pad(new BigInteger(1, get().digest(data)).toString(16));
}

public String pad(String hash) {
return (pad + hash).substring(hash.length());
}
@Override
public boolean hasNext() {
return values.length > index;
}

private static final byte[] HEX_ARRAY = "0123456789abcdef".getBytes(StandardCharsets.UTF_8);
public static String bytesToHex(byte[] bytes) {
if (bytes == null || bytes.length == 0)
return "";
byte[] hexChars = new byte[bytes.length * 3 - 1];
for (int j = 0, k = 0; j < bytes.length; j++) {
int v = bytes[j] & 0xFF;
hexChars[k++] = HEX_ARRAY[v >>> 4];
hexChars[k++] = HEX_ARRAY[v & 0x0F];
if (j < bytes.length - 1)
hexChars[k++] = ' ';
}
return new String(hexChars, StandardCharsets.UTF_8);
@Override
public HashFunction next() {
return values[index++];
}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,17 @@
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

import static net.minecraftforge.util.hash.HashUtils.sneak;

@NotNullByDefault
public class HashStore {
private static final HashFunction HASH = HashFunction.SHA1;

private static final HashFunction HASH = HashFunction.sha1();
private final String root;
private final Map<String, String> oldHashes = new HashMap<>();
private final Map<String, String> newHashes = new HashMap<>();
private final HashMap<String, String> oldHashes = new HashMap<>();
private final HashMap<String, String> newHashes = new HashMap<>();
private @Nullable File target;
private boolean saved;

Expand Down Expand Up @@ -97,7 +94,7 @@ public HashStore load(File file) {

try {
for (String line : Files.readAllLines(file.toPath())) {
String[] split = line.split("=");
String[] split = line.split("=", 2);
oldHashes.put(split[0], split[1]);
}
} catch (IOException e) {
Expand Down Expand Up @@ -142,7 +139,7 @@ public HashStore add(@Nullable String key, File file) {

if (file.isDirectory()) {
String prefix = getPath(file);
for (File f : HashUtils.listFiles(file)) {
for (File f : listFiles(file)) {
String suffix = getPath(f).substring(prefix.length());
this.newHashes.put(key + " - " + suffix, HASH.hash(f));
}
Expand Down Expand Up @@ -189,9 +186,9 @@ public void save() {
}

public void save(File file) {
StringBuilder buf = new StringBuilder();
ArrayList<String> keys = new ArrayList<>(this.newHashes.keySet());
Collections.sort(keys);
keys.sort(null);
StringBuilder buf = new StringBuilder((keys.size() + 2) * 64); // rough estimate of size

for (String key : keys)
buf.append(key).append('=').append(this.newHashes.get(key)).append('\n');
Expand All @@ -208,6 +205,28 @@ public boolean isSaved() {
return this.saved;
}

private static ArrayList<File> listFiles(File path) {
return listFiles(path, new ArrayList<>());
}

private static ArrayList<File> listFiles(File dir, ArrayList<File> files) {
if (!dir.exists())
return files;

if (!dir.isDirectory())
throw new IllegalArgumentException("Path must be directory: " + dir.getAbsolutePath());

//noinspection DataFlowIssue - checked by File#isDirectory
for (File file : dir.listFiles()) {
if (file.isDirectory())
files = listFiles(file, files);
else
files.add(file);
}

return files;
}

private String getPath(File file) {
String path = file.getAbsolutePath();

Expand Down
Loading