KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > hudson > model > Fingerprint


1 package hudson.model;
2
3 import com.thoughtworks.xstream.XStream;
4 import com.thoughtworks.xstream.converters.Converter;
5 import com.thoughtworks.xstream.converters.MarshallingContext;
6 import com.thoughtworks.xstream.converters.UnmarshallingContext;
7 import com.thoughtworks.xstream.converters.collections.CollectionConverter;
8 import com.thoughtworks.xstream.io.HierarchicalStreamReader;
9 import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
10 import hudson.Util;
11 import hudson.XmlFile;
12 import hudson.util.HexBinaryConverter;
13 import hudson.util.XStream2;
14
15 import java.io.File JavaDoc;
16 import java.io.IOException JavaDoc;
17 import java.util.ArrayList JavaDoc;
18 import java.util.Collections JavaDoc;
19 import java.util.Date JavaDoc;
20 import java.util.Hashtable JavaDoc;
21 import java.util.List JavaDoc;
22 import java.util.Map.Entry;
23 import java.util.logging.Level JavaDoc;
24 import java.util.logging.Logger JavaDoc;
25
26 /**
27  * A file being tracked by Hudson.
28  *
29  * @author Kohsuke Kawaguchi
30  */

31 public class Fingerprint implements ModelObject {
32     /**
33      * Pointer to a {@link Build}.
34      */

35     public static class BuildPtr {
36         final String JavaDoc name;
37         final int number;
38
39         public BuildPtr(String JavaDoc name, int number) {
40             this.name = name;
41             this.number = number;
42         }
43
44         public BuildPtr(Run run) {
45             this( run.getParent().getFullName(), run.getNumber() );
46         }
47
48         /**
49          * Gets {@link Job#getFullName() the full name of the job}.
50          * <p>
51          * Such job could be since then removed,
52          * so there might not be a corresponding
53          * {@link Job}.
54          */

55         public String JavaDoc getName() {
56             return name;
57         }
58
59         /**
60          * Gets the {@link Job} that this pointer points to,
61          * or null if such a job no longer exists.
62          */

63         public AbstractProject getJob() {
64             return Hudson.getInstance().getItemByFullName(name,AbstractProject.class);
65         }
66
67         /**
68          * Gets the project build number.
69          * <p>
70          * Such {@link Run} could be since then
71          * discarded.
72          */

73         public int getNumber() {
74             return number;
75         }
76
77         /**
78          * Gets the {@link Job} that this pointer points to,
79          * or null if such a job no longer exists.
80          */

81         public Run getRun() {
82             Job j = getJob();
83             if(j==null) return null;
84             return j.getBuildByNumber(number);
85         }
86
87         private boolean isAlive() {
88             return getRun()!=null;
89         }
90
91         /**
92          * Returns true if {@link BuildPtr} points to the given run.
93          */

94         public boolean is(Run r) {
95             return r.getNumber()==number && r.getParent().getFullName().equals(name);
96         }
97
98         /**
99          * Returns true if {@link BuildPtr} points to the given job.
100          */

101         public boolean is(Job job) {
102             return job.getFullName().equals(name);
103         }
104     }
105
106     /**
107      * Range of build numbers [start,end). Immutable.
108      */

109     public static final class Range {
110         final int start;
111         final int end;
112
113         public Range(int start, int end) {
114             assert start<end;
115             this.start = start;
116             this.end = end;
117         }
118
119         public int getStart() {
120             return start;
121         }
122
123         public int getEnd() {
124             return end;
125         }
126
127         public boolean isSmallerThan(int i) {
128             return end<=i;
129         }
130
131         public boolean isBiggerThan(int i) {
132             return i<start;
133         }
134
135         public boolean includes(int i) {
136             return start<=i && i<end;
137         }
138
139         public Range expandRight() {
140             return new Range(start,end+1);
141         }
142
143         public Range expandLeft() {
144             return new Range(start-1,end);
145         }
146
147         public boolean isAdjacentTo(Range that) {
148             return this.end==that.start;
149         }
150
151         public String JavaDoc toString() {
152             return "["+start+","+end+")";
153         }
154
155         /**
156          * Returns true if two {@link Range}s can't be combined into a single range.
157          */

158         public boolean isIndependent(Range that) {
159             return this.end<that.start ||that.end<this.start;
160         }
161
162         /**
163          * Returns the {@link Range} that combines two ranges.
164          */

165         public Range combine(Range that) {
166             assert !isIndependent(that);
167             return new Range(
168                 Math.min(this.start,that.start),
169                 Math.max(this.end ,that.end ));
170         }
171     }
172
173     /**
174      * Set of {@link Range}s.
175      */

176     public static final class RangeSet {
177         // sorted
178
private final List JavaDoc<Range> ranges;
179
180         public RangeSet() {
181             this(new ArrayList JavaDoc<Range>());
182         }
183
184         private RangeSet(List JavaDoc<Range> data) {
185             this.ranges = data;
186         }
187
188         /**
189          * Gets all the ranges.
190          */

191         public synchronized List JavaDoc<Range> getRanges() {
192             return new ArrayList JavaDoc<Range>(ranges);
193         }
194
195         /**
196          * Expands the range set to include the given value.
197          * If the set already includes this number, this will be a no-op.
198          */

199         public synchronized void add(int n) {
200             for( int i=0; i<ranges.size(); i++ ) {
201                 Range r = ranges.get(i);
202                 if(r.includes(n)) return; // already included
203
if(r.end==n) {
204                     ranges.set(i,r.expandRight());
205                     checkCollapse(i);
206                     return;
207                 }
208                 if(r.start==n+1) {
209                     ranges.set(i,r.expandLeft());
210                     checkCollapse(i-1);
211                     return;
212                 }
213                 if(r.isBiggerThan(n)) {
214                     // needs to insert a single-value Range
215
ranges.add(i,new Range(n,n+1));
216                     return;
217                 }
218             }
219
220             ranges.add(new Range(n,n+1));
221         }
222
223         private void checkCollapse(int i) {
224             if(i<0 || i==ranges.size()-1) return;
225             Range lhs = ranges.get(i);
226             Range rhs = ranges.get(i+1);
227             if(lhs.isAdjacentTo(rhs)) {
228                 // collapsed
229
Range r = new Range(lhs.start,rhs.end);
230                 ranges.set(i,r);
231                 ranges.remove(i+1);
232             }
233         }
234
235         public synchronized boolean includes(int i) {
236             for (Range r : ranges) {
237                 if(r.includes(i))
238                     return true;
239             }
240             return false;
241         }
242
243         public synchronized void add(RangeSet that) {
244             int lhs=0,rhs=0;
245             while(lhs<this.ranges.size() && rhs<that.ranges.size()) {
246                 Range lr = this.ranges.get(lhs);
247                 Range rr = that.ranges.get(rhs);
248
249                 // no overlap
250
if(lr.end<rr.start) {
251                     lhs++;
252                     continue;
253                 }
254                 if(rr.end<lr.start) {
255                     ranges.add(lhs,rr);
256                     lhs++;
257                     rhs++;
258                     continue;
259                 }
260
261                 // overlap. merge two
262
Range m = lr.combine(rr);
263                 rhs++;
264
265                 // since ranges[lhs] is expanded, it might overlap with others in this.ranges
266
while(lhs+1<this.ranges.size() && !m.isIndependent(this.ranges.get(lhs+1))) {
267                     m = m.combine(this.ranges.get(lhs+1));
268                     this.ranges.remove(lhs+1);
269                 }
270
271                 this.ranges.set(lhs,m);
272             }
273
274             // if anything is left in that.ranges, add them all
275
this.ranges.addAll(that.ranges.subList(rhs,that.ranges.size()));
276         }
277
278         public synchronized String JavaDoc toString() {
279             StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
280             for (Range r : ranges) {
281                 if(buf.length()>0) buf.append(',');
282                 buf.append(r);
283             }
284             return buf.toString();
285         }
286
287         public synchronized boolean isEmpty() {
288             return ranges.isEmpty();
289         }
290
291         /**
292          * Returns true if all the integers logically in this {@link RangeSet}
293          * is smaller than the given integer. For example, {[1,3)} is smaller than 3,
294          * but {[1,3),[100,105)} is not smaller than anything less than 105.
295          *
296          * Note that {} is smaller than any n.
297          */

298         public synchronized boolean isSmallerThan(int n) {
299             if(ranges.isEmpty()) return true;
300
301             return ranges.get(ranges.size() - 1).isSmallerThan(n);
302         }
303
304         static final class ConverterImpl implements Converter {
305             private final Converter collectionConv; // used to convert ArrayList in it
306

307             public ConverterImpl(Converter collectionConv) {
308                 this.collectionConv = collectionConv;
309             }
310
311             public boolean canConvert(Class JavaDoc type) {
312                 return type==RangeSet.class;
313             }
314
315             public void marshal(Object JavaDoc source, HierarchicalStreamWriter writer, MarshallingContext context) {
316                 collectionConv.marshal( ((RangeSet)source).getRanges(), writer, context );
317             }
318
319             public Object JavaDoc unmarshal(HierarchicalStreamReader reader, final UnmarshallingContext context) {
320                 return new RangeSet((List JavaDoc<Range>)(collectionConv.unmarshal(reader,context)));
321             }
322         }
323     }
324
325     private final Date JavaDoc timestamp;
326
327     /**
328      * Null if this fingerprint is for a file that's
329      * apparently produced outside.
330      */

331     private final BuildPtr original;
332
333     private final byte[] md5sum;
334
335     private final String JavaDoc fileName;
336
337     /**
338      * Range of builds that use this file keyed by a job full name.
339      */

340     private final Hashtable JavaDoc<String JavaDoc,RangeSet> usages = new Hashtable JavaDoc<String JavaDoc,RangeSet>();
341
342     public Fingerprint(Run build, String JavaDoc fileName, byte[] md5sum) throws IOException JavaDoc {
343         this.original = build==null ? null : new BuildPtr(build);
344         this.md5sum = md5sum;
345         this.fileName = fileName;
346         this.timestamp = new Date JavaDoc();
347         save();
348     }
349
350     /**
351      * The first build in which this file showed up,
352      * if the file looked like it's created there.
353      * <p>
354      * This is considered as the "source" of this file,
355      * or the owner, in the sense that this project "owns"
356      * this file.
357      *
358      * @return null
359      * if the file is apparently created outside Hudson.
360      */

361     public BuildPtr getOriginal() {
362         return original;
363     }
364
365     public String JavaDoc getDisplayName() {
366         return fileName;
367     }
368
369     /**
370      * The file name (like "foo.jar" without path).
371      */

372     public String JavaDoc getFileName() {
373         return fileName;
374     }
375
376     /**
377      * Gets the MD5 hash string.
378      */

379     public String JavaDoc getHashString() {
380         return Util.toHexString(md5sum);
381     }
382
383     /**
384      * Gets the timestamp when this record is created.
385      */

386     public Date JavaDoc getTimestamp() {
387         return timestamp;
388     }
389
390     /**
391      * Gets the string that says how long since this build has scheduled.
392      *
393      * @return
394      * string like "3 minutes" "1 day" etc.
395      */

396     public String JavaDoc getTimestampString() {
397         long duration = System.currentTimeMillis()-timestamp.getTime();
398         return Util.getTimeSpanString(duration);
399
400     }
401
402     /**
403      * Gets the build range set for the given job name.
404      *
405      * <p>
406      * These builds of this job has used this file.
407      */

408     public RangeSet getRangeSet(String JavaDoc jobFullName) {
409         RangeSet r = usages.get(jobFullName);
410         if(r==null) r = new RangeSet();
411         return r;
412     }
413
414     public RangeSet getRangeSet(Job job) {
415         return getRangeSet(job.getFullName());
416     }
417
418     /**
419      * Gets the sorted list of job names where this jar is used.
420      */

421     public List JavaDoc<String JavaDoc> getJobs() {
422         List JavaDoc<String JavaDoc> r = new ArrayList JavaDoc<String JavaDoc>();
423         r.addAll(usages.keySet());
424         Collections.sort(r);
425         return r;
426     }
427
428     public Hashtable JavaDoc<String JavaDoc,RangeSet> getUsages() {
429         return usages;
430     }
431
432     public synchronized void add(AbstractBuild b) throws IOException JavaDoc {
433         add(b.getParent().getFullName(),b.getNumber());
434     }
435
436     /**
437      * Records that a build of a job has used this file.
438      */

439     public synchronized void add(String JavaDoc jobFullName, int n) throws IOException JavaDoc {
440         synchronized(usages) {
441             RangeSet r = usages.get(jobFullName);
442             if(r==null) {
443                 r = new RangeSet();
444                 usages.put(jobFullName,r);
445             }
446             r.add(n);
447         }
448         save();
449     }
450
451     /**
452      * Returns true if any of the builds recorded in this fingerprint
453      * is still retained.
454      *
455      * <p>
456      * This is used to find out old fingerprint records that can be removed
457      * without losing too much information.
458      */

459     public synchronized boolean isAlive() {
460         if(original.isAlive())
461             return true;
462
463         for (Entry<String JavaDoc,RangeSet> e : usages.entrySet()) {
464             Job j = Hudson.getInstance().getItemByFullName(e.getKey(),Job.class);
465             if(j==null)
466                 continue;
467
468             int oldest = j.getFirstBuild().getNumber();
469             if(!e.getValue().isSmallerThan(oldest))
470                 return true;
471         }
472         return false;
473     }
474
475     /**
476      * Save the settings to a file.
477      */

478     public synchronized void save() throws IOException JavaDoc {
479         XmlFile f = getConfigFile(getFingerprintFile(md5sum));
480         f.mkdirs();
481         f.write(this);
482     }
483
484     /**
485      * The file we save our configuration.
486      */

487     private static XmlFile getConfigFile(File file) {
488         return new XmlFile(XSTREAM,file);
489     }
490
491     /**
492      * Determines the file name from md5sum.
493      */

494     private static File getFingerprintFile(byte[] md5sum) {
495         assert md5sum.length==16;
496         return new File( Hudson.getInstance().getRootDir(),
497             "fingerprints/"+ Util.toHexString(md5sum,0,1)+'/'+Util.toHexString(md5sum,1,1)+'/'+Util.toHexString(md5sum,2,md5sum.length-2)+".xml");
498     }
499
500     /**
501      * Loads a {@link Fingerprint} from a file in the image.
502      */

503     /*package*/ static Fingerprint load(byte[] md5sum) throws IOException JavaDoc {
504         return load(getFingerprintFile(md5sum));
505     }
506     /*package*/ static Fingerprint load(File file) throws IOException JavaDoc {
507         XmlFile configFile = getConfigFile(file);
508         if(!configFile.exists())
509             return null;
510         try {
511             return (Fingerprint)configFile.read();
512         } catch (IOException JavaDoc e) {
513             logger.log(Level.WARNING, "Failed to load "+configFile,e);
514             throw e;
515         }
516     }
517
518     private static final XStream XSTREAM = new XStream2();
519     static {
520         XSTREAM.alias("fingerprint",Fingerprint.class);
521         XSTREAM.alias("range",Range.class);
522         XSTREAM.alias("ranges",RangeSet.class);
523         XSTREAM.registerConverter(new HexBinaryConverter(),10);
524         XSTREAM.registerConverter(new RangeSet.ConverterImpl(
525             new CollectionConverter(XSTREAM.getClassMapper()) {
526                 protected Object JavaDoc createCollection(Class JavaDoc type) {
527                     return new ArrayList JavaDoc();
528                 }
529             }
530         ),10);
531     }
532
533     private static final Logger JavaDoc logger = Logger.getLogger(Fingerprint.class.getName());
534 }
535
Popular Tags