diff --git a/divinemc-server/minecraft-patches/sources/net/minecraft/server/PlayerAdvancements.java.patch b/divinemc-server/minecraft-patches/sources/net/minecraft/server/PlayerAdvancements.java.patch index 2facf9c..a7a439e 100644 --- a/divinemc-server/minecraft-patches/sources/net/minecraft/server/PlayerAdvancements.java.patch +++ b/divinemc-server/minecraft-patches/sources/net/minecraft/server/PlayerAdvancements.java.patch @@ -1,11 +1,26 @@ --- a/net/minecraft/server/PlayerAdvancements.java +++ b/net/minecraft/server/PlayerAdvancements.java -@@ -60,7 +_,7 @@ +@@ -51,16 +_,18 @@ + private final PlayerList playerList; + private final Path playerSavePath; + private AdvancementTree tree; +- private final Map progress = new LinkedHashMap<>(); +- private final Set visible = new HashSet<>(); +- private final Set progressChanged = new HashSet<>(); +- private final Set rootsToUpdate = new HashSet<>(); ++ // DivineMC start - Use synchronized map ++ private final Map progress = java.util.Collections.synchronizedMap(new LinkedHashMap<>()); ++ private final Set visible = com.google.common.collect.Sets.newConcurrentHashSet(); ++ private final Set progressChanged = com.google.common.collect.Sets.newConcurrentHashSet(); ++ private final Set rootsToUpdate = com.google.common.collect.Sets.newConcurrentHashSet(); ++ // DivineMC end - Use synchronized map + private ServerPlayer player; + @Nullable private AdvancementHolder lastSelectedTab; private boolean isFirstPacket = true; private final Codec codec; - public final Map, Set>> criterionData = new java.util.IdentityHashMap<>(); // Paper - fix advancement data player leakage -+ public final Map, Set>> criterionData = new org.bxteam.divinemc.util.map.ConcurrentReferenceHashMap<>(); // Paper - fix advancement data player leakage // DivineMC - Use ConcurrentReferenceHashMap ++ public final Map, Set>> criterionData = java.util.Collections.synchronizedMap(new java.util.IdentityHashMap<>()); // Paper - fix advancement data player leakage // DivineMC - Use synchronized map public PlayerAdvancements(DataFixer dataFixer, PlayerList playerList, ServerAdvancementManager manager, Path playerSavePath, ServerPlayer player) { this.playerList = playerList; diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/Assert.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/Assert.java deleted file mode 100644 index 4ea68e0..0000000 --- a/divinemc-server/src/main/java/org/bxteam/divinemc/util/Assert.java +++ /dev/null @@ -1,421 +0,0 @@ -package org.bxteam.divinemc.util; - -import java.util.Collection; -import java.util.function.Supplier; - -import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NotNull; -import org.jspecify.annotations.Nullable; - -/** - * Assertion utility class that assists in validating arguments. - * - *

Useful for identifying programmer errors early and clearly at runtime. - * - *

For example, if the contract of a public method states it does not - * allow {@code null} arguments, {@code Assert} can be used to validate that - * contract. Doing this clearly indicates a contract violation when it - * occurs and protects the class's invariants. - * - *

Typically used to validate method arguments rather than configuration - * properties, to check for cases that are usually programmer errors rather - * than configuration errors. In contrast to configuration initialization - * code, there is usually no point in falling back to defaults in such methods. - * - *

This class is similar to JUnit's assertion library. If an argument value is - * deemed invalid, an {@link IllegalArgumentException} is thrown (typically). - * For example: - * - *

- * Assert.notNull(clazz, "The class must not be null");
- * Assert.isTrue(i > 0, "The value must be greater than zero");
- * - *

Mainly for internal use within the framework; for a more comprehensive suite - * of assertion utilities consider {@code org.apache.commons.lang3.Validate} from - * Apache Commons Lang, - * Google Guava's - * Preconditions, - * or similar third-party libraries. - * - * @author Keith Donald - * @author Juergen Hoeller - * @author Sam Brannen - * @author Colin Sampaleanu - * @author Rob Harrop - * @author Sebastien Deleuze - */ -public abstract class Assert { - /** - * Assert a boolean expression, throwing an {@code IllegalStateException} - * if the expression evaluates to {@code false}. - *

