• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

aspectran / aspectran / #3645

12 Aug 2024 08:33PM CUT coverage: 34.07% (-0.005%) from 34.075%
#3645

Pull #687

github

web-flow
Bump slf4j.version from 2.0.15 to 2.0.16

Bumps `slf4j.version` from 2.0.15 to 2.0.16.

Updates `org.slf4j:slf4j-api` from 2.0.15 to 2.0.16

Updates `org.slf4j:jcl-over-slf4j` from 2.0.15 to 2.0.16

Updates `org.slf4j:jul-to-slf4j` from 2.0.15 to 2.0.16

Updates `org.slf4j:log4j-over-slf4j` from 2.0.15 to 2.0.16

---
updated-dependencies:
- dependency-name: org.slf4j:slf4j-api
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.slf4j:jcl-over-slf4j
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.slf4j:jul-to-slf4j
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.slf4j:log4j-over-slf4j
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #687: Bump slf4j.version from 2.0.15 to 2.0.16

13344 of 39166 relevant lines covered (34.07%)

0.34 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

40.57
/utils/src/main/java/com/aspectran/utils/ConcurrentReferenceHashMap.java
1
/*
2
 * Copyright (c) 2008-2024 The Aspectran Project
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
package com.aspectran.utils;
17

18
import com.aspectran.utils.annotation.jsr305.NonNull;
19
import com.aspectran.utils.annotation.jsr305.Nullable;
20

21
import java.lang.ref.ReferenceQueue;
22
import java.lang.ref.SoftReference;
23
import java.lang.ref.WeakReference;
24
import java.lang.reflect.Array;
25
import java.util.AbstractMap;
26
import java.util.AbstractSet;
27
import java.util.Collections;
28
import java.util.EnumSet;
29
import java.util.HashSet;
30
import java.util.Iterator;
31
import java.util.Map;
32
import java.util.NoSuchElementException;
33
import java.util.Set;
34
import java.util.concurrent.ConcurrentHashMap;
35
import java.util.concurrent.ConcurrentMap;
36
import java.util.concurrent.atomic.AtomicInteger;
37
import java.util.concurrent.locks.ReentrantLock;
38

39
/**
40
 * <p>This class is a clone of org.springframework.util.ConcurrentReferenceHashMap</p>
41
 *
42
 * A {@link ConcurrentHashMap} that uses {@link ReferenceType#SOFT soft} or
43
 * {@linkplain ReferenceType#WEAK weak} references for both {@code keys} and {@code values}.
44
 *
45
 * <p>This class can be used as an alternative to
46
 * {@code Collections.synchronizedMap(new WeakHashMap<K, Reference<V>>())} in order to
47
 * support better performance when accessed concurrently. This implementation follows the
48
 * same design constraints as {@link ConcurrentHashMap} with the exception that
49
 * {@code null} values and {@code null} keys are supported.</p>
50
 *
51
 * <p><b>NOTE:</b> The use of references means that there is no guarantee that items
52
 * placed into the map will be subsequently available. The garbage collector may discard
53
 * references at any time, so it may appear that an unknown thread is silently removing
54
 * entries.</p>
55
 *
56
 * <p>If not explicitly specified, this implementation will use
57
 * {@linkplain SoftReference soft entry references}.</p>
58
 *
59
 * @param <K> the key type
60
 * @param <V> the value type
61
 * @author Phillip Webb
62
 * @author Juergen Hoeller
63
 */
64
public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V> {
65

66
    private static final int DEFAULT_INITIAL_CAPACITY = 16;
67

68
    private static final float DEFAULT_LOAD_FACTOR = 0.75f;
69

70
    private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
71

72
    private static final ReferenceType DEFAULT_REFERENCE_TYPE = ReferenceType.SOFT;
1✔
73

74
    private static final int MAXIMUM_CONCURRENCY_LEVEL = 1 << 16;
75

76
    private static final int MAXIMUM_SEGMENT_SIZE = 1 << 30;
77

78
    /**
79
     * Array of segments indexed using the high order bits from the hash.
80
     */
81
    private final Segment[] segments;
82

83
    /**
84
     * When the average number of references per table exceeds this value resize will be attempted.
85
     */
86
    private final float loadFactor;
87

88
    /**
89
     * The reference type: SOFT or WEAK.
90
     */
91
    private final ReferenceType referenceType;
92

93
    /**
94
     * The shift value used to calculate the size of the segments array and an index from the hash.
95
     */
96
    private final int shift;
97

98
    /**
99
     * Late binding entry set.
100
     */
101
    @Nullable
102
    private volatile Set<Map.Entry<K, V>> entrySet;
103

104
    /**
105
     * Create a new {@code ConcurrentReferenceHashMap} instance.
106
     */
107
    public ConcurrentReferenceHashMap() {
108
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL, DEFAULT_REFERENCE_TYPE);
1✔
109
    }
1✔
110

111
    /**
112
     * Create a new {@code ConcurrentReferenceHashMap} instance.
113
     * @param initialCapacity the initial capacity of the map
114
     */
