added Jacoco and improved test coverage
This commit is contained in:
27
build.gradle
27
build.gradle
@@ -1,6 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'java-library'
|
id 'java-library'
|
||||||
id 'maven-publish'
|
id 'maven-publish'
|
||||||
|
id 'jacoco'
|
||||||
alias(catalog.plugins.multi.release.jar)
|
alias(catalog.plugins.multi.release.jar)
|
||||||
alias(catalog.plugins.lombok)
|
alias(catalog.plugins.lombok)
|
||||||
alias(catalog.plugins.sambal)
|
alias(catalog.plugins.sambal)
|
||||||
@@ -20,7 +21,6 @@ allprojects {
|
|||||||
pluginManager.withPlugin('java-library') {
|
pluginManager.withPlugin('java-library') {
|
||||||
|
|
||||||
java {
|
java {
|
||||||
|
|
||||||
toolchain {
|
toolchain {
|
||||||
languageVersion = JavaLanguageVersion.of(17)
|
languageVersion = JavaLanguageVersion.of(17)
|
||||||
vendor = JvmVendorSpec.GRAAL_VM
|
vendor = JvmVendorSpec.GRAAL_VM
|
||||||
@@ -35,6 +35,21 @@ allprojects {
|
|||||||
|
|
||||||
test {
|
test {
|
||||||
useJUnitPlatform()
|
useJUnitPlatform()
|
||||||
|
|
||||||
|
systemProperties([
|
||||||
|
'junit.jupiter.execution.parallel.enabled' : true,
|
||||||
|
'junit.jupiter.execution.parallel.mode.default' : 'concurrent',
|
||||||
|
'junit.jupiter.execution.parallel.mode.classes.default' : 'concurrent'
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
pluginManager.withPlugin('jacoco') {
|
||||||
|
test {
|
||||||
|
finalizedBy jacocoTestReport
|
||||||
|
}
|
||||||
|
jacocoTestReport {
|
||||||
|
dependsOn test
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,11 +90,18 @@ ext {
|
|||||||
|
|
||||||
configurations {
|
configurations {
|
||||||
pathClassloaderTest
|
pathClassloaderTest
|
||||||
|
zipTestBundle {
|
||||||
|
transitive = false
|
||||||
|
canBeConsumed = false
|
||||||
|
visible = false
|
||||||
|
canBeResolved = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation catalog.slf4j.api
|
implementation catalog.slf4j.api
|
||||||
pathClassloaderTest group: 'com.google.inject', name: 'guice', version: getProperty('guice.version')
|
pathClassloaderTest group: 'com.google.inject', name: 'guice', version: getProperty('guice.version')
|
||||||
|
zipTestBundle group: 'com.google.inject', name: 'guice', version: getProperty('guice.version')
|
||||||
}
|
}
|
||||||
|
|
||||||
compileJava {
|
compileJava {
|
||||||
@@ -104,7 +126,8 @@ test {
|
|||||||
}.first()
|
}.first()
|
||||||
systemProperties([
|
systemProperties([
|
||||||
'junit.jupiter.engine.jar' : junitJupiterEngineJar.toString(),
|
'junit.jupiter.engine.jar' : junitJupiterEngineJar.toString(),
|
||||||
'path.classloader.test.bundle' : pathClassLoaderTestBundleTask.get().outputs.files.singleFile
|
'path.classloader.test.bundle' : pathClassLoaderTestBundleTask.get().outputs.files.singleFile,
|
||||||
|
'zip.test.bundle' : configurations.zipTestBundle.singleFile
|
||||||
])
|
])
|
||||||
|
|
||||||
jvmArgs(['--add-opens', 'java.base/sun.nio.fs=ALL-UNNAMED'])
|
jvmArgs(['--add-opens', 'java.base/sun.nio.fs=ALL-UNNAMED'])
|
||||||
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
3
gradle/wrapper/gradle-wrapper.properties
vendored
3
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,7 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
8
gradlew
vendored
8
gradlew
vendored
@@ -83,7 +83,8 @@ done
|
|||||||
# This is normally unused
|
# This is normally unused
|
||||||
# shellcheck disable=SC2034
|
# shellcheck disable=SC2034
|
||||||
APP_BASE_NAME=${0##*/}
|
APP_BASE_NAME=${0##*/}
|
||||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||||
|
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD=maximum
|
MAX_FD=maximum
|
||||||
@@ -130,10 +131,13 @@ location of your Java installation."
|
|||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
JAVACMD=java
|
JAVACMD=java
|
||||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
if ! command -v java >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
Please set the JAVA_HOME variable in your environment to match the
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
location of your Java installation."
|
location of your Java installation."
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Increase the maximum file descriptors if we can.
|
# Increase the maximum file descriptors if we can.
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'java-library'
|
id 'java-library'
|
||||||
|
id 'jacoco'
|
||||||
alias(catalog.plugins.lombok)
|
alias(catalog.plugins.lombok)
|
||||||
id 'maven-publish'
|
id 'maven-publish'
|
||||||
}
|
}
|
||||||
|
@@ -35,39 +35,6 @@ public class Application {
|
|||||||
return new Builder().name(name);
|
return new Builder().name(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean validateConfigurationDirectory(Path candidate) {
|
|
||||||
try {
|
|
||||||
if (!Files.exists(candidate)) {
|
|
||||||
Files.createDirectories(candidate);
|
|
||||||
return true;
|
|
||||||
} else if (!Files.isDirectory(candidate)) {
|
|
||||||
log.debug("Configuration directory '{}' discarded because it is not a directory", candidate);
|
|
||||||
return false;
|
|
||||||
} else if (!Files.isWritable(candidate)) {
|
|
||||||
log.debug("Configuration directory '{}' discarded because it is not writable", candidate);
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
log.debug("Using configuration directory '{}'", candidate);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} catch (Exception ioe) {
|
|
||||||
log.debug(
|
|
||||||
String.format("configuration directory '%s' discarded: %s", candidate.toString(), ioe.getMessage()),
|
|
||||||
ioe
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SneakyThrows
|
|
||||||
private static Path selectCandidate(Stream<Path> candidates, String successMessage, String errorMessage) {
|
|
||||||
return candidates
|
|
||||||
.filter(Application::validateConfigurationDirectory)
|
|
||||||
.peek(p -> log.debug(successMessage, p))
|
|
||||||
.findFirst()
|
|
||||||
.orElseThrow((Sup<Throwable>) () -> new FileNotFoundException(errorMessage));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean validateWritableDirectory(Path candidate) {
|
private static boolean validateWritableDirectory(Path candidate) {
|
||||||
try {
|
try {
|
||||||
if (!Files.exists(candidate)) {
|
if (!Files.exists(candidate)) {
|
||||||
|
@@ -9,6 +9,7 @@ import java.util.HashMap;
|
|||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.ListIterator;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.NavigableMap;
|
import java.util.NavigableMap;
|
||||||
import java.util.NavigableSet;
|
import java.util.NavigableSet;
|
||||||
@@ -124,10 +125,16 @@ public class CollectionUtils {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <T> BinaryOperator<T> throwingMerger() {
|
public static <T> T throwingMerger(T v1, T v2) {
|
||||||
return (v1, v2) -> {
|
throw new IllegalStateException(String.format("Duplicate key %s", v1));
|
||||||
throw new IllegalStateException(String.format("Duplicate key %s", v1));
|
}
|
||||||
};
|
|
||||||
|
public static <T> T oldValueMerger(T oldValue, T newValue) {
|
||||||
|
return oldValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T newValueMerger(T oldValue, T newValue) {
|
||||||
|
return newValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <T, K, V> Collector<T, ?, Map<K, V>> toUnmodifiableHashMap(
|
public static <T, K, V> Collector<T, ?, Map<K, V>> toUnmodifiableHashMap(
|
||||||
@@ -162,62 +169,109 @@ public class CollectionUtils {
|
|||||||
return toNavigableMap(() -> new TreeMap<>(comparator), keyExtractor, valueExtractor);
|
return toNavigableMap(() -> new TreeMap<>(comparator), keyExtractor, valueExtractor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static <T, K, V, MAP_TYPE extends NavigableMap<K, V>> Collector<T, ?, MAP_TYPE> toNavigableMap(
|
||||||
|
Supplier<MAP_TYPE> constructor,
|
||||||
|
Function<T, K> keyExtractor,
|
||||||
|
Function<T, V> valueExtractor) {
|
||||||
|
return toNavigableMap(
|
||||||
|
constructor,
|
||||||
|
keyExtractor,
|
||||||
|
valueExtractor,
|
||||||
|
CollectionUtils::throwingMerger
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public static <T, K, V, MAP_TYPE extends NavigableMap<K, V>> Collector<T, ?, MAP_TYPE> toNavigableMap(
|
public static <T, K, V, MAP_TYPE extends NavigableMap<K, V>> Collector<T, ?, MAP_TYPE> toNavigableMap(
|
||||||
Supplier<MAP_TYPE> constructor,
|
Supplier<MAP_TYPE> constructor,
|
||||||
Function<T, K> keyExtractor,
|
Function<T, K> keyExtractor,
|
||||||
Function<T, V> valueExtractor) {
|
Function<T, V> valueExtractor,
|
||||||
|
BinaryOperator<V> valueMerger) {
|
||||||
BiConsumer<MAP_TYPE, T> accumulator = (map, streamElement) -> {
|
BiConsumer<MAP_TYPE, T> accumulator = (map, streamElement) -> {
|
||||||
map.merge(keyExtractor.apply(streamElement), valueExtractor.apply(streamElement), throwingMerger());
|
map.merge(keyExtractor.apply(streamElement), valueExtractor.apply(streamElement),
|
||||||
|
valueMerger
|
||||||
|
);
|
||||||
};
|
};
|
||||||
return Collector.of(
|
return Collector.of(
|
||||||
constructor,
|
constructor,
|
||||||
accumulator,
|
accumulator,
|
||||||
mapMerger(throwingMerger())
|
mapMerger(valueMerger)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static <T, K, V> Collector<T, ?, Map<K, V>> toMap(
|
||||||
|
Supplier<Map<K, V>> constructor,
|
||||||
|
Function<T, K> keyExtractor,
|
||||||
|
Function<T, V> valueExtractor) {
|
||||||
|
return toMap(constructor, keyExtractor, valueExtractor, CollectionUtils::throwingMerger);
|
||||||
|
}
|
||||||
|
|
||||||
public static <T, K, V> Collector<T, ?, Map<K, V>> toMap(
|
public static <T, K, V> Collector<T, ?, Map<K, V>> toMap(
|
||||||
Supplier<Map<K, V>> constructor,
|
Supplier<Map<K, V>> constructor,
|
||||||
Function<T, K> keyExtractor,
|
Function<T, K> keyExtractor,
|
||||||
Function<T, V> valueExtractor) {
|
Function<T, V> valueExtractor,
|
||||||
|
BinaryOperator<V> valueMerger) {
|
||||||
BiConsumer<Map<K, V>, T> accumulator = (map, streamElement) -> {
|
BiConsumer<Map<K, V>, T> accumulator = (map, streamElement) -> {
|
||||||
map.merge(keyExtractor.apply(streamElement), valueExtractor.apply(streamElement), throwingMerger());
|
map.merge(keyExtractor.apply(streamElement), valueExtractor.apply(streamElement),
|
||||||
|
valueMerger
|
||||||
|
);
|
||||||
};
|
};
|
||||||
return Collector.of(
|
return Collector.of(
|
||||||
constructor,
|
constructor,
|
||||||
accumulator,
|
accumulator,
|
||||||
mapMerger(throwingMerger())
|
mapMerger(valueMerger)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static <T, K, V> Collector<T, ?, Map<K, V>> toUnmodifiableMap(
|
||||||
|
Supplier<Map<K, V>> constructor,
|
||||||
|
Function<T, K> keyExtractor,
|
||||||
|
Function<T, V> valueExtractor) {
|
||||||
|
return toUnmodifiableMap(constructor, keyExtractor, valueExtractor, CollectionUtils::throwingMerger);
|
||||||
|
}
|
||||||
public static <T, K, V> Collector<T, ?, Map<K, V>> toUnmodifiableMap(
|
public static <T, K, V> Collector<T, ?, Map<K, V>> toUnmodifiableMap(
|
||||||
Supplier<Map<K, V>> constructor,
|
Supplier<Map<K, V>> constructor,
|
||||||
Function<T, K> keyExtractor,
|
Function<T, K> keyExtractor,
|
||||||
Function<T, V> valueExtractor) {
|
Function<T, V> valueExtractor,
|
||||||
|
BinaryOperator<V> valueMerger) {
|
||||||
BiConsumer<Map<K, V>, T> accumulator = (map, streamElement) -> {
|
BiConsumer<Map<K, V>, T> accumulator = (map, streamElement) -> {
|
||||||
map.merge(keyExtractor.apply(streamElement), valueExtractor.apply(streamElement), throwingMerger());
|
map.merge(keyExtractor.apply(streamElement),
|
||||||
|
valueExtractor.apply(streamElement),
|
||||||
|
valueMerger
|
||||||
|
);
|
||||||
};
|
};
|
||||||
return Collector.of(
|
return Collector.of(
|
||||||
constructor,
|
constructor,
|
||||||
accumulator,
|
accumulator,
|
||||||
mapMerger(throwingMerger()),
|
mapMerger(valueMerger),
|
||||||
Collections::unmodifiableMap
|
Collections::unmodifiableMap
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static <T, K, V> Collector<T, ?, NavigableMap<K, V>> toUnmodifiableNavigableMap(
|
||||||
|
Supplier<NavigableMap<K, V>> constructor,
|
||||||
|
Function<T, K> keyExtractor,
|
||||||
|
Function<T, V> valueExtractor,
|
||||||
|
BinaryOperator<V> valueMerger
|
||||||
|
) {
|
||||||
|
BiConsumer<NavigableMap<K, V>, T> accumulator = (map, streamElement) -> {
|
||||||
|
map.merge(
|
||||||
|
keyExtractor.apply(streamElement),
|
||||||
|
valueExtractor.apply(streamElement),
|
||||||
|
valueMerger
|
||||||
|
);
|
||||||
|
};
|
||||||
|
return Collector.of(
|
||||||
|
constructor,
|
||||||
|
accumulator,
|
||||||
|
mapMerger(valueMerger),
|
||||||
|
Collections::unmodifiableNavigableMap
|
||||||
|
);
|
||||||
|
}
|
||||||
public static <T, K, V> Collector<T, ?, NavigableMap<K, V>> toUnmodifiableNavigableMap(
|
public static <T, K, V> Collector<T, ?, NavigableMap<K, V>> toUnmodifiableNavigableMap(
|
||||||
Supplier<NavigableMap<K, V>> constructor,
|
Supplier<NavigableMap<K, V>> constructor,
|
||||||
Function<T, K> keyExtractor,
|
Function<T, K> keyExtractor,
|
||||||
Function<T, V> valueExtractor) {
|
Function<T, V> valueExtractor) {
|
||||||
BiConsumer<NavigableMap<K, V>, T> accumulator = (map, streamElement) -> {
|
return toUnmodifiableNavigableMap(constructor, keyExtractor, valueExtractor, CollectionUtils::throwingMerger);
|
||||||
map.merge(keyExtractor.apply(streamElement), valueExtractor.apply(streamElement), throwingMerger());
|
|
||||||
};
|
|
||||||
return Collector.of(
|
|
||||||
constructor,
|
|
||||||
accumulator,
|
|
||||||
mapMerger(throwingMerger()),
|
|
||||||
Collections::unmodifiableNavigableMap
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
public static <K, V, U> Stream<Map.Entry<K, U>> mapValues(Map<K, V> map, Fun<V, U> xform) {
|
public static <K, V, U> Stream<Map.Entry<K, U>> mapValues(Map<K, V> map, Fun<V, U> xform) {
|
||||||
return map
|
return map
|
||||||
@@ -225,4 +279,26 @@ public class CollectionUtils {
|
|||||||
.stream()
|
.stream()
|
||||||
.map(entry -> new AbstractMap.SimpleEntry<>(entry.getKey(), xform.apply(entry.getValue())));
|
.map(entry -> new AbstractMap.SimpleEntry<>(entry.getKey(), xform.apply(entry.getValue())));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static <T> Iterator<T> reverseIterator(List<T> list) {
|
||||||
|
return new Iterator<T>() {
|
||||||
|
private final ListIterator<T> it = list.listIterator(list.size());
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
return it.hasPrevious();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T next() {
|
||||||
|
return it.previous();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> Iterator<T> reverseIterator(NavigableSet<T> set) {
|
||||||
|
return set.descendingIterator();
|
||||||
|
}
|
||||||
|
public static <K, V> Iterator<Map.Entry<K, V>> reverseIterator(NavigableMap<K, V> map) {
|
||||||
|
return map.descendingMap().entrySet().iterator();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,20 +1,17 @@
|
|||||||
package net.woggioni.jwo;
|
package net.woggioni.jwo;
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
public class DelegatingMap<K,V> implements Map<K, V> {
|
import static net.woggioni.jwo.JWO.streamCat;
|
||||||
private final Supplier<Map<K,V>> mapFactory;
|
|
||||||
private final List<Map<K,V>> delegates;
|
|
||||||
private final Map<K,V> thisMap;
|
|
||||||
|
|
||||||
|
public class DelegatingMap<K,V> extends UnmodifiableDelegatingMap<K, V> {
|
||||||
|
private final Map<K,V> thisMap;
|
||||||
public DelegatingMap(Supplier<Map<K,V>> mapFactory, List<Map<K,V>> delegates) {
|
public DelegatingMap(Supplier<Map<K,V>> mapFactory, List<Map<K,V>> delegates) {
|
||||||
this.mapFactory = mapFactory;
|
super(mapFactory, delegates);
|
||||||
this.delegates = delegates;
|
|
||||||
thisMap = mapFactory.get();
|
thisMap = mapFactory.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,67 +23,36 @@ public class DelegatingMap<K,V> implements Map<K, V> {
|
|||||||
@Override
|
@Override
|
||||||
public boolean isEmpty() {
|
public boolean isEmpty() {
|
||||||
if(!thisMap.isEmpty()) return false;
|
if(!thisMap.isEmpty()) return false;
|
||||||
for(Map<K,V> delegate : delegates) {
|
return super.isEmpty();
|
||||||
if(!delegate.isEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean containsKey(Object key) {
|
public boolean containsKey(Object key) {
|
||||||
if(thisMap.containsKey(key)) return true;
|
if(thisMap.containsKey(key)) return true;
|
||||||
for(Map<K,V> delegate : delegates) {
|
return super.containsKey(key);
|
||||||
if(!delegate.containsKey(key)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean containsValue(Object value) {
|
public boolean containsValue(Object value) {
|
||||||
if(thisMap.containsValue(value)) return true;
|
if(thisMap.containsValue(value)) return true;
|
||||||
for(Map<K,V> delegate : delegates) {
|
return super.containsValue(value);
|
||||||
if(!delegate.containsValue(value)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public V get(Object key) {
|
public V get(Object key) {
|
||||||
V result = thisMap.get(key);
|
return Optional.ofNullable(thisMap.get(key)).orElseGet(
|
||||||
if(result != null) return result;
|
() -> super.get(key)
|
||||||
for(Map<K,V> delegate : delegates) {
|
);
|
||||||
result = delegate.get(key);
|
|
||||||
if(result != null) break;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public V put(K key, V value) {
|
public V put(K key, V value) {
|
||||||
V result = thisMap.put(key, value);
|
return thisMap.put(key, value);
|
||||||
if(result != null) return result;
|
|
||||||
for(Map<K,V> delegate : delegates) {
|
|
||||||
result = delegate.put(key, value);
|
|
||||||
if(result != null) break;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public V remove(Object key) {
|
public V remove(Object key) {
|
||||||
V result = thisMap.remove(key);
|
return thisMap.remove(key);
|
||||||
if(result != null) return result;
|
|
||||||
for(Map<K,V> delegate : delegates) {
|
|
||||||
result = delegate.remove(key);
|
|
||||||
if(result != null) break;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -104,24 +70,8 @@ public class DelegatingMap<K,V> implements Map<K, V> {
|
|||||||
return flatten().keySet();
|
return flatten().keySet();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<K, V> flatten() {
|
protected Map<K, V> flatten() {
|
||||||
Map<K, V> result = mapFactory.get();
|
return streamCat(super.stream(), thisMap.entrySet().stream())
|
||||||
int i = delegates.size();
|
.collect(CollectionUtils.toUnmodifiableMap(this.mapFactory, Map.Entry::getKey, Map.Entry::getValue));
|
||||||
while(i-->0) {
|
|
||||||
Map<K, V> delegate = delegates.get(i);
|
|
||||||
result.putAll(delegate);
|
|
||||||
}
|
|
||||||
result.putAll(thisMap);
|
|
||||||
return Collections.unmodifiableMap(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<V> values() {
|
|
||||||
return flatten().values();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<Entry<K, V>> entrySet() {
|
|
||||||
return flatten().entrySet();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -6,6 +6,9 @@ import lombok.RequiredArgsConstructor;
|
|||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.security.DigestInputStream;
|
||||||
|
import java.security.DigestOutputStream;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
|
|
||||||
@EqualsAndHashCode
|
@EqualsAndHashCode
|
||||||
@@ -27,6 +30,14 @@ public class Hash {
|
|||||||
public MessageDigest newMessageDigest() {
|
public MessageDigest newMessageDigest() {
|
||||||
return MessageDigest.getInstance(key);
|
return MessageDigest.getInstance(key);
|
||||||
}
|
}
|
||||||
|
@SneakyThrows
|
||||||
|
public DigestOutputStream newOutputStream(OutputStream delegate) {
|
||||||
|
return new DigestOutputStream(delegate, MessageDigest.getInstance(key));
|
||||||
|
}
|
||||||
|
@SneakyThrows
|
||||||
|
public DigestInputStream newInputStream(InputStream delegate) {
|
||||||
|
return new DigestInputStream(delegate, MessageDigest.getInstance(key));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
|
@@ -22,6 +22,7 @@ import java.io.InputStreamReader;
|
|||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.io.OutputStreamWriter;
|
import java.io.OutputStreamWriter;
|
||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
|
import java.io.StringWriter;
|
||||||
import java.io.Writer;
|
import java.io.Writer;
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
import java.nio.channels.Channels;
|
import java.nio.channels.Channels;
|
||||||
@@ -104,23 +105,19 @@ public class JWO {
|
|||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
public static void writeBytes2File(Path file, byte[] bytes) {
|
public static void writeBytes2File(Path file, byte[] bytes) {
|
||||||
try (OutputStream os = new FileOutputStream(file.toString())) {
|
try (OutputStream os = Files.newOutputStream(file)) {
|
||||||
os.write(bytes);
|
os.write(bytes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
public static String readFile2String(File file) {
|
public static String readFile2String(File file) {
|
||||||
StringBuilder builder = new StringBuilder();
|
StringWriter writer = new StringWriter();
|
||||||
try (Reader reader = new InputStreamReader(new BufferedInputStream(new FileInputStream(file.getPath())))) {
|
try (Reader reader = Files.newBufferedReader(file.toPath())) {
|
||||||
char[] buffer = new char[1024];
|
char[] buffer = new char[1024];
|
||||||
while (true) {
|
JWO.copy(reader, writer, buffer);
|
||||||
int read = reader.read(buffer);
|
|
||||||
builder.append(buffer, 0, read);
|
|
||||||
if (read < buffer.length) break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return builder.toString();
|
return writer.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
@@ -137,6 +134,13 @@ public class JWO {
|
|||||||
return builder.toString();
|
return builder.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
public static <T extends Throwable> T newThrowable(Class<T> cls) {
|
||||||
|
Constructor<T> constructor = cls.getDeclaredConstructor();
|
||||||
|
constructor.setAccessible(true);
|
||||||
|
return constructor.newInstance();
|
||||||
|
}
|
||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
public static <T extends Throwable> T newThrowable(Class<T> cls, String format, Object... args) {
|
public static <T extends Throwable> T newThrowable(Class<T> cls, String format, Object... args) {
|
||||||
Constructor<T> constructor = cls.getDeclaredConstructor(String.class);
|
Constructor<T> constructor = cls.getDeclaredConstructor(String.class);
|
||||||
@@ -150,6 +154,11 @@ public class JWO {
|
|||||||
return constructor.newInstance(String.format(format, args), throwable);
|
return constructor.newInstance(String.format(format, args), throwable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
public static <T extends Throwable> T raise(Class<T> cls) {
|
||||||
|
throw newThrowable(cls);
|
||||||
|
}
|
||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
public static <T extends Throwable> void raise(Class<T> cls, Throwable throwable, String format, Object... args) {
|
public static <T extends Throwable> void raise(Class<T> cls, Throwable throwable, String format, Object... args) {
|
||||||
throw newThrowable(cls, throwable, format, args);
|
throw newThrowable(cls, throwable, format, args);
|
||||||
@@ -160,7 +169,6 @@ public class JWO {
|
|||||||
throw newThrowable(cls, format, args);
|
throw newThrowable(cls, format, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static SSLSocketFactory defaultSSLSocketFactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
|
private static SSLSocketFactory defaultSSLSocketFactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
|
||||||
private static HostnameVerifier defaultHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
|
private static HostnameVerifier defaultHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
|
||||||
|
|
||||||
@@ -563,13 +571,13 @@ public class JWO {
|
|||||||
byte[] buffer = new byte[0x10000];
|
byte[] buffer = new byte[0x10000];
|
||||||
expandZip(sourceArchive, (BiCon<ZipInputStream, ZipEntry>)
|
expandZip(sourceArchive, (BiCon<ZipInputStream, ZipEntry>)
|
||||||
(ZipInputStream zipInputStream, ZipEntry zipEntry) -> {
|
(ZipInputStream zipInputStream, ZipEntry zipEntry) -> {
|
||||||
Path newFile = destinationFolder.resolve(zipEntry.getName());
|
Path entryPath = destinationFolder.resolve(zipEntry.getName());
|
||||||
Files.createDirectories(newFile.getParent());
|
if(zipEntry.isDirectory()) {
|
||||||
try(OutputStream outputStream = Files.newOutputStream(newFile)) {
|
Files.createDirectories(entryPath);
|
||||||
while (true) {
|
} else {
|
||||||
int read = zipInputStream.read(buffer);
|
Files.createDirectories(entryPath.getParent());
|
||||||
if (read < 0) break;
|
try(OutputStream outputStream = Files.newOutputStream(entryPath)) {
|
||||||
outputStream.write(buffer, 0, read);
|
copy(zipInputStream, outputStream, buffer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -815,7 +823,7 @@ public class JWO {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void replaceFileIfDifferent(InputStream inputStream, Path destination, FileAttribute<?>... attrs) {
|
public static void replaceFileIfDifferent(InputStream inputStream, Path destination, FileAttribute<?>... attrs) {
|
||||||
replaceFileIfDifferent(() -> inputStream, destination, attrs);
|
replaceFileIfDifferent(() -> inputStream, destination, null, attrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
@@ -874,6 +882,18 @@ public class JWO {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static <T, U> Runnable curry(Consumer<T> original, Supplier<T> sup) {
|
||||||
|
return () -> original.accept(sup.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> Runnable curry(Consumer<T> original, T arg) {
|
||||||
|
return () -> original.accept(arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T, U> Supplier<U> curry(Fun<T, U> original, T arg) {
|
||||||
|
return () -> original.apply(arg);
|
||||||
|
}
|
||||||
|
|
||||||
public static <T, U, V> Fun<U, V> curry1(BiFun<T, U, V> original, T arg) {
|
public static <T, U, V> Fun<U, V> curry1(BiFun<T, U, V> original, T arg) {
|
||||||
return u -> original.apply(arg, u);
|
return u -> original.apply(arg, u);
|
||||||
}
|
}
|
||||||
@@ -894,7 +914,7 @@ public class JWO {
|
|||||||
return Stream.generate(valueSupplier).limit(1);
|
return Stream.generate(valueSupplier).limit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <T, U> Supplier<U> compose(Supplier<T> sup, Function<T, U> fun) {
|
public static <T, U> Supplier<U> compose(Supplier<T> sup, Function<? super T, ? extends U> fun) {
|
||||||
return () -> fun.apply(sup.get());
|
return () -> fun.apply(sup.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -72,6 +72,11 @@ public enum JavaVersion {
|
|||||||
*/
|
*/
|
||||||
VERSION_20,
|
VERSION_20,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Java 20 major version.
|
||||||
|
*/
|
||||||
|
VERSION_21,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Higher version of Java.
|
* Higher version of Java.
|
||||||
*/
|
*/
|
||||||
@@ -79,7 +84,10 @@ public enum JavaVersion {
|
|||||||
// Since Java 9, version should be X instead of 1.X
|
// Since Java 9, version should be X instead of 1.X
|
||||||
// However, to keep backward compatibility, we change from 11
|
// However, to keep backward compatibility, we change from 11
|
||||||
private static final int FIRST_MAJOR_VERSION_ORDINAL = 10;
|
private static final int FIRST_MAJOR_VERSION_ORDINAL = 10;
|
||||||
private static JavaVersion currentJavaVersion;
|
private static LazyValue<JavaVersion> currentJavaVersion = LazyValue.of(
|
||||||
|
() -> toVersion(System.getProperty("java.version")),
|
||||||
|
LazyValue.ThreadSafetyMode.SYNCHRONIZED
|
||||||
|
);
|
||||||
private final String versionName;
|
private final String versionName;
|
||||||
|
|
||||||
JavaVersion() {
|
JavaVersion() {
|
||||||
@@ -125,10 +133,7 @@ public enum JavaVersion {
|
|||||||
* @return The version of the current JVM.
|
* @return The version of the current JVM.
|
||||||
*/
|
*/
|
||||||
public static JavaVersion current() {
|
public static JavaVersion current() {
|
||||||
if (currentJavaVersion == null) {
|
return currentJavaVersion.get();
|
||||||
currentJavaVersion = toVersion(System.getProperty("java.version"));
|
|
||||||
}
|
|
||||||
return currentJavaVersion;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void resetCurrent() {
|
static void resetCurrent() {
|
||||||
@@ -146,90 +151,6 @@ public enum JavaVersion {
|
|||||||
return forClassVersion(classData[7] & 0xFF);
|
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
|
* Returns if this version is compatible with the given version
|
||||||
*
|
*
|
||||||
|
110
src/main/java/net/woggioni/jwo/LRUCache.java
Normal file
110
src/main/java/net/woggioni/jwo/LRUCache.java
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
package net.woggioni.jwo;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class LRUCache<K, V> implements Map<K, V> {
|
||||||
|
private final Map<K, V> delegate;
|
||||||
|
|
||||||
|
private LRUCache(final long maxSize, final Function<K, V> loader, Class<K> cls) {
|
||||||
|
delegate = new LinkedHashMap<K, V>() {
|
||||||
|
@Override
|
||||||
|
protected boolean removeEldestEntry(Map.Entry eldest) {
|
||||||
|
return size() >= maxSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V get(Object key) {
|
||||||
|
if(cls.isInstance(key)) {
|
||||||
|
return computeIfAbsent((K) key, loader);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V get(Object key) {
|
||||||
|
return delegate.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsKey(Object key) {
|
||||||
|
return delegate.containsKey(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return delegate.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return delegate.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V put(K key, V value) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsValue(Object value) {
|
||||||
|
return delegate.containsValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V remove(Object key) {
|
||||||
|
return delegate.remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void putAll(Map<? extends K, ? extends V> m) {
|
||||||
|
delegate.putAll(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
delegate.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<K> keySet() {
|
||||||
|
return delegate.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<V> values() {
|
||||||
|
return delegate.values();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Entry<K, V>> entrySet() {
|
||||||
|
return delegate.entrySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if(o instanceof LRUCache) {
|
||||||
|
return delegate.equals(o);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return delegate.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <K, V> LRUCache<K, V> of(final long maxSize, final Function<K, V> loader, Class<K> cls) {
|
||||||
|
return new LRUCache<>(maxSize, loader, cls);
|
||||||
|
}
|
||||||
|
}
|
14
src/main/java/net/woggioni/jwo/NullReader.java
Normal file
14
src/main/java/net/woggioni/jwo/NullReader.java
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package net.woggioni.jwo;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.Reader;
|
||||||
|
|
||||||
|
public class NullReader extends Reader {
|
||||||
|
@Override
|
||||||
|
public int read(char[] cbuf, int off, int len) throws IOException {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {}
|
||||||
|
}
|
18
src/main/java/net/woggioni/jwo/NullWriter.java
Normal file
18
src/main/java/net/woggioni/jwo/NullWriter.java
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package net.woggioni.jwo;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.Writer;
|
||||||
|
|
||||||
|
public class NullWriter extends Writer {
|
||||||
|
@Override
|
||||||
|
public void write(char[] cbuf, int off, int len) throws IOException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void flush() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
}
|
@@ -30,7 +30,7 @@ public class RetryExecutor {
|
|||||||
private final ExceptionHandler exceptionHandler = err -> ExceptionHandlerOutcome.CONTINUE;
|
private final ExceptionHandler exceptionHandler = err -> ExceptionHandlerOutcome.CONTINUE;
|
||||||
|
|
||||||
@Builder.Default
|
@Builder.Default
|
||||||
private final ExecutorService executorService = ForkJoinPool.commonPool();
|
private final Executor executor = ForkJoinPool.commonPool();
|
||||||
|
|
||||||
public enum ExceptionHandlerOutcome {
|
public enum ExceptionHandlerOutcome {
|
||||||
THROW, CONTINUE
|
THROW, CONTINUE
|
||||||
@@ -41,7 +41,7 @@ public class RetryExecutor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public CompletableFuture<Void> submit(Runnable cb) {
|
public CompletableFuture<Void> submit(Runnable cb) {
|
||||||
return submit(cb, maxAttempts, initialDelay, exp, exceptionHandler, executorService);
|
return submit(cb, maxAttempts, initialDelay, exp, exceptionHandler, executor);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompletableFuture<Void> submit(
|
public CompletableFuture<Void> submit(
|
||||||
@@ -79,36 +79,36 @@ public class RetryExecutor {
|
|||||||
Duration initialDelay,
|
Duration initialDelay,
|
||||||
Double exp,
|
Double exp,
|
||||||
ExceptionHandler exceptionHandler,
|
ExceptionHandler exceptionHandler,
|
||||||
ExecutorService executorService) {
|
Executor executor) {
|
||||||
return submit(() -> {
|
return submit(() -> {
|
||||||
cb.run();
|
cb.run();
|
||||||
return null;
|
return null;
|
||||||
}, maxAttempts, initialDelay, exp, exceptionHandler, executorService);
|
}, maxAttempts, initialDelay, exp, exceptionHandler, executor);
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T> CompletableFuture<T> submit(Callable<T> cb) {
|
public <T> CompletableFuture<T> submit(Callable<T> cb) {
|
||||||
return submit(cb, maxAttempts, initialDelay, exp, exceptionHandler, executorService);
|
return submit(cb, maxAttempts, initialDelay, exp, exceptionHandler, executor);
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T> CompletableFuture<T> submit(
|
public <T> CompletableFuture<T> submit(
|
||||||
Callable<T> cb,
|
Callable<T> cb,
|
||||||
ExecutorService executorService) {
|
Executor executor) {
|
||||||
return submit(cb, maxAttempts, initialDelay, exp, exceptionHandler, executorService);
|
return submit(cb, maxAttempts, initialDelay, exp, exceptionHandler, executor);
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T> CompletableFuture<T> submit(
|
public <T> CompletableFuture<T> submit(
|
||||||
Callable<T> cb,
|
Callable<T> cb,
|
||||||
ExceptionHandler exceptionHandler,
|
ExceptionHandler exceptionHandler,
|
||||||
ExecutorService executorService) {
|
Executor executor) {
|
||||||
return submit(cb, maxAttempts, initialDelay, exp, exceptionHandler, executorService);
|
return submit(cb, maxAttempts, initialDelay, exp, exceptionHandler, executor);
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T> CompletableFuture<T> submit(
|
public <T> CompletableFuture<T> submit(
|
||||||
Callable<T> cb,
|
Callable<T> cb,
|
||||||
Double exp,
|
Double exp,
|
||||||
ExceptionHandler exceptionHandler,
|
ExceptionHandler exceptionHandler,
|
||||||
ExecutorService executorService) {
|
ExecutorService executor) {
|
||||||
return submit(cb, maxAttempts, initialDelay, exp, exceptionHandler, executorService);
|
return submit(cb, maxAttempts, initialDelay, exp, exceptionHandler, executor);
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T> CompletableFuture<T> submit(
|
public <T> CompletableFuture<T> submit(
|
||||||
@@ -116,8 +116,8 @@ public class RetryExecutor {
|
|||||||
Duration initialDelay,
|
Duration initialDelay,
|
||||||
Double exp,
|
Double exp,
|
||||||
ExceptionHandler exceptionHandler,
|
ExceptionHandler exceptionHandler,
|
||||||
ExecutorService executorService) {
|
ExecutorService executor) {
|
||||||
return submit(cb, maxAttempts, initialDelay, exp, exceptionHandler, executorService);
|
return submit(cb, maxAttempts, initialDelay, exp, exceptionHandler, executor);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <T> CompletableFuture<T> submit(
|
public static <T> CompletableFuture<T> submit(
|
||||||
@@ -126,8 +126,8 @@ public class RetryExecutor {
|
|||||||
Duration initialDelay,
|
Duration initialDelay,
|
||||||
Double exp,
|
Double exp,
|
||||||
ExceptionHandler exceptionHandler,
|
ExceptionHandler exceptionHandler,
|
||||||
ExecutorService executorService) {
|
Executor executor) {
|
||||||
CompletableFuture<T> result = CompletableFuture.supplyAsync((Sup<T>) cb::call, executorService);
|
CompletableFuture<T> result = CompletableFuture.supplyAsync((Sup<T>) cb::call, executor);
|
||||||
double delay = initialDelay.toMillis();
|
double delay = initialDelay.toMillis();
|
||||||
for(int i = 1; i <= maxAttempts; i++) {
|
for(int i = 1; i <= maxAttempts; i++) {
|
||||||
int attempt = i;
|
int attempt = i;
|
||||||
@@ -148,7 +148,7 @@ public class RetryExecutor {
|
|||||||
Executor delayedExecutor = delayedExecutor(
|
Executor delayedExecutor = delayedExecutor(
|
||||||
(long) thisAttemptDelay,
|
(long) thisAttemptDelay,
|
||||||
TimeUnit.MILLISECONDS,
|
TimeUnit.MILLISECONDS,
|
||||||
executorService
|
executor
|
||||||
);
|
);
|
||||||
return CompletableFuture.supplyAsync((Sup<T>) cb::call, delayedExecutor);
|
return CompletableFuture.supplyAsync((Sup<T>) cb::call, delayedExecutor);
|
||||||
default:
|
default:
|
||||||
@@ -159,9 +159,9 @@ public class RetryExecutor {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, executorService).thenComposeAsync(Function.identity(), executorService);
|
}, executor).thenComposeAsync(Function.identity(), executor);
|
||||||
delay *= exp;
|
delay *= exp;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
123
src/main/java/net/woggioni/jwo/UnmodifiableDelegatingMap.java
Normal file
123
src/main/java/net/woggioni/jwo/UnmodifiableDelegatingMap.java
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
package net.woggioni.jwo;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static net.woggioni.jwo.JWO.newThrowable;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class UnmodifiableDelegatingMap<K, V> implements Map<K, V> {
|
||||||
|
protected final Supplier<Map<K, V>> mapFactory;
|
||||||
|
private final List<Map<K, V>> delegates;
|
||||||
|
|
||||||
|
public static <K, V> UnmodifiableDelegatingMap<K, V> of(
|
||||||
|
Supplier<Map<K, V>> mapFactory,
|
||||||
|
Map<K, V>... delegates
|
||||||
|
) {
|
||||||
|
return new UnmodifiableDelegatingMap<>(mapFactory, Arrays.asList(delegates));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmpty() {
|
||||||
|
for (Map<K, V> delegate : delegates) {
|
||||||
|
if (!delegate.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsKey(Object key) {
|
||||||
|
for (Map<K, V> delegate : delegates) {
|
||||||
|
if (delegate.containsKey(key)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsValue(Object value) {
|
||||||
|
for (Map<K, V> delegate : delegates) {
|
||||||
|
if (delegate.containsValue(value)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V get(Object key) {
|
||||||
|
V result = null;
|
||||||
|
for (Map<K, V> delegate : delegates) {
|
||||||
|
result = delegate.get(key);
|
||||||
|
if (result != null) break;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V put(K key, V value) {
|
||||||
|
throw newThrowable(UnsupportedOperationException.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V remove(Object key) {
|
||||||
|
throw newThrowable(UnsupportedOperationException.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void putAll(Map<? extends K, ? extends V> m) {
|
||||||
|
throw newThrowable(UnsupportedOperationException.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
throw newThrowable(UnsupportedOperationException.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<K> keySet() {
|
||||||
|
return flatten().keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<K, V> flatten() {
|
||||||
|
return stream().collect(
|
||||||
|
CollectionUtils.toUnmodifiableMap(
|
||||||
|
mapFactory,
|
||||||
|
Map.Entry::getKey,
|
||||||
|
Map.Entry::getValue,
|
||||||
|
CollectionUtils::newValueMerger
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Stream<Entry<K, V>> stream() {
|
||||||
|
return JWO.iterator2Stream(CollectionUtils.reverseIterator(delegates)).flatMap(
|
||||||
|
it -> it.entrySet().stream()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<V> values() {
|
||||||
|
return flatten().values();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Entry<K, V>> entrySet() {
|
||||||
|
return flatten().entrySet();
|
||||||
|
}
|
||||||
|
}
|
@@ -1,23 +1,12 @@
|
|||||||
package net.woggioni.jwo.internal;
|
package net.woggioni.jwo.internal;
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.Data;
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
import lombok.Setter;
|
|
||||||
import net.woggioni.jwo.MutableTuple2;
|
import net.woggioni.jwo.MutableTuple2;
|
||||||
|
|
||||||
@EqualsAndHashCode
|
@Data
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class MutableTuple2Impl<T, U> implements MutableTuple2<T, U> {
|
public class MutableTuple2Impl<T, U> implements MutableTuple2<T, U> {
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private T _1;
|
private T _1;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private U _2;
|
private U _2;
|
||||||
}
|
}
|
@@ -1,25 +1,15 @@
|
|||||||
package net.woggioni.jwo.internal;
|
package net.woggioni.jwo.internal;
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.Data;
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
import lombok.Setter;
|
|
||||||
import net.woggioni.jwo.MutableTuple3;
|
import net.woggioni.jwo.MutableTuple3;
|
||||||
|
|
||||||
@EqualsAndHashCode
|
@Data
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class MutableTuple3Impl<T, U, V> implements MutableTuple3<T, U, V> {
|
public class MutableTuple3Impl<T, U, V> implements MutableTuple3<T, U, V> {
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private T _1;
|
private T _1;
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private U _2;
|
private U _2;
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private V _3;
|
private V _3;
|
||||||
}
|
}
|
@@ -1,17 +1,12 @@
|
|||||||
package net.woggioni.jwo.internal;
|
package net.woggioni.jwo.internal;
|
||||||
|
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.Data;
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import net.woggioni.jwo.Tuple2;
|
import net.woggioni.jwo.Tuple2;
|
||||||
|
|
||||||
@EqualsAndHashCode
|
@Data
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class Tuple2Impl<T, U> implements Tuple2<T, U> {
|
public class Tuple2Impl<T, U> implements Tuple2<T, U> {
|
||||||
@Getter
|
|
||||||
private final T _1;
|
private final T _1;
|
||||||
|
|
||||||
@Getter
|
|
||||||
private final U _2;
|
private final U _2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
21
src/test/java/net/woggioni/jwo/ApplicationTest.java
Normal file
21
src/test/java/net/woggioni/jwo/ApplicationTest.java
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package net.woggioni.jwo;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
public class ApplicationTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void builderTest() {
|
||||||
|
final var app = Application.builder("app")
|
||||||
|
.configurationDirectoryPropertyKey("app.conf.dir")
|
||||||
|
.dataDirectoryPropertyKey("app.data.dir")
|
||||||
|
.cacheDirectoryPropertyKey("app.cache.dir")
|
||||||
|
.cacheDirectoryEnvVar("APP_CACHE_DIR")
|
||||||
|
.dataDirectoryEnvVar("APP_DATA_DIR")
|
||||||
|
.configurationDirectoryEnvVar("APP_CONF_DIR")
|
||||||
|
.build();
|
||||||
|
final var confDir = app.computeConfigurationDirectory();
|
||||||
|
final var cacheDir = app.computeCacheDirectory();
|
||||||
|
final var dataDir = app.computeDataDirectory();
|
||||||
|
}
|
||||||
|
}
|
@@ -3,33 +3,65 @@ package net.woggioni.jwo;
|
|||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import org.junit.jupiter.api.Assertions;
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Nested;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.condition.EnabledOnOs;
|
import org.junit.jupiter.api.condition.EnabledOnOs;
|
||||||
import org.junit.jupiter.api.condition.OS;
|
import org.junit.jupiter.api.condition.OS;
|
||||||
import org.junit.jupiter.api.io.TempDir;
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
import org.junit.jupiter.params.ParameterizedTest;
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
import org.junit.jupiter.params.provider.EnumSource;
|
import org.junit.jupiter.params.provider.EnumSource;
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
|
import java.io.Writer;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.FileVisitResult;
|
||||||
|
import java.nio.file.FileVisitor;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.attribute.BasicFileAttributes;
|
||||||
|
import java.nio.file.attribute.FileAttribute;
|
||||||
|
import java.nio.file.attribute.FileTime;
|
||||||
import java.nio.file.attribute.PosixFileAttributes;
|
import java.nio.file.attribute.PosixFileAttributes;
|
||||||
import java.nio.file.attribute.UserPrincipal;
|
import java.nio.file.attribute.UserPrincipal;
|
||||||
|
import java.security.DigestInputStream;
|
||||||
|
import java.security.DigestOutputStream;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Supplier;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipFile;
|
||||||
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
import static net.woggioni.jwo.CollectionUtils.immutableList;
|
import static net.woggioni.jwo.CollectionUtils.immutableList;
|
||||||
import static net.woggioni.jwo.CollectionUtils.newArrayList;
|
import static net.woggioni.jwo.CollectionUtils.newArrayList;
|
||||||
|
import static net.woggioni.jwo.Misc.CRACKLIB_RESOURCE;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
public class JWOTest {
|
public class JWOTest {
|
||||||
|
|
||||||
@@ -40,38 +72,57 @@ public class JWOTest {
|
|||||||
if (n > 3) return Optional.of(n);
|
if (n > 3) return Optional.of(n);
|
||||||
else return Optional.empty();
|
else return Optional.empty();
|
||||||
}).collect(Collectors.toList());
|
}).collect(Collectors.toList());
|
||||||
Assertions.assertEquals(Collections.singletonList(4), l);
|
assertEquals(Collections.singletonList(4), l);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void optional2StreamTest() {
|
public void optional2StreamTest() {
|
||||||
Integer integer = 3;
|
Integer integer = 3;
|
||||||
Optional<Integer> s = Optional.of(integer);
|
Optional<Integer> s = Optional.of(integer);
|
||||||
JWO.optional2Stream(s).forEach(n -> Assertions.assertEquals(integer, n));
|
JWO.optional2Stream(s).forEach(n -> assertEquals(integer, n));
|
||||||
s = Optional.empty();
|
s = Optional.empty();
|
||||||
JWO.optional2Stream(s).forEach(n -> Assertions.fail(
|
JWO.optional2Stream(s).forEach(n -> Assertions.fail(
|
||||||
"Stream should have been empty and this piece of code never executed")
|
"Stream should have been empty and this piece of code never executed")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void optional2StreamTest2() {
|
||||||
|
Integer integer = 3;
|
||||||
|
Optional<Integer> o1 = Optional.of(integer);
|
||||||
|
Integer integer2 = 3;
|
||||||
|
Optional<Integer> o2 = Optional.of(integer2);
|
||||||
|
final var values = JWO.optional2Stream(o1, Optional.empty(), o2)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
assertEquals(Arrays.asList(integer, integer2), values);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void optionalOrTest() {
|
||||||
|
Integer integer = 3;
|
||||||
|
Optional<Integer> o1 = Optional.of(integer);
|
||||||
|
Optional<Integer> o2 = Optional.of(integer);
|
||||||
|
assertEquals(o2, JWO.or(Optional.empty(), o2));
|
||||||
|
assertEquals(o1, JWO.or(o1, Optional.empty()));
|
||||||
|
assertEquals(Optional.empty(), JWO.or(Optional.empty(), Optional.empty()));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
enum IndexOfWithEscapeTestCase {
|
enum IndexOfWithEscapeTestCase {
|
||||||
SIMPLE(" dsds $sdsa \\$dfivbdsf \\\\$sdgsga", '$', '\\',
|
SIMPLE(" dsds $sdsa \\$dfivbdsf \\\\$sdgsga", '$', '\\',
|
||||||
immutableList(6, 25)),
|
immutableList(6, 25)),
|
||||||
SIMPLE2("asdasd$$vdfv$", '$', '$',
|
SIMPLE2("asdasd$$vdfv$", '$', '$',
|
||||||
immutableList(12)),
|
immutableList(12)),
|
||||||
NO_NEEDLE("asdasd$$vdfv$", '#', '\\',
|
NO_NEEDLE("asdasd$$vdfv$", '#', '\\',
|
||||||
immutableList()),
|
immutableList()),
|
||||||
ESCAPED_NEEDLE("asdasd$$vdfv$#sdfs", '#', '$',
|
ESCAPED_NEEDLE("asdasd$$vdfv$#sdfs", '#', '$',
|
||||||
immutableList()),
|
immutableList()),
|
||||||
NOT_ESCAPED_NEEDLE("asdasd$$#vdfv$#sdfs", '#', '$',
|
NOT_ESCAPED_NEEDLE("asdasd$$#vdfv$#sdfs", '#', '$',
|
||||||
immutableList(8)),
|
immutableList(8)),
|
||||||
|
|
||||||
SDFSD("\n${sys:user.home}${env:HOME}", ':', '\\',
|
SDFSD("\n${sys:user.home}${env:HOME}", ':', '\\',
|
||||||
immutableList(6, 22))
|
immutableList(6, 22));
|
||||||
|
|
||||||
;
|
|
||||||
final String haystack;
|
final String haystack;
|
||||||
final Character needle;
|
final Character needle;
|
||||||
|
|
||||||
@@ -86,13 +137,13 @@ public class JWOTest {
|
|||||||
String haystack = testCase.haystack;
|
String haystack = testCase.haystack;
|
||||||
List<Integer> solution = newArrayList();
|
List<Integer> solution = newArrayList();
|
||||||
int i = 0;
|
int i = 0;
|
||||||
while(true) {
|
while (true) {
|
||||||
i = JWO.indexOfWithEscape(haystack, testCase.needle, testCase.escape, i, haystack.length());
|
i = JWO.indexOfWithEscape(haystack, testCase.needle, testCase.escape, i, haystack.length());
|
||||||
if(i < 0) break;
|
if (i < 0) break;
|
||||||
solution.add(i);
|
solution.add(i);
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
Assertions.assertEquals(testCase.solution, solution);
|
assertEquals(testCase.solution, solution);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -103,35 +154,35 @@ public class JWOTest {
|
|||||||
valuesMap.put("date", "2020-03-25 16:22");
|
valuesMap.put("date", "2020-03-25 16:22");
|
||||||
valuesMap.put("adjective", "simple");
|
valuesMap.put("adjective", "simple");
|
||||||
String expected = """
|
String expected = """
|
||||||
This is a simple test made by John Doe on 2020-03-25 16:22. It's really simple!
|
This is a simple test made by John Doe on 2020-03-25 16:22. It's really simple!
|
||||||
/home/user
|
/home/user
|
||||||
/home/user
|
/home/user
|
||||||
defaultValue
|
defaultValue
|
||||||
""";
|
""";
|
||||||
Map<String, Map<String, Object>> contextMap = new MapBuilder<String, Map<String, Object>>()
|
Map<String, Map<String, Object>> contextMap = new MapBuilder<String, Map<String, Object>>()
|
||||||
.entry("env",
|
.entry("env",
|
||||||
new MapBuilder<String, String>()
|
new MapBuilder<String, String>()
|
||||||
.entry("HOME", "/home/user")
|
.entry("HOME", "/home/user")
|
||||||
.build(TreeMap::new, Collections::unmodifiableMap)
|
.build(TreeMap::new, Collections::unmodifiableMap)
|
||||||
)
|
)
|
||||||
.entry("sys",
|
.entry("sys",
|
||||||
new MapBuilder<String, String>()
|
new MapBuilder<String, String>()
|
||||||
.entry("user.home", "/home/user")
|
.entry("user.home", "/home/user")
|
||||||
.build(TreeMap::new, Collections::unmodifiableMap) ).build(TreeMap::new, Collections::unmodifiableMap);
|
.build(TreeMap::new, Collections::unmodifiableMap)).build(TreeMap::new, Collections::unmodifiableMap);
|
||||||
try (Reader reader = new InputStreamReader(
|
try (Reader reader = new InputStreamReader(
|
||||||
JWOTest.class.getResourceAsStream("/render_template_test.txt"))) {
|
JWOTest.class.getResourceAsStream("/render_template_test.txt"))) {
|
||||||
String rendered = JWO.renderTemplate(reader, valuesMap, contextMap);
|
String rendered = JWO.renderTemplate(reader, valuesMap, contextMap);
|
||||||
Assertions.assertEquals(expected, rendered);
|
assertEquals(expected, rendered);
|
||||||
}
|
}
|
||||||
try (Reader reader = new InputStreamReader(
|
try (Reader reader = new InputStreamReader(
|
||||||
JWOTest.class.getResourceAsStream("/render_template_test.txt"))) {
|
JWOTest.class.getResourceAsStream("/render_template_test.txt"))) {
|
||||||
String rendered = JWO.renderTemplate(JWO.readAll(reader), valuesMap, contextMap);
|
String rendered = JWO.renderTemplate(JWO.readAll(reader), valuesMap, contextMap);
|
||||||
Assertions.assertEquals(expected, rendered);
|
assertEquals(expected, rendered);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static String renderTemplateNaive(String template, Map<String, Object> valuesMap){
|
public static String renderTemplateNaive(String template, Map<String, Object> valuesMap) {
|
||||||
StringBuilder formatter = new StringBuilder(template);
|
StringBuilder formatter = new StringBuilder(template);
|
||||||
Object absent = new Object();
|
Object absent = new Object();
|
||||||
|
|
||||||
@@ -149,7 +200,7 @@ public class JWOTest {
|
|||||||
// If the key is not present, leave the variable untouched.
|
// If the key is not present, leave the variable untouched.
|
||||||
if (index != -1) {
|
if (index != -1) {
|
||||||
Object value = valuesMap.getOrDefault(key, absent);
|
Object value = valuesMap.getOrDefault(key, absent);
|
||||||
if(value != absent) {
|
if (value != absent) {
|
||||||
String valueStr = value != null ? value.toString() : "";
|
String valueStr = value != null ? value.toString() : "";
|
||||||
formatter.replace(index, index + formatKey.length(), valueStr);
|
formatter.replace(index, index + formatKey.length(), valueStr);
|
||||||
}
|
}
|
||||||
@@ -169,6 +220,451 @@ public class JWOTest {
|
|||||||
m.setAccessible(true);
|
m.setAccessible(true);
|
||||||
int expectedUserId = (Integer) m.invoke(expectedUser);
|
int expectedUserId = (Integer) m.invoke(expectedUser);
|
||||||
int uid = (int) JWO.uid();
|
int uid = (int) JWO.uid();
|
||||||
Assertions.assertEquals(expectedUserId, uid);
|
assertEquals(expectedUserId, uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class ReplaceFileIfDifferentTest {
|
||||||
|
|
||||||
|
private interface CommonInterface {
|
||||||
|
void replaceFileIfDifferent(
|
||||||
|
Supplier<InputStream> inputStreamSupplier,
|
||||||
|
Path destination,
|
||||||
|
FileAttribute<?>... attrs
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public enum MethodToTest {
|
||||||
|
STREAM_METHOD((Supplier<InputStream> inputStreamSupplier,
|
||||||
|
Path destination,
|
||||||
|
FileAttribute<?>... attrs) -> {
|
||||||
|
JWO.replaceFileIfDifferent(inputStreamSupplier.get(), destination, attrs);
|
||||||
|
}),
|
||||||
|
SUPPLIER_METHOD(JWO::replaceFileIfDifferent);
|
||||||
|
|
||||||
|
private final CommonInterface xface;
|
||||||
|
|
||||||
|
public void replaceFileIfDifferent(
|
||||||
|
Supplier<InputStream> inputStreamSupplier,
|
||||||
|
Path destination,
|
||||||
|
FileAttribute<?>... attrs
|
||||||
|
) {
|
||||||
|
xface.replaceFileIfDifferent(inputStreamSupplier, destination, attrs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Supplier<InputStream> source = (Sup<InputStream>) () -> Optional.ofNullable(
|
||||||
|
ReplaceFileIfDifferentTest.class.getResourceAsStream(CRACKLIB_RESOURCE)
|
||||||
|
).orElseThrow(Assertions::fail);
|
||||||
|
|
||||||
|
@TempDir
|
||||||
|
private Path testDir;
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
@ParameterizedTest
|
||||||
|
@EnumSource(MethodToTest.class)
|
||||||
|
public void ensureFileCopy(MethodToTest methodToTest) {
|
||||||
|
final var dest = testDir.resolve("cracklib-small");
|
||||||
|
methodToTest.replaceFileIfDifferent(source, dest);
|
||||||
|
Hash newFileHash, newContentHash;
|
||||||
|
try (final var inputStream = source.get()) {
|
||||||
|
newContentHash = Hash.md5(inputStream);
|
||||||
|
}
|
||||||
|
try (final var inputStream = Files.newInputStream(dest)) {
|
||||||
|
newFileHash = Hash.md5(inputStream);
|
||||||
|
}
|
||||||
|
assertEquals(newContentHash, newFileHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
@ParameterizedTest
|
||||||
|
@EnumSource(MethodToTest.class)
|
||||||
|
public void ensureNoWriteWithNoChange(MethodToTest methodToTest) {
|
||||||
|
final var dest = testDir.resolve("cracklib-small");
|
||||||
|
try (final var inputStream = source.get()) {
|
||||||
|
Files.copy(inputStream, dest);
|
||||||
|
}
|
||||||
|
final var initialTime = Files.getLastModifiedTime(dest);
|
||||||
|
methodToTest.replaceFileIfDifferent(source, dest);
|
||||||
|
final var replacedTime = Files.getLastModifiedTime(dest);
|
||||||
|
assertEquals(initialTime, replacedTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
@ParameterizedTest
|
||||||
|
@EnumSource(MethodToTest.class)
|
||||||
|
public void ensureWriteWithContentChange(MethodToTest methodToTest) {
|
||||||
|
final var dest = testDir.resolve("cracklib-small");
|
||||||
|
try (final var inputStream = source.get()) {
|
||||||
|
Files.copy(inputStream, dest);
|
||||||
|
}
|
||||||
|
final var newContent = (Sup<InputStream>) () -> new ByteArrayInputStream(
|
||||||
|
"new File content".getBytes(StandardCharsets.UTF_8)
|
||||||
|
);
|
||||||
|
final var initialTime = Files.getLastModifiedTime(dest);
|
||||||
|
methodToTest.replaceFileIfDifferent(newContent, dest);
|
||||||
|
final var replacedTime = Files.getLastModifiedTime(dest);
|
||||||
|
assertTrue(
|
||||||
|
Comparator.<FileTime>naturalOrder()
|
||||||
|
.compare(initialTime, replacedTime) <= 0);
|
||||||
|
Hash newFileHash, newContentHash;
|
||||||
|
try (final var inputStream = newContent.get()) {
|
||||||
|
newContentHash = Hash.md5(inputStream);
|
||||||
|
}
|
||||||
|
try (final var inputStream = Files.newInputStream(dest)) {
|
||||||
|
newFileHash = Hash.md5(inputStream);
|
||||||
|
}
|
||||||
|
assertEquals(newContentHash, newFileHash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class CapitalizeTest {
|
||||||
|
private static Stream<TestCase<String, String>> testCases() {
|
||||||
|
return Stream.of(
|
||||||
|
new TestCase<>("Home", "Home", null),
|
||||||
|
new TestCase<>("HOME", "HOME", null),
|
||||||
|
new TestCase<>("leilei", "Leilei", null),
|
||||||
|
new TestCase<>("4365", "4365", null),
|
||||||
|
new TestCase<>("芦 雷雷", "芦 雷雷", null),
|
||||||
|
new TestCase<>("dž123", "Dž123", null),
|
||||||
|
new TestCase<>(null, null, NullPointerException.class)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("testCases")
|
||||||
|
public void capitalizeTest(TestCase<String, String> testCase) {
|
||||||
|
if (testCase.error() == null) {
|
||||||
|
assertEquals(testCase.expectedOutput(), JWO.capitalize(testCase.input()));
|
||||||
|
} else {
|
||||||
|
assertThrows(
|
||||||
|
testCase.error(),
|
||||||
|
JWO.curry(
|
||||||
|
(Consumer<String>) JWO::capitalize,
|
||||||
|
(Supplier<String>) testCase::input
|
||||||
|
)::run
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class DecapitalizeTest {
|
||||||
|
private static Stream<TestCase<String, String>> testCases() {
|
||||||
|
return Stream.of(
|
||||||
|
new TestCase<>("Home", "home", null),
|
||||||
|
new TestCase<>("HOME", "hOME", null),
|
||||||
|
new TestCase<>("Leilei", "leilei", null),
|
||||||
|
new TestCase<>("4365", "4365", null),
|
||||||
|
new TestCase<>("芦 雷雷", "芦 雷雷", null),
|
||||||
|
new TestCase<>("Dž123", "dž123", null),
|
||||||
|
new TestCase<>(null, null, NullPointerException.class)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("testCases")
|
||||||
|
public void decapitalizeTest(TestCase<String, String> testCase) {
|
||||||
|
if (testCase.error() == null) {
|
||||||
|
assertEquals(testCase.expectedOutput(), JWO.decapitalize(testCase.input()));
|
||||||
|
} else {
|
||||||
|
assertThrows(
|
||||||
|
testCase.error(),
|
||||||
|
JWO.curry(
|
||||||
|
(Consumer<String>) JWO::decapitalize,
|
||||||
|
(Supplier<String>) testCase::input
|
||||||
|
)::run
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class InstallResourceTest {
|
||||||
|
@TempDir
|
||||||
|
private Path testDir;
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
@Test
|
||||||
|
public void ensureFileCopy() {
|
||||||
|
final var dest = testDir.resolve("some/nested/path/cracklib-small");
|
||||||
|
JWO.installResource(CRACKLIB_RESOURCE, dest, getClass());
|
||||||
|
Hash newFileHash, newContentHash;
|
||||||
|
try (final var inputStream = getClass().getResourceAsStream(CRACKLIB_RESOURCE)) {
|
||||||
|
newContentHash = Hash.md5(inputStream);
|
||||||
|
}
|
||||||
|
try (final var inputStream = Files.newInputStream(dest)) {
|
||||||
|
newFileHash = Hash.md5(inputStream);
|
||||||
|
}
|
||||||
|
assertEquals(newContentHash, newFileHash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
public class ArrayIteratorTest {
|
||||||
|
public static Stream<TestCase<Integer[], Void>> test() {
|
||||||
|
return Stream.of(
|
||||||
|
new TestCase<>(new Integer[]{}, null, null),
|
||||||
|
new TestCase<>(new Integer[]{1, 2, 3, 4, 5, null, 6, 7, 8, 9, 10}, null, null)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@MethodSource
|
||||||
|
@ParameterizedTest
|
||||||
|
public void test(TestCase<Integer[], Void> testCase) {
|
||||||
|
final var it = JWO.iterator(testCase.input());
|
||||||
|
|
||||||
|
for (Integer n : testCase.input()) {
|
||||||
|
final var m = it.next();
|
||||||
|
assertEquals(n, m);
|
||||||
|
}
|
||||||
|
assertFalse(it.hasNext());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SneakyThrows
|
||||||
|
public void copyTest() {
|
||||||
|
MessageDigest md1 = Hash.Algorithm.MD5.newMessageDigest();
|
||||||
|
final Supplier<Reader> source = JWO.compose(
|
||||||
|
JWO.compose(
|
||||||
|
() -> getClass().getResourceAsStream(CRACKLIB_RESOURCE),
|
||||||
|
(InputStream is) -> new DigestInputStream(is, md1)
|
||||||
|
),
|
||||||
|
(InputStream is) -> new InputStreamReader(is)
|
||||||
|
);
|
||||||
|
MessageDigest md2 = Hash.Algorithm.MD5.newMessageDigest();
|
||||||
|
final Supplier<Writer> destination = JWO.compose(
|
||||||
|
JWO.compose(
|
||||||
|
NullOutputStream::new,
|
||||||
|
(OutputStream os) -> new DigestOutputStream(os, md2)
|
||||||
|
),
|
||||||
|
(OutputStream is) -> new OutputStreamWriter(is)
|
||||||
|
);
|
||||||
|
|
||||||
|
try (final var reader = source.get()) {
|
||||||
|
try (Writer writer = destination.get()) {
|
||||||
|
JWO.copy(reader, writer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertArrayEquals(md1.digest(), md2.digest());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SneakyThrows
|
||||||
|
public void extractZipTest(@TempDir Path testDir) {
|
||||||
|
final var testBundle = Optional.ofNullable(System.getProperty("zip.test.bundle"))
|
||||||
|
.map(Path::of)
|
||||||
|
.orElseGet(Assertions::fail);
|
||||||
|
final var destination1 = testDir.resolve("bundle");
|
||||||
|
final var destination2 = testDir.resolve("bundle2");
|
||||||
|
final var reassembledBundle = testDir.resolve("bundle3.zip");
|
||||||
|
final var destination3 = testDir.resolve("bundle3");
|
||||||
|
try (final var zos = new ZipOutputStream(Files.newOutputStream(reassembledBundle))) {
|
||||||
|
JWO.expandZip(testBundle, (zis, zipEntry) -> {
|
||||||
|
final var baos = new ByteArrayOutputStream();
|
||||||
|
JWO.copy(zis, baos);
|
||||||
|
if (zipEntry.isDirectory()) {
|
||||||
|
final var dir = destination1.resolve(zipEntry.getName());
|
||||||
|
Files.createDirectories(dir);
|
||||||
|
JWO.writeZipEntry(zos,
|
||||||
|
() -> new ByteArrayInputStream(baos.toByteArray()),
|
||||||
|
zipEntry.getName(),
|
||||||
|
ZipEntry.STORED
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
final var file = destination1.resolve(zipEntry.getName());
|
||||||
|
Files.createDirectories(file.getParent());
|
||||||
|
try (final var os = Files.newOutputStream(file)) {
|
||||||
|
JWO.copy(new ByteArrayInputStream(baos.toByteArray()), os);
|
||||||
|
}
|
||||||
|
JWO.writeZipEntry(zos,
|
||||||
|
() -> new ByteArrayInputStream(baos.toByteArray()),
|
||||||
|
zipEntry.getName(),
|
||||||
|
ZipEntry.DEFLATED
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
JWO.extractZip(testBundle, destination2);
|
||||||
|
JWO.extractZip(reassembledBundle, destination3);
|
||||||
|
final var visitor = new FileVisitor<Path>() {
|
||||||
|
@Override
|
||||||
|
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
|
||||||
|
return FileVisitResult.CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
|
||||||
|
final var relativePath = destination1.relativize(file);
|
||||||
|
final var hashes = Stream.of(
|
||||||
|
destination1.resolve(relativePath),
|
||||||
|
destination2.resolve(relativePath),
|
||||||
|
destination3.resolve(relativePath)
|
||||||
|
).map((Fun<Path, Hash>) p -> {
|
||||||
|
Hash hash;
|
||||||
|
try (final var is = Files.newInputStream(p)) {
|
||||||
|
hash = Hash.md5(is);
|
||||||
|
}
|
||||||
|
return hash;
|
||||||
|
}).collect(Collectors.toSet());
|
||||||
|
assertTrue(hashes.size() == 1);
|
||||||
|
return FileVisitResult.CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FileVisitResult visitFileFailed(Path file, IOException exc) {
|
||||||
|
return FileVisitResult.CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FileVisitResult postVisitDirectory(Path dir, IOException exc) {
|
||||||
|
return FileVisitResult.CONTINUE;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Files.walkFileTree(destination1, visitor);
|
||||||
|
Stream.of(destination1, destination2, destination3)
|
||||||
|
.forEach(p -> {
|
||||||
|
JWO.deletePath(p);
|
||||||
|
assertFalse(Files.exists(p));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SneakyThrows
|
||||||
|
public void readResource2StringTest() {
|
||||||
|
final var hash1 = Optional.of(JWO.readResource2String(CRACKLIB_RESOURCE))
|
||||||
|
.map(it -> it.getBytes(StandardCharsets.UTF_8))
|
||||||
|
.map(ByteArrayInputStream::new)
|
||||||
|
.map(Hash::md5)
|
||||||
|
.orElseGet(Assertions::fail);
|
||||||
|
|
||||||
|
Hash hash2;
|
||||||
|
try (final var is = getClass().getResourceAsStream(CRACKLIB_RESOURCE)) {
|
||||||
|
hash2 = Hash.md5(is);
|
||||||
|
}
|
||||||
|
assertEquals(hash1, hash2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class NewThrowableTest {
|
||||||
|
private static class SomeWeirdException extends RuntimeException {
|
||||||
|
public SomeWeirdException() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SomeWeirdException(String msg) {
|
||||||
|
super(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SomeWeirdException(String msg, Throwable cause) {
|
||||||
|
super(msg, cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void newThrowableTest1() {
|
||||||
|
final var ex = JWO.newThrowable(NewThrowableTest.SomeWeirdException.class);
|
||||||
|
assertThrows(NewThrowableTest.SomeWeirdException.class, () -> {
|
||||||
|
throw ex;
|
||||||
|
});
|
||||||
|
assertThrowsExactly(NewThrowableTest.SomeWeirdException.class, () -> {
|
||||||
|
JWO.raise(NewThrowableTest.SomeWeirdException.class);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void newThrowableTest2() {
|
||||||
|
final var ex = JWO.newThrowable(NewThrowableTest.SomeWeirdException.class, "some message without placeholder");
|
||||||
|
assertThrowsExactly(NewThrowableTest.SomeWeirdException.class, () -> {
|
||||||
|
throw ex;
|
||||||
|
});
|
||||||
|
assertThrowsExactly(NewThrowableTest.SomeWeirdException.class, () -> {
|
||||||
|
JWO.raise(NewThrowableTest.SomeWeirdException.class,
|
||||||
|
"some message without placeholder"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void newThrowableTest3() {
|
||||||
|
final var ex = JWO.newThrowable(NewThrowableTest.SomeWeirdException.class,
|
||||||
|
"some message with placeholder %d",
|
||||||
|
25
|
||||||
|
);
|
||||||
|
assertThrowsExactly(NewThrowableTest.SomeWeirdException.class, () -> {
|
||||||
|
throw ex;
|
||||||
|
});
|
||||||
|
assertThrowsExactly(NewThrowableTest.SomeWeirdException.class, () -> {
|
||||||
|
JWO.raise(NewThrowableTest.SomeWeirdException.class,
|
||||||
|
"some message with placeholder %d",
|
||||||
|
25
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void newThrowableTest4() {
|
||||||
|
final var cause = new RuntimeException();
|
||||||
|
final var ex = JWO.newThrowable(NewThrowableTest.SomeWeirdException.class,
|
||||||
|
cause,
|
||||||
|
"some message with placeholder %d",
|
||||||
|
25
|
||||||
|
);
|
||||||
|
assertThrowsExactly(NewThrowableTest.SomeWeirdException.class, () -> {
|
||||||
|
try {
|
||||||
|
throw ex;
|
||||||
|
} catch (Throwable t) {
|
||||||
|
assertTrue(t.getCause() == cause);
|
||||||
|
throw t;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
assertThrowsExactly(NewThrowableTest.SomeWeirdException.class, () -> {
|
||||||
|
try {
|
||||||
|
JWO.raise(NewThrowableTest.SomeWeirdException.class,
|
||||||
|
cause,
|
||||||
|
"some message with placeholder %d",
|
||||||
|
25
|
||||||
|
);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
assertTrue(t.getCause() == cause);
|
||||||
|
throw t;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void enumerationTest() {
|
||||||
|
final var list = immutableList(1, 2, 3, 4, 5, 6, 7);
|
||||||
|
final var enumeration = Collections.enumeration(list);
|
||||||
|
final var list2 = JWO.iterator2Stream(
|
||||||
|
JWO.enumeration2Iterator(enumeration)
|
||||||
|
).collect(Collectors.toList());
|
||||||
|
assertEquals(list, list2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SneakyThrows
|
||||||
|
public void readFile(@TempDir Path testDir) {
|
||||||
|
final var destination = testDir.resolve("cracklib-small");
|
||||||
|
final var dis = Hash.Algorithm.MD5.newInputStream(getClass().getResourceAsStream(CRACKLIB_RESOURCE));
|
||||||
|
final var md = dis.getMessageDigest();
|
||||||
|
try {
|
||||||
|
try(final var os = Files.newOutputStream(destination)) {
|
||||||
|
JWO.copy(dis, os);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
dis.close();
|
||||||
|
}
|
||||||
|
final var originalHash = md.digest();
|
||||||
|
final var content = JWO.readFile2String(destination.toFile());
|
||||||
|
final var readHash = Hash.md5(new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8)));
|
||||||
|
assertArrayEquals(originalHash, readHash.getBytes());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
88
src/test/java/net/woggioni/jwo/JavaVersionTest.java
Normal file
88
src/test/java/net/woggioni/jwo/JavaVersionTest.java
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
package net.woggioni.jwo;
|
||||||
|
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
public class JavaVersionTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
final var current = JavaVersion.current();
|
||||||
|
assertTrue(current.isCompatibleWith(current));
|
||||||
|
assertTrue(current.isCompatibleWith(JavaVersion.VERSION_1_8));
|
||||||
|
assertFalse(JavaVersion.VERSION_1_4.isCompatibleWith(current));
|
||||||
|
assertFalse(JavaVersion.VERSION_1_4.isCompatibleWith(JavaVersion.VERSION_1_7));
|
||||||
|
assertTrue(JavaVersion.VERSION_17.isCompatibleWith(JavaVersion.VERSION_1_8));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Stream<TestCase<Tuple2<JavaVersion, JavaVersion>, Integer>> comparatorTest() {
|
||||||
|
return Stream.of(
|
||||||
|
new TestCase<>(
|
||||||
|
Tuple2.newInstance(JavaVersion.VERSION_1_4, JavaVersion.VERSION_1_4),
|
||||||
|
0,
|
||||||
|
null
|
||||||
|
),
|
||||||
|
new TestCase<>(
|
||||||
|
Tuple2.newInstance(JavaVersion.VERSION_1_1, JavaVersion.VERSION_1_4),
|
||||||
|
-1,
|
||||||
|
null
|
||||||
|
),
|
||||||
|
new TestCase<>(
|
||||||
|
Tuple2.newInstance(JavaVersion.VERSION_21, JavaVersion.VERSION_1_8),
|
||||||
|
1,
|
||||||
|
null
|
||||||
|
),
|
||||||
|
new TestCase<>(
|
||||||
|
Tuple2.newInstance(JavaVersion.VERSION_17, JavaVersion.VERSION_1_5),
|
||||||
|
1,
|
||||||
|
null
|
||||||
|
),
|
||||||
|
new TestCase<>(
|
||||||
|
Tuple2.newInstance(JavaVersion.VERSION_17, JavaVersion.VERSION_19),
|
||||||
|
-1,
|
||||||
|
null
|
||||||
|
),
|
||||||
|
new TestCase<>(
|
||||||
|
Tuple2.newInstance(JavaVersion.VERSION_21, JavaVersion.VERSION_20),
|
||||||
|
1,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@MethodSource
|
||||||
|
@ParameterizedTest
|
||||||
|
public void comparatorTest(TestCase<Tuple2<JavaVersion, JavaVersion>, Integer> testCase) {
|
||||||
|
final var comparator = Comparator.<JavaVersion>naturalOrder();
|
||||||
|
final var pair = testCase.input();
|
||||||
|
final var comparisonResult = Optional.of(comparator.compare(pair.get_1(), pair.get_2()))
|
||||||
|
.map(v -> v / (v == 0 ? 1 : Math.abs(v)))
|
||||||
|
.orElseGet(Assertions::fail);
|
||||||
|
assertEquals(testCase.expectedOutput(), comparisonResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SneakyThrows
|
||||||
|
public void parseClassTest() {
|
||||||
|
try(final var is = getClass().getResourceAsStream("/net/woggioni/jwo/JWO.class")) {
|
||||||
|
final var baos = new ByteArrayOutputStream();
|
||||||
|
try (baos) {
|
||||||
|
JWO.copy(is, baos);
|
||||||
|
}
|
||||||
|
final var jwoClassVersion = JavaVersion.forClass(baos.toByteArray());
|
||||||
|
assertEquals(JavaVersion.VERSION_1_8, jwoClassVersion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
src/test/java/net/woggioni/jwo/Misc.java
Normal file
9
src/test/java/net/woggioni/jwo/Misc.java
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package net.woggioni.jwo;
|
||||||
|
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
public class Misc {
|
||||||
|
public static final String CRACKLIB_RESOURCE = "/cracklib-small";
|
||||||
|
}
|
49
src/test/java/net/woggioni/jwo/RetryExecutorTest.java
Normal file
49
src/test/java/net/woggioni/jwo/RetryExecutorTest.java
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
package net.woggioni.jwo;
|
||||||
|
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class RetryExecutorTest {
|
||||||
|
@Test
|
||||||
|
@SneakyThrows
|
||||||
|
public void test() {
|
||||||
|
int expectedValue = 42;
|
||||||
|
ExecutorService executorService = Executors.newSingleThreadExecutor();
|
||||||
|
RetryExecutor.ExceptionHandler exceptionHandler = err -> RetryExecutor.ExceptionHandlerOutcome.CONTINUE;
|
||||||
|
CompletableFuture<Integer> n = RetryExecutor.submit(
|
||||||
|
new Callable<>() {
|
||||||
|
private int counter = 0;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer call() {
|
||||||
|
log.info("Attempt {}", counter);
|
||||||
|
try {
|
||||||
|
if (counter != 5) {
|
||||||
|
throw new RuntimeException();
|
||||||
|
} else {
|
||||||
|
return expectedValue;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
10,
|
||||||
|
Duration.ofMillis(100),
|
||||||
|
1.3,
|
||||||
|
exceptionHandler,
|
||||||
|
executorService
|
||||||
|
);
|
||||||
|
Assertions.assertEquals(expectedValue, n.get(10, TimeUnit.SECONDS));
|
||||||
|
}
|
||||||
|
}
|
4
src/test/java/net/woggioni/jwo/TestCase.java
Normal file
4
src/test/java/net/woggioni/jwo/TestCase.java
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
package net.woggioni.jwo;
|
||||||
|
|
||||||
|
public record TestCase<T, U>(T input, U expectedOutput, Class<? extends Throwable> error) {
|
||||||
|
}
|
@@ -0,0 +1,94 @@
|
|||||||
|
package net.woggioni.jwo;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static net.woggioni.jwo.CollectionUtils.toMap;
|
||||||
|
import static net.woggioni.jwo.CollectionUtils.toUnmodifiableMap;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
public class UnmodifiableDelegatingMapTest {
|
||||||
|
|
||||||
|
private static Map<String, Integer> createMap() {
|
||||||
|
final var m1 = Stream.of(
|
||||||
|
Map.entry("a", 1),
|
||||||
|
Map.entry("b", 2),
|
||||||
|
Map.entry("c", 3),
|
||||||
|
Map.entry("d", 4),
|
||||||
|
Map.entry("e", 5),
|
||||||
|
Map.entry("f", 6)
|
||||||
|
).collect(toUnmodifiableMap(TreeMap::new, Map.Entry::getKey, Map.Entry::getValue));
|
||||||
|
final var m2 = Stream.of(
|
||||||
|
Map.entry("a", 7),
|
||||||
|
Map.entry("c", 8),
|
||||||
|
Map.entry("e", 9),
|
||||||
|
Map.entry("g", 10),
|
||||||
|
Map.entry("h", 11)
|
||||||
|
).collect(toUnmodifiableMap(TreeMap::new, Map.Entry::getKey, Map.Entry::getValue));
|
||||||
|
return UnmodifiableDelegatingMap.of(TreeMap::new, m1, m2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Map<String, Integer> delegatingMap = createMap();
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
assertEquals(1, delegatingMap.get("a"));
|
||||||
|
assertEquals(2, delegatingMap.get("b"));
|
||||||
|
assertEquals(10, delegatingMap.get("g"));
|
||||||
|
|
||||||
|
for(final var entry : delegatingMap.entrySet()) {
|
||||||
|
assertEquals(delegatingMap.get(entry.getKey()), entry.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test2() {
|
||||||
|
assertFalse(delegatingMap.isEmpty());
|
||||||
|
assertEquals(1, delegatingMap.get("a"));
|
||||||
|
assertEquals(2, delegatingMap.get("b"));
|
||||||
|
assertEquals(10, delegatingMap.get("g"));
|
||||||
|
|
||||||
|
for(final var entry : delegatingMap.entrySet()) {
|
||||||
|
assertTrue(delegatingMap.containsKey(entry.getKey()),
|
||||||
|
String.format("Expected key '%s' was not found in map", entry.getKey()));
|
||||||
|
assertTrue(delegatingMap.containsValue(entry.getValue()),
|
||||||
|
String.format("Expected value '%d' was not found in map", entry.getValue()));
|
||||||
|
assertEquals(delegatingMap.get(entry.getKey()), entry.getValue());
|
||||||
|
}
|
||||||
|
assertThrows(UnsupportedOperationException.class, () -> {
|
||||||
|
delegatingMap.put("key", 42);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void checkPutNotAllowed() {
|
||||||
|
assertThrows(UnsupportedOperationException.class, () -> {
|
||||||
|
delegatingMap.put("key", 42);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
public void checkClearNotAllowed() {
|
||||||
|
assertThrows(UnsupportedOperationException.class, () -> {
|
||||||
|
delegatingMap.clear();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
public void checkRemoveNotAllowed() {
|
||||||
|
assertThrows(UnsupportedOperationException.class, () -> {
|
||||||
|
delegatingMap.remove("a");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
public void checkPutAllNotAllowed() {
|
||||||
|
assertThrows(UnsupportedOperationException.class, () -> {
|
||||||
|
delegatingMap.putAll(Stream.of(Map.entry("c", 42))
|
||||||
|
.collect(toMap(HashMap::new, Map.Entry::getKey, Map.Entry::getValue)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
38
src/test/java/net/woggioni/jwo/internal/LazyValueTest.java
Normal file
38
src/test/java/net/woggioni/jwo/internal/LazyValueTest.java
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package net.woggioni.jwo.internal;
|
||||||
|
|
||||||
|
import net.woggioni.jwo.LazyValue;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
public class LazyValueTest {
|
||||||
|
@Test
|
||||||
|
void unsynchronizedLazyValueTest() {
|
||||||
|
final var bool = new boolean[1];
|
||||||
|
final Supplier<Void> supplier = () -> {
|
||||||
|
bool[0] = true;
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
assertFalse(bool[0]);
|
||||||
|
final var lazy = LazyValue.of(supplier, LazyValue.ThreadSafetyMode.NONE);
|
||||||
|
assertFalse(bool[0]);
|
||||||
|
lazy.get();
|
||||||
|
assertTrue(bool[0]);
|
||||||
|
final Supplier<Void> throwingSupplier = () -> {
|
||||||
|
throw new RuntimeException();
|
||||||
|
};
|
||||||
|
|
||||||
|
final var throwingLazy = LazyValue.of(throwingSupplier, LazyValue.ThreadSafetyMode.NONE);
|
||||||
|
throwingLazy.handle((v, ex) -> {
|
||||||
|
assertNotNull(ex);
|
||||||
|
assertEquals(RuntimeException.class, ex.getClass());
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
58
src/test/java/net/woggioni/jwo/internal/TupleTest.java
Normal file
58
src/test/java/net/woggioni/jwo/internal/TupleTest.java
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
package net.woggioni.jwo.internal;
|
||||||
|
|
||||||
|
import net.woggioni.jwo.CollectionUtils;
|
||||||
|
import net.woggioni.jwo.MutableTuple2;
|
||||||
|
import net.woggioni.jwo.MutableTuple3;
|
||||||
|
import net.woggioni.jwo.Tuple2;
|
||||||
|
import net.woggioni.jwo.Tuple3;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
public class TupleTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void mutableTuple2Test() {
|
||||||
|
final var t1 = MutableTuple2.newInstance(1, "a");
|
||||||
|
final var t2 = MutableTuple2.newInstance(1, "a");
|
||||||
|
assertEquals(t1, t2);
|
||||||
|
final var s = Stream.of(t1, t2).collect(CollectionUtils.toUnmodifiableSet());
|
||||||
|
assertEquals(1, s.size());
|
||||||
|
t1.set_1(2);
|
||||||
|
t1.set_2("b");
|
||||||
|
assertEquals(t1, MutableTuple2.newInstance(2, "b"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void mutableTuple3Test() {
|
||||||
|
final var t1 = MutableTuple3.newInstance(1, "a", BigDecimal.ONE);
|
||||||
|
final var t2 = MutableTuple3.newInstance(1, "a", BigDecimal.ONE);
|
||||||
|
assertEquals(t1, t2);
|
||||||
|
final var s = Stream.of(t1, t2).collect(CollectionUtils.toUnmodifiableSet());
|
||||||
|
assertEquals(1, s.size());
|
||||||
|
t1.set_1(2);
|
||||||
|
t1.set_2("b");
|
||||||
|
t1.set_3(BigDecimal.ZERO);
|
||||||
|
assertEquals(t1, MutableTuple3.newInstance(2, "b", BigDecimal.ZERO));
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
void tuple2Test() {
|
||||||
|
final var t1 = Tuple2.newInstance(1, "a");
|
||||||
|
final var t2 = Tuple2.newInstance(1, "a");
|
||||||
|
assertEquals(t1, t2);
|
||||||
|
final var s = Stream.of(t1, t2).collect(CollectionUtils.toUnmodifiableSet());
|
||||||
|
assertEquals(1, s.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void tuple3Test() {
|
||||||
|
final var t1 = Tuple3.newInstance(1, "a", BigDecimal.ONE);
|
||||||
|
final var t2 = Tuple3.newInstance(1, "a", BigDecimal.ONE);
|
||||||
|
assertEquals(t1, t2);
|
||||||
|
final var s = Stream.of(t1, t2).collect(CollectionUtils.toUnmodifiableSet());
|
||||||
|
assertEquals(1, s.size());
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user