From 42853b67068e300f7184ceffa2ff1cc63d5d0b95 Mon Sep 17 00:00:00 2001
From: Looly
Date: Tue, 28 Mar 2023 02:04:37 +0800
Subject: [PATCH] add toml support
---
.../cn/hutool/core/io/FastStringWriter.java | 37 +-
.../main/java/cn/hutool/cron/CronUtil.java | 4 +-
hutool-setting/pom.xml | 3 +-
...meException.java => SettingException.java} | 14 +-
.../java/cn/hutool/setting/toml/Toml.java | 32 +
.../cn/hutool/setting/toml/TomlReader.java | 676 ++++++++++++++++++
.../cn/hutool/setting/toml/TomlWriter.java | 353 +++++++++
.../cn/hutool/setting/toml/package-info.java | 25 +
hutool-setting/src/test/resources/test.toml | 33 +
9 files changed, 1159 insertions(+), 18 deletions(-)
rename hutool-setting/src/main/java/cn/hutool/setting/{SettingRuntimeException.java => SettingException.java} (61%)
create mode 100644 hutool-setting/src/main/java/cn/hutool/setting/toml/Toml.java
create mode 100644 hutool-setting/src/main/java/cn/hutool/setting/toml/TomlReader.java
create mode 100644 hutool-setting/src/main/java/cn/hutool/setting/toml/TomlWriter.java
create mode 100644 hutool-setting/src/main/java/cn/hutool/setting/toml/package-info.java
create mode 100644 hutool-setting/src/test/resources/test.toml
diff --git a/hutool-core/src/main/java/cn/hutool/core/io/FastStringWriter.java b/hutool-core/src/main/java/cn/hutool/core/io/FastStringWriter.java
index 5fa10f97d..0a1930adb 100644
--- a/hutool-core/src/main/java/cn/hutool/core/io/FastStringWriter.java
+++ b/hutool-core/src/main/java/cn/hutool/core/io/FastStringWriter.java
@@ -25,7 +25,7 @@ public final class FastStringWriter extends Writer {
private static final int DEFAULT_CAPACITY = 16;
- private final StringBuilder builder;
+ private final StringBuilder stringBuilder;
/**
* 构造
@@ -43,31 +43,51 @@ public final class FastStringWriter extends Writer {
if (initialSize < 0) {
initialSize = DEFAULT_CAPACITY;
}
- this.builder = new StringBuilder(initialSize);
+ this.stringBuilder = new StringBuilder(initialSize);
}
+ // region ----- append
+ @Override
+ public FastStringWriter append(final char c) {
+ this.stringBuilder.append(c);
+ return this;
+ }
+ @Override
+ public FastStringWriter append(final CharSequence csq, final int start, final int end) {
+ this.stringBuilder.append(csq, start, end);
+ return this;
+ }
+
+ @Override
+ public FastStringWriter append(final CharSequence csq) {
+ this.stringBuilder.append(csq);
+ return this;
+ }
+ // endregion
+
+ // region ----- write
@Override
public void write(final int c) {
- this.builder.append((char) c);
+ this.stringBuilder.append((char) c);
}
@Override
public void write(final String str) {
- this.builder.append(str);
+ this.stringBuilder.append(str);
}
@Override
public void write(final String str, final int off, final int len) {
- this.builder.append(str, off, off + len);
+ this.stringBuilder.append(str, off, off + len);
}
@Override
public void write(final char[] cbuf) {
- this.builder.append(cbuf, 0, cbuf.length);
+ this.stringBuilder.append(cbuf, 0, cbuf.length);
}
@@ -79,8 +99,9 @@ public final class FastStringWriter extends Writer {
} else if (len == 0) {
return;
}
- this.builder.append(cbuf, off, len);
+ this.stringBuilder.append(cbuf, off, len);
}
+ // endregion
@Override
@@ -97,7 +118,7 @@ public final class FastStringWriter extends Writer {
@Override
public String toString() {
- return this.builder.toString();
+ return this.stringBuilder.toString();
}
}
diff --git a/hutool-cron/src/main/java/cn/hutool/cron/CronUtil.java b/hutool-cron/src/main/java/cn/hutool/cron/CronUtil.java
index 696179027..754d59e71 100644
--- a/hutool-cron/src/main/java/cn/hutool/cron/CronUtil.java
+++ b/hutool-cron/src/main/java/cn/hutool/cron/CronUtil.java
@@ -18,7 +18,7 @@ import cn.hutool.core.io.resource.NoResourceException;
import cn.hutool.cron.pattern.CronPattern;
import cn.hutool.cron.task.Task;
import cn.hutool.setting.Setting;
-import cn.hutool.setting.SettingRuntimeException;
+import cn.hutool.setting.SettingException;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@@ -58,7 +58,7 @@ public class CronUtil {
public static void setCronSetting(final String cronSettingPath) {
try {
crontabSetting = new Setting(cronSettingPath, Setting.DEFAULT_CHARSET, false);
- } catch (final SettingRuntimeException | NoResourceException e) {
+ } catch (final SettingException | NoResourceException e) {
// ignore setting file parse error and no config error
}
}
diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml
index 39fe4a107..1ef129316 100755
--- a/hutool-setting/pom.xml
+++ b/hutool-setting/pom.xml
@@ -43,10 +43,11 @@
hutool-log${project.parent.version}
+
org.yamlsnakeyaml
- 1.32
+ 2.0true
diff --git a/hutool-setting/src/main/java/cn/hutool/setting/SettingRuntimeException.java b/hutool-setting/src/main/java/cn/hutool/setting/SettingException.java
similarity index 61%
rename from hutool-setting/src/main/java/cn/hutool/setting/SettingRuntimeException.java
rename to hutool-setting/src/main/java/cn/hutool/setting/SettingException.java
index 426ac1b44..c5aeda431 100644
--- a/hutool-setting/src/main/java/cn/hutool/setting/SettingRuntimeException.java
+++ b/hutool-setting/src/main/java/cn/hutool/setting/SettingException.java
@@ -19,30 +19,30 @@ import cn.hutool.core.text.StrUtil;
*
* @author xiaoleilu
*/
-public class SettingRuntimeException extends RuntimeException {
+public class SettingException extends RuntimeException {
private static final long serialVersionUID = 7941096116780378387L;
- public SettingRuntimeException(final Throwable e) {
+ public SettingException(final Throwable e) {
super(e);
}
- public SettingRuntimeException(final String message) {
+ public SettingException(final String message) {
super(message);
}
- public SettingRuntimeException(final String messageTemplate, final Object... params) {
+ public SettingException(final String messageTemplate, final Object... params) {
super(StrUtil.format(messageTemplate, params));
}
- public SettingRuntimeException(final String message, final Throwable throwable) {
+ public SettingException(final String message, final Throwable throwable) {
super(message, throwable);
}
- public SettingRuntimeException(final String message, final Throwable throwable, final boolean enableSuppression, final boolean writableStackTrace) {
+ public SettingException(final String message, final Throwable throwable, final boolean enableSuppression, final boolean writableStackTrace) {
super(message, throwable, enableSuppression, writableStackTrace);
}
- public SettingRuntimeException(final Throwable throwable, final String messageTemplate, final Object... params) {
+ public SettingException(final Throwable throwable, final String messageTemplate, final Object... params) {
super(StrUtil.format(messageTemplate, params), throwable);
}
}
diff --git a/hutool-setting/src/main/java/cn/hutool/setting/toml/Toml.java b/hutool-setting/src/main/java/cn/hutool/setting/toml/Toml.java
new file mode 100644
index 000000000..782b4957a
--- /dev/null
+++ b/hutool-setting/src/main/java/cn/hutool/setting/toml/Toml.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2023 looly(loolly@aliyun.com)
+ * Hutool is licensed under Mulan PSL v2.
+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
+ * You may obtain a copy of Mulan PSL v2 at:
+ * http://license.coscl.org.cn/MulanPSL2
+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
+ * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
+ * See the Mulan PSL v2 for more details.
+ */
+
+package cn.hutool.setting.toml;
+
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeFormatterBuilder;
+
+public class Toml {
+ /**
+ * A DateTimeFormatter that uses the TOML format.
+ */
+ public static final DateTimeFormatter DATE_FORMATTER = new DateTimeFormatterBuilder()
+ .append(DateTimeFormatter.ISO_LOCAL_DATE)
+ .optionalStart()
+ .appendLiteral('T')
+ .append(DateTimeFormatter.ISO_LOCAL_TIME)
+ .optionalStart()
+ .appendOffsetId()
+ .optionalEnd()
+ .optionalEnd()
+ .toFormatter();
+}
diff --git a/hutool-setting/src/main/java/cn/hutool/setting/toml/TomlReader.java b/hutool-setting/src/main/java/cn/hutool/setting/toml/TomlReader.java
new file mode 100644
index 000000000..5bf0a00d0
--- /dev/null
+++ b/hutool-setting/src/main/java/cn/hutool/setting/toml/TomlReader.java
@@ -0,0 +1,676 @@
+/*
+ * Copyright (c) 2023 looly(loolly@aliyun.com)
+ * Hutool is licensed under Mulan PSL v2.
+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
+ * You may obtain a copy of Mulan PSL v2 at:
+ * http://license.coscl.org.cn/MulanPSL2
+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
+ * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
+ * See the Mulan PSL v2 for more details.
+ */
+
+package cn.hutool.setting.toml;
+
+import cn.hutool.setting.SettingException;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.ZonedDateTime;
+import java.util.*;
+
+/**
+ * TOML文件读取
+ *
DateTimes support
+ *
+ * The datetime support is more extended than in the TOML specification. This reader supports three kind of datetimes:
+ *
+ *
Full RFC 3339. Examples: 1979-05-27T07:32:00Z, 1979-05-27T00:32:00-07:00, 1979-05-27T00:32:00.999999-07:00
+ *
Without local offset. Examples: 1979-05-27T07:32:00, 1979-05-27T00:32:00.999999
+ *
Without time (just the date). Example: 2015-03-20
+ *
+ * Moreover, parsing datetimes gives different objects according to the informations provided. For example, 2015-03-20
+ * is parsed as a {@link LocalDate}, 2015-03-20T19:04:35 as a {@link LocalDateTime}, and 2015-03-20T19:04:35+01:00 as a
+ * {@link ZonedDateTime}.
+ *
+ *
Lenient bare keys
+ *
+ * This library allows "lenient" bare keys by default, as opposite to the "strict" bare keys required by the TOML
+ * specification. Strict bare keys may only contain letters, numbers, underscores, and dashes (A-Za-z0-9_-). Lenient
+ * bare keys may contain any character except those below the space character ' ' in the unicode table, '.', '[', ']'
+ * and '='. The behaviour of TomlReader regarding bare keys is set in its constructor.
+ *
+ *
+ * @author TheElectronWill
+ *
+ */
+public class TomlReader {
+
+ private final String data;
+ private final boolean strictAsciiBareKeys;
+ private int pos = 0;// current position
+ private int line = 1;// current line
+
+ /**
+ * Creates a new TomlReader.
+ *
+ * @param data the TOML data to read
+ * @param strictAsciiBareKeys true to allow only strict bare keys, {@code false} to allow lenient ones.
+ */
+ public TomlReader(final String data, final boolean strictAsciiBareKeys) {
+ this.data = data;
+ this.strictAsciiBareKeys = strictAsciiBareKeys;
+ }
+
+ private boolean hasNext() {
+ return pos < data.length();
+ }
+
+ private char next() {
+ return data.charAt(pos++);
+ }
+
+ private char nextUseful(final boolean skipComments) {
+ char c = ' ';
+ while (hasNext() && (c == ' ' || c == '\t' || c == '\r' || c == '\n' || (c == '#' && skipComments))) {
+ c = next();
+ if (skipComments && c == '#') {
+ final int nextLinebreak = data.indexOf('\n', pos);
+ if (nextLinebreak == -1) {
+ pos = data.length();
+ } else {
+ pos = nextLinebreak + 1;
+ line++;
+ }
+ } else if (c == '\n') {
+ line++;
+ }
+ }
+ return c;
+ }
+
+ private char nextUsefulOrLinebreak() {
+ char c = ' ';
+ while (c == ' ' || c == '\t' || c == '\r') {
+ if (!hasNext())// fixes error when no '\n' at the end of the file
+ return '\n';
+ c = next();
+ }
+ if (c == '\n')
+ line++;
+ return c;
+ }
+
+ private Object nextValue(final char firstChar) {
+ switch (firstChar) {
+ case '+':
+ case '-':
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ return nextNumberOrDate(firstChar);
+ case '"':
+ if (pos + 1 < data.length()) {
+ final char c2 = data.charAt(pos);
+ final char c3 = data.charAt(pos + 1);
+ if (c2 == '"' && c3 == '"') {
+ pos += 2;
+ return nextBasicMultilineString();
+ }
+ }
+ return nextBasicString();
+ case '\'':
+ if (pos + 1 < data.length()) {
+ final char c2 = data.charAt(pos);
+ final char c3 = data.charAt(pos + 1);
+ if (c2 == '\'' && c3 == '\'') {
+ pos += 2;
+ return nextLiteralMultilineString();
+ }
+ }
+ return nextLiteralString();
+ case '[':
+ return nextArray();
+ case '{':
+ return nextInlineTable();
+ case 't':// Must be "true"
+ if (pos + 3 > data.length() || next() != 'r' || next() != 'u' || next() != 'e') {
+ throw new SettingException("Invalid value at line " + line);
+ }
+ return true;
+ case 'f':// Must be "false"
+ if (pos + 4 > data.length() || next() != 'a' || next() != 'l' || next() != 's' || next() != 'e') {
+ throw new SettingException("Invalid value at line " + line);
+ }
+ return false;
+ default:
+ throw new SettingException("Invalid character '" + toString(firstChar) + "' at line " + line);
+ }
+ }
+
+ public Map read() {
+ final Map map = nextTableContent();
+
+ if (!hasNext() && pos > 0 && data.charAt(pos - 1) == '[')
+ throw new SettingException("Invalid table declaration at line " + line + ": it never ends");
+
+ while (hasNext()) {
+ char c = nextUseful(true);
+ final boolean twoBrackets;
+ if (c == '[') {
+ twoBrackets = true;
+ c = nextUseful(false);
+ } else {
+ twoBrackets = false;
+ }
+ pos--;
+
+ // --- Reads the key --
+ final List keyParts = new ArrayList<>(4);
+ boolean insideSquareBrackets = true;
+ while (insideSquareBrackets) {
+ if (!hasNext())
+ throw new SettingException("Invalid table declaration at line " + line + ": it never ends");
+
+ String name = null;
+ final char nameFirstChar = nextUseful(false);
+ switch (nameFirstChar) {
+ case '"': {
+ if (pos + 1 < data.length()) {
+ final char c2 = data.charAt(pos);
+ final char c3 = data.charAt(pos + 1);
+ if (c2 == '"' && c3 == '"') {
+ pos += 2;
+ name = nextBasicMultilineString();
+ }
+ }
+ if (name == null) {
+ name = nextBasicString();
+ }
+ break;
+ }
+ case '\'': {
+ if (pos + 1 < data.length()) {
+ final char c2 = data.charAt(pos);
+ final char c3 = data.charAt(pos + 1);
+ if (c2 == '\'' && c3 == '\'') {
+ pos += 2;
+ name = nextLiteralMultilineString();
+ }
+ }
+ if (name == null) {
+ name = nextLiteralString();
+ }
+ break;
+ }
+ default:
+ pos--;// to include the first (already read) non-space character
+ name = nextBareKey(']', '.').trim();
+ if (data.charAt(pos) == ']') {
+ if (!name.isEmpty())
+ keyParts.add(name);
+ insideSquareBrackets = false;
+ } else if (name.isEmpty()) {
+ throw new SettingException("Invalid empty key at line " + line);
+ }
+
+ pos++;// to go after the character we stopped at in nextBareKey()
+ break;
+ }
+ if (insideSquareBrackets)
+ keyParts.add(name.trim());
+ }
+
+ // -- Checks --
+ if (keyParts.isEmpty())
+ throw new SettingException("Invalid empty key at line " + line);
+
+ if (twoBrackets && next() != ']') {// 2 brackets at the start but only one at the end!
+ throw new SettingException("Missing character ']' at line " + line);
+ }
+
+ // -- Reads the value (table content) --
+ final Map value = nextTableContent();
+
+ // -- Saves the value --
+ Map valueMap = map;// the map that contains the value
+ for (int i = 0; i < keyParts.size() - 1; i++) {
+ final String part = keyParts.get(i);
+ final Object child = valueMap.get(part);
+ final Map childMap;
+ if (child == null) {// implicit table
+ childMap = new HashMap<>(4);
+ valueMap.put(part, childMap);
+ } else if (child instanceof Map) {// table
+ childMap = (Map) child;
+ } else {// array
+ final List