115
    public ConcurrentReferenceHashMap(int initialCapacity) {
116
        this(initialCapacity, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL, DEFAULT_REFERENCE_TYPE);
1✔
117
    }
1✔
118

119
    /**
120
     * Create a new {@code ConcurrentReferenceHashMap} instance.
121
     * @param initialCapacity the initial capacity of the map
122
     * @param loadFactor the load factor. When the average number of references per table
123
     * exceeds this value resize will be attempted
124
     */
125
    public ConcurrentReferenceHashMap(int initialCapacity, float loadFactor) {
126
        this(initialCapacity, loadFactor, DEFAULT_CONCURRENCY_LEVEL, DEFAULT_REFERENCE_TYPE);
×
127
    }
×
128

129
    /**
130
     * Create a new {@code ConcurrentReferenceHashMap} instance.
131
     * @param initialCapacity the initial capacity of the map
132
     * @param concurrencyLevel the expected number of threads that will concurrently
133
     * write to the map
134
     */
135
    public ConcurrentReferenceHashMap(int initialCapacity, int concurrencyLevel) {
136
        this(initialCapacity, DEFAULT_LOAD_FACTOR, concurrencyLevel, DEFAULT_REFERENCE_TYPE);
×
137
    }
×
138

139
    /**
140
     * Create a new {@code ConcurrentReferenceHashMap} instance.
141
     * @param initialCapacity the initial capacity of the map
142
     * @param referenceType the reference type used for entries (soft or weak)
143
     */
144
    public ConcurrentReferenceHashMap(int initialCapacity, ReferenceType referenceType) {
145
        this(initialCapacity, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL, referenceType);
×
146
    }
×
147

148
    /**
149
     * Create a new {@code ConcurrentReferenceHashMap} instance.
150
     * @param initialCapacity the initial capacity of the map
151
     * @param loadFactor the load factor. When the average number of references per
152
     * table exceeds this value, resize will be attempted.
153
     * @param concurrencyLevel the expected number of threads that will concurrently
154
     * write to the map
155
     */
156
    public ConcurrentReferenceHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) {
157
        this(initialCapacity, loadFactor, concurrencyLevel, DEFAULT_REFERENCE_TYPE);
×
158
    }
×
159

160
    /**
161
     * Create a new {@code ConcurrentReferenceHashMap} instance.
162
     * @param initialCapacity the initial capacity of the map
163
     * @param loadFactor the load factor. When the average number of references per
164
     * table exceeds this value, resize will be attempted.
165
     * @param concurrencyLevel the expected number of threads that will concurrently
166
     * write to the map
167
     * @param referenceType the reference type used for entries (soft or weak)
168
     */
169
    @SuppressWarnings("unchecked")
170
    public ConcurrentReferenceHashMap(int initialCapacity, float loadFactor, int concurrencyLevel, ReferenceType referenceType) {
1✔
171
        Assert.isTrue(initialCapacity >= 0, "Initial capacity must not be negative");
1✔
172
        Assert.isTrue(loadFactor > 0f, "Load factor must be positive");
1✔
173
        Assert.isTrue(concurrencyLevel > 0, "Concurrency level must be positive");
1✔
174
        Assert.notNull(referenceType, "Reference type must not be null");
1✔
175
        this.loadFactor = loadFactor;
1✔
176
        this.shift = calculateShift(concurrencyLevel, MAXIMUM_CONCURRENCY_LEVEL);
1✔
177
        int size = 1 << this.shift;
1✔
178
        this.referenceType = referenceType;
1✔
179
        int roundedUpSegmentCapacity = (int)((initialCapacity + size - 1L) / size);
1✔
180
        int initialSize = 1 << calculateShift(roundedUpSegmentCapacity, MAXIMUM_SEGMENT_SIZE);
1✔
181
        Segment[] segments = (Segment[])Array.newInstance(Segment.class, size);
1✔
182
        int resizeThreshold = (int)(initialSize * getLoadFactor());
1✔
183
        for (int i = 0; i < segments.length; i++) {
1✔
184
            segments[i] = new Segment(initialSize, resizeThreshold);
1✔
185
        }
186
        this.segments = segments;
1✔
187
    }
1✔
188

189

190
    protected final float getLoadFactor() {
191
        return this.loadFactor;
1✔
192
    }
193

194
    protected final int getSegmentsSize() {
195
        return this.segments.length;
×
196
    }
197

198
    protected final Segment getSegment(int index) {
199
        return this.segments[index];
×
200
    }
201

202
    /**
203
     * Factory method that returns the {@link ReferenceManager}.
204
     * This method will be called once for each {@link Segment}.
205
     * @return a new reference manager
206
     */
207
    protected ReferenceManager createReferenceManager() {
208
        return new ReferenceManager();
1✔
209
    }
210

211
    /**
212
     * Get the hash for a given object, apply an additional hash function to reduce
213
     * collisions. This implementation uses the same Wang/Jenkins algorithm as
214
     * {@link ConcurrentHashMap}. Subclasses can override to provide alternative hashing.
215
     * @param o the object to hash (may be null)
216
     * @return the resulting hash code
217
     */
