package org.dcache.util;
import com.google.common.base.CharMatcher;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import java.io.Serializable;
import java.util.Set;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Strings.padStart;
import static org.dcache.util.ChecksumType.ADLER32;
public class Checksum implements Serializable
{
private static final long serialVersionUID = 7338775749513974986L;
private static final String HEX_DIGITS = "0123456789abcdef";
private static final CharMatcher HEXADECIMAL =
CharMatcher.anyOf(HEX_DIGITS);
private static final char DELIMITER = ':';
private final ChecksumType type;
private final String value;
/**
* Creates a new instance of Checksum.
* @throws IllegalArugmentException if the number of bytes in value is
* incorrect for the supplied type
* @throws NullPointerException if either argument is null
*/
public Checksum(ChecksumType type, byte[] value)
{
this(type, bytesToHexString(value));
}
/**
* Creates a new instance of Checksum based on supplied type and a
* string of the checksum value in hexadecimal. If the type is ADLER32
* then the value may omit any leading zeros.
* @throws IllegalArugmentException if the value is inappropriate for
* the supplied checksum type.
* @throws NullPointerException if either argument is null
*/
public Checksum(ChecksumType type, String value)
{
checkNotNull(type, "type may not be null");
checkNotNull(value, "value may not be null");
this.type = type;
this.value = normalise(value);
}
private String normalise(String original)
{
String normalised = original.trim().toLowerCase();
/**
* Due to bug in checksum calculation module, some ADLER32
* sums are stored without leading zeros.
*/
if (type == ADLER32) {
normalised = padStart(normalised, type.getNibbles(), '0');
}
if (!HEXADECIMAL.matchesAllOf(normalised)) {
throw new IllegalArgumentException("checksum value \"" +
original + "\" contains non-hexadecimal digits");
}
if (normalised.length() != type.getNibbles()) {
throw new IllegalArgumentException(type.getName() + " requires " +
type.getNibbles() + " hexadecimal digits but \"" +
original + "\" has " + normalised.length());
}
return normalised;
}
public ChecksumType getType()
{
return type;
}
public String getValue()
{
return value;
}
public byte[] getBytes()
{
return stringToBytes(value);
}
@Override
public boolean equals(Object other)
{
if(other == null){
return false;
}
if (other == this) {
return true;
}
if (!(other.getClass().equals(Checksum.class))) {
return false;
}
Checksum that = (Checksum) other;
return ((this.type == that.type) && this.value.equals(that.value));
}
@Override
public int hashCode()
{
return value.hashCode() ^ type.hashCode();
}
@Override
public String toString()
{
return toString(false);
}
public String toString(boolean useStringKey)
{
return (useStringKey ? type.getName() : String.valueOf(type.getType())) + ':' + value;
}
public static String bytesToHexString(byte[] bytes)
{
checkNotNull(bytes, "byte array may not be null");
StringBuilder sb = new StringBuilder();
for (byte aByte : bytes) {
sb.append(HEX_DIGITS.charAt((aByte >> 4) & 0xf));
sb.append(HEX_DIGITS.charAt(aByte & 0xf));
}
return sb.toString();
}
private static byte[] stringToBytes(String str)
{
if ((str.length() % 2) != 0) {
str = '0' + str;
}
byte[] r = new byte[str.length() / 2];
for (int i = 0, l = str.length(); i < l; i += 2) {
r[i / 2] = (byte) Integer.parseInt(str.substring(i, i + 2), 16);
}
return r;
}
/**
* Create a new checksum instance for an already computed digest
* of a particular type.
*
* @param digest the input must have the following format:
* <type>:<hexadecimal digest>
* @throws IllegalArgumentException if argument has wrong form
* @throws NullPointerException if argument is null
*/
public static Checksum parseChecksum(String digest)
{
checkNotNull(digest, "value may not be null");
int del = digest.indexOf(DELIMITER);
if (del < 1) {
throw new IllegalArgumentException("Not a dCache checksum: " + digest);
}
String type = digest.substring(0, del);
String checksum = digest.substring(del + 1);
return new Checksum(ChecksumType.getChecksumType(type), checksum);
}
/**
* Returns an {@link Optional} containing checksum of a given type. If
* no matching checksum type is found, an empty {@link Optional} will be returned.
* @param checksums to evaluate
* @param type of checksum
* @return Optional containing checksum
*/
public static Optional<Checksum> forType(final Set<Checksum> checksums, final ChecksumType type) {
return Iterables.tryFind(checksums, new Predicate<Checksum>() {
@Override
public boolean apply(Checksum t) {
return t.getType() == type;
}
});
}
}