File D:\maven-qcr-plugin-testsunitaires\qcr4java\jenkins-master\target\site\qc\core\src\main\java\jenkins\model\lazy\AbstractLazyLoadRunMap.java.html

1 /*
2 * The MIT License
3 *
4 * Copyright (c) 2012, CloudBees, Inc.
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 * THE SOFTWARE.
23 */
24 package jenkins.model.lazy;
25
26 import hudson.model.Job;
27 import hudson.model.Run;
28 import hudson.model.RunMap;
29 import org.apache.commons.collections.keyvalue.DefaultMapEntry;
30 import org.kohsuke.accmod.Restricted;
31 import org.kohsuke.accmod.restrictions.NoExternalUse;
32
33 import java.io.File;
34 import java.io.FilenameFilter;
35 import java.io.IOException;
36 import java.lang.ref.Reference;
37 import java.util.AbstractMap;
38 import java.util.ArrayList;
39 import java.util.Arrays;
40 import java.util.Collections;
41 import java.util.Comparator;
42 import java.util.Map;
43 import java.util.NoSuchElementException;
44 import java.util.Set;
45 import java.util.SortedMap;
46 import java.util.TreeMap;
47 import java.util.logging.Level;
48 import java.util.logging.Logger;
49
50 import static jenkins.model.lazy.AbstractLazyLoadRunMap.Direction.*;
51 import static jenkins.model.lazy.Boundary.*;
52
53 /**
54 * {@link SortedMap} that keeps build records by their build numbers, in the descending order
55 * (newer ones first.)
56 *
57 * <p>
58 * The main thing about this class is that it encapsulates the lazy loading logic.
59 * That is, while this class looks and feels like a normal {@link SortedMap} from outside,
60 * it actually doesn't have every item in the map instantiated yet. As items in the map get
61 * requested, this class {@link #retrieve(File) retrieves them} on demand, one by one.
62 *
63 * <p>
64 * The lookup is primarily done by using the build number as the key (hence the key type is {@link Integer}),
65 * but this class also provides look up based on {@linkplain #getIdOf(Object) the build ID}.
66 *
67 * <p>
68 * This class makes the following assumption about the on-disk layout of the data:
69 *
70 * <ul>
71 * <li>Every build is stored in a directory, named after its ID.
72 * <li>ID and build number are in the consistent order. That is,
73 * if there are two builds #M and #N, {@code M>N <=> M.id > N.id}.
74 * </ul>
75 *
76 * <p>
77 * On certain platforms, there are symbolic links named after build numbers that link to the build ID.
78 * If these are available, they are used as a hint to speed up the lookup. Otherwise
79 * we rely on the assumption above and perform a binary search to locate the build.
80 * (notice that we'll have to do linear search if we don't have the consistent ordering assumption,
81 * which robs the whole point of doing lazy loading.)
82 *
83 * <p>
84 * Some of the {@link SortedMap} operations are weakly implemented. For example,
85 * {@link #size()} may be inaccurate because we only count the number of directories that look like
86 * build records, without checking if they are loadable. But these weaknesses aren't distinguishable
87 * from concurrent modifications, where another thread deletes a build while one thread iterates them.
88 *
89 * <p>
90 * Some of the {@link SortedMap} operations are inefficiently implemented, by
91 * {@linkplain #all() loading all the build records eagerly}. We hope to replace
92 * these implementations by more efficient lazy-loading ones as we go.
93 *
94 * <p>
95 * Object lock of {@code this} is used to make sure mutation occurs sequentially.
96 * That is, ensure that only one thread is actually calling {@link #retrieve(File)} and
97 * updating {@link Index#byNumber} and {@link Index#byId}.
98 *
99 * @author Kohsuke Kawaguchi
100 * @since 1.485
101 */
102 public abstract class AbstractLazyLoadRunMap<R> extends AbstractMap<Integer,R> implements SortedMap<Integer,R> {
103 /**
104 * Used in {@link #all()} to quickly determine if we've already loaded everything.
105 */
106 private boolean fullyLoaded;
107
108 /**
109 * Currently visible index.
110 * Updated atomically. Once set to this field, the index object may not be modified.
111 */
112 private volatile Index index = new Index();
113
114 /**
115 * Pair of two maps into a single class, so that the changes can be made visible atomically,
116 * and updates can happen concurrently to read.
117 *
118 * The idiom is that you put yourself in a synchronized block, {@linkplain #copy() make a copy of this},
119 * update the copy, then set it to {@link #index}.
120 */
121 private class Index {
122 /**
123 * Stores the mapping from build number to build, for builds that are already loaded.
124 */
125 private final TreeMap<Integer,BuildReference<R>> byNumber;
126
127 /**
128 * Stores the build ID to build number for builds that we already know.
129 *
130 * If we have known load failure of the given ID, we record that in the map
131 * by using the null value (not to be confused with a non-null {@link BuildReference}
132 * with null referent, which just means the record was GCed.)
133 */
134 private final TreeMap<String,BuildReference<R>> byId;
135
136 private Index() {
137 byId = new TreeMap<String,BuildReference<R>>();
138 byNumber = new TreeMap<Integer,BuildReference<R>>(COMPARATOR);
139 }
140
141 private Index(Index rhs) {
142 byId = new TreeMap<String, BuildReference<R>>(rhs.byId);
143 byNumber = new TreeMap<Integer,BuildReference<R>>(rhs.byNumber);
144 }
145
146 /**
147 * Returns the build record #M (<=n)
148 */
149 private Map.Entry<Integer,BuildReference<R>> ceilingEntry(int n) {
150 // switch to this once we depend on JDK6
151 // return byNumber.ceilingEntry(n);
152
153 Set<Entry<Integer, BuildReference<R>>> s = byNumber.tailMap(n).entrySet();
154 if (s.isEmpty()) return null;
155 else return s.iterator().next();
156 }
157
158 /**
159 * Returns the build record #M (>=n)
160 */
161 // >= and not <= because byNumber is in the descending order
162 private Map.Entry<Integer,BuildReference<R>> floorEntry(int n) {
163 // switch to this once we depend on JDK6
164 // return byNumber.floorEntry(n);
165
166 SortedMap<Integer, BuildReference<R>> sub = byNumber.headMap(n);
167 if (sub.isEmpty()) return null;
168 Integer k = sub.lastKey();
169 return new DefaultMapEntry(k,sub.get(k));
170 }
171 }
172
173 /**
174 * Build IDs found as directories, in the ascending order.
175 */
176 // copy on write
177 private volatile SortedList<String> idOnDisk = new SortedList<String>(Collections.<String>emptyList());
178
179 /**
180 * Build number shortcuts found on disk, in the ascending order.
181 */
182 // copy on write
183 private volatile SortedIntList numberOnDisk = new SortedIntList(0);
184
185 /**
186 * Base directory for data.
187 * In effect this is treated as a final field, but can't mark it final
188 * because the compatibility requires that we make it settable
189 * in the first call after the constructor.
190 */
191 private File dir;
192
193 protected AbstractLazyLoadRunMap(File dir) {
194 initBaseDir(dir);
195 }
196
197 @Restricted(NoExternalUse.class)
198 protected void initBaseDir(File dir) {
199 assert this.dir==null;
200 this.dir = dir;
201 if (dir!=null)
202 loadIdOnDisk();
203 }
204
205 /**
206 * @return true if {@link AbstractLazyLoadRunMap#AbstractLazyLoadRunMap} was called with a non-null param, or {@link RunMap#load(Job, RunMap.Constructor)} was called
207 */
208 @Restricted(NoExternalUse.class)
209 public final boolean baseDirInitialized() {
210 return dir != null;
211 }
212
213 private void loadIdOnDisk() {
214 String[] buildDirs = dir.list(createDirectoryFilter());
215 if (buildDirs==null) {
216 // the job may have just been created
217 buildDirs=EMPTY_STRING_ARRAY;
218 }
219 // wrap into ArrayList to enable mutation
220 Arrays.sort(buildDirs);
221 idOnDisk = new SortedList(new ArrayList<String>(Arrays.asList(buildDirs)));
222
223 // TODO: should we check that shortcuts is a symlink?
224 String[] shortcuts = dir.list();
225 if (shortcuts==null) shortcuts=EMPTY_STRING_ARRAY;
226 SortedIntList list = new SortedIntList(shortcuts.length/2);
227 for (String s : shortcuts) {
228 try {
229 list.add(Integer.parseInt(s));
230 } catch (NumberFormatException e) {
231 // this isn't a shortcut
232 }
233 }
234 list.sort();
235 numberOnDisk = list;
236 }
237
238 public Comparator<? super Integer> comparator() {
239 return COMPARATOR;
240 }
241
242 /**
243 * If we have non-zero R in memory, we can return false right away.
244 * If we have zero R in memory, try loading one and see if we can find something.
245 */
246 @Override
247 public boolean isEmpty() {
248 return index.byId.isEmpty() && search(Integer.MAX_VALUE, DESC)==null;
249 }
250
251 @Override
252 public Set<Entry<Integer, R>> entrySet() {
253 assert baseDirInitialized();
254 return Collections.unmodifiableSet(new BuildReferenceMapAdapter<R>(this,all()).entrySet());
255 }
256
257 /**
258 * Returns a read-only view of records that has already been loaded.
259 */
260 public SortedMap<Integer,R> getLoadedBuilds() {
261 return Collections.unmodifiableSortedMap(new BuildReferenceMapAdapter<R>(this, index.byNumber));
262 }
263
264 /**
265 * @param fromKey
266 * Biggest build number to be in the returned set.
267 * @param toKey
268 * Smallest build number-1 to be in the returned set (-1 because this is exclusive)
269 */
270 public SortedMap<Integer, R> subMap(Integer fromKey, Integer toKey) {
271 // TODO: if this method can produce a lazy map, that'd be wonderful
272 // because due to the lack of floor/ceil/higher/lower kind of methods
273 // to look up keys in SortedMap, various places of Jenkins rely on
274 // subMap+firstKey/lastKey combo.
275
276 R start = search(fromKey, DESC);
277 if (start==null) return EMPTY_SORTED_MAP;
278
279 R end = search(toKey, ASC);
280 if (end==null) return EMPTY_SORTED_MAP;
281
282 for (R i=start; i!=end; ) {
283 i = search(getNumberOf(i)-1,DESC);
284 assert i!=null;
285 }
286
287 return Collections.unmodifiableSortedMap(new BuildReferenceMapAdapter<R>(this, index.byNumber.subMap(fromKey, toKey)));
288 }
289
290 public SortedMap<Integer, R> headMap(Integer toKey) {
291 return subMap(Integer.MAX_VALUE, toKey);
292 }
293
294 public SortedMap<Integer, R> tailMap(Integer fromKey) {
295 return subMap(fromKey, Integer.MIN_VALUE);
296 }
297
298 public Integer firstKey() {
299 R r = newestBuild();
300 if (r==null) throw new NoSuchElementException();
301 return getNumberOf(r);
302 }
303
304 public Integer lastKey() {
305 R r = oldestBuild();
306 if (r==null) throw new NoSuchElementException();
307 return getNumberOf(r);
308 }
309
310 public R newestBuild() {
311 return search(Integer.MAX_VALUE, DESC);
312 }
313
314 public R oldestBuild() {
315 return search(Integer.MIN_VALUE, ASC);
316 }
317
318 @Override
319 public R get(Object key) {
320 if (key instanceof Integer) {
321 int n = (Integer) key;
322 return get(n);
323 }
324 return super.get(key);
325 }
326
327 public R get(int n) {
328 return search(n,Direction.EXACT);
329 }
330
331 /**
332 * Finds the build #M where M is nearby the given 'n'.
333 *
334 * <p>
335 *
336 *
337 * @param n
338 * the index to start the search from
339 * @param d
340 * defines what we mean by "nearby" above.
341 * If EXACT, find #N or return null.
342 * If ASC, finds the closest #M that satisfies M>=N.
343 * If DESC, finds the closest #M that satisfies M<=N.
344 */
345 public R search(final int n, final Direction d) {
346 Entry<Integer, BuildReference<R>> c = index.ceilingEntry(n);
347 if (c!=null && c.getKey()== n) {
348 R r = c.getValue().get();
349 if (r!=null)
350 return r; // found the exact #n
351 }
352
353 // at this point we know that we don't have #n loaded yet
354
355 {// check numberOnDisk as a cache to see if we can find it there
356 int npos = numberOnDisk.find(n);
357 if (npos>=0) {// found exact match
358 R r = load(numberOnDisk.get(npos), null);
359 if (r!=null)
360 return r;
361 }
362
363 switch (d) {
364 case ASC:
365 case DESC:
366 // didn't find the exact match, but what's the nearest ascending value in the cache?
367 int neighbor = (d==ASC?HIGHER:LOWER).apply(npos);
368 if (numberOnDisk.isInRange(neighbor)) {
369 R r = getByNumber(numberOnDisk.get(neighbor));
370 if (r!=null) {
371 // make sure that the cache is accurate by looking at the previous ID
372 // and it actually satisfies the constraint
373 int prev = (d==ASC?LOWER:HIGHER).apply(idOnDisk.find(getIdOf(r)));
374 if (idOnDisk.isInRange(prev)) {
375 R pr = getById(idOnDisk.get(prev));
376 // sign*sign is making sure that #pr and #r sandwiches #n.
377 if (pr!=null && signOfCompare(getNumberOf(pr),n)*signOfCompare(n,getNumberOf(r))>0)
378 return r;
379 else {
380 // cache is lying. there's something fishy.
381 // ignore the cache and do the slow search
382 }
383 } else {
384 // r is the build with youngest ID
385 return r;
386 }
387 } else {
388 // cache says we should have a build but we didn't.
389 // ignore the cache and do the slow search
390 }
391 }
392 break;
393 case EXACT:
394 // fall through
395 }
396
397 // didn't find it in the cache, but don't give up yet
398 // maybe the cache just doesn't exist.
399 // so fall back to the slow search
400 }
401
402 // capture the snapshot and work off with it since it can be overwritten by other threads
403 SortedList<String> idOnDisk = this.idOnDisk;
404 boolean clonedIdOnDisk = false; // if we modify idOnDisk we need to write it back. this flag is set to true when we overwrit idOnDisk local var
405
406 // slow path: we have to find the build from idOnDisk by guessing ID of the build.
407 // first, narrow down the candidate IDs to try by using two known number-to-ID mapping
408 if (idOnDisk.isEmpty()) return null;
409
410 Entry<Integer, BuildReference<R>> f = index.floorEntry(n);
411
412 // if bound is null, use a sentinel value
413 String cid = c==null ? "\u0000" : c.getValue().id;
414 String fid = f==null ? "\uFFFF" : f.getValue().id;
415 // at this point, #n must be in (cid,fid)
416
417 // We know that the build we are looking for exists in [lo,hi) --- it's "hi)" and not "hi]" because we do +1.
418 // we will narrow this down via binary search
419 final int initialSize = idOnDisk.size();
420 int lo = idOnDisk.higher(cid);
421 int hi = idOnDisk.lower(fid)+1;
422
423 final int initialLo = lo, initialHi = hi;
424
425 if (!(0<=lo && lo<=hi && hi<=idOnDisk.size())) {
426 // assertion error, but we are so far unable to get to the bottom of this bug.
427 // but don't let this kill the loading the hard way
428 String msg = String.format(
429 "Assertion error: failing to load #%d %s: lo=%d,hi=%d,size=%d,size2=%d",
430 n, d, lo, hi, idOnDisk.size(), initialSize);
431 LOGGER.log(Level.WARNING, msg,new Exception());
432 throw new ArrayIndexOutOfBoundsException(msg);
433 }
434
435 while (lo<hi) {
436 final int pivot = (lo+hi)/2;
437 if (!(0<=lo && lo<=pivot && pivot<hi && hi<=idOnDisk.size())) {
438 // assertion error, but we are so far unable to get to the bottom of this bug.
439 // but don't let this kill the loading the hard way
440 String msg = String.format(
441 "Assertion error: failing to load #%d %s: lo=%d,hi=%d,pivot=%d,size=%d (initial:lo=%d,hi=%d,size=%d)",
442 n, d, lo, hi, pivot, idOnDisk.size(), initialLo, initialHi, initialSize);
443 LOGGER.log(Level.WARNING, msg,new Exception());
444 throw new ArrayIndexOutOfBoundsException(msg);
445 }
446 R r = load(idOnDisk.get(pivot), null);
447 if (r==null) {
448 // this ID isn't valid. get rid of that and retry pivot
449 hi--;
450 if (!clonedIdOnDisk) {// if we are making an edit, we need to own a copy
451 idOnDisk = new SortedList<String>(idOnDisk);
452 clonedIdOnDisk = true;
453 }
454 idOnDisk.remove(pivot);
455 continue;
456 }
457
458 int found = getNumberOf(r);
459 if (found==n)
460 return r; // exact match
461
462 if (found<n) lo = pivot+1; // the pivot was too small. look in the upper half
463 else hi = pivot; // the pivot was too big. look in the lower half
464 }
465
466 if (clonedIdOnDisk)
467 this.idOnDisk = idOnDisk; // feedback the modified result atomically
468
469 assert lo==hi;
470 // didn't find the exact match
471 // both lo and hi point to the insertion point on idOnDisk
472 switch (d) {
473 case ASC:
474 if (hi==idOnDisk.size()) return null;
475 return getById(idOnDisk.get(hi));
476 case DESC:
477 if (lo<=0) return null;
478 if (lo-1>=idOnDisk.size()) {
479 // assertion error, but we are so far unable to get to the bottom of this bug.
480 // but don't let this kill the loading the hard way
481 LOGGER.log(Level.WARNING, String.format(
482 "Assertion error: failing to load #%d %s: lo=%d,hi=%d,size=%d (initial:lo=%d,hi=%d,size=%d)",
483 n,d,lo,hi,idOnDisk.size(), initialLo,initialHi,initialSize),new Exception());
484 return null;
485 }
486 return getById(idOnDisk.get(lo-1));
487 case EXACT:
488 return null;
489 default:
490 throw new AssertionError();
491 }
492 }
493
494 /**
495 * sign of (a-b).
496 */
497 private static int signOfCompare(int a, int b) {
498 if (a>b) return 1;
499 if (a<b) return -1;
500 return 0;
501 }
502
503 public R getById(String id) {
504 Index snapshot = index;
505 if (snapshot.byId.containsKey(id)) {
506 BuildReference<R> ref = snapshot.byId.get(id);
507 if (ref==null) return null; // known failure
508 R v = unwrap(ref);
509 if (v!=null) return v; // already in memory
510 // otherwise fall through to load
511 }
512 return load(id,null);
513 }
514
515 public R getByNumber(int n) {
516 return search(n,Direction.EXACT);
517 }
518
519 public R put(R value) {
520 return _put(value);
521 }
522
523 protected R _put(R value) {
524 return put(getNumberOf(value),value);
525 }
526
527 @Override
528 public synchronized R put(Integer key, R r) {
529 String id = getIdOf(r);
530 int n = getNumberOf(r);
531
532 Index copy = copy();
533 BuildReference<R> ref = createReference(r);
534 BuildReference<R> old = copy.byId.put(id,ref);
535 copy.byNumber.put(n,ref);
536 index = copy;
537
538 /*
539 search relies on the fact that every object added via
540 put() method be available in the xyzOnDisk index, so I'm adding them here
541 however, this is awfully inefficient. I wonder if there's any better way to do this?
542 */
543 if (!idOnDisk.contains(id)) {
544 ArrayList<String> a = new ArrayList<String>(idOnDisk);
545 a.add(id);
546 Collections.sort(a);
547 idOnDisk = new SortedList<String>(a);
548 }
549
550 if (!numberOnDisk.contains(n)) {
551 SortedIntList a = new SortedIntList(numberOnDisk);
552 a.add(n);
553 a.sort();
554 numberOnDisk = a;
555 }
556
557 return unwrap(old);
558 }
559
560 private R unwrap(Reference<R> ref) {
561 return ref!=null ? ref.get() : null;
562 }
563
564 @Override
565 public synchronized void putAll(Map<? extends Integer,? extends R> rhs) {
566 Index copy = copy();
567 for (R r : rhs.values()) {
568 String id = getIdOf(r);
569 BuildReference<R> ref = createReference(r);
570 copy.byId.put(id,ref);
571 copy.byNumber.put(getNumberOf(r),ref);
572 }
573 index = copy;
574 }
575
576 /**
577 * Loads all the build records to fully populate the map.
578 * Calling this method results in eager loading everything,
579 * so the whole point of this class is to avoid this call as much as possible
580 * for typical code path.
581 *
582 * @return
583 * fully populated map.
584 */
585 private TreeMap<Integer,BuildReference<R>> all() {
586 if (!fullyLoaded) {
587 synchronized (this) {
588 if (!fullyLoaded) {
589 Index copy = copy();
590 for (String id : idOnDisk) {
591 if (!copy.byId.containsKey(id))
592 load(id,copy);
593 }
594 index = copy;
595 fullyLoaded = true;
596 }
597 }
598 }
599 return index.byNumber;
600 }
601
602 /**
603 * Creates a duplicate for the COW data structure in preparation for mutation.
604 */
605 private Index copy() {
606 return new Index(index);
607 }
608
609 /**
610 * Tries to load the record #N by using the shortcut.
611 *
612 * @return null if the data failed to load.
613 */
614 protected R load(int n, Index editInPlace) {
615 R r = null;
616 File shortcut = new File(dir,String.valueOf(n));
617 if (shortcut.isDirectory()) {
618 synchronized (this) {
619 r = load(shortcut,editInPlace);
620
621 // make sure what we actually loaded is #n,
622 // because the shortcuts can lie.
623 if (r!=null && getNumberOf(r)!=n)
624 r = null;
625
626 if (r==null) {
627 // if failed to locate, record that fact
628 SortedIntList update = new SortedIntList(numberOnDisk);
629 update.removeValue(n);
630 numberOnDisk = update;
631 }
632 }
633 }
634 return r;
635 }
636
637
638 protected R load(String id, Index editInPlace) {
639 assert dir != null;
640 R v = load(new File(dir, id), editInPlace);
641 if (v==null && editInPlace!=null) {
642 // remember the failure.
643 // if editInPlace==null, we can create a new copy for this, but not sure if it's worth doing,
644 // given that we also update idOnDisk anyway.
645 editInPlace.byId.put(id,null);
646 }
647 return v;
648 }
649
650 /**
651 * @param editInPlace
652 * If non-null, update this data structure.
653 * Otherwise do a copy-on-write of {@link #index}
654 */
655 protected synchronized R load(File dataDir, Index editInPlace) {
656 try {
657 R r = retrieve(dataDir);
658 if (r==null) return null;
659
660 Index copy = editInPlace!=null ? editInPlace : new Index(index);
661
662 String id = getIdOf(r);
663 BuildReference<R> ref = createReference(r);
664 copy.byId.put(id,ref);
665 copy.byNumber.put(getNumberOf(r),ref);
666
667 if (editInPlace==null) index = copy;
668
669 return r;
670 } catch (IOException e) {
671 LOGGER.log(Level.WARNING, "Failed to load "+dataDir,e);
672 }
673 return null;
674 }
675
676 /**
677 * Subtype to provide {@link Run#getNumber()} so that this class doesn't have to depend on it.
678 */
679 protected abstract int getNumberOf(R r);
680 /**
681 * Subtype to provide {@link Run#getId()} so that this class doesn't have to depend on it.
682 */
683 protected abstract String getIdOf(R r);
684
685 /**
686 * Allow subtype to capture a reference.
687 */
688 protected BuildReference<R> createReference(R r) {
689 return new BuildReference<R>(getIdOf(r),r);
690 }
691
692
693 /**
694 * Parses {@code R} instance from data in the specified directory.
695 *
696 * @return
697 * null if the parsing failed.
698 * @throws IOException
699 * if the parsing failed. This is just like returning null
700 * except the caller will catch the exception and report it.
701 */
702 protected abstract R retrieve(File dir) throws IOException;
703
704 public synchronized boolean removeValue(R run) {
705 Index copy = copy();
706 copy.byNumber.remove(getNumberOf(run));
707 BuildReference<R> old = copy.byId.remove(getIdOf(run));
708 this.index = copy;
709
710 return unwrap(old)!=null;
711 }
712
713 /**
714 * Replaces all the current loaded Rs with the given ones.
715 */
716 public synchronized void reset(TreeMap<Integer,R> builds) {
717 Index index = new Index();
718 for (R r : builds.values()) {
719 String id = getIdOf(r);
720 BuildReference<R> ref = createReference(r);
721 index.byId.put(id,ref);
722 index.byNumber.put(getNumberOf(r),ref);
723 }
724
725 this.index = index;
726 }
727
728 @Override
729 public int hashCode() {
730 return System.identityHashCode(this);
731 }
732
733 @Override
734 public boolean equals(Object o) {
735 return o==this;
736 }
737
738 /**
739 * Lists the actual data directory
740 */
741 protected abstract FilenameFilter createDirectoryFilter();
742
743 private static final Comparator<Comparable> COMPARATOR = new Comparator<Comparable>() {
744 public int compare(Comparable o1, Comparable o2) {
745 return -o1.compareTo(o2);
746 }
747 };
748
749 public enum Direction {
750 ASC, DESC, EXACT
751 }
752
753 private static final String[] EMPTY_STRING_ARRAY = new String[0];
754
755 private static final SortedMap EMPTY_SORTED_MAP = Collections.unmodifiableSortedMap(new TreeMap());
756
757 static final Logger LOGGER = Logger.getLogger(AbstractLazyLoadRunMap.class.getName());
758 }
759