218
    protected int getHash(@Nullable Object o) {
219
        int hash = (o != null ? o.hashCode() : 0);
1✔
220
        hash += (hash << 15) ^ 0xffffcd7d;
1✔
221
        hash ^= (hash >>> 10);
1✔
222
        hash += (hash << 3);
1✔
223
        hash ^= (hash >>> 6);
1✔
224
        hash += (hash << 2) + (hash << 14);
1✔
225
        hash ^= (hash >>> 16);
1✔
226
        return hash;
1✔
227
    }
228

229
    @Override
230
    @Nullable
231
    public V get(@Nullable Object key) {
232
        Reference<K, V> ref = getReference(key, Restructure.WHEN_NECESSARY);
1✔
233
        Entry<K, V> entry = (ref != null ? ref.get() : null);
1✔
234
        return (entry != null ? entry.getValue() : null);
1✔
235
    }
236

237
    @Override
238
    @Nullable
239
    public V getOrDefault(@Nullable Object key, @Nullable V defaultValue) {
240
        Reference<K, V> ref = getReference(key, Restructure.WHEN_NECESSARY);
×
241
        Entry<K, V> entry = (ref != null ? ref.get() : null);
×
242
        return (entry != null ? entry.getValue() : defaultValue);
×
243
    }
244

245
    @Override
246
    public boolean containsKey(@Nullable Object key) {
247
        Reference<K, V> ref = getReference(key, Restructure.WHEN_NECESSARY);
×
248
        Entry<K, V> entry = (ref != null ? ref.get() : null);
×
249
        return (entry != null && ObjectUtils.nullSafeEquals(entry.getKey(), key));
×
250
    }
251

252
    /**
253
     * Return a {@link Reference} to the {@link Entry} for the specified {@code key},
254
     * or {@code null} if not found.
255
     * @param key the key (can be {@code null})
256
     * @param restructure types of restructure allowed during this call
257
     * @return the reference, or {@code null} if not found
258
     */
259
    @Nullable
260
    protected final Reference<K, V> getReference(@Nullable Object key, Restructure restructure) {
261
        int hash = getHash(key);
1✔
262
        return getSegmentForHash(hash).getReference(key, hash, restructure);
1✔
263
    }
264

265
    @Override
266
    @Nullable
267
    public V put(@Nullable K key, @Nullable V value) {
268
        return put(key, value, true);
1✔
269
    }
270

271
    @Override
272
    @Nullable
273
    public V putIfAbsent(@Nullable K key, @Nullable V value) {
274
        return put(key, value, false);
1✔
275
    }
276

277
    @Nullable
278
    private V put(@Nullable final K key, @Nullable final V value, final boolean overwriteExisting) {
279
        return doTask(key, new Task<V>(TaskOption.RESTRUCTURE_BEFORE, TaskOption.RESIZE) {
1✔
280
            @Override
281
            @Nullable
282
            protected V execute(@Nullable Reference<K, V> ref, @Nullable Entry<K, V> entry, @Nullable Entries<V> entries) {
283
                if (entry != null) {
1✔
284
                    V oldValue = entry.getValue();
×
285
                    if (overwriteExisting) {
×
286
                        entry.setValue(value);
×
287
                    }
288
                    return oldValue;
×
289
                }
290
                Assert.state(entries != null, "No entries segment");
1✔
291
                entries.add(value);
1✔
292
                return null;
1✔
293
            }
294
        });
295
    }
296

297
    @Override
298
    @Nullable
299
    public V remove(Object key) {
300
        return doTask(key, new Task<V>(TaskOption.RESTRUCTURE_AFTER, TaskOption.SKIP_IF_EMPTY) {
×
301
            @Override
302
            @Nullable
303
            protected V execute(@Nullable Reference<K, V> ref, @Nullable Entry<K, V> entry) {
304
                if (entry != null) {
×
305
                    if (ref != null) {
×
306
                        ref.release();
×
307
                    }
308
                    return entry.value;
×
309
                }
310
                return null;
×
311
            }
312
        });
313
    }
314

315
    @Override
316
    public boolean remove(@NonNull Object key, final Object value) {
317
        Boolean result = doTask(key, new Task<Boolean>(TaskOption.RESTRUCTURE_AFTER, TaskOption.SKIP_IF_EMPTY) {
×
318
            @Override
319
            protected Boolean execute(@Nullable Reference<K, V> ref, @Nullable Entry<K, V> entry) {
320
                if (entry != null && ObjectUtils.nullSafeEquals(entry.getValue(), value)) {
×
321
                    if (ref != null) {
×
322
                        ref.release();
×
323
                    }
324
                    return true;
×
325
                }
326
                return false;
×
327
            }
328
        });
329
        return Boolean.TRUE.equals(result);
×
330
    }
331

332
    @Override
333
    public boolean replace(@NonNull K key, @NonNull final V oldValue, @NonNull final V newValue) {
334
        Boolean result = doTask(key, new Task<Boolean>(TaskOption.RESTRUCTURE_BEFORE, TaskOption.SKIP_IF_EMPTY) {
×
335
            @Override
336
            protected Boolean execute(@Nullable Reference<K, V> ref, @Nullable Entry<K, V> entry) {
337
                if (entry != null && ObjectUtils.nullSafeEquals(entry.getValue(), oldValue)) {
×
338
                    entry.setValue(newValue);
×
339
                    return true;
×
340
                }
341
                return false;
×
342
            }
343
        });
344
        return Boolean.TRUE.equals(result);
×
345
    }
