View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.codec.digest;
19  
20  import static java.lang.Integer.rotateLeft;
21  
22  import java.util.zip.Checksum;
23  
24  /**
25   * Implements the xxHash32 hash algorithm.
26   *
27   * <p>
28   * Copied from Commons Compress 1.14 <a href=
29   * "https://gitbox.apache.org/repos/asf?p=commons-compress.git;a=blob;f=src/main/java/org/apache/commons/compress/compressors/lz4/XXHash32.java;h=a406ffc197449be594d46f0d2712b2d4786a1e68;hb=HEAD">https://gitbox.apache.org/repos/asf?p=commons-compress.git;a=blob;f=src/main/java/org/apache/commons/compress/compressors/lz4/XXHash32.java;h=a406ffc197449be594d46f0d2712b2d4786a1e68;hb=HEAD</a>
30   * </p>
31   * <p>
32   * NotThreadSafe
33   * </p>
34   *
35   * @see <a href="https://cyan4973.github.io/xxHash/">xxHash</a>
36   * @since 1.11
37   */
38  public class XXHash32 implements Checksum {
39  
40      private static final int BUF_SIZE = 16;
41      private static final int ROTATE_BITS = 13;
42  
43      private static final int PRIME1 = (int) 2654435761L;
44      private static final int PRIME2 = (int) 2246822519L;
45      private static final int PRIME3 = (int) 3266489917L;
46      private static final int PRIME4 =  668265263;
47      private static final int PRIME5 =  374761393;
48  
49      /**
50       * Gets the little-endian int from 4 bytes starting at the specified index.
51       *
52       * @param buffer The data.
53       * @param idx The index.
54       * @return The little-endian int.
55       */
56      private static int getInt(final byte[] buffer, final int idx) {
57          return buffer[idx    ] & 0xff |
58                 (buffer[idx + 1] & 0xff) <<  8 |
59                 (buffer[idx + 2] & 0xff) << 16 |
60                 (buffer[idx + 3] & 0xff) << 24;
61      }
62      private final byte[] oneByte = new byte[1];
63      private final int[] state = new int[4];
64      // Note: The code used to use ByteBuffer but the manual method is 50% faster
65      // See: https://gitbox.apache.org/repos/asf/commons-compress/diff/2f56fb5c
66      private final byte[] buffer = new byte[BUF_SIZE];
67  
68      private final int seed;
69      private int totalLen;
70  
71      private int pos;
72  
73      /** Sets to true when the state array has been updated since the last reset. */
74      private boolean stateUpdated;
75  
76      /**
77       * Creates an XXHash32 instance with a seed of 0.
78       */
79      public XXHash32() {
80          this(0);
81      }
82  
83      /**
84       * Creates an XXHash32 instance.
85       *
86       * @param seed the seed to use
87       */
88      public XXHash32(final int seed) {
89          this.seed = seed;
90          initializeState();
91      }
92  
93      @Override
94      public long getValue() {
95          int hash;
96          if (stateUpdated) {
97              // Hash with the state
98              hash =
99                  rotateLeft(state[0],  1) +
100                 rotateLeft(state[1],  7) +
101                 rotateLeft(state[2], 12) +
102                 rotateLeft(state[3], 18);
103         } else {
104             // Hash using the original seed from position 2
105             hash = state[2] + PRIME5;
106         }
107         hash += totalLen;
108         int idx = 0;
109         final int limit = pos - 4;
110         for (; idx <= limit; idx += 4) {
111             hash = rotateLeft(hash + getInt(buffer, idx) * PRIME3, 17) * PRIME4;
112         }
113         while (idx < pos) {
114             hash = rotateLeft(hash + (buffer[idx++] & 0xff) * PRIME5, 11) * PRIME1;
115         }
116         hash ^= hash >>> 15;
117         hash *= PRIME2;
118         hash ^= hash >>> 13;
119         hash *= PRIME3;
120         hash ^= hash >>> 16;
121         return hash & 0xffffffffL;
122     }
123 
124     private void initializeState() {
125         state[0] = seed + PRIME1 + PRIME2;
126         state[1] = seed + PRIME2;
127         state[2] = seed;
128         state[3] = seed - PRIME1;
129     }
130 
131     private void process(final byte[] b, final int offset) {
132         // local shadows for performance
133         int s0 = state[0];
134         int s1 = state[1];
135         int s2 = state[2];
136         int s3 = state[3];
137         s0 = rotateLeft(s0 + getInt(b, offset) * PRIME2, ROTATE_BITS) * PRIME1;
138         s1 = rotateLeft(s1 + getInt(b, offset + 4) * PRIME2, ROTATE_BITS) * PRIME1;
139         s2 = rotateLeft(s2 + getInt(b, offset + 8) * PRIME2, ROTATE_BITS) * PRIME1;
140         s3 = rotateLeft(s3 + getInt(b, offset + 12) * PRIME2, ROTATE_BITS) * PRIME1;
141         state[0] = s0;
142         state[1] = s1;
143         state[2] = s2;
144         state[3] = s3;
145         stateUpdated = true;
146     }
147 
148     @Override
149     public void reset() {
150         initializeState();
151         totalLen = 0;
152         pos = 0;
153         stateUpdated = false;
154     }
155 
156     @Override
157     public void update(final byte[] b, int off, final int len) {
158         if (len <= 0) {
159             return;
160         }
161         totalLen += len;
162         final int end = off + len;
163         // Check if the unprocessed bytes and new bytes can fill a block of 16.
164         // Make this overflow safe in the event that len is Integer.MAX_VALUE.
165         // Equivalent to: (pos + len < BUF_SIZE)
166         if (pos + len - BUF_SIZE < 0) {
167             System.arraycopy(b, off, buffer, pos, len);
168             pos += len;
169             return;
170         }
171         // Process left-over bytes with new bytes
172         if (pos > 0) {
173             final int size = BUF_SIZE - pos;
174             System.arraycopy(b, off, buffer, pos, size);
175             process(buffer, 0);
176             off += size;
177         }
178         final int limit = end - BUF_SIZE;
179         while (off <= limit) {
180             process(b, off);
181             off += BUF_SIZE;
182         }
183         // Handle left-over bytes
184         if (off < end) {
185             pos = end - off;
186             System.arraycopy(b, off, buffer, 0, pos);
187         } else {
188             pos = 0;
189         }
190     }
191 
192     @Override
193     public void update(final int b) {
194         oneByte[0] = (byte) (b & 0xff);
195         update(oneByte, 0, 1);
196     }
197 }