/** * 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.*; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.math.BigInteger; import java.net.InetAddress; import java.net.URL; import java.net.UnknownHostException; import java.nio.ByteBuffer; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.*; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import com.google.common.base.Joiner; import org.apache.commons.collections.iterators.CollatingIterator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.cassandra.cache.IRowCacheProvider; import org.apache.cassandra.config.ConfigurationException; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.db.DecoratedKey; import org.apache.cassandra.db.marshal.AbstractType; import org.apache.cassandra.db.marshal.TypeParser; import org.apache.cassandra.dht.IPartitioner; import org.apache.cassandra.dht.Range; import org.apache.cassandra.dht.Token; import org.apache.cassandra.locator.PropertyFileSnitch; import org.apache.thrift.TBase; import org.apache.thrift.TDeserializer; import org.apache.thrift.TException; import org.apache.thrift.TSerializer; public class FBUtilities { private static Logger logger_ = LoggerFactory.getLogger(FBUtilities.class); public static final BigInteger TWO = new BigInteger("2"); private static volatile InetAddress localInetAddress_; private final static byte[] charToByte = new byte[256]; // package protected for use by ByteBufferUtil. Do not modify this array !! static final char[] byteToChar = new char[16]; static { for (char c = 0; c < charToByte.length; ++c) { if (c >= '0' && c <= '9') charToByte[c] = (byte)(c - '0'); else if (c >= 'A' && c <= 'F') charToByte[c] = (byte)(c - 'A' + 10); else if (c >= 'a' && c <= 'f') charToByte[c] = (byte)(c - 'a' + 10); else charToByte[c] = (byte)-1; } for (int i = 0; i < 16; ++i) { byteToChar[i] = Integer.toHexString(i).charAt(0); } } /** * This constructor enables us to construct a String directly by wrapping a char array, with zero-copy. * This can save time, and a lot of memory, when converting large column values. */ private static final Constructor<String> stringConstructor = getProtectedConstructor(String.class, int.class, int.class, char[].class); /** * Create a String from a char array with zero-copy (if available), using reflection to access a package-protected constructor of String. * */ public static String wrapCharArray(char[] c) { if (c == null) return null; String s = null; if (stringConstructor != null) { try { s = stringConstructor.newInstance(0, c.length, c); } catch (Exception e) { // Swallowing as we'll just use a copying constructor } } return s == null ? new String(c) : s; } private static final ThreadLocal<MessageDigest> localMD5Digest = new ThreadLocal<MessageDigest>() { @Override protected MessageDigest initialValue() { return newMessageDigest("MD5"); } @Override public MessageDigest get() { MessageDigest digest = super.get(); digest.reset(); return digest; } }; private static final ThreadLocal<Random> localRandom = new ThreadLocal<Random>() { @Override protected Random initialValue() { return new Random(); } }; public static final int MAX_UNSIGNED_SHORT = 0xFFFF; public static MessageDigest threadLocalMD5Digest() { return localMD5Digest.get(); } 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); } } public static Random threadLocalRandom() { return localRandom.get(); } /** * Parses a string representing either a fraction, absolute value or percentage. */ public static double parseDoubleOrPercent(String value) { if (value.endsWith("%")) { return Double.parseDouble(value.substring(0, value.length() - 1)) / 100; } else { return Double.parseDouble(value); } } public static InetAddress getLocalAddress() { if (localInetAddress_ == null) try { localInetAddress_ = DatabaseDescriptor.getListenAddress() == null ? InetAddress.getLocalHost() : DatabaseDescriptor.getListenAddress(); } catch (UnknownHostException e) { throw new RuntimeException(e); } return localInetAddress_; } /** * @param fractOrAbs A double that may represent a fraction or absolute value. * @param total If fractionOrAbs is a fraction, the total to take the fraction from * @return An absolute value which may be larger than the total. */ public static long absoluteFromFraction(double fractOrAbs, long total) { if (fractOrAbs < 0) throw new UnsupportedOperationException("unexpected negative value " + fractOrAbs); if (0 < fractOrAbs && fractOrAbs <= 1) { // fraction return Math.max(1, (long)(fractOrAbs * total)); } // absolute assert fractOrAbs >= 1 || fractOrAbs == 0; return (long)fractOrAbs; } /** * 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 new Pair<BigInteger, Boolean>(midpoint, remainder); } /** * Copy bytes from int into bytes starting from offset. * @param bytes Target array * @param offset Offset into the array * @param i Value to write */ public static void copyIntoBytes(byte[] bytes, int offset, int i) { bytes[offset] = (byte)( ( i >>> 24 ) & 0xFF ); bytes[offset+1] = (byte)( ( i >>> 16 ) & 0xFF ); bytes[offset+2] = (byte)( ( i >>> 8 ) & 0xFF ); bytes[offset+3] = (byte)( i & 0xFF ); } /** * @param i Write this int to an array * @return 4-byte array containing the int */ public static byte[] toByteArray(int i) { byte[] bytes = new byte[4]; copyIntoBytes(bytes, 0, i); return bytes; } /** * Copy bytes from long into bytes starting from offset. * @param bytes Target array * @param offset Offset into the array * @param l Value to write */ public static void copyIntoBytes(byte[] bytes, int offset, long l) { bytes[offset] = (byte)( ( l >>> 56 ) & 0xFF ); bytes[offset+1] = (byte)( ( l >>> 48 ) & 0xFF ); bytes[offset+2] = (byte)( ( l >>> 40 ) & 0xFF ); bytes[offset+3] = (byte)( ( l >>> 32 ) & 0xFF ); bytes[offset+4] = (byte)( ( l >>> 24 ) & 0xFF ); bytes[offset+5] = (byte)( ( l >>> 16 ) & 0xFF ); bytes[offset+6] = (byte)( ( l >>> 8 ) & 0xFF ); bytes[offset+7] = (byte)( l & 0xFF ); } /** * @param l Write this long to an array * @return 8-byte array containing the long */ public static byte[] toByteArray(long l) { byte[] bytes = new byte[8]; copyIntoBytes(bytes, 0, l); return bytes; } public static int compareUnsigned(byte[] bytes1, byte[] bytes2, int offset1, int offset2, int len1, int len2) { if (bytes1 == null) { return bytes2 == null ? 0 : -1; } if (bytes2 == null) return 1; int minLength = Math.min(len1 - offset1, len2 - offset2); for (int x = 0, i = offset1, j = offset2; x < minLength; x++, i++, j++) { if (bytes1[i] == bytes2[j]) continue; // compare non-equal bytes as unsigned return (bytes1[i] & 0xFF) < (bytes2[j] & 0xFF) ? -1 : 1; } if ((len1 - offset1) == (len2 - offset2)) return 0; else return ((len1 - offset1) < (len2 - offset2)) ? -1 : 1; } /** * @return The bitwise XOR of the inputs. The output will be the same length as the * longer input, but if either input is null, the output will be null. */ public static byte[] xor(byte[] left, byte[] right) { if (left == null || right == null) return null; if (left.length > right.length) { byte[] swap = left; left = right; right = swap; } // left.length is now <= right.length byte[] out = Arrays.copyOf(right, right.length); for (int i = 0; i < left.length; i++) { out[i] = (byte)((left[i] & 0xFF) ^ (right[i] & 0xFF)); } return out; } public static BigInteger hashToBigInteger(ByteBuffer data) { byte[] result = hash(data); BigInteger hash = new BigInteger(result); return hash.abs(); } public static byte[] hash(ByteBuffer... data) { MessageDigest messageDigest = localMD5Digest.get(); for(ByteBuffer block : data) { messageDigest.update(block.duplicate()); } return messageDigest.digest(); } public static byte[] hexToBytes(String str) { if (str.length() % 2 == 1) str = "0" + str; byte[] bytes = new byte[str.length()/2]; for (int i = 0; i < bytes.length; i++) { bytes[i] = (byte)((charToByte[str.charAt(i * 2)] << 4) | charToByte[str.charAt(i*2 + 1)]); } return bytes; } public static String bytesToHex(byte... bytes) { char[] c = new char[bytes.length * 2]; for (int i = 0; i < bytes.length; i++) { int bint = bytes[i]; c[i * 2] = FBUtilities.byteToChar[(bint & 0xf0) >> 4]; c[1 + i * 2] = FBUtilities.byteToChar[bint & 0x0f]; } return wrapCharArray(c); } public static void renameWithConfirm(String tmpFilename, String filename) throws IOException { if (!new File(tmpFilename).renameTo(new File(filename))) { throw new IOException("rename failed of " + filename); } } /* TODO how to make this work w/ ReducingKeyIterator? public static <T extends Comparable<T>> CollatingIterator getCollatingIterator() { // CollatingIterator will happily NPE if you do not specify a comparator explicitly return new CollatingIterator(new Comparator<T>() { public int compare(T o1, T o2) { return o1.compareTo(o2); } }); } */ public static CollatingIterator getCollatingIterator() { // CollatingIterator will happily NPE if you do not specify a comparator explicitly return new CollatingIterator(new Comparator() { public int compare(Object o1, Object o2) { return ((Comparable) o1).compareTo(o2); } }); } public static void atomicSetMax(AtomicInteger atomic, int i) { while (true) { int j = atomic.get(); if (j >= i || atomic.compareAndSet(j, i)) break; } } public static void atomicSetMax(AtomicLong atomic, long i) { while (true) { long j = atomic.get(); if (j >= i || atomic.compareAndSet(j, i)) break; } } public static void serialize(TSerializer serializer, TBase struct, DataOutput out) throws IOException { assert serializer != null; assert struct != null; assert out != null; byte[] bytes; try { bytes = serializer.serialize(struct); } catch (TException e) { throw new RuntimeException(e); } out.writeInt(bytes.length); out.write(bytes); } public static void deserialize(TDeserializer deserializer, TBase struct, DataInput in) throws IOException { assert deserializer != null; assert struct != null; assert in != null; byte[] bytes = new byte[in.readInt()]; in.readFully(bytes); try { deserializer.deserialize(struct, bytes); } catch (TException ex) { throw new IOException(ex); } } public static void sortSampledKeys(List<DecoratedKey> keys, Range 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.token) < 0 && right.compareTo(o2.token) < 0) || (right.compareTo(o1.token) > 0 && right.compareTo(o2.token) > 0)) { // both tokens are on the same side of the wrap point return o1.compareTo(o2); } return -o1.compareTo(o2); } }; Collections.sort(keys, comparator); } else { // unwrapped range (left < right). standard sort is all we need. Collections.sort(keys); } } public static int encodedUTF8Length(String st) { int strlen = st.length(); int utflen = 0; for (int i = 0; i < strlen; i++) { int c = st.charAt(i); if ((c >= 0x0001) && (c <= 0x007F)) utflen++; else if (c > 0x07FF) utflen += 3; else utflen += 2; } return utflen; } public static String resourceToFile(String filename) throws ConfigurationException { ClassLoader loader = PropertyFileSnitch.class.getClassLoader(); URL scpurl = loader.getResource(filename); if (scpurl == null) throw new ConfigurationException("unable to locate " + filename); return scpurl.getFile(); } public static String getReleaseVersionString() { try { InputStream in = FBUtilities.class.getClassLoader().getResourceAsStream("org/apache/cassandra/config/version.properties"); if (in == null) { return "Unknown"; } Properties props = new Properties(); props.load(in); return props.getProperty("CassandraVersion"); } catch (Exception e) { logger_.warn("Unable to load version.properties", e); return "debug version"; } } public static long timestampMicros() { // we use microsecond resolution for compatibility with other client libraries, even though // we can't actually get microsecond precision. return System.currentTimeMillis() * 1000; } public static void waitOnFutures(Iterable<Future<?>> futures) { for (Future f : futures) { try { f.get(); } catch (ExecutionException ee) { throw new RuntimeException(ee); } catch (InterruptedException ie) { throw new AssertionError(ie); } } } public static IPartitioner newPartitioner(String partitionerClassName) throws ConfigurationException { if (!partitionerClassName.contains(".")) partitionerClassName = "org.apache.cassandra.dht." + partitionerClassName; return FBUtilities.construct(partitionerClassName, "partitioner"); } /** * @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 e) { throw new ConfigurationException(String.format("Unable to find %s class '%s'", readable, classname)); } } /** * Constructs an instance of the given class, which must have a no-arg 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); try { return cls.getConstructor().newInstance(); } catch (NoSuchMethodException e) { throw new ConfigurationException(String.format("No default constructor for %s class '%s'.", readable, classname)); } 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 (InvocationTargetException e) { 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 extends Comparable> SortedSet<T> singleton(T column) { return new TreeSet<T>(Arrays.asList(column)); } public static String toString(Map<?,?> map) { 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) { Field field; try { field = klass.getDeclaredField(fieldName); field.setAccessible(true); } catch (Exception e) { throw new AssertionError(e); } return field; } /** * Used to get access to protected/private constructor of the specified class * @param klass - name of the class * @param paramTypes - types of the constructor parameters * @return Constructor if successful, null if the constructor cannot be * accessed */ public static Constructor getProtectedConstructor(Class klass, Class... paramTypes) { Constructor c; try { c = klass.getDeclaredConstructor(paramTypes); c.setAccessible(true); return c; } catch (Exception e) { return null; } } public static IRowCacheProvider newCacheProvider(String cache_provider) throws ConfigurationException { if (!cache_provider.contains(".")) cache_provider = "org.apache.cassandra.cache." + cache_provider; return FBUtilities.construct(cache_provider, "row cache provider"); } }