Compare commits
10 Commits
70123e1c08
...
90e55618bc
Author | SHA1 | Date | |
---|---|---|---|
90e55618bc
|
|||
84e115693f
|
|||
f21f528166
|
|||
0ad79bbfdc
|
|||
21753f8e0c
|
|||
ecbae35415
|
|||
109b387a09
|
|||
11e6a9fc92
|
|||
6a81b48666
|
|||
16d2bffde6
|
30
Jenkinsfile
vendored
30
Jenkinsfile
vendored
@@ -6,27 +6,25 @@ pipeline {
|
||||
stages {
|
||||
stage("Build") {
|
||||
steps {
|
||||
sh "./gradlew assemble"
|
||||
}
|
||||
}
|
||||
stage("Check") {
|
||||
steps {
|
||||
sh "./gradlew test"
|
||||
sh "./gradlew build"
|
||||
}
|
||||
post {
|
||||
always {
|
||||
junit 'build/test-results/test/*.xml'
|
||||
junit 'jmath/build/test-results/test/*.xml'
|
||||
}
|
||||
success {
|
||||
jacoco(
|
||||
execPattern: '**/build/jacoco/*.exec',
|
||||
classPattern: '**/build/classes/java/main',
|
||||
sourcePattern: '**/src/main'
|
||||
)
|
||||
//javadoc javadocDir: "build/docs/javadoc", keepAll: true
|
||||
archiveArtifacts artifacts: '**/build/libs/*.jar',
|
||||
allowEmptyArchive: false,
|
||||
fingerprint: true,
|
||||
onlyIfSuccessful: true
|
||||
}
|
||||
}
|
||||
}
|
||||
stage("Archive") {
|
||||
steps {
|
||||
sh "./gradlew build"
|
||||
javadoc javadocDir: "build/docs/javadoc", keepAll: true
|
||||
archiveArtifacts artifacts: 'build/libs/*.jar,benchmark/build/libs/*.jar',
|
||||
allowEmptyArchive: true,
|
||||
fingerprint: true,
|
||||
onlyIfSuccessful: true
|
||||
}
|
||||
}
|
||||
stage("Publish") {
|
||||
|
@@ -1,7 +1,11 @@
|
||||
import net.woggioni.gradle.graalvm.NativeImageTask
|
||||
import net.woggioni.gradle.graalvm.NativeImagePlugin
|
||||
|
||||
plugins {
|
||||
id 'java-library'
|
||||
alias(catalog.plugins.lombok)
|
||||
alias(catalog.plugins.envelope)
|
||||
alias(catalog.plugins.graalvm.native.image)
|
||||
}
|
||||
|
||||
repositories {
|
||||
@@ -12,7 +16,11 @@ dependencies {
|
||||
implementation project(':')
|
||||
}
|
||||
|
||||
envelopeJar {
|
||||
application {
|
||||
mainClass = 'net.woggioni.jwo.benchmark.Main'
|
||||
}
|
||||
|
||||
tasks.named(NativeImagePlugin.NATIVE_IMAGE_TASK_NAME, NativeImageTask) {
|
||||
useMusl = true
|
||||
buildStaticImage = true
|
||||
}
|
6
benchmark/native-image/jni-config.json
Normal file
6
benchmark/native-image/jni-config.json
Normal file
@@ -0,0 +1,6 @@
|
||||
[
|
||||
{
|
||||
"name":"java.lang.Boolean",
|
||||
"methods":[{"name":"getBoolean","parameterTypes":["java.lang.String"] }]
|
||||
}
|
||||
]
|
1
benchmark/native-image/native-image.properties
Normal file
1
benchmark/native-image/native-image.properties
Normal file
@@ -0,0 +1 @@
|
||||
Args=-H:Optimize=3 --initialize-at-build-time=net.woggioni.jwo.benchmark.Main
|
8
benchmark/native-image/predefined-classes-config.json
Normal file
8
benchmark/native-image/predefined-classes-config.json
Normal file
@@ -0,0 +1,8 @@
|
||||
[
|
||||
{
|
||||
"type":"agent-extracted",
|
||||
"classes":[
|
||||
]
|
||||
}
|
||||
]
|
||||
|
2
benchmark/native-image/proxy-config.json
Normal file
2
benchmark/native-image/proxy-config.json
Normal file
@@ -0,0 +1,2 @@
|
||||
[
|
||||
]
|
128
benchmark/native-image/reflect-config.json
Normal file
128
benchmark/native-image/reflect-config.json
Normal file
@@ -0,0 +1,128 @@
|
||||
[
|
||||
{
|
||||
"name":"com.sun.crypto.provider.AESCipher$General",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"com.sun.crypto.provider.ARCFOURCipher",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"com.sun.crypto.provider.ChaCha20Cipher$ChaCha20Poly1305",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"com.sun.crypto.provider.DESCipher",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"com.sun.crypto.provider.DESedeCipher",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"com.sun.crypto.provider.GaloisCounterMode$AESGCM",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"java.security.AlgorithmParametersSpi"
|
||||
},
|
||||
{
|
||||
"name":"java.security.KeyStoreSpi"
|
||||
},
|
||||
{
|
||||
"name":"java.security.SecureRandomParameters"
|
||||
},
|
||||
{
|
||||
"name":"javax.security.auth.x500.X500Principal",
|
||||
"fields":[{"name":"thisX500Name"}],
|
||||
"methods":[{"name":"<init>","parameterTypes":["sun.security.x509.X500Name"] }]
|
||||
},
|
||||
{
|
||||
"name":"sun.security.pkcs12.PKCS12KeyStore",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"sun.security.pkcs12.PKCS12KeyStore$DualFormatPKCS12",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"sun.security.provider.JavaKeyStore$JKS",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"sun.security.provider.NativePRNG",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"<init>","parameterTypes":["java.security.SecureRandomParameters"] }]
|
||||
},
|
||||
{
|
||||
"name":"sun.security.provider.SHA",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"sun.security.provider.X509Factory",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"sun.security.rsa.RSAKeyFactory$Legacy",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"sun.security.ssl.KeyManagerFactoryImpl$SunX509",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"sun.security.ssl.SSLContextImpl$DefaultSSLContext",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"sun.security.ssl.TrustManagerFactoryImpl$PKIXFactory",
|
||||
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||
},
|
||||
{
|
||||
"name":"sun.security.x509.AuthorityInfoAccessExtension",
|
||||
"methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
|
||||
},
|
||||
{
|
||||
"name":"sun.security.x509.AuthorityKeyIdentifierExtension",
|
||||
"methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
|
||||
},
|
||||
{
|
||||
"name":"sun.security.x509.BasicConstraintsExtension",
|
||||
"methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
|
||||
},
|
||||
{
|
||||
"name":"sun.security.x509.CRLDistributionPointsExtension",
|
||||
"methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
|
||||
},
|
||||
{
|
||||
"name":"sun.security.x509.CertificatePoliciesExtension",
|
||||
"methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
|
||||
},
|
||||
{
|
||||
"name":"sun.security.x509.ExtendedKeyUsageExtension",
|
||||
"methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
|
||||
},
|
||||
{
|
||||
"name":"sun.security.x509.IssuerAlternativeNameExtension",
|
||||
"methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
|
||||
},
|
||||
{
|
||||
"name":"sun.security.x509.KeyUsageExtension",
|
||||
"methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
|
||||
},
|
||||
{
|
||||
"name":"sun.security.x509.NetscapeCertTypeExtension",
|
||||
"methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
|
||||
},
|
||||
{
|
||||
"name":"sun.security.x509.PrivateKeyUsageExtension",
|
||||
"methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
|
||||
},
|
||||
{
|
||||
"name":"sun.security.x509.SubjectAlternativeNameExtension",
|
||||
"methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
|
||||
},
|
||||
{
|
||||
"name":"sun.security.x509.SubjectKeyIdentifierExtension",
|
||||
"methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
|
||||
}
|
||||
]
|
7
benchmark/native-image/resource-config.json
Normal file
7
benchmark/native-image/resource-config.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"resources":{
|
||||
"includes":[{
|
||||
"pattern":"\\Qrender_template_test.txt\\E"
|
||||
}]},
|
||||
"bundles":[]
|
||||
}
|
8
benchmark/native-image/serialization-config.json
Normal file
8
benchmark/native-image/serialization-config.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"types":[
|
||||
],
|
||||
"lambdaCapturingTypes":[
|
||||
],
|
||||
"proxies":[
|
||||
]
|
||||
}
|
@@ -2,9 +2,9 @@ package net.woggioni.jwo.benchmark;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import net.woggioni.jwo.Chronometer;
|
||||
import net.woggioni.jwo.CircularInputStream;
|
||||
import net.woggioni.jwo.CollectionUtils;
|
||||
import net.woggioni.jwo.JWO;
|
||||
import net.woggioni.jwo.CircularInputStream;
|
||||
import net.woggioni.jwo.Tuple2;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
55
build.gradle
55
build.gradle
@@ -1,8 +1,10 @@
|
||||
plugins {
|
||||
id 'java-library'
|
||||
id 'maven-publish'
|
||||
id 'jacoco'
|
||||
alias(catalog.plugins.multi.release.jar)
|
||||
alias(catalog.plugins.lombok)
|
||||
alias(catalog.plugins.sambal)
|
||||
}
|
||||
|
||||
allprojects {
|
||||
@@ -15,9 +17,15 @@ allprojects {
|
||||
}
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
pluginManager.withPlugin('java-library') {
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(21)
|
||||
vendor = JvmVendorSpec.GRAAL_VM
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation catalog.junit.jupiter.api
|
||||
testImplementation catalog.junit.jupiter.params
|
||||
@@ -26,6 +34,21 @@ allprojects {
|
||||
|
||||
test {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +59,14 @@ allprojects {
|
||||
}
|
||||
|
||||
pluginManager.withPlugin('maven-publish') {
|
||||
|
||||
pluginManager.withPlugin('java-library') {
|
||||
java {
|
||||
// withJavadocJar()
|
||||
withSourcesJar()
|
||||
}
|
||||
}
|
||||
|
||||
publishing {
|
||||
repositories {
|
||||
maven {
|
||||
@@ -51,11 +82,6 @@ allprojects {
|
||||
}
|
||||
}
|
||||
|
||||
java {
|
||||
withJavadocJar()
|
||||
withSourcesJar()
|
||||
}
|
||||
|
||||
ext {
|
||||
setProperty('jpms.module.name', 'net.woggioni.jwo')
|
||||
}
|
||||
@@ -63,16 +89,18 @@ ext {
|
||||
|
||||
configurations {
|
||||
pathClassloaderTest
|
||||
zipTestBundle {
|
||||
transitive = false
|
||||
canBeConsumed = false
|
||||
visible = false
|
||||
canBeResolved = true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation catalog.slf4j.api
|
||||
pathClassloaderTest group: 'com.google.inject', name: 'guice', version: getProperty('guice.version')
|
||||
}
|
||||
|
||||
java {
|
||||
withJavadocJar()
|
||||
withSourcesJar()
|
||||
zipTestBundle group: 'com.google.inject', name: 'guice', version: getProperty('guice.version')
|
||||
}
|
||||
|
||||
compileJava {
|
||||
@@ -97,12 +125,11 @@ test {
|
||||
}.first()
|
||||
systemProperties([
|
||||
'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'])
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
@@ -1,3 +1,3 @@
|
||||
jwo.version = 2023.10.01
|
||||
lys.version = 2023.09.26
|
||||
jwo.version = 2024.02.09
|
||||
lys.version = 2024.02.01
|
||||
guice.version = 5.0.1
|
||||
|
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
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
22
gradlew
vendored
22
gradlew
vendored
@@ -83,7 +83,8 @@ done
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
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.
|
||||
MAX_FD=maximum
|
||||
@@ -130,10 +131,13 @@ location of your Java installation."
|
||||
fi
|
||||
else
|
||||
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
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
@@ -141,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
@@ -149,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
@@ -198,11 +202,11 @@ fi
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
|
@@ -1,7 +1,11 @@
|
||||
import net.woggioni.gradle.graalvm.NativeImagePlugin
|
||||
import net.woggioni.gradle.graalvm.NativeImageTask
|
||||
|
||||
plugins {
|
||||
id 'java-library'
|
||||
alias(catalog.plugins.lombok)
|
||||
alias(catalog.plugins.envelope)
|
||||
alias(catalog.plugins.graalvm.native.image)
|
||||
}
|
||||
|
||||
repositories {
|
||||
@@ -12,7 +16,15 @@ dependencies {
|
||||
implementation project(':jmath')
|
||||
}
|
||||
|
||||
envelopeJar {
|
||||
application {
|
||||
mainClass = 'net.woggioni.jmath.benchmark.Main'
|
||||
}
|
||||
|
||||
configureNativeImage {
|
||||
args('20')
|
||||
}
|
||||
|
||||
tasks.named(NativeImagePlugin.NATIVE_IMAGE_TASK_NAME, NativeImageTask) {
|
||||
useMusl = true
|
||||
buildStaticImage = true
|
||||
}
|
2
jmath-benchmark/native-image/jni-config.json
Normal file
2
jmath-benchmark/native-image/jni-config.json
Normal file
@@ -0,0 +1,2 @@
|
||||
[
|
||||
]
|
1
jmath-benchmark/native-image/native-image.properties
Normal file
1
jmath-benchmark/native-image/native-image.properties
Normal file
@@ -0,0 +1 @@
|
||||
Args=-H:Optimize=3 --initialize-at-build-time=net.woggioni.jmath.benchmark.Main
|
@@ -0,0 +1,8 @@
|
||||
[
|
||||
{
|
||||
"type":"agent-extracted",
|
||||
"classes":[
|
||||
]
|
||||
}
|
||||
]
|
||||
|
2
jmath-benchmark/native-image/proxy-config.json
Normal file
2
jmath-benchmark/native-image/proxy-config.json
Normal file
@@ -0,0 +1,2 @@
|
||||
[
|
||||
]
|
2
jmath-benchmark/native-image/reflect-config.json
Normal file
2
jmath-benchmark/native-image/reflect-config.json
Normal file
@@ -0,0 +1,2 @@
|
||||
[
|
||||
]
|
7
jmath-benchmark/native-image/resource-config.json
Normal file
7
jmath-benchmark/native-image/resource-config.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"resources":{
|
||||
"includes":[{
|
||||
"pattern":"\\QMETA-INF/services/java.time.zone.ZoneRulesProvider\\E"
|
||||
}]},
|
||||
"bundles":[]
|
||||
}
|
8
jmath-benchmark/native-image/serialization-config.json
Normal file
8
jmath-benchmark/native-image/serialization-config.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"types":[
|
||||
],
|
||||
"lambdaCapturingTypes":[
|
||||
],
|
||||
"proxies":[
|
||||
]
|
||||
}
|
@@ -24,10 +24,15 @@ public class Main {
|
||||
Matrix<Rational> mtx = Matrix.of(numericTypeFactory, size, size, init);
|
||||
Matrix<Rational> lu = mtx.clone();
|
||||
Matrix.Pivot pivot = lu.lup();
|
||||
IntFunction<Rational> initVector = (i) -> Rational.of(rnd.nextInt(0, size), size);
|
||||
Vector<Rational> b = Vector.of(numericTypeFactory, size, initVector);
|
||||
Vector<Rational> x = lu.luSolve(b, pivot);
|
||||
Vector<Rational> error = mtx.mmul(x).sub(b);
|
||||
System.out.println(error.norm());
|
||||
for(int i = 0; i < size; i++) {
|
||||
IntFunction<Rational> initVector = (j) -> Rational.of(rnd.nextInt(0, size), size);
|
||||
Vector<Rational> b = Vector.of(numericTypeFactory, size, initVector);
|
||||
Vector<Rational> x = lu.luSolve(b, pivot);
|
||||
Vector<Rational> error = mtx.mmul(x).sub(b);
|
||||
Rational norm = error.norm();
|
||||
if(norm.compareTo(Rational.ZERO) != 0) {
|
||||
throw new RuntimeException(String.format("Error is %s", norm));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
plugins {
|
||||
id 'java-library'
|
||||
id 'jacoco'
|
||||
alias(catalog.plugins.lombok)
|
||||
id 'maven-publish'
|
||||
}
|
||||
@@ -11,8 +12,4 @@ dependencies {
|
||||
java {
|
||||
withJavadocJar()
|
||||
withSourcesJar()
|
||||
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(17)
|
||||
}
|
||||
}
|
@@ -4,7 +4,6 @@ import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.Objects;
|
||||
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class BigIntegerExt {
|
||||
@@ -29,21 +28,6 @@ public class BigIntegerExt {
|
||||
return a;
|
||||
}
|
||||
|
||||
// static BigInteger gcd(BigInteger n1, BigInteger n2) {
|
||||
// BigInteger remainder;
|
||||
// BigInteger result;
|
||||
// while (true) {
|
||||
// remainder = n1.mod(n2);
|
||||
// result = n2;
|
||||
// if (BigInteger.ZERO.equals(remainder)) break;
|
||||
// else {
|
||||
// n1 = n2;
|
||||
// n2 = remainder;
|
||||
// }
|
||||
// }
|
||||
// return result;
|
||||
// }
|
||||
|
||||
public static BigInteger mcm(BigInteger n1, BigInteger n2) {
|
||||
return n1.multiply(n2).divide(gcd(n1, n2));
|
||||
}
|
||||
|
@@ -4,7 +4,6 @@ import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.Random;
|
||||
import java.util.function.IntFunction;
|
||||
import java.util.stream.IntStream;
|
||||
|
@@ -9,9 +9,10 @@ import org.junit.jupiter.params.provider.ArgumentsProvider;
|
||||
import org.junit.jupiter.params.provider.ArgumentsSource;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import static java.math.BigInteger.valueOf;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static java.math.BigInteger.valueOf;
|
||||
|
||||
public class RationalTest {
|
||||
|
||||
private enum Operation {
|
||||
|
@@ -35,39 +35,6 @@ public class Application {
|
||||
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) {
|
||||
try {
|
||||
if (!Files.exists(candidate)) {
|
||||
|
36
src/main/java/net/woggioni/jwo/ChannerWriter.java
Normal file
36
src/main/java/net/woggioni/jwo/ChannerWriter.java
Normal file
@@ -0,0 +1,36 @@
|
||||
package net.woggioni.jwo;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class ChannerWriter extends Writer {
|
||||
|
||||
private final WritableByteChannel ch;
|
||||
|
||||
private final Charset charset;
|
||||
|
||||
|
||||
@Override
|
||||
public void write(char[] cbuf, int off, int len) throws IOException {
|
||||
write(new String(cbuf, off, len));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(String str) throws IOException {
|
||||
ch.write(ByteBuffer.wrap(str.getBytes(charset)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() {}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
ch.close();
|
||||
}
|
||||
}
|
@@ -1,6 +1,21 @@
|
||||
package net.woggioni.jwo;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Map;
|
||||
import java.util.NavigableMap;
|
||||
import java.util.NavigableSet;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.TreeSet;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BinaryOperator;
|
||||
import java.util.function.Function;
|
||||
@@ -10,6 +25,10 @@ import java.util.stream.Stream;
|
||||
|
||||
public class CollectionUtils {
|
||||
|
||||
public enum MapMergeStrategy {
|
||||
THROW, REPLACE, KEEP
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static <T> ArrayList<T> newArrayList(T... args) {
|
||||
return new ArrayList<>(Arrays.asList(args));
|
||||
@@ -115,7 +134,38 @@ public class CollectionUtils {
|
||||
throw new IllegalStateException(String.format("Duplicate key %s", v1));
|
||||
};
|
||||
}
|
||||
private static <T> BinaryOperator<T> updatingMerger() {
|
||||
return (v1, v2) -> v2;
|
||||
}
|
||||
|
||||
private static <T> BinaryOperator<T> conservativeMerger() {
|
||||
return (v1, v2) -> v1;
|
||||
}
|
||||
|
||||
private static <T> BinaryOperator<T> getMerger(MapMergeStrategy mapMergeStrategy) {
|
||||
BinaryOperator<T> result;
|
||||
switch (mapMergeStrategy) {
|
||||
case KEEP:
|
||||
result = conservativeMerger();
|
||||
break;
|
||||
case THROW:
|
||||
result = throwingMerger();
|
||||
break;
|
||||
case REPLACE:
|
||||
result = updatingMerger();
|
||||
break;
|
||||
default:
|
||||
throw new NullPointerException();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <T, K, V> Collector<T, ?, Map<K, V>> toUnmodifiableHashMap(
|
||||
Function<T, K> keyExtractor,
|
||||
Function<T, V> valueExtractor,
|
||||
MapMergeStrategy mapMergeStrategy) {
|
||||
return toUnmodifiableMap(HashMap::new, keyExtractor, valueExtractor, mapMergeStrategy);
|
||||
}
|
||||
public static <T, K, V> Collector<T, ?, Map<K, V>> toUnmodifiableHashMap(
|
||||
Function<T, K> keyExtractor,
|
||||
Function<T, V> valueExtractor) {
|
||||
@@ -128,6 +178,19 @@ public class CollectionUtils {
|
||||
return toUnmodifiableNavigableMap(TreeMap::new, keyExtractor, valueExtractor);
|
||||
}
|
||||
|
||||
public static <T, K, V> Collector<T, ?, NavigableMap<K, V>> toUnmodifiableTreeMap(
|
||||
Function<T, K> keyExtractor,
|
||||
Function<T, V> valueExtractor,
|
||||
MapMergeStrategy mapMergeStrategy) {
|
||||
return toUnmodifiableNavigableMap(TreeMap::new, keyExtractor, valueExtractor, mapMergeStrategy);
|
||||
}
|
||||
public static <T, K, V> Collector<T, ?, NavigableMap<K, V>> toUnmodifiableTreeMap(
|
||||
Function<T, K> keyExtractor,
|
||||
Function<T, V> valueExtractor,
|
||||
Comparator<K> comparator,
|
||||
MapMergeStrategy mapMergeStrategy) {
|
||||
return toUnmodifiableNavigableMap(() -> new TreeMap<>(comparator), keyExtractor, valueExtractor, mapMergeStrategy);
|
||||
}
|
||||
public static <T, K, V> Collector<T, ?, NavigableMap<K, V>> toUnmodifiableTreeMap(
|
||||
Function<T, K> keyExtractor,
|
||||
Function<T, V> valueExtractor,
|
||||
@@ -135,6 +198,12 @@ public class CollectionUtils {
|
||||
return toUnmodifiableNavigableMap(() -> new TreeMap<>(comparator), keyExtractor, valueExtractor);
|
||||
}
|
||||
|
||||
public static <T, K, V> Collector<T, ?, NavigableMap<K, V>> toTreeMap(
|
||||
Function<T, K> keyExtractor,
|
||||
Function<T, V> valueExtractor,
|
||||
MapMergeStrategy mapMergeStrategy) {
|
||||
return toNavigableMap(TreeMap::new, keyExtractor, valueExtractor, mapMergeStrategy);
|
||||
}
|
||||
public static <T, K, V> Collector<T, ?, NavigableMap<K, V>> toTreeMap(
|
||||
Function<T, K> keyExtractor,
|
||||
Function<T, V> valueExtractor) {
|
||||
@@ -148,62 +217,121 @@ public class CollectionUtils {
|
||||
return toNavigableMap(() -> new TreeMap<>(comparator), keyExtractor, valueExtractor);
|
||||
}
|
||||
|
||||
public static <T, K, V> Collector<T, ?, NavigableMap<K, V>> toTreeMap(
|
||||
Function<T, K> keyExtractor,
|
||||
Function<T, V> valueExtractor,
|
||||
Comparator<K> comparator,
|
||||
MapMergeStrategy mapMergeStrategy) {
|
||||
return toNavigableMap(() -> new TreeMap<>(comparator), keyExtractor, valueExtractor, mapMergeStrategy);
|
||||
}
|
||||
|
||||
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,
|
||||
MapMergeStrategy.THROW
|
||||
);
|
||||
}
|
||||
|
||||
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) {
|
||||
Function<T, V> valueExtractor,
|
||||
MapMergeStrategy mapMergeStrategy) {
|
||||
BinaryOperator<V> valueMerger = getMerger(mapMergeStrategy);
|
||||
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(
|
||||
constructor,
|
||||
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, MapMergeStrategy.THROW);
|
||||
}
|
||||
|
||||
public static <T, K, V> Collector<T, ?, Map<K, V>> toMap(
|
||||
Supplier<Map<K, V>> constructor,
|
||||
Function<T, K> keyExtractor,
|
||||
Function<T, V> valueExtractor) {
|
||||
Function<T, V> valueExtractor,
|
||||
MapMergeStrategy mapMergeStrategy) {
|
||||
BinaryOperator<V> valueMerger = getMerger(mapMergeStrategy);
|
||||
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(
|
||||
constructor,
|
||||
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, MapMergeStrategy.THROW);
|
||||
}
|
||||
public static <T, K, V> Collector<T, ?, Map<K, V>> toUnmodifiableMap(
|
||||
Supplier<Map<K, V>> constructor,
|
||||
Function<T, K> keyExtractor,
|
||||
Function<T, V> valueExtractor) {
|
||||
Function<T, V> valueExtractor,
|
||||
MapMergeStrategy mapMergeStrategy) {
|
||||
BinaryOperator<V> valueMerger = getMerger(mapMergeStrategy);
|
||||
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(
|
||||
constructor,
|
||||
accumulator,
|
||||
mapMerger(throwingMerger()),
|
||||
mapMerger(valueMerger),
|
||||
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,
|
||||
MapMergeStrategy mapMergeStrategy
|
||||
) {
|
||||
BinaryOperator<V> valueMerger = getMerger(mapMergeStrategy);
|
||||
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(
|
||||
Supplier<NavigableMap<K, V>> constructor,
|
||||
Function<T, K> keyExtractor,
|
||||
Function<T, V> valueExtractor) {
|
||||
BiConsumer<NavigableMap<K, V>, T> accumulator = (map, streamElement) -> {
|
||||
map.merge(keyExtractor.apply(streamElement), valueExtractor.apply(streamElement), throwingMerger());
|
||||
};
|
||||
return Collector.of(
|
||||
constructor,
|
||||
accumulator,
|
||||
mapMerger(throwingMerger()),
|
||||
Collections::unmodifiableNavigableMap
|
||||
);
|
||||
return toUnmodifiableNavigableMap(constructor, keyExtractor, valueExtractor, MapMergeStrategy.THROW);
|
||||
}
|
||||
public static <K, V, U> Stream<Map.Entry<K, U>> mapValues(Map<K, V> map, Fun<V, U> xform) {
|
||||
return map
|
||||
@@ -211,4 +339,26 @@ public class CollectionUtils {
|
||||
.stream()
|
||||
.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;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class DelegatingMap<K,V> implements Map<K, V> {
|
||||
private final Supplier<Map<K,V>> mapFactory;
|
||||
private final List<Map<K,V>> delegates;
|
||||
private final Map<K,V> thisMap;
|
||||
import static net.woggioni.jwo.JWO.streamCat;
|
||||
|
||||
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) {
|
||||
this.mapFactory = mapFactory;
|
||||
this.delegates = delegates;
|
||||
super(mapFactory, delegates);
|
||||
thisMap = mapFactory.get();
|
||||
}
|
||||
|
||||
@@ -26,67 +23,36 @@ public class DelegatingMap<K,V> implements Map<K, V> {
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
if(!thisMap.isEmpty()) return false;
|
||||
for(Map<K,V> delegate : delegates) {
|
||||
if(!delegate.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return super.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsKey(Object key) {
|
||||
if(thisMap.containsKey(key)) return true;
|
||||
for(Map<K,V> delegate : delegates) {
|
||||
if(!delegate.containsKey(key)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return super.containsKey(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsValue(Object value) {
|
||||
if(thisMap.containsValue(value)) return true;
|
||||
for(Map<K,V> delegate : delegates) {
|
||||
if(!delegate.containsValue(value)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return super.containsValue(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public V get(Object key) {
|
||||
V result = thisMap.get(key);
|
||||
if(result != null) return result;
|
||||
for(Map<K,V> delegate : delegates) {
|
||||
result = delegate.get(key);
|
||||
if(result != null) break;
|
||||
}
|
||||
return result;
|
||||
return Optional.ofNullable(thisMap.get(key)).orElseGet(
|
||||
() -> super.get(key)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public V put(K key, V value) {
|
||||
V result = 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;
|
||||
return thisMap.put(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public V remove(Object key) {
|
||||
V result = thisMap.remove(key);
|
||||
if(result != null) return result;
|
||||
for(Map<K,V> delegate : delegates) {
|
||||
result = delegate.remove(key);
|
||||
if(result != null) break;
|
||||
}
|
||||
return result;
|
||||
return thisMap.remove(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -104,24 +70,8 @@ public class DelegatingMap<K,V> implements Map<K, V> {
|
||||
return flatten().keySet();
|
||||
}
|
||||
|
||||
private Map<K, V> flatten() {
|
||||
Map<K, V> result = mapFactory.get();
|
||||
int i = delegates.size();
|
||||
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();
|
||||
protected Map<K, V> flatten() {
|
||||
return streamCat(super.stream(), thisMap.entrySet().stream())
|
||||
.collect(CollectionUtils.toUnmodifiableMap(this.mapFactory, Map.Entry::getKey, Map.Entry::getValue));
|
||||
}
|
||||
}
|
||||
|
@@ -5,9 +5,15 @@ import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.security.DigestInputStream;
|
||||
import java.security.DigestOutputStream;
|
||||
import java.security.MessageDigest;
|
||||
|
||||
import static net.woggioni.jwo.JWO.newThrowable;
|
||||
|
||||
@EqualsAndHashCode
|
||||
@RequiredArgsConstructor
|
||||
public class Hash {
|
||||
@@ -27,6 +33,14 @@ public class Hash {
|
||||
public MessageDigest newMessageDigest() {
|
||||
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
|
||||
@@ -76,6 +90,33 @@ public class Hash {
|
||||
return new String(hexChars);
|
||||
}
|
||||
|
||||
public static byte[] hexToBytes(String hexString) {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
if (hexString.length() % 2 != 0) {
|
||||
throw newThrowable(IllegalArgumentException.class, "Hex string length must be even," +
|
||||
" string has length '%d' instead", hexString.length());
|
||||
}
|
||||
int lim = hexString.length() / 2;
|
||||
for(int i = 0; i < lim; i++) {
|
||||
int tmp = 0;
|
||||
for (int j = 0; j < 2; j++) {
|
||||
int c = hexString.charAt(i * 2 + j);
|
||||
if (c >= '0' && c <= '9') {
|
||||
c -= '0';
|
||||
} else if (c >= 'A' && c <= 'F') {
|
||||
c -= 'A' - 10;
|
||||
} else if(c >= 'a' && c <= 'f') {
|
||||
c -= 'a' - 10;
|
||||
} else {
|
||||
throw newThrowable(IllegalArgumentException.class, "Illegal hex char '%c'", c);
|
||||
}
|
||||
tmp |= (c << 4 * (1 - j));
|
||||
}
|
||||
baos.write(tmp);
|
||||
}
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return bytesToHex(bytes);
|
||||
|
@@ -2,22 +2,42 @@ package net.woggioni.jwo;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.ToString;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.CookieManager;
|
||||
import java.net.CookieStore;
|
||||
import java.net.HttpCookie;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static net.woggioni.jwo.JWO.newThrowable;
|
||||
|
||||
public class HttpClient {
|
||||
private static final Logger log = LoggerController.lazyLogger(HttpClient.class);
|
||||
private static final String COOKIES_HEADER = "Set-Cookie";
|
||||
private final CookieStore cookieStore = new CookieManager().getCookieStore();
|
||||
|
||||
private SSLSocketFactory socketFactory;
|
||||
|
||||
@Getter
|
||||
private Map<String, List<String>> stickyHeaders = new TreeMap<>();
|
||||
|
||||
public HttpClient() {}
|
||||
|
||||
public HttpClient(final SSLContext sslContext) {
|
||||
@@ -25,15 +45,29 @@ public class HttpClient {
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public HttpsURLConnection call(HttpRequest httpRequest) {
|
||||
HttpsURLConnection conn = (HttpsURLConnection) httpRequest.url.openConnection();
|
||||
if(socketFactory != null) {
|
||||
conn.setSSLSocketFactory(socketFactory);
|
||||
public HttpURLConnection call(HttpRequest httpRequest) {
|
||||
HttpURLConnection conn = (HttpURLConnection) httpRequest.url.openConnection();
|
||||
|
||||
if (socketFactory != null && conn instanceof HttpsURLConnection) {
|
||||
((HttpsURLConnection) conn).setSSLSocketFactory(socketFactory);
|
||||
}
|
||||
conn.setInstanceFollowRedirects(false);
|
||||
conn.setInstanceFollowRedirects(true);
|
||||
conn.setRequestMethod(httpRequest.method.text);
|
||||
httpRequest.headers.forEach((key, value) ->
|
||||
value.forEach(headerValue -> conn.addRequestProperty(key, headerValue)));
|
||||
Stream.of(stickyHeaders, httpRequest.headers)
|
||||
.map(Map::entrySet)
|
||||
.flatMap(Set::stream)
|
||||
.forEach(entry ->
|
||||
entry.getValue()
|
||||
.forEach(headerValue ->
|
||||
conn.addRequestProperty(entry.getKey(), headerValue)));
|
||||
List<HttpCookie> cookies = cookieStore.get(httpRequest.getUrl().toURI());
|
||||
if (!cookies.isEmpty()) {
|
||||
conn.setRequestProperty("Cookie",
|
||||
cookies.stream()
|
||||
.map(HttpCookie::toString)
|
||||
.collect(Collectors.joining(";"))
|
||||
);
|
||||
}
|
||||
switch (httpRequest.method) {
|
||||
case PUT:
|
||||
case POST:
|
||||
@@ -54,6 +88,14 @@ public class HttpClient {
|
||||
case OPTIONS:
|
||||
break;
|
||||
}
|
||||
conn.getResponseCode();
|
||||
Map<String, List<String>> headerFields = conn.getHeaderFields();
|
||||
List<String> cookiesHeader = headerFields.get(COOKIES_HEADER);
|
||||
if (cookiesHeader != null) {
|
||||
for (String cookie : cookiesHeader) {
|
||||
cookieStore.add(httpRequest.url.toURI(), HttpCookie.parse(cookie).get(0));
|
||||
}
|
||||
}
|
||||
return conn;
|
||||
}
|
||||
|
||||
@@ -72,54 +114,55 @@ public class HttpClient {
|
||||
}
|
||||
}
|
||||
|
||||
@ToString
|
||||
public enum HttpStatus {
|
||||
OK(200),
|
||||
INTERNAL_SERVER_ERROR(500),
|
||||
SERVICE_UNAVAILABLE(503),
|
||||
BAD_REQUEST(400),
|
||||
UNAUTHORIZED(401),
|
||||
FORBIDDEN(403),
|
||||
NOT_FOUND(404),
|
||||
CONFLICT(409);
|
||||
METHOD_NOT_ALLOWED(405),
|
||||
CONFLICT(409),
|
||||
UNSUPPORTED_MEDIA_TYPE(415),
|
||||
GATEWAY_TIMEOUT(504);
|
||||
|
||||
public final int code;
|
||||
|
||||
HttpStatus(int code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public static HttpStatus of(int code) {
|
||||
return Arrays.stream(values())
|
||||
.filter(it -> it.code == code)
|
||||
.findFirst()
|
||||
.orElseThrow(() -> newThrowable(
|
||||
IllegalArgumentException.class,
|
||||
"HTTP status code %d is not mapped",
|
||||
code)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Builder(builderMethodName = "privateBuilder", access = AccessLevel.PUBLIC)
|
||||
public static class HttpRequest {
|
||||
|
||||
final URL url;
|
||||
private final URL url;
|
||||
|
||||
final HttpMethod method = HttpMethod.GET;
|
||||
@Builder.Default
|
||||
private final HttpMethod method = HttpMethod.GET;
|
||||
|
||||
final Map<String, List<String>> headers = Collections.emptyMap();
|
||||
@Builder.Default
|
||||
private final Map<String, List<String>> headers = Collections.emptyMap();
|
||||
|
||||
final InputStream body = null;
|
||||
@Builder.Default
|
||||
private final InputStream body = null;
|
||||
|
||||
public static HttpRequestBuilder builder(URL url) {
|
||||
return HttpRequest.privateBuilder().url(url);
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private final URL url;
|
||||
|
||||
private HttpMethod method = HttpMethod.GET;
|
||||
|
||||
private Map<String, List<String>> headers = Collections.emptyMap();
|
||||
|
||||
private InputStream body = null;
|
||||
|
||||
private Builder(URL url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public Builder method(HttpMethod method) {
|
||||
this.method = method;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -4,6 +4,7 @@ import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.woggioni.jwo.exception.ChildProcessException;
|
||||
import net.woggioni.jwo.internal.CharFilterReader;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
@@ -12,9 +13,7 @@ import javax.net.ssl.SSLSession;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
@@ -22,8 +21,11 @@ import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Reader;
|
||||
import java.io.StringWriter;
|
||||
import java.io.Writer;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.net.URL;
|
||||
import java.net.URLStreamHandlerFactory;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@@ -34,6 +36,8 @@ import java.nio.file.attribute.FileAttribute;
|
||||
import java.security.DigestOutputStream;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.EnumSet;
|
||||
@@ -66,8 +70,11 @@ import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
@Slf4j
|
||||
public class JWO {
|
||||
private static final Logger log = LoggerController.lazyLogger(JWO.class);
|
||||
private static final String PROTOCOL_HANDLER = "java.protocol.handler.pkgs";
|
||||
private static final String HANDLERS_PACKAGE = "net.woggioni.jwo.url";
|
||||
|
||||
public static <T> Stream<T> iterable2Stream(Iterable<T> iterable) {
|
||||
return StreamSupport.stream(iterable.spliterator(), false);
|
||||
}
|
||||
@@ -104,23 +111,19 @@ public class JWO {
|
||||
|
||||
@SneakyThrows
|
||||
public static void writeBytes2File(Path file, byte[] bytes) {
|
||||
try (OutputStream os = new FileOutputStream(file.toString())) {
|
||||
try (OutputStream os = Files.newOutputStream(file)) {
|
||||
os.write(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static String readFile2String(File file) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
try (Reader reader = new InputStreamReader(new BufferedInputStream(new FileInputStream(file.getPath())))) {
|
||||
StringWriter writer = new StringWriter();
|
||||
try (Reader reader = Files.newBufferedReader(file.toPath())) {
|
||||
char[] buffer = new char[1024];
|
||||
while (true) {
|
||||
int read = reader.read(buffer);
|
||||
builder.append(buffer, 0, read);
|
||||
if (read < buffer.length) break;
|
||||
}
|
||||
JWO.copy(reader, writer, buffer);
|
||||
}
|
||||
return builder.toString();
|
||||
return writer.toString();
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@@ -137,6 +140,13 @@ public class JWO {
|
||||
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
|
||||
public static <T extends Throwable> T newThrowable(Class<T> cls, String format, Object... args) {
|
||||
Constructor<T> constructor = cls.getDeclaredConstructor(String.class);
|
||||
@@ -150,6 +160,11 @@ public class JWO {
|
||||
return constructor.newInstance(String.format(format, args), throwable);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static <T extends Throwable> T raise(Class<T> cls) {
|
||||
throw newThrowable(cls);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static <T extends Throwable> void raise(Class<T> cls, Throwable throwable, String format, Object... args) {
|
||||
throw newThrowable(cls, throwable, format, args);
|
||||
@@ -160,7 +175,6 @@ public class JWO {
|
||||
throw newThrowable(cls, format, args);
|
||||
}
|
||||
|
||||
|
||||
private static SSLSocketFactory defaultSSLSocketFactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
|
||||
private static HostnameVerifier defaultHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
|
||||
|
||||
@@ -219,16 +233,8 @@ public class JWO {
|
||||
}
|
||||
}
|
||||
|
||||
final private static char[] hexArray = "0123456789ABCDEF".toCharArray();
|
||||
|
||||
public static String bytesToHex(byte[] bytes) {
|
||||
char[] hexChars = new char[bytes.length * 2];
|
||||
for (int j = 0; j < bytes.length; j++) {
|
||||
int v = bytes[j] & 0xFF;
|
||||
hexChars[j * 2] = hexArray[v >>> 4];
|
||||
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
|
||||
}
|
||||
return new String(hexChars);
|
||||
return Hash.bytesToHex(bytes);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@@ -563,13 +569,13 @@ public class JWO {
|
||||
byte[] buffer = new byte[0x10000];
|
||||
expandZip(sourceArchive, (BiCon<ZipInputStream, ZipEntry>)
|
||||
(ZipInputStream zipInputStream, ZipEntry zipEntry) -> {
|
||||
Path newFile = destinationFolder.resolve(zipEntry.getName());
|
||||
Files.createDirectories(newFile.getParent());
|
||||
try(OutputStream outputStream = Files.newOutputStream(newFile)) {
|
||||
while (true) {
|
||||
int read = zipInputStream.read(buffer);
|
||||
if (read < 0) break;
|
||||
outputStream.write(buffer, 0, read);
|
||||
Path entryPath = destinationFolder.resolve(zipEntry.getName());
|
||||
if(zipEntry.isDirectory()) {
|
||||
Files.createDirectories(entryPath);
|
||||
} else {
|
||||
Files.createDirectories(entryPath.getParent());
|
||||
try(OutputStream outputStream = Files.newOutputStream(entryPath)) {
|
||||
copy(zipInputStream, outputStream, buffer);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -815,7 +821,7 @@ public class JWO {
|
||||
}
|
||||
|
||||
public static void replaceFileIfDifferent(InputStream inputStream, Path destination, FileAttribute<?>... attrs) {
|
||||
replaceFileIfDifferent(() -> inputStream, destination, attrs);
|
||||
replaceFileIfDifferent(() -> inputStream, destination, null, attrs);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@@ -874,6 +880,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) {
|
||||
return u -> original.apply(arg, u);
|
||||
}
|
||||
@@ -894,7 +912,7 @@ public class JWO {
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -984,4 +1002,100 @@ public class JWO {
|
||||
}
|
||||
public void run() { executor.execute(action); }
|
||||
}
|
||||
|
||||
public static String toUnixPath(Path path) {
|
||||
String result;
|
||||
if (OS.isUnix) {
|
||||
result = path.toString();
|
||||
} else {
|
||||
result = (path.isAbsolute() ? "/" : "") +
|
||||
iterable2Stream(path)
|
||||
.map(Path::toString)
|
||||
.collect(Collectors.joining("/"));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void registerUrlProtocolHandler() {
|
||||
String handlers = System.getProperty(PROTOCOL_HANDLER, "");
|
||||
System.setProperty(PROTOCOL_HANDLER,
|
||||
((handlers == null || handlers.isEmpty()) ? HANDLERS_PACKAGE : handlers + "|" + HANDLERS_PACKAGE));
|
||||
resetCachedUrlHandlers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset any cached handlers just in case a jar protocol has already been used. We
|
||||
* reset the handler by trying to set a null {@link URLStreamHandlerFactory} which
|
||||
* should have no effect other than clearing the handlers cache.
|
||||
*/
|
||||
private static void resetCachedUrlHandlers() {
|
||||
try {
|
||||
URL.setURLStreamHandlerFactory(null);
|
||||
} catch (Error ex) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static void deletePath(Logger log, Path path) {
|
||||
if (Files.exists(path)) {
|
||||
if (log.isInfoEnabled()) {
|
||||
log.info("Wiping '{}'", path);
|
||||
}
|
||||
deletePath(path);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T, U extends T> U downCast(T param) {
|
||||
return (U) param;
|
||||
}
|
||||
|
||||
public static <T extends U, U> U upCast(T param) {
|
||||
return param;
|
||||
}
|
||||
|
||||
public static <T, U extends T> Optional<U> asInstance(T param, Class<U> cls) {
|
||||
return Optional.ofNullable(param)
|
||||
.filter(cls::isInstance)
|
||||
.map(cls::cast);
|
||||
}
|
||||
|
||||
public static <K, V, U> Stream<Map.Entry<K, U>> mapValues(Map<K, V> map, Fun<V, U> xform) {
|
||||
return map
|
||||
.entrySet()
|
||||
.stream()
|
||||
.map(entry -> new AbstractMap.SimpleEntry<>(entry.getKey(), xform.apply(entry.getValue())));
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static Process startJavaProcess(
|
||||
Logger log,
|
||||
Path javaHome,
|
||||
List<String> args,
|
||||
Path cwd,
|
||||
Map<String, String> env) {
|
||||
Path javaExecutable = javaHome.resolve("bin/java" + (OS.isWindows ? ".exe" : ""));
|
||||
ProcessBuilder pb = new ProcessBuilder();
|
||||
List<String> cmd = new ArrayList<>();
|
||||
cmd.add(javaExecutable.toString());
|
||||
cmd.addAll(args);
|
||||
pb.command(cmd);
|
||||
pb.inheritIO();
|
||||
pb.directory(cwd.toFile());
|
||||
pb.environment().putAll(env);
|
||||
if (log.isTraceEnabled()) {
|
||||
String cmdLineListString = '[' + cmd.stream().map(s -> '\'' + s + '\'').collect(Collectors.joining(", ")) + ']';
|
||||
log.trace("Starting child java process with command line: {}", cmdLineListString);
|
||||
}
|
||||
return pb.start();
|
||||
}
|
||||
|
||||
public static <T, U> U let(T object, Function<T, U> cb) {
|
||||
return cb.apply(object);
|
||||
}
|
||||
|
||||
public static <T> T also(T object, Consumer<T> cb) {
|
||||
cb.accept(object);
|
||||
return object;
|
||||
}
|
||||
}
|
||||
|
@@ -1,10 +1,6 @@
|
||||
package net.woggioni.jwo;
|
||||
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -76,6 +72,11 @@ public enum JavaVersion {
|
||||
*/
|
||||
VERSION_20,
|
||||
|
||||
/**
|
||||
* Java 20 major version.
|
||||
*/
|
||||
VERSION_21,
|
||||
|
||||
/**
|
||||
* Higher version of Java.
|
||||
*/
|
||||
@@ -83,7 +84,10 @@ public enum JavaVersion {
|
||||
// Since Java 9, version should be X instead of 1.X
|
||||
// However, to keep backward compatibility, we change from 11
|
||||
private static final int FIRST_MAJOR_VERSION_ORDINAL = 10;
|
||||
private static JavaVersion currentJavaVersion;
|
||||
private static LazyValue<JavaVersion> currentJavaVersion = LazyValue.of(
|
||||
() -> toVersion(System.getProperty("java.version")),
|
||||
LazyValue.ThreadSafetyMode.SYNCHRONIZED
|
||||
);
|
||||
private final String versionName;
|
||||
|
||||
JavaVersion() {
|
||||
@@ -129,10 +133,7 @@ public enum JavaVersion {
|
||||
* @return The version of the current JVM.
|
||||
*/
|
||||
public static JavaVersion current() {
|
||||
if (currentJavaVersion == null) {
|
||||
currentJavaVersion = toVersion(System.getProperty("java.version"));
|
||||
}
|
||||
return currentJavaVersion;
|
||||
return currentJavaVersion.get();
|
||||
}
|
||||
|
||||
static void resetCurrent() {
|
||||
@@ -150,90 +151,6 @@ public enum JavaVersion {
|
||||
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
|
||||
*
|
||||
|
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);
|
||||
}
|
||||
}
|
77
src/main/java/net/woggioni/jwo/LazyOptional.java
Normal file
77
src/main/java/net/woggioni/jwo/LazyOptional.java
Normal file
@@ -0,0 +1,77 @@
|
||||
package net.woggioni.jwo;
|
||||
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class LazyOptional<T> {
|
||||
private static final LazyOptional empty = new LazyOptional<>(() -> null);
|
||||
|
||||
private final Supplier<T> producer;
|
||||
private final MutableTuple2<T, Boolean> instance = MutableTuple2.newInstance(null, false);
|
||||
|
||||
public static <U> LazyOptional<U> of(Supplier<U> producer) {
|
||||
return new LazyOptional<>(producer);
|
||||
}
|
||||
|
||||
public static <U> LazyOptional<U> or(LazyOptional<U>... opts) {
|
||||
return LazyOptional.of(() -> {
|
||||
for (LazyOptional<U> opt : opts) {
|
||||
U value = opt.get();
|
||||
if (value != null) return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public static <U> LazyOptional<U> empty() {
|
||||
return (LazyOptional<U>) empty;
|
||||
}
|
||||
|
||||
public <U> LazyOptional<U> map(Function<T, U> mapping) {
|
||||
return LazyOptional.of(() -> {
|
||||
T prevValue = producer.get();
|
||||
if (prevValue == null) return null;
|
||||
else return mapping.apply(prevValue);
|
||||
});
|
||||
}
|
||||
|
||||
public LazyOptional<T> filter(Predicate<T> predicate) {
|
||||
return LazyOptional.of(() -> {
|
||||
T prevValue = producer.get();
|
||||
if (predicate.test(prevValue)) return prevValue;
|
||||
else return null;
|
||||
});
|
||||
}
|
||||
|
||||
public T get() {
|
||||
if (instance.get_2()) return instance.get_1();
|
||||
synchronized (instance) {
|
||||
if (instance.get_2()) return instance.get_1();
|
||||
else {
|
||||
T value = producer.get();
|
||||
instance.set_1(value);
|
||||
instance.set_2(true);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public <U> LazyOptional<U> flatMap(Function<T, LazyOptional<U>> mapping) {
|
||||
return new LazyOptional<>(() -> {
|
||||
T prevValue = producer.get();
|
||||
if (prevValue == null) return null;
|
||||
else return mapping.apply(prevValue).get();
|
||||
});
|
||||
}
|
||||
|
||||
public Optional<T> getOptional() {
|
||||
return Optional.ofNullable(get());
|
||||
}
|
||||
}
|
@@ -1,10 +1,12 @@
|
||||
package net.woggioni.jwo;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.woggioni.jwo.internal.SynchronizedLazyValue;
|
||||
import net.woggioni.jwo.internal.UnsynchronizedLazyValue;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
@@ -30,19 +32,22 @@ public interface LazyValue<T> {
|
||||
*/
|
||||
Optional<T> close();
|
||||
|
||||
|
||||
static <T> LazyValue<T> of(Supplier<T> supplier, ThreadSafetyMode locking) {
|
||||
static <T> LazyValue<T> of(Supplier<T> supplier, ThreadSafetyMode locking, Consumer<T> finalizer) {
|
||||
LazyValue<T> result;
|
||||
switch (locking) {
|
||||
case SYNCHRONIZED:
|
||||
result = new SynchronizedLazyValue<>(supplier);
|
||||
result = new SynchronizedLazyValue<>(supplier, finalizer);
|
||||
break;
|
||||
case NONE:
|
||||
result = new UnsynchronizedLazyValue<>(supplier);
|
||||
result = new UnsynchronizedLazyValue<>(supplier, finalizer);
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("This should never happen");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static <T> LazyValue<T> of(Supplier<T> supplier, ThreadSafetyMode locking) {
|
||||
return of(supplier, locking, null);
|
||||
}
|
||||
}
|
||||
|
@@ -2,7 +2,12 @@ package net.woggioni.jwo;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Objects;
|
||||
|
||||
import static java.lang.Math.min;
|
||||
|
||||
|
@@ -1,39 +1,40 @@
|
||||
package net.woggioni.jwo;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.io.RandomAccessFile;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.FileLock;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.EnumSet;
|
||||
|
||||
public class LockFile implements AutoCloseable {
|
||||
public class LockFile implements Closeable {
|
||||
|
||||
private final Path path;
|
||||
private final FileLock lock;
|
||||
private final RandomAccessFile randomAccessFile;
|
||||
|
||||
public LockFile(Path path) {
|
||||
this(path, false);
|
||||
private static FileChannel openFileChannel(Path path) throws IOException {
|
||||
Files.createDirectories(path.getParent());
|
||||
return FileChannel.open(path, EnumSet.of(StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE));
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public LockFile(Path path, boolean shared) {
|
||||
this.path = path;
|
||||
try {
|
||||
Files.createDirectories(path.getParent());
|
||||
Files.createFile(path);
|
||||
} catch(FileAlreadyExistsException faee) {
|
||||
}
|
||||
randomAccessFile = new RandomAccessFile(path.toFile(), "rw");
|
||||
lock = randomAccessFile.getChannel().lock(0L, Long.MAX_VALUE, shared);
|
||||
public static LockFile acquire(Path path, boolean shared) throws IOException {
|
||||
FileChannel channel = openFileChannel(path);
|
||||
return new LockFile(channel.lock(0L, Long.MAX_VALUE, shared));
|
||||
}
|
||||
|
||||
public static LockFile tryAcquire(Path path, boolean shared) throws IOException {
|
||||
FileChannel channel = openFileChannel(path);
|
||||
FileLock lock = channel.tryLock(0L, Long.MAX_VALUE, shared);
|
||||
return (lock != null) ? new LockFile(lock) : null;
|
||||
}
|
||||
|
||||
private LockFile(FileLock lock) {
|
||||
this.lock = lock;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void close() {
|
||||
lock.release();
|
||||
randomAccessFile.close();
|
||||
public void close() throws IOException {
|
||||
lock.channel().close();
|
||||
}
|
||||
}
|
||||
|
@@ -2,7 +2,11 @@ package net.woggioni.jwo;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
@@ -5,6 +5,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class MapBuilder<K,V> {
|
||||
private final List<Map.Entry<K, V>> entries = new ArrayList<>();
|
||||
@@ -22,7 +23,15 @@ public class MapBuilder<K,V> {
|
||||
return finalizer.apply(result);
|
||||
}
|
||||
|
||||
public Stream<Map.Entry<K,V>> stream() {
|
||||
return entries.stream();
|
||||
}
|
||||
|
||||
public Map<K,V> build(Sup<Map<K,V>> factory) {
|
||||
return build(factory, Function.identity());
|
||||
}
|
||||
|
||||
public static <K, V> MapBuilder<K, V> with(K key, V value) {
|
||||
return new MapBuilder<K, V>().entry(key, value);
|
||||
}
|
||||
}
|
||||
|
701
src/main/java/net/woggioni/jwo/MavenVersion.java
Normal file
701
src/main/java/net/woggioni/jwo/MavenVersion.java
Normal file
@@ -0,0 +1,701 @@
|
||||
package net.woggioni.jwo;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Deque;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Generic implementation of version comparison. Shamelessly borrowed from
|
||||
* https://github.com/apache/maven/blob/master/maven-artifact/src/main/java/org/apache/maven/artifact/versioning/ComparableVersion.java
|
||||
* </p>
|
||||
*
|
||||
* Features:
|
||||
* <ul>
|
||||
* <li>mixing of '<code>-</code>' (hyphen) and '<code>.</code>' (dot) separators,</li>
|
||||
* <li>transition between characters and digits also constitutes a separator:
|
||||
* <code>1.0alpha1 => [1, 0, alpha, 1]</code></li>
|
||||
* <li>unlimited number of version components,</li>
|
||||
* <li>version components in the text can be digits or strings,</li>
|
||||
* <li>strings are checked for well-known qualifiers and the qualifier ordering is used for version ordering.
|
||||
* Well-known qualifiers (case insensitive) are:<ul>
|
||||
* <li><code>alpha</code> or <code>a</code></li>
|
||||
* <li><code>beta</code> or <code>b</code></li>
|
||||
* <li><code>milestone</code> or <code>m</code></li>
|
||||
* <li><code>rc</code> or <code>cr</code></li>
|
||||
* <li><code>snapshot</code></li>
|
||||
* <li><code>(the empty string)</code> or <code>ga</code> or <code>final</code></li>
|
||||
* <li><code>sp</code></li>
|
||||
* </ul>
|
||||
* Unknown qualifiers are considered after known qualifiers, with lexical order (always case insensitive),
|
||||
* </li>
|
||||
* <li>a hyphen usually precedes a qualifier, and is always less important than digits/number, for example
|
||||
* {@code 1.0.RC2 < 1.0-RC3 < 1.0.1}; but prefer {@code 1.0.0-RC1} over {@code 1.0.0.RC1}, and more
|
||||
* generally: {@code 1.0.X2 < 1.0-X3 < 1.0.1} for any string {@code X}; but prefer {@code 1.0.0-X1}
|
||||
* over {@code 1.0.0.X1}.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @see <a href="https://cwiki.apache.org/confluence/display/MAVENOLD/Versioning">"Versioning" on Maven Wiki</a>
|
||||
* @author <a href="mailto:kenney@apache.org">Kenney Westerhof</a>
|
||||
* @author <a href="mailto:hboutemy@apache.org">Hervè Boutemy</a>
|
||||
*/
|
||||
public class MavenVersion implements Comparable<MavenVersion> {
|
||||
private static final int MAX_INTITEM_LENGTH = 9;
|
||||
|
||||
private static final int MAX_LONGITEM_LENGTH = 18;
|
||||
|
||||
private String value;
|
||||
|
||||
private String canonical;
|
||||
|
||||
private ListItem items;
|
||||
|
||||
private interface Item {
|
||||
int INT_ITEM = 3;
|
||||
int LONG_ITEM = 4;
|
||||
int BIGINTEGER_ITEM = 0;
|
||||
int STRING_ITEM = 1;
|
||||
int LIST_ITEM = 2;
|
||||
|
||||
int compareTo(Item item);
|
||||
|
||||
int getType();
|
||||
|
||||
boolean isNull();
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a numeric item in the version item list that can be represented with an int.
|
||||
*/
|
||||
private static class IntItem implements Item {
|
||||
private final int value;
|
||||
|
||||
public static final IntItem ZERO = new IntItem();
|
||||
|
||||
private IntItem() {
|
||||
this.value = 0;
|
||||
}
|
||||
|
||||
IntItem(String str) {
|
||||
this.value = Integer.parseInt(str);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getType() {
|
||||
return INT_ITEM;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNull() {
|
||||
return value == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Item item) {
|
||||
if (item == null) {
|
||||
return (value == 0) ? 0 : 1; // 1.0 == 1, 1.1 > 1
|
||||
}
|
||||
|
||||
switch (item.getType()) {
|
||||
case INT_ITEM:
|
||||
int itemValue = ((IntItem) item).value;
|
||||
return Integer.compare(value, itemValue);
|
||||
case LONG_ITEM:
|
||||
case BIGINTEGER_ITEM:
|
||||
return -1;
|
||||
|
||||
case STRING_ITEM:
|
||||
return 1; // 1.1 > 1-sp
|
||||
|
||||
case LIST_ITEM:
|
||||
return 1; // 1.1 > 1-1
|
||||
|
||||
default:
|
||||
throw new IllegalStateException("invalid item: " + item.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
IntItem intItem = (IntItem) o;
|
||||
|
||||
return value == intItem.value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Integer.toString(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a numeric item in the version item list that can be represented with a long.
|
||||
*/
|
||||
private static class LongItem implements Item {
|
||||
private final long value;
|
||||
|
||||
LongItem(String str) {
|
||||
this.value = Long.parseLong(str);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getType() {
|
||||
return LONG_ITEM;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNull() {
|
||||
return value == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Item item) {
|
||||
if (item == null) {
|
||||
return (value == 0) ? 0 : 1; // 1.0 == 1, 1.1 > 1
|
||||
}
|
||||
|
||||
switch (item.getType()) {
|
||||
case INT_ITEM:
|
||||
return 1;
|
||||
case LONG_ITEM:
|
||||
long itemValue = ((LongItem) item).value;
|
||||
return Long.compare(value, itemValue);
|
||||
case BIGINTEGER_ITEM:
|
||||
return -1;
|
||||
|
||||
case STRING_ITEM:
|
||||
return 1; // 1.1 > 1-sp
|
||||
|
||||
case LIST_ITEM:
|
||||
return 1; // 1.1 > 1-1
|
||||
|
||||
default:
|
||||
throw new IllegalStateException("invalid item: " + item.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
LongItem longItem = (LongItem) o;
|
||||
|
||||
return value == longItem.value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return (int) (value ^ (value >>> 32));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Long.toString(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a numeric item in the version item list.
|
||||
*/
|
||||
private static class BigIntegerItem implements Item {
|
||||
private final BigInteger value;
|
||||
|
||||
BigIntegerItem(String str) {
|
||||
this.value = new BigInteger(str);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getType() {
|
||||
return BIGINTEGER_ITEM;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNull() {
|
||||
return BigInteger.ZERO.equals(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Item item) {
|
||||
if (item == null) {
|
||||
return BigInteger.ZERO.equals(value) ? 0 : 1; // 1.0 == 1, 1.1 > 1
|
||||
}
|
||||
|
||||
switch (item.getType()) {
|
||||
case INT_ITEM:
|
||||
case LONG_ITEM:
|
||||
return 1;
|
||||
|
||||
case BIGINTEGER_ITEM:
|
||||
return value.compareTo(((BigIntegerItem) item).value);
|
||||
|
||||
case STRING_ITEM:
|
||||
return 1; // 1.1 > 1-sp
|
||||
|
||||
case LIST_ITEM:
|
||||
return 1; // 1.1 > 1-1
|
||||
|
||||
default:
|
||||
throw new IllegalStateException("invalid item: " + item.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
BigIntegerItem that = (BigIntegerItem) o;
|
||||
|
||||
return value.equals(that.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return value.hashCode();
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return value.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a string in the version item list, usually a qualifier.
|
||||
*/
|
||||
private static class StringItem implements Item {
|
||||
private static final List<String> QUALIFIERS =
|
||||
Arrays.asList("alpha", "beta", "milestone", "rc", "snapshot", "", "sp");
|
||||
|
||||
private static final Properties ALIASES = new Properties();
|
||||
|
||||
static {
|
||||
ALIASES.put("ga", "");
|
||||
ALIASES.put("final", "");
|
||||
ALIASES.put("release", "");
|
||||
ALIASES.put("cr", "rc");
|
||||
}
|
||||
|
||||
/**
|
||||
* A comparable value for the empty-string qualifier. This one is used to determine if a given qualifier makes
|
||||
* the version older than one without a qualifier, or more recent.
|
||||
*/
|
||||
private static final String RELEASE_VERSION_INDEX = String.valueOf(QUALIFIERS.indexOf(""));
|
||||
|
||||
private final String value;
|
||||
|
||||
StringItem(String value, boolean followedByDigit) {
|
||||
if (followedByDigit && value.length() == 1) {
|
||||
// a1 = alpha-1, b1 = beta-1, m1 = milestone-1
|
||||
switch (value.charAt(0)) {
|
||||
case 'a':
|
||||
value = "alpha";
|
||||
break;
|
||||
case 'b':
|
||||
value = "beta";
|
||||
break;
|
||||
case 'm':
|
||||
value = "milestone";
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
this.value = ALIASES.getProperty(value, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getType() {
|
||||
return STRING_ITEM;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNull() {
|
||||
return (comparableQualifier(value).compareTo(RELEASE_VERSION_INDEX) == 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a comparable value for a qualifier.
|
||||
*
|
||||
* This method takes into account the ordering of known qualifiers then unknown qualifiers with lexical
|
||||
* ordering.
|
||||
*
|
||||
* just returning an Integer with the index here is faster, but requires a lot of if/then/else to check for -1
|
||||
* or QUALIFIERS.size and then resort to lexical ordering. Most comparisons are decided by the first character,
|
||||
* so this is still fast. If more characters are needed then it requires a lexical sort anyway.
|
||||
*
|
||||
* @param qualifier
|
||||
* @return an equivalent value that can be used with lexical comparison
|
||||
*/
|
||||
public static String comparableQualifier(String qualifier) {
|
||||
int i = QUALIFIERS.indexOf(qualifier);
|
||||
|
||||
return i == -1 ? (QUALIFIERS.size() + "-" + qualifier) : String.valueOf(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Item item) {
|
||||
if (item == null) {
|
||||
// 1-rc < 1, 1-ga > 1
|
||||
return comparableQualifier(value).compareTo(RELEASE_VERSION_INDEX);
|
||||
}
|
||||
switch (item.getType()) {
|
||||
case INT_ITEM:
|
||||
case LONG_ITEM:
|
||||
case BIGINTEGER_ITEM:
|
||||
return -1; // 1.any < 1.1 ?
|
||||
|
||||
case STRING_ITEM:
|
||||
return comparableQualifier(value).compareTo(comparableQualifier(((StringItem) item).value));
|
||||
|
||||
case LIST_ITEM:
|
||||
return -1; // 1.any < 1-1
|
||||
|
||||
default:
|
||||
throw new IllegalStateException("invalid item: " + item.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
StringItem that = (StringItem) o;
|
||||
|
||||
return value.equals(that.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return value.hashCode();
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a version list item. This class is used both for the global item list and for sub-lists (which start
|
||||
* with '-(number)' in the version specification).
|
||||
*/
|
||||
private static class ListItem extends ArrayList<Item> implements Item {
|
||||
@Override
|
||||
public int getType() {
|
||||
return LIST_ITEM;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNull() {
|
||||
return (size() == 0);
|
||||
}
|
||||
|
||||
void normalize() {
|
||||
for (int i = size() - 1; i >= 0; i--) {
|
||||
Item lastItem = get(i);
|
||||
|
||||
if (lastItem.isNull()) {
|
||||
// remove null trailing items: 0, "", empty list
|
||||
remove(i);
|
||||
} else if (!(lastItem instanceof ListItem)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Item item) {
|
||||
if (item == null) {
|
||||
if (size() == 0) {
|
||||
return 0; // 1-0 = 1- (normalize) = 1
|
||||
}
|
||||
// Compare the entire list of items with null - not just the first one, MNG-6964
|
||||
for (Item i : this) {
|
||||
int result = i.compareTo(null);
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
switch (item.getType()) {
|
||||
case INT_ITEM:
|
||||
case LONG_ITEM:
|
||||
case BIGINTEGER_ITEM:
|
||||
return -1; // 1-1 < 1.0.x
|
||||
|
||||
case STRING_ITEM:
|
||||
return 1; // 1-1 > 1-sp
|
||||
|
||||
case LIST_ITEM:
|
||||
Iterator<Item> left = iterator();
|
||||
Iterator<Item> right = ((ListItem) item).iterator();
|
||||
|
||||
while (left.hasNext() || right.hasNext()) {
|
||||
Item l = left.hasNext() ? left.next() : null;
|
||||
Item r = right.hasNext() ? right.next() : null;
|
||||
|
||||
// if this is shorter, then invert the compare and mul with -1
|
||||
int result = l == null ? (r == null ? 0 : -1 * r.compareTo(l)) : l.compareTo(r);
|
||||
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
default:
|
||||
throw new IllegalStateException("invalid item: " + item.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
for (Item item : this) {
|
||||
if (buffer.length() > 0) {
|
||||
buffer.append((item instanceof ListItem) ? '-' : '.');
|
||||
}
|
||||
buffer.append(item);
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the contents in the same format that is used when you call toString() on a List.
|
||||
*/
|
||||
private String toListString() {
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
buffer.append("[");
|
||||
for (Item item : this) {
|
||||
if (buffer.length() > 1) {
|
||||
buffer.append(", ");
|
||||
}
|
||||
if (item instanceof ListItem) {
|
||||
buffer.append(((ListItem) item).toListString());
|
||||
} else {
|
||||
buffer.append(item);
|
||||
}
|
||||
}
|
||||
buffer.append("]");
|
||||
return buffer.toString();
|
||||
}
|
||||
}
|
||||
|
||||
public MavenVersion(String version) {
|
||||
parseVersion(version);
|
||||
}
|
||||
|
||||
@SuppressWarnings("checkstyle:innerassignment")
|
||||
public final void parseVersion(String version) {
|
||||
this.value = version;
|
||||
|
||||
items = new ListItem();
|
||||
|
||||
version = version.toLowerCase(Locale.ENGLISH);
|
||||
|
||||
ListItem list = items;
|
||||
|
||||
Deque<Item> stack = new ArrayDeque<>();
|
||||
stack.push(list);
|
||||
|
||||
boolean isDigit = false;
|
||||
|
||||
int startIndex = 0;
|
||||
|
||||
for (int i = 0; i < version.length(); i++) {
|
||||
char c = version.charAt(i);
|
||||
|
||||
if (c == '.') {
|
||||
if (i == startIndex) {
|
||||
list.add(IntItem.ZERO);
|
||||
} else {
|
||||
list.add(parseItem(isDigit, version.substring(startIndex, i)));
|
||||
}
|
||||
startIndex = i + 1;
|
||||
} else if (c == '-') {
|
||||
if (i == startIndex) {
|
||||
list.add(IntItem.ZERO);
|
||||
} else {
|
||||
list.add(parseItem(isDigit, version.substring(startIndex, i)));
|
||||
}
|
||||
startIndex = i + 1;
|
||||
|
||||
list.add(list = new ListItem());
|
||||
stack.push(list);
|
||||
} else if (Character.isDigit(c)) {
|
||||
if (!isDigit && i > startIndex) {
|
||||
// 1.0.0.X1 < 1.0.0-X2
|
||||
// treat .X as -X for any string qualifier X
|
||||
if (!list.isEmpty()) {
|
||||
list.add(list = new ListItem());
|
||||
stack.push(list);
|
||||
}
|
||||
|
||||
list.add(new StringItem(version.substring(startIndex, i), true));
|
||||
startIndex = i;
|
||||
|
||||
list.add(list = new ListItem());
|
||||
stack.push(list);
|
||||
}
|
||||
|
||||
isDigit = true;
|
||||
} else {
|
||||
if (isDigit && i > startIndex) {
|
||||
list.add(parseItem(true, version.substring(startIndex, i)));
|
||||
startIndex = i;
|
||||
|
||||
list.add(list = new ListItem());
|
||||
stack.push(list);
|
||||
}
|
||||
|
||||
isDigit = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (version.length() > startIndex) {
|
||||
// 1.0.0.X1 < 1.0.0-X2
|
||||
// treat .X as -X for any string qualifier X
|
||||
if (!isDigit && !list.isEmpty()) {
|
||||
list.add(list = new ListItem());
|
||||
stack.push(list);
|
||||
}
|
||||
|
||||
list.add(parseItem(isDigit, version.substring(startIndex)));
|
||||
}
|
||||
|
||||
while (!stack.isEmpty()) {
|
||||
list = (ListItem) stack.pop();
|
||||
list.normalize();
|
||||
}
|
||||
}
|
||||
|
||||
private static Item parseItem(boolean isDigit, String buf) {
|
||||
if (isDigit) {
|
||||
buf = stripLeadingZeroes(buf);
|
||||
if (buf.length() <= MAX_INTITEM_LENGTH) {
|
||||
// lower than 2^31
|
||||
return new IntItem(buf);
|
||||
} else if (buf.length() <= MAX_LONGITEM_LENGTH) {
|
||||
// lower than 2^63
|
||||
return new LongItem(buf);
|
||||
}
|
||||
return new BigIntegerItem(buf);
|
||||
}
|
||||
return new StringItem(buf, false);
|
||||
}
|
||||
|
||||
private static String stripLeadingZeroes(String buf) {
|
||||
if (buf == null || buf.isEmpty()) {
|
||||
return "0";
|
||||
}
|
||||
for (int i = 0; i < buf.length(); ++i) {
|
||||
char c = buf.charAt(i);
|
||||
if (c != '0') {
|
||||
return buf.substring(i);
|
||||
}
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(MavenVersion o) {
|
||||
return items.compareTo(o.items);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public String getCanonical() {
|
||||
if (canonical == null) {
|
||||
canonical = items.toString();
|
||||
}
|
||||
return canonical;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return (o instanceof MavenVersion) && items.equals(((MavenVersion) o).items);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return items.hashCode();
|
||||
}
|
||||
|
||||
// CHECKSTYLE_OFF: LineLength
|
||||
/**
|
||||
* Main to test version parsing and comparison.
|
||||
* <p>
|
||||
* To check how "1.2.7" compares to "1.2-SNAPSHOT", for example, you can issue
|
||||
* <pre>java -jar ${maven.repo.local}/org/apache/maven/maven-artifact/${maven.version}/maven-artifact-${maven.version}.jar "1.2.7" "1.2-SNAPSHOT"</pre>
|
||||
* command to command line. Result of given command will be something like this:
|
||||
* <pre>
|
||||
* Display parameters as parsed by Maven (in canonical form) and comparison result:
|
||||
* 1. 1.2.7 == 1.2.7
|
||||
* 1.2.7 > 1.2-SNAPSHOT
|
||||
* 2. 1.2-SNAPSHOT == 1.2-snapshot
|
||||
* </pre>
|
||||
*
|
||||
* @param args the version strings to parse and compare. You can pass arbitrary number of version strings and always
|
||||
* two adjacent will be compared
|
||||
*/
|
||||
// CHECKSTYLE_ON: LineLength
|
||||
public static void main(String... args) {
|
||||
System.out.println("Display parameters as parsed by Maven (in canonical form and as a list of tokens) and"
|
||||
+ " comparison result:");
|
||||
if (args.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
MavenVersion prev = null;
|
||||
int i = 1;
|
||||
for (String version : args) {
|
||||
MavenVersion c = new MavenVersion(version);
|
||||
|
||||
if (prev != null) {
|
||||
int compare = prev.compareTo(c);
|
||||
System.out.println(" " + prev.toString() + ' ' + ((compare == 0) ? "==" : ((compare < 0) ? "<" : ">"))
|
||||
+ ' ' + version);
|
||||
}
|
||||
|
||||
System.out.println(
|
||||
(i++) + ". " + version + " -> " + c.getCanonical() + "; tokens: " + c.items.toListString());
|
||||
|
||||
prev = c;
|
||||
}
|
||||
}
|
||||
}
|
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() {
|
||||
}
|
||||
}
|
@@ -11,9 +11,17 @@ import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.net.URLStreamHandler;
|
||||
import java.nio.file.*;
|
||||
import java.nio.file.FileVisitResult;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.SimpleFileVisitor;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A classloader that loads classes from a {@link Path} instance
|
||||
|
@@ -30,7 +30,7 @@ public class RetryExecutor {
|
||||
private final ExceptionHandler exceptionHandler = err -> ExceptionHandlerOutcome.CONTINUE;
|
||||
|
||||
@Builder.Default
|
||||
private final ExecutorService executorService = ForkJoinPool.commonPool();
|
||||
private final Executor executor = ForkJoinPool.commonPool();
|
||||
|
||||
public enum ExceptionHandlerOutcome {
|
||||
THROW, CONTINUE
|
||||
@@ -41,7 +41,7 @@ public class RetryExecutor {
|
||||
}
|
||||
|
||||
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(
|
||||
@@ -79,36 +79,36 @@ public class RetryExecutor {
|
||||
Duration initialDelay,
|
||||
Double exp,
|
||||
ExceptionHandler exceptionHandler,
|
||||
ExecutorService executorService) {
|
||||
Executor executor) {
|
||||
return submit(() -> {
|
||||
cb.run();
|
||||
return null;
|
||||
}, maxAttempts, initialDelay, exp, exceptionHandler, executorService);
|
||||
}, maxAttempts, initialDelay, exp, exceptionHandler, executor);
|
||||
}
|
||||
|
||||
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(
|
||||
Callable<T> cb,
|
||||
ExecutorService executorService) {
|
||||
return submit(cb, maxAttempts, initialDelay, exp, exceptionHandler, executorService);
|
||||
Executor executor) {
|
||||
return submit(cb, maxAttempts, initialDelay, exp, exceptionHandler, executor);
|
||||
}
|
||||
|
||||
public <T> CompletableFuture<T> submit(
|
||||
Callable<T> cb,
|
||||
ExceptionHandler exceptionHandler,
|
||||
ExecutorService executorService) {
|
||||
return submit(cb, maxAttempts, initialDelay, exp, exceptionHandler, executorService);
|
||||
Executor executor) {
|
||||
return submit(cb, maxAttempts, initialDelay, exp, exceptionHandler, executor);
|
||||
}
|
||||
|
||||
public <T> CompletableFuture<T> submit(
|
||||
Callable<T> cb,
|
||||
Double exp,
|
||||
ExceptionHandler exceptionHandler,
|
||||
ExecutorService executorService) {
|
||||
return submit(cb, maxAttempts, initialDelay, exp, exceptionHandler, executorService);
|
||||
ExecutorService executor) {
|
||||
return submit(cb, maxAttempts, initialDelay, exp, exceptionHandler, executor);
|
||||
}
|
||||
|
||||
public <T> CompletableFuture<T> submit(
|
||||
@@ -116,8 +116,8 @@ public class RetryExecutor {
|
||||
Duration initialDelay,
|
||||
Double exp,
|
||||
ExceptionHandler exceptionHandler,
|
||||
ExecutorService executorService) {
|
||||
return submit(cb, maxAttempts, initialDelay, exp, exceptionHandler, executorService);
|
||||
ExecutorService executor) {
|
||||
return submit(cb, maxAttempts, initialDelay, exp, exceptionHandler, executor);
|
||||
}
|
||||
|
||||
public static <T> CompletableFuture<T> submit(
|
||||
@@ -126,8 +126,8 @@ public class RetryExecutor {
|
||||
Duration initialDelay,
|
||||
Double exp,
|
||||
ExceptionHandler exceptionHandler,
|
||||
ExecutorService executorService) {
|
||||
CompletableFuture<T> result = CompletableFuture.supplyAsync((Sup<T>) cb::call, executorService);
|
||||
Executor executor) {
|
||||
CompletableFuture<T> result = CompletableFuture.supplyAsync((Sup<T>) cb::call, executor);
|
||||
double delay = initialDelay.toMillis();
|
||||
for(int i = 1; i <= maxAttempts; i++) {
|
||||
int attempt = i;
|
||||
@@ -148,7 +148,7 @@ public class RetryExecutor {
|
||||
Executor delayedExecutor = delayedExecutor(
|
||||
(long) thisAttemptDelay,
|
||||
TimeUnit.MILLISECONDS,
|
||||
executorService
|
||||
executor
|
||||
);
|
||||
return CompletableFuture.supplyAsync((Sup<T>) cb::call, delayedExecutor);
|
||||
default:
|
||||
@@ -159,9 +159,9 @@ public class RetryExecutor {
|
||||
);
|
||||
}
|
||||
}
|
||||
}, executorService).thenComposeAsync(Function.identity(), executorService);
|
||||
}, executor).thenComposeAsync(Function.identity(), executor);
|
||||
delay *= exp;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
@@ -10,5 +10,5 @@ public interface Run extends Runnable {
|
||||
exec();
|
||||
}
|
||||
|
||||
boolean exec() throws Throwable;
|
||||
void exec() throws Throwable;
|
||||
}
|
||||
|
96
src/main/java/net/woggioni/jwo/SQL.java
Normal file
96
src/main/java/net/woggioni/jwo/SQL.java
Normal file
@@ -0,0 +1,96 @@
|
||||
package net.woggioni.jwo;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import static net.woggioni.jwo.JWO.newThrowable;
|
||||
|
||||
public class SQL {
|
||||
|
||||
public enum Operation {
|
||||
INSERT
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public static class QueryBuilder {
|
||||
private final Operation operation;
|
||||
private final String tableName;
|
||||
|
||||
private final Map<String, Tuple2<Object, Class<?>>> fields = new TreeMap<>();
|
||||
|
||||
public QueryBuilder field(String name, Object value, Class<?> cls) {
|
||||
fields.put(name, Tuple2.newInstance(value, cls));
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryBuilder field(String name, Object value) {
|
||||
if(value == null) {
|
||||
throw newThrowable(IllegalArgumentException.class, "Class argument required for null value");
|
||||
}
|
||||
return field(name, value, value.getClass());
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public PreparedStatement buildStatement(Connection conn) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
switch (operation) {
|
||||
case INSERT:
|
||||
sb.append("INSERT INTO ");
|
||||
sb.append(tableName);
|
||||
sb.append(" (");
|
||||
int i = 0;
|
||||
List<Map.Entry<String, Tuple2<Object, Class<?>>>> entries = new ArrayList<>(fields.entrySet());
|
||||
for(Map.Entry<String, Tuple2<Object, Class<?>>> entry : entries) {
|
||||
if(i++ > 0) sb.append(',');
|
||||
sb.append(entry.getKey());
|
||||
}
|
||||
sb.append(") VALUES(");
|
||||
while(i-->0) {
|
||||
sb.append("?");
|
||||
if(i > 0) sb.append(',');
|
||||
}
|
||||
sb.append(");");
|
||||
PreparedStatement stmt = conn.prepareStatement(sb.toString());
|
||||
i = 1;
|
||||
for(Map.Entry<String, Tuple2<Object, Class<?>>> entry : entries) {
|
||||
Tuple2<Object, Class<?>> tuple2 = entry.getValue();
|
||||
Object value = tuple2.get_1();
|
||||
Class<?> cls = tuple2.get_2();
|
||||
if(cls.isAssignableFrom(String.class)) {
|
||||
stmt.setString(i, (String) value);
|
||||
} else if(cls.isAssignableFrom(Integer.class)) {
|
||||
stmt.setInt(i, (Integer) value);
|
||||
} else if(cls.isAssignableFrom(Double.class)) {
|
||||
stmt.setDouble(i, (Double) value);
|
||||
} else if(cls.isAssignableFrom(Float.class)) {
|
||||
stmt.setFloat(i, (Float) value);
|
||||
} else if(cls.isAssignableFrom(Short.class)) {
|
||||
stmt.setShort(i, (Short) value);
|
||||
} else if(cls.isAssignableFrom(Byte.class)) {
|
||||
stmt.setByte(i, (Byte) value);
|
||||
} else if(cls.isAssignableFrom(Boolean.class)) {
|
||||
stmt.setBoolean(i, (Boolean) value);
|
||||
} else if(cls.isAssignableFrom(Timestamp.class)) {
|
||||
stmt.setTimestamp(i, (Timestamp) value);
|
||||
} else {
|
||||
throw newThrowable(IllegalArgumentException.class, "Class '%s' is not supported",
|
||||
value.getClass());
|
||||
}
|
||||
++i;
|
||||
}
|
||||
return stmt;
|
||||
default:
|
||||
throw new RuntimeException("This should never happen");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -1,8 +1,9 @@
|
||||
package net.woggioni.jwo;
|
||||
|
||||
import net.woggioni.jwo.internal.Tuple2Impl;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.function.Function;
|
||||
import net.woggioni.jwo.internal.Tuple2Impl;
|
||||
|
||||
public interface Tuple2<T, U> {
|
||||
T get_1();
|
||||
|
@@ -1,8 +1,9 @@
|
||||
package net.woggioni.jwo;
|
||||
|
||||
import java.util.Comparator;
|
||||
import net.woggioni.jwo.internal.Tuple3Impl;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
public interface Tuple3<T, U, V> {
|
||||
T get_1();
|
||||
U get_2();
|
||||
|
14
src/main/java/net/woggioni/jwo/UncloseableWriter.java
Normal file
14
src/main/java/net/woggioni/jwo/UncloseableWriter.java
Normal file
@@ -0,0 +1,14 @@
|
||||
package net.woggioni.jwo;
|
||||
|
||||
import java.io.FilterWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
|
||||
public class UncloseableWriter extends FilterWriter {
|
||||
public UncloseableWriter(Writer destination) {
|
||||
super(destination);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {}
|
||||
}
|
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.MapMergeStrategy.REPLACE
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.Data;
|
||||
import net.woggioni.jwo.MutableTuple2;
|
||||
|
||||
@EqualsAndHashCode
|
||||
@NoArgsConstructor
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class MutableTuple2Impl<T, U> implements MutableTuple2<T, U> {
|
||||
@Getter
|
||||
@Setter
|
||||
private T _1;
|
||||
|
||||
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private U _2;
|
||||
}
|
@@ -1,25 +1,15 @@
|
||||
package net.woggioni.jwo.internal;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.Data;
|
||||
import net.woggioni.jwo.MutableTuple3;
|
||||
|
||||
@EqualsAndHashCode
|
||||
@NoArgsConstructor
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class MutableTuple3Impl<T, U, V> implements MutableTuple3<T, U, V> {
|
||||
@Getter
|
||||
@Setter
|
||||
private T _1;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private U _2;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private V _3;
|
||||
}
|
@@ -1,17 +1,12 @@
|
||||
package net.woggioni.jwo.internal;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Data;
|
||||
import net.woggioni.jwo.Tuple2;
|
||||
|
||||
@EqualsAndHashCode
|
||||
@RequiredArgsConstructor
|
||||
@Data
|
||||
public class Tuple2Impl<T, U> implements Tuple2<T, U> {
|
||||
@Getter
|
||||
private final T _1;
|
||||
|
||||
@Getter
|
||||
private final U _2;
|
||||
}
|
||||
|
||||
|
24
src/main/java/net/woggioni/jwo/url/classpath/Handler.java
Normal file
24
src/main/java/net/woggioni/jwo/url/classpath/Handler.java
Normal file
@@ -0,0 +1,24 @@
|
||||
package net.woggioni.jwo.url.classpath;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.net.URLStreamHandler;
|
||||
|
||||
public class Handler extends URLStreamHandler {
|
||||
private final ClassLoader classLoader;
|
||||
|
||||
public Handler() {
|
||||
this.classLoader = getClass().getClassLoader();
|
||||
}
|
||||
|
||||
public Handler(ClassLoader classLoader) {
|
||||
this.classLoader = classLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected URLConnection openConnection(URL u) throws IOException {
|
||||
final URL resourceUrl = classLoader.getResource(u.getPath());
|
||||
return resourceUrl.openConnection();
|
||||
}
|
||||
}
|
102
src/main/java/net/woggioni/jwo/xml/DocumentWalker.java
Normal file
102
src/main/java/net/woggioni/jwo/xml/DocumentWalker.java
Normal file
@@ -0,0 +1,102 @@
|
||||
package net.woggioni.jwo.xml;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@Getter
|
||||
class StackElement {
|
||||
|
||||
@Setter
|
||||
private XMLNodeVisitor.NodeVisitResultPre resultPre;
|
||||
private final Node node;
|
||||
private final NodeListIterator iterator;
|
||||
|
||||
StackElement(Node node) {
|
||||
this.resultPre = null;
|
||||
this.node = node;
|
||||
this.iterator = new NodeListIterator(node.getChildNodes());
|
||||
}
|
||||
}
|
||||
|
||||
class Stack {
|
||||
private final List<StackElement> stack = new ArrayList<>();
|
||||
|
||||
private final List<Node> nodeStack = new ArrayList<>();
|
||||
private final List<Node> nodes = Collections.unmodifiableList(nodeStack);
|
||||
|
||||
|
||||
public void push(Node node) {
|
||||
stack.add(new StackElement(node));
|
||||
nodeStack.add(node);
|
||||
}
|
||||
|
||||
public StackElement last() {
|
||||
return stack.get(stack.size() - 1);
|
||||
}
|
||||
|
||||
public StackElement pop() {
|
||||
nodeStack.remove(nodeStack.size() -1);
|
||||
return stack.remove(stack.size() - 1);
|
||||
}
|
||||
|
||||
public List<Node> nodes() {
|
||||
return nodes;
|
||||
}
|
||||
|
||||
public boolean isNotEmpty() {
|
||||
return !stack.isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class DocumentWalker {
|
||||
|
||||
public static void walk(Node root, XMLNodeVisitor visitor) {
|
||||
new DocumentWalker(root).walk(visitor);
|
||||
}
|
||||
|
||||
private final Node root;
|
||||
|
||||
public void walk(XMLNodeVisitor visitor) {
|
||||
Stack stack = new Stack();
|
||||
stack.push(root);
|
||||
|
||||
loop:
|
||||
while(stack.isNotEmpty()) {
|
||||
StackElement se = stack.last();
|
||||
if(se.getIterator().hasNext()) {
|
||||
Node childNode = se.getIterator().next();
|
||||
XMLNodeVisitor.NodeVisitResultPre result = se.getResultPre();
|
||||
if(result == null) {
|
||||
result = visitor.visitNodePre(stack.nodes());
|
||||
se.setResultPre(result);
|
||||
}
|
||||
switch (result) {
|
||||
case CONTINUE:
|
||||
stack.push(childNode);
|
||||
break;
|
||||
case SKIP_SUBTREE:
|
||||
break;
|
||||
case END_TRAVERSAL:
|
||||
break loop;
|
||||
}
|
||||
} else {
|
||||
XMLNodeVisitor.NodeVisitResultPost result = visitor.visitNodePost(stack.nodes());
|
||||
stack.pop();
|
||||
switch (result) {
|
||||
case CONTINUE:
|
||||
break;
|
||||
case END_TRAVERSAL:
|
||||
break loop;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
77
src/main/java/net/woggioni/jwo/xml/ElementBuilder.java
Normal file
77
src/main/java/net/woggioni/jwo/xml/ElementBuilder.java
Normal file
@@ -0,0 +1,77 @@
|
||||
package net.woggioni.jwo.xml;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.woggioni.jwo.MapBuilder;
|
||||
import net.woggioni.jwo.Tuple2;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class ElementBuilder {
|
||||
|
||||
private final Document doc;
|
||||
|
||||
@Getter
|
||||
private final Element root;
|
||||
|
||||
public ElementBuilder node(String name) {
|
||||
return node(name, eb -> {
|
||||
});
|
||||
}
|
||||
|
||||
public ElementBuilder node(String name, Consumer<ElementBuilder> cb) {
|
||||
Element child = doc.createElement(name);
|
||||
if(root == null) {
|
||||
doc.appendChild(child);
|
||||
} else {
|
||||
root.appendChild(child);
|
||||
}
|
||||
ElementBuilder eb = new ElementBuilder(doc, child);
|
||||
cb.accept(eb);
|
||||
return eb;
|
||||
}
|
||||
|
||||
public final ElementBuilder node(String name, String textContent, Map<String, String> attrs) {
|
||||
return node(name, eb -> {
|
||||
if(textContent != null) eb.text(textContent);
|
||||
for(Map.Entry<String, String> attr : attrs.entrySet()) {
|
||||
eb.attr(attr.getKey(), attr.getValue());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public final ElementBuilder node(String name, String textContent, Tuple2<String, String>...attrs) {
|
||||
MapBuilder<String, String> mapBuilder = new MapBuilder<>();
|
||||
for(Tuple2<String, String> attr : attrs) {
|
||||
mapBuilder.entry(attr.get_1(), attr.get_2());
|
||||
}
|
||||
return node(name, textContent, mapBuilder.build(TreeMap::new));
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public final ElementBuilder node(String name, Tuple2<String, String>...attrs) {
|
||||
return node(name, null, attrs);
|
||||
}
|
||||
|
||||
public final ElementBuilder node(String name, Map<String, String> attrs) {
|
||||
return node(name, null, attrs);
|
||||
}
|
||||
|
||||
public ElementBuilder text(String textContent) {
|
||||
root.setTextContent(textContent);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ElementBuilder attr(String name, String value) {
|
||||
root.setAttribute(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
}
|
49
src/main/java/net/woggioni/jwo/xml/ElementIterator.java
Normal file
49
src/main/java/net/woggioni/jwo/xml/ElementIterator.java
Normal file
@@ -0,0 +1,49 @@
|
||||
package net.woggioni.jwo.xml;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Objects;
|
||||
|
||||
public class ElementIterator implements Iterator<Element> {
|
||||
private final NodeListIterator it;
|
||||
private final String name;
|
||||
private Element next;
|
||||
|
||||
public ElementIterator(Element parent) {
|
||||
this(parent, null);
|
||||
}
|
||||
|
||||
public ElementIterator(Element parent, String name) {
|
||||
it = new NodeListIterator(parent.getChildNodes());
|
||||
this.name = name;
|
||||
next = getNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return next != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Element next() {
|
||||
if(next == null) throw new NoSuchElementException();
|
||||
Element result = next;
|
||||
next = getNext();
|
||||
return result;
|
||||
}
|
||||
|
||||
private Element getNext() {
|
||||
Element result = null;
|
||||
while(it.hasNext()) {
|
||||
Node node = it.next();
|
||||
if(node instanceof Element && (name == null || Objects.equals(name, ((Element) node).getTagName()))) {
|
||||
result = (Element) node;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
27
src/main/java/net/woggioni/jwo/xml/NodeListIterator.java
Normal file
27
src/main/java/net/woggioni/jwo/xml/NodeListIterator.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package net.woggioni.jwo.xml;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class NodeListIterator implements Iterator<Node> {
|
||||
|
||||
private final NodeList nodeList;
|
||||
|
||||
private int cursor = 0;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return cursor < nodeList.getLength();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node next() {
|
||||
if(hasNext()) return nodeList.item(cursor++);
|
||||
else throw new NoSuchElementException();
|
||||
}
|
||||
}
|
48
src/main/java/net/woggioni/jwo/xml/XMLNodeVisitor.java
Normal file
48
src/main/java/net/woggioni/jwo/xml/XMLNodeVisitor.java
Normal file
@@ -0,0 +1,48 @@
|
||||
package net.woggioni.jwo.xml;
|
||||
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public interface XMLNodeVisitor {
|
||||
default NodeVisitResultPre visitNodePre(List<Node> stack) {
|
||||
return NodeVisitResultPre.CONTINUE;
|
||||
}
|
||||
|
||||
default NodeVisitResultPost visitNodePost(List<Node> stack) {
|
||||
return NodeVisitResultPost.CONTINUE;
|
||||
}
|
||||
|
||||
enum NodeVisitResultPre {
|
||||
CONTINUE, SKIP_SUBTREE, END_TRAVERSAL
|
||||
}
|
||||
|
||||
enum NodeVisitResultPost {
|
||||
CONTINUE, END_TRAVERSAL
|
||||
}
|
||||
|
||||
static boolean stackMatches(List<Node> nodes, String... names) {
|
||||
return stackMatches(nodes, false, names);
|
||||
}
|
||||
|
||||
static boolean stackSame(List<Node> nodes, String... names) {
|
||||
return stackMatches(nodes, true, names);
|
||||
}
|
||||
|
||||
static boolean stackMatches(List<Node> nodes, boolean strict, String... names) {
|
||||
if(nodes.size() < names.length) return false;
|
||||
int nameIndex = 0;
|
||||
int nodeIndex = 0;
|
||||
while(nameIndex < names.length) {
|
||||
if(nodeIndex >= nodes.size()) return false;
|
||||
Node node = nodes.get(nodeIndex++);
|
||||
if(!strict && node.getNodeType() != Node.ELEMENT_NODE) continue;
|
||||
String name = names[nameIndex++];
|
||||
if(name != null &&
|
||||
node.getNodeType() == Node.ELEMENT_NODE &&
|
||||
!Objects.equals(name, node.getNodeName())) return false;
|
||||
}
|
||||
return !strict || (nodeIndex == nodes.size() - 1);
|
||||
}
|
||||
}
|
91
src/main/java/net/woggioni/jwo/xml/Xml.java
Normal file
91
src/main/java/net/woggioni/jwo/xml/Xml.java
Normal file
@@ -0,0 +1,91 @@
|
||||
package net.woggioni.jwo.xml;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import net.woggioni.jwo.Con;
|
||||
import net.woggioni.jwo.Fun;
|
||||
import net.woggioni.jwo.JWO;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Spliterator;
|
||||
import java.util.Spliterators;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class Xml {
|
||||
|
||||
public static Iterator<Node> iterator(NodeList node) {
|
||||
return new NodeListIterator(node);
|
||||
}
|
||||
|
||||
public static Iterator<Element> iterator(Element element, String tagName) {
|
||||
return new ElementIterator(element, tagName);
|
||||
}
|
||||
|
||||
public static Iterator<Element> iterator(Element element) {
|
||||
return new ElementIterator(element);
|
||||
}
|
||||
|
||||
public static Spliterator<Node> spliterator(NodeList node) {
|
||||
return Spliterators.spliteratorUnknownSize(iterator(node), 0);
|
||||
}
|
||||
|
||||
public static Spliterator<Element> spliterator(Element element) {
|
||||
return Spliterators.spliteratorUnknownSize(iterator(element), 0);
|
||||
}
|
||||
|
||||
public static Stream<Node> stream(NodeList node) {
|
||||
return StreamSupport.stream(spliterator(node), false);
|
||||
}
|
||||
|
||||
public static Stream<Element> stream(Element element) {
|
||||
return StreamSupport.stream(spliterator(element), false);
|
||||
}
|
||||
public static Iterable<Node> iterable(NodeList node) {
|
||||
return () -> iterator(node);
|
||||
}
|
||||
|
||||
public static Iterable<Element> iterable(Element element, String tagName) {
|
||||
return () -> iterator(element, tagName);
|
||||
}
|
||||
|
||||
public static Iterable<Element> iterable(Element element) {
|
||||
return () -> iterator(element);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T withChild(Node node, Fun<Node, T> callback, String... path) {
|
||||
Object[] result = new Object[1];
|
||||
XMLNodeVisitor visitor = new XMLNodeVisitor() {
|
||||
@Override
|
||||
public NodeVisitResultPre visitNodePre(List<Node> stack) {
|
||||
if (XMLNodeVisitor.stackMatches(stack, path)) {
|
||||
result[0] = callback.apply(JWO.tail(stack));
|
||||
return NodeVisitResultPre.END_TRAVERSAL;
|
||||
} else if (stack.size() < path.length) return NodeVisitResultPre.CONTINUE;
|
||||
else {
|
||||
return NodeVisitResultPre.SKIP_SUBTREE;
|
||||
}
|
||||
}
|
||||
};
|
||||
new DocumentWalker(node).walk(visitor);
|
||||
return (T) result[0];
|
||||
}
|
||||
|
||||
public static void withChild(Element element, String tagName, Con<Element> callback) {
|
||||
for (Element el : iterable(element, tagName)) {
|
||||
callback.accept(el);
|
||||
}
|
||||
}
|
||||
|
||||
public static void withChild(Element element, Con<Element> callback) {
|
||||
for (Element el : iterable(element)) {
|
||||
callback.accept(el);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,6 +1,11 @@
|
||||
module net.woggioni.jwo {
|
||||
requires static java.xml;
|
||||
requires static java.sql;
|
||||
requires static lombok;
|
||||
requires org.slf4j;
|
||||
requires static org.slf4j;
|
||||
|
||||
exports net.woggioni.jwo;
|
||||
exports net.woggioni.jwo.exception;
|
||||
exports net.woggioni.jwo.url.classpath;
|
||||
exports net.woggioni.jwo.xml;
|
||||
}
|
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();
|
||||
}
|
||||
}
|
@@ -1,7 +1,6 @@
|
||||
package net.woggioni.jwo;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import net.woggioni.jwo.CircularBuffer;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
|
@@ -10,7 +10,14 @@ import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.*;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.FileVisitResult;
|
||||
import java.nio.file.FileVisitor;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.SimpleFileVisitor;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.NavigableMap;
|
||||
import java.util.Objects;
|
||||
|
@@ -3,9 +3,15 @@ package net.woggioni.jwo;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtensionContext;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.ArgumentsProvider;
|
||||
import org.junit.jupiter.params.provider.ArgumentsSource;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.util.Random;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class HashTest {
|
||||
|
||||
@@ -42,4 +48,34 @@ public class HashTest {
|
||||
Assertions.assertNotEquals(hash1, hash2);
|
||||
Assertions.assertNotEquals(hash1.toString(), hash2.toString());
|
||||
}
|
||||
|
||||
|
||||
private static class HexTestArguments implements ArgumentsProvider {
|
||||
@Override
|
||||
public Stream<? extends Arguments> provideArguments(
|
||||
ExtensionContext extensionContext
|
||||
) {
|
||||
return Stream.of(
|
||||
Arguments.of("A41D767EEF6084823F250E954BDD48CF", null),
|
||||
Arguments.of("A41D767eeF6084823f250E954bdD48CF", null),
|
||||
Arguments.of("A41D767EEF6084823F250E954BDD48C", IllegalArgumentException.class),
|
||||
Arguments.of("A41D767xEF6084823F250E954BDD48C", IllegalArgumentException.class),
|
||||
Arguments.of("A41D767ZeF6084823f250E954bdD48C", IllegalArgumentException.class),
|
||||
Arguments.of("A41D767eeF6084823f250E954bdD4x", IllegalArgumentException.class)
|
||||
);
|
||||
}
|
||||
}
|
||||
@ArgumentsSource(HexTestArguments.class)
|
||||
@ParameterizedTest
|
||||
public void hexTest(String sourceString, Class<? extends Throwable> t) {
|
||||
if(t != null) {
|
||||
Assertions.assertThrows(t, () ->
|
||||
Hash.hexToBytes(sourceString)
|
||||
);
|
||||
} else {
|
||||
byte[] bytes = Hash.hexToBytes(sourceString);
|
||||
Assertions.assertEquals(sourceString.length() / 2, bytes.length);
|
||||
Assertions.assertEquals(sourceString.toUpperCase(), Hash.bytesToHex(bytes));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -3,28 +3,65 @@ package net.woggioni.jwo;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledOnOs;
|
||||
import org.junit.jupiter.api.condition.OS;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.EnumSource;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.io.*;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.lang.reflect.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.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.UserPrincipal;
|
||||
import java.util.*;
|
||||
import java.security.DigestInputStream;
|
||||
import java.security.DigestOutputStream;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.TreeMap;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
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.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 {
|
||||
|
||||
@@ -35,38 +72,57 @@ public class JWOTest {
|
||||
if (n > 3) return Optional.of(n);
|
||||
else return Optional.empty();
|
||||
}).collect(Collectors.toList());
|
||||
Assertions.assertEquals(Collections.singletonList(4), l);
|
||||
assertEquals(Collections.singletonList(4), l);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void optional2StreamTest() {
|
||||
Integer integer = 3;
|
||||
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();
|
||||
JWO.optional2Stream(s).forEach(n -> Assertions.fail(
|
||||
"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
|
||||
enum IndexOfWithEscapeTestCase {
|
||||
SIMPLE(" dsds $sdsa \\$dfivbdsf \\\\$sdgsga", '$', '\\',
|
||||
immutableList(6, 25)),
|
||||
immutableList(6, 25)),
|
||||
SIMPLE2("asdasd$$vdfv$", '$', '$',
|
||||
immutableList(12)),
|
||||
immutableList(12)),
|
||||
NO_NEEDLE("asdasd$$vdfv$", '#', '\\',
|
||||
immutableList()),
|
||||
immutableList()),
|
||||
ESCAPED_NEEDLE("asdasd$$vdfv$#sdfs", '#', '$',
|
||||
immutableList()),
|
||||
immutableList()),
|
||||
NOT_ESCAPED_NEEDLE("asdasd$$#vdfv$#sdfs", '#', '$',
|
||||
immutableList(8)),
|
||||
immutableList(8)),
|
||||
|
||||
SDFSD("\n${sys:user.home}${env:HOME}", ':', '\\',
|
||||
immutableList(6, 22))
|
||||
|
||||
;
|
||||
immutableList(6, 22));
|
||||
final String haystack;
|
||||
final Character needle;
|
||||
|
||||
@@ -81,13 +137,13 @@ public class JWOTest {
|
||||
String haystack = testCase.haystack;
|
||||
List<Integer> solution = newArrayList();
|
||||
int i = 0;
|
||||
while(true) {
|
||||
while (true) {
|
||||
i = JWO.indexOfWithEscape(haystack, testCase.needle, testCase.escape, i, haystack.length());
|
||||
if(i < 0) break;
|
||||
if (i < 0) break;
|
||||
solution.add(i);
|
||||
++i;
|
||||
}
|
||||
Assertions.assertEquals(testCase.solution, solution);
|
||||
assertEquals(testCase.solution, solution);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -98,35 +154,35 @@ public class JWOTest {
|
||||
valuesMap.put("date", "2020-03-25 16:22");
|
||||
valuesMap.put("adjective", "simple");
|
||||
String expected = """
|
||||
This is a simple test made by John Doe on 2020-03-25 16:22. It's really simple!
|
||||
/home/user
|
||||
/home/user
|
||||
defaultValue
|
||||
""";
|
||||
This is a simple test made by John Doe on 2020-03-25 16:22. It's really simple!
|
||||
/home/user
|
||||
/home/user
|
||||
defaultValue
|
||||
""";
|
||||
Map<String, Map<String, Object>> contextMap = new MapBuilder<String, Map<String, Object>>()
|
||||
.entry("env",
|
||||
new MapBuilder<String, String>()
|
||||
.entry("HOME", "/home/user")
|
||||
.build(TreeMap::new, Collections::unmodifiableMap)
|
||||
)
|
||||
.entry("sys",
|
||||
new MapBuilder<String, String>()
|
||||
.entry("user.home", "/home/user")
|
||||
.build(TreeMap::new, Collections::unmodifiableMap) ).build(TreeMap::new, Collections::unmodifiableMap);
|
||||
.entry("env",
|
||||
new MapBuilder<String, String>()
|
||||
.entry("HOME", "/home/user")
|
||||
.build(TreeMap::new, Collections::unmodifiableMap)
|
||||
)
|
||||
.entry("sys",
|
||||
new MapBuilder<String, String>()
|
||||
.entry("user.home", "/home/user")
|
||||
.build(TreeMap::new, Collections::unmodifiableMap)).build(TreeMap::new, Collections::unmodifiableMap);
|
||||
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);
|
||||
Assertions.assertEquals(expected, rendered);
|
||||
assertEquals(expected, rendered);
|
||||
}
|
||||
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);
|
||||
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);
|
||||
Object absent = new Object();
|
||||
|
||||
@@ -144,7 +200,7 @@ public class JWOTest {
|
||||
// If the key is not present, leave the variable untouched.
|
||||
if (index != -1) {
|
||||
Object value = valuesMap.getOrDefault(key, absent);
|
||||
if(value != absent) {
|
||||
if (value != absent) {
|
||||
String valueStr = value != null ? value.toString() : "";
|
||||
formatter.replace(index, index + formatKey.length(), valueStr);
|
||||
}
|
||||
@@ -164,6 +220,451 @@ public class JWOTest {
|
||||
m.setAccessible(true);
|
||||
int expectedUserId = (Integer) m.invoke(expectedUser);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -8,7 +8,12 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Function;
|
||||
|
||||
@EqualsAndHashCode
|
||||
|
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";
|
||||
}
|
@@ -1,7 +1,6 @@
|
||||
package net.woggioni.jwo;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import net.woggioni.jwo.PathClassLoader;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
|
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) {
|
||||
}
|
@@ -5,7 +5,12 @@ import lombok.RequiredArgsConstructor;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
@@ -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)));
|
||||
});
|
||||
}
|
||||
}
|
@@ -7,7 +7,11 @@ import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.io.*;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.security.DigestInputStream;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Arrays;
|
||||
|
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