/*
 * 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;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.URL;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NavigableSet;
import java.util.Optional;
import java.util.Properties;
import java.util.Scanner;
import java.util.TreeSet;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.CRC32;
import java.util.zip.Checksum;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
import com.vdurmont.semver4j.Semver;
import com.vdurmont.semver4j.SemverException;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.apache.cassandra.audit.IAuditLogger;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.dht.IPartitioner;
import org.apache.cassandra.dht.LocalPartitioner;
import org.apache.cassandra.dht.Range;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.io.IVersionedSerializer;
import org.apache.cassandra.io.sstable.Descriptor;
import org.apache.cassandra.io.sstable.format.StatsComponent;
import org.apache.cassandra.io.sstable.metadata.MetadataType;
import org.apache.cassandra.io.util.DataOutputBuffer;
import org.apache.cassandra.io.util.DataOutputBufferFixed;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.io.util.FileUtils;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.security.AbstractCryptoProvider;
import org.apache.cassandra.security.ISslContextFactory;
import org.apache.cassandra.utils.concurrent.FutureCombiner;
import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException;
import org.objectweb.asm.Opcodes;

import static org.apache.cassandra.config.CassandraRelevantProperties.CASSANDRA_AVAILABLE_PROCESSORS;
import static org.apache.cassandra.config.CassandraRelevantProperties.GIT_SHA;
import static org.apache.cassandra.config.CassandraRelevantProperties.LINE_SEPARATOR;
import static org.apache.cassandra.config.CassandraRelevantProperties.OS_NAME;
import static org.apache.cassandra.config.CassandraRelevantProperties.RELEASE_VERSION;
import static org.apache.cassandra.config.CassandraRelevantProperties.TRIGGERS_DIR;
import static org.apache.cassandra.config.CassandraRelevantProperties.USER_HOME;
import static org.apache.cassandra.utils.Clock.Global.currentTimeMillis;
import static org.apache.cassandra.utils.Clock.Global.nanoTime;

public class FBUtilities
{
    static
    {
        preventIllegalAccessWarnings();
    }

    private static final Logger logger = LoggerFactory.getLogger(FBUtilities.class);
    public static final String UNKNOWN_RELEASE_VERSION = "Unknown";
    public static final String UNKNOWN_GIT_SHA = "Unknown";

    public static final BigInteger TWO = new BigInteger("2");
    private static final String DEFAULT_TRIGGER_DIR = "triggers";

    private static final String OPERATING_SYSTEM = OS_NAME.getString().toLowerCase();
    public static final boolean isLinux = OPERATING_SYSTEM.contains("linux");

    private static volatile InetAddress localInetAddress;
    private static volatile InetAddress broadcastInetAddress;
    private static volatile InetAddress broadcastNativeAddress;
    private static volatile InetAddressAndPort broadcastNativeAddressAndPort;
    private static volatile InetAddressAndPort broadcastInetAddressAndPort;
    private static volatile InetAddressAndPort localInetAddressAndPort;

    private static volatile String previousReleaseVersionString;

    private static int availableProcessors = CASSANDRA_AVAILABLE_PROCESSORS.getInt(DatabaseDescriptor.getAvailableProcessors());

    private static volatile Supplier<Semver> kernelVersionSupplier = Suppliers.memoize(FBUtilities::getKernelVersionFromUname);

    public static void setAvailableProcessors(int value)
    {
        availableProcessors = value;
    }

    @VisibleForTesting
    public static void setKernelVersionSupplier(Supplier<Semver> supplier)
    {
        kernelVersionSupplier = supplier;
    }

    public static int getAvailableProcessors()
    {
        if (availableProcessors > 0)
            return availableProcessors;
        else
            return Runtime.getRuntime().availableProcessors();
    }

    public static final int MAX_UNSIGNED_SHORT = 0xFFFF;

    public static final int ASM_BYTECODE_VERSION = Opcodes.ASM9;

    public static MessageDigest newMessageDigest(String algorithm)
    {
        try
        {
            return MessageDigest.getInstance(algorithm);
        }
        catch (NoSuchAlgorithmException nsae)
        {
            throw new RuntimeException("the requested digest algorithm (" + algorithm + ") is not available", nsae);
        }
    }

    /**
     * Please use getJustBroadcastAddress instead. You need this only when you have to listen/connect. It's also missing
     * the port you should be using. 99% of code doesn't want this.
     */
    public static InetAddress getJustLocalAddress()
    {
        if (localInetAddress == null)
        {
            if (DatabaseDescriptor.getListenAddress() == null)
            {
                try
                {
                    localInetAddress = InetAddress.getLocalHost();
                    logger.info("InetAddress.getLocalHost() was used to resolve listen_address to {}, double check this is "
                                + "correct. Please check your node's config and set the listen_address in cassandra.yaml accordingly if applicable.",
                                localInetAddress);
                }
                catch(UnknownHostException e)
                {
                    logger.info("InetAddress.getLocalHost() could not resolve the address for the hostname ({}), please "
                                + "check your node's config and set the listen_address in cassandra.yaml. Falling back to {}",
                                e,
                                InetAddress.getLoopbackAddress());
                    // CASSANDRA-15901 fallback for misconfigured nodes
                    localInetAddress = InetAddress.getLoopbackAddress();
                }
            }
            else
                localInetAddress = DatabaseDescriptor.getListenAddress();
        }
        return localInetAddress;
    }

    /**
     * The address and port to listen on for intra-cluster storage traffic (not client). Use this to get the correct
     * stuff to listen on for intra-cluster communication.
     */
    public static InetAddressAndPort getLocalAddressAndPort()
    {
        if (localInetAddressAndPort == null)
        {
            if(DatabaseDescriptor.getRawConfig() == null)
            {
                localInetAddressAndPort = InetAddressAndPort.getByAddress(getJustLocalAddress());
            }
            else
            {
                localInetAddressAndPort = InetAddressAndPort.getByAddressOverrideDefaults(getJustLocalAddress(),
                                                                                          DatabaseDescriptor.getStoragePort());
            }
        }
        return localInetAddressAndPort;
    }

    /**
     * Retrieve just the broadcast address but not the port. This is almost always the wrong thing to be using because
     * it's ambiguous since you need the address and port to identify a node. You want getBroadcastAddressAndPort
     */
    public static InetAddress getJustBroadcastAddress()
    {
        if (broadcastInetAddress == null)
            broadcastInetAddress = DatabaseDescriptor.getBroadcastAddress() == null
                                 ? getJustLocalAddress()
                                 : DatabaseDescriptor.getBroadcastAddress();
        return broadcastInetAddress;
    }

    /**
     * Get the broadcast address and port for intra-cluster storage traffic. This the address to advertise that uniquely
     * identifies the node and is reachable from everywhere. This is the one you want unless you are trying to connect
     * to the local address specifically.
     */
    public static InetAddressAndPort getBroadcastAddressAndPort()
    {
        if (broadcastInetAddressAndPort == null)
        {
            if(DatabaseDescriptor.getRawConfig() == null)
            {
                broadcastInetAddressAndPort = InetAddressAndPort.getByAddress(getJustBroadcastAddress());
            }
            else
            {
                broadcastInetAddressAndPort = InetAddressAndPort.getByAddressOverrideDefaults(getJustBroadcastAddress(),
                                                                                              DatabaseDescriptor.getStoragePort());
            }
        }
        return broadcastInetAddressAndPort;
    }

    /**
     * <b>THIS IS FOR TESTING ONLY!!</b>
     */
    public static void setBroadcastInetAddress(InetAddress addr)
    {
        broadcastInetAddress = addr;
        broadcastInetAddressAndPort = InetAddressAndPort.getByAddress(broadcastInetAddress);
    }

    /**
     * <b>THIS IS FOR TESTING ONLY!!</b>
     */
    public static void setBroadcastInetAddressAndPort(InetAddressAndPort addr)
    {
        broadcastInetAddress = addr.getAddress();
        broadcastInetAddressAndPort = addr;
    }

    /**
     * This returns the address that is bound to for the native protocol for communicating with clients. This is ambiguous
     * because it doesn't include the port and it's almost always the wrong thing to be using you want getBroadcastNativeAddressAndPort
     */
    public static InetAddress getJustBroadcastNativeAddress()
    {
        if (broadcastNativeAddress == null)
            broadcastNativeAddress = DatabaseDescriptor.getBroadcastRpcAddress() == null
                                   ? DatabaseDescriptor.getRpcAddress()
                                   : DatabaseDescriptor.getBroadcastRpcAddress();
        return broadcastNativeAddress;
    }

    /**
     * This returns the address that is bound to for the native protocol for communicating with clients. This is almost
     * always what you need to identify a node and how to connect to it as a client.
     */
    public static InetAddressAndPort getBroadcastNativeAddressAndPort()
    {
        if (broadcastNativeAddressAndPort == null)
            if(DatabaseDescriptor.getRawConfig() == null)
            {
                broadcastNativeAddressAndPort = InetAddressAndPort.getByAddress(getJustBroadcastNativeAddress());
            }
            else
            {
                broadcastNativeAddressAndPort = InetAddressAndPort.getByAddressOverrideDefaults(getJustBroadcastNativeAddress(),
                                                                                                DatabaseDescriptor.getNativeTransportPort());
            }
        return broadcastNativeAddressAndPort;
    }

    public static String getNetworkInterface(InetAddress localAddress)
    {
        try
        {
            for(NetworkInterface ifc : Collections.list(NetworkInterface.getNetworkInterfaces()))
            {
                if(ifc.isUp())
                {
                    for(InetAddress addr : Collections.list(ifc.getInetAddresses()))
                    {
                        if (addr.equals(localAddress))
                            return ifc.getDisplayName();
                    }
                }
            }
        }
        catch (SocketException e) {}
        return null;
    }

    /**
     * Given two bit arrays represented as BigIntegers, containing the given
     * number of significant bits, calculate a midpoint.
     *
     * @param left The left point.
     * @param right The right point.
     * @param sigbits The number of bits in the points that are significant.
     * @return A midpoint that will compare bitwise halfway between the params, and
     * a boolean representing whether a non-zero lsbit remainder was generated.
     */
    public static Pair<BigInteger,Boolean> midpoint(BigInteger left, BigInteger right, int sigbits)
    {
        BigInteger midpoint;
        boolean remainder;
        if (left.compareTo(right) < 0)
        {
            BigInteger sum = left.add(right);
            remainder = sum.testBit(0);
            midpoint = sum.shiftRight(1);
        }
        else
        {
            BigInteger max = TWO.pow(sigbits);
            // wrapping case
            BigInteger distance = max.add(right).subtract(left);
            remainder = distance.testBit(0);
            midpoint = distance.shiftRight(1).add(left).mod(max);
        }
        return Pair.create(midpoint, remainder);
    }

    public static int compareUnsigned(byte[] bytes1, byte[] bytes2, int offset1, int offset2, int len1, int len2)
    {
        return FastByteOperations.compareUnsigned(bytes1, offset1, len1, bytes2, offset2, len2);
    }

    public static int compareUnsigned(byte[] bytes1, byte[] bytes2)
    {
        return compareUnsigned(bytes1, bytes2, 0, 0, bytes1.length, bytes2.length);
    }

    public static void sortSampledKeys(List<DecoratedKey> keys, Range<Token> range)
    {
        if (range.left.compareTo(range.right) >= 0)
        {
            // range wraps.  have to be careful that we sort in the same order as the range to find the right midpoint.
            final Token right = range.right;
            Comparator<DecoratedKey> comparator = new Comparator<DecoratedKey>()
            {
                public int compare(DecoratedKey o1, DecoratedKey o2)
                {
                    if ((right.compareTo(o1.getToken()) < 0 && right.compareTo(o2.getToken()) < 0)
                        || (right.compareTo(o1.getToken()) > 0 && right.compareTo(o2.getToken()) > 0))
                    {
                        // both tokens are on the same side of the wrap point
                        return o1.compareTo(o2);
                    }
                    return o2.compareTo(o1);
                }
            };
            Collections.sort(keys, comparator);
        }
        else
        {
            // unwrapped range (left < right).  standard sort is all we need.
            Collections.sort(keys);
        }
    }

    public static String resourceToFile(String filename) throws ConfigurationException
    {
        ClassLoader loader = FBUtilities.class.getClassLoader();
        URL scpurl = loader.getResource(filename);
        if (scpurl == null)
            throw new ConfigurationException("unable to locate " + filename);

        return new File(scpurl.getFile()).absolutePath();
    }

    public static File cassandraTriggerDir()
    {
        File triggerDir = null;
        if (TRIGGERS_DIR.getString() != null)
        {
            triggerDir = new File(TRIGGERS_DIR.getString());
        }
        else
        {
            URL confDir = FBUtilities.class.getClassLoader().getResource(DEFAULT_TRIGGER_DIR);
            if (confDir != null)
                triggerDir = new File(confDir.getFile());
        }
        if (triggerDir == null || !triggerDir.exists())
        {
            logger.warn("Trigger directory doesn't exist, please create it and try again.");
            return null;
        }
        return triggerDir;
    }

    public static void setPreviousReleaseVersionString(String previousReleaseVersionString)
    {
        FBUtilities.previousReleaseVersionString = previousReleaseVersionString;
    }

    public static String getPreviousReleaseVersionString()
    {
        return previousReleaseVersionString;
    }

    private static final Supplier<Properties> loadedProperties = Suppliers.memoize(() -> {
        try (InputStream in = FBUtilities.class.getClassLoader().getResourceAsStream("org/apache/cassandra/config/version.properties"))
        {
            if (in == null)
                return null;
            Properties props = new Properties();
            props.load(in);
            return props;
        }
        catch (Exception e)
        {
            JVMStabilityInspector.inspectThrowable(e);
            logger.warn("Unable to load version.properties", e);
            return null;
        }
    });

    public static String getReleaseVersionString()
    {
        Properties props = loadedProperties.get();
        if (props == null)
            return RELEASE_VERSION.getString(UNKNOWN_RELEASE_VERSION);
        return props.getProperty("CassandraVersion");
    }

    public static String getGitSHA()
    {
        Properties props = loadedProperties.get();
        if (props == null)
            return GIT_SHA.getString(UNKNOWN_GIT_SHA);
        return props.getProperty("GitSHA", UNKNOWN_GIT_SHA);
    }

    public static String getReleaseVersionMajor()
    {
        String releaseVersion = FBUtilities.getReleaseVersionString();
        if (FBUtilities.UNKNOWN_RELEASE_VERSION.equals(releaseVersion))
        {
            throw new AssertionError("Release version is unknown");
        }
        return releaseVersion.substring(0, releaseVersion.indexOf('.'));
    }

    public static long timestampMicros()
    {
        // we use microsecond resolution for compatibility with other client libraries, even though
        // we can't actually get microsecond precision.
        return currentTimeMillis() * 1000;
    }

    public static long nowInSeconds()
    {
        return currentTimeMillis() / 1000l;
    }

    public static Instant now()
    {
        long epochMilli = currentTimeMillis();
        return Instant.ofEpochMilli(epochMilli);
    }

    public static <T> List<T> waitOnFutures(Iterable<? extends Future<? extends T>> futures)
    {
        return waitOnFutures(futures, -1, null);
    }

    /**
     * Block for a collection of futures, with optional timeout.
     *
     * @param futures
     * @param timeout The number of units to wait in total. If this value is less than or equal to zero,
     *           no tiemout value will be passed to {@link Future#get()}.
     * @param units The units of timeout.
     */
    public static <T> List<T> waitOnFutures(Iterable<? extends Future<? extends T>> futures, long timeout, TimeUnit units)
    {
        long endNanos = 0;
        if (timeout > 0)
            endNanos = nanoTime() + units.toNanos(timeout);
        List<T> results = new ArrayList<>();
        Throwable fail = null;
        for (Future<? extends T> f : futures)
        {
            try
            {
                if (endNanos == 0)
                {
                    results.add(f.get());
                }
                else
                {
                    long waitFor = Math.max(1, endNanos - nanoTime());
                    results.add(f.get(waitFor, TimeUnit.NANOSECONDS));
                }
            }
            catch (Throwable t)
            {
                fail = Throwables.merge(fail, t);
            }
        }
        Throwables.maybeFail(fail);
        return results;
    }

    public static <T> T waitOnFuture(Future<T> future)
    {
        try
        {
            return future.get();
        }
        catch (ExecutionException ee)
        {
            throw Throwables.cleaned(ee);
        }
        catch (InterruptedException ie)
        {
            throw new UncheckedInterruptedException(ie);
        }
    }

    public static <T, F extends Future<? extends T>> F waitOnFirstFuture(Iterable<? extends F> futures)
    {
        return waitOnFirstFuture(futures, 100);
    }
    /**
     * Only wait for the first future to finish from a list of futures. Will block until at least 1 future finishes.
     * @param futures The futures to wait on
     * @return future that completed.
     */
    public static <T, F extends Future<? extends T>> F waitOnFirstFuture(Iterable<? extends F> futures, long delay)
    {
        while (true)
        {
            Iterator<? extends F> iter = futures.iterator();
            if (!iter.hasNext())
                throw new IllegalArgumentException();

            while (true)
            {
                F f = iter.next();
                boolean isDone;
                if ((isDone = f.isDone()) || !iter.hasNext())
                {
                    try
                    {
                        f.get(delay, TimeUnit.MILLISECONDS);
                    }
                    catch (InterruptedException e)
                    {
                        throw new UncheckedInterruptedException(e);
                    }
                    catch (ExecutionException e)
                    {
                        throw new RuntimeException(e);
                    }
                    catch (TimeoutException e)
                    {
                        if (!isDone) // prevent infinite loops on bad implementations (not encountered)
                            break;
                    }
                    return f;
                }
            }
        }
    }

    /**
     * Returns a new {@link Future} wrapping the given list of futures and returning a list of their results.
     */
    public static <T> org.apache.cassandra.utils.concurrent.Future<List<T>> allOf(Collection<? extends org.apache.cassandra.utils.concurrent.Future<? extends T>> futures)
    {
        return FutureCombiner.allOf(futures);
    }

    /**
     * Create a new instance of a partitioner defined in an SSTable Descriptor
     * @param desc Descriptor of an sstable
     * @return a new IPartitioner instance
     * @throws IOException
     */
    public static IPartitioner newPartitioner(Descriptor desc) throws IOException
    {
        StatsComponent statsComponent = StatsComponent.load(desc, MetadataType.VALIDATION, MetadataType.HEADER);
        return newPartitioner(statsComponent.validationMetadata().partitioner, Optional.of(statsComponent.serializationHeader().getKeyType()));
    }

    public static IPartitioner newPartitioner(String partitionerClassName) throws ConfigurationException
    {
        return newPartitioner(partitionerClassName, Optional.empty());
    }

    @VisibleForTesting
    static IPartitioner newPartitioner(String partitionerClassName, Optional<AbstractType<?>> comparator) throws ConfigurationException
    {
        if (!partitionerClassName.contains("."))
            partitionerClassName = "org.apache.cassandra.dht." + partitionerClassName;

        if (partitionerClassName.equals("org.apache.cassandra.dht.LocalPartitioner"))
        {
            assert comparator.isPresent() : "Expected a comparator for local partitioner";
            return new LocalPartitioner(comparator.get());
        }
        return FBUtilities.instanceOrConstruct(partitionerClassName, "partitioner");
    }

    public static IAuditLogger newAuditLogger(String className, Map<String, String> parameters) throws ConfigurationException
    {
        if (!className.contains("."))
            className = "org.apache.cassandra.audit." + className;

        try
        {
            Class<?> auditLoggerClass = FBUtilities.classForName(className, "Audit logger");
            return (IAuditLogger) auditLoggerClass.getConstructor(Map.class).newInstance(parameters);
        }
        catch (Exception ex)
        {
            throw new ConfigurationException("Unable to create instance of IAuditLogger.", ex);
        }
    }

    public static ISslContextFactory newSslContextFactory(String className, Map<String,Object> parameters) throws ConfigurationException
    {
        if (!className.contains("."))
            className = "org.apache.cassandra.security." + className;

        try
        {
            Class<?> sslContextFactoryClass = Class.forName(className);
            return (ISslContextFactory) sslContextFactoryClass.getConstructor(Map.class).newInstance(parameters);
        }
        catch (Exception ex)
        {
            throw new ConfigurationException("Unable to create instance of ISslContextFactory for " + className, ex);
        }
    }

    public static AbstractCryptoProvider newCryptoProvider(String className, Map<String, String> parameters) throws ConfigurationException
    {
        try
        {
            if (!className.contains("."))
                className = "org.apache.cassandra.security." + className;

            Class<?> cryptoProviderClass = FBUtilities.classForName(className, "crypto provider class");
            return (AbstractCryptoProvider) cryptoProviderClass.getConstructor(Map.class).newInstance(Collections.unmodifiableMap(parameters));
        }
        catch (Exception e)
        {
            // no need to wrap it in another ConfgurationException if FBUtilities.classForName might throw it
            if (e instanceof ConfigurationException)
                throw (ConfigurationException) e;
            else
                throw new ConfigurationException(String.format("Unable to create an instance of crypto provider for %s", className), e);
        }
    }

    /**
     * @return The Class for the given name.
     * @param classname Fully qualified classname.
     * @param readable Descriptive noun for the role the class plays.
     * @throws ConfigurationException If the class cannot be found.
     */
    public static <T> Class<T> classForName(String classname, String readable) throws ConfigurationException
    {
        try
        {
            return (Class<T>)Class.forName(classname);
        }
        catch (ClassNotFoundException | NoClassDefFoundError e)
        {
            throw new ConfigurationException(String.format("Unable to find %s class '%s'", readable, classname), e);
        }
    }

    /**
     * Constructs an instance of the given class, which must have a no-arg or default constructor.
     * @param classname Fully qualified classname.
     * @param readable Descriptive noun for the role the class plays.
     * @throws ConfigurationException If the class cannot be found.
     */
    public static <T> T instanceOrConstruct(String classname, String readable) throws ConfigurationException
    {
        Class<T> cls = FBUtilities.classForName(classname, readable);
        try
        {
            Field instance = cls.getField("instance");
            return cls.cast(instance.get(null));
        }
        catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e)
        {
            // Could not get instance field. Try instantiating.
            return construct(cls, classname, readable);
        }
    }

    /**
     * Constructs an instance of the given class, which must have a no-arg or default constructor.
     * @param classname Fully qualified classname.
     * @param readable Descriptive noun for the role the class plays.
     * @throws ConfigurationException If the class cannot be found.
     */
    public static <T> T construct(String classname, String readable) throws ConfigurationException
    {
        Class<T> cls = FBUtilities.classForName(classname, readable);
        return construct(cls, classname, readable);
    }

    private static <T> T construct(Class<T> cls, String classname, String readable) throws ConfigurationException
    {
        try
        {
            return cls.newInstance();
        }
        catch (IllegalAccessException e)
        {
            throw new ConfigurationException(String.format("Default constructor for %s class '%s' is inaccessible.", readable, classname));
        }
        catch (InstantiationException e)
        {
            throw new ConfigurationException(String.format("Cannot use abstract class '%s' as %s.", classname, readable));
        }
        catch (Exception e)
        {
            // Catch-all because Class.newInstance() "propagates any exception thrown by the nullary constructor, including a checked exception".
            if (e.getCause() instanceof ConfigurationException)
                throw (ConfigurationException)e.getCause();
            throw new ConfigurationException(String.format("Error instantiating %s class '%s'.", readable, classname), e);
        }
    }

    public static <T> NavigableSet<T> singleton(T column, Comparator<? super T> comparator)
    {
        NavigableSet<T> s = new TreeSet<T>(comparator);
        s.add(column);
        return s;
    }

    public static <T> NavigableSet<T> emptySortedSet(Comparator<? super T> comparator)
    {
        return new TreeSet<T>(comparator);
    }

    /**
     * Make straing out of the given {@code Map}.
     *
     * @param map Map to make string.
     * @return String representation of all entries in the map,
     *         where key and value pair is concatenated with ':'.
     */
    @Nonnull
    public static String toString(@Nullable Map<?, ?> map)
    {
        if (map == null)
            return "";
        Joiner.MapJoiner joiner = Joiner.on(", ").withKeyValueSeparator(":");
        return joiner.join(map);
    }

    /**
     * Used to get access to protected/private field of the specified class
     * @param klass - name of the class
     * @param fieldName - name of the field
     * @return Field or null on error
     */
    public static Field getProtectedField(Class klass, String fieldName)
    {
        try
        {
            Field field = klass.getDeclaredField(fieldName);
            field.setAccessible(true);
            return field;
        }
        catch (Exception e)
        {
            throw new AssertionError(e);
        }
    }

    public static <T> CloseableIterator<T> closeableIterator(Iterator<T> iterator)
    {
        return new WrappedCloseableIterator<T>(iterator);
    }

    final static String UNIT_PREFIXES = "qryzafpnum KMGTPEZYRQ";
    final static int UNIT_PREFIXES_BASE = UNIT_PREFIXES.indexOf(' ');
    final static Pattern BASE_NUMBER_PATTERN = Pattern.compile("NaN|[+-]?Infinity|[+-]?\\d+(\\.\\d+)?([eE]([+-]?)\\d+)?");
    final static Pattern BINARY_EXPONENT = Pattern.compile("\\*2\\^([+-]?\\d+)");

    /**
     * Convert the given size in bytes to a human-readable value using binary (i.e. 2^10-based) modifiers.
     * For example, 1.000KiB, 2.100GiB etc., up to 8.000 EiB.
     * @param size      Number to convert.
     */
    public static String prettyPrintMemory(long size)
    {
        return prettyPrintMemory(size, "");
    }

    /**
     * Convert the given size in bytes to a human-readable value using binary (i.e. 2^10-based) modifiers.
     * For example, 1.000KiB, 2.100GiB etc., up to 8.000 EiB.
     * @param size      Number to convert.
     * @param separator Separator between the number and the (modified) unit.
     */
    public static String prettyPrintMemory(long size, String separator)
    {
        int prefixIndex = (63 - Long.numberOfLeadingZeros(Math.abs(size))) / 10;
        if (prefixIndex == 0)
            return String.format("%d%sB", size, separator);
        else
            return String.format("%.3f%s%ciB",
                                 Math.scalb(size, -prefixIndex * 10),
                                 separator,
                                 UNIT_PREFIXES.charAt(UNIT_PREFIXES_BASE + prefixIndex));
    }

    /**
     * Convert the given value to a human-readable string using binary (i.e. 2^10-based) modifiers.
     * If the number is outside the modifier range (i.e. < 1 qi or > 1 Qi), it will be printed as v*2^e where e is a
     * multiple of 10 with sign.
     * For example, 1.000KiB, 2.100 miB/s, 7.006*2^+150, -Infinity.
     * @param value     Number to convert.
     * @param separator Separator between the number and the (modified) unit.
     */
    public static String prettyPrintBinary(double value, String unit, String separator)
    {
        int prefixIndex = Math.floorDiv(Math.getExponent(value), 10);
        if (prefixIndex == 0 || !Double.isFinite(value) || value == 0)
            return String.format("%.3f%s%s", value, separator, unit);
        else if (prefixIndex > UNIT_PREFIXES_BASE || prefixIndex < -UNIT_PREFIXES_BASE)
            return String.format("%.3f*2^%+d%s%s",
                                 Math.scalb(value, -prefixIndex * 10),
                                 prefixIndex * 10,
                                 separator,
                                 unit);
        else
            return String.format("%.3f%s%ci%s",
                                 Math.scalb(value, -prefixIndex * 10),
                                 separator,
                                 UNIT_PREFIXES.charAt(UNIT_PREFIXES_BASE + prefixIndex),
                                 unit);
    }

    /**
     * Convert the given value to a human-readable string using decimal (i.e. 10^3-based) modifiers.
     * If the number is outside the modifier range (i.e. < 1 qi or > 1 Qi), it will be printed as vEe where e is a
     * multiple of 3 with sign.
     * For example, 1.000km, 2.100 ms, 10E+45, NaN.
     * @param value     Number to convert.
     * @param separator Separator between the number and the (modified) unit.
     */
    public static String prettyPrintDecimal(double value, String unit, String separator)
    {
        int prefixIndex = (int) Math.floor(Math.log10(Math.abs(value)) / 3);
        double base = value * Math.pow(1000.0, -prefixIndex);
        if (prefixIndex == 0 || !Double.isFinite(value) || !Double.isFinite(base) || value == 0)
            return String.format("%.3f%s%s", value, separator, unit);
        else if (prefixIndex > UNIT_PREFIXES_BASE || prefixIndex < -UNIT_PREFIXES_BASE)
            return String.format("%.3fe%+d%s%s",
                                 base,
                                 prefixIndex * 3,
                                 separator,
                                 unit);
        else
            return String.format("%.3f%s%c%s",
                                 base,
                                 separator,
                                 UNIT_PREFIXES.charAt(UNIT_PREFIXES_BASE + prefixIndex),
                                 unit);
    }

    public static String prettyPrintMemoryPerSecond(long rate)
    {
        return prettyPrintMemory(rate) + "/s";
    }

    public static String prettyPrintMemoryPerSecond(long bytes, long timeInNano)
    {
        return prettyPrintBinary(bytes * 1.0e9 / timeInNano, "B/s", "");
    }

    /**
     * Parse a human-readable value printed using one of the methods above. Understands both binary and decimal
     * modifiers, as well as decimal exponents using the E notation and binary exponents using *2^e.
     *
     * @param datum     The human-readable number.
     * @param separator Expected separator, null to accept any amount of whitespace.
     * @param unit      Expected unit. If null, the method will accept any string as unit, i.e. it will parse the number
     *                  at the start of the supplied string and ignore any remainder.
     * @return The parsed value.
     */
    public static double parseHumanReadable(String datum, String separator, String unit)
    {
        int end = datum.length();
        if (unit != null)
        {
            if (!datum.endsWith(unit))
                throw new NumberFormatException(datum + " does not end in unit " + unit);
            end -= unit.length();
        }

        Matcher m = BASE_NUMBER_PATTERN.matcher(datum);
        m.region(0, end);
        if (!m.lookingAt())
            throw new NumberFormatException();
        double v = Double.parseDouble(m.group(0));

        int pos = m.end();
        if (m.group(2) == null) // possible binary exponent, parse
        {
            m = BINARY_EXPONENT.matcher(datum);
            m.region(pos, end);
            if (m.lookingAt())
            {
                int power = Integer.parseInt(m.group(1));
                v = Math.scalb(v, power);
                pos = m.end();
            }
        }

        if (separator != null)
        {
            if (!datum.startsWith(separator, pos))
                throw new NumberFormatException("Missing separator " + separator + " in " + datum);
            pos += separator.length();
        }
        else
        {
            while (pos < end && Character.isWhitespace(datum.charAt(pos)))
                ++pos;
        }

        if (pos < end)
        {
            char prefixChar = datum.charAt(pos);
            int prefixIndex = UNIT_PREFIXES.indexOf(prefixChar);
            if (prefixIndex >= 0)
            {
                prefixIndex -= UNIT_PREFIXES_BASE;
                ++pos;
                if (pos < end && datum.charAt(pos) == 'i')
                {
                    ++pos;
                    v = Math.scalb(v, prefixIndex * 10);
                }
                else
                {
                    v *= Math.exp(Math.log(1000.0) * prefixIndex);
                }
            }
        }

        if (pos != end && unit != null)
            throw new NumberFormatException("Unexpected characters between pos " + pos + " and " + end + " in " + datum);

        return v;
    }

    public static long parseHumanReadableBytes(String value)
    {
        return (long) parseHumanReadable(value, null, "B");
    }

    /**
     * Parse a double where both a direct value and a percentage are accepted.
     * For example, for inputs "0.1" and "10%", this function will return 0.1.
     */
    public static double parsePercent(String value)
    {
        value = value.trim();
        if (value.endsWith("%"))
        {
            value = value.substring(0, value.length() - 1).trim();
            return Double.parseDouble(value) / 100.0;
        }
        else
            return Double.parseDouble(value);
    }

    /**
     * Starts and waits for the given @param pb to finish.
     * @throws java.io.IOException on non-zero exit code
     */
    public static void exec(ProcessBuilder pb) throws IOException
    {
        Process p = pb.start();
        try
        {
            int errCode = p.waitFor();
            if (errCode != 0)
            {
                try (BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()));
                     BufferedReader err = new BufferedReader(new InputStreamReader(p.getErrorStream())))
                {
                    String lineSep = LINE_SEPARATOR.getString();
                    StringBuilder sb = new StringBuilder();
                    String str;
                    while ((str = in.readLine()) != null)
                        sb.append(str).append(lineSep);
                    while ((str = err.readLine()) != null)
                        sb.append(str).append(lineSep);
                    throw new IOException("Exception while executing the command: "+ StringUtils.join(pb.command(), " ") +
                                          ", command error Code: " + errCode +
                                          ", command output: "+ sb.toString());
                }
            }
        }
        catch (InterruptedException e)
        {
            throw new UncheckedInterruptedException(e);
        }
    }

    /**
     * Starts and waits for the given <code>cmd</code> to finish. If the process does not finish within <code>timeout</code>,
     * it will be destroyed.
     *
     * @param env        additional environment variables to set
     * @param timeout    timeout for the process to finish, or zero/null to wait forever
     * @param outBufSize the maximum size of the collected std output; the overflow will be discarded
     * @param errBufSize the maximum size of the collected std error; the overflow will be discarded
     * @param cmd        the command to execute
     * @return the std output of the process up to the size specified by <code>outBufSize</code>
     */
    public static String exec(Map<String, String> env, Duration timeout, int outBufSize, int errBufSize, String... cmd) throws IOException, TimeoutException, InterruptedException
    {
        if (env == null)
            env = Map.of();
        if (timeout == null)
            timeout = Duration.ZERO;

        ProcessBuilder processBuilder = new ProcessBuilder(cmd);
        processBuilder.environment().putAll(env);
        Process process = processBuilder.start();
        try (DataOutputBuffer err = new DataOutputBuffer();
             DataOutputBuffer out = new DataOutputBuffer();
             OutputStream overflowSink = OutputStream.nullOutputStream())
        {
            boolean completed;
            if (timeout.isZero())
            {
                process.waitFor();
                completed = true;
            }
            else
            {
                completed = process.waitFor(timeout.toMillis(), TimeUnit.MILLISECONDS);
            }

            copy(process.getInputStream(), out, outBufSize);
            long outOverflow = process.getInputStream().transferTo(overflowSink);

            copy(process.getErrorStream(), err, errBufSize);
            long errOverflow = process.getErrorStream().transferTo(overflowSink);

            if (!completed)
            {
                process.destroyForcibly();
                logger.error("Command {} did not complete in {}, killed forcibly:\noutput:\n{}\n(truncated {} bytes)\nerror:\n{}\n(truncated {} bytes)",
                            Arrays.toString(cmd), timeout, out.asString(), outOverflow, err.asString(), errOverflow);
                throw new TimeoutException("Command " + Arrays.toString(cmd) + " did not complete in " + timeout);
            }
            int r = process.exitValue();
            if (r != 0)
            {
                logger.error("Command {} failed with exit code {}:\noutput:\n{}\n(truncated {} bytes)\nerror:\n{}\n(truncated {} bytes)",
                            Arrays.toString(cmd), r, out.asString(), outOverflow, err.asString(), errOverflow);
                throw new IOException("Command " + Arrays.toString(cmd) + " failed with exit code " + r);
            }
            return out.asString();
        }
    }

    public static void updateChecksumInt(Checksum checksum, int v)
    {
        checksum.update((v >>> 24) & 0xFF);
        checksum.update((v >>> 16) & 0xFF);
        checksum.update((v >>> 8) & 0xFF);
        checksum.update((v >>> 0) & 0xFF);
    }

    /**
      * Updates checksum with the provided ByteBuffer at the given offset + length.
      * Resets position and limit back to their original values on return.
      * This method is *NOT* thread-safe.
      */
    public static void updateChecksum(CRC32 checksum, ByteBuffer buffer, int offset, int length)
    {
        int position = buffer.position();
        int limit = buffer.limit();

        buffer.position(offset).limit(offset + length);
        checksum.update(buffer);

        buffer.position(position).limit(limit);
    }

    /**
     * Updates checksum with the provided ByteBuffer.
     * Resets position back to its original values on return.
     * This method is *NOT* thread-safe.
     */
    public static void updateChecksum(CRC32 checksum, ByteBuffer buffer)
    {
        int position = buffer.position();
        checksum.update(buffer);
        buffer.position(position);
    }

    public static long abs(long index)
    {
        long negbit = index >> 63;
        return (index ^ negbit) - negbit;
    }

    private static final class WrappedCloseableIterator<T>
        extends AbstractIterator<T> implements CloseableIterator<T>
    {
        private final Iterator<T> source;
        public WrappedCloseableIterator(Iterator<T> source)
        {
            this.source = source;
        }

        protected T computeNext()
        {
            if (!source.hasNext())
                return endOfData();
            return source.next();
        }

        public void close() {}
    }

    public static <T> byte[] serialize(T object, IVersionedSerializer<T> serializer, int version)
    {
        int size = (int) serializer.serializedSize(object, version);

        try (DataOutputBuffer buffer = new DataOutputBufferFixed(size))
        {
            serializer.serialize(object, buffer, version);
            assert buffer.getLength() == size && buffer.getData().length == size
                : String.format("Final buffer length %s to accommodate data size of %s (predicted %s) for %s",
                        buffer.getData().length, buffer.getLength(), size, object);
            return buffer.getData();
        }
        catch (IOException e)
        {
            // We're doing in-memory serialization...
            throw new AssertionError(e);
        }
    }

    public static long copy(InputStream from, OutputStream to, long limit) throws IOException
    {
        byte[] buffer = new byte[64]; // 64 byte buffer
        long copied = 0;
        int toCopy = buffer.length;
        while (true)
        {
            if (limit < buffer.length + copied)
                toCopy = (int) (limit - copied);
            int sofar = from.read(buffer, 0, toCopy);
            if (sofar == -1)
                break;
            to.write(buffer, 0, sofar);
            copied += sofar;
            if (limit == copied)
                break;
        }
        return copied;
    }

    public static File getToolsOutputDirectory()
    {
        File historyDir = new File(USER_HOME.getString(), ".cassandra");
        FileUtils.createDirectory(historyDir);
        return historyDir;
    }

    public static void closeAll(Collection<? extends AutoCloseable> l) throws Exception
    {
        Exception toThrow = null;
        for (AutoCloseable c : l)
        {
            try
            {
                c.close();
            }
            catch (Exception e)
            {
                if (toThrow == null)
                    toThrow = e;
                else
                    toThrow.addSuppressed(e);
            }
        }
        if (toThrow != null)
            throw toThrow;
    }

    public static byte[] toWriteUTFBytes(String s)
    {
        try
        {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            DataOutputStream dos = new DataOutputStream(baos);
            dos.writeUTF(s);
            dos.flush();
            return baos.toByteArray();
        }
        catch (IOException e)
        {
            throw new RuntimeException(e);
        }
    }

	public static void sleepQuietly(long millis)
    {
        try
        {
            Thread.sleep(millis);
        }
        catch (InterruptedException e)
        {
            throw new UncheckedInterruptedException(e);
        }
    }

    public static long align(long val, int boundary)
    {
        return (val + boundary) & ~(boundary - 1);
    }

    @VisibleForTesting
    public static void reset()
    {
        localInetAddress = null;
        localInetAddressAndPort = null;
        broadcastInetAddress = null;
        broadcastInetAddressAndPort = null;
        broadcastNativeAddress = null;
    }

    /**
     * Hack to prevent the ugly "illegal access" warnings in Java 11+ like the following.
     */
    public static void preventIllegalAccessWarnings()
    {
        // Example "annoying" trace:
        //        WARNING: An illegal reflective access operation has occurred
        //        WARNING: Illegal reflective access by io.netty.util.internal.ReflectionUtil (file:...)
        //        WARNING: Please consider reporting this to the maintainers of io.netty.util.internal.ReflectionUtil
        //        WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
        //        WARNING: All illegal access operations will be denied in a future release
        try
        {
            Class<?> c = Class.forName("jdk.internal.module.IllegalAccessLogger");
            Field f = c.getDeclaredField("logger");
            f.setAccessible(true);
            f.set(null, null);
        }
        catch (Exception e)
        {
            // ignore
        }
    }

    public static String camelToSnake(String camel)
    {
        StringBuilder sb = new StringBuilder();
        for (char c : camel.toCharArray())
        {
            if (Character.isUpperCase(c))
            {
                // if first char is uppercase, then avoid adding the _ prefix
                if (sb.length() > 0)
                    sb.append('_');
                sb.append(Character.toLowerCase(c));
            }
            else
            {
                sb.append(c);
            }
        }
        return sb.toString();
    }

    @SafeVarargs
    public static <T> ImmutableList<T> immutableListWithFilteredNulls(T... values)
    {
        ImmutableList.Builder<T> builder = ImmutableList.builderWithExpectedSize(values.length);
        for (int i = 0; i < values.length; i++)
        {
            if (values[i] != null)
                builder.add(values[i]);
        }
        return builder.build();
    }

    public static void closeQuietly(Object o)
    {
        if (!(o instanceof AutoCloseable))
            return;
        try
        {
            ((AutoCloseable) o).close();
        }
        catch (Exception e)
        {
            logger.warn("Closing {} had an unexpected exception", o, e);
        }
    }

    public static Semver getKernelVersion()
    {
        return kernelVersionSupplier.get();
    }

    @VisibleForTesting
    static Semver getKernelVersionFromUname()
    {
        // TODO rewrite this method with Oshi when it is eventually included in the project
        if (!isLinux)
            return null;

        String output = null;
        try
        {
            output = exec(Map.of(), Duration.ofSeconds(5), 1024, 1024, "uname", "-r");

            if (output.isEmpty())
                throw new RuntimeException("Error while trying to get kernel version, 'uname -r' returned empty output");

            return parseKernelVersion(output);
        }
        catch (SemverException e)
        {
            logger.error("SemverException parsing {}", output, e);
            throw e;
        }
        catch (IOException | TimeoutException e)
        {
            throw new RuntimeException("Error while trying to get kernel version", e);
        }
        catch (InterruptedException e)
        {
            Thread.currentThread().interrupt();
            throw new RuntimeException(e);
        }
    }

    @VisibleForTesting
    static Semver parseKernelVersion(String versionString)
    {
        Preconditions.checkNotNull(versionString, "kernel version cannot be null");
        // ignore blank lines
        try (Scanner scanner = new Scanner(versionString))
        {
            while (scanner.hasNextLine())
            {
                String version = scanner.nextLine().trim();
                if (version.isEmpty())
                    continue;

                if (version.endsWith("+"))
                        // gcp's cos_containerd has a trailing +
                        version = StringUtils.chop(version);

                return new Semver(version, Semver.SemverType.LOOSE);
            }
        }
        throw new IllegalArgumentException("Error while trying to parse kernel version - no version found");
    }
}