initial commit

This commit is contained in:
2019-10-28 19:02:45 +00:00
commit 5a644ac6b5
33 changed files with 1769 additions and 0 deletions

15
benchmark/build.sbt Normal file
View 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)

View File

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

View File

@@ -0,0 +1 @@
This is a ${adjective} test made by ${author} on ${date}. It's really ${adjective}!

View File

@@ -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
View 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
View File

@@ -0,0 +1 @@
sbt.version=1.3.1

7
project/plugins.sbt Normal file
View 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")

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

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

View 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 &ge; 0 || negativeOffset &lt; -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" =&gt; "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();
}
}
}

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

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

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

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

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

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

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

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

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

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

View File

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

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

View File

@@ -0,0 +1,7 @@
package net.woggioni.jwo.tree;
import java.util.Iterator;
public interface TreeNode<NODE extends TreeNode> {
Iterator<NODE> children();
}

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

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

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

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

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

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

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

View File

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

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

View File

@@ -0,0 +1 @@
This is a ${adjective} test made by ${author} on ${date}. It's really ${adjective}!