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 ; 16 import java.io.IOException ; 17 import java.util.ArrayList ; 18 import java.util.Collections ; 19 import java.util.Date ; 20 import java.util.Hashtable ; 21 import java.util.List ; 22 import java.util.Map.Entry; 23 import java.util.logging.Level ; 24 import java.util.logging.Logger ; 25 26 31 public class Fingerprint implements ModelObject { 32 35 public static class BuildPtr { 36 final String name; 37 final int number; 38 39 public BuildPtr(String 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 55 public String getName() { 56 return name; 57 } 58 59 63 public AbstractProject getJob() { 64 return Hudson.getInstance().getItemByFullName(name,AbstractProject.class); 65 } 66 67 73 public int getNumber() { 74 return number; 75 } 76 77 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 94 public boolean is(Run r) { 95 return r.getNumber()==number && r.getParent().getFullName().equals(name); 96 } 97 98 101 public boolean is(Job job) { 102 return job.getFullName().equals(name); 103 } 104 } 105 106 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 toString() { 152 return "["+start+","+end+")"; 153 } 154 155 158 public boolean isIndependent(Range that) { 159 return this.end<that.start ||that.end<this.start; 160 } 161 162 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 176 public static final class RangeSet { 177 private final List <Range> ranges; 179 180 public RangeSet() { 181 this(new ArrayList <Range>()); 182 } 183 184 private RangeSet(List <Range> data) { 185 this.ranges = data; 186 } 187 188 191 public synchronized List <Range> getRanges() { 192 return new ArrayList <Range>(ranges); 193 } 194 195 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; 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 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 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 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 Range m = lr.combine(rr); 263 rhs++; 264 265 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 this.ranges.addAll(that.ranges.subList(rhs,that.ranges.size())); 276 } 277 278 public synchronized String toString() { 279 StringBuffer buf = new StringBuffer (); 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 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; 307 public ConverterImpl(Converter collectionConv) { 308 this.collectionConv = collectionConv; 309 } 310 311 public boolean canConvert(Class type) { 312 return type==RangeSet.class; 313 } 314 315 public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) { 316 collectionConv.marshal( ((RangeSet)source).getRanges(), writer, context ); 317 } 318 319 public Object unmarshal(HierarchicalStreamReader reader, final UnmarshallingContext context) { 320 return new RangeSet((List <Range>)(collectionConv.unmarshal(reader,context))); 321 } 322 } 323 } 324 325 private final Date timestamp; 326 327 331 private final BuildPtr original; 332 333 private final byte[] md5sum; 334 335 private final String fileName; 336 337 340 private final Hashtable <String ,RangeSet> usages = new Hashtable <String ,RangeSet>(); 341 342 public Fingerprint(Run build, String fileName, byte[] md5sum) throws IOException { 343 this.original = build==null ? null : new BuildPtr(build); 344 this.md5sum = md5sum; 345 this.fileName = fileName; 346 this.timestamp = new Date (); 347 save(); 348 } 349 350 361 public BuildPtr getOriginal() { 362 return original; 363 } 364 365 public String getDisplayName() { 366 return fileName; 367 } 368 369 372 public String getFileName() { 373 return fileName; 374 } 375 376 379 public String getHashString() { 380 return Util.toHexString(md5sum); 381 } 382 383 386 public Date getTimestamp() { 387 return timestamp; 388 } 389 390 396 public String getTimestampString() { 397 long duration = System.currentTimeMillis()-timestamp.getTime(); 398 return Util.getTimeSpanString(duration); 399 400 } 401 402 408 public RangeSet getRangeSet(String 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 421 public List <String > getJobs() { 422 List <String > r = new ArrayList <String >(); 423 r.addAll(usages.keySet()); 424 Collections.sort(r); 425 return r; 426 } 427 428 public Hashtable <String ,RangeSet> getUsages() { 429 return usages; 430 } 431 432 public synchronized void add(AbstractBuild b) throws IOException { 433 add(b.getParent().getFullName(),b.getNumber()); 434 } 435 436 439 public synchronized void add(String jobFullName, int n) throws IOException { 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 459 public synchronized boolean isAlive() { 460 if(original.isAlive()) 461 return true; 462 463 for (Entry<String ,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 478 public synchronized void save() throws IOException { 479 XmlFile f = getConfigFile(getFingerprintFile(md5sum)); 480 f.mkdirs(); 481 f.write(this); 482 } 483 484 487 private static XmlFile getConfigFile(File file) { 488 return new XmlFile(XSTREAM,file); 489 } 490 491 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 503 static Fingerprint load(byte[] md5sum) throws IOException { 504 return load(getFingerprintFile(md5sum)); 505 } 506 static Fingerprint load(File file) throws IOException { 507 XmlFile configFile = getConfigFile(file); 508 if(!configFile.exists()) 509 return null; 510 try { 511 return (Fingerprint)configFile.read(); 512 } catch (IOException 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 createCollection(Class type) { 527 return new ArrayList (); 528 } 529 } 530 ),10); 531 } 532 533 private static final Logger logger = Logger.getLogger(Fingerprint.class.getName()); 534 } 535 | Popular Tags |