package org.eclipse.jdt.internal.launching.sourcelookup.advanced;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
public class FileHashing {
public static interface Hasher {
Object hash(File file);
}
private static final HasherImpl HASHER = new HasherImpl(5000);
public static Hasher hasher() {
return HASHER;
}
public static Hasher newHasher() {
return new HasherImpl(HASHER);
}
private static class CacheKey {
public final File file;
private final long length;
private final long lastModified;
public CacheKey(File file) throws IOException {
this.file = file.getCanonicalFile();
this.length = file.length();
this.lastModified = file.lastModified();
}
@Override
public int hashCode() {
int hash = 17;
hash = hash * 31 + file.hashCode();
hash = hash * 31 + (int) length;
hash = hash * 31 + (int) lastModified;
return hash;
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof CacheKey)) {
return false;
}
CacheKey other = (CacheKey) obj;
return file.equals(other.file) && length == other.length && lastModified == other.lastModified;
}
}
private static class HashCode {
private final byte[] bytes;
public HashCode(byte[] bytes) {
this.bytes = bytes;
}
@Override
public int hashCode() {
return Arrays.hashCode(bytes);
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof HashCode)) {
return false;
}
return Arrays.equals(bytes, ((HashCode) obj).bytes);
}
@Override
public final String toString() {
StringBuilder sb = new StringBuilder(2 * bytes.length);
for (byte b : bytes) {
sb.append(hexDigits[(b >> 4) & 0xf]).append(hexDigits[b & 0xf]);
}
return sb.toString();
}
private static final char[] hexDigits = "0123456789abcdef".toCharArray();
}
private static class HasherImpl implements Hasher {
private final Map<CacheKey, HashCode> cache;
@SuppressWarnings("serial")
public HasherImpl(int cacheSize) {
this.cache = new LinkedHashMap<CacheKey, HashCode>() {
@Override
protected boolean removeEldestEntry(Map.Entry<CacheKey, HashCode> eldest) {
return size() > cacheSize;
}
};
}
public HasherImpl(HasherImpl initial) {
this.cache = new LinkedHashMap<>(initial.cache);
}
@Override
public Object hash(File file) {
if (file == null || !file.isFile()) {
return null;
}
try {
CacheKey cacheKey = new CacheKey(file);
synchronized (cache) {
HashCode hashCode = cache.get(cacheKey);
if (hashCode != null) {
return hashCode;
}
}
HashCode hashCode = sha1(file);
synchronized (cache) {
cache.put(cacheKey, hashCode);
}
return hashCode;
}
catch (IOException e) {
return null;
}
}
}
private static HashCode sha1(File file) throws IOException {
MessageDigest digest;
try {
digest = MessageDigest.getInstance("SHA1");
}
catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("Unsupported JVM", e);
}
byte[] buf = new byte[4096];
try (InputStream is = new FileInputStream(file)) {
int len;
while ((len = is.read(buf)) > 0) {
digest.update(buf, 0, len);
}
}
return new HashCode(digest.digest());
}
}