View Javadoc

1   /*
2    * LICENSE
3    *
4    * "THE BEER-WARE LICENSE" (Revision 42):
5    * "Sven Strittmatter" <ich@weltraumschaf.de> wrote this file.
6    * As long as you retain this notice you can do whatever you want with
7    * this stuff. If we meet some day, and you think this stuff is worth it,
8    * you can buy me a beer in return.
9    */
10  package org.jenkinsci.plugins.darcs;
11  
12  import hudson.scm.ChangeLogSet;
13  import hudson.model.AbstractBuild;
14  
15  import java.util.ArrayList;
16  import java.util.Iterator;
17  import java.util.List;
18  import java.util.Collections;
19  import java.util.Comparator;
20  
21  import java.security.MessageDigest;
22  import java.security.NoSuchAlgorithmException;
23  
24  /**
25   * List of change set that went into a particular build.
26   *
27   * @author Sven Strittmatter <ich@weltraumschaf.de>
28   */
29  public class DarcsChangeSetList extends ChangeLogSet<DarcsChangeSet> {
30  
31      /**
32       * Kind description string.
33       */
34      private static final String KIND = "darcs";
35      /**
36       * Used to mask bytes.
37       */
38      private static final int BYTE_MASK = 0xFF;
39  
40      /**
41       * Set of the changes.
42       */
43      private final List<DarcsChangeSet> changeSets;
44      /**
45       * Lazy computed digest over all change set hashes.
46       */
47      private String digest;
48  
49      /**
50       * Convenience constructor with empty change set list.
51       */
52      public DarcsChangeSetList() {
53          this(new ArrayList<DarcsChangeSet>());
54      }
55  
56      /**
57       * Constructs without build.
58       *
59       * @param changes list of patches
60       */
61      public DarcsChangeSetList(final List<DarcsChangeSet> changes) {
62          this(null, changes);
63      }
64  
65      /**
66       * Constructs with build and changes.
67       *
68       * @param build current build associated with change set
69       * @param changes list of patches
70       */
71      @SuppressWarnings("LeakingThisInConstructor") // because its' at the end od constructor
72      public DarcsChangeSetList(final AbstractBuild build, final List<DarcsChangeSet> changes) {
73          super(build);
74  
75          // we want the changesets allways in same order for digesting
76          Collections.sort(changes, new Comparator<DarcsChangeSet>() {
77              public int compare(DarcsChangeSet a, DarcsChangeSet b) {
78                  return a.getHash().compareTo(b.getHash());
79              }
80          });
81          changeSets = Collections.unmodifiableList(changes);
82  
83          for (final DarcsChangeSet log : changes) {
84              log.setParent(this);
85          }
86      }
87  
88      @Override
89      public boolean isEmptySet() {
90          return getChangeSets().isEmpty();
91      }
92  
93      /**
94       * Returns an iterator for the list.
95       *
96       * @return change set iterator
97       */
98      public Iterator<DarcsChangeSet> iterator() {
99          return getChangeSets().iterator();
100     }
101 
102     /**
103      * Returns the count of change sets.
104      *
105      * @return size of changes
106      */
107     public int size() {
108         return getChangeSets().size();
109     }
110 
111     /**
112      * Returns the change set list.
113      *
114      * @return change set list
115      */
116     public List<DarcsChangeSet> getChangeSets() {
117         return changeSets;
118     }
119 
120     /**
121      * Returns the kind as string.
122      *
123      * @return {@value #KIND}
124      */
125     @Override
126     public String getKind() {
127         return KIND;
128     }
129 
130     /**
131      * Calculates md5 digest over all changesets darcs hashes.
132      *
133      * Inspired by http://www.stratos.me/2008/05/java-string-calculate-md5/
134      *
135      * @return md5 hashed string
136      */
137     private String calcDigest() {
138         final StringBuilder res = new StringBuilder();
139 
140         try {
141             final MessageDigest algorithm = MessageDigest.getInstance("MD5");
142             algorithm.reset();
143 
144             if (isEmptySet()) {
145                 algorithm.update("".getBytes());
146             } else {
147                 for (final DarcsChangeSet cs : this) {
148                     algorithm.update(cs.getHash().getBytes());
149                 }
150             }
151 
152             final byte[] md5 = algorithm.digest();
153 
154             for (int i = 0; i < md5.length; i++) {
155                 final String tmp = (Integer.toHexString(BYTE_MASK & md5[i]));
156 
157                 if (tmp.length() == 1) {
158                     res.append("0");
159                 }
160 
161                 res.append(tmp);
162             }
163         } catch (NoSuchAlgorithmException ex) {
164             res.append("");
165         }
166 
167         return res.toString();
168     }
169 
170     /**
171      * Returns the digest for the whole change set.
172      *
173      * Lazy computes the digest one time.
174      *
175      * @return md5 hashed digest
176      */
177     public String digest() {
178         if (null == digest) {
179             digest = calcDigest();
180         }
181 
182         return digest;
183     }
184 
185     @Override
186     public boolean equals(final Object object) {
187         if (!(object instanceof DarcsChangeSetList)) {
188             return false;
189         }
190 
191         final DarcsChangeSetList other = (DarcsChangeSetList) object;
192         return digest().equals(other.digest());
193     }
194 
195     @Override
196     public int hashCode() {
197         return digest().hashCode();
198     }
199 
200     @Override
201     public String toString() {
202         return String.format("DarcsChangeSetList{changeSets=%s, digest=%s}", changeSets, digest());
203     }
204 
205 }