346

347
    @Override
348
    @Nullable
349
    public V replace(@NonNull K key, @NonNull final V value) {
350
        return doTask(key, new Task<V>(TaskOption.RESTRUCTURE_BEFORE, TaskOption.SKIP_IF_EMPTY) {
×
351
            @Override
352
            @Nullable
353
            protected V execute(@Nullable Reference<K, V> ref, @Nullable Entry<K, V> entry) {
354
                if (entry != null) {
×
355
                    V oldValue = entry.getValue();
×
356
                    entry.setValue(value);
×
357
                    return oldValue;
×
358
                }
359
                return null;
×
360
            }
361
        });
362
    }
363

364
    @Override
365
    public void clear() {
366
        for (Segment segment : this.segments) {
×
367
            segment.clear();
×
368
        }
369
    }
×
370

371
    /**
372
     * Remove any entries that have been garbage collected and are no longer referenced.
373
     * Under normal circumstances garbage collected entries are automatically purged as
374
     * items are added or removed from the Map. This method can be used to force a purge,
375
     * and is useful when the Map is read frequently but updated less often.
376
     */
377
    public void purgeUnreferencedEntries() {
378
        for (Segment segment : this.segments) {
×
379
            segment.restructureIfNecessary(false);
×
380
        }
381
    }
×
382

383
    @Override
384
    public int size() {
385
        int size = 0;
×
386
        for (Segment segment : this.segments) {
×
387
            size += segment.getCount();
×
388
        }
389
        return size;
×
390
    }
391

392
    @Override
393
    public boolean isEmpty() {
394
        for (Segment segment : this.segments) {
×
395
            if (segment.getCount() > 0) {
×
396
                return false;
×
397
            }
398
        }
399
        return true;
×
400
    }
401

402
    @Override
403
    public Set<Map.Entry<K, V>> entrySet() {
404
        Set<Map.Entry<K, V>> entrySet = this.entrySet;
×
405
        if (entrySet == null) {
×
406
            entrySet = new EntrySet();
×
407
            this.entrySet = entrySet;
×
408
        }
409
        return entrySet;
×
410
    }
411

412
    @Nullable
413
    private <T> T doTask(@Nullable Object key, Task<T> task) {
414
        int hash = getHash(key);
1✔
415
        return getSegmentForHash(hash).doTask(hash, key, task);
1✔
416
    }
417

418
    private Segment getSegmentForHash(int hash) {
419
        return this.segments[(hash >>> (32 - this.shift)) & (this.segments.length - 1)];
1✔
420
    }
421

422
    /**
423
     * Calculate a shift value that can be used to create a power-of-two value between
424
     * the specified maximum and minimum values.
425
     * @param minimumValue the minimum value
426
     * @param maximumValue the maximum value
427
     * @return the calculated shift (use {@code 1 << shift} to obtain a value)
428
     */
429
    protected static int calculateShift(int minimumValue, int maximumValue) {
430
        int shift = 0;
1✔
431
        int value = 1;
1✔
432
        while (value < minimumValue && value < maximumValue) {
1✔
433
            value <<= 1;
1✔
434
            shift++;
1✔
435
        }
436
        return shift;
1✔
437
    }
438

439

440
    /**
441
     * Various reference types supported by this map.
442
     */
443
    public enum ReferenceType {
1✔
444

445
        /**
446
         * Use {@link SoftReference SoftReferences}.
447
         */
448
        SOFT,
1✔
449

450
        /**
451
         * Use {@link WeakReference WeakReferences}.
452
         */
453
        WEAK
1✔
454

455
    }
456

457

458
    /**
459
     * A single segment used to divide the map to allow better concurrent performance.
460
     */
