added some utilities
This commit is contained in:
@@ -72,6 +72,8 @@ test {
|
||||
'junit.jupiter.engine.jar' : junitJupiterEngineJar.toString(),
|
||||
'path.classloader.test.bundle' : pathClassLoaderTestBundleTask.get().outputs.files.singleFile
|
||||
])
|
||||
|
||||
jvmArgs(['--add-opens', 'java.base/sun.nio.fs=ALL-UNNAMED'])
|
||||
}
|
||||
|
||||
publishing {
|
||||
|
31
src/main/java/net/woggioni/jwo/CPU.java
Normal file
31
src/main/java/net/woggioni/jwo/CPU.java
Normal file
@@ -0,0 +1,31 @@
|
||||
package net.woggioni.jwo;
|
||||
|
||||
public enum CPU {
|
||||
X86("x86"),
|
||||
AMD64("amd64"),
|
||||
ARM("arm"),
|
||||
AARCH64("aarch64"),
|
||||
S390("s390"),
|
||||
S390X("s390x"),
|
||||
RISCV("riscv"),
|
||||
RISCV64("riscv64");
|
||||
|
||||
|
||||
private final String value;
|
||||
|
||||
CPU(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public static CPU current;
|
||||
|
||||
static {
|
||||
String archName = System.getProperty("os.arch").toLowerCase();
|
||||
for(CPU cpu : values()) {
|
||||
if(archName.startsWith(cpu.value)) {
|
||||
current = cpu;
|
||||
}
|
||||
}
|
||||
if(current == null) throw new IllegalArgumentException(String.format("Unrecognized cpu arch '%s'", archName));
|
||||
}
|
||||
}
|
@@ -205,4 +205,10 @@ public class CollectionUtils {
|
||||
Collections::unmodifiableNavigableMap
|
||||
);
|
||||
}
|
||||
public static <K, V, U> Stream<Map.Entry<K, U>> mapValues(Map<K, V> map, Fun<V, U> xform) {
|
||||
return map
|
||||
.entrySet()
|
||||
.stream()
|
||||
.map(entry -> new AbstractMap.SimpleEntry<>(entry.getKey(), xform.apply(entry.getValue())));
|
||||
}
|
||||
}
|
||||
|
@@ -2,19 +2,58 @@ package net.woggioni.jwo;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.woggioni.jwo.exception.ChildProcessException;
|
||||
import net.woggioni.jwo.internal.CharFilterReader;
|
||||
|
||||
import javax.net.ssl.*;
|
||||
import java.io.*;
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSession;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.nio.file.attribute.FileAttribute;
|
||||
import java.security.DigestOutputStream;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.MissingFormatArgumentException;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
import java.util.zip.CRC32;
|
||||
@@ -169,10 +208,6 @@ public class JWO {
|
||||
return stream.map(mappingFunction).filter(Optional::isPresent).map(Optional::get);
|
||||
}
|
||||
|
||||
public static <T> Stream<T> optional2Stream(Optional<T> optional) {
|
||||
return optional.map(Stream::of).orElse(Stream.empty());
|
||||
}
|
||||
|
||||
public static void setSystemPropertyIfNotDefined(String key, String value) {
|
||||
if (System.getProperty(key) == null) {
|
||||
System.setProperty(key, value);
|
||||
@@ -240,11 +275,7 @@ public class JWO {
|
||||
}
|
||||
|
||||
public static <T> Stream<T> streamCat(Stream<T>... streams) {
|
||||
Stream<T> result = Stream.empty();
|
||||
for (Stream<T> s : streams) {
|
||||
result = Stream.concat(result, s);
|
||||
}
|
||||
return result;
|
||||
return Stream.of(streams).flatMap(Function.identity());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -562,6 +593,9 @@ public class JWO {
|
||||
public static <T> Stream<T> optional2Stream(Iterable<Optional<T>> opts) {
|
||||
return iterable2Stream(opts).filter(Optional::isPresent).map(Optional::get);
|
||||
}
|
||||
public static <T> Stream<T> optional2Stream(Optional<T> optional) {
|
||||
return optional.map(Stream::of).orElse(Stream.empty());
|
||||
}
|
||||
|
||||
public static String decapitalize(String s, Locale locale) {
|
||||
if (!s.isEmpty() && !Character.isLowerCase(s.charAt(0)))
|
||||
@@ -613,4 +647,228 @@ public class JWO {
|
||||
byte[] buffer = new byte[0x10000];
|
||||
copy(is, os, buffer);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static void copy(Reader reader, Writer writer, char[] buffer) {
|
||||
while (true) {
|
||||
int read = reader.read(buffer);
|
||||
if (read < 0) break;
|
||||
writer.write(buffer, 0, read);
|
||||
}
|
||||
}
|
||||
|
||||
public static void copy(Reader reader, Writer writer, int bufferSize) {
|
||||
char[] buffer = new char[bufferSize];
|
||||
copy(reader, writer, buffer);
|
||||
}
|
||||
|
||||
public static void copy(Reader reader, Writer writer) {
|
||||
char[] buffer = new char[0x10000];
|
||||
copy(reader, writer, buffer);
|
||||
}
|
||||
|
||||
|
||||
public static void waitProcess(
|
||||
List<String> cmd,
|
||||
Path cwd,
|
||||
Map<String, String> env) {
|
||||
waitProcess(cmd, cwd, env, 0, null);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static Process startProcess(
|
||||
List<String> cmd,
|
||||
Path cwd, Map<String, String> env) {
|
||||
ProcessBuilder pb = new ProcessBuilder();
|
||||
pb.command(cmd);
|
||||
pb.inheritIO();
|
||||
pb.directory(cwd.toFile());
|
||||
pb.environment().putAll(env);
|
||||
if (log.isTraceEnabled()) {
|
||||
String cmdLineListString = '[' + cmd.stream().map(s -> '\'' + s + '\'').collect(Collectors.joining(", ")) + ']';
|
||||
log.trace("Starting child java process with command line: {}", cmdLineListString);
|
||||
}
|
||||
return pb.start();
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static void waitProcess(
|
||||
List<String> cmd,
|
||||
Path workingDirectory,
|
||||
Map<String, String> env,
|
||||
long tout,
|
||||
TimeUnit tunit) {
|
||||
Process process = startProcess(cmd, workingDirectory, env);
|
||||
int rc;
|
||||
if (tout > 0) {
|
||||
boolean finished = process.waitFor(tout, tunit);
|
||||
if (finished)
|
||||
rc = process.exitValue();
|
||||
else
|
||||
throw newThrowable(TimeoutException.class, "Timeout waiting for process [%s]", "");
|
||||
|
||||
} else {
|
||||
rc = process.waitFor();
|
||||
}
|
||||
if (rc != 0) {
|
||||
throw new ChildProcessException(cmd, rc);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> Iterator<T> enumeration2Iterator(Enumeration<T> enumeration) {
|
||||
return new Iterator<T>() {
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return enumeration.hasMoreElements();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T next() {
|
||||
return enumeration.nextElement();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static <T> Optional<T> or(Optional<T> ...opts) {
|
||||
for(Optional<T> opt : opts) {
|
||||
if(opt.isPresent()) return opt;
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static String sh(String... cmdLine) {
|
||||
ProcessBuilder pb = new ProcessBuilder();
|
||||
pb.command(cmdLine);
|
||||
pb.redirectInput(ProcessBuilder.Redirect.PIPE);
|
||||
Process process = pb.start();
|
||||
int rc = process.waitFor();
|
||||
if (rc == 0) {
|
||||
try (Reader reader = new CharFilterReader(new InputStreamReader(process.getInputStream()), '\n')) {
|
||||
return readAll(reader);
|
||||
}
|
||||
} else {
|
||||
try (Reader reader = new InputStreamReader(process.getErrorStream())) {
|
||||
throw new RuntimeException(readAll(reader));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static long uid() {
|
||||
return Long.parseLong(sh("id", "-u"));
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static void replaceFileIfDifferent(Supplier<InputStream> inputStreamSupplier, Path destination, FileAttribute<?>... attrs) {
|
||||
Hash hash;
|
||||
try (InputStream inputStream = inputStreamSupplier.get()) {
|
||||
hash = Hash.md5(inputStream);
|
||||
}
|
||||
replaceFileIfDifferent(inputStreamSupplier, destination, hash, attrs);
|
||||
}
|
||||
|
||||
public static void replaceFileIfDifferent(InputStream inputStream, Path destination, FileAttribute<?>... attrs) {
|
||||
replaceFileIfDifferent(() -> inputStream, destination, attrs);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private static void replaceFileIfDifferent(
|
||||
Supplier<InputStream> inputStreamSupplier,
|
||||
Path destination,
|
||||
Hash newFileHash,
|
||||
FileAttribute<?>... attrs) {
|
||||
if (Files.exists(destination)) {
|
||||
Hash existingFileHash;
|
||||
try (InputStream existingFileStream = Files.newInputStream(destination)) {
|
||||
existingFileHash = Hash.md5(existingFileStream);
|
||||
}
|
||||
if (newFileHash == null) {
|
||||
MessageDigest md = MessageDigest.getInstance(Hash.Algorithm.MD5.name());
|
||||
Path tmpFile = Files.createTempFile(destination.getParent(), destination.getFileName().toString(), ".tmp", attrs);
|
||||
try {
|
||||
try (InputStream inputStream = inputStreamSupplier.get();
|
||||
OutputStream outputStream = new DigestOutputStream(Files.newOutputStream(tmpFile), md)) {
|
||||
copy(inputStream, outputStream);
|
||||
}
|
||||
newFileHash = new Hash(Hash.Algorithm.MD5, md.digest());
|
||||
if (!Objects.equals(existingFileHash, newFileHash)) {
|
||||
Files.move(tmpFile, destination, StandardCopyOption.ATOMIC_MOVE);
|
||||
}
|
||||
} finally {
|
||||
if (Files.exists(tmpFile)) {
|
||||
Files.delete(tmpFile);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!Objects.equals(existingFileHash, newFileHash)) {
|
||||
EnumSet<StandardOpenOption> opts = EnumSet.of(StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE);
|
||||
try (InputStream inputStream = inputStreamSupplier.get();
|
||||
OutputStream outputStream = Channels.newOutputStream(Files.newByteChannel(destination, opts, attrs))) {
|
||||
copy(inputStream, outputStream);
|
||||
}
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace("File '{}' rewritten", destination);
|
||||
}
|
||||
} else {
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace("File '{}' unchanged", destination);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace("Creating file '{}'", destination);
|
||||
}
|
||||
EnumSet<StandardOpenOption> opts = EnumSet.of(StandardOpenOption.CREATE, StandardOpenOption.WRITE);
|
||||
try (InputStream inputStream = inputStreamSupplier.get();
|
||||
OutputStream outputStream = Channels.newOutputStream(Files.newByteChannel(destination, opts, attrs))) {
|
||||
copy(inputStream, outputStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static <T, U, V> Fun<U, V> curry1(BiFun<T, U, V> original, T arg) {
|
||||
return u -> original.apply(arg, u);
|
||||
}
|
||||
|
||||
public static <T, U, V> Fun<T, V> curry2(BiFun<T, U, V> original, U arg) {
|
||||
return t -> original.apply(t, arg);
|
||||
}
|
||||
|
||||
public static <T, U, V> Fun<U, V> curry1(BiFun<T, U, V> original, Supplier<T> sup) {
|
||||
return u -> original.apply(sup.get(), u);
|
||||
}
|
||||
|
||||
public static <T, U, V> Fun<T, V> curry2(BiFun<T, U, V> original, Supplier<U> sup) {
|
||||
return t -> original.apply(t, sup.get());
|
||||
}
|
||||
|
||||
public static <T> Stream<T> lazyValue(Supplier<T> valueSupplier) {
|
||||
return Stream.generate(valueSupplier).limit(1);
|
||||
}
|
||||
|
||||
public static <T, U> Supplier<U> compose(Supplier<T> sup, Function<T, U> fun) {
|
||||
return () -> fun.apply(sup.get());
|
||||
}
|
||||
|
||||
public static <T, U, V> Function<T, V> compose(Function<T, U> fun1, Function<U, V> fun2) {
|
||||
return param -> fun2.apply(fun1.apply(param));
|
||||
}
|
||||
|
||||
public static <T, U> Consumer<T> compose(Function<T, U> fun, Consumer<U> con) {
|
||||
return param -> con.accept(fun.apply(param));
|
||||
}
|
||||
|
||||
public static <T, U> Predicate<T> compose(Function<T, U> fun, Predicate<U> pred) {
|
||||
return param -> pred.test(fun.apply(param));
|
||||
}
|
||||
|
||||
public static <T> Supplier<Boolean> compose(Supplier<T> fun, Predicate<T> pred) {
|
||||
return () -> pred.test(fun.get());
|
||||
}
|
||||
|
||||
public static <T> Runnable compose(Supplier<T> sup, Consumer<T> con) {
|
||||
return () -> con.accept(sup.get());
|
||||
}
|
||||
}
|
||||
|
303
src/main/java/net/woggioni/jwo/JavaVersion.java
Normal file
303
src/main/java/net/woggioni/jwo/JavaVersion.java
Normal file
@@ -0,0 +1,303 @@
|
||||
package net.woggioni.jwo;
|
||||
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* An enumeration of Java versions.
|
||||
* Before 9: http://www.oracle.com/technetwork/java/javase/versioning-naming-139433.html
|
||||
* 9+: http://openjdk.java.net/jeps/223
|
||||
*/
|
||||
public enum JavaVersion {
|
||||
VERSION_1_1, VERSION_1_2, VERSION_1_3, VERSION_1_4,
|
||||
VERSION_1_5, VERSION_1_6, VERSION_1_7, VERSION_1_8,
|
||||
VERSION_1_9, VERSION_1_10,
|
||||
/**
|
||||
* Java 11 major version.
|
||||
*
|
||||
*/
|
||||
VERSION_11,
|
||||
|
||||
/**
|
||||
* Java 12 major version.
|
||||
*
|
||||
*/
|
||||
VERSION_12,
|
||||
|
||||
/**
|
||||
* Java 13 major version.
|
||||
*
|
||||
*/
|
||||
VERSION_13,
|
||||
|
||||
/**
|
||||
* Java 14 major version.
|
||||
*
|
||||
*/
|
||||
VERSION_14,
|
||||
|
||||
/**
|
||||
* Java 15 major version.
|
||||
*
|
||||
*/
|
||||
VERSION_15,
|
||||
|
||||
/**
|
||||
* Java 16 major version.
|
||||
*
|
||||
*/
|
||||
VERSION_16,
|
||||
|
||||
/**
|
||||
* Java 17 major version.
|
||||
*
|
||||
*/
|
||||
VERSION_17,
|
||||
|
||||
/**
|
||||
* Java 18 major version.
|
||||
*
|
||||
*/
|
||||
VERSION_18,
|
||||
|
||||
/**
|
||||
* Java 19 major version.
|
||||
*
|
||||
*/
|
||||
VERSION_19,
|
||||
|
||||
/**
|
||||
* Java 20 major version.
|
||||
*/
|
||||
VERSION_20,
|
||||
|
||||
/**
|
||||
* Higher version of Java.
|
||||
*/
|
||||
VERSION_HIGHER;
|
||||
// Since Java 9, version should be X instead of 1.X
|
||||
// However, to keep backward compatibility, we change from 11
|
||||
private static final int FIRST_MAJOR_VERSION_ORDINAL = 10;
|
||||
private static JavaVersion currentJavaVersion;
|
||||
private final String versionName;
|
||||
|
||||
JavaVersion() {
|
||||
this.versionName = ordinal() >= FIRST_MAJOR_VERSION_ORDINAL ? getMajorVersion() : "1." + getMajorVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given object into a {@code JavaVersion}.
|
||||
*
|
||||
* @param value An object whose toString() value is to be converted. May be null.
|
||||
* @return The version, or null if the provided value is null.
|
||||
* @throws IllegalArgumentException when the provided value cannot be converted.
|
||||
*/
|
||||
public static JavaVersion toVersion(Object value) throws IllegalArgumentException {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
if (value instanceof JavaVersion) {
|
||||
return (JavaVersion) value;
|
||||
}
|
||||
if (value instanceof Integer) {
|
||||
return getVersionForMajor((Integer) value);
|
||||
}
|
||||
|
||||
String name = value.toString();
|
||||
|
||||
int firstNonVersionCharIndex = findFirstNonVersionCharIndex(name);
|
||||
|
||||
String[] versionStrings = name.substring(0, firstNonVersionCharIndex).split("\\.");
|
||||
List<Integer> versions = convertToNumber(name, versionStrings);
|
||||
|
||||
if (isLegacyVersion(versions)) {
|
||||
assertTrue(name, versions.get(1) > 0);
|
||||
return getVersionForMajor(versions.get(1));
|
||||
} else {
|
||||
return getVersionForMajor(versions.get(0));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the version of the current JVM.
|
||||
*
|
||||
* @return The version of the current JVM.
|
||||
*/
|
||||
public static JavaVersion current() {
|
||||
if (currentJavaVersion == null) {
|
||||
currentJavaVersion = toVersion(System.getProperty("java.version"));
|
||||
}
|
||||
return currentJavaVersion;
|
||||
}
|
||||
|
||||
static void resetCurrent() {
|
||||
currentJavaVersion = null;
|
||||
}
|
||||
|
||||
public static JavaVersion forClassVersion(int classVersion) {
|
||||
return getVersionForMajor(classVersion - 44); //class file versions: 1.1 == 45, 1.2 == 46...
|
||||
}
|
||||
|
||||
public static JavaVersion forClass(byte[] classData) {
|
||||
if (classData.length < 8) {
|
||||
throw new IllegalArgumentException("Invalid class format. Should contain at least 8 bytes");
|
||||
}
|
||||
return forClassVersion(classData[7] & 0xFF);
|
||||
}
|
||||
|
||||
public boolean isJava5() {
|
||||
return this == VERSION_1_5;
|
||||
}
|
||||
|
||||
public boolean isJava6() {
|
||||
return this == VERSION_1_6;
|
||||
}
|
||||
|
||||
public boolean isJava7() {
|
||||
return this == VERSION_1_7;
|
||||
}
|
||||
|
||||
public boolean isJava8() {
|
||||
return this == VERSION_1_8;
|
||||
}
|
||||
|
||||
public boolean isJava9() {
|
||||
return this == VERSION_1_9;
|
||||
}
|
||||
|
||||
public boolean isJava10() {
|
||||
return this == VERSION_1_10;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the version is Java 11.
|
||||
*
|
||||
* @since 4.7
|
||||
*/
|
||||
public boolean isJava11() {
|
||||
return this == VERSION_11;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the version is Java 12.
|
||||
*
|
||||
* @since 5.0
|
||||
*/
|
||||
public boolean isJava12() {
|
||||
return this == VERSION_12;
|
||||
}
|
||||
|
||||
public boolean isJava5Compatible() {
|
||||
return isCompatibleWith(VERSION_1_5);
|
||||
}
|
||||
|
||||
public boolean isJava6Compatible() {
|
||||
return isCompatibleWith(VERSION_1_6);
|
||||
}
|
||||
|
||||
public boolean isJava7Compatible() {
|
||||
return isCompatibleWith(VERSION_1_7);
|
||||
}
|
||||
|
||||
public boolean isJava8Compatible() {
|
||||
return isCompatibleWith(VERSION_1_8);
|
||||
}
|
||||
|
||||
public boolean isJava9Compatible() {
|
||||
return isCompatibleWith(VERSION_1_9);
|
||||
}
|
||||
|
||||
public boolean isJava10Compatible() {
|
||||
return isCompatibleWith(VERSION_1_10);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the version is Java 11 compatible.
|
||||
*
|
||||
* @since 4.7
|
||||
*/
|
||||
public boolean isJava11Compatible() {
|
||||
return isCompatibleWith(VERSION_11);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the version is Java 12 compatible.
|
||||
*
|
||||
* @since 5.0
|
||||
*/
|
||||
public boolean isJava12Compatible() {
|
||||
return isCompatibleWith(VERSION_12);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if this version is compatible with the given version
|
||||
*
|
||||
* @since 6.0
|
||||
*/
|
||||
public boolean isCompatibleWith(JavaVersion otherVersion) {
|
||||
return this.compareTo(otherVersion) >= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return versionName;
|
||||
}
|
||||
|
||||
public String getMajorVersion() {
|
||||
return String.valueOf(ordinal() + 1);
|
||||
}
|
||||
|
||||
private static JavaVersion getVersionForMajor(int major) {
|
||||
return major >= values().length ? JavaVersion.VERSION_HIGHER : values()[major - 1];
|
||||
}
|
||||
|
||||
private static void assertTrue(String value, boolean condition) {
|
||||
if (!condition) {
|
||||
throw new IllegalArgumentException("Could not determine java version from '" + value + "'.");
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isLegacyVersion(List<Integer> versions) {
|
||||
return 1 == versions.get(0) && versions.size() > 1;
|
||||
}
|
||||
|
||||
private static List<Integer> convertToNumber(String value, String[] versionStrs) {
|
||||
List<Integer> result = new ArrayList<>();
|
||||
for (String s : versionStrs) {
|
||||
assertTrue(value, !isNumberStartingWithZero(s));
|
||||
try {
|
||||
result.add(Integer.parseInt(s));
|
||||
} catch (NumberFormatException e) {
|
||||
assertTrue(value, false);
|
||||
}
|
||||
}
|
||||
assertTrue(value, !result.isEmpty() && result.get(0) > 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static boolean isNumberStartingWithZero(String number) {
|
||||
return number.length() > 1 && number.startsWith("0");
|
||||
}
|
||||
|
||||
private static int findFirstNonVersionCharIndex(String s) {
|
||||
assertTrue(s, s.length() != 0);
|
||||
|
||||
for (int i = 0; i < s.length(); ++i) {
|
||||
if (!isDigitOrPeriod(s.charAt(i))) {
|
||||
assertTrue(s, i != 0);
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return s.length();
|
||||
}
|
||||
|
||||
private static boolean isDigitOrPeriod(char c) {
|
||||
return (c >= '0' && c <= '9') || c == '.';
|
||||
}
|
||||
}
|
@@ -1,50 +1,48 @@
|
||||
package net.woggioni.jwo;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.woggioni.jwo.internal.SynchronizedLazyValue;
|
||||
import net.woggioni.jwo.internal.UnsynchronizedLazyValue;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class LazyValue<T> {
|
||||
private final Supplier<T> valueSupplier;
|
||||
public interface LazyValue<T> {
|
||||
|
||||
private final Consumer<T> finalizer;
|
||||
private final MutableTuple2<T, Boolean> instance = MutableTuple2.newInstance(null, false);
|
||||
|
||||
public LazyValue(Supplier<T> valueSupplier) {
|
||||
this(valueSupplier, null);
|
||||
enum ThreadSafetyMode {
|
||||
SYNCHRONIZED, NONE
|
||||
}
|
||||
|
||||
public T get() {
|
||||
if(instance.get_2()) return instance.get_1();
|
||||
synchronized (instance) {
|
||||
if(instance.get_2()) return instance.get_1();
|
||||
else {
|
||||
T value = valueSupplier.get();
|
||||
instance.set_1(value);
|
||||
instance.set_2(true);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
T get();
|
||||
|
||||
<U> LazyValue<U> handle(BiFunction<T, Throwable, U> bifun);
|
||||
|
||||
<U> LazyValue<U> map(Function<T, U> fun);
|
||||
|
||||
Stream<T> stream();
|
||||
|
||||
/**
|
||||
* Execute the finalized on the wrapped object, if it has been initialized, and then returns it.
|
||||
* Execute the finalizer on the wrapped object, if it has been initialized, and then returns it.
|
||||
* It does nothing if {@link LazyValue#get()} has never been invoked.
|
||||
* @return the wrapped value if initialized, otherwise an empty {@link Optional}
|
||||
*/
|
||||
public Optional<T> close() {
|
||||
T result = null;
|
||||
synchronized (instance) {
|
||||
if(instance.get_2()) {
|
||||
instance.set_2(false);
|
||||
result = instance.get_1();
|
||||
instance.set_1(null);
|
||||
}
|
||||
Optional<T> close();
|
||||
|
||||
|
||||
static <T> LazyValue<T> of(Supplier<T> supplier, ThreadSafetyMode locking) {
|
||||
LazyValue<T> result;
|
||||
switch (locking) {
|
||||
case SYNCHRONIZED:
|
||||
result = new SynchronizedLazyValue<>(supplier);
|
||||
break;
|
||||
case NONE:
|
||||
result = new UnsynchronizedLazyValue<>(supplier);
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("This should never happen");
|
||||
}
|
||||
if(result != null) finalizer.accept(result);
|
||||
return Optional.ofNullable(result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
28
src/main/java/net/woggioni/jwo/MapBuilder.java
Normal file
28
src/main/java/net/woggioni/jwo/MapBuilder.java
Normal file
@@ -0,0 +1,28 @@
|
||||
package net.woggioni.jwo;
|
||||
|
||||
import java.util.AbstractMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class MapBuilder<K,V> {
|
||||
private final List<Map.Entry<K, V>> entries = new ArrayList<>();
|
||||
|
||||
public MapBuilder<K, V> entry(K key, V value) {
|
||||
entries.add(new AbstractMap.SimpleEntry<>(key, value));
|
||||
return this;
|
||||
}
|
||||
|
||||
public <T> T build(Sup<Map<K,V>> factory, Function<Map<K,V>, T> finalizer) {
|
||||
Map<K, V> result = factory.get();
|
||||
for(Map.Entry<K, V> entry : entries) {
|
||||
result.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
return finalizer.apply(result);
|
||||
}
|
||||
|
||||
public Map<K,V> build(Sup<Map<K,V>> factory) {
|
||||
return build(factory, Function.identity());
|
||||
}
|
||||
}
|
@@ -21,23 +21,24 @@ public enum OS {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public static final OS current;
|
||||
public static final boolean isUnix;
|
||||
public static final boolean isWindows;
|
||||
public static final boolean isMac;
|
||||
public static OS current;
|
||||
public static boolean isUnix;
|
||||
public static boolean isWindows;
|
||||
public static boolean isMac;
|
||||
|
||||
public static boolean isLinux;
|
||||
|
||||
static {
|
||||
OS currentOs = null;
|
||||
String osName = System.getProperty("os.name").toLowerCase();
|
||||
for(OS os : values()) {
|
||||
if(osName.startsWith(os.value)) {
|
||||
currentOs = os;
|
||||
current = os;
|
||||
}
|
||||
}
|
||||
if(currentOs == null) throw new IllegalArgumentException(String.format("Unrecognized OS '%s'", osName));
|
||||
current = currentOs;
|
||||
isUnix = Stream.of(LINUX, SOLARIS, BSD, AIX, HP_UX).collect(Collectors.toSet()).contains(current);
|
||||
if(current == null) throw new IllegalArgumentException(String.format("Unrecognized OS '%s'", osName));
|
||||
isUnix = Stream.of(LINUX, SOLARIS, BSD, AIX, HP_UX, MACOS).collect(Collectors.toSet()).contains(current);
|
||||
isWindows = current == WINDOWS;
|
||||
isMac = current == MACOS;
|
||||
isLinux = current == LINUX;
|
||||
}
|
||||
}
|
||||
}
|
52
src/main/java/net/woggioni/jwo/SortedProperties.java
Normal file
52
src/main/java/net/woggioni/jwo/SortedProperties.java
Normal file
@@ -0,0 +1,52 @@
|
||||
package net.woggioni.jwo;
|
||||
|
||||
import java.util.Enumeration;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.TreeSet;
|
||||
|
||||
public class SortedProperties extends Properties {
|
||||
|
||||
@Override
|
||||
public Enumeration<Object> keys() {
|
||||
Iterator<Object> it = keySet().iterator();
|
||||
return new Enumeration<Object>() {
|
||||
@Override
|
||||
public boolean hasMoreElements() {
|
||||
return it.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object nextElement() {
|
||||
return it.next();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Object> keySet() {
|
||||
Enumeration<Object> enumeration = super.keys();
|
||||
|
||||
TreeSet<String> sortedSet = new TreeSet<>();
|
||||
while(enumeration.hasMoreElements()) {
|
||||
String key = (String) enumeration.nextElement();
|
||||
sortedSet.add(key);
|
||||
}
|
||||
return (Set) sortedSet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Map.Entry<Object, Object>> entrySet() {
|
||||
Enumeration<Object> enumeration = keys();
|
||||
|
||||
TreeMap<String, Object> sortedMap = new TreeMap<>();
|
||||
while(enumeration.hasMoreElements()) {
|
||||
String key = (String) enumeration.nextElement();
|
||||
sortedMap.put(key, get(key));
|
||||
}
|
||||
return (Set) sortedMap.entrySet();
|
||||
}
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
package net.woggioni.jwo.exception;
|
||||
|
||||
import net.woggioni.jwo.JWO;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ChildProcessException extends RuntimeException {
|
||||
|
||||
private static final String cmdLine2str(Iterable<String> cmdLine) {
|
||||
return JWO.iterable2Stream(cmdLine)
|
||||
.map(it -> it.replace("\"", "\\\""))
|
||||
.map(it -> "\"" + it + "\"")
|
||||
.collect(Collectors.joining(", "));
|
||||
}
|
||||
|
||||
public ChildProcessException(Iterable<String> cmdline, int exitCode) {
|
||||
super(String.format("Child process [%s] terminated with exit code %d", cmdLine2str(cmdline), exitCode));
|
||||
}
|
||||
}
|
@@ -0,0 +1,54 @@
|
||||
package net.woggioni.jwo.internal;
|
||||
|
||||
import net.woggioni.jwo.CollectionUtils;
|
||||
import net.woggioni.jwo.JWO;
|
||||
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Set;
|
||||
|
||||
public class ByteFilterInputStream extends FilterInputStream {
|
||||
|
||||
private boolean finished = false;
|
||||
|
||||
private final Set<Byte> forbidden;
|
||||
|
||||
public ByteFilterInputStream(InputStream source, Iterable<Byte> filteredChars) {
|
||||
super(source);
|
||||
forbidden = JWO.iterable2Stream(filteredChars).collect(CollectionUtils.toUnmodifiableTreeSet());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
while(true) {
|
||||
int res = super.read();
|
||||
if(!forbidden.contains(res)) {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b) throws IOException {
|
||||
return read(b, 0, b.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
if(finished) return -1;
|
||||
int i = 0;
|
||||
int lim = Math.min(len, b.length - off);
|
||||
while(i < lim) {
|
||||
int c = read();
|
||||
if(c < 0) {
|
||||
break;
|
||||
}
|
||||
b[off + i] = (byte) c;
|
||||
++i;
|
||||
}
|
||||
if(i == 0) finished = true;
|
||||
return i == 0 ? -1 : i;
|
||||
}
|
||||
}
|
@@ -0,0 +1,66 @@
|
||||
package net.woggioni.jwo.internal;
|
||||
|
||||
import net.woggioni.jwo.CollectionUtils;
|
||||
import net.woggioni.jwo.JWO;
|
||||
|
||||
import java.io.FilterReader;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.nio.CharBuffer;
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
|
||||
public class CharFilterReader extends FilterReader {
|
||||
|
||||
private boolean finished = false;
|
||||
|
||||
private final Set<Character> forbidden;
|
||||
|
||||
public CharFilterReader(Reader source, Iterable<Character> filteredChars) {
|
||||
super(source);
|
||||
forbidden = JWO.iterable2Stream(filteredChars).collect(CollectionUtils.toUnmodifiableTreeSet());
|
||||
}
|
||||
|
||||
public CharFilterReader(Reader source, Character ...filteredChars) {
|
||||
super(source);
|
||||
forbidden = Arrays.stream(filteredChars).collect(CollectionUtils.toUnmodifiableTreeSet());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
while(true) {
|
||||
int res = super.read();
|
||||
if(!forbidden.contains((char) res)) {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(char[] cbuf, int off, int len) throws IOException {
|
||||
if(finished) return -1;
|
||||
int i = 0;
|
||||
int lim = Math.min(len, cbuf.length - off);
|
||||
while(i < lim) {
|
||||
int c = read();
|
||||
if(c < 0) {
|
||||
break;
|
||||
}
|
||||
cbuf[off + i] = (char) c;
|
||||
++i;
|
||||
}
|
||||
if(i == 0) finished = true;
|
||||
return i == 0 ? -1 : i;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(CharBuffer target) throws IOException {
|
||||
return read(target.array());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(char[] cbuf) throws IOException {
|
||||
return read(cbuf, 0, cbuf.length);
|
||||
}
|
||||
}
|
@@ -0,0 +1,77 @@
|
||||
package net.woggioni.jwo.internal;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.woggioni.jwo.LazyValue;
|
||||
import net.woggioni.jwo.MutableTuple2;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class SynchronizedLazyValue<T> implements LazyValue<T> {
|
||||
private final Supplier<T> valueSupplier;
|
||||
|
||||
private final Consumer<T> finalizer;
|
||||
private final MutableTuple2<T, Boolean> instance = MutableTuple2.newInstance(null, false);
|
||||
|
||||
public SynchronizedLazyValue(Supplier<T> valueSupplier) {
|
||||
this(valueSupplier, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <U> LazyValue<U> map(Function<T, U> fun) {
|
||||
return new SynchronizedLazyValue<>(() -> fun.apply(get()));
|
||||
}
|
||||
|
||||
public Stream<T> stream() {
|
||||
return Stream.generate(this::get).limit(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get() {
|
||||
if(instance.get_2()) return instance.get_1();
|
||||
synchronized (instance) {
|
||||
if(instance.get_2()) return instance.get_1();
|
||||
else {
|
||||
T value = valueSupplier.get();
|
||||
instance.set_1(value);
|
||||
instance.set_2(true);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <U> SynchronizedLazyValue<U> handle(BiFunction<T, Throwable, U> bicon) {
|
||||
return new SynchronizedLazyValue<>(() -> {
|
||||
try {
|
||||
return bicon.apply(get(), null);
|
||||
} catch (Throwable t) {
|
||||
return bicon.apply(null, t);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the finalizer on the wrapped object, if it has been initialized, and then returns it.
|
||||
* It does nothing if {@link SynchronizedLazyValue#get()} has never been invoked.
|
||||
* @return the wrapped value if initialized, otherwise an empty {@link Optional}
|
||||
*/
|
||||
@Override
|
||||
public Optional<T> close() {
|
||||
T result = null;
|
||||
synchronized (instance) {
|
||||
if(instance.get_2()) {
|
||||
instance.set_2(false);
|
||||
result = instance.get_1();
|
||||
instance.set_1(null);
|
||||
}
|
||||
}
|
||||
if(result != null) finalizer.accept(result);
|
||||
return Optional.ofNullable(result);
|
||||
}
|
||||
}
|
@@ -0,0 +1,72 @@
|
||||
package net.woggioni.jwo.internal;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.woggioni.jwo.LazyValue;
|
||||
import net.woggioni.jwo.MutableTuple2;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class UnsynchronizedLazyValue<T> implements LazyValue<T> {
|
||||
private final Supplier<T> valueSupplier;
|
||||
|
||||
private final Consumer<T> finalizer;
|
||||
private final MutableTuple2<T, Boolean> instance = MutableTuple2.newInstance(null, false);
|
||||
|
||||
public UnsynchronizedLazyValue(Supplier<T> valueSupplier) {
|
||||
this(valueSupplier, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <U> LazyValue<U> map(Function<T, U> fun) {
|
||||
return new UnsynchronizedLazyValue<>(() -> fun.apply(get()));
|
||||
}
|
||||
|
||||
public Stream<T> stream() {
|
||||
return Stream.generate(this::get).limit(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get() {
|
||||
if(instance.get_2()) return instance.get_1();
|
||||
else {
|
||||
T value = valueSupplier.get();
|
||||
instance.set_1(value);
|
||||
instance.set_2(true);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <U> UnsynchronizedLazyValue<U> handle(BiFunction<T, Throwable, U> bicon) {
|
||||
return new UnsynchronizedLazyValue<>(() -> {
|
||||
try {
|
||||
return bicon.apply(get(), null);
|
||||
} catch (Throwable t) {
|
||||
return bicon.apply(null, t);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the finalizer on the wrapped object, if it has been initialized, and then returns it.
|
||||
* It does nothing if {@link UnsynchronizedLazyValue#get()} has never been invoked.
|
||||
* @return the wrapped value if initialized, otherwise an empty {@link Optional}
|
||||
*/
|
||||
@Override
|
||||
public Optional<T> close() {
|
||||
T result = null;
|
||||
if(instance.get_2()) {
|
||||
instance.set_2(false);
|
||||
result = instance.get_1();
|
||||
instance.set_1(null);
|
||||
}
|
||||
if(result != null) finalizer.accept(result);
|
||||
return Optional.ofNullable(result);
|
||||
}
|
||||
}
|
@@ -1,4 +1,5 @@
|
||||
module net.woggioni.jwo {
|
||||
requires org.slf4j;
|
||||
exports net.woggioni.jwo;
|
||||
exports net.woggioni.jwo.exception;
|
||||
}
|
@@ -4,8 +4,16 @@ import lombok.SneakyThrows;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledOnOs;
|
||||
import org.junit.jupiter.api.condition.OS;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import java.io.*;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.attribute.PosixFileAttributes;
|
||||
import java.nio.file.attribute.UserPrincipal;
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -82,4 +90,18 @@ public class JWOTest {
|
||||
}
|
||||
return formatter.toString();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SneakyThrows
|
||||
@EnabledOnOs(OS.LINUX)
|
||||
public void uidTest(@TempDir Path testDir) {
|
||||
PosixFileAttributes pfa = Files.readAttributes(testDir, PosixFileAttributes.class);
|
||||
UserPrincipal expectedUser = pfa.owner();
|
||||
Class<? extends UserPrincipal> userClass = expectedUser.getClass();
|
||||
Method m = userClass.getDeclaredMethod("uid");
|
||||
m.setAccessible(true);
|
||||
int expectedUserId = (Integer) m.invoke(expectedUser);
|
||||
int uid = (int) JWO.uid();
|
||||
Assertions.assertEquals(expectedUserId, uid);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user