blob: bbc2b43b1a5087970a8d71f7dcd0fbada9304a47 [file] [log] [blame]
/*
* 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.jena.atlas.lib;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Stream;
import java.util.zip.Adler32;
import java.util.zip.CRC32;
import java.util.zip.Checksum;
import org.apache.commons.codec.digest.MurmurHash3;
import org.apache.commons.lang3.StringUtils;
import org.apache.jena.atlas.logging.Log;
public class Lib
{
private Lib() {}
/**
* Concatenate two "/" delimited path names.
* <ul>
* <li>If path (second argument) starts with "/", it is assumed to be absolute and path is returned.</li>
* <li>If path (second argument) is "", return the directory.</li>
* <li>Otherwise the arguments are concatenated ensuring there is a "/" between them.</li>
* </ul>
*/
public static String concatPaths(String directory, String path) {
Objects.requireNonNull(directory);
Objects.requireNonNull(path);
if ( path.startsWith("/") )
return path;
if ( path.isEmpty())
return directory;
if ( directory.endsWith("/") )
return directory+path;
else
return directory+"/"+path;
}
/** Stream to {@link List} */
public static <X> List<X> toList(Stream<X> stream) {
// Findability.
return StreamOps.toList(stream);
}
/** "ConcurrentHashSet" */
public static final <X> Set<X> concurrentHashSet() {
return SetUtils.concurrentHashSet();
}
public static final void sync(Object object) {
if ( object instanceof Sync syncobj )
syncobj.sync();
}
/**
* Return true if obj1 and obj are both null or are .equals, else return false
* Prefer {@link Objects#equals(Object, Object)}
*/
public static final <T> boolean equals(T obj1, T obj2) {
// Include because other equals* operations are here.
return Objects.equals(obj1, obj2);
}
/** Return true if obj1 and obj are both null or are .equals, else return false */
public static final boolean equalsIgnoreCase(String str1, String str2) {
if ( str1 == null )
return str2 == null;
return str1.equalsIgnoreCase(str2);
}
/** Return true if obj1 and obj are not equal */
public static final <T> boolean notEqual(T obj1, T obj2) {
return !Objects.equals(obj1, obj2);
}
/** Safely return the class short name for an object -- obj.getClass().getSimpleName() */
static public final String className(Object obj) {
if ( obj == null )
return "null";
return classShortName(obj.getClass());
}
/** Safely return the class short name for a class */
static public final String classShortName(Class<? > cls) {
if ( cls == null )
return "null";
return cls.getSimpleName();
}
/** Create {@link UnsupportedOperationException} with formatted message. */
static public UnsupportedOperationException unsupportedMethod(Object object, String method) {
return new UnsupportedOperationException(Lib.className(object) + "." + method);
}
/**
* Return a {@linkplain RuntimeException}. If the argument is already
* {@code RuntimeException}, return the argument. Otherwise, wrap in
* {@code RuntimeException}, with the same message, and return the
* {@code RuntimeException}
*/
public static RuntimeException runtimeException(Exception ex) {
if ( ex instanceof RuntimeException ex2)
throw ex2;
return new RuntimeException(ex.getMessage(), ex);
}
/** Do two lists have the same elements without considering the order of the lists nor duplicates? */
public static <T> boolean equalsListAsSet(List<T> list1, List<T> list2) {
if ( list1 == null && list2 == null )
return true;
if ( list1 == null ) return false;
if ( list2 == null ) return false;
return list1.containsAll(list2) && list2.containsAll(list1);
}
/** HashCode - allow nulls */
public static final int hashCodeObject(Object obj) { return hashCodeObject(obj, -4); }
/** HashCode - allow nulls */
public static final int hashCodeObject(Object obj, int nullHashCode) {
if ( obj == null )
return nullHashCode;
return obj.hashCode();
}
public static boolean isEmpty(CharSequence cs) {
// Hide implementation
return StringUtils.isEmpty(cs);
}
/** Non-locale lowercase {@link Locale#ROOT} */
public static String lowercase(String string) {
// Hide implementation
return string.toLowerCase(Locale.ROOT);
}
/** Non-locale uppercase {@link Locale#ROOT} */
public static String uppercase(String string) {
// Hide implementation
return string.toUpperCase(Locale.ROOT);
}
public static final void sleep(int milliSeconds) {
try { Thread.sleep(milliSeconds); }
catch (InterruptedException ex) { Log.warn(Lib.class, "interrupted", ex); }
}
/** Get an environment variable value; if not found try in the system properties. */
public static String getenv(String name) {
return getenv(name, name);
}
/**
* Get system properties (argument sysPropName) or if not found, read an
* environment variable value (argument envName).
*/
public static String getenv(String sysPropName, String envName) {
String x = System.getProperty(sysPropName);
if ( x == null )
x = System.getenv(envName);
return x;
}
/** Test whether a property (environment variable or system property) is true. */
public static boolean isPropertyOrEnvVarSetToTrue(String name) {
return isPropertyOrEnvVarSetToTrue(name, name);
}
/**
* Test whether a property (argument sysPropName) or an environment variable
* (argument envName) is true.
*/
public static boolean isPropertyOrEnvVarSetToTrue(String sysPropName, String envName) {
String value = getenv(sysPropName, envName);
if ( value == null )
return false;
return value.equalsIgnoreCase("true");
}
/**
* Read thread local, assuming that "null" means it does not exist for this thread.
* If null is read, the thread local is removed.
*/
public static <X> X readThreadLocal(ThreadLocal<X> threadLocal) {
X x = threadLocal.get();
if ( x == null )
threadLocal.remove();
return x;
}
/**
* @see CRC32
*/
public static long crc32(byte[] bytes)
{
return crc(new CRC32(), bytes);
}
/** Faster than CRC32, nearly as good.
* @see Adler32
*/
public static long adler32(byte[] bytes) {
return crc(new Adler32(), bytes);
}
private static long crc(Checksum alg, byte[] bytes) {
alg.reset();
alg.update(bytes, 0, bytes.length);
return alg.getValue();
}
/** Calculate the Murmur3 hash of a string, and return it as a hex-encoded string. */
public static String murmurHashHex(String string) {
byte[] input = string.getBytes(StandardCharsets.UTF_8);
long[] x = MurmurHash3.hash128x64(input);
// Important to do long to hex in lo to hi byte order. Then, it agrees with the Guava function
// To put it another way, a hash value is bytes, not longs (in Java long is hi-lo byte order).
// String xs = String.format("%016x%016x", Long.reverseBytes(x[0]), Long.reverseBytes(x[1]));
char[] chars = new char[32];
longAsHexLC(x[0], chars, 0);
longAsHexLC(x[1], chars, 16);
return new String(chars);
}
/** Long to hex (lower case) chars with low byte first. */
private static void longAsHexLC(long value, char[] chars, int start) {
// Avoiding generating intermediate strings from e.g. Bytes.asHexLC
// Byte loop.
// Bytes get encoded "high bits first". "AF" is value A*16+F
for ( int idx = 0; idx < 8; idx++ ) {
int i = idx * 8;
int bValue = (int)((value >> i) & 0xFF);
// Keep order of the byte - high nibble, low nibble.
int hi = (bValue & 0xF0) >> 4;
int lo = (bValue & 0x0F);
char chHi = Chars.hexDigitsLC[hi];
char chLo = Chars.hexDigitsLC[lo];
chars[start + 2 * idx] = chHi;
chars[start + 2 * idx + 1] = chLo;
}
}
// Powerset (returned as a list).
// Calculate 2^N then loop i on 0 to 2^n-1
// create the set for this entry.
// for the j-th element of the input
// include the element if the j-th bit is set in i.
// See also Guava.Sets.powerSet
/** PowerSet */
public static <X> Set<Set<X>> powerSet(Set<X> elts) {
List<X> list = new ArrayList<>(elts);
int size = list.size();
long N = (long) Math.pow(2, list.size());
Set<Set<X>> output = new HashSet<>();
for (int i = 0; i < N; i++) {
// This elements.
Set<X> elt = new HashSet<>();
// for every bit in N
for (int j = 0; j < size; j++) {
int x = (1<<j);
if ( (x & i) != 0 )
elt.add(list.get(j));
}
output.add(elt);
}
return output;
}
}