Skip to content

Commit e4db705

Browse files
committed
Use a central cache for the FileSignature, so that we keep the local performance based on lastModified that we had prior to this PR.
1 parent 823c2c3 commit e4db705

File tree

1 file changed

+61
-22
lines changed

1 file changed

+61
-22
lines changed

lib/src/main/java/com/diffplug/spotless/FileSignature.java

+61-22
Original file line numberDiff line numberDiff line change
@@ -21,20 +21,24 @@
2121

2222
import java.io.File;
2323
import java.io.IOException;
24+
import java.io.InputStream;
2425
import java.io.Serializable;
2526
import java.nio.file.Files;
27+
import java.nio.file.Path;
28+
import java.nio.file.Paths;
2629
import java.security.MessageDigest;
27-
import java.security.NoSuchAlgorithmException;
2830
import java.util.Arrays;
2931
import java.util.Collection;
3032
import java.util.Collections;
33+
import java.util.HashMap;
3134
import java.util.List;
35+
import java.util.Map;
3236

3337
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
3438

3539
/** Computes a signature for any needed files. */
3640
public final class FileSignature implements Serializable {
37-
private static final long serialVersionUID = 1L;
41+
private static final long serialVersionUID = 2L;
3842

3943
/*
4044
* Transient because not needed to uniquely identify a FileSignature instance, and also because
@@ -43,10 +47,7 @@ public final class FileSignature implements Serializable {
4347
*/
4448
@SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED")
4549
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;
5051

5152
/** Method has been renamed to {@link FileSignature#signAsSet}.
5253
* In case no sorting and removal of duplicates is required,
@@ -86,16 +87,11 @@ public static FileSignature signAsSet(Iterable<File> files) throws IOException {
8687

8788
private FileSignature(final List<File> files) throws IOException {
8889
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()];
9391

9492
int i = 0;
9593
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);
9995
++i;
10096
}
10197
}
@@ -135,15 +131,58 @@ private static List<File> validateInputFiles(List<File> files) {
135131
return files;
136132
}
137133

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;
144186
}
145-
byte[] fileContent = Files.readAllBytes(file.toPath());
146-
messageDigest.update(fileContent);
147-
return messageDigest.digest();
148187
}
149188
}

0 commit comments

Comments
 (0)