Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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;

public final class Adler32 extends ChecksumHashFunction {
public 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;

public final class CRC32 extends ChecksumHashFunction {
public 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());
}
}
141 changes: 12 additions & 129 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,33 @@
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.Locale;

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

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 String extension() {
return this.ext;
}

public static HashFunction find(String name) {
String cleaned = name.toUpperCase(Locale.ENGLISH);
for (HashFunction func : values()) {
if (cleaned.equals(func.name()))
return func;
}

return null;
}

public static HashFunction findByHash(String hash) {
int len = hash.length();
for (HashFunction func : values()) {
if (func.pad.length() == len)
return func;
}

return null;
}

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

public MessageDigest get() {
try {
return MessageDigest.getInstance(algo);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}

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

public String sneakyHash(File file) {
try {
return hash(file);
} catch (IOException e) {
return HashUtils.sneak(e);
}
}

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

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

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

for (File file : files) {
if (!file.exists())
continue;
public abstract String hash(Iterable<File> files) throws IOException;

try (FileInputStream fin = new FileInputStream(file)) {
int count = -1;
while ((count = fin.read(buf)) != -1)
hash.update(buf, 0, count);
}
}
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 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));
public final String hash(String data) {
return hash(data == null ? new byte[0] : data.getBytes());
}

public String hash(byte[] data) {
return pad(new BigInteger(1, get().digest(data)).toString(16));
}
public abstract String hash(InputStream inputStream) throws IOException;
public abstract String hash(byte[] bytes);

public String pad(String hash) {
return (pad + hash).substring(hash.length());
}
public abstract String pad(String hash);

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);
public static HashFunction[] values() {
return new HashFunction[] { Adler32.INSTANCE, CRC32.INSTANCE, MD5.INSTANCE, SHA1.INSTANCE, SHA256.INSTANCE, SHA512.INSTANCE };
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,16 @@
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 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 @@ -75,13 +71,13 @@ public boolean isSame(File file) {
String hash = oldHashes.get(path);
if (hash == null) {
if (file.exists()) {
newHashes.put(path, HASH.hash(file));
newHashes.put(path, SHA1.INSTANCE.hash(file));
return false;
}
return true;
}

String fileHash = HASH.hash(file);
String fileHash = SHA1.INSTANCE.hash(file);
newHashes.put(path, fileHash);
return fileHash.equals(hash);
} catch (IOException e) {
Expand All @@ -97,7 +93,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 All @@ -123,13 +119,13 @@ public HashStore addKnown(String key, String data) {

public HashStore add(String key, String data) {
if (!data.isEmpty())
newHashes.put(Objects.requireNonNull(key), HASH.hash(data));
newHashes.put(Objects.requireNonNull(key), SHA1.INSTANCE.hash(data));
return this;
}

public HashStore add(String key, byte[] data) {
if (data.length > 0)
this.newHashes.put(Objects.requireNonNull(key), HASH.hash(data));
this.newHashes.put(Objects.requireNonNull(key), SHA1.INSTANCE.hash(data));
return this;
}

Expand All @@ -142,12 +138,12 @@ 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));
this.newHashes.put(key + " - " + suffix, SHA1.INSTANCE.hash(f));
}
} else {
this.newHashes.put(key, HASH.hash(file));
this.newHashes.put(key, SHA1.INSTANCE.hash(file));
}
} catch (IOException e) {
throw new RuntimeException(e);
Expand Down Expand Up @@ -189,9 +185,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 +204,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