21
21
22
22
import java .io .File ;
23
23
import java .io .IOException ;
24
+ import java .io .InputStream ;
24
25
import java .io .Serializable ;
25
26
import java .nio .file .Files ;
27
+ import java .nio .file .Path ;
28
+ import java .nio .file .Paths ;
26
29
import java .security .MessageDigest ;
27
- import java .security .NoSuchAlgorithmException ;
28
30
import java .util .Arrays ;
29
31
import java .util .Collection ;
30
32
import java .util .Collections ;
33
+ import java .util .HashMap ;
31
34
import java .util .List ;
35
+ import java .util .Map ;
32
36
33
37
import edu .umd .cs .findbugs .annotations .SuppressFBWarnings ;
34
38
35
39
/** Computes a signature for any needed files. */
36
40
public final class FileSignature implements Serializable {
37
- private static final long serialVersionUID = 1L ;
41
+ private static final long serialVersionUID = 2L ;
38
42
39
43
/*
40
44
* Transient because not needed to uniquely identify a FileSignature instance, and also because
@@ -43,10 +47,7 @@ public final class FileSignature implements Serializable {
43
47
*/
44
48
@ SuppressFBWarnings ("SE_TRANSIENT_FIELD_NOT_RESTORED" )
45
49
private final transient List <File > files ;
46
-
47
- private final String [] filenames ;
48
- private final long [] filesizes ;
49
- private final byte [][] fileHashes ;
50
+ private final Sig [] signatures ;
50
51
51
52
/** Method has been renamed to {@link FileSignature#signAsSet}.
52
53
* In case no sorting and removal of duplicates is required,
@@ -86,16 +87,11 @@ public static FileSignature signAsSet(Iterable<File> files) throws IOException {
86
87
87
88
private FileSignature (final List <File > files ) throws IOException {
88
89
this .files = validateInputFiles (files );
89
-
90
- filenames = new String [this .files .size ()];
91
- filesizes = new long [this .files .size ()];
92
- fileHashes = new byte [this .files .size ()][];
90
+ this .signatures = new Sig [this .files .size ()];
93
91
94
92
int i = 0 ;
95
93
for (File file : this .files ) {
96
- filenames [i ] = file .getName ();
97
- filesizes [i ] = file .length ();
98
- fileHashes [i ] = hash (file );
94
+ signatures [i ] = cache .sign (file );
99
95
++i ;
100
96
}
101
97
}
@@ -135,15 +131,58 @@ private static List<File> validateInputFiles(List<File> files) {
135
131
return files ;
136
132
}
137
133
138
- private static byte [] hash (File file ) throws IOException {
139
- MessageDigest messageDigest ;
140
- try {
141
- messageDigest = MessageDigest .getInstance ("SHA-256" );
142
- } catch (NoSuchAlgorithmException e ) {
143
- throw new IllegalStateException ("SHA-256 digest algorithm not available" , e );
134
+ /**
135
+ * It is very common for a given set of files to be "signed" many times. For example,
136
+ * the jars which constitute any given formatter live in a central cache, but will be signed
137
+ * over and over. To save this I/O, we maintain a cache, invalidated by lastModified time.
138
+ */
139
+ static final Cache cache = new Cache ();
140
+
141
+ static class Cache {
142
+ Map <Key , Sig > cache = new HashMap <>();
143
+
144
+ synchronized Sig sign (File file ) throws IOException {
145
+ String canonicalPath = file .getCanonicalPath ();
146
+ long lastModified = file .lastModified ();
147
+ return cache .computeIfAbsent (new Key (canonicalPath , lastModified ), ThrowingEx .<Key , Sig > wrap (key -> {
148
+ MessageDigest digest = MessageDigest .getInstance ("SHA-256" );
149
+ Path path = Paths .get (key .canonicalPath );
150
+ // calculate the size and content hash of the file
151
+ long size = 0 ;
152
+ byte [] data = Files .readAllBytes (path );
153
+ try (InputStream input = Files .newInputStream (path )) {
154
+ int numRead = input .read (data );
155
+ while (numRead != -1 ) {
156
+ size += numRead ;
157
+ digest .update (data , 0 , numRead );
158
+ }
159
+ }
160
+ return new Sig (path .getFileName ().toString (), size , digest .digest ());
161
+ }));
162
+ }
163
+ }
164
+
165
+ static class Key {
166
+ final String canonicalPath ;
167
+ final long lastModified ;
168
+
169
+ public Key (String canonicalPath , long lastModified ) {
170
+ this .canonicalPath = canonicalPath ;
171
+ this .lastModified = lastModified ;
172
+ }
173
+ }
174
+
175
+ static class Sig implements Serializable {
176
+ private static final long serialVersionUID = 4375346948593472485L ;
177
+
178
+ final String name ;
179
+ final long size ;
180
+ final byte [] hash ;
181
+
182
+ Sig (String name , long size , byte [] hash ) {
183
+ this .name = name ;
184
+ this .size = size ;
185
+ this .hash = hash ;
144
186
}
145
- byte [] fileContent = Files .readAllBytes (file .toPath ());
146
- messageDigest .update (fileContent );
147
- return messageDigest .digest ();
148
187
}
149
188
}
0 commit comments