update to 2024.01.31

This commit is contained in:
2024-01-31 06:53:52 +08:00
parent ecbae35415
commit 21753f8e0c
26 changed files with 1589 additions and 58 deletions

2
Jenkinsfile vendored
View File

@@ -19,7 +19,7 @@ pipeline {
classPattern: '**/build/classes/java/main', classPattern: '**/build/classes/java/main',
sourcePattern: '**/src/main' sourcePattern: '**/src/main'
) )
javadoc javadocDir: "build/docs/javadoc", keepAll: true //javadoc javadocDir: "build/docs/javadoc", keepAll: true
archiveArtifacts artifacts: '**/build/libs/*.jar', archiveArtifacts artifacts: '**/build/libs/*.jar',
allowEmptyArchive: false, allowEmptyArchive: false,
fingerprint: true, fingerprint: true,

View File

@@ -17,12 +17,11 @@ allprojects {
} }
mavenCentral() mavenCentral()
} }
pluginManager.withPlugin('java-library') { pluginManager.withPlugin('java-library') {
java { java {
toolchain { toolchain {
languageVersion = JavaLanguageVersion.of(17) languageVersion = JavaLanguageVersion.of(21)
vendor = JvmVendorSpec.GRAAL_VM vendor = JvmVendorSpec.GRAAL_VM
} }
} }
@@ -63,7 +62,7 @@ allprojects {
pluginManager.withPlugin('java-library') { pluginManager.withPlugin('java-library') {
java { java {
withJavadocJar() // withJavadocJar()
withSourcesJar() withSourcesJar()
} }
} }

View File

@@ -1,3 +1,3 @@
jwo.version = 2024.01.29 jwo.version = 2024.01.31
lys.version = 2024.01.29 lys.version = 2024.01.29
guice.version = 5.0.1 guice.version = 5.0.1

View File

@@ -16,7 +16,7 @@ dependencies {
implementation project(':jmath') implementation project(':jmath')
} }
envelopeJar { application {
mainClass = 'net.woggioni.jmath.benchmark.Main' mainClass = 'net.woggioni.jmath.benchmark.Main'
} }

View File

@@ -1,5 +1,7 @@
{ {
"resources":{ "resources":{
"includes":[]}, "includes":[{
"pattern":"\\QMETA-INF/services/java.time.zone.ZoneRulesProvider\\E"
}]},
"bundles":[] "bundles":[]
} }

View File

@@ -24,10 +24,15 @@ public class Main {
Matrix<Rational> mtx = Matrix.of(numericTypeFactory, size, size, init); Matrix<Rational> mtx = Matrix.of(numericTypeFactory, size, size, init);
Matrix<Rational> lu = mtx.clone(); Matrix<Rational> lu = mtx.clone();
Matrix.Pivot pivot = lu.lup(); Matrix.Pivot pivot = lu.lup();
IntFunction<Rational> initVector = (i) -> Rational.of(rnd.nextInt(0, size), size); for(int i = 0; i < size; i++) {
Vector<Rational> b = Vector.of(numericTypeFactory, size, initVector); IntFunction<Rational> initVector = (j) -> Rational.of(rnd.nextInt(0, size), size);
Vector<Rational> x = lu.luSolve(b, pivot); Vector<Rational> b = Vector.of(numericTypeFactory, size, initVector);
Vector<Rational> error = mtx.mmul(x).sub(b); Vector<Rational> x = lu.luSolve(b, pivot);
System.out.println(error.norm()); 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));
}
}
} }
} }

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

View File

