From 65eaae68a84690ba134cb42cdfadcba699db901c Mon Sep 17 00:00:00 2001 From: OLEGSHA Date: Wed, 26 Aug 2020 18:34:52 +0300 Subject: [PATCH] Added some packages from JPUtil (https://github.com/OLEGSHA/JPUtil) - ru.windcorp.common.util.ThrowingRunnable replaced with ru.windcorp.jputil.functions.ThrowingRunnable --- .../java/ru/windcorp/jputil/ArrayUtil.java | 525 +++++++ .../java/ru/windcorp/jputil/CSVWriter.java | 104 ++ .../ru/windcorp/jputil/PrimitiveUtil.java | 60 + .../java/ru/windcorp/jputil/SyncStreams.java | 1210 +++++++++++++++++ .../ru/windcorp/jputil/SyntaxException.java | 44 + .../jputil/chars/CharArrayIterator.java | 128 ++ .../windcorp/jputil/chars/CharConsumer.java | 42 + .../windcorp/jputil/chars/CharConsumers.java | 69 + .../windcorp/jputil/chars/CharPredicate.java | 84 ++ .../windcorp/jputil/chars/CharSupplier.java | 35 + .../jputil/chars/EscapeException.java | 44 + .../ru/windcorp/jputil/chars/Escaper.java | 474 +++++++ .../jputil/chars/FancyCharacterIterator.java | 102 ++ .../jputil/chars/IndentedStringBuilder.java | 135 ++ .../ru/windcorp/jputil/chars/StringUtil.java | 778 +++++++++++ .../chars/UncheckedEscapeException.java | 37 + .../ru/windcorp/jputil/chars/WordReader.java | 139 ++ .../chars/reader/AbstractCharReader.java | 104 ++ .../jputil/chars/reader/ArrayCharReader.java | 59 + .../chars/reader/BufferedCharReader.java | 122 ++ .../jputil/chars/reader/CharReader.java | 270 ++++ .../jputil/chars/reader/CharReaders.java | 112 ++ .../jputil/chars/reader/ReaderCharReader.java | 70 + .../jputil/chars/reader/StringCharReader.java | 65 + .../jputil/functions/ThrowingBiConsumer.java | 72 + .../jputil/functions/ThrowingConsumer.java | 62 + .../jputil/functions/ThrowingFunction.java | 73 + .../jputil/functions/ThrowingRunnable.java | 64 + .../functions/ThrowingSupplier.java} | 71 +- .../jputil/iterators/ArrayIterator.java | 47 + .../jputil/iterators/FunctionIterator.java | 61 + .../jputil/iterators/PeekingIterator.java | 60 + .../jputil/iterators/RangeIterator.java | 67 + .../windcorp/jputil/iterators/Reiterator.java | 70 + .../selectors/AbstractSelectorOperator.java | 39 + .../selectors/NamedParameterizedSelector.java | 57 + .../jputil/selectors/NamedSelector.java | 50 + .../jputil/selectors/OperatorAnd.java | 36 + .../jputil/selectors/OperatorExclude.java | 36 + .../jputil/selectors/OperatorNot.java | 34 + .../windcorp/jputil/selectors/OperatorOr.java | 36 + .../jputil/selectors/OperatorXor.java | 36 + .../jputil/selectors/PredicateWrapper.java | 36 + .../windcorp/jputil/selectors/Selector.java | 28 + .../jputil/selectors/SelectorOperator.java | 29 + .../jputil/selectors/SelectorSystem.java | 176 +++ .../graphics/backend/RenderTaskQueue.java | 10 +- 47 files changed, 6015 insertions(+), 47 deletions(-) create mode 100644 src/main/java/ru/windcorp/jputil/ArrayUtil.java create mode 100644 src/main/java/ru/windcorp/jputil/CSVWriter.java create mode 100644 src/main/java/ru/windcorp/jputil/PrimitiveUtil.java create mode 100644 src/main/java/ru/windcorp/jputil/SyncStreams.java create mode 100644 src/main/java/ru/windcorp/jputil/SyntaxException.java create mode 100644 src/main/java/ru/windcorp/jputil/chars/CharArrayIterator.java create mode 100644 src/main/java/ru/windcorp/jputil/chars/CharConsumer.java create mode 100644 src/main/java/ru/windcorp/jputil/chars/CharConsumers.java create mode 100644 src/main/java/ru/windcorp/jputil/chars/CharPredicate.java create mode 100644 src/main/java/ru/windcorp/jputil/chars/CharSupplier.java create mode 100644 src/main/java/ru/windcorp/jputil/chars/EscapeException.java create mode 100644 src/main/java/ru/windcorp/jputil/chars/Escaper.java create mode 100644 src/main/java/ru/windcorp/jputil/chars/FancyCharacterIterator.java create mode 100644 src/main/java/ru/windcorp/jputil/chars/IndentedStringBuilder.java create mode 100644 src/main/java/ru/windcorp/jputil/chars/StringUtil.java create mode 100644 src/main/java/ru/windcorp/jputil/chars/UncheckedEscapeException.java create mode 100644 src/main/java/ru/windcorp/jputil/chars/WordReader.java create mode 100644 src/main/java/ru/windcorp/jputil/chars/reader/AbstractCharReader.java create mode 100644 src/main/java/ru/windcorp/jputil/chars/reader/ArrayCharReader.java create mode 100644 src/main/java/ru/windcorp/jputil/chars/reader/BufferedCharReader.java create mode 100644 src/main/java/ru/windcorp/jputil/chars/reader/CharReader.java create mode 100644 src/main/java/ru/windcorp/jputil/chars/reader/CharReaders.java create mode 100644 src/main/java/ru/windcorp/jputil/chars/reader/ReaderCharReader.java create mode 100644 src/main/java/ru/windcorp/jputil/chars/reader/StringCharReader.java create mode 100644 src/main/java/ru/windcorp/jputil/functions/ThrowingBiConsumer.java create mode 100644 src/main/java/ru/windcorp/jputil/functions/ThrowingConsumer.java create mode 100644 src/main/java/ru/windcorp/jputil/functions/ThrowingFunction.java create mode 100644 src/main/java/ru/windcorp/jputil/functions/ThrowingRunnable.java rename src/main/java/ru/windcorp/{progressia/common/util/ThrowingRunnable.java => jputil/functions/ThrowingSupplier.java} (51%) create mode 100644 src/main/java/ru/windcorp/jputil/iterators/ArrayIterator.java create mode 100644 src/main/java/ru/windcorp/jputil/iterators/FunctionIterator.java create mode 100644 src/main/java/ru/windcorp/jputil/iterators/PeekingIterator.java create mode 100644 src/main/java/ru/windcorp/jputil/iterators/RangeIterator.java create mode 100644 src/main/java/ru/windcorp/jputil/iterators/Reiterator.java create mode 100644 src/main/java/ru/windcorp/jputil/selectors/AbstractSelectorOperator.java create mode 100644 src/main/java/ru/windcorp/jputil/selectors/NamedParameterizedSelector.java create mode 100644 src/main/java/ru/windcorp/jputil/selectors/NamedSelector.java create mode 100644 src/main/java/ru/windcorp/jputil/selectors/OperatorAnd.java create mode 100644 src/main/java/ru/windcorp/jputil/selectors/OperatorExclude.java create mode 100644 src/main/java/ru/windcorp/jputil/selectors/OperatorNot.java create mode 100644 src/main/java/ru/windcorp/jputil/selectors/OperatorOr.java create mode 100644 src/main/java/ru/windcorp/jputil/selectors/OperatorXor.java create mode 100644 src/main/java/ru/windcorp/jputil/selectors/PredicateWrapper.java create mode 100644 src/main/java/ru/windcorp/jputil/selectors/Selector.java create mode 100644 src/main/java/ru/windcorp/jputil/selectors/SelectorOperator.java create mode 100644 src/main/java/ru/windcorp/jputil/selectors/SelectorSystem.java diff --git a/src/main/java/ru/windcorp/jputil/ArrayUtil.java b/src/main/java/ru/windcorp/jputil/ArrayUtil.java new file mode 100644 index 0000000..4c849b7 --- /dev/null +++ b/src/main/java/ru/windcorp/jputil/ArrayUtil.java @@ -0,0 +1,525 @@ +/******************************************************************************* + * 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; + +import java.lang.reflect.Array; +import java.math.BigInteger; +import java.util.Objects; + +public class ArrayUtil { + + private ArrayUtil() {} + + public static int firstIndexOf(byte[] array, byte element) { + for (int i = 0; i < array.length; ++i) { + if (array[i] == element) { + return i; + } + } + return -1; + } + + public static int lastIndexOf(byte[] array, byte element) { + for (int i = array.length - 1; i >= 0; --i) { + if (array[i] == element) { + return i; + } + } + return -1; + } + + public static int occurences(byte[] array, byte element) { + int result = 0; + for (int i = 0; i < array.length; ++i) { + if (array[i] == element) { + ++result; + } + } + return result; + } + + public static int hasDuplicates(byte[] array) { + for (int i = 0; i < array.length; ++i) { + byte a = array[i]; + for (int j = i + 1; j < array.length; ++j) { + if (array[j] == a) { + return i; + } + } + } + + return -1; + } + + public static int firstIndexOf(short[] array, short element) { + for (int i = 0; i < array.length; ++i) { + if (array[i] == element) { + return i; + } + } + return -1; + } + + public static int lastIndexOf(short[] array, short element) { + for (int i = array.length - 1; i >= 0; --i) { + if (array[i] == element) { + return i; + } + } + return -1; + } + + public static int occurences(short[] array, short element) { + int result = 0; + for (int i = 0; i < array.length; ++i) { + if (array[i] == element) { + ++result; + } + } + return result; + } + + public static int hasDuplicates(short[] array) { + for (int i = 0; i < array.length; ++i) { + short a = array[i]; + for (int j = i + 1; j < array.length; ++j) { + if (array[j] == a) { + return i; + } + } + } + + return -1; + } + + public static int firstIndexOf(int[] array, int element) { + for (int i = 0; i < array.length; ++i) { + if (array[i] == element) { + return i; + } + } + return -1; + } + + public static int lastIndexOf(int[] array, int element) { + for (int i = array.length - 1; i >= 0; --i) { + if (array[i] == element) { + return i; + } + } + return -1; + } + + public static int occurences(int[] array, int element) { + int result = 0; + for (int i = 0; i < array.length; ++i) { + if (array[i] == element) { + ++result; + } + } + return result; + } + + public static int hasDuplicates(int[] array) { + for (int i = 0; i < array.length; ++i) { + int a = array[i]; + for (int j = i + 1; j < array.length; ++j) { + if (array[j] == a) { + return i; + } + } + } + + return -1; + } + + public static int firstIndexOf(long[] array, long element) { + for (int i = 0; i < array.length; ++i) { + if (array[i] == element) { + return i; + } + } + return -1; + } + + public static int lastIndexOf(long[] array, long element) { + for (int i = array.length - 1; i >= 0; --i) { + if (array[i] == element) { + return i; + } + } + return -1; + } + + public static int occurences(long[] array, long element) { + int result = 0; + for (int i = 0; i < array.length; ++i) { + if (array[i] == element) { + ++result; + } + } + return result; + } + + public static int hasDuplicates(long[] array) { + for (int i = 0; i < array.length; ++i) { + long a = array[i]; + for (int j = i + 1; j < array.length; ++j) { + if (array[j] == a) { + return i; + } + } + } + + return -1; + } + + public static int firstIndexOf(float[] array, float element) { + for (int i = 0; i < array.length; ++i) { + if (array[i] == element) { + return i; + } + } + return -1; + } + + public static int lastIndexOf(float[] array, float element) { + for (int i = array.length - 1; i >= 0; --i) { + if (array[i] == element) { + return i; + } + } + return -1; + } + + public static int occurences(float[] array, float element) { + int result = 0; + for (int i = 0; i < array.length; ++i) { + if (array[i] == element) { + ++result; + } + } + return result; + } + + public static int hasDuplicates(float[] array) { + for (int i = 0; i < array.length; ++i) { + float a = array[i]; + for (int j = i + 1; j < array.length; ++j) { + if (array[j] == a) { + return i; + } + } + } + + return -1; + } + + public static int firstIndexOf(double[] array, double element) { + for (int i = 0; i < array.length; ++i) { + if (array[i] == element) { + return i; + } + } + return -1; + } + + public static int lastIndexOf(double[] array, double element) { + for (int i = array.length - 1; i >= 0; --i) { + if (array[i] == element) { + return i; + } + } + return -1; + } + + public static int occurences(double[] array, double element) { + int result = 0; + for (int i = 0; i < array.length; ++i) { + if (array[i] == element) { + ++result; + } + } + return result; + } + + public static int hasDuplicates(double[] array) { + for (int i = 0; i < array.length; ++i) { + double a = array[i]; + for (int j = i + 1; j < array.length; ++j) { + if (array[j] == a) { + return i; + } + } + } + + return -1; + } + + public static int firstIndexOf(boolean[] array, boolean element) { + for (int i = 0; i < array.length; ++i) { + if (array[i] == element) { + return i; + } + } + return -1; + } + + public static int lastIndexOf(boolean[] array, boolean element) { + for (int i = array.length - 1; i >= 0; --i) { + if (array[i] == element) { + return i; + } + } + return -1; + } + + public static int occurences(boolean[] array, boolean element) { + int result = 0; + for (int i = 0; i < array.length; ++i) { + if (array[i] == element) { + ++result; + } + } + return result; + } + + public static int firstIndexOf(char[] array, char element) { + for (int i = 0; i < array.length; ++i) { + if (array[i] == element) { + return i; + } + } + return -1; + } + + public static int lastIndexOf(char[] array, char element) { + for (int i = array.length - 1; i >= 0; --i) { + if (array[i] == element) { + return i; + } + } + return -1; + } + + public static int occurences(char[] array, char element) { + int result = 0; + for (int i = 0; i < array.length; ++i) { + if (array[i] == element) { + ++result; + } + } + return result; + } + + public static int hasDuplicates(char[] array) { + for (int i = 0; i < array.length; ++i) { + char a = array[i]; + for (int j = i + 1; j < array.length; ++j) { + if (array[j] == a) { + return i; + } + } + } + + return -1; + } + + public static int firstIndexOf(Object[] array, Object element) { + for (int i = 0; i < array.length; ++i) { + if (array[i] == element) { + return i; + } + } + return -1; + } + + public static int lastIndexOf(Object[] array, Object element) { + for (int i = array.length - 1; i >= 0; --i) { + if (array[i] == element) { + return i; + } + } + return -1; + } + + public static int occurences(Object[] array, Object element) { + int result = 0; + for (int i = 0; i < array.length; ++i) { + if (array[i] == element) { + ++result; + } + } + return result; + } + + public static int hasDuplicates(Object[] array) { + for (int i = 0; i < array.length; ++i) { + Object a = array[i]; + for (int j = i + 1; j < array.length; ++j) { + if (array[j] == a) { + return i; + } + } + } + + return -1; + } + + public static int firstIndexOfEqual(Object[] array, Object element) { + for (int i = 0; i < array.length; ++i) { + if (Objects.equals(array[i], element)) { + return i; + } + } + return -1; + } + + public static int lastIndexOfEqual(Object[] array, Object element) { + for (int i = array.length - 1; i >= 0; --i) { + if (Objects.equals(array[i], element)) { + return i; + } + } + return -1; + } + + public static int occurencesOfEqual(Object[] array, Object element) { + int result = 0; + for (int i = 0; i < array.length; ++i) { + if (Objects.equals(array[i], element)) { + ++result; + } + } + return result; + } + + public static int hasEquals(Object[] array) { + for (int i = 0; i < array.length; ++i) { + Object a = array[i]; + for (int j = i + 1; j < array.length; ++j) { + if (Objects.equals(array[j], a)) { + return i; + } + } + } + + return -1; + } + + public static long sum(byte[] array, int start, int length) { + long s = 0; + length += start; + for (int i = start; i < length; ++i) { + s += array[i]; + } + return s; + } + + public static long sum(short[] array, int start, int length) { + long s = 0; + length += start; + for (int i = start; i < length; ++i) { + s += array[i]; + } + return s; + } + + public static long sum(int[] array, int start, int length) { + long s = 0; + length += start; + for (int i = start; i < length; ++i) { + s += array[i]; + } + return s; + } + + public static long sum(long[] array, int start, int length) { + long s = 0; + length += start; + for (int i = start; i < length; ++i) { + s += array[i]; + } + return s; + } + + public static BigInteger longSum(long[] array, int start, int length) { + BigInteger s = BigInteger.ZERO; + length += start; + for (int i = start; i < length; ++i) { + s = s.add(BigInteger.valueOf(array[i])); + } + return s; + } + + public static float sum(float[] array, int start, int length) { + float s = 0; + length += start; + for (int i = start; i < length; ++i) { + s += array[i]; + } + return s; + } + + public static double sum(double[] array, int start, int length) { + double s = 0; + length += start; + for (int i = start; i < length; ++i) { + s += array[i]; + } + return s; + } + + public static long sum(char[] array, int start, int length) { + long s = 0; + length += start; + for (int i = start; i < length; ++i) { + s += array[i]; + } + return s; + } + + public static int checkArrayOffsetLength(Object array, int offset, int length) { + int arrayLength = Array.getLength(array); + + if (length < 0) + length = arrayLength; + + int end = offset + length; + if (end > arrayLength || offset < 0) + throw new IllegalArgumentException("Array contains [0; " + arrayLength + "), requested [" + offset + "; " + end + ")"); + + return length; + } + + public static int checkArrayStartEnd(Object array, int start, int end) { + int arrayLength = Array.getLength(array); + + if (end < 0) + end = arrayLength; + + if (start > end) + throw new IllegalArgumentException("Start > end: " + start + " > " + end); + + if (end > arrayLength || start < 0) + throw new IllegalArgumentException("Array contains [0; " + arrayLength + "), requested [" + start + "; " + end + ")"); + + return end; + } + +} diff --git a/src/main/java/ru/windcorp/jputil/CSVWriter.java b/src/main/java/ru/windcorp/jputil/CSVWriter.java new file mode 100644 index 0000000..585869e --- /dev/null +++ b/src/main/java/ru/windcorp/jputil/CSVWriter.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; + +import java.io.OutputStream; +import java.io.PrintWriter; +import java.io.Writer; + +public class CSVWriter { + + private String columnSeparator = ";"; + private String rowSeparator = "\n"; + + private boolean shouldAddSeparator = false; + + private final PrintWriter parent; + + public CSVWriter(PrintWriter output) { + this.parent = output; + } + + public CSVWriter(Writer output) { + this(new PrintWriter(output)); + } + + public CSVWriter(OutputStream output) { + this(new PrintWriter(output)); + } + + public PrintWriter getParent() { + return parent; + } + + public String getColumnSeparator() { + return columnSeparator; + } + + public CSVWriter setColumnSeparator(String columnSeparator) { + this.columnSeparator = columnSeparator; + return this; + } + + public String getRowSeparator() { + return rowSeparator; + } + + public CSVWriter setRowSeparator(String rowSeparator) { + this.rowSeparator = rowSeparator; + return this; + } + + public void print(Object object) { + skip(); + getParent().print(String.valueOf(object)); + } + + public void skip() { + if (shouldAddSeparator) { + getParent().print(getColumnSeparator()); + } else { + shouldAddSeparator = true; + } + } + + public void skip(int amount) { + for (int i = 0; i < amount; ++i) { + skip(); + } + } + + public void endRow() { + getParent().print(getRowSeparator()); + shouldAddSeparator = false; + } + + public void endRow(Object object) { + print(object); + endRow(); + } + + public void flush() { + getParent().flush(); + } + + public void close() { + getParent().close(); + } + +} diff --git a/src/main/java/ru/windcorp/jputil/PrimitiveUtil.java b/src/main/java/ru/windcorp/jputil/PrimitiveUtil.java new file mode 100644 index 0000000..8da189a --- /dev/null +++ b/src/main/java/ru/windcorp/jputil/PrimitiveUtil.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; + +import java.util.HashMap; +import java.util.Map; + +public class PrimitiveUtil { + + private PrimitiveUtil() {} + + private static final Map, Class> PRIMITIVE_TO_BOXED = new HashMap<>(); + private static final Map, Object> PRIMITIVE_TO_NULL = new HashMap<>(); + + static { + for (Class boxed : new Class[] { + Boolean.class, Byte.class, Short.class, Character.class, + Integer.class, Long.class, Float.class, Double.class + }) { + try { + PRIMITIVE_TO_BOXED.put((Class) boxed.getField("TYPE").get(null), boxed); + } catch (Exception e) { + e.printStackTrace(); + } + } + + PRIMITIVE_TO_NULL.put(Boolean.TYPE, Boolean.FALSE); + PRIMITIVE_TO_NULL.put(Byte.TYPE, Byte.valueOf((byte) 0)); + PRIMITIVE_TO_NULL.put(Short.TYPE, Short.valueOf((short) 0)); + PRIMITIVE_TO_NULL.put(Integer.TYPE, Integer.valueOf(0)); + PRIMITIVE_TO_NULL.put(Long.TYPE, Long.valueOf(0)); + PRIMITIVE_TO_NULL.put(Float.TYPE, Float.valueOf(Float.NaN)); + PRIMITIVE_TO_NULL.put(Double.TYPE, Double.valueOf(Double.NaN)); + PRIMITIVE_TO_NULL.put(Character.TYPE, Character.valueOf('\u0000')); + } + + public static Class getBoxedClass(Class primitiveClass) { + return PRIMITIVE_TO_BOXED.getOrDefault(primitiveClass, primitiveClass); + } + + public static Object getPrimitiveNull(Class primitiveClass) { + return PRIMITIVE_TO_NULL.get(primitiveClass); + } + +} diff --git a/src/main/java/ru/windcorp/jputil/SyncStreams.java b/src/main/java/ru/windcorp/jputil/SyncStreams.java new file mode 100644 index 0000000..e75e9a2 --- /dev/null +++ b/src/main/java/ru/windcorp/jputil/SyncStreams.java @@ -0,0 +1,1210 @@ +/* + * 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; + +import java.util.function.*; + +import java.util.Comparator; +import java.util.DoubleSummaryStatistics; +import java.util.IntSummaryStatistics; +import java.util.Iterator; +import java.util.LongSummaryStatistics; +import java.util.Objects; +import java.util.Optional; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.OptionalLong; +import java.util.PrimitiveIterator; +import java.util.Spliterator; +import java.util.stream.Collector; +import java.util.stream.DoubleStream; +import java.util.stream.IntStream; +import java.util.stream.LongStream; +import java.util.stream.Stream; + +/** + * Contains static methods to create {@link Stream Streams} that synchronize their + * + * terminal operations on a given monitor. + * @author Javapony (kvadropups@gmail.com) + */ + +// SonarLint: "Stream.peek" should be used with caution (java:S3864) +// We are implementing Stream, so peek() is required. +@SuppressWarnings("squid:S3864") + +public class SyncStreams { + + public static class SyncStream implements Stream { + + private final Stream parent; + private final Object monitor; + + public SyncStream(Stream parent, Object monitor) { + this.parent = parent; + this.monitor = monitor == null ? this : monitor; + } + + public Stream getParent() { + return parent; + } + + public Object getMonitor() { + return monitor; + } + + /* + * Returns null when child streams should sync on themselves + */ + private Object getInheritableMonitor() { + return monitor == this ? null : monitor; + } + + @Override + public void close() { + parent.close(); + } + + @Override + public Iterator iterator() { + return parent.iterator(); + } + + @Override + public Spliterator spliterator() { + return parent.spliterator(); + } + + @Override + public boolean isParallel() { + return parent.isParallel(); + } + + @Override + public Stream sequential() { + return synchronizedStream(parent.sequential(), getInheritableMonitor()); + } + + @Override + public Stream parallel() { + return synchronizedStream(parent.parallel(), getInheritableMonitor()); + } + + @Override + public Stream unordered() { + return synchronizedStream(parent.unordered(), getInheritableMonitor()); + } + + @Override + public Stream onClose(Runnable closeHandler) { + return synchronizedStream(parent.onClose(closeHandler), getInheritableMonitor()); + } + + @Override + public Stream filter(Predicate predicate) { + return synchronizedStream(parent.filter(predicate), getInheritableMonitor()); + } + + @Override + public Stream map(Function mapper) { + return synchronizedStream(parent.map(mapper), getInheritableMonitor()); + } + + @Override + public IntStream mapToInt(ToIntFunction mapper) { + return synchronizedStream(parent.mapToInt(mapper), getInheritableMonitor()); + } + + @Override + public LongStream mapToLong(ToLongFunction mapper) { + return synchronizedStream(parent.mapToLong(mapper), getInheritableMonitor()); + } + + @Override + public DoubleStream mapToDouble(ToDoubleFunction mapper) { + return synchronizedStream(parent.mapToDouble(mapper), getInheritableMonitor()); + } + + @Override + public Stream flatMap(Function> mapper) { + return synchronizedStream(parent.flatMap(mapper), getInheritableMonitor()); + } + + @Override + public IntStream flatMapToInt(Function mapper) { + return synchronizedStream(parent.flatMapToInt(mapper), getInheritableMonitor()); + } + + @Override + public LongStream flatMapToLong(Function mapper) { + return synchronizedStream(parent.flatMapToLong(mapper), getInheritableMonitor()); + } + + @Override + public DoubleStream flatMapToDouble(Function mapper) { + return synchronizedStream(parent.flatMapToDouble(mapper), getInheritableMonitor()); + } + + @Override + public Stream distinct() { + return synchronizedStream(parent.distinct(), getInheritableMonitor()); + } + + @Override + public Stream sorted() { + return synchronizedStream(parent.sorted(), getInheritableMonitor()); + } + + @Override + public Stream sorted(Comparator comparator) { + return synchronizedStream(parent.sorted(comparator), getInheritableMonitor()); + } + + @Override + public Stream peek(Consumer action) { + return synchronizedStream(parent.peek(action), getInheritableMonitor()); + } + + @Override + public Stream limit(long maxSize) { + return synchronizedStream(parent.limit(maxSize), getInheritableMonitor()); + } + + @Override + public Stream skip(long n) { + return synchronizedStream(parent.skip(n), getInheritableMonitor()); + } + + @Override + public T reduce(T identity, BinaryOperator accumulator) { + synchronized (monitor) { + return parent.reduce(identity, accumulator); + } + } + + @Override + public Optional reduce(BinaryOperator accumulator) { + synchronized (monitor) { + return parent.reduce(accumulator); + } + } + + @Override + public U reduce(U identity, BiFunction accumulator, BinaryOperator combiner) { + synchronized (monitor) { + return parent.reduce(identity, accumulator, combiner); + } + } + + @Override + public void forEach(Consumer action) { + synchronized (monitor) { + parent.forEach(action); + } + } + + @Override + public void forEachOrdered(Consumer action) { + synchronized (monitor) { + parent.forEachOrdered(action); + } + } + + @Override + public Object[] toArray() { + synchronized (monitor) { + return parent.toArray(); + } + } + + @Override + public A[] toArray(IntFunction generator) { + synchronized (monitor) { + return parent.toArray(generator); + } + } + + @Override + public R collect(Supplier supplier, BiConsumer accumulator, BiConsumer combiner) { + synchronized (monitor) { + return parent.collect(supplier, accumulator, combiner); + } + } + + @Override + public R collect(Collector collector) { + synchronized (monitor) { + return parent.collect(collector); + } + } + + @Override + public Optional min(Comparator comparator) { + synchronized (monitor) { + return parent.min(comparator); + } + } + + @Override + public Optional max(Comparator comparator) { + synchronized (monitor) { + return parent.max(comparator); + } + } + + @Override + public long count() { + synchronized (monitor) { + return parent.count(); + } + } + + @Override + public boolean anyMatch(Predicate predicate) { + synchronized (monitor) { + return parent.anyMatch(predicate); + } + } + + @Override + public boolean allMatch(Predicate predicate) { + synchronized (monitor) { + return parent.allMatch(predicate); + } + } + + @Override + public boolean noneMatch(Predicate predicate) { + synchronized (monitor) { + return parent.noneMatch(predicate); + } + } + + @Override + public Optional findFirst() { + synchronized (monitor) { + return parent.findFirst(); + } + } + + @Override + public Optional findAny() { + synchronized (monitor) { + return parent.findAny(); + } + } + + } + + public static class SyncIntStream implements IntStream { + + private final IntStream parent; + private final Object monitor; + + public SyncIntStream(IntStream parent, Object monitor) { + this.parent = parent; + this.monitor = monitor == null ? this : monitor; + } + + public IntStream getParent() { + return parent; + } + + public Object getMonitor() { + return monitor; + } + + /* + * Returns null when child streams should sync on themselves + */ + private Object getInheritableMonitor() { + return monitor == this ? null : monitor; + } + + @Override + public void close() { + parent.close(); + } + + @Override + public PrimitiveIterator.OfInt iterator() { + return parent.iterator(); + } + + @Override + public Spliterator.OfInt spliterator() { + return parent.spliterator(); + } + + @Override + public boolean isParallel() { + return parent.isParallel(); + } + + @Override + public IntStream sequential() { + return synchronizedStream(parent.sequential(), getInheritableMonitor()); + } + + @Override + public IntStream parallel() { + return synchronizedStream(parent.parallel(), getInheritableMonitor()); + } + + @Override + public IntStream unordered() { + return synchronizedStream(parent.unordered(), getInheritableMonitor()); + } + + @Override + public IntStream onClose(Runnable closeHandler) { + return synchronizedStream(parent.onClose(closeHandler), getInheritableMonitor()); + } + + @Override + public IntStream filter(IntPredicate predicate) { + return synchronizedStream(parent.filter(predicate), getInheritableMonitor()); + } + + @Override + public IntStream map(IntUnaryOperator mapper) { + return synchronizedStream(parent.map(mapper), getInheritableMonitor()); + } + + @Override + public LongStream mapToLong(IntToLongFunction mapper) { + return synchronizedStream(parent.mapToLong(mapper), getInheritableMonitor()); + } + + @Override + public DoubleStream mapToDouble(IntToDoubleFunction mapper) { + return synchronizedStream(parent.mapToDouble(mapper), getInheritableMonitor()); + } + + @Override + public Stream mapToObj(IntFunction mapper) { + return synchronizedStream(parent.mapToObj(mapper), getInheritableMonitor()); + } + + @Override + public IntStream flatMap(IntFunction mapper) { + return synchronizedStream(parent.flatMap(mapper), getInheritableMonitor()); + } + + @Override + public IntStream distinct() { + return synchronizedStream(parent.distinct(), getInheritableMonitor()); + } + + @Override + public IntStream sorted() { + return synchronizedStream(parent.sorted(), getInheritableMonitor()); + } + + @Override + public IntStream peek(IntConsumer action) { + return synchronizedStream(parent.peek(action), getInheritableMonitor()); + } + + @Override + public IntStream limit(long maxSize) { + return synchronizedStream(parent.limit(maxSize), getInheritableMonitor()); + } + + @Override + public IntStream skip(long n) { + return synchronizedStream(parent.skip(n), getInheritableMonitor()); + } + + @Override + public LongStream asLongStream() { + return synchronizedStream(parent.asLongStream(), getInheritableMonitor()); + } + + @Override + public DoubleStream asDoubleStream() { + return synchronizedStream(parent.asDoubleStream(), getInheritableMonitor()); + } + + @Override + public Stream boxed() { + return synchronizedStream(parent.boxed(), getInheritableMonitor()); + } + + @Override + public int reduce(int identity, IntBinaryOperator accumulator) { + synchronized (monitor) { + return parent.reduce(identity, accumulator); + } + } + + @Override + public OptionalInt reduce(IntBinaryOperator accumulator) { + synchronized (monitor) { + return parent.reduce(accumulator); + } + } + + @Override + public void forEach(IntConsumer action) { + synchronized (monitor) { + parent.forEach(action); + } + } + + @Override + public void forEachOrdered(IntConsumer action) { + synchronized (monitor) { + parent.forEachOrdered(action); + } + } + + @Override + public int[] toArray() { + synchronized (monitor) { + return parent.toArray(); + } + } + + @Override + public R collect(Supplier supplier, ObjIntConsumer accumulator, BiConsumer combiner) { + synchronized (monitor) { + return parent.collect(supplier, accumulator, combiner); + } + } + + @Override + public int sum() { + synchronized (monitor) { + return parent.sum(); + } + } + + @Override + public OptionalInt min() { + synchronized (monitor) { + return parent.min(); + } + } + + @Override + public OptionalInt max() { + synchronized (monitor) { + return parent.max(); + } + } + + @Override + public long count() { + synchronized (monitor) { + return parent.count(); + } + } + + @Override + public OptionalDouble average() { + synchronized (monitor) { + return parent.average(); + } + } + + @Override + public IntSummaryStatistics summaryStatistics() { + synchronized (monitor) { + return parent.summaryStatistics(); + } + } + + @Override + public boolean anyMatch(IntPredicate predicate) { + synchronized (monitor) { + return parent.anyMatch(predicate); + } + } + + @Override + public boolean allMatch(IntPredicate predicate) { + synchronized (monitor) { + return parent.allMatch(predicate); + } + } + + @Override + public boolean noneMatch(IntPredicate predicate) { + synchronized (monitor) { + return parent.noneMatch(predicate); + } + } + + @Override + public OptionalInt findFirst() { + synchronized (monitor) { + return parent.findFirst(); + } + } + + @Override + public OptionalInt findAny() { + synchronized (monitor) { + return parent.findAny(); + } + } + + } + + public static class SyncLongStream implements LongStream { + + private final LongStream parent; + private final Object monitor; + + public SyncLongStream(LongStream parent, Object monitor) { + this.parent = parent; + this.monitor = monitor == null ? this : monitor; + } + + public LongStream getParent() { + return parent; + } + + public Object getMonitor() { + return monitor; + } + + /* + * Returns null when child streams should sync on themselves + */ + private Object getInheritableMonitor() { + return monitor == this ? null : monitor; + } + + @Override + public void close() { + parent.close(); + } + + @Override + public PrimitiveIterator.OfLong iterator() { + return parent.iterator(); + } + + @Override + public Spliterator.OfLong spliterator() { + return parent.spliterator(); + } + + @Override + public boolean isParallel() { + return parent.isParallel(); + } + + @Override + public LongStream sequential() { + return synchronizedStream(parent.sequential(), getInheritableMonitor()); + } + + @Override + public LongStream parallel() { + return synchronizedStream(parent.parallel(), getInheritableMonitor()); + } + + @Override + public LongStream unordered() { + return synchronizedStream(parent.unordered(), getInheritableMonitor()); + } + + @Override + public LongStream onClose(Runnable closeHandler) { + return synchronizedStream(parent.onClose(closeHandler), getInheritableMonitor()); + } + + @Override + public LongStream filter(LongPredicate predicate) { + return synchronizedStream(parent.filter(predicate), getInheritableMonitor()); + } + + @Override + public LongStream map(LongUnaryOperator mapper) { + return synchronizedStream(parent.map(mapper), getInheritableMonitor()); + } + + @Override + public IntStream mapToInt(LongToIntFunction mapper) { + return synchronizedStream(parent.mapToInt(mapper), getInheritableMonitor()); + } + + @Override + public DoubleStream mapToDouble(LongToDoubleFunction mapper) { + return synchronizedStream(parent.mapToDouble(mapper), getInheritableMonitor()); + } + + @Override + public Stream mapToObj(LongFunction mapper) { + return synchronizedStream(parent.mapToObj(mapper), getInheritableMonitor()); + } + + @Override + public LongStream flatMap(LongFunction mapper) { + return synchronizedStream(parent.flatMap(mapper), getInheritableMonitor()); + } + + @Override + public LongStream distinct() { + return synchronizedStream(parent.distinct(), getInheritableMonitor()); + } + + @Override + public LongStream sorted() { + return synchronizedStream(parent.sorted(), getInheritableMonitor()); + } + + @Override + public LongStream peek(LongConsumer action) { + return synchronizedStream(parent.peek(action), getInheritableMonitor()); + } + + @Override + public LongStream limit(long maxSize) { + return synchronizedStream(parent.limit(maxSize), getInheritableMonitor()); + } + + @Override + public LongStream skip(long n) { + return synchronizedStream(parent.skip(n), getInheritableMonitor()); + } + + @Override + public DoubleStream asDoubleStream() { + return synchronizedStream(parent.asDoubleStream(), getInheritableMonitor()); + } + + @Override + public Stream boxed() { + return synchronizedStream(parent.boxed(), getInheritableMonitor()); + } + + @Override + public long reduce(long identity, LongBinaryOperator accumulator) { + synchronized (monitor) { + return parent.reduce(identity, accumulator); + } + } + + @Override + public OptionalLong reduce(LongBinaryOperator accumulator) { + synchronized (monitor) { + return parent.reduce(accumulator); + } + } + + @Override + public void forEach(LongConsumer action) { + synchronized (monitor) { + parent.forEach(action); + } + } + + @Override + public void forEachOrdered(LongConsumer action) { + synchronized (monitor) { + parent.forEachOrdered(action); + } + } + + @Override + public long[] toArray() { + synchronized (monitor) { + return parent.toArray(); + } + } + + @Override + public R collect(Supplier supplier, ObjLongConsumer accumulator, BiConsumer combiner) { + synchronized (monitor) { + return parent.collect(supplier, accumulator, combiner); + } + } + + @Override + public long sum() { + synchronized (monitor) { + return parent.sum(); + } + } + + @Override + public OptionalLong min() { + synchronized (monitor) { + return parent.min(); + } + } + + @Override + public OptionalLong max() { + synchronized (monitor) { + return parent.max(); + } + } + + @Override + public long count() { + synchronized (monitor) { + return parent.count(); + } + } + + @Override + public OptionalDouble average() { + synchronized (monitor) { + return parent.average(); + } + } + + @Override + public LongSummaryStatistics summaryStatistics() { + synchronized (monitor) { + return parent.summaryStatistics(); + } + } + + @Override + public boolean anyMatch(LongPredicate predicate) { + synchronized (monitor) { + return parent.anyMatch(predicate); + } + } + + @Override + public boolean allMatch(LongPredicate predicate) { + synchronized (monitor) { + return parent.allMatch(predicate); + } + } + + @Override + public boolean noneMatch(LongPredicate predicate) { + synchronized (monitor) { + return parent.noneMatch(predicate); + } + } + + @Override + public OptionalLong findFirst() { + synchronized (monitor) { + return parent.findFirst(); + } + } + + @Override + public OptionalLong findAny() { + synchronized (monitor) { + return parent.findAny(); + } + } + + } + + public static class SyncDoubleStream implements DoubleStream { + + private final DoubleStream parent; + private final Object monitor; + + public SyncDoubleStream(DoubleStream parent, Object monitor) { + this.parent = parent; + this.monitor = monitor == null ? this : monitor; + } + + public DoubleStream getParent() { + return parent; + } + + public Object getMonitor() { + return monitor; + } + + /* + * Returns null when child streams should sync on themselves + */ + private Object getInheritableMonitor() { + return monitor == this ? null : monitor; + } + + @Override + public void close() { + parent.close(); + } + + @Override + public PrimitiveIterator.OfDouble iterator() { + return parent.iterator(); + } + + @Override + public Spliterator.OfDouble spliterator() { + return parent.spliterator(); + } + + @Override + public boolean isParallel() { + return parent.isParallel(); + } + + @Override + public DoubleStream sequential() { + return synchronizedStream(parent.sequential(), getInheritableMonitor()); + } + + @Override + public DoubleStream parallel() { + return synchronizedStream(parent.parallel(), getInheritableMonitor()); + } + + @Override + public DoubleStream unordered() { + return synchronizedStream(parent.unordered(), getInheritableMonitor()); + } + + @Override + public DoubleStream onClose(Runnable closeHandler) { + return synchronizedStream(parent.onClose(closeHandler), getInheritableMonitor()); + } + + @Override + public DoubleStream filter(DoublePredicate predicate) { + return synchronizedStream(parent.filter(predicate), getInheritableMonitor()); + } + + @Override + public DoubleStream map(DoubleUnaryOperator mapper) { + return synchronizedStream(parent.map(mapper), getInheritableMonitor()); + } + + @Override + public IntStream mapToInt(DoubleToIntFunction mapper) { + return synchronizedStream(parent.mapToInt(mapper), getInheritableMonitor()); + } + + @Override + public LongStream mapToLong(DoubleToLongFunction mapper) { + return synchronizedStream(parent.mapToLong(mapper), getInheritableMonitor()); + } + + @Override + public Stream mapToObj(DoubleFunction mapper) { + return synchronizedStream(parent.mapToObj(mapper), getInheritableMonitor()); + } + + @Override + public DoubleStream flatMap(DoubleFunction mapper) { + return synchronizedStream(parent.flatMap(mapper), getInheritableMonitor()); + } + + @Override + public DoubleStream distinct() { + return synchronizedStream(parent.distinct(), getInheritableMonitor()); + } + + @Override + public DoubleStream sorted() { + return synchronizedStream(parent.sorted(), getInheritableMonitor()); + } + + @Override + public DoubleStream peek(DoubleConsumer action) { + return synchronizedStream(parent.peek(action), getInheritableMonitor()); + } + + @Override + public DoubleStream limit(long maxSize) { + return synchronizedStream(parent.limit(maxSize), getInheritableMonitor()); + } + + @Override + public DoubleStream skip(long n) { + return synchronizedStream(parent.skip(n), getInheritableMonitor()); + } + + @Override + public Stream boxed() { + return synchronizedStream(parent.boxed(), getInheritableMonitor()); + } + + @Override + public double reduce(double identity, DoubleBinaryOperator accumulator) { + synchronized (monitor) { + return parent.reduce(identity, accumulator); + } + } + + @Override + public OptionalDouble reduce(DoubleBinaryOperator accumulator) { + synchronized (monitor) { + return parent.reduce(accumulator); + } + } + + @Override + public void forEach(DoubleConsumer action) { + synchronized (monitor) { + parent.forEach(action); + } + } + + @Override + public void forEachOrdered(DoubleConsumer action) { + synchronized (monitor) { + parent.forEachOrdered(action); + } + } + + @Override + public double[] toArray() { + synchronized (monitor) { + return parent.toArray(); + } + } + + @Override + public R collect(Supplier supplier, ObjDoubleConsumer accumulator, BiConsumer combiner) { + synchronized (monitor) { + return parent.collect(supplier, accumulator, combiner); + } + } + + @Override + public double sum() { + synchronized (monitor) { + return parent.sum(); + } + } + + @Override + public OptionalDouble min() { + synchronized (monitor) { + return parent.min(); + } + } + + @Override + public OptionalDouble max() { + synchronized (monitor) { + return parent.max(); + } + } + + @Override + public long count() { + synchronized (monitor) { + return parent.count(); + } + } + + @Override + public OptionalDouble average() { + synchronized (monitor) { + return parent.average(); + } + } + + @Override + public DoubleSummaryStatistics summaryStatistics() { + synchronized (monitor) { + return parent.summaryStatistics(); + } + } + + @Override + public boolean anyMatch(DoublePredicate predicate) { + synchronized (monitor) { + return parent.anyMatch(predicate); + } + } + + @Override + public boolean allMatch(DoublePredicate predicate) { + synchronized (monitor) { + return parent.allMatch(predicate); + } + } + + @Override + public boolean noneMatch(DoublePredicate predicate) { + synchronized (monitor) { + return parent.noneMatch(predicate); + } + } + + @Override + public OptionalDouble findFirst() { + synchronized (monitor) { + return parent.findFirst(); + } + } + + @Override + public OptionalDouble findAny() { + synchronized (monitor) { + return parent.findAny(); + } + } + + } + + /** + * Wraps the given {@link Stream} 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 Stream}'s {@link Stream#iterator() iterator()} and {@link Stream#spliterator() + * spliterator()} methods return regular non-synchronized iterators and spliterators respectively. It + * is the user's responsibility to avoid concurrency issues: + *

+	 * synchronized (stream.getMonitor()) {
+	 *     Iterator it = stream.iterator();
+	 *         ...
+	 * }
+	 * 
+ * Usage example: + *
+	 * Set<Object> s = Collections.synchronizedSet(new HashSet<>());
+	 *    ...
+	 * Stream<?> stream = SyncStreams.synchronizedStream(s.stream(), s);
+	 * stream = stream.map(Object::toString); // Still synchronized
+	 * stream.forEach(System.out::println); // Should never throw a ConcurrentModificationException 
+	 * 
+ * + * @param the class of objects in the Stream + * @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 SyncStream SyncStream<T>} synchronized on {@code monitor} and backed by {@code stream}. + * @throws NullPointerException if {@code stream == null}. + */ + public static SyncStream synchronizedStream(Stream 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:

+ * + * + * + * + * + * + * + * + * + * + * + *
srctargetskipreturns
a.b.c'.'01
a.b.c'.'13
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+1th 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:

+ * + * + * + * + * + * + * + * + * + * + * + *
srctargetskipreturns
a.b.c'.'03
a.b.c'.'11
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+1th targetcharacter + * 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 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 first, + ThrowingBiConsumer second) { + return (t, u) -> { + first.accept(t, u); + second.accept(t, u); + }; + } + + public static ThrowingBiConsumer concat( + BiConsumer first, + ThrowingBiConsumer second) { + return (t, u) -> { + first.accept(t, u); + second.accept(t, u); + }; + } + + public static ThrowingBiConsumer concat( + ThrowingBiConsumer first, + BiConsumer 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 handler) { + return t -> { + try { + accept(t); + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + handler.accept(t, (E) e); + } + }; + } + + public static ThrowingConsumer concat(ThrowingConsumer first, ThrowingConsumer second) { + return t -> { + first.accept(t); + second.accept(t); + }; + } + + public static ThrowingConsumer concat(Consumer first, ThrowingConsumer second) { + return t -> { + first.accept(t); + second.accept(t); + }; + } + + public static ThrowingConsumer concat(ThrowingConsumer first, Consumer 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 handler, Function 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 handler, Supplier value) { + return withHandler(handler, t -> value.get()); + } + + default Function withHandler(BiConsumer handler, R value) { + return withHandler(handler, t -> value); + } + + default Function withHandler(BiConsumer handler) { + return withHandler(handler, (Function) null); + } + + public static ThrowingFunction compose( + ThrowingFunction first, + ThrowingFunction second) { + return t -> second.apply(first.apply(t)); + } + + public static ThrowingFunction compose( + Function first, + ThrowingFunction second) { + return t -> second.apply(first.apply(t)); + } + + public static ThrowingFunction compose( + ThrowingFunction first, + Function 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 handler) { + return () -> { + try { + run(); + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + handler.accept((E) e); + } + }; + } + + public static ThrowingRunnable concat( + ThrowingRunnable first, + ThrowingRunnable 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 handler, Supplier 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 handler, T value) { + return withHandler(handler, () -> value); + } + + default Supplier withHandler(Consumer 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 source; + private E next = null; + + public PeekingIterator(Iterator 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 predicate; + + public PredicateWrapper(String name, Predicate 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 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 } }