added some utilities

This commit is contained in:
2022-12-09 09:40:47 +08:00
parent 541166dc35
commit d3c32b1765
16 changed files with 1045 additions and 55 deletions

View File

@@ -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 {

View 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));
}
}

View File

@@ -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())));
}
}

View File

@@ -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());
}
}

View 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 == '.';
}
}

View File

@@ -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;
}
}

View 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());
}
}

View File

@@ -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;
}
}
}

View 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();
}
}

View File

@@ -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));
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -1,4 +1,5 @@
module net.woggioni.jwo {
requires org.slf4j;
exports net.woggioni.jwo;
exports net.woggioni.jwo.exception;
}

View File

@@ -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);
}
}