Skip to content

Commit fefa708

Browse files
author
Dmytro Ukhlov
committed
Add NavigableMap support for CopyOnWriteMap.Tree
Optimize AbstractLazyLoadRunMap's search, newestBuild, oldestBuild, firstKey, lastKey methods using NavigableMap features Simplify wrapping with BuildReferenceMapAdapter by introducing single configuration class BuildReferenceMapAdapter.Resolver Make CopyOnWriteMap.Tree.getView method protected (was public)
1 parent af417ec commit fefa708

File tree

3 files changed

+153
-81
lines changed

3 files changed

+153
-81
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: 21 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,6 @@
2424

2525
package jenkins.model.lazy;
2626

27-
import static jenkins.model.lazy.AbstractLazyLoadRunMap.Direction.ASC;
28-
import static jenkins.model.lazy.AbstractLazyLoadRunMap.Direction.DESC;
29-
3027
import edu.umd.cs.findbugs.annotations.CheckForNull;
3128
import hudson.model.Job;
3229
import hudson.model.Run;
@@ -39,8 +36,8 @@
3936
import java.util.Collection;
4037
import java.util.Collections;
4138
import java.util.Comparator;
42-
import java.util.Iterator;
4339
import java.util.Map;
40+
import java.util.NavigableMap;
4441
import java.util.NoSuchElementException;
4542
import java.util.Set;
4643
import java.util.SortedMap;
@@ -96,8 +93,9 @@
9693
public abstract class AbstractLazyLoadRunMap<R> extends AbstractMap<Integer, R> implements SortedMap<Integer, R> {
9794
private final CopyOnWriteMap.Tree<Integer, BuildReference<R>> core = new CopyOnWriteMap.Tree<>(
9895
Collections.reverseOrder());
99-
private final BuildReferenceMapAdapter<R> adapter = new BuildReferenceMapAdapter<>(core,
100-
this::resolveBuildRef, this::getNumberOf, this::getBuildClass) {
96+
private final BuildReferenceMapAdapter.Resolver<R> buildResolver = new BuildReferenceMapAdapter.Resolver<>(
97+
this::resolveBuildRef, this::getNumberOf, this::getBuildClass);
98+
private final BuildReferenceMapAdapter<R> adapter = new BuildReferenceMapAdapter<>(core, buildResolver) {
10199
@Override
102100
protected boolean removeValue(R value) {
103101
return AbstractLazyLoadRunMap.this.removeValue(value);
@@ -260,7 +258,7 @@ public SortedMap<Integer, R> getLoadedBuilds() {
260258
res.put(entry.getKey(), buildRef);
261259
}
262260
}
263-
return new BuildReferenceMapAdapter<>(res, this::resolveBuildRef, this::getNumberOf, this::getBuildClass);
261+
return new BuildReferenceMapAdapter<>(res, buildResolver);
264262
}
265263

266264
/**
@@ -286,24 +284,21 @@ public SortedMap<Integer, R> tailMap(Integer fromKey) {
286284

287285
@Override
288286
public Integer firstKey() {
289-
R r = newestBuild();
290-
if (r == null) throw new NoSuchElementException();
291-
return getNumberOf(r);
287+
return adapter.firstKey();
292288
}
293289

294290
@Override
295291
public Integer lastKey() {
296-
R r = oldestBuild();
297-
if (r == null) throw new NoSuchElementException();
298-
return getNumberOf(r);
292+
return adapter.lastKey();
299293
}
300294

301295
public R newestBuild() {
302-
return search(Integer.MAX_VALUE, DESC);
296+
return adapter.values().stream().findFirst().orElse(null);
303297
}
304298

305299
public R oldestBuild() {
306-
return search(Integer.MIN_VALUE, ASC);
300+
return new BuildReferenceMapAdapter<>(core.descendingMap(), buildResolver)
301+
.values().stream().findFirst().orElse(null);
307302
}
308303

309304
@Override
@@ -339,48 +334,27 @@ public boolean runExists(int number) {
339334
* If DESC, finds the closest #M that satisfies M ≤ N.
340335
*/
341336
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();
337+
if (d == Direction.EXACT) {
338+
return this.adapter.get(n);
371339
}
340+
// prepare sub map, where we need to find first resolvable entry
341+
NavigableMap<Integer, BuildReference<R>> subCore = (d == Direction.ASC)
342+
? core.headMap(n, true).descendingMap()
343+
: core.tailMap(n, true);
344+
// wrap with BuildReferenceMapAdapter to skip unresolvable entries
345+
return new BuildReferenceMapAdapter<>(subCore, buildResolver).values().stream().findFirst().orElse(null);
372346
}
373347

374348
public R getById(String id) {
375349
return getByNumber(Integer.parseInt(id));
376350
}
377351

378352
/**
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
353+
* Ensure loading referent object if needed, cache it and return
354+
* Save that object as 'unloadable' in case of failure to avoid next load attempts
381355
*
382356
* @param ref reference object to be resolved
383-
* @return R referent build object, or null if can't be resolved
357+
* @return R referent build object, or null if it can't be resolved
384358
*/
385359
private R resolveBuildRef(BuildReference<R> ref) {
386360
if (ref == null || ref.isUnloadable()) {

0 commit comments

Comments
 (0)