|
|
|
@ -1,5 +1,5 @@ |
|
|
|
/* |
|
|
|
/* |
|
|
|
* Copyright 2002-2012 the original author or authors. |
|
|
|
* Copyright 2002-2013 the original author or authors. |
|
|
|
* |
|
|
|
* |
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
|
|
* you may not use this file except in compliance with the License. |
|
|
|
* you may not use this file except in compliance with the License. |
|
|
|
@ -56,8 +56,7 @@ import java.util.concurrent.locks.ReentrantLock; |
|
|
|
* @author Phillip Webb |
|
|
|
* @author Phillip Webb |
|
|
|
* @since 3.2 |
|
|
|
* @since 3.2 |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implements |
|
|
|
public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V> { |
|
|
|
ConcurrentMap<K, V> { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static final int DEFAULT_INITIAL_CAPACITY = 16; |
|
|
|
private static final int DEFAULT_INITIAL_CAPACITY = 16; |
|
|
|
|
|
|
|
|
|
|
|
@ -82,6 +81,9 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
private final float loadFactor; |
|
|
|
private final float loadFactor; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* The reference type: SOFT or WEAK. |
|
|
|
|
|
|
|
*/ |
|
|
|
private final ReferenceType referenceType; |
|
|
|
private final ReferenceType referenceType; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
@ -99,8 +101,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen |
|
|
|
* Create a new {@code ConcurrentReferenceHashMap} instance. |
|
|
|
* Create a new {@code ConcurrentReferenceHashMap} instance. |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public ConcurrentReferenceHashMap() { |
|
|
|
public ConcurrentReferenceHashMap() { |
|
|
|
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL, |
|
|
|
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL, DEFAULT_REFERENCE_TYPE); |
|
|
|
DEFAULT_REFERENCE_TYPE); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
@ -108,8 +109,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen |
|
|
|
* @param initialCapacity the initial capacity of the map |
|
|
|
* @param initialCapacity the initial capacity of the map |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public ConcurrentReferenceHashMap(int initialCapacity) { |
|
|
|
public ConcurrentReferenceHashMap(int initialCapacity) { |
|
|
|
this(initialCapacity, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL, |
|
|
|
this(initialCapacity, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL, DEFAULT_REFERENCE_TYPE); |
|
|
|
DEFAULT_REFERENCE_TYPE); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
@ -119,45 +119,44 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen |
|
|
|
* exceeds this value resize will be attempted |
|
|
|
* exceeds this value resize will be attempted |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public ConcurrentReferenceHashMap(int initialCapacity, float loadFactor) { |
|
|
|
public ConcurrentReferenceHashMap(int initialCapacity, float loadFactor) { |
|
|
|
this(initialCapacity, loadFactor, DEFAULT_CONCURRENCY_LEVEL, |
|
|
|
this(initialCapacity, loadFactor, DEFAULT_CONCURRENCY_LEVEL, DEFAULT_REFERENCE_TYPE); |
|
|
|
DEFAULT_REFERENCE_TYPE); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Create a new {@code ConcurrentReferenceHashMap} instance. |
|
|
|
* Create a new {@code ConcurrentReferenceHashMap} instance. |
|
|
|
* @param initialCapacity the initial capacity of the map |
|
|
|
* @param initialCapacity the initial capacity of the map |
|
|
|
* @param concurrencyLevel the expected number of threads that will concurrently write |
|
|
|
* @param concurrencyLevel the expected number of threads that will concurrently |
|
|
|
* to the map |
|
|
|
* write to the map |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public ConcurrentReferenceHashMap(int initialCapacity, int concurrencyLevel) { |
|
|
|
public ConcurrentReferenceHashMap(int initialCapacity, int concurrencyLevel) { |
|
|
|
this(initialCapacity, DEFAULT_LOAD_FACTOR, concurrencyLevel, |
|
|
|
this(initialCapacity, DEFAULT_LOAD_FACTOR, concurrencyLevel, DEFAULT_REFERENCE_TYPE); |
|
|
|
DEFAULT_REFERENCE_TYPE); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Create a new {@code ConcurrentReferenceHashMap} instance. |
|
|
|
* Create a new {@code ConcurrentReferenceHashMap} instance. |
|
|
|
* @param initialCapacity the initial capacity of the map |
|
|
|
* @param initialCapacity the initial capacity of the map |
|
|
|
* @param loadFactor the load factor. When the average number of references per table |
|
|
|
* @param loadFactor the load factor. When the average number of references per |
|
|
|
* exceeds this value resize will be attempted |
|
|
|
* table exceeds this value, resize will be attempted. |
|
|
|
* @param concurrencyLevel the expected number of threads that will concurrently write |
|
|
|
* @param concurrencyLevel the expected number of threads that will concurrently |
|
|
|
* to the map |
|
|
|
* write to the map |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public ConcurrentReferenceHashMap(int initialCapacity, float loadFactor, |
|
|
|
public ConcurrentReferenceHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) { |
|
|
|
int concurrencyLevel) { |
|
|
|
|
|
|
|
this(initialCapacity, loadFactor, concurrencyLevel, DEFAULT_REFERENCE_TYPE); |
|
|
|
this(initialCapacity, loadFactor, concurrencyLevel, DEFAULT_REFERENCE_TYPE); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Create a new {@code ConcurrentReferenceHashMap} instance. |
|
|
|
* Create a new {@code ConcurrentReferenceHashMap} instance. |
|
|
|
* @param initialCapacity the initial capacity of the map |
|
|
|
* @param initialCapacity the initial capacity of the map |
|
|
|
* @param loadFactor the load factor. When the average number of references per table |
|
|
|
* @param loadFactor the load factor. When the average number of references per |
|
|
|
* exceeds this value resize will be attempted |
|
|
|
* table exceeds this value, resize will be attempted. |
|
|
|
* @param concurrencyLevel the expected number of threads that will concurrently write |
|
|
|
* @param concurrencyLevel the expected number of threads that will concurrently |
|
|
|
* to the map |
|
|
|
* write to the map |
|
|
|
* @param referenceType the reference type used for entries |
|
|
|
* @param referenceType the reference type used for entries |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public ConcurrentReferenceHashMap(int initialCapacity, float loadFactor, |
|
|
|
@SuppressWarnings("unchecked") |
|
|
|
int concurrencyLevel, ReferenceType referenceType) { |
|
|
|
public ConcurrentReferenceHashMap(int initialCapacity, float loadFactor, int concurrencyLevel, |
|
|
|
|
|
|
|
ReferenceType referenceType) { |
|
|
|
|
|
|
|
|
|
|
|
Assert.isTrue(concurrencyLevel > 0, "ConcurrencyLevel must be positive"); |
|
|
|
Assert.isTrue(concurrencyLevel > 0, "ConcurrencyLevel must be positive"); |
|
|
|
Assert.isTrue(initialCapacity >= 0, "InitialCapacity must not be negative"); |
|
|
|
Assert.isTrue(initialCapacity >= 0, "InitialCapacity must not be negative"); |
|
|
|
Assert.isTrue(loadFactor > 0f, "LoadFactor must be positive"); |
|
|
|
Assert.isTrue(loadFactor > 0f, "LoadFactor must be positive"); |
|
|
|
@ -167,17 +166,12 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen |
|
|
|
int size = 1 << this.shift; |
|
|
|
int size = 1 << this.shift; |
|
|
|
this.referenceType = referenceType; |
|
|
|
this.referenceType = referenceType; |
|
|
|
int roundedUpSegmentCapactity = (int) ((initialCapacity + size - 1L) / size); |
|
|
|
int roundedUpSegmentCapactity = (int) ((initialCapacity + size - 1L) / size); |
|
|
|
this.segments = createSegmentsArray(size); |
|
|
|
this.segments = (Segment[]) Array.newInstance(Segment.class, size); |
|
|
|
for (int i = 0; i < this.segments.length; i++) { |
|
|
|
for (int i = 0; i < this.segments.length; i++) { |
|
|
|
this.segments[i] = new Segment(roundedUpSegmentCapactity); |
|
|
|
this.segments[i] = new Segment(roundedUpSegmentCapactity); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@SuppressWarnings("unchecked") |
|
|
|
|
|
|
|
private Segment[] createSegmentsArray(int size) { |
|
|
|
|
|
|
|
return (Segment[]) Array.newInstance(Segment.class, size); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
protected final float getLoadFactor() { |
|
|
|
protected final float getLoadFactor() { |
|
|
|
return this.loadFactor; |
|
|
|
return this.loadFactor; |
|
|
|
@ -222,7 +216,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen |
|
|
|
public V get(Object key) { |
|
|
|
public V get(Object key) { |
|
|
|
Reference<K, V> reference = getReference(key, Restructure.WHEN_NECESSARY); |
|
|
|
Reference<K, V> reference = getReference(key, Restructure.WHEN_NECESSARY); |
|
|
|
Entry<K, V> entry = (reference == null ? null : reference.get()); |
|
|
|
Entry<K, V> entry = (reference == null ? null : reference.get()); |
|
|
|
return (entry == null ? null : entry.getValue()); |
|
|
|
return (entry != null ? entry.getValue() : null); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
@Override |
|
|
|
@ -392,7 +386,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen |
|
|
|
/** |
|
|
|
/** |
|
|
|
* Use {@link WeakReference}s. |
|
|
|
* Use {@link WeakReference}s. |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
WEAK; |
|
|
|
WEAK |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -425,7 +419,6 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
private int resizeThreshold; |
|
|
|
private int resizeThreshold; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public Segment(int initialCapacity) { |
|
|
|
public Segment(int initialCapacity) { |
|
|
|
this.referenceManager = createReferenceManager(); |
|
|
|
this.referenceManager = createReferenceManager(); |
|
|
|
this.initialSize = 1 << calculateShift(initialCapacity, MAXIMUM_SEGMENT_SIZE); |
|
|
|
this.initialSize = 1 << calculateShift(initialCapacity, MAXIMUM_SEGMENT_SIZE); |
|
|
|
@ -456,17 +449,13 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen |
|
|
|
* @return the result of the operation |
|
|
|
* @return the result of the operation |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public <T> T doTask(final int hash, final Object key, final Task<T> task) { |
|
|
|
public <T> T doTask(final int hash, final Object key, final Task<T> task) { |
|
|
|
|
|
|
|
|
|
|
|
boolean resize = task.hasOption(TaskOption.RESIZE); |
|
|
|
boolean resize = task.hasOption(TaskOption.RESIZE); |
|
|
|
|
|
|
|
|
|
|
|
if (task.hasOption(TaskOption.RESTRUCTURE_BEFORE)) { |
|
|
|
if (task.hasOption(TaskOption.RESTRUCTURE_BEFORE)) { |
|
|
|
restructureIfNecessary(resize); |
|
|
|
restructureIfNecessary(resize); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (task.hasOption(TaskOption.SKIP_IF_EMPTY) && (this.count == 0)) { |
|
|
|
if (task.hasOption(TaskOption.SKIP_IF_EMPTY) && (this.count == 0)) { |
|
|
|
return task.execute(null, null, null); |
|
|
|
return task.execute(null, null, null); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
lock(); |
|
|
|
lock(); |
|
|
|
try { |
|
|
|
try { |
|
|
|
final int index = getIndex(hash, this.references); |
|
|
|
final int index = getIndex(hash, this.references); |
|
|
|
@ -484,7 +473,8 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen |
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
}; |
|
|
|
return task.execute(reference, entry, entries); |
|
|
|
return task.execute(reference, entry, entries); |
|
|
|
} finally { |
|
|
|
} |
|
|
|
|
|
|
|
finally { |
|
|
|
unlock(); |
|
|
|
unlock(); |
|
|
|
if (task.hasOption(TaskOption.RESTRUCTURE_AFTER)) { |
|
|
|
if (task.hasOption(TaskOption.RESTRUCTURE_AFTER)) { |
|
|
|
restructureIfNecessary(resize); |
|
|
|
restructureIfNecessary(resize); |
|
|
|
@ -573,8 +563,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private Reference<K, V> findInChain(Reference<K, V> reference, Object key, |
|
|
|
private Reference<K, V> findInChain(Reference<K, V> reference, Object key, int hash) { |
|
|
|
int hash) { |
|
|
|
|
|
|
|
while (reference != null) { |
|
|
|
while (reference != null) { |
|
|
|
if (reference.getHash() == hash) { |
|
|
|
if (reference.getHash() == hash) { |
|
|
|
Entry<K, V> entry = reference.get(); |
|
|
|
Entry<K, V> entry = reference.get(); |
|
|
|
@ -759,6 +748,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen |
|
|
|
* Various options supported by a {@link Task}. |
|
|
|
* Various options supported by a {@link Task}. |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
private static enum TaskOption { |
|
|
|
private static enum TaskOption { |
|
|
|
|
|
|
|
|
|
|
|
RESTRUCTURE_BEFORE, RESTRUCTURE_AFTER, SKIP_IF_EMPTY, RESIZE |
|
|
|
RESTRUCTURE_BEFORE, RESTRUCTURE_AFTER, SKIP_IF_EMPTY, RESIZE |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -790,8 +780,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen |
|
|
|
public boolean contains(Object o) { |
|
|
|
public boolean contains(Object o) { |
|
|
|
if (o != null && o instanceof Map.Entry<?, ?>) { |
|
|
|
if (o != null && o instanceof Map.Entry<?, ?>) { |
|
|
|
Map.Entry<?, ?> entry = (java.util.Map.Entry<?, ?>) o; |
|
|
|
Map.Entry<?, ?> entry = (java.util.Map.Entry<?, ?>) o; |
|
|
|
Reference<K, V> reference = ConcurrentReferenceHashMap.this.getReference( |
|
|
|
Reference<K, V> reference = ConcurrentReferenceHashMap.this.getReference(entry.getKey(), Restructure.NEVER); |
|
|
|
entry.getKey(), Restructure.NEVER); |
|
|
|
|
|
|
|
Entry<K, V> other = (reference == null ? null : reference.get()); |
|
|
|
Entry<K, V> other = (reference == null ? null : reference.get()); |
|
|
|
if (other != null) { |
|
|
|
if (other != null) { |
|
|
|
return ObjectUtils.nullSafeEquals(entry.getValue(), other.getValue()); |
|
|
|
return ObjectUtils.nullSafeEquals(entry.getValue(), other.getValue()); |
|
|
|
@ -804,8 +793,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen |
|
|
|
public boolean remove(Object o) { |
|
|
|
public boolean remove(Object o) { |
|
|
|
if (o instanceof Map.Entry<?, ?>) { |
|
|
|
if (o instanceof Map.Entry<?, ?>) { |
|
|
|
Map.Entry<?, ?> entry = (Map.Entry<?, ?>) o; |
|
|
|
Map.Entry<?, ?> entry = (Map.Entry<?, ?>) o; |
|
|
|
return ConcurrentReferenceHashMap.this.remove(entry.getKey(), |
|
|
|
return ConcurrentReferenceHashMap.this.remove(entry.getKey(), entry.getValue()); |
|
|
|
entry.getValue()); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
|
@ -926,8 +914,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen |
|
|
|
* @param next the next reference in the chain or {@code null} |
|
|
|
* @param next the next reference in the chain or {@code null} |
|
|
|
* @return a new {@link Reference} |
|
|
|
* @return a new {@link Reference} |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public Reference<K, V> createReference(Entry<K, V> entry, int hash, |
|
|
|
public Reference<K, V> createReference(Entry<K, V> entry, int hash, Reference<K, V> next) { |
|
|
|
Reference<K, V> next) { |
|
|
|
|
|
|
|
if (ConcurrentReferenceHashMap.this.referenceType == ReferenceType.WEAK) { |
|
|
|
if (ConcurrentReferenceHashMap.this.referenceType == ReferenceType.WEAK) { |
|
|
|
return new WeakEntryReference<K, V>(entry, hash, next, this.queue); |
|
|
|
return new WeakEntryReference<K, V>(entry, hash, next, this.queue); |
|
|
|
} |
|
|
|
} |
|
|
|
@ -951,15 +938,13 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen |
|
|
|
/** |
|
|
|
/** |
|
|
|
* Internal {@link Reference} implementation for {@link SoftReference}s. |
|
|
|
* Internal {@link Reference} implementation for {@link SoftReference}s. |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
private static final class SoftEntryReference<K, V> extends |
|
|
|
private static final class SoftEntryReference<K, V> extends SoftReference<Entry<K, V>> implements Reference<K, V> { |
|
|
|
SoftReference<Entry<K, V>> implements Reference<K, V> { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private final int hash; |
|
|
|
private final int hash; |
|
|
|
|
|
|
|
|
|
|
|
private final Reference<K, V> nextReference; |
|
|
|
private final Reference<K, V> nextReference; |
|
|
|
|
|
|
|
|
|
|
|
public SoftEntryReference(Entry<K, V> entry, int hash, Reference<K, V> next, |
|
|
|
public SoftEntryReference(Entry<K, V> entry, int hash, Reference<K, V> next, ReferenceQueue<Entry<K, V>> queue) { |
|
|
|
ReferenceQueue<Entry<K, V>> queue) { |
|
|
|
|
|
|
|
super(entry, queue); |
|
|
|
super(entry, queue); |
|
|
|
this.hash = hash; |
|
|
|
this.hash = hash; |
|
|
|
this.nextReference = next; |
|
|
|
this.nextReference = next; |
|
|
|
@ -986,15 +971,13 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen |
|
|
|
/** |
|
|
|
/** |
|
|
|
* Internal {@link Reference} implementation for {@link WeakReference}s. |
|
|
|
* Internal {@link Reference} implementation for {@link WeakReference}s. |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
private static final class WeakEntryReference<K, V> extends |
|
|
|
private static final class WeakEntryReference<K, V> extends WeakReference<Entry<K, V>> implements Reference<K, V> { |
|
|
|
WeakReference<Entry<K, V>> implements Reference<K, V> { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private final int hash; |
|
|
|
private final int hash; |
|
|
|
|
|
|
|
|
|
|
|
private final Reference<K, V> nextReference; |
|
|
|
private final Reference<K, V> nextReference; |
|
|
|
|
|
|
|
|
|
|
|
public WeakEntryReference(Entry<K, V> entry, int hash, Reference<K, V> next, |
|
|
|
public WeakEntryReference(Entry<K, V> entry, int hash, Reference<K, V> next, ReferenceQueue<Entry<K, V>> queue) { |
|
|
|
ReferenceQueue<Entry<K, V>> queue) { |
|
|
|
|
|
|
|
super(entry, queue); |
|
|
|
super(entry, queue); |
|
|
|
this.hash = hash; |
|
|
|
this.hash = hash; |
|
|
|
this.nextReference = next; |
|
|
|
this.nextReference = next; |
|
|
|
@ -1016,4 +999,5 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen |
|
|
|
clear(); |
|
|
|
clear(); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|