@@ -267,7 +267,7 @@ static private Hasher hashFile( Hasher hasher, Path path, HashMode mode, Path ba
267
267
268
268
if ( (mode ==HashMode .STANDARD || mode ==HashMode .LENIENT ) && isAssetFile (path ) ) {
269
269
if ( attrs ==null ) {
270
- // when file attributes are not avail or it's a directory
270
+ // when file attributes are not avail, or it's a directory
271
271
// hash the file using the file name path and the repository
272
272
log .warn ("Unable to fetch attribute for file: {} - Hash is inferred from Git repository commit Id" , FilesEx .toUriString (path ));
273
273
return hashFileAsset (hasher , path );
@@ -322,18 +322,36 @@ static protected Hasher hashFileSha256( Hasher hasher, Path path, Path base ) {
322
322
return hasher ;
323
323
}
324
324
325
+ /**
326
+ * Compute an, order independent, hash of a directory path traversing recursively the directory content.
327
+ *
328
+ * @param hasher
329
+ * The {@link Hasher} object to which the resulting directory hash will be added.
330
+ * @param dir
331
+ * The target directory path to be hashed.
332
+ * @param base
333
+ * The "base" directory path against which resolve relative paths.
334
+ * @return
335
+ * The resulting {@link Hasher} object updated with the directory path.
336
+ */
325
337
static protected Hasher hashDirSha256 ( Hasher hasher , Path dir , Path base ) {
338
+ if ( base ==null )
339
+ throw new IllegalArgumentException ("Argument 'base' cannot be null" );
340
+ // the byte array used as "accumulator" for
341
+ final byte [] resultBytes = new byte [HASH_BYTES ];
326
342
try {
327
343
Files .walkFileTree (dir , new SimpleFileVisitor <Path >() {
328
344
public FileVisitResult visitFile (Path path , BasicFileAttributes attrs ) throws IOException {
329
345
log .trace ("Hash sha-256 dir content [FILE] path={} - base={}" , path , base );
330
346
try {
331
347
// the file relative base
332
- if ( base !=null )
333
- hasher .putUnencodedChars (base .relativize (path ).toString ());
348
+ final String relPath = base .relativize (path ).toString ();
349
+ // compute the file path hash and sum to the result hash
350
+ // since the sum is commutative, the traverse order does not matter
351
+ sumBytes (resultBytes , hashBytes (relPath , HashMode .STANDARD ));
334
352
// the file content sha-256 checksum
335
- String sha256 = sha256Cache .get (path );
336
- hasher . putUnencodedChars ( sha256 );
353
+ final String sha256 = sha256Cache .get (path );
354
+ sumBytes ( resultBytes , hashBytes ( sha256 , HashMode . STANDARD ) );
337
355
return FileVisitResult .CONTINUE ;
338
356
}
339
357
catch (ExecutionException t ) {
@@ -344,12 +362,15 @@ public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) throws IO
344
362
public FileVisitResult preVisitDirectory (Path path , BasicFileAttributes attrs ) {
345
363
log .trace ("Hash sha-256 dir content [DIR] path={} - base={}" , path , base );
346
364
// the file relative base
347
- if ( base !=null )
348
- hasher .putUnencodedChars (base .relativize (path ).toString ());
349
- hasher .putUnencodedChars (base .relativize (path ).toString ());
365
+ final String relPath = base .relativize (path ).toString ();
366
+ // compute the file path hash and sum to the result hash
367
+ // since the sum is commutative, the traverse order does not matter
368
+ sumBytes (resultBytes , hashBytes (relPath , HashMode .STANDARD ));
350
369
return FileVisitResult .CONTINUE ;
351
370
}
352
371
});
372
+ // finally put the result bytes in the hashing
373
+ hasher .putBytes (resultBytes );
353
374
}
354
375
catch (IOException t ) {
355
376
Throwable err = t .getCause ()!=null ? t .getCause () : t ;
@@ -441,21 +462,48 @@ static HashCode hashContent( Path file, HashFunction function ) {
441
462
}
442
463
443
464
static private Hasher hashUnorderedCollection (Hasher hasher , Collection collection , HashMode mode ) {
444
-
445
465
byte [] resultBytes = new byte [HASH_BYTES ];
446
466
for (Object item : collection ) {
447
- byte [] nextBytes = HashBuilder .hasher (defaultHasher (), item , mode ).hash ().asBytes ();
448
- if ( nextBytes .length != resultBytes .length )
449
- throw new IllegalStateException ("All hash codes must have the same bit length" );
450
-
451
- for (int i = 0 ; i < nextBytes .length ; i ++) {
452
- resultBytes [i ] += nextBytes [i ];
453
- }
467
+ // hash ghe collection item
468
+ byte [] nextBytes = hashBytes (item , mode );
469
+ // sum the hash bytes to the "resultBytes" accumulator
470
+ // since the sum is a commutative operation the order does not matter
471
+ sumBytes (resultBytes , nextBytes );
454
472
}
455
-
473
+ // add the result bytes and return the resulting object
456
474
return hasher .putBytes (resultBytes );
457
475
}
458
476
477
+ static private byte [] hashBytes (Object item , HashMode mode ) {
478
+ return hasher (defaultHasher (), item , mode ).hash ().asBytes ();
479
+ }
480
+
481
+ /**
482
+ * Sum two arras of bytes having the same length, required to compute hash of unordered collections.
483
+ *
484
+ * - For each byte position, add the corresponding byte from nextBytes into resultBytes
485
+ * - Order doesn't matter: addition is commutative (a + b = b + a), so the final result is
486
+ * the same no matter the order of items.
487
+ * - This is what makes it suitable for unordered collections
488
+ *
489
+ * @param resultBytes
490
+ * The first argument to be summed. This array is used as the accumulator array (i.e. the result)
491
+ * @param nextBytes
492
+ * The second argument to be summed.
493
+ * @return
494
+ * The array resulting adding the bytes in the second array to the first one. Note,
495
+ * the result array instance is the same object passed as first argument.
496
+ *
497
+ */
498
+ static private byte [] sumBytes (byte [] resultBytes , byte [] nextBytes ) {
499
+ if ( nextBytes .length != resultBytes .length )
500
+ throw new IllegalStateException ("All hash codes must have the same bit length" );
501
+ for (int i = 0 ; i < nextBytes .length ; i ++) {
502
+ resultBytes [i ] += nextBytes [i ];
503
+ }
504
+ return resultBytes ;
505
+ }
506
+
459
507
/**
460
508
* Check if the argument is an asset file i.e. a file that makes part of the
461
509
* pipeline Git repository
0 commit comments