461
    protected final class Segment extends ReentrantLock {
462

463
        private final ReferenceManager referenceManager;
464

465
        private final int initialSize;
466

467
        /**
468
         * Array of references indexed using the low order bits from the hash.
469
         * This property should only be set along with {@code resizeThreshold}.
470
         */
471
        private volatile Reference<K, V>[] references;
472

473
        /**
474
         * The total number of references contained in this segment. This includes chained
475
         * references and references that have been garbage collected but not purged.
476
         */
477
        private final AtomicInteger count = new AtomicInteger();
1✔
478

479
        /**
480
         * The threshold when resizing of the references should occur. When {@code count}
481
         * exceeds this value references will be resized.
482
         */
483
        private int resizeThreshold;
484

485
        public Segment(int initialSize, int resizeThreshold) {
1✔
486
            this.referenceManager = createReferenceManager();
1✔
487
            this.initialSize = initialSize;
1✔
488
            this.references = createReferenceArray(initialSize);
1✔
489
            this.resizeThreshold = resizeThreshold;
1✔
490
        }
1✔
491

492
        @Nullable
493
        public Reference<K, V> getReference(@Nullable Object key, int hash, Restructure restructure) {
494
            if (restructure == Restructure.WHEN_NECESSARY) {
1✔
495
                restructureIfNecessary(false);
1✔
496
            }
497
            if (this.count.get() == 0) {
1✔
498
                return null;
1✔
499
            }
500
            // Use a local copy to protect against other threads writing
501
            Reference<K, V>[] references = this.references;
1✔
502
            int index = getIndex(hash, references);
1✔
503
            Reference<K, V> head = references[index];
1✔
504
            return findInChain(head, key, hash);
1✔
505
        }
506

507
        /**
508
         * Apply an update operation to this segment.
509
         * The segment will be locked during the update.
510
         *
511
         * @param hash the hash of the key
512
         * @param key the key
513
         * @param task the update operation
514
         * @return the result of the operation
515
         */
516
        @Nullable
517
        private <T> T doTask(final int hash, @Nullable final Object key, @NonNull final Task<T> task) {
518
            boolean resize = task.hasOption(TaskOption.RESIZE);
1✔
519
            if (task.hasOption(TaskOption.RESTRUCTURE_BEFORE)) {
1✔
520
                restructureIfNecessary(resize);
1✔
521
            }
522
            if (task.hasOption(TaskOption.SKIP_IF_EMPTY) && this.count.get() == 0) {
1✔
523
                return task.execute(null, null, null);
×
524
            }
525
            lock();
1✔
526
            try {
527
                final int index = getIndex(hash, this.references);
1✔
528
                final Reference<K, V> head = this.references[index];
1✔
529
                Reference<K, V> ref = findInChain(head, key, hash);
1✔
530
                Entry<K, V> entry = (ref != null ? ref.get() : null);
1✔
531
                Entries<V> entries = value -> {
1✔
532
                    @SuppressWarnings("unchecked")
533
                    Entry<K, V> newEntry = new Entry<>((K)key, value);
1✔
534
                    Reference<K, V> newReference = Segment.this.referenceManager.createReference(newEntry, hash, head);
1✔
535
                    Segment.this.references[index] = newReference;
1✔
536
                    Segment.this.count.incrementAndGet();
1✔
537
                };
1✔
538
                return task.execute(ref, entry, entries);
1✔
539
            } finally {
540
                unlock();
1✔
541
                if (task.hasOption(TaskOption.RESTRUCTURE_AFTER)) {
1✔
542
                    restructureIfNecessary(resize);
×
543
                }
544
            }
545
        }
546

547
        /**
548
         * Clear all items from this segment.
549
         */
550
        public void clear() {
551
            if (this.count.get() == 0) {
×
552
                return;
×
553
            }
554
            lock();
×
555
            try {
556
                this.references = createReferenceArray(this.initialSize);
×
557
                this.resizeThreshold = (int)(this.references.length * getLoadFactor());
×
558
                this.count.set(0);
×
559
            } finally {
560
                unlock();
×
561
            }
562
        }
×
563

564
        /**
565
         * Restructure the underlying data structure when it becomes necessary. This
566
         * method can increase the size of the references table as well as purge any
567
         * references that have been garbage collected.
568
         *
569
         * @param allowResize if resizing is permitted
570
         */
571
        private final void restructureIfNecessary(boolean allowResize) {
572
            int currCount = this.count.get();
1✔
573
            boolean needsResize = allowResize && (currCount > 0 && currCount >= this.resizeThreshold);
1✔
574
            Reference<K, V> ref = this.referenceManager.pollForPurge();
1✔
575
            if (ref != null || (needsResize)) {
1✔
576
                restructure(allowResize, ref);
×
577
            }
578
        }
1✔
579

580
        private void restructure(boolean allowResize, @Nullable Reference<K, V> ref) {
581
            boolean needsResize;
582
            lock();
×
583
            try {
584
                int countAfterRestructure = this.count.get();
×
585
                Set<Reference<K, V>> toPurge = Collections.emptySet();
×
586
                if (ref != null) {
×
587
                    toPurge = new HashSet<>();
×
588
                    while (ref != null) {
×
589
                        toPurge.add(ref);
×
590
                        ref = this.referenceManager.pollForPurge();
×
591
                    }
592
                }
593
                countAfterRestructure -= toPurge.size();
×
594

595
                // Recalculate taking into account count inside lock and items that
596
                // will be purged
597
                needsResize = (countAfterRestructure > 0 && countAfterRestructure >= this.resizeThreshold);
×
598
                boolean resizing = false;
×
599
                int restructureSize = this.references.length;
×
600
                if (allowResize && needsResize && restructureSize < MAXIMUM_SEGMENT_SIZE) {
×
601
                    restructureSize <<= 1;
×
602
                    resizing = true;
×
603
                }
604

605
                // Either create a new table or reuse the existing one
606
                Reference<K, V>[] restructured = (resizing ? createReferenceArray(restructureSize) : this.references);
×
607

608
                // Restructure
609
                for (int i = 0; i < this.references.length; i++) {
×
610
                    ref = this.references[i];
×
611
                    if (!resizing) {
×
612
                        restructured[i] = null;
×
613
                    }
614
                    while (ref != null) {
×
615
                        if (!toPurge.contains(ref)) {
×
616
                            Entry<K, V> entry = ref.get();
×
617
                            if (entry != null) {
×
618
                                int index = getIndex(ref.getHash(), restructured);
×
619
                                restructured[index] = this.referenceManager.createReference(
×
620
                                        entry, ref.getHash(), restructured[index]);
×
621
                            }
622
                        }
623
                        ref = ref.getNext();
×
624
                    }
625
                }
626

627
                // Replace volatile members
628
                if (resizing) {
×
629
                    this.references = restructured;
×
630
                    this.resizeThreshold = (int)(this.references.length * getLoadFactor());
×
631
                }
632
                this.count.set(Math.max(countAfterRestructure, 0));
×
633
            } finally {
634
                unlock();
×
635
            }
636
        }
×
637

638
        @Nullable
639
        private Reference<K, V> findInChain(Reference<K, V> ref, @Nullable Object key, int hash) {
640
            Reference<K, V> currRef = ref;
1✔
641
            while (currRef != null) {
1✔
642
                if (currRef.getHash() == hash) {
1✔
643
                    Entry<K, V> entry = currRef.get();
1✔
644
                    if (entry != null) {
1✔
645
                        K entryKey = entry.getKey();
1✔
646
                        if (ObjectUtils.nullSafeEquals(entryKey, key)) {
1✔
647
                            return currRef;
1✔
648
                        }
649
                    }
650
                }
651
                currRef = currRef.getNext();
×
652
            }
653
            return null;
1✔
654
        }
655

656
        @SuppressWarnings({"unchecked"})
657
        @NonNull
658
        private Reference<K, V>[] createReferenceArray(int size) {
659
            return new Reference[size];
1✔
660
        }
661

662
        @NonNull
663
        private int getIndex(int hash, @NonNull Reference<K, V>[] references) {
664
            return (hash & (references.length - 1));
1✔
665
        }
666

667
        /**
668
         * Return the size of the current references array.
669
         */
670
        public final int getSize() {
671
            return this.references.length;
×
672
        }
673

674
        /**
675
         * Return the total number of references in this segment.
676
         */
677
        public final int getCount() {
678
            return this.count.get();
×
679
        }
680

681
    }
