Author: rhauch
Date: 2008-08-22 15:32:12 -0400 (Fri, 22 Aug 2008)
New Revision: 461
Modified:
trunk/dna-common/src/main/java/org/jboss/dna/common/util/StringUtil.java
trunk/dna-spi/src/main/java/org/jboss/dna/spi/SpiI18n.java
trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/Binary.java
trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/ValueComparators.java
trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/impl/InMemoryBinary.java
trunk/dna-spi/src/main/resources/org/jboss/dna/spi/SpiI18n.properties
trunk/dna-spi/src/test/java/org/jboss/dna/spi/graph/impl/InMemoryBinaryTest.java
Log:
Added to the Binary interface the ability to obtain the SHA-1 hash of the content. This
can be used to determine whether two Binary instances contain the same content, as the
SHA-1 digests will be the same given the same content.
Modified: trunk/dna-common/src/main/java/org/jboss/dna/common/util/StringUtil.java
===================================================================
--- trunk/dna-common/src/main/java/org/jboss/dna/common/util/StringUtil.java 2008-08-20
19:16:50 UTC (rev 460)
+++ trunk/dna-common/src/main/java/org/jboss/dna/common/util/StringUtil.java 2008-08-22
19:32:12 UTC (rev 461)
@@ -336,8 +336,8 @@
}
/**
- * Read and return the entire contents of the supplied {@link InputStream}. This
method always closes the stream when
- * finished reading.
+ * Read and return the entire contents of the supplied {@link InputStream}. This
method always closes the stream when finished
+ * reading.
*
* @param stream the streamed contents; may be null
* @return the contents, or an empty string if the supplied stream is null
@@ -383,8 +383,8 @@
* <li>A string is written wrapped by double quotes.</li>
* <li>A boolean is written using {@link Boolean#toString()}.</li>
* <li>A {@link Number number} is written using the standard {@link
Number#toString() toString()} method.</li>
- * <li>A {@link java.util.Date date} is written using the the {@link
DateUtil#getDateAsStandardString(java.util.Date)}
- * utility method.</li>
+ * <li>A {@link java.util.Date date} is written using the the {@link
DateUtil#getDateAsStandardString(java.util.Date)} utility
+ * method.</li>
* <li>A {@link java.sql.Date SQL date} is written using the the {@link
DateUtil#getDateAsStandardString(java.util.Date)}
* utility method.</li>
* <li>A {@link Calendar Calendar instance} is written using the the {@link
DateUtil#getDateAsStandardString(Calendar)}
@@ -392,10 +392,10 @@
* <li>An array of bytes is written with a leading "[ " and trailing
" ]" surrounding the bytes written as UTF-8.
* <li>An array of objects is written with a leading "[ " and
trailing " ]", and with all objects sent through
* {@link #readableString(Object)} and separated by ", ".</li>
- * <li>A collection of objects (e.g,
<code>Collection<?></code>) is written with a leading "[ " and
trailing " ]", and
- * with all objects sent through {@link #readableString(Object)} and separated by
", ".</li>
- * <li>A map of objects (e.g, <code>Map<?></code>) is written
with a leading "{ " and trailing " }", and with all map
- * entries written in the form "key => value" and separated by ",
". All key and value objects are sent through the
+ * <li>A collection of objects (e.g,
<code>Collection<?></code>) is written with a leading "[ " and
trailing " ]", and with
+ * all objects sent through {@link #readableString(Object)} and separated by ",
".</li>
+ * <li>A map of objects (e.g, <code>Map<?></code>) is written
with a leading "{ " and trailing " }", and with all map entries
+ * written in the form "key => value" and separated by ", ".
All key and value objects are sent through the
* {@link #readableString(Object)} method.</li>
* <li>Any other object is written using the object's {@link
Object#toString() toString()} method.</li>
* </ul>
@@ -649,6 +649,28 @@
return NORMALIZE_PATTERN.matcher(text).replaceAll(" ").trim();
}
+ private static final byte[] HEX_CHAR_TABLE = {(byte)'0', (byte)'1',
(byte)'2', (byte)'3', (byte)'4', (byte)'5',
(byte)'6',
+ (byte)'7', (byte)'8', (byte)'9', (byte)'a',
(byte)'b', (byte)'c', (byte)'d', (byte)'e',
(byte)'f'};
+
+ /**
+ * Get the hexadecimal string representation of the supplied byte array.
+ *
+ * @param bytes the byte array
+ * @return the hex string representation of the byte array; never null
+ * @throws UnsupportedEncodingException
+ */
+ public static String getHexString( byte[] bytes ) throws UnsupportedEncodingException
{
+ byte[] hex = new byte[2 * bytes.length];
+ int index = 0;
+
+ for (byte b : bytes) {
+ int v = b & 0xFF;
+ hex[index++] = HEX_CHAR_TABLE[v >>> 4];
+ hex[index++] = HEX_CHAR_TABLE[v & 0xF];
+ }
+ return new String(hex, "ASCII");
+ }
+
private StringUtil() {
// Prevent construction
}
Modified: trunk/dna-spi/src/main/java/org/jboss/dna/spi/SpiI18n.java
===================================================================
--- trunk/dna-spi/src/main/java/org/jboss/dna/spi/SpiI18n.java 2008-08-20 19:16:50 UTC
(rev 460)
+++ trunk/dna-spi/src/main/java/org/jboss/dna/spi/SpiI18n.java 2008-08-22 19:32:12 UTC
(rev 461)
@@ -58,6 +58,7 @@
public static I18n pathExpressionIsInvalid;
public static I18n pathExpressionHasInvalidSelect;
public static I18n pathExpressionHasInvalidMatch;
+ public static I18n messageDigestNotFound;
public static I18n executingGraphCommand;
public static I18n executedGraphCommand;
Modified: trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/Binary.java
===================================================================
--- trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/Binary.java 2008-08-20 19:16:50
UTC (rev 460)
+++ trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/Binary.java 2008-08-22 19:32:12
UTC (rev 461)
@@ -23,10 +23,12 @@
import java.io.InputStream;
import java.io.Serializable;
+import java.security.MessageDigest;
import net.jcip.annotations.Immutable;
/**
* Value holder for binary data. Binary instances are not mutable.
+ *
* @author Randall Hauch
*/
@Immutable
@@ -34,29 +36,53 @@
/**
* Get the length of this binary data.
+ *
* @return the number of bytes in this binary data
+ * @see #acquire()
*/
public long getSize();
/**
+ * Get the SHA-1 hash of the contents. This hash can be used to determine whether two
Binary instances contain the same
+ * content.
+ * <p>
+ * Repeatedly calling this method should generally be efficient, as it most
implementations will compute the hash only once.
+ * </p>
+ *
+ * @return the hash of the contents as a byte array, or an empty array if the hash
could not be computed.
+ * @see #acquire()
+ * @see MessageDigest#digest(byte[])
+ * @see MessageDigest#getInstance(String)
+ */
+ public byte[] getHash();
+
+ /**
* Get the contents of this data as a stream.
+ *
* @return the stream to this data's contents
+ * @see #acquire()
*/
public InputStream getStream();
/**
* Get the contents of this data as a byte array.
+ *
* @return the data as an array
+ * @see #acquire()
*/
public byte[] getBytes();
/**
* Acquire any resources for this data. This method must be called before any other
method on this object.
+ *
+ * @see #release()
*/
public void acquire();
/**
* Release any acquired resources. This method must be called after a client is
finished with this value.
+ *
+ * @see #acquire()
*/
public void release();
Modified: trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/ValueComparators.java
===================================================================
--- trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/ValueComparators.java 2008-08-20
19:16:50 UTC (rev 460)
+++ trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/ValueComparators.java 2008-08-22
19:32:12 UTC (rev 461)
@@ -109,6 +109,19 @@
final long len2 = o2.getSize();
if (len1 < len2) return -1;
if (len1 > len2) return 1;
+
+ // Compare using the hashes, if available
+ byte[] hash1 = o1.getHash();
+ byte[] hash2 = o2.getHash();
+ if (hash1.length != 0 || hash2.length != 0) {
+ assert hash1.length == hash2.length;
+ for (int i = 0; i != hash1.length; ++i) {
+ int diff = hash1[i] - hash2[i];
+ if (diff != 0) return diff;
+ }
+ return 0;
+ }
+
// Otherwise they are the same length ...
InputStream stream1 = null;
InputStream stream2 = null;
Modified: trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/impl/InMemoryBinary.java
===================================================================
---
trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/impl/InMemoryBinary.java 2008-08-20
19:16:50 UTC (rev 460)
+++
trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/impl/InMemoryBinary.java 2008-08-22
19:32:12 UTC (rev 461)
@@ -23,18 +23,29 @@
import java.io.ByteArrayInputStream;
import java.io.InputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
import net.jcip.annotations.Immutable;
import org.jboss.dna.common.util.ArgCheck;
+import org.jboss.dna.common.util.Logger;
+import org.jboss.dna.spi.SpiI18n;
import org.jboss.dna.spi.graph.Binary;
import org.jboss.dna.spi.graph.ValueComparators;
/**
* An implementation of {@link Binary} that keeps the binary data in-memory.
+ *
* @author Randall Hauch
*/
@Immutable
public class InMemoryBinary implements Binary {
+ protected static final Set<String> ALGORITHMS_NOT_FOUND_AND_LOGGED = new
CopyOnWriteArraySet<String>();
+ private static final String SHA1DIGEST_NAME = "SHA-1";
+ private static final byte[] NO_HASH = new byte[] {};
+
/**
*/
private static final long serialVersionUID = 8792863149767123559L;
@@ -42,6 +53,7 @@
protected static final byte[] EMPTY_CONTENT = new byte[0];
private final byte[] bytes;
+ private byte[] sha1hash;
public InMemoryBinary( byte[] bytes ) {
ArgCheck.isNotNull(bytes, "bytes");
@@ -57,7 +69,40 @@
/**
* {@inheritDoc}
+ *
+ * @see org.jboss.dna.spi.graph.Binary#getHash()
*/
+ public byte[] getHash() {
+ if (sha1hash == null) {
+ // Omnipotent, so doesn't matter if we recompute in concurrent threads
...
+ try {
+ sha1hash = getHash(SHA1DIGEST_NAME);
+ } catch (NoSuchAlgorithmException e) {
+ if (ALGORITHMS_NOT_FOUND_AND_LOGGED.add(SHA1DIGEST_NAME)) {
+ Logger.getLogger(getClass()).error(e, SpiI18n.messageDigestNotFound,
SHA1DIGEST_NAME);
+ }
+ sha1hash = NO_HASH;
+ }
+ }
+ return sha1hash;
+ }
+
+ /**
+ * Get the hash of the contents, using the digest identified by the supplied name.
+ *
+ * @param digestName the name of the hashing function (or {@link MessageDigest
message digest}) that should be used
+ * @return the hash of the contents as a byte array
+ * @throws NoSuchAlgorithmException if the supplied algorithm could not be found
+ */
+ protected byte[] getHash( String digestName ) throws NoSuchAlgorithmException {
+ MessageDigest digest = MessageDigest.getInstance(digestName);
+ assert digest != null;
+ return digest.digest(bytes);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
public byte[] getBytes() {
return this.bytes;
}
Modified: trunk/dna-spi/src/main/resources/org/jboss/dna/spi/SpiI18n.properties
===================================================================
--- trunk/dna-spi/src/main/resources/org/jboss/dna/spi/SpiI18n.properties 2008-08-20
19:16:50 UTC (rev 460)
+++ trunk/dna-spi/src/main/resources/org/jboss/dna/spi/SpiI18n.properties 2008-08-22
19:32:12 UTC (rev 461)
@@ -45,6 +45,7 @@
pathExpressionIsInvalid = The path expression {0} is not valid
pathExpressionHasInvalidSelect = Invalid select expression "{0}" in the path
expression "{1}"
pathExpressionHasInvalidMatch = Invalid match expression "{0}" in the path
expression "{1}"
+messageDigestNotFound = The "{0}" message digest algorithm could not be found
executingGraphCommand = Executing {0}
executedGraphCommand = Executed {0}
Modified:
trunk/dna-spi/src/test/java/org/jboss/dna/spi/graph/impl/InMemoryBinaryTest.java
===================================================================
---
trunk/dna-spi/src/test/java/org/jboss/dna/spi/graph/impl/InMemoryBinaryTest.java 2008-08-20
19:16:50 UTC (rev 460)
+++
trunk/dna-spi/src/test/java/org/jboss/dna/spi/graph/impl/InMemoryBinaryTest.java 2008-08-22
19:32:12 UTC (rev 461)
@@ -28,6 +28,7 @@
import java.io.IOException;
import java.io.InputStream;
import org.jboss.dna.common.util.IoUtil;
+import org.jboss.dna.common.util.StringUtil;
import org.jboss.dna.spi.graph.Binary;
import org.junit.Before;
import org.junit.Test;
@@ -109,4 +110,24 @@
assertThat(another, hasContent(shorterContent));
}
+ @Test
+ public void shouldComputeSha1HashOfEmptyContent() throws Exception {
+ validByteArrayContent = new byte[0];
+ binary = new InMemoryBinary(validByteArrayContent);
+ assertThat(binary.getSize(), is(0l));
+ assertThat(binary, hasNoContent());
+ byte[] hash = binary.getHash();
+ assertThat(hash.length, is(20));
+ assertThat(StringUtil.getHexString(hash),
is("da39a3ee5e6b4b0d3255bfef95601890afd80709"));
+ }
+
+ @Test
+ public void shouldComputeSha1HashOfNonEmptyContent() throws Exception {
+ binary = new InMemoryBinary(validByteArrayContent);
+ assertThat(binary.getSize(), is((long)validByteArrayContent.length));
+ byte[] hash = binary.getHash();
+ assertThat(hash.length, is(20));
+ assertThat(StringUtil.getHexString(hash),
is("14abe696257e85ba18b7c784d6c7855f46ce50ea"));
+ }
+
}