1 package eu.simuline.m2latex.core;
2
3 import java.io.File;
4 import java.security.MessageDigest;
5 import java.security.NoSuchAlgorithmException;
6
7 /**
8 * Provides an immutable identifier for a file
9 * consisting of the number of relevant lines lines
10 * and a hash computed from these lines.
11 * The the hash is computed with fixed algorithm {@link #ALGORITHM}.
12 * <p>
13 * The relevant parts of a file is given by an {@link Auxiliary},
14 * So, the constructor has signature {@link #FileId()}.
15 * <p>
16 * If two files coincide in their relevant lines,
17 * their {@link FileId}s coincide,
18 * i.e. they are equal according to {@link #equals(Object)}.
19 * If they don't coincide, then it is conceivable but still unlikely,
20 * that their {@link FileId}s coincide.
21 * We call this a collision.
22 * So the {@link FileId} may be used to detect file changes quite safely.
23 * <p>
24 * This class is applied in rerun file check,
25 * i.e. to trigger the run of a tool
26 * if a file changed.
27 * In case of a collision,
28 * the execution of the tool is not triggered although needed.
29 */
30 public class FileId {
31
32 /**
33 * The algorithm used to compute the hash {@link #hash}
34 * from lines of a file determined by an {@link Auxiliary}.
35 *
36 * @see #FileId()
37 */
38 private static final String ALGORITHM = "MD5";
39
40 /**
41 * The number of lines written in a file relevant for the {@link Auxiliary}
42 * initialized by the constructor {@link #FileId()}
43 * and incremented by {@link #update(String)}.
44 */
45 private int numLines;
46
47 /**
48 * The intermediate digest of a file relevant for the {@link Auxiliary}
49 * initialized by the constructor {@link #FileId()}
50 * and incremented by {@link #update(String)}.
51 * At the end, {@link #finalizFileId()} is invoked
52 * which hashes the result and writes it into {@link #hash}.
53 */
54 private MessageDigest md;
55
56 /**
57 * The hash of lines the number of which is given by {@link #numLines}
58 * computed from {@link #md}.
59 * This is initially <code>null</code>
60 * and properly initialized by {@link #finalizFileId()}.
61 */
62 private String hash;
63
64
65 FileId() {
66 try {
67 this.md = MessageDigest.getInstance(ALGORITHM);
68 } catch (NoSuchAlgorithmException nsae) {
69 throw new IllegalStateException(
70 "Algorithm " + ALGORITHM + " should be known. ");
71 }
72 this.numLines = 0;
73 this.hash = null;
74 }
75
76 void update(String line) {
77 this.numLines++;
78 this.md.update(line.getBytes());
79 }
80
81 FileId finalizFileId() {
82 this.hash = new String(md.digest());
83 return this;
84 }
85
86 /**
87 * The given object equals this one if it is an instance of {@link FileId}
88 * and both fields are equal.
89 */
90 public boolean equals(Object obj) {
91 if (!(obj instanceof FileId)) {
92 return false;
93 }
94 FileId other = (FileId) obj;
95 assert other.hash != null;
96 return this.numLines == other.numLines
97 //&& new String(this.md.digest()).equals(new String(other.md.digest()));
98 //&& Arrays.equals(this.md.digest(), other.md.digest());
99 && this.hash.equals(other.hash);
100 }
101
102 public String toString() {
103 return new String(this.hash) + " " + this.numLines;
104 //return this.hash + " " + this.numLines;
105 }
106 }
107