682

683

684
    /**
685
     * A reference to an {@link Entry} contained in the map. Implementations are usually
686
     * wrappers around specific Java reference implementations (e.g., {@link SoftReference}).
687
     * @param <K> the key type
688
     * @param <V> the value type
689
     */
690
    protected interface Reference<K, V> {
691

692
        /**
693
         * Return the referenced entry, or {@code null} if the entry is no longer available.
694
         */
695
        @Nullable
696
        Entry<K, V> get();
697

698
        /**
699
         * Return the hash for the reference.
700
         */
701
        int getHash();
702

703
        /**
704
         * Return the next reference in the chain, or {@code null} if none.
705
         */
706
        @Nullable
707
        Reference<K, V> getNext();
708

709
        /**
710
         * Release this entry and ensure that it will be returned from
711
         * {@code ReferenceManager#pollForPurge()}.
712
         */
713
        void release();
714

715
    }
716

717

718
    /**
719
     * A single map entry.
720
     * @param <K> the key type
721
     * @param <V> the value type
722
     */
723
    protected static final class Entry<K, V> implements Map.Entry<K, V> {
724

725
        @Nullable
726
        private final K key;
727

728
        @Nullable
729
        private volatile V value;
730

731
        public Entry(@Nullable K key, @Nullable V value) {
1✔
732
            this.key = key;
1✔
733
            this.value = value;
1✔
734
        }
1✔
735

736
        @Override
737
        @Nullable
738
        public K getKey() {
739
            return this.key;
1✔
740
        }
741

742
        @Override
743
        @Nullable
744
        public V getValue() {
745
            return this.value;
1✔
746
        }
747

748
        @Override
749
        @Nullable
750
        public V setValue(@Nullable V value) {
751
            V previous = this.value;
×
752
            this.value = value;
×
753
            return previous;
×
754
        }
755

756
        @Override
757
        @NonNull
758
        public String toString() {
759
            return (this.key + "=" + this.value);
×
760
        }
761

762
        @Override
763
        @SuppressWarnings("rawtypes")
764
        public boolean equals(@Nullable Object other) {
765
            if (this == other) {
×
766
                return true;
×
767
            }
768
            if (!(other instanceof Map.Entry otherEntry)) {
×
769
                return false;
×
770
            }
771
            return (ObjectUtils.nullSafeEquals(getKey(), otherEntry.getKey()) &&
×
772
                    ObjectUtils.nullSafeEquals(getValue(), otherEntry.getValue()));
×
773
        }
774

775
        @Override
776
        public int hashCode() {
777
            return (ObjectUtils.nullSafeHashCode(this.key) ^ ObjectUtils.nullSafeHashCode(this.value));
×
778
        }
779
    }
780

781

782
    /**
783
     * A task that can be {@link Segment#doTask run} against a {@link Segment}.
784
     */
