stream, Object monitor) {
+ Objects.requireNonNull(stream, "stream cannot be null");
+ return new SyncStream<>(stream, monitor);
+ }
+
+ /**
+ * Wraps the given {@link IntStream} to make all
+ *
+ * terminal operations acquire the provided monitor's lock before execution. Intermediate operations
+ * return streams that are also synchronized on the same object. The created stream will behave identically
+ * to the provided stream in all other aspects. Use this to synchronize access to stream's source.
+ * The returned {@code IntStream}'s {@link IntStream#iterator() iterator()} and
+ * {@link IntStream#spliterator() spliterator()} methods return regular non-synchronized iterators and
+ * spliterators respectively. It is the user's responsibility to avoid concurrency issues:
+ *
+ * synchronized (stream.getMonitor()) {
+ * PrimitiveIterator.OfInt it = stream.iterator();
+ * ...
+ * }
+ *
+ * Usage example:
+ *
+ * Set<Object> s = Collections.synchronizedSet(new HashSet<>());
+ * ...
+ * IntStream stream = SyncStreams.synchronizedStream(s.stream().mapToInt(Object::hashCode), s);
+ * stream = stream.map(i -> i % 67); // Still synchronized
+ * stream.forEach(System.out::println); // Should never throw a ConcurrentModificationException
+ *
+ *
+ * @param stream the stream to wrap.
+ * @param monitor the object that the stream will use for synchronization. When {@code null}, the stream
+ * will synchronize on itself.
+ * @return a {@link SyncIntStream} synchronized on {@code monitor} and backed by {@code stream}.
+ * @throws NullPointerException if {@code stream == null}.
+ */
+ public static SyncIntStream synchronizedStream(IntStream stream, Object monitor) {
+ Objects.requireNonNull(stream, "stream cannot be null");
+ return new SyncIntStream(stream, monitor);
+ }
+
+ /**
+ * Wraps the given {@link LongStream} to make all
+ *
+ * terminal operations acquire the provided monitor's lock before execution. Intermediate operations
+ * return streams that are also synchronized on the same object. The created stream will behave identically
+ * to the provided stream in all other aspects. Use this to synchronize access to stream's source.
+ * The returned {@code LongStream}'s {@link LongStream#iterator() iterator()} and
+ * {@link LongStream#spliterator() spliterator()} methods return regular non-synchronized iterators and
+ * spliterators respectively. It is the user's responsibility to avoid concurrency issues:
+ *
+ * synchronized (stream.getMonitor()) {
+ * PrimitiveIterator.OfLong it = stream.iterator();
+ * ...
+ * }
+ *
+ * Usage example:
+ *
+ * Set<Object> s = Collections.synchronizedSet(new HashSet<>());
+ * ...
+ * LongStream stream = SyncStreams.synchronizedStream(s.stream().mapToLong(o -> (long) o.hashCode()), s);
+ * stream = stream.map(i -> i % 67); // Still synchronized
+ * stream.forEach(System.out::println); // Should never throw a ConcurrentModificationException
+ *
+ *
+ * @param stream the stream to wrap.
+ * @param monitor the object that the stream will use for synchronization. When {@code null}, the stream
+ * will synchronize on itself.
+ * @return a {@link SyncLongStream} synchronized on {@code monitor} and backed by {@code stream}.
+ * @throws NullPointerException if {@code stream == null}.
+ */
+ public static SyncLongStream synchronizedStream(LongStream stream, Object monitor) {
+ Objects.requireNonNull(stream, "stream cannot be null");
+ return new SyncLongStream(stream, monitor);
+ }
+
+ /**
+ * Wraps the given {@link DoubleStream} to make all
+ *
+ * terminal operations acquire the provided monitor's lock before execution. Intermediate operations
+ * return streams that are also synchronized on the same object. The created stream will behave identically
+ * to the provided stream in all other aspects. Use this to synchronize access to stream's source.
+ * The returned {@code DoubleStream}'s {@link DoubleStream#iterator() iterator()} and
+ * {@link DoubleStream#spliterator() spliterator()} methods return regular non-synchronized iterators and
+ * spliterators respectively. It is the user's responsibility to avoid concurrency issues:
+ *
+ * synchronized (stream.getMonitor()) {
+ * PrimitiveIterator.OfDouble it = stream.iterator();
+ * ...
+ * }
+ *
+ * Usage example:
+ *
+ * Set<Object> s = Collections.synchronizedSet(new HashSet<>());
+ * ...
+ * DoubleStream stream = SyncStreams.synchronizedStream(s.stream().mapToLong(o -> (double) o.hashCode()), s);
+ * stream = stream.map(Math::sin); // Still synchronized
+ * stream.forEach(System.out::println); // Should never throw a ConcurrentModificationException
+ *
+ *
+ * @param stream the stream to wrap.
+ * @param monitor the object that the stream will use for synchronization. When {@code null}, the stream
+ * will synchronize on itself.
+ * @return a {@link SyncDoubleStream} synchronized on {@code monitor} and backed by {@code stream}.
+ * @throws NullPointerException if {@code stream == null}.
+ */
+ public static SyncDoubleStream synchronizedStream(DoubleStream stream, Object monitor) {
+ Objects.requireNonNull(stream, "stream cannot be null");
+ return new SyncDoubleStream(stream, monitor);
+ }
+
+ /*
+ * Private constructor
+ */
+ private SyncStreams() {}
+
+}
diff --git a/src/main/java/ru/windcorp/jputil/SyntaxException.java b/src/main/java/ru/windcorp/jputil/SyntaxException.java
new file mode 100644
index 0000000..e47d123
--- /dev/null
+++ b/src/main/java/ru/windcorp/jputil/SyntaxException.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * JPUtil
+ * Copyright (C) 2019 Javapony/OLEGSHA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *******************************************************************************/
+package ru.windcorp.jputil;
+
+public class SyntaxException extends Exception {
+
+ private static final long serialVersionUID = -4052144233640072750L;
+
+ public SyntaxException() {
+
+ }
+
+ public SyntaxException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+
+ public SyntaxException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public SyntaxException(String message) {
+ super(message);
+ }
+
+ public SyntaxException(Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/jputil/chars/CharArrayIterator.java b/src/main/java/ru/windcorp/jputil/chars/CharArrayIterator.java
new file mode 100644
index 0000000..f65ddf4
--- /dev/null
+++ b/src/main/java/ru/windcorp/jputil/chars/CharArrayIterator.java
@@ -0,0 +1,128 @@
+/*******************************************************************************
+ * JPUtil
+ * Copyright (C) 2019 Javapony/OLEGSHA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *******************************************************************************/
+package ru.windcorp.jputil.chars;
+
+import java.text.CharacterIterator;
+
+public class CharArrayIterator implements CharacterIterator {
+
+ private final char[] array;
+ private int pos;
+
+ public CharArrayIterator(char[] array) {
+ this.array = array;
+ }
+
+ public CharArrayIterator(String src) {
+ this(src.toCharArray());
+ }
+
+ @Override
+ public char first() {
+ pos = 0;
+ if (array.length != 0) {
+ return array[pos];
+ }
+ return DONE;
+ }
+
+ @Override
+ public char last() {
+ pos = array.length;
+ if (array.length != 0) {
+ pos -= 1;
+ return array[pos];
+ }
+ return DONE;
+ }
+
+ @Override
+ public char current() {
+ if (array.length != 0 && pos < array.length) {
+ return array[pos];
+ }
+ return DONE;
+ }
+
+ @Override
+ public char next() {
+ pos += 1;
+ if (pos >= array.length) {
+ pos = array.length;
+ return DONE;
+ }
+ return current();
+ }
+
+ @Override
+ public char previous() {
+ if (pos == 0) {
+ return DONE;
+ }
+ pos -= 1;
+ return current();
+ }
+
+ @Override
+ public char setIndex(int position) {
+ if (position < 0 || position > array.length) {
+ throw new IllegalArgumentException("bad position: " + position);
+ }
+
+ pos = position;
+
+ if (pos != array.length && array.length != 0) {
+ return array[pos];
+ }
+ return DONE;
+ }
+
+ @Override
+ public int getBeginIndex() {
+ return 0;
+ }
+
+ @Override
+ public int getEndIndex() {
+ return array.length;
+ }
+
+ @Override
+ public int getIndex() {
+ return pos;
+ }
+
+// @SuppressWarnings("all") Just STFU, this _is_ terrific
+
+ // SonarLint: "clone" should not be overridden (java:S2975)
+ // And I wouldn't have done that if only CharacterIterator had not required exception safety.
+ // SonarLint: "toString()" and "clone()" methods should not return null (java:S2225)
+ // The clause is unreachable: CharacterArrayIterator implements Cloneable and superclass is Object.
+ @SuppressWarnings({"squid:S2975", "squid:S2225"})
+
+ @Override
+ public CharArrayIterator clone() {
+ try {
+ return (CharArrayIterator) super.clone();
+ } catch (CloneNotSupportedException cnse) {
+ // Impossible
+ return null;
+ }
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/jputil/chars/CharConsumer.java b/src/main/java/ru/windcorp/jputil/chars/CharConsumer.java
new file mode 100644
index 0000000..de1d372
--- /dev/null
+++ b/src/main/java/ru/windcorp/jputil/chars/CharConsumer.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * JPUtil
+ * Copyright (C) 2019 Javapony/OLEGSHA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *******************************************************************************/
+package ru.windcorp.jputil.chars;
+
+import java.util.function.IntConsumer;
+
+@FunctionalInterface
+public interface CharConsumer {
+
+ void accept(char c);
+
+ public static CharConsumer andThen(CharConsumer first, CharConsumer second) {
+ return c -> {
+ first.accept(c);
+ second.accept(c);
+ };
+ }
+
+ public static IntConsumer toInt(CharConsumer consumer) {
+ return i -> consumer.accept((char) i);
+ }
+
+ public static CharConsumer toChar(IntConsumer consumer) {
+ return consumer::accept;
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/jputil/chars/CharConsumers.java b/src/main/java/ru/windcorp/jputil/chars/CharConsumers.java
new file mode 100644
index 0000000..e0613bc
--- /dev/null
+++ b/src/main/java/ru/windcorp/jputil/chars/CharConsumers.java
@@ -0,0 +1,69 @@
+/*******************************************************************************
+ * JPUtil
+ * Copyright (C) 2019 Javapony/OLEGSHA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *******************************************************************************/
+package ru.windcorp.jputil.chars;
+
+import java.util.Objects;
+
+import ru.windcorp.jputil.ArrayUtil;
+
+/**
+ * @author Javapony
+ *
+ */
+public class CharConsumers {
+
+ private CharConsumers() {}
+
+ public static CharConsumer fillArray(char[] array, int offset, int length) {
+ return new ArrayFiller(array, offset, length);
+ }
+
+ public static CharConsumer fillArray(char[] array) {
+ return fillArray(array, 0, -1);
+ }
+
+ private static class ArrayFiller implements CharConsumer {
+
+ final char[] array;
+ int i;
+ final int end;
+
+ /**
+ * @param array
+ * @param offset
+ * @param length
+ */
+ ArrayFiller(char[] array, int offset, int length) {
+ this.array = Objects.requireNonNull(array, "array");
+ this.end = ArrayUtil.checkArrayStartEnd(array, offset, offset + length);
+ this.i = offset;
+ }
+
+ /**
+ * @see ru.windcorp.jputil.chars.CharConsumer#accept(char)
+ */
+ @Override
+ public void accept(char c) {
+ if (i == end)
+ throw new ArrayIndexOutOfBoundsException(end);
+ array[i++] = c;
+ }
+
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/jputil/chars/CharPredicate.java b/src/main/java/ru/windcorp/jputil/chars/CharPredicate.java
new file mode 100644
index 0000000..b4be567
--- /dev/null
+++ b/src/main/java/ru/windcorp/jputil/chars/CharPredicate.java
@@ -0,0 +1,84 @@
+/*******************************************************************************
+ * JPUtil
+ * Copyright (C) 2019 Javapony/OLEGSHA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *******************************************************************************/
+package ru.windcorp.jputil.chars;
+
+import java.util.Arrays;
+import java.util.function.IntPredicate;
+
+import ru.windcorp.jputil.ArrayUtil;
+
+@FunctionalInterface
+public interface CharPredicate {
+
+ boolean test(char c);
+
+ public static CharPredicate and(CharPredicate first, CharPredicate second) {
+ return c -> first.test(c) && second.test(c);
+ }
+
+ public static CharPredicate or(CharPredicate first, CharPredicate second) {
+ return c -> first.test(c) || second.test(c);
+ }
+
+ public static CharPredicate negate(CharPredicate predicate) {
+ return c -> !predicate.test(c);
+ }
+
+ public static IntPredicate toInt(CharPredicate predicate) {
+ return i -> predicate.test((char) i);
+ }
+
+ public static CharPredicate toChar(IntPredicate predicate) {
+ return predicate::test;
+ }
+
+ public static CharPredicate forArray(char... chars) {
+ if (chars.length == 0) {
+ return c -> false;
+ }
+
+ if (chars.length == 1) {
+ return forChar(chars[0]);
+ }
+
+ if (chars.length < 16) {
+ return c -> ArrayUtil.firstIndexOf(chars, c) >= 0;
+ } else {
+ final char[] sorted = Arrays.copyOf(chars, chars.length);
+ Arrays.sort(sorted);
+ return c -> Arrays.binarySearch(chars, c) >= 0;
+ }
+ }
+
+ public static CharPredicate forChar(final char c) {
+ return given -> given == c;
+ }
+
+ public static CharPredicate forRange(final char minInclusive, final char maxExclusive) {
+ if (minInclusive > maxExclusive) {
+ throw new IllegalArgumentException("min > max: " + minInclusive + " > " + maxExclusive);
+ }
+
+ if (minInclusive == maxExclusive) {
+ return c -> false;
+ }
+
+ return c -> c >= minInclusive && c < maxExclusive;
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/jputil/chars/CharSupplier.java b/src/main/java/ru/windcorp/jputil/chars/CharSupplier.java
new file mode 100644
index 0000000..580deef
--- /dev/null
+++ b/src/main/java/ru/windcorp/jputil/chars/CharSupplier.java
@@ -0,0 +1,35 @@
+/*******************************************************************************
+ * JPUtil
+ * Copyright (C) 2019 Javapony/OLEGSHA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *******************************************************************************/
+package ru.windcorp.jputil.chars;
+
+import java.util.function.IntSupplier;
+
+@FunctionalInterface
+public interface CharSupplier {
+
+ char getAsChar();
+
+ public static IntSupplier toInt(CharSupplier consumer) {
+ return consumer::getAsChar;
+ }
+
+ public static CharSupplier toChar(IntSupplier consumer) {
+ return () -> (char) consumer.getAsInt();
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/jputil/chars/EscapeException.java b/src/main/java/ru/windcorp/jputil/chars/EscapeException.java
new file mode 100644
index 0000000..2578dad
--- /dev/null
+++ b/src/main/java/ru/windcorp/jputil/chars/EscapeException.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * JPUtil
+ * Copyright (C) 2019 Javapony/OLEGSHA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *******************************************************************************/
+package ru.windcorp.jputil.chars;
+
+public class EscapeException extends Exception {
+
+ private static final long serialVersionUID = -3647188859290365053L;
+
+ public EscapeException() {
+ super();
+ }
+
+ public EscapeException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+
+ public EscapeException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public EscapeException(String message) {
+ super(message);
+ }
+
+ public EscapeException(Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/jputil/chars/Escaper.java b/src/main/java/ru/windcorp/jputil/chars/Escaper.java
new file mode 100644
index 0000000..17e9b26
--- /dev/null
+++ b/src/main/java/ru/windcorp/jputil/chars/Escaper.java
@@ -0,0 +1,474 @@
+/*******************************************************************************
+ * JPUtil
+ * Copyright (C) 2019 Javapony/OLEGSHA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *******************************************************************************/
+package ru.windcorp.jputil.chars;
+
+import java.text.CharacterIterator;
+
+import ru.windcorp.jputil.ArrayUtil;
+import ru.windcorp.jputil.chars.reader.CharReader;
+import ru.windcorp.jputil.chars.reader.CharReaders;
+
+public class Escaper {
+
+ public static class EscaperBuilder {
+ private char escapeChar = '\\';
+ private char unicodeEscapeChar = 'u';
+ private char[] safes = null;
+ private char[] unsafes = null;
+
+ private boolean preferUnicode = false;
+ private boolean strict = true;
+
+ public EscaperBuilder withEscapeChar(char escapeChar) {
+ this.escapeChar = escapeChar;
+ return this;
+ }
+
+ public EscaperBuilder withUnicodeEscapeChar(char unicodeEscapeChar) {
+ this.unicodeEscapeChar = unicodeEscapeChar;
+ return this;
+ }
+
+ public EscaperBuilder withChars(char[] safes, char[] unsafes) {
+ this.safes = safes;
+ this.unsafes = unsafes;
+ return this;
+ }
+
+ public EscaperBuilder withChars(String safes, String unsafes) {
+ this.safes = safes.toCharArray();
+ this.unsafes = unsafes.toCharArray();
+ return this;
+ }
+
+ public EscaperBuilder withChars(char[] chars) {
+ this.safes = this.unsafes = chars;
+ return this;
+ }
+
+ public EscaperBuilder withChars(String chars) {
+ this.safes = this.unsafes = chars.toCharArray();
+ return this;
+ }
+
+ public EscaperBuilder withSafes(char[] safes) {
+ this.safes = safes;
+ return this;
+ }
+
+ public EscaperBuilder withSafes(String safes) {
+ this.safes = safes.toCharArray();
+ return this;
+ }
+
+ public EscaperBuilder withUnsafes(char[] unsafes) {
+ this.unsafes = unsafes;
+ return this;
+ }
+
+ public EscaperBuilder withUnsafes(String unsafes) {
+ this.unsafes = unsafes.toCharArray();
+ return this;
+ }
+
+ public EscaperBuilder preferUnicode(boolean preferUnicode) {
+ this.preferUnicode = preferUnicode;
+ return this;
+ }
+
+ public EscaperBuilder strict(boolean strict) {
+ this.strict = strict;
+ return this;
+ }
+
+ public Escaper build() {
+ return new Escaper(escapeChar, unicodeEscapeChar, safes, unsafes, preferUnicode, strict);
+ }
+
+ }
+
+ public static final Escaper JAVA = new Escaper('\\', 'u', "tbnrf'\"".toCharArray(), "\t\b\n\r\f\'\"".toCharArray(), true, true);
+
+ private final char escapeChar;
+ private final char unicodeEscapeChar;
+ private final char[] safes;
+ private final char[] unsafes;
+
+ private final boolean preferUnicode;
+ private final boolean strict;
+
+ protected Escaper(
+ char escapeChar, char unicodeEscapeChar,
+ char[] safes, char[] unsafes,
+ boolean preferUnicode, boolean strict) {
+ this.escapeChar = escapeChar;
+ this.unicodeEscapeChar = unicodeEscapeChar;
+ this.safes = safes;
+ this.unsafes = unsafes;
+ this.preferUnicode = preferUnicode;
+ this.strict = strict;
+
+ int duplicate;
+ if ((duplicate = ArrayUtil.hasDuplicates(safes)) != -1)
+ throw new IllegalArgumentException("Duplicate safe character '" + safes[duplicate] + "'");
+
+ if ((duplicate = ArrayUtil.hasDuplicates(unsafes)) != -1)
+ throw new IllegalArgumentException("Duplicate unsafe character '" + unsafes[duplicate] + "'");
+
+ for (char c : safes) {
+ if (c == escapeChar) throw new IllegalArgumentException("Safe characters contain escape chatacter");
+ if (c == unicodeEscapeChar) throw new IllegalArgumentException("Safe characters contain Unicode escape chatacter");
+ }
+
+ for (char c : unsafes) {
+ if (c == escapeChar) throw new IllegalArgumentException("Unsafe characters contain escape chatacter (escape character is escaped automatically)");
+ if (c == unicodeEscapeChar) throw new IllegalArgumentException("Unsafe characters contain Unicode escape chatacter");
+ }
+ }
+
+ public static EscaperBuilder create() {
+ return new EscaperBuilder();
+ }
+
+ /*
+ * Logic - escape
+ */
+
+ public void escape(CharReader src, int length, CharPredicate until, CharConsumer output) {
+ int end;
+ if (length < 0) end = Integer.MAX_VALUE;
+ else end = src.getPosition() + length;
+ while (src.has() &&
+ src.getPosition() < end &&
+ (until == null || !until.test(src.current())))
+ escape(src.consume(), output);
+ }
+
+ public void escape(char c, CharConsumer output) {
+ if (c == escapeChar) {
+ output.accept(escapeChar);
+ output.accept(escapeChar);
+ return;
+ }
+
+ int index = ArrayUtil.firstIndexOf(unsafes, c);
+
+ if (index >= 0) {
+ output.accept(escapeChar);
+ output.accept(safes[index]);
+ } else {
+ if (preferUnicode && !isRegular(c)) {
+ escapeAsHex(c, output);
+ } else {
+ output.accept(c);
+ }
+ }
+ }
+
+ // SonarLint: Assignments should not be made from within sub-expressions (java:S1121)
+ // Seems self-evident enough
+ @SuppressWarnings("squid:S1121")
+
+ private void escapeAsHex(char c, CharConsumer output) {
+ output.accept(escapeChar);
+ output.accept(unicodeEscapeChar);
+ output.accept(StringUtil.hexDigit(c >>= (4 * 3)));
+ output.accept(StringUtil.hexDigit(c >>= (4 * 2)));
+ output.accept(StringUtil.hexDigit(c >>= (4 * 1)));
+ output.accept(StringUtil.hexDigit(c >> (4 * 0)));
+ }
+
+ public int getEscapedLength(CharReader src, int length, CharPredicate until) {
+ int end;
+ if (length < 0) end = Integer.MAX_VALUE;
+ else end = src.getPosition() + length;
+
+ int result = 0;
+
+ while (src.has() &&
+ src.getPosition() < end &&
+ (until == null || !until.test(src.current()))) {
+ result += getEscapedLength(src.consume());
+ }
+
+ return result;
+ }
+
+ public int getEscapedLength(char c) {
+ if (c == escapeChar || ArrayUtil.firstIndexOf(unsafes, c) >= 0)
+ return 2;
+ else {
+ if (preferUnicode && !isRegular(c))
+ return 6;
+ else
+ return 1;
+ }
+ }
+
+ /*
+ * Logic - unescape
+ */
+
+ public void unescape(CharReader src, int length, CharPredicate until, CharConsumer output) throws EscapeException {
+ int end;
+ if (length < 0) end = Integer.MAX_VALUE;
+ else end = src.getPosition() + length;
+ while (src.has() &&
+ src.getPosition() < end &&
+ (until == null || !until.test(src.current()))) {
+ output.accept(unescapeOneSequence(src));
+ }
+ }
+
+ public char unescapeOneSequence(CharReader src) throws EscapeException {
+ int resetPos = src.getPosition();
+ try {
+ if (src.current() == escapeChar) {
+ src.next();
+
+ if (src.isEnd())
+ throw new EscapeException("Incomplete escape sequence at the end");
+
+ if (src.current() == escapeChar) {
+ src.next();
+ return escapeChar;
+ }
+
+ if (src.current() == unicodeEscapeChar) {
+ src.next();
+ return (char) (
+ hexValue(src.consume()) << (4 * 3) |
+ hexValue(src.consume()) << (4 * 2) |
+ hexValue(src.consume()) << (4 * 1) |
+ hexValue(src.consume()) << (4 * 0)
+ );
+ }
+
+ int index = ArrayUtil.firstIndexOf(safes, src.current());
+ if (index >= 0) {
+ src.next();
+ return unsafes[index];
+ }
+
+ if (strict)
+ throw new EscapeException("Unknown escape sequence \"" + escapeChar + src.current() + "\"");
+ else
+ return src.consume();
+ } else
+ return src.consume();
+ } catch (EscapeException | RuntimeException e) {
+ src.setPosition(resetPos);
+ throw e;
+ }
+ }
+
+ public int getUnescapedLength(CharReader src, int length, CharPredicate until) {
+ int end;
+ if (length < 0) end = Integer.MAX_VALUE;
+ else end = src.getPosition() + length;
+
+ int result = 0;
+
+ while (src.has() &&
+ src.getPosition() < end &&
+ (until == null || !until.test(src.current()))) {
+ skipOneSequence(src);
+ result++;
+ }
+
+ return result;
+ }
+
+ public void skipOneSequence(CharReader src) {
+ if (
+ src.current() == escapeChar
+ &&
+ src.next() == unicodeEscapeChar
+ ) {
+ src.advance(4);
+ }
+ src.next();
+ }
+
+ /*
+ * Utility
+ */
+
+ public void escape(CharReader src, int length, CharConsumer output) {
+ escape(src, length, null, output);
+ }
+
+ public void escape(CharReader src, CharPredicate until, CharConsumer output) {
+ escape(src, -1, until, output);
+ }
+
+ public void escape(CharReader src, CharConsumer output) {
+ escape(src, -1, null, output);
+ }
+
+ public int getEscapedLength(CharReader src, int length) {
+ return getEscapedLength(src, length, null);
+ }
+
+ public int getEscapedLength(CharReader src, CharPredicate until) {
+ return getEscapedLength(src, -1, until);
+ }
+
+ public int getEscapedLength(CharReader src) {
+ return getEscapedLength(src, -1, null);
+ }
+
+ public char[] escape(CharReader src, int length, CharPredicate until) {
+ src.mark();
+ char[] result = new char[getEscapedLength(src, length, until)];
+ src.reset();
+ escape(src, length, until, CharConsumers.fillArray(result));
+ return result;
+ }
+
+ public char[] escape(CharReader src, int length) {
+ return escape(src, length, (CharPredicate) null);
+ }
+
+ public char[] escape(CharReader src, CharPredicate until) {
+ return escape(src, -1, until);
+ }
+
+ public char[] escape(CharReader src) {
+ return escape(src, -1, (CharPredicate) null);
+ }
+
+ public void unescape(CharReader src, int length, CharConsumer output) throws EscapeException {
+ unescape(src, length, null, output);
+ }
+
+ public void unescape(CharReader src, CharPredicate until, CharConsumer output) throws EscapeException {
+ unescape(src, -1, until, output);
+ }
+
+ public void unescape(CharReader src, CharConsumer output) throws EscapeException {
+ unescape(src, -1, null, output);
+ }
+
+ public int getUnescapedLength(CharReader src, int length) {
+ return getUnescapedLength(src, length, null);
+ }
+
+ public int getUnescapedLength(CharReader src, CharPredicate until) {
+ return getUnescapedLength(src, -1, until);
+ }
+
+ public int getUnescapedLength(CharReader src) {
+ return getUnescapedLength(src, -1, null);
+ }
+
+ public char[] unescape(CharReader src, int length, CharPredicate until) throws EscapeException {
+ src.mark();
+ char[] result = new char[getUnescapedLength(src, length, until)];
+ src.reset();
+ unescape(src, length, until, CharConsumers.fillArray(result));
+ return result;
+ }
+
+ public char[] unescape(CharReader src, int length) throws EscapeException {
+ return unescape(src, length, (CharPredicate) null);
+ }
+
+ public char[] unescape(CharReader src, CharPredicate until) throws EscapeException {
+ return unescape(src, -1, until);
+ }
+
+ public char[] unescape(CharReader src) throws EscapeException {
+ return unescape(src, -1, (CharPredicate) null);
+ }
+
+ @Deprecated()
+ public char[] unescape(CharacterIterator src, char until) throws EscapeException {
+ int index = src.getIndex();
+ CharReader reader = CharReaders.wrap(src);
+
+ char[] result = unescape(reader, -1, CharPredicate.forChar(until));
+
+ src.setIndex(index + reader.getPosition());
+ return result;
+ }
+
+ public String escape(String src) {
+ StringBuilder result = new StringBuilder(src.length());
+ escape(CharReaders.wrap(src), (CharConsumer) result::append);
+ return result.toString();
+ }
+
+ public String unescape(String src) throws EscapeException {
+ StringBuilder result = new StringBuilder(src.length());
+ unescape(CharReaders.wrap(src), (CharConsumer) result::append);
+ return result.toString();
+ }
+
+ /*
+ * Misc
+ */
+
+ private static int hexValue(char c) throws EscapeException {
+ if (c < '0') throw thisIsNotAHexDigit(c);
+ if (c <= '9') return c - '0';
+ if (c < 'A') throw thisIsNotAHexDigit(c);
+ if (c <= 'F') return c - 'A';
+ if (c < 'a') throw thisIsNotAHexDigit(c);
+ if (c <= 'f') return c - 'a';
+ if (c == CharReader.DONE) throw new EscapeException("Incomplete Unicode escape sequence at the end");
+ throw thisIsNotAHexDigit(c);
+ }
+
+ private static EscapeException thisIsNotAHexDigit(char c) {
+ return new EscapeException("Invalid hex digit '" + c + "', expected [0-9A-Fa-f]");
+ }
+
+ protected static boolean isRegular(char c) {
+ return c >= ' ' && c <= '~';
+ }
+
+ /*
+ * Getters / setters
+ */
+
+ public char getEscapeChar() {
+ return escapeChar;
+ }
+
+ public char getUnicodeEscapeChar() {
+ return unicodeEscapeChar;
+ }
+
+ public char[] getSafes() {
+ return safes;
+ }
+
+ public char[] getUnsafes() {
+ return unsafes;
+ }
+
+ public boolean isPreferUnicode() {
+ return preferUnicode;
+ }
+
+ public boolean isStrict() {
+ return strict;
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/jputil/chars/FancyCharacterIterator.java b/src/main/java/ru/windcorp/jputil/chars/FancyCharacterIterator.java
new file mode 100644
index 0000000..b309786
--- /dev/null
+++ b/src/main/java/ru/windcorp/jputil/chars/FancyCharacterIterator.java
@@ -0,0 +1,102 @@
+/*
+ * JPUtil
+ * Copyright (C) 2019 Javapony/OLEGSHA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+package ru.windcorp.jputil.chars;
+
+import java.text.CharacterIterator;
+import java.text.StringCharacterIterator;
+
+public class FancyCharacterIterator implements CharacterIterator {
+ private final StringCharacterIterator obj;
+ private final String data;
+
+ public FancyCharacterIterator(String data) {
+ this.obj = new StringCharacterIterator(data);
+ this.data = data;
+ }
+
+ @Override
+ public char first() {
+ return obj.first();
+ }
+
+ @Override
+ public char last() {
+ return obj.last();
+ }
+
+ @Override
+ public char setIndex(int p) {
+ return obj.setIndex(p);
+ }
+
+ @Override
+ public char current() {
+ return obj.current();
+ }
+
+ @Override
+ public char next() {
+ return obj.next();
+ }
+
+ @Override
+ public char previous() {
+ return obj.previous();
+ }
+
+ @Override
+ public int getBeginIndex() {
+ return obj.getBeginIndex();
+ }
+
+ @Override
+ public int getEndIndex() {
+ return obj.getEndIndex();
+ }
+
+ @Override
+ public int getIndex() {
+ return obj.getIndex();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("\"");
+ sb.append(data);
+ sb.append("\"\n ");
+ for (int i = 0; i < obj.getIndex(); ++i) sb.append(' ');
+ sb.append("^ Here.");
+ return sb.toString();
+ }
+
+// @SuppressWarnings("all") Just STFU, this _is_ terrific
+
+ // SonarLint: "clone" should not be overridden (java:S2975)
+ // And I wouldn't have done that if only CharacterIterator had not required exception safety.
+ // SonarLint: "toString()" and "clone()" methods should not return null (java:S2225)
+ // The clause is unreachable: CharacterArrayIterator implements Cloneable and superclass is Object.
+ @SuppressWarnings({"squid:S2975", "squid:S2225"})
+
+ @Override
+ public FancyCharacterIterator clone() {
+ try {
+ return (FancyCharacterIterator) super.clone();
+ } catch (CloneNotSupportedException cnse) {
+ // Impossible
+ return null;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/ru/windcorp/jputil/chars/IndentedStringBuilder.java b/src/main/java/ru/windcorp/jputil/chars/IndentedStringBuilder.java
new file mode 100644
index 0000000..2bfe3b6
--- /dev/null
+++ b/src/main/java/ru/windcorp/jputil/chars/IndentedStringBuilder.java
@@ -0,0 +1,135 @@
+/*******************************************************************************
+ * JPUtil
+ * Copyright (C) 2019 Javapony/OLEGSHA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *******************************************************************************/
+package ru.windcorp.jputil.chars;
+
+public class IndentedStringBuilder {
+
+ private final StringBuilder sb = new StringBuilder();
+
+ private int indentLevel = 0;
+ private boolean indentApplied = false;
+
+ private String[] indentCache = new String[16];
+ private String indent = "";
+ private final char[] indentFill;
+
+ public IndentedStringBuilder(char[] indentFill) {
+ this.indentFill = indentFill;
+ }
+
+ public IndentedStringBuilder(String indentFill) {
+ this(indentFill.toCharArray());
+ }
+
+ public IndentedStringBuilder(char indentChar, int length) {
+ this(StringUtil.sequence(indentChar, length));
+ }
+
+ public IndentedStringBuilder() {
+ this(new char[] {' '});
+ }
+
+ @Override
+ public String toString() {
+ return sb.toString();
+ }
+
+ public int getIndentLevel() {
+ return indentLevel;
+ }
+
+ public void setIndentLevel(int level) {
+ this.indentLevel = level;
+ updateIndent();
+ }
+
+ public char[] getIndentFill() {
+ return indentFill;
+ }
+
+ protected void updateIndent() {
+ if (indentLevel < indentCache.length) {
+ indent = indentCache[indentLevel];
+ if (indent != null) return;
+ }
+
+ char[] fill = getIndentFill();
+ char[] array = new char[fill.length * getIndentLevel()];
+ for (int i = 0; i < array.length; i += fill.length)
+ System.arraycopy(fill, 0, array, i, fill.length);
+ indent = new String(array);
+
+ if (indentLevel < indentCache.length) {
+ indentCache[indentLevel] = indent;
+ }
+ }
+
+ public IndentedStringBuilder indent() {
+ setIndentLevel(getIndentLevel() + 1);
+ return this;
+ }
+
+ public IndentedStringBuilder unindent() {
+ setIndentLevel(getIndentLevel() - 1);
+ return this;
+ }
+
+ public IndentedStringBuilder append(Object x) {
+ if (x == null) {
+ appendRaw("null");
+ return this;
+ }
+
+ String str = x.toString();
+ int newLines = StringUtil.count(str, '\n');
+
+ if (newLines == 0) {
+ appendRaw(str);
+ return this;
+ }
+
+ String[] lines = StringUtil.split(str, '\n', newLines + 1);
+ appendRaw(lines[0]);
+
+ for (int i = 1; i < lines.length; ++i) {
+ newLine();
+ appendRaw(lines[i]);
+ }
+
+ return this;
+ }
+
+ public IndentedStringBuilder appendRaw(String str) {
+ if (str.isEmpty()) return this; // Do not append indent
+
+ if (!indentApplied) {
+ sb.append(indent);
+ indentApplied = true;
+ }
+
+ sb.append(str);
+ return this;
+ }
+
+ public IndentedStringBuilder newLine() {
+ sb.append('\n');
+ indentApplied = false;
+ return this;
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/jputil/chars/StringUtil.java b/src/main/java/ru/windcorp/jputil/chars/StringUtil.java
new file mode 100644
index 0000000..9a8933b
--- /dev/null
+++ b/src/main/java/ru/windcorp/jputil/chars/StringUtil.java
@@ -0,0 +1,778 @@
+/*******************************************************************************
+ * JPUtil
+ * Copyright (C) 2019 Javapony/OLEGSHA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *******************************************************************************/
+package ru.windcorp.jputil.chars;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.function.IntFunction;
+
+public class StringUtil {
+
+ private StringUtil() {}
+
+ private static final String NULL_PLACEHOLDER = "[null]";
+ private static final String EMPTY_PLACEHOLDER = "[empty]";
+ private static final String DEFAULT_SEPARATOR = "; ";
+
+ public static String arrayToString(
+ T[] array,
+ String separator,
+ String empty,
+ String nullPlaceholder,
+ String nullArray
+ ) {
+
+ if (separator == null) {
+ throw new IllegalArgumentException(new NullPointerException());
+ }
+
+ if (array == null) {
+ return nullArray;
+ }
+
+ if (array.length == 0) {
+ return empty;
+ }
+
+ StringBuilder sb = new StringBuilder(array[0] == null ? nullPlaceholder : array[0].toString());
+
+ for (int i = 1; i < array.length; ++i) {
+ sb.append(separator);
+ sb.append(array[i] == null ? nullPlaceholder : array[i].toString());
+ }
+
+ return sb.toString();
+ }
+
+ public static String arrayToString(T[] array, String separator) {
+ return arrayToString(array, separator, EMPTY_PLACEHOLDER, NULL_PLACEHOLDER, "[null array]");
+ }
+
+ public static String arrayToString(T[] array) {
+ return arrayToString(array, DEFAULT_SEPARATOR);
+ }
+
+ public static String iteratorToString(
+ Iterator> iterator,
+ String separator,
+ String empty,
+ String nullPlaceholder,
+ String nullIterator
+ ) {
+
+ if (separator == null) {
+ throw new IllegalArgumentException(new NullPointerException());
+ }
+
+ if (iterator == null) {
+ return nullIterator;
+ }
+
+ if (!iterator.hasNext()) {
+ return empty;
+ }
+
+ Object obj = iterator.next();
+ StringBuilder sb = new StringBuilder(obj == null ? nullPlaceholder : obj.toString());
+
+ while (iterator.hasNext()) {
+ obj = iterator.next();
+ sb.append(separator);
+ sb.append(obj == null ? nullPlaceholder : obj.toString());
+ }
+
+ return sb.toString();
+ }
+
+ public static String iteratorToString(Iterator> iterator, String separator) {
+ return iteratorToString(iterator, separator, EMPTY_PLACEHOLDER, NULL_PLACEHOLDER, "[null iterator]");
+ }
+
+ public static String iteratorToString(Iterator> iterator) {
+ return iteratorToString(iterator, DEFAULT_SEPARATOR);
+ }
+
+ public static String iterableToString(
+ Iterable> iterable,
+ String separator,
+ String empty,
+ String nullPlaceholder,
+ String nullIterable
+ ) {
+
+ if (separator == null) {
+ throw new IllegalArgumentException(new NullPointerException());
+ }
+
+ if (iterable == null) {
+ return nullIterable;
+ }
+
+ return iteratorToString(iterable.iterator(), separator, empty, nullPlaceholder, nullIterable);
+ }
+
+ public static String iterableToString(Iterable> iterable, String separator) {
+ return iterableToString(iterable, separator, EMPTY_PLACEHOLDER, NULL_PLACEHOLDER, "[null iterable]");
+ }
+
+ public static String iterableToString(Iterable> iterable) {
+ return iterableToString(iterable, DEFAULT_SEPARATOR);
+ }
+
+ public static String supplierToString(
+ IntFunction supplier,
+ int length,
+ String separator,
+ String empty,
+ String nullPlaceholder,
+ String nullSupplier
+ ) {
+
+ if (separator == null) throw new IllegalArgumentException(new NullPointerException());
+ if (supplier == null) return nullSupplier;
+ if (length == 0) return empty;
+
+ if (length > 0) {
+ return supplierToStringExactly(
+ supplier,
+ length,
+ separator,
+ nullPlaceholder
+ );
+ } else {
+ return supplierToStringUntilNull(
+ supplier,
+ separator,
+ empty
+ );
+ }
+
+ }
+
+ private static String supplierToStringExactly(
+ IntFunction supplier,
+ int length,
+ String separator,
+ String nullPlaceholder
+ ) {
+ T element = supplier.apply(0);
+
+ StringBuilder sb = new StringBuilder(element == null ? nullPlaceholder : element.toString());
+
+ for (int i = 1; i < length; ++i) {
+ sb.append(separator);
+ element = supplier.apply(i);
+ sb.append(element == null ? nullPlaceholder : element.toString());
+ }
+
+ return sb.toString();
+ }
+
+ private static String supplierToStringUntilNull(
+ IntFunction supplier,
+ String separator,
+ String empty
+ ) {
+ T element = supplier.apply(0);
+
+ if (element == null) {
+ return empty;
+ }
+
+ StringBuilder sb = new StringBuilder(element.toString());
+
+ int i = 0;
+ while ((element = supplier.apply(i++)) != null) {
+ sb.append(separator);
+ sb.append(element);
+ }
+
+ return sb.toString();
+ }
+
+ public static String supplierToString(IntFunction> supplier, int length, String separator) {
+ return supplierToString(supplier, length, separator, EMPTY_PLACEHOLDER, NULL_PLACEHOLDER, "[null supplier]");
+ }
+
+ public static String supplierToString(IntFunction> supplier, String separator) {
+ return supplierToString(supplier, -1, separator, EMPTY_PLACEHOLDER, NULL_PLACEHOLDER, "[null supplier]");
+ }
+
+ public static String supplierToString(IntFunction> supplier, int length) {
+ return supplierToString(supplier, length, DEFAULT_SEPARATOR);
+ }
+
+ public static String supplierToString(IntFunction> supplier) {
+ return supplierToString(supplier, -1, DEFAULT_SEPARATOR);
+ }
+
+ public static byte[] toJavaByteArray(String str) {
+ char[] chars = str.toCharArray();
+ byte[] bytes = new byte[chars.length];
+
+ for (int i = 0; i < bytes.length; ++i) {
+ bytes[i] = (byte) chars[i];
+ }
+
+ return bytes;
+ }
+
+ public static int count(String src, char target) {
+ int i = 0;
+ for (char c : src.toCharArray()) {
+ if (c == target) {
+ ++i;
+ }
+ }
+
+ return i;
+ }
+
+ public static String[] split(String src, char separator) {
+ return split(src, separator, count(src, separator) + 1);
+ }
+
+ public static String[] split(String src, char separator, int arrayLength) {
+ if (arrayLength < 0) throw illegalArrayLength(arrayLength);
+ else if (arrayLength == 0) return new String[0];
+ else if (arrayLength == 1) return new String[] { src };
+
+ String[] result = new String[arrayLength];
+
+ int resultIndex = 0;
+ StringBuilder sb = new StringBuilder();
+ for (char c : src.toCharArray()) {
+ if (c == separator && (resultIndex + 1) < arrayLength) {
+ result[resultIndex] = resetStringBuilder(sb);
+ ++resultIndex;
+ } else {
+ sb.append(c);
+ }
+ }
+
+ result[resultIndex] = sb.toString();
+
+ return result;
+ }
+
+ public static int count(String src, char... target) {
+ int i = 0;
+ for (char c : src.toCharArray()) {
+ for (char t : target) {
+ if (c == t) {
+ ++i;
+ break;
+ }
+ }
+ }
+
+ return i;
+ }
+
+ public static String[] split(String src, char... separator) {
+ return split(src, count(src, separator) + 1, separator);
+ }
+
+ public static String[] split(String src, int arrayLength, char... separator) {
+ if (arrayLength < 0) throw illegalArrayLength(arrayLength);
+ else if (arrayLength == 0) return new String[0];
+ else if (arrayLength == 1) return new String[] { src };
+
+ String[] result = new String[arrayLength];
+
+ int resultIndex = 0;
+ StringBuilder sb = new StringBuilder();
+
+ charLoop:
+ for (char c : src.toCharArray()) {
+ if ((resultIndex + 1) < arrayLength) {
+ for (char h : separator) {
+ if (c == h) {
+ result[resultIndex] = resetStringBuilder(sb);
+ ++resultIndex;
+ continue charLoop;
+ }
+ }
+ }
+
+ sb.append(c);
+ }
+
+ result[resultIndex] = sb.toString();
+
+ return result;
+ }
+
+ public static int count(String src, CharPredicate test) {
+ int i = 0;
+ for (char c : src.toCharArray()) {
+ if (test.test(c)) i++;
+ }
+
+ return i;
+ }
+
+ public static String[] split(String src, CharPredicate test) {
+ return split(src, count(src, test) + 1, test);
+ }
+
+ public static String[] split(String src, int arrayLength, CharPredicate test) {
+ if (arrayLength < 0) throw illegalArrayLength(arrayLength);
+ else if (arrayLength == 0) return new String[0];
+ else if (arrayLength == 1) return new String[] { src };
+
+ String[] result = new String[arrayLength];
+
+ int resultIndex = 0;
+ StringBuilder sb = new StringBuilder();
+
+ charLoop:
+ for (char c : src.toCharArray()) {
+ if (
+ (resultIndex + 1) < arrayLength
+ &&
+ test.test(c)
+ ) {
+ result[resultIndex] = resetStringBuilder(sb);
+ ++resultIndex;
+ continue charLoop;
+ }
+
+ sb.append(c);
+ }
+
+ result[resultIndex] = sb.toString();
+
+ return result;
+ }
+
+ private static IllegalArgumentException illegalArrayLength(int length) {
+ return new IllegalArgumentException("arrayLength must be non-negative (" + length + ")");
+ }
+
+ public static String remove(String src, char... remove) {
+ char[] result = new char[src.length() - count(src, remove)];
+
+ char current;
+ int resultIndex = 0;
+
+ mainLoop:
+ for (int srcIndex = 0; srcIndex < src.length(); ++srcIndex) {
+ current = src.charAt(srcIndex);
+
+ for (char c : remove) {
+ if (current == c) {
+ continue mainLoop;
+ }
+ }
+
+ result[resultIndex++] = current;
+ }
+
+ return new String(result);
+ }
+
+ public static String resetStringBuilder(StringBuilder sb) {
+ String result = sb.toString();
+ sb.setLength(0);
+ sb.ensureCapacity(10);
+ return result;
+ }
+
+ public static String readToString(InputStream is, Charset encoding, int bufferSize) throws IOException {
+ char[] buffer = new char[bufferSize];
+ StringBuilder result = new StringBuilder();
+
+ Reader reader = new InputStreamReader(is, encoding);
+ while (true) {
+ int readChars = reader.read(buffer, 0, buffer.length);
+
+ if (readChars == -1) {
+ break;
+ }
+
+ result.append(buffer, 0, readChars);
+ }
+
+ return result.toString();
+ }
+
+ public static boolean equalsPart(char[] a, char[] b, int beginPos, int endPos) {
+ if (beginPos < 0) {
+ throw new IllegalArgumentException("beginPos must be non-negative (" + beginPos + ")");
+ }
+
+ if (endPos < beginPos) {
+ throw new IllegalArgumentException("endPos must be greater than or equal to beginPos (endPos="
+ + endPos + ", beginPos=" + beginPos + ")");
+ }
+
+ if (endPos >= Math.min(a.length, b.length)) {
+ return false; // At least one of the arrays does not contain at least one of the required elements
+ }
+
+ for (int i = beginPos; i < endPos; ++i) {
+ if (a[i] != b[i]) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ // Java 8 is for pussies
+ public static char[] join(char[]... srcs) {
+ int tmp = 0;
+ for (int i = 0; i < srcs.length; ++i) {
+ tmp += srcs[i].length;
+ }
+
+ char[] result = new char[tmp];
+ tmp = 0;
+ for (int i = 0; i < srcs.length; ++i) {
+ System.arraycopy(srcs[i], 0, result, tmp, srcs[i].length);
+ tmp += srcs[i].length;
+ }
+
+ return result;
+ }
+
+ /**
+ * Finds and returns the index of the specified appearance of the specified character
+ * in the given array. The search starts at index 0.
+ * Examples:
+ *
+ *
+ * src |
+ * target |
+ * skip |
+ * returns |
+ *
+ * a.b.c | '.' | 0 | 1 |
+ * a.b.c | '.' | 1 | 3 |
+ * a.b.c | '.' | 2 | -1 |
+ * a.b.c | 'd' | any | -1 |
+ *
+ * @param src - the array to search in.
+ * @param target - the character to search for.
+ * @param skip - the amount of target
characters to be skipped.
+ * @return The index of the skip+1
th target
character or -1, if none found.
+ * @see StringUtil#indexFromEnd(char[], char, int)
+ */
+ public static int indexFromBeginning(char[] src, char target, int skip) {
+ for (int i = 0; i < src.length; ++i) {
+ if (src[i] == target) {
+ if (skip == 0) {
+ return i;
+ }
+
+ --skip;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Finds and returns the index of the specified appearance of the specified character
+ * in the given array. The search starts at index src.length - 1
.
+ * Examples:
+ *
+ *
+ * src |
+ * target |
+ * skip |
+ * returns |
+ *
+ * a.b.c | '.' | 0 | 3 |
+ * a.b.c | '.' | 1 | 1 |
+ * a.b.c | '.' | 2 | -1 |
+ * a.b.c | 'd' | any | -1 |
+ *
+ * @param src - the array to search in.
+ * @param target - the character to search for.
+ * @param skip - the amount of target
characters to be skipped.
+ * @return The index of the skip+1
th target
character
+ * from the end of the array or -1, if none found.
+ * @see StringUtil#indexFromBeginning(char[], char, int)
+ */
+ public static int indexFromEnd(char[] src, char target, int skip) {
+ for (int i = src.length - 1; i >= 0; --i) {
+ if (src[i] == target) {
+ if (skip == 0) {
+ return i;
+ }
+
+ --skip;
+ }
+ }
+
+ return -1;
+ }
+
+ public static String padToLeft(String src, int length, char c) {
+ if (length <= 0) {
+ throw new IllegalArgumentException("length must be positive (" + length + ")");
+ }
+
+ if (length <= src.length()) {
+ return src;
+ }
+
+ char[] result = new char[length];
+
+ int i = 0;
+ for (; i < src.length(); ++i) {
+ result[i] = src.charAt(i);
+ }
+
+ for (; i < length; ++i) {
+ result[i] = c;
+ }
+
+ return new String(result);
+ }
+
+ public static String padToLeft(String src, int length) {
+ return padToLeft(src, length, ' ');
+ }
+
+ public static String padToRight(String src, int length, char c) {
+ if (length <= 0) {
+ throw new IllegalArgumentException("length must be positive (" + length + ")");
+ }
+
+ if (length <= src.length()) {
+ return src;
+ }
+
+ char[] result = new char[length];
+
+ int i = 0;
+ int srcLength = src.length();
+
+ for (; i < length - srcLength; ++i) {
+ result[i] = c;
+ }
+
+ for (; i < length; ++i) {
+ result[i] = src.charAt(i - (length - srcLength));
+ }
+
+ return new String(result);
+ }
+
+ public static String padToRight(String src, int length) {
+ return padToRight(src, length, ' ');
+ }
+
+ public static int countWords(String src) {
+ int i = 0;
+ boolean isWord = false;
+
+ for (char c : src.toCharArray()) {
+ if (Character.isWhitespace(c)) {
+ if (isWord) {
+ isWord = false;
+ i++;
+ }
+ } else {
+ isWord = true;
+ }
+ }
+
+ if (isWord) {
+ i++;
+ }
+
+ return i;
+ }
+
+ public static String[] splitWords(String src) {
+ String[] result = new String[countWords(src)];
+
+ int i = 0;
+ StringBuilder sb = new StringBuilder();
+ for (char c : src.toCharArray()) {
+ if (Character.isWhitespace(c)) {
+ if (sb.length() != 0) {
+ result[i++] = resetStringBuilder(sb);
+ }
+ } else {
+ sb.append(c);
+ }
+ }
+
+ if (sb.length() != 0) {
+ result[i] = resetStringBuilder(sb);
+ }
+
+ return result;
+ }
+
+ public static char[] sequence(char c, int length) {
+ char[] result = new char[length];
+ Arrays.fill(result, c);
+ return result;
+ }
+
+ public static String stripPrefix(String string, String prefix) {
+ if (prefix != null && string.startsWith(prefix)) {
+ return string.substring(prefix.length());
+ }
+
+ return string;
+ }
+
+ public static String stripSuffix(String string, String suffix) {
+ if (suffix != null && string.endsWith(suffix)) {
+ return string.substring(suffix.length());
+ }
+
+ return string;
+ }
+
+ @SafeVarargs
+ public static Collection allCombinations(Iterable... parts) {
+ StringBuilder sb = new StringBuilder();
+ Collection result = new ArrayList<>();
+ buildCombinations(sb, result, parts, 0);
+ return result;
+ }
+
+ private static void buildCombinations(StringBuilder sb, Collection result, Iterable[] parts,
+ int index) {
+ if (index >= parts.length) {
+ result.add(sb.toString());
+ } else {
+ int startLength = sb.length();
+ for (String part : parts[index]) {
+ sb.append(part);
+ buildCombinations(sb, result, parts, index + 1);
+ sb.setLength(startLength);
+ }
+ }
+ }
+
+ @SafeVarargs
+ public static String[] allCombinations(String[]... parts) {
+ StringBuilder sb = new StringBuilder();
+
+ int length = 1;
+ for (String[] array : parts) length *= array.length;
+ String[] result = new String[length];
+
+ buildCombinations(sb, result, new int[] {0}, parts, 0);
+ return result;
+ }
+
+ private static void buildCombinations(StringBuilder sb, String[] result, int[] resultIndex, String[][] parts,
+ int index) {
+ if (index >= parts.length) {
+ result[resultIndex[0]++] = sb.toString();
+ } else {
+ int startLength = sb.length();
+ for (String part : parts[index]) {
+ sb.append(part);
+ buildCombinations(sb, result, resultIndex, parts, index + 1);
+ sb.setLength(startLength);
+ }
+ }
+ }
+
+ public static String toUnsignedHexString(byte b) {
+ int unsigned = b;
+ if (b < 0) {
+ unsigned += 0x100;
+ }
+
+ char[] chars = new char[2];
+
+ chars[0] = Character.forDigit(unsigned >>> 4, 0x10);
+ chars[1] = Character.forDigit(unsigned & 0x0F, 0x10);
+
+ return new String(chars);
+ }
+
+ public static String toUnsignedHexString(byte[] bytes, String separator, int size) {
+ StringBuilder sb = new StringBuilder();
+
+ for (int i = 0; i < bytes.length; ++i) {
+ sb.append(toUnsignedHexString(bytes[i]));
+ if (i < bytes.length - 1 && ((i + 1) % size == 0)) {
+ sb.append(separator);
+ }
+ }
+
+ return sb.toString();
+ }
+
+ public static String toUnsignedHexString(byte[] bytes) {
+ return toUnsignedHexString(bytes, ", ", 1);
+ }
+
+ public static char[] toFullHex(byte x) {
+ return toFullHex(x, Byte.BYTES);
+ }
+
+ public static char[] toFullHex(short x) {
+ return toFullHex(x, Short.BYTES);
+ }
+
+ public static char[] toFullHex(int x) {
+ return toFullHex(x, Integer.BYTES);
+ }
+
+ public static char[] toFullHex(long x) {
+ return toFullHex(x, Long.BYTES);
+ }
+
+ private static char[] toFullHex(long x, int bytes) {
+ final int digits = bytes * 2;
+
+ char[] result = new char[digits + 2];
+ result[0] = '0';
+ result[1] = 'x';
+
+ for (int digit = 0; digit < digits; ++digit) {
+ result[(digits - digit - 1) + 2] =
+ hexDigit(x, digit);
+ }
+
+ return result;
+ }
+
+ private static char hexDigit(long value, int digit) {
+ return hexDigit(
+ (int) (value >>> (4 * digit))
+ & 0xF
+ );
+ }
+
+ public static char hexDigit(int value) {
+ if (value < 0xA) return (char) ('0' + value);
+ else return (char) ('A' - 0xA + value);
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/jputil/chars/UncheckedEscapeException.java b/src/main/java/ru/windcorp/jputil/chars/UncheckedEscapeException.java
new file mode 100644
index 0000000..03c7a5f
--- /dev/null
+++ b/src/main/java/ru/windcorp/jputil/chars/UncheckedEscapeException.java
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * JPUtil
+ * Copyright (C) 2019 Javapony/OLEGSHA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *******************************************************************************/
+package ru.windcorp.jputil.chars;
+
+public class UncheckedEscapeException extends RuntimeException {
+
+ private static final long serialVersionUID = 5392628641744570926L;
+
+ public UncheckedEscapeException(String message, EscapeException cause) {
+ super(message, cause);
+ }
+
+ public UncheckedEscapeException(EscapeException cause) {
+ super(cause);
+ }
+
+ @Override
+ public synchronized EscapeException getCause() {
+ return (EscapeException) super.getCause();
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/jputil/chars/WordReader.java b/src/main/java/ru/windcorp/jputil/chars/WordReader.java
new file mode 100644
index 0000000..8636734
--- /dev/null
+++ b/src/main/java/ru/windcorp/jputil/chars/WordReader.java
@@ -0,0 +1,139 @@
+/*******************************************************************************
+ * JPUtil
+ * Copyright (C) 2019 Javapony/OLEGSHA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *******************************************************************************/
+package ru.windcorp.jputil.chars;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.nio.CharBuffer;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+public class WordReader implements Iterator {
+
+ private final Reader reader;
+
+ private char[] wordBuffer = new char[1024];
+ private final CharBuffer inputBuffer;
+
+ private String next = null;
+ private boolean isExhausted = false;
+
+ private IOException lastException = null;
+
+ public WordReader(Reader src, int bufferSize) {
+ this.reader = src;
+ this.inputBuffer = CharBuffer.allocate(bufferSize);
+ }
+
+ public WordReader(Reader src) {
+ this(src, 2048);
+ }
+
+ public WordReader(char[] array, int offset, int length) {
+ this.reader = null;
+ this.inputBuffer = CharBuffer.wrap(Arrays.copyOfRange(array, offset, length + offset));
+ }
+
+ public WordReader(char[] array) {
+ this.reader = null;
+ this.inputBuffer = CharBuffer.wrap(array);
+ }
+
+ public WordReader(String str) {
+ this(str.toCharArray());
+ }
+
+ @Override
+ public String next() {
+ if (!hasNext()) {
+ throw new NoSuchElementException();
+ }
+
+ String result = next;
+ next = null;
+ return result;
+ }
+
+ @Override
+ public boolean hasNext() {
+ if (next != null) {
+ return true;
+ }
+
+ if (isExhausted) {
+ return false;
+ }
+
+ int length = 0;
+ char c;
+ while (true) {
+ c = nextChar();
+
+ if (isExhausted) break;
+
+ if (Character.isWhitespace(c)) {
+ if (length == 0) continue;
+ else break;
+ }
+
+ if (wordBuffer.length == length) {
+ char[] newBuf = new char[wordBuffer.length * 2];
+ System.arraycopy(wordBuffer, 0, newBuf, 0, wordBuffer.length);
+ wordBuffer = newBuf;
+ }
+
+ wordBuffer[length++] = c;
+ }
+
+ if (length == 0) {
+ return false;
+ }
+
+ next = new String(wordBuffer, 0, length);
+ return true;
+ }
+
+ private char nextChar() {
+ if (!inputBuffer.hasRemaining()) {
+ if (reader == null) {
+ isExhausted = true;
+ return 0;
+ }
+
+ inputBuffer.rewind();
+ try {
+ if (reader.read(inputBuffer) == -1) {
+ isExhausted = true;
+ }
+ } catch (IOException e) {
+ lastException = e;
+ isExhausted = true;
+ return 0;
+ }
+
+ }
+
+ return inputBuffer.get();
+ }
+
+ public IOException getLastException() {
+ return lastException;
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/jputil/chars/reader/AbstractCharReader.java b/src/main/java/ru/windcorp/jputil/chars/reader/AbstractCharReader.java
new file mode 100644
index 0000000..1d48158
--- /dev/null
+++ b/src/main/java/ru/windcorp/jputil/chars/reader/AbstractCharReader.java
@@ -0,0 +1,104 @@
+/*******************************************************************************
+ * JPUtil
+ * Copyright (C) 2019 Javapony/OLEGSHA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *******************************************************************************/
+package ru.windcorp.jputil.chars.reader;
+
+/**
+ * @author Javapony
+ *
+ */
+public abstract class AbstractCharReader implements CharReader {
+
+ protected static final int DEFAULT_MARK_STACK_SIZE = 8;
+
+ /**
+ * Current position of this CharReader. The reader maps its input to positions starting from 0.
+ * Positions that are negative or lower than 0 are invalid. {@link #current()}
+ * will throw an exception if position is invalid.
+ */
+ protected int position = 0;
+
+ private int[] marks = new int[DEFAULT_MARK_STACK_SIZE];
+ private int nextMark = 0;
+
+ protected static int closestGreaterPowerOf2(int x) {
+ x |= x >> 1;
+ x |= x >> 2;
+ x |= x >> 4;
+ x |= x >> 8;
+ x |= x >> 16;
+ return x + 1;
+ }
+
+ /**
+ * @see ru.windcorp.jputil.chars.reader.CharReader#getPosition()
+ */
+ @Override
+ public int getPosition() {
+ return position;
+ }
+
+ /**
+ * @see ru.windcorp.jputil.chars.reader.CharReader#setPosition(int)
+ */
+ @Override
+ public int setPosition(int position) {
+ this.position = position;
+ return position;
+ }
+
+ /**
+ * @see ru.windcorp.jputil.chars.reader.CharReader#mark()
+ */
+ @Override
+ public int mark() {
+ ensureMarksCapacity();
+ marks[nextMark++] = position;
+ return position;
+ }
+
+ /**
+ * @see ru.windcorp.jputil.chars.reader.CharReader#forget()
+ */
+ @Override
+ public int forget() {
+ return marks[--nextMark];
+ }
+
+ private void ensureMarksCapacity() {
+ if (nextMark < marks.length) return;
+ int[] newMarks = new int[closestGreaterPowerOf2(nextMark)];
+ System.arraycopy(marks, 0, newMarks, 0, nextMark);
+ marks = newMarks;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("\"");
+
+ mark();
+ position = 0;
+ sb.append(getChars());
+ reset();
+
+ sb.append("\"\n ");
+ for (int i = 0; i < position; ++i) sb.append(' ');
+ sb.append("^ (pos " + position + ")");
+ return sb.toString();
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/ru/windcorp/jputil/chars/reader/ArrayCharReader.java b/src/main/java/ru/windcorp/jputil/chars/reader/ArrayCharReader.java
new file mode 100644
index 0000000..13a32d2
--- /dev/null
+++ b/src/main/java/ru/windcorp/jputil/chars/reader/ArrayCharReader.java
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * JPUtil
+ * Copyright (C) 2019 Javapony/OLEGSHA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *******************************************************************************/
+package ru.windcorp.jputil.chars.reader;
+
+import java.util.Objects;
+
+import ru.windcorp.jputil.ArrayUtil;
+
+/**
+ * @author Javapony
+ *
+ */
+public class ArrayCharReader extends AbstractCharReader {
+
+ private final char[] array;
+ private final int offset;
+ private final int length;
+
+ public ArrayCharReader(char[] array, int offset, int length) {
+ this.array = Objects.requireNonNull(array, "array");
+ this.length = ArrayUtil.checkArrayOffsetLength(array, offset, length);
+ this.offset = offset;
+ }
+
+ /**
+ * @see ru.windcorp.jputil.chars.reader.CharReader#current()
+ */
+ @Override
+ public char current() {
+ if (position >= length) return DONE;
+ if (position < 0)
+ throw new IllegalStateException("Position " + position + " is invalid");
+ return array[position + offset];
+ }
+
+ /**
+ * @see ru.windcorp.jputil.chars.reader.CharReader#remaining()
+ */
+ @Override
+ public int remaining() {
+ return length - position;
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/jputil/chars/reader/BufferedCharReader.java b/src/main/java/ru/windcorp/jputil/chars/reader/BufferedCharReader.java
new file mode 100644
index 0000000..5c6d5bd
--- /dev/null
+++ b/src/main/java/ru/windcorp/jputil/chars/reader/BufferedCharReader.java
@@ -0,0 +1,122 @@
+/*******************************************************************************
+ * JPUtil
+ * Copyright (C) 2019 Javapony/OLEGSHA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *******************************************************************************/
+package ru.windcorp.jputil.chars.reader;
+
+/**
+ * @author Javapony
+ *
+ */
+public abstract class BufferedCharReader extends AbstractCharReader {
+
+ protected static final int DEFAULT_BUFFER_SIZE = 256;
+ /**
+ * Buffer to store data acquired with {@link #pullChars(char[], int, int)}.
+ * Contains characters for positions [0; bufferNextIndex)
.
+ */
+ private char[] buffer = new char[DEFAULT_BUFFER_SIZE];
+
+ /**
+ * The index of the next character.
+ */
+ private int bufferNextIndex = 0;
+
+ /**
+ * Whether this reader has been buffered completely.
+ */
+ private boolean exhausted = false;
+
+ /**
+ * Acquires the next character.
+ * @return the character or {@link #DONE} if the end of the reader has been reached
+ */
+ protected abstract char pullChar();
+
+ /**
+ * Acquires next characters and stores them in the array.
+ *
+ * @param buffer the output array
+ * @param offset index of the first character
+ * @param length maximum amount of characters to be pulled
+ * @return the amount of characters actually pulled
+ */
+ protected int pullChars(char[] buffer, int offset, int length) {
+ for (int i = 0; i < length; ++i) {
+ if ((buffer[offset + i] = pullChar()) == DONE) {
+ return i;
+ }
+ }
+
+ return length;
+ }
+
+ private int pullChars(int offset, int length) {
+ if (exhausted || length == 0) return 0;
+
+ int pulled = pullChars(buffer, offset, length);
+ if (pulled != length) {
+ exhausted = true;
+ }
+
+ return pulled;
+ }
+
+ @Override
+ public char current() {
+ if (getPosition() < 0) {
+ throw new IllegalStateException("Position " + getPosition() + " is invalid");
+ }
+
+ if (getPosition() >= bufferNextIndex) {
+ if (exhausted) return DONE;
+
+ ensureBufferCapacity();
+
+ int needToPull = getPosition() - bufferNextIndex + 1;
+ assert needToPull <= buffer.length : "buffer size not ensured!";
+
+ int pulled = pullChars(bufferNextIndex, needToPull);
+ bufferNextIndex += pulled;
+
+ if (exhausted) return DONE;
+ }
+
+ // TODO test the shit out of current()
+
+ return buffer[getPosition()];
+ }
+
+ private void ensureBufferCapacity() {
+ if (getPosition() < buffer.length) return;
+ char[] newBuffer = new char[closestGreaterPowerOf2(getPosition())];
+ System.arraycopy(buffer, 0, newBuffer, 0, bufferNextIndex);
+ buffer = newBuffer;
+ }
+
+ /**
+ * @see ru.windcorp.jputil.chars.reader.CharReader#remaining()
+ */
+ @Override
+ public int remaining() {
+ if (exhausted) {
+ return Math.max(bufferNextIndex - getPosition(), 0);
+ }
+
+ return super.remaining();
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/jputil/chars/reader/CharReader.java b/src/main/java/ru/windcorp/jputil/chars/reader/CharReader.java
new file mode 100644
index 0000000..c6dd90a
--- /dev/null
+++ b/src/main/java/ru/windcorp/jputil/chars/reader/CharReader.java
@@ -0,0 +1,270 @@
+/*******************************************************************************
+ * JPUtil
+ * Copyright (C) 2019 Javapony/OLEGSHA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *******************************************************************************/
+package ru.windcorp.jputil.chars.reader;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+import ru.windcorp.jputil.chars.CharPredicate;
+import ru.windcorp.jputil.chars.EscapeException;
+import ru.windcorp.jputil.chars.Escaper;
+
+/**
+ * @author Javapony
+ *
+ */
+
+// SonarLint: Constants should not be defined in interfaces (java:S1214)
+// DONE is an essential part of the interface
+@SuppressWarnings("squid:S1214")
+
+public interface CharReader {
+
+ char DONE = '\uFFFF';
+
+ char current();
+ int getPosition();
+ int setPosition(int position);
+
+ default char next() {
+ return advance(1);
+ }
+
+ default char previous() {
+ return rollBack(1);
+ }
+
+ default char consume() {
+ char c = current();
+ advance(1);
+ return c;
+ }
+
+ default char advance(int forward) {
+ setPosition(getPosition() + forward);
+ return current();
+ }
+
+ default char rollBack(int backward) {
+ return advance(-backward);
+ }
+
+ default boolean isEnd() {
+ return current() == DONE;
+ }
+
+ default boolean has() {
+ return current() != DONE;
+ }
+
+ default boolean is(char c) {
+ return current() == c;
+ }
+
+ default int getChars(char[] output, int offset, int length) {
+ for (int i = 0; i < length; ++i) {
+ if ((output[offset + i] = current()) == DONE) {
+ return i;
+ }
+ next();
+ }
+
+ return length;
+ }
+
+ default int getChars(char[] output) {
+ return getChars(output, 0, output.length);
+ }
+
+ default char[] getChars(int length) {
+ char[] result = new char[length];
+ int from = getChars(result);
+ if (from != length) Arrays.fill(result, from, length, DONE);
+ return result;
+ }
+
+ default char[] getChars() {
+ return getChars(remaining());
+ }
+
+ default String getString(int length) {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < length && !isEnd(); ++i) sb.append(consume());
+ return sb.toString();
+ }
+
+ default String getString() {
+ return getString(Integer.MAX_VALUE);
+ }
+
+ default boolean match(CharSequence seq) {
+ for (int i = 0; i < seq.length(); ++i) {
+ if (isEnd()) return false;
+ if (current() != seq.charAt(i)) return false;
+ next();
+ }
+
+ return true;
+ }
+
+ default boolean matchOrReset(CharSequence seq) {
+ mark();
+ if (match(seq)) {
+ forget();
+ return true;
+ } else {
+ reset();
+ return false;
+ }
+ }
+
+ default boolean match(char[] array) {
+ for (int i = 0; i < array.length; ++i) {
+ if (isEnd()) return false;
+ if (current() != array[i]) return false;
+ next();
+ }
+
+ return true;
+ }
+
+ default boolean matchOrReset(char[] array) {
+ mark();
+ if (match(array)) {
+ forget();
+ return true;
+ } else {
+ reset();
+ return false;
+ }
+ }
+
+ default int skip(CharPredicate condition) {
+ int i = 0;
+
+ while (has() && condition.test(current())) {
+ i++;
+ next();
+ }
+
+ return i;
+ }
+
+ default int skipWhitespace() {
+ return skip(Character::isWhitespace);
+ }
+
+ /**
+ * Skips to the end of the current line. Both "\n"
, "\r"
+ * and "\r\n"
are considered line separators.
+ * @return the amount of characters in the skipped line
+ */
+ default int skipLine() {
+ int i = 0;
+
+ while (!isEnd()) {
+ if (current() == '\r') {
+ if (next() == '\n') {
+ next();
+ }
+ break;
+ } else if (current() == '\n') {
+ next();
+ break;
+ }
+
+ i++;
+ next();
+ }
+
+ return i;
+ }
+
+ default char[] readWhile(CharPredicate condition) {
+ return readUntil(CharPredicate.negate(condition));
+ }
+
+ default char[] readUntil(CharPredicate condition) {
+ mark();
+ int length = 0;
+ while (!isEnd() && !condition.test(current())) {
+ length++;
+ next();
+ }
+ reset();
+
+ char[] result = new char[length];
+ for (int i = 0; i < length; ++i) result[i] = consume();
+ return result;
+ }
+
+ default char[] readWord() {
+ skipWhitespace();
+ return readUntil(Character::isWhitespace);
+ }
+
+ default char[] readWord(Escaper escaper, char quotes) throws EscapeException {
+ skipWhitespace();
+
+ if (current() == quotes) {
+ return escaper.unescape(this, quotes);
+ } else {
+ return readWord();
+ }
+ }
+
+ default char[] readLine() {
+ mark();
+ int length = skipLine();
+ reset();
+
+ char[] result = new char[length];
+ for (int i = 0; i < result.length; ++i) result[i] = consume();
+ return result;
+ }
+
+ default int remaining() {
+ mark();
+ int result = 0;
+
+ while (consume() != DONE) result++;
+
+ reset();
+ return result;
+ }
+
+ int mark();
+ int forget();
+
+ default int reset() {
+ return setPosition(forget());
+ }
+
+ default IOException getLastException() {
+ return null;
+ }
+
+ default void resetLastException() {
+ // Do nothing
+ }
+
+ default boolean hasErrored() {
+ return getLastException() != null;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/ru/windcorp/jputil/chars/reader/CharReaders.java b/src/main/java/ru/windcorp/jputil/chars/reader/CharReaders.java
new file mode 100644
index 0000000..9cd02e0
--- /dev/null
+++ b/src/main/java/ru/windcorp/jputil/chars/reader/CharReaders.java
@@ -0,0 +1,112 @@
+/*******************************************************************************
+ * JPUtil
+ * Copyright (C) 2019 Javapony/OLEGSHA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *******************************************************************************/
+package ru.windcorp.jputil.chars.reader;
+
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.text.CharacterIterator;
+import java.util.function.IntSupplier;
+
+import ru.windcorp.jputil.chars.CharSupplier;
+
+/**
+ * @author Javapony
+ *
+ */
+public class CharReaders {
+
+ private CharReaders() {}
+
+ public static CharReader wrap(char[] array, int offset, int length) {
+ return new ArrayCharReader(array, offset, length);
+ }
+
+ public static CharReader wrap(char[] array) {
+ return wrap(array, 0, array.length);
+ }
+
+ public static CharReader wrap(String str, int offset, int length) {
+ return new StringCharReader(str, offset, length);
+ }
+
+ public static CharReader wrap(String str) {
+ return wrap(str, 0, str.length());
+ }
+
+ public static CharReader wrap(CharSupplier supplier) {
+ return new BufferedCharReader() {
+ @Override
+ protected char pullChar() {
+ try {
+ return supplier.getAsChar();
+ } catch (Exception e) {
+ return DONE;
+ }
+ }
+ };
+ }
+
+ public static CharReader wrap(IntSupplier supplier) {
+ return new BufferedCharReader() {
+ @Override
+ protected char pullChar() {
+ try {
+ int i = supplier.getAsInt();
+ if (i < 0 || i > Character.MAX_VALUE) {
+ return DONE;
+ } else {
+ return (char) i;
+ }
+ } catch (Exception e) {
+ return DONE;
+ }
+ }
+ };
+ }
+
+ public static CharReader wrap(CharacterIterator it) {
+ return new BufferedCharReader() {
+ @Override
+ protected char pullChar() {
+ char result = it.current();
+ it.next();
+ return result;
+ }
+ };
+ }
+
+ public static CharReader wrap(Reader reader) {
+ return new ReaderCharReader(reader);
+ }
+
+ public static CharReader wrap(InputStream is, Charset charset) {
+ return wrap(new InputStreamReader(is, charset));
+ }
+
+ public static CharReader wrapDefaultCS(InputStream is) {
+ return wrap(new InputStreamReader(is));
+ }
+
+ public static CharReader wrapUTF8(InputStream is) {
+ return wrap(new InputStreamReader(is, StandardCharsets.UTF_8));
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/jputil/chars/reader/ReaderCharReader.java b/src/main/java/ru/windcorp/jputil/chars/reader/ReaderCharReader.java
new file mode 100644
index 0000000..0a2435f
--- /dev/null
+++ b/src/main/java/ru/windcorp/jputil/chars/reader/ReaderCharReader.java
@@ -0,0 +1,70 @@
+/*******************************************************************************
+ * JPUtil
+ * Copyright (C) 2019 Javapony/OLEGSHA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *******************************************************************************/
+package ru.windcorp.jputil.chars.reader;
+
+import java.io.IOException;
+import java.io.Reader;
+
+/**
+ * @author Javapony
+ *
+ */
+public class ReaderCharReader extends BufferedCharReader {
+
+ private final Reader src;
+ private IOException lastException = null;
+
+ public ReaderCharReader(Reader src) {
+ this.src = src;
+ }
+
+ /**
+ * @see ru.windcorp.jputil.chars.reader.BufferedCharReader#pullChar()
+ */
+ @Override
+ protected char pullChar() {
+ try {
+ return (char) src.read(); // Handles DONE correctly
+ } catch (IOException e) {
+ lastException = e;
+ return DONE;
+ }
+ }
+
+ /**
+ * @see ru.windcorp.jputil.chars.reader.BufferedCharReader#pullChars(char[], int, int)
+ */
+ @Override
+ protected int pullChars(char[] buffer, int offset, int length) {
+ try {
+ return src.read(buffer, offset, length);
+ } catch (IOException e) {
+ lastException = e;
+ return 0;
+ }
+ }
+
+ /**
+ * @return the exception
+ */
+ @Override
+ public IOException getLastException() {
+ return lastException;
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/jputil/chars/reader/StringCharReader.java b/src/main/java/ru/windcorp/jputil/chars/reader/StringCharReader.java
new file mode 100644
index 0000000..c082321
--- /dev/null
+++ b/src/main/java/ru/windcorp/jputil/chars/reader/StringCharReader.java
@@ -0,0 +1,65 @@
+/*******************************************************************************
+ * JPUtil
+ * Copyright (C) 2019 Javapony/OLEGSHA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *******************************************************************************/
+package ru.windcorp.jputil.chars.reader;
+
+import java.util.Objects;
+
+/**
+ * @author Javapony
+ *
+ */
+public class StringCharReader extends AbstractCharReader {
+
+ private final String str;
+ private final int offset;
+ private final int length;
+
+ public StringCharReader(String str, int offset, int length) {
+ this.str = Objects.requireNonNull(str, "str");
+
+ if (length < 0)
+ length = str.length();
+
+ int end = offset + length;
+ if (end > str.length() || offset < 0)
+ throw new IllegalArgumentException("String contains [0; " + str.length() + "), requested [" + offset + "; " + end + ")");
+
+ this.offset = offset;
+ this.length = length;
+ }
+
+ /**
+ * @see ru.windcorp.jputil.chars.reader.CharReader#current()
+ */
+ @Override
+ public char current() {
+ if (position >= length) return DONE;
+ if (position < 0)
+ throw new IllegalStateException("Position " + position + " is invalid");
+ return str.charAt(position + offset);
+ }
+
+ /**
+ * @see ru.windcorp.jputil.chars.reader.CharReader#remaining()
+ */
+ @Override
+ public int remaining() {
+ return length - position;
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/jputil/functions/ThrowingBiConsumer.java b/src/main/java/ru/windcorp/jputil/functions/ThrowingBiConsumer.java
new file mode 100644
index 0000000..4e1c26e
--- /dev/null
+++ b/src/main/java/ru/windcorp/jputil/functions/ThrowingBiConsumer.java
@@ -0,0 +1,72 @@
+/*******************************************************************************
+ * JPUtil
+ * Copyright (C) 2019 Javapony/OLEGSHA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *******************************************************************************/
+package ru.windcorp.jputil.functions;
+
+import java.util.function.BiConsumer;
+
+@FunctionalInterface
+public interface ThrowingBiConsumer {
+
+ @FunctionalInterface
+ public static interface BiConsumerHandler {
+ void handle(T t, U u, E e);
+ }
+
+ void accept(T t, U u) throws E;
+
+ @SuppressWarnings("unchecked")
+ default BiConsumer withHandler(BiConsumerHandler super T, ? super U, ? super E> handler) {
+ return (t, u) -> {
+ try {
+ accept(t, u);
+ } catch (RuntimeException e) {
+ throw e;
+ } catch (Exception e) {
+ handler.handle(t, u, (E) e);
+ }
+ };
+ }
+
+ public static ThrowingBiConsumer concat(
+ ThrowingBiConsumer super T, ? super U, ? extends E> first,
+ ThrowingBiConsumer super T, ? super U, ? extends E> second) {
+ return (t, u) -> {
+ first.accept(t, u);
+ second.accept(t, u);
+ };
+ }
+
+ public static ThrowingBiConsumer concat(
+ BiConsumer super T, ? super U> first,
+ ThrowingBiConsumer super T, ? super U, E> second) {
+ return (t, u) -> {
+ first.accept(t, u);
+ second.accept(t, u);
+ };
+ }
+
+ public static ThrowingBiConsumer concat(
+ ThrowingBiConsumer super T, ? super U, E> first,
+ BiConsumer super T, ? super U> second) {
+ return (t, u) -> {
+ first.accept(t, u);
+ second.accept(t, u);
+ };
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/jputil/functions/ThrowingConsumer.java b/src/main/java/ru/windcorp/jputil/functions/ThrowingConsumer.java
new file mode 100644
index 0000000..dcd1931
--- /dev/null
+++ b/src/main/java/ru/windcorp/jputil/functions/ThrowingConsumer.java
@@ -0,0 +1,62 @@
+/*******************************************************************************
+ * JPUtil
+ * Copyright (C) 2019 Javapony/OLEGSHA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *******************************************************************************/
+package ru.windcorp.jputil.functions;
+
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+
+@FunctionalInterface
+public interface ThrowingConsumer {
+
+ void accept(T t) throws E;
+
+ @SuppressWarnings("unchecked")
+ default Consumer withHandler(BiConsumer super T, ? super E> handler) {
+ return t -> {
+ try {
+ accept(t);
+ } catch (RuntimeException e) {
+ throw e;
+ } catch (Exception e) {
+ handler.accept(t, (E) e);
+ }
+ };
+ }
+
+ public static ThrowingConsumer concat(ThrowingConsumer super T, ? extends E> first, ThrowingConsumer super T, ? extends E> second) {
+ return t -> {
+ first.accept(t);
+ second.accept(t);
+ };
+ }
+
+ public static ThrowingConsumer concat(Consumer super T> first, ThrowingConsumer super T, ? extends E> second) {
+ return t -> {
+ first.accept(t);
+ second.accept(t);
+ };
+ }
+
+ public static ThrowingConsumer concat(ThrowingConsumer super T, ? extends E> first, Consumer super T> second) {
+ return t -> {
+ first.accept(t);
+ second.accept(t);
+ };
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/jputil/functions/ThrowingFunction.java b/src/main/java/ru/windcorp/jputil/functions/ThrowingFunction.java
new file mode 100644
index 0000000..faffb44
--- /dev/null
+++ b/src/main/java/ru/windcorp/jputil/functions/ThrowingFunction.java
@@ -0,0 +1,73 @@
+/*******************************************************************************
+ * JPUtil
+ * Copyright (C) 2019 Javapony/OLEGSHA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *******************************************************************************/
+package ru.windcorp.jputil.functions;
+
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+@FunctionalInterface
+public interface ThrowingFunction {
+
+ R apply(T t) throws E;
+
+ @SuppressWarnings("unchecked")
+ default Function withHandler(BiConsumer super T, ? super E> handler, Function super T, ? extends R> value) {
+ return t -> {
+ try {
+ return apply(t);
+ } catch (RuntimeException e) {
+ throw e;
+ } catch (Exception e) {
+ if (handler != null) handler.accept(t, (E) e);
+ return value == null ? null : value.apply(t);
+ }
+ };
+ }
+
+ default Function withHandler(BiConsumer super T, ? super E> handler, Supplier extends R> value) {
+ return withHandler(handler, t -> value.get());
+ }
+
+ default Function withHandler(BiConsumer super T, ? super E> handler, R value) {
+ return withHandler(handler, t -> value);
+ }
+
+ default Function withHandler(BiConsumer super T, ? super E> handler) {
+ return withHandler(handler, (Function) null);
+ }
+
+ public static ThrowingFunction compose(
+ ThrowingFunction super T, I, ? extends E> first,
+ ThrowingFunction super I, ? extends R, ? extends E> second) {
+ return t -> second.apply(first.apply(t));
+ }
+
+ public static ThrowingFunction compose(
+ Function super T, I> first,
+ ThrowingFunction super I, ? extends R, E> second) {
+ return t -> second.apply(first.apply(t));
+ }
+
+ public static ThrowingFunction compose(
+ ThrowingFunction super T, I, E> first,
+ Function super I, ? extends R> second) {
+ return t -> second.apply(first.apply(t));
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/jputil/functions/ThrowingRunnable.java b/src/main/java/ru/windcorp/jputil/functions/ThrowingRunnable.java
new file mode 100644
index 0000000..0684122
--- /dev/null
+++ b/src/main/java/ru/windcorp/jputil/functions/ThrowingRunnable.java
@@ -0,0 +1,64 @@
+/*******************************************************************************
+ * JPUtil
+ * Copyright (C) 2019 Javapony/OLEGSHA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *******************************************************************************/
+package ru.windcorp.jputil.functions;
+
+import java.util.function.Consumer;
+
+@FunctionalInterface
+public interface ThrowingRunnable {
+
+ void run() throws E;
+
+ @SuppressWarnings("unchecked")
+ default Runnable withHandler(Consumer super E> handler) {
+ return () -> {
+ try {
+ run();
+ } catch (RuntimeException e) {
+ throw e;
+ } catch (Exception e) {
+ handler.accept((E) e);
+ }
+ };
+ }
+
+ public static ThrowingRunnable concat(
+ ThrowingRunnable extends E> first,
+ ThrowingRunnable extends E> second
+ ) {
+ return () -> {
+ first.run();
+ second.run();
+ };
+ }
+
+ public static ThrowingRunnable concat(Runnable first, ThrowingRunnable second) {
+ return () -> {
+ first.run();
+ second.run();
+ };
+ }
+
+ public static ThrowingRunnable concat(ThrowingRunnable first, Runnable second) {
+ return () -> {
+ first.run();
+ second.run();
+ };
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/common/util/ThrowingRunnable.java b/src/main/java/ru/windcorp/jputil/functions/ThrowingSupplier.java
similarity index 51%
rename from src/main/java/ru/windcorp/progressia/common/util/ThrowingRunnable.java
rename to src/main/java/ru/windcorp/jputil/functions/ThrowingSupplier.java
index 3d322ee..32327e5 100644
--- a/src/main/java/ru/windcorp/progressia/common/util/ThrowingRunnable.java
+++ b/src/main/java/ru/windcorp/jputil/functions/ThrowingSupplier.java
@@ -1,6 +1,6 @@
/*******************************************************************************
- * Progressia
- * Copyright (C) 2020 Wind Corporation
+ * JPUtil
+ * Copyright (C) 2019 Javapony/OLEGSHA
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,49 +15,36 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*******************************************************************************/
-package ru.windcorp.progressia.common.util;
+package ru.windcorp.jputil.functions;
import java.util.function.Consumer;
-
-import com.google.common.base.Throwables;
+import java.util.function.Supplier;
@FunctionalInterface
-public interface ThrowingRunnable {
-
- void run() throws T;
-
- default Runnable withCatcher(
- Consumer catcher,
- Class throwableClass
- ) {
- return () -> {
-
- try {
- ThrowingRunnable.this.run();
- } catch (Throwable t) {
- if (t.getClass() == throwableClass) {
- catcher.accept(throwableClass.cast(t));
- }
-
- Throwables.throwIfUnchecked(t);
-
- // This should never happen
- throw new AssertionError("This should not have been thrown", t);
- }
-
- };
- }
-
- default Runnable withCatcher(
- Consumer catcher
- ) {
- return () -> {
- try {
- ThrowingRunnable.this.run();
- } catch (Throwable t) {
- catcher.accept(t);
- }
- };
- }
+public interface ThrowingSupplier {
+ T get() throws E;
+
+ @SuppressWarnings("unchecked")
+ default Supplier withHandler(Consumer super E> handler, Supplier extends T> value) {
+ return () -> {
+ try {
+ return get();
+ } catch (RuntimeException e) {
+ throw e;
+ } catch (Exception e) {
+ if (handler != null) handler.accept((E) e);
+ return value == null ? null : value.get();
+ }
+ };
+ }
+
+ default Supplier withHandler(Consumer super E> handler, T value) {
+ return withHandler(handler, () -> value);
+ }
+
+ default Supplier withHandler(Consumer super E> handler) {
+ return withHandler(handler, (Supplier) null);
+ }
+
}
diff --git a/src/main/java/ru/windcorp/jputil/iterators/ArrayIterator.java b/src/main/java/ru/windcorp/jputil/iterators/ArrayIterator.java
new file mode 100644
index 0000000..dc0cdcd
--- /dev/null
+++ b/src/main/java/ru/windcorp/jputil/iterators/ArrayIterator.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * JPUtil
+ * Copyright (C) 2019 Javapony/OLEGSHA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *******************************************************************************/
+package ru.windcorp.jputil.iterators;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+public class ArrayIterator implements Iterator {
+
+ private final E[] array;
+ private int next;
+
+ @SafeVarargs
+ public ArrayIterator(E... array) {
+ this.array = array;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return next < array.length;
+ }
+
+ @Override
+ public E next() {
+ try {
+ return array[next++];
+ } catch (ArrayIndexOutOfBoundsException e) {
+ throw new NoSuchElementException();
+ }
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/jputil/iterators/FunctionIterator.java b/src/main/java/ru/windcorp/jputil/iterators/FunctionIterator.java
new file mode 100644
index 0000000..a8d63ae
--- /dev/null
+++ b/src/main/java/ru/windcorp/jputil/iterators/FunctionIterator.java
@@ -0,0 +1,61 @@
+/*******************************************************************************
+ * JPUtil
+ * Copyright (C) 2019 Javapony/OLEGSHA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *******************************************************************************/
+package ru.windcorp.jputil.iterators;
+
+import java.util.Iterator;
+import java.util.function.Function;
+
+/**
+ * @author Javapony
+ *
+ */
+public class FunctionIterator implements Iterator {
+
+ private final Iterator parent;
+ private final Function function;
+
+ public FunctionIterator(Iterator parent, Function function) {
+ this.parent = parent;
+ this.function = function;
+ }
+
+ /**
+ * @see java.util.Iterator#hasNext()
+ */
+ @Override
+ public boolean hasNext() {
+ return parent.hasNext();
+ }
+
+ /**
+ * @see java.util.Iterator#next()
+ */
+ @Override
+ public E next() {
+ return function.apply(parent.next());
+ }
+
+ /**
+ * @see java.util.Iterator#remove()
+ */
+ @Override
+ public void remove() {
+ parent.remove();
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/jputil/iterators/PeekingIterator.java b/src/main/java/ru/windcorp/jputil/iterators/PeekingIterator.java
new file mode 100644
index 0000000..0eaff06
--- /dev/null
+++ b/src/main/java/ru/windcorp/jputil/iterators/PeekingIterator.java
@@ -0,0 +1,60 @@
+/*******************************************************************************
+ * JPUtil
+ * Copyright (C) 2019 Javapony/OLEGSHA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *******************************************************************************/
+package ru.windcorp.jputil.iterators;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+public class PeekingIterator implements Iterator {
+
+ private final Iterator extends E> source;
+ private E next = null;
+
+ public PeekingIterator(Iterator extends E> source) {
+ this.source = source;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return next != null || source.hasNext();
+ }
+
+ public E peek() {
+ if (next == null) {
+ if (source.hasNext()) {
+ next = source.next();
+ } else {
+ throw new NoSuchElementException();
+ }
+ }
+
+ return next;
+ }
+
+ // SonarLint: "Iterator.next()" methods should throw "NoSuchElementException" (java:S2272)
+ // peek() throws NoSuchElementException as expected
+ @SuppressWarnings("squid:S2272")
+
+ @Override
+ public E next() {
+ E element = peek();
+ next = null;
+ return element;
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/jputil/iterators/RangeIterator.java b/src/main/java/ru/windcorp/jputil/iterators/RangeIterator.java
new file mode 100644
index 0000000..49d69fa
--- /dev/null
+++ b/src/main/java/ru/windcorp/jputil/iterators/RangeIterator.java
@@ -0,0 +1,67 @@
+/*******************************************************************************
+ * JPUtil
+ * Copyright (C) 2019 Javapony/OLEGSHA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *******************************************************************************/
+package ru.windcorp.jputil.iterators;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+public class RangeIterator implements Iterator {
+
+ private final Iterator parent;
+ private final int from;
+ private final int amount;
+
+ private int nextIndex = 0;
+
+ public RangeIterator(Iterator iterator, int from, int amount) {
+ this.parent = iterator;
+ this.from = from;
+ this.amount = amount < 0 ? Integer.MAX_VALUE : amount;
+ }
+
+ public RangeIterator(Iterator iterator, int from) {
+ this(iterator, from, -1);
+ }
+
+ @Override
+ public boolean hasNext() {
+ update();
+ return nextIndex < from + amount && parent.hasNext();
+ }
+
+ @Override
+ public E next() {
+ update();
+ if (nextIndex >= from + amount) {
+ throw new NoSuchElementException("RangeIterator about to retrieve element " + nextIndex
+ + " which exceeds upper boundary " + (from + amount));
+ }
+
+ E result = parent.next();
+ nextIndex++;
+ return result;
+ }
+
+ protected void update() {
+ while (nextIndex < from && parent.hasNext()) {
+ parent.next();
+ nextIndex++;
+ }
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/jputil/iterators/Reiterator.java b/src/main/java/ru/windcorp/jputil/iterators/Reiterator.java
new file mode 100644
index 0000000..71adb06
--- /dev/null
+++ b/src/main/java/ru/windcorp/jputil/iterators/Reiterator.java
@@ -0,0 +1,70 @@
+/*******************************************************************************
+ * JPUtil
+ * Copyright (C) 2019 Javapony/OLEGSHA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *******************************************************************************/
+package ru.windcorp.jputil.iterators;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+public class Reiterator implements Iterable {
+
+ private class ReiteratorIterator implements Iterator {
+
+ int index = 0;
+
+ @Override
+ public boolean hasNext() {
+ synchronized (source) {
+ if (index >= data.size()) {
+ if (!source.hasNext()) {
+ return false;
+ } else {
+ data.add(source.next());
+ }
+ }
+
+ return true;
+ }
+ }
+
+ @Override
+ public E next() {
+ E result;
+ synchronized (source) {
+ if (!hasNext()) throw new NoSuchElementException();
+ result = data.get(index);
+ }
+ index++;
+ return result;
+ }
+
+ }
+
+ private final Iterator source;
+ private final ArrayList data = new ArrayList<>();
+
+ public Reiterator(Iterator source) {
+ this.source = source;
+ }
+
+ @Override
+ public Iterator iterator() {
+ return new ReiteratorIterator();
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/jputil/selectors/AbstractSelectorOperator.java b/src/main/java/ru/windcorp/jputil/selectors/AbstractSelectorOperator.java
new file mode 100644
index 0000000..1b0181f
--- /dev/null
+++ b/src/main/java/ru/windcorp/jputil/selectors/AbstractSelectorOperator.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * JPUtil
+ * Copyright (C) 2019 Javapony/OLEGSHA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *******************************************************************************/
+package ru.windcorp.jputil.selectors;
+
+public abstract class AbstractSelectorOperator implements SelectorOperator {
+
+ private final String[] names;
+
+ public AbstractSelectorOperator(String[] names) {
+ this.names = names;
+ }
+
+ @Override
+ public boolean matchesName(String name) {
+ for (String n : names) {
+ if (n.equals(name)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/jputil/selectors/NamedParameterizedSelector.java b/src/main/java/ru/windcorp/jputil/selectors/NamedParameterizedSelector.java
new file mode 100644
index 0000000..8893a05
--- /dev/null
+++ b/src/main/java/ru/windcorp/jputil/selectors/NamedParameterizedSelector.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * JPUtil
+ * Copyright (C) 2019 Javapony/OLEGSHA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *******************************************************************************/
+package ru.windcorp.jputil.selectors;
+
+import ru.windcorp.jputil.SyntaxException;
+import ru.windcorp.jputil.chars.StringUtil;
+
+public abstract class NamedParameterizedSelector extends NamedSelector {
+
+ private final char separator;
+ private String givenName;
+
+ public NamedParameterizedSelector(char separator, String... names) {
+ super(names);
+ this.separator = separator;
+ }
+
+ @Override
+ public Selector derive(String name) throws SyntaxException {
+ String[] parts = StringUtil.split(name, separator, 2);
+
+ if (parts[1] == null) {
+ return null;
+ }
+
+ if (!matchesName(parts[0])) {
+ return null;
+ }
+
+ NamedParameterizedSelector selector = deriveImpl(parts[1]);
+ selector.givenName = name;
+ return selector;
+ }
+
+ protected abstract NamedParameterizedSelector deriveImpl(String param) throws SyntaxException;
+
+ @Override
+ public String toString() {
+ return givenName;
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/jputil/selectors/NamedSelector.java b/src/main/java/ru/windcorp/jputil/selectors/NamedSelector.java
new file mode 100644
index 0000000..864c214
--- /dev/null
+++ b/src/main/java/ru/windcorp/jputil/selectors/NamedSelector.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * JPUtil
+ * Copyright (C) 2019 Javapony/OLEGSHA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *******************************************************************************/
+package ru.windcorp.jputil.selectors;
+
+import ru.windcorp.jputil.SyntaxException;
+
+public abstract class NamedSelector implements Selector {
+
+ private final String[] names;
+
+ public NamedSelector(String... names) {
+ this.names = names;
+ }
+
+ public boolean matchesName(String name) {
+ for (String n : names) {
+ if (n.equalsIgnoreCase(name)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public Selector derive(String name) throws SyntaxException {
+ return matchesName(name) ? this : null;
+ }
+
+ @Override
+ public String toString() {
+ return names[0];
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/jputil/selectors/OperatorAnd.java b/src/main/java/ru/windcorp/jputil/selectors/OperatorAnd.java
new file mode 100644
index 0000000..61641c4
--- /dev/null
+++ b/src/main/java/ru/windcorp/jputil/selectors/OperatorAnd.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * JPUtil
+ * Copyright (C) 2019 Javapony/OLEGSHA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *******************************************************************************/
+package ru.windcorp.jputil.selectors;
+
+import java.util.Deque;
+import java.util.function.Predicate;
+
+public class OperatorAnd extends AbstractSelectorOperator {
+
+ public OperatorAnd(String... names) {
+ super(names);
+ }
+
+ @Override
+ public void process(Deque> stack) {
+ Predicate arg2 = stack.pop();
+ Predicate arg1 = stack.pop();
+ stack.push(obj -> arg1.test(obj) && arg2.test(obj));
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/jputil/selectors/OperatorExclude.java b/src/main/java/ru/windcorp/jputil/selectors/OperatorExclude.java
new file mode 100644
index 0000000..5e7235a
--- /dev/null
+++ b/src/main/java/ru/windcorp/jputil/selectors/OperatorExclude.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * JPUtil
+ * Copyright (C) 2019 Javapony/OLEGSHA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *******************************************************************************/
+package ru.windcorp.jputil.selectors;
+
+import java.util.Deque;
+import java.util.function.Predicate;
+
+public class OperatorExclude extends AbstractSelectorOperator {
+
+ public OperatorExclude(String... names) {
+ super(names);
+ }
+
+ @Override
+ public void process(Deque> stack) {
+ Predicate arg2 = stack.pop();
+ Predicate arg1 = stack.pop();
+ stack.push(obj -> arg1.test(obj) && !arg2.test(obj));
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/jputil/selectors/OperatorNot.java b/src/main/java/ru/windcorp/jputil/selectors/OperatorNot.java
new file mode 100644
index 0000000..cd445d8
--- /dev/null
+++ b/src/main/java/ru/windcorp/jputil/selectors/OperatorNot.java
@@ -0,0 +1,34 @@
+/*******************************************************************************
+ * JPUtil
+ * Copyright (C) 2019 Javapony/OLEGSHA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *******************************************************************************/
+package ru.windcorp.jputil.selectors;
+
+import java.util.Deque;
+import java.util.function.Predicate;
+
+public class OperatorNot extends AbstractSelectorOperator {
+
+ public OperatorNot(String... names) {
+ super(names);
+ }
+
+ @Override
+ public void process(Deque> stack) {
+ stack.push(stack.pop().negate());
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/jputil/selectors/OperatorOr.java b/src/main/java/ru/windcorp/jputil/selectors/OperatorOr.java
new file mode 100644
index 0000000..e40a1a1
--- /dev/null
+++ b/src/main/java/ru/windcorp/jputil/selectors/OperatorOr.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * JPUtil
+ * Copyright (C) 2019 Javapony/OLEGSHA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *******************************************************************************/
+package ru.windcorp.jputil.selectors;
+
+import java.util.Deque;
+import java.util.function.Predicate;
+
+public class OperatorOr extends AbstractSelectorOperator {
+
+ public OperatorOr(String... names) {
+ super(names);
+ }
+
+ @Override
+ public void process(Deque> stack) {
+ Predicate arg2 = stack.pop();
+ Predicate arg1 = stack.pop();
+ stack.push(obj -> arg1.test(obj) || arg2.test(obj));
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/jputil/selectors/OperatorXor.java b/src/main/java/ru/windcorp/jputil/selectors/OperatorXor.java
new file mode 100644
index 0000000..e4ff44d
--- /dev/null
+++ b/src/main/java/ru/windcorp/jputil/selectors/OperatorXor.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * JPUtil
+ * Copyright (C) 2019 Javapony/OLEGSHA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *******************************************************************************/
+package ru.windcorp.jputil.selectors;
+
+import java.util.Deque;
+import java.util.function.Predicate;
+
+public class OperatorXor extends AbstractSelectorOperator {
+
+ public OperatorXor(String... names) {
+ super(names);
+ }
+
+ @Override
+ public void process(Deque> stack) {
+ Predicate arg2 = stack.pop();
+ Predicate arg1 = stack.pop();
+ stack.push(obj -> arg1.test(obj) != arg2.test(obj));
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/jputil/selectors/PredicateWrapper.java b/src/main/java/ru/windcorp/jputil/selectors/PredicateWrapper.java
new file mode 100644
index 0000000..86f21fc
--- /dev/null
+++ b/src/main/java/ru/windcorp/jputil/selectors/PredicateWrapper.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * JPUtil
+ * Copyright (C) 2019 Javapony/OLEGSHA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *******************************************************************************/
+package ru.windcorp.jputil.selectors;
+
+import java.util.function.Predicate;
+
+public class PredicateWrapper extends NamedSelector {
+
+ private final Predicate super T> predicate;
+
+ public PredicateWrapper(String name, Predicate super T> predicate) {
+ super(name);
+ this.predicate = predicate;
+ }
+
+ @Override
+ public boolean test(T obj) {
+ return predicate.test(obj);
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/jputil/selectors/Selector.java b/src/main/java/ru/windcorp/jputil/selectors/Selector.java
new file mode 100644
index 0000000..4db3a08
--- /dev/null
+++ b/src/main/java/ru/windcorp/jputil/selectors/Selector.java
@@ -0,0 +1,28 @@
+/*******************************************************************************
+ * JPUtil
+ * Copyright (C) 2019 Javapony/OLEGSHA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *******************************************************************************/
+package ru.windcorp.jputil.selectors;
+
+import java.util.function.Predicate;
+
+import ru.windcorp.jputil.SyntaxException;
+
+public interface Selector extends Predicate {
+
+ public Selector derive(String name) throws SyntaxException;
+
+}
diff --git a/src/main/java/ru/windcorp/jputil/selectors/SelectorOperator.java b/src/main/java/ru/windcorp/jputil/selectors/SelectorOperator.java
new file mode 100644
index 0000000..fc8250f
--- /dev/null
+++ b/src/main/java/ru/windcorp/jputil/selectors/SelectorOperator.java
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * JPUtil
+ * Copyright (C) 2019 Javapony/OLEGSHA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *******************************************************************************/
+package ru.windcorp.jputil.selectors;
+
+import java.util.Deque;
+import java.util.function.Predicate;
+
+public interface SelectorOperator {
+
+ public void process(Deque> stack);
+
+ public boolean matchesName(String name);
+
+}
diff --git a/src/main/java/ru/windcorp/jputil/selectors/SelectorSystem.java b/src/main/java/ru/windcorp/jputil/selectors/SelectorSystem.java
new file mode 100644
index 0000000..9238c2b
--- /dev/null
+++ b/src/main/java/ru/windcorp/jputil/selectors/SelectorSystem.java
@@ -0,0 +1,176 @@
+/*******************************************************************************
+ * JPUtil
+ * Copyright (C) 2019 Javapony/OLEGSHA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *******************************************************************************/
+package ru.windcorp.jputil.selectors;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.function.Predicate;
+
+import ru.windcorp.jputil.SyntaxException;
+import ru.windcorp.jputil.iterators.PeekingIterator;
+
+public class SelectorSystem {
+
+ public static final char EXPRESSION_OPEN = '(';
+ public static final char EXPRESSION_CLOSE = ')';
+
+ private final Collection> selectors =
+ Collections.synchronizedCollection(new ArrayList>());
+
+ private final Collection operators =
+ Collections.synchronizedCollection(new ArrayList());
+
+ private String stackPrefix = null;
+
+ public Collection> getSelectors() {
+ return this.selectors;
+ }
+
+ public Collection getSelectorOperators() {
+ return this.operators;
+ }
+
+ public String getStackPrefix() {
+ return stackPrefix;
+ }
+
+ public SelectorSystem setStackPrefix(String stackPrefix) {
+ this.stackPrefix = stackPrefix;
+ return this;
+ }
+
+ public SelectorSystem add(Selector selector) {
+ getSelectors().add(selector);
+ return this;
+ }
+
+ public SelectorSystem add(SelectorOperator operator) {
+ getSelectorOperators().add(operator);
+ return this;
+ }
+
+ public Predicate parse(Iterator tokens) throws SyntaxException {
+ PeekingIterator peeker = new PeekingIterator<>(tokens);
+
+ if (getStackPrefix() != null && peeker.hasNext() && getStackPrefix().equals(peeker.peek())) {
+ peeker.next();
+ return parseStack(peeker);
+ }
+
+ Deque> stack = new LinkedList<>();
+
+ synchronized (getSelectorOperators()) {
+ synchronized (getSelectors()) {
+
+ while (peeker.hasNext()) {
+ parseToken(stack, peeker);
+ }
+
+ }
+ }
+
+ return compress(stack);
+ }
+
+ private void parseToken(Deque> stack, Iterator tokens) throws SyntaxException {
+
+ if (!tokens.hasNext()) {
+ throw new SyntaxException("Not enough tokens");
+ }
+ String token = tokens.next();
+
+ for (SelectorOperator operator : getSelectorOperators()) {
+ if (operator.matchesName(token.toLowerCase())) {
+ parseToken(stack, tokens);
+ operator.process(stack);
+ return;
+ }
+ }
+
+ Selector tmp;
+ for (Selector selector : getSelectors()) {
+ if ((tmp = selector.derive(token)) != null) {
+ stack.push(tmp);
+ return;
+ }
+ }
+
+ throw new SyntaxException("Unknown token \"" + token + "\"");
+ }
+
+ public Predicate parseStack(Iterator tokens) throws SyntaxException {
+ Deque> stack = new LinkedList<>();
+
+ String token;
+
+ synchronized (getSelectorOperators()) {
+ synchronized (getSelectors()) {
+
+ tokenCycle:
+ while (tokens.hasNext()) {
+ token = tokens.next();
+
+ for (SelectorOperator operator : getSelectorOperators()) {
+ if (operator.matchesName(token.toLowerCase())) {
+ operator.process(stack);
+ continue tokenCycle;
+ }
+ }
+
+ for (Selector selector : getSelectors()) {
+ Selector tmp;
+ if ((tmp = selector.derive(token)) != null) {
+ stack.push(tmp);
+ continue tokenCycle;
+ }
+ }
+
+ throw new SyntaxException("Unknown token \"" + token + "\"");
+
+ }
+ }
+ }
+
+ return compress(stack);
+ }
+
+ private Predicate compress(Deque> stack) throws SyntaxException {
+ if (stack.isEmpty()) {
+ throw new SyntaxException("Stack is empty");
+ }
+
+ if (stack.size() == 1) {
+ return stack.pop();
+ }
+
+ return obj -> {
+ for (Predicate super T> predicate : stack) {
+ if (predicate.test(obj)) {
+ return true;
+ }
+ }
+
+ return false;
+ };
+ }
+
+}
diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/backend/RenderTaskQueue.java b/src/main/java/ru/windcorp/progressia/client/graphics/backend/RenderTaskQueue.java
index ff3f241..a30168a 100644
--- a/src/main/java/ru/windcorp/progressia/client/graphics/backend/RenderTaskQueue.java
+++ b/src/main/java/ru/windcorp/progressia/client/graphics/backend/RenderTaskQueue.java
@@ -23,7 +23,7 @@ import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
-import ru.windcorp.progressia.common.util.ThrowingRunnable;
+import ru.windcorp.jputil.functions.ThrowingRunnable;
public class RenderTaskQueue {
@@ -46,9 +46,9 @@ public class RenderTaskQueue {
private static final Object WAIT_AND_INVOKE_MONITOR = new Object();
@SuppressWarnings("unchecked")
- public static void waitAndInvoke(
- ThrowingRunnable task
- ) throws InterruptedException, T {
+ public static void waitAndInvoke(
+ ThrowingRunnable task
+ ) throws InterruptedException, E {
if (GraphicsInterface.isRenderThread()) {
task.run();
@@ -91,7 +91,7 @@ public class RenderTaskQueue {
throw (Error) thrown;
}
- throw (T) thrown; // Guaranteed
+ throw (E) thrown; // Guaranteed
}
}