001    /*--------------------------------------------------------------------------+
002    $Id: MD5Digest.java 26722 2010-03-15 08:00:59Z heineman $
003    |                                                                          |
004    | Copyright 2005-2010 Technische Universitaet Muenchen                     |
005    |                                                                          |
006    | Licensed under the Apache License, Version 2.0 (the "License");          |
007    | you may not use this file except in compliance with the License.         |
008    | You may obtain a copy of the License at                                  |
009    |                                                                          |
010    |    http://www.apache.org/licenses/LICENSE-2.0                            |
011    |                                                                          |
012    | Unless required by applicable law or agreed to in writing, software      |
013    | distributed under the License is distributed on an "AS IS" BASIS,        |
014    | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
015    | See the License for the specific language governing permissions and      |
016    | limitations under the License.                                           |
017    +--------------------------------------------------------------------------*/
018    package edu.tum.cs.commons.digest;
019    
020    import java.io.IOException;
021    import java.io.ObjectInputStream;
022    import java.io.ObjectOutputStream;
023    import java.io.Serializable;
024    import java.security.MessageDigest;
025    import java.util.Arrays;
026    
027    import edu.tum.cs.commons.assertion.CCSMPre;
028    import edu.tum.cs.commons.string.StringUtils;
029    
030    /**
031     * An MD5 digest. This is just a thin thin wrapper around a byte array with some
032     * convenience methods and {@link #hashCode()}, {@link #equals(Object)} and
033     * {@link #compareTo(MD5Digest)} implemented correctly. This class is used
034     * instead of plain strings to save both memory and (some) execution time. The
035     * class is immutable. Custom (de)serialization is provided to make this
036     * efficient to use in storage or RMI senarios.
037     * 
038     * @author hummelb
039     * @author $Author: heineman $
040     * @version $Rev: 26722 $
041     * @levd.rating GREEN Hash: 1F3799422871C3DC0A88A5AF75FE6BCD
042     */
043    public final class MD5Digest implements Serializable, Comparable<MD5Digest> {
044    
045            /** The digest. */
046            private byte[] digest;
047    
048            /** Number of bytes in an MD5 sum. */
049            public static final int MD5_BYTES = 16;
050    
051            /**
052             * Constructor. This calls {@link MessageDigest#digest()}, so the digester
053             * will be reset afterwards.
054             */
055            public MD5Digest(MessageDigest digester) {
056                    digest = digester.digest();
057                    CCSMPre.isTrue(digest.length == MD5_BYTES,
058                                    "Invalid digester used; not MD5");
059            }
060    
061            /** Constructor. */
062            public MD5Digest(byte[] digest) {
063                    CCSMPre.isTrue(digest.length == MD5_BYTES,
064                                    "Invalid size of MD5 digest!");
065                    this.digest = digest.clone();
066            }
067    
068            /**
069             * Inserts the digest data into the given MD. This method is used to rehash
070             * multiple hashes.
071             * <p>
072             * This method is provided instead of a getter, to keep this immutable.
073             */
074            public void insertIntoDigester(MessageDigest md) {
075                    md.update(digest);
076            }
077    
078            /** {@inheritDoc} */
079            @Override
080            public int hashCode() {
081                    return Arrays.hashCode(digest);
082            }
083    
084            /**
085             * Calculates and returns a hashcode that only depends on the first 3 bytes.
086             */
087            public int partialHashCode() {
088                    return digest[0] | (digest[1] << 8) | (digest[2] << 16);
089            }
090    
091            /** {@inheritDoc} */
092            @Override
093            public boolean equals(Object o) {
094                    if (!(o instanceof MD5Digest)) {
095                            return false;
096                    }
097                    return Arrays.equals(digest, ((MD5Digest) o).digest);
098            }
099    
100            /** {@inheritDoc} */
101            public int compareTo(MD5Digest o) {
102                    if (o == null) {
103                            return -1;
104                    }
105    
106                    int lengthDelta = digest.length - o.digest.length;
107                    if (lengthDelta != 0) {
108                            return lengthDelta;
109                    }
110    
111                    for (int i = 0; i < digest.length; ++i) {
112                            int delta = digest[i] - o.digest[i];
113                            if (delta != 0) {
114                                    return delta;
115                            }
116                    }
117    
118                    return 0;
119            }
120    
121            /** Returns a copy of the internal byte representation. */
122            public byte[] getBytes() {
123                    return digest.clone();
124            }
125    
126            /** {@inheritDoc} */
127            @Override
128            public String toString() {
129                    return StringUtils.encodeAsHex(digest);
130            }
131    
132            /** Custom serialization for MD5 hashes. */
133            private void writeObject(ObjectOutputStream out) throws IOException {
134                    out.writeByte(digest.length);
135                    out.write(digest);
136            }
137    
138            /** Custom deserialization for MD5 hashes. */
139            private void readObject(ObjectInputStream in) throws IOException {
140                    int size = in.readByte();
141                    digest = new byte[size];
142                    int pos = 0;
143                    while (pos < size) {
144                            pos += in.read(digest, pos, size - pos);
145                    }
146            }
147    
148            /** Comparator for {@link MD5Digest}. */
149            public static class Comparator implements java.util.Comparator<MD5Digest>,
150                            Serializable {
151    
152                    /** {@inheritDoc} */
153                    @Override
154                    public int compare(MD5Digest o1, MD5Digest o2) {
155                            return o1.compareTo(o2);
156                    }
157            }
158    }