You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tsfile.apache.org by ja...@apache.org on 2025/10/22 07:57:29 UTC
(tsfile) 01/02: Refactored some dependencies
This is an automated email from the ASF dual-hosted git repository.
jackietien pushed a commit to branch snapshot/2.2.0-251022
in repository https://gitbox.apache.org/repos/asf/tsfile.git
commit f5c0682b8ad26d9bd6f28e11f97a0e5f755f5291
Author: Caideyipi <87...@users.noreply.github.com>
AuthorDate: Wed Oct 22 15:54:15 2025 +0800
Refactored some dependencies
---
LICENSE | 42 +
java/pom.xml | 10 -
java/tools/pom.xml | 8 -
.../java/org/apache/tsfile/tools/TsFileTool.java | 5 +-
.../org/apache/tsfile/tools/TsfiletoolsTest.java | 2 +-
java/tsfile/pom.xml | 8 -
.../tsfile/external/commons/codec/binary/Hex.java | 118 ++
.../codec/binary/MessageDigestAlgorithms.java | 52 +
.../external/commons/codec/binary/StringUtils.java | 54 +
.../external/commons/codec/digest/DigestUtils.java | 141 ++
.../external/commons/collections4/BoundedMap.java | 44 +
.../commons/collections4/CollectionUtils.java | 52 +
.../tsfile/external/commons/collections4/Get.java | 91 ++
.../external/commons/collections4/IterableGet.java | 47 +
.../external/commons/collections4/IterableMap.java | 43 +
.../external/commons/collections4/KeyValue.java | 44 +
.../external/commons/collections4/MapIterator.java | 110 ++
.../external/commons/collections4/MapUtils.java | 39 +
.../commons/collections4/OrderedIterator.java | 46 +
.../external/commons/collections4/OrderedMap.java | 70 +
.../commons/collections4/OrderedMapIterator.java | 46 +
.../tsfile/external/commons/collections4/Put.java | 60 +
.../commons/collections4/ResettableIterator.java | 33 +
.../collections4/comparators/ComparatorChain.java | 197 +++
.../iterators/AbstractEmptyIterator.java | 56 +
.../iterators/AbstractEmptyMapIterator.java | 44 +
.../collections4/iterators/EmptyIterator.java | 74 ++
.../collections4/iterators/EmptyMapIterator.java | 55 +
.../iterators/EmptyOrderedIterator.java | 53 +
.../iterators/EmptyOrderedMapIterator.java | 55 +
.../collections4/map/AbstractHashedMap.java | 1394 ++++++++++++++++++++
.../collections4/map/AbstractLinkedMap.java | 610 +++++++++
.../external/commons/collections4/map/LRUMap.java | 573 ++++++++
.../tsfile/external/commons/io/Charsets.java | 211 +++
.../external/commons/io/FileExistsException.java | 40 +
.../tsfile/external/commons/io/FileUtils.java | 928 +++++++++++++
.../tsfile/external/commons/io/FilenameUtils.java | 290 ++++
.../apache/tsfile/external/commons/io/IOCase.java | 247 ++++
.../external/commons/io/IOExceptionList.java | 140 ++
.../external/commons/io/IOIndexedException.java | 68 +
.../apache/tsfile/external/commons/io/IOUtils.java | 558 ++++++++
.../external/commons/io/RandomAccessFileMode.java | 96 ++
.../external/commons/io/RandomAccessFiles.java | 46 +
.../external/commons/io/build/AbstractOrigin.java | 484 +++++++
.../commons/io/build/AbstractOriginSupplier.java | 276 ++++
.../commons/io/build/AbstractStreamBuilder.java | 177 +++
.../commons/io/build/AbstractSupplier.java | 41 +
.../commons/io/charset/CharsetDecoders.java | 44 +
.../commons/io/charset/CharsetEncoders.java | 58 +
.../tsfile/external/commons/io/file/Counters.java | 419 ++++++
.../commons/io/file/CountingPathVisitor.java | 155 +++
.../external/commons/io/file/DeleteOption.java | 30 +
.../commons/io/file/DeletingPathVisitor.java | 157 +++
.../external/commons/io/file/PathFilter.java | 40 +
.../tsfile/external/commons/io/file/PathUtils.java | 532 ++++++++
.../external/commons/io/file/PathVisitor.java | 30 +
.../commons/io/file/SimplePathVisitor.java | 46 +
.../commons/io/file/StandardDeleteOption.java | 51 +
.../commons/io/filefilter/AbstractFileFilter.java | 177 +++
.../commons/io/filefilter/AndFileFilter.java | 131 ++
.../io/filefilter/ConditionalFileFilter.java | 33 +
.../commons/io/filefilter/FalseFileFilter.java | 109 ++
.../commons/io/filefilter/FileFileFilter.java | 111 ++
.../commons/io/filefilter/IOFileFilter.java | 109 ++
.../commons/io/filefilter/NotFileFilter.java | 104 ++
.../commons/io/filefilter/OrFileFilter.java | 162 +++
.../commons/io/filefilter/SuffixFileFilter.java | 163 +++
.../io/filefilter/SymbolicLinkFileFilter.java | 141 ++
.../commons/io/filefilter/TrueFileFilter.java | 109 ++
.../external/commons/io/function/Constants.java | 60 +
.../tsfile/external/commons/io/function/Erase.java | 191 +++
.../external/commons/io/function/IOBaseStream.java | 141 ++
.../commons/io/function/IOBaseStreamAdapter.java | 50 +
.../external/commons/io/function/IOBiConsumer.java | 55 +
.../external/commons/io/function/IOBiFunction.java | 65 +
.../commons/io/function/IOBinaryOperator.java | 32 +
.../external/commons/io/function/IOComparator.java | 58 +
.../external/commons/io/function/IOConsumer.java | 145 ++
.../external/commons/io/function/IOFunction.java | 41 +
.../commons/io/function/IOIntSupplier.java | 38 +
.../external/commons/io/function/IOIterator.java | 78 ++
.../commons/io/function/IOIteratorAdapter.java | 55 +
.../external/commons/io/function/IOPredicate.java | 79 ++
.../commons/io/function/IOQuadFunction.java | 51 +
.../external/commons/io/function/IORunnable.java | 36 +
.../commons/io/function/IOSpliterator.java | 135 ++
.../commons/io/function/IOSpliteratorAdapter.java | 44 +
.../external/commons/io/function/IOStream.java | 633 +++++++++
.../commons/io/function/IOStreamAdapter.java | 44 +
.../external/commons/io/function/IOStreams.java | 91 ++
.../external/commons/io/function/IOSupplier.java | 54 +
.../commons/io/function/IOTriConsumer.java | 44 +
.../commons/io/function/IOTriFunction.java | 49 +
.../commons/io/function/IOUnaryOperator.java | 32 +
.../external/commons/io/function/Uncheck.java | 310 +++++
.../commons/io/function/UncheckedIOIterator.java | 60 +
.../commons/io/input/ClosedInputStream.java | 58 +
.../commons/io/input/ReaderInputStream.java | 427 ++++++
.../input/UnsynchronizedByteArrayInputStream.java | 276 ++++
.../io/output/AbstractByteArrayOutputStream.java | 394 ++++++
.../commons/io/output/ByteArrayOutputStream.java | 104 ++
.../commons/io/output/NullOutputStream.java | 86 ++
.../commons/io/output/StringBuilderWriter.java | 140 ++
.../io/output/ThresholdingOutputStream.java | 210 +++
.../UnsynchronizedByteArrayOutputStream.java | 172 +++
.../commons/io/output/WriterOutputStream.java | 479 +++++++
.../tsfile/external/commons/lang3/ArrayFill.java | 40 +
.../tsfile/external/commons/lang3/ArrayUtils.java | 477 +++++++
.../external/commons/lang3/CharSequenceUtils.java | 163 +++
.../tsfile/external/commons/lang3/ClassUtils.java | 34 +
.../commons/lang3/NotImplementedException.java | 143 ++
.../tsfile/external/commons/lang3/ObjectUtils.java | 172 +++
.../tsfile/external/commons/lang3/StringUtils.java | 679 ++++++++++
.../tsfile/external/commons/lang3/Strings.java | 464 +++++++
.../external/commons/lang3/SystemProperties.java | 94 ++
.../tsfile/external/commons/lang3/SystemUtils.java | 144 ++
.../tsfile/external/commons/lang3/Validate.java | 150 +++
.../commons/lang3/builder/HashCodeBuilder.java | 109 ++
.../commons/lang3/exception/ExceptionUtils.java | 70 +
.../commons/lang3/function/FailableBiConsumer.java | 78 ++
.../commons/lang3/function/FailableBiFunction.java | 77 ++
.../commons/lang3/function/FailableFunction.java | 114 ++
.../external/commons/lang3/function/Suppliers.java | 71 +
.../commons/lang3/function/TriFunction.java | 65 +
.../external/commons/lang3/math/NumberUtils.java | 153 +++
.../commons/lang3/stream/LangCollectors.java | 115 ++
.../external/commons/lang3/stream/Streams.java | 56 +
.../commons/lang3/tuple/ImmutablePair.java | 204 +++
.../commons/lang3/tuple/ImmutableTriple.java | 168 +++
.../tsfile/external/commons/lang3/tuple/Pair.java | 264 ++++
.../external/commons/lang3/tuple/Triple.java | 205 +++
.../fileSystem/fsFactory/LocalFSFactory.java | 2 +-
.../java/org/apache/tsfile/read/common/Path.java | 5 +-
.../read/common/block/column/DictionaryColumn.java | 6 +-
.../tsfile/read/common/parser/PathVisitor.java | 3 +-
.../main/java/org/apache/tsfile/utils/Pair.java | 1 +
.../tsfile/write/v4/TsFileWriterBuilder.java | 3 +-
.../apache/tsfile/write/writer/TsFileIOWriter.java | 2 +-
.../TsFileGeneratorForSeriesReaderByTimestamp.java | 10 +-
.../read/reader/chunk/TableChunkReaderTest.java | 2 +-
.../org/apache/tsfile/tableview/TableViewTest.java | 2 +-
.../org/apache/tsfile/utils/FilePathUtilsTest.java | 6 +-
.../utils/{FileUtils.java => FileTestUtils.java} | 2 +-
.../{FileUtilsTest.java => FileTestUtilsTest.java} | 26 +-
.../writer/TsFileIOWriterMemoryControlTest.java | 2 +-
145 files changed, 20126 insertions(+), 61 deletions(-)
diff --git a/LICENSE b/LICENSE
index 7b741bb5..014d343f 100644
--- a/LICENSE
+++ b/LICENSE
@@ -295,3 +295,45 @@ The following files include code is copied from lzokay project.
Copyright: (c) 2018 Jack Andersen
Project page: https://github.com/AxioDL/lzokay
License: https://github.com/AxioDL/lzokay/blob/master/LICENSE
+
+--------------------------------------------------------------------------------
+
+The following files include code modified from Apache Commons Lang project.
+
+./java/tsfile/src/main/java/org/apache/tsfile/external/commons/lang3/*
+
+Apache Commons Lang is open source software licensed under the Apache License 2.0 and supported by the Apache Software Foundation.
+Project page: https://github.com/apache/commons-lang
+License: https://github.com/apache/commons-lang/blob/master/LICENSE.txt
+
+--------------------------------------------------------------------------------
+
+The following files include code modified from Apache Commons IO project.
+
+./java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/*
+
+Apache Commons IO is open source software licensed under the Apache License 2.0 and supported by the Apache Software Foundation.
+Project page: https://github.com/apache/commons-io
+License: https://github.com/apache/commons-io/blob/master/LICENSE.txt
+
+--------------------------------------------------------------------------------
+
+The following files include code modified from Apache Commons CodeC project.
+
+./java/tsfile/src/main/java/org/apache/tsfile/external/commons/codec/*
+
+Apache Commons CodeC is open source software licensed under the Apache License 2.0 and supported by the Apache Software Foundation.
+Project page: https://github.com/apache/commons-codec
+License: https://github.com/apache/commons-codec/blob/master/LICENSE.txt
+
+--------------------------------------------------------------------------------
+
+The following files include code modified from Apache Commons Collections project.
+
+./java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/*
+
+Apache Commons Collections is open source software licensed under the Apache License 2.0 and supported by the Apache Software Foundation.
+Project page: https://github.com/apache/commons-collections
+License: https://github.com/apache/commons-collections/blob/master/LICENSE.txt
+
+--------------------------------------------------------------------------------
diff --git a/java/pom.xml b/java/pom.xml
index a380d4a4..b8e5a551 100644
--- a/java/pom.xml
+++ b/java/pom.xml
@@ -48,16 +48,6 @@
<artifactId>snappy-java</artifactId>
<version>1.1.10.5</version>
</dependency>
- <dependency>
- <groupId>commons-io</groupId>
- <artifactId>commons-io</artifactId>
- <version>2.17.0</version>
- </dependency>
- <dependency>
- <groupId>org.apache.commons</groupId>
- <artifactId>commons-lang3</artifactId>
- <version>3.18.0</version>
- </dependency>
<dependency>
<groupId>org.lz4</groupId>
<artifactId>lz4-java</artifactId>
diff --git a/java/tools/pom.xml b/java/tools/pom.xml
index c8a76f97..80549e7c 100644
--- a/java/tools/pom.xml
+++ b/java/tools/pom.xml
@@ -39,14 +39,6 @@
<artifactId>commons-cli</artifactId>
<version>1.9.0</version>
</dependency>
- <dependency>
- <groupId>commons-io</groupId>
- <artifactId>commons-io</artifactId>
- </dependency>
- <dependency>
- <groupId>org.apache.commons</groupId>
- <artifactId>commons-lang3</artifactId>
- </dependency>
<dependency>
<groupId>org.apache.tsfile</groupId>
<artifactId>tsfile</artifactId>
diff --git a/java/tools/src/main/java/org/apache/tsfile/tools/TsFileTool.java b/java/tools/src/main/java/org/apache/tsfile/tools/TsFileTool.java
index 5dd2afe5..26b69916 100644
--- a/java/tools/src/main/java/org/apache/tsfile/tools/TsFileTool.java
+++ b/java/tools/src/main/java/org/apache/tsfile/tools/TsFileTool.java
@@ -16,10 +16,13 @@
* specific language governing permissions and limitations
* under the License.
*/
+
package org.apache.tsfile.tools;
import org.apache.tsfile.enums.ColumnCategory;
import org.apache.tsfile.enums.TSDataType;
+import org.apache.tsfile.external.commons.io.FilenameUtils;
+import org.apache.tsfile.external.commons.lang3.StringUtils;
import org.apache.tsfile.file.metadata.TableSchema;
import org.apache.tsfile.file.metadata.enums.CompressionType;
import org.apache.tsfile.file.metadata.enums.TSEncoding;
@@ -35,8 +38,6 @@ import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
-import org.apache.commons.io.FilenameUtils;
-import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
diff --git a/java/tools/src/test/java/org/apache/tsfile/tools/TsfiletoolsTest.java b/java/tools/src/test/java/org/apache/tsfile/tools/TsfiletoolsTest.java
index ddbe32e8..5fb09c75 100644
--- a/java/tools/src/test/java/org/apache/tsfile/tools/TsfiletoolsTest.java
+++ b/java/tools/src/test/java/org/apache/tsfile/tools/TsfiletoolsTest.java
@@ -19,6 +19,7 @@
package org.apache.tsfile.tools;
+import org.apache.tsfile.external.commons.io.FileUtils;
import org.apache.tsfile.read.TsFileSequenceReader;
import org.apache.tsfile.read.common.block.TsBlock;
import org.apache.tsfile.read.controller.CachedChunkLoaderImpl;
@@ -26,7 +27,6 @@ import org.apache.tsfile.read.controller.MetadataQuerierByFileImpl;
import org.apache.tsfile.read.query.executor.TableQueryExecutor;
import org.apache.tsfile.read.reader.block.TsBlockReader;
-import org.apache.commons.io.FileUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
diff --git a/java/tsfile/pom.xml b/java/tsfile/pom.xml
index 9187b60e..6d0d41b0 100644
--- a/java/tsfile/pom.xml
+++ b/java/tsfile/pom.xml
@@ -48,14 +48,6 @@
<groupId>org.xerial.snappy</groupId>
<artifactId>snappy-java</artifactId>
</dependency>
- <dependency>
- <groupId>commons-io</groupId>
- <artifactId>commons-io</artifactId>
- </dependency>
- <dependency>
- <groupId>org.apache.commons</groupId>
- <artifactId>commons-lang3</artifactId>
- </dependency>
<dependency>
<groupId>org.lz4</groupId>
<artifactId>lz4-java</artifactId>
diff --git a/java/tsfile/src/main/java/org/apache/tsfile/external/commons/codec/binary/Hex.java b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/codec/binary/Hex.java
new file mode 100644
index 00000000..982b519c
--- /dev/null
+++ b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/codec/binary/Hex.java
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.tsfile.external.commons.codec.binary;
+
+/////////////////////////////////////////////////////////////////////////////////////////////////
+// IoTDB
+/////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class Hex {
+
+ /** Used to build output as hex. */
+ private static final char[] DIGITS_LOWER = {
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
+ };
+
+ /** Used to build output as hex. */
+ private static final char[] DIGITS_UPPER = {
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
+ };
+
+ /**
+ * Converts an array of bytes into a String representing the hexadecimal values of each byte in
+ * order. The returned String will be double the length of the passed array, as it takes two
+ * characters to represent any given byte.
+ *
+ * @param data a byte[] to convert to hexadecimal characters
+ * @return A String containing lower-case hexadecimal characters
+ * @since 1.4
+ */
+ public static String encodeHexString(final byte[] data) {
+ return new String(encodeHex(data));
+ }
+
+ /**
+ * Converts an array of bytes into an array of characters representing the hexadecimal values of
+ * each byte in order. The returned array will be double the length of the passed array, as it
+ * takes two characters to represent any given byte.
+ *
+ * @param data a byte[] to convert to hexadecimal characters
+ * @return A char[] containing lower-case hexadecimal characters
+ */
+ public static char[] encodeHex(final byte[] data) {
+ return encodeHex(data, true);
+ }
+
+ /**
+ * Converts an array of bytes into an array of characters representing the hexadecimal values of
+ * each byte in order. The returned array will be double the length of the passed array, as it
+ * takes two characters to represent any given byte.
+ *
+ * @param data a byte[] to convert to Hex characters
+ * @param toLowerCase {@code true} converts to lowercase, {@code false} to uppercase
+ * @return A char[] containing hexadecimal characters in the selected case
+ * @since 1.4
+ */
+ public static char[] encodeHex(final byte[] data, final boolean toLowerCase) {
+ return encodeHex(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER);
+ }
+
+ /**
+ * Converts an array of bytes into an array of characters representing the hexadecimal values of
+ * each byte in order. The returned array will be double the length of the passed array, as it
+ * takes two characters to represent any given byte.
+ *
+ * @param data a byte[] to convert to hexadecimal characters
+ * @param toDigits the output alphabet (must contain at least 16 chars)
+ * @return A char[] containing the appropriate characters from the alphabet For best results, this
+ * should be either upper- or lower-case hex.
+ * @since 1.4
+ */
+ protected static char[] encodeHex(final byte[] data, final char[] toDigits) {
+ final int dataLength = data.length;
+ final char[] out = new char[dataLength << 1];
+ encodeHex(data, 0, dataLength, toDigits, out, 0);
+ return out;
+ }
+
+ /**
+ * Converts an array of bytes into an array of characters representing the hexadecimal values of
+ * each byte in order.
+ *
+ * @param data a byte[] to convert to hexadecimal characters
+ * @param dataOffset the position in {@code data} to start encoding from
+ * @param dataLen the number of bytes from {@code dataOffset} to encode
+ * @param toDigits the output alphabet (must contain at least 16 chars)
+ * @param out a char[] which will hold the resultant appropriate characters from the alphabet.
+ * @param outOffset the position within {@code out} at which to start writing the encoded
+ * characters.
+ */
+ private static void encodeHex(
+ final byte[] data,
+ final int dataOffset,
+ final int dataLen,
+ final char[] toDigits,
+ final char[] out,
+ final int outOffset) {
+ // two characters form the hex value.
+ for (int i = dataOffset, j = outOffset; i < dataOffset + dataLen; i++) {
+ out[j++] = toDigits[(0xF0 & data[i]) >>> 4];
+ out[j++] = toDigits[0x0F & data[i]];
+ }
+ }
+}
diff --git a/java/tsfile/src/main/java/org/apache/tsfile/external/commons/codec/binary/MessageDigestAlgorithms.java b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/codec/binary/MessageDigestAlgorithms.java
new file mode 100644
index 00000000..738af14d
--- /dev/null
+++ b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/codec/binary/MessageDigestAlgorithms.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.tsfile.external.commons.codec.binary;
+
+import java.security.MessageDigest;
+
+/**
+ * Standard {@link MessageDigest} algorithm names from the <cite>Java Cryptography Architecture
+ * Standard Algorithm Name Documentation</cite>.
+ *
+ * <p>This class is immutable and thread-safe.
+ *
+ * <ul>
+ * <li>Java 8 and up: SHA-224.
+ * <li>Java 9 and up: SHA3-224, SHA3-256, SHA3-384, SHA3-512.
+ * </ul>
+ *
+ * @see <a
+ * href="https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#MessageDigest">
+ * Java 8 Cryptography Architecture Standard Algorithm Name Documentation</a>
+ * @see <a
+ * href="https://docs.oracle.com/en/java/javase/11/docs/specs/security/standard-names.html#messagedigest-algorithms">
+ * Java 11 Cryptography Architecture Standard Algorithm Name Documentation</a>
+ * @see <a
+ * href="https://docs.oracle.com/en/java/javase/17/docs/specs/security/standard-names.html#messagedigest-algorithms">
+ * Java 17 Cryptography Architecture Standard Algorithm Name Documentation</a>
+ * @see <a
+ * href="https://docs.oracle.com/en/java/javase/21/docs/specs/security/standard-names.html#messagedigest-algorithms">
+ * Java 21 Cryptography Architecture Standard Algorithm Name Documentation</a>
+ * @see <a href="https://dx.doi.org/10.6028/NIST.FIPS.180-4">FIPS PUB 180-4</a>
+ * @see <a href="https://dx.doi.org/10.6028/NIST.FIPS.202">FIPS PUB 202</a>
+ * @since 1.7
+ */
+public class MessageDigestAlgorithms {
+ /** The MD2 message digest algorithm defined in RFC 1319. */
+ public static final String MD2 = "MD2";
+}
diff --git a/java/tsfile/src/main/java/org/apache/tsfile/external/commons/codec/binary/StringUtils.java b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/codec/binary/StringUtils.java
new file mode 100644
index 00000000..5106d8b4
--- /dev/null
+++ b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/codec/binary/StringUtils.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.tsfile.external.commons.codec.binary;
+
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+/////////////////////////////////////////////////////////////////////////////////////////////////
+// IoTDB
+/////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class StringUtils {
+ /**
+ * Encodes the given string into a sequence of bytes using the UTF-8 charset, storing the result
+ * into a new byte array.
+ *
+ * @param string the String to encode, may be {@code null}
+ * @return encoded bytes, or {@code null} if the input string was {@code null}
+ * @throws NullPointerException Thrown if {@link StandardCharsets#UTF_8} is not initialized, which
+ * should never happen since it is required by the Java platform specification.
+ * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException
+ * @see Charset
+ * @see #getBytesUnchecked(String, String)
+ */
+ public static byte[] getBytesUtf8(final String string) {
+ return getBytes(string, StandardCharsets.UTF_8);
+ }
+
+ /**
+ * Calls {@link String#getBytes(Charset)}
+ *
+ * @param string The string to encode (if null, return null).
+ * @param charset The {@link Charset} to encode the {@code String}
+ * @return the encoded bytes
+ */
+ private static byte[] getBytes(final String string, final Charset charset) {
+ return string == null ? null : string.getBytes(charset);
+ }
+}
diff --git a/java/tsfile/src/main/java/org/apache/tsfile/external/commons/codec/digest/DigestUtils.java b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/codec/digest/DigestUtils.java
new file mode 100644
index 00000000..8f3fc127
--- /dev/null
+++ b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/codec/digest/DigestUtils.java
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.tsfile.external.commons.codec.digest;
+
+import org.apache.tsfile.external.commons.codec.binary.Hex;
+import org.apache.tsfile.external.commons.codec.binary.MessageDigestAlgorithms;
+import org.apache.tsfile.external.commons.codec.binary.StringUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/////////////////////////////////////////////////////////////////////////////////////////////////
+// IoTDB
+/////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class DigestUtils {
+
+ /**
+ * Calculates the MD2 digest and returns the value as a 32 character hexadecimal string.
+ *
+ * @param data Data to digest
+ * @return MD2 digest as a hexadecimal string
+ * @since 1.7
+ */
+ public static String md2Hex(final String data) {
+ return Hex.encodeHexString(md2(data));
+ }
+
+ /**
+ * Calculates the MD2 digest and returns the value as a 16 element {@code byte[]}.
+ *
+ * @param data Data to digest; converted to bytes using {@link StringUtils#getBytesUtf8(String)}
+ * @return MD2 digest
+ * @since 1.7
+ */
+ public static byte[] md2(final String data) {
+ return md2(StringUtils.getBytesUtf8(data));
+ }
+
+ /**
+ * Calculates the MD2 digest and returns the value as a 16 element {@code byte[]}.
+ *
+ * @param data Data to digest
+ * @return MD2 digest
+ * @since 1.7
+ */
+ public static byte[] md2(final byte[] data) {
+ return getMd2Digest().digest(data);
+ }
+
+ /**
+ * Gets an MD2 MessageDigest.
+ *
+ * @return An MD2 digest instance.
+ * @throws IllegalArgumentException when a {@link NoSuchAlgorithmException} is caught, which
+ * should never happen because MD2 is a built-in algorithm
+ * @see MessageDigestAlgorithms#MD2
+ * @since 1.7
+ */
+ public static MessageDigest getMd2Digest() {
+ return getDigest(MessageDigestAlgorithms.MD2);
+ }
+
+ public static String md5Hex(InputStream data) throws IOException {
+ return Hex.encodeHexString(md5(data));
+ }
+
+ public static byte[] md5(InputStream data) throws IOException {
+ return digest(getMd5Digest(), data);
+ }
+
+ /**
+ * Calculates the MD5 digest and returns the value as a 32 character hexadecimal string.
+ *
+ * @param data Data to digest
+ * @return MD5 digest as a hexadecimal string
+ */
+ public static String md5Hex(final byte[] data) {
+ return Hex.encodeHexString(md5(data));
+ }
+
+ /**
+ * Calculates the MD5 digest and returns the value as a 16 element {@code byte[]}.
+ *
+ * @param data Data to digest
+ * @return MD5 digest
+ */
+ public static byte[] md5(final byte[] data) {
+ return getMd5Digest().digest(data);
+ }
+
+ public static byte[] digest(MessageDigest messageDigest, InputStream data) throws IOException {
+ return updateDigest(messageDigest, data).digest();
+ }
+
+ public static MessageDigest updateDigest(MessageDigest digest, InputStream inputStream)
+ throws IOException {
+ byte[] buffer = new byte[1024];
+
+ for (int read = inputStream.read(buffer, 0, 1024);
+ read > -1;
+ read = inputStream.read(buffer, 0, 1024)) {
+ digest.update(buffer, 0, read);
+ }
+
+ return digest;
+ }
+
+ public static MessageDigest getMd5Digest() {
+ return getDigest("MD5");
+ }
+
+ public static MessageDigest getDigest(String algorithm) {
+ try {
+ return getMessageDigest(algorithm);
+ } catch (NoSuchAlgorithmException var2) {
+ throw new IllegalArgumentException(var2);
+ }
+ }
+
+ private static MessageDigest getMessageDigest(String algorithm) throws NoSuchAlgorithmException {
+ return MessageDigest.getInstance(algorithm);
+ }
+}
diff --git a/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/BoundedMap.java b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/BoundedMap.java
new file mode 100644
index 00000000..d0a5ab16
--- /dev/null
+++ b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/BoundedMap.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tsfile.external.commons.collections4;
+
+/**
+ * Defines a map that is bounded in size.
+ *
+ * <p>The size of the map can vary, but it can never exceed a preset maximum number of elements.
+ * This interface allows the querying of details associated with the maximum number of elements.
+ *
+ * @param <K> the type of the keys in this map
+ * @param <V> the type of the values in this map
+ * @since 3.0
+ */
+public interface BoundedMap<K, V> extends IterableMap<K, V> {
+
+ /**
+ * Returns true if this map is full and no new elements can be added.
+ *
+ * @return <code>true</code> if the map is full
+ */
+ boolean isFull();
+
+ /**
+ * Gets the maximum size of the map (the bound).
+ *
+ * @return the maximum number of elements the map can hold
+ */
+ int maxSize();
+}
diff --git a/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/CollectionUtils.java b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/CollectionUtils.java
new file mode 100644
index 00000000..db35216d
--- /dev/null
+++ b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/CollectionUtils.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.tsfile.external.commons.collections4;
+
+import java.util.Collection;
+
+/////////////////////////////////////////////////////////////////////////////////////////////////
+// IoTDB
+/////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class CollectionUtils {
+ /**
+ * Null-safe check if the specified collection is empty.
+ *
+ * <p>Null returns true.
+ *
+ * @param coll the collection to check, may be null
+ * @return true if empty or null
+ * @since 3.2
+ */
+ public static boolean isEmpty(final Collection<?> coll) {
+ return coll == null || coll.isEmpty();
+ }
+
+ /**
+ * Null-safe check if the specified collection is not empty.
+ *
+ * <p>Null returns false.
+ *
+ * @param coll the collection to check, may be null
+ * @return true if non-null and non-empty
+ * @since 3.2
+ */
+ public static boolean isNotEmpty(final Collection<?> coll) {
+ return !isEmpty(coll);
+ }
+}
diff --git a/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/Get.java b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/Get.java
new file mode 100644
index 00000000..adb43c4e
--- /dev/null
+++ b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/Get.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tsfile.external.commons.collections4;
+
+import java.util.Collection;
+import java.util.Set;
+
+/**
+ * The "read" subset of the {@link java.util.Map} interface.
+ *
+ * @param <K> the type of the keys in this map
+ * @param <V> the type of the values in this map
+ * @since 4.0
+ * @see Put
+ */
+public interface Get<K, V> {
+
+ /**
+ * @param key key whose presence in this map is to be tested
+ * @return <code>true</code> if this map contains a mapping for the specified key
+ * @see java.util.Map#containsKey(Object)
+ */
+ boolean containsKey(Object key);
+
+ /**
+ * @param value value whose presence in this map is to be tested
+ * @return <code>true</code> if this map maps one or more keys to the specified value
+ * @see java.util.Map#containsValue(Object)
+ */
+ boolean containsValue(Object value);
+
+ /**
+ * @return a set view of the mappings contained in this map
+ * @see java.util.Map#entrySet()
+ */
+ Set<java.util.Map.Entry<K, V>> entrySet();
+
+ /**
+ * @param key the key whose associated value is to be returned
+ * @return the value to which the specified key is mapped, or {@code null} if this map contains no
+ * mapping for the key
+ * @see java.util.Map#get(Object)
+ */
+ V get(Object key);
+
+ /**
+ * @param key key whose mapping is to be removed from the map
+ * @return the previous value associated with <code>key</code>, or <code>null</code> if there was
+ * no mapping for <code>key</code>.
+ * @see java.util.Map#remove(Object)
+ */
+ V remove(Object key);
+
+ /**
+ * @return <code>true</code> if this map contains no key-value mappings
+ * @see java.util.Map#isEmpty()
+ */
+ boolean isEmpty();
+
+ /**
+ * @return a set view of the keys contained in this map
+ * @see java.util.Map#keySet()
+ */
+ Set<K> keySet();
+
+ /**
+ * @return the number of key-value mappings in this map
+ * @see java.util.Map#size()
+ */
+ int size();
+
+ /**
+ * @return a collection view of the values contained in this map
+ * @see java.util.Map#values()
+ */
+ Collection<V> values();
+}
diff --git a/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/IterableGet.java b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/IterableGet.java
new file mode 100644
index 00000000..5767c749
--- /dev/null
+++ b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/IterableGet.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tsfile.external.commons.collections4;
+
+/**
+ * The "read" subset of the {@link java.util.Map} interface.
+ *
+ * @param <K> the type of the keys in this map
+ * @param <V> the type of the values in this map
+ * @since 4.0
+ * @see Put
+ */
+public interface IterableGet<K, V> extends Get<K, V> {
+ /**
+ * Obtains a <code>MapIterator</code> over the map.
+ *
+ * <p>A map iterator is an efficient way of iterating over maps. There is no need to access the
+ * entry set or use Map Entry objects.
+ *
+ * <pre>
+ * IterableMap<String,Integer> map = new HashedMap<String,Integer>();
+ * MapIterator<String,Integer> it = map.mapIterator();
+ * while (it.hasNext()) {
+ * String key = it.next();
+ * Integer value = it.getValue();
+ * it.setValue(value + 1);
+ * }
+ * </pre>
+ *
+ * @return a map iterator
+ */
+ MapIterator<K, V> mapIterator();
+}
diff --git a/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/IterableMap.java b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/IterableMap.java
new file mode 100644
index 00000000..e7ad28ca
--- /dev/null
+++ b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/IterableMap.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tsfile.external.commons.collections4;
+
+import java.util.Map;
+
+/**
+ * Defines a map that can be iterated directly without needing to create an entry set.
+ *
+ * <p>A map iterator is an efficient way of iterating over maps. There is no need to access the
+ * entry set or use Map Entry objects.
+ *
+ * <pre>
+ * IterableMap<String,Integer> map = new HashedMap<String,Integer>();
+ * MapIterator<String,Integer> it = map.mapIterator();
+ * while (it.hasNext()) {
+ * String key = it.next();
+ * Integer value = it.getValue();
+ * it.setValue(value + 1);
+ * }
+ * </pre>
+ *
+ * @param <K> the type of the keys in this map
+ * @param <V> the type of the values in this map
+ * @since 3.0
+ */
+public interface IterableMap<K, V> extends Map<K, V>, Put<K, V>, IterableGet<K, V> {
+ // empty
+}
diff --git a/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/KeyValue.java b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/KeyValue.java
new file mode 100644
index 00000000..d173b68e
--- /dev/null
+++ b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/KeyValue.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tsfile.external.commons.collections4;
+
+/**
+ * Defines a simple key value pair.
+ *
+ * <p>A Map Entry has considerable additional semantics over and above a simple key-value pair. This
+ * interface defines the minimum key value, with just the two get methods.
+ *
+ * @param <K> the type of the key
+ * @param <V> the type of the value
+ * @since 3.0
+ */
+public interface KeyValue<K, V> {
+
+ /**
+ * Gets the key from the pair.
+ *
+ * @return the key
+ */
+ K getKey();
+
+ /**
+ * Gets the value from the pair.
+ *
+ * @return the value
+ */
+ V getValue();
+}
diff --git a/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/MapIterator.java b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/MapIterator.java
new file mode 100644
index 00000000..9f9de0a7
--- /dev/null
+++ b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/MapIterator.java
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tsfile.external.commons.collections4;
+
+import java.util.Iterator;
+
+/**
+ * Defines an iterator that operates over a <code>Map</code>.
+ *
+ * <p>This iterator is a special version designed for maps. It can be more efficient to use this
+ * rather than an entry set iterator where the option is available, and it is certainly more
+ * convenient.
+ *
+ * <p>A map that provides this interface may not hold the data internally using Map Entry objects,
+ * thus this interface can avoid lots of object creation.
+ *
+ * <p>In use, this iterator iterates through the keys in the map. After each call to <code>next()
+ * </code>, the <code>getValue()</code> method provides direct access to the value. The value can
+ * also be set using <code>setValue()</code>.
+ *
+ * <pre>{@code
+ * MapIterator<String,Integer> it = map.mapIterator();
+ * while (it.hasNext()) {
+ * String key = it.next();
+ * Integer value = it.getValue();
+ * it.setValue(value + 1);
+ * }
+ * }</pre>
+ *
+ * @param <K> the type of the keys in the map
+ * @param <V> the type of the values in the map
+ * @since 3.0
+ */
+public interface MapIterator<K, V> extends Iterator<K> {
+
+ /**
+ * Checks to see if there are more entries still to be iterated.
+ *
+ * @return <code>true</code> if the iterator has more elements
+ */
+ @Override
+ boolean hasNext();
+
+ /**
+ * Gets the next <em>key</em> from the <code>Map</code>.
+ *
+ * @return the next key in the iteration
+ * @throws java.util.NoSuchElementException if the iteration is finished
+ */
+ @Override
+ K next();
+
+ // -----------------------------------------------------------------------
+ /**
+ * Gets the current key, which is the key returned by the last call to <code>next()</code>.
+ *
+ * @return the current key
+ * @throws IllegalStateException if <code>next()</code> has not yet been called
+ */
+ K getKey();
+
+ /**
+ * Gets the current value, which is the value associated with the last key returned by <code>
+ * next()</code>.
+ *
+ * @return the current value
+ * @throws IllegalStateException if <code>next()</code> has not yet been called
+ */
+ V getValue();
+
+ // -----------------------------------------------------------------------
+ /**
+ * Removes the last returned key from the underlying <code>Map</code> (optional operation).
+ *
+ * <p>This method can be called once per call to <code>next()</code>.
+ *
+ * @throws UnsupportedOperationException if remove is not supported by the map
+ * @throws IllegalStateException if <code>next()</code> has not yet been called
+ * @throws IllegalStateException if <code>remove()</code> has already been called since the last
+ * call to <code>next()</code>
+ */
+ @Override
+ void remove();
+
+ /**
+ * Sets the value associated with the current key (optional operation).
+ *
+ * @param value the new value
+ * @return the previous value
+ * @throws UnsupportedOperationException if setValue is not supported by the map
+ * @throws IllegalStateException if <code>next()</code> has not yet been called
+ * @throws IllegalStateException if <code>remove()</code> has been called since the last call to
+ * <code>next()</code>
+ */
+ V setValue(V value);
+}
diff --git a/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/MapUtils.java b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/MapUtils.java
new file mode 100644
index 00000000..d8e94118
--- /dev/null
+++ b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/MapUtils.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.tsfile.external.commons.collections4;
+
+/////////////////////////////////////////////////////////////////////////////////////////////////
+// IoTDB
+/////////////////////////////////////////////////////////////////////////////////////////////////
+
+import java.util.Map;
+
+public class MapUtils {
+ /**
+ * Null-safe check if the specified map is empty.
+ *
+ * <p>Null returns true.
+ *
+ * @param map the map to check, may be null
+ * @return true if empty or null
+ * @since 3.2
+ */
+ public static boolean isEmpty(final Map<?, ?> map) {
+ return map == null || map.isEmpty();
+ }
+}
diff --git a/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/OrderedIterator.java b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/OrderedIterator.java
new file mode 100644
index 00000000..7f81134e
--- /dev/null
+++ b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/OrderedIterator.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tsfile.external.commons.collections4;
+
+import java.util.Iterator;
+
+/**
+ * Defines an iterator that operates over an ordered container. Subset of {@link
+ * java.util.ListIterator}.
+ *
+ * <p>This iterator allows both forward and reverse iteration through the container.
+ *
+ * @param <E> the type of elements returned by this iterator
+ * @since 3.0
+ */
+public interface OrderedIterator<E> extends Iterator<E> {
+
+ /**
+ * Checks to see if there is a previous element that can be iterated to.
+ *
+ * @return <code>true</code> if the iterator has a previous element
+ */
+ boolean hasPrevious();
+
+ /**
+ * Gets the previous element from the container.
+ *
+ * @return the previous element in the iteration
+ * @throws java.util.NoSuchElementException if the iteration is finished
+ */
+ E previous();
+}
diff --git a/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/OrderedMap.java b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/OrderedMap.java
new file mode 100644
index 00000000..f87f0057
--- /dev/null
+++ b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/OrderedMap.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tsfile.external.commons.collections4;
+
+/**
+ * Defines a map that maintains order and allows both forward and backward iteration through that
+ * order.
+ *
+ * @param <K> the type of the keys in the map
+ * @param <V> the type of the values in the map
+ * @since 3.0
+ */
+public interface OrderedMap<K, V> extends IterableMap<K, V> {
+
+ /**
+ * Obtains an <code>OrderedMapIterator</code> over the map.
+ *
+ * <p>A ordered map iterator is an efficient way of iterating over maps in both directions.
+ *
+ * @return a map iterator
+ */
+ @Override
+ OrderedMapIterator<K, V> mapIterator();
+
+ /**
+ * Gets the first key currently in this map.
+ *
+ * @return the first key currently in this map
+ * @throws java.util.NoSuchElementException if this map is empty
+ */
+ K firstKey();
+
+ /**
+ * Gets the last key currently in this map.
+ *
+ * @return the last key currently in this map
+ * @throws java.util.NoSuchElementException if this map is empty
+ */
+ K lastKey();
+
+ /**
+ * Gets the next key after the one specified.
+ *
+ * @param key the key to search for next from
+ * @return the next key, null if no match or at end
+ */
+ K nextKey(K key);
+
+ /**
+ * Gets the previous key before the one specified.
+ *
+ * @param key the key to search for previous from
+ * @return the previous key, null if no match or at start
+ */
+ K previousKey(K key);
+}
diff --git a/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/OrderedMapIterator.java b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/OrderedMapIterator.java
new file mode 100644
index 00000000..6f3b4368
--- /dev/null
+++ b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/OrderedMapIterator.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tsfile.external.commons.collections4;
+
+/**
+ * Defines an iterator that operates over an ordered <code>Map</code>.
+ *
+ * <p>This iterator allows both forward and reverse iteration through the map.
+ *
+ * @param <K> the type of the keys in the map
+ * @param <V> the type of the values in the map
+ * @since 3.0
+ */
+public interface OrderedMapIterator<K, V> extends MapIterator<K, V>, OrderedIterator<K> {
+
+ /**
+ * Checks to see if there is a previous entry that can be iterated to.
+ *
+ * @return <code>true</code> if the iterator has a previous element
+ */
+ @Override
+ boolean hasPrevious();
+
+ /**
+ * Gets the previous <em>key</em> from the <code>Map</code>.
+ *
+ * @return the previous key in the iteration
+ * @throws java.util.NoSuchElementException if the iteration is finished
+ */
+ @Override
+ K previous();
+}
diff --git a/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/Put.java b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/Put.java
new file mode 100644
index 00000000..9bab9773
--- /dev/null
+++ b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/Put.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tsfile.external.commons.collections4;
+
+import java.util.Map;
+
+/**
+ * The "write" subset of the {@link Map} interface.
+ *
+ * <p>NOTE: in the original {@link Map} interface, {@link Map#put(Object, Object)} is known to have
+ * the same return type as {@link Map#get(Object)}, namely {@code V}. {@link Put} makes no
+ * assumptions in this regard (there is no association with, nor even knowledge of, a "reading"
+ * interface) and thus defines {@link #put(Object, Object)} as returning {@link Object}.
+ *
+ * @param <K> the type of the keys in this map
+ * @param <V> the type of the values in this map
+ * @since 4.0
+ * @see Get
+ */
+public interface Put<K, V> {
+
+ /**
+ * @see Map#clear()
+ */
+ void clear();
+
+ /**
+ * Note that the return type is Object, rather than V as in the Map interface. See the class
+ * Javadoc for further info.
+ *
+ * @param key key with which the specified value is to be associated
+ * @param value value to be associated with the specified key
+ * @return the previous value associated with <code>key</code>, or <code>null</code> if there was
+ * no mapping for <code>key</code>. (A <code>null</code> return can also indicate that the map
+ * previously associated <code>null</code> with <code>key</code>, if the implementation
+ * supports <code>null</code> values.)
+ * @see Map#put(Object, Object)
+ */
+ Object put(K key, V value);
+
+ /**
+ * @param t mappings to be stored in this map
+ * @see Map#putAll(Map)
+ */
+ void putAll(Map<? extends K, ? extends V> t);
+}
diff --git a/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/ResettableIterator.java b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/ResettableIterator.java
new file mode 100644
index 00000000..2b8b2ec1
--- /dev/null
+++ b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/ResettableIterator.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tsfile.external.commons.collections4;
+
+import java.util.Iterator;
+
+/**
+ * Defines an iterator that can be reset back to an initial state.
+ *
+ * <p>This interface allows an iterator to be repeatedly reused.
+ *
+ * @param <E> the type of elements returned by this iterator
+ * @since 3.0
+ */
+public interface ResettableIterator<E> extends Iterator<E> {
+
+ /** Resets the iterator back to the position at which the iterator was created. */
+ void reset();
+}
diff --git a/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/comparators/ComparatorChain.java b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/comparators/ComparatorChain.java
new file mode 100644
index 00000000..2acefe07
--- /dev/null
+++ b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/comparators/ComparatorChain.java
@@ -0,0 +1,197 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tsfile.external.commons.collections4.comparators;
+
+import java.io.Serializable;
+import java.util.BitSet;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+
+/////////////////////////////////////////////////////////////////////////////////////////////////
+// IoTDB
+/////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * A ComparatorChain is a Comparator that wraps one or more Comparators in sequence. The
+ * ComparatorChain calls each Comparator in sequence until either 1) any single Comparator returns a
+ * non-zero result (and that result is then returned), or 2) the ComparatorChain is exhausted (and
+ * zero is returned). This type of sorting is very similar to multi-column sorting in SQL, and this
+ * class allows Java classes to emulate that kind of behaviour when sorting a List.
+ *
+ * <p>To further facilitate SQL-like sorting, the order of any single Comparator in the list can be
+ * reversed.
+ *
+ * <p>Calling a method that adds new Comparators or changes the ascend/descend sort <i>after
+ * compare(Object, Object) has been called</i> will result in an UnsupportedOperationException.
+ * However, <i>take care</i> to not alter the underlying List of Comparators or the BitSet that
+ * defines the sort order.
+ *
+ * <p>Instances of ComparatorChain are not synchronized. The class is not thread-safe at
+ * construction time, but it <i>is</i> thread-safe to perform multiple comparisons after all the
+ * setup operations are complete.
+ *
+ * @param <E> the type of objects compared by this comparator
+ * @since 2.0
+ */
+public class ComparatorChain<E> implements Comparator<E>, Serializable {
+
+ /** Serialization version from Collections 2.0. */
+ private static final long serialVersionUID = -721644942746081630L;
+
+ /** The list of comparators in the chain. */
+ private final List<Comparator<E>> comparatorChain;
+
+ /** Order - false (clear) = ascend; true (set) = descend. */
+ private BitSet orderingBits = null;
+
+ /** Whether the chain has been "locked". */
+ private boolean isLocked = false;
+
+ /**
+ * Construct a ComparatorChain from the Comparators in the List. All Comparators will default to
+ * the forward sort order.
+ *
+ * @param list List of Comparators
+ * @see #ComparatorChain(List,BitSet)
+ */
+ public ComparatorChain(final List<Comparator<E>> list) {
+ this(list, new BitSet(list.size()));
+ }
+
+ /**
+ * Construct a ComparatorChain from the Comparators in the given List. The sort order of each
+ * column will be drawn from the given BitSet. When determining the sort order for Comparator at
+ * index <i>i</i> in the List, the ComparatorChain will call BitSet.get(<i>i</i>). If that method
+ * returns <i>false</i>, the forward sort order is used; a return value of <i>true</i> indicates
+ * reverse sort order.
+ *
+ * @param list List of Comparators. NOTE: This constructor does not perform a defensive copy of
+ * the list
+ * @param bits Sort order for each Comparator. Extra bits are ignored, unless extra Comparators
+ * are added by another method.
+ */
+ public ComparatorChain(final List<Comparator<E>> list, final BitSet bits) {
+ comparatorChain = list;
+ orderingBits = bits;
+ }
+
+ /**
+ * Throws an exception if the {@link ComparatorChain} is empty.
+ *
+ * @throws UnsupportedOperationException if the {@link ComparatorChain} is empty
+ */
+ private void checkChainIntegrity() {
+ if (comparatorChain.size() == 0) {
+ throw new UnsupportedOperationException(
+ "ComparatorChains must contain at least one Comparator");
+ }
+ }
+
+ // -----------------------------------------------------------------------
+ /**
+ * Perform comparisons on the Objects as per Comparator.compare(o1,o2).
+ *
+ * @param o1 the first object to compare
+ * @param o2 the second object to compare
+ * @return -1, 0, or 1
+ * @throws UnsupportedOperationException if the ComparatorChain does not contain at least one
+ * Comparator
+ */
+ @Override
+ public int compare(final E o1, final E o2) throws UnsupportedOperationException {
+ if (isLocked == false) {
+ checkChainIntegrity();
+ isLocked = true;
+ }
+
+ // iterate over all comparators in the chain
+ final Iterator<Comparator<E>> comparators = comparatorChain.iterator();
+ for (int comparatorIndex = 0; comparators.hasNext(); ++comparatorIndex) {
+
+ final Comparator<? super E> comparator = comparators.next();
+ int retval = comparator.compare(o1, o2);
+ if (retval != 0) {
+ // invert the order if it is a reverse sort
+ if (orderingBits.get(comparatorIndex) == true) {
+ if (retval > 0) {
+ retval = -1;
+ } else {
+ retval = 1;
+ }
+ }
+ return retval;
+ }
+ }
+
+ // if comparators are exhausted, return 0
+ return 0;
+ }
+
+ // -----------------------------------------------------------------------
+ /**
+ * Implement a hash code for this comparator that is consistent with {@link #equals(Object)
+ * equals}.
+ *
+ * @return a suitable hash code
+ * @since 3.0
+ */
+ @Override
+ public int hashCode() {
+ int hash = 0;
+ if (null != comparatorChain) {
+ hash ^= comparatorChain.hashCode();
+ }
+ if (null != orderingBits) {
+ hash ^= orderingBits.hashCode();
+ }
+ return hash;
+ }
+
+ /**
+ * Returns <code>true</code> iff <i>that</i> Object is is a {@link Comparator} whose ordering is
+ * known to be equivalent to mine.
+ *
+ * <p>This implementation returns <code>true</code> iff <code>
+ * <i>object</i>.{@link Object#getClass() getClass()}</code> equals <code>this.getClass()</code>,
+ * and the underlying comparators and order bits are equal. Subclasses may want to override this
+ * behavior to remain consistent with the {@link Comparator#equals(Object)} contract.
+ *
+ * @param object the object to compare with
+ * @return true if equal
+ * @since 3.0
+ */
+ @Override
+ public boolean equals(final Object object) {
+ if (this == object) {
+ return true;
+ }
+ if (null == object) {
+ return false;
+ }
+ if (object.getClass().equals(this.getClass())) {
+ final ComparatorChain<?> chain = (ComparatorChain<?>) object;
+ return (null == orderingBits
+ ? null == chain.orderingBits
+ : orderingBits.equals(chain.orderingBits))
+ && (null == comparatorChain
+ ? null == chain.comparatorChain
+ : comparatorChain.equals(chain.comparatorChain));
+ }
+ return false;
+ }
+}
diff --git a/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/iterators/AbstractEmptyIterator.java b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/iterators/AbstractEmptyIterator.java
new file mode 100644
index 00000000..01085769
--- /dev/null
+++ b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/iterators/AbstractEmptyIterator.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tsfile.external.commons.collections4.iterators;
+
+import java.util.NoSuchElementException;
+
+/**
+ * Provides an implementation of an empty iterator.
+ *
+ * @since 3.1
+ */
+abstract class AbstractEmptyIterator<E> {
+
+ /** Constructor. */
+ protected AbstractEmptyIterator() {
+ super();
+ }
+
+ public boolean hasNext() {
+ return false;
+ }
+
+ public E next() {
+ throw new NoSuchElementException("Iterator contains no elements");
+ }
+
+ public boolean hasPrevious() {
+ return false;
+ }
+
+ public E previous() {
+ throw new NoSuchElementException("Iterator contains no elements");
+ }
+
+ public void remove() {
+ throw new IllegalStateException("Iterator contains no elements");
+ }
+
+ public void reset() {
+ // do nothing
+ }
+}
diff --git a/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/iterators/AbstractEmptyMapIterator.java b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/iterators/AbstractEmptyMapIterator.java
new file mode 100644
index 00000000..ae45cf5c
--- /dev/null
+++ b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/iterators/AbstractEmptyMapIterator.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tsfile.external.commons.collections4.iterators;
+
+/**
+ * Provides an implementation of an empty map iterator.
+ *
+ * @param <K> the type of keys
+ * @param <V> the type of mapped values
+ * @since 4.0
+ */
+public abstract class AbstractEmptyMapIterator<K, V> extends AbstractEmptyIterator<K> {
+
+ /** Create a new AbstractEmptyMapIterator. */
+ public AbstractEmptyMapIterator() {
+ super();
+ }
+
+ public K getKey() {
+ throw new IllegalStateException("Iterator contains no elements");
+ }
+
+ public V getValue() {
+ throw new IllegalStateException("Iterator contains no elements");
+ }
+
+ public V setValue(final V value) {
+ throw new IllegalStateException("Iterator contains no elements");
+ }
+}
diff --git a/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/iterators/EmptyIterator.java b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/iterators/EmptyIterator.java
new file mode 100644
index 00000000..f108487c
--- /dev/null
+++ b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/iterators/EmptyIterator.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tsfile.external.commons.collections4.iterators;
+
+import org.apache.tsfile.external.commons.collections4.ResettableIterator;
+
+import java.util.Iterator;
+
+/**
+ * Provides an implementation of an empty iterator.
+ *
+ * <p>This class provides an implementation of an empty iterator. This class provides for binary
+ * compatibility between Commons Collections 2.1.1 and 3.1 due to issues with <code>IteratorUtils
+ * </code>.
+ *
+ * @since 2.1.1 and 3.1
+ */
+public class EmptyIterator<E> extends AbstractEmptyIterator<E> implements ResettableIterator<E> {
+
+ /**
+ * Singleton instance of the iterator.
+ *
+ * @since 3.1
+ */
+ @SuppressWarnings("rawtypes")
+ public static final ResettableIterator RESETTABLE_INSTANCE = new EmptyIterator<>();
+
+ /**
+ * Singleton instance of the iterator.
+ *
+ * @since 2.1.1 and 3.1
+ */
+ @SuppressWarnings("rawtypes")
+ public static final Iterator INSTANCE = RESETTABLE_INSTANCE;
+
+ /**
+ * Get a typed resettable empty iterator instance.
+ *
+ * @param <E> the element type
+ * @return ResettableIterator<E>
+ */
+ public static <E> ResettableIterator<E> resettableEmptyIterator() {
+ return RESETTABLE_INSTANCE;
+ }
+
+ /**
+ * Get a typed empty iterator instance.
+ *
+ * @param <E> the element type
+ * @return Iterator<E>
+ */
+ public static <E> Iterator<E> emptyIterator() {
+ return INSTANCE;
+ }
+
+ /** Constructor. */
+ protected EmptyIterator() {
+ super();
+ }
+}
diff --git a/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/iterators/EmptyMapIterator.java b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/iterators/EmptyMapIterator.java
new file mode 100644
index 00000000..3d27b12a
--- /dev/null
+++ b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/iterators/EmptyMapIterator.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tsfile.external.commons.collections4.iterators;
+
+import org.apache.tsfile.external.commons.collections4.MapIterator;
+import org.apache.tsfile.external.commons.collections4.ResettableIterator;
+
+/**
+ * Provides an implementation of an empty map iterator.
+ *
+ * @param <K> the type of keys
+ * @param <V> the type of mapped values
+ * @since 3.1
+ */
+public class EmptyMapIterator<K, V> extends AbstractEmptyMapIterator<K, V>
+ implements MapIterator<K, V>, ResettableIterator<K> {
+
+ /**
+ * Singleton instance of the iterator.
+ *
+ * @since 3.1
+ */
+ @SuppressWarnings("rawtypes")
+ public static final MapIterator INSTANCE = new EmptyMapIterator<>();
+
+ /**
+ * Get a typed instance of the iterator.
+ *
+ * @param <K> the key type
+ * @param <V> the value type
+ * @return {@link MapIterator}<K, V>
+ */
+ public static <K, V> MapIterator<K, V> emptyMapIterator() {
+ return INSTANCE;
+ }
+
+ /** Constructor. */
+ protected EmptyMapIterator() {
+ super();
+ }
+}
diff --git a/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/iterators/EmptyOrderedIterator.java b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/iterators/EmptyOrderedIterator.java
new file mode 100644
index 00000000..ea144b26
--- /dev/null
+++ b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/iterators/EmptyOrderedIterator.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tsfile.external.commons.collections4.iterators;
+
+import org.apache.tsfile.external.commons.collections4.OrderedIterator;
+import org.apache.tsfile.external.commons.collections4.ResettableIterator;
+
+/**
+ * Provides an implementation of an empty ordered iterator.
+ *
+ * @param <E> the type to iterate.
+ * @since 3.1
+ */
+public class EmptyOrderedIterator<E> extends AbstractEmptyIterator<E>
+ implements OrderedIterator<E>, ResettableIterator<E> {
+
+ /**
+ * Singleton instance of the iterator.
+ *
+ * @since 3.1
+ */
+ @SuppressWarnings("rawtypes")
+ public static final OrderedIterator INSTANCE = new EmptyOrderedIterator<>();
+
+ /**
+ * Typed instance of the iterator.
+ *
+ * @param <E> the element type
+ * @return OrderedIterator<E>
+ */
+ public static <E> OrderedIterator<E> emptyOrderedIterator() {
+ return INSTANCE;
+ }
+
+ /** Constructor. */
+ protected EmptyOrderedIterator() {
+ super();
+ }
+}
diff --git a/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/iterators/EmptyOrderedMapIterator.java b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/iterators/EmptyOrderedMapIterator.java
new file mode 100644
index 00000000..8a1b502c
--- /dev/null
+++ b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/iterators/EmptyOrderedMapIterator.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tsfile.external.commons.collections4.iterators;
+
+import org.apache.tsfile.external.commons.collections4.OrderedMapIterator;
+import org.apache.tsfile.external.commons.collections4.ResettableIterator;
+
+/**
+ * Provides an implementation of an empty ordered map iterator.
+ *
+ * @param <K> the type of keys
+ * @param <V> the type of mapped values
+ * @since 3.1
+ */
+public class EmptyOrderedMapIterator<K, V> extends AbstractEmptyMapIterator<K, V>
+ implements OrderedMapIterator<K, V>, ResettableIterator<K> {
+
+ /**
+ * Singleton instance of the iterator.
+ *
+ * @since 3.1
+ */
+ @SuppressWarnings("rawtypes")
+ public static final OrderedMapIterator INSTANCE = new EmptyOrderedMapIterator<>();
+
+ /**
+ * Get a typed instance of the iterator.
+ *
+ * @param <K> the key type
+ * @param <V> the value type
+ * @return {@link OrderedMapIterator}<K, V>
+ */
+ public static <K, V> OrderedMapIterator<K, V> emptyOrderedMapIterator() {
+ return INSTANCE;
+ }
+
+ /** Constructor. */
+ protected EmptyOrderedMapIterator() {
+ super();
+ }
+}
diff --git a/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/map/AbstractHashedMap.java b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/map/AbstractHashedMap.java
new file mode 100644
index 00000000..47718bbb
--- /dev/null
+++ b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/map/AbstractHashedMap.java
@@ -0,0 +1,1394 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tsfile.external.commons.collections4.map;
+
+import org.apache.tsfile.external.commons.collections4.IterableMap;
+import org.apache.tsfile.external.commons.collections4.KeyValue;
+import org.apache.tsfile.external.commons.collections4.MapIterator;
+import org.apache.tsfile.external.commons.collections4.iterators.EmptyIterator;
+import org.apache.tsfile.external.commons.collections4.iterators.EmptyMapIterator;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.AbstractCollection;
+import java.util.AbstractMap;
+import java.util.AbstractSet;
+import java.util.Collection;
+import java.util.ConcurrentModificationException;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+/**
+ * An abstract implementation of a hash-based map which provides numerous points for subclasses to
+ * override.
+ *
+ * <p>This class implements all the features necessary for a subclass hash-based map. Key-value
+ * entries are stored in instances of the <code>HashEntry</code> class, which can be overridden and
+ * replaced. The iterators can similarly be replaced, without the need to replace the KeySet,
+ * EntrySet and Values view classes.
+ *
+ * <p>Overridable methods are provided to change the default hashing behaviour, and to change how
+ * entries are added to and removed from the map. Hopefully, all you need for unusual subclasses is
+ * here.
+ *
+ * <p>NOTE: From Commons Collections 3.1 this class extends AbstractMap. This is to provide
+ * backwards compatibility for ReferenceMap between v3.0 and v3.1. This extends clause will be
+ * removed in v5.0.
+ *
+ * @param <K> the type of the keys in this map
+ * @param <V> the type of the values in this map
+ * @since 3.0
+ */
+public class AbstractHashedMap<K, V> extends AbstractMap<K, V> implements IterableMap<K, V> {
+
+ protected static final String NO_NEXT_ENTRY = "No next() entry in the iteration";
+ protected static final String NO_PREVIOUS_ENTRY = "No previous() entry in the iteration";
+ protected static final String REMOVE_INVALID = "remove() can only be called once after next()";
+ protected static final String GETKEY_INVALID =
+ "getKey() can only be called after next() and before remove()";
+ protected static final String GETVALUE_INVALID =
+ "getValue() can only be called after next() and before remove()";
+ protected static final String SETVALUE_INVALID =
+ "setValue() can only be called after next() and before remove()";
+
+ /** The default capacity to use */
+ protected static final int DEFAULT_CAPACITY = 16;
+
+ /** The default threshold to use */
+ protected static final int DEFAULT_THRESHOLD = 12;
+
+ /** The default load factor to use */
+ protected static final float DEFAULT_LOAD_FACTOR = 0.75f;
+
+ /** The maximum capacity allowed */
+ protected static final int MAXIMUM_CAPACITY = 1 << 30;
+
+ /** An object for masking null */
+ protected static final Object NULL = new Object();
+
+ /** Load factor, normally 0.75 */
+ transient float loadFactor;
+
+ /** The size of the map */
+ transient int size;
+
+ /** Map entries */
+ transient HashEntry<K, V>[] data;
+
+ /** Size at which to rehash */
+ transient int threshold;
+
+ /** Modification count for iterators */
+ transient int modCount;
+
+ /** Entry set */
+ transient EntrySet<K, V> entrySet;
+
+ /** Key set */
+ transient KeySet<K> keySet;
+
+ /** Values */
+ transient Values<V> values;
+
+ /** Constructor only used in deserialization, do not use otherwise. */
+ protected AbstractHashedMap() {
+ super();
+ }
+
+ /**
+ * Constructor which performs no validation on the passed in parameters.
+ *
+ * @param initialCapacity the initial capacity, must be a power of two
+ * @param loadFactor the load factor, must be > 0.0f and generally < 1.0f
+ * @param threshold the threshold, must be sensible
+ */
+ @SuppressWarnings("unchecked")
+ protected AbstractHashedMap(
+ final int initialCapacity, final float loadFactor, final int threshold) {
+ super();
+ this.loadFactor = loadFactor;
+ this.data = new HashEntry[initialCapacity];
+ this.threshold = threshold;
+ init();
+ }
+
+ /**
+ * Constructs a new, empty map with the specified initial capacity and default load factor.
+ *
+ * @param initialCapacity the initial capacity
+ * @throws IllegalArgumentException if the initial capacity is negative
+ */
+ protected AbstractHashedMap(final int initialCapacity) {
+ this(initialCapacity, DEFAULT_LOAD_FACTOR);
+ }
+
+ /**
+ * Constructs a new, empty map with the specified initial capacity and load factor.
+ *
+ * @param initialCapacity the initial capacity
+ * @param loadFactor the load factor
+ * @throws IllegalArgumentException if the initial capacity is negative
+ * @throws IllegalArgumentException if the load factor is less than or equal to zero
+ */
+ @SuppressWarnings("unchecked")
+ protected AbstractHashedMap(int initialCapacity, final float loadFactor) {
+ super();
+ if (initialCapacity < 0) {
+ throw new IllegalArgumentException("Initial capacity must be a non negative number");
+ }
+ if (loadFactor <= 0.0f || Float.isNaN(loadFactor)) {
+ throw new IllegalArgumentException("Load factor must be greater than 0");
+ }
+ this.loadFactor = loadFactor;
+ initialCapacity = calculateNewCapacity(initialCapacity);
+ this.threshold = calculateThreshold(initialCapacity, loadFactor);
+ this.data = new HashEntry[initialCapacity];
+ init();
+ }
+
+ /**
+ * Constructor copying elements from another map.
+ *
+ * @param map the map to copy
+ * @throws NullPointerException if the map is null
+ */
+ protected AbstractHashedMap(final Map<? extends K, ? extends V> map) {
+ this(Math.max(2 * map.size(), DEFAULT_CAPACITY), DEFAULT_LOAD_FACTOR);
+ _putAll(map);
+ }
+
+ /** Initialise subclasses during construction, cloning or deserialization. */
+ protected void init() {}
+
+ // -----------------------------------------------------------------------
+ /**
+ * Gets the value mapped to the key specified.
+ *
+ * @param key the key
+ * @return the mapped value, null if no match
+ */
+ @Override
+ public V get(Object key) {
+ key = convertKey(key);
+ final int hashCode = hash(key);
+ HashEntry<K, V> entry = data[hashIndex(hashCode, data.length)]; // no local for hash index
+ while (entry != null) {
+ if (entry.hashCode == hashCode && isEqualKey(key, entry.key)) {
+ return entry.getValue();
+ }
+ entry = entry.next;
+ }
+ return null;
+ }
+
+ /**
+ * Gets the size of the map.
+ *
+ * @return the size
+ */
+ @Override
+ public int size() {
+ return size;
+ }
+
+ /**
+ * Checks whether the map is currently empty.
+ *
+ * @return true if the map is currently size zero
+ */
+ @Override
+ public boolean isEmpty() {
+ return size == 0;
+ }
+
+ // -----------------------------------------------------------------------
+ /**
+ * Checks whether the map contains the specified key.
+ *
+ * @param key the key to search for
+ * @return true if the map contains the key
+ */
+ @Override
+ public boolean containsKey(Object key) {
+ key = convertKey(key);
+ final int hashCode = hash(key);
+ HashEntry<K, V> entry = data[hashIndex(hashCode, data.length)]; // no local for hash index
+ while (entry != null) {
+ if (entry.hashCode == hashCode && isEqualKey(key, entry.key)) {
+ return true;
+ }
+ entry = entry.next;
+ }
+ return false;
+ }
+
+ /**
+ * Checks whether the map contains the specified value.
+ *
+ * @param value the value to search for
+ * @return true if the map contains the value
+ */
+ @Override
+ public boolean containsValue(final Object value) {
+ if (value == null) {
+ for (final HashEntry<K, V> element : data) {
+ HashEntry<K, V> entry = element;
+ while (entry != null) {
+ if (entry.getValue() == null) {
+ return true;
+ }
+ entry = entry.next;
+ }
+ }
+ } else {
+ for (final HashEntry<K, V> element : data) {
+ HashEntry<K, V> entry = element;
+ while (entry != null) {
+ if (isEqualValue(value, entry.getValue())) {
+ return true;
+ }
+ entry = entry.next;
+ }
+ }
+ }
+ return false;
+ }
+
+ // -----------------------------------------------------------------------
+ /**
+ * Puts a key-value mapping into this map.
+ *
+ * @param key the key to add
+ * @param value the value to add
+ * @return the value previously mapped to this key, null if none
+ */
+ @Override
+ public V put(final K key, final V value) {
+ final Object convertedKey = convertKey(key);
+ final int hashCode = hash(convertedKey);
+ final int index = hashIndex(hashCode, data.length);
+ HashEntry<K, V> entry = data[index];
+ while (entry != null) {
+ if (entry.hashCode == hashCode && isEqualKey(convertedKey, entry.key)) {
+ final V oldValue = entry.getValue();
+ updateEntry(entry, value);
+ return oldValue;
+ }
+ entry = entry.next;
+ }
+
+ addMapping(index, hashCode, key, value);
+ return null;
+ }
+
+ /**
+ * Puts all the values from the specified map into this map.
+ *
+ * <p>This implementation iterates around the specified map and uses {@link #put(Object, Object)}.
+ *
+ * @param map the map to add
+ * @throws NullPointerException if the map is null
+ */
+ @Override
+ public void putAll(final Map<? extends K, ? extends V> map) {
+ _putAll(map);
+ }
+
+ /**
+ * Puts all the values from the specified map into this map.
+ *
+ * <p>This implementation iterates around the specified map and uses {@link #put(Object, Object)}.
+ *
+ * <p>It is private to allow the constructor to still call it even when putAll is overriden.
+ *
+ * @param map the map to add
+ * @throws NullPointerException if the map is null
+ */
+ private void _putAll(final Map<? extends K, ? extends V> map) {
+ final int mapSize = map.size();
+ if (mapSize == 0) {
+ return;
+ }
+ final int newSize = (int) ((size + mapSize) / loadFactor + 1);
+ ensureCapacity(calculateNewCapacity(newSize));
+ for (final Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
+ put(entry.getKey(), entry.getValue());
+ }
+ }
+
+ /**
+ * Removes the specified mapping from this map.
+ *
+ * @param key the mapping to remove
+ * @return the value mapped to the removed key, null if key not in map
+ */
+ @Override
+ public V remove(Object key) {
+ key = convertKey(key);
+ final int hashCode = hash(key);
+ final int index = hashIndex(hashCode, data.length);
+ HashEntry<K, V> entry = data[index];
+ HashEntry<K, V> previous = null;
+ while (entry != null) {
+ if (entry.hashCode == hashCode && isEqualKey(key, entry.key)) {
+ final V oldValue = entry.getValue();
+ removeMapping(entry, index, previous);
+ return oldValue;
+ }
+ previous = entry;
+ entry = entry.next;
+ }
+ return null;
+ }
+
+ /**
+ * Clears the map, resetting the size to zero and nullifying references to avoid garbage
+ * collection issues.
+ */
+ @Override
+ public void clear() {
+ modCount++;
+ final HashEntry<K, V>[] data = this.data;
+ for (int i = data.length - 1; i >= 0; i--) {
+ data[i] = null;
+ }
+ size = 0;
+ }
+
+ // -----------------------------------------------------------------------
+ /**
+ * Converts input keys to another object for storage in the map. This implementation masks nulls.
+ * Subclasses can override this to perform alternate key conversions.
+ *
+ * <p>The reverse conversion can be changed, if required, by overriding the getKey() method in the
+ * hash entry.
+ *
+ * @param key the key convert
+ * @return the converted key
+ */
+ protected Object convertKey(final Object key) {
+ return key == null ? NULL : key;
+ }
+
+ /**
+ * Gets the hash code for the key specified. This implementation uses the additional hashing
+ * routine from JDK1.4. Subclasses can override this to return alternate hash codes.
+ *
+ * @param key the key to get a hash code for
+ * @return the hash code
+ */
+ protected int hash(final Object key) {
+ // same as JDK 1.4
+ int h = key.hashCode();
+ h += ~(h << 9);
+ h ^= h >>> 14;
+ h += h << 4;
+ h ^= h >>> 10;
+ return h;
+ }
+
+ /**
+ * Compares two keys, in internal converted form, to see if they are equal. This implementation
+ * uses the equals method and assumes neither key is null. Subclasses can override this to match
+ * differently.
+ *
+ * @param key1 the first key to compare passed in from outside
+ * @param key2 the second key extracted from the entry via <code>entry.key</code>
+ * @return true if equal
+ */
+ protected boolean isEqualKey(final Object key1, final Object key2) {
+ return key1 == key2 || key1.equals(key2);
+ }
+
+ /**
+ * Compares two values, in external form, to see if they are equal. This implementation uses the
+ * equals method and assumes neither value is null. Subclasses can override this to match
+ * differently.
+ *
+ * @param value1 the first value to compare passed in from outside
+ * @param value2 the second value extracted from the entry via <code>getValue()</code>
+ * @return true if equal
+ */
+ protected boolean isEqualValue(final Object value1, final Object value2) {
+ return value1 == value2 || value1.equals(value2);
+ }
+
+ /**
+ * Gets the index into the data storage for the hashCode specified. This implementation uses the
+ * least significant bits of the hashCode. Subclasses can override this to return alternate
+ * bucketing.
+ *
+ * @param hashCode the hash code to use
+ * @param dataSize the size of the data to pick a bucket from
+ * @return the bucket index
+ */
+ protected int hashIndex(final int hashCode, final int dataSize) {
+ return hashCode & dataSize - 1;
+ }
+
+ // -----------------------------------------------------------------------
+ /**
+ * Gets the entry mapped to the key specified.
+ *
+ * <p>This method exists for subclasses that may need to perform a multi-step process accessing
+ * the entry. The public methods in this class don't use this method to gain a small performance
+ * boost.
+ *
+ * @param key the key
+ * @return the entry, null if no match
+ */
+ protected HashEntry<K, V> getEntry(Object key) {
+ key = convertKey(key);
+ final int hashCode = hash(key);
+ HashEntry<K, V> entry = data[hashIndex(hashCode, data.length)]; // no local for hash index
+ while (entry != null) {
+ if (entry.hashCode == hashCode && isEqualKey(key, entry.key)) {
+ return entry;
+ }
+ entry = entry.next;
+ }
+ return null;
+ }
+
+ // -----------------------------------------------------------------------
+ /**
+ * Updates an existing key-value mapping to change the value.
+ *
+ * <p>This implementation calls <code>setValue()</code> on the entry. Subclasses could override to
+ * handle changes to the map.
+ *
+ * @param entry the entry to update
+ * @param newValue the new value to store
+ */
+ protected void updateEntry(final HashEntry<K, V> entry, final V newValue) {
+ entry.setValue(newValue);
+ }
+
+ /**
+ * Reuses an existing key-value mapping, storing completely new data.
+ *
+ * <p>This implementation sets all the data fields on the entry. Subclasses could populate
+ * additional entry fields.
+ *
+ * @param entry the entry to update, not null
+ * @param hashIndex the index in the data array
+ * @param hashCode the hash code of the key to add
+ * @param key the key to add
+ * @param value the value to add
+ */
+ protected void reuseEntry(
+ final HashEntry<K, V> entry,
+ final int hashIndex,
+ final int hashCode,
+ final K key,
+ final V value) {
+ entry.next = data[hashIndex];
+ entry.hashCode = hashCode;
+ entry.key = key;
+ entry.value = value;
+ }
+
+ // -----------------------------------------------------------------------
+ /**
+ * Adds a new key-value mapping into this map.
+ *
+ * <p>This implementation calls <code>createEntry()</code>, <code>addEntry()</code> and <code>
+ * checkCapacity()</code>. It also handles changes to <code>modCount</code> and <code>size</code>.
+ * Subclasses could override to fully control adds to the map.
+ *
+ * @param hashIndex the index into the data array to store at
+ * @param hashCode the hash code of the key to add
+ * @param key the key to add
+ * @param value the value to add
+ */
+ protected void addMapping(final int hashIndex, final int hashCode, final K key, final V value) {
+ modCount++;
+ final HashEntry<K, V> entry = createEntry(data[hashIndex], hashCode, key, value);
+ addEntry(entry, hashIndex);
+ size++;
+ checkCapacity();
+ }
+
+ /**
+ * Creates an entry to store the key-value data.
+ *
+ * <p>This implementation creates a new HashEntry instance. Subclasses can override this to return
+ * a different storage class, or implement caching.
+ *
+ * @param next the next entry in sequence
+ * @param hashCode the hash code to use
+ * @param key the key to store
+ * @param value the value to store
+ * @return the newly created entry
+ */
+ protected HashEntry<K, V> createEntry(
+ final HashEntry<K, V> next, final int hashCode, final K key, final V value) {
+ return new HashEntry<>(next, hashCode, convertKey(key), value);
+ }
+
+ /**
+ * Adds an entry into this map.
+ *
+ * <p>This implementation adds the entry to the data storage table. Subclasses could override to
+ * handle changes to the map.
+ *
+ * @param entry the entry to add
+ * @param hashIndex the index into the data array to store at
+ */
+ protected void addEntry(final HashEntry<K, V> entry, final int hashIndex) {
+ data[hashIndex] = entry;
+ }
+
+ // -----------------------------------------------------------------------
+ /**
+ * Removes a mapping from the map.
+ *
+ * <p>This implementation calls <code>removeEntry()</code> and <code>destroyEntry()</code>. It
+ * also handles changes to <code>modCount</code> and <code>size</code>. Subclasses could override
+ * to fully control removals from the map.
+ *
+ * @param entry the entry to remove
+ * @param hashIndex the index into the data structure
+ * @param previous the previous entry in the chain
+ */
+ protected void removeMapping(
+ final HashEntry<K, V> entry, final int hashIndex, final HashEntry<K, V> previous) {
+ modCount++;
+ removeEntry(entry, hashIndex, previous);
+ size--;
+ destroyEntry(entry);
+ }
+
+ /**
+ * Removes an entry from the chain stored in a particular index.
+ *
+ * <p>This implementation removes the entry from the data storage table. The size is not updated.
+ * Subclasses could override to handle changes to the map.
+ *
+ * @param entry the entry to remove
+ * @param hashIndex the index into the data structure
+ * @param previous the previous entry in the chain
+ */
+ protected void removeEntry(
+ final HashEntry<K, V> entry, final int hashIndex, final HashEntry<K, V> previous) {
+ if (previous == null) {
+ data[hashIndex] = entry.next;
+ } else {
+ previous.next = entry.next;
+ }
+ }
+
+ /**
+ * Kills an entry ready for the garbage collector.
+ *
+ * <p>This implementation prepares the HashEntry for garbage collection. Subclasses can override
+ * this to implement caching (override clear as well).
+ *
+ * @param entry the entry to destroy
+ */
+ protected void destroyEntry(final HashEntry<K, V> entry) {
+ entry.next = null;
+ entry.key = null;
+ entry.value = null;
+ }
+
+ // -----------------------------------------------------------------------
+ /**
+ * Checks the capacity of the map and enlarges it if necessary.
+ *
+ * <p>This implementation uses the threshold to check if the map needs enlarging
+ */
+ protected void checkCapacity() {
+ if (size >= threshold) {
+ final int newCapacity = data.length * 2;
+ if (newCapacity <= MAXIMUM_CAPACITY) {
+ ensureCapacity(newCapacity);
+ }
+ }
+ }
+
+ /**
+ * Changes the size of the data structure to the capacity proposed.
+ *
+ * @param newCapacity the new capacity of the array (a power of two, less or equal to max)
+ */
+ @SuppressWarnings("unchecked")
+ protected void ensureCapacity(final int newCapacity) {
+ final int oldCapacity = data.length;
+ if (newCapacity <= oldCapacity) {
+ return;
+ }
+ if (size == 0) {
+ threshold = calculateThreshold(newCapacity, loadFactor);
+ data = new HashEntry[newCapacity];
+ } else {
+ final HashEntry<K, V> oldEntries[] = data;
+ final HashEntry<K, V> newEntries[] = new HashEntry[newCapacity];
+
+ modCount++;
+ for (int i = oldCapacity - 1; i >= 0; i--) {
+ HashEntry<K, V> entry = oldEntries[i];
+ if (entry != null) {
+ oldEntries[i] = null; // gc
+ do {
+ final HashEntry<K, V> next = entry.next;
+ final int index = hashIndex(entry.hashCode, newCapacity);
+ entry.next = newEntries[index];
+ newEntries[index] = entry;
+ entry = next;
+ } while (entry != null);
+ }
+ }
+ threshold = calculateThreshold(newCapacity, loadFactor);
+ data = newEntries;
+ }
+ }
+
+ /**
+ * Calculates the new capacity of the map. This implementation normalizes the capacity to a power
+ * of two.
+ *
+ * @param proposedCapacity the proposed capacity
+ * @return the normalized new capacity
+ */
+ protected int calculateNewCapacity(final int proposedCapacity) {
+ int newCapacity = 1;
+ if (proposedCapacity > MAXIMUM_CAPACITY) {
+ newCapacity = MAXIMUM_CAPACITY;
+ } else {
+ while (newCapacity < proposedCapacity) {
+ newCapacity <<= 1; // multiply by two
+ }
+ if (newCapacity > MAXIMUM_CAPACITY) {
+ newCapacity = MAXIMUM_CAPACITY;
+ }
+ }
+ return newCapacity;
+ }
+
+ /**
+ * Calculates the new threshold of the map, where it will be resized. This implementation uses the
+ * load factor.
+ *
+ * @param newCapacity the new capacity
+ * @param factor the load factor
+ * @return the new resize threshold
+ */
+ protected int calculateThreshold(final int newCapacity, final float factor) {
+ return (int) (newCapacity * factor);
+ }
+
+ // -----------------------------------------------------------------------
+ /**
+ * Gets the <code>next</code> field from a <code>HashEntry</code>. Used in subclasses that have no
+ * visibility of the field.
+ *
+ * @param entry the entry to query, must not be null
+ * @return the <code>next</code> field of the entry
+ * @throws NullPointerException if the entry is null
+ * @since 3.1
+ */
+ protected HashEntry<K, V> entryNext(final HashEntry<K, V> entry) {
+ return entry.next;
+ }
+
+ /**
+ * Gets the <code>hashCode</code> field from a <code>HashEntry</code>. Used in subclasses that
+ * have no visibility of the field.
+ *
+ * @param entry the entry to query, must not be null
+ * @return the <code>hashCode</code> field of the entry
+ * @throws NullPointerException if the entry is null
+ * @since 3.1
+ */
+ protected int entryHashCode(final HashEntry<K, V> entry) {
+ return entry.hashCode;
+ }
+
+ /**
+ * Gets the <code>key</code> field from a <code>HashEntry</code>. Used in subclasses that have no
+ * visibility of the field.
+ *
+ * @param entry the entry to query, must not be null
+ * @return the <code>key</code> field of the entry
+ * @throws NullPointerException if the entry is null
+ * @since 3.1
+ */
+ protected K entryKey(final HashEntry<K, V> entry) {
+ return entry.getKey();
+ }
+
+ /**
+ * Gets the <code>value</code> field from a <code>HashEntry</code>. Used in subclasses that have
+ * no visibility of the field.
+ *
+ * @param entry the entry to query, must not be null
+ * @return the <code>value</code> field of the entry
+ * @throws NullPointerException if the entry is null
+ * @since 3.1
+ */
+ protected V entryValue(final HashEntry<K, V> entry) {
+ return entry.getValue();
+ }
+
+ // -----------------------------------------------------------------------
+ /**
+ * Gets an iterator over the map. Changes made to the iterator affect this map.
+ *
+ * <p>A MapIterator returns the keys in the map. It also provides convenient methods to get the
+ * key and value, and set the value. It avoids the need to create an entrySet/keySet/values
+ * object. It also avoids creating the Map.Entry object.
+ *
+ * @return the map iterator
+ */
+ @Override
+ public MapIterator<K, V> mapIterator() {
+ if (size == 0) {
+ return EmptyMapIterator.<K, V>emptyMapIterator();
+ }
+ return new HashMapIterator<>(this);
+ }
+
+ /** MapIterator implementation. */
+ protected static class HashMapIterator<K, V> extends HashIterator<K, V>
+ implements MapIterator<K, V> {
+
+ protected HashMapIterator(final AbstractHashedMap<K, V> parent) {
+ super(parent);
+ }
+
+ @Override
+ public K next() {
+ return super.nextEntry().getKey();
+ }
+
+ @Override
+ public K getKey() {
+ final HashEntry<K, V> current = currentEntry();
+ if (current == null) {
+ throw new IllegalStateException(AbstractHashedMap.GETKEY_INVALID);
+ }
+ return current.getKey();
+ }
+
+ @Override
+ public V getValue() {
+ final HashEntry<K, V> current = currentEntry();
+ if (current == null) {
+ throw new IllegalStateException(AbstractHashedMap.GETVALUE_INVALID);
+ }
+ return current.getValue();
+ }
+
+ @Override
+ public V setValue(final V value) {
+ final HashEntry<K, V> current = currentEntry();
+ if (current == null) {
+ throw new IllegalStateException(AbstractHashedMap.SETVALUE_INVALID);
+ }
+ return current.setValue(value);
+ }
+ }
+
+ // -----------------------------------------------------------------------
+ /**
+ * Gets the entrySet view of the map. Changes made to the view affect this map. To simply iterate
+ * through the entries, use {@link #mapIterator()}.
+ *
+ * @return the entrySet view
+ */
+ @Override
+ public Set<Map.Entry<K, V>> entrySet() {
+ if (entrySet == null) {
+ entrySet = new EntrySet<>(this);
+ }
+ return entrySet;
+ }
+
+ /**
+ * Creates an entry set iterator. Subclasses can override this to return iterators with different
+ * properties.
+ *
+ * @return the entrySet iterator
+ */
+ protected Iterator<Map.Entry<K, V>> createEntrySetIterator() {
+ if (size() == 0) {
+ return EmptyIterator.<Map.Entry<K, V>>emptyIterator();
+ }
+ return new EntrySetIterator<>(this);
+ }
+
+ /** EntrySet implementation. */
+ protected static class EntrySet<K, V> extends AbstractSet<Map.Entry<K, V>> {
+ /** The parent map */
+ private final AbstractHashedMap<K, V> parent;
+
+ protected EntrySet(final AbstractHashedMap<K, V> parent) {
+ super();
+ this.parent = parent;
+ }
+
+ @Override
+ public int size() {
+ return parent.size();
+ }
+
+ @Override
+ public void clear() {
+ parent.clear();
+ }
+
+ @Override
+ public boolean contains(final Object entry) {
+ if (entry instanceof Map.Entry) {
+ final Map.Entry<?, ?> e = (Map.Entry<?, ?>) entry;
+ final Entry<K, V> match = parent.getEntry(e.getKey());
+ return match != null && match.equals(e);
+ }
+ return false;
+ }
+
+ @Override
+ public boolean remove(final Object obj) {
+ if (obj instanceof Map.Entry == false) {
+ return false;
+ }
+ if (contains(obj) == false) {
+ return false;
+ }
+ final Map.Entry<?, ?> entry = (Map.Entry<?, ?>) obj;
+ parent.remove(entry.getKey());
+ return true;
+ }
+
+ @Override
+ public Iterator<Map.Entry<K, V>> iterator() {
+ return parent.createEntrySetIterator();
+ }
+ }
+
+ /** EntrySet iterator. */
+ protected static class EntrySetIterator<K, V> extends HashIterator<K, V>
+ implements Iterator<Map.Entry<K, V>> {
+
+ protected EntrySetIterator(final AbstractHashedMap<K, V> parent) {
+ super(parent);
+ }
+
+ @Override
+ public Map.Entry<K, V> next() {
+ return super.nextEntry();
+ }
+ }
+
+ // -----------------------------------------------------------------------
+ /**
+ * Gets the keySet view of the map. Changes made to the view affect this map. To simply iterate
+ * through the keys, use {@link #mapIterator()}.
+ *
+ * @return the keySet view
+ */
+ @Override
+ public Set<K> keySet() {
+ if (keySet == null) {
+ keySet = new KeySet<>(this);
+ }
+ return keySet;
+ }
+
+ /**
+ * Creates a key set iterator. Subclasses can override this to return iterators with different
+ * properties.
+ *
+ * @return the keySet iterator
+ */
+ protected Iterator<K> createKeySetIterator() {
+ if (size() == 0) {
+ return EmptyIterator.<K>emptyIterator();
+ }
+ return new KeySetIterator<>(this);
+ }
+
+ /** KeySet implementation. */
+ protected static class KeySet<K> extends AbstractSet<K> {
+ /** The parent map */
+ private final AbstractHashedMap<K, ?> parent;
+
+ protected KeySet(final AbstractHashedMap<K, ?> parent) {
+ super();
+ this.parent = parent;
+ }
+
+ @Override
+ public int size() {
+ return parent.size();
+ }
+
+ @Override
+ public void clear() {
+ parent.clear();
+ }
+
+ @Override
+ public boolean contains(final Object key) {
+ return parent.containsKey(key);
+ }
+
+ @Override
+ public boolean remove(final Object key) {
+ final boolean result = parent.containsKey(key);
+ parent.remove(key);
+ return result;
+ }
+
+ @Override
+ public Iterator<K> iterator() {
+ return parent.createKeySetIterator();
+ }
+ }
+
+ /** KeySet iterator. */
+ protected static class KeySetIterator<K> extends HashIterator<K, Object> implements Iterator<K> {
+
+ @SuppressWarnings("unchecked")
+ protected KeySetIterator(final AbstractHashedMap<K, ?> parent) {
+ super((AbstractHashedMap<K, Object>) parent);
+ }
+
+ @Override
+ public K next() {
+ return super.nextEntry().getKey();
+ }
+ }
+
+ // -----------------------------------------------------------------------
+ /**
+ * Gets the values view of the map. Changes made to the view affect this map. To simply iterate
+ * through the values, use {@link #mapIterator()}.
+ *
+ * @return the values view
+ */
+ @Override
+ public Collection<V> values() {
+ if (values == null) {
+ values = new Values<>(this);
+ }
+ return values;
+ }
+
+ /**
+ * Creates a values iterator. Subclasses can override this to return iterators with different
+ * properties.
+ *
+ * @return the values iterator
+ */
+ protected Iterator<V> createValuesIterator() {
+ if (size() == 0) {
+ return EmptyIterator.<V>emptyIterator();
+ }
+ return new ValuesIterator<>(this);
+ }
+
+ /** Values implementation. */
+ protected static class Values<V> extends AbstractCollection<V> {
+ /** The parent map */
+ private final AbstractHashedMap<?, V> parent;
+
+ protected Values(final AbstractHashedMap<?, V> parent) {
+ super();
+ this.parent = parent;
+ }
+
+ @Override
+ public int size() {
+ return parent.size();
+ }
+
+ @Override
+ public void clear() {
+ parent.clear();
+ }
+
+ @Override
+ public boolean contains(final Object value) {
+ return parent.containsValue(value);
+ }
+
+ @Override
+ public Iterator<V> iterator() {
+ return parent.createValuesIterator();
+ }
+ }
+
+ /** Values iterator. */
+ protected static class ValuesIterator<V> extends HashIterator<Object, V> implements Iterator<V> {
+
+ @SuppressWarnings("unchecked")
+ protected ValuesIterator(final AbstractHashedMap<?, V> parent) {
+ super((AbstractHashedMap<Object, V>) parent);
+ }
+
+ @Override
+ public V next() {
+ return super.nextEntry().getValue();
+ }
+ }
+
+ // -----------------------------------------------------------------------
+ /**
+ * HashEntry used to store the data.
+ *
+ * <p>If you subclass <code>AbstractHashedMap</code> but not <code>HashEntry</code> then you will
+ * not be able to access the protected fields. The <code>entryXxx()</code> methods on <code>
+ * AbstractHashedMap</code> exist to provide the necessary access.
+ */
+ protected static class HashEntry<K, V> implements Map.Entry<K, V>, KeyValue<K, V> {
+ /** The next entry in the hash chain */
+ protected HashEntry<K, V> next;
+
+ /** The hash code of the key */
+ protected int hashCode;
+
+ /** The key */
+ protected Object key;
+
+ /** The value */
+ protected Object value;
+
+ protected HashEntry(
+ final HashEntry<K, V> next, final int hashCode, final Object key, final V value) {
+ super();
+ this.next = next;
+ this.hashCode = hashCode;
+ this.key = key;
+ this.value = value;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public K getKey() {
+ if (key == NULL) {
+ return null;
+ }
+ return (K) key;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public V getValue() {
+ return (V) value;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public V setValue(final V value) {
+ final Object old = this.value;
+ this.value = value;
+ return (V) old;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (obj instanceof Map.Entry == false) {
+ return false;
+ }
+ final Map.Entry<?, ?> other = (Map.Entry<?, ?>) obj;
+ return (getKey() == null ? other.getKey() == null : getKey().equals(other.getKey()))
+ && (getValue() == null ? other.getValue() == null : getValue().equals(other.getValue()));
+ }
+
+ @Override
+ public int hashCode() {
+ return (getKey() == null ? 0 : getKey().hashCode())
+ ^ (getValue() == null ? 0 : getValue().hashCode());
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder().append(getKey()).append('=').append(getValue()).toString();
+ }
+ }
+
+ /** Base Iterator */
+ protected abstract static class HashIterator<K, V> {
+
+ /** The parent map */
+ private final AbstractHashedMap<K, V> parent;
+
+ /** The current index into the array of buckets */
+ private int hashIndex;
+
+ /** The last returned entry */
+ private HashEntry<K, V> last;
+
+ /** The next entry */
+ private HashEntry<K, V> next;
+
+ /** The modification count expected */
+ private int expectedModCount;
+
+ protected HashIterator(final AbstractHashedMap<K, V> parent) {
+ super();
+ this.parent = parent;
+ final HashEntry<K, V>[] data = parent.data;
+ int i = data.length;
+ HashEntry<K, V> next = null;
+ while (i > 0 && next == null) {
+ next = data[--i];
+ }
+ this.next = next;
+ this.hashIndex = i;
+ this.expectedModCount = parent.modCount;
+ }
+
+ public boolean hasNext() {
+ return next != null;
+ }
+
+ protected HashEntry<K, V> nextEntry() {
+ if (parent.modCount != expectedModCount) {
+ throw new ConcurrentModificationException();
+ }
+ final HashEntry<K, V> newCurrent = next;
+ if (newCurrent == null) {
+ throw new NoSuchElementException(AbstractHashedMap.NO_NEXT_ENTRY);
+ }
+ final HashEntry<K, V>[] data = parent.data;
+ int i = hashIndex;
+ HashEntry<K, V> n = newCurrent.next;
+ while (n == null && i > 0) {
+ n = data[--i];
+ }
+ next = n;
+ hashIndex = i;
+ last = newCurrent;
+ return newCurrent;
+ }
+
+ protected HashEntry<K, V> currentEntry() {
+ return last;
+ }
+
+ public void remove() {
+ if (last == null) {
+ throw new IllegalStateException(AbstractHashedMap.REMOVE_INVALID);
+ }
+ if (parent.modCount != expectedModCount) {
+ throw new ConcurrentModificationException();
+ }
+ parent.remove(last.getKey());
+ last = null;
+ expectedModCount = parent.modCount;
+ }
+
+ @Override
+ public String toString() {
+ if (last != null) {
+ return "Iterator[" + last.getKey() + "=" + last.getValue() + "]";
+ }
+ return "Iterator[]";
+ }
+ }
+
+ // -----------------------------------------------------------------------
+ /**
+ * Writes the map data to the stream. This method must be overridden if a subclass must be setup
+ * before <code>put()</code> is used.
+ *
+ * <p>Serialization is not one of the JDK's nicest topics. Normal serialization will initialise
+ * the superclass before the subclass. Sometimes however, this isn't what you want, as in this
+ * case the <code>put()</code> method on read can be affected by subclass state.
+ *
+ * <p>The solution adopted here is to serialize the state data of this class in this protected
+ * method. This method must be called by the <code>writeObject()</code> of the first serializable
+ * subclass.
+ *
+ * <p>Subclasses may override if they have a specific field that must be present on read before
+ * this implementation will work. Generally, the read determines what must be serialized here, if
+ * anything.
+ *
+ * @param out the output stream
+ * @throws IOException if an error occurs while writing tothe stream
+ */
+ protected void doWriteObject(final ObjectOutputStream out) throws IOException {
+ out.writeFloat(loadFactor);
+ out.writeInt(data.length);
+ out.writeInt(size);
+ for (final MapIterator<K, V> it = mapIterator(); it.hasNext(); ) {
+ out.writeObject(it.next());
+ out.writeObject(it.getValue());
+ }
+ }
+
+ /**
+ * Reads the map data from the stream. This method must be overridden if a subclass must be setup
+ * before <code>put()</code> is used.
+ *
+ * <p>Serialization is not one of the JDK's nicest topics. Normal serialization will initialise
+ * the superclass before the subclass. Sometimes however, this isn't what you want, as in this
+ * case the <code>put()</code> method on read can be affected by subclass state.
+ *
+ * <p>The solution adopted here is to deserialize the state data of this class in this protected
+ * method. This method must be called by the <code>readObject()</code> of the first serializable
+ * subclass.
+ *
+ * <p>Subclasses may override if the subclass has a specific field that must be present before
+ * <code>put()</code> or <code>calculateThreshold()</code> will work correctly.
+ *
+ * @param in the input stream
+ * @throws IOException if an error occurs while reading from the stream
+ * @throws ClassNotFoundException if an object read from the stream can not be loaded
+ */
+ @SuppressWarnings("unchecked")
+ protected void doReadObject(final ObjectInputStream in)
+ throws IOException, ClassNotFoundException {
+ loadFactor = in.readFloat();
+ final int capacity = in.readInt();
+ final int size = in.readInt();
+ init();
+ threshold = calculateThreshold(capacity, loadFactor);
+ data = new HashEntry[capacity];
+ for (int i = 0; i < size; i++) {
+ final K key = (K) in.readObject();
+ final V value = (V) in.readObject();
+ put(key, value);
+ }
+ }
+
+ // -----------------------------------------------------------------------
+ /**
+ * Clones the map without cloning the keys or values.
+ *
+ * <p>To implement <code>clone()</code>, a subclass must implement the <code>Cloneable</code>
+ * interface and make this method public.
+ *
+ * @return a shallow clone
+ * @throws InternalError if {@link AbstractMap#clone()} failed
+ */
+ @Override
+ @SuppressWarnings("unchecked")
+ protected AbstractHashedMap<K, V> clone() {
+ try {
+ final AbstractHashedMap<K, V> cloned = (AbstractHashedMap<K, V>) super.clone();
+ cloned.data = new HashEntry[data.length];
+ cloned.entrySet = null;
+ cloned.keySet = null;
+ cloned.values = null;
+ cloned.modCount = 0;
+ cloned.size = 0;
+ cloned.init();
+ cloned.putAll(this);
+ return cloned;
+ } catch (final CloneNotSupportedException ex) {
+ throw new InternalError();
+ }
+ }
+
+ /**
+ * Compares this map with another.
+ *
+ * @param obj the object to compare to
+ * @return true if equal
+ */
+ @Override
+ public boolean equals(final Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (obj instanceof Map == false) {
+ return false;
+ }
+ final Map<?, ?> map = (Map<?, ?>) obj;
+ if (map.size() != size()) {
+ return false;
+ }
+ final MapIterator<?, ?> it = mapIterator();
+ try {
+ while (it.hasNext()) {
+ final Object key = it.next();
+ final Object value = it.getValue();
+ if (value == null) {
+ if (map.get(key) != null || map.containsKey(key) == false) {
+ return false;
+ }
+ } else {
+ if (value.equals(map.get(key)) == false) {
+ return false;
+ }
+ }
+ }
+ } catch (final ClassCastException ignored) {
+ return false;
+ } catch (final NullPointerException ignored) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Gets the standard Map hashCode.
+ *
+ * @return the hash code defined in the Map interface
+ */
+ @Override
+ public int hashCode() {
+ int total = 0;
+ final Iterator<Map.Entry<K, V>> it = createEntrySetIterator();
+ while (it.hasNext()) {
+ total += it.next().hashCode();
+ }
+ return total;
+ }
+
+ /**
+ * Gets the map as a String.
+ *
+ * @return a string version of the map
+ */
+ @Override
+ public String toString() {
+ if (size() == 0) {
+ return "{}";
+ }
+ final StringBuilder buf = new StringBuilder(32 * size());
+ buf.append('{');
+
+ final MapIterator<K, V> it = mapIterator();
+ boolean hasNext = it.hasNext();
+ while (hasNext) {
+ final K key = it.next();
+ final V value = it.getValue();
+ buf.append(key == this ? "(this Map)" : key)
+ .append('=')
+ .append(value == this ? "(this Map)" : value);
+
+ hasNext = it.hasNext();
+ if (hasNext) {
+ buf.append(',').append(' ');
+ }
+ }
+
+ buf.append('}');
+ return buf.toString();
+ }
+}
diff --git a/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/map/AbstractLinkedMap.java b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/map/AbstractLinkedMap.java
new file mode 100644
index 00000000..a60e670b
--- /dev/null
+++ b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/map/AbstractLinkedMap.java
@@ -0,0 +1,610 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tsfile.external.commons.collections4.map;
+
+import org.apache.tsfile.external.commons.collections4.OrderedIterator;
+import org.apache.tsfile.external.commons.collections4.OrderedMap;
+import org.apache.tsfile.external.commons.collections4.OrderedMapIterator;
+import org.apache.tsfile.external.commons.collections4.ResettableIterator;
+import org.apache.tsfile.external.commons.collections4.iterators.EmptyOrderedIterator;
+import org.apache.tsfile.external.commons.collections4.iterators.EmptyOrderedMapIterator;
+
+import java.util.ConcurrentModificationException;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NoSuchElementException;
+
+/**
+ * An abstract implementation of a hash-based map that links entries to create an ordered map and
+ * which provides numerous points for subclasses to override.
+ *
+ * <p>This class implements all the features necessary for a subclass linked hash-based map.
+ * Key-value entries are stored in instances of the <code>LinkEntry</code> class which can be
+ * overridden and replaced. The iterators can similarly be replaced, without the need to replace the
+ * KeySet, EntrySet and Values view classes.
+ *
+ * <p>Overridable methods are provided to change the default hashing behaviour, and to change how
+ * entries are added to and removed from the map. Hopefully, all you need for unusual subclasses is
+ * here.
+ *
+ * <p>This implementation maintains order by original insertion, but subclasses may work
+ * differently. The <code>OrderedMap</code> interface is implemented to provide access to
+ * bidirectional iteration and extra convenience methods.
+ *
+ * <p>The <code>orderedMapIterator()</code> method provides direct access to a bidirectional
+ * iterator. The iterators from the other views can also be cast to <code>OrderedIterator</code> if
+ * required.
+ *
+ * <p>All the available iterators can be reset back to the start by casting to <code>
+ * ResettableIterator</code> and calling <code>reset()</code>.
+ *
+ * <p>The implementation is also designed to be subclassed, with lots of useful methods exposed.
+ *
+ * @param <K> the type of the keys in this map
+ * @param <V> the type of the values in this map
+ * @since 3.0
+ */
+public abstract class AbstractLinkedMap<K, V> extends AbstractHashedMap<K, V>
+ implements OrderedMap<K, V> {
+
+ /** Header in the linked list */
+ transient LinkEntry<K, V> header;
+
+ /** Constructor only used in deserialization, do not use otherwise. */
+ protected AbstractLinkedMap() {
+ super();
+ }
+
+ /**
+ * Constructor which performs no validation on the passed in parameters.
+ *
+ * @param initialCapacity the initial capacity, must be a power of two
+ * @param loadFactor the load factor, must be > 0.0f and generally < 1.0f
+ * @param threshold the threshold, must be sensible
+ */
+ protected AbstractLinkedMap(
+ final int initialCapacity, final float loadFactor, final int threshold) {
+ super(initialCapacity, loadFactor, threshold);
+ }
+
+ /**
+ * Constructs a new, empty map with the specified initial capacity.
+ *
+ * @param initialCapacity the initial capacity
+ * @throws IllegalArgumentException if the initial capacity is negative
+ */
+ protected AbstractLinkedMap(final int initialCapacity) {
+ super(initialCapacity);
+ }
+
+ /**
+ * Constructs a new, empty map with the specified initial capacity and load factor.
+ *
+ * @param initialCapacity the initial capacity
+ * @param loadFactor the load factor
+ * @throws IllegalArgumentException if the initial capacity is negative
+ * @throws IllegalArgumentException if the load factor is less than zero
+ */
+ protected AbstractLinkedMap(final int initialCapacity, final float loadFactor) {
+ super(initialCapacity, loadFactor);
+ }
+
+ /**
+ * Constructor copying elements from another map.
+ *
+ * @param map the map to copy
+ * @throws NullPointerException if the map is null
+ */
+ protected AbstractLinkedMap(final Map<? extends K, ? extends V> map) {
+ super(map);
+ }
+
+ /**
+ * Initialise this subclass during construction.
+ *
+ * <p>NOTE: As from v3.2 this method calls {@link #createEntry(HashEntry, int, Object, Object)} to
+ * create the map entry object.
+ */
+ @Override
+ protected void init() {
+ header = createEntry(null, -1, null, null);
+ header.before = header.after = header;
+ }
+
+ // -----------------------------------------------------------------------
+ /**
+ * Checks whether the map contains the specified value.
+ *
+ * @param value the value to search for
+ * @return true if the map contains the value
+ */
+ @Override
+ public boolean containsValue(final Object value) {
+ // override uses faster iterator
+ if (value == null) {
+ for (LinkEntry<K, V> entry = header.after; entry != header; entry = entry.after) {
+ if (entry.getValue() == null) {
+ return true;
+ }
+ }
+ } else {
+ for (LinkEntry<K, V> entry = header.after; entry != header; entry = entry.after) {
+ if (isEqualValue(value, entry.getValue())) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Clears the map, resetting the size to zero and nullifying references to avoid garbage
+ * collection issues.
+ */
+ @Override
+ public void clear() {
+ // override to reset the linked list
+ super.clear();
+ header.before = header.after = header;
+ }
+
+ // -----------------------------------------------------------------------
+ /**
+ * Gets the first key in the map, which is the first inserted.
+ *
+ * @return the eldest key
+ */
+ @Override
+ public K firstKey() {
+ if (size == 0) {
+ throw new NoSuchElementException("Map is empty");
+ }
+ return header.after.getKey();
+ }
+
+ /**
+ * Gets the last key in the map, which is the most recently inserted.
+ *
+ * @return the most recently inserted key
+ */
+ @Override
+ public K lastKey() {
+ if (size == 0) {
+ throw new NoSuchElementException("Map is empty");
+ }
+ return header.before.getKey();
+ }
+
+ /**
+ * Gets the next key in sequence.
+ *
+ * @param key the key to get after
+ * @return the next key
+ */
+ @Override
+ public K nextKey(final Object key) {
+ final LinkEntry<K, V> entry = getEntry(key);
+ return entry == null || entry.after == header ? null : entry.after.getKey();
+ }
+
+ @Override
+ protected LinkEntry<K, V> getEntry(final Object key) {
+ return (LinkEntry<K, V>) super.getEntry(key);
+ }
+
+ /**
+ * Gets the previous key in sequence.
+ *
+ * @param key the key to get before
+ * @return the previous key
+ */
+ @Override
+ public K previousKey(final Object key) {
+ final LinkEntry<K, V> entry = getEntry(key);
+ return entry == null || entry.before == header ? null : entry.before.getKey();
+ }
+
+ // -----------------------------------------------------------------------
+ /**
+ * Gets the key at the specified index.
+ *
+ * @param index the index to retrieve
+ * @return the key at the specified index
+ * @throws IndexOutOfBoundsException if the index is invalid
+ */
+ protected LinkEntry<K, V> getEntry(final int index) {
+ if (index < 0) {
+ throw new IndexOutOfBoundsException("Index " + index + " is less than zero");
+ }
+ if (index >= size) {
+ throw new IndexOutOfBoundsException("Index " + index + " is invalid for size " + size);
+ }
+ LinkEntry<K, V> entry;
+ if (index < size / 2) {
+ // Search forwards
+ entry = header.after;
+ for (int currentIndex = 0; currentIndex < index; currentIndex++) {
+ entry = entry.after;
+ }
+ } else {
+ // Search backwards
+ entry = header;
+ for (int currentIndex = size; currentIndex > index; currentIndex--) {
+ entry = entry.before;
+ }
+ }
+ return entry;
+ }
+
+ /**
+ * Adds an entry into this map, maintaining insertion order.
+ *
+ * <p>This implementation adds the entry to the data storage table and to the end of the linked
+ * list.
+ *
+ * @param entry the entry to add
+ * @param hashIndex the index into the data array to store at
+ */
+ @Override
+ protected void addEntry(final HashEntry<K, V> entry, final int hashIndex) {
+ final LinkEntry<K, V> link = (LinkEntry<K, V>) entry;
+ link.after = header;
+ link.before = header.before;
+ header.before.after = link;
+ header.before = link;
+ data[hashIndex] = link;
+ }
+
+ /**
+ * Creates an entry to store the data.
+ *
+ * <p>This implementation creates a new LinkEntry instance.
+ *
+ * @param next the next entry in sequence
+ * @param hashCode the hash code to use
+ * @param key the key to store
+ * @param value the value to store
+ * @return the newly created entry
+ */
+ @Override
+ protected LinkEntry<K, V> createEntry(
+ final HashEntry<K, V> next, final int hashCode, final K key, final V value) {
+ return new LinkEntry<>(next, hashCode, convertKey(key), value);
+ }
+
+ /**
+ * Removes an entry from the map and the linked list.
+ *
+ * <p>This implementation removes the entry from the linked list chain, then calls the superclass
+ * implementation.
+ *
+ * @param entry the entry to remove
+ * @param hashIndex the index into the data structure
+ * @param previous the previous entry in the chain
+ */
+ @Override
+ protected void removeEntry(
+ final HashEntry<K, V> entry, final int hashIndex, final HashEntry<K, V> previous) {
+ final LinkEntry<K, V> link = (LinkEntry<K, V>) entry;
+ link.before.after = link.after;
+ link.after.before = link.before;
+ link.after = null;
+ link.before = null;
+ super.removeEntry(entry, hashIndex, previous);
+ }
+
+ // -----------------------------------------------------------------------
+ /**
+ * Gets the <code>before</code> field from a <code>LinkEntry</code>. Used in subclasses that have
+ * no visibility of the field.
+ *
+ * @param entry the entry to query, must not be null
+ * @return the <code>before</code> field of the entry
+ * @throws NullPointerException if the entry is null
+ * @since 3.1
+ */
+ protected LinkEntry<K, V> entryBefore(final LinkEntry<K, V> entry) {
+ return entry.before;
+ }
+
+ /**
+ * Gets the <code>after</code> field from a <code>LinkEntry</code>. Used in subclasses that have
+ * no visibility of the field.
+ *
+ * @param entry the entry to query, must not be null
+ * @return the <code>after</code> field of the entry
+ * @throws NullPointerException if the entry is null
+ * @since 3.1
+ */
+ protected LinkEntry<K, V> entryAfter(final LinkEntry<K, V> entry) {
+ return entry.after;
+ }
+
+ // -----------------------------------------------------------------------
+ /** {@inheritDoc} */
+ @Override
+ public OrderedMapIterator<K, V> mapIterator() {
+ if (size == 0) {
+ return EmptyOrderedMapIterator.<K, V>emptyOrderedMapIterator();
+ }
+ return new LinkMapIterator<>(this);
+ }
+
+ /** MapIterator implementation. */
+ protected static class LinkMapIterator<K, V> extends LinkIterator<K, V>
+ implements OrderedMapIterator<K, V>, ResettableIterator<K> {
+
+ protected LinkMapIterator(final AbstractLinkedMap<K, V> parent) {
+ super(parent);
+ }
+
+ @Override
+ public K next() {
+ return super.nextEntry().getKey();
+ }
+
+ @Override
+ public K previous() {
+ return super.previousEntry().getKey();
+ }
+
+ @Override
+ public K getKey() {
+ final LinkEntry<K, V> current = currentEntry();
+ if (current == null) {
+ throw new IllegalStateException(AbstractHashedMap.GETKEY_INVALID);
+ }
+ return current.getKey();
+ }
+
+ @Override
+ public V getValue() {
+ final LinkEntry<K, V> current = currentEntry();
+ if (current == null) {
+ throw new IllegalStateException(AbstractHashedMap.GETVALUE_INVALID);
+ }
+ return current.getValue();
+ }
+
+ @Override
+ public V setValue(final V value) {
+ final LinkEntry<K, V> current = currentEntry();
+ if (current == null) {
+ throw new IllegalStateException(AbstractHashedMap.SETVALUE_INVALID);
+ }
+ return current.setValue(value);
+ }
+ }
+
+ // -----------------------------------------------------------------------
+ /**
+ * Creates an entry set iterator. Subclasses can override this to return iterators with different
+ * properties.
+ *
+ * @return the entrySet iterator
+ */
+ @Override
+ protected Iterator<Map.Entry<K, V>> createEntrySetIterator() {
+ if (size() == 0) {
+ return EmptyOrderedIterator.<Map.Entry<K, V>>emptyOrderedIterator();
+ }
+ return new EntrySetIterator<>(this);
+ }
+
+ /** EntrySet iterator. */
+ protected static class EntrySetIterator<K, V> extends LinkIterator<K, V>
+ implements OrderedIterator<Map.Entry<K, V>>, ResettableIterator<Map.Entry<K, V>> {
+
+ protected EntrySetIterator(final AbstractLinkedMap<K, V> parent) {
+ super(parent);
+ }
+
+ @Override
+ public Map.Entry<K, V> next() {
+ return super.nextEntry();
+ }
+
+ @Override
+ public Map.Entry<K, V> previous() {
+ return super.previousEntry();
+ }
+ }
+
+ // -----------------------------------------------------------------------
+ /**
+ * Creates a key set iterator. Subclasses can override this to return iterators with different
+ * properties.
+ *
+ * @return the keySet iterator
+ */
+ @Override
+ protected Iterator<K> createKeySetIterator() {
+ if (size() == 0) {
+ return EmptyOrderedIterator.<K>emptyOrderedIterator();
+ }
+ return new KeySetIterator<>(this);
+ }
+
+ /** KeySet iterator. */
+ protected static class KeySetIterator<K> extends LinkIterator<K, Object>
+ implements OrderedIterator<K>, ResettableIterator<K> {
+
+ @SuppressWarnings("unchecked")
+ protected KeySetIterator(final AbstractLinkedMap<K, ?> parent) {
+ super((AbstractLinkedMap<K, Object>) parent);
+ }
+
+ @Override
+ public K next() {
+ return super.nextEntry().getKey();
+ }
+
+ @Override
+ public K previous() {
+ return super.previousEntry().getKey();
+ }
+ }
+
+ // -----------------------------------------------------------------------
+ /**
+ * Creates a values iterator. Subclasses can override this to return iterators with different
+ * properties.
+ *
+ * @return the values iterator
+ */
+ @Override
+ protected Iterator<V> createValuesIterator() {
+ if (size() == 0) {
+ return EmptyOrderedIterator.<V>emptyOrderedIterator();
+ }
+ return new ValuesIterator<>(this);
+ }
+
+ /** Values iterator. */
+ protected static class ValuesIterator<V> extends LinkIterator<Object, V>
+ implements OrderedIterator<V>, ResettableIterator<V> {
+
+ @SuppressWarnings("unchecked")
+ protected ValuesIterator(final AbstractLinkedMap<?, V> parent) {
+ super((AbstractLinkedMap<Object, V>) parent);
+ }
+
+ @Override
+ public V next() {
+ return super.nextEntry().getValue();
+ }
+
+ @Override
+ public V previous() {
+ return super.previousEntry().getValue();
+ }
+ }
+
+ // -----------------------------------------------------------------------
+ /**
+ * LinkEntry that stores the data.
+ *
+ * <p>If you subclass <code>AbstractLinkedMap</code> but not <code>LinkEntry</code> then you will
+ * not be able to access the protected fields. The <code>entryXxx()</code> methods on <code>
+ * AbstractLinkedMap</code> exist to provide the necessary access.
+ */
+ protected static class LinkEntry<K, V> extends HashEntry<K, V> {
+ /** The entry before this one in the order */
+ protected LinkEntry<K, V> before;
+
+ /** The entry after this one in the order */
+ protected LinkEntry<K, V> after;
+
+ /**
+ * Constructs a new entry.
+ *
+ * @param next the next entry in the hash bucket sequence
+ * @param hashCode the hash code
+ * @param key the key
+ * @param value the value
+ */
+ protected LinkEntry(
+ final HashEntry<K, V> next, final int hashCode, final Object key, final V value) {
+ super(next, hashCode, key, value);
+ }
+ }
+
+ /** Base Iterator that iterates in link order. */
+ protected abstract static class LinkIterator<K, V> {
+
+ /** The parent map */
+ protected final AbstractLinkedMap<K, V> parent;
+
+ /** The current (last returned) entry */
+ protected LinkEntry<K, V> last;
+
+ /** The next entry */
+ protected LinkEntry<K, V> next;
+
+ /** The modification count expected */
+ protected int expectedModCount;
+
+ protected LinkIterator(final AbstractLinkedMap<K, V> parent) {
+ super();
+ this.parent = parent;
+ this.next = parent.header.after;
+ this.expectedModCount = parent.modCount;
+ }
+
+ public boolean hasNext() {
+ return next != parent.header;
+ }
+
+ public boolean hasPrevious() {
+ return next.before != parent.header;
+ }
+
+ protected LinkEntry<K, V> nextEntry() {
+ if (parent.modCount != expectedModCount) {
+ throw new ConcurrentModificationException();
+ }
+ if (next == parent.header) {
+ throw new NoSuchElementException(AbstractHashedMap.NO_NEXT_ENTRY);
+ }
+ last = next;
+ next = next.after;
+ return last;
+ }
+
+ protected LinkEntry<K, V> previousEntry() {
+ if (parent.modCount != expectedModCount) {
+ throw new ConcurrentModificationException();
+ }
+ final LinkEntry<K, V> previous = next.before;
+ if (previous == parent.header) {
+ throw new NoSuchElementException(AbstractHashedMap.NO_PREVIOUS_ENTRY);
+ }
+ next = previous;
+ last = previous;
+ return last;
+ }
+
+ protected LinkEntry<K, V> currentEntry() {
+ return last;
+ }
+
+ public void remove() {
+ if (last == null) {
+ throw new IllegalStateException(AbstractHashedMap.REMOVE_INVALID);
+ }
+ if (parent.modCount != expectedModCount) {
+ throw new ConcurrentModificationException();
+ }
+ parent.remove(last.getKey());
+ last = null;
+ expectedModCount = parent.modCount;
+ }
+
+ public void reset() {
+ last = null;
+ next = parent.header.after;
+ }
+
+ @Override
+ public String toString() {
+ if (last != null) {
+ return "Iterator[" + last.getKey() + "=" + last.getValue() + "]";
+ }
+ return "Iterator[]";
+ }
+ }
+}
diff --git a/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/map/LRUMap.java b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/map/LRUMap.java
new file mode 100644
index 00000000..4ae916a6
--- /dev/null
+++ b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/collections4/map/LRUMap.java
@@ -0,0 +1,573 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tsfile.external.commons.collections4.map;
+
+import org.apache.tsfile.external.commons.collections4.BoundedMap;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.util.Map;
+
+/**
+ * A <code>Map</code> implementation with a fixed maximum size which removes the least recently used
+ * entry if an entry is added when full.
+ *
+ * <p>The least recently used algorithm works on the get and put operations only. Iteration of any
+ * kind, including setting the value by iteration, does not change the order. Queries such as
+ * containsKey and containsValue or access via views also do not change the order.
+ *
+ * <p>A somewhat subtle ramification of the least recently used algorithm is that calls to {@link
+ * #get(Object)} stand a very good chance of modifying the map's iteration order and thus
+ * invalidating any iterators currently in use. It is therefore suggested that iterations over an
+ * {@link LRUMap} instance access entry values only through a {@link
+ * org.apache.tsfile.external.commons.collections4.MapIterator MapIterator} or {@link #entrySet()}
+ * iterator.
+ *
+ * <p>The map implements <code>OrderedMap</code> and entries may be queried using the bidirectional
+ * <code>OrderedMapIterator</code>. The order returned is least recently used to most recently used.
+ * Iterators from map views can also be cast to <code>OrderedIterator</code> if required.
+ *
+ * <p>All the available iterators can be reset back to the start by casting to <code>
+ * ResettableIterator</code> and calling <code>reset()</code>.
+ *
+ * <p><strong>Note that LRUMap is not synchronized and is not thread-safe.</strong> If you wish to
+ * use this map from multiple threads concurrently, you must use appropriate synchronization. The
+ * simplest approach is to wrap this map using {@link java.util.Collections#synchronizedMap(Map)}.
+ * This class may throw <code>NullPointerException</code>'s when accessed by concurrent threads.
+ *
+ * @param <K> the type of the keys in this map
+ * @param <V> the type of the values in this map
+ * @since 3.0 (previously in main package v1.0)
+ */
+public class LRUMap<K, V> extends AbstractLinkedMap<K, V>
+ implements BoundedMap<K, V>, Serializable, Cloneable {
+
+ /** Serialisation version */
+ private static final long serialVersionUID = -612114643488955218L;
+
+ /** Default maximum size */
+ protected static final int DEFAULT_MAX_SIZE = 100;
+
+ /** Maximum size */
+ private transient int maxSize;
+
+ /** Scan behaviour */
+ private boolean scanUntilRemovable;
+
+ /** Constructs a new empty map with a maximum size of 100. */
+ public LRUMap() {
+ this(DEFAULT_MAX_SIZE, DEFAULT_LOAD_FACTOR, false);
+ }
+
+ /**
+ * Constructs a new, empty map with the specified maximum size.
+ *
+ * @param maxSize the maximum size of the map
+ * @throws IllegalArgumentException if the maximum size is less than one
+ */
+ public LRUMap(final int maxSize) {
+ this(maxSize, DEFAULT_LOAD_FACTOR);
+ }
+
+ /**
+ * Constructs a new, empty map with the specified maximum size.
+ *
+ * @param maxSize the maximum size of the map
+ * @param initialSize the initial size of the map
+ * @throws IllegalArgumentException if the maximum size is less than one
+ * @throws IllegalArgumentException if the initial size is negative or larger than the maximum
+ * size
+ * @since 4.1
+ */
+ public LRUMap(final int maxSize, final int initialSize) {
+ this(maxSize, initialSize, DEFAULT_LOAD_FACTOR);
+ }
+
+ /**
+ * Constructs a new, empty map with the specified maximum size.
+ *
+ * @param maxSize the maximum size of the map
+ * @param scanUntilRemovable scan until a removeable entry is found, default false
+ * @throws IllegalArgumentException if the maximum size is less than one
+ * @since 3.1
+ */
+ public LRUMap(final int maxSize, final boolean scanUntilRemovable) {
+ this(maxSize, DEFAULT_LOAD_FACTOR, scanUntilRemovable);
+ }
+
+ /**
+ * Constructs a new, empty map with the specified max capacity and load factor.
+ *
+ * @param maxSize the maximum size of the map
+ * @param loadFactor the load factor
+ * @throws IllegalArgumentException if the maximum size is less than one
+ * @throws IllegalArgumentException if the load factor is less than zero
+ */
+ public LRUMap(final int maxSize, final float loadFactor) {
+ this(maxSize, loadFactor, false);
+ }
+
+ /**
+ * Constructs a new, empty map with the specified max / initial capacity and load factor.
+ *
+ * @param maxSize the maximum size of the map
+ * @param initialSize the initial size of the map
+ * @param loadFactor the load factor
+ * @throws IllegalArgumentException if the maximum size is less than one
+ * @throws IllegalArgumentException if the initial size is negative or larger than the maximum
+ * size
+ * @throws IllegalArgumentException if the load factor is less than zero
+ * @since 4.1
+ */
+ public LRUMap(final int maxSize, final int initialSize, final float loadFactor) {
+ this(maxSize, initialSize, loadFactor, false);
+ }
+
+ /**
+ * Constructs a new, empty map with the specified max capacity and load factor.
+ *
+ * @param maxSize the maximum size of the map
+ * @param loadFactor the load factor
+ * @param scanUntilRemovable scan until a removeable entry is found, default false
+ * @throws IllegalArgumentException if the maximum size is less than one
+ * @throws IllegalArgumentException if the load factor is less than zero
+ * @since 3.1
+ */
+ public LRUMap(final int maxSize, final float loadFactor, final boolean scanUntilRemovable) {
+ this(maxSize, maxSize, loadFactor, scanUntilRemovable);
+ }
+
+ /**
+ * Constructs a new, empty map with the specified max / initial capacity and load factor.
+ *
+ * @param maxSize the maximum size of the map
+ * @param initialSize the initial size of the map
+ * @param loadFactor the load factor
+ * @param scanUntilRemovable scan until a removeable entry is found, default false
+ * @throws IllegalArgumentException if the maximum size is less than one
+ * @throws IllegalArgumentException if the initial size is negative or larger than the maximum
+ * size
+ * @throws IllegalArgumentException if the load factor is less than zero
+ * @since 4.1
+ */
+ public LRUMap(
+ final int maxSize,
+ final int initialSize,
+ final float loadFactor,
+ final boolean scanUntilRemovable) {
+
+ super(initialSize, loadFactor);
+ if (maxSize < 1) {
+ throw new IllegalArgumentException("LRUMap max size must be greater than 0");
+ }
+ if (initialSize > maxSize) {
+ throw new IllegalArgumentException("LRUMap initial size must not be greather than max size");
+ }
+ this.maxSize = maxSize;
+ this.scanUntilRemovable = scanUntilRemovable;
+ }
+
+ /**
+ * Constructor copying elements from another map.
+ *
+ * <p>The maximum size is set from the map's size.
+ *
+ * @param map the map to copy
+ * @throws NullPointerException if the map is null
+ * @throws IllegalArgumentException if the map is empty
+ */
+ public LRUMap(final Map<? extends K, ? extends V> map) {
+ this(map, false);
+ }
+
+ /**
+ * Constructor copying elements from another map.
+ *
+ * <p>The maximum size is set from the map's size.
+ *
+ * @param map the map to copy
+ * @param scanUntilRemovable scan until a removeable entry is found, default false
+ * @throws NullPointerException if the map is null
+ * @throws IllegalArgumentException if the map is empty
+ * @since 3.1
+ */
+ public LRUMap(final Map<? extends K, ? extends V> map, final boolean scanUntilRemovable) {
+ this(map.size(), DEFAULT_LOAD_FACTOR, scanUntilRemovable);
+ putAll(map);
+ }
+
+ // -----------------------------------------------------------------------
+ /**
+ * Gets the value mapped to the key specified.
+ *
+ * <p>This operation changes the position of the key in the map to the most recently used position
+ * (last).
+ *
+ * @param key the key
+ * @return the mapped value, null if no match
+ */
+ @Override
+ public V get(final Object key) {
+ return get(key, true);
+ }
+
+ /**
+ * Gets the value mapped to the key specified.
+ *
+ * <p>If {@code updateToMRU} is {@code true}, the position of the key in the map is changed to the
+ * most recently used position (last), otherwise the iteration order is not changed by this
+ * operation.
+ *
+ * @param key the key
+ * @param updateToMRU whether the key shall be updated to the most recently used position
+ * @return the mapped value, null if no match
+ * @since 4.1
+ */
+ public V get(final Object key, final boolean updateToMRU) {
+ final LinkEntry<K, V> entry = getEntry(key);
+ if (entry == null) {
+ return null;
+ }
+ if (updateToMRU) {
+ moveToMRU(entry);
+ }
+ return entry.getValue();
+ }
+
+ // -----------------------------------------------------------------------
+ /**
+ * Moves an entry to the MRU position at the end of the list.
+ *
+ * <p>This implementation moves the updated entry to the end of the list.
+ *
+ * @param entry the entry to update
+ */
+ protected void moveToMRU(final LinkEntry<K, V> entry) {
+ if (entry.after != header) {
+ modCount++;
+ // remove
+ if (entry.before == null) {
+ throw new IllegalStateException(
+ "Entry.before is null."
+ + " This should not occur if your keys are immutable, and you have used synchronization properly.");
+ }
+ entry.before.after = entry.after;
+ entry.after.before = entry.before;
+ // add first
+ entry.after = header;
+ entry.before = header.before;
+ header.before.after = entry;
+ header.before = entry;
+ } else if (entry == header) {
+ throw new IllegalStateException(
+ "Can't move header to MRU"
+ + " This should not occur if your keys are immutable, and you have used synchronization properly.");
+ }
+ }
+
+ /**
+ * Updates an existing key-value mapping.
+ *
+ * <p>This implementation moves the updated entry to the end of the list using {@link
+ * #moveToMRU(AbstractLinkedMap.LinkEntry)}.
+ *
+ * @param entry the entry to update
+ * @param newValue the new value to store
+ */
+ @Override
+ protected void updateEntry(final HashEntry<K, V> entry, final V newValue) {
+ moveToMRU((LinkEntry<K, V>) entry); // handles modCount
+ entry.setValue(newValue);
+ }
+
+ /**
+ * Adds a new key-value mapping into this map.
+ *
+ * <p>This implementation checks the LRU size and determines whether to discard an entry or not
+ * using {@link #removeLRU(AbstractLinkedMap.LinkEntry)}.
+ *
+ * <p>From Commons Collections 3.1 this method uses {@link #isFull()} rather than accessing <code>
+ * size</code> and <code>maxSize</code> directly. It also handles the scanUntilRemovable
+ * functionality.
+ *
+ * @param hashIndex the index into the data array to store at
+ * @param hashCode the hash code of the key to add
+ * @param key the key to add
+ * @param value the value to add
+ */
+ @Override
+ protected void addMapping(final int hashIndex, final int hashCode, final K key, final V value) {
+ if (isFull()) {
+ LinkEntry<K, V> reuse = header.after;
+ boolean removeLRUEntry = false;
+ if (scanUntilRemovable) {
+ while (reuse != header && reuse != null) {
+ if (removeLRU(reuse)) {
+ removeLRUEntry = true;
+ break;
+ }
+ reuse = reuse.after;
+ }
+ if (reuse == null) {
+ throw new IllegalStateException(
+ "Entry.after=null, header.after="
+ + header.after
+ + " header.before="
+ + header.before
+ + " key="
+ + key
+ + " value="
+ + value
+ + " size="
+ + size
+ + " maxSize="
+ + maxSize
+ + " This should not occur if your keys are immutable, and you have used synchronization properly.");
+ }
+ } else {
+ removeLRUEntry = removeLRU(reuse);
+ }
+
+ if (removeLRUEntry) {
+ if (reuse == null) {
+ throw new IllegalStateException(
+ "reuse=null, header.after="
+ + header.after
+ + " header.before="
+ + header.before
+ + " key="
+ + key
+ + " value="
+ + value
+ + " size="
+ + size
+ + " maxSize="
+ + maxSize
+ + " This should not occur if your keys are immutable, and you have used synchronization properly.");
+ }
+ reuseMapping(reuse, hashIndex, hashCode, key, value);
+ } else {
+ super.addMapping(hashIndex, hashCode, key, value);
+ }
+ } else {
+ super.addMapping(hashIndex, hashCode, key, value);
+ }
+ }
+
+ /**
+ * Reuses an entry by removing it and moving it to a new place in the map.
+ *
+ * <p>This method uses {@link #removeEntry}, {@link #reuseEntry} and {@link #addEntry}.
+ *
+ * @param entry the entry to reuse
+ * @param hashIndex the index into the data array to store at
+ * @param hashCode the hash code of the key to add
+ * @param key the key to add
+ * @param value the value to add
+ */
+ protected void reuseMapping(
+ final LinkEntry<K, V> entry,
+ final int hashIndex,
+ final int hashCode,
+ final K key,
+ final V value) {
+ // find the entry before the entry specified in the hash table
+ // remember that the parameters (except the first) refer to the new entry,
+ // not the old one
+ try {
+ final int removeIndex = hashIndex(entry.hashCode, data.length);
+ final HashEntry<K, V>[] tmp = data; // may protect against some sync issues
+ HashEntry<K, V> loop = tmp[removeIndex];
+ HashEntry<K, V> previous = null;
+ while (loop != entry && loop != null) {
+ previous = loop;
+ loop = loop.next;
+ }
+ if (loop == null) {
+ throw new IllegalStateException(
+ "Entry.next=null, data[removeIndex]="
+ + data[removeIndex]
+ + " previous="
+ + previous
+ + " key="
+ + key
+ + " value="
+ + value
+ + " size="
+ + size
+ + " maxSize="
+ + maxSize
+ + " This should not occur if your keys are immutable, and you have used synchronization properly.");
+ }
+
+ // reuse the entry
+ modCount++;
+ removeEntry(entry, removeIndex, previous);
+ reuseEntry(entry, hashIndex, hashCode, key, value);
+ addEntry(entry, hashIndex);
+ } catch (final NullPointerException ex) {
+ throw new IllegalStateException(
+ "NPE, entry="
+ + entry
+ + " entryIsHeader="
+ + (entry == header)
+ + " key="
+ + key
+ + " value="
+ + value
+ + " size="
+ + size
+ + " maxSize="
+ + maxSize
+ + " This should not occur if your keys are immutable, and you have used synchronization properly.");
+ }
+ }
+
+ /**
+ * Subclass method to control removal of the least recently used entry from the map.
+ *
+ * <p>This method exists for subclasses to override. A subclass may wish to provide cleanup of
+ * resources when an entry is removed. For example:
+ *
+ * <pre>
+ * protected boolean removeLRU(LinkEntry entry) {
+ * releaseResources(entry.getValue()); // release resources held by entry
+ * return true; // actually delete entry
+ * }
+ * </pre>
+ *
+ * <p>Alternatively, a subclass may choose to not remove the entry or selectively keep certain LRU
+ * entries. For example:
+ *
+ * <pre>
+ * protected boolean removeLRU(LinkEntry entry) {
+ * if (entry.getKey().toString().startsWith("System.")) {
+ * return false; // entry not removed from LRUMap
+ * } else {
+ * return true; // actually delete entry
+ * }
+ * }
+ * </pre>
+ *
+ * The effect of returning false is dependent on the scanUntilRemovable flag. If the flag is true,
+ * the next LRU entry will be passed to this method and so on until one returns false and is
+ * removed, or every entry in the map has been passed. If the scanUntilRemovable flag is false,
+ * the map will exceed the maximum size.
+ *
+ * <p>NOTE: Commons Collections 3.0 passed the wrong entry to this method. This is fixed in
+ * version 3.1 onwards.
+ *
+ * @param entry the entry to be removed
+ * @return {@code true}
+ */
+ protected boolean removeLRU(final LinkEntry<K, V> entry) {
+ return true;
+ }
+
+ // -----------------------------------------------------------------------
+ /**
+ * Returns true if this map is full and no new mappings can be added.
+ *
+ * @return <code>true</code> if the map is full
+ */
+ @Override
+ public boolean isFull() {
+ return size >= maxSize;
+ }
+
+ /**
+ * Gets the maximum size of the map (the bound).
+ *
+ * @return the maximum number of elements the map can hold
+ */
+ @Override
+ public int maxSize() {
+ return maxSize;
+ }
+
+ /**
+ * Whether this LRUMap will scan until a removable entry is found when the map is full.
+ *
+ * @return true if this map scans
+ * @since 3.1
+ */
+ public boolean isScanUntilRemovable() {
+ return scanUntilRemovable;
+ }
+
+ // -----------------------------------------------------------------------
+ /**
+ * Clones the map without cloning the keys or values.
+ *
+ * @return a shallow clone
+ */
+ @Override
+ public LRUMap<K, V> clone() {
+ return (LRUMap<K, V>) super.clone();
+ }
+
+ /**
+ * Write the map out using a custom routine.
+ *
+ * @param out the output stream
+ * @throws IOException if an error occurs while writing to the stream
+ */
+ private void writeObject(final ObjectOutputStream out) throws IOException {
+ out.defaultWriteObject();
+ doWriteObject(out);
+ }
+
+ /**
+ * Read the map in using a custom routine.
+ *
+ * @param in the input stream
+ * @throws IOException if an error occurs while reading from the stream
+ * @throws ClassNotFoundException if an object read from the stream can not be loaded
+ */
+ private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
+ in.defaultReadObject();
+ doReadObject(in);
+ }
+
+ /**
+ * Writes the data necessary for <code>put()</code> to work in deserialization.
+ *
+ * @param out the output stream
+ * @throws IOException if an error occurs while writing to the stream
+ */
+ @Override
+ protected void doWriteObject(final ObjectOutputStream out) throws IOException {
+ out.writeInt(maxSize);
+ super.doWriteObject(out);
+ }
+
+ /**
+ * Reads the data necessary for <code>put()</code> to work in the superclass.
+ *
+ * @param in the input stream
+ * @throws IOException if an error occurs while reading from the stream
+ * @throws ClassNotFoundException if an object read from the stream can not be loaded
+ */
+ @Override
+ protected void doReadObject(final ObjectInputStream in)
+ throws IOException, ClassNotFoundException {
+ maxSize = in.readInt();
+ super.doReadObject(in);
+ }
+}
diff --git a/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/Charsets.java b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/Charsets.java
new file mode 100644
index 00000000..2429385f
--- /dev/null
+++ b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/Charsets.java
@@ -0,0 +1,211 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tsfile.external.commons.io;
+
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.charset.UnsupportedCharsetException;
+import java.util.Collections;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+/**
+ * Charsets required of every implementation of the Java platform.
+ *
+ * <p>From the Java documentation <a
+ * href="https://docs.oracle.com/javase/7/docs/api/java/nio/charset/Charset.html">Standard
+ * charsets</a>:
+ *
+ * <p><cite>Every implementation of the Java platform is required to support the following character
+ * encodings. Consult the release documentation for your implementation to see if any other
+ * encodings are supported. Consult the release documentation for your implementation to see if any
+ * other encodings are supported. </cite>
+ *
+ * <ul>
+ * <li>{@code US-ASCII}<br>
+ * Seven-bit ASCII, a.k.a. ISO646-US, a.k.a. the Basic Latin block of the Unicode character
+ * set.
+ * <li>{@code ISO-8859-1}<br>
+ * ISO Latin Alphabet No. 1, a.k.a. ISO-LATIN-1.
+ * <li>{@code UTF-8}<br>
+ * Eight-bit Unicode Transformation Format.
+ * <li>{@code UTF-16BE}<br>
+ * Sixteen-bit Unicode Transformation Format, big-endian byte order.
+ * <li>{@code UTF-16LE}<br>
+ * Sixteen-bit Unicode Transformation Format, little-endian byte order.
+ * <li>{@code UTF-16}<br>
+ * Sixteen-bit Unicode Transformation Format, byte order specified by a mandatory initial
+ * byte-order mark (either order accepted on input, big-endian used on output.)
+ * </ul>
+ *
+ * @see <a href="https://docs.oracle.com/javase/7/docs/api/java/nio/charset/Charset.html">Standard
+ * charsets</a>
+ * @since 2.3
+ */
+public class Charsets {
+
+ //
+ // This class should only contain Charset instances for required encodings. This guarantees that
+ // it will load
+ // correctly and without delay on all Java platforms.
+ //
+
+ private static final SortedMap<String, Charset> STANDARD_CHARSET_MAP;
+
+ static {
+ final SortedMap<String, Charset> standardCharsetMap =
+ new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+ standardCharsetMap.put(StandardCharsets.ISO_8859_1.name(), StandardCharsets.ISO_8859_1);
+ standardCharsetMap.put(StandardCharsets.US_ASCII.name(), StandardCharsets.US_ASCII);
+ standardCharsetMap.put(StandardCharsets.UTF_16.name(), StandardCharsets.UTF_16);
+ standardCharsetMap.put(StandardCharsets.UTF_16BE.name(), StandardCharsets.UTF_16BE);
+ standardCharsetMap.put(StandardCharsets.UTF_16LE.name(), StandardCharsets.UTF_16LE);
+ standardCharsetMap.put(StandardCharsets.UTF_8.name(), StandardCharsets.UTF_8);
+ STANDARD_CHARSET_MAP = Collections.unmodifiableSortedMap(standardCharsetMap);
+ }
+
+ /**
+ * CharEncodingISO Latin Alphabet No. 1, a.k.a. ISO-LATIN-1.
+ *
+ * <p>Every implementation of the Java platform is required to support this character encoding.
+ *
+ * @see <a href="https://docs.oracle.com/javase/7/docs/api/java/nio/charset/Charset.html">Standard
+ * charsets</a>
+ * @deprecated Use Java 7's {@link java.nio.charset.StandardCharsets}
+ */
+ @Deprecated public static final Charset ISO_8859_1 = StandardCharsets.ISO_8859_1;
+
+ /**
+ * Seven-bit ASCII, also known as ISO646-US, also known as the Basic Latin block of the Unicode
+ * character set.
+ *
+ * <p>Every implementation of the Java platform is required to support this character encoding.
+ *
+ * @see <a href="https://docs.oracle.com/javase/7/docs/api/java/nio/charset/Charset.html">Standard
+ * charsets</a>
+ * @deprecated Use Java 7's {@link java.nio.charset.StandardCharsets}
+ */
+ @Deprecated public static final Charset US_ASCII = StandardCharsets.US_ASCII;
+
+ /**
+ * Sixteen-bit Unicode Transformation Format, The byte order specified by a mandatory initial
+ * byte-order mark (either order accepted on input, big-endian used on output)
+ *
+ * <p>Every implementation of the Java platform is required to support this character encoding.
+ *
+ * @see <a href="https://docs.oracle.com/javase/7/docs/api/java/nio/charset/Charset.html">Standard
+ * charsets</a>
+ * @deprecated Use Java 7's {@link java.nio.charset.StandardCharsets}
+ */
+ @Deprecated public static final Charset UTF_16 = StandardCharsets.UTF_16;
+
+ /**
+ * Sixteen-bit Unicode Transformation Format, big-endian byte order.
+ *
+ * <p>Every implementation of the Java platform is required to support this character encoding.
+ *
+ * @see <a href="https://docs.oracle.com/javase/7/docs/api/java/nio/charset/Charset.html">Standard
+ * charsets</a>
+ * @deprecated Use Java 7's {@link java.nio.charset.StandardCharsets}
+ */
+ @Deprecated public static final Charset UTF_16BE = StandardCharsets.UTF_16BE;
+
+ /**
+ * Sixteen-bit Unicode Transformation Format, little-endian byte order.
+ *
+ * <p>Every implementation of the Java platform is required to support this character encoding.
+ *
+ * @see <a href="https://docs.oracle.com/javase/7/docs/api/java/nio/charset/Charset.html">Standard
+ * charsets</a>
+ * @deprecated Use Java 7's {@link java.nio.charset.StandardCharsets}
+ */
+ @Deprecated public static final Charset UTF_16LE = StandardCharsets.UTF_16LE;
+
+ /**
+ * Eight-bit Unicode Transformation Format.
+ *
+ * <p>Every implementation of the Java platform is required to support this character encoding.
+ *
+ * @see <a href="https://docs.oracle.com/javase/7/docs/api/java/nio/charset/Charset.html">Standard
+ * charsets</a>
+ * @deprecated Use Java 7's {@link java.nio.charset.StandardCharsets}
+ */
+ @Deprecated public static final Charset UTF_8 = StandardCharsets.UTF_8;
+
+ /**
+ * Constructs a sorted map from canonical charset names to charset objects required of every
+ * implementation of the Java platform.
+ *
+ * <p>From the Java documentation <a
+ * href="https://docs.oracle.com/javase/7/docs/api/java/nio/charset/Charset.html">Standard
+ * charsets</a>:
+ *
+ * @return An immutable, case-insensitive map from canonical charset names to charset objects.
+ * @see Charset#availableCharsets()
+ * @since 2.5
+ */
+ public static SortedMap<String, Charset> requiredCharsets() {
+ return STANDARD_CHARSET_MAP;
+ }
+
+ /**
+ * Returns the given Charset or the default Charset if the given Charset is null.
+ *
+ * @param charset A charset or null.
+ * @return the given Charset or the default Charset if the given Charset is null
+ */
+ public static Charset toCharset(final Charset charset) {
+ return charset == null ? Charset.defaultCharset() : charset;
+ }
+
+ /**
+ * Returns the given charset if non-null, otherwise return defaultCharset.
+ *
+ * @param charset The charset to test, may be null.
+ * @param defaultCharset The charset to return if charset is null, may be null.
+ * @return a Charset .
+ * @since 2.12.0
+ */
+ public static Charset toCharset(final Charset charset, final Charset defaultCharset) {
+ return charset == null ? defaultCharset : charset;
+ }
+
+ /**
+ * Returns a Charset for the named charset. If the name is null, return the default Charset.
+ *
+ * @param charsetName The name of the requested charset, may be null.
+ * @return a Charset for the named charset.
+ * @throws UnsupportedCharsetException If the named charset is unavailable (unchecked exception).
+ */
+ public static Charset toCharset(final String charsetName) throws UnsupportedCharsetException {
+ return toCharset(charsetName, Charset.defaultCharset());
+ }
+
+ /**
+ * Returns a Charset for the named charset. If the name is null, return the given default Charset.
+ *
+ * @param charsetName The name of the requested charset, may be null.
+ * @param defaultCharset The name charset to return if charsetName is null, may be null.
+ * @return a Charset for the named charset.
+ * @throws UnsupportedCharsetException If the named charset is unavailable (unchecked exception).
+ * @since 2.12.0
+ */
+ public static Charset toCharset(final String charsetName, final Charset defaultCharset)
+ throws UnsupportedCharsetException {
+ return charsetName == null ? defaultCharset : Charset.forName(charsetName);
+ }
+}
diff --git a/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/FileExistsException.java b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/FileExistsException.java
new file mode 100644
index 00000000..b1cb2537
--- /dev/null
+++ b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/FileExistsException.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.tsfile.external.commons.io;
+
+import java.io.IOException;
+
+/**
+ * Indicates that a file already exists.
+ *
+ * @since 2.0
+ */
+public class FileExistsException extends IOException {
+
+ /** Defines the serial version UID. */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Constructs an instance with the specified message.
+ *
+ * @param message The error message
+ */
+ public FileExistsException(final String message) {
+ super(message);
+ }
+}
diff --git a/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/FileUtils.java b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/FileUtils.java
new file mode 100644
index 00000000..97f22787
--- /dev/null
+++ b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/FileUtils.java
@@ -0,0 +1,928 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.tsfile.external.commons.io;
+
+import org.apache.tsfile.external.commons.io.file.Counters;
+import org.apache.tsfile.external.commons.io.file.PathUtils;
+import org.apache.tsfile.external.commons.io.file.StandardDeleteOption;
+import org.apache.tsfile.external.commons.io.filefilter.FileFileFilter;
+import org.apache.tsfile.external.commons.io.filefilter.IOFileFilter;
+import org.apache.tsfile.external.commons.io.filefilter.SuffixFileFilter;
+import org.apache.tsfile.external.commons.io.function.IOConsumer;
+import org.apache.tsfile.external.commons.io.function.Uncheck;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.URL;
+import java.nio.charset.Charset;
+import java.nio.file.CopyOption;
+import java.nio.file.FileVisitOption;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.attribute.BasicFileAttributeView;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.FileTime;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+public class FileUtils {
+
+ private static final String PROTOCOL_FILE = "file";
+
+ public static void deleteDirectory(final File directory) throws IOException {
+ Objects.requireNonNull(directory, "directory");
+ if (!directory.exists()) {
+ return;
+ }
+ if (!isSymlink(directory)) {
+ cleanDirectory(directory);
+ }
+ delete(directory);
+ }
+
+ public static File delete(final File file) throws IOException {
+ Objects.requireNonNull(file, PROTOCOL_FILE);
+ Files.delete(file.toPath());
+ return file;
+ }
+
+ public static boolean isSymlink(final File file) {
+ return file != null && Files.isSymbolicLink(file.toPath());
+ }
+
+ private static void requireDirectoryExists(final File directory, final String name)
+ throws FileNotFoundException {
+ Objects.requireNonNull(directory, name);
+ if (!directory.isDirectory()) {
+ if (directory.exists()) {
+ throw new IllegalArgumentException(
+ "Parameter '" + name + "' is not a directory: '" + directory + "'");
+ }
+ throw new FileNotFoundException("Directory '" + directory + "' does not exist.");
+ }
+ }
+
+ private static File[] listFiles(final File directory, final FileFilter fileFilter)
+ throws IOException {
+ requireDirectoryExists(directory, "directory");
+ final File[] files =
+ fileFilter == null ? directory.listFiles() : directory.listFiles(fileFilter);
+ if (files == null) {
+ // null if the directory does not denote a directory, or if an I/O error occurs.
+ throw new IOException("Unknown I/O error listing contents of directory: " + directory);
+ }
+ return files;
+ }
+
+ /**
+ * Creates all directories for a File object, including any necessary but non-existent parent
+ * directories. If the {@code directory} already exists or is null, nothing happens.
+ *
+ * <p>Calls {@link File#mkdirs()} and throws an {@link IOException} on failure.
+ *
+ * @param directory the receiver for {@code mkdirs()}. If the {@code directory} already exists or
+ * is null, nothing happens.
+ * @throws IOException if the directory was not created along with all its parent directories.
+ * @throws IOException if the given file object is not a directory.
+ * @throws SecurityException See {@link File#mkdirs()}.
+ * @see File#mkdirs()
+ */
+ public static void forceMkdir(final File directory) throws IOException {
+ mkdirs(directory);
+ }
+
+ /**
+ * Creates all directories for a File object, including any necessary but non-existent parent
+ * directories. If the parent directory already exists or is null, nothing happens.
+ *
+ * <p>Calls {@link File#mkdirs()} for the parent of @{code file}.
+ *
+ * @param file file with parents to create, must not be {@code null}.
+ * @throws NullPointerException if the file is {@code null}.
+ * @throws IOException if the directory was not created along with all its parent directories.
+ * @throws SecurityException See {@link File#mkdirs()}.
+ * @see File#mkdirs()
+ * @since 2.5
+ */
+ public static void forceMkdirParent(File file) throws IOException {
+ forceMkdir(getParentFile(Objects.requireNonNull(file, "file")));
+ }
+
+ public static void forceDelete(final File file) throws IOException {
+ Objects.requireNonNull(file, PROTOCOL_FILE);
+
+ final Counters.PathCounters deleteCounters;
+ try {
+ deleteCounters =
+ PathUtils.delete(
+ file.toPath(),
+ PathUtils.EMPTY_LINK_OPTION_ARRAY,
+ StandardDeleteOption.OVERRIDE_READ_ONLY);
+ } catch (final IOException ex) {
+ throw new IOException("Cannot delete file: " + file, ex);
+ }
+ if (deleteCounters.getFileCounter().get() < 1
+ && deleteCounters.getDirectoryCounter().get() < 1) {
+ // didn't find a file to delete.
+ throw new FileNotFoundException("File does not exist: " + file);
+ }
+ }
+
+ public static void cleanDirectory(final File directory) throws IOException {
+ IOConsumer.forAll(FileUtils::forceDelete, listFiles(directory, null));
+ }
+
+ public static void moveFile(
+ final File srcFile, final File destFile, final CopyOption... copyOptions) throws IOException {
+ validateMoveParameters(srcFile, destFile);
+ checkFileExists(srcFile, "srcFile");
+ requireAbsent(destFile, "destFile");
+ final boolean rename = srcFile.renameTo(destFile);
+ if (!rename) {
+ // Don't interfere with file date on move, handled by StandardCopyOption.COPY_ATTRIBUTES
+ copyFile(srcFile, destFile, false, copyOptions);
+ if (!srcFile.delete()) {
+ deleteQuietly(destFile);
+ throw new IOException(
+ "Failed to delete original file '" + srcFile + "' after copy to '" + destFile + "'");
+ }
+ }
+ }
+
+ private static void checkFileExists(final File file, final String name)
+ throws FileNotFoundException {
+ Objects.requireNonNull(file, name);
+ if (!file.isFile()) {
+ if (file.exists()) {
+ throw new IllegalArgumentException("Parameter '" + name + "' is not a file: " + file);
+ }
+ if (!Files.isSymbolicLink(file.toPath())) {
+ throw new FileNotFoundException("Source '" + file + "' does not exist");
+ }
+ }
+ }
+
+ /**
+ * Requires that the given {@link File} is a file.
+ *
+ * @param file The {@link File} to check.
+ * @param name The parameter name to use in the exception message.
+ * @return the given file.
+ * @throws NullPointerException if the given {@link File} is {@code null}.
+ * @throws IllegalArgumentException if the given {@link File} does not exist or is not a file.
+ */
+ private static File requireFile(final File file, final String name) {
+ Objects.requireNonNull(file, name);
+ if (!file.isFile()) {
+ throw new IllegalArgumentException("Parameter '" + name + "' is not a file: " + file);
+ }
+ return file;
+ }
+
+ private static void requireAbsent(final File file, final String name) throws FileExistsException {
+ if (file.exists()) {
+ throw new FileExistsException(
+ String.format("File element in parameter '%s' already exists: '%s'", name, file));
+ }
+ }
+
+ public static void copyFile(final File srcFile, final File destFile) throws IOException {
+ copyFile(srcFile, destFile, StandardCopyOption.REPLACE_EXISTING);
+ }
+
+ public static void copyFile(
+ final File srcFile, final File destFile, final CopyOption... copyOptions) throws IOException {
+ copyFile(srcFile, destFile, true, copyOptions);
+ }
+
+ public static void copyFile(
+ final File srcFile,
+ final File destFile,
+ final boolean preserveFileDate,
+ final CopyOption... copyOptions)
+ throws IOException {
+ Objects.requireNonNull(destFile, "destination");
+ checkFileExists(srcFile, "srcFile");
+ requireCanonicalPathsNotEquals(srcFile, destFile);
+ createParentDirectories(destFile);
+ if (destFile.exists()) {
+ checkFileExists(destFile, "destFile");
+ }
+
+ final Path srcPath = srcFile.toPath();
+
+ Files.copy(srcPath, destFile.toPath(), copyOptions);
+
+ // On Windows, the last modified time is copied by default.
+ if (preserveFileDate && !Files.isSymbolicLink(srcPath) && !setTimes(srcFile, destFile)) {
+ throw new IOException("Cannot set the file time.");
+ }
+ }
+
+ private static void requireCanonicalPathsNotEquals(final File file1, final File file2)
+ throws IOException {
+ final String canonicalPath = file1.getCanonicalPath();
+ if (canonicalPath.equals(file2.getCanonicalPath())) {
+ throw new IllegalArgumentException(
+ String.format(
+ "File canonical paths are equal: '%s' (file1='%s', file2='%s')",
+ canonicalPath, file1, file2));
+ }
+ }
+
+ public static File createParentDirectories(final File file) throws IOException {
+ return mkdirs(getParentFile(file));
+ }
+
+ private static File mkdirs(final File directory) throws IOException {
+ if (directory != null && !directory.mkdirs() && !directory.isDirectory()) {
+ throw new IOException("Cannot create directory '" + directory + "'.");
+ }
+ return directory;
+ }
+
+ private static File getParentFile(final File file) {
+ return file == null ? null : file.getParentFile();
+ }
+
+ private static boolean setTimes(final File sourceFile, final File targetFile) {
+ Objects.requireNonNull(sourceFile, "sourceFile");
+ Objects.requireNonNull(targetFile, "targetFile");
+ try {
+ // Set creation, modified, last accessed to match source file
+ final BasicFileAttributes srcAttr =
+ Files.readAttributes(sourceFile.toPath(), BasicFileAttributes.class);
+ final BasicFileAttributeView destAttrView =
+ Files.getFileAttributeView(targetFile.toPath(), BasicFileAttributeView.class);
+ // null guards are not needed; BasicFileAttributes.setTimes(...) is null safe
+ destAttrView.setTimes(
+ srcAttr.lastModifiedTime(), srcAttr.lastAccessTime(), srcAttr.creationTime());
+ return true;
+ } catch (final IOException ignored) {
+ // Fallback: Only set modified time to match source file
+ return targetFile.setLastModified(sourceFile.lastModified());
+ }
+ }
+
+ public static boolean deleteQuietly(final File file) {
+ if (file == null) {
+ return false;
+ }
+ try {
+ if (file.isDirectory()) {
+ cleanDirectory(file);
+ }
+ } catch (final Exception ignored) {
+ // ignore
+ }
+
+ try {
+ return file.delete();
+ } catch (final Exception ignored) {
+ return false;
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////////////////////////////////
+ // IoTDB
+ /////////////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * The empty String {@code ""}.
+ *
+ * @since 2.0
+ */
+ public static final String EMPTY = "";
+
+ /**
+ * Writes a CharSequence to a file creating the file if it does not exist.
+ *
+ * @param file the file to write
+ * @param data the content to write to the file
+ * @param charset the charset to use, {@code null} means platform default
+ * @param append if {@code true}, then the data will be added to the end of the file rather than
+ * overwriting
+ * @throws IOException in case of an I/O error
+ * @since 2.3
+ */
+ public static void write(
+ final File file, final CharSequence data, final Charset charset, final boolean append)
+ throws IOException {
+ writeStringToFile(file, Objects.toString(data, null), charset, append);
+ }
+
+ /**
+ * Writes a String to a file creating the file if it does not exist.
+ *
+ * @param file the file to write
+ * @param data the content to write to the file
+ * @param charset the charset to use, {@code null} means platform default
+ * @param append if {@code true}, then the String will be added to the end of the file rather than
+ * overwriting
+ * @throws IOException in case of an I/O error
+ * @since 2.3
+ */
+ public static void writeStringToFile(
+ final File file, final String data, final Charset charset, final boolean append)
+ throws IOException {
+ try (OutputStream out = newOutputStream(file, append)) {
+ IOUtils.write(data, out, charset);
+ }
+ }
+
+ /**
+ * Creates a new OutputStream by opening or creating a file, returning an output stream that may
+ * be used to write bytes to the file.
+ *
+ * @param append Whether or not to append.
+ * @param file the File.
+ * @return a new OutputStream.
+ * @throws IOException if an I/O error occurs.
+ * @see PathUtils#newOutputStream(Path, boolean)
+ * @since 2.12.0
+ */
+ public static OutputStream newOutputStream(final File file, final boolean append)
+ throws IOException {
+ return PathUtils.newOutputStream(Objects.requireNonNull(file, "file").toPath(), append);
+ }
+
+ /**
+ * Moves a file preserving attributes.
+ *
+ * <p>Shorthand for {@code moveFile(srcFile, destFile, StandardCopyOption.COPY_ATTRIBUTES)}.
+ *
+ * <p>When the destination file is on another file system, do a "copy and delete".
+ *
+ * @param srcFile the file to be moved.
+ * @param destFile the destination file.
+ * @throws NullPointerException if any of the given {@link File}s are {@code null}.
+ * @throws FileExistsException if the destination file exists.
+ * @throws FileNotFoundException if the source file does not exist.
+ * @throws IOException if source or destination is invalid.
+ * @throws IOException if an error occurs.
+ * @since 1.4
+ */
+ public static void moveFile(final File srcFile, final File destFile) throws IOException {
+ moveFile(srcFile, destFile, StandardCopyOption.COPY_ATTRIBUTES);
+ }
+
+ /**
+ * Copies bytes from the URL {@code source} to a file {@code destination}. The directories up to
+ * {@code destination} will be created if they don't already exist. {@code destination} will be
+ * overwritten if it already exists.
+ *
+ * <p>Warning: this method does not set a connection or read timeout and thus might block forever.
+ * Use {@link #copyURLToFile(URL, File, int, int)} with reasonable timeouts to prevent this.
+ *
+ * @param source the {@link URL} to copy bytes from, must not be {@code null}
+ * @param destination the non-directory {@link File} to write bytes to (possibly overwriting),
+ * must not be {@code null}
+ * @throws IOException if {@code source} URL cannot be opened
+ * @throws IOException if {@code destination} is a directory
+ * @throws IOException if {@code destination} cannot be written
+ * @throws IOException if {@code destination} needs creating but can't be
+ * @throws IOException if an IO error occurs during copying
+ */
+ public static void copyURLToFile(final URL source, final File destination) throws IOException {
+ final Path path = destination.toPath();
+ PathUtils.createParentDirectories(path);
+ PathUtils.copy(source::openStream, path, StandardCopyOption.REPLACE_EXISTING);
+ }
+
+ /**
+ * Moves a file to a directory.
+ *
+ * <p>If {@code createDestDir} is true, creates all destination parent directories, including any
+ * necessary but non-existent parent directories.
+ *
+ * @param srcFile the file to be moved.
+ * @param destDir the destination file.
+ * @param createDestDir If {@code true} create the destination directory, otherwise if {@code
+ * false} throw an IOException.
+ * @throws NullPointerException if any of the given {@link File}s are {@code null}.
+ * @throws FileExistsException if the destination file exists.
+ * @throws FileNotFoundException if the source file does not exist.
+ * @throws IOException if source or destination is invalid.
+ * @throws IOException if the directory was not created along with all its parent directories, if
+ * enabled.
+ * @throws IOException if an error occurs or setting the last-modified time didn't succeed.
+ * @throws SecurityException See {@link File#mkdirs()}.
+ * @since 1.4
+ */
+ public static void moveFileToDirectory(
+ final File srcFile, final File destDir, final boolean createDestDir) throws IOException {
+ validateMoveParameters(srcFile, destDir);
+ if (!destDir.exists() && createDestDir) {
+ mkdirs(destDir);
+ }
+ requireExistsChecked(destDir, "destDir");
+ requireDirectory(destDir, "destDir");
+ moveFile(srcFile, new File(destDir, srcFile.getName()));
+ }
+
+ /**
+ * Validates the given arguments.
+ *
+ * <ul>
+ * <li>Throws {@link NullPointerException} if {@code source} is null
+ * <li>Throws {@link NullPointerException} if {@code destination} is null
+ * <li>Throws {@link FileNotFoundException} if {@code source} does not exist
+ * </ul>
+ *
+ * @param source the file or directory to be moved.
+ * @param destination the destination file or directory.
+ * @throws NullPointerException if any of the given {@link File}s are {@code null}.
+ * @throws FileNotFoundException if the source file does not exist.
+ */
+ private static void validateMoveParameters(final File source, final File destination)
+ throws FileNotFoundException {
+ Objects.requireNonNull(source, "source");
+ Objects.requireNonNull(destination, "destination");
+ if (!source.exists()) {
+ throw new FileNotFoundException("Source '" + source + "' does not exist");
+ }
+ }
+
+ /**
+ * Requires that the given {@link File} exists and throws an {@link FileNotFoundException} if it
+ * doesn't.
+ *
+ * @param file The {@link File} to check.
+ * @param fileParamName The parameter name to use in the exception message in case of {@code null}
+ * input.
+ * @return the given file.
+ * @throws NullPointerException if the given {@link File} is {@code null}.
+ * @throws FileNotFoundException if the given {@link File} does not exist.
+ */
+ private static File requireExistsChecked(final File file, final String fileParamName)
+ throws FileNotFoundException {
+ Objects.requireNonNull(file, fileParamName);
+ if (!file.exists()) {
+ throw new FileNotFoundException(
+ "File system element for parameter '"
+ + fileParamName
+ + "' does not exist: '"
+ + file
+ + "'");
+ }
+ return file;
+ }
+
+ /**
+ * Requires that the given {@link File} is a directory.
+ *
+ * @param directory The {@link File} to check.
+ * @param name The parameter name to use in the exception message in case of null input or if the
+ * file is not a directory.
+ * @return the given directory.
+ * @throws NullPointerException if the given {@link File} is {@code null}.
+ * @throws IllegalArgumentException if the given {@link File} does not exist or is not a
+ * directory.
+ */
+ private static File requireDirectory(final File directory, final String name) {
+ Objects.requireNonNull(directory, name);
+ if (!directory.isDirectory()) {
+ throw new IllegalArgumentException(
+ "Parameter '" + name + "' is not a directory: '" + directory + "'");
+ }
+ return directory;
+ }
+
+ /**
+ * Finds files within a given directory (and optionally its subdirectories) which match an array
+ * of extensions.
+ *
+ * @param directory the directory to search in
+ * @param extensions an array of extensions, ex. {"java","xml"}. If this parameter is {@code
+ * null}, all files are returned.
+ * @param recursive if true all subdirectories are searched as well
+ * @return a collection of java.io.File with the matching files
+ */
+ public static Collection<File> listFiles(
+ final File directory, final String[] extensions, final boolean recursive) {
+ return Uncheck.apply(d -> toList(streamFiles(d, recursive, extensions)), directory);
+ }
+
+ private static List<File> toList(final Stream<File> stream) {
+ return stream.collect(Collectors.toList());
+ }
+
+ /**
+ * Streams over the files in a given directory (and optionally its subdirectories) which match an
+ * array of extensions.
+ *
+ * @param directory the directory to search in
+ * @param recursive if true all subdirectories are searched as well
+ * @param extensions an array of extensions, ex. {"java","xml"}. If this parameter is {@code
+ * null}, all files are returned.
+ * @return an iterator of java.io.File with the matching files
+ * @since 2.9.0
+ */
+ public static Stream<File> streamFiles(
+ final File directory, final boolean recursive, final String... extensions)
+ throws IOException {
+ // @formatter:off
+ final IOFileFilter filter =
+ extensions == null
+ ? FileFileFilter.INSTANCE
+ : FileFileFilter.INSTANCE.and(new SuffixFileFilter(toSuffixes(extensions)));
+ // @formatter:on
+ return PathUtils.walk(
+ directory.toPath(), filter, toMaxDepth(recursive), false, FileVisitOption.FOLLOW_LINKS)
+ .map(Path::toFile);
+ }
+
+ /**
+ * Converts an array of file extensions to suffixes.
+ *
+ * @param extensions an array of extensions. Format: {"java", "xml"}
+ * @return an array of suffixes. Format: {".java", ".xml"}
+ * @throws NullPointerException if the parameter is null
+ */
+ private static String[] toSuffixes(final String... extensions) {
+ return Stream.of(Objects.requireNonNull(extensions, "extensions"))
+ .map(e -> "." + e)
+ .toArray(String[]::new);
+ }
+
+ /**
+ * Converts whether or not to recurse into a recursion max depth.
+ *
+ * @param recursive whether or not to recurse
+ * @return the recursion depth
+ */
+ private static int toMaxDepth(final boolean recursive) {
+ return recursive ? Integer.MAX_VALUE : 1;
+ }
+
+ /**
+ * Converts each of an array of {@link File} to a {@link URL}.
+ *
+ * <p>Returns an array of the same size as the input.
+ *
+ * @param files the files to convert, must not be {@code null}
+ * @return an array of URLs matching the input
+ * @throws IOException if a file cannot be converted
+ * @throws NullPointerException if the parameter is null
+ */
+ public static URL[] toURLs(final File... files) throws IOException {
+ Objects.requireNonNull(files, "files");
+ final URL[] urls = new URL[files.length];
+ for (int i = 0; i < urls.length; i++) {
+ urls[i] = files[i].toURI().toURL();
+ }
+ return urls;
+ }
+
+ public static void moveDirectory(final File srcDir, final File destDir) throws IOException {
+ validateMoveParameters(srcDir, destDir);
+ requireDirectory(srcDir, "srcDir");
+ requireAbsent(destDir, "destDir");
+ if (!srcDir.renameTo(destDir)) {
+ if (destDir.getCanonicalPath().startsWith(srcDir.getCanonicalPath() + File.separator)) {
+ throw new IOException(
+ "Cannot move directory: " + srcDir + " to a subdirectory of itself: " + destDir);
+ }
+ copyDirectory(srcDir, destDir);
+ deleteDirectory(srcDir);
+ if (srcDir.exists()) {
+ throw new IOException(
+ "Failed to delete original directory '" + srcDir + "' after copy to '" + destDir + "'");
+ }
+ }
+ }
+
+ /**
+ * Copies a whole directory to a new location preserving the file dates.
+ *
+ * <p>This method copies the specified directory and all its child directories and files to the
+ * specified destination. The destination is the new location and name of the directory.
+ *
+ * <p>The destination directory is created if it does not exist. If the destination directory did
+ * exist, then this method merges the source with the destination, with the source taking
+ * precedence.
+ *
+ * <p><strong>Note:</strong> Setting {@code preserveFileDate} to {@code true} tries to preserve
+ * the file's last modified date/times using {@link BasicFileAttributeView#setTimes(FileTime,
+ * FileTime, FileTime)}, however it is not guaranteed that the operation will succeed. If the
+ * modification operation fails it will fallback to {@link File#setLastModified(long)} and if that
+ * fails, the methods throws IOException.
+ *
+ * @param srcDir an existing directory to copy, must not be {@code null}.
+ * @param destDir the new directory, must not be {@code null}.
+ * @throws NullPointerException if any of the given {@link File}s are {@code null}.
+ * @throws IllegalArgumentException if the source or destination is invalid.
+ * @throws FileNotFoundException if the source does not exist.
+ * @throws IOException if an error occurs or setting the last-modified time didn't succeed.
+ * @since 1.1
+ */
+ public static void copyDirectory(final File srcDir, final File destDir) throws IOException {
+ copyDirectory(srcDir, destDir, true);
+ }
+
+ /**
+ * Copies a whole directory to a new location.
+ *
+ * <p>This method copies the contents of the specified source directory to within the specified
+ * destination directory.
+ *
+ * <p>The destination directory is created if it does not exist. If the destination directory did
+ * exist, then this method merges the source with the destination, with the source taking
+ * precedence.
+ *
+ * <p><strong>Note:</strong> Setting {@code preserveFileDate} to {@code true} tries to preserve
+ * the files' last modified date/times using {@link File#setLastModified(long)}, however it is not
+ * guaranteed that those operations will succeed. If the modification operation fails, the methods
+ * throws IOException.
+ *
+ * @param srcDir an existing directory to copy, must not be {@code null}.
+ * @param destDir the new directory, must not be {@code null}.
+ * @param preserveFileDate true if the file date of the copy should be the same as the original.
+ * @throws NullPointerException if any of the given {@link File}s are {@code null}.
+ * @throws IllegalArgumentException if the source or destination is invalid.
+ * @throws FileNotFoundException if the source does not exist.
+ * @throws IOException if an error occurs or setting the last-modified time didn't succeed.
+ * @since 1.1
+ */
+ public static void copyDirectory(
+ final File srcDir, final File destDir, final boolean preserveFileDate) throws IOException {
+ copyDirectory(srcDir, destDir, null, preserveFileDate);
+ }
+
+ /**
+ * Copies a filtered directory to a new location.
+ *
+ * <p>This method copies the contents of the specified source directory to within the specified
+ * destination directory.
+ *
+ * <p>The destination directory is created if it does not exist. If the destination directory did
+ * exist, then this method merges the source with the destination, with the source taking
+ * precedence.
+ *
+ * <p><strong>Note:</strong> Setting {@code preserveFileDate} to {@code true} tries to preserve
+ * the file's last modified date/times using {@link BasicFileAttributeView#setTimes(FileTime,
+ * FileTime, FileTime)}, however it is not guaranteed that the operation will succeed. If the
+ * modification operation fails it will fallback to {@link File#setLastModified(long)} and if that
+ * fails, the methods throws IOException. <b>Example: Copy directories only</b>
+ *
+ * <pre>
+ * // only copy the directory structure
+ * FileUtils.copyDirectory(srcDir, destDir, DirectoryFileFilter.DIRECTORY, false);
+ * </pre>
+ *
+ * <b>Example: Copy directories and txt files</b>
+ *
+ * <pre>
+ * // Create a filter for ".txt" files
+ * IOFileFilter txtSuffixFilter = FileFilterUtils.suffixFileFilter(".txt");
+ * IOFileFilter txtFiles = FileFilterUtils.andFileFilter(FileFileFilter.FILE, txtSuffixFilter);
+ *
+ * // Create a filter for either directories or ".txt" files
+ * FileFilter filter = FileFilterUtils.orFileFilter(DirectoryFileFilter.DIRECTORY, txtFiles);
+ *
+ * // Copy using the filter
+ * FileUtils.copyDirectory(srcDir, destDir, filter, false);
+ * </pre>
+ *
+ * @param srcDir an existing directory to copy, must not be {@code null}.
+ * @param destDir the new directory, must not be {@code null}.
+ * @param filter the filter to apply, null means copy all directories and files.
+ * @param preserveFileDate true if the file date of the copy should be the same as the original.
+ * @throws NullPointerException if any of the given {@link File}s are {@code null}.
+ * @throws IllegalArgumentException if the source or destination is invalid.
+ * @throws FileNotFoundException if the source does not exist.
+ * @throws IOException if an error occurs or setting the last-modified time didn't succeed.
+ * @since 1.4
+ */
+ public static void copyDirectory(
+ final File srcDir,
+ final File destDir,
+ final FileFilter filter,
+ final boolean preserveFileDate)
+ throws IOException {
+ copyDirectory(srcDir, destDir, filter, preserveFileDate, StandardCopyOption.REPLACE_EXISTING);
+ }
+
+ /**
+ * Copies a filtered directory to a new location.
+ *
+ * <p>This method copies the contents of the specified source directory to within the specified
+ * destination directory.
+ *
+ * <p>The destination directory is created if it does not exist. If the destination directory did
+ * exist, then this method merges the source with the destination, with the source taking
+ * precedence.
+ *
+ * <p><strong>Note:</strong> Setting {@code preserveFileDate} to {@code true} tries to preserve
+ * the file's last modified date/times using {@link BasicFileAttributeView#setTimes(FileTime,
+ * FileTime, FileTime)}, however it is not guaranteed that the operation will succeed. If the
+ * modification operation fails it will fallback to {@link File#setLastModified(long)} and if that
+ * fails, the methods throws IOException. <b>Example: Copy directories only</b>
+ *
+ * <pre>
+ * // only copy the directory structure
+ * FileUtils.copyDirectory(srcDir, destDir, DirectoryFileFilter.DIRECTORY, false);
+ * </pre>
+ *
+ * <b>Example: Copy directories and txt files</b>
+ *
+ * <pre>
+ * // Create a filter for ".txt" files
+ * IOFileFilter txtSuffixFilter = FileFilterUtils.suffixFileFilter(".txt");
+ * IOFileFilter txtFiles = FileFilterUtils.andFileFilter(FileFileFilter.FILE, txtSuffixFilter);
+ *
+ * // Create a filter for either directories or ".txt" files
+ * FileFilter filter = FileFilterUtils.orFileFilter(DirectoryFileFilter.DIRECTORY, txtFiles);
+ *
+ * // Copy using the filter
+ * FileUtils.copyDirectory(srcDir, destDir, filter, false);
+ * </pre>
+ *
+ * @param srcDir an existing directory to copy, must not be {@code null}
+ * @param destDir the new directory, must not be {@code null}
+ * @param fileFilter the filter to apply, null means copy all directories and files
+ * @param preserveFileDate true if the file date of the copy should be the same as the original
+ * @param copyOptions options specifying how the copy should be done, for example {@link
+ * StandardCopyOption}.
+ * @throws NullPointerException if any of the given {@link File}s are {@code null}.
+ * @throws IllegalArgumentException if the source or destination is invalid.
+ * @throws FileNotFoundException if the source does not exist.
+ * @throws IOException if an error occurs or setting the last-modified time didn't succeed.
+ * @since 2.8.0
+ */
+ public static void copyDirectory(
+ final File srcDir,
+ final File destDir,
+ final FileFilter fileFilter,
+ final boolean preserveFileDate,
+ final CopyOption... copyOptions)
+ throws IOException {
+ requireFileCopy(srcDir, destDir);
+ requireDirectory(srcDir, "srcDir");
+ requireCanonicalPathsNotEquals(srcDir, destDir);
+
+ // Cater for destination being directory within the source directory (see IO-141)
+ List<String> exclusionList = null;
+ final String srcDirCanonicalPath = srcDir.getCanonicalPath();
+ final String destDirCanonicalPath = destDir.getCanonicalPath();
+ if (destDirCanonicalPath.startsWith(srcDirCanonicalPath)) {
+ final File[] srcFiles = listFiles(srcDir, fileFilter);
+ if (srcFiles.length > 0) {
+ exclusionList = new ArrayList<>(srcFiles.length);
+ for (final File srcFile : srcFiles) {
+ exclusionList.add(new File(destDir, srcFile.getName()).getCanonicalPath());
+ }
+ }
+ }
+ doCopyDirectory(srcDir, destDir, fileFilter, exclusionList, preserveFileDate, copyOptions);
+ }
+
+ /**
+ * Requires parameter attributes for a file copy operation.
+ *
+ * @param source the source file
+ * @param destination the destination
+ * @throws NullPointerException if any of the given {@link File}s are {@code null}.
+ * @throws FileNotFoundException if the source does not exist.
+ */
+ private static void requireFileCopy(final File source, final File destination)
+ throws FileNotFoundException {
+ requireExistsChecked(source, "source");
+ Objects.requireNonNull(destination, "destination");
+ }
+
+ /**
+ * Internal copy directory method. Creates all destination parent directories, including any
+ * necessary but non-existent parent directories.
+ *
+ * @param srcDir the validated source directory, must not be {@code null}.
+ * @param destDir the validated destination directory, must not be {@code null}.
+ * @param fileFilter the filter to apply, null means copy all directories and files.
+ * @param exclusionList List of files and directories to exclude from the copy, may be null.
+ * @param preserveDirDate preserve the directories last modified dates.
+ * @param copyOptions options specifying how the copy should be done, see {@link
+ * StandardCopyOption}.
+ * @throws IOException if the directory was not created along with all its parent directories.
+ * @throws SecurityException See {@link File#mkdirs()}.
+ */
+ private static void doCopyDirectory(
+ final File srcDir,
+ final File destDir,
+ final FileFilter fileFilter,
+ final List<String> exclusionList,
+ final boolean preserveDirDate,
+ final CopyOption... copyOptions)
+ throws IOException {
+ // recurse dirs, copy files.
+ final File[] srcFiles = listFiles(srcDir, fileFilter);
+ requireDirectoryIfExists(destDir, "destDir");
+ mkdirs(destDir);
+ requireCanWrite(destDir, "destDir");
+ for (final File srcFile : srcFiles) {
+ final File dstFile = new File(destDir, srcFile.getName());
+ if (exclusionList == null || !exclusionList.contains(srcFile.getCanonicalPath())) {
+ if (srcFile.isDirectory()) {
+ doCopyDirectory(
+ srcFile, dstFile, fileFilter, exclusionList, preserveDirDate, copyOptions);
+ } else {
+ copyFile(srcFile, dstFile, preserveDirDate, copyOptions);
+ }
+ }
+ }
+ // Do this last, as the above has probably affected directory metadata
+ if (preserveDirDate) {
+ setTimes(srcDir, destDir);
+ }
+ }
+
+ /**
+ * Throws an {@link IllegalArgumentException} if the file is not writable. This provides a more
+ * precise exception message than a plain access denied.
+ *
+ * @param file The file to test.
+ * @param name The parameter name to use in the exception message.
+ * @throws NullPointerException if the given {@link File} is {@code null}.
+ * @throws IllegalArgumentException if the file is not writable.
+ */
+ private static void requireCanWrite(final File file, final String name) {
+ Objects.requireNonNull(file, "file");
+ if (!file.canWrite()) {
+ throw new IllegalArgumentException(
+ "File parameter '" + name + " is not writable: '" + file + "'");
+ }
+ }
+
+ /**
+ * Requires that the given {@link File} is a directory if it exists.
+ *
+ * @param directory The {@link File} to check.
+ * @param name The parameter name to use in the exception message in case of null input.
+ * @return the given directory.
+ * @throws NullPointerException if the given {@link File} is {@code null}.
+ * @throws IllegalArgumentException if the given {@link File} exists but is not a directory.
+ */
+ private static File requireDirectoryIfExists(final File directory, final String name) {
+ Objects.requireNonNull(directory, name);
+ if (directory.exists()) {
+ requireDirectory(directory, name);
+ }
+ return directory;
+ }
+
+ /**
+ * Tests whether the specified {@link File} is a directory or not. Implemented as a null-safe
+ * delegate to {@link Files#isDirectory(Path path, LinkOption... options)}.
+ *
+ * @param file the path to the file.
+ * @param options options indicating how symbolic links are handled
+ * @return {@code true} if the file is a directory; {@code false} if the path is null, the file
+ * does not exist, is not a directory, or it cannot be determined if the file is a directory
+ * or not.
+ * @throws SecurityException In the case of the default provider, and a security manager is
+ * installed, the {@link SecurityManager#checkRead(String) checkRead} method is invoked to
+ * check read access to the directory.
+ * @since 2.9.0
+ */
+ public static boolean isDirectory(final File file, final LinkOption... options) {
+ return file != null && Files.isDirectory(file.toPath(), options);
+ }
+
+ /**
+ * Reads the contents of a file into a String. The file is always closed.
+ *
+ * @param file the file to read, must not be {@code null}
+ * @param charsetName the name of the requested charset, {@code null} means platform default
+ * @return the file contents, never {@code null}
+ * @throws NullPointerException if file is {@code null}.
+ * @throws FileNotFoundException if the file does not exist, is a directory rather than a regular
+ * file, or for some other reason cannot be opened for reading.
+ * @throws IOException if an I/O error occurs.
+ * @since 2.3
+ */
+ public static String readFileToString(final File file, final Charset charsetName)
+ throws IOException {
+ return IOUtils.toString(
+ () -> Files.newInputStream(file.toPath()), Charsets.toCharset(charsetName));
+ }
+}
diff --git a/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/FilenameUtils.java b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/FilenameUtils.java
new file mode 100644
index 00000000..fb327944
--- /dev/null
+++ b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/FilenameUtils.java
@@ -0,0 +1,290 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.tsfile.external.commons.io;
+
+import java.io.File;
+
+public class FilenameUtils {
+ /**
+ * Gets the base name, minus the full path and extension, from a full fileName.
+ *
+ * <p>This method will handle a file in either Unix or Windows format. The text after the last
+ * forward or backslash and before the last dot is returned.
+ *
+ * <pre>
+ * a/b/c.txt --> c
+ * a.txt --> a
+ * a/b/c --> c
+ * a/b/c/ --> ""
+ * </pre>
+ *
+ * <p>The output will be the same irrespective of the machine that the code is running on.
+ *
+ * @param fileName the fileName to query, null returns null
+ * @return the name of the file without the path, or an empty string if none exists
+ * @throws IllegalArgumentException if the fileName contains the null character ({@code U+0000})
+ */
+ public static String getBaseName(final String fileName) {
+ return removeExtension(getName(fileName));
+ }
+
+ /**
+ * Removes the extension from a fileName.
+ *
+ * <p>This method returns the textual part of the fileName before the last dot. There must be no
+ * directory separator after the dot.
+ *
+ * <pre>
+ * foo.txt --> foo
+ * a\b\c.jpg --> a\b\c
+ * a\b\c --> a\b\c
+ * a.b\c --> a.b\c
+ * </pre>
+ *
+ * <p>The output will be the same irrespective of the machine that the code is running on.
+ *
+ * @param fileName the fileName to query, null returns null
+ * @return the fileName minus the extension
+ * @throws IllegalArgumentException if the fileName contains the null character ({@code U+0000})
+ */
+ public static String removeExtension(final String fileName) {
+ if (fileName == null) {
+ return null;
+ }
+ requireNonNullChars(fileName);
+
+ final int index = indexOfExtension(fileName);
+ if (index == NOT_FOUND) {
+ return fileName;
+ }
+ return fileName.substring(0, index);
+ }
+
+ /**
+ * Gets the name minus the path from a full fileName.
+ *
+ * <p>This method will handle a file in either Unix or Windows format. The text after the last
+ * forward or backslash is returned.
+ *
+ * <pre>
+ * a/b/c.txt --> c.txt
+ * a.txt --> a.txt
+ * a/b/c --> c
+ * a/b/c/ --> ""
+ * </pre>
+ *
+ * <p>The output will be the same irrespective of the machine that the code is running on.
+ *
+ * @param fileName the fileName to query, null returns null
+ * @return the name of the file without the path, or an empty string if none exists
+ * @throws IllegalArgumentException if the fileName contains the null character ({@code U+0000})
+ */
+ public static String getName(final String fileName) {
+ if (fileName == null) {
+ return null;
+ }
+ return requireNonNullChars(fileName).substring(indexOfLastSeparator(fileName) + 1);
+ }
+
+ /**
+ * Checks the input for null characters ({@code U+0000}), a sign of unsanitized data being passed
+ * to file level functions.
+ *
+ * <p>This may be used for poison byte attacks.
+ *
+ * @param path the path to check
+ * @return The input
+ * @throws IllegalArgumentException if path contains the null character ({@code U+0000})
+ */
+ private static String requireNonNullChars(final String path) {
+ if (path.indexOf(0) >= 0) {
+ throw new IllegalArgumentException(
+ "Null character present in file/path name. There are no known legitimate use cases for such data, but several injection attacks may use it");
+ }
+ return path;
+ }
+
+ /////////////////////////////////////////////////////////////////////////////////////////////////
+ // IoTDB
+ /////////////////////////////////////////////////////////////////////////////////////////////////
+
+ private static final int NOT_FOUND = -1;
+ private static final String EMPTY_STRING = "";
+
+ /**
+ * The extension separator character.
+ *
+ * @since 1.4
+ */
+ public static final char EXTENSION_SEPARATOR = '.';
+
+ /** The system separator character. */
+ private static final char SYSTEM_NAME_SEPARATOR = File.separatorChar;
+
+ /** The separator character that is the opposite of the system separator. */
+ private static final char OTHER_SEPARATOR = flipSeparator(SYSTEM_NAME_SEPARATOR);
+
+ /** The Unix separator character. */
+ private static final char UNIX_NAME_SEPARATOR = '/';
+
+ /** The Windows separator character. */
+ private static final char WINDOWS_NAME_SEPARATOR = '\\';
+
+ /**
+ * Gets the extension of a fileName.
+ *
+ * <p>This method returns the textual part of the fileName after the last dot. There must be no
+ * directory separator after the dot.
+ *
+ * <pre>
+ * foo.txt --> "txt"
+ * a/b/c.jpg --> "jpg"
+ * a/b.txt/c --> ""
+ * a/b/c --> ""
+ * </pre>
+ *
+ * <p>The output will be the same irrespective of the machine that the code is running on, with
+ * the exception of a possible {@link IllegalArgumentException} on Windows (see below).
+ *
+ * <p><b>Note:</b> This method used to have a hidden problem for names like "foo.exe:bar.txt". In
+ * this case, the name wouldn't be the name of a file, but the identifier of an alternate data
+ * stream (bar.txt) on the file foo.exe. The method used to return ".txt" here, which would be
+ * misleading. Commons IO 2.7, and later versions, are throwing an {@link
+ * IllegalArgumentException} for names like this.
+ *
+ * @param fileName the fileName to retrieve the extension of.
+ * @return the extension of the file or an empty string if none exists or {@code null} if the
+ * fileName is {@code null}.
+ * @throws IllegalArgumentException <b>Windows only:</b> The fileName parameter is, in fact, the
+ * identifier of an Alternate Data Stream, for example "foo.exe:bar.txt".
+ */
+ public static String getExtension(final String fileName) throws IllegalArgumentException {
+ if (fileName == null) {
+ return null;
+ }
+ final int index = indexOfExtension(fileName);
+ if (index == NOT_FOUND) {
+ return EMPTY_STRING;
+ }
+ return fileName.substring(index + 1);
+ }
+
+ /**
+ * Returns the index of the last extension separator character, which is a dot.
+ *
+ * <p>This method also checks that there is no directory separator after the last dot. To do this
+ * it uses {@link #indexOfLastSeparator(String)} which will handle a file in either Unix or
+ * Windows format.
+ *
+ * <p>The output will be the same irrespective of the machine that the code is running on, with
+ * the exception of a possible {@link IllegalArgumentException} on Windows (see below).
+ * <b>Note:</b> This method used to have a hidden problem for names like "foo.exe:bar.txt". In
+ * this case, the name wouldn't be the name of a file, but the identifier of an alternate data
+ * stream (bar.txt) on the file foo.exe. The method used to return ".txt" here, which would be
+ * misleading. Commons IO 2.7, and later versions, are throwing an {@link
+ * IllegalArgumentException} for names like this.
+ *
+ * @param fileName the fileName to find the last extension separator in, null returns -1
+ * @return the index of the last extension separator character, or -1 if there is no such
+ * character
+ * @throws IllegalArgumentException <b>Windows only:</b> The fileName parameter is, in fact, the
+ * identifier of an Alternate Data Stream, for example "foo.exe:bar.txt".
+ */
+ public static int indexOfExtension(final String fileName) throws IllegalArgumentException {
+ if (fileName == null) {
+ return NOT_FOUND;
+ }
+ if (isSystemWindows()) {
+ // Special handling for NTFS ADS: Don't accept colon in the fileName.
+ final int offset = fileName.indexOf(':', getAdsCriticalOffset(fileName));
+ if (offset != -1) {
+ throw new IllegalArgumentException("NTFS ADS separator (':') in file name is forbidden.");
+ }
+ }
+ final int extensionPos = fileName.lastIndexOf(EXTENSION_SEPARATOR);
+ final int lastSeparator = indexOfLastSeparator(fileName);
+ return lastSeparator > extensionPos ? NOT_FOUND : extensionPos;
+ }
+
+ /**
+ * Determines if Windows file system is in use.
+ *
+ * @return true if the system is Windows
+ */
+ static boolean isSystemWindows() {
+ return SYSTEM_NAME_SEPARATOR == WINDOWS_NAME_SEPARATOR;
+ }
+
+ /**
+ * Flips the Windows name separator to Linux and vice-versa.
+ *
+ * @param ch The Windows or Linux name separator.
+ * @return The Windows or Linux name separator.
+ */
+ static char flipSeparator(final char ch) {
+ if (ch == UNIX_NAME_SEPARATOR) {
+ return WINDOWS_NAME_SEPARATOR;
+ }
+ if (ch == WINDOWS_NAME_SEPARATOR) {
+ return UNIX_NAME_SEPARATOR;
+ }
+ throw new IllegalArgumentException(String.valueOf(ch));
+ }
+
+ /**
+ * Special handling for NTFS ADS: Don't accept colon in the fileName.
+ *
+ * @param fileName a file name
+ * @return ADS offsets.
+ */
+ private static int getAdsCriticalOffset(final String fileName) {
+ // Step 1: Remove leading path segments.
+ final int offset1 = fileName.lastIndexOf(SYSTEM_NAME_SEPARATOR);
+ final int offset2 = fileName.lastIndexOf(OTHER_SEPARATOR);
+ if (offset1 == -1) {
+ if (offset2 == -1) {
+ return 0;
+ }
+ return offset2 + 1;
+ }
+ if (offset2 == -1) {
+ return offset1 + 1;
+ }
+ return Math.max(offset1, offset2) + 1;
+ }
+
+ /**
+ * Returns the index of the last directory separator character.
+ *
+ * <p>This method will handle a file in either Unix or Windows format. The position of the last
+ * forward or backslash is returned.
+ *
+ * <p>The output will be the same irrespective of the machine that the code is running on.
+ *
+ * @param fileName the fileName to find the last path separator in, null returns -1
+ * @return the index of the last separator character, or -1 if there is no such character
+ */
+ public static int indexOfLastSeparator(final String fileName) {
+ if (fileName == null) {
+ return NOT_FOUND;
+ }
+ final int lastUnixPos = fileName.lastIndexOf(UNIX_NAME_SEPARATOR);
+ final int lastWindowsPos = fileName.lastIndexOf(WINDOWS_NAME_SEPARATOR);
+ return Math.max(lastUnixPos, lastWindowsPos);
+ }
+}
diff --git a/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/IOCase.java b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/IOCase.java
new file mode 100644
index 00000000..c3e5a042
--- /dev/null
+++ b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/IOCase.java
@@ -0,0 +1,247 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tsfile.external.commons.io;
+
+import java.util.Objects;
+import java.util.stream.Stream;
+
+/**
+ * Enumeration of IO case sensitivity.
+ *
+ * <p>Different filing systems have different rules for case-sensitivity. Windows is
+ * case-insensitive, Unix is case-sensitive.
+ *
+ * <p>This class captures that difference, providing an enumeration to control how file name
+ * comparisons should be performed. It also provides methods that use the enumeration to perform
+ * comparisons.
+ *
+ * <p>Wherever possible, you should use the {@code check} methods in this class to compare file
+ * names.
+ *
+ * @since 1.3
+ */
+public enum IOCase {
+
+ /** The constant for case-sensitive regardless of operating system. */
+ SENSITIVE("Sensitive", true),
+
+ /** The constant for case-insensitive regardless of operating system. */
+ INSENSITIVE("Insensitive", false);
+
+ /** Serialization version. */
+ private static final long serialVersionUID = -6343169151696340687L;
+
+ /**
+ * Factory method to create an IOCase from a name.
+ *
+ * @param name the name to find
+ * @return the IOCase object
+ * @throws IllegalArgumentException if the name is invalid
+ */
+ public static IOCase forName(final String name) {
+ return Stream.of(IOCase.values())
+ .filter(ioCase -> ioCase.getName().equals(name))
+ .findFirst()
+ .orElseThrow(() -> new IllegalArgumentException("Illegal IOCase name: " + name));
+ }
+
+ /**
+ * Tests for cases sensitivity in a null-safe manner.
+ *
+ * @param ioCase an IOCase.
+ * @return true if the input is non-null and {@link #isCaseSensitive()}.
+ * @since 2.10.0
+ */
+ public static boolean isCaseSensitive(final IOCase ioCase) {
+ return ioCase != null && ioCase.isCaseSensitive();
+ }
+
+ /**
+ * Returns the given value if not-null, the defaultValue if null.
+ *
+ * @param value the value to test.
+ * @param defaultValue the default value.
+ * @return the given value if not-null, the defaultValue if null.
+ * @since 2.12.0
+ */
+ public static IOCase value(final IOCase value, final IOCase defaultValue) {
+ return value != null ? value : defaultValue;
+ }
+
+ /** The enumeration name. */
+ private final String name;
+
+ /** The sensitivity flag. */
+ private final transient boolean sensitive;
+
+ /**
+ * Constructs a new instance.
+ *
+ * @param name the name
+ * @param sensitive the sensitivity
+ */
+ IOCase(final String name, final boolean sensitive) {
+ this.name = name;
+ this.sensitive = sensitive;
+ }
+
+ /**
+ * Compares two strings using the case-sensitivity rule.
+ *
+ * <p>This method mimics {@link String#compareTo} but takes case-sensitivity into account.
+ *
+ * @param str1 the first string to compare, not null
+ * @param str2 the second string to compare, not null
+ * @return true if equal using the case rules
+ * @throws NullPointerException if either string is null
+ */
+ public int checkCompareTo(final String str1, final String str2) {
+ Objects.requireNonNull(str1, "str1");
+ Objects.requireNonNull(str2, "str2");
+ return sensitive ? str1.compareTo(str2) : str1.compareToIgnoreCase(str2);
+ }
+
+ /**
+ * Checks if one string ends with another using the case-sensitivity rule.
+ *
+ * <p>This method mimics {@link String#endsWith} but takes case-sensitivity into account.
+ *
+ * @param str the string to check
+ * @param end the end to compare against
+ * @return true if equal using the case rules, false if either input is null
+ */
+ public boolean checkEndsWith(final String str, final String end) {
+ if (str == null || end == null) {
+ return false;
+ }
+ final int endLen = end.length();
+ return str.regionMatches(!sensitive, str.length() - endLen, end, 0, endLen);
+ }
+
+ /**
+ * Compares two strings using the case-sensitivity rule.
+ *
+ * <p>This method mimics {@link String#equals} but takes case-sensitivity into account.
+ *
+ * @param str1 the first string to compare, not null
+ * @param str2 the second string to compare, not null
+ * @return true if equal using the case rules
+ * @throws NullPointerException if either string is null
+ */
+ public boolean checkEquals(final String str1, final String str2) {
+ Objects.requireNonNull(str1, "str1");
+ Objects.requireNonNull(str2, "str2");
+ return sensitive ? str1.equals(str2) : str1.equalsIgnoreCase(str2);
+ }
+
+ /**
+ * Checks if one string contains another starting at a specific index using the case-sensitivity
+ * rule.
+ *
+ * <p>This method mimics parts of {@link String#indexOf(String, int)} but takes case-sensitivity
+ * into account.
+ *
+ * @param str the string to check, not null
+ * @param strStartIndex the index to start at in str
+ * @param search the start to search for, not null
+ * @return the first index of the search String, -1 if no match or {@code null} string input
+ * @throws NullPointerException if either string is null
+ * @since 2.0
+ */
+ public int checkIndexOf(final String str, final int strStartIndex, final String search) {
+ final int endIndex = str.length() - search.length();
+ if (endIndex >= strStartIndex) {
+ for (int i = strStartIndex; i <= endIndex; i++) {
+ if (checkRegionMatches(str, i, search)) {
+ return i;
+ }
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Checks if one string contains another at a specific index using the case-sensitivity rule.
+ *
+ * <p>This method mimics parts of {@link String#regionMatches(boolean, int, String, int, int)} but
+ * takes case-sensitivity into account.
+ *
+ * @param str the string to check, not null
+ * @param strStartIndex the index to start at in str
+ * @param search the start to search for, not null
+ * @return true if equal using the case rules
+ * @throws NullPointerException if either string is null
+ */
+ public boolean checkRegionMatches(
+ final String str, final int strStartIndex, final String search) {
+ return str.regionMatches(!sensitive, strStartIndex, search, 0, search.length());
+ }
+
+ /**
+ * Checks if one string starts with another using the case-sensitivity rule.
+ *
+ * <p>This method mimics {@link String#startsWith(String)} but takes case-sensitivity into
+ * account.
+ *
+ * @param str the string to check
+ * @param start the start to compare against
+ * @return true if equal using the case rules, false if either input is null
+ */
+ public boolean checkStartsWith(final String str, final String start) {
+ return str != null
+ && start != null
+ && str.regionMatches(!sensitive, 0, start, 0, start.length());
+ }
+
+ /**
+ * Gets the name of the constant.
+ *
+ * @return the name of the constant
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Does the object represent case-sensitive comparison.
+ *
+ * @return true if case-sensitive
+ */
+ public boolean isCaseSensitive() {
+ return sensitive;
+ }
+
+ /**
+ * Replaces the enumeration from the stream with a real one. This ensures that the correct flag is
+ * set for SYSTEM.
+ *
+ * @return the resolved object
+ */
+ private Object readResolve() {
+ return forName(name);
+ }
+
+ /**
+ * Gets a string describing the sensitivity.
+ *
+ * @return a string describing the sensitivity
+ */
+ @Override
+ public String toString() {
+ return name;
+ }
+}
diff --git a/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/IOExceptionList.java b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/IOExceptionList.java
new file mode 100644
index 00000000..30d8e35c
--- /dev/null
+++ b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/IOExceptionList.java
@@ -0,0 +1,140 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.tsfile.external.commons.io;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * An IOException based on a list of Throwable causes.
+ *
+ * <p>The first exception in the list is used as this exception's cause and is accessible with the
+ * usual {@link #getCause()} while the complete list is accessible with {@link #getCauseList()}.
+ *
+ * @since 2.7
+ */
+public class IOExceptionList extends IOException implements Iterable<Throwable> {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Throws this exception if the list is not null or empty.
+ *
+ * @param causeList The list to test.
+ * @param message The detail message, see {@link #getMessage()}.
+ * @throws IOExceptionList if the list is not null or empty.
+ * @since 2.12.0
+ */
+ public static void checkEmpty(final List<? extends Throwable> causeList, final Object message)
+ throws IOExceptionList {
+ if (!isEmpty(causeList)) {
+ throw new IOExceptionList(Objects.toString(message, null), causeList);
+ }
+ }
+
+ private static boolean isEmpty(final List<? extends Throwable> causeList) {
+ return size(causeList) == 0;
+ }
+
+ private static int size(final List<? extends Throwable> causeList) {
+ return causeList != null ? causeList.size() : 0;
+ }
+
+ private static String toMessage(final List<? extends Throwable> causeList) {
+ return String.format("%,d exception(s): %s", size(causeList), causeList);
+ }
+
+ /** List of causes. */
+ private final List<? extends Throwable> causeList;
+
+ /**
+ * Constructs a new exception caused by a list of exceptions.
+ *
+ * @param causeList a list of cause exceptions.
+ */
+ public IOExceptionList(final List<? extends Throwable> causeList) {
+ this(toMessage(causeList), causeList);
+ }
+
+ /**
+ * Constructs a new exception caused by a list of exceptions.
+ *
+ * @param message The detail message, see {@link #getMessage()}.
+ * @param causeList a list of cause exceptions.
+ * @since 2.9.0
+ */
+ public IOExceptionList(final String message, final List<? extends Throwable> causeList) {
+ super(
+ message != null ? message : toMessage(causeList),
+ isEmpty(causeList) ? null : causeList.get(0));
+ this.causeList = causeList == null ? Collections.emptyList() : causeList;
+ }
+
+ /**
+ * Gets the cause exception at the given index.
+ *
+ * @param <T> type of exception to return.
+ * @param index index in the cause list.
+ * @return The list of causes.
+ */
+ public <T extends Throwable> T getCause(final int index) {
+ return (T) causeList.get(index);
+ }
+
+ /**
+ * Gets the cause exception at the given index.
+ *
+ * @param <T> type of exception to return.
+ * @param index index in the cause list.
+ * @param clazz type of exception to return.
+ * @return The list of causes.
+ */
+ public <T extends Throwable> T getCause(final int index, final Class<T> clazz) {
+ return clazz.cast(getCause(index));
+ }
+
+ /**
+ * Gets the cause list.
+ *
+ * @param <T> type of exception to return.
+ * @return The list of causes.
+ */
+ public <T extends Throwable> List<T> getCauseList() {
+ return (List<T>) new ArrayList<>(causeList);
+ }
+
+ /**
+ * Works around Throwable and Generics, may fail at runtime depending on the argument value.
+ *
+ * @param <T> type of exception to return.
+ * @param clazz the target type
+ * @return The list of causes.
+ */
+ public <T extends Throwable> List<T> getCauseList(final Class<T> clazz) {
+ return (List<T>) new ArrayList<>(causeList);
+ }
+
+ @Override
+ public Iterator<Throwable> iterator() {
+ return getCauseList().iterator();
+ }
+}
diff --git a/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/IOIndexedException.java b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/IOIndexedException.java
new file mode 100644
index 00000000..64a319ea
--- /dev/null
+++ b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/IOIndexedException.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.tsfile.external.commons.io;
+
+import java.io.IOException;
+
+/**
+ * A IOException associated with a source index.
+ *
+ * @since 2.7
+ */
+public class IOIndexedException extends IOException {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Converts input to a suitable String for exception message.
+ *
+ * @param index An index into a source collection.
+ * @param cause A cause.
+ * @return A message.
+ */
+ protected static String toMessage(final int index, final Throwable cause) {
+ // Letting index be any int
+ final String unspecified = "Null";
+ final String name = cause == null ? unspecified : cause.getClass().getSimpleName();
+ final String msg = cause == null ? unspecified : cause.getMessage();
+ return String.format("%s #%,d: %s", name, index, msg);
+ }
+
+ /** Index. */
+ private final int index;
+
+ /**
+ * Constructs a new exception.
+ *
+ * @param index index of this exception.
+ * @param cause cause exceptions.
+ */
+ public IOIndexedException(final int index, final Throwable cause) {
+ super(toMessage(index, cause), cause);
+ this.index = index;
+ }
+
+ /**
+ * The index of this exception.
+ *
+ * @return index of this exception.
+ */
+ public int getIndex() {
+ return index;
+ }
+}
diff --git a/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/IOUtils.java b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/IOUtils.java
new file mode 100644
index 00000000..1642a652
--- /dev/null
+++ b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/IOUtils.java
@@ -0,0 +1,558 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.tsfile.external.commons.io;
+
+import org.apache.tsfile.external.commons.io.function.IOSupplier;
+import org.apache.tsfile.external.commons.io.function.IOTriFunction;
+import org.apache.tsfile.external.commons.io.output.ByteArrayOutputStream;
+import org.apache.tsfile.external.commons.io.output.StringBuilderWriter;
+import org.apache.tsfile.external.commons.io.output.ThresholdingOutputStream;
+import org.apache.tsfile.external.commons.io.output.UnsynchronizedByteArrayOutputStream;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.Writer;
+import java.nio.channels.Channels;
+import java.nio.charset.Charset;
+import java.util.Arrays;
+import java.util.Objects;
+
+public class IOUtils {
+
+ /**
+ * A singleton empty byte array.
+ *
+ * @since 2.9.0
+ */
+ public static final byte[] EMPTY_BYTE_ARRAY = {};
+
+ /**
+ * Represents the end-of-file (or stream).
+ *
+ * @since 2.5 (made public)
+ */
+ public static final int EOF = -1;
+
+ /** The default buffer size ({@value}) to use in copy methods. */
+ public static final int DEFAULT_BUFFER_SIZE = 8192;
+
+ public static int length(final Object[] array) {
+ return array == null ? 0 : array.length;
+ }
+
+ /////////////////////////////////////////////////////////////////////////////////////////////////
+ // IoTDB
+ /////////////////////////////////////////////////////////////////////////////////////////////////
+
+ /** Internal char array buffer, intended for both reading and writing. */
+ private static final ThreadLocal<char[]> SCRATCH_CHAR_BUFFER_RW =
+ ThreadLocal.withInitial(IOUtils::charArray);
+
+ /**
+ * Returns a new char array of size {@link #DEFAULT_BUFFER_SIZE}.
+ *
+ * @return a new char array of size {@link #DEFAULT_BUFFER_SIZE}.
+ * @since 2.9.0
+ */
+ private static char[] charArray() {
+ return charArray(DEFAULT_BUFFER_SIZE);
+ }
+
+ /**
+ * Returns a new char array of the given size.
+ *
+ * <p>TODO Consider guarding or warning against large allocations...
+ *
+ * @param size array size.
+ * @return a new char array of the given size.
+ * @since 2.9.0
+ */
+ private static char[] charArray(final int size) {
+ return new char[size];
+ }
+
+ /**
+ * Gets the contents of a {@link Reader} as a {@code byte[]} using the specified character
+ * encoding.
+ *
+ * <p>This method buffers the input internally, so there is no need to use a {@link
+ * BufferedReader}.
+ *
+ * @param reader the {@link Reader} to read
+ * @param charset the charset to use, null means platform default
+ * @return the requested byte array
+ * @throws NullPointerException if the input is null
+ * @throws IOException if an I/O error occurs
+ * @since 2.3
+ */
+ public static byte[] toByteArray(final Reader reader, final Charset charset) throws IOException {
+ try (ByteArrayOutputStream output = new ByteArrayOutputStream()) {
+ copy(reader, output, charset);
+ return output.toByteArray();
+ }
+ }
+
+ /**
+ * Copies chars from a {@link Reader} to bytes on an {@link OutputStream} using the specified
+ * character encoding, and calling flush.
+ *
+ * <p>This method buffers the input internally, so there is no need to use a {@link
+ * BufferedReader}.
+ *
+ * <p>Due to the implementation of OutputStreamWriter, this method performs a flush.
+ *
+ * <p>This method uses {@link OutputStreamWriter}.
+ *
+ * @param reader the {@link Reader} to read
+ * @param output the {@link OutputStream} to write to
+ * @param outputCharset the charset to use for the OutputStream, null means platform default
+ * @throws NullPointerException if the input or output is null
+ * @throws IOException if an I/O error occurs
+ * @since 2.3
+ */
+ public static void copy(
+ final Reader reader, final OutputStream output, final Charset outputCharset)
+ throws IOException {
+ final OutputStreamWriter writer =
+ new OutputStreamWriter(output, Charsets.toCharset(outputCharset));
+ copy(reader, writer);
+ // XXX Unless anyone is planning on rewriting OutputStreamWriter,
+ // we have to flush here.
+ writer.flush();
+ }
+
+ /**
+ * Gets the contents of an {@link InputStream} as a {@code byte[]}.
+ *
+ * <p>This method buffers the input internally, so there is no need to use a {@link
+ * BufferedInputStream}.
+ *
+ * @param inputStream the {@link InputStream} to read.
+ * @return the requested byte array.
+ * @throws NullPointerException if the InputStream is {@code null}.
+ * @throws IOException if an I/O error occurs or reading more than {@link Integer#MAX_VALUE}
+ * occurs.
+ */
+ public static byte[] toByteArray(final InputStream inputStream) throws IOException {
+ // We use a ThresholdingOutputStream to avoid reading AND writing more than Integer.MAX_VALUE.
+ try (UnsynchronizedByteArrayOutputStream ubaOutput =
+ UnsynchronizedByteArrayOutputStream.builder().get();
+ ThresholdingOutputStream thresholdOutput =
+ new ThresholdingOutputStream(
+ Integer.MAX_VALUE,
+ os -> {
+ throw new IllegalArgumentException(
+ String.format(
+ "Cannot read more than %,d into a byte array", Integer.MAX_VALUE));
+ },
+ os -> ubaOutput)) {
+ copy(inputStream, thresholdOutput);
+ return ubaOutput.toByteArray();
+ }
+ }
+
+ /**
+ * Returns a new byte array of size {@link #DEFAULT_BUFFER_SIZE}.
+ *
+ * @return a new byte array of size {@link #DEFAULT_BUFFER_SIZE}.
+ * @since 2.9.0
+ */
+ public static byte[] byteArray() {
+ return byteArray(DEFAULT_BUFFER_SIZE);
+ }
+
+ /**
+ * Returns a new byte array of the given size.
+ *
+ * <p>TODO Consider guarding or warning against large allocations...
+ *
+ * @param size array size.
+ * @return a new byte array of the given size.
+ * @throws NegativeArraySizeException if the size is negative.
+ * @since 2.9.0
+ */
+ public static byte[] byteArray(final int size) {
+ return new byte[size];
+ }
+
+ /**
+ * Copies bytes from an {@link InputStream} to an {@link OutputStream}.
+ *
+ * <p>This method buffers the input internally, so there is no need to use a {@link
+ * BufferedInputStream}.
+ *
+ * <p>Large streams (over 2GB) will return a bytes copied value of {@code -1} after the copy has
+ * completed since the correct number of bytes cannot be returned as an int. For large streams use
+ * the {@link #copyLarge(InputStream, OutputStream)} method.
+ *
+ * @param inputStream the {@link InputStream} to read.
+ * @param outputStream the {@link OutputStream} to write.
+ * @return the number of bytes copied, or -1 if greater than {@link Integer#MAX_VALUE}.
+ * @throws NullPointerException if the InputStream is {@code null}.
+ * @throws NullPointerException if the OutputStream is {@code null}.
+ * @throws IOException if an I/O error occurs.
+ * @since 1.1
+ */
+ public static int copy(final InputStream inputStream, final OutputStream outputStream)
+ throws IOException {
+ final long count = copyLarge(inputStream, outputStream);
+ return count > Integer.MAX_VALUE ? EOF : (int) count;
+ }
+
+ /**
+ * Copies bytes from an {@link InputStream} to an {@link OutputStream} using an internal buffer of
+ * the given size.
+ *
+ * <p>This method buffers the input internally, so there is no need to use a {@link
+ * BufferedInputStream}.
+ *
+ * @param inputStream the {@link InputStream} to read.
+ * @param outputStream the {@link OutputStream} to write to
+ * @param bufferSize the bufferSize used to copy from the input to the output
+ * @return the number of bytes copied.
+ * @throws NullPointerException if the InputStream is {@code null}.
+ * @throws NullPointerException if the OutputStream is {@code null}.
+ * @throws IOException if an I/O error occurs.
+ * @since 2.5
+ */
+ public static long copy(
+ final InputStream inputStream, final OutputStream outputStream, final int bufferSize)
+ throws IOException {
+ return copyLarge(inputStream, outputStream, IOUtils.byteArray(bufferSize));
+ }
+
+ /**
+ * Copies bytes from a large (over 2GB) {@link InputStream} to an {@link OutputStream}.
+ *
+ * <p>This method buffers the input internally, so there is no need to use a {@link
+ * BufferedInputStream}.
+ *
+ * <p>The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}.
+ *
+ * @param inputStream the {@link InputStream} to read.
+ * @param outputStream the {@link OutputStream} to write.
+ * @return the number of bytes copied.
+ * @throws NullPointerException if the InputStream is {@code null}.
+ * @throws NullPointerException if the OutputStream is {@code null}.
+ * @throws IOException if an I/O error occurs.
+ * @since 1.3
+ */
+ public static long copyLarge(final InputStream inputStream, final OutputStream outputStream)
+ throws IOException {
+ return copy(inputStream, outputStream, DEFAULT_BUFFER_SIZE);
+ }
+
+ /**
+ * Copies bytes from a large (over 2GB) {@link InputStream} to an {@link OutputStream}.
+ *
+ * <p>This method uses the provided buffer, so there is no need to use a {@link
+ * BufferedInputStream}.
+ *
+ * @param inputStream the {@link InputStream} to read.
+ * @param outputStream the {@link OutputStream} to write.
+ * @param buffer the buffer to use for the copy
+ * @return the number of bytes copied.
+ * @throws NullPointerException if the InputStream is {@code null}.
+ * @throws NullPointerException if the OutputStream is {@code null}.
+ * @throws IOException if an I/O error occurs.
+ * @since 2.2
+ */
+ @SuppressWarnings("resource") // streams are closed by the caller.
+ public static long copyLarge(
+ final InputStream inputStream, final OutputStream outputStream, final byte[] buffer)
+ throws IOException {
+ Objects.requireNonNull(inputStream, "inputStream");
+ Objects.requireNonNull(outputStream, "outputStream");
+ long count = 0;
+ int n;
+ while (EOF != (n = inputStream.read(buffer))) {
+ outputStream.write(buffer, 0, n);
+ count += n;
+ }
+ return count;
+ }
+
+ /**
+ * Gets the contents of a {@link Reader} as a String.
+ *
+ * <p>This method buffers the input internally, so there is no need to use a {@link
+ * BufferedReader}.
+ *
+ * @param reader the {@link Reader} to read
+ * @return the requested String
+ * @throws NullPointerException if the input is null
+ * @throws IOException if an I/O error occurs
+ */
+ public static String toString(final Reader reader) throws IOException {
+ try (StringBuilderWriter sw = new StringBuilderWriter()) {
+ copy(reader, sw);
+ return sw.toString();
+ }
+ }
+
+ /**
+ * Copies chars from a {@link Reader} to a {@link Writer}.
+ *
+ * <p>This method buffers the input internally, so there is no need to use a {@link
+ * BufferedReader}.
+ *
+ * <p>Large streams (over 2GB) will return a chars copied value of {@code -1} after the copy has
+ * completed since the correct number of chars cannot be returned as an int. For large streams use
+ * the {@link #copyLarge(Reader, Writer)} method.
+ *
+ * @param reader the {@link Reader} to read.
+ * @param writer the {@link Writer} to write.
+ * @return the number of characters copied, or -1 if > Integer.MAX_VALUE
+ * @throws NullPointerException if the input or output is null
+ * @throws IOException if an I/O error occurs
+ * @since 1.1
+ */
+ public static int copy(final Reader reader, final Writer writer) throws IOException {
+ final long count = copyLarge(reader, writer);
+ if (count > Integer.MAX_VALUE) {
+ return EOF;
+ }
+ return (int) count;
+ }
+
+ /**
+ * Copies chars from a large (over 2GB) {@link Reader} to a {@link Writer}.
+ *
+ * <p>This method buffers the input internally, so there is no need to use a {@link
+ * BufferedReader}.
+ *
+ * <p>The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}.
+ *
+ * @param reader the {@link Reader} to source.
+ * @param writer the {@link Writer} to target.
+ * @return the number of characters copied
+ * @throws NullPointerException if the input or output is null
+ * @throws IOException if an I/O error occurs
+ * @since 1.3
+ */
+ public static long copyLarge(final Reader reader, final Writer writer) throws IOException {
+ return copyLarge(reader, writer, getScratchCharArray());
+ }
+
+ /**
+ * Copies chars from a large (over 2GB) {@link Reader} to a {@link Writer}.
+ *
+ * <p>This method uses the provided buffer, so there is no need to use a {@link BufferedReader}.
+ *
+ * @param reader the {@link Reader} to source.
+ * @param writer the {@link Writer} to target.
+ * @param buffer the buffer to be used for the copy
+ * @return the number of characters copied
+ * @throws NullPointerException if the input or output is null
+ * @throws IOException if an I/O error occurs
+ * @since 2.2
+ */
+ public static long copyLarge(final Reader reader, final Writer writer, final char[] buffer)
+ throws IOException {
+ long count = 0;
+ int n;
+ while (EOF != (n = reader.read(buffer))) {
+ writer.write(buffer, 0, n);
+ count += n;
+ }
+ return count;
+ }
+
+ /**
+ * Gets the char byte array buffer, intended for both reading and writing.
+ *
+ * @return the char byte array buffer, intended for both reading and writing.
+ */
+ static char[] getScratchCharArray() {
+ return fill0(SCRATCH_CHAR_BUFFER_RW.get());
+ }
+
+ /**
+ * Fills the given array with 0s.
+ *
+ * @param arr The array to fill.
+ * @return The given array.
+ */
+ private static char[] fill0(final char[] arr) {
+ Arrays.fill(arr, (char) 0);
+ return arr;
+ }
+
+ /**
+ * Gets the contents of an input as a {@code byte[]}.
+ *
+ * @param input the input to read.
+ * @param size the size of the input to read, where 0 < {@code size} <= length of input.
+ * @return byte [] of length {@code size}.
+ * @throws IOException if an I/O error occurs or input length is smaller than parameter {@code
+ * size}.
+ * @throws IllegalArgumentException if {@code size} is less than zero.
+ */
+ static byte[] toByteArray(
+ final IOTriFunction<byte[], Integer, Integer, Integer> input, final int size)
+ throws IOException {
+
+ if (size < 0) {
+ throw new IllegalArgumentException("Size must be equal or greater than zero: " + size);
+ }
+
+ if (size == 0) {
+ return EMPTY_BYTE_ARRAY;
+ }
+
+ final byte[] data = byteArray(size);
+ int offset = 0;
+ int read;
+
+ while (offset < size && (read = input.apply(data, offset, size - offset)) != EOF) {
+ offset += read;
+ }
+
+ if (offset != size) {
+ throw new IOException("Unexpected read size, current: " + offset + ", expected: " + size);
+ }
+
+ return data;
+ }
+
+ /**
+ * Writes chars from a {@link String} to bytes on an {@link OutputStream} using the specified
+ * character encoding.
+ *
+ * <p>This method uses {@link String#getBytes(String)}.
+ *
+ * @param data the {@link String} to write, null ignored
+ * @param output the {@link OutputStream} to write to
+ * @param charset the charset to use, null means platform default
+ * @throws NullPointerException if output is null
+ * @throws IOException if an I/O error occurs
+ * @since 2.3
+ */
+ @SuppressWarnings("resource")
+ public static void write(final String data, final OutputStream output, final Charset charset)
+ throws IOException {
+ if (data != null) {
+ // Use Charset#encode(String), since calling String#getBytes(Charset) might result in
+ // NegativeArraySizeException or OutOfMemoryError.
+ // The underlying OutputStream should not be closed, so the channel is not closed.
+ Channels.newChannel(output).write(Charsets.toCharset(charset).encode(data));
+ }
+ }
+
+ /**
+ * Gets the contents of an {@link InputStream} from a supplier as a String using the specified
+ * character encoding.
+ *
+ * <p>This method buffers the input internally, so there is no need to use a {@link
+ * BufferedInputStream}.
+ *
+ * @param input supplies the {@link InputStream} to read
+ * @param charset the charset to use, null means platform default
+ * @return the requested String
+ * @throws NullPointerException if the input is null
+ * @throws IOException if an I/O error occurs
+ * @since 2.12.0
+ */
+ public static String toString(final IOSupplier<InputStream> input, final Charset charset)
+ throws IOException {
+ return toString(
+ input,
+ charset,
+ () -> {
+ throw new NullPointerException("input");
+ });
+ }
+
+ /**
+ * Gets the contents of an {@link InputStream} from a supplier as a String using the specified
+ * character encoding.
+ *
+ * <p>This method buffers the input internally, so there is no need to use a {@link
+ * BufferedInputStream}.
+ *
+ * @param input supplies the {@link InputStream} to read
+ * @param charset the charset to use, null means platform default
+ * @param defaultString the default return value if the supplier or its value is null.
+ * @return the requested String
+ * @throws NullPointerException if the input is null
+ * @throws IOException if an I/O error occurs
+ * @since 2.12.0
+ */
+ public static String toString(
+ final IOSupplier<InputStream> input,
+ final Charset charset,
+ final IOSupplier<String> defaultString)
+ throws IOException {
+ if (input == null) {
+ return defaultString.get();
+ }
+ try (InputStream inputStream = input.get()) {
+ return inputStream != null ? toString(inputStream, charset) : defaultString.get();
+ }
+ }
+
+ /**
+ * Gets the contents of an {@link InputStream} as a String using the specified character encoding.
+ *
+ * <p>This method buffers the input internally, so there is no need to use a {@link
+ * BufferedInputStream}.
+ *
+ * @param input the {@link InputStream} to read
+ * @param charset the charset to use, null means platform default
+ * @return the requested String
+ * @throws NullPointerException if the input is null
+ * @throws IOException if an I/O error occurs
+ * @since 2.3
+ */
+ public static String toString(final InputStream input, final Charset charset) throws IOException {
+ try (StringBuilderWriter sw = new StringBuilderWriter()) {
+ copy(input, sw, charset);
+ return sw.toString();
+ }
+ }
+
+ /**
+ * Copies bytes from an {@link InputStream} to chars on a {@link Writer} using the specified
+ * character encoding.
+ *
+ * <p>This method buffers the input internally, so there is no need to use a {@link
+ * BufferedInputStream}.
+ *
+ * <p>This method uses {@link InputStreamReader}.
+ *
+ * @param input the {@link InputStream} to read
+ * @param writer the {@link Writer} to write to
+ * @param inputCharset the charset to use for the input stream, null means platform default
+ * @throws NullPointerException if the input or output is null
+ * @throws IOException if an I/O error occurs
+ * @since 2.3
+ */
+ public static void copy(final InputStream input, final Writer writer, final Charset inputCharset)
+ throws IOException {
+ final InputStreamReader reader = new InputStreamReader(input, Charsets.toCharset(inputCharset));
+ copy(reader, writer);
+ }
+}
diff --git a/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/RandomAccessFileMode.java b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/RandomAccessFileMode.java
new file mode 100644
index 00000000..13a1b314
--- /dev/null
+++ b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/RandomAccessFileMode.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tsfile.external.commons.io;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.RandomAccessFile;
+import java.nio.file.Path;
+
+/**
+ * Access modes and factory methods for {@link RandomAccessFile}.
+ *
+ * @since 2.12.0
+ */
+public enum RandomAccessFileMode {
+
+ /** Mode {@code "r"} opens for reading only. */
+ READ_ONLY("r"),
+
+ /** Mode {@code "rw"} opens for reading and writing. */
+ READ_WRITE("rw"),
+
+ /**
+ * Mode {@code "rws"} opens for reading and writing, as with {@code "rw"}, and also require that
+ * every update to the file's content or metadata be written synchronously to the underlying
+ * storage device.
+ */
+ READ_WRITE_SYNC_ALL("rws"),
+
+ /**
+ * Mode {@code "rwd"} open for reading and writing, as with {@code "rw"}, and also require that
+ * every update to the file's content be written synchronously to the underlying storage device.
+ */
+ READ_WRITE_SYNC_CONTENT("rwd");
+
+ private final String mode;
+
+ RandomAccessFileMode(final String mode) {
+ this.mode = mode;
+ }
+
+ /**
+ * Constructs a random access file stream to read from, and optionally to write to, the file
+ * specified by the {@link File} argument.
+ *
+ * @param file the file object
+ * @return a random access file stream
+ * @throws FileNotFoundException See {@link RandomAccessFile#RandomAccessFile(File, String)}.
+ */
+ public RandomAccessFile create(final File file) throws FileNotFoundException {
+ return new RandomAccessFile(file, mode);
+ }
+
+ /**
+ * Constructs a random access file stream to read from, and optionally to write to, the file
+ * specified by the {@link File} argument.
+ *
+ * @param file the file object
+ * @return a random access file stream
+ * @throws FileNotFoundException See {@link RandomAccessFile#RandomAccessFile(File, String)}.
+ */
+ public RandomAccessFile create(final Path file) throws FileNotFoundException {
+ return create(file.toFile());
+ }
+
+ /**
+ * Constructs a random access file stream to read from, and optionally to write to, the file
+ * specified by the {@link File} argument.
+ *
+ * @param file the file object
+ * @return a random access file stream
+ * @throws FileNotFoundException See {@link RandomAccessFile#RandomAccessFile(File, String)}.
+ */
+ public RandomAccessFile create(final String file) throws FileNotFoundException {
+ return new RandomAccessFile(file, mode);
+ }
+
+ @Override
+ public String toString() {
+ return mode;
+ }
+}
diff --git a/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/RandomAccessFiles.java b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/RandomAccessFiles.java
new file mode 100644
index 00000000..e69f79a1
--- /dev/null
+++ b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/RandomAccessFiles.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.tsfile.external.commons.io;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+/**
+ * Works on RandomAccessFile.
+ *
+ * @since 2.13.0
+ */
+public class RandomAccessFiles {
+
+ /**
+ * Reads a byte array starting at "position" for "length" bytes.
+ *
+ * @param input The source RandomAccessFile.
+ * @param position The offset position, measured in bytes from the beginning of the file, at which
+ * to set the file pointer.
+ * @param length How many bytes to read.
+ * @return a new byte array.
+ * @throws IOException If the first byte cannot be read for any reason other than end of file, or
+ * if the random access file has been closed, or if some other I/O error occurs.
+ */
+ public static byte[] read(final RandomAccessFile input, final long position, final int length)
+ throws IOException {
+ input.seek(position);
+ return IOUtils.toByteArray(input::read, length);
+ }
+}
diff --git a/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/build/AbstractOrigin.java b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/build/AbstractOrigin.java
new file mode 100644
index 00000000..5189dfa8
--- /dev/null
+++ b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/build/AbstractOrigin.java
@@ -0,0 +1,484 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.tsfile.external.commons.io.build;
+
+import org.apache.tsfile.external.commons.io.IOUtils;
+import org.apache.tsfile.external.commons.io.input.ReaderInputStream;
+import org.apache.tsfile.external.commons.io.output.WriterOutputStream;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.Writer;
+import java.net.URI;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Objects;
+
+/**
+ * Abstracts the origin of data for builders like a {@link File}, {@link Path}, {@link Reader},
+ * {@link Writer}, {@link InputStream}, {@link OutputStream}, and {@link URI}.
+ *
+ * <p>Some methods may throw {@link UnsupportedOperationException} if that method is not implemented
+ * in a concrete subclass, see {@link #getFile()} and {@link #getPath()}.
+ *
+ * @param <T> the type of instances to build.
+ * @param <B> the type of builder subclass.
+ * @since 2.12.0
+ */
+public abstract class AbstractOrigin<T, B extends AbstractOrigin<T, B>>
+ extends AbstractSupplier<T, B> {
+
+ /** A {@code byte[]} origin. */
+ public static class ByteArrayOrigin extends AbstractOrigin<byte[], ByteArrayOrigin> {
+
+ /**
+ * Constructs a new instance for the given origin.
+ *
+ * @param origin The origin.
+ */
+ public ByteArrayOrigin(final byte[] origin) {
+ super(origin);
+ }
+
+ @Override
+ public byte[] getByteArray() {
+ // No conversion
+ return get();
+ }
+
+ @Override
+ public InputStream getInputStream(final OpenOption... options) throws IOException {
+ return new ByteArrayInputStream(origin);
+ }
+
+ @Override
+ public Reader getReader(final Charset charset) throws IOException {
+ return new InputStreamReader(getInputStream(), charset);
+ }
+
+ @Override
+ public long size() throws IOException {
+ return origin.length;
+ }
+ }
+
+ /** A {@link CharSequence} origin. */
+ public static class CharSequenceOrigin extends AbstractOrigin<CharSequence, CharSequenceOrigin> {
+
+ /**
+ * Constructs a new instance for the given origin.
+ *
+ * @param origin The origin.
+ */
+ public CharSequenceOrigin(final CharSequence origin) {
+ super(origin);
+ }
+
+ @Override
+ public byte[] getByteArray() {
+ // TODO Pass in a Charset? Consider if call sites actually need this.
+ return origin.toString().getBytes(Charset.defaultCharset());
+ }
+
+ @Override
+ public CharSequence getCharSequence(final Charset charset) {
+ // No conversion
+ return get();
+ }
+
+ @Override
+ public InputStream getInputStream(final OpenOption... options) throws IOException {
+ // TODO Pass in a Charset? Consider if call sites actually need this.
+ return new ByteArrayInputStream(origin.toString().getBytes(Charset.defaultCharset()));
+ // Needs [IO-795] CharSequenceInputStream.reset() only works once.
+ // return
+ // CharSequenceInputStream.builder().setCharSequence(getCharSequence(Charset.defaultCharset())).get();
+ }
+
+ @Override
+ public Reader getReader(final Charset charset) throws IOException {
+ return new InputStreamReader(getInputStream(), charset);
+ }
+
+ @Override
+ public long size() throws IOException {
+ return origin.length();
+ }
+ }
+
+ /**
+ * A {@link File} origin.
+ *
+ * <p>Starting from this origin, you can get a byte array, a file, an input stream, an output
+ * stream, a path, a reader, and a writer.
+ */
+ public static class FileOrigin extends AbstractOrigin<File, FileOrigin> {
+
+ /**
+ * Constructs a new instance for the given origin.
+ *
+ * @param origin The origin.
+ */
+ public FileOrigin(final File origin) {
+ super(origin);
+ }
+
+ @Override
+ public File getFile() {
+ // No conversion
+ return get();
+ }
+
+ @Override
+ public Path getPath() {
+ return get().toPath();
+ }
+ }
+
+ /**
+ * An {@link InputStream} origin.
+ *
+ * <p>This origin cannot provide some of the other aspects.
+ */
+ public static class InputStreamOrigin extends AbstractOrigin<InputStream, InputStreamOrigin> {
+
+ /**
+ * Constructs a new instance for the given origin.
+ *
+ * @param origin The origin.
+ */
+ public InputStreamOrigin(final InputStream origin) {
+ super(origin);
+ }
+
+ @Override
+ public byte[] getByteArray() throws IOException {
+ return IOUtils.toByteArray(origin);
+ }
+
+ @Override
+ public InputStream getInputStream(final OpenOption... options) {
+ // No conversion
+ return get();
+ }
+
+ @Override
+ public Reader getReader(final Charset charset) throws IOException {
+ return new InputStreamReader(getInputStream(), charset);
+ }
+ }
+
+ /**
+ * An {@link OutputStream} origin.
+ *
+ * <p>This origin cannot provide some of the other aspects.
+ */
+ public static class OutputStreamOrigin extends AbstractOrigin<OutputStream, OutputStreamOrigin> {
+
+ /**
+ * Constructs a new instance for the given origin.
+ *
+ * @param origin The origin.
+ */
+ public OutputStreamOrigin(final OutputStream origin) {
+ super(origin);
+ }
+
+ @Override
+ public OutputStream getOutputStream(final OpenOption... options) {
+ // No conversion
+ return get();
+ }
+
+ @Override
+ public Writer getWriter(final Charset charset, final OpenOption... options) throws IOException {
+ return new OutputStreamWriter(origin, charset);
+ }
+ }
+
+ /**
+ * A {@link Path} origin.
+ *
+ * <p>Starting from this origin, you can get a byte array, a file, an input stream, an output
+ * stream, a path, a reader, and a writer.
+ */
+ public static class PathOrigin extends AbstractOrigin<Path, PathOrigin> {
+
+ /**
+ * Constructs a new instance for the given origin.
+ *
+ * @param origin The origin.
+ */
+ public PathOrigin(final Path origin) {
+ super(origin);
+ }
+
+ @Override
+ public File getFile() {
+ return get().toFile();
+ }
+
+ @Override
+ public Path getPath() {
+ // No conversion
+ return get();
+ }
+ }
+
+ /**
+ * An {@link Reader} origin.
+ *
+ * <p>This origin cannot provide other aspects.
+ */
+ public static class ReaderOrigin extends AbstractOrigin<Reader, ReaderOrigin> {
+
+ /**
+ * Constructs a new instance for the given origin.
+ *
+ * @param origin The origin.
+ */
+ public ReaderOrigin(final Reader origin) {
+ super(origin);
+ }
+
+ @Override
+ public byte[] getByteArray() throws IOException {
+ // TODO Pass in a Charset? Consider if call sites actually need this.
+ return IOUtils.toByteArray(origin, Charset.defaultCharset());
+ }
+
+ @Override
+ public CharSequence getCharSequence(final Charset charset) throws IOException {
+ return IOUtils.toString(origin);
+ }
+
+ @Override
+ public InputStream getInputStream(final OpenOption... options) throws IOException {
+ // TODO Pass in a Charset? Consider if call sites actually need this.
+ return ReaderInputStream.builder()
+ .setReader(origin)
+ .setCharset(Charset.defaultCharset())
+ .get();
+ }
+
+ @Override
+ public Reader getReader(final Charset charset) throws IOException {
+ // No conversion
+ return get();
+ }
+ }
+
+ /** A {@link URI} origin. */
+ public static class URIOrigin extends AbstractOrigin<URI, URIOrigin> {
+
+ /**
+ * Constructs a new instance for the given origin.
+ *
+ * @param origin The origin.
+ */
+ public URIOrigin(final URI origin) {
+ super(origin);
+ }
+
+ @Override
+ public File getFile() {
+ return getPath().toFile();
+ }
+
+ @Override
+ public Path getPath() {
+ return Paths.get(get());
+ }
+ }
+
+ /**
+ * An {@link Writer} origin.
+ *
+ * <p>This origin cannot provide other aspects.
+ */
+ public static class WriterOrigin extends AbstractOrigin<Writer, WriterOrigin> {
+
+ /**
+ * Constructs a new instance for the given origin.
+ *
+ * @param origin The origin.
+ */
+ public WriterOrigin(final Writer origin) {
+ super(origin);
+ }
+
+ @Override
+ public OutputStream getOutputStream(final OpenOption... options) throws IOException {
+ // TODO Pass in a Charset? Consider if call sites actually need this.
+ return WriterOutputStream.builder()
+ .setWriter(origin)
+ .setCharset(Charset.defaultCharset())
+ .get();
+ }
+
+ @Override
+ public Writer getWriter(final Charset charset, final OpenOption... options) throws IOException {
+ // No conversion
+ return get();
+ }
+ }
+
+ /** The non-null origin. */
+ final T origin;
+
+ /**
+ * Constructs a new instance for a subclass.
+ *
+ * @param origin The origin.
+ */
+ protected AbstractOrigin(final T origin) {
+ this.origin = Objects.requireNonNull(origin, "origin");
+ }
+
+ /**
+ * Gets the origin.
+ *
+ * @return the origin.
+ */
+ @Override
+ public T get() {
+ return origin;
+ }
+
+ /**
+ * Gets this origin as a byte array, if possible.
+ *
+ * @return this origin as a byte array, if possible.
+ * @throws IOException if an I/O error occurs.
+ * @throws UnsupportedOperationException if the origin cannot be converted to a Path.
+ */
+ public byte[] getByteArray() throws IOException {
+ return Files.readAllBytes(getPath());
+ }
+
+ /**
+ * Gets this origin as a byte array, if possible.
+ *
+ * @param charset The charset to use if conversion from bytes is needed.
+ * @return this origin as a byte array, if possible.
+ * @throws IOException if an I/O error occurs.
+ * @throws UnsupportedOperationException if the origin cannot be converted to a Path.
+ */
+ public CharSequence getCharSequence(final Charset charset) throws IOException {
+ return new String(getByteArray(), charset);
+ }
+
+ /**
+ * Gets this origin as a Path, if possible.
+ *
+ * @return this origin as a Path, if possible.
+ * @throws UnsupportedOperationException if this method is not implemented in a concrete subclass.
+ */
+ public File getFile() {
+ throw new UnsupportedOperationException(
+ String.format(
+ "%s#getFile() for %s origin %s",
+ getClass().getSimpleName(), origin.getClass().getSimpleName(), origin));
+ }
+
+ /**
+ * Gets this origin as an InputStream, if possible.
+ *
+ * @param options options specifying how the file is opened
+ * @return this origin as an InputStream, if possible.
+ * @throws IOException if an I/O error occurs.
+ * @throws UnsupportedOperationException if the origin cannot be converted to a Path.
+ */
+ public InputStream getInputStream(final OpenOption... options) throws IOException {
+ return Files.newInputStream(getPath(), options);
+ }
+
+ /**
+ * Gets this origin as an OutputStream, if possible.
+ *
+ * @param options options specifying how the file is opened
+ * @return this origin as an OutputStream, if possible.
+ * @throws IOException if an I/O error occurs.
+ * @throws UnsupportedOperationException if the origin cannot be converted to a Path.
+ */
+ public OutputStream getOutputStream(final OpenOption... options) throws IOException {
+ return Files.newOutputStream(getPath(), options);
+ }
+
+ /**
+ * Gets this origin as a Path, if possible.
+ *
+ * @return this origin as a Path, if possible.
+ * @throws UnsupportedOperationException if this method is not implemented in a concrete subclass.
+ */
+ public Path getPath() {
+ throw new UnsupportedOperationException(
+ String.format(
+ "%s#getPath() for %s origin %s",
+ getClass().getSimpleName(), origin.getClass().getSimpleName(), origin));
+ }
+
+ /**
+ * Gets a new Reader on the origin, buffered by default.
+ *
+ * @param charset the charset to use for decoding
+ * @return a new Reader on the origin.
+ * @throws IOException if an I/O error occurs opening the file.
+ */
+ public Reader getReader(final Charset charset) throws IOException {
+ return Files.newBufferedReader(getPath(), charset);
+ }
+
+ /**
+ * Gets a new Writer on the origin, buffered by default.
+ *
+ * @param charset the charset to use for encoding
+ * @param options options specifying how the file is opened
+ * @return a new Writer on the origin.
+ * @throws IOException if an I/O error occurs opening or creating the file.
+ * @throws UnsupportedOperationException if the origin cannot be converted to a Path.
+ */
+ public Writer getWriter(final Charset charset, final OpenOption... options) throws IOException {
+ return Files.newBufferedWriter(getPath(), charset, options);
+ }
+
+ /**
+ * Gets the size of the origin, if possible.
+ *
+ * @return the size of the origin in bytes or characters.
+ * @throws IOException if an I/O error occurs.
+ * @since 2.13.0
+ */
+ public long size() throws IOException {
+ return Files.size(getPath());
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + "[" + origin.toString() + "]";
+ }
+}
diff --git a/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/build/AbstractOriginSupplier.java b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/build/AbstractOriginSupplier.java
new file mode 100644
index 00000000..60ff5569
--- /dev/null
+++ b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/build/AbstractOriginSupplier.java
@@ -0,0 +1,276 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.tsfile.external.commons.io.build;
+
+import org.apache.tsfile.external.commons.io.build.AbstractOrigin.ByteArrayOrigin;
+import org.apache.tsfile.external.commons.io.build.AbstractOrigin.CharSequenceOrigin;
+import org.apache.tsfile.external.commons.io.build.AbstractOrigin.FileOrigin;
+import org.apache.tsfile.external.commons.io.build.AbstractOrigin.InputStreamOrigin;
+import org.apache.tsfile.external.commons.io.build.AbstractOrigin.OutputStreamOrigin;
+import org.apache.tsfile.external.commons.io.build.AbstractOrigin.PathOrigin;
+import org.apache.tsfile.external.commons.io.build.AbstractOrigin.ReaderOrigin;
+import org.apache.tsfile.external.commons.io.build.AbstractOrigin.URIOrigin;
+import org.apache.tsfile.external.commons.io.build.AbstractOrigin.WriterOrigin;
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.Writer;
+import java.net.URI;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+/**
+ * Abstracts building an instance of {@code T}.
+ *
+ * @param <T> the type of instances to build.
+ * @param <B> the type of builder subclass.
+ * @since 2.12.0
+ */
+public abstract class AbstractOriginSupplier<T, B extends AbstractOriginSupplier<T, B>>
+ extends AbstractSupplier<T, B> {
+
+ /**
+ * Constructs a new byte array origin for a byte array.
+ *
+ * @param origin the byte array.
+ * @return a new byte array origin.
+ */
+ protected static ByteArrayOrigin newByteArrayOrigin(final byte[] origin) {
+ return new ByteArrayOrigin(origin);
+ }
+
+ /**
+ * Constructs a new CharSequence origin for a CharSequence.
+ *
+ * @param origin the CharSequence.
+ * @return a new file origin.
+ * @since 2.13.0
+ */
+ protected static CharSequenceOrigin newCharSequenceOrigin(final CharSequence origin) {
+ return new CharSequenceOrigin(origin);
+ }
+
+ /**
+ * Constructs a new file origin for a file.
+ *
+ * @param origin the file.
+ * @return a new file origin.
+ */
+ protected static FileOrigin newFileOrigin(final File origin) {
+ return new FileOrigin(origin);
+ }
+
+ /**
+ * Constructs a new file origin for a file path.
+ *
+ * @param origin the file path.
+ * @return a new file origin.
+ */
+ protected static FileOrigin newFileOrigin(final String origin) {
+ return new FileOrigin(new File(origin));
+ }
+
+ /**
+ * Constructs a new input stream origin for a file.
+ *
+ * @param origin the input stream.
+ * @return a new input stream origin.
+ */
+ protected static InputStreamOrigin newInputStreamOrigin(final InputStream origin) {
+ return new InputStreamOrigin(origin);
+ }
+
+ /**
+ * Constructs a new output stream origin for a file.
+ *
+ * @param origin the output stream.
+ * @return a new output stream origin.
+ */
+ protected static OutputStreamOrigin newOutputStreamOrigin(final OutputStream origin) {
+ return new OutputStreamOrigin(origin);
+ }
+
+ /**
+ * Constructs a new path origin for a file.
+ *
+ * @param origin the path.
+ * @return a new path origin.
+ */
+ protected static PathOrigin newPathOrigin(final Path origin) {
+ return new PathOrigin(origin);
+ }
+
+ /**
+ * Constructs a new path name origin for a path name.
+ *
+ * @param origin the path name.
+ * @return a new path name origin.
+ */
+ protected static PathOrigin newPathOrigin(final String origin) {
+ return new PathOrigin(Paths.get(origin));
+ }
+
+ /**
+ * Constructs a new reader origin for a reader.
+ *
+ * @param origin the reader.
+ * @return a new reader origin.
+ */
+ protected static ReaderOrigin newReaderOrigin(final Reader origin) {
+ return new ReaderOrigin(origin);
+ }
+
+ /**
+ * Constructs a new reader origin for a URI.
+ *
+ * @param origin the URI.
+ * @return a new URI origin.
+ */
+ protected static URIOrigin newURIOrigin(final URI origin) {
+ return new URIOrigin(origin);
+ }
+
+ /**
+ * Constructs a new writer origin for a file.
+ *
+ * @param origin the writer.
+ * @return a new writer .
+ */
+ protected static WriterOrigin newWriterOrigin(final Writer origin) {
+ return new WriterOrigin(origin);
+ }
+
+ /** The underlying origin. */
+ private AbstractOrigin<?, ?> origin;
+
+ /**
+ * Checks whether the origin is null.
+ *
+ * @return the origin.
+ * @throws IllegalStateException if the {@code origin} is {@code null}.
+ */
+ protected AbstractOrigin<?, ?> checkOrigin() {
+ if (origin == null) {
+ throw new IllegalStateException("origin == null");
+ }
+ return origin;
+ }
+
+ /**
+ * Sets a new origin.
+ *
+ * @param origin the new origin.
+ * @return this
+ */
+ public B setByteArray(final byte[] origin) {
+ return setOrigin(newByteArrayOrigin(origin));
+ }
+
+ /**
+ * Sets a new origin.
+ *
+ * @param origin the new origin.
+ * @return this
+ */
+ public B setFile(final File origin) {
+ return setOrigin(newFileOrigin(origin));
+ }
+
+ /**
+ * Sets a new origin.
+ *
+ * @param origin the new origin.
+ * @return this
+ */
+ public B setFile(final String origin) {
+ return setOrigin(newFileOrigin(origin));
+ }
+
+ /**
+ * Sets a new origin.
+ *
+ * @param origin the new origin.
+ * @return this
+ */
+ public B setInputStream(final InputStream origin) {
+ return setOrigin(newInputStreamOrigin(origin));
+ }
+
+ /**
+ * Sets a new origin.
+ *
+ * @param origin the new origin.
+ * @return this
+ */
+ protected B setOrigin(final AbstractOrigin<?, ?> origin) {
+ this.origin = origin;
+ return asThis();
+ }
+
+ /**
+ * Sets a new origin.
+ *
+ * @param origin the new origin.
+ * @return this
+ */
+ public B setOutputStream(final OutputStream origin) {
+ return setOrigin(newOutputStreamOrigin(origin));
+ }
+
+ /**
+ * Sets a new origin.
+ *
+ * @param origin the new origin.
+ * @return this
+ */
+ public B setPath(final Path origin) {
+ return setOrigin(newPathOrigin(origin));
+ }
+
+ /**
+ * Sets a new origin.
+ *
+ * @param origin the new origin.
+ * @return this
+ */
+ public B setPath(final String origin) {
+ return setOrigin(newPathOrigin(origin));
+ }
+
+ /**
+ * Sets a new origin.
+ *
+ * @param origin the new origin.
+ * @return this
+ */
+ public B setReader(final Reader origin) {
+ return setOrigin(newReaderOrigin(origin));
+ }
+
+ /**
+ * Sets a new origin.
+ *
+ * @param origin the new origin.
+ * @return this
+ */
+ public B setWriter(final Writer origin) {
+ return setOrigin(newWriterOrigin(origin));
+ }
+}
diff --git a/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/build/AbstractStreamBuilder.java b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/build/AbstractStreamBuilder.java
new file mode 100644
index 00000000..cd5d7513
--- /dev/null
+++ b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/build/AbstractStreamBuilder.java
@@ -0,0 +1,177 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.tsfile.external.commons.io.build;
+
+import org.apache.tsfile.external.commons.io.Charsets;
+import org.apache.tsfile.external.commons.io.IOUtils;
+import org.apache.tsfile.external.commons.io.file.PathUtils;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.nio.charset.Charset;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.util.function.IntUnaryOperator;
+
+/**
+ * Abstracts building a typed instance of {@code T}.
+ *
+ * @param <T> the type of instances to build.
+ * @param <B> the type of builder subclass.
+ * @since 2.12.0
+ */
+public abstract class AbstractStreamBuilder<T, B extends AbstractStreamBuilder<T, B>>
+ extends AbstractOriginSupplier<T, B> {
+
+ private static final int DEFAULT_MAX_VALUE = Integer.MAX_VALUE;
+
+ private static final OpenOption[] DEFAULT_OPEN_OPTIONS = PathUtils.EMPTY_OPEN_OPTION_ARRAY;
+
+ /**
+ * The buffer size, defaults to {@link IOUtils#DEFAULT_BUFFER_SIZE} ({@value
+ * IOUtils#DEFAULT_BUFFER_SIZE}).
+ */
+ private int bufferSize = IOUtils.DEFAULT_BUFFER_SIZE;
+
+ /**
+ * The buffer size, defaults to {@link IOUtils#DEFAULT_BUFFER_SIZE} ({@value
+ * IOUtils#DEFAULT_BUFFER_SIZE}).
+ */
+ private int bufferSizeDefault = IOUtils.DEFAULT_BUFFER_SIZE;
+
+ /** The maximum buffer size. */
+ private int bufferSizeMax = DEFAULT_MAX_VALUE;
+
+ /** The Charset, defaults to {@link Charset#defaultCharset()}. */
+ private Charset charset = Charset.defaultCharset();
+
+ /** The Charset, defaults to {@link Charset#defaultCharset()}. */
+ private Charset charsetDefault = Charset.defaultCharset();
+
+ private OpenOption[] openOptions = DEFAULT_OPEN_OPTIONS;
+
+ /**
+ * The default checking behavior for a buffer size request. Throws a {@link
+ * IllegalArgumentException} by default.
+ */
+ private final IntUnaryOperator defaultSizeChecker =
+ size -> size > bufferSizeMax ? throwIae(size, bufferSizeMax) : size;
+
+ /** The checking behavior for a buffer size request. */
+ private IntUnaryOperator bufferSizeChecker = defaultSizeChecker;
+
+ /**
+ * Applies the buffer size request.
+ *
+ * @param size the size request.
+ * @return the size to use, usually the input, or can throw an unchecked exception, like {@link
+ * IllegalArgumentException}.
+ */
+ private int checkBufferSize(final int size) {
+ return bufferSizeChecker.applyAsInt(size);
+ }
+
+ /**
+ * Gets the buffer size, defaults to {@link IOUtils#DEFAULT_BUFFER_SIZE} ({@value
+ * IOUtils#DEFAULT_BUFFER_SIZE}).
+ *
+ * @return the buffer size, defaults to {@link IOUtils#DEFAULT_BUFFER_SIZE} ({@value
+ * IOUtils#DEFAULT_BUFFER_SIZE}).
+ */
+ protected int getBufferSize() {
+ return bufferSize;
+ }
+
+ /**
+ * Gets the Charset, defaults to {@link Charset#defaultCharset()}.
+ *
+ * @return the Charset, defaults to {@link Charset#defaultCharset()}.
+ */
+ public Charset getCharset() {
+ return charset;
+ }
+
+ /**
+ * Gets the Charset default, defaults to {@link Charset#defaultCharset()}.
+ *
+ * @return the Charset default, defaults to {@link Charset#defaultCharset()}.
+ */
+ protected Charset getCharsetDefault() {
+ return charsetDefault;
+ }
+
+ protected OpenOption[] getOpenOptions() {
+ return openOptions;
+ }
+
+ /**
+ * Gets a Path from the origin.
+ *
+ * @return A Path
+ * @throws UnsupportedOperationException if the origin cannot be converted to a Path.
+ * @throws IllegalStateException if the {@code origin} is {@code null}.
+ * @see AbstractOrigin#getPath()
+ * @since 2.13.0
+ */
+ protected Path getPath() {
+ return checkOrigin().getPath();
+ }
+
+ /**
+ * Gets an writer from the origin with open options.
+ *
+ * @return An writer.
+ * @throws IOException if an I/O error occurs.
+ * @throws UnsupportedOperationException if the origin cannot be converted to a Writer.
+ * @throws IllegalStateException if the {@code origin} is {@code null}.
+ * @see AbstractOrigin#getOutputStream(OpenOption...)
+ * @since 2.13.0
+ */
+ protected Writer getWriter() throws IOException {
+ return checkOrigin().getWriter(getCharset(), getOpenOptions());
+ }
+
+ /**
+ * Sets the Charset.
+ *
+ * <p>Subclasses may ignore this setting.
+ *
+ * @param charset the Charset, null resets to the default.
+ * @return this.
+ */
+ public B setCharset(final Charset charset) {
+ this.charset = Charsets.toCharset(charset, charsetDefault);
+ return asThis();
+ }
+
+ /**
+ * Sets the Charset.
+ *
+ * <p>Subclasses may ignore this setting.
+ *
+ * @param charset the Charset name, null resets to the default.
+ * @return this.
+ */
+ public B setCharset(final String charset) {
+ return setCharset(Charsets.toCharset(charset, charsetDefault));
+ }
+
+ private int throwIae(final int size, final int max) {
+ throw new IllegalArgumentException(String.format("Request %,d exceeds maximum %,d", size, max));
+ }
+}
diff --git a/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/build/AbstractSupplier.java b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/build/AbstractSupplier.java
new file mode 100644
index 00000000..fdb42d6a
--- /dev/null
+++ b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/build/AbstractSupplier.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.tsfile.external.commons.io.build;
+
+import org.apache.tsfile.external.commons.io.function.IOSupplier;
+
+/**
+ * Abstracts supplying an instance of {@code T}. Use to implement the builder pattern.
+ *
+ * @param <T> the type of instances to build.
+ * @param <B> the type of builder subclass.
+ * @since 2.12.0
+ */
+public abstract class AbstractSupplier<T, B extends AbstractSupplier<T, B>>
+ implements IOSupplier<T> {
+
+ /**
+ * Returns this instance typed as the proper subclass type.
+ *
+ * @return this instance typed as the proper subclass type.
+ */
+ @SuppressWarnings("unchecked")
+ protected B asThis() {
+ return (B) this;
+ }
+}
diff --git a/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/charset/CharsetDecoders.java b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/charset/CharsetDecoders.java
new file mode 100644
index 00000000..b7c356c5
--- /dev/null
+++ b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/charset/CharsetDecoders.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.tsfile.external.commons.io.charset;
+
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+
+/**
+ * Works with {@link CharsetDecoder}.
+ *
+ * @since 2.12.0
+ */
+public final class CharsetDecoders {
+
+ /**
+ * Returns the given non-null CharsetDecoder or a new default CharsetDecoder.
+ *
+ * @param charsetDecoder The CharsetDecoder to test.
+ * @return the given non-null CharsetDecoder or a new default CharsetDecoder.
+ */
+ public static CharsetDecoder toCharsetDecoder(final CharsetDecoder charsetDecoder) {
+ return charsetDecoder != null ? charsetDecoder : Charset.defaultCharset().newDecoder();
+ }
+
+ /** No instances. */
+ private CharsetDecoders() {
+ // No instances.
+ }
+}
diff --git a/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/charset/CharsetEncoders.java b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/charset/CharsetEncoders.java
new file mode 100644
index 00000000..902ba284
--- /dev/null
+++ b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/charset/CharsetEncoders.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.tsfile.external.commons.io.charset;
+
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetEncoder;
+import java.util.function.Supplier;
+
+/**
+ * Works with {@link CharsetEncoder}.
+ *
+ * @since 2.12.0
+ */
+public final class CharsetEncoders {
+
+ /**
+ * Returns the given non-null CharsetEncoder or a new default CharsetEncoder.
+ *
+ * @param charsetEncoder The CharsetEncoder to test.
+ * @return the given non-null CharsetEncoder or a new default CharsetEncoder.
+ */
+ public static CharsetEncoder toCharsetEncoder(final CharsetEncoder charsetEncoder) {
+ return toCharsetEncoder(charsetEncoder, () -> Charset.defaultCharset().newEncoder());
+ }
+
+ /**
+ * Returns the given non-null CharsetEncoder or a new default CharsetEncoder.
+ *
+ * @param charsetEncoder The CharsetEncoder to test.
+ * @param defaultSupplier The CharsetEncoder supplier to get when charsetEncoder is null.
+ * @return the given non-null CharsetEncoder or a new default CharsetEncoder.
+ * @since 2.13.0
+ */
+ public static CharsetEncoder toCharsetEncoder(
+ final CharsetEncoder charsetEncoder, final Supplier<CharsetEncoder> defaultSupplier) {
+ return charsetEncoder != null ? charsetEncoder : defaultSupplier.get();
+ }
+
+ /** No instances. */
+ private CharsetEncoders() {
+ // No instances.
+ }
+}
diff --git a/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/file/Counters.java b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/file/Counters.java
new file mode 100644
index 00000000..3c2fcf60
--- /dev/null
+++ b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/file/Counters.java
@@ -0,0 +1,419 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.tsfile.external.commons.io.file;
+
+import java.math.BigInteger;
+import java.util.Objects;
+
+/**
+ * Provides counters for files, directories, and sizes, as a visit proceeds.
+ *
+ * @since 2.7
+ */
+public class Counters {
+
+ /** Counts files, directories, and sizes, as a visit proceeds. */
+ private static class AbstractPathCounters implements PathCounters {
+
+ private final Counter byteCounter;
+ private final Counter directoryCounter;
+ private final Counter fileCounter;
+
+ /**
+ * Constructs a new instance.
+ *
+ * @param byteCounter the byte counter.
+ * @param directoryCounter the directory counter.
+ * @param fileCounter the file counter.
+ */
+ protected AbstractPathCounters(
+ final Counter byteCounter, final Counter directoryCounter, final Counter fileCounter) {
+ this.byteCounter = byteCounter;
+ this.directoryCounter = directoryCounter;
+ this.fileCounter = fileCounter;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof AbstractPathCounters)) {
+ return false;
+ }
+ final AbstractPathCounters other = (AbstractPathCounters) obj;
+ return Objects.equals(byteCounter, other.byteCounter)
+ && Objects.equals(directoryCounter, other.directoryCounter)
+ && Objects.equals(fileCounter, other.fileCounter);
+ }
+
+ @Override
+ public Counter getByteCounter() {
+ return byteCounter;
+ }
+
+ @Override
+ public Counter getDirectoryCounter() {
+ return directoryCounter;
+ }
+
+ /**
+ * Gets the count of visited files.
+ *
+ * @return the byte count of visited files.
+ */
+ @Override
+ public Counter getFileCounter() {
+ return this.fileCounter;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(byteCounter, directoryCounter, fileCounter);
+ }
+
+ @Override
+ public void reset() {
+ byteCounter.reset();
+ directoryCounter.reset();
+ fileCounter.reset();
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ "%,d files, %,d directories, %,d bytes",
+ Long.valueOf(fileCounter.get()),
+ Long.valueOf(directoryCounter.get()),
+ Long.valueOf(byteCounter.get()));
+ }
+ }
+
+ /** Counts using a {@link BigInteger} number. */
+ private static final class BigIntegerCounter implements Counter {
+
+ private BigInteger value = BigInteger.ZERO;
+
+ @Override
+ public void add(final long val) {
+ value = value.add(BigInteger.valueOf(val));
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof Counter)) {
+ return false;
+ }
+ final Counter other = (Counter) obj;
+ return Objects.equals(value, other.getBigInteger());
+ }
+
+ @Override
+ public long get() {
+ return value.longValueExact();
+ }
+
+ @Override
+ public BigInteger getBigInteger() {
+ return value;
+ }
+
+ @Override
+ public Long getLong() {
+ return Long.valueOf(value.longValueExact());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(value);
+ }
+
+ @Override
+ public void increment() {
+ value = value.add(BigInteger.ONE);
+ }
+
+ @Override
+ public void reset() {
+ value = BigInteger.ZERO;
+ }
+
+ @Override
+ public String toString() {
+ return value.toString();
+ }
+ }
+
+ /** Counts files, directories, and sizes, as a visit proceeds, using BigInteger numbers. */
+ private static final class BigIntegerPathCounters extends AbstractPathCounters {
+
+ /** Constructs a new initialized instance. */
+ protected BigIntegerPathCounters() {
+ super(bigIntegerCounter(), bigIntegerCounter(), bigIntegerCounter());
+ }
+ }
+
+ /** Counts using a number. */
+ public interface Counter {
+
+ /**
+ * Adds the given number to this counter.
+ *
+ * @param val the value to add.
+ */
+ void add(long val);
+
+ /**
+ * Gets the counter as a long.
+ *
+ * @return the counter as a long.
+ */
+ long get();
+
+ /**
+ * Gets the counter as a BigInteger.
+ *
+ * @return the counter as a BigInteger.
+ */
+ BigInteger getBigInteger();
+
+ /**
+ * Gets the counter as a Long.
+ *
+ * @return the counter as a Long.
+ */
+ Long getLong();
+
+ /** Adds one to this counter. */
+ void increment();
+
+ /** Resets this count to 0. */
+ default void reset() {
+ // binary compat, do nothing
+ }
+ }
+
+ /** Counts using a {@code long} number. */
+ private static final class LongCounter implements Counter {
+
+ private long value;
+
+ @Override
+ public void add(final long add) {
+ value += add;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof Counter)) {
+ return false;
+ }
+ final Counter other = (Counter) obj;
+ return value == other.get();
+ }
+
+ @Override
+ public long get() {
+ return value;
+ }
+
+ @Override
+ public BigInteger getBigInteger() {
+ return BigInteger.valueOf(value);
+ }
+
+ @Override
+ public Long getLong() {
+ return Long.valueOf(value);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(value);
+ }
+
+ @Override
+ public void increment() {
+ value++;
+ }
+
+ @Override
+ public void reset() {
+ value = 0L;
+ }
+
+ @Override
+ public String toString() {
+ return Long.toString(value);
+ }
+ }
+
+ /** Counts files, directories, and sizes, as a visit proceeds, using long numbers. */
+ private static final class LongPathCounters extends AbstractPathCounters {
+
+ /** Constructs a new initialized instance. */
+ protected LongPathCounters() {
+ super(longCounter(), longCounter(), longCounter());
+ }
+ }
+
+ /** Counts nothing. */
+ private static final class NoopCounter implements Counter {
+
+ static final NoopCounter INSTANCE = new NoopCounter();
+
+ @Override
+ public void add(final long add) {
+ // noop
+ }
+
+ @Override
+ public long get() {
+ return 0;
+ }
+
+ @Override
+ public BigInteger getBigInteger() {
+ return BigInteger.ZERO;
+ }
+
+ @Override
+ public Long getLong() {
+ return 0L;
+ }
+
+ @Override
+ public void increment() {
+ // noop
+ }
+
+ /**
+ * Returns {@code "0"}, always.
+ *
+ * @return {@code "0"}, always.
+ * @since 2.12.0
+ */
+ @Override
+ public String toString() {
+ return "0";
+ }
+ }
+
+ /** Counts nothing. */
+ private static final class NoopPathCounters extends AbstractPathCounters {
+
+ static final NoopPathCounters INSTANCE = new NoopPathCounters();
+
+ /** Constructs a new initialized instance. */
+ private NoopPathCounters() {
+ super(noopCounter(), noopCounter(), noopCounter());
+ }
+ }
+
+ /** Counts files, directories, and sizes, as a visit proceeds. */
+ public interface PathCounters {
+
+ /**
+ * Gets the byte counter.
+ *
+ * @return the byte counter.
+ */
+ Counter getByteCounter();
+
+ /**
+ * Gets the directory counter.
+ *
+ * @return the directory counter.
+ */
+ Counter getDirectoryCounter();
+
+ /**
+ * Gets the file counter.
+ *
+ * @return the file counter.
+ */
+ Counter getFileCounter();
+
+ /** Resets the counts to 0. */
+ default void reset() {
+ // binary compat, do nothing
+ }
+ }
+
+ /**
+ * Returns a new BigInteger Counter.
+ *
+ * @return a new BigInteger Counter.
+ */
+ public static Counter bigIntegerCounter() {
+ return new BigIntegerCounter();
+ }
+
+ /**
+ * Returns a new BigInteger PathCounters.
+ *
+ * @return a new BigInteger PathCounters.
+ */
+ public static PathCounters bigIntegerPathCounters() {
+ return new BigIntegerPathCounters();
+ }
+
+ /**
+ * Returns a new long Counter.
+ *
+ * @return a new long Counter.
+ */
+ public static Counter longCounter() {
+ return new LongCounter();
+ }
+
+ /**
+ * Returns a new BigInteger PathCounters.
+ *
+ * @return a new BigInteger PathCounters.
+ */
+ public static PathCounters longPathCounters() {
+ return new LongPathCounters();
+ }
+
+ /**
+ * Returns the no-op Counter.
+ *
+ * @return the no-op Counter.
+ * @since 2.9.0
+ */
+ public static Counter noopCounter() {
+ return NoopCounter.INSTANCE;
+ }
+
+ /**
+ * Returns the no-op PathCounters.
+ *
+ * @return the no-op PathCounters.
+ * @since 2.9.0
+ */
+ public static PathCounters noopPathCounters() {
+ return NoopPathCounters.INSTANCE;
+ }
+}
diff --git a/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/file/CountingPathVisitor.java b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/file/CountingPathVisitor.java
new file mode 100644
index 00000000..2caba293
--- /dev/null
+++ b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/file/CountingPathVisitor.java
@@ -0,0 +1,155 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.tsfile.external.commons.io.file;
+
+import org.apache.tsfile.external.commons.io.file.Counters.PathCounters;
+import org.apache.tsfile.external.commons.io.filefilter.IOFileFilter;
+import org.apache.tsfile.external.commons.io.filefilter.SymbolicLinkFileFilter;
+import org.apache.tsfile.external.commons.io.filefilter.TrueFileFilter;
+
+import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.Objects;
+
+/**
+ * Counts files, directories, and sizes, as a visit proceeds.
+ *
+ * @since 2.7
+ */
+public class CountingPathVisitor extends SimplePathVisitor {
+
+ static final String[] EMPTY_STRING_ARRAY = {};
+
+ static IOFileFilter defaultDirFilter() {
+ return TrueFileFilter.INSTANCE;
+ }
+
+ static IOFileFilter defaultFileFilter() {
+ return new SymbolicLinkFileFilter(FileVisitResult.TERMINATE, FileVisitResult.CONTINUE);
+ }
+
+ private final PathCounters pathCounters;
+ private final PathFilter fileFilter;
+ private final PathFilter dirFilter;
+
+ /**
+ * Constructs a new instance.
+ *
+ * @param pathCounter How to count path visits.
+ */
+ public CountingPathVisitor(final PathCounters pathCounter) {
+ this(pathCounter, defaultFileFilter(), defaultDirFilter());
+ }
+
+ /**
+ * Constructs a new instance.
+ *
+ * @param pathCounter How to count path visits.
+ * @param fileFilter Filters which files to count.
+ * @param dirFilter Filters which directories to count.
+ * @since 2.9.0
+ */
+ public CountingPathVisitor(
+ final PathCounters pathCounter, final PathFilter fileFilter, final PathFilter dirFilter) {
+ this.pathCounters = Objects.requireNonNull(pathCounter, "pathCounter");
+ this.fileFilter = Objects.requireNonNull(fileFilter, "fileFilter");
+ this.dirFilter = Objects.requireNonNull(dirFilter, "dirFilter");
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof CountingPathVisitor)) {
+ return false;
+ }
+ final CountingPathVisitor other = (CountingPathVisitor) obj;
+ return Objects.equals(pathCounters, other.pathCounters);
+ }
+
+ /**
+ * Gets the visitation counts.
+ *
+ * @return the visitation counts.
+ */
+ public PathCounters getPathCounters() {
+ return pathCounters;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(pathCounters);
+ }
+
+ @Override
+ public FileVisitResult postVisitDirectory(final Path dir, final IOException exc)
+ throws IOException {
+ updateDirCounter(dir, exc);
+ return FileVisitResult.CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult preVisitDirectory(final Path dir, final BasicFileAttributes attributes)
+ throws IOException {
+ final FileVisitResult accept = dirFilter.accept(dir, attributes);
+ return accept != FileVisitResult.CONTINUE
+ ? FileVisitResult.SKIP_SUBTREE
+ : FileVisitResult.CONTINUE;
+ }
+
+ @Override
+ public String toString() {
+ return pathCounters.toString();
+ }
+
+ /**
+ * Updates the counter for visiting the given directory.
+ *
+ * @param dir the visited directory.
+ * @param exc Encountered exception.
+ * @since 2.9.0
+ */
+ protected void updateDirCounter(final Path dir, final IOException exc) {
+ pathCounters.getDirectoryCounter().increment();
+ }
+
+ /**
+ * Updates the counters for visiting the given file.
+ *
+ * @param file the visited file.
+ * @param attributes the visited file attributes.
+ */
+ protected void updateFileCounters(final Path file, final BasicFileAttributes attributes) {
+ pathCounters.getFileCounter().increment();
+ pathCounters.getByteCounter().add(attributes.size());
+ }
+
+ @Override
+ public FileVisitResult visitFile(final Path file, final BasicFileAttributes attributes)
+ throws IOException {
+ // Note: A file can be a symbolic link to a directory.
+ if (Files.exists(file) && fileFilter.accept(file, attributes) == FileVisitResult.CONTINUE) {
+ updateFileCounters(file, attributes);
+ }
+ return FileVisitResult.CONTINUE;
+ }
+}
diff --git a/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/file/DeleteOption.java b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/file/DeleteOption.java
new file mode 100644
index 00000000..ce0aee7f
--- /dev/null
+++ b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/file/DeleteOption.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.tsfile.external.commons.io.file;
+
+/**
+ * An object that configures how to delete a file.
+ *
+ * <p>The {@link StandardDeleteOption} enumeration type defines our standard options.
+ *
+ * @see StandardDeleteOption
+ * @since 2.8.0
+ */
+public interface DeleteOption {
+ // empty
+}
diff --git a/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/file/DeletingPathVisitor.java b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/file/DeletingPathVisitor.java
new file mode 100644
index 00000000..dcf56933
--- /dev/null
+++ b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/file/DeletingPathVisitor.java
@@ -0,0 +1,157 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.tsfile.external.commons.io.file;
+
+import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * Deletes files and directories as a visit proceeds.
+ *
+ * @since 2.7
+ */
+public class DeletingPathVisitor extends CountingPathVisitor {
+
+ private final String[] skip;
+ private final boolean overrideReadOnly;
+ private final LinkOption[] linkOptions;
+
+ /**
+ * Constructs a new visitor that deletes files except for the files and directories explicitly
+ * given.
+ *
+ * @param pathCounter How to count visits.
+ * @param deleteOption How deletion is handled.
+ * @param skip The files to skip deleting.
+ * @since 2.8.0
+ */
+ public DeletingPathVisitor(
+ final Counters.PathCounters pathCounter,
+ final DeleteOption[] deleteOption,
+ final String... skip) {
+ this(pathCounter, PathUtils.noFollowLinkOptionArray(), deleteOption, skip);
+ }
+
+ /**
+ * Constructs a new visitor that deletes files except for the files and directories explicitly
+ * given.
+ *
+ * @param pathCounter How to count visits.
+ * @param linkOptions How symbolic links are handled.
+ * @param deleteOption How deletion is handled.
+ * @param skip The files to skip deleting.
+ * @since 2.9.0
+ */
+ public DeletingPathVisitor(
+ final Counters.PathCounters pathCounter,
+ final LinkOption[] linkOptions,
+ final DeleteOption[] deleteOption,
+ final String... skip) {
+ super(pathCounter);
+ final String[] temp = skip != null ? skip.clone() : EMPTY_STRING_ARRAY;
+ Arrays.sort(temp);
+ this.skip = temp;
+ this.overrideReadOnly = StandardDeleteOption.overrideReadOnly(deleteOption);
+ // TODO Files.deleteIfExists() never follows links, so use LinkOption.NOFOLLOW_LINKS in other
+ // calls to Files.
+ this.linkOptions =
+ linkOptions == null ? PathUtils.noFollowLinkOptionArray() : linkOptions.clone();
+ }
+
+ /**
+ * Returns true to process the given path, false if not.
+ *
+ * @param path the path to test.
+ * @return true to process the given path, false if not.
+ */
+ private boolean accept(final Path path) {
+ return Arrays.binarySearch(skip, PathUtils.getFileNameString(path)) < 0;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!super.equals(obj)) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final DeletingPathVisitor other = (DeletingPathVisitor) obj;
+ return overrideReadOnly == other.overrideReadOnly && Arrays.equals(skip, other.skip);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + Arrays.hashCode(skip);
+ result = prime * result + Objects.hash(overrideReadOnly);
+ return result;
+ }
+
+ @Override
+ public FileVisitResult postVisitDirectory(final Path dir, final IOException exc)
+ throws IOException {
+ if (PathUtils.isEmptyDirectory(dir)) {
+ Files.deleteIfExists(dir);
+ }
+ return super.postVisitDirectory(dir, exc);
+ }
+
+ @Override
+ public FileVisitResult preVisitDirectory(final Path dir, final BasicFileAttributes attrs)
+ throws IOException {
+ super.preVisitDirectory(dir, attrs);
+ return accept(dir) ? FileVisitResult.CONTINUE : FileVisitResult.SKIP_SUBTREE;
+ }
+
+ @Override
+ public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs)
+ throws IOException {
+ if (accept(file)) {
+ // delete files and valid links, respecting linkOptions
+ if (Files.exists(file, linkOptions)) {
+ if (overrideReadOnly) {
+ PathUtils.setReadOnly(file, false, linkOptions);
+ }
+ Files.deleteIfExists(file);
+ }
+ // invalid links will survive previous delete, different approach needed:
+ if (Files.isSymbolicLink(file)) {
+ try {
+ // deleteIfExists does not work for this case
+ Files.delete(file);
+ } catch (final NoSuchFileException ignored) {
+ // ignore
+ }
+ }
+ }
+ updateFileCounters(file, attrs);
+ return FileVisitResult.CONTINUE;
+ }
+}
diff --git a/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/file/PathFilter.java b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/file/PathFilter.java
new file mode 100644
index 00000000..5123b846
--- /dev/null
+++ b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/file/PathFilter.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.tsfile.external.commons.io.file;
+
+import java.nio.file.FileVisitResult;
+import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributes;
+
+/**
+ * A filter for {@link Path}s.
+ *
+ * @since 2.9.0
+ */
+@FunctionalInterface
+public interface PathFilter {
+
+ /**
+ * Tests whether or not to include the specified Path in a result.
+ *
+ * @param path The Path to test.
+ * @param attributes the file's basic attributes (TODO may be null).
+ * @return a FileVisitResult
+ */
+ FileVisitResult accept(Path path, BasicFileAttributes attributes);
+}
diff --git a/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/file/PathUtils.java b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/file/PathUtils.java
new file mode 100644
index 00000000..59d3356d
--- /dev/null
+++ b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/file/PathUtils.java
@@ -0,0 +1,532 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.tsfile.external.commons.io.file;
+
+import org.apache.tsfile.external.commons.io.function.IOFunction;
+import org.apache.tsfile.external.commons.io.function.IOSupplier;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UncheckedIOException;
+import java.nio.file.AccessDeniedException;
+import java.nio.file.CopyOption;
+import java.nio.file.DirectoryStream;
+import java.nio.file.FileVisitOption;
+import java.nio.file.FileVisitResult;
+import java.nio.file.FileVisitor;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.DosFileAttributeView;
+import java.nio.file.attribute.FileAttribute;
+import java.nio.file.attribute.PosixFileAttributes;
+import java.nio.file.attribute.PosixFilePermission;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+public class PathUtils {
+ public static final LinkOption[] NOFOLLOW_LINK_OPTION_ARRAY = {LinkOption.NOFOLLOW_LINKS};
+
+ /** Empty {@link OpenOption} array. */
+ public static final OpenOption[] EMPTY_OPEN_OPTION_ARRAY = {};
+
+ public static final LinkOption[] EMPTY_LINK_OPTION_ARRAY = {};
+ public static final DeleteOption[] EMPTY_DELETE_OPTION_ARRAY = {};
+
+ public static Counters.PathCounters delete(
+ final Path path, final LinkOption[] linkOptions, final DeleteOption... deleteOptions)
+ throws IOException {
+ // File deletion through Files deletes links, not targets, so use LinkOption.NOFOLLOW_LINKS.
+ return Files.isDirectory(path, linkOptions)
+ ? deleteDirectory(path, linkOptions, deleteOptions)
+ : deleteFile(path, linkOptions, deleteOptions);
+ }
+
+ public static LinkOption[] noFollowLinkOptionArray() {
+ return NOFOLLOW_LINK_OPTION_ARRAY.clone();
+ }
+
+ public static Counters.PathCounters deleteFile(
+ final Path file, final LinkOption[] linkOptions, final DeleteOption... deleteOptions)
+ throws NoSuchFileException, IOException {
+ //
+ // TODO Needs clean up
+ //
+ if (Files.isDirectory(file, linkOptions)) {
+ throw new NoSuchFileException(file.toString());
+ }
+ final Counters.PathCounters pathCounts = Counters.longPathCounters();
+ boolean exists = exists(file, linkOptions);
+ long size = exists && !Files.isSymbolicLink(file) ? Files.size(file) : 0;
+ try {
+ if (Files.deleteIfExists(file)) {
+ pathCounts.getFileCounter().increment();
+ pathCounts.getByteCounter().add(size);
+ return pathCounts;
+ }
+ } catch (final AccessDeniedException ignored) {
+ // Ignore and try again below.
+ }
+ final Path parent = getParent(file);
+ PosixFileAttributes posixFileAttributes = null;
+ try {
+ if (overrideReadOnly(deleteOptions)) {
+ posixFileAttributes = readPosixFileAttributes(parent, linkOptions);
+ setReadOnly(file, false, linkOptions);
+ }
+ // Read size _after_ having read/execute access on POSIX.
+ exists = exists(file, linkOptions);
+ size = exists && !Files.isSymbolicLink(file) ? Files.size(file) : 0;
+ if (Files.deleteIfExists(file)) {
+ pathCounts.getFileCounter().increment();
+ pathCounts.getByteCounter().add(size);
+ }
+ } finally {
+ if (posixFileAttributes != null) {
+ Files.setPosixFilePermissions(parent, posixFileAttributes.permissions());
+ }
+ }
+ return pathCounts;
+ }
+
+ public static Path setReadOnly(
+ final Path path, final boolean readOnly, final LinkOption... linkOptions) throws IOException {
+ try {
+ // Windows is simplest
+ if (setDosReadOnly(path, readOnly, linkOptions)) {
+ return path;
+ }
+ } catch (final IOException ignored) {
+ // Retry with POSIX below.
+ }
+ final Path parent = getParent(path);
+ if (!isPosix(
+ parent, linkOptions)) { // Test parent because we may not the permissions to test the file.
+ throw new IOException(
+ String.format(
+ "DOS or POSIX file operations not available for '%s', linkOptions %s",
+ path, Arrays.toString(linkOptions)));
+ }
+ // POSIX
+ if (readOnly) {
+ // RO
+ // File, then parent dir (if any).
+ setPosixReadOnlyFile(path, readOnly, linkOptions);
+ setPosixDeletePermissions(parent, false, linkOptions);
+ } else {
+ // RE
+ // Parent dir (if any), then file.
+ setPosixDeletePermissions(parent, true, linkOptions);
+ }
+ return path;
+ }
+
+ private static boolean setPosixDeletePermissions(
+ final Path parent, final boolean enableDeleteChildren, final LinkOption... linkOptions)
+ throws IOException {
+ // To delete a file in POSIX, you need write and execute permissions on its parent directory.
+ // @formatter:off
+ return setPosixPermissions(
+ parent,
+ enableDeleteChildren,
+ Arrays.asList(
+ PosixFilePermission.OWNER_WRITE,
+ // PosixFilePermission.GROUP_WRITE,
+ // PosixFilePermission.OTHERS_WRITE,
+ PosixFilePermission.OWNER_EXECUTE
+ // PosixFilePermission.GROUP_EXECUTE,
+ // PosixFilePermission.OTHERS_EXECUTE
+ ),
+ linkOptions);
+ // @formatter:on
+ }
+
+ private static boolean setPosixPermissions(
+ final Path path,
+ final boolean addPermissions,
+ final List<PosixFilePermission> updatePermissions,
+ final LinkOption... linkOptions)
+ throws IOException {
+ if (path != null) {
+ final Set<PosixFilePermission> permissions = Files.getPosixFilePermissions(path, linkOptions);
+ final Set<PosixFilePermission> newPermissions = new HashSet<>(permissions);
+ if (addPermissions) {
+ newPermissions.addAll(updatePermissions);
+ } else {
+ newPermissions.removeAll(updatePermissions);
+ }
+ if (!newPermissions.equals(permissions)) {
+ Files.setPosixFilePermissions(path, newPermissions);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ private static void setPosixReadOnlyFile(
+ final Path path, final boolean readOnly, final LinkOption... linkOptions) throws IOException {
+ // Not Windows 10
+ final Set<PosixFilePermission> permissions = Files.getPosixFilePermissions(path, linkOptions);
+ // @formatter:off
+ final List<PosixFilePermission> readPermissions =
+ Arrays.asList(
+ PosixFilePermission.OWNER_READ
+ // PosixFilePermission.GROUP_READ,
+ // PosixFilePermission.OTHERS_READ
+ );
+ final List<PosixFilePermission> writePermissions =
+ Arrays.asList(
+ PosixFilePermission.OWNER_WRITE
+ // PosixFilePermission.GROUP_WRITE,
+ // PosixFilePermission.OTHERS_WRITE
+ );
+ // @formatter:on
+ if (readOnly) {
+ // RO: We can read, we cannot write.
+ permissions.addAll(readPermissions);
+ permissions.removeAll(writePermissions);
+ } else {
+ // Not RO: We can read, we can write.
+ permissions.addAll(readPermissions);
+ permissions.addAll(writePermissions);
+ }
+ Files.setPosixFilePermissions(path, permissions);
+ }
+
+ public static boolean isPosix(final Path test, final LinkOption... options) {
+ return exists(test, options) && readPosixFileAttributes(test, options) != null;
+ }
+
+ private static boolean exists(final Path path, final LinkOption... options) {
+ return path != null && (options != null ? Files.exists(path, options) : Files.exists(path));
+ }
+
+ public static PosixFileAttributes readPosixFileAttributes(
+ final Path path, final LinkOption... options) {
+ return readAttributes(path, PosixFileAttributes.class, options);
+ }
+
+ private static boolean setDosReadOnly(
+ final Path path, final boolean readOnly, final LinkOption... linkOptions) throws IOException {
+ final DosFileAttributeView dosFileAttributeView = getDosFileAttributeView(path, linkOptions);
+ if (dosFileAttributeView != null) {
+ dosFileAttributeView.setReadOnly(readOnly);
+ return true;
+ }
+ return false;
+ }
+
+ public static <A extends BasicFileAttributes> A readAttributes(
+ final Path path, final Class<A> type, final LinkOption... options) {
+ try {
+ return path == null ? null : Files.readAttributes(path, type, options);
+ } catch (final UnsupportedOperationException | IOException e) {
+ // For example, on Windows.
+ return null;
+ }
+ }
+
+ public static DosFileAttributeView getDosFileAttributeView(
+ final Path path, final LinkOption... options) {
+ return Files.getFileAttributeView(path, DosFileAttributeView.class, options);
+ }
+
+ public static Counters.PathCounters deleteDirectory(
+ final Path directory, final LinkOption[] linkOptions, final DeleteOption... deleteOptions)
+ throws IOException {
+ return visitFileTree(
+ new DeletingPathVisitor(Counters.longPathCounters(), linkOptions, deleteOptions),
+ directory)
+ .getPathCounters();
+ }
+
+ public static <T extends FileVisitor<? super Path>> T visitFileTree(
+ final T visitor, final Path directory) throws IOException {
+ Files.walkFileTree(directory, visitor);
+ return visitor;
+ }
+
+ private static Path getParent(final Path path) {
+ return path == null ? null : path.getParent();
+ }
+
+ private static boolean overrideReadOnly(final DeleteOption... deleteOptions) {
+ if (deleteOptions == null) {
+ return false;
+ }
+ return Stream.of(deleteOptions).anyMatch(e -> e == StandardDeleteOption.OVERRIDE_READ_ONLY);
+ }
+
+ public static String getFileNameString(final Path path) {
+ return getFileName(path, Path::toString);
+ }
+
+ public static <R> R getFileName(final Path path, final Function<Path, R> function) {
+ final Path fileName = path != null ? path.getFileName() : null;
+ return fileName != null ? function.apply(fileName) : null;
+ }
+
+ public static boolean isEmptyDirectory(final Path directory) throws IOException {
+ try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(directory)) {
+ return !directoryStream.iterator().hasNext();
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////////////////////////////////
+ // IoTDB
+ /////////////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * A LinkOption used to follow link in this class, the inverse of {@link
+ * LinkOption#NOFOLLOW_LINKS}.
+ *
+ * @since 2.12.0
+ */
+ static final LinkOption NULL_LINK_OPTION = null;
+
+ private static final OpenOption[] OPEN_OPTIONS_TRUNCATE = {
+ StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING
+ };
+
+ private static final OpenOption[] OPEN_OPTIONS_APPEND = {
+ StandardOpenOption.CREATE, StandardOpenOption.APPEND
+ };
+
+ /**
+ * Deletes a directory including subdirectories.
+ *
+ * @param directory directory to delete.
+ * @return The visitor used to delete the given directory.
+ * @throws IOException if an I/O error is thrown by a visitor method.
+ */
+ public static Counters.PathCounters deleteDirectory(final Path directory) throws IOException {
+ return deleteDirectory(directory, EMPTY_DELETE_OPTION_ARRAY);
+ }
+
+ /**
+ * Deletes a directory including subdirectories.
+ *
+ * @param directory directory to delete.
+ * @param deleteOptions How to handle deletion.
+ * @return The visitor used to delete the given directory.
+ * @throws IOException if an I/O error is thrown by a visitor method.
+ * @since 2.8.0
+ */
+ public static Counters.PathCounters deleteDirectory(
+ final Path directory, final DeleteOption... deleteOptions) throws IOException {
+ final LinkOption[] linkOptions = PathUtils.noFollowLinkOptionArray();
+ // POSIX ops will noop on non-POSIX.
+ return withPosixFileAttributes(
+ getParent(directory),
+ linkOptions,
+ overrideReadOnly(deleteOptions),
+ pfa ->
+ visitFileTree(
+ new DeletingPathVisitor(
+ Counters.longPathCounters(), linkOptions, deleteOptions),
+ directory)
+ .getPathCounters());
+ }
+
+ private static <R> R withPosixFileAttributes(
+ final Path path,
+ final LinkOption[] linkOptions,
+ final boolean overrideReadOnly,
+ final IOFunction<PosixFileAttributes, R> function)
+ throws IOException {
+ final PosixFileAttributes posixFileAttributes =
+ overrideReadOnly ? readPosixFileAttributes(path, linkOptions) : null;
+ try {
+ return function.apply(posixFileAttributes);
+ } finally {
+ if (posixFileAttributes != null && path != null && Files.exists(path, linkOptions)) {
+ Files.setPosixFilePermissions(path, posixFileAttributes.permissions());
+ }
+ }
+ }
+
+ /**
+ * Creates a new OutputStream by opening or creating a file, returning an output stream that may
+ * be used to write bytes to the file.
+ *
+ * @param path the Path.
+ * @param append Whether or not to append.
+ * @return a new OutputStream.
+ * @throws IOException if an I/O error occurs.
+ * @see Files#newOutputStream(Path, OpenOption...)
+ * @since 2.12.0
+ */
+ public static OutputStream newOutputStream(final Path path, final boolean append)
+ throws IOException {
+ return newOutputStream(
+ path, EMPTY_LINK_OPTION_ARRAY, append ? OPEN_OPTIONS_APPEND : OPEN_OPTIONS_TRUNCATE);
+ }
+
+ static OutputStream newOutputStream(
+ final Path path, final LinkOption[] linkOptions, final OpenOption... openOptions)
+ throws IOException {
+ if (!exists(path, linkOptions)) {
+ createParentDirectories(
+ path, linkOptions != null && linkOptions.length > 0 ? linkOptions[0] : NULL_LINK_OPTION);
+ }
+ final List<OpenOption> list =
+ new ArrayList<>(Arrays.asList(openOptions != null ? openOptions : EMPTY_OPEN_OPTION_ARRAY));
+ list.addAll(Arrays.asList(linkOptions != null ? linkOptions : EMPTY_LINK_OPTION_ARRAY));
+ return Files.newOutputStream(path, list.toArray(EMPTY_OPEN_OPTION_ARRAY));
+ }
+
+ /**
+ * Creates the parent directories for the given {@code path}.
+ *
+ * <p>If the parent directory already exists, then return it.
+ *
+ * <p>
+ *
+ * @param path The path to a file (or directory).
+ * @param attrs An optional list of file attributes to set atomically when creating the
+ * directories.
+ * @return The Path for the {@code path}'s parent directory or null if the given path has no
+ * parent.
+ * @throws IOException if an I/O error occurs.
+ * @since 2.9.0
+ */
+ public static Path createParentDirectories(final Path path, final FileAttribute<?>... attrs)
+ throws IOException {
+ return createParentDirectories(path, LinkOption.NOFOLLOW_LINKS, attrs);
+ }
+
+ /**
+ * Creates the parent directories for the given {@code path}.
+ *
+ * <p>If the parent directory already exists, then return it.
+ *
+ * <p>
+ *
+ * @param path The path to a file (or directory).
+ * @param linkOption A {@link LinkOption} or null.
+ * @param attrs An optional list of file attributes to set atomically when creating the
+ * directories.
+ * @return The Path for the {@code path}'s parent directory or null if the given path has no
+ * parent.
+ * @throws IOException if an I/O error occurs.
+ * @since 2.12.0
+ */
+ public static Path createParentDirectories(
+ final Path path, final LinkOption linkOption, final FileAttribute<?>... attrs)
+ throws IOException {
+ Path parent = getParent(path);
+ parent = linkOption == LinkOption.NOFOLLOW_LINKS ? parent : readIfSymbolicLink(parent);
+ if (parent == null) {
+ return null;
+ }
+ final boolean exists =
+ linkOption == null ? Files.exists(parent) : Files.exists(parent, linkOption);
+ return exists ? parent : Files.createDirectories(parent, attrs);
+ }
+
+ private static Path readIfSymbolicLink(final Path path) throws IOException {
+ return path != null ? Files.isSymbolicLink(path) ? Files.readSymbolicLink(path) : path : null;
+ }
+
+ /**
+ * Copies the InputStream from the supplier with {@link Files#copy(InputStream, Path,
+ * CopyOption...)}.
+ *
+ * @param in Supplies the InputStream.
+ * @param target See {@link Files#copy(InputStream, Path, CopyOption...)}.
+ * @param copyOptions See {@link Files#copy(InputStream, Path, CopyOption...)}.
+ * @return See {@link Files#copy(InputStream, Path, CopyOption...)}
+ * @throws IOException See {@link Files#copy(InputStream, Path, CopyOption...)}
+ * @since 2.12.0
+ */
+ public static long copy(
+ final IOSupplier<InputStream> in, final Path target, final CopyOption... copyOptions)
+ throws IOException {
+ try (InputStream inputStream = in.get()) {
+ return Files.copy(inputStream, target, copyOptions);
+ }
+ }
+
+ /**
+ * Returns a stream of filtered paths.
+ *
+ * @param start the start path
+ * @param pathFilter the path filter
+ * @param maxDepth the maximum depth of directories to walk.
+ * @param readAttributes whether to call the filters with file attributes (false passes null).
+ * @param options the options to configure the walk.
+ * @return a filtered stream of paths.
+ * @throws IOException if an I/O error is thrown when accessing the starting file.
+ * @since 2.9.0
+ */
+ public static Stream<Path> walk(
+ final Path start,
+ final PathFilter pathFilter,
+ final int maxDepth,
+ final boolean readAttributes,
+ final FileVisitOption... options)
+ throws IOException {
+ return Files.walk(start, maxDepth, options)
+ .filter(
+ path ->
+ pathFilter.accept(
+ path, readAttributes ? readBasicFileAttributesUnchecked(path) : null)
+ == FileVisitResult.CONTINUE);
+ }
+
+ /**
+ * Reads the BasicFileAttributes from the given path. Returns null instead of throwing {@link
+ * UnsupportedOperationException}.
+ *
+ * @param path the path to read.
+ * @return the path attributes.
+ * @throws UncheckedIOException if an I/O error occurs
+ * @since 2.9.0
+ * @deprecated Use {@link #readBasicFileAttributes(Path, LinkOption...)}.
+ */
+ @Deprecated
+ public static BasicFileAttributes readBasicFileAttributesUnchecked(final Path path) {
+ return readBasicFileAttributes(path, EMPTY_LINK_OPTION_ARRAY);
+ }
+
+ /**
+ * Reads the BasicFileAttributes from the given path. Returns null instead of throwing {@link
+ * UnsupportedOperationException}.
+ *
+ * @param path the path to read.
+ * @param options options indicating how to handle symbolic links.
+ * @return the path attributes.
+ * @since 2.12.0
+ */
+ public static BasicFileAttributes readBasicFileAttributes(
+ final Path path, final LinkOption... options) {
+ return readAttributes(path, BasicFileAttributes.class, options);
+ }
+}
diff --git a/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/file/PathVisitor.java b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/file/PathVisitor.java
new file mode 100644
index 00000000..931d630a
--- /dev/null
+++ b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/file/PathVisitor.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.tsfile.external.commons.io.file;
+
+import java.nio.file.FileVisitor;
+import java.nio.file.Path;
+
+/**
+ * A {@link FileVisitor} typed to a {@link Path}.
+ *
+ * @since 2.9.0
+ */
+public interface PathVisitor extends FileVisitor<Path> {
+ // empty
+}
diff --git a/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/file/SimplePathVisitor.java b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/file/SimplePathVisitor.java
new file mode 100644
index 00000000..46a1ad66
--- /dev/null
+++ b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/file/SimplePathVisitor.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.tsfile.external.commons.io.file;
+
+import org.apache.tsfile.external.commons.io.function.IOBiFunction;
+
+import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+
+/**
+ * A {@link SimpleFileVisitor} typed to a {@link Path}.
+ *
+ * @since 2.7
+ */
+public abstract class SimplePathVisitor extends SimpleFileVisitor<Path> implements PathVisitor {
+
+ private final IOBiFunction<Path, IOException, FileVisitResult> visitFileFailedFunction;
+
+ /** Constructs a new instance. */
+ protected SimplePathVisitor() {
+ this.visitFileFailedFunction = super::visitFileFailed;
+ }
+
+ @Override
+ public FileVisitResult visitFileFailed(final Path file, final IOException exc)
+ throws IOException {
+ return visitFileFailedFunction.apply(file, exc);
+ }
+}
diff --git a/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/file/StandardDeleteOption.java b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/file/StandardDeleteOption.java
new file mode 100644
index 00000000..9c619d7a
--- /dev/null
+++ b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/file/StandardDeleteOption.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.tsfile.external.commons.io.file;
+
+import org.apache.tsfile.external.commons.io.IOUtils;
+
+import java.util.stream.Stream;
+
+/**
+ * Defines the standard delete options.
+ *
+ * @since 2.8.0
+ */
+public enum StandardDeleteOption implements DeleteOption {
+
+ /**
+ * Overrides the read-only attribute to allow deletion, on POSIX, this means Write and Execute on
+ * the parent.
+ */
+ OVERRIDE_READ_ONLY;
+
+ /**
+ * Returns true if the given options contain {@link StandardDeleteOption#OVERRIDE_READ_ONLY}.
+ *
+ * <p>For now, assume the array is not sorted.
+ *
+ * @param options the array to test
+ * @return true if the given options contain {@link StandardDeleteOption#OVERRIDE_READ_ONLY}.
+ */
+ public static boolean overrideReadOnly(final DeleteOption[] options) {
+ if (IOUtils.length(options) == 0) {
+ return false;
+ }
+ return Stream.of(options).anyMatch(e -> OVERRIDE_READ_ONLY == e);
+ }
+}
diff --git a/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/filefilter/AbstractFileFilter.java b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/filefilter/AbstractFileFilter.java
new file mode 100644
index 00000000..9b184a91
--- /dev/null
+++ b/java/tsfile/src/main/java/org/apache/tsfile/external/commons/io/filefilter/AbstractFileFilter.java
@@ -0,0 +1,177 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
... 11433 lines suppressed ...