785
    private abstract class Task<T> {
786

787
        private final EnumSet<TaskOption> options;
788

789
        public Task(@NonNull TaskOption... options) {
1✔
790
            this.options = (options.length == 0 ? EnumSet.noneOf(TaskOption.class) : EnumSet.of(options[0], options));
1✔
791
        }
1✔
792

793
        public boolean hasOption(TaskOption option) {
794
            return this.options.contains(option);
1✔
795
        }
796

797
        /**
798
         * Execute the task.
799
         *
800
         * @param ref the found reference (or {@code null})
801
         * @param entry the found entry (or {@code null})
802
         * @param entries access to the underlying entries
803
         * @return the result of the task
804
         * @see #execute(Reference, Entry)
805
         */
806
        @Nullable
807
        protected T execute(@Nullable Reference<K, V> ref, @Nullable Entry<K, V> entry, @Nullable Entries<V> entries) {
808
            return execute(ref, entry);
×
809
        }
810

811
        /**
812
         * Convenience method that can be used for tasks that do not need access to {@link Entries}.
813
         *
814
         * @param ref the found reference (or {@code null})
815
         * @param entry the found entry (or {@code null})
816
         * @return the result of the task
817
         * @see #execute(Reference, Entry, Entries)
818
         */
819
        @Nullable
820
        protected T execute(@Nullable Reference<K, V> ref, @Nullable Entry<K, V> entry) {
821
            return null;
×
822
        }
823

824
    }
825

826

827
    /**
828
     * Various options supported by a {@code Task}.
829
     */
830
    private enum TaskOption {
1✔
831
        RESTRUCTURE_BEFORE, RESTRUCTURE_AFTER, SKIP_IF_EMPTY, RESIZE
1✔
832
    }
833

834

835
    /**
836
     * Allows a task access to {@link ConcurrentReferenceHashMap.Segment} entries.
837
     */
838
    private interface Entries<V> {
839

840
        /**
841
         * Add a new entry with the specified value.
842
         *
843
         * @param value the value to add
844
         */
845
        void add(@Nullable V value);
846

847
    }
848

849

850
    /**
851
     * Internal entry-set implementation.
852
     */
853
    private class EntrySet extends AbstractSet<Map.Entry<K, V>> {
×
854

855
        @Override
856
        @NonNull
857
        public Iterator<Map.Entry<K, V>> iterator() {
858
            return new EntryIterator();
×
859
        }
860

861
        @Override
862
        public boolean contains(@Nullable Object o) {
863
            if (o instanceof Map.Entry<?, ?> entry) {
×
864
                Reference<K, V> ref = ConcurrentReferenceHashMap.this.getReference(entry.getKey(), Restructure.NEVER);
×
865
                Entry<K, V> otherEntry = (ref != null ? ref.get() : null);
×
866
                if (otherEntry != null) {
×
867
                    return ObjectUtils.nullSafeEquals(entry.getValue(), otherEntry.getValue());
×
868
                }
869
            }
870
            return false;
×
871
        }
872

873
        @Override
874
        public boolean remove(Object o) {
875
            if (o instanceof Map.Entry<?, ?> entry) {
×
876
                return ConcurrentReferenceHashMap.this.remove(entry.getKey(), entry.getValue());
×
877
            }
878
            return false;
×
879
        }
880

881
        @Override
882
        public int size() {
883
            return ConcurrentReferenceHashMap.this.size();
×
884
        }
885

886
        @Override
887
        public void clear() {
888
            ConcurrentReferenceHashMap.this.clear();
×
889
        }
×
890

891
    }
892

893

894
    /**
895
     * Internal entry iterator implementation.
896
     */
897
    private class EntryIterator implements Iterator<Map.Entry<K, V>> {
898

899
        private int segmentIndex;
900

901
        private int referenceIndex;
902

903
        @Nullable
904
        private Reference<K, V>[] references;
905

906
        @Nullable
907
        private Reference<K, V> reference;
908

909
        @Nullable
910
        private Entry<K, V> next;
911

912
        @Nullable
913
        private Entry<K, V> last;
914

915
        public EntryIterator() {
×
916
            moveToNextSegment();
×
917
        }
×
918

919
        @Override
920
        public boolean hasNext() {
921
            getNextIfNecessary();
×
922
            return (this.next != null);
×
923
        }
924

925
        @Override
926
        public Entry<K, V> next() {
927
            getNextIfNecessary();
×
928
            if (this.next == null) {
×
929
                throw new NoSuchElementException();
×
930
            }
931
            this.last = this.next;
×
932
            this.next = null;
×
933
            return this.last;
×
934
        }
935

936
        private void getNextIfNecessary() {
937
            while (this.next == null) {
×
938
                moveToNextReference();
×
939
                if (this.reference == null) {
×
940
                    return;
×
941
                }
942
                this.next = this.reference.get();
×
943
            }
944
        }
×
945

946
        private void moveToNextReference() {
947
            if (this.reference != null) {
×
948
                this.reference = this.reference.getNext();
×
949
            }
950
            while (this.reference == null && this.references != null) {
×
951
                if (this.referenceIndex >= this.references.length) {
×
952
                    moveToNextSegment();
×
953
                    this.referenceIndex = 0;
×
954
                } else {
955
                    this.reference = this.references[this.referenceIndex];
×
956
                    this.referenceIndex++;
×
957
                }
958
            }
959
        }
×
960

961
        private void moveToNextSegment() {
962
            this.reference = null;
×
963
            this.references = null;
×
964
            if (this.segmentIndex < ConcurrentReferenceHashMap.this.segments.length) {
×
965
                this.references = ConcurrentReferenceHashMap.this.segments[this.segmentIndex].references;
×
966
                this.segmentIndex++;
×
967
            }
968
        }
×
969

970
        @Override
971
        public void remove() {
972
            Assert.state(this.last != null, "No element to remove");
×
973
            ConcurrentReferenceHashMap.this.remove(this.last.getKey());
×
974
            this.last = null;
×
975
        }
×
976

977
    }
