initial commit
This commit is contained in:
15
benchmark/build.sbt
Normal file
15
benchmark/build.sbt
Normal file
@@ -0,0 +1,15 @@
|
||||
organization := (organization in LocalRootProject).value
|
||||
name := "jwo-benchmark"
|
||||
version := (version in LocalRootProject).value
|
||||
resourceDirectory in Compile := (resourceDirectory in(LocalRootProject, Test)).value
|
||||
skip in publish := true
|
||||
maintainer := (maintainer in LocalRootProject).value
|
||||
mainClass := Some("net.woggioni.jwo.benchmark.Main")
|
||||
fork := true
|
||||
// libraryDependencies += "com.fasterxml.jackson.core" % "jackson-databind" % "2.9.6",
|
||||
// libraryDependencies += "org.tukaani" % "xz" % "1.8",
|
||||
// libraryDependencies += "com.beust" % "jcommander" % "1.72",
|
||||
// libraryDependencies += "org.projectlombok" % "lombok" % "1.18.8" % Provided
|
||||
|
||||
enablePlugins(JavaAppPackaging)
|
||||
enablePlugins(UniversalPlugin)
|
@@ -0,0 +1,34 @@
|
||||
package net.woggioni.jwo.benchmark;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class NaiveTemplateRenderer {
|
||||
public static String renderTemplateNaive(String template, Map<String, Object> valuesMap){
|
||||
StringBuffer formatter = new StringBuffer(template);
|
||||
Object absent = new Object();
|
||||
|
||||
Matcher matcher = Pattern.compile("\\$\\{(\\w+)}").matcher(template);
|
||||
|
||||
while (matcher.find()) {
|
||||
String key = matcher.group(1);
|
||||
|
||||
String formatKey = String.format("${%s}", key);
|
||||
int index = formatter.indexOf(formatKey);
|
||||
|
||||
// If the key is present:
|
||||
// - If the value is not null, then replace the variable for the value
|
||||
// - If the value is null, replace the variable for empty string
|
||||
// If the key is not present, leave the variable untouched.
|
||||
if (index != -1) {
|
||||
Object value = valuesMap.getOrDefault(key, absent);
|
||||
if(value != absent) {
|
||||
String valueStr = value != null ? value.toString() : "";
|
||||
formatter.replace(index, index + formatKey.length(), valueStr);
|
||||
}
|
||||
}
|
||||
}
|
||||
return formatter.toString();
|
||||
}
|
||||
}
|
1
benchmark/src/main/resources/render_template_test.txt
Normal file
1
benchmark/src/main/resources/render_template_test.txt
Normal file
@@ -0,0 +1 @@
|
||||
This is a ${adjective} test made by ${author} on ${date}. It's really ${adjective}!
|
@@ -0,0 +1,47 @@
|
||||
package net.woggioni.jwo.benchmark
|
||||
|
||||
import java.io.{InputStream, InputStreamReader, Reader}
|
||||
|
||||
import net.woggioni.jwo.io.CircularInputStream
|
||||
import net.woggioni.jwo.{Chronometer, JWO}
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
|
||||
object Main {
|
||||
|
||||
def withBenchmarkReader(fn : Reader => Unit) = {
|
||||
val reader = new InputStreamReader(classOf[JWO].getResourceAsStream("/render_template_test.txt"))
|
||||
val is : InputStream = new CircularInputStream(JWO.readAll(reader).getBytes, 10000)
|
||||
try {
|
||||
fn(new InputStreamReader(is))
|
||||
} finally {
|
||||
if (reader != null) reader.close()
|
||||
}
|
||||
}
|
||||
|
||||
def renderTemplateBenchmark() {
|
||||
val valuesMap = Map[String, Object]("author" -> "John Doe",
|
||||
"date" -> "2020-03-25 16:22",
|
||||
"adjective" -> "simple")
|
||||
withBenchmarkReader(
|
||||
reader => {
|
||||
val chronometer = new Chronometer
|
||||
val result = JWO.renderTemplate(reader, valuesMap.asJava)
|
||||
printf("Elapsed time: %.3f\n", chronometer.elapsed(Chronometer.UnitOfMeasure.SECONDS))
|
||||
}
|
||||
)
|
||||
|
||||
withBenchmarkReader(
|
||||
reader => {
|
||||
val chronometer = new Chronometer
|
||||
val result = NaiveTemplateRenderer.renderTemplateNaive(JWO.readAll(reader), valuesMap.asJava)
|
||||
printf("Elapsed time: %.3f\n", chronometer.elapsed(Chronometer.UnitOfMeasure.SECONDS))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
def main(argv : Array[String]) = {
|
||||
renderTemplateBenchmark()
|
||||
}
|
||||
|
||||
}
|
28
build.sbt
Normal file
28
build.sbt
Normal file
@@ -0,0 +1,28 @@
|
||||
name := "jwo"
|
||||
|
||||
organization := "net.woggioni"
|
||||
|
||||
maintainer := "oggioni.walter@gmail.com"
|
||||
|
||||
version := "1.0"
|
||||
resolvers += Resolver.mavenLocal
|
||||
|
||||
git.useGitDescribe := true
|
||||
crossPaths := false
|
||||
|
||||
autoScalaLibrary := false
|
||||
|
||||
libraryDependencies += "org.projectlombok" % "lombok" % "1.18.8" % Provided
|
||||
|
||||
libraryDependencies += "org.slf4j" % "slf4j-api" % "1.7.28"
|
||||
|
||||
libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % Test
|
||||
libraryDependencies += "org.apache.logging.log4j" % "log4j-core" % "2.12.1" % Test
|
||||
libraryDependencies += "org.apache.logging.log4j" % "log4j-slf4j-impl" % "2.12.1" % Test
|
||||
|
||||
javacOptions ++= Seq("-source", "1.8", "-target", "1.8")
|
||||
javacOptions in (Compile, doc) := Seq("-source", "1.8")
|
||||
|
||||
enablePlugins(Delombok)
|
||||
enablePlugins(DelombokJavadoc)
|
||||
lazy val benchmark = (project in file("benchmark")).dependsOn(LocalRootProject)
|
1
project/build.properties
Normal file
1
project/build.properties
Normal file
@@ -0,0 +1 @@
|
||||
sbt.version=1.3.1
|
7
project/plugins.sbt
Normal file
7
project/plugins.sbt
Normal file
@@ -0,0 +1,7 @@
|
||||
addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.0")
|
||||
addSbtPlugin("com.typesafe.sbt" % "sbt-git" % "0.9.3")
|
||||
addSbtPlugin("de.heikoseeberger" % "sbt-header" % "4.1.0")
|
||||
addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.3.6")
|
||||
addSbtPlugin("com.thoughtworks.sbt" % "delombokjavadoc" % "1.0.0+66-8fbcf18c")
|
||||
|
||||
//addSbtPlugin("com.thoughtworks.sbt" % "delombokjavadoc" % "latest.release")
|
44
src/main/java/net/woggioni/jwo/Chronometer.java
Normal file
44
src/main/java/net/woggioni/jwo/Chronometer.java
Normal file
@@ -0,0 +1,44 @@
|
||||
package net.woggioni.jwo;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
public class Chronometer {
|
||||
|
||||
public enum UnitOfMeasure {
|
||||
NANOSECONDS(1),
|
||||
MICROSECONDS(NANOSECONDS.nanoseconds_size * 1000),
|
||||
MILLISECONDS(MICROSECONDS.nanoseconds_size * 1000),
|
||||
SECONDS(MILLISECONDS.nanoseconds_size * 1000),
|
||||
MINUTES(SECONDS.nanoseconds_size * 60),
|
||||
HOURS(MINUTES.nanoseconds_size * 60),
|
||||
DAYS(HOURS.nanoseconds_size * 24),
|
||||
WEEKS(DAYS.nanoseconds_size * 7),
|
||||
MONTHS(DAYS.nanoseconds_size * 30),
|
||||
YEARS(DAYS.nanoseconds_size * 365);
|
||||
|
||||
public long nanoseconds_size;
|
||||
|
||||
UnitOfMeasure(long nanoseconds_size) {
|
||||
this.nanoseconds_size = nanoseconds_size;
|
||||
}
|
||||
}
|
||||
|
||||
private long start;
|
||||
|
||||
@SneakyThrows
|
||||
public Chronometer() {
|
||||
start = System.nanoTime();
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
start = System.nanoTime();
|
||||
}
|
||||
|
||||
public long elapsed() {
|
||||
return System.nanoTime() - start;
|
||||
}
|
||||
|
||||
public double elapsed(UnitOfMeasure unitOfMeasure) {
|
||||
return ((double) (System.nanoTime() - start)) / unitOfMeasure.nanoseconds_size;
|
||||
}
|
||||
}
|
138
src/main/java/net/woggioni/jwo/CollectionUtils.java
Normal file
138
src/main/java/net/woggioni/jwo/CollectionUtils.java
Normal file
@@ -0,0 +1,138 @@
|
||||
package net.woggioni.jwo;
|
||||
|
||||
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.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BinaryOperator;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collector;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class CollectionUtils {
|
||||
|
||||
@SafeVarargs
|
||||
public static <T> ArrayList<T> newArrayList(T... args) {
|
||||
return new ArrayList<>(Arrays.asList(args));
|
||||
}
|
||||
|
||||
public static <T> Collector<T, ?, List<T>> toUnmodifiableList() {
|
||||
return Collector.of(
|
||||
ArrayList::new,
|
||||
List::add,
|
||||
(ArrayList<T> l1, ArrayList<T> l2) -> {
|
||||
l1.addAll(l2);
|
||||
return l1;
|
||||
},
|
||||
Collections::unmodifiableList);
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static <T> List<T> immutableList(T... elements) {
|
||||
return Stream.of(elements).collect(toUnmodifiableList());
|
||||
}
|
||||
|
||||
public static <T> Collector<T, ?, Set<T>> toUnmodifiableSet() {
|
||||
return Collector.of(
|
||||
HashSet::new,
|
||||
Set::add,
|
||||
(Set<T> s1, Set<T> s2) -> {
|
||||
s1.addAll(s2);
|
||||
return s1;
|
||||
},
|
||||
Collections::unmodifiableSet);
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static <T> Set<T> immutableSet(T... elements) {
|
||||
return Stream.of(elements).collect(toUnmodifiableSet());
|
||||
}
|
||||
|
||||
private static <T> Collector<T, ?, Set<T>> createTreeSetCollector(Function<Set<T>, Set<T>> finalizer, Comparator<? super T> comparator) {
|
||||
return Collector.of(
|
||||
() -> new TreeSet<>(comparator),
|
||||
Set::add,
|
||||
(Set<T> s1, Set<T> s2) -> {
|
||||
s1.addAll(s2);
|
||||
return s1;
|
||||
},
|
||||
finalizer);
|
||||
}
|
||||
|
||||
private static <T extends Comparable<T>> Collector<T, ?, Set<T>>
|
||||
createTreeSetCollector(Function<Set<T>, Set<T>> finalizer) {
|
||||
return Collector.of(
|
||||
TreeSet::new,
|
||||
Set::add,
|
||||
(Set<T> s1, Set<T> s2) -> {
|
||||
s1.addAll(s2);
|
||||
return s1;
|
||||
},
|
||||
finalizer);
|
||||
}
|
||||
|
||||
public static <T extends Comparable<T>> Collector<T, ?, Set<T>> toTreeSet() {
|
||||
return createTreeSetCollector(Function.identity());
|
||||
}
|
||||
|
||||
public static <T> Collector<T, ?, Set<T>> toTreeSet(Comparator<? super T> comparator) {
|
||||
return createTreeSetCollector(Function.identity(), comparator);
|
||||
}
|
||||
|
||||
public static <T> Collector<T, ?, Set<T>> toUnmodifiableTreeSet(Comparator<? super T> comparator) {
|
||||
return createTreeSetCollector(Collections::unmodifiableSet, comparator);
|
||||
}
|
||||
|
||||
public static <T extends Comparable<T>> Collector<T, ?, Set<T>> toUnmodifiableTreeSet() {
|
||||
return createTreeSetCollector(Collections::unmodifiableSet);
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static <T> Set<T> immutableTreeSet(Comparator<? super T> comparator, T... elements) {
|
||||
return Stream.of(elements).collect(toUnmodifiableTreeSet(comparator));
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static <T extends Comparable<T>> Set<T> immutableTreeSet(T... elements) {
|
||||
return Stream.of(elements).collect(toUnmodifiableTreeSet());
|
||||
}
|
||||
|
||||
private static <K, V, M extends Map<K, V>> BinaryOperator<M> mapMerger(BinaryOperator<V> var0) {
|
||||
return (m1, m2) -> {
|
||||
Iterator<Map.Entry<K, V>> it = m2.entrySet().iterator();
|
||||
|
||||
while (it.hasNext()) {
|
||||
Map.Entry<K, V> entry = it.next();
|
||||
m1.merge(entry.getKey(), entry.getValue(), var0);
|
||||
}
|
||||
|
||||
return m1;
|
||||
};
|
||||
}
|
||||
|
||||
private static <T> BinaryOperator<T> throwingMerger() {
|
||||
return (v1, v2) -> {
|
||||
throw new IllegalStateException(String.format("Duplicate key %s", v1));
|
||||
};
|
||||
}
|
||||
|
||||
public static <T, K, V> Collector<T, ?, Map<K, V>> toUnmodifiableMap(Function<T, K> keyExtractor, Function<T, V> valueExtractor) {
|
||||
BiConsumer<Map<K, V>, T> accumulator = (map, streamElement) -> {
|
||||
map.merge(keyExtractor.apply(streamElement), valueExtractor.apply(streamElement), throwingMerger());
|
||||
};
|
||||
return Collector.of(
|
||||
HashMap::new,
|
||||
accumulator,
|
||||
mapMerger(throwingMerger()),
|
||||
Collections::unmodifiableMap
|
||||
);
|
||||
}
|
||||
}
|
341
src/main/java/net/woggioni/jwo/JWO.java
Normal file
341
src/main/java/net/woggioni/jwo/JWO.java
Normal file
@@ -0,0 +1,341 @@
|
||||
package net.woggioni.jwo;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.net.ssl.*;
|
||||
import java.io.*;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
@Slf4j
|
||||
public class JWO {
|
||||
public static <T> Stream<T> iterable2stream(Iterable<T> iterable) {
|
||||
return StreamSupport.stream(iterable.spliterator(), false);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static void writeObject2File(Path file, Object o) {
|
||||
try (Writer writer = new OutputStreamWriter(new FileOutputStream(file.toString()))) {
|
||||
writer.write(o.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public static void writeObject2File(String fileName, Object o) {
|
||||
writeObject2File(new File(fileName), o);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static void writeObject2File(File file, Object o) {
|
||||
try (Writer writer = new OutputStreamWriter(new FileOutputStream(file.getPath()))) {
|
||||
writer.write(o.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static void writeBytes2File(Path file, byte[] bytes) {
|
||||
try (OutputStream os = new FileOutputStream(file.toString())) {
|
||||
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())))) {
|
||||
char[] buffer = new char[1024];
|
||||
while (true) {
|
||||
int read = reader.read(buffer);
|
||||
builder.append(buffer, 0, read);
|
||||
if (read < buffer.length) break;
|
||||
}
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static String readResource2String(String classpath) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
try (Reader reader = new InputStreamReader(JWO.class.getResourceAsStream(classpath))) {
|
||||
char[] buffer = new char[1024];
|
||||
while (true) {
|
||||
int read = reader.read(buffer);
|
||||
builder.append(buffer, 0, read);
|
||||
if (read < buffer.length) break;
|
||||
}
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static <T extends Throwable> T newThrowable(Class<T> cls, String format, Object... args) {
|
||||
Constructor<T> constructor = cls.getDeclaredConstructor(String.class);
|
||||
constructor.setAccessible(true);
|
||||
return constructor.newInstance(String.format(format, args));
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static <T extends Throwable> T newThrowable(Class<T> cls, Throwable throwable, String format, Object... args) {
|
||||
Constructor<T> constructor = cls.getConstructor(String.class, Throwable.class);
|
||||
return constructor.newInstance(String.format(format, args), throwable);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static <T extends Throwable> void raise(Class<T> cls, Throwable throwable, String format, Object... args) {
|
||||
throw newThrowable(cls, throwable, format, args);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static <T extends Throwable> void raise(Class<T> cls, String format, Object... args) {
|
||||
throw newThrowable(cls, format, args);
|
||||
}
|
||||
|
||||
|
||||
private static SSLSocketFactory defaultSSLSocketFactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
|
||||
private static HostnameVerifier defaultHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
|
||||
|
||||
@SneakyThrows
|
||||
public static void setSSLVerifyPeerHostName(boolean verify) {
|
||||
if (verify) {
|
||||
HttpsURLConnection.setDefaultHostnameVerifier(defaultHostnameVerifier);
|
||||
} else {
|
||||
HostnameVerifier allHostsValid = new HostnameVerifier() {
|
||||
public boolean verify(String hostname, SSLSession session) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static void setSSLVerifyPeerCertificate(boolean verify) {
|
||||
if (verify) {
|
||||
HttpsURLConnection.setDefaultSSLSocketFactory(defaultSSLSocketFactory);
|
||||
} else {
|
||||
// Create a trust manager that does not validate certificate chains
|
||||
TrustManager[] trustAllCerts = new TrustManager[]{
|
||||
new X509TrustManager() {
|
||||
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void checkClientTrusted(X509Certificate[] certs, String authType) {
|
||||
}
|
||||
|
||||
public void checkServerTrusted(X509Certificate[] certs, String authType) {
|
||||
}
|
||||
}
|
||||
};
|
||||
// Install the all-trusting trust manager
|
||||
SSLContext sc = SSLContext.getInstance("SSL");
|
||||
sc.init(null, trustAllCerts, new java.security.SecureRandom());
|
||||
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> Predicate<T> not(Predicate<T> p) {
|
||||
return p.negate();
|
||||
}
|
||||
|
||||
public static <V, T> Stream<V> flatMap(Stream<T> stream,
|
||||
Function<? super T, Optional<? extends V>> mappingFunction) {
|
||||
return stream.map(mappingFunction).filter(Optional::isPresent).map(Optional::get);
|
||||
}
|
||||
|
||||
public static <T> Stream<T> optional2Stream(Optional<T> optional) {
|
||||
return optional.map(Stream::of).orElse(Stream.empty());
|
||||
}
|
||||
|
||||
public static void setSystemPropertyIfNotDefined(String key, String value) {
|
||||
if (System.getProperty(key) == null) {
|
||||
System.setProperty(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static void deletePath(Path path) {
|
||||
Files.walk(path)
|
||||
.sorted(Comparator.reverseOrder())
|
||||
.map(Path::toFile)
|
||||
.forEach(File::delete);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns an element of a list at the given position counting from the end of the list,
|
||||
* inspiration comes from the Python programming language where the element
|
||||
* at position -1 is the last element of the list, the one at position -2 is the element before the last and so on
|
||||
*
|
||||
* @param list the list from which the element will be retrieved
|
||||
* @param negativeOffset a negative integer representing the offset from the end of the list
|
||||
* @param <T> the type parameter of the list
|
||||
* @return the designated list element
|
||||
* @throws IndexOutOfBoundsException if the negativeOffset is out of range
|
||||
* (negativeOffset ≥ 0 || negativeOffset < -size())
|
||||
*/
|
||||
public static <T> T tail(List<T> list, int negativeOffset) {
|
||||
return list.get(list.size() + negativeOffset);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list the input list
|
||||
* @param <T> the type parameter of the list
|
||||
* @return the last element of the input list
|
||||
* @throws IndexOutOfBoundsException if the list is empty
|
||||
*/
|
||||
public static <T> T tail(List<T> list) {
|
||||
return tail(list, -1);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This methods simply removes the last element of the list and returns it
|
||||
*
|
||||
* @param list the input list
|
||||
* @param <T> the type parameter of the {@link List}
|
||||
* @throws IndexOutOfBoundsException if the list is empty
|
||||
* @throws UnsupportedOperationException if the remove operation is not supported by this list
|
||||
* @return the input list
|
||||
*/
|
||||
public static <T> T pop(List<T> list) {
|
||||
return list.remove(list.size() - 1);
|
||||
}
|
||||
|
||||
public static <T> Stream<T> streamCat(Stream<T>... streams) {
|
||||
Stream<T> result = Stream.empty();
|
||||
for (Stream<T> s : streams) {
|
||||
result = Stream.concat(result, s);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param template Template text containing the variables to be replaced by this method. <br>
|
||||
* Variables follow the format ${variable_name}. <br>
|
||||
* Example: <br>
|
||||
* "This template was created by ${author}."
|
||||
* @param valuesMap A hashmap with the values of the variables to be replaced. <br>
|
||||
* The key is the variable name and the value is the value to be replaced in the template. <br>
|
||||
* Example: <br>
|
||||
* {"author" => "John Doe"}
|
||||
* @return The template text (String) with the variable names replaced by the values passed in the map. <br>
|
||||
* If any of the variable names is not contained in the map it will be replaced by an empty string. <br>
|
||||
* Example: <br>
|
||||
* "This template was created by John Doe."
|
||||
*/
|
||||
public static String renderTemplate(String template, Map<String, Object> valuesMap) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
Object absent = new Object();
|
||||
|
||||
int cursor = 0;
|
||||
while (cursor < template.length()) {
|
||||
String key;
|
||||
char ch = template.charAt(cursor);
|
||||
if (ch != '$' || (cursor > 0 && template.charAt(cursor - 1) == '\\')) {
|
||||
sb.append(template.charAt(cursor++));
|
||||
} else if (cursor + 1 < template.length() && template.charAt(cursor + 1) == '{') {
|
||||
int end = template.indexOf('}', cursor + 1);
|
||||
key = template.substring(cursor + 2, end);
|
||||
Object value = valuesMap.getOrDefault(key, absent);
|
||||
if (value != absent) {
|
||||
sb.append(value.toString());
|
||||
} else {
|
||||
raise(MissingFormatArgumentException.class, "Missing value for placeholder '%s'", key);
|
||||
}
|
||||
cursor = end + 1;
|
||||
} else {
|
||||
sb.append(template.charAt(cursor++));
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param reader the reader reading the template text
|
||||
* @param valuesMap a map containing the values to replace in the template
|
||||
* {@link #renderTemplate(String, Map)}
|
||||
*/
|
||||
@SneakyThrows
|
||||
public static String renderTemplate(Reader reader, Map<String, Object> valuesMap) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
char[] buf = new char[1024];
|
||||
int read;
|
||||
while (!((read = reader.read(buf)) < 0)) {
|
||||
sb.append(buf, 0, read);
|
||||
}
|
||||
return renderTemplate(sb.toString(), valuesMap);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static String readAll(Reader reader) {
|
||||
char[] buffer = new char[1024];
|
||||
StringBuilder sb = new StringBuilder();
|
||||
int read;
|
||||
while (!((read = reader.read(buffer, 0, buffer.length)) < 0)) {
|
||||
sb.append(buffer, 0, read);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static Path computeCacheDirectory(String appName) {
|
||||
return Stream.of(
|
||||
Optional.ofNullable(System.getProperty("user.home"))
|
||||
.map(prefix -> Paths.get(prefix, ".cache", appName)),
|
||||
Optional.ofNullable(System.getProperty("java.io.tmpdir")).map(Paths::get).map(p -> p.resolve(appName)),
|
||||
Optional.of(Paths.get("/tmp", appName)))
|
||||
.flatMap(JWO::optional2Stream)
|
||||
.filter(JWO::validateCacheDirectory)
|
||||
.findFirst()
|
||||
.orElseThrow(() -> newThrowable(FileNotFoundException.class, "Unable to find a usable cache directory"));
|
||||
}
|
||||
|
||||
private static boolean validateCacheDirectory(Path candidate) {
|
||||
try {
|
||||
if (!Files.exists(candidate)) {
|
||||
Files.createDirectories(candidate);
|
||||
return true;
|
||||
} else if (!Files.isDirectory(candidate)) {
|
||||
log.debug("Cache directory '{}' discarded because it is not a directory", candidate.toString());
|
||||
return false;
|
||||
} else if (!Files.isWritable(candidate)) {
|
||||
log.debug("Cache directory '{}' discarded because it is not writable", candidate.toString());
|
||||
return false;
|
||||
} else {
|
||||
log.info("Using cache directory '{}'", candidate.toString());
|
||||
return true;
|
||||
}
|
||||
} catch (Exception ioe) {
|
||||
log.debug(String.format("Cache directory '%s' discarded: %s", candidate.toString(), ioe.getMessage()), ioe);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static <T, U extends T> Optional<U> cast(T value, Class<U> cls) {
|
||||
if(cls.isInstance(value)) {
|
||||
return Optional.of((U) value);
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
}
|
60
src/main/java/net/woggioni/jwo/JavaProcessBuilder.java
Normal file
60
src/main/java/net/woggioni/jwo/JavaProcessBuilder.java
Normal file
@@ -0,0 +1,60 @@
|
||||
package net.woggioni.jwo;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class JavaProcessBuilder {
|
||||
|
||||
private final Class<?> mainClass;
|
||||
|
||||
private String javaHome = System.getProperty("java.home");
|
||||
private String classPath = System.getProperty("java.class.path");
|
||||
private Properties properties = new Properties();
|
||||
private String[] cliArgs = null;
|
||||
|
||||
public JavaProcessBuilder javaHome(String javaHome) {
|
||||
this.javaHome = javaHome;
|
||||
return this;
|
||||
}
|
||||
|
||||
public JavaProcessBuilder classPath(String classPath) {
|
||||
this.classPath = classPath;
|
||||
return this;
|
||||
}
|
||||
|
||||
public JavaProcessBuilder properties(Properties properties) {
|
||||
this.properties = properties;
|
||||
return this;
|
||||
}
|
||||
|
||||
public JavaProcessBuilder cliArgs(String ...cliArgs) {
|
||||
this.cliArgs = cliArgs;
|
||||
return this;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public ProcessBuilder exec() {
|
||||
Path javaBin = Paths.get(javaHome, "bin", "java");
|
||||
Stream<String> propertyStream = Optional.ofNullable(properties)
|
||||
.map(p -> p.entrySet().stream())
|
||||
.orElse(Stream.empty())
|
||||
.map(entry -> String.format("-D%s=%s", entry.getKey(), entry.getValue()));
|
||||
List<String> cmd = JWO.streamCat(
|
||||
Stream.of(javaBin.toString(), "-cp", classPath),
|
||||
propertyStream,
|
||||
Stream.of(mainClass.getCanonicalName()),
|
||||
Optional.ofNullable(cliArgs).map(Arrays::stream).orElse(Stream.empty()))
|
||||
.collect(Collectors.toList());
|
||||
return new ProcessBuilder(cmd);
|
||||
}
|
||||
}
|
244
src/main/java/net/woggioni/jwo/ListView.java
Normal file
244
src/main/java/net/woggioni/jwo/ListView.java
Normal file
@@ -0,0 +1,244 @@
|
||||
package net.woggioni.jwo;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static java.lang.Math.min;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class ListView<T> implements List<T> {
|
||||
private final List<T> delegate;
|
||||
private final int start;
|
||||
private final int end;
|
||||
|
||||
public ListView(List<T> delegate, int start) {
|
||||
this(delegate, start, -1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return end < 0 ? delegate.size() : min(end, delegate.size()) - start;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return size() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object o) {
|
||||
Iterator<T> it = iterator();
|
||||
while (it.hasNext()) {
|
||||
if(Objects.equals(o, it.next())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
return new Iterator<T>() {
|
||||
int index = start;
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return end < 0 ? index < size() : index < min(end, size());
|
||||
}
|
||||
|
||||
@Override
|
||||
public T next() {
|
||||
return get(index++);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] toArray() {
|
||||
int size = size();
|
||||
Object[] result = new Object[size];
|
||||
for(int i = 0; i < size; i++) {
|
||||
result[i] = get(i);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T1> T1[] toArray(T1[] t1s) {
|
||||
int size = size();
|
||||
T1[] result = Arrays.copyOf(t1s, size);
|
||||
for(int i = 0; i < size; i++) {
|
||||
result[i] = (T1) get(i);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(T t) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object o) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAll(Collection<?> collection) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(Collection<? extends T> collection) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(int i, Collection<? extends T> collection) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAll(Collection<?> collection) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retainAll(Collection<?> collection) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get(int i) {
|
||||
int index = start + i;
|
||||
if(end >= 0 && index < end) {
|
||||
throw new IndexOutOfBoundsException(Integer.toString(i));
|
||||
}
|
||||
return delegate.get(start + i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T set(int i, T t) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(int i, T t) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T remove(int i) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int indexOf(Object o) {
|
||||
int size = size();
|
||||
for(int i = 0; i < size; i++) {
|
||||
if(Objects.equals(o, get(i))) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int lastIndexOf(Object o) {
|
||||
int size = size();
|
||||
for(int i = size - 1; i >= 0; i--) {
|
||||
if(Objects.equals(o, get(i))) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListIterator<T> listIterator() {
|
||||
return new ListViewIterator<>(this, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListIterator<T> listIterator(int i) {
|
||||
if(i < 0 || i > size()) {
|
||||
throw new IndexOutOfBoundsException(Integer.toString(0));
|
||||
} else {
|
||||
return new ListViewIterator<>(this, start);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<T> subList(int i, int i1) {
|
||||
if(i < 0) {
|
||||
throw new IndexOutOfBoundsException(Integer.toString(0));
|
||||
} else if(i1 > size()) {
|
||||
throw new IndexOutOfBoundsException(Integer.toString(i1));
|
||||
} else {
|
||||
return new ListView<>(delegate, start + i, start + i1);
|
||||
}
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
private static class ListViewIterator<T> implements ListIterator<T> {
|
||||
private final ListView<T> listView;
|
||||
int size;
|
||||
int i;
|
||||
|
||||
public ListViewIterator(ListView<T> listView, int start) {
|
||||
this.listView = listView;
|
||||
size = listView.size();
|
||||
i = start;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return i < size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T next() {
|
||||
return listView.get(i++);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPrevious() {
|
||||
return i > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T previous() {
|
||||
return listView.get(--i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nextIndex() {
|
||||
return i + 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int previousIndex() {
|
||||
return i;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(T t) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(T t) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
}
|
39
src/main/java/net/woggioni/jwo/LockFile.java
Normal file
39
src/main/java/net/woggioni/jwo/LockFile.java
Normal file
@@ -0,0 +1,39 @@
|
||||
package net.woggioni.jwo;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.channels.FileLock;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class LockFile implements AutoCloseable {
|
||||
|
||||
private final Path path;
|
||||
private final FileLock lock;
|
||||
private final RandomAccessFile randomAccessFile;
|
||||
|
||||
public LockFile(Path path) {
|
||||
this(path, false);
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void close() {
|
||||
lock.release();
|
||||
randomAccessFile.close();
|
||||
}
|
||||
}
|
52
src/main/java/net/woggioni/jwo/http/HttpClient.java
Normal file
52
src/main/java/net/woggioni/jwo/http/HttpClient.java
Normal file
@@ -0,0 +1,52 @@
|
||||
package net.woggioni.jwo.http;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public class HttpClient {
|
||||
|
||||
private SSLSocketFactory socketFactory;
|
||||
|
||||
public HttpClient() {}
|
||||
|
||||
public HttpClient(final SSLContext sslContext) {
|
||||
socketFactory = sslContext.getSocketFactory();
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public HttpsURLConnection call(HttpRequest httpRequest) {
|
||||
HttpsURLConnection conn = (HttpsURLConnection) httpRequest.url.openConnection();
|
||||
if(socketFactory != null) {
|
||||
conn.setSSLSocketFactory(socketFactory);
|
||||
}
|
||||
conn.setInstanceFollowRedirects(false);
|
||||
conn.setRequestMethod(httpRequest.method.text);
|
||||
httpRequest.headers.forEach((key, value) ->
|
||||
value.forEach(headerValue -> conn.addRequestProperty(key, headerValue)));
|
||||
switch (httpRequest.method) {
|
||||
case PUT:
|
||||
case POST:
|
||||
case DELETE:
|
||||
if (httpRequest.body != null) {
|
||||
conn.setDoOutput(true);
|
||||
byte[] buffer = new byte[1024];
|
||||
OutputStream os = conn.getOutputStream();
|
||||
while (true) {
|
||||
int read = httpRequest.body.read(buffer, 0, buffer.length);
|
||||
if (read < 0) break;
|
||||
os.write(buffer, 0, read);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case GET:
|
||||
case HEAD:
|
||||
case OPTIONS:
|
||||
break;
|
||||
}
|
||||
return conn;
|
||||
}
|
||||
}
|
16
src/main/java/net/woggioni/jwo/http/HttpMethod.java
Normal file
16
src/main/java/net/woggioni/jwo/http/HttpMethod.java
Normal file
@@ -0,0 +1,16 @@
|
||||
package net.woggioni.jwo.http;
|
||||
|
||||
public enum HttpMethod {
|
||||
PUT("PUT"),
|
||||
GET("GET"),
|
||||
POST("POST"),
|
||||
DELETE("DELETE"),
|
||||
HEAD("HEAD"),
|
||||
OPTIONS("OPTIONS");
|
||||
|
||||
public final String text;
|
||||
|
||||
HttpMethod(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
}
|
47
src/main/java/net/woggioni/jwo/http/HttpRequest.java
Normal file
47
src/main/java/net/woggioni/jwo/http/HttpRequest.java
Normal file
@@ -0,0 +1,47 @@
|
||||
package net.woggioni.jwo.http;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Builder;
|
||||
import net.woggioni.jwo.http.HttpMethod;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Builder(builderMethodName = "privateBuilder", access = AccessLevel.PUBLIC)
|
||||
public class HttpRequest {
|
||||
|
||||
final URL url;
|
||||
|
||||
final HttpMethod method = HttpMethod.GET;
|
||||
|
||||
final Map<String, List<String>> headers = Collections.emptyMap();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
17
src/main/java/net/woggioni/jwo/http/HttpStatus.java
Normal file
17
src/main/java/net/woggioni/jwo/http/HttpStatus.java
Normal file
@@ -0,0 +1,17 @@
|
||||
package net.woggioni.jwo.http;
|
||||
|
||||
public enum HttpStatus {
|
||||
OK(200),
|
||||
INTERNAL_SERVER_ERROR(500),
|
||||
BAD_REQUEST(400),
|
||||
UNAUTHORIZED(401),
|
||||
FORBIDDEN(403),
|
||||
NOT_FOUND(404),
|
||||
CONFLICT(409);
|
||||
|
||||
public final int code;
|
||||
|
||||
HttpStatus(int code) {
|
||||
this.code = code;
|
||||
}
|
||||
}
|
38
src/main/java/net/woggioni/jwo/io/CircularBuffer.java
Normal file
38
src/main/java/net/woggioni/jwo/io/CircularBuffer.java
Normal file
@@ -0,0 +1,38 @@
|
||||
package net.woggioni.jwo.io;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.io.Reader;
|
||||
|
||||
public class CircularBuffer {
|
||||
|
||||
private int[] buffer;
|
||||
private Reader reader;
|
||||
private int delta = 0, cursor = 0;
|
||||
|
||||
public CircularBuffer(Reader reader, int size) {
|
||||
this.reader = reader;
|
||||
buffer = new int[size];
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public int next() {
|
||||
if (delta < 0)
|
||||
return buffer[Math.floorMod(cursor + delta++, buffer.length)];
|
||||
else {
|
||||
int result = reader.read();
|
||||
if (result < 0) return result;
|
||||
buffer[cursor] = result;
|
||||
cursor = (cursor + 1) % buffer.length;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public int prev() {
|
||||
return buffer[cursor + --delta >= 0 ? cursor + delta : cursor + delta + buffer.length];
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return buffer.length;
|
||||
}
|
||||
}
|
55
src/main/java/net/woggioni/jwo/io/CircularInputStream.java
Normal file
55
src/main/java/net/woggioni/jwo/io/CircularInputStream.java
Normal file
@@ -0,0 +1,55 @@
|
||||
package net.woggioni.jwo.io;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class CircularInputStream extends InputStream {
|
||||
final byte[] monomer;
|
||||
final int maxLoops;
|
||||
int loops = 0;
|
||||
int cursor = 0;
|
||||
|
||||
public CircularInputStream(byte[] monomer) {
|
||||
this(monomer, -1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() {
|
||||
if (cursor < 0) {
|
||||
return cursor;
|
||||
} else {
|
||||
int result = monomer[cursor];
|
||||
incrementCursor();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) {
|
||||
int read = 0;
|
||||
while (read < len) {
|
||||
if(cursor < 0) break;
|
||||
int toBeRead = Math.min(monomer.length - cursor, len - read);
|
||||
System.arraycopy(monomer, cursor, b, off + read, toBeRead);
|
||||
incrementCursor(toBeRead);
|
||||
read += toBeRead;
|
||||
}
|
||||
return read > 0 ? read : -1;
|
||||
}
|
||||
|
||||
int incrementCursor() {
|
||||
return incrementCursor(1);
|
||||
}
|
||||
|
||||
int incrementCursor(int increment) {
|
||||
loops = (loops * monomer.length + increment) / monomer.length;
|
||||
if (maxLoops < 0 || loops < maxLoops) {
|
||||
cursor = (cursor + increment) % monomer.length;
|
||||
} else {
|
||||
cursor = -1;
|
||||
}
|
||||
return cursor;
|
||||
}
|
||||
}
|
39
src/main/java/net/woggioni/jwo/io/LookAheadInputStream.java
Normal file
39
src/main/java/net/woggioni/jwo/io/LookAheadInputStream.java
Normal file
@@ -0,0 +1,39 @@
|
||||
package net.woggioni.jwo.io;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
public class LookAheadInputStream extends InputStream {
|
||||
|
||||
private final byte[] buffer = new byte[1024];
|
||||
private final InputStream stream;
|
||||
private int bufferFill = -1;
|
||||
private int cursor = -1;
|
||||
private int currentByte;
|
||||
|
||||
public LookAheadInputStream(InputStream stream) {
|
||||
this.stream = stream;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public int read() {
|
||||
if (cursor > bufferFill) {
|
||||
return -1;
|
||||
} else if (cursor == bufferFill) {
|
||||
do {
|
||||
bufferFill = stream.read(buffer, 0, buffer.length) - 1;
|
||||
cursor = 0;
|
||||
} while(bufferFill == -1);
|
||||
currentByte = bufferFill == -2 ? -1 : Math.floorMod(buffer[0], 256);
|
||||
} else {
|
||||
currentByte = Math.floorMod(buffer[++cursor], 256);
|
||||
}
|
||||
return currentByte;
|
||||
}
|
||||
|
||||
public int getCurrentByte() {
|
||||
return currentByte;
|
||||
}
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
package net.woggioni.jwo.io;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
|
||||
public class LookAheadTextInputStream extends InputStream {
|
||||
|
||||
private final char[] buffer = new char[1024];
|
||||
private final Reader reader;
|
||||
private int bufferFill = -1;
|
||||
private int cursor = -1;
|
||||
private int currentChar;
|
||||
|
||||
|
||||
public LookAheadTextInputStream(Reader reader) {
|
||||
this.reader = reader;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public int read() {
|
||||
if (cursor > bufferFill) {
|
||||
return -1;
|
||||
} else if (cursor == bufferFill) {
|
||||
do {
|
||||
bufferFill = reader.read(buffer, 0, buffer.length) - 1;
|
||||
cursor = 0;
|
||||
} while(bufferFill == -1);
|
||||
currentChar = bufferFill == -2 ? -1 : buffer[0];
|
||||
} else {
|
||||
currentChar = buffer[++cursor];
|
||||
}
|
||||
return currentChar;
|
||||
}
|
||||
|
||||
public int getCurrentByte() {
|
||||
return currentChar;
|
||||
}
|
||||
}
|
25
src/main/java/net/woggioni/jwo/tree/StackContext.java
Normal file
25
src/main/java/net/woggioni/jwo/tree/StackContext.java
Normal file
@@ -0,0 +1,25 @@
|
||||
package net.woggioni.jwo.tree;
|
||||
|
||||
/**
|
||||
* This interface exposes the methods that are visible to the user of
|
||||
* {@link TreeWalker}, it allows to
|
||||
* set/get a custom object in the current stack context or to get the current link's Aci
|
||||
* @param <T> the type of the context object used
|
||||
*/
|
||||
public interface StackContext<NODE extends TreeNode, T> {
|
||||
|
||||
/**
|
||||
* @param ctx the user object to set for this stack level
|
||||
*/
|
||||
void setContext(T ctx);
|
||||
|
||||
/**
|
||||
* @return the current user object
|
||||
*/
|
||||
T getContext();
|
||||
|
||||
/**
|
||||
* @return the current TreeNode
|
||||
*/
|
||||
NODE getNode();
|
||||
}
|
7
src/main/java/net/woggioni/jwo/tree/TreeNode.java
Normal file
7
src/main/java/net/woggioni/jwo/tree/TreeNode.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package net.woggioni.jwo.tree;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
public interface TreeNode<NODE extends TreeNode> {
|
||||
Iterator<NODE> children();
|
||||
}
|
38
src/main/java/net/woggioni/jwo/tree/TreeNodeVisitor.java
Normal file
38
src/main/java/net/woggioni/jwo/tree/TreeNodeVisitor.java
Normal file
@@ -0,0 +1,38 @@
|
||||
package net.woggioni.jwo.tree;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This interface must be implemented by the user of {@link TreeWalker} and its methods will be called by
|
||||
* {@link TreeWalker#walk(TreeNode)}. The methods will receive as an input a list of {@link StackContext}
|
||||
* instances each one correspond to a node in the tree, each node is preceded in the list
|
||||
* by its parents in the tree. Each instance has a method, {@link StackContext#setContext(Object)}
|
||||
* to set a custom object that can be used in the {@link #visitPre(List)} method and the method
|
||||
* {@link StackContext#getContext()} that can be used in the {@link #visitPost(List)} method to retrieve
|
||||
* the same instance. This is to provide support for algorithms that require both pre-order and post-order logic.
|
||||
* The last element of the list corresponds to the node currently being traversed.
|
||||
* @param <T> the type of the context object used
|
||||
*/
|
||||
public interface TreeNodeVisitor<NODE extends TreeNode<NODE>, T> {
|
||||
|
||||
enum VisitOutcome {
|
||||
CONTINUE, SKIP, EARLY_EXIT
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will be called for each link using
|
||||
* <a href="https://en.wikipedia.org/wiki/Tree_traversal#Pre-order_(NLR)">a Depth-first pre-oder algorithm</a>
|
||||
* @param stack is a list of {@link StackContext} instances corresponding to the full path from the root to the
|
||||
* current node in the tree
|
||||
* @return a boolean that will be used to decide whether to traverse the subtree rooted in the current link or not
|
||||
*/
|
||||
default VisitOutcome visitPre(List<StackContext<NODE, T>> stack) { return VisitOutcome.CONTINUE; }
|
||||
|
||||
/**
|
||||
* This method will be called for each node using
|
||||
* <a href="https://en.wikipedia.org/wiki/Tree_traversal#Post-order_(LRN)">a Depth-first post-oder algorithm</a>
|
||||
* @param stack is a list of {@link StackContext} instances corresponding to the full path from the root to the
|
||||
* current node in the tree
|
||||
*/
|
||||
default void visitPost(List<StackContext<NODE, T>> stack) {}
|
||||
}
|
76
src/main/java/net/woggioni/jwo/tree/TreeWalker.java
Normal file
76
src/main/java/net/woggioni/jwo/tree/TreeWalker.java
Normal file
@@ -0,0 +1,76 @@
|
||||
package net.woggioni.jwo.tree;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import static net.woggioni.jwo.JWO.pop;
|
||||
import static net.woggioni.jwo.JWO.tail;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class TreeWalker<NODE extends TreeNode<NODE>, T> {
|
||||
|
||||
@RequiredArgsConstructor
|
||||
private static class StackElement<NODE extends TreeNode<NODE>, T> implements StackContext<NODE, T> {
|
||||
|
||||
@Getter
|
||||
final NODE node;
|
||||
|
||||
Iterator<NODE> childrenIterator;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
T context;
|
||||
}
|
||||
|
||||
private final TreeNodeVisitor<NODE, T> visitor;
|
||||
|
||||
/**
|
||||
* This methods does the actual job of traversing the tree calling the methods of the provided
|
||||
* {@link TreeNodeVisitor} instance
|
||||
* @param root the root node of the tree
|
||||
*/
|
||||
public void walk(NODE root) {
|
||||
List<StackElement<NODE, T>> stack = new ArrayList<>();
|
||||
StackElement<NODE, T> rootStackElement = new StackElement<>(root);
|
||||
stack.add(rootStackElement);
|
||||
List<StackContext<NODE, T>> publicStack = Collections.unmodifiableList(stack);
|
||||
switch (visitor.visitPre(publicStack)) {
|
||||
case CONTINUE:
|
||||
rootStackElement.childrenIterator = root.children();
|
||||
break;
|
||||
case SKIP:
|
||||
rootStackElement.childrenIterator = null;
|
||||
break;
|
||||
case EARLY_EXIT:
|
||||
return;
|
||||
}
|
||||
while(!stack.isEmpty()) {
|
||||
StackElement<NODE, T> lastElement = tail(stack);
|
||||
if(lastElement.childrenIterator != null && lastElement.childrenIterator.hasNext()) {
|
||||
NODE childNode = lastElement.childrenIterator.next();
|
||||
StackElement<NODE, T> childStackElement =
|
||||
new StackElement<>(childNode);
|
||||
stack.add(childStackElement);
|
||||
switch (visitor.visitPre(publicStack)) {
|
||||
case CONTINUE:
|
||||
childStackElement.childrenIterator = childNode.children();
|
||||
break;
|
||||
case SKIP:
|
||||
childStackElement.childrenIterator = null;
|
||||
break;
|
||||
case EARLY_EXIT:
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
visitor.visitPost(publicStack);
|
||||
pop(stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
21
src/main/java/net/woggioni/jwo/tuple/MutableTuple2.java
Normal file
21
src/main/java/net/woggioni/jwo/tuple/MutableTuple2.java
Normal file
@@ -0,0 +1,21 @@
|
||||
package net.woggioni.jwo.tuple;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
@EqualsAndHashCode
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class MutableTuple2<T, U> {
|
||||
public T _1;
|
||||
public U _2;
|
||||
|
||||
public static <X extends Comparable<X>, Y extends Comparable<Y>> Comparator<MutableTuple2<X, Y>> getComparator(Class<X> cls1, Class<Y> cls2) {
|
||||
return Comparator
|
||||
.comparing((MutableTuple2<X, Y> t) -> t._1)
|
||||
.thenComparing((MutableTuple2<X, Y> t) -> t._2);
|
||||
}
|
||||
}
|
24
src/main/java/net/woggioni/jwo/tuple/MutableTuple3.java
Normal file
24
src/main/java/net/woggioni/jwo/tuple/MutableTuple3.java
Normal file
@@ -0,0 +1,24 @@
|
||||
package net.woggioni.jwo.tuple;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
@EqualsAndHashCode
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class MutableTuple3<T, U, V> {
|
||||
public T _1;
|
||||
public U _2;
|
||||
public V _3;
|
||||
|
||||
public static <X extends Comparable<X>, Y extends Comparable<Y>, Z extends Comparable<Z>> Comparator<MutableTuple3<X, Y, Z>> getComparator(Class<X> cls1, Class<Y> cls2, Class<Z> cls3) {
|
||||
return Comparator
|
||||
.comparing((MutableTuple3<X, Y, Z> t) -> t._1)
|
||||
.thenComparing((MutableTuple3<X, Y, Z> t) -> t._2)
|
||||
.thenComparing((MutableTuple3<X, Y, Z> t) -> t._3);
|
||||
}
|
||||
}
|
||||
|
19
src/main/java/net/woggioni/jwo/tuple/Tuple2.java
Normal file
19
src/main/java/net/woggioni/jwo/tuple/Tuple2.java
Normal file
@@ -0,0 +1,19 @@
|
||||
package net.woggioni.jwo.tuple;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
@EqualsAndHashCode
|
||||
@RequiredArgsConstructor
|
||||
public class Tuple2<T, U> {
|
||||
public final T _1;
|
||||
public final U _2;
|
||||
|
||||
public static <X extends Comparable<X>, Y extends Comparable<Y>> Comparator<Tuple2<X, Y>> getComparator(Class<X> cls1, Class<Y> cls2) {
|
||||
return Comparator
|
||||
.comparing((Tuple2<X, Y> t) -> t._1)
|
||||
.thenComparing((Tuple2<X, Y> t) -> t._2);
|
||||
}
|
||||
}
|
21
src/main/java/net/woggioni/jwo/tuple/Tuple3.java
Normal file
21
src/main/java/net/woggioni/jwo/tuple/Tuple3.java
Normal file
@@ -0,0 +1,21 @@
|
||||
package net.woggioni.jwo.tuple;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
@EqualsAndHashCode
|
||||
@RequiredArgsConstructor
|
||||
public class Tuple3<T, U, V> {
|
||||
public final T _1;
|
||||
public final U _2;
|
||||
public final V _3;
|
||||
|
||||
public static <X extends Comparable<X>, Y extends Comparable<Y>, Z extends Comparable<Z>> Comparator<Tuple3<X, Y, Z>> getComparator(Class<X> cls1, Class<Y> cls2, Class<Z> cls3) {
|
||||
return Comparator
|
||||
.comparing((Tuple3<X, Y, Z> t) -> t._1)
|
||||
.thenComparing((Tuple3<X, Y, Z> t) -> t._2)
|
||||
.thenComparing((Tuple3<X, Y, Z> t) -> t._3);
|
||||
}
|
||||
}
|
86
src/test/java/net/woggioni/jwo/utils/JWOTest.java
Normal file
86
src/test/java/net/woggioni/jwo/utils/JWOTest.java
Normal file
@@ -0,0 +1,86 @@
|
||||
package net.woggioni.jwo.utils;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import net.woggioni.jwo.Chronometer;
|
||||
import net.woggioni.jwo.JWO;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.URI;
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class JWOTest {
|
||||
|
||||
@Test
|
||||
public void flatMapTest() {
|
||||
Stream<Integer> s = Stream.of(3, 4);
|
||||
List<Integer> l = JWO.flatMap(s, (n) -> {
|
||||
if (n > 3) return Optional.of(n);
|
||||
else return Optional.empty();
|
||||
}).collect(Collectors.toList());
|
||||
Assert.assertEquals(Collections.singletonList(4), l);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void optional2StreamTest() {
|
||||
Integer integer = 3;
|
||||
Optional<Integer> s = Optional.of(integer);
|
||||
JWO.optional2Stream(s).forEach(n -> Assert.assertEquals(integer, n));
|
||||
s = Optional.empty();
|
||||
JWO.optional2Stream(s).forEach(n -> Assert.fail());
|
||||
}
|
||||
|
||||
@Test
|
||||
@SneakyThrows
|
||||
public void testRenderTemplate() {
|
||||
Map valuesMap = new HashMap<String, String>();
|
||||
valuesMap.put("author", "John Doe");
|
||||
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!\n";
|
||||
try (Reader reader = new InputStreamReader(
|
||||
JWOTest.class.getResourceAsStream("/render_template_test.txt"))) {
|
||||
String rendered = JWO.renderTemplate(reader, valuesMap);
|
||||
Assert.assertEquals(expected, rendered);
|
||||
}
|
||||
try (Reader reader = new InputStreamReader(
|
||||
JWOTest.class.getResourceAsStream("/render_template_test.txt"))) {
|
||||
String rendered = JWO.renderTemplate(JWO.readAll(reader), valuesMap);
|
||||
Assert.assertEquals(expected, rendered);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static String renderTemplateNaive(String template, Map<String, Object> valuesMap){
|
||||
StringBuffer formatter = new StringBuffer(template);
|
||||
Object absent = new Object();
|
||||
|
||||
Matcher matcher = Pattern.compile("\\$\\{(\\w+)}").matcher(template);
|
||||
|
||||
while (matcher.find()) {
|
||||
String key = matcher.group(1);
|
||||
|
||||
String formatKey = String.format("${%s}", key);
|
||||
int index = formatter.indexOf(formatKey);
|
||||
|
||||
// If the key is present:
|
||||
// - If the value is not null, then replace the variable for the value
|
||||
// - If the value is null, replace the variable for empty string
|
||||
// If the key is not present, leave the variable untouched.
|
||||
if (index != -1) {
|
||||
Object value = valuesMap.getOrDefault(key, absent);
|
||||
if(value != absent) {
|
||||
String valueStr = value != null ? value.toString() : "";
|
||||
formatter.replace(index, index + formatKey.length(), valueStr);
|
||||
}
|
||||
}
|
||||
}
|
||||
return formatter.toString();
|
||||
}
|
||||
}
|
@@ -0,0 +1,37 @@
|
||||
package net.woggioni.jwo.utils.io;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import net.woggioni.jwo.io.CircularBuffer;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.security.DigestInputStream;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Random;
|
||||
|
||||
public class CircularBufferTest {
|
||||
|
||||
@Test
|
||||
@SneakyThrows
|
||||
public void test() {
|
||||
MessageDigest streamDigest = MessageDigest.getInstance("MD5"), outputDigest = MessageDigest.getInstance("MD5");
|
||||
InputStream is = new DigestInputStream(getClass().getResourceAsStream("/render_template_test.txt"), streamDigest);
|
||||
CircularBuffer cb = new CircularBuffer(new InputStreamReader(is), 32);
|
||||
Random rand = new Random();
|
||||
while (true) {
|
||||
int b = cb.next();
|
||||
if (b < 0) break;
|
||||
if (rand.nextInt() % 2 == 0) {
|
||||
cb.prev();
|
||||
} else {
|
||||
char c = (char) b;
|
||||
outputDigest.update((byte) b);
|
||||
System.out.print(c);
|
||||
}
|
||||
}
|
||||
System.out.println();
|
||||
Assert.assertArrayEquals(streamDigest.digest(), outputDigest.digest());
|
||||
}
|
||||
}
|
110
src/test/java/net/woggioni/jwo/utils/tree/TreeWalkerTest.java
Normal file
110
src/test/java/net/woggioni/jwo/utils/tree/TreeWalkerTest.java
Normal file
@@ -0,0 +1,110 @@
|
||||
package net.woggioni.jwo.utils.tree;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.woggioni.jwo.tree.StackContext;
|
||||
import net.woggioni.jwo.tree.TreeNode;
|
||||
import net.woggioni.jwo.tree.TreeNodeVisitor;
|
||||
import net.woggioni.jwo.tree.TreeWalker;
|
||||
import net.woggioni.jwo.tuple.Tuple2;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
class Node implements TreeNode<Node> {
|
||||
@Getter
|
||||
private final Integer id;
|
||||
private final List<Node> children;
|
||||
|
||||
@Override
|
||||
public Iterator<Node> children() {
|
||||
return children.iterator();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class TreeWalkerTest {
|
||||
|
||||
private Map<Integer, List<Integer>> parentChildRelationshipMap =
|
||||
Stream.of(
|
||||
new Tuple2<>(1, Collections.singletonList(2)),
|
||||
new Tuple2<>(2, Collections.singletonList(3)),
|
||||
new Tuple2<>(3, Arrays.asList(4, 5)),
|
||||
new Tuple2<>(4, Arrays.asList(6, 7)),
|
||||
new Tuple2<>(5, Collections.singletonList(8))
|
||||
).collect(Collectors.toMap(t -> t._1, t -> t._2));
|
||||
|
||||
private Map<Integer, Node> testNodeMap = parentChildRelationshipMap.entrySet().stream()
|
||||
.map(entry -> newNode(entry.getKey(), entry.getValue()))
|
||||
.collect(Collectors.toMap(Node::getId, Function.identity()));
|
||||
|
||||
private Node newNode(int id, List<Integer> children) {
|
||||
if(children == null) {
|
||||
return new Node(id, Collections.emptyList());
|
||||
} else {
|
||||
return new Node(id, children.stream()
|
||||
.map(nodeId -> newNode(nodeId, parentChildRelationshipMap.get(nodeId)))
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void treeTraversalOrderTest() {
|
||||
List<Integer> expected_pre_sequence = Stream.of(1, 2, 3, 4, 6, 7, 5, 8)
|
||||
.collect(Collectors.toList());
|
||||
List<Integer> expected_post_sequence = Stream.of(6, 7, 4, 8, 5, 3, 2, 1)
|
||||
.collect(Collectors.toList());
|
||||
Iterator<Integer> it_pre = expected_pre_sequence.iterator();
|
||||
Iterator<Integer> it_post = expected_post_sequence.iterator();
|
||||
TreeNodeVisitor<Node, Void> nodeVisitor = new TreeNodeVisitor<Node, Void>() {
|
||||
@Override
|
||||
public VisitOutcome visitPre(List<StackContext<Node, Void>> stackContextList) {
|
||||
Assert.assertTrue(it_pre.hasNext());
|
||||
Assert.assertEquals(it_pre.next(),
|
||||
stackContextList.get(stackContextList.size() - 1).getNode().getId());
|
||||
return VisitOutcome.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPost(List<StackContext<Node, Void>> stackContextList) {
|
||||
Assert.assertTrue(it_post.hasNext());
|
||||
Assert.assertEquals(it_post.next(),
|
||||
stackContextList.get(stackContextList.size() - 1).getNode().getId());
|
||||
}
|
||||
};
|
||||
TreeWalker<Node, Void> walker =
|
||||
new TreeWalker<>(nodeVisitor);
|
||||
walker.walk(testNodeMap.get(1));
|
||||
Assert.assertFalse(it_pre.hasNext());
|
||||
Assert.assertFalse(it_post.hasNext());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void filterTest() {
|
||||
List<Integer> expected_pre_sequence = Stream.of(1, 2, 3, 4, 5, 8)
|
||||
.collect(Collectors.toList());
|
||||
Iterator<Integer> it = expected_pre_sequence.iterator();
|
||||
TreeNodeVisitor<Node, Void> linkVisitor = new TreeNodeVisitor<Node, Void>() {
|
||||
@Override
|
||||
public VisitOutcome visitPre(List<StackContext<Node, Void>> nodePath) {
|
||||
Assert.assertTrue(it.hasNext());
|
||||
Integer id = nodePath.get(nodePath.size() - 1).getNode().getId();
|
||||
Assert.assertEquals(it.next(), id);
|
||||
if(Objects.equals(4, nodePath.get(nodePath.size() - 1).getNode().getId())) {
|
||||
return VisitOutcome.SKIP;
|
||||
} else {
|
||||
return VisitOutcome.CONTINUE;
|
||||
}
|
||||
}
|
||||
};
|
||||
TreeWalker<Node, Void> walker =
|
||||
new TreeWalker<>(linkVisitor);
|
||||
walker.walk(testNodeMap.get(1));
|
||||
Assert.assertFalse(it.hasNext());
|
||||
}
|
||||
}
|
1
src/test/resources/render_template_test.txt
Normal file
1
src/test/resources/render_template_test.txt
Normal file
@@ -0,0 +1 @@
|
||||
This is a ${adjective} test made by ${author} on ${date}. It's really ${adjective}!
|
Reference in New Issue
Block a user