@@ -25,6 +25,10 @@ import java.util.stream.Stream;
public class CollectionUtils { public class CollectionUtils {
public enum MapMergeStrategy {
THROW, REPLACE, KEEP
}
@SafeVarargs @SafeVarargs
public static <T> ArrayList<T> newArrayList(T... args) { public static <T> ArrayList<T> newArrayList(T... args) {
return new ArrayList<>(Arrays.asList(args)); return new ArrayList<>(Arrays.asList(args));
@@ -125,18 +129,43 @@ public class CollectionUtils {
}; };
} }
public static <T> T throwingMerger(T v1, T v2) { private static <T> BinaryOperator<T> throwingMerger() {
throw new IllegalStateException(String.format("Duplicate key %s", v1)); return (v1, v2) -> {
throw new IllegalStateException(String.format("Duplicate key %s", v1));
};
}
private static <T> BinaryOperator<T> updatingMerger() {
return (v1, v2) -> v2;
} }
public static <T> T oldValueMerger(T oldValue, T newValue) { private static <T> BinaryOperator<T> conservativeMerger() {
return oldValue; return (v1, v2) -> v1;
} }
public static <T> T newValueMerger(T oldValue, T newValue) { private static <T> BinaryOperator<T> getMerger(MapMergeStrategy mapMergeStrategy) {
return newValue; 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( public static <T, K, V> Collector<T, ?, Map<K, V>> toUnmodifiableHashMap(
Function<T, K> keyExtractor, Function<T, K> keyExtractor,
Function<T, V> valueExtractor) { Function<T, V> valueExtractor) {
@@ -149,6 +178,19 @@ public class CollectionUtils {
return toUnmodifiableNavigableMap(TreeMap::new, keyExtractor, valueExtractor); 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( public static <T, K, V> Collector<T, ?, NavigableMap<K, V>> toUnmodifiableTreeMap(
Function<T, K> keyExtractor, Function<T, K> keyExtractor,
Function<T, V> valueExtractor, Function<T, V> valueExtractor,
@@ -156,6 +198,12 @@ public class CollectionUtils {
return toUnmodifiableNavigableMap(() -> new TreeMap<>(comparator), keyExtractor, valueExtractor); 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( public static <T, K, V> Collector<T, ?, NavigableMap<K, V>> toTreeMap(
Function<T, K> keyExtractor, Function<T, K> keyExtractor,
Function<T, V> valueExtractor) { Function<T, V> valueExtractor) {
@@ -169,6 +217,14 @@ public class CollectionUtils {
return toNavigableMap(() -> new TreeMap<>(comparator), keyExtractor, valueExtractor); 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( public static <T, K, V, MAP_TYPE extends NavigableMap<K, V>> Collector<T, ?, MAP_TYPE> toNavigableMap(
Supplier<MAP_TYPE> constructor, Supplier<MAP_TYPE> constructor,
Function<T, K> keyExtractor, Function<T, K> keyExtractor,
@@ -177,7 +233,7 @@ public class CollectionUtils {
constructor, constructor,
keyExtractor, keyExtractor,
valueExtractor, valueExtractor,
CollectionUtils::throwingMerger MapMergeStrategy.THROW
); );
} }
@@ -185,7 +241,8 @@ public class CollectionUtils {
Supplier<MAP_TYPE> constructor, Supplier<MAP_TYPE> constructor,
Function<T, K> keyExtractor, Function<T, K> keyExtractor,
Function<T, V> valueExtractor, Function<T, V> valueExtractor,
BinaryOperator<V> valueMerger) { MapMergeStrategy mapMergeStrategy) {
BinaryOperator<V> valueMerger = getMerger(mapMergeStrategy);
BiConsumer<MAP_TYPE, T> accumulator = (map, streamElement) -> { BiConsumer<MAP_TYPE, T> accumulator = (map, streamElement) -> {
map.merge(keyExtractor.apply(streamElement), valueExtractor.apply(streamElement), map.merge(keyExtractor.apply(streamElement), valueExtractor.apply(streamElement),
valueMerger valueMerger
@@ -202,14 +259,15 @@ public class CollectionUtils {
Supplier<Map<K, V>> constructor, Supplier<Map<K, V>> constructor,
Function<T, K> keyExtractor, Function<T, K> keyExtractor,
Function<T, V> valueExtractor) { Function<T, V> valueExtractor) {
return toMap(constructor, keyExtractor, valueExtractor, CollectionUtils::throwingMerger); return toMap(constructor, keyExtractor, valueExtractor, MapMergeStrategy.THROW);
} }
public static <T, K, V> Collector<T, ?, Map<K, V>> toMap( public static <T, K, V> Collector<T, ?, Map<K, V>> toMap(
Supplier<Map<K, V>> constructor, Supplier<Map<K, V>> constructor,
Function<T, K> keyExtractor, Function<T, K> keyExtractor,
Function<T, V> valueExtractor, Function<T, V> valueExtractor,
BinaryOperator<V> valueMerger) { MapMergeStrategy mapMergeStrategy) {
BinaryOperator<V> valueMerger = getMerger(mapMergeStrategy);
BiConsumer<Map<K, V>, T> accumulator = (map, streamElement) -> { BiConsumer<Map<K, V>, T> accumulator = (map, streamElement) -> {
map.merge(keyExtractor.apply(streamElement), valueExtractor.apply(streamElement), map.merge(keyExtractor.apply(streamElement), valueExtractor.apply(streamElement),
valueMerger valueMerger
@@ -226,13 +284,14 @@ public class CollectionUtils {
Supplier<Map<K, V>> constructor, Supplier<Map<K, V>> constructor,
Function<T, K> keyExtractor, Function<T, K> keyExtractor,
Function<T, V> valueExtractor) { Function<T, V> valueExtractor) {
return toUnmodifiableMap(constructor, keyExtractor, valueExtractor, CollectionUtils::throwingMerger); return toUnmodifiableMap(constructor, keyExtractor, valueExtractor, MapMergeStrategy.THROW);
} }
public static <T, K, V> Collector<T, ?, Map<K, V>> toUnmodifiableMap( public static <T, K, V> Collector<T, ?, Map<K, V>> toUnmodifiableMap(
Supplier<Map<K, V>> constructor, Supplier<Map<K, V>> constructor,
Function<T, K> keyExtractor, Function<T, K> keyExtractor,
Function<T, V> valueExtractor, Function<T, V> valueExtractor,
BinaryOperator<V> valueMerger) { MapMergeStrategy mapMergeStrategy) {
BinaryOperator<V> valueMerger = getMerger(mapMergeStrategy);
BiConsumer<Map<K, V>, T> accumulator = (map, streamElement) -> { BiConsumer<Map<K, V>, T> accumulator = (map, streamElement) -> {
map.merge(keyExtractor.apply(streamElement), map.merge(keyExtractor.apply(streamElement),
valueExtractor.apply(streamElement), valueExtractor.apply(streamElement),
@@ -251,8 +310,9 @@ public class CollectionUtils {
Supplier<NavigableMap<K, V>> constructor, Supplier<NavigableMap<K, V>> constructor,
Function<T, K> keyExtractor, Function<T, K> keyExtractor,
Function<T, V> valueExtractor, Function<T, V> valueExtractor,
BinaryOperator<V> valueMerger MapMergeStrategy mapMergeStrategy
) { ) {
BinaryOperator<V> valueMerger = getMerger(mapMergeStrategy);
BiConsumer<NavigableMap<K, V>, T> accumulator = (map, streamElement) -> { BiConsumer<NavigableMap<K, V>, T> accumulator = (map, streamElement) -> {
map.merge( map.merge(
keyExtractor.apply(streamElement), keyExtractor.apply(streamElement),
@@ -271,7 +331,7 @@ public class CollectionUtils {
Supplier<NavigableMap<K, V>> constructor, Supplier<NavigableMap<K, V>> constructor,
Function<T, K> keyExtractor, Function<T, K> keyExtractor,
Function<T, V> valueExtractor) { Function<T, V> valueExtractor) {
return toUnmodifiableNavigableMap(constructor, keyExtractor, valueExtractor, CollectionUtils::throwingMerger); 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) { public static <K, V, U> Stream<Map.Entry<K, U>> mapValues(Map<K, V> map, Fun<V, U> xform) {
return map return map

View File

@@ -4,6 +4,7 @@ import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.woggioni.jwo.exception.ChildProcessException; import net.woggioni.jwo.exception.ChildProcessException;
import net.woggioni.jwo.internal.CharFilterReader; import net.woggioni.jwo.internal.CharFilterReader;
import org.slf4j.Logger;
import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.HttpsURLConnection;
@@ -12,9 +13,7 @@ import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager; import javax.net.ssl.X509TrustManager;
import java.io.BufferedInputStream;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.InputStream; import java.io.InputStream;
@@ -25,6 +24,8 @@ import java.io.Reader;
import java.io.StringWriter; import java.io.StringWriter;
import java.io.Writer; import java.io.Writer;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.net.URL;
import java.net.URLStreamHandlerFactory;
import java.nio.channels.Channels; import java.nio.channels.Channels;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
@@ -35,6 +36,8 @@ import java.nio.file.attribute.FileAttribute;
import java.security.DigestOutputStream; import java.security.DigestOutputStream;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Comparator; import java.util.Comparator;
import java.util.EnumSet; import java.util.EnumSet;
@@ -69,6 +72,10 @@ import java.util.zip.ZipOutputStream;
@Slf4j @Slf4j
public class JWO { public class JWO {
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) { public static <T> Stream<T> iterable2Stream(Iterable<T> iterable) {
return StreamSupport.stream(iterable.spliterator(), false); return StreamSupport.stream(iterable.spliterator(), false);
} }
@@ -1004,4 +1011,100 @@ public class JWO {
} }
public void run() { executor.execute(action); } 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;
}
} }

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

View File

@@ -1,10 +1,12 @@
package net.woggioni.jwo; package net.woggioni.jwo;
import lombok.RequiredArgsConstructor;
import net.woggioni.jwo.internal.SynchronizedLazyValue; import net.woggioni.jwo.internal.SynchronizedLazyValue;
import net.woggioni.jwo.internal.UnsynchronizedLazyValue; import net.woggioni.jwo.internal.UnsynchronizedLazyValue;
import java.util.Optional; import java.util.Optional;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Stream; import java.util.stream.Stream;
@@ -30,19 +32,22 @@ public interface LazyValue<T> {
*/ */
Optional<T> close(); Optional<T> close();
static <T> LazyValue<T> of(Supplier<T> supplier, ThreadSafetyMode locking, Consumer<T> finalizer) {
static <T> LazyValue<T> of(Supplier<T> supplier, ThreadSafetyMode locking) {
LazyValue<T> result; LazyValue<T> result;
switch (locking) { switch (locking) {
case SYNCHRONIZED: case SYNCHRONIZED:
result = new SynchronizedLazyValue<>(supplier); result = new SynchronizedLazyValue<>(supplier, finalizer);
break; break;
case NONE: case NONE:
result = new UnsynchronizedLazyValue<>(supplier); result = new UnsynchronizedLazyValue<>(supplier, finalizer);
break; break;
default: default:
throw new RuntimeException("This should never happen"); throw new RuntimeException("This should never happen");
} }
return result; return result;
} }
static <T> LazyValue<T> of(Supplier<T> supplier, ThreadSafetyMode locking) {
return of(supplier, locking, null);
}
} }

View File

@@ -1,39 +1,40 @@
package net.woggioni.jwo; package net.woggioni.jwo;
import lombok.SneakyThrows; import java.io.Closeable;
import java.io.IOException;
import java.io.RandomAccessFile; import java.nio.channels.FileChannel;
import java.nio.channels.FileLock; import java.nio.channels.FileLock;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; 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 FileLock lock;
private final RandomAccessFile randomAccessFile;
public LockFile(Path path) { private static FileChannel openFileChannel(Path path) throws IOException {
this(path, false); Files.createDirectories(path.getParent());
return FileChannel.open(path, EnumSet.of(StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE));
} }
@SneakyThrows public static LockFile acquire(Path path, boolean shared) throws IOException {
public LockFile(Path path, boolean shared) { FileChannel channel = openFileChannel(path);
this.path = path; return new LockFile(channel.lock(0L, Long.MAX_VALUE, shared));
try { }
Files.createDirectories(path.getParent());
Files.createFile(path); public static LockFile tryAcquire(Path path, boolean shared) throws IOException {
} catch(FileAlreadyExistsException faee) { FileChannel channel = openFileChannel(path);
} FileLock lock = channel.tryLock(0L, Long.MAX_VALUE, shared);
randomAccessFile = new RandomAccessFile(path.toFile(), "rw"); return (lock != null) ? new LockFile(lock) : null;
lock = randomAccessFile.getChannel().lock(0L, Long.MAX_VALUE, shared); }
private LockFile(FileLock lock) {
this.lock = lock;
} }
@Override @Override
@SneakyThrows public void close() throws IOException {
public void close() { lock.channel().close();
lock.release();
randomAccessFile.close();
} }
} }

View File

@@ -5,6 +5,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Stream;
public class MapBuilder<K,V> { public class MapBuilder<K,V> {
private final List<Map.Entry<K, V>> entries = new ArrayList<>(); private final List<Map.Entry<K, V>> entries = new ArrayList<>();
@@ -22,7 +23,15 @@ public class MapBuilder<K,V> {
return finalizer.apply(result); return finalizer.apply(result);
} }
public Stream<Map.Entry<K,V>> stream() {
return entries.stream();
}
public Map<K,V> build(Sup<Map<K,V>> factory) { public Map<K,V> build(Sup<Map<K,V>> factory) {
return build(factory, Function.identity()); 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);
}
} }

View 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 =&gt; [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 &gt; 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;
}
}
}

View File

@@ -10,5 +10,5 @@ public interface Run extends Runnable {
exec(); exec();
} }
boolean exec() throws Throwable; void exec() throws Throwable;
} }

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

View 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 {}
}

View File

@@ -100,7 +100,7 @@ public class UnmodifiableDelegatingMap<K, V> implements Map<K, V> {
mapFactory, mapFactory,
Map.Entry::getKey, Map.Entry::getKey,
Map.Entry::getValue, Map.Entry::getValue,
CollectionUtils::newValueMerger CollectionUtils.MapMergeStrategy.REPLACE
) )
); );
} }

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

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

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

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

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

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

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

View File

@@ -1,6 +1,11 @@
module net.woggioni.jwo { module net.woggioni.jwo {
requires static java.xml;
requires static java.sql;
requires static lombok; requires static lombok;
requires org.slf4j; requires static org.slf4j;
exports net.woggioni.jwo; exports net.woggioni.jwo;
exports net.woggioni.jwo.exception; exports net.woggioni.jwo.exception;
exports net.woggioni.jwo.url.classpath;
exports net.woggioni.jwo.xml;
} }