Skip to content

Commit 3a6949d

Browse files
authored
[JENKINS-76028] Add NavigableMap support for CopyOnWriteMap.Tree (#10997)
2 parents 65e05c0 + ea3b238 commit 3a6949d

File tree

3 files changed

+184
-78
lines changed

3 files changed

+184
-78
lines changed

core/src/main/java/hudson/util/CopyOnWriteMap.java

Lines changed: 96 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import java.util.LinkedHashMap;
3939
import java.util.Map;
4040
import java.util.NavigableMap;
41+
import java.util.NavigableSet;
4142
import java.util.Set;
4243
import java.util.SortedMap;
4344
import java.util.TreeMap;
@@ -82,7 +83,7 @@ protected void update(Map<K, V> m) {
8283
/**
8384
* Atomically replaces the entire map by the copy of the specified map.
8485
*/
85-
public void replaceBy(Map<? extends K, ? extends V> data) {
86+
public synchronized void replaceBy(Map<? extends K, ? extends V> data) {
8687
Map<K, V> d = copy();
8788
d.clear();
8889
d.putAll(data);
@@ -224,7 +225,7 @@ public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingC
224225
/**
225226
* {@link CopyOnWriteMap} backed by {@link TreeMap}.
226227
*/
227-
public static final class Tree<K, V> extends CopyOnWriteMap<K, V> implements SortedMap<K, V> {
228+
public static final class Tree<K, V> extends CopyOnWriteMap<K, V> implements NavigableMap<K, V> {
228229
private final Comparator<K> comparator;
229230

230231
public Tree(Map<K, V> core, Comparator<K> comparator) {
@@ -259,14 +260,106 @@ protected NavigableMap<K, V> createView() {
259260
}
260261

261262
@Override
262-
public NavigableMap<K, V> getView() {
263+
protected NavigableMap<K, V> getView() {
263264
return (NavigableMap<K, V>) super.getView();
264265
}
265266

267+
@Override
268+
public synchronized Entry<K, V> pollFirstEntry() {
269+
TreeMap<K, V> d = copy();
270+
Entry<K, V> res = d.pollFirstEntry();
271+
update(d);
272+
return res;
273+
}
274+
275+
@Override
276+
public synchronized Entry<K, V> pollLastEntry() {
277+
TreeMap<K, V> d = copy();
278+
Entry<K, V> res = d.pollLastEntry();
279+
update(d);
280+
return res;
281+
}
282+
283+
@Override
284+
public Entry<K, V> lowerEntry(K key) {
285+
return getView().lowerEntry(key);
286+
}
287+
288+
@Override
289+
public K lowerKey(K key) {
290+
return getView().lowerKey(key);
291+
}
292+
293+
@Override
294+
public Entry<K, V> floorEntry(K key) {
295+
return getView().floorEntry(key);
296+
}
297+
298+
@Override
299+
public K floorKey(K key) {
300+
return getView().floorKey(key);
301+
}
302+
303+
@Override
304+
public Entry<K, V> ceilingEntry(K key) {
305+
return getView().ceilingEntry(key);
306+
}
307+
308+
@Override
309+
public K ceilingKey(K key) {
310+
return getView().ceilingKey(key);
311+
}
312+
313+
@Override
314+
public Entry<K, V> higherEntry(K key) {
315+
return getView().higherEntry(key);
316+
}
317+
318+
@Override
319+
public K higherKey(K key) {
320+
return getView().higherKey(key);
321+
}
322+
323+
@Override
324+
public Entry<K, V> firstEntry() {
325+
return getView().firstEntry();
326+
}
327+
328+
@Override
329+
public Entry<K, V> lastEntry() {
330+
return getView().lastEntry();
331+
}
332+
333+
@Override
266334
public NavigableMap<K, V> descendingMap() {
267335
return getView().descendingMap();
268336
}
269337

338+
@Override
339+
public NavigableSet<K> navigableKeySet() {
340+
return getView().navigableKeySet();
341+
}
342+
343+
@Override
344+
public NavigableSet<K> descendingKeySet() {
345+
return getView().descendingKeySet();
346+
}
347+
348+
@Override
349+
public NavigableMap<K, V> subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) {
350+
return getView().subMap(fromKey, fromInclusive, toKey, toInclusive);
351+
}
352+
353+
@Override
354+
public NavigableMap<K, V> headMap(K toKey, boolean inclusive) {
355+
return getView().headMap(toKey, inclusive);
356+
}
357+
358+
@Override
359+
public NavigableMap<K, V> tailMap(K fromKey, boolean inclusive) {
360+
return getView().tailMap(fromKey, inclusive);
361+
}
362+
270363
@Override
271364
public Comparator<? super K> comparator() {
272365
return getView().comparator();

core/src/main/java/jenkins/model/lazy/AbstractLazyLoadRunMap.java

Lines changed: 35 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
import static jenkins.model.lazy.AbstractLazyLoadRunMap.Direction.ASC;
2828
import static jenkins.model.lazy.AbstractLazyLoadRunMap.Direction.DESC;
29+
import static jenkins.model.lazy.AbstractLazyLoadRunMap.Direction.EXACT;
2930

3031
import edu.umd.cs.findbugs.annotations.CheckForNull;
3132
import hudson.model.Job;
@@ -39,8 +40,8 @@
3940
import java.util.Collection;
4041
import java.util.Collections;
4142
import java.util.Comparator;
42-
import java.util.Iterator;
4343
import java.util.Map;
44+
import java.util.NavigableMap;
4445
import java.util.NoSuchElementException;
4546
import java.util.Set;
4647
import java.util.SortedMap;
@@ -96,8 +97,8 @@
9697
public abstract class AbstractLazyLoadRunMap<R> extends AbstractMap<Integer, R> implements SortedMap<Integer, R> {
9798
private final CopyOnWriteMap.Tree<Integer, BuildReference<R>> core = new CopyOnWriteMap.Tree<>(
9899
Collections.reverseOrder());
99-
private final BuildReferenceMapAdapter<R> adapter = new BuildReferenceMapAdapter<>(core,
100-
this::resolveBuildRef, this::getNumberOf, this::getBuildClass) {
100+
private final BuildReferenceMapAdapter.Resolver<R> buildResolver = new BuildReferenceMapAdapterResolver();
101+
private final BuildReferenceMapAdapter<R> adapter = new BuildReferenceMapAdapter<>(core, buildResolver) {
101102
@Override
102103
protected boolean removeValue(R value) {
103104
return AbstractLazyLoadRunMap.this.removeValue(value);
@@ -260,7 +261,7 @@ public SortedMap<Integer, R> getLoadedBuilds() {
260261
res.put(entry.getKey(), buildRef);
261262
}
262263
}
263-
return new BuildReferenceMapAdapter<>(res, this::resolveBuildRef, this::getNumberOf, this::getBuildClass);
264+
return new BuildReferenceMapAdapter<>(res, buildResolver);
264265
}
265266

266267
/**
@@ -286,16 +287,12 @@ public SortedMap<Integer, R> tailMap(Integer fromKey) {
286287

287288
@Override
288289
public Integer firstKey() {
289-
R r = newestBuild();
290-
if (r == null) throw new NoSuchElementException();
291-
return getNumberOf(r);
290+
return adapter.firstKey();
292291
}
293292

294293
@Override
295294
public Integer lastKey() {
296-
R r = oldestBuild();
297-
if (r == null) throw new NoSuchElementException();
298-
return getNumberOf(r);
295+
return adapter.lastKey();
299296
}
300297

301298
public R newestBuild() {
@@ -339,48 +336,27 @@ public boolean runExists(int number) {
339336
* If DESC, finds the closest #M that satisfies M ≤ N.
340337
*/
341338
public @CheckForNull R search(final int n, final Direction d) {
342-
switch (d) {
343-
case EXACT:
344-
return getByNumber(n);
345-
case ASC:
346-
for (int m : core.descendingMap().keySet()) {
347-
if (m < n) {
348-
continue;
349-
}
350-
R r = getByNumber(m);
351-
if (r != null) {
352-
return r;
353-
}
354-
}
355-
return null;
356-
case DESC:
357-
Iterator<Integer> iterator = core.keySet().iterator();
358-
while (iterator.hasNext()) {
359-
int m = iterator.next();
360-
if (m > n) {
361-
continue;
362-
}
363-
R r = getByNumber(m);
364-
if (r != null) {
365-
return r;
366-
}
367-
}
368-
return null;
369-
default:
370-
throw new AssertionError();
339+
if (d == EXACT) {
340+
return this.adapter.get(n);
371341
}
342+
// prepare sub map, where we need to find first resolvable entry
343+
NavigableMap<Integer, BuildReference<R>> subCore = (d == ASC)
344+
? core.headMap(n, true).descendingMap()
345+
: core.tailMap(n, true);
346+
// wrap with BuildReferenceMapAdapter to skip unresolvable entries
347+
return new BuildReferenceMapAdapter<>(subCore, buildResolver).values().stream().findFirst().orElse(null);
372348
}
373349

374350
public R getById(String id) {
375351
return getByNumber(Integer.parseInt(id));
376352
}
377353

378354
/**
379-
* Ensure load referent object if needed, cache it and return
380-
* Save that object is unloadable in case of failure to avoid next load attempts
355+
* Ensure loading referent object if needed, cache it and return
356+
* Save that object as 'unloadable' in case of failure to avoid next load attempts
381357
*
382358
* @param ref reference object to be resolved
383-
* @return R referent build object, or null if can't be resolved
359+
* @return R referent build object, or null if it can't be resolved
384360
*/
385361
private R resolveBuildRef(BuildReference<R> ref) {
386362
if (ref == null || ref.isUnloadable()) {
@@ -551,5 +527,22 @@ public enum Direction {
551527
ASC, DESC, EXACT
552528
}
553529

530+
private class BuildReferenceMapAdapterResolver implements BuildReferenceMapAdapter.Resolver<R> {
531+
@Override
532+
public R resolveBuildRef(BuildReference<R> buildRef) {
533+
return AbstractLazyLoadRunMap.this.resolveBuildRef(buildRef);
534+
}
535+
536+
@Override
537+
public Integer getNumberOf(R build) {
538+
return AbstractLazyLoadRunMap.this.getNumberOf(build);
539+
}
540+
541+
@Override
542+
public Class<R> getBuildClass() {
543+
return AbstractLazyLoadRunMap.this.getBuildClass();
544+
}
545+
}
546+
554547
static final Logger LOGGER = Logger.getLogger(AbstractLazyLoadRunMap.class.getName());
555548
}

0 commit comments

Comments
 (0)