Call {@link #isTrue} if you wish to throw an {@code IllegalArgumentException} - * on an assertion failure. - *

Assert.state(id == null, "The id property must not already be initialized");
- * @param expression a boolean expression - * @param message the exception message to use if the assertion fails - * @throws IllegalStateException if {@code expression} is {@code false} - */ - @Contract("false, _ -> fail") - public static void state(boolean expression, String message) { - if (!expression) { - throw new IllegalStateException(message); - } - } - - /** - * Assert a boolean expression, throwing an {@code IllegalStateException} - * if the expression evaluates to {@code false}. - *

Call {@link #isTrue} if you wish to throw an {@code IllegalArgumentException} - * on an assertion failure. - *

-     * Assert.state(entity.getId() == null,
-     *     () -> "ID for entity " + entity.getName() + " must not already be initialized");
-     * 
- * @param expression a boolean expression - * @param messageSupplier a supplier for the exception message to use if the - * assertion fails - * @throws IllegalStateException if {@code expression} is {@code false} - * @since 5.0 - */ - @Contract("false, _ -> fail") - public static void state(boolean expression, Supplier messageSupplier) { - if (!expression) { - throw new IllegalStateException(nullSafeGet(messageSupplier)); - } - } - - /** - * Assert a boolean expression, throwing an {@code IllegalArgumentException} - * if the expression evaluates to {@code false}. - *
Assert.isTrue(i > 0, "The value must be greater than zero");
- * @param expression a boolean expression - * @param message the exception message to use if the assertion fails - * @throws IllegalArgumentException if {@code expression} is {@code false} - */ - @Contract("false, _ -> fail") - public static void isTrue(boolean expression, String message) { - if (!expression) { - throw new IllegalArgumentException(message); - } - } - - /** - * Assert a boolean expression, throwing an {@code IllegalArgumentException} - * if the expression evaluates to {@code false}. - *
-     * Assert.isTrue(i > 0, () -> "The value '" + i + "' must be greater than zero");
-     * 
- * @param expression a boolean expression - * @param messageSupplier a supplier for the exception message to use if the - * assertion fails - * @throws IllegalArgumentException if {@code expression} is {@code false} - * @since 5.0 - */ - @Contract("false, _ -> fail") - public static void isTrue(boolean expression, Supplier messageSupplier) { - if (!expression) { - throw new IllegalArgumentException(nullSafeGet(messageSupplier)); - } - } - - /** - * Assert that an object is {@code null}. - *
Assert.isNull(value, "The value must be null");
- * @param object the object to check - * @param message the exception message to use if the assertion fails - * @throws IllegalArgumentException if the object is not {@code null} - */ - @Contract("!null, _ -> fail") - public static void isNull(@Nullable Object object, String message) { - if (object != null) { - throw new IllegalArgumentException(message); - } - } - - /** - * Assert that an object is {@code null}. - *
-     * Assert.isNull(value, () -> "The value '" + value + "' must be null");
-     * 
- * @param object the object to check - * @param messageSupplier a supplier for the exception message to use if the - * assertion fails - * @throws IllegalArgumentException if the object is not {@code null} - * @since 5.0 - */ - @Contract("!null, _ -> fail") - public static void isNull(@Nullable Object object, Supplier messageSupplier) { - if (object != null) { - throw new IllegalArgumentException(nullSafeGet(messageSupplier)); - } - } - - /** - * Assert that an object is not {@code null}. - *
Assert.notNull(clazz, "The class must not be null");
- * @param object the object to check - * @param message the exception message to use if the assertion fails - * @throws IllegalArgumentException if the object is {@code null} - */ - @Contract("null, _ -> fail") - public static void notNull(@Nullable Object object, String message) { - if (object == null) { - throw new IllegalArgumentException(message); - } - } - - /** - * Assert that an object is not {@code null}. - *
-     * Assert.notNull(entity.getId(),
-     *     () -> "ID for entity " + entity.getName() + " must not be null");
-     * 
- * @param object the object to check - * @param messageSupplier a supplier for the exception message to use if the - * assertion fails - * @throws IllegalArgumentException if the object is {@code null} - * @since 5.0 - */ - @Contract("null, _ -> fail") - public static void notNull(@Nullable Object object, Supplier messageSupplier) { - if (object == null) { - throw new IllegalArgumentException(nullSafeGet(messageSupplier)); - } - } - - /** - * Assert that an array contains no {@code null} elements. - *

Note: Does not complain if the array is empty! - *

Assert.noNullElements(array, "The array must contain non-null elements");
- * @param array the array to check - * @param message the exception message to use if the assertion fails - * @throws IllegalArgumentException if the object array contains a {@code null} element - */ - public static void noNullElements(Object @Nullable [] array, String message) { - if (array != null) { - for (Object element : array) { - if (element == null) { - throw new IllegalArgumentException(message); - } - } - } - } - - /** - * Assert that an array contains no {@code null} elements. - *

Note: Does not complain if the array is empty! - *

-     * Assert.noNullElements(array, () -> "The " + arrayType + " array must contain non-null elements");
-     * 
- * @param array the array to check - * @param messageSupplier a supplier for the exception message to use if the - * assertion fails - * @throws IllegalArgumentException if the object array contains a {@code null} element - * @since 5.0 - */ - public static void noNullElements(Object @Nullable [] array, Supplier messageSupplier) { - if (array != null) { - for (Object element : array) { - if (element == null) { - throw new IllegalArgumentException(nullSafeGet(messageSupplier)); - } - } - } - } - - /** - * Assert that a collection contains no {@code null} elements. - *

Note: Does not complain if the collection is empty! - *

Assert.noNullElements(collection, "Collection must contain non-null elements");
- * @param collection the collection to check - * @param message the exception message to use if the assertion fails - * @throws IllegalArgumentException if the collection contains a {@code null} element - * @since 5.2 - */ - public static void noNullElements(@Nullable Collection collection, String message) { - if (collection != null) { - for (Object element : collection) { - if (element == null) { - throw new IllegalArgumentException(message); - } - } - } - } - - /** - * Assert that a collection contains no {@code null} elements. - *

Note: Does not complain if the collection is empty! - *

-     * Assert.noNullElements(collection, () -> "Collection " + collectionName + " must contain non-null elements");
-     * 
- * @param collection the collection to check - * @param messageSupplier a supplier for the exception message to use if the - * assertion fails - * @throws IllegalArgumentException if the collection contains a {@code null} element - * @since 5.2 - */ - public static void noNullElements(@Nullable Collection collection, Supplier messageSupplier) { - if (collection != null) { - for (Object element : collection) { - if (element == null) { - throw new IllegalArgumentException(nullSafeGet(messageSupplier)); - } - } - } - } - - /** - * Assert that the provided object is an instance of the provided class. - *
Assert.instanceOf(Foo.class, foo, "Foo expected");
- * @param type the type to check against - * @param obj the object to check - * @param message a message which will be prepended to provide further context. - * If it is empty or ends in ":" or ";" or "," or ".", a full exception message - * will be appended. If it ends in a space, the name of the offending object's - * type will be appended. In any other case, a ":" with a space and the name - * of the offending object's type will be appended. - * @throws IllegalArgumentException if the object is not an instance of type - */ - @Contract("_, null, _ -> fail") - public static void isInstanceOf(Class type, @Nullable Object obj, String message) { - notNull(type, "Type to check against must not be null"); - if (!type.isInstance(obj)) { - instanceCheckFailed(type, obj, message); - } - } - - /** - * Assert that the provided object is an instance of the provided class. - *
-     * Assert.instanceOf(Foo.class, foo, () -> "Processing " + Foo.class.getSimpleName() + ":");
-     * 
- * @param type the type to check against - * @param obj the object to check - * @param messageSupplier a supplier for the exception message to use if the - * assertion fails. See {@link #isInstanceOf(Class, Object, String)} for details. - * @throws IllegalArgumentException if the object is not an instance of type - * @since 5.0 - */ - @Contract("_, null, _ -> fail") - public static void isInstanceOf(Class type, @Nullable Object obj, Supplier messageSupplier) { - notNull(type, "Type to check against must not be null"); - if (!type.isInstance(obj)) { - instanceCheckFailed(type, obj, nullSafeGet(messageSupplier)); - } - } - - /** - * Assert that the provided object is an instance of the provided class. - *
Assert.instanceOf(Foo.class, foo);
- * @param type the type to check against - * @param obj the object to check - * @throws IllegalArgumentException if the object is not an instance of type - */ - @Contract("_, null -> fail") - public static void isInstanceOf(Class type, @Nullable Object obj) { - isInstanceOf(type, obj, ""); - } - - /** - * Assert that {@code superType.isAssignableFrom(subType)} is {@code true}. - *
Assert.isAssignable(Number.class, myClass, "Number expected");
- * @param superType the supertype to check against - * @param subType the subtype to check - * @param message a message which will be prepended to provide further context. - * If it is empty or ends in ":" or ";" or "," or ".", a full exception message - * will be appended. If it ends in a space, the name of the offending subtype - * will be appended. In any other case, a ":" with a space and the name of the - * offending subtype will be appended. - * @throws IllegalArgumentException if the classes are not assignable - */ - @Contract("_, null, _ -> fail") - public static void isAssignable(Class superType, @Nullable Class subType, String message) { - notNull(superType, "Supertype to check against must not be null"); - if (subType == null || !superType.isAssignableFrom(subType)) { - assignableCheckFailed(superType, subType, message); - } - } - - /** - * Assert that {@code superType.isAssignableFrom(subType)} is {@code true}. - *
-     * Assert.isAssignable(Number.class, myClass, () -> "Processing " + myAttributeName + ":");
-     * 
- * @param superType the supertype to check against - * @param subType the subtype to check - * @param messageSupplier a supplier for the exception message to use if the - * assertion fails. See {@link #isAssignable(Class, Class, String)} for details. - * @throws IllegalArgumentException if the classes are not assignable - * @since 5.0 - */ - @Contract("_, null, _ -> fail") - public static void isAssignable(Class superType, @Nullable Class subType, Supplier messageSupplier) { - notNull(superType, "Supertype to check against must not be null"); - if (subType == null || !superType.isAssignableFrom(subType)) { - assignableCheckFailed(superType, subType, nullSafeGet(messageSupplier)); - } - } - - /** - * Assert that {@code superType.isAssignableFrom(subType)} is {@code true}. - *
Assert.isAssignable(Number.class, myClass);
- * @param superType the supertype to check - * @param subType the subtype to check - * @throws IllegalArgumentException if the classes are not assignable - */ - @Contract("_, null -> fail") - public static void isAssignable(Class superType, @Nullable Class subType) { - isAssignable(superType, subType, ""); - } - - - private static void instanceCheckFailed(Class type, @Nullable Object obj, @Nullable String msg) { - String className = (obj != null ? obj.getClass().getName() : "null"); - String result = ""; - boolean defaultMessage = true; - if (StringUtil.hasLength(msg)) { - if (endsWithSeparator(msg)) { - result = msg + " "; - } - else { - result = messageWithTypeName(msg, className); - defaultMessage = false; - } - } - if (defaultMessage) { - result = result + ("Object of class [" + className + "] must be an instance of " + type); - } - throw new IllegalArgumentException(result); - } - - private static void assignableCheckFailed(Class superType, @Nullable Class subType, @Nullable String msg) { - String result = ""; - boolean defaultMessage = true; - if (StringUtil.hasLength(msg)) { - if (endsWithSeparator(msg)) { - result = msg + " "; - } - else { - result = messageWithTypeName(msg, subType); - defaultMessage = false; - } - } - if (defaultMessage) { - result = result + (subType + " is not assignable to " + superType); - } - throw new IllegalArgumentException(result); - } - - private static boolean endsWithSeparator(@NotNull String msg) { - return (msg.endsWith(":") || msg.endsWith(";") || msg.endsWith(",") || msg.endsWith(".")); - } - - @Contract(pure = true) - private static @NotNull String messageWithTypeName(@NotNull String msg, @Nullable Object typeName) { - return msg + (msg.endsWith(" ") ? "" : ": ") + typeName; - } - - private static @Nullable String nullSafeGet(@Nullable Supplier messageSupplier) { - return (messageSupplier != null ? messageSupplier.get() : null); - } -} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/ObjectUtil.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/ObjectUtil.java deleted file mode 100644 index 358a393..0000000 --- a/divinemc-server/src/main/java/org/bxteam/divinemc/util/ObjectUtil.java +++ /dev/null @@ -1,108 +0,0 @@ -package org.bxteam.divinemc.util; - -import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.Nullable; -import java.util.Arrays; - -public class ObjectUtil { - @Contract("null, null -> true; null, _ -> false; _, null -> false") - public static boolean nullSafeEquals(@Nullable Object o1, @Nullable Object o2) { - if (o1 == o2) { - return true; - } - if (o1 == null || o2 == null) { - return false; - } - if (o1.equals(o2)) { - return true; - } - if (o1.getClass().isArray() && o2.getClass().isArray()) { - return arrayEquals(o1, o2); - } - return false; - } - - /** - * Compare the given arrays with {@code Arrays.equals}, performing an equality - * check based on the array elements rather than the array reference. - * @param o1 first array to compare - * @param o2 second array to compare - * @return whether the given objects are equal - * @see #nullSafeEquals(Object, Object) - * @see java.util.Arrays#equals - */ - private static boolean arrayEquals(Object o1, Object o2) { - if (o1 instanceof Object[] objects1 && o2 instanceof Object[] objects2) { - return Arrays.equals(objects1, objects2); - } - if (o1 instanceof boolean[] booleans1 && o2 instanceof boolean[] booleans2) { - return Arrays.equals(booleans1, booleans2); - } - if (o1 instanceof byte[] bytes1 && o2 instanceof byte[] bytes2) { - return Arrays.equals(bytes1, bytes2); - } - if (o1 instanceof char[] chars1 && o2 instanceof char[] chars2) { - return Arrays.equals(chars1, chars2); - } - if (o1 instanceof double[] doubles1 && o2 instanceof double[] doubles2) { - return Arrays.equals(doubles1, doubles2); - } - if (o1 instanceof float[] floats1 && o2 instanceof float[] floats2) { - return Arrays.equals(floats1, floats2); - } - if (o1 instanceof int[] ints1 && o2 instanceof int[] ints2) { - return Arrays.equals(ints1, ints2); - } - if (o1 instanceof long[] longs1 && o2 instanceof long[] longs2) { - return Arrays.equals(longs1, longs2); - } - if (o1 instanceof short[] shorts1 && o2 instanceof short[] shorts2) { - return Arrays.equals(shorts1, shorts2); - } - return false; - } - - /** - * Return a hash code for the given object; typically the value of - * {@code Object#hashCode()}}. If the object is an array, - * this method will delegate to any of the {@code Arrays.hashCode} - * methods. If the object is {@code null}, this method returns 0. - * @see Object#hashCode() - * @see Arrays - */ - public static int nullSafeHashCode(@Nullable Object obj) { - if (obj == null) { - return 0; - } - if (obj.getClass().isArray()) { - if (obj instanceof Object[] objects) { - return Arrays.hashCode(objects); - } - if (obj instanceof boolean[] booleans) { - return Arrays.hashCode(booleans); - } - if (obj instanceof byte[] bytes) { - return Arrays.hashCode(bytes); - } - if (obj instanceof char[] chars) { - return Arrays.hashCode(chars); - } - if (obj instanceof double[] doubles) { - return Arrays.hashCode(doubles); - } - if (obj instanceof float[] floats) { - return Arrays.hashCode(floats); - } - if (obj instanceof int[] ints) { - return Arrays.hashCode(ints); - } - if (obj instanceof long[] longs) { - return Arrays.hashCode(longs); - } - if (obj instanceof short[] shorts) { - return Arrays.hashCode(shorts); - } - } - return obj.hashCode(); - } -} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/StringUtil.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/StringUtil.java deleted file mode 100644 index 1d1ba14..0000000 --- a/divinemc-server/src/main/java/org/bxteam/divinemc/util/StringUtil.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.bxteam.divinemc.util; - -import org.jetbrains.annotations.Nullable; - -public final class StringUtil { - public static boolean hasLength(@Nullable String str) { - return (str != null && !str.isEmpty()); - } -} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/map/ConcurrentReferenceHashMap.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/map/ConcurrentReferenceHashMap.java deleted file mode 100644 index 4271658..0000000 --- a/divinemc-server/src/main/java/org/bxteam/divinemc/util/map/ConcurrentReferenceHashMap.java +++ /dev/null @@ -1,1054 +0,0 @@ -package org.bxteam.divinemc.util.map; - -import java.lang.ref.ReferenceQueue; -import java.lang.ref.SoftReference; -import java.lang.ref.WeakReference; -import java.lang.reflect.Array; -import java.util.AbstractMap; -import java.util.AbstractSet; -import java.util.Collections; -import java.util.EnumSet; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.locks.ReentrantLock; -import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NotNull; -import org.jspecify.annotations.Nullable; -import org.bxteam.divinemc.util.Assert; -import org.bxteam.divinemc.util.ObjectUtil; - -/** - * A {@link ConcurrentHashMap} that uses {@link ReferenceType#SOFT soft} or - * {@linkplain ReferenceType#WEAK weak} references for both {@code keys} and {@code values}. - * - *

This class can be used as an alternative to - * {@code Collections.synchronizedMap(new WeakHashMap>())} in order to - * support better performance when accessed concurrently. This implementation follows the - * same design constraints as {@link ConcurrentHashMap} with the exception that - * {@code null} values and {@code null} keys are supported. - * - *

NOTE: The use of references means that there is no guarantee that items - * placed into the map will be subsequently available. The garbage collector may discard - * references at any time, so it may appear that an unknown thread is silently removing - * entries. - * - *

If not explicitly specified, this implementation will use - * {@linkplain SoftReference soft entry references}. - * - * @param the key type - * @param the value type - * @author Phillip Webb - * @author Juergen Hoeller - * @author Brian Clozel - */ -public class ConcurrentReferenceHashMap extends AbstractMap implements ConcurrentMap { - private static final int DEFAULT_INITIAL_CAPACITY = 16; - private static final float DEFAULT_LOAD_FACTOR = 0.75f; - private static final int DEFAULT_CONCURRENCY_LEVEL = 16; - private static final ReferenceType DEFAULT_REFERENCE_TYPE = ReferenceType.SOFT; - private static final int MAXIMUM_CONCURRENCY_LEVEL = 1 << 16; - private static final int MAXIMUM_SEGMENT_SIZE = 1 << 30; - - /** - * Array of segments indexed using the high order bits from the hash. - */ - private final Segment[] segments; - - /** - * When the average number of references per table exceeds this value resize will be attempted. - */ - private final float loadFactor; - - /** - * The reference type: SOFT or WEAK. - */ - private final ReferenceType referenceType; - - /** - * The shift value used to calculate the size of the segments array and an index from the hash. - */ - private final int shift; - - /** - * Late binding entry set. - */ - private volatile @Nullable Set> entrySet; - - /** - * Create a new {@code ConcurrentReferenceHashMap} instance. - */ - public ConcurrentReferenceHashMap() { - this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL, DEFAULT_REFERENCE_TYPE); - } - - /** - * Create a new {@code ConcurrentReferenceHashMap} instance. - * - * @param initialCapacity the initial capacity of the map - */ - public ConcurrentReferenceHashMap(int initialCapacity) { - this(initialCapacity, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL, DEFAULT_REFERENCE_TYPE); - } - - /** - * Create a new {@code ConcurrentReferenceHashMap} instance. - * - * @param initialCapacity the initial capacity of the map - * @param loadFactor the load factor. When the average number of references per table - * exceeds this value resize will be attempted - */ - public ConcurrentReferenceHashMap(int initialCapacity, float loadFactor) { - this(initialCapacity, loadFactor, DEFAULT_CONCURRENCY_LEVEL, DEFAULT_REFERENCE_TYPE); - } - - /** - * Create a new {@code ConcurrentReferenceHashMap} instance. - * - * @param initialCapacity the initial capacity of the map - * @param concurrencyLevel the expected number of threads that will concurrently - * write to the map - */ - public ConcurrentReferenceHashMap(int initialCapacity, int concurrencyLevel) { - this(initialCapacity, DEFAULT_LOAD_FACTOR, concurrencyLevel, DEFAULT_REFERENCE_TYPE); - } - - /** - * Create a new {@code ConcurrentReferenceHashMap} instance. - * - * @param initialCapacity the initial capacity of the map - * @param referenceType the reference type used for entries (soft or weak) - */ - public ConcurrentReferenceHashMap(int initialCapacity, ReferenceType referenceType) { - this(initialCapacity, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL, referenceType); - } - - /** - * Create a new {@code ConcurrentReferenceHashMap} instance. - * - * @param initialCapacity the initial capacity of the map - * @param loadFactor the load factor. When the average number of references per - * table exceeds this value, resize will be attempted. - * @param concurrencyLevel the expected number of threads that will concurrently - * write to the map - */ - public ConcurrentReferenceHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) { - this(initialCapacity, loadFactor, concurrencyLevel, DEFAULT_REFERENCE_TYPE); - } - - /** - * Create a new {@code ConcurrentReferenceHashMap} instance. - * - * @param initialCapacity the initial capacity of the map - * @param loadFactor the load factor. When the average number of references per - * table exceeds this value, resize will be attempted. - * @param concurrencyLevel the expected number of threads that will concurrently - * write to the map - * @param referenceType the reference type used for entries (soft or weak) - */ - @SuppressWarnings("unchecked") - public ConcurrentReferenceHashMap( - int initialCapacity, float loadFactor, int concurrencyLevel, ReferenceType referenceType) { - - Assert.isTrue(initialCapacity >= 0, "Initial capacity must not be negative"); - Assert.isTrue(loadFactor > 0f, "Load factor must be positive"); - Assert.isTrue(concurrencyLevel > 0, "Concurrency level must be positive"); - Assert.notNull(referenceType, "Reference type must not be null"); - this.loadFactor = loadFactor; - this.shift = calculateShift(concurrencyLevel, MAXIMUM_CONCURRENCY_LEVEL); - int size = 1 << this.shift; - this.referenceType = referenceType; - int roundedUpSegmentCapacity = (int) ((initialCapacity + size - 1L) / size); - int initialSize = 1 << calculateShift(roundedUpSegmentCapacity, MAXIMUM_SEGMENT_SIZE); - Segment[] segments = (Segment[]) Array.newInstance(Segment.class, size); - int resizeThreshold = (int) (initialSize * getLoadFactor()); - for (int i = 0; i < segments.length; i++) { - segments[i] = new Segment(initialSize, resizeThreshold); - } - this.segments = segments; - } - - /** - * Calculate a shift value that can be used to create a power-of-two value between - * the specified maximum and minimum values. - * - * @param minimumValue the minimum value - * @param maximumValue the maximum value - * @return the calculated shift (use {@code 1 << shift} to obtain a value) - */ - protected static int calculateShift(int minimumValue, int maximumValue) { - int shift = 0; - int value = 1; - while (value < minimumValue && value < maximumValue) { - value <<= 1; - shift++; - } - return shift; - } - - protected final float getLoadFactor() { - return this.loadFactor; - } - - protected final int getSegmentsSize() { - return this.segments.length; - } - - protected final Segment getSegment(int index) { - return this.segments[index]; - } - - /** - * Factory method that returns the {@link ReferenceManager}. - * This method will be called once for each {@link Segment}. - * - * @return a new reference manager - */ - protected ReferenceManager createReferenceManager() { - return new ReferenceManager(); - } - - /** - * Get the hash for a given object, apply an additional hash function to reduce - * collisions. This implementation uses the same Wang/Jenkins algorithm as - * {@link ConcurrentHashMap}. Subclasses can override to provide alternative hashing. - * - * @param o the object to hash (may be null) - * @return the resulting hash code - */ - protected int getHash(@Nullable Object o) { - int hash = (o != null ? o.hashCode() : 0); - hash += (hash << 15) ^ 0xffffcd7d; - hash ^= (hash >>> 10); - hash += (hash << 3); - hash ^= (hash >>> 6); - hash += (hash << 2) + (hash << 14); - hash ^= (hash >>> 16); - return hash; - } - - @Override - public @Nullable V get(@Nullable Object key) { - Reference ref = getReference(key, Restructure.WHEN_NECESSARY); - Entry entry = (ref != null ? ref.get() : null); - return (entry != null ? entry.getValue() : null); - } - - @Override - public @Nullable V getOrDefault(@Nullable Object key, @Nullable V defaultValue) { - Reference ref = getReference(key, Restructure.WHEN_NECESSARY); - Entry entry = (ref != null ? ref.get() : null); - return (entry != null ? entry.getValue() : defaultValue); - } - - @Override - public boolean containsKey(@Nullable Object key) { - Reference ref = getReference(key, Restructure.WHEN_NECESSARY); - Entry entry = (ref != null ? ref.get() : null); - return (entry != null && ObjectUtil.nullSafeEquals(entry.getKey(), key)); - } - - /** - * Return a {@link Reference} to the {@link Entry} for the specified {@code key}, - * or {@code null} if not found. - * - * @param key the key (can be {@code null}) - * @param restructure types of restructure allowed during this call - * @return the reference, or {@code null} if not found - */ - protected final @Nullable Reference getReference(@Nullable Object key, Restructure restructure) { - int hash = getHash(key); - return getSegmentForHash(hash).getReference(key, hash, restructure); - } - - @Override - public @Nullable V put(@Nullable K key, @Nullable V value) { - return put(key, value, true); - } - - @Override - public @Nullable V putIfAbsent(@Nullable K key, @Nullable V value) { - return put(key, value, false); - } - - private @Nullable V put(final @Nullable K key, final @Nullable V value, final boolean overwriteExisting) { - return doTask(key, new Task(TaskOption.RESTRUCTURE_BEFORE, TaskOption.RESIZE) { - @Override - protected @Nullable V execute(@Nullable Reference ref, @Nullable Entry entry, @Nullable Entries entries) { - if (entry != null) { - V oldValue = entry.getValue(); - if (overwriteExisting) { - entry.setValue(value); - } - return oldValue; - } - Assert.state(entries != null, "No entries segment"); - entries.add(value); - return null; - } - }); - } - - @Override - public @Nullable V remove(@Nullable Object key) { - return doTask(key, new Task(TaskOption.RESTRUCTURE_AFTER, TaskOption.SKIP_IF_EMPTY) { - @Override - protected @Nullable V execute(@Nullable Reference ref, @Nullable Entry entry) { - if (entry != null) { - if (ref != null) { - ref.release(); - } - return entry.value; - } - return null; - } - }); - } - - @Override - public boolean remove(@Nullable Object key, final @Nullable Object value) { - Boolean result = doTask(key, new Task(TaskOption.RESTRUCTURE_AFTER, TaskOption.SKIP_IF_EMPTY) { - @Override - protected Boolean execute(@Nullable Reference ref, @Nullable Entry entry) { - if (entry != null && ObjectUtil.nullSafeEquals(entry.getValue(), value)) { - if (ref != null) { - ref.release(); - } - return true; - } - return false; - } - }); - return (Boolean.TRUE.equals(result)); - } - - @Override - public boolean replace(@Nullable K key, final @Nullable V oldValue, final @Nullable V newValue) { - Boolean result = doTask(key, new Task(TaskOption.RESTRUCTURE_BEFORE, TaskOption.SKIP_IF_EMPTY) { - @Override - protected Boolean execute(@Nullable Reference ref, @Nullable Entry entry) { - if (entry != null && ObjectUtil.nullSafeEquals(entry.getValue(), oldValue)) { - entry.setValue(newValue); - return true; - } - return false; - } - }); - return (Boolean.TRUE.equals(result)); - } - - @Override - public @Nullable V replace(@Nullable K key, final @Nullable V value) { - return doTask(key, new Task(TaskOption.RESTRUCTURE_BEFORE, TaskOption.SKIP_IF_EMPTY) { - @Override - protected @Nullable V execute(@Nullable Reference ref, @Nullable Entry entry) { - if (entry != null) { - V oldValue = entry.getValue(); - entry.setValue(value); - return oldValue; - } - return null; - } - }); - } - - @Override - public void clear() { - for (Segment segment : this.segments) { - segment.clear(); - } - } - - /** - * Remove any entries that have been garbage collected and are no longer referenced. - * Under normal circumstances garbage collected entries are automatically purged as - * items are added or removed from the Map. This method can be used to force a purge, - * and is useful when the Map is read frequently but updated less often. - */ - public void purgeUnreferencedEntries() { - for (Segment segment : this.segments) { - segment.restructureIfNecessary(false); - } - } - - @Override - public int size() { - int size = 0; - for (Segment segment : this.segments) { - size += segment.getCount(); - } - return size; - } - - @Override - public boolean isEmpty() { - for (Segment segment : this.segments) { - if (segment.getCount() > 0) { - return false; - } - } - return true; - } - - @Override - public Set> entrySet() { - Set> entrySet = this.entrySet; - if (entrySet == null) { - entrySet = new EntrySet(); - this.entrySet = entrySet; - } - return entrySet; - } - - private @Nullable T doTask(@Nullable Object key, Task task) { - int hash = getHash(key); - return getSegmentForHash(hash).doTask(hash, key, task); - } - - private Segment getSegmentForHash(int hash) { - return this.segments[(hash >>> (32 - this.shift)) & (this.segments.length - 1)]; - } - - - /** - * Various reference types supported by this map. - */ - public enum ReferenceType { - - /** - * Use {@link SoftReference SoftReferences}. - */ - SOFT, - - /** - * Use {@link WeakReference WeakReferences}. - */ - WEAK - } - - - /** - * Various options supported by a {@code Task}. - */ - private enum TaskOption { - - RESTRUCTURE_BEFORE, RESTRUCTURE_AFTER, SKIP_IF_EMPTY, RESIZE - } - - - /** - * The types of restructuring that can be performed. - */ - protected enum Restructure { - - WHEN_NECESSARY, NEVER - } - - - /** - * A reference to an {@link Entry} contained in the map. Implementations are usually - * wrappers around specific Java reference implementations (for example, {@link SoftReference}). - * - * @param the key type - * @param the value type - */ - protected interface Reference { - - /** - * Return the referenced entry, or {@code null} if the entry is no longer available. - */ - @Nullable - Entry get(); - - /** - * Return the hash for the reference. - */ - int getHash(); - - /** - * Return the next reference in the chain, or {@code null} if none. - */ - @Nullable - Reference getNext(); - - /** - * Release this entry and ensure that it will be returned from - * {@code ReferenceManager#pollForPurge()}. - */ - void release(); - } - - - /** - * Allows a task access to {@link ConcurrentReferenceHashMap.Segment} entries. - */ - private interface Entries { - - /** - * Add a new entry with the specified value. - * - * @param value the value to add - */ - void add(@Nullable V value); - } - - /** - * A single map entry. - * - * @param the key type - * @param the value type - */ - protected static final class Entry implements Map.Entry { - - private final @Nullable K key; - - private volatile @Nullable V value; - - public Entry(@Nullable K key, @Nullable V value) { - this.key = key; - this.value = value; - } - - @Override - public @Nullable K getKey() { - return this.key; - } - - @Override - public @Nullable V getValue() { - return this.value; - } - - @Override - public @Nullable V setValue(@Nullable V value) { - V previous = this.value; - this.value = value; - return previous; - } - - @Override - public boolean equals(@Nullable Object other) { - return (this == other || (other instanceof Map.Entry that && - ObjectUtil.nullSafeEquals(getKey(), that.getKey()) && - ObjectUtil.nullSafeEquals(getValue(), that.getValue()))); - } - - @Override - public int hashCode() { - return (ObjectUtil.nullSafeHashCode(this.key) ^ ObjectUtil.nullSafeHashCode(this.value)); - } - - @Contract(pure = true) - @Override - public @NotNull String toString() { - return (this.key + "=" + this.value); - } - } - - /** - * Internal {@link Reference} implementation for {@link SoftReference SoftReferences}. - */ - private static final class SoftEntryReference extends SoftReference> implements Reference { - - private final int hash; - - private final @Nullable Reference nextReference; - - public SoftEntryReference(Entry entry, int hash, @Nullable Reference next, - ReferenceQueue> queue) { - - super(entry, queue); - this.hash = hash; - this.nextReference = next; - } - - @Override - public int getHash() { - return this.hash; - } - - @Override - public @Nullable Reference getNext() { - return this.nextReference; - } - - @Override - public void release() { - enqueue(); - } - } - - /** - * Internal {@link Reference} implementation for {@link WeakReference WeakReferences}. - */ - private static final class WeakEntryReference extends WeakReference> implements Reference { - - private final int hash; - - private final @Nullable Reference nextReference; - - public WeakEntryReference(Entry entry, int hash, @Nullable Reference next, - ReferenceQueue> queue) { - - super(entry, queue); - this.hash = hash; - this.nextReference = next; - } - - @Override - public int getHash() { - return this.hash; - } - - @Override - public @Nullable Reference getNext() { - return this.nextReference; - } - - @Override - public void release() { - enqueue(); - } - } - - /** - * A single segment used to divide the map to allow better concurrent performance. - */ - @SuppressWarnings("serial") - protected final class Segment extends ReentrantLock { - - private final ReferenceManager referenceManager; - - private final int initialSize; - /** - * The total number of references contained in this segment. This includes chained - * references and references that have been garbage collected but not purged. - */ - private final AtomicInteger count = new AtomicInteger(); - /** - * Array of references indexed using the low order bits from the hash. - * This property should only be set along with {@code resizeThreshold}. - */ - private volatile @Nullable Reference[] references; - /** - * The threshold when resizing of the references should occur. When {@code count} - * exceeds this value references will be resized. - */ - private int resizeThreshold; - - public Segment(int initialSize, int resizeThreshold) { - this.referenceManager = createReferenceManager(); - this.initialSize = initialSize; - this.references = createReferenceArray(initialSize); - this.resizeThreshold = resizeThreshold; - } - - public @Nullable Reference getReference(@Nullable Object key, int hash, Restructure restructure) { - if (restructure == Restructure.WHEN_NECESSARY) { - restructureIfNecessary(false); - } - if (this.count.get() == 0) { - return null; - } - // Use a local copy to protect against other threads writing - @Nullable Reference[] references = this.references; - int index = getIndex(hash, references); - Reference head = references[index]; - return findInChain(head, key, hash); - } - - /** - * Apply an update operation to this segment. - * The segment will be locked during the update. - * - * @param hash the hash of the key - * @param key the key - * @param task the update operation - * @return the result of the operation - */ - public @Nullable T doTask(final int hash, final @Nullable Object key, final @NotNull Task task) { - boolean resize = task.hasOption(TaskOption.RESIZE); - if (task.hasOption(TaskOption.RESTRUCTURE_BEFORE)) { - restructureIfNecessary(resize); - } - if (task.hasOption(TaskOption.SKIP_IF_EMPTY) && this.count.get() == 0) { - return task.execute(null, null, null); - } - lock(); - try { - final int index = getIndex(hash, this.references); - final Reference head = this.references[index]; - Reference ref = findInChain(head, key, hash); - Entry entry = (ref != null ? ref.get() : null); - Entries entries = value -> { - @SuppressWarnings("unchecked") - Entry newEntry = new Entry<>((K) key, value); - Reference newReference = Segment.this.referenceManager.createReference(newEntry, hash, head); - Segment.this.references[index] = newReference; - Segment.this.count.incrementAndGet(); - }; - return task.execute(ref, entry, entries); - } finally { - unlock(); - if (task.hasOption(TaskOption.RESTRUCTURE_AFTER)) { - restructureIfNecessary(resize); - } - } - } - - /** - * Clear all items from this segment. - */ - public void clear() { - if (this.count.get() == 0) { - return; - } - lock(); - try { - this.references = createReferenceArray(this.initialSize); - this.resizeThreshold = (int) (this.references.length * getLoadFactor()); - this.count.set(0); - } finally { - unlock(); - } - } - - /** - * Restructure the underlying data structure when it becomes necessary. This - * method can increase the size of the references table as well as purge any - * references that have been garbage collected. - * - * @param allowResize if resizing is permitted - */ - void restructureIfNecessary(boolean allowResize) { - int currCount = this.count.get(); - boolean needsResize = allowResize && (currCount > 0 && currCount >= this.resizeThreshold); - Reference ref = this.referenceManager.pollForPurge(); - if (ref != null || (needsResize)) { - restructure(allowResize, ref); - } - } - - private void restructure(boolean allowResize, @Nullable Reference ref) { - boolean needsResize; - lock(); - try { - int expectedCount = this.count.get(); - Set> toPurge = Collections.emptySet(); - if (ref != null) { - toPurge = new HashSet<>(); - while (ref != null) { - toPurge.add(ref); - ref = this.referenceManager.pollForPurge(); - } - } - expectedCount -= toPurge.size(); - - // Estimate new count, taking into account count inside lock and items that - // will be purged. - needsResize = (expectedCount > 0 && expectedCount >= this.resizeThreshold); - boolean resizing = false; - int restructureSize = this.references.length; - if (allowResize && needsResize && restructureSize < MAXIMUM_SEGMENT_SIZE) { - restructureSize <<= 1; - resizing = true; - } - - int newCount = 0; - // Restructure the resized reference array - if (resizing) { - Reference[] restructured = createReferenceArray(restructureSize); - for (Reference reference : this.references) { - ref = reference; - while (ref != null) { - if (!toPurge.contains(ref)) { - Entry entry = ref.get(); - // Also filter out null references that are now null - // they should be polled from the queue in a later restructure call. - if (entry != null) { - int index = getIndex(ref.getHash(), restructured); - restructured[index] = this.referenceManager.createReference( - entry, ref.getHash(), restructured[index]); - newCount++; - } - } - ref = ref.getNext(); - } - } - // Replace volatile members - this.references = restructured; - this.resizeThreshold = (int) (this.references.length * getLoadFactor()); - } - // Restructure the existing reference array "in place" - else { - for (int i = 0; i < this.references.length; i++) { - Reference purgedRef = null; - ref = this.references[i]; - while (ref != null) { - if (!toPurge.contains(ref)) { - Entry entry = ref.get(); - // Also filter out null references that are now null - // they should be polled from the queue in a later restructure call. - if (entry != null) { - purgedRef = this.referenceManager.createReference( - entry, ref.getHash(), purgedRef); - } - newCount++; - } - ref = ref.getNext(); - } - this.references[i] = purgedRef; - } - } - this.count.set(Math.max(newCount, 0)); - } finally { - unlock(); - } - } - - private @Nullable Reference findInChain(@Nullable Reference ref, @Nullable Object key, int hash) { - Reference currRef = ref; - while (currRef != null) { - if (currRef.getHash() == hash) { - Entry entry = currRef.get(); - if (entry != null) { - K entryKey = entry.getKey(); - if (ObjectUtil.nullSafeEquals(entryKey, key)) { - return currRef; - } - } - } - currRef = currRef.getNext(); - } - return null; - } - - @Contract(value = "_ -> new", pure = true) - @SuppressWarnings({"unchecked"}) - private Reference @NotNull [] createReferenceArray(int size) { - return new Reference[size]; - } - - private int getIndex(int hash, @Nullable Reference[] references) { - return (hash & (references.length - 1)); - } - - /** - * Return the size of the current references array. - */ - public int getSize() { - return this.references.length; - } - - /** - * Return the total number of references in this segment. - */ - public int getCount() { - return this.count.get(); - } - } - - /** - * A task that can be {@link Segment#doTask run} against a {@link Segment}. - */ - private abstract class Task { - - private final EnumSet options; - - public Task(TaskOption... options) { - this.options = (options.length == 0 ? EnumSet.noneOf(TaskOption.class) : EnumSet.of(options[0], options)); - } - - public boolean hasOption(TaskOption option) { - return this.options.contains(option); - } - - /** - * Execute the task. - * - * @param ref the found reference (or {@code null}) - * @param entry the found entry (or {@code null}) - * @param entries access to the underlying entries - * @return the result of the task - * @see #execute(Reference, Entry) - */ - protected @Nullable T execute(@Nullable Reference ref, @Nullable Entry entry, @Nullable Entries entries) { - return execute(ref, entry); - } - - /** - * Convenience method that can be used for tasks that do not need access to {@link Entries}. - * - * @param ref the found reference (or {@code null}) - * @param entry the found entry (or {@code null}) - * @return the result of the task - * @see #execute(Reference, Entry, Entries) - */ - protected @Nullable T execute(@Nullable Reference ref, @Nullable Entry entry) { - return null; - } - } - - /** - * Internal entry-set implementation. - */ - private class EntrySet extends AbstractSet> { - - @Override - public Iterator> iterator() { - return new EntryIterator(); - } - - @Override - public boolean contains(@Nullable Object o) { - if (o instanceof Map.Entry entry) { - Reference ref = ConcurrentReferenceHashMap.this.getReference(entry.getKey(), Restructure.NEVER); - Entry otherEntry = (ref != null ? ref.get() : null); - if (otherEntry != null) { - return ObjectUtil.nullSafeEquals(entry.getValue(), otherEntry.getValue()); - } - } - return false; - } - - @Override - public boolean remove(Object o) { - if (o instanceof Map.Entry entry) { - return ConcurrentReferenceHashMap.this.remove(entry.getKey(), entry.getValue()); - } - return false; - } - - @Override - public int size() { - return ConcurrentReferenceHashMap.this.size(); - } - - @Override - public void clear() { - ConcurrentReferenceHashMap.this.clear(); - } - } - - /** - * Internal entry iterator implementation. - */ - private class EntryIterator implements Iterator> { - - private int segmentIndex; - - private int referenceIndex; - - private @Nullable Reference @Nullable [] references; - - private @Nullable Reference reference; - - private @Nullable Entry next; - - private @Nullable Entry last; - - public EntryIterator() { - moveToNextSegment(); - } - - @Override - public boolean hasNext() { - getNextIfNecessary(); - return (this.next != null); - } - - @Override - public Entry next() { - getNextIfNecessary(); - if (this.next == null) { - throw new NoSuchElementException(); - } - this.last = this.next; - this.next = null; - return this.last; - } - - private void getNextIfNecessary() { - while (this.next == null) { - moveToNextReference(); - if (this.reference == null) { - return; - } - this.next = this.reference.get(); - } - } - - private void moveToNextReference() { - if (this.reference != null) { - this.reference = this.reference.getNext(); - } - while (this.reference == null && this.references != null) { - if (this.referenceIndex >= this.references.length) { - moveToNextSegment(); - this.referenceIndex = 0; - } else { - this.reference = this.references[this.referenceIndex]; - this.referenceIndex++; - } - } - } - - private void moveToNextSegment() { - this.reference = null; - this.references = null; - if (this.segmentIndex < ConcurrentReferenceHashMap.this.segments.length) { - this.references = ConcurrentReferenceHashMap.this.segments[this.segmentIndex].references; - this.segmentIndex++; - } - } - - @Override - public void remove() { - Assert.state(this.last != null, "No element to remove"); - ConcurrentReferenceHashMap.this.remove(this.last.getKey()); - this.last = null; - } - } - - /** - * Strategy class used to manage {@link Reference References}. - * This class can be overridden if alternative reference types need to be supported. - */ - protected class ReferenceManager { - - private final ReferenceQueue> queue = new ReferenceQueue<>(); - - /** - * Factory method used to create a new {@link Reference}. - * - * @param entry the entry contained in the reference - * @param hash the hash - * @param next the next reference in the chain, or {@code null} if none - * @return a new {@link Reference} - */ - public Reference createReference(Entry entry, int hash, @Nullable Reference next) { - if (ConcurrentReferenceHashMap.this.referenceType == ReferenceType.WEAK) { - return new WeakEntryReference<>(entry, hash, next, this.queue); - } - return new SoftEntryReference<>(entry, hash, next, this.queue); - } - - /** - * Return any reference that has been garbage collected and can be purged from the - * underlying structure or {@code null} if no references need purging. This - * method must be thread safe and ideally should not block when returning - * {@code null}. References should be returned once and only once. - * - * @return a reference to purge or {@code null} - */ - @SuppressWarnings("unchecked") - public @Nullable Reference pollForPurge() { - return (Reference) this.queue.poll(); - } - } -}