View Javadoc
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