2
* This file is a part of Java Bindings for Librhash
3
* Copyright (c) 2011-2012, Sergey Basalaev <sbasalaev@gmail.com>
4
* Librhash is (c) 2011-2012, Aleksey Kravchenko <rhash.admin@gmail.com>
6
* Permission is hereby granted, free of charge, to any person obtaining a copy
7
* of this software and associated documentation files (the "Software"), to deal
8
* in the Software without restriction, including without limitation the rights
9
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
* copies of the Software, and to permit persons to whom the Software is
13
* This library is distributed in the hope that it will be useful, but WITHOUT
14
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15
* FOR A PARTICULAR PURPOSE. Use it at your own risk!
21
import java.io.FileInputStream;
22
import java.io.IOException;
23
import java.io.InputStream;
24
import java.io.UnsupportedEncodingException;
29
* This class allows you to do incremental hashing for set
30
* of hashing algorithms.
32
* To do hashing <code>RHash</code> instance is first created
33
* and then filled with message chunks using <code>update()</code>
34
* methods. Finally, <code>finish()</code> should be called to end
35
* all calculations and generate digests, which then can be obtained
36
* with <code>getDigest()</code> method. Note, that trying to update
37
* finished <code>RHash</code> has no effect other than throwing
38
* <code>IllegalStateException</code> though you can reuse this class
39
* by calling <code>reset()</code> method, returning it to the state
40
* which was immediately after creating.
42
* To quickly produce message digest for a single message/file
43
* and a single algorithm you may use convenience methods
44
* <code>RHash.computeHash()</code>.
46
* This class is thread safe.
49
public final class RHash {
51
/* == EXCEPTION MESSAGES == */
53
static private final String ERR_FINISHED = "RHash is finished, data update is not possible";
54
static private final String ERR_NOHASH = "No HashTypes specified";
55
static private final String ERR_UNFINISHED = "RHash should be finished before generating Digest";
56
static private final String ERR_WRONGTYPE = "RHash was not created to generate Digest for ";
59
* Computes hash of given range in data.
60
* This method calculates message digest for byte subsequence
61
* in array <code>data</code> starting from <code>data[ofs]</code>
62
* and ending at <code>data[ofs+len-1]</code>.
64
* @param type type of hash algorithm
65
* @param data the bytes to process
66
* @param ofs index of the first byte in array to process
67
* @param len count of bytes to process
68
* @return message digest for specified subarray
69
* @throws NullPointerException
70
* if either <code>type</code> or <code>data</code>
71
* is <code>null</code>
72
* @throws IndexOutOfBoundsException
73
* if <code>ofs < 0</code>, <code>len < 0</code> or
74
* <code>ofs+len > data.length</code>
76
static public Digest computeHash(HashType type, byte[] data, int ofs, int len) {
77
if (type == null || data == null) {
78
throw new NullPointerException();
80
if (ofs < 0 || len < 0 || ofs+len > data.length) {
81
throw new IndexOutOfBoundsException();
83
return new Digest(Bindings.rhash_msg(type.hashId(), data, ofs, len), type);
87
* Computes hash of given data.
89
* @param type type of hash algorithm
90
* @param data the bytes to process
91
* @return message digest for specified array
92
* @throws NullPointerException
93
* if either <code>type</code> or <code>data</code>
94
* is <code>null</code>
96
static public Digest computeHash(HashType type, byte[] data) {
97
return computeHash(type, data, 0, data.length);
101
* Computes hash of given string.
102
* String is encoded into a sequence of bytes
103
* using the specified charset.
105
* @param type type of hash algorithm
106
* @param str the string to process
107
* @param encoding encoding to use
108
* @return message digest for specified string
109
* @throws NullPointerException if any of arguments is <code>null</code>
110
* @throws UnsupportedEncodingException if specified encoding is not supported
112
static public Digest computeHash(HashType type, String str, String encoding)
113
throws UnsupportedEncodingException {
114
if (type == null || str == null || encoding == null) {
115
throw new NullPointerException();
117
return computeHash(type, str.getBytes(encoding));
121
* Computes hash of given string.
122
* String is encoded into a sequence of bytes using the
123
* default platform encoding.
125
* @param type type of hash algorithm
126
* @param str the string to process
127
* @return message digest for specified string
128
* @throws NullPointerException if any of arguments is <code>null</code>
130
static public Digest computeHash(HashType type, String str) {
131
if (type == null || str == null) throw new NullPointerException();
132
return computeHash(type, str.getBytes());
136
* Computes hash of given string.
137
* @param type type of hash algorithm
138
* @param file the file to process
140
* @throws NullPointerException if any of arguments is <code>null</code>
141
* @throws IOException
142
* if an I/O error occurs while hashing
144
static public Digest computeHash(HashType type, File file) throws IOException {
145
if (type == null || file == null) {
146
throw new NullPointerException();
148
RHash hasher = new RHash(type);
149
hasher.update(file).finish();
150
return hasher.getDigest();
154
* Produces magnet link for specified file with given hashes.
156
* @param filename the file to generate magnet for
157
* @param types types of hashing algorithms
159
static public String getMagnetFor(String filename, HashType... types) throws IOException {
160
RHash hasher = new RHash(types);
161
hasher.update(new File(filename)).finish();
162
return hasher.getMagnet(filename);
166
* Produces magnet link for specified file with given hashes.
168
* @param filename the file to generate magnet for
169
* @param types set of hashing types
171
static public String getMagnetFor(String filename, Set<HashType> types) throws IOException {
172
RHash hasher = new RHash(types);
173
hasher.update(new File(filename)).finish();
174
return hasher.getMagnet(filename);
177
/** Indicates whether this <code>RHash</code> is finished. */
178
private boolean finished = false;
180
/** Mask of hash_id values. */
181
private final int hash_flags;
183
/** Pointer to the native hash context. */
184
private final long context_ptr;
186
/** Default hash type used in <code>getDigest()</code>. */
187
private final HashType deftype;
190
* Creates new <code>RHash</code> to compute
191
* message digests for given types.
192
* @param types types of hashing algorithms
193
* @throws NullPointerException
194
* if any of arguments is <code>null</code>
195
* @throws IllegalArgumentException
196
* if zero hash types specified
198
public RHash(HashType... types) {
199
if (types.length == 0) {
200
throw new IllegalArgumentException(ERR_NOHASH);
203
HashType def = types[0];
204
for (HashType t : types) {
206
if (def.compareTo(t) > 0) def = t;
209
this.hash_flags = flags;
210
this.context_ptr = Bindings.rhash_init(flags);
214
* Creates new <code>RHash</code> to compute
215
* message digests for given types.
216
* @param types set of hashing types
217
* @throws NullPointerException
218
* if argument is <code>null</code>
219
* @throws IllegalArgumentException
220
* if argument is empty set
222
public RHash(Set<HashType> types) {
223
if (types.isEmpty()) {
224
throw new IllegalArgumentException(ERR_NOHASH);
228
for (HashType t : types) {
230
if (def == null || def.compareTo(t) > 0) def = t;
233
this.hash_flags = flags;
234
this.context_ptr = Bindings.rhash_init(flags);
238
* Updates this <code>RHash</code> with new data chunk.
239
* This method hashes bytes from <code>data[ofs]</code>
240
* through <code>data[ofs+len-1]</code>.
242
* @param data data to be hashed
243
* @param ofs index of the first byte to hash
244
* @param len number of bytes to hash
245
* @return this object
246
* @throws NullPointerException
247
* if <code>data</code> is <code>null</code>
248
* @throws IndexOutOfBoundsException
249
* if <code>ofs < 0</code>, <code>len < 0</code> or
250
* <code>ofs+len > data.length</code>
251
* @throws IllegalStateException
252
* if <code>finish()</code> was called and there were no
253
* subsequent calls of <code>reset()</code>
255
public synchronized RHash update(byte[] data, int ofs, int len) {
257
throw new IllegalStateException(ERR_FINISHED);
259
if (ofs < 0 || len < 0 || ofs+len > data.length) {
260
throw new IndexOutOfBoundsException();
262
Bindings.rhash_update(context_ptr, data, ofs, len);
267
* Updates this <code>RHash</code> with new data chunk.
268
* This method has the same effect as
269
* <pre>update(data, 0, data.length)</pre>
271
* @param data data to be hashed
272
* @return this object
273
* @throws NullPointerException
274
* if <code>data</code> is <code>null</code>
275
* @throws IllegalStateException
276
* if <code>finish()</code> was called and there were no
277
* subsequent calls of <code>reset()</code>
279
public RHash update(byte[] data) {
280
return update(data, 0, data.length);
284
* Updates this <code>RHash</code> with new data chunk.
285
* String is encoded into a sequence of bytes using the
286
* default platform encoding.
288
* @param str string to be hashed
289
* @return this object
290
* @throws NullPointerException
291
* if <code>str</code> is <code>null</code>
292
* @throws IllegalStateException
293
* if <code>finish()</code> was called and there were no
294
* subsequent calls of <code>reset()</code>
296
public RHash update(String str) {
297
return update(str.getBytes());
301
* Updates this <code>RHash</code> with data from given file.
303
* @param file file to be hashed
304
* @return this object
305
* @throws IOException if an I/O error occurs
306
* @throws NullPointerException
307
* if <code>file</code> is <code>null</code>
308
* @throws IllegalStateException
309
* if <code>finish()</code> was called and there were no
310
* subsequent calls of <code>reset()</code>
312
public synchronized RHash update(File file) throws IOException {
314
throw new IllegalStateException(ERR_FINISHED);
316
InputStream in = new FileInputStream(file);
317
byte[] buf = new byte[8192]; //shouldn't we avoid magic numbers?
318
int len = in.read(buf);
320
this.update(buf, 0, len);
328
* Finishes calculation of hash codes.
329
* Does nothing if <code>RHash</code> is already finished.
331
public synchronized void finish() {
333
Bindings.rhash_final(context_ptr);
339
* Resets this <code>RHash</code> to initial state.
340
* The <code>RHash</code> becomes available to process
341
* new data chunks. Note, that this method returns
342
* <code>RHash</code> to the state after creating the
343
* object, NOT the state when hashing continues.
344
* Therefore, all previously calculated hashes are lost
345
* and process starts from the very beginning.
347
public synchronized void reset() {
348
Bindings.rhash_reset(context_ptr);
353
* Tests whether this <code>RHash</code> is finished or not.
355
* <code>false</code> if this <code>RHash</code> is ready to
356
* receive new data for hashing;
357
* <code>true</code> if hash calculations are finished
359
public boolean isFinished() {
364
* Returns digest for given hash type.
366
* @param type hash type
367
* @return <code>Digest</code> for processed data
368
* @throws NullPointerException
369
* if <code>type</code> is <code>null</code>
370
* @throws IllegalStateException
371
* if this <code>RHash</code> is not finished
372
* @throws IllegalArgumentException
373
* if this <code>RHash</code> was not created to calculate
374
* hash for specified algorithm
376
public Digest getDigest(HashType type) {
378
throw new NullPointerException();
381
throw new IllegalStateException(ERR_UNFINISHED);
383
if ((hash_flags & type.hashId()) == 0) {
384
throw new IllegalArgumentException(ERR_WRONGTYPE+type);
386
return new Digest(Bindings.rhash_print(context_ptr, type.hashId()), type);
390
* Returns digest for processed data.
391
* If more than one hashing type was passed to the
392
* <code>RHash</code> constructor, then the least
393
* hash type (in the order induced by
394
* {@link Enum#compareTo(Enum) compareTo()}) is used.
396
* @return <code>Digest</code> for processed data
397
* @throws IllegalStateException
398
* if this <code>RHash</code> is not finished
400
public Digest getDigest() {
401
return getDigest(deftype);
405
* Returns magnet link that includes specified filename
406
* and hashes for given algorithms. Only hashes that were
407
* computed by this <code>RHash</code> are included.
409
* @param filename file name to include in magnet, may be <code>null</code>
410
* @return magnet link
411
* @throws IllegalStateException
412
* if this <code>RHash</code> is not finished
414
public String getMagnet(String filename, HashType... types) {
416
throw new IllegalStateException(ERR_UNFINISHED);
419
for (HashType t : types) {
422
return Bindings.rhash_print_magnet(context_ptr, filename, flags);
426
* Returns magnet link for given filename.
427
* Magnet includes all hashes that were computed
428
* by this <code>RHash</code>.
430
* @param filename file name to include in magnet, may be <code>null</code>
431
* @return magnet link
432
* @throws IllegalStateException
433
* if this <code>RHash</code> is not finished
435
public String getMagnet(String filename) {
437
throw new IllegalStateException(ERR_UNFINISHED);
439
return Bindings.rhash_print_magnet(context_ptr, filename, hash_flags);
443
* Called by garbage collector to free native resources.
446
protected void finalize() {
447
Bindings.rhash_free(context_ptr);