JavaBuilder/src/dorkbox/build/util/Hash.java

230 lines
7.2 KiB
Java

/*
* Copyright 2012 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.build.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashSet;
import java.util.Set;
import com.esotericsoftware.wildcard.Paths;
import dorkbox.BuildOptions;
import dorkbox.Builder;
import dorkbox.build.Project;
import dorkbox.util.Base64Fast;
import dorkbox.util.IO;
/**
* CHECKSUM LOGIC
*/
@SuppressWarnings({"Convert2Diamond", "AnonymousHasLambdaAlternative"})
public
class Hash {
private static final ThreadLocal<MessageDigest> digestThreadLocal = new ThreadLocal<MessageDigest>() {
@Override
protected
MessageDigest initialValue() {
try {
return MessageDigest.getInstance("SHA-1");
} catch (NoSuchAlgorithmException ignored) {
// will never happen, since SHA1 is part of java.
return null;
}
}
};
// set by Project.java
public static boolean forceRebuildAll = false;
private transient Paths checksumPaths = new Paths();
private final String projectName;
private BuildOptions buildOptions;
public
Hash(final String projectName, BuildOptions buildOptions) {
this.projectName = projectName;
this.buildOptions = buildOptions;
}
/**
* Add paths to be checksum'd.
*/
public
void add(final Paths paths) {
this.checksumPaths.add(paths);
}
public
void add(final String file) {
this.checksumPaths.addFile(file);
}
/**
* @return true if the checksums for path match the saved checksums and the jar file exists, false if the check failed and the
* project needs to rebuild
*/
public
boolean verifyChecksums() throws IOException {
if (forceRebuildAll || this.buildOptions.compiler.forceRebuild) {
return false;
}
// check to see if our SOURCES *and check-summed files* have changed.
String hashedContents = generateChecksums(this.checksumPaths);
String checkContents = Builder.settings.get(this.projectName, String.class);
return hashedContents != null && hashedContents.equals(checkContents);
}
/**
* Saves the checksums for a given path
*/
public
void saveChecksums() throws IOException {
// hash/save the sources *and check-summed files* files
String hashedContents = generateChecksums(this.checksumPaths);
Builder.settings.save(this.projectName, hashedContents);
}
/**
* Generates checksums for the given path
*/
public static
String generateChecksum(File file) throws IOException {
synchronized (Project.class) {
// calculate the hash of file
boolean found = false;
if (file.isFile() && file.canRead()) {
found = true;
}
if (!found) {
return null;
}
MessageDigest sha1 = digestThreadLocal.get();
sha1.reset();
FileInputStream inputStream = null;
try {
inputStream = new FileInputStream(file);
FileChannel channel = inputStream.getChannel();
long length = file.length();
if (length > Integer.MAX_VALUE) {
// you could make this work with some care,
// but this code does not bother.
throw new IOException("File " + file.getAbsolutePath() + " is too large.");
}
ByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, length);
int bufsize = 1024 * 8;
byte[] temp = new byte[bufsize];
int bytesRead = 0;
while (bytesRead < length) {
int numBytes = (int) length - bytesRead >= bufsize ? bufsize : (int) length - bytesRead;
buffer.get(temp, 0, numBytes);
sha1.update(temp, 0, numBytes);
bytesRead += numBytes;
}
byte[] hashBytes = sha1.digest();
return Base64Fast.encodeToString(hashBytes, false);
} finally {
IO.closeQuietly(inputStream);
}
}
}
/**
* Generates checksums for the given path
*/
public static
String generateChecksums(Paths... paths) throws IOException {
synchronized (Project.class) {
// calculate the hash of all the files in the source path
Set<String> names = new HashSet<String>(64);
for (Paths path : paths) {
names.addAll(path.getPaths());
}
// hash of all files. faster than using java to hash files
MessageDigest sha1 = digestThreadLocal.get();
sha1.reset();
int bufsize = 1024 * 8;
byte[] temp = new byte[bufsize];
int bytesRead = 0;
boolean found = false;
for (String name : names) {
File file = new File(name);
if (file.isFile() && file.canRead()) {
found = true;
FileInputStream inputStream = null;
try {
inputStream = new FileInputStream(file);
FileChannel channel = inputStream.getChannel();
long length = file.length();
if (length > Integer.MAX_VALUE) {
// you could make this work with some care,
// but this code does not bother.
throw new IOException("File " + file.getAbsolutePath() + " is too large.");
}
ByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, length);
while (bytesRead < length) {
int numBytes = (int) length - bytesRead >= bufsize ? bufsize : (int) length - bytesRead;
buffer.get(temp, 0, numBytes);
sha1.update(temp, 0, numBytes);
bytesRead += numBytes;
}
} finally {
IO.closeQuietly(inputStream);
}
}
}
if (!found) {
return null;
}
byte[] hashBytes = sha1.digest();
return Base64Fast.encodeToString(hashBytes, false);
}
}
}