978

979

980
    /**
981
     * The types of restructuring that can be performed.
982
     */
983
    protected enum Restructure {
1✔
984
        WHEN_NECESSARY, NEVER
1✔
985
    }
986

987

988
    /**
989
     * Strategy class used to manage {@link Reference References}.
990
     * This class can be overridden if alternative reference types need to be supported.
991
     */
992
    protected class ReferenceManager {
1✔
993

994
        private final ReferenceQueue<Entry<K, V>> queue = new ReferenceQueue<>();
1✔
995

996
        /**
997
         * Factory method used to create a new {@link Reference}.
998
         *
999
         * @param entry the entry contained in the reference
1000
         * @param hash the hash
1001
         * @param next the next reference in the chain, or {@code null} if none
1002
         * @return a new {@link Reference}
1003
         */
1004
        public Reference<K, V> createReference(Entry<K, V> entry, int hash, @Nullable Reference<K, V> next) {
1005
            if (ConcurrentReferenceHashMap.this.referenceType == ReferenceType.WEAK) {
1✔
1006
                return new WeakEntryReference<>(entry, hash, next, this.queue);
×
1007
            }
1008
            return new SoftEntryReference<>(entry, hash, next, this.queue);
1✔
1009
        }
1010

1011
        /**
1012
         * Return any reference that has been garbage collected and can be purged from the
1013
         * underlying structure or {@code null} if no references need purging. This
1014
         * method must be thread safe and ideally should not block when returning
1015
         * {@code null}. References should be returned once and only once.
1016
         *
1017
         * @return a reference to purge or {@code null}
1018
         */
1019
        @SuppressWarnings("unchecked")
1020
        @Nullable
1021
        public Reference<K, V> pollForPurge() {
1022
            return (Reference<K, V>)this.queue.poll();
1✔
1023
        }
1024

1025
    }
1026

1027

1028
    /**
1029
     * Internal {@link Reference} implementation for {@link SoftReference SoftReferences}.
1030
     */
1031
    private static final class SoftEntryReference<K, V> extends SoftReference<Entry<K, V>> implements Reference<K, V> {
1032

1033
        private final int hash;
1034

1035
        @Nullable
1036
        private final Reference<K, V> nextReference;
1037

1038
        public SoftEntryReference(Entry<K, V> entry, int hash, @Nullable Reference<K, V> next,
1039
                                  ReferenceQueue<Entry<K, V>> queue) {
1040
            super(entry, queue);
1✔
1041
            this.hash = hash;
1✔
1042
            this.nextReference = next;
1✔
1043
        }
1✔
1044

1045
        @Override
1046
        public int getHash() {
1047
            return this.hash;
1✔
1048
        }
1049

1050
        @Override
1051
        @Nullable
1052
        public Reference<K, V> getNext() {
1053
            return this.nextReference;
×
1054
        }
1055

1056
        @Override
1057
        public void release() {
1058
            enqueue();
×
1059
            clear();
×
1060
        }
×
1061

1062
    }
1063

1064

1065
    /**
1066
     * Internal {@link Reference} implementation for {@link WeakReference WeakReferences}.
1067
     */
1068
    private static final class WeakEntryReference<K, V> extends WeakReference<Entry<K, V>> implements Reference<K, V> {
1069

1070
        private final int hash;
1071

1072
        @Nullable
1073
        private final Reference<K, V> nextReference;
1074

1075
        public WeakEntryReference(Entry<K, V> entry, int hash, @Nullable Reference<K, V> next,
1076
                                  ReferenceQueue<Entry<K, V>> queue) {
1077
            super(entry, queue);
×
1078
            this.hash = hash;
×
1079
            this.nextReference = next;
×
1080
        }
×
1081

1082
        @Override
1083
        public int getHash() {
1084
            return this.hash;
×
1085
        }
1086

1087
        @Override
1088
        @Nullable
1089
        public Reference<K, V> getNext() {
1090
            return this.nextReference;
×
1091
        }
1092

1093
        @Override
1094
        public void release() {
1095
            enqueue();
×
1096
            clear();
×
1097
        }
×
1098

1099
    }
1100

1101
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc