Merge branch 'cassandra-4.0' into cassandra-4.1

* cassandra-4.0:
  Fix few types issues and implement types compatibility tests
This commit is contained in:
Jacek Lewandowski 2024-04-18 12:33:53 +02:00
commit 445ae1a4b1
26 changed files with 2162 additions and 63 deletions

View File

@ -3,6 +3,7 @@
* Do not go to disk for reading hints file sizes (CASSANDRA-19477)
* Fix system_views.settings to handle array types (CASSANDRA-19475)
Merged from 4.0:
* Fix few types issues and implement types compatibility tests (CASSANDRA-19479)
* Optionally avoid hint transfer during decommission (CASSANDRA-19525)
* Change logging to TRACE when failing to get peer certificate (CASSANDRA-19508)
* Push LocalSessions info logs to debug (CASSANDRA-18335)

View File

@ -737,9 +737,9 @@
<dependency groupId="com.google.j2objc" artifactId="j2objc-annotations" version="1.3"/>
<!-- adding this dependency is necessary for assertj. When updating assertj, need to also update the version of
this that the new assertj's `assertj-parent-pom` depends on. -->
<dependency groupId="org.junit" artifactId="junit-bom" version="5.6.0" type="pom" scope="test"/>
<dependency groupId="org.junit" artifactId="junit-bom" version="5.10.1" type="pom" scope="test"/>
<!-- when updating assertj, make sure to also update the corresponding junit-bom dependency -->
<dependency groupId="org.assertj" artifactId="assertj-core" version="3.15.0" scope="test"/>
<dependency groupId="org.assertj" artifactId="assertj-core" version="3.25.3" scope="test"/>
<dependency groupId="org.awaitility" artifactId="awaitility" version="4.0.3" scope="test">
<exclusion groupId="org.hamcrest" artifactId="hamcrest"/>
</dependency>

View File

@ -59,7 +59,7 @@ public abstract class AbstractCompositeType extends AbstractType<ByteBuffer>
int offsetL = startingOffset(isStaticL);
int offsetR = startingOffset(isStaticR);
while (!accessorL.isEmptyFromOffset(left, offsetL) && !accessorR.isEmptyFromOffset(right, offsetL))
while (!accessorL.isEmptyFromOffset(left, offsetL) && !accessorR.isEmptyFromOffset(right, offsetR))
{
AbstractType<?> comparator = getComparator(i, left, accessorL, right, accessorR, offsetL, offsetR);
offsetL += getComparatorSize(i, left, accessorL, offsetL);

View File

@ -248,6 +248,13 @@ public class ByteArrayAccessor implements ValueAccessor<byte[]>
return Ballot.deserialize(value);
}
@Override
public int putByte(byte[] dst, int offset, byte value)
{
dst[offset] = value;
return TypeSizes.BYTE_SIZE;
}
@Override
public int putShort(byte[] dst, int offset, short value)
{

View File

@ -252,6 +252,13 @@ public class ByteBufferAccessor implements ValueAccessor<ByteBuffer>
return Ballot.deserialize(value);
}
@Override
public int putByte(ByteBuffer dst, int offset, byte value)
{
dst.put(dst.position() + offset, value);
return TypeSizes.BYTE_SIZE;
}
@Override
public int putShort(ByteBuffer dst, int offset, short value)
{

View File

@ -21,6 +21,7 @@ import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@ -29,7 +30,9 @@ import com.google.common.collect.Lists;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.exceptions.SyntaxException;
import org.apache.cassandra.serializers.BytesSerializer;
import org.apache.cassandra.serializers.MarshalException;
import org.apache.cassandra.serializers.TypeSerializer;
import org.apache.cassandra.utils.ByteBufferUtil;
import static com.google.common.collect.Iterables.any;
@ -62,9 +65,37 @@ import static com.google.common.collect.Iterables.transform;
*/
public class CompositeType extends AbstractCompositeType
{
private static class Serializer extends BytesSerializer
{
// types are held to make sure the serializer is unique for each collection of types, this is to make sure it's
// safe to cache in all cases
public final List<AbstractType<?>> types;
public Serializer(List<AbstractType<?>> types)
{
this.types = types;
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Serializer that = (Serializer) o;
return types.equals(that.types);
}
@Override
public int hashCode()
{
return Objects.hash(types);
}
}
private static final int STATIC_MARKER = 0xFFFF;
public final List<AbstractType<?>> types;
private final Serializer serializer;
// interning instances
private static final ConcurrentMap<List<AbstractType<?>>, CompositeType> instances = new ConcurrentHashMap<>();
@ -136,8 +167,16 @@ public class CompositeType extends AbstractCompositeType
protected CompositeType(List<AbstractType<?>> types)
{
this.types = ImmutableList.copyOf(types);
this.serializer = new Serializer(this.types);
}
@Override
public TypeSerializer<ByteBuffer> getSerializer()
{
return serializer;
}
protected <V> AbstractType<?> getComparator(int i, V value, ValueAccessor<V> accessor, int offset)
{
try

View File

@ -20,8 +20,11 @@ package org.apache.cassandra.db.marshal;
import java.nio.ByteBuffer;
import java.nio.charset.CharacterCodingException;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -30,6 +33,7 @@ import org.apache.cassandra.cql3.Term;
import org.apache.cassandra.db.TypeSizes;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.exceptions.SyntaxException;
import org.apache.cassandra.serializers.BytesSerializer;
import org.apache.cassandra.serializers.MarshalException;
import org.apache.cassandra.serializers.TypeSerializer;
import org.apache.cassandra.transport.ProtocolVersion;
@ -60,7 +64,36 @@ public class DynamicCompositeType extends AbstractCompositeType
{
private static final Logger logger = LoggerFactory.getLogger(DynamicCompositeType.class);
private final Map<Byte, AbstractType<?>> aliases;
public static class Serializer extends BytesSerializer
{
// aliases are held to make sure the serializer is unique for each collection of types, this is to make sure it's
// safe to cache in all cases
private final Map<Byte, AbstractType<?>> aliases;
public Serializer(Map<Byte, AbstractType<?>> aliases)
{
this.aliases = aliases;
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Serializer that = (Serializer) o;
return aliases.equals(that.aliases);
}
@Override
public int hashCode()
{
return Objects.hash(aliases);
}
}
@VisibleForTesting
public final Map<Byte, AbstractType<?>> aliases;
private final Serializer serializer;
// interning instances
private static final ConcurrentHashMap<Map<Byte, AbstractType<?>>, DynamicCompositeType> instances = new ConcurrentHashMap<>();
@ -80,7 +113,14 @@ public class DynamicCompositeType extends AbstractCompositeType
private DynamicCompositeType(Map<Byte, AbstractType<?>> aliases)
{
this.aliases = aliases;
this.aliases = ImmutableMap.copyOf(aliases);
this.serializer = new Serializer(aliases);
}
@Override
public TypeSerializer<ByteBuffer> getSerializer()
{
return serializer;
}
protected <V> boolean readIsStatic(V value, ValueAccessor<V> accessor)
@ -378,7 +418,8 @@ public class DynamicCompositeType extends AbstractCompositeType
* A comparator that always sorts it's first argument before the second
* one.
*/
private static class FixedValueComparator extends AbstractType<Void>
@VisibleForTesting
public static class FixedValueComparator extends AbstractType<Void>
{
public static final FixedValueComparator alwaysLesserThan = new FixedValueComparator(-1);
public static final FixedValueComparator alwaysGreaterThan = new FixedValueComparator(1);

View File

@ -31,6 +31,12 @@ public class LexicalUUIDType extends AbstractType<UUID>
{
public static final LexicalUUIDType instance = new LexicalUUIDType();
// we just need it to be something different to UUIDSerializer
private static class Serializer extends UUIDSerializer
{
public static final Serializer instance = new Serializer();
}
LexicalUUIDType()
{
super(ComparisonType.CUSTOM);
@ -80,7 +86,7 @@ public class LexicalUUIDType extends AbstractType<UUID>
public TypeSerializer<UUID> getSerializer()
{
return UUIDSerializer.instance;
return Serializer.instance;
}
@Override

View File

@ -24,6 +24,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.regex.Pattern;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Objects;
import com.google.common.collect.Lists;
@ -63,7 +64,8 @@ public class TupleType extends AbstractType<ByteBuffer>
this(types, true);
}
protected TupleType(List<AbstractType<?>> types, boolean freezeInner)
@VisibleForTesting
public TupleType(List<AbstractType<?>> types, boolean freezeInner)
{
super(ComparisonType.CUSTOM);
@ -441,7 +443,7 @@ public class TupleType extends AbstractType<ByteBuffer>
@Override
public boolean equals(Object o)
{
if(!(o instanceof TupleType))
if (o.getClass() != TupleType.class)
return false;
TupleType that = (TupleType)o;

View File

@ -225,7 +225,7 @@ public class TypeParser
String alias = readNextIdentifier();
if (alias.length() != 1)
throwSyntaxError("An alias should be a single character");
throwSyntaxError("An alias should be a single character: '" + alias + "', string: " + str);
char aliasChar = alias.charAt(0);
if (aliasChar < 33 || aliasChar > 127)
throwSyntaxError("An alias should be a single character in [0..9a..bA..B-+._&]");

View File

@ -342,7 +342,7 @@ public class UserType extends TupleType implements SchemaElement
@Override
public boolean equals(Object o)
{
if(!(o instanceof UserType))
if (o.getClass() != UserType.class)
return false;
UserType that = (UserType)o;

View File

@ -330,6 +330,12 @@ public interface ValueAccessor<V>
/** returns a TimeUUID from offset 0 */
Ballot toBallot(V value);
/**
* writes the byte value {@param value} to {@param dst} at offset {@param offset}
* @return the number of bytes written to {@param value}
*/
int putByte(V dst, int offset, byte value);
/**
* writes the short value {@param value} to {@param dst} at offset {@param offset}
* @return the number of bytes written to {@param value}

View File

@ -303,4 +303,9 @@ public class CassandraVersion implements Comparable<CassandraVersion>
sb.append('+').append(StringUtils.join(build, "."));
return sb.toString();
}
public String toMajorMinorString()
{
return String.format("%d.%d", major, minor);
}
}

View File

@ -70,6 +70,11 @@ public class FastByteOperations
return BestHolder.BEST.compare(b1, b2);
}
public static int compareUnsigned(byte[] b1, byte[] b2)
{
return compareUnsigned(b1, 0, b1.length, b2, 0, b2.length);
}
public static void copy(byte[] src, int srcPosition, byte[] trg, int trgPosition, int length)
{
BestHolder.BEST.copy(src, srcPosition, trg, trgPosition, length);

Binary file not shown.

Binary file not shown.

View File

@ -526,6 +526,6 @@ public class SSTableIdGenerationTest extends TestBaseImpl
SimpleQueryResult result = instance.executeInternalWithResult(format("SELECT * FROM %s.%s", ks, tableName));
Object[][] rows = result.toObjectArrays();
assertThat(rows).withFailMessage("Invalid results for %s.%s - should have %d rows but has %d: \n%s", ks, tableName, expectedNumber,
rows.length, result.toString()).hasSize(expectedNumber);
rows.length, result.toString()).hasNumberOfRows(expectedNumber);
}
}

View File

@ -26,6 +26,7 @@ import org.apache.cassandra.distributed.api.Row;
import org.apache.cassandra.distributed.api.SimpleQueryResult;
import org.apache.cassandra.tools.nodetool.formatter.TableBuilder;
import org.assertj.core.api.Assertions;
import org.assertj.core.data.Index;
public class QueryResultUtil
{
@ -161,20 +162,20 @@ public class QueryResultUtil
public SimpleQueryResultAssertHelper isEqualTo(Object... values)
{
Assertions.assertThat(qr.toObjectArrays())
.hasSize(1)
.contains(values);
.hasNumberOfRows(1)
.contains(values, Index.atIndex(0));
return this;
}
public SimpleQueryResultAssertHelper hasSize(int size)
{
Assertions.assertThat(qr.toObjectArrays()).hasSize(size);
Assertions.assertThat((Object[]) qr.toObjectArrays()).hasSize(size);
return this;
}
public SimpleQueryResultAssertHelper hasSizeGreaterThan(int size)
{
Assertions.assertThat(qr.toObjectArrays()).hasSizeGreaterThan(size);
Assertions.assertThat((Object[]) qr.toObjectArrays()).hasSizeGreaterThan(size);
return this;
}

View File

@ -33,6 +33,8 @@ import org.junit.Test;
import org.apache.cassandra.cql3.CQLTester;
import org.apache.cassandra.cql3.UntypedResultSet;
import org.apache.cassandra.db.marshal.DecimalType;
import org.apache.cassandra.db.marshal.DurationType;
import org.apache.cassandra.db.marshal.TupleType;
import org.apache.cassandra.utils.AbstractTypeGenerators.TypeSupport;
import org.quicktheories.core.Gen;
@ -332,7 +334,8 @@ public class TupleTypeTest extends CQLTester
private static Gen<TypeAndRows> typesAndRowsGen(int numRows)
{
Gen<TupleType> typeGen = tupleTypeGen(primitiveTypeGen(), SourceDSL.integers().between(1, 10));
// duration type is invalid for keys and decimal type is problematic for equality checks
Gen<TupleType> typeGen = tupleTypeGen(primitiveTypeGen(DurationType.instance, DecimalType.instance), SourceDSL.integers().between(1, 10));
Set<ByteBuffer> distinctRows = new HashSet<>(numRows); // reuse the memory
Gen<TypeAndRows> gen = rnd -> {
TypeAndRows c = new TypeAndRows();

File diff suppressed because it is too large Load Diff

View File

@ -37,7 +37,6 @@ import java.util.stream.Collectors;
import com.google.common.collect.ImmutableMap;
import com.google.common.util.concurrent.ListenableFuture;
import org.assertj.core.api.Assertions;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
@ -76,18 +75,18 @@ import org.apache.cassandra.utils.ObjectSizes;
import org.apache.cassandra.utils.Throwables;
import org.apache.cassandra.utils.TimeUUID;
import org.apache.cassandra.utils.asserts.SyncTaskListAssert;
import org.assertj.core.api.Assertions;
import static java.util.Collections.emptySet;
import static org.apache.cassandra.net.Verb.PAXOS2_CLEANUP_REQ;
import static org.apache.cassandra.net.Verb.PAXOS2_CLEANUP_START_PREPARE_REQ;
import static org.apache.cassandra.repair.RepairParallelism.SEQUENTIAL;
import static org.apache.cassandra.streaming.PreviewKind.NONE;
import static org.apache.cassandra.utils.TimeUUID.Generator.nextTimeUUID;
import static org.apache.cassandra.utils.asserts.SyncTaskAssert.assertThat;
import static org.apache.cassandra.utils.asserts.SyncTaskListAssert.assertThat;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.apache.cassandra.net.Verb.PAXOS2_CLEANUP_START_PREPARE_REQ;
import static org.apache.cassandra.net.Verb.PAXOS2_CLEANUP_REQ;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@ -712,7 +711,7 @@ public class RepairJobTest
false,
PreviewKind.ALL));
assertThat(tasks.values()).areAllInstanceOf(AsymmetricRemoteSyncTask.class);
SyncTaskListAssert.assertThat(tasks.values()).areAllInstanceOf(AsymmetricRemoteSyncTask.class);
// addr1 streams range1 from addr3:
assertThat(tasks.get(pair(addr1, addr3)).rangesToSync).contains(RANGE_1);

View File

@ -33,6 +33,7 @@ import org.apache.commons.lang3.builder.MultilineRecursiveToStringStyle;
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.apache.cassandra.cql3.ColumnIdentifier;
import org.apache.cassandra.cql3.Duration;
import org.apache.cassandra.cql3.FieldIdentifier;
import org.apache.cassandra.db.ReadCommand;
import org.apache.cassandra.db.SchemaCQLHelper;
@ -316,4 +317,24 @@ public final class CassandraGenerators
}
}, true);
}
public static Gen<Duration> duration()
{
Constraint ints = Constraint.between(0, Integer.MAX_VALUE);
Constraint longs = Constraint.between(0, Long.MAX_VALUE);
Gen<Boolean> neg = SourceDSL.booleans().all();
return rnd -> {
int months = (int) rnd.next(ints);
int days = (int) rnd.next(ints);
long nanoseconds = rnd.next(longs);
if (neg.generate(rnd))
{
months = -1 * months;
days = -1 * days;
nanoseconds = -1 * nanoseconds;
}
return Duration.newInstance(months, days, nanoseconds);
};
}
}

View File

@ -17,6 +17,8 @@
*/
package org.apache.cassandra.utils;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
@ -61,6 +63,10 @@ public final class Generators
public static final Gen<String> IDENTIFIER_GEN = Generators.regexWord(SourceDSL.integers().between(1, 50));
public static Gen<Character> letterOrDigit()
{
return SourceDSL.integers().between(0, LETTER_OR_DIGIT_DOMAIN.length - 1).map(idx -> LETTER_OR_DIGIT_DOMAIN[idx]);
}
public static final Gen<UUID> UUID_RANDOM_GEN = rnd -> {
long most = rnd.next(Constraint.none());
most &= 0x0f << 8; /* clear version */
@ -71,6 +77,16 @@ public final class Generators
return new UUID(most, least);
};
public static final Gen<UUID> UUID_TIME_GEN = rnd -> {
long most = rnd.next(Constraint.none());
most &= 0x0f << 8; /* clear version */
most += 0x10 << 8; /* set to version 1 */
long least = rnd.next(Constraint.none());
least &= 0x3fl << 56; /* clear variant */
least |= 0x80l << 56; /* set to IETF variant */
return new UUID(most, least);
};
public static final Gen<String> DNS_DOMAIN_NAME = rnd -> {
// how many parts to generate
int numParts = (int) rnd.next(DNS_DOMAIN_PARTS_CONSTRAINT);
@ -331,6 +347,48 @@ public final class Generators
.map(s -> new String(s.getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8));
}
public static Gen<BigInteger> bigInt()
{
return bigInt(SourceDSL.integers().between(1, 32));
}
public static Gen<BigInteger> bigInt(Gen<Integer> numBitsGen)
{
Gen<Integer> signumGen = SourceDSL.arbitrary().pick(-1, 0, 1);
return rnd -> {
int signum = signumGen.generate(rnd);
if (signum == 0)
return BigInteger.ZERO;
int numBits = numBitsGen.generate(rnd);
if (numBits < 0)
throw new IllegalArgumentException("numBits must be non-negative");
int numBytes = (int)(((long)numBits+7)/8); // avoid overflow
// Generate random bytes and mask out any excess bits
byte[] randomBits = new byte[0];
if (numBytes > 0) {
randomBits = bytes(numBytes, numBytes).map(bb -> ByteBufferUtil.getArray(bb)).generate(rnd);
int excessBits = 8*numBytes - numBits;
randomBits[0] &= (1 << (8-excessBits)) - 1;
}
return new BigInteger(signum, randomBits);
};
}
public static Gen<BigDecimal> bigDecimal()
{
return bigDecimal(SourceDSL.integers().between(1, 100), bigInt());
}
public static Gen<BigDecimal> bigDecimal(Gen<Integer> scaleGen, Gen<BigInteger> bigIntegerGen)
{
return rnd -> {
int scale = scaleGen.generate(rnd);
BigInteger bigInt = bigIntegerGen.generate(rnd);
return new BigDecimal(bigInt, scale);
};
}
private static boolean isDash(char c)
{
switch (c)

View File

@ -0,0 +1,44 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.utils.asserts;
import java.util.concurrent.atomic.AtomicInteger;
import org.assertj.core.api.SoftAssertions;
public class SoftAssertionsWithLimit extends SoftAssertions
{
private final AtomicInteger counter = new AtomicInteger();
private final int limit;
public SoftAssertionsWithLimit(int limit)
{
this.limit = limit;
}
@Override
public void collectAssertionError(AssertionError error)
{
int cnt = counter.incrementAndGet();
if (cnt < limit)
super.collectAssertionError(error);
else if (cnt == limit)
super.collectAssertionError(new AssertionError("Too many assertion errors, stopping collecting them."));
}
}

View File

@ -0,0 +1,112 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.quicktheories.impl;
import java.util.Objects;
import java.util.Random;
import org.quicktheories.core.DetatchedRandomnessSource;
import org.quicktheories.core.RandomnessSource;
/**
* The {@link Constraint} class does not expose {@link Constraint#min()} or {@link Constraint#max()} outside the package, so
* the only way to build a {@link RandomnessSource} is to define it in the package "org.quicktheories.impl"
*/
public class JavaRandom implements RandomnessSource, DetatchedRandomnessSource
{
private final Random random;
public JavaRandom(long seed)
{
this.random = new Random(seed);
}
public JavaRandom(Random random)
{
this.random = Objects.requireNonNull(random);
}
public void setSeed(long seed)
{
this.random.setSeed(seed);
}
public static JavaRandom wrap(RandomnessSource rnd)
{
if (rnd instanceof JavaRandom)
return (JavaRandom) rnd;
return new JavaRandom(rnd.next(Constraint.none().withNoShrinkPoint()));
}
@Override
public long next(Constraint constraint)
{
long max = constraint.max();
return nextLong(constraint.min(), max == Long.MAX_VALUE ? max : max + 1);
}
private long nextLong(long minInclusive, long maxExclusive)
{
// pulled from accord.utils.RandomSource#nextLong(long, long)...
// long term it will be great to unify the two and drop QuickTheories classes all together
// this is diff behavior than ThreadLocalRandom, which returns nextLong
if (minInclusive >= maxExclusive)
throw new IllegalArgumentException(String.format("Min (%s) should be less than max (%d).", minInclusive, maxExclusive));
long result = random.nextLong();
long delta = maxExclusive - minInclusive;
long mask = delta - 1;
if ((delta & mask) == 0L) // power of two
result = (result & mask) + minInclusive;
else if (delta > 0L)
{
// reject over-represented candidates
for (long u = result >>> 1; // ensure nonnegative
u + mask - (result = u % delta) < 0L; // rejection check
u = random.nextLong() >>> 1) // retry
;
result += minInclusive;
}
else
{
// range not representable as long
while (result < minInclusive || result >= maxExclusive)
result = random.nextLong();
}
return result;
}
@Override
public DetatchedRandomnessSource detach()
{
return this;
}
@Override
public void registerFailedAssumption()
{
}
@Override
public void commit()
{
// no-op
}
}