mirror of
https://github.com/plexusorg/toml4j.git
synced 2024-05-18 10:31:33 +00:00
Compare commits
328 commits
toml4j-0.1
...
master
Author | SHA1 | Date | |
---|---|---|---|
23cc28b7b2 | |||
d66054552a | |||
72179d67a8 | |||
928a60487a | |||
48df906a43 | |||
1f32acc597 | |||
9f39895380 | |||
4d206e4af6 | |||
7f59ab2e51 | |||
885e536836 | |||
1fdfe4b4ad | |||
3e29b470d4 | |||
293f512068 | |||
dd4d1b08bc | |||
e2b212e0a5 | |||
b919f5f446 | |||
e6b66d2612 | |||
3fee15dd9c | |||
214143fe84 | |||
2d2a2c2708 | |||
644c23f17d | |||
e0ce9c67e7 | |||
cc039ff25f | |||
bede018b3d | |||
120d1396fd | |||
e27ba3e8fb | |||
40f866f5cd | |||
ddeab75689 | |||
0a5b5190b9 | |||
ea845af1de | |||
55d5b91e1d | |||
d977160e1b | |||
f0da6d4c36 | |||
b7d3b34619 | |||
a37dfd80a8 | |||
ab05197c9d | |||
15666880a8 | |||
963507ae61 | |||
6318013c81 | |||
acca966946 | |||
a5e34f464f | |||
d9b3c191fb | |||
e024b51b0c | |||
2f45ce657c | |||
aacd2bf319 | |||
b2daf2475f | |||
a5f0c6aec7 | |||
b1436c557d | |||
41b5438707 | |||
175c8d645f | |||
23567297fb | |||
c88f283b3c | |||
5740936619 | |||
4773638a12 | |||
d60736e104 | |||
039b487115 | |||
b21391add7 | |||
212bd49bf6 | |||
97e0133450 | |||
c90d4e1e71 | |||
16915c8edb | |||
dbbd2f5e0e | |||
30d4898938 | |||
568a77b1bf | |||
8a8319b5f9 | |||
50522d6a8c | |||
912ab61158 | |||
9358162a58 | |||
95e7f28cfa | |||
47bd5022f1 | |||
7d5e429e12 | |||
fa591a0f0c | |||
1244fdbfb3 | |||
dbab02ce52 | |||
b59a246b86 | |||
a6e31da5ca | |||
d9abbe7bc3 | |||
ea36399255 | |||
6340d39ba7 | |||
5af1ca6f06 | |||
56be048ab1 | |||
0f42d37f60 | |||
d8a02ce356 | |||
210d9dcb3c | |||
d9964fd61a | |||
3a3915416a | |||
feb3aec259 | |||
123aa21aed | |||
9968add0ae | |||
121db7ef05 | |||
a879385f4c | |||
99c48231c2 | |||
7ce548e616 | |||
f3dca7d24f | |||
b550e638c7 | |||
1a0d38bc72 | |||
77f7b922be | |||
3d90260d32 | |||
ecbd9f048d | |||
72941c146d | |||
24a6503d19 | |||
8b3652a309 | |||
bcc52f5cb0 | |||
ec30be7901 | |||
acc952a572 | |||
c076099ddd | |||
ca6e043926 | |||
b729e450ab | |||
1216a009cb | |||
4ed3740732 | |||
ad8a9fd932 | |||
f210ad02fb | |||
2fec857f14 | |||
64afb09d96 | |||
9f717384a4 | |||
bb0d9b1146 | |||
f8f55a6f16 | |||
e7d7de7ae5 | |||
8571d258ca | |||
38666593dd | |||
0cea9ae187 | |||
1b2aa582d9 | |||
9f29f6951f | |||
1c3e0642ae | |||
80008fca15 | |||
4074242e0b | |||
68513b0851 | |||
15c014a327 | |||
a0bd307f58 | |||
5402ce22f3 | |||
ae2911c82c | |||
24a5134c2c | |||
a210896477 | |||
a342b1fef6 | |||
4d32f77ce0 | |||
0817b40d8f | |||
8cae6c2e32 | |||
0795127266 | |||
1a6ddbd5ac | |||
1e19f0847f | |||
af5a2a5867 | |||
439d1c600d | |||
d958baebb5 | |||
c0eafb9f17 | |||
d31792e5a8 | |||
7f11f97020 | |||
f5e19dbb4b | |||
414bd396fa | |||
3773c4eadf | |||
5a608cad02 | |||
bc04eec538 | |||
7f0ea04e99 | |||
ffb0e08290 | |||
8d183061ec | |||
baec3a1697 | |||
598c319ef3 | |||
252a704a61 | |||
8c66284960 | |||
7aa496582c | |||
a3edb55e9c | |||
8d9f08e93f | |||
8528c22b93 | |||
58e4f9191c | |||
7e88ad0f93 | |||
4e22176f1e | |||
2b9af91bf4 | |||
40f12ca3b1 | |||
14896a6033 | |||
ba98daeb85 | |||
ca7fcc766b | |||
be85328555 | |||
cdef758f4c | |||
b69f8514cb | |||
96220b7a88 | |||
8388e394ed | |||
6e4c8fd022 | |||
d468ec1892 | |||
d28fffa4e6 | |||
1c87a9e85a | |||
8a6ca61101 | |||
7292fe5468 | |||
786529fce6 | |||
c0e78db681 | |||
c4027ed2d5 | |||
5567f6c8bd | |||
66f7946542 | |||
21c5dfd235 | |||
3e2d9fad37 | |||
d1d7145a03 | |||
ddb061b9f9 | |||
5bd87a6b56 | |||
a5a6ab22dc | |||
de1bcbdca2 | |||
91e5c65577 | |||
d32ead52d4 | |||
55d598c3b1 | |||
9093fec59b | |||
1bc8d4bcc5 | |||
1a8e991f95 | |||
4615ed50f7 | |||
7b7906d0ed | |||
5a9dd3c58c | |||
b70661a683 | |||
5d17559728 | |||
0baf7ea32e | |||
0dc5f6d206 | |||
0f198e5a5e | |||
84de939b7c | |||
77788d3c87 | |||
40cab5c499 | |||
bde639951a | |||
b6b0d733a5 | |||
7380d74592 | |||
e0762bf82a | |||
b7b546dc83 | |||
f804c99534 | |||
537c75757e | |||
9a8b85c6ea | |||
3ca947785d | |||
6ad2743276 | |||
ac3c0fd230 | |||
be3b027d1e | |||
ba84494583 | |||
1380c72d48 | |||
89d2c70b15 | |||
df235e5a29 | |||
62b2718a17 | |||
49f0d4b6c0 | |||
0026bb3467 | |||
6906dea7d0 | |||
112d56d60c | |||
8fd7e6691c | |||
cd94968907 | |||
9f89f6ee6e | |||
db950ab054 | |||
98701694c4 | |||
51fcf76e4e | |||
28c57069c6 | |||
e425f81011 | |||
7805561133 | |||
8a16f487f5 | |||
3cec32eb8e | |||
b0e933fb28 | |||
14684e07d2 | |||
5ed9a4155e | |||
714fb81330 | |||
1b3c08a433 | |||
12e73fb314 | |||
6267ac6c9d | |||
28ef345bba | |||
1390487d3f | |||
16e9fa16f0 | |||
53aba456ce | |||
729e0e4ed9 | |||
4c9d28bfc4 | |||
7a780b58b3 | |||
39300478f1 | |||
4eb948b694 | |||
16c8b409e6 | |||
ccca314649 | |||
1f290c94b8 | |||
fcd77dca6b | |||
dfd5501132 | |||
b6af2c6d48 | |||
2b8bf136a9 | |||
697349e9a2 | |||
c841c4e1fe | |||
cd7b7b68d9 | |||
7e101ce3a3 | |||
8f877776d6 | |||
2d76120562 | |||
2667e2c20d | |||
1dd40b14ed | |||
ff4c0cbc16 | |||
b2888519fb | |||
0bca81e5da | |||
feba3c7e1a | |||
86e921cb4b | |||
bc4ffcac47 | |||
eeb9e7734a | |||
bc6fa8e736 | |||
0a4ac0d875 | |||
e2a085d310 | |||
010bc88a20 | |||
5253a80748 | |||
bdfe33382a | |||
adbfeae05e | |||
155781b5e2 | |||
922408e979 | |||
6b67baf3bf | |||
01f2ff7d76 | |||
39e828ff10 | |||
3ac6916678 | |||
946416be97 | |||
329b56e7d3 | |||
055480ee0b | |||
14d424e573 | |||
60cbf7ea36 | |||
59a408314f | |||
96016c02d2 | |||
d9e1ccc98a | |||
e36b8dbe8e | |||
14ec051e22 | |||
9e075203e4 | |||
de956a842f | |||
9eef0d02d1 | |||
1e97e7602b | |||
6193e68a8f | |||
90df434b5b | |||
14a0aa68ac | |||
6329f9cd62 | |||
35c76d85a0 | |||
0af30b90ac | |||
2f355b47f3 | |||
b0a8de8b19 | |||
86257278b7 | |||
1773bd5d1e | |||
8b8722596b | |||
a8e1acdba7 | |||
ecf83ff8a3 | |||
f7f3d37ef0 | |||
8810f558df | |||
453ce84fc5 | |||
095e33a03b | |||
5c3c868682 | |||
bc78ff64b5 | |||
66a18af38a | |||
03d022cb5b |
18
.github/workflows/gradle.yml
vendored
Normal file
18
.github/workflows/gradle.yml
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
name: Gradle
|
||||
|
||||
on: [ push ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 17
|
||||
cache: gradle
|
||||
- name: Build with Gradle
|
||||
run: chmod a+x gradlew && ./gradlew build --no-daemon
|
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
/.gradle/
|
||||
/build/
|
||||
/.idea/
|
94
CHANGELOG.md
Normal file
94
CHANGELOG.md
Normal file
|
@ -0,0 +1,94 @@
|
|||
# toml4j Changelog
|
||||
|
||||
## 0.7.3 / 2022-05-05
|
||||
|
||||
## Updated
|
||||
* Added SeriaizedName annotation support
|
||||
* Add proper indentation for array types
|
||||
* Append object fields instead of overwriting the whole file
|
||||
|
||||
|
||||
## 0.7.2 / 2017-08-05
|
||||
|
||||
## Updated
|
||||
|
||||
* [Update Gson to 2.8.1 and various Maven plugins to latest versions](https://github.com/mwanji/toml4j/pull/45) (thanks to __[DanilaFe](https://github.com/DanilaFe)__)
|
||||
|
||||
## Fixed
|
||||
|
||||
* [tomlWriter.write NullPointerException in JDK9](https://github.com/mwanji/toml4j/issues/46) (thanks to __[iwangxiaodong](https://github.com/iwangxiaodong)__)
|
||||
* Change build to be able to release a new version entirely from the command line
|
||||
|
||||
## 0.7.1 / 2016-07-27
|
||||
|
||||
* [Support literal strings in table names](https://github.com/mwanji/toml4j/issues/36) (thanks to __[bruno-medeiros](https://github.com/bruno-medeiros)__)
|
||||
|
||||
## 0.7.0 / 2016-07-12
|
||||
|
||||
## Added
|
||||
|
||||
* Toml#read(Toml) merges two Toml instances (thanks to __[gustavkarlsson](https://github.com/gustavkarlsson)__)
|
||||
|
||||
## 0.6.0 / 2016-06-14
|
||||
|
||||
## Added
|
||||
|
||||
* Toml#toMap() convenience method (thanks to __[andytill](https://github.com/andytill)__ and __[Gyscos](https://github.com/Gyscos))
|
||||
|
||||
## Fixed
|
||||
|
||||
* Transient fields are not written to TOML files (thanks to __[lare96](https://github.com/lare96)__)
|
||||
* Support positive timezone offset in datetime (thanks to __[aloyse](https://github.com/aloyse)__)
|
||||
|
||||
## 0.5.1 / 2016-01-24
|
||||
|
||||
### Fixed
|
||||
|
||||
* [Handling of tables with same name in different table array items](https://github.com/mwanji/toml4j/issues/26) (thanks to __[stanofujdiar](https://github.com/stanofujdiar)__)
|
||||
|
||||
## 0.5.0 / 2015-12-10
|
||||
|
||||
### Changed
|
||||
|
||||
* __BREAKING:__ Toml#parse methods renamed to read
|
||||
* __BREAKING:__ Toml#getList(String), Toml#getTable(String) and Toml#getTables(String) return null when key is not found
|
||||
* Removed trailing newline from error messages (thanks to __[Zero3](https://github.com/Zero3)__)
|
||||
* Toml#read(File) forces encoding to UTF-8 (thanks to __[Bruno Medeiros](https://github.com/bruno-medeiros)__)
|
||||
|
||||
### Added
|
||||
|
||||
* Support for writing objects to TOML with TomlWriter (thanks to __[dilecti](https://github.com/dilecti)__)
|
||||
* Support for underscores in numbers (the feature branch had accidentally not been merged into 0.4.0! :( )
|
||||
* Set<Map.Entry> Toml#entrySet() cf. Reflection section in README (thanks __[Zero3](https://github.com/Zero3)__ and __[d3xter](https://github.com/d3xter)__)
|
||||
* Overloaded getters that take a default value (thanks to __[udiabon](https://github.com/udiabon)__)
|
||||
* Toml#contains(String) and Toml#containsXXX(String) methods to check for existence of keys
|
||||
|
||||
## 0.4.0 / 2015-02-16
|
||||
|
||||
### Changed
|
||||
|
||||
* __BREAKING:__ Toml#getList(String) replaced Toml#getList(String, Class)
|
||||
* Dropped dependency on Parboiled and its significant transitive dependencies
|
||||
|
||||
### Added
|
||||
|
||||
* Support for [TOML 0.4.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.4.0.md)
|
||||
* Toml#isEmpty()
|
||||
* More detailed error messages, including line numbers
|
||||
|
||||
### Fixed
|
||||
|
||||
* Short-form Unicode escape handling
|
||||
* Exponent handling
|
||||
|
||||
## 0.3.1 / 2014-12-16
|
||||
* Support for [TOML 0.3.1](https://github.com/toml-lang/toml/tree/v0.3.1) spec
|
||||
* Pass TOML validator (https://github.com/BurntSushi/toml-test), which uncovered many bugs.
|
||||
* Reduced visibility of internal classes, so that only Toml class is visible to users.
|
||||
* Refactored parsing into several steps.
|
||||
|
||||
## 0.2 / 2014-04-10
|
||||
* Support for [TOML 0.2](https://github.com/toml-lang/toml/tree/v0.2.0) spec, most notably table arrays.
|
||||
|
||||
## 0.1 / 2014-04-06
|
||||
* Support for [TOML 0.1](https://github.com/toml-lang/toml/tree/v0.1.0) spec.
|
8
CONTRIBUTING.md
Normal file
8
CONTRIBUTING.md
Normal file
|
@ -0,0 +1,8 @@
|
|||
Thank you for taking the time to contribute to toml4j! Here are a few guidelines to streamline the process.
|
||||
|
||||
* Pull Requests should be opened against the wip branch. Master changes only when there is a new release.
|
||||
* Amend README.md as necessary
|
||||
* Use 2 spaces for indentation
|
||||
* Opening braces, parentheses, etc. are not on a new line
|
||||
|
||||
If you are unsure about how something should be implemented, open a pull request and we'll discuss it.
|
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2022 Plexus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
345
README.md
345
README.md
|
@ -1,91 +1,340 @@
|
|||
# toml4j
|
||||
# toml4j [![Build Status](https://ci.plex.us.org/job/toml4j/badge/icon)](https://ci.plex.us.org/job/toml4j/)
|
||||
|
||||
toml4j is a [TOML 0.1.0](https://github.com/mojombo/toml) parser for Java that uses the [Parboiled](http://www.parboiled.org) PEG parser.
|
||||
toml4j is a [TOML 0.4.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.4.0.md) parser for Java.
|
||||
|
||||
## Installation
|
||||
|
||||
Add the following dependency to your POM:
|
||||
Add the following dependency to your POM (or equivalent for other dependency managers):
|
||||
|
||||
````xml
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.moandjiezana.toml</groupId>
|
||||
<groupId>dev.plex</groupId>
|
||||
<artifactId>toml4j</artifactId>
|
||||
<version>0.1.0</version>
|
||||
<version>0.7.3</version>
|
||||
</dependency>
|
||||
````
|
||||
```
|
||||
|
||||
If you use Gradle, you can use the following code:
|
||||
```gradle
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
url = uri("https://nexus.telesphoreo.me/repository/totalfreedom/")
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
...
|
||||
implementation("dev.plex:toml4j:0.7.3")
|
||||
}
|
||||
```
|
||||
|
||||
Requires Java 17 above.
|
||||
|
||||
## Quick start
|
||||
|
||||
```java
|
||||
Toml toml = new Toml().read(getTomlFile());
|
||||
String someValue = toml.getString("someKey");
|
||||
Date someDate = toml.getDate("someTable.someDate");
|
||||
MyClass myClass = toml.to(MyClass.class);
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
A `dev.plex.Toml` instance is populated by calling one of `read(File)`, `read(InputStream)`, `read(Reader)`, `read(String)` or `read(Toml)`.
|
||||
|
||||
```java
|
||||
Toml toml = new Toml().read("a=1");
|
||||
```
|
||||
|
||||
An exception is thrown if the source is not valid TOML.
|
||||
|
||||
The data can then be accessed either by converting the Toml instance to your own class or by accessing tables and keys by name.
|
||||
|
||||
### Maps
|
||||
|
||||
`Toml#toMap()` is a quick way to turn a Toml instance into a `Map<String, Object>`.
|
||||
|
||||
```java
|
||||
Map<String, Object> map = new Toml().read("a=1").toMap();
|
||||
```
|
||||
|
||||
### Custom classes
|
||||
|
||||
A Toml object can be mapped to a custom class with the `Toml#to(Class<T>)` method.
|
||||
`Toml#to(Class<T>)` maps a Toml instance to the given class.
|
||||
|
||||
Any keys not found in both the TOML and the class are ignored.
|
||||
|
||||
Key groups can be mapped to other custom classes. Fields may be private.
|
||||
|
||||
````
|
||||
```toml
|
||||
name = "Mwanji Ezana"
|
||||
|
||||
[address]
|
||||
street = "123 A Street"
|
||||
city = "AnyVille"
|
||||
````
|
||||
|
||||
[contacts]
|
||||
"email address" = "me@example.com"
|
||||
```
|
||||
|
||||
````java
|
||||
class Address {
|
||||
String street;
|
||||
String city;
|
||||
}
|
||||
```java
|
||||
class Address {
|
||||
String street;
|
||||
String city;
|
||||
}
|
||||
|
||||
class User {
|
||||
String name;
|
||||
Address address;
|
||||
}
|
||||
````
|
||||
class User {
|
||||
String name;
|
||||
Address address;
|
||||
Map<String, Object> contacts;
|
||||
}
|
||||
```
|
||||
|
||||
````java
|
||||
Toml toml = new Toml().parse(tomlFile);
|
||||
User user = toml.to(User.class);
|
||||
```java
|
||||
User user = new Toml().read(tomlFile).to(User.class);
|
||||
|
||||
assert user.name.equals("Mwanji Ezana");
|
||||
assert user.address.street.equals("123 A Street");
|
||||
````
|
||||
assert user.contacts.get("\"email address\"").equals("me@example.com");
|
||||
```
|
||||
|
||||
When defaults are present, a shallow merge is performed.
|
||||
Any keys not found in both the TOML and the class are ignored. Fields may be private.
|
||||
|
||||
Quoted keys cannot be mapped directly to a Java object, but they can be used as keys within a `Map`.
|
||||
|
||||
TOML primitives can be mapped to a number of Java types:
|
||||
|
||||
TOML | Java
|
||||
---- | ----
|
||||
Integer | `int`, `long` (or wrapper), `java.math.BigInteger`
|
||||
Float | `float`, `double` (or wrapper), `java.math.BigDecimal`
|
||||
String | `String`, enum, `java.net.URI`, `java.net.URL`
|
||||
One-letter String | `char`, `Character`
|
||||
Multiline and Literal Strings | `String`
|
||||
Array | `List`, `Set`, array. The generic type can be anything that can be converted.
|
||||
Table | Custom class, `Map<String, Object>`
|
||||
|
||||
Custom classes, Maps and collections thereof can be nested to any level. See [TomlToClassTest#should_convert_fruit_table_array()](src/test/java/com/moandjiezana/toml/TomlToClassTest.java) for an example.
|
||||
|
||||
### Key names
|
||||
|
||||
1. Create a `com.moandjiezana.toml.Toml` instance
|
||||
2. Call the `parse` method of your choice
|
||||
3. Use the getters to retrieve the data
|
||||
Use the getters to retrieve the data:
|
||||
|
||||
````java
|
||||
Toml toml = new Toml().parse(getTomlFile()); // throws an Exception if the TOML is incorrect
|
||||
* `getString(String)`
|
||||
* `getDate(String)`
|
||||
* `getBoolean(String)`
|
||||
* `getLong(String)`
|
||||
* `getDouble(String)`
|
||||
* `getList(String)`
|
||||
* `getTable(String)` returns a new Toml instance containing only the keys in that table.
|
||||
* `getTables(String)`, for table arrays, returns `List<Toml>`.
|
||||
|
||||
String title = toml.getString("title"); // if a key doesn't exist, returns null
|
||||
Boolean enabled = toml.getBoolean("database.enabled"); // gets the value of enabled from the database key group
|
||||
Toml servers = toml.getKeyGroup("servers"); // returns a new Toml instance containing only the key group's values
|
||||
````
|
||||
You can also navigate values within a table with a compound key of the form `table.key`. Use a zero-based index such as `tableArray[0].key` to navigate table arrays.
|
||||
|
||||
Non-existent keys return null.
|
||||
|
||||
When retrieving quoted keys, the quotes must be used and the key must be spelled exactly the same way, including quotes and whitespace. The only exceptions are Unicode escapes: `"\u00B1" = "value"` would be retrieved with `toml.getString("\"±\"")`.
|
||||
|
||||
```toml
|
||||
title = "TOML Example"
|
||||
"sub title" = "Now with quoted keys"
|
||||
|
||||
[database]
|
||||
ports = [ 8001, 8001, 8002 ]
|
||||
enabled = true
|
||||
[database.credentials]
|
||||
password = "password"
|
||||
|
||||
[servers]
|
||||
cluster = "hyades"
|
||||
[servers.alpha]
|
||||
ip = "10.0.0.1"
|
||||
|
||||
[[networks]]
|
||||
name = "Level 1"
|
||||
[networks.status]
|
||||
bandwidth = 10
|
||||
|
||||
[[networks]]
|
||||
name = "Level 2"
|
||||
|
||||
[[networks]]
|
||||
name = "Level 3"
|
||||
[[networks.operators]]
|
||||
location = "Geneva"
|
||||
[[networks.operators]]
|
||||
location = "Paris"
|
||||
```
|
||||
|
||||
```java
|
||||
Toml toml = new Toml().read(getTomlFile());
|
||||
|
||||
String title = toml.getString("title");
|
||||
String subTitle = toml.getString("\"sub title\"");
|
||||
Boolean enabled = toml.getBoolean("database.enabled");
|
||||
List<Long> ports = toml.getList("database.ports");
|
||||
String password = toml.getString("database.credentials.password");
|
||||
|
||||
Toml servers = toml.getTable("servers");
|
||||
String cluster = servers.getString("cluster"); // navigation is relative to current Toml instance
|
||||
String ip = servers.getString("alpha.ip");
|
||||
|
||||
Toml network1 = toml.getTable("networks[0]");
|
||||
String network2Name = toml.getString("networks[1].name"); // "Level 2"
|
||||
List<Toml> network3Operators = toml.getTables("networks[2].operators");
|
||||
String network3Operator2Location = toml.getString("networks[2].operators[1].location"); // "Paris"
|
||||
```
|
||||
|
||||
### Defaults
|
||||
|
||||
The constructor can be given a set of default values that will be used if necessary.
|
||||
The constructor can be given a set of default values that will be used as fallbacks. For tables and table arrays, a shallow merge is performed.
|
||||
|
||||
````java
|
||||
Toml defaults = new Toml().parse("a = 2\nb = 3");
|
||||
Toml toml = new Toml(defaults).parse("a = 1");
|
||||
`Toml#read(Toml)` is used to merge two Toml instances:
|
||||
|
||||
```java
|
||||
Toml toml1 = new Toml().read("a=1");
|
||||
Toml toml2 = new Toml().read(getTomlFile());
|
||||
|
||||
Toml mergedToml = new Toml(toml1).read(toml2);
|
||||
```
|
||||
|
||||
You can also call an overloaded version of the getters that take a default value. Note that the default value provided in the constructor take precedence over the one provided by the getter.
|
||||
|
||||
```toml
|
||||
# defaults
|
||||
a = 2
|
||||
b = 3
|
||||
|
||||
[table]
|
||||
c = 4
|
||||
d = 5
|
||||
```
|
||||
|
||||
```toml
|
||||
a = 1
|
||||
|
||||
[table]
|
||||
c = 2
|
||||
|
||||
[[array]]
|
||||
d = 3
|
||||
```
|
||||
|
||||
```java
|
||||
Toml defaults = new Toml().read(getDefaultsFile());
|
||||
Toml toml = new Toml(defaults).read(getTomlFile());
|
||||
|
||||
Long a = toml.getLong("a"); // returns 1, not 2
|
||||
Long b = toml.getLong("b"); // returns 3
|
||||
Long b = toml.getLong("b"); // returns 3, taken from defaults provided to constructor
|
||||
Long bPrefersConstructor = toml.getLong("b", 5); // returns 3, not 5
|
||||
Long c = toml.getLong("c"); // returns null
|
||||
````
|
||||
Long cWithDefault = toml.getLong("c", 5); // returns 5
|
||||
Long tableC = toml.getLong("table.c"); // returns 2, not 4
|
||||
Long tableD = toml.getLong("table.d"); // returns null, not 5, because of shallow merge
|
||||
Long arrayD = toml.getLong("array[0].d"); // returns 3
|
||||
```
|
||||
|
||||
## TODO
|
||||
### Reflection
|
||||
|
||||
* Fail on invalid definitions
|
||||
`Toml#entrySet()` returns a Set of [Map.Entry](http://docs.oracle.com/javase/6/docs/api/java/util/Map.Entry.html) instances. Modifications to the returned Set are not reflected in the Toml instance. Note that Map.Entry#setValue() will throw an UnsupportedOperationException.
|
||||
|
||||
```java
|
||||
for (Map.Entry<String, Object> entry : myToml.entrySet()) {
|
||||
System.out.println(entry.getKey() + " " + entry.getValue());
|
||||
}
|
||||
```
|
||||
|
||||
`Toml#contains(String)` verifies that the instance contains a key of any type (primitive, table or array of tables) of the given name. `Toml#containsPrimitive(String)`, `Toml#containsTable(String)` and `Toml#containsTableArray(String)` return true only if a key exists and is a primitive, table or array of tables, respectively. Compound keys can be used to check existence at any depth.
|
||||
|
||||
|
||||
```java
|
||||
Toml toml = new Toml().read("a = 1");
|
||||
|
||||
toml.contains("a"); // true
|
||||
toml.conatinsKey("a"); // true
|
||||
toml.containsTable("a"); // false
|
||||
toml.containsTableArray("a"); // false
|
||||
```
|
||||
|
||||
### Converting Objects To TOML
|
||||
|
||||
You can write `Map`s and custom objects to a TOML `String`, `File`, `Writer`, or `OutputStream` with a `TomlWriter`. Each TomlWriter instance is customisable, immutable and threadsafe, so it can be reused and passed around. Constants and transient fields are ignored.
|
||||
|
||||
To write a `List` of objects as a table array, put the list in a `Map` or in a custom object.
|
||||
|
||||
```java
|
||||
class AClass {
|
||||
int anInt = 1;
|
||||
int[] anArray = { 2, 3 };
|
||||
}
|
||||
|
||||
TomlWriter tomlWriter = new TomlWriter();
|
||||
AClass obj = new AClass();
|
||||
|
||||
Map<String, Object> map = new HashMap<String, Object>();
|
||||
int[] intArray = { 2, 3 };
|
||||
map.put("anInt", 1);
|
||||
map.put("anArray", intArray);
|
||||
|
||||
String tomlString = tomlWriter.write(obj);
|
||||
tomlString = tomlWriter.write(map);
|
||||
|
||||
tomlWriter.write(obj, new File("path/to/file"));
|
||||
tomlWriter.write(obj, new ByteArrayOutputStream());
|
||||
tomlWriter.write(obj, new OutputStreamWriter(anOutputStream));
|
||||
|
||||
/*
|
||||
All methods output:
|
||||
|
||||
anInt = 1
|
||||
anArray = [2, 3]
|
||||
*/
|
||||
```
|
||||
|
||||
You can customise formatting with a TomlWriter.Builder:
|
||||
|
||||
```java
|
||||
class BClass {
|
||||
Map<String, ?> aMap = new HashMap<String, Object>();
|
||||
}
|
||||
|
||||
BClass obj = new BClass();
|
||||
obj.aMap.put("item", 1);
|
||||
|
||||
TomlWriter tomlWriter = new TomlWriter.Builder()
|
||||
.indentValuesBy(2)
|
||||
.indentTablesBy(4)
|
||||
.padArrayDelimitersBy(3)
|
||||
.build();
|
||||
|
||||
String tomlString = tomlWriter.write(obj);
|
||||
|
||||
/*
|
||||
Output:
|
||||
|
||||
[aMap]
|
||||
item = 1
|
||||
|
||||
[aMap.a]
|
||||
anInt = 1
|
||||
anArray = [ 2, 3 ]
|
||||
*/
|
||||
```
|
||||
|
||||
### Threadsafety
|
||||
|
||||
`TomlWriter` is threadsafe, however the JDK's streams and `Writer` are not. Take care not to write to the same stream in parallel.
|
||||
|
||||
### Limitations
|
||||
|
||||
Date precision is limited to milliseconds.
|
||||
|
||||
## Changelog
|
||||
|
||||
Please see the [changelog](CHANGELOG.md).
|
||||
|
||||
## Contributing
|
||||
|
||||
Please see the [contribution guidelines](CONTRIBUTING.md).
|
||||
|
||||
## License
|
||||
|
||||
toml4j is copyright of Moandji Ezana and is licensed under the [MIT License](http://www.opensource.org/licenses/mit-license.php)
|
||||
|
||||
toml4j is copyright (c) 2013-2015 Moandji Ezana and is licensed under the [MIT License](LICENSE)
|
||||
|
|
37
build.gradle.kts
Normal file
37
build.gradle.kts
Normal file
|
@ -0,0 +1,37 @@
|
|||
plugins {
|
||||
java
|
||||
`maven-publish`
|
||||
}
|
||||
|
||||
group = "dev.plex"
|
||||
version = "0.7.3"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("com.google.code.gson:gson:2.9.0")
|
||||
implementation("org.projectlombok:lombok:1.18.24")
|
||||
implementation("org.jetbrains:annotations:23.0.0")
|
||||
annotationProcessor("org.projectlombok:lombok:1.18.24")
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
create<MavenPublication>("maven") {
|
||||
from(components["java"])
|
||||
}
|
||||
repositories {
|
||||
maven {
|
||||
val releasesRepoUrl = uri("https://nexus.telesphoreo.me/repository/plex-releases")
|
||||
val snapshotsRepoUrl = uri("https://nexus.telesphoreo.me/repository/plex-snapshots")
|
||||
url = uri(if (version.toString().endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl)
|
||||
credentials {
|
||||
username = System.getenv("plexUser")
|
||||
password = System.getenv("plexPassword")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
234
gradlew
vendored
Normal file
234
gradlew
vendored
Normal file
|
@ -0,0 +1,234 @@
|
|||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# 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
|
||||
#
|
||||
# https://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.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=${0##*/}
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
89
gradlew.bat
vendored
Normal file
89
gradlew.bat
vendored
Normal file
|
@ -0,0 +1,89 @@
|
|||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
102
pom.xml
102
pom.xml
|
@ -1,102 +0,0 @@
|
|||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.sonatype.oss</groupId>
|
||||
<artifactId>oss-parent</artifactId>
|
||||
<version>7</version>
|
||||
</parent>
|
||||
<groupId>com.moandjiezana.toml</groupId>
|
||||
<artifactId>toml4j</artifactId>
|
||||
<version>0.1.0</version>
|
||||
<name>toml4j</name>
|
||||
<description>A parser for TOML</description>
|
||||
<url>http://moandjiezana.com/toml/</url>
|
||||
<developers>
|
||||
<developer>
|
||||
<id>moandji.ezana</id>
|
||||
<name>Moandji Ezana</name>
|
||||
<email>mwanji@gmail.com</email>
|
||||
</developer>
|
||||
</developers>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.11</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.parboiled</groupId>
|
||||
<artifactId>parboiled-java</artifactId>
|
||||
<version>1.1.6</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
<version>2.2.2</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.1</version>
|
||||
<configuration>
|
||||
<source>1.6</source>
|
||||
<target>1.6</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<licenses>
|
||||
<license>
|
||||
<name>The MIT License</name>
|
||||
<url>http://www.opensource.org/licenses/mit-license.php</url>
|
||||
<distribution>repo</distribution>
|
||||
</license>
|
||||
</licenses>
|
||||
<scm>
|
||||
<connection>scm:git:git@github.com:mwanji/toml4j.git</connection>
|
||||
<developerConnection>scm:git:git@github.com:mwanji/toml4j.git</developerConnection>
|
||||
<url>https://github.com/mwanji/toml4j</url>
|
||||
</scm>
|
||||
<issueManagement>
|
||||
<system>Github Issues</system>
|
||||
<url>https://github.com/mwanji/toml4j/issues</url>
|
||||
</issueManagement>
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>release-sign-artifacts</id>
|
||||
<activation>
|
||||
<property>
|
||||
<name>performRelease</name>
|
||||
<value>true</value>
|
||||
</property>
|
||||
</activation>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-gpg-plugin</artifactId>
|
||||
<version>1.4</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>sign-artifacts</id>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>sign</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
2
settings.gradle.kts
Normal file
2
settings.gradle.kts
Normal file
|
@ -0,0 +1,2 @@
|
|||
rootProject.name = "toml4j"
|
||||
|
|
@ -1,135 +0,0 @@
|
|||
package com.moandjiezana.toml;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Scanner;
|
||||
|
||||
import org.parboiled.Parboiled;
|
||||
import org.parboiled.parserunners.RecoveringParseRunner;
|
||||
import org.parboiled.support.ParsingResult;
|
||||
|
||||
/**
|
||||
*
|
||||
* All getters can fall back to default values if they have been provided and will return null if no matching key exists.
|
||||
*
|
||||
*/
|
||||
public class Toml {
|
||||
|
||||
private Map<String, Object> values = new HashMap<String, Object>();
|
||||
private final Toml defaults;
|
||||
|
||||
public Toml() {
|
||||
this((Toml) null);
|
||||
}
|
||||
|
||||
public Toml(Toml defaults) {
|
||||
this.defaults = defaults;
|
||||
}
|
||||
|
||||
public Toml parse(File file) {
|
||||
try {
|
||||
return parse(new Scanner(file).useDelimiter("\\Z").next());
|
||||
} catch (FileNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public Toml parse(String tomlString) {
|
||||
TomlParser parser = Parboiled.createParser(TomlParser.class);
|
||||
ParsingResult<Object> result = new RecoveringParseRunner<Object>(parser.Toml()).run(tomlString);
|
||||
// ParsingResult<Object> parsingResult = new ReportingParseRunner<Object>(parser.Toml()).run(tomlString);
|
||||
// System.out.println(ParseTreeUtils.printNodeTree(parsingResult));
|
||||
|
||||
TomlParser.Results results = (TomlParser.Results) result.valueStack.peek(result.valueStack.size() - 1);
|
||||
if (results.errors.length() > 0) {
|
||||
throw new IllegalStateException(results.errors.toString());
|
||||
}
|
||||
|
||||
this.values = results.values;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getString(String key) {
|
||||
return (String) get(key);
|
||||
}
|
||||
|
||||
public Long getLong(String key) {
|
||||
return (Long) get(key);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> List<T> getList(String key, Class<T> itemClass) {
|
||||
return (List<T>) get(key);
|
||||
}
|
||||
|
||||
public Boolean getBoolean(String key) {
|
||||
return (Boolean) get(key);
|
||||
}
|
||||
|
||||
public Date getDate(String key) {
|
||||
return (Date) get(key);
|
||||
}
|
||||
|
||||
public Double getDouble(String key) {
|
||||
return (Double) get(key);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public Toml getKeyGroup(String key) {
|
||||
return new Toml((Map<String, Object>) get(key));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Object get(String key) {
|
||||
String[] split = key.split("\\.");
|
||||
Object current = new HashMap<String, Object>(values);
|
||||
Object currentDefaults = defaults != null ? defaults.values : null;
|
||||
for (String splitKey : split) {
|
||||
current = ((Map<String, Object>) current).get(splitKey);
|
||||
if (currentDefaults != null) {
|
||||
currentDefaults = ((Map<String, Object>) currentDefaults).get(splitKey);
|
||||
if (current instanceof Map && currentDefaults instanceof Map) {
|
||||
for (Map.Entry<String, Object> entry : ((Map<String, Object>) currentDefaults).entrySet()) {
|
||||
if (!((Map<String, Object>) current).containsKey(entry.getKey())) {
|
||||
((Map<String, Object>) current).put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (current == null && currentDefaults != null) {
|
||||
current = currentDefaults;
|
||||
}
|
||||
if (current == null) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
private Toml(Map<String, Object> values) {
|
||||
this.values = values;
|
||||
this.defaults = null;
|
||||
}
|
||||
|
||||
public <T> T to(Class<T> targetClass) {
|
||||
HashMap<String, Object> valuesCopy = new HashMap<String, Object>(values);
|
||||
if (defaults != null) {
|
||||
for (Map.Entry<String, Object> entry : defaults.values.entrySet()) {
|
||||
if (!valuesCopy.containsKey(entry.getKey())) {
|
||||
valuesCopy.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
Gson gson = new Gson();
|
||||
String json = gson.toJson(valuesCopy);
|
||||
return gson.fromJson(json, targetClass);
|
||||
}
|
||||
}
|
|
@ -1,288 +0,0 @@
|
|||
package com.moandjiezana.toml;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.parboiled.BaseParser;
|
||||
import org.parboiled.Rule;
|
||||
import org.parboiled.annotations.BuildParseTree;
|
||||
import org.parboiled.annotations.SuppressNode;
|
||||
|
||||
@BuildParseTree
|
||||
class TomlParser extends BaseParser<Object> {
|
||||
|
||||
static class Results {
|
||||
public Map<String, Object> values = new HashMap<String, Object>();
|
||||
public Set<String> keyGroups = new HashSet<String>();
|
||||
public StringBuilder errors = new StringBuilder();
|
||||
}
|
||||
|
||||
public Rule Toml() {
|
||||
return Sequence(push(new TomlParser.Results()), push(((TomlParser.Results) peek()).values), OneOrMore(FirstOf(KeyGroup(), '\n', Comment(), Key())));
|
||||
}
|
||||
|
||||
Rule KeyGroup() {
|
||||
return Sequence(Sequence(KeyGroupDelimiter(), KeyGroupName(), addKeyGroup((String) pop()), KeyGroupDelimiter(), Spacing()), checkKeyGroup(match()));
|
||||
}
|
||||
|
||||
boolean checkKeyGroup(String definition) {
|
||||
String afterBracket = definition.substring(definition.indexOf(']') + 1);
|
||||
for (char character : afterBracket.toCharArray()) {
|
||||
if (character == '#') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!Character.isWhitespace(character)) {
|
||||
results().errors.append("Invalid key group definition: ").append(definition).append(". You may have forgotten a #");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Rule Key() {
|
||||
return Sequence(Spacing(), KeyName(), EqualsSign(), VariableValues(), Spacing(), swap(), addKey((String) pop(), pop()));
|
||||
}
|
||||
|
||||
Rule KeyGroupName() {
|
||||
return Sequence(OneOrMore(TestNot(KeyGroupDelimiter()), FirstOf(Letter(), Digit(), ANY)), push(match()));
|
||||
}
|
||||
|
||||
Rule KeyName() {
|
||||
return Sequence(OneOrMore(TestNot(EqualsSign()), ANY), push(match()));
|
||||
}
|
||||
|
||||
Rule VariableValues() {
|
||||
return FirstOf(ArrayValue(), DateValue(), BooleanValue(), NumberValue(), StringValue());
|
||||
}
|
||||
|
||||
Rule ArrayValue() {
|
||||
return Sequence(push(ArrayList.class), '[', Spacing(), ZeroOrMore(VariableValues(), Optional(ArrayDelimiter())), Spacing(), ']', pushList());
|
||||
}
|
||||
|
||||
Rule DateValue() {
|
||||
return Sequence(Sequence(Year(), '-', Month(), '-', Day(), 'T', Digit(), Digit(), ':', Digit(), Digit(), ':', Digit(), Digit(), 'Z'), pushDate(match()));
|
||||
}
|
||||
|
||||
Rule BooleanValue() {
|
||||
return Sequence(FirstOf("true", "false"), push(Boolean.valueOf(match())));
|
||||
}
|
||||
|
||||
Rule NumberValue() {
|
||||
return Sequence(Sequence(Optional('-'), OneOrMore(FirstOf(Digit(), '.'))), pushNumber(match()));
|
||||
}
|
||||
|
||||
Rule StringValue() {
|
||||
return Sequence(push(new StringBuilder()), '"', OneOrMore(TestNot('"'), FirstOf(UnicodeCharacter(), SpecialCharacter(), AnyCharacter())), pushString(((StringBuilder) pop()).toString()), '"');
|
||||
}
|
||||
|
||||
Rule Year() {
|
||||
return Sequence(Digit(), Digit(), Digit(), Digit());
|
||||
}
|
||||
|
||||
Rule Month() {
|
||||
return Sequence(CharRange('0', '1'), Digit());
|
||||
}
|
||||
|
||||
Rule Day() {
|
||||
return Sequence(CharRange('0', '3'), Digit());
|
||||
}
|
||||
|
||||
Rule Digit() {
|
||||
return CharRange('0', '9');
|
||||
}
|
||||
|
||||
Rule Letter() {
|
||||
return CharRange('a', 'z');
|
||||
}
|
||||
|
||||
Rule UnicodeCharacter() {
|
||||
return Sequence(Sequence('\\', 'u', OneOrMore(FirstOf(CharRange('0', '9'), CharRange('A', 'F')))), pushCharacter(match()));
|
||||
}
|
||||
|
||||
Rule SpecialCharacter() {
|
||||
return Sequence(Sequence('\\', ANY), pushCharacter(match()));
|
||||
}
|
||||
|
||||
Rule AnyCharacter() {
|
||||
return Sequence(ANY, pushCharacter(match()));
|
||||
}
|
||||
|
||||
@SuppressNode
|
||||
Rule KeyGroupDelimiter() {
|
||||
return AnyOf("[]");
|
||||
}
|
||||
|
||||
@SuppressNode
|
||||
Rule EqualsSign() {
|
||||
return Sequence(Spacing(), '=', Spacing());
|
||||
}
|
||||
|
||||
@SuppressNode
|
||||
Rule Spacing() {
|
||||
return ZeroOrMore(FirstOf(Comment(), Whitespace(), NewLine(), AnyOf("\f")));
|
||||
}
|
||||
|
||||
Rule IllegalCharacters() {
|
||||
return Sequence(ZeroOrMore(Whitespace()), OneOrMore(TestNot('#', NewLine()), ANY));
|
||||
//return Sequence(ZeroOrMore(Whitespace()), TestNot('#', NewLine()), OneOrMore(ANY));
|
||||
}
|
||||
|
||||
@SuppressNode
|
||||
Rule Whitespace() {
|
||||
return AnyOf(" \t");
|
||||
}
|
||||
|
||||
@SuppressNode
|
||||
Rule NewLine() {
|
||||
return AnyOf("\r\n");
|
||||
}
|
||||
|
||||
@SuppressNode
|
||||
Rule ArrayDelimiter() {
|
||||
return Sequence(Spacing(), ',', Spacing());
|
||||
}
|
||||
|
||||
@SuppressNode
|
||||
Rule Comment() {
|
||||
return Sequence('#', ZeroOrMore(TestNot(NewLine()), ANY), FirstOf(NewLine(), EOI));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
boolean addKeyGroup(String name) {
|
||||
String[] split = name.split("\\.");
|
||||
|
||||
while (getContext().getValueStack().size() > 2) {
|
||||
drop();
|
||||
}
|
||||
|
||||
Map<String, Object> newKeyGroup = (Map<String, Object>) getContext().getValueStack().peek();
|
||||
|
||||
if (!results().keyGroups.add(name)) {
|
||||
results().errors.append("Could not create key group ").append(name).append(": key group already exists!\n");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
for (String splitKey : split) {
|
||||
if (!newKeyGroup.containsKey(splitKey)) {
|
||||
newKeyGroup.put(splitKey, new HashMap<String, Object>());
|
||||
}
|
||||
Object currentValue = newKeyGroup.get(splitKey);
|
||||
if (!(currentValue instanceof Map)) {
|
||||
results().errors.append("Could not create key group ").append(name).append(": key already has a value!\n");
|
||||
|
||||
return true;
|
||||
}
|
||||
newKeyGroup = (Map<String, Object>) currentValue;
|
||||
}
|
||||
|
||||
push(newKeyGroup);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean addKey(String key, Object value) {
|
||||
if (key.contains(".")) {
|
||||
results().errors.append(key).append(" is invalid: key names may not contain a dot!\n");
|
||||
|
||||
return true;
|
||||
}
|
||||
putValue(key, value);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean pushList() {
|
||||
ArrayList<Object> list = new ArrayList<Object>();
|
||||
while (peek() != ArrayList.class) {
|
||||
list.add(0, pop());
|
||||
}
|
||||
|
||||
poke(list);
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean pushDate(String dateString) {
|
||||
String s = dateString.replace("Z", "+00:00");
|
||||
try {
|
||||
s = s.substring(0, 22) + s.substring(23);
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
|
||||
dateFormat.setLenient(false);
|
||||
Date date = dateFormat.parse(s);
|
||||
push(date);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
results().errors.append("Invalid date: ").append(dateString).append("\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
boolean pushNumber(String number) {
|
||||
if (number.contains(".")) {
|
||||
push(Double.valueOf(number));
|
||||
} else {
|
||||
push(Long.valueOf(number));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean pushString(String line) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
String[] split = line.split("\\\\n");
|
||||
for (String string : split) {
|
||||
builder.append(string).append('\n');
|
||||
}
|
||||
builder.deleteCharAt(builder.length() - 1);
|
||||
push(builder.toString());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean pushCharacter(String sc) {
|
||||
StringBuilder sb = (StringBuilder) peek();
|
||||
if (sc.equals("\\n")) {
|
||||
sb.append('\n');
|
||||
} else if (sc.equals("\\\"")) {
|
||||
sb.append('\"');
|
||||
} else if (sc.equals("\\t")) {
|
||||
sb.append('\t');
|
||||
} else if (sc.equals("\\r")) {
|
||||
sb.append('\r');
|
||||
} else if (sc.equals("\\\\")) {
|
||||
sb.append('\\');
|
||||
} else if (sc.equals("\\/")) {
|
||||
sb.append('/');
|
||||
} else if (sc.equals("\\b")) {
|
||||
sb.append('\b');
|
||||
} else if (sc.equals("\\f")) {
|
||||
sb.append('\f');
|
||||
} else if (sc.startsWith("\\u")) {
|
||||
sb.append(Character.toChars(Integer.parseInt(sc.substring(2), 16)));
|
||||
} else if (sc.startsWith("\\")) {
|
||||
results().errors.append(sc + " is a reserved special character and cannot be used!\n");
|
||||
} else {
|
||||
sb.append(sc);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
void putValue(String name, Object value) {
|
||||
Map<String, Object> values = (Map<String, Object>) peek();
|
||||
if (values.containsKey(name)) {
|
||||
results().errors.append("Key ").append(name).append(" already exists!\n");
|
||||
return;
|
||||
}
|
||||
values.put(name, value);
|
||||
}
|
||||
|
||||
TomlParser.Results results() {
|
||||
return (Results) peek(getContext().getValueStack().size() - 1);
|
||||
}
|
||||
}
|
77
src/main/java/dev/plex/toml/ArrayValueReader.java
Normal file
77
src/main/java/dev/plex/toml/ArrayValueReader.java
Normal file
|
@ -0,0 +1,77 @@
|
|||
package dev.plex.toml;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class ArrayValueReader implements ValueReader {
|
||||
|
||||
public static final ArrayValueReader ARRAY_VALUE_READER = new ArrayValueReader();
|
||||
@Override
|
||||
public boolean canRead(String s) {
|
||||
return s.startsWith("[");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object read(String s, AtomicInteger index, dev.plex.toml.Context context) {
|
||||
AtomicInteger line = context.line;
|
||||
int startLine = line.get();
|
||||
int startIndex = index.get();
|
||||
List<Object> arrayItems = new ArrayList<Object>();
|
||||
boolean terminated = false;
|
||||
boolean inComment = false;
|
||||
dev.plex.toml.Results.Errors errors = new dev.plex.toml.Results.Errors();
|
||||
|
||||
for (int i = index.incrementAndGet(); i < s.length(); i = index.incrementAndGet()) {
|
||||
|
||||
char c = s.charAt(i);
|
||||
|
||||
if (c == '#' && !inComment) {
|
||||
inComment = true;
|
||||
} else if (c == '\n') {
|
||||
inComment = false;
|
||||
line.incrementAndGet();
|
||||
} else if (inComment || Character.isWhitespace(c) || c == ',') {
|
||||
continue;
|
||||
} else if (c == '[') {
|
||||
Object converted = read(s, index, context);
|
||||
if (converted instanceof dev.plex.toml.Results.Errors) {
|
||||
errors.add((dev.plex.toml.Results.Errors) converted);
|
||||
} else if (!isHomogenousArray(converted, arrayItems)) {
|
||||
errors.heterogenous(context.identifier.getName(), line.get());
|
||||
} else {
|
||||
arrayItems.add(converted);
|
||||
}
|
||||
continue;
|
||||
} else if (c == ']') {
|
||||
terminated = true;
|
||||
break;
|
||||
} else {
|
||||
Object converted = ValueReaders.VALUE_READERS.convert(s, index, context);
|
||||
if (converted instanceof dev.plex.toml.Results.Errors) {
|
||||
errors.add((dev.plex.toml.Results.Errors) converted);
|
||||
} else if (!isHomogenousArray(converted, arrayItems)) {
|
||||
errors.heterogenous(context.identifier.getName(), line.get());
|
||||
} else {
|
||||
arrayItems.add(converted);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!terminated) {
|
||||
errors.unterminated(context.identifier.getName(), s.substring(startIndex, s.length()), startLine);
|
||||
}
|
||||
|
||||
if (errors.hasErrors()) {
|
||||
return errors;
|
||||
}
|
||||
|
||||
return arrayItems;
|
||||
}
|
||||
|
||||
private boolean isHomogenousArray(Object o, List<?> values) {
|
||||
return values.isEmpty() || values.get(0).getClass().isAssignableFrom(o.getClass()) || o.getClass().isAssignableFrom(values.get(0).getClass());
|
||||
}
|
||||
|
||||
private ArrayValueReader() {}
|
||||
}
|
66
src/main/java/dev/plex/toml/ArrayValueWriter.java
Normal file
66
src/main/java/dev/plex/toml/ArrayValueWriter.java
Normal file
|
@ -0,0 +1,66 @@
|
|||
package dev.plex.toml;
|
||||
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
import static dev.plex.toml.ValueWriters.WRITERS;
|
||||
|
||||
public abstract class ArrayValueWriter implements dev.plex.toml.ValueWriter
|
||||
{
|
||||
static protected boolean isArrayish(Object value) {
|
||||
return value instanceof Collection || value.getClass().isArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPrimitiveType() {
|
||||
return false;
|
||||
}
|
||||
|
||||
static boolean isArrayOfPrimitive(Object array) {
|
||||
Object first = peek(array);
|
||||
if (first != null) {
|
||||
dev.plex.toml.ValueWriter valueWriter = WRITERS.findWriterFor(first);
|
||||
return valueWriter.isPrimitiveType() || isArrayish(first);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Collection<?> normalize(Object value) {
|
||||
Collection<Object> collection;
|
||||
|
||||
if (value.getClass().isArray()) {
|
||||
// Arrays.asList() interprets an array as a single element,
|
||||
// so convert it to a list by hand
|
||||
collection = new ArrayList<Object>(Array.getLength(value));
|
||||
for (int i = 0; i < Array.getLength(value); i++) {
|
||||
Object elem = Array.get(value, i);
|
||||
collection.add(elem);
|
||||
}
|
||||
} else {
|
||||
collection = (Collection<Object>) value;
|
||||
}
|
||||
|
||||
return collection;
|
||||
}
|
||||
|
||||
private static Object peek(Object value) {
|
||||
if (value.getClass().isArray()) {
|
||||
if (Array.getLength(value) > 0) {
|
||||
return Array.get(value, 0);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
Collection<?> collection = (Collection<?>) value;
|
||||
if (collection.size() > 0) {
|
||||
return collection.iterator().next();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
48
src/main/java/dev/plex/toml/BooleanValueReaderWriter.java
Normal file
48
src/main/java/dev/plex/toml/BooleanValueReaderWriter.java
Normal file
|
@ -0,0 +1,48 @@
|
|||
package dev.plex.toml;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
|
||||
class BooleanValueReaderWriter implements ValueReader, ValueWriter {
|
||||
|
||||
static final BooleanValueReaderWriter BOOLEAN_VALUE_READER_WRITER = new BooleanValueReaderWriter();
|
||||
|
||||
@Override
|
||||
public boolean canRead(String s) {
|
||||
return s.startsWith("true") || s.startsWith("false");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object read(String s, AtomicInteger index, Context context) {
|
||||
s = s.substring(index.get());
|
||||
Boolean b = s.startsWith("true") ? Boolean.TRUE : Boolean.FALSE;
|
||||
|
||||
int endIndex = b == Boolean.TRUE ? 4 : 5;
|
||||
|
||||
index.addAndGet(endIndex - 1);
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canWrite(Object value) {
|
||||
return Boolean.class.isInstance(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(Object value, WriterContext context) {
|
||||
context.write(value.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPrimitiveType() {
|
||||
return true;
|
||||
}
|
||||
|
||||
private BooleanValueReaderWriter() {}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "boolean";
|
||||
}
|
||||
}
|
122
src/main/java/dev/plex/toml/Container.java
Normal file
122
src/main/java/dev/plex/toml/Container.java
Normal file
|
@ -0,0 +1,122 @@
|
|||
package dev.plex.toml;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class Container {
|
||||
|
||||
abstract boolean accepts(String key);
|
||||
abstract void put(String key, Object value);
|
||||
abstract Object get(String key);
|
||||
abstract boolean isImplicit();
|
||||
|
||||
static class Table extends Container {
|
||||
private final Map<String, Object> values = new HashMap<String, Object>();
|
||||
final String name;
|
||||
final boolean implicit;
|
||||
|
||||
Table() {
|
||||
this(null, false);
|
||||
}
|
||||
|
||||
public Table(String name) {
|
||||
this(name, false);
|
||||
}
|
||||
|
||||
public Table(String tableName, boolean implicit) {
|
||||
this.name = tableName;
|
||||
this.implicit = implicit;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean accepts(String key) {
|
||||
return !values.containsKey(key) || values.get(key) instanceof TableArray;
|
||||
}
|
||||
|
||||
@Override
|
||||
void put(String key, Object value) {
|
||||
values.put(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
Object get(String key) {
|
||||
return values.get(key);
|
||||
}
|
||||
|
||||
boolean isImplicit() {
|
||||
return implicit;
|
||||
}
|
||||
|
||||
/**
|
||||
* This modifies the Table's internal data structure, such that it is no longer usable.
|
||||
*
|
||||
* Therefore, this method must only be called when all data has been gathered.
|
||||
|
||||
* @return A Map-and-List-based of the TOML data
|
||||
*/
|
||||
Map<String, Object> consume() {
|
||||
for (Map.Entry<String, Object> entry : values.entrySet()) {
|
||||
if (entry.getValue() instanceof Table) {
|
||||
entry.setValue(((Table) entry.getValue()).consume());
|
||||
} else if (entry.getValue() instanceof TableArray) {
|
||||
entry.setValue(((TableArray) entry.getValue()).getValues());
|
||||
}
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return values.toString();
|
||||
}
|
||||
}
|
||||
|
||||
static class TableArray extends Container {
|
||||
private final List<Table> values = new ArrayList<Table>();
|
||||
|
||||
TableArray() {
|
||||
values.add(new Table());
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean accepts(String key) {
|
||||
return getCurrent().accepts(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
void put(String key, Object value) {
|
||||
values.add((Table) value);
|
||||
}
|
||||
|
||||
@Override
|
||||
Object get(String key) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
boolean isImplicit() {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<Map<String, Object>> getValues() {
|
||||
ArrayList<Map<String, Object>> unwrappedValues = new ArrayList<Map<String,Object>>();
|
||||
for (Table table : values) {
|
||||
unwrappedValues.add(table.consume());
|
||||
}
|
||||
return unwrappedValues;
|
||||
}
|
||||
|
||||
Table getCurrent() {
|
||||
return values.get(values.size() - 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return values.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private Container() {}
|
||||
}
|
19
src/main/java/dev/plex/toml/Context.java
Normal file
19
src/main/java/dev/plex/toml/Context.java
Normal file
|
@ -0,0 +1,19 @@
|
|||
package dev.plex.toml;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class Context {
|
||||
final dev.plex.toml.Identifier identifier;
|
||||
final AtomicInteger line;
|
||||
final Results.Errors errors;
|
||||
|
||||
public Context(dev.plex.toml.Identifier identifier, AtomicInteger line, Results.Errors errors) {
|
||||
this.identifier = identifier;
|
||||
this.line = line;
|
||||
this.errors = errors;
|
||||
}
|
||||
|
||||
public Context with(dev.plex.toml.Identifier identifier) {
|
||||
return new Context(identifier, line, errors);
|
||||
}
|
||||
}
|
22
src/main/java/dev/plex/toml/DatePolicy.java
Normal file
22
src/main/java/dev/plex/toml/DatePolicy.java
Normal file
|
@ -0,0 +1,22 @@
|
|||
package dev.plex.toml;
|
||||
|
||||
import java.util.TimeZone;
|
||||
|
||||
public class DatePolicy {
|
||||
|
||||
private final TimeZone timeZone;
|
||||
private final boolean showFractionalSeconds;
|
||||
|
||||
DatePolicy(TimeZone timeZone, boolean showFractionalSeconds) {
|
||||
this.timeZone = timeZone;
|
||||
this.showFractionalSeconds = showFractionalSeconds;
|
||||
}
|
||||
|
||||
TimeZone getTimeZone() {
|
||||
return timeZone;
|
||||
}
|
||||
|
||||
boolean isShowFractionalSeconds() {
|
||||
return showFractionalSeconds;
|
||||
}
|
||||
}
|
160
src/main/java/dev/plex/toml/DateValueReaderWriter.java
Normal file
160
src/main/java/dev/plex/toml/DateValueReaderWriter.java
Normal file
|
@ -0,0 +1,160 @@
|
|||
package dev.plex.toml;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class DateValueReaderWriter implements ValueReader, ValueWriter {
|
||||
|
||||
static final DateValueReaderWriter DATE_VALUE_READER_WRITER = new DateValueReaderWriter();
|
||||
static final DateValueReaderWriter DATE_PARSER_JDK_6 = new DateConverterJdk6();
|
||||
private static final Pattern DATE_REGEX = Pattern.compile("(\\d{4}-[0-1][0-9]-[0-3][0-9]T[0-2][0-9]:[0-5][0-9]:[0-5][0-9])(\\.\\d*)?(Z|(?:[+\\-]\\d{2}:\\d{2}))(.*)");
|
||||
|
||||
@Override
|
||||
public boolean canRead(String s) {
|
||||
if (s.length() < 5) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
char c = s.charAt(i);
|
||||
|
||||
if (i < 4) {
|
||||
if (!Character.isDigit(c)) {
|
||||
return false;
|
||||
}
|
||||
} else if (c != '-') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object read(String original, AtomicInteger index, Context context) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for (int i = index.get(); i < original.length(); i = index.incrementAndGet()) {
|
||||
char c = original.charAt(i);
|
||||
if (Character.isDigit(c) || c == '-' || c == '+' || c == ':' || c == '.' || c == 'T' || c == 'Z') {
|
||||
sb.append(c);
|
||||
} else {
|
||||
index.decrementAndGet();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
String s = sb.toString();
|
||||
Matcher matcher = DATE_REGEX.matcher(s);
|
||||
|
||||
if (!matcher.matches()) {
|
||||
Results.Errors errors = new Results.Errors();
|
||||
errors.invalidValue(context.identifier.getName(), s, context.line.get());
|
||||
return errors;
|
||||
}
|
||||
|
||||
String dateString = matcher.group(1);
|
||||
String zone = matcher.group(3);
|
||||
String fractionalSeconds = matcher.group(2);
|
||||
String format = "yyyy-MM-dd'T'HH:mm:ss";
|
||||
if (fractionalSeconds != null && !fractionalSeconds.isEmpty()) {
|
||||
format += ".SSS";
|
||||
dateString += fractionalSeconds;
|
||||
}
|
||||
format += "Z";
|
||||
if ("Z".equals(zone)) {
|
||||
dateString += "+0000";
|
||||
} else if (zone.contains(":")) {
|
||||
dateString += zone.replace(":", "");
|
||||
}
|
||||
|
||||
try {
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat(format);
|
||||
dateFormat.setLenient(false);
|
||||
return dateFormat.parse(dateString);
|
||||
} catch (Exception e) {
|
||||
Results.Errors errors = new Results.Errors();
|
||||
errors.invalidValue(context.identifier.getName(), s, context.line.get());
|
||||
return errors;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canWrite(Object value) {
|
||||
return value instanceof Date;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(Object value, WriterContext context) {
|
||||
DateFormat formatter = getFormatter(context.getDatePolicy());
|
||||
context.write(formatter.format(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPrimitiveType() {
|
||||
return true;
|
||||
}
|
||||
|
||||
private DateFormat getFormatter(DatePolicy datePolicy) {
|
||||
boolean utc = "UTC".equals(datePolicy.getTimeZone().getID());
|
||||
String format;
|
||||
|
||||
if (utc && datePolicy.isShowFractionalSeconds()) {
|
||||
format = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
|
||||
} else if (utc) {
|
||||
format = "yyyy-MM-dd'T'HH:mm:ss'Z'";
|
||||
} else if (datePolicy.isShowFractionalSeconds()) {
|
||||
format = getTimeZoneAndFractionalSecondsFormat();
|
||||
} else {
|
||||
format = getTimeZoneFormat();
|
||||
}
|
||||
SimpleDateFormat formatter = new SimpleDateFormat(format);
|
||||
formatter.setTimeZone(datePolicy.getTimeZone());
|
||||
|
||||
return formatter;
|
||||
}
|
||||
|
||||
String getTimeZoneFormat() {
|
||||
return "yyyy-MM-dd'T'HH:mm:ssXXX";
|
||||
}
|
||||
|
||||
String getTimeZoneAndFractionalSecondsFormat() {
|
||||
return "yyyy-MM-dd'T'HH:mm:ss.SSSXXX";
|
||||
}
|
||||
|
||||
private DateValueReaderWriter() {}
|
||||
|
||||
private static class DateConverterJdk6 extends DateValueReaderWriter {
|
||||
@Override
|
||||
public void write(Object value, WriterContext context) {
|
||||
DateFormat formatter = super.getFormatter(context.getDatePolicy());
|
||||
String date = formatter.format(value);
|
||||
|
||||
if ("UTC".equals(context.getDatePolicy().getTimeZone().getID())) {
|
||||
context.write(date);
|
||||
} else {
|
||||
int insertionIndex = date.length() - 2;
|
||||
context.write(date.substring(0, insertionIndex)).write(':').write(date.substring(insertionIndex));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
String getTimeZoneAndFractionalSecondsFormat() {
|
||||
return "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
|
||||
}
|
||||
|
||||
@Override
|
||||
String getTimeZoneFormat() {
|
||||
return "yyyy-MM-dd'T'HH:mm:ssZ";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "datetime";
|
||||
}
|
||||
}
|
331
src/main/java/dev/plex/toml/Identifier.java
Normal file
331
src/main/java/dev/plex/toml/Identifier.java
Normal file
|
@ -0,0 +1,331 @@
|
|||
package dev.plex.toml;
|
||||
|
||||
public class Identifier
|
||||
{
|
||||
|
||||
static final Identifier INVALID = new Identifier("", null);
|
||||
|
||||
private static final String ALLOWED_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890_-";
|
||||
|
||||
private final String name;
|
||||
private final Type type;
|
||||
|
||||
static Identifier from(String name, Context context)
|
||||
{
|
||||
Type type;
|
||||
boolean valid;
|
||||
name = name.trim();
|
||||
if (name.startsWith("[["))
|
||||
{
|
||||
type = Type.TABLE_ARRAY;
|
||||
valid = isValidTableArray(name, context);
|
||||
} else if (name.startsWith("["))
|
||||
{
|
||||
type = Type.TABLE;
|
||||
valid = isValidTable(name, context);
|
||||
} else
|
||||
{
|
||||
type = Type.KEY;
|
||||
valid = isValidKey(name, context);
|
||||
}
|
||||
|
||||
if (!valid)
|
||||
{
|
||||
return Identifier.INVALID;
|
||||
}
|
||||
|
||||
return new Identifier(extractName(name), type);
|
||||
}
|
||||
|
||||
private Identifier(String name, Type type)
|
||||
{
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
String getName()
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
String getBareName()
|
||||
{
|
||||
if (isKey())
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
if (isTable())
|
||||
{
|
||||
return name.substring(1, name.length() - 1);
|
||||
}
|
||||
|
||||
return name.substring(2, name.length() - 2);
|
||||
}
|
||||
|
||||
boolean isKey()
|
||||
{
|
||||
return type == Type.KEY;
|
||||
}
|
||||
|
||||
boolean isTable()
|
||||
{
|
||||
return type == Type.TABLE;
|
||||
}
|
||||
|
||||
boolean isTableArray()
|
||||
{
|
||||
return type == Type.TABLE_ARRAY;
|
||||
}
|
||||
|
||||
private static enum Type
|
||||
{
|
||||
KEY, TABLE, TABLE_ARRAY;
|
||||
}
|
||||
|
||||
private static String extractName(String raw)
|
||||
{
|
||||
boolean quoted = false;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < raw.length(); i++)
|
||||
{
|
||||
char c = raw.charAt(i);
|
||||
if (c == '"' && (i == 0 || raw.charAt(i - 1) != '\\'))
|
||||
{
|
||||
quoted = !quoted;
|
||||
sb.append('"');
|
||||
} else if (quoted || !Character.isWhitespace(c))
|
||||
{
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
|
||||
return StringValueReaderWriter.STRING_VALUE_READER_WRITER.replaceUnicodeCharacters(sb.toString());
|
||||
}
|
||||
|
||||
private static boolean isValidKey(String name, Context context)
|
||||
{
|
||||
if (name.trim().isEmpty())
|
||||
{
|
||||
context.errors.invalidKey(name, context.line.get());
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean quoted = false;
|
||||
for (int i = 0; i < name.length(); i++)
|
||||
{
|
||||
char c = name.charAt(i);
|
||||
|
||||
if (c == '"' && (i == 0 || name.charAt(i - 1) != '\\'))
|
||||
{
|
||||
if (!quoted && i > 0 && name.charAt(i - 1) != '.')
|
||||
{
|
||||
context.errors.invalidKey(name, context.line.get());
|
||||
return false;
|
||||
}
|
||||
quoted = !quoted;
|
||||
} else if (!quoted && (ALLOWED_CHARS.indexOf(c) == -1))
|
||||
{
|
||||
context.errors.invalidKey(name, context.line.get());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean isValidTable(String name, Context context)
|
||||
{
|
||||
boolean valid = true;
|
||||
|
||||
if (!name.endsWith("]"))
|
||||
{
|
||||
valid = false;
|
||||
}
|
||||
|
||||
String trimmed = name.substring(1, name.length() - 1).trim();
|
||||
if (trimmed.isEmpty() || trimmed.charAt(0) == '.' || trimmed.endsWith("."))
|
||||
{
|
||||
valid = false;
|
||||
}
|
||||
|
||||
if (!valid)
|
||||
{
|
||||
context.errors.invalidTable(name, context.line.get());
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean quoted = false;
|
||||
boolean dotAllowed = false;
|
||||
boolean quoteAllowed = true;
|
||||
boolean charAllowed = true;
|
||||
|
||||
for (int i = 0; i < trimmed.length(); i++)
|
||||
{
|
||||
char c = trimmed.charAt(i);
|
||||
|
||||
if (!valid)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (Keys.isQuote(c))
|
||||
{
|
||||
if (!quoteAllowed)
|
||||
{
|
||||
valid = false;
|
||||
} else if (quoted && trimmed.charAt(i - 1) != '\\')
|
||||
{
|
||||
charAllowed = false;
|
||||
dotAllowed = true;
|
||||
quoteAllowed = false;
|
||||
} else if (!quoted)
|
||||
{
|
||||
quoted = true;
|
||||
quoteAllowed = true;
|
||||
}
|
||||
} else if (quoted)
|
||||
{
|
||||
continue;
|
||||
} else if (c == '.')
|
||||
{
|
||||
if (dotAllowed)
|
||||
{
|
||||
charAllowed = true;
|
||||
dotAllowed = false;
|
||||
quoteAllowed = true;
|
||||
} else
|
||||
{
|
||||
context.errors.emptyImplicitTable(name, context.line.get());
|
||||
return false;
|
||||
}
|
||||
} else if (Character.isWhitespace(c))
|
||||
{
|
||||
char prev = trimmed.charAt(i - 1);
|
||||
if (!Character.isWhitespace(prev) && prev != '.')
|
||||
{
|
||||
charAllowed = false;
|
||||
dotAllowed = true;
|
||||
quoteAllowed = true;
|
||||
}
|
||||
} else
|
||||
{
|
||||
if (charAllowed && ALLOWED_CHARS.indexOf(c) > -1)
|
||||
{
|
||||
charAllowed = true;
|
||||
dotAllowed = true;
|
||||
quoteAllowed = false;
|
||||
} else
|
||||
{
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!valid)
|
||||
{
|
||||
context.errors.invalidTable(name, context.line.get());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean isValidTableArray(String line, Context context)
|
||||
{
|
||||
boolean valid = true;
|
||||
|
||||
if (!line.endsWith("]]"))
|
||||
{
|
||||
valid = false;
|
||||
}
|
||||
|
||||
String trimmed = line.substring(2, line.length() - 2).trim();
|
||||
if (trimmed.isEmpty() || trimmed.charAt(0) == '.' || trimmed.endsWith("."))
|
||||
{
|
||||
valid = false;
|
||||
}
|
||||
|
||||
if (!valid)
|
||||
{
|
||||
context.errors.invalidTableArray(line, context.line.get());
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean quoted = false;
|
||||
boolean dotAllowed = false;
|
||||
boolean quoteAllowed = true;
|
||||
boolean charAllowed = true;
|
||||
|
||||
for (int i = 0; i < trimmed.length(); i++)
|
||||
{
|
||||
char c = trimmed.charAt(i);
|
||||
|
||||
if (!valid)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (c == '"')
|
||||
{
|
||||
if (!quoteAllowed)
|
||||
{
|
||||
valid = false;
|
||||
} else if (quoted && trimmed.charAt(i - 1) != '\\')
|
||||
{
|
||||
charAllowed = false;
|
||||
dotAllowed = true;
|
||||
quoteAllowed = false;
|
||||
} else if (!quoted)
|
||||
{
|
||||
quoted = true;
|
||||
quoteAllowed = true;
|
||||
}
|
||||
} else if (quoted)
|
||||
{
|
||||
continue;
|
||||
} else if (c == '.')
|
||||
{
|
||||
if (dotAllowed)
|
||||
{
|
||||
charAllowed = true;
|
||||
dotAllowed = false;
|
||||
quoteAllowed = true;
|
||||
} else
|
||||
{
|
||||
context.errors.emptyImplicitTable(line, context.line.get());
|
||||
return false;
|
||||
}
|
||||
} else if (Character.isWhitespace(c))
|
||||
{
|
||||
char prev = trimmed.charAt(i - 1);
|
||||
if (!Character.isWhitespace(prev) && prev != '.' && prev != '"')
|
||||
{
|
||||
charAllowed = false;
|
||||
dotAllowed = true;
|
||||
quoteAllowed = true;
|
||||
}
|
||||
} else
|
||||
{
|
||||
if (charAllowed && ALLOWED_CHARS.indexOf(c) > -1)
|
||||
{
|
||||
charAllowed = true;
|
||||
dotAllowed = true;
|
||||
quoteAllowed = false;
|
||||
} else
|
||||
{
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!valid)
|
||||
{
|
||||
context.errors.invalidTableArray(line, context.line.get());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
62
src/main/java/dev/plex/toml/IdentifierConverter.java
Normal file
62
src/main/java/dev/plex/toml/IdentifierConverter.java
Normal file
|
@ -0,0 +1,62 @@
|
|||
package dev.plex.toml;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class IdentifierConverter {
|
||||
|
||||
static final IdentifierConverter IDENTIFIER_CONVERTER = new IdentifierConverter();
|
||||
|
||||
Identifier convert(String s, AtomicInteger index, Context context) {
|
||||
boolean quoted = false;
|
||||
StringBuilder name = new StringBuilder();
|
||||
boolean terminated = false;
|
||||
boolean isKey = s.charAt(index.get()) != '[';
|
||||
boolean isTableArray = !isKey && s.length() > index.get() + 1 && s.charAt(index.get() + 1) == '[';
|
||||
boolean inComment = false;
|
||||
|
||||
for (int i = index.get(); i < s.length(); i = index.incrementAndGet()) {
|
||||
char c = s.charAt(i);
|
||||
if (Keys.isQuote(c) && (i == 0 || s.charAt(i - 1) != '\\')) {
|
||||
quoted = !quoted;
|
||||
name.append(c);
|
||||
} else if (c == '\n') {
|
||||
index.decrementAndGet();
|
||||
break;
|
||||
} else if (quoted) {
|
||||
name.append(c);
|
||||
} else if (c == '=' && isKey) {
|
||||
terminated = true;
|
||||
break;
|
||||
} else if (c == ']' && !isKey) {
|
||||
if (!isTableArray || s.length() > index.get() + 1 && s.charAt(index.get() + 1) == ']') {
|
||||
terminated = true;
|
||||
name.append(']');
|
||||
if (isTableArray) {
|
||||
name.append(']');
|
||||
}
|
||||
}
|
||||
} else if (terminated && c == '#') {
|
||||
inComment = true;
|
||||
} else if (terminated && !Character.isWhitespace(c) && !inComment) {
|
||||
terminated = false;
|
||||
break;
|
||||
} else if (!terminated) {
|
||||
name.append(c);
|
||||
}
|
||||
}
|
||||
|
||||
if (!terminated) {
|
||||
if (isKey) {
|
||||
context.errors.unterminatedKey(name.toString(), context.line.get());
|
||||
} else {
|
||||
context.errors.invalidKey(name.toString(), context.line.get());
|
||||
}
|
||||
|
||||
return Identifier.INVALID;
|
||||
}
|
||||
|
||||
return Identifier.from(name.toString(), context);
|
||||
}
|
||||
|
||||
private IdentifierConverter() {}
|
||||
}
|
30
src/main/java/dev/plex/toml/IndentationPolicy.java
Normal file
30
src/main/java/dev/plex/toml/IndentationPolicy.java
Normal file
|
@ -0,0 +1,30 @@
|
|||
package dev.plex.toml;
|
||||
|
||||
/**
|
||||
* Controls how a {@link TomlWriter} indents tables and key/value pairs.
|
||||
*
|
||||
* The default policy is to not indent.
|
||||
*/
|
||||
public class IndentationPolicy {
|
||||
private final int tableIndent;
|
||||
private final int keyValueIndent;
|
||||
private final int arrayDelimiterPadding;
|
||||
|
||||
IndentationPolicy(int keyIndentation, int tableIndentation, int arrayDelimiterPadding) {
|
||||
this.keyValueIndent = keyIndentation;
|
||||
this.tableIndent = tableIndentation;
|
||||
this.arrayDelimiterPadding = arrayDelimiterPadding;
|
||||
}
|
||||
|
||||
int getTableIndent() {
|
||||
return tableIndent;
|
||||
}
|
||||
|
||||
int getKeyValueIndent() {
|
||||
return keyValueIndent;
|
||||
}
|
||||
|
||||
int getArrayDelimiterPadding() {
|
||||
return arrayDelimiterPadding;
|
||||
}
|
||||
}
|
76
src/main/java/dev/plex/toml/InlineTableValueReader.java
Normal file
76
src/main/java/dev/plex/toml/InlineTableValueReader.java
Normal file
|
@ -0,0 +1,76 @@
|
|||
package dev.plex.toml;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
class InlineTableValueReader implements dev.plex.toml.ValueReader
|
||||
{
|
||||
|
||||
static final InlineTableValueReader INLINE_TABLE_VALUE_READER = new InlineTableValueReader();
|
||||
|
||||
@Override
|
||||
public boolean canRead(String s) {
|
||||
return s.startsWith("{");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object read(String s, AtomicInteger sharedIndex, dev.plex.toml.Context context) {
|
||||
AtomicInteger line = context.line;
|
||||
int startLine = line.get();
|
||||
int startIndex = sharedIndex.get();
|
||||
boolean inKey = true;
|
||||
boolean inValue = false;
|
||||
boolean terminated = false;
|
||||
StringBuilder currentKey = new StringBuilder();
|
||||
HashMap<String, Object> results = new HashMap<String, Object>();
|
||||
dev.plex.toml.Results.Errors errors = new dev.plex.toml.Results.Errors();
|
||||
|
||||
for (int i = sharedIndex.incrementAndGet(); sharedIndex.get() < s.length(); i = sharedIndex.incrementAndGet()) {
|
||||
char c = s.charAt(i);
|
||||
|
||||
if (inValue && !Character.isWhitespace(c)) {
|
||||
Object converted = dev.plex.toml.ValueReaders.VALUE_READERS.convert(s, sharedIndex, context.with(dev.plex.toml.Identifier.from(currentKey.toString(), context)));
|
||||
|
||||
if (converted instanceof dev.plex.toml.Results.Errors) {
|
||||
errors.add((dev.plex.toml.Results.Errors) converted);
|
||||
return errors;
|
||||
}
|
||||
|
||||
String currentKeyTrimmed = currentKey.toString().trim();
|
||||
Object previous = results.put(currentKeyTrimmed, converted);
|
||||
|
||||
if (previous != null) {
|
||||
errors.duplicateKey(currentKeyTrimmed, context.line.get());
|
||||
return errors;
|
||||
}
|
||||
|
||||
currentKey = new StringBuilder();
|
||||
inValue = false;
|
||||
} else if (c == ',') {
|
||||
inKey = true;
|
||||
inValue = false;
|
||||
currentKey = new StringBuilder();
|
||||
} else if (c == '=') {
|
||||
inKey = false;
|
||||
inValue = true;
|
||||
} else if (c == '}') {
|
||||
terminated = true;
|
||||
break;
|
||||
} else if (inKey) {
|
||||
currentKey.append(c);
|
||||
}
|
||||
}
|
||||
|
||||
if (!terminated) {
|
||||
errors.unterminated(context.identifier.getName(), s.substring(startIndex), startLine);
|
||||
}
|
||||
|
||||
if (errors.hasErrors()) {
|
||||
return errors;
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private InlineTableValueReader() {}
|
||||
}
|
69
src/main/java/dev/plex/toml/Keys.java
Normal file
69
src/main/java/dev/plex/toml/Keys.java
Normal file
|
@ -0,0 +1,69 @@
|
|||
package dev.plex.toml;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
class Keys {
|
||||
|
||||
static class Key {
|
||||
final String name;
|
||||
final int index;
|
||||
final String path;
|
||||
|
||||
Key(String name, int index, Key next) {
|
||||
this.name = name;
|
||||
this.index = index;
|
||||
if (next != null) {
|
||||
this.path = name + "." + next.path;
|
||||
} else {
|
||||
this.path = name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static Key[] split(String key) {
|
||||
List<Key> splitKey = new ArrayList<Key>();
|
||||
StringBuilder current = new StringBuilder();
|
||||
boolean quoted = false;
|
||||
boolean indexable = true;
|
||||
boolean inIndex = false;
|
||||
int index = -1;
|
||||
|
||||
for (int i = key.length() - 1; i > -1; i--) {
|
||||
char c = key.charAt(i);
|
||||
if (c == ']' && indexable) {
|
||||
inIndex = true;
|
||||
continue;
|
||||
}
|
||||
indexable = false;
|
||||
if (c == '[' && inIndex) {
|
||||
inIndex = false;
|
||||
index = Integer.parseInt(current.toString());
|
||||
current = new StringBuilder();
|
||||
continue;
|
||||
}
|
||||
if (isQuote(c) && (i == 0 || key.charAt(i - 1) != '\\')) {
|
||||
quoted = !quoted;
|
||||
indexable = false;
|
||||
}
|
||||
if (c != '.' || quoted) {
|
||||
current.insert(0, c);
|
||||
} else {
|
||||
splitKey.add(0, new Key(current.toString(), index, !splitKey.isEmpty() ? splitKey.get(0) : null));
|
||||
indexable = true;
|
||||
index = -1;
|
||||
current = new StringBuilder();
|
||||
}
|
||||
}
|
||||
|
||||
splitKey.add(0, new Key(current.toString(), index, !splitKey.isEmpty() ? splitKey.get(0) : null));
|
||||
|
||||
return splitKey.toArray(new Key[0]);
|
||||
}
|
||||
|
||||
static boolean isQuote(char c) {
|
||||
return c == '"' || c == '\'';
|
||||
}
|
||||
|
||||
private Keys() {}
|
||||
}
|
47
src/main/java/dev/plex/toml/LiteralStringValueReader.java
Normal file
47
src/main/java/dev/plex/toml/LiteralStringValueReader.java
Normal file
|
@ -0,0 +1,47 @@
|
|||
package dev.plex.toml;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class LiteralStringValueReader implements ValueReader
|
||||
{
|
||||
public static final LiteralStringValueReader LITERAL_STRING_VALUE_READER = new LiteralStringValueReader();
|
||||
@Override
|
||||
public boolean canRead(String s)
|
||||
{
|
||||
return s.startsWith("'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object read(String s, AtomicInteger index, dev.plex.toml.Context context)
|
||||
{
|
||||
int startLine = context.line.get();
|
||||
boolean terminated = false;
|
||||
int startIndex = index.incrementAndGet();
|
||||
|
||||
for (int i = index.get(); i < s.length(); i = index.incrementAndGet())
|
||||
{
|
||||
char c = s.charAt(i);
|
||||
|
||||
if (c == '\'')
|
||||
{
|
||||
terminated = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!terminated)
|
||||
{
|
||||
Results.Errors errors = new Results.Errors();
|
||||
errors.unterminated(context.identifier.getName(), s.substring(startIndex), startLine);
|
||||
return errors;
|
||||
}
|
||||
|
||||
String substring = s.substring(startIndex, index.get());
|
||||
|
||||
return substring;
|
||||
}
|
||||
|
||||
private LiteralStringValueReader()
|
||||
{
|
||||
}
|
||||
}
|
167
src/main/java/dev/plex/toml/MapValueWriter.java
Normal file
167
src/main/java/dev/plex/toml/MapValueWriter.java
Normal file
|
@ -0,0 +1,167 @@
|
|||
package dev.plex.toml;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
class MapValueWriter implements dev.plex.toml.ValueWriter
|
||||
{
|
||||
static final dev.plex.toml.ValueWriter MAP_VALUE_WRITER = new MapValueWriter();
|
||||
|
||||
private static final Pattern REQUIRED_QUOTING_PATTERN = Pattern.compile("^.*[^A-Za-z\\d_-].*$");
|
||||
|
||||
@Override
|
||||
public boolean canWrite(Object value)
|
||||
{
|
||||
return value instanceof Map;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(Object value, WriterContext context)
|
||||
{
|
||||
File file = null;
|
||||
if (context.file != null)
|
||||
{
|
||||
file = context.file;
|
||||
}
|
||||
|
||||
Map<?, ?> from = (Map<?, ?>) value;
|
||||
|
||||
Toml toml = null;
|
||||
|
||||
if (file != null)
|
||||
{
|
||||
toml = new Toml().read(file);
|
||||
}
|
||||
|
||||
if (hasPrimitiveValues(from, context))
|
||||
{
|
||||
if (context.hasRun)
|
||||
{
|
||||
if (toml != null)
|
||||
{
|
||||
if (!toml.getValues().containsKey(context.key))
|
||||
{
|
||||
context.writeKey();
|
||||
}
|
||||
} else
|
||||
{
|
||||
context.writeKey();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Render primitive types and arrays of primitive first so they are
|
||||
// grouped under the same table (if there is one)
|
||||
for (Map.Entry<?, ?> entry : from.entrySet())
|
||||
{
|
||||
Object key = entry.getKey();
|
||||
Object fromValue = entry.getValue();
|
||||
if (fromValue == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (context.hasRun && toml != null)
|
||||
{
|
||||
if (context.key != null)
|
||||
{
|
||||
if (key.toString().equalsIgnoreCase(context.key)) continue;
|
||||
if (toml.contains(context.key + "." + key)) continue;
|
||||
}
|
||||
}
|
||||
|
||||
dev.plex.toml.ValueWriter valueWriter = dev.plex.toml.ValueWriters.WRITERS.findWriterFor(fromValue);
|
||||
if (valueWriter.isPrimitiveType())
|
||||
{
|
||||
context.indent();
|
||||
context.write(quoteKey(key)).write(" = ");
|
||||
valueWriter.write(fromValue, context);
|
||||
context.write('\n');
|
||||
} else if (valueWriter == dev.plex.toml.PrimitiveArrayValueWriter.PRIMITIVE_ARRAY_VALUE_WRITER)
|
||||
{
|
||||
context.indent();
|
||||
context.setArrayKey(key.toString());
|
||||
context.write(quoteKey(key)).write(" = ");
|
||||
valueWriter.write(fromValue, context);
|
||||
context.write('\n');
|
||||
}
|
||||
}
|
||||
|
||||
// Now render (sub)tables and arrays of tables
|
||||
for (Object key : from.keySet())
|
||||
{
|
||||
Object fromValue = from.get(key);
|
||||
if (fromValue == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (context.hasRun && toml != null)
|
||||
{
|
||||
if (context.key != null)
|
||||
{
|
||||
if (key.toString().equalsIgnoreCase(context.key)) continue;
|
||||
if (toml.contains(context.key + "." + key)) continue;
|
||||
}
|
||||
}
|
||||
|
||||
dev.plex.toml.ValueWriter valueWriter = dev.plex.toml.ValueWriters.WRITERS.findWriterFor(fromValue);
|
||||
if (valueWriter == this || valueWriter == dev.plex.toml.ObjectValueWriter.OBJECT_VALUE_WRITER || valueWriter == dev.plex.toml.TableArrayValueWriter.TABLE_ARRAY_VALUE_WRITER)
|
||||
{
|
||||
WriterContext context1 = context.pushTable(quoteKey(key));
|
||||
context1.parentName = key.toString();
|
||||
context1.hasRun = true;
|
||||
context1.file = context.file;
|
||||
valueWriter.write(fromValue, context1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPrimitiveType()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
private static String quoteKey(Object key)
|
||||
{
|
||||
String stringKey = key.toString();
|
||||
Matcher matcher = REQUIRED_QUOTING_PATTERN.matcher(stringKey);
|
||||
if (matcher.matches())
|
||||
{
|
||||
stringKey = "\"" + stringKey + "\"";
|
||||
}
|
||||
|
||||
return stringKey;
|
||||
}
|
||||
|
||||
private static boolean hasPrimitiveValues(Map<?, ?> values, WriterContext context)
|
||||
{
|
||||
for (Object key : values.keySet())
|
||||
{
|
||||
Object fromValue = values.get(key);
|
||||
if (fromValue == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
dev.plex.toml.ValueWriter valueWriter = dev.plex.toml.ValueWriters.WRITERS.findWriterFor(fromValue);
|
||||
if (valueWriter.isPrimitiveType() || valueWriter == dev.plex.toml.PrimitiveArrayValueWriter.PRIMITIVE_ARRAY_VALUE_WRITER)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private MapValueWriter()
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package dev.plex.toml;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
class MultilineLiteralStringValueReader implements ValueReader
|
||||
{
|
||||
|
||||
static final MultilineLiteralStringValueReader MULTILINE_LITERAL_STRING_VALUE_READER = new MultilineLiteralStringValueReader();
|
||||
|
||||
@Override
|
||||
public boolean canRead(String s) {
|
||||
return s.startsWith("'''");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object read(String s, AtomicInteger index, Context context) {
|
||||
AtomicInteger line = context.line;
|
||||
int startLine = line.get();
|
||||
int originalStartIndex = index.get();
|
||||
int startIndex = index.addAndGet(3);
|
||||
int endIndex = -1;
|
||||
|
||||
if (s.charAt(startIndex) == '\n') {
|
||||
startIndex = index.incrementAndGet();
|
||||
line.incrementAndGet();
|
||||
}
|
||||
|
||||
for (int i = startIndex; i < s.length(); i = index.incrementAndGet()) {
|
||||
char c = s.charAt(i);
|
||||
|
||||
if (c == '\n') {
|
||||
line.incrementAndGet();
|
||||
}
|
||||
|
||||
if (c == '\'' && s.length() > i + 2 && s.charAt(i + 1) == '\'' && s.charAt(i + 2) == '\'') {
|
||||
endIndex = i;
|
||||
index.addAndGet(2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (endIndex == -1) {
|
||||
Results.Errors errors = new Results.Errors();
|
||||
errors.unterminated(context.identifier.getName(), s.substring(originalStartIndex), startLine);
|
||||
return errors;
|
||||
}
|
||||
|
||||
return s.substring(startIndex, endIndex);
|
||||
}
|
||||
|
||||
private MultilineLiteralStringValueReader() {}
|
||||
}
|
57
src/main/java/dev/plex/toml/MultilineStringValueReader.java
Normal file
57
src/main/java/dev/plex/toml/MultilineStringValueReader.java
Normal file
|
@ -0,0 +1,57 @@
|
|||
package dev.plex.toml;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
class MultilineStringValueReader implements ValueReader
|
||||
{
|
||||
|
||||
static final MultilineStringValueReader MULTILINE_STRING_VALUE_READER = new MultilineStringValueReader();
|
||||
|
||||
@Override
|
||||
public boolean canRead(String s) {
|
||||
return s.startsWith("\"\"\"");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object read(String s, AtomicInteger index, dev.plex.toml.Context context) {
|
||||
AtomicInteger line = context.line;
|
||||
int startLine = line.get();
|
||||
int originalStartIndex = index.get();
|
||||
int startIndex = index.addAndGet(3);
|
||||
int endIndex = -1;
|
||||
|
||||
if (s.charAt(startIndex) == '\n') {
|
||||
startIndex = index.incrementAndGet();
|
||||
line.incrementAndGet();
|
||||
}
|
||||
|
||||
for (int i = startIndex; i < s.length(); i = index.incrementAndGet()) {
|
||||
char c = s.charAt(i);
|
||||
|
||||
if (c == '\n') {
|
||||
line.incrementAndGet();
|
||||
} else if (c == '"' && s.length() > i + 2 && s.charAt(i + 1) == '"' && s.charAt(i + 2) == '"') {
|
||||
endIndex = i;
|
||||
index.addAndGet(2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (endIndex == -1) {
|
||||
dev.plex.toml.Results.Errors errors = new dev.plex.toml.Results.Errors();
|
||||
errors.unterminated(context.identifier.getName(), s.substring(originalStartIndex), startLine);
|
||||
return errors;
|
||||
}
|
||||
|
||||
s = s.substring(startIndex, endIndex);
|
||||
s = s.replaceAll("\\\\\\s+", "");
|
||||
s = dev.plex.toml.StringValueReaderWriter.STRING_VALUE_READER_WRITER.replaceUnicodeCharacters(s);
|
||||
s = dev.plex.toml.StringValueReaderWriter.STRING_VALUE_READER_WRITER.replaceSpecialCharacters(s);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
private MultilineStringValueReader() {
|
||||
}
|
||||
|
||||
}
|
106
src/main/java/dev/plex/toml/NumberValueReaderWriter.java
Normal file
106
src/main/java/dev/plex/toml/NumberValueReaderWriter.java
Normal file
|
@ -0,0 +1,106 @@
|
|||
package dev.plex.toml;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
class NumberValueReaderWriter implements dev.plex.toml.ValueReader, dev.plex.toml.ValueWriter
|
||||
{
|
||||
static final NumberValueReaderWriter NUMBER_VALUE_READER_WRITER = new NumberValueReaderWriter();
|
||||
|
||||
@Override
|
||||
public boolean canRead(String s) {
|
||||
char firstChar = s.charAt(0);
|
||||
|
||||
return firstChar == '+' || firstChar == '-' || Character.isDigit(firstChar);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object read(String s, AtomicInteger index, dev.plex.toml.Context context) {
|
||||
boolean signable = true;
|
||||
boolean dottable = false;
|
||||
boolean exponentable = false;
|
||||
boolean terminatable = false;
|
||||
boolean underscorable = false;
|
||||
String type = "";
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for (int i = index.get(); i < s.length(); i = index.incrementAndGet()) {
|
||||
char c = s.charAt(i);
|
||||
boolean notLastChar = s.length() > i + 1;
|
||||
|
||||
if (Character.isDigit(c)) {
|
||||
sb.append(c);
|
||||
signable = false;
|
||||
terminatable = true;
|
||||
if (type.isEmpty()) {
|
||||
type = "integer";
|
||||
dottable = true;
|
||||
}
|
||||
underscorable = notLastChar;
|
||||
exponentable = !type.equals("exponent");
|
||||
} else if ((c == '+' || c == '-') && signable && notLastChar) {
|
||||
signable = false;
|
||||
terminatable = false;
|
||||
if (c == '-') {
|
||||
sb.append('-');
|
||||
}
|
||||
} else if (c == '.' && dottable && notLastChar) {
|
||||
sb.append('.');
|
||||
type = "float";
|
||||
terminatable = false;
|
||||
dottable = false;
|
||||
exponentable = false;
|
||||
underscorable = false;
|
||||
} else if ((c == 'E' || c == 'e') && exponentable && notLastChar) {
|
||||
sb.append('E');
|
||||
type = "exponent";
|
||||
terminatable = false;
|
||||
signable = true;
|
||||
dottable = false;
|
||||
exponentable = false;
|
||||
underscorable = false;
|
||||
} else if (c == '_' && underscorable && notLastChar && Character.isDigit(s.charAt(i + 1))) {
|
||||
underscorable = false;
|
||||
} else {
|
||||
if (!terminatable) {
|
||||
type = "";
|
||||
}
|
||||
index.decrementAndGet();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (type.equals("integer")) {
|
||||
return Long.valueOf(sb.toString());
|
||||
} else if (type.equals("float")) {
|
||||
return Double.valueOf(sb.toString());
|
||||
} else if (type.equals("exponent")) {
|
||||
String[] exponentString = sb.toString().split("E");
|
||||
|
||||
return Double.parseDouble(exponentString[0]) * Math.pow(10, Double.parseDouble(exponentString[1]));
|
||||
} else {
|
||||
dev.plex.toml.Results.Errors errors = new dev.plex.toml.Results.Errors();
|
||||
errors.invalidValue(context.identifier.getName(), sb.toString(), context.line.get());
|
||||
return errors;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canWrite(Object value) {
|
||||
return Number.class.isInstance(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(Object value, dev.plex.toml.WriterContext context) {
|
||||
context.write(value.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPrimitiveType() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "number";
|
||||
}
|
||||
}
|
88
src/main/java/dev/plex/toml/ObjectValueWriter.java
Normal file
88
src/main/java/dev/plex/toml/ObjectValueWriter.java
Normal file
|
@ -0,0 +1,88 @@
|
|||
package dev.plex.toml;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.*;
|
||||
|
||||
class ObjectValueWriter implements ValueWriter
|
||||
{
|
||||
static final ValueWriter OBJECT_VALUE_WRITER = new ObjectValueWriter();
|
||||
|
||||
@Override
|
||||
public boolean canWrite(Object value)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(Object value, WriterContext context)
|
||||
{
|
||||
Map<String, Object> to = new LinkedHashMap<String, Object>();
|
||||
Set<Field> fields = getFields(value.getClass());
|
||||
for (Field field : fields)
|
||||
{
|
||||
if (field.isAnnotationPresent(SerializedName.class))
|
||||
{
|
||||
to.put(field.getDeclaredAnnotation(SerializedName.class).value(), getFieldValue(field, value));
|
||||
} else {
|
||||
to.put(field.getName(), getFieldValue(field, value));
|
||||
}
|
||||
}
|
||||
|
||||
MapValueWriter.MAP_VALUE_WRITER.write(to, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPrimitiveType()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
private static Set<Field> getFields(Class<?> cls)
|
||||
{
|
||||
Set<Field> fields = new LinkedHashSet<Field>(Arrays.asList(cls.getDeclaredFields()));
|
||||
while (cls != Object.class)
|
||||
{
|
||||
fields.addAll(Arrays.asList(cls.getDeclaredFields()));
|
||||
cls = cls.getSuperclass();
|
||||
}
|
||||
removeConstantsAndSyntheticFields(fields);
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
private static void removeConstantsAndSyntheticFields(Set<Field> fields)
|
||||
{
|
||||
Iterator<Field> iterator = fields.iterator();
|
||||
while (iterator.hasNext())
|
||||
{
|
||||
Field field = iterator.next();
|
||||
if ((Modifier.isFinal(field.getModifiers()) && Modifier.isStatic(field.getModifiers())) || field.isSynthetic() || Modifier.isTransient(field.getModifiers()))
|
||||
{
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Object getFieldValue(Field field, Object o)
|
||||
{
|
||||
boolean isAccessible = field.isAccessible();
|
||||
field.setAccessible(true);
|
||||
Object value = null;
|
||||
try
|
||||
{
|
||||
value = field.get(o);
|
||||
} catch (IllegalAccessException ignored)
|
||||
{
|
||||
}
|
||||
field.setAccessible(isAccessible);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private ObjectValueWriter()
|
||||
{
|
||||
}
|
||||
}
|
53
src/main/java/dev/plex/toml/PrimitiveArrayValueWriter.java
Normal file
53
src/main/java/dev/plex/toml/PrimitiveArrayValueWriter.java
Normal file
|
@ -0,0 +1,53 @@
|
|||
package dev.plex.toml;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
class PrimitiveArrayValueWriter extends ArrayValueWriter
|
||||
{
|
||||
static final ValueWriter PRIMITIVE_ARRAY_VALUE_WRITER = new PrimitiveArrayValueWriter();
|
||||
|
||||
@Override
|
||||
public boolean canWrite(Object value) {
|
||||
return isArrayish(value) && isArrayOfPrimitive(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(Object o, WriterContext context) {
|
||||
Collection<?> values = normalize(o);
|
||||
|
||||
context.write('[');
|
||||
context.writeArrayDelimiterPadding();
|
||||
|
||||
boolean first = true;
|
||||
ValueWriter firstWriter = null;
|
||||
|
||||
for (Object value : values) {
|
||||
if (first) {
|
||||
firstWriter = ValueWriters.WRITERS.findWriterFor(value);
|
||||
first = false;
|
||||
} else {
|
||||
ValueWriter writer = ValueWriters.WRITERS.findWriterFor(value);
|
||||
if (writer != firstWriter) {
|
||||
throw new IllegalStateException(
|
||||
context.getContextPath() +
|
||||
": cannot write a heterogeneous array; first element was of type " + firstWriter +
|
||||
" but found " + writer
|
||||
);
|
||||
}
|
||||
context.write(", ");
|
||||
}
|
||||
|
||||
ValueWriters.WRITERS.findWriterFor(value).write(value, context);
|
||||
}
|
||||
|
||||
context.writeArrayDelimiterPadding();
|
||||
context.write(']');
|
||||
}
|
||||
|
||||
private PrimitiveArrayValueWriter() {}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "primitive-array";
|
||||
}
|
||||
}
|
290
src/main/java/dev/plex/toml/Results.java
Normal file
290
src/main/java/dev/plex/toml/Results.java
Normal file
|
@ -0,0 +1,290 @@
|
|||
package dev.plex.toml;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
class Results {
|
||||
|
||||
static class Errors {
|
||||
|
||||
private final StringBuilder sb = new StringBuilder();
|
||||
|
||||
void duplicateTable(String table, int line) {
|
||||
sb.append("Duplicate table definition on line ")
|
||||
.append(line)
|
||||
.append(": [")
|
||||
.append(table)
|
||||
.append("]");
|
||||
}
|
||||
|
||||
public void tableDuplicatesKey(String table, AtomicInteger line) {
|
||||
sb.append("Key already exists for table defined on line ")
|
||||
.append(line.get())
|
||||
.append(": [")
|
||||
.append(table)
|
||||
.append("]");
|
||||
}
|
||||
|
||||
public void keyDuplicatesTable(String key, AtomicInteger line) {
|
||||
sb.append("Table already exists for key defined on line ")
|
||||
.append(line.get())
|
||||
.append(": ")
|
||||
.append(key);
|
||||
}
|
||||
|
||||
void emptyImplicitTable(String table, int line) {
|
||||
sb.append("Invalid table definition due to empty implicit table name: ")
|
||||
.append(table);
|
||||
}
|
||||
|
||||
void invalidTable(String table, int line) {
|
||||
sb.append("Invalid table definition on line ")
|
||||
.append(line)
|
||||
.append(": ")
|
||||
.append(table)
|
||||
.append("]");
|
||||
}
|
||||
|
||||
void duplicateKey(String key, int line) {
|
||||
sb.append("Duplicate key");
|
||||
if (line > -1) {
|
||||
sb.append(" on line ")
|
||||
.append(line);
|
||||
}
|
||||
sb.append(": ")
|
||||
.append(key);
|
||||
}
|
||||
|
||||
void invalidTextAfterIdentifier(dev.plex.toml.Identifier identifier, char text, int line) {
|
||||
sb.append("Invalid text after key ")
|
||||
.append(identifier.getName())
|
||||
.append(" on line ")
|
||||
.append(line)
|
||||
.append(". Make sure to terminate the value or add a comment (#).");
|
||||
}
|
||||
|
||||
void invalidKey(String key, int line) {
|
||||
sb.append("Invalid key on line ")
|
||||
.append(line)
|
||||
.append(": ")
|
||||
.append(key);
|
||||
}
|
||||
|
||||
void invalidTableArray(String tableArray, int line) {
|
||||
sb.append("Invalid table array definition on line ")
|
||||
.append(line)
|
||||
.append(": ")
|
||||
.append(tableArray);
|
||||
}
|
||||
|
||||
void invalidValue(String key, String value, int line) {
|
||||
sb.append("Invalid value on line ")
|
||||
.append(line)
|
||||
.append(": ")
|
||||
.append(key)
|
||||
.append(" = ")
|
||||
.append(value);
|
||||
}
|
||||
|
||||
void unterminatedKey(String key, int line) {
|
||||
sb.append("Key is not followed by an equals sign on line ")
|
||||
.append(line)
|
||||
.append(": ")
|
||||
.append(key);
|
||||
}
|
||||
|
||||
void unterminated(String key, String value, int line) {
|
||||
sb.append("Unterminated value on line ")
|
||||
.append(line)
|
||||
.append(": ")
|
||||
.append(key)
|
||||
.append(" = ")
|
||||
.append(value.trim());
|
||||
}
|
||||
|
||||
public void heterogenous(String key, int line) {
|
||||
sb.append(key)
|
||||
.append(" becomes a heterogeneous array on line ")
|
||||
.append(line);
|
||||
}
|
||||
|
||||
boolean hasErrors() {
|
||||
return sb.length() > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public void add(Errors other) {
|
||||
sb.append(other.sb);
|
||||
}
|
||||
}
|
||||
|
||||
final Errors errors = new Errors();
|
||||
private final Set<String> tables = new HashSet<String>();
|
||||
private final Deque<Container> stack = new ArrayDeque<Container>();
|
||||
|
||||
Results() {
|
||||
stack.push(new Container.Table(""));
|
||||
}
|
||||
|
||||
void addValue(String key, Object value, AtomicInteger line) {
|
||||
Container currentTable = stack.peek();
|
||||
|
||||
if (value instanceof Map) {
|
||||
String path = getInlineTablePath(key);
|
||||
if (path == null) {
|
||||
startTable(key, line);
|
||||
} else if (path.isEmpty()) {
|
||||
startTables(dev.plex.toml.Identifier.from(key, null), line);
|
||||
} else {
|
||||
startTables(dev.plex.toml.Identifier.from(path, null), line);
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> valueMap = (Map<String, Object>) value;
|
||||
for (Map.Entry<String, Object> entry : valueMap.entrySet()) {
|
||||
addValue(entry.getKey(), entry.getValue(), line);
|
||||
}
|
||||
stack.pop();
|
||||
} else if (currentTable.accepts(key)) {
|
||||
currentTable.put(key, value);
|
||||
} else {
|
||||
if (currentTable.get(key) instanceof Container) {
|
||||
errors.keyDuplicatesTable(key, line);
|
||||
} else {
|
||||
errors.duplicateKey(key, line != null ? line.get() : -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void startTableArray(dev.plex.toml.Identifier identifier, AtomicInteger line) {
|
||||
String tableName = identifier.getBareName();
|
||||
while (stack.size() > 1) {
|
||||
stack.pop();
|
||||
}
|
||||
|
||||
dev.plex.toml.Keys.Key[] tableParts = dev.plex.toml.Keys.split(tableName);
|
||||
for (int i = 0; i < tableParts.length; i++) {
|
||||
String tablePart = tableParts[i].name;
|
||||
Container currentContainer = stack.peek();
|
||||
|
||||
if (currentContainer.get(tablePart) instanceof Container.TableArray) {
|
||||
Container.TableArray currentTableArray = (Container.TableArray) currentContainer.get(tablePart);
|
||||
stack.push(currentTableArray);
|
||||
|
||||
if (i == tableParts.length - 1) {
|
||||
currentTableArray.put(tablePart, new Container.Table());
|
||||
}
|
||||
|
||||
stack.push(currentTableArray.getCurrent());
|
||||
currentContainer = stack.peek();
|
||||
} else if (currentContainer.get(tablePart) instanceof Container.Table && i < tableParts.length - 1) {
|
||||
Container nextTable = (Container) currentContainer.get(tablePart);
|
||||
stack.push(nextTable);
|
||||
} else if (currentContainer.accepts(tablePart)) {
|
||||
Container newContainer = i == tableParts.length - 1 ? new Container.TableArray() : new Container.Table();
|
||||
addValue(tablePart, newContainer, line);
|
||||
stack.push(newContainer);
|
||||
|
||||
if (newContainer instanceof Container.TableArray) {
|
||||
stack.push(((Container.TableArray) newContainer).getCurrent());
|
||||
}
|
||||
} else {
|
||||
errors.duplicateTable(tableName, line.get());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void startTables(dev.plex.toml.Identifier id, AtomicInteger line) {
|
||||
String tableName = id.getBareName();
|
||||
|
||||
while (stack.size() > 1) {
|
||||
stack.pop();
|
||||
}
|
||||
|
||||
dev.plex.toml.Keys.Key[] tableParts = dev.plex.toml.Keys.split(tableName);
|
||||
for (int i = 0; i < tableParts.length; i++) {
|
||||
String tablePart = tableParts[i].name;
|
||||
Container currentContainer = stack.peek();
|
||||
if (currentContainer.get(tablePart) instanceof Container) {
|
||||
Container nextTable = (Container) currentContainer.get(tablePart);
|
||||
if (i == tableParts.length - 1 && !nextTable.isImplicit()) {
|
||||
errors.duplicateTable(tableName, line.get());
|
||||
return;
|
||||
}
|
||||
stack.push(nextTable);
|
||||
if (stack.peek() instanceof Container.TableArray) {
|
||||
stack.push(((Container.TableArray) stack.peek()).getCurrent());
|
||||
}
|
||||
} else if (currentContainer.accepts(tablePart)) {
|
||||
startTable(tablePart, i < tableParts.length - 1, line);
|
||||
} else {
|
||||
errors.tableDuplicatesKey(tablePart, line);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Warning: After this method has been called, this instance is no longer usable.
|
||||
*/
|
||||
Map<String, Object> consume() {
|
||||
Container values = stack.getLast();
|
||||
stack.clear();
|
||||
|
||||
return ((Container.Table) values).consume();
|
||||
}
|
||||
|
||||
private Container startTable(String tableName, AtomicInteger line) {
|
||||
Container newTable = new Container.Table(tableName);
|
||||
addValue(tableName, newTable, line);
|
||||
stack.push(newTable);
|
||||
|
||||
return newTable;
|
||||
}
|
||||
|
||||
private Container startTable(String tableName, boolean implicit, AtomicInteger line) {
|
||||
Container newTable = new Container.Table(tableName, implicit);
|
||||
addValue(tableName, newTable, line);
|
||||
stack.push(newTable);
|
||||
|
||||
return newTable;
|
||||
}
|
||||
|
||||
private String getInlineTablePath(String key) {
|
||||
Iterator<Container> descendingIterator = stack.descendingIterator();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
while (descendingIterator.hasNext()) {
|
||||
Container next = descendingIterator.next();
|
||||
if (next instanceof Container.TableArray) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Container.Table table = (Container.Table) next;
|
||||
|
||||
if (table.name == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (sb.length() > 0) {
|
||||
sb.append('.');
|
||||
}
|
||||
|
||||
sb.append(table.name);
|
||||
}
|
||||
|
||||
if (sb.length() > 0) {
|
||||
sb.append('.');
|
||||
}
|
||||
|
||||
sb.append(key)
|
||||
.insert(0, '[')
|
||||
.append(']');
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
129
src/main/java/dev/plex/toml/StringValueReaderWriter.java
Normal file
129
src/main/java/dev/plex/toml/StringValueReaderWriter.java
Normal file
|
@ -0,0 +1,129 @@
|
|||
package dev.plex.toml;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
class StringValueReaderWriter implements ValueReader, ValueWriter
|
||||
{
|
||||
|
||||
static final StringValueReaderWriter STRING_VALUE_READER_WRITER = new StringValueReaderWriter();
|
||||
private static final Pattern UNICODE_REGEX = Pattern.compile("\\\\[uU](.{4})");
|
||||
|
||||
static private final String[] specialCharacterEscapes = new String[93];
|
||||
|
||||
static {
|
||||
specialCharacterEscapes['\b'] = "\\b";
|
||||
specialCharacterEscapes['\t'] = "\\t";
|
||||
specialCharacterEscapes['\n'] = "\\n";
|
||||
specialCharacterEscapes['\f'] = "\\f";
|
||||
specialCharacterEscapes['\r'] = "\\r";
|
||||
specialCharacterEscapes['"'] = "\\\"";
|
||||
specialCharacterEscapes['\\'] = "\\\\";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRead(String s) {
|
||||
return s.startsWith("\"");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object read(String s, AtomicInteger index, Context context) {
|
||||
int startIndex = index.incrementAndGet();
|
||||
int endIndex = -1;
|
||||
|
||||
for (int i = index.get(); i < s.length(); i = index.incrementAndGet()) {
|
||||
char ch = s.charAt(i);
|
||||
if (ch == '"' && s.charAt(i - 1) != '\\') {
|
||||
endIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (endIndex == -1) {
|
||||
Results.Errors errors = new Results.Errors();
|
||||
errors.unterminated(context.identifier.getName(), s.substring(startIndex - 1), context.line.get());
|
||||
return errors;
|
||||
}
|
||||
|
||||
String raw = s.substring(startIndex, endIndex);
|
||||
s = replaceUnicodeCharacters(raw);
|
||||
s = replaceSpecialCharacters(s);
|
||||
|
||||
if (s == null) {
|
||||
Results.Errors errors = new Results.Errors();
|
||||
errors.invalidValue(context.identifier.getName(), raw, context.line.get());
|
||||
return errors;
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
String replaceUnicodeCharacters(String value) {
|
||||
Matcher unicodeMatcher = UNICODE_REGEX.matcher(value);
|
||||
|
||||
while (unicodeMatcher.find()) {
|
||||
value = value.replace(unicodeMatcher.group(), new String(Character.toChars(Integer.parseInt(unicodeMatcher.group(1), 16))));
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
String replaceSpecialCharacters(String s) {
|
||||
for (int i = 0; i < s.length() - 1; i++) {
|
||||
char ch = s.charAt(i);
|
||||
char next = s.charAt(i + 1);
|
||||
|
||||
if (ch == '\\' && next == '\\') {
|
||||
i++;
|
||||
} else if (ch == '\\' && !(next == 'b' || next == 'f' || next == 'n' || next == 't' || next == 'r' || next == '"' || next == '\\')) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return s.replace("\\n", "\n")
|
||||
.replace("\\\"", "\"")
|
||||
.replace("\\t", "\t")
|
||||
.replace("\\r", "\r")
|
||||
.replace("\\\\", "\\")
|
||||
.replace("\\/", "/")
|
||||
.replace("\\b", "\b")
|
||||
.replace("\\f", "\f");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canWrite(Object value) {
|
||||
return value instanceof String || value instanceof Character || value instanceof URL || value instanceof URI || value instanceof Enum;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(Object value, WriterContext context) {
|
||||
context.write('"');
|
||||
escapeUnicode(value.toString(), context);
|
||||
context.write('"');
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPrimitiveType() {
|
||||
return true;
|
||||
}
|
||||
|
||||
private void escapeUnicode(String in, WriterContext context) {
|
||||
for (int i = 0; i < in.length(); i++) {
|
||||
int codePoint = in.codePointAt(i);
|
||||
if (codePoint < specialCharacterEscapes.length && specialCharacterEscapes[codePoint] != null) {
|
||||
context.write(specialCharacterEscapes[codePoint]);
|
||||
} else {
|
||||
context.write(in.charAt(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private StringValueReaderWriter() {}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "string";
|
||||
}
|
||||
}
|
33
src/main/java/dev/plex/toml/TableArrayValueWriter.java
Normal file
33
src/main/java/dev/plex/toml/TableArrayValueWriter.java
Normal file
|
@ -0,0 +1,33 @@
|
|||
package dev.plex.toml;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import static dev.plex.toml.ValueWriters.WRITERS;
|
||||
|
||||
class TableArrayValueWriter extends ArrayValueWriter
|
||||
{
|
||||
static final ValueWriter TABLE_ARRAY_VALUE_WRITER = new TableArrayValueWriter();
|
||||
|
||||
@Override
|
||||
public boolean canWrite(Object value) {
|
||||
return isArrayish(value) && !isArrayOfPrimitive(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(Object from, WriterContext context) {
|
||||
Collection<?> values = normalize(from);
|
||||
|
||||
WriterContext subContext = context.pushTableFromArray();
|
||||
|
||||
for (Object value : values) {
|
||||
WRITERS.findWriterFor(value).write(value, subContext);
|
||||
}
|
||||
}
|
||||
|
||||
private TableArrayValueWriter() {}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "table-array";
|
||||
}
|
||||
}
|
481
src/main/java/dev/plex/toml/Toml.java
Normal file
481
src/main/java/dev/plex/toml/Toml.java
Normal file
|
@ -0,0 +1,481 @@
|
|||
package dev.plex.toml;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* <p>Provides access to the keys and tables in a TOML data source.</p>
|
||||
*
|
||||
* <p>All getters can fall back to default values if they have been provided as a constructor argument.
|
||||
* Getters for simple values (String, Date, etc.) will return null if no matching key exists.
|
||||
* {@link #getList(String)}, {@link #getTable(String)} and {@link #getTables(String)} return empty values if there is no matching key.</p>
|
||||
*
|
||||
* <p>All read methods throw an {@link IllegalStateException} if the TOML is incorrect.</p>
|
||||
*
|
||||
* <p>Example usage:</p>
|
||||
* <pre><code>
|
||||
* Toml toml = new Toml().read(getTomlFile());
|
||||
* String name = toml.getString("name");
|
||||
* Long port = toml.getLong("server.ip"); // compound key. Is equivalent to:
|
||||
* Long port2 = toml.getTable("server").getLong("ip");
|
||||
* MyConfig config = toml.to(MyConfig.class);
|
||||
* </code></pre>
|
||||
*/
|
||||
public class Toml
|
||||
{
|
||||
|
||||
private static final Gson DEFAULT_GSON = new Gson();
|
||||
|
||||
@Getter
|
||||
private Map<String, Object> values = new HashMap<String, Object>();
|
||||
private final Toml defaults;
|
||||
|
||||
/**
|
||||
* Creates Toml instance with no defaults.
|
||||
*/
|
||||
public Toml()
|
||||
{
|
||||
this(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param defaults fallback values used when the requested key or table is not present in the TOML source that has been read.
|
||||
*/
|
||||
public Toml(Toml defaults)
|
||||
{
|
||||
this(defaults, new HashMap<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the current Toml instance with values from file.
|
||||
*
|
||||
* @param file The File to be read. Expected to be encoded as UTF-8.
|
||||
* @return this instance
|
||||
* @throws IllegalStateException If file contains invalid TOML
|
||||
*/
|
||||
public Toml read(File file)
|
||||
{
|
||||
try
|
||||
{
|
||||
return read(new InputStreamReader(new FileInputStream(file), "UTF8"));
|
||||
} catch (Exception e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the current Toml instance with values from inputStream.
|
||||
*
|
||||
* @param inputStream Closed after it has been read.
|
||||
* @return this instance
|
||||
* @throws IllegalStateException If file contains invalid TOML
|
||||
*/
|
||||
public Toml read(InputStream inputStream)
|
||||
{
|
||||
return read(new InputStreamReader(inputStream));
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the current Toml instance with values from reader.
|
||||
*
|
||||
* @param reader Closed after it has been read.
|
||||
* @return this instance
|
||||
* @throws IllegalStateException If file contains invalid TOML
|
||||
*/
|
||||
public Toml read(Reader reader)
|
||||
{
|
||||
BufferedReader bufferedReader = null;
|
||||
try
|
||||
{
|
||||
bufferedReader = new BufferedReader(reader);
|
||||
|
||||
StringBuilder w = new StringBuilder();
|
||||
String line = bufferedReader.readLine();
|
||||
while (line != null)
|
||||
{
|
||||
w.append(line).append('\n');
|
||||
line = bufferedReader.readLine();
|
||||
}
|
||||
read(w.toString());
|
||||
} catch (IOException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
} finally
|
||||
{
|
||||
try
|
||||
{
|
||||
bufferedReader.close();
|
||||
} catch (IOException e)
|
||||
{
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the current Toml instance with values from otherToml.
|
||||
*
|
||||
* @param otherToml
|
||||
* @return this instance
|
||||
*/
|
||||
public Toml read(Toml otherToml)
|
||||
{
|
||||
this.values = otherToml.values;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the current Toml instance with values from tomlString.
|
||||
*
|
||||
* @param tomlString String to be read.
|
||||
* @return this instance
|
||||
* @throws IllegalStateException If tomlString is not valid TOML
|
||||
*/
|
||||
public Toml read(String tomlString) throws IllegalStateException
|
||||
{
|
||||
dev.plex.toml.Results results = dev.plex.toml.TomlParser.run(tomlString);
|
||||
if (results.errors.hasErrors())
|
||||
{
|
||||
throw new IllegalStateException(results.errors.toString());
|
||||
}
|
||||
|
||||
this.values = results.consume();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getString(String key)
|
||||
{
|
||||
return (String) get(key);
|
||||
}
|
||||
|
||||
public String getString(String key, String defaultValue)
|
||||
{
|
||||
String val = getString(key);
|
||||
return val == null ? defaultValue : val;
|
||||
}
|
||||
|
||||
public Long getLong(String key)
|
||||
{
|
||||
return (Long) get(key);
|
||||
}
|
||||
|
||||
public Long getLong(String key, Long defaultValue)
|
||||
{
|
||||
Long val = getLong(key);
|
||||
return val == null ? defaultValue : val;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param key a TOML key
|
||||
* @param <T> type of list items
|
||||
* @return <code>null</code> if the key is not found
|
||||
*/
|
||||
public <T> List<T> getList(String key)
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
List<T> list = (List<T>) get(key);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param key a TOML key
|
||||
* @param defaultValue a list of default values
|
||||
* @param <T> type of list items
|
||||
* @return <code>null</code> is the key is not found
|
||||
*/
|
||||
public <T> List<T> getList(String key, List<T> defaultValue)
|
||||
{
|
||||
List<T> list = getList(key);
|
||||
|
||||
return list != null ? list : defaultValue;
|
||||
}
|
||||
|
||||
public Boolean getBoolean(String key)
|
||||
{
|
||||
return (Boolean) get(key);
|
||||
}
|
||||
|
||||
public Boolean getBoolean(String key, Boolean defaultValue)
|
||||
{
|
||||
Boolean val = getBoolean(key);
|
||||
return val == null ? defaultValue : val;
|
||||
}
|
||||
|
||||
public Date getDate(String key)
|
||||
{
|
||||
return (Date) get(key);
|
||||
}
|
||||
|
||||
public Date getDate(String key, Date defaultValue)
|
||||
{
|
||||
Date val = getDate(key);
|
||||
return val == null ? defaultValue : val;
|
||||
}
|
||||
|
||||
public Double getDouble(String key)
|
||||
{
|
||||
return (Double) get(key);
|
||||
}
|
||||
|
||||
public Double getDouble(String key, Double defaultValue)
|
||||
{
|
||||
Double val = getDouble(key);
|
||||
return val == null ? defaultValue : val;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param key A table name, not including square brackets.
|
||||
* @return A new Toml instance or <code>null</code> if no value is found for key.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public Toml getTable(String key)
|
||||
{
|
||||
Map<String, Object> map = (Map<String, Object>) get(key);
|
||||
|
||||
return map != null ? new Toml(null, map) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param key Name of array of tables, not including square brackets.
|
||||
* @return A {@link List} of Toml instances or <code>null</code> if no value is found for key.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public List<Toml> getTables(String key)
|
||||
{
|
||||
List<Map<String, Object>> tableArray = (List<Map<String, Object>>) get(key);
|
||||
|
||||
if (tableArray == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
ArrayList<Toml> tables = new ArrayList<Toml>();
|
||||
|
||||
for (Map<String, Object> table : tableArray)
|
||||
{
|
||||
tables.add(new Toml(null, table));
|
||||
}
|
||||
|
||||
return tables;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param key a key name, can be compound (eg. a.b.c)
|
||||
* @return true if key is present
|
||||
*/
|
||||
public boolean contains(String key)
|
||||
{
|
||||
return get(key) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param key a key name, can be compound (eg. a.b.c)
|
||||
* @return true if key is present and is a primitive
|
||||
*/
|
||||
public boolean containsPrimitive(String key)
|
||||
{
|
||||
Object object = get(key);
|
||||
|
||||
return object != null && !(object instanceof Map) && !(object instanceof List);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param key a key name, can be compound (eg. a.b.c)
|
||||
* @return true if key is present and is a table
|
||||
*/
|
||||
public boolean containsTable(String key)
|
||||
{
|
||||
Object object = get(key);
|
||||
|
||||
return object != null && (object instanceof Map);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param key a key name, can be compound (eg. a.b.c)
|
||||
* @return true if key is present and is a table array
|
||||
*/
|
||||
public boolean containsTableArray(String key)
|
||||
{
|
||||
Object object = get(key);
|
||||
|
||||
return object != null && (object instanceof List);
|
||||
}
|
||||
|
||||
public boolean isEmpty()
|
||||
{
|
||||
return values.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Populates an instance of targetClass with the values of this Toml instance.
|
||||
* The target's field names must match keys or tables.
|
||||
* Keys not present in targetClass will be ignored.
|
||||
* </p>
|
||||
*
|
||||
* <p>Tables are recursively converted to custom classes or to {@link Map Map<String, Object>}.</p>
|
||||
*
|
||||
* <p>In addition to straight-forward conversion of TOML primitives, the following are also available:</p>
|
||||
*
|
||||
* <ul>
|
||||
* <li>Integer -> int, long (or wrapper), {@link java.math.BigInteger}</li>
|
||||
* <li>Float -> float, double (or wrapper), {@link java.math.BigDecimal}</li>
|
||||
* <li>One-letter String -> char, {@link Character}</li>
|
||||
* <li>String -> {@link String}, enum, {@link java.net.URI}, {@link java.net.URL}</li>
|
||||
* <li>Multiline and Literal Strings -> {@link String}</li>
|
||||
* <li>Array -> {@link List}, {@link Set}, array. The generic type can be anything that can be converted.</li>
|
||||
* <li>Table -> Custom class, {@link Map Map<String, Object>}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param targetClass Class to deserialize TOML to.
|
||||
* @param <T> type of targetClass.
|
||||
* @return A new instance of targetClass.
|
||||
*/
|
||||
public <T> T to(Class<T> targetClass)
|
||||
{
|
||||
JsonElement json = DEFAULT_GSON.toJsonTree(toMap());
|
||||
|
||||
if (targetClass == JsonElement.class)
|
||||
{
|
||||
return targetClass.cast(json);
|
||||
}
|
||||
|
||||
return DEFAULT_GSON.fromJson(json, targetClass);
|
||||
}
|
||||
|
||||
public Map<String, Object> toMap()
|
||||
{
|
||||
HashMap<String, Object> valuesCopy = new HashMap<String, Object>(values);
|
||||
|
||||
if (defaults != null)
|
||||
{
|
||||
for (Map.Entry<String, Object> entry : defaults.values.entrySet())
|
||||
{
|
||||
if (!valuesCopy.containsKey(entry.getKey()))
|
||||
{
|
||||
valuesCopy.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return valuesCopy;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a {@link Set} of Map.Entry instances. Modifications to the {@link Set} are not reflected in this Toml instance. Entries are immutable, so {@link Map.Entry#setValue(Object)} throws an UnsupportedOperationException.
|
||||
*/
|
||||
public Set<Map.Entry<String, Object>> entrySet()
|
||||
{
|
||||
Set<Map.Entry<String, Object>> entries = new LinkedHashSet<Map.Entry<String, Object>>();
|
||||
|
||||
for (Map.Entry<String, Object> entry : values.entrySet())
|
||||
{
|
||||
Class<? extends Object> entryClass = entry.getValue().getClass();
|
||||
|
||||
if (Map.class.isAssignableFrom(entryClass))
|
||||
{
|
||||
entries.add(new Entry(entry.getKey(), getTable(entry.getKey())));
|
||||
} else if (List.class.isAssignableFrom(entryClass))
|
||||
{
|
||||
List<?> value = (List<?>) entry.getValue();
|
||||
if (!value.isEmpty() && value.get(0) instanceof Map)
|
||||
{
|
||||
entries.add(new Entry(entry.getKey(), getTables(entry.getKey())));
|
||||
} else
|
||||
{
|
||||
entries.add(new Entry(entry.getKey(), value));
|
||||
}
|
||||
} else
|
||||
{
|
||||
entries.add(new Entry(entry.getKey(), entry.getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
private class Entry implements Map.Entry<String, Object>
|
||||
{
|
||||
|
||||
private final String key;
|
||||
private final Object value;
|
||||
|
||||
@Override
|
||||
public String getKey()
|
||||
{
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValue()
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object setValue(Object value)
|
||||
{
|
||||
throw new UnsupportedOperationException("TOML entry values cannot be changed.");
|
||||
}
|
||||
|
||||
private Entry(String key, Object value)
|
||||
{
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public Object get(String key)
|
||||
{
|
||||
if (values.containsKey(key))
|
||||
{
|
||||
return values.get(key);
|
||||
}
|
||||
|
||||
Object current = new HashMap<>(values);
|
||||
|
||||
dev.plex.toml.Keys.Key[] keys = dev.plex.toml.Keys.split(key);
|
||||
|
||||
for (dev.plex.toml.Keys.Key k : keys)
|
||||
{
|
||||
if (k.index == -1 && current instanceof Map && ((Map<String, Object>) current).containsKey(k.path))
|
||||
{
|
||||
return ((Map<String, Object>) current).get(k.path);
|
||||
}
|
||||
|
||||
current = ((Map<String, Object>) current).get(k.name);
|
||||
|
||||
if (k.index > -1 && current != null)
|
||||
{
|
||||
if (k.index >= ((List<?>) current).size())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
current = ((List<?>) current).get(k.index);
|
||||
}
|
||||
|
||||
if (current == null)
|
||||
{
|
||||
return defaults != null ? defaults.get(key) : null;
|
||||
}
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
private Toml(Toml defaults, Map<String, Object> values)
|
||||
{
|
||||
this.values = values;
|
||||
this.defaults = defaults;
|
||||
}
|
||||
}
|
63
src/main/java/dev/plex/toml/TomlParser.java
Normal file
63
src/main/java/dev/plex/toml/TomlParser.java
Normal file
|
@ -0,0 +1,63 @@
|
|||
package dev.plex.toml;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
class TomlParser {
|
||||
|
||||
static dev.plex.toml.Results run(String tomlString) {
|
||||
final dev.plex.toml.Results results = new dev.plex.toml.Results();
|
||||
|
||||
if (tomlString.isEmpty()) {
|
||||
return results;
|
||||
}
|
||||
|
||||
AtomicInteger index = new AtomicInteger();
|
||||
boolean inComment = false;
|
||||
AtomicInteger line = new AtomicInteger(1);
|
||||
dev.plex.toml.Identifier identifier = null;
|
||||
Object value = null;
|
||||
|
||||
for (int i = index.get(); i < tomlString.length(); i = index.incrementAndGet()) {
|
||||
char c = tomlString.charAt(i);
|
||||
|
||||
if (results.errors.hasErrors()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (c == '#' && !inComment) {
|
||||
inComment = true;
|
||||
} else if (!Character.isWhitespace(c) && !inComment && identifier == null) {
|
||||
dev.plex.toml.Identifier id = dev.plex.toml.IdentifierConverter.IDENTIFIER_CONVERTER.convert(tomlString, index, new dev.plex.toml.Context(null, line, results.errors));
|
||||
|
||||
if (id != dev.plex.toml.Identifier.INVALID) {
|
||||
if (id.isKey()) {
|
||||
identifier = id;
|
||||
} else if (id.isTable()) {
|
||||
results.startTables(id, line);
|
||||
} else if (id.isTableArray()) {
|
||||
results.startTableArray(id, line);
|
||||
}
|
||||
}
|
||||
} else if (c == '\n') {
|
||||
inComment = false;
|
||||
identifier = null;
|
||||
value = null;
|
||||
line.incrementAndGet();
|
||||
} else if (!inComment && identifier != null && identifier.isKey() && value == null && !Character.isWhitespace(c)) {
|
||||
value = ValueReaders.VALUE_READERS.convert(tomlString, index, new dev.plex.toml.Context(identifier, line, results.errors));
|
||||
|
||||
if (value instanceof dev.plex.toml.Results.Errors) {
|
||||
results.errors.add((dev.plex.toml.Results.Errors) value);
|
||||
} else {
|
||||
results.addValue(identifier.getName(), value, line);
|
||||
}
|
||||
} else if (value != null && !inComment && !Character.isWhitespace(c)) {
|
||||
results.errors.invalidTextAfterIdentifier(identifier, c, line.get());
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private TomlParser() {}
|
||||
}
|
178
src/main/java/dev/plex/toml/TomlWriter.java
Normal file
178
src/main/java/dev/plex/toml/TomlWriter.java
Normal file
|
@ -0,0 +1,178 @@
|
|||
package dev.plex.toml;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import static dev.plex.toml.ValueWriters.WRITERS;
|
||||
|
||||
/**
|
||||
* <p>Converts Objects to TOML</p>
|
||||
*
|
||||
* <p>An input Object can comprise arbitrarily nested combinations of Java primitive types,
|
||||
* other {@link Object}s, {@link Map}s, {@link List}s, and Arrays. {@link Object}s and {@link Map}s
|
||||
* are output to TOML tables, and {@link List}s and Array to TOML arrays.</p>
|
||||
*
|
||||
* <p>Example usage:</p>
|
||||
* <pre><code>
|
||||
* class AClass {
|
||||
* int anInt = 1;
|
||||
* int[] anArray = { 2, 3 };
|
||||
* }
|
||||
*
|
||||
* String tomlString = new TomlWriter().write(new AClass());
|
||||
* </code></pre>
|
||||
*/
|
||||
public class TomlWriter
|
||||
{
|
||||
|
||||
public static class Builder
|
||||
{
|
||||
private int keyIndentation;
|
||||
private int tableIndentation;
|
||||
private int arrayDelimiterPadding = 0;
|
||||
private TimeZone timeZone = TimeZone.getTimeZone("UTC");
|
||||
private boolean showFractionalSeconds = false;
|
||||
|
||||
public Builder indentValuesBy(int spaces)
|
||||
{
|
||||
this.keyIndentation = spaces;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder indentTablesBy(int spaces)
|
||||
{
|
||||
this.tableIndentation = spaces;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder timeZone(TimeZone timeZone)
|
||||
{
|
||||
this.timeZone = timeZone;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param spaces number of spaces to put between opening square bracket and first item and between closing square bracket and last item
|
||||
* @return this TomlWriter.Builder instance
|
||||
*/
|
||||
public Builder padArrayDelimitersBy(int spaces)
|
||||
{
|
||||
this.arrayDelimiterPadding = spaces;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public TomlWriter build()
|
||||
{
|
||||
return new TomlWriter(keyIndentation, tableIndentation, arrayDelimiterPadding, timeZone, showFractionalSeconds);
|
||||
}
|
||||
|
||||
public Builder showFractionalSeconds()
|
||||
{
|
||||
this.showFractionalSeconds = true;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
private final IndentationPolicy indentationPolicy;
|
||||
private final dev.plex.toml.DatePolicy datePolicy;
|
||||
|
||||
/**
|
||||
* Creates a TomlWriter instance.
|
||||
*/
|
||||
public TomlWriter()
|
||||
{
|
||||
this(0, 0, 0, TimeZone.getTimeZone("UTC"), false);
|
||||
}
|
||||
|
||||
private TomlWriter(int keyIndentation, int tableIndentation, int arrayDelimiterPadding, TimeZone timeZone, boolean showFractionalSeconds)
|
||||
{
|
||||
this.indentationPolicy = new IndentationPolicy(keyIndentation, tableIndentation, arrayDelimiterPadding);
|
||||
this.datePolicy = new dev.plex.toml.DatePolicy(timeZone, showFractionalSeconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write an Object into TOML String.
|
||||
*
|
||||
* @param from the object to be written
|
||||
* @return a string containing the TOML representation of the given Object
|
||||
*/
|
||||
public String write(Object from)
|
||||
{
|
||||
try
|
||||
{
|
||||
StringWriter output = new StringWriter();
|
||||
write(from, output, null);
|
||||
|
||||
return output.toString();
|
||||
} catch (IOException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write an Object in TOML to a {@link File}. Output is encoded as UTF-8.
|
||||
*
|
||||
* @param from the object to be written
|
||||
* @param target the File to which the TOML will be written
|
||||
* @throws IOException if any file operations fail
|
||||
*/
|
||||
public void write(Object from, File target) throws IOException
|
||||
{
|
||||
OutputStream outputStream = new FileOutputStream(target, true);
|
||||
try
|
||||
{
|
||||
write(from, outputStream, target);
|
||||
} finally
|
||||
{
|
||||
outputStream.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write an Object in TOML to a {@link OutputStream}. Output is encoded as UTF-8.
|
||||
*
|
||||
* @param from the object to be written
|
||||
* @param target the OutputStream to which the TOML will be written. The stream is NOT closed after being written to.
|
||||
* @throws IOException if target.write() fails
|
||||
*/
|
||||
public void write(Object from, OutputStream target, @Nullable File file) throws IOException
|
||||
{
|
||||
OutputStreamWriter writer = new OutputStreamWriter(target, "UTF-8");
|
||||
write(from, writer, file);
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Write an Object in TOML to a {@link Writer}. You MUST ensure that the {@link Writer}s's encoding is set to UTF-8 for the TOML to be valid.
|
||||
*
|
||||
* @param from the object to be written. Can be a Map or a custom type. Must not be null.
|
||||
* @param target the Writer to which TOML will be written. The Writer is not closed.
|
||||
* @throws IOException if target.write() fails
|
||||
* @throws IllegalArgumentException if from is of an invalid type
|
||||
*/
|
||||
public void write(Object from, Writer target, @Nullable File file) throws IOException
|
||||
{
|
||||
dev.plex.toml.ValueWriter valueWriter = WRITERS.findWriterFor(from);
|
||||
if (valueWriter == MapValueWriter.MAP_VALUE_WRITER || valueWriter == dev.plex.toml.ObjectValueWriter.OBJECT_VALUE_WRITER)
|
||||
{
|
||||
WriterContext context = new WriterContext(indentationPolicy, datePolicy, target);
|
||||
if (file != null && file.exists())
|
||||
{
|
||||
context.file = file;
|
||||
}
|
||||
valueWriter.write(from, context);
|
||||
} else
|
||||
{
|
||||
throw new IllegalArgumentException("An object of class " + from.getClass().getSimpleName() + " cannot produce valid TOML. Please pass in a Map or a custom type.");
|
||||
}
|
||||
}
|
||||
}
|
21
src/main/java/dev/plex/toml/ValueReader.java
Normal file
21
src/main/java/dev/plex/toml/ValueReader.java
Normal file
|
@ -0,0 +1,21 @@
|
|||
package dev.plex.toml;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
interface ValueReader {
|
||||
|
||||
/**
|
||||
* @param s must already have been trimmed
|
||||
*/
|
||||
boolean canRead(String s);
|
||||
|
||||
/**
|
||||
* Partial validation. Stops after type terminator, rather than at EOI.
|
||||
*
|
||||
* @param s must already have been validated by {@link #canRead(String)}
|
||||
* @param index where to start in s
|
||||
* @param line current line number, used for error reporting
|
||||
* @return a value or a {@link dev.plex.toml.Results.Errors}
|
||||
*/
|
||||
Object read(String s, AtomicInteger index, dev.plex.toml.Context context);
|
||||
}
|
35
src/main/java/dev/plex/toml/ValueReaders.java
Normal file
35
src/main/java/dev/plex/toml/ValueReaders.java
Normal file
|
@ -0,0 +1,35 @@
|
|||
package dev.plex.toml;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static dev.plex.toml.ArrayValueReader.ARRAY_VALUE_READER;
|
||||
import static dev.plex.toml.BooleanValueReaderWriter.BOOLEAN_VALUE_READER_WRITER;
|
||||
import static dev.plex.toml.DateValueReaderWriter.DATE_VALUE_READER_WRITER;
|
||||
import static dev.plex.toml.LiteralStringValueReader.LITERAL_STRING_VALUE_READER;
|
||||
import static dev.plex.toml.MultilineLiteralStringValueReader.MULTILINE_LITERAL_STRING_VALUE_READER;
|
||||
import static dev.plex.toml.MultilineStringValueReader.MULTILINE_STRING_VALUE_READER;
|
||||
import static dev.plex.toml.StringValueReaderWriter.STRING_VALUE_READER_WRITER;
|
||||
|
||||
class ValueReaders {
|
||||
|
||||
static final ValueReaders VALUE_READERS = new ValueReaders();
|
||||
|
||||
Object convert(String value, AtomicInteger index, dev.plex.toml.Context context) {
|
||||
String substring = value.substring(index.get());
|
||||
for (dev.plex.toml.ValueReader valueParser : READERS) {
|
||||
if (valueParser.canRead(substring)) {
|
||||
return valueParser.read(value, index, context);
|
||||
}
|
||||
}
|
||||
|
||||
dev.plex.toml.Results.Errors errors = new dev.plex.toml.Results.Errors();
|
||||
errors.invalidValue(context.identifier.getName(), substring, context.line.get());
|
||||
return errors;
|
||||
}
|
||||
|
||||
private ValueReaders() {}
|
||||
|
||||
private static final dev.plex.toml.ValueReader[] READERS = {
|
||||
MULTILINE_STRING_VALUE_READER, MULTILINE_LITERAL_STRING_VALUE_READER, LITERAL_STRING_VALUE_READER, STRING_VALUE_READER_WRITER, DATE_VALUE_READER_WRITER, NumberValueReaderWriter.NUMBER_VALUE_READER_WRITER, BOOLEAN_VALUE_READER_WRITER, ARRAY_VALUE_READER, InlineTableValueReader.INLINE_TABLE_VALUE_READER
|
||||
};
|
||||
}
|
9
src/main/java/dev/plex/toml/ValueWriter.java
Normal file
9
src/main/java/dev/plex/toml/ValueWriter.java
Normal file
|
@ -0,0 +1,9 @@
|
|||
package dev.plex.toml;
|
||||
|
||||
interface ValueWriter {
|
||||
boolean canWrite(Object value);
|
||||
|
||||
void write(Object value, WriterContext context);
|
||||
|
||||
boolean isPrimitiveType();
|
||||
}
|
28
src/main/java/dev/plex/toml/ValueWriters.java
Normal file
28
src/main/java/dev/plex/toml/ValueWriters.java
Normal file
|
@ -0,0 +1,28 @@
|
|||
package dev.plex.toml;
|
||||
|
||||
class ValueWriters {
|
||||
|
||||
static final ValueWriters WRITERS = new ValueWriters();
|
||||
|
||||
ValueWriter findWriterFor(Object value) {
|
||||
for (ValueWriter valueWriter : VALUE_WRITERS) {
|
||||
if (valueWriter.canWrite(value)) {
|
||||
return valueWriter;
|
||||
}
|
||||
}
|
||||
|
||||
return ObjectValueWriter.OBJECT_VALUE_WRITER;
|
||||
}
|
||||
|
||||
private ValueWriters() {}
|
||||
|
||||
private static dev.plex.toml.DateValueReaderWriter getPlatformSpecificDateConverter() {
|
||||
String specificationVersion = Runtime.class.getPackage().getSpecificationVersion();
|
||||
return specificationVersion != null && specificationVersion.startsWith("1.6") ? dev.plex.toml.DateValueReaderWriter.DATE_PARSER_JDK_6 : dev.plex.toml.DateValueReaderWriter.DATE_VALUE_READER_WRITER;
|
||||
}
|
||||
|
||||
private static final ValueWriter[] VALUE_WRITERS = {
|
||||
StringValueReaderWriter.STRING_VALUE_READER_WRITER, NumberValueReaderWriter.NUMBER_VALUE_READER_WRITER, dev.plex.toml.BooleanValueReaderWriter.BOOLEAN_VALUE_READER_WRITER, getPlatformSpecificDateConverter(),
|
||||
MapValueWriter.MAP_VALUE_WRITER, dev.plex.toml.PrimitiveArrayValueWriter.PRIMITIVE_ARRAY_VALUE_WRITER, TableArrayValueWriter.TABLE_ARRAY_VALUE_WRITER
|
||||
};
|
||||
}
|
182
src/main/java/dev/plex/toml/WriterContext.java
Normal file
182
src/main/java/dev/plex/toml/WriterContext.java
Normal file
|
@ -0,0 +1,182 @@
|
|||
package dev.plex.toml;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.util.Arrays;
|
||||
|
||||
class WriterContext
|
||||
{
|
||||
private String arrayKey = null;
|
||||
private boolean isArrayOfTable = false;
|
||||
private boolean empty = true;
|
||||
public final String key;
|
||||
private final String currentTableIndent;
|
||||
private final String currentFieldIndent;
|
||||
private final Writer output;
|
||||
private final dev.plex.toml.IndentationPolicy indentationPolicy;
|
||||
private final dev.plex.toml.DatePolicy datePolicy;
|
||||
|
||||
public File file;
|
||||
public String parentName;
|
||||
public boolean hasRun = false;
|
||||
|
||||
WriterContext(dev.plex.toml.IndentationPolicy indentationPolicy, dev.plex.toml.DatePolicy datePolicy, Writer output)
|
||||
{
|
||||
this("", "", output, indentationPolicy, datePolicy);
|
||||
}
|
||||
|
||||
WriterContext pushTable(String newKey)
|
||||
{
|
||||
String newIndent = "";
|
||||
if (!key.isEmpty())
|
||||
{
|
||||
newIndent = growIndent(indentationPolicy);
|
||||
}
|
||||
|
||||
String fullKey = key.isEmpty() ? newKey : key + "." + newKey;
|
||||
|
||||
WriterContext subContext = new WriterContext(fullKey, newIndent, output, indentationPolicy, datePolicy);
|
||||
if (!empty)
|
||||
{
|
||||
subContext.empty = false;
|
||||
}
|
||||
|
||||
return subContext;
|
||||
}
|
||||
|
||||
WriterContext pushTableFromArray()
|
||||
{
|
||||
WriterContext subContext = new WriterContext(key, currentTableIndent, output, indentationPolicy, datePolicy);
|
||||
if (!empty)
|
||||
{
|
||||
subContext.empty = false;
|
||||
}
|
||||
subContext.setIsArrayOfTable(true);
|
||||
|
||||
return subContext;
|
||||
}
|
||||
|
||||
WriterContext write(String s)
|
||||
{
|
||||
try
|
||||
{
|
||||
output.write(s);
|
||||
if (empty && !s.isEmpty())
|
||||
{
|
||||
empty = false;
|
||||
}
|
||||
|
||||
return this;
|
||||
} catch (IOException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
void write(char[] chars)
|
||||
{
|
||||
for (char c : chars)
|
||||
{
|
||||
write(c);
|
||||
}
|
||||
}
|
||||
|
||||
WriterContext write(char c)
|
||||
{
|
||||
try
|
||||
{
|
||||
output.write(c);
|
||||
empty = false;
|
||||
|
||||
return this;
|
||||
} catch (IOException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
void writeKey()
|
||||
{
|
||||
if (key.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!empty)
|
||||
{
|
||||
write('\n');
|
||||
}
|
||||
|
||||
write(currentTableIndent);
|
||||
|
||||
if (isArrayOfTable)
|
||||
{
|
||||
write("[[").write(key).write("]]\n");
|
||||
} else
|
||||
{
|
||||
write('[').write(key).write("]\n");
|
||||
}
|
||||
}
|
||||
|
||||
void writeArrayDelimiterPadding()
|
||||
{
|
||||
for (int i = 0; i < indentationPolicy.getArrayDelimiterPadding(); i++)
|
||||
{
|
||||
write(' ');
|
||||
}
|
||||
}
|
||||
|
||||
void indent()
|
||||
{
|
||||
if (!key.isEmpty())
|
||||
{
|
||||
write(currentFieldIndent);
|
||||
}
|
||||
}
|
||||
|
||||
dev.plex.toml.DatePolicy getDatePolicy()
|
||||
{
|
||||
return datePolicy;
|
||||
}
|
||||
|
||||
WriterContext setIsArrayOfTable(boolean isArrayOfTable)
|
||||
{
|
||||
this.isArrayOfTable = isArrayOfTable;
|
||||
return this;
|
||||
}
|
||||
|
||||
WriterContext setArrayKey(String arrayKey)
|
||||
{
|
||||
this.arrayKey = arrayKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
String getContextPath()
|
||||
{
|
||||
return key.isEmpty() ? arrayKey : key + "." + arrayKey;
|
||||
}
|
||||
|
||||
private String growIndent(dev.plex.toml.IndentationPolicy indentationPolicy)
|
||||
{
|
||||
return currentTableIndent + fillStringWithSpaces(indentationPolicy.getTableIndent());
|
||||
}
|
||||
|
||||
private String fillStringWithSpaces(int count)
|
||||
{
|
||||
char[] chars = new char[count];
|
||||
Arrays.fill(chars, ' ');
|
||||
|
||||
return new String(chars);
|
||||
}
|
||||
|
||||
private WriterContext(String key, String tableIndent, Writer output, dev.plex.toml.IndentationPolicy indentationPolicy, dev.plex.toml.DatePolicy datePolicy)
|
||||
{
|
||||
this.key = key;
|
||||
this.output = output;
|
||||
this.indentationPolicy = indentationPolicy;
|
||||
this.currentTableIndent = tableIndent;
|
||||
this.datePolicy = datePolicy;
|
||||
this.currentFieldIndent = tableIndent + fillStringWithSpaces(this.indentationPolicy.getKeyValueIndent());
|
||||
}
|
||||
}
|
|
@ -1,91 +0,0 @@
|
|||
package com.moandjiezana.toml;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.Map;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class RealWorldTest {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void should_parse_example() throws Exception {
|
||||
Toml toml = new Toml().parse(new File(getClass().getResource("example.toml").getFile()));
|
||||
|
||||
// printMap(root);
|
||||
|
||||
assertEquals("TOML Example", toml.getString("title"));
|
||||
|
||||
Toml owner = toml.getKeyGroup("owner");
|
||||
assertEquals("Tom Preston-Werner", owner.getString("name"));
|
||||
assertEquals("GitHub", owner.getString("organization"));
|
||||
assertEquals("GitHub Cofounder & CEO\nLikes tater tots and beer.", owner.getString("bio"));
|
||||
|
||||
Calendar dob = Calendar.getInstance();
|
||||
dob.set(1979, Calendar.MAY, 27, 7, 32, 0);
|
||||
dob.set(Calendar.MILLISECOND, 0);
|
||||
dob.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
assertEquals(dob.getTime(), owner.getDate("dob"));
|
||||
|
||||
Toml database = toml.getKeyGroup("database");
|
||||
assertEquals("192.168.1.1", database.getString("server"));
|
||||
assertEquals(5000L, database.getLong("connection_max").longValue());
|
||||
assertTrue(database.getBoolean("enabled"));
|
||||
assertEquals(Arrays.asList(8001L, 8001L, 8002L), database.getList("ports", Long.class));
|
||||
|
||||
Toml servers = toml.getKeyGroup("servers");
|
||||
Toml alphaServers = servers.getKeyGroup("alpha");
|
||||
assertEquals("10.0.0.1", alphaServers.getString("ip"));
|
||||
assertEquals("eqdc10", alphaServers.getString("dc"));
|
||||
Toml betaServers = servers.getKeyGroup("beta");
|
||||
assertEquals("10.0.0.2", betaServers.getString("ip"));
|
||||
assertEquals("eqdc10", betaServers.getString("dc"));
|
||||
|
||||
Toml clients = toml.getKeyGroup("clients");
|
||||
assertEquals(asList(asList("gamma", "delta"), asList(1L, 2L)), clients.getList("data", String.class));
|
||||
assertEquals(asList("alpha", "omega"), clients.getList("hosts", String.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_parse_hard_example() throws Exception {
|
||||
Toml toml = new Toml().parse(new File(getClass().getResource("hard_example.toml").getFile()));
|
||||
|
||||
assertEquals("You'll hate me after this - #", toml.getString("the.test_string"));
|
||||
assertEquals(asList("] ", " # "), toml.getList("the.hard.test_array", String.class));
|
||||
assertEquals(asList("Test #11 ]proved that", "Experiment #9 was a success"), toml.getList("the.hard.test_array2", String.class));
|
||||
assertEquals(" Same thing, but with a string #", toml.getString("the.hard.another_test_string"));
|
||||
assertEquals(" And when \"'s are in the string, along with # \"", toml.getString("the.hard.harder_test_string"));
|
||||
Toml theHardBit = toml.getKeyGroup("the.hard.bit#");
|
||||
assertEquals("You don't think some user won't do that?", theHardBit.getString("what?"));
|
||||
assertEquals(asList("]"), theHardBit.getList("multi_line_array", String.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_allow_keys_with_same_name_in_different_groups() throws Exception {
|
||||
Toml toml = new Toml().parse(new File(getClass().getResource("should_allow_keys_with_same_name_in_different_groups.toml").getFile()));
|
||||
|
||||
assertTrue(toml.getKeyGroup("siteInfo.local.sh").getBoolean("enable"));
|
||||
assertFalse(toml.getKeyGroup("siteInfo.localMobile.sh").getBoolean("enable"));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void printMap(Map<String, Object> map) {
|
||||
for (Map.Entry<String, Object> entry : map.entrySet()) {
|
||||
if (entry.getValue() instanceof Map) {
|
||||
System.out.println("[" + entry.getKey() + "]");
|
||||
printMap((Map<String, Object>) entry.getValue());
|
||||
System.out.println("[/" + entry.getKey() + "]");
|
||||
} else {
|
||||
System.out.println(entry.getKey() + " = " + entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
package com.moandjiezana.toml;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
|
||||
public class TomlDefaultsTest {
|
||||
|
||||
private Toml defaultToml;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
defaultToml = new Toml().parse("a = \"a\"\n[group]\na=\"a\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_fall_back_to_default_value() {
|
||||
Toml toml = new Toml(defaultToml);
|
||||
|
||||
assertEquals("a", toml.getString("a"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_use_value_when_present_in_values_and_defaults() {
|
||||
Toml toml = new Toml(defaultToml).parse("a = \"b\"");
|
||||
|
||||
assertEquals("b", toml.getString("a"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_return_null_when_no_defaults_for_key() throws Exception {
|
||||
Toml toml = new Toml(defaultToml).parse("");
|
||||
|
||||
assertNull(toml.getString("b"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_fall_back_to_default_with_multi_key() throws Exception {
|
||||
Toml toml = new Toml(defaultToml).parse("");
|
||||
|
||||
assertEquals("a", toml.getString("group.a"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_fall_back_to_key_group() throws Exception {
|
||||
Toml toml = new Toml(defaultToml).parse("");
|
||||
|
||||
assertEquals("a", toml.getKeyGroup("group").getString("a"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_fall_back_to_key_within_key_group() throws Exception {
|
||||
Toml toml = new Toml(defaultToml).parse("[group]\nb=1");
|
||||
|
||||
assertEquals(1, toml.getKeyGroup("group").getLong("b").intValue());
|
||||
assertEquals("a", toml.getKeyGroup("group").getString("a"));
|
||||
}
|
||||
}
|
|
@ -1,251 +0,0 @@
|
|||
package com.moandjiezana.toml;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Calendar;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
public class TomlTest {
|
||||
|
||||
@Test
|
||||
public void should_get_string() throws Exception {
|
||||
Toml toml = new Toml().parse("a = \"a\"");
|
||||
|
||||
assertEquals("a", toml.getString("a"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_get_number() throws Exception {
|
||||
Toml toml = new Toml().parse("b = 1001");
|
||||
|
||||
assertEquals(1001, toml.getLong("b").intValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_get_negative_number() throws Exception {
|
||||
Toml toml = new Toml().parse("b = -1001");
|
||||
|
||||
assertEquals(-1001, toml.getLong("b").intValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_get_list() throws Exception {
|
||||
Toml toml = new Toml().parse("list = [\"a\", \"b\", \"c\"]");
|
||||
|
||||
assertEquals(asList("a", "b", "c"), toml.getList("list", String.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_get_boolean() throws Exception {
|
||||
Toml toml = new Toml().parse("bool_false = false\nbool_true = true");
|
||||
|
||||
assertFalse(toml.getBoolean("bool_false"));
|
||||
assertTrue(toml.getBoolean("bool_true"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_get_date() throws Exception {
|
||||
Toml toml = new Toml().parse("a_date = 2011-11-10T13:12:00Z");
|
||||
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.set(2011, Calendar.NOVEMBER, 10, 13, 12, 00);
|
||||
calendar.set(Calendar.MILLISECOND, 0);
|
||||
calendar.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
|
||||
assertEquals(calendar.getTime(), toml.getDate("a_date"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_get_double() throws Exception {
|
||||
Toml toml = new Toml().parse("double = 5.25");
|
||||
|
||||
assertEquals(5.25D, toml.getDouble("double").doubleValue(), 0.0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_get_negative_double() throws Exception {
|
||||
Toml toml = new Toml().parse("double = -5.25");
|
||||
|
||||
assertEquals(-5.25D, toml.getDouble("double").doubleValue(), 0.0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_get_key_group() throws Exception {
|
||||
Toml toml = new Toml().parse("[group]\nkey = \"value\"");
|
||||
|
||||
Toml group = toml.getKeyGroup("group");
|
||||
|
||||
assertEquals("value", group.getString("key"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_get_value_for_multi_key() throws Exception {
|
||||
Toml toml = new Toml().parse("[group]\nkey = \"value\"");
|
||||
|
||||
assertEquals("value", toml.getString("group.key"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_get_value_for_multi_key_with_no_parent_key_group() throws Exception {
|
||||
Toml toml = new Toml().parse("[group.sub]\nkey = \"value\"");
|
||||
|
||||
assertEquals("value", toml.getString("group.sub.key"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_get_key_group_for_multi_key() throws Exception {
|
||||
Toml toml = new Toml().parse("[group]\nother=1\n[group.sub]\nkey = \"value\"");
|
||||
|
||||
assertEquals("value", toml.getKeyGroup("group.sub").getString("key"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_get_key_group_for_multi_key_with_no_parent_key_group() throws Exception {
|
||||
Toml toml = new Toml().parse("[group.sub]\nkey = \"value\"");
|
||||
|
||||
assertEquals("value", toml.getKeyGroup("group.sub").getString("key"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_get_value_from_key_group_with_sub_key_group() throws Exception {
|
||||
Toml toml = new Toml().parse("[a.b]\nc=1\n[a]\nd=2");
|
||||
|
||||
assertEquals(2, toml.getLong("a.d").intValue());
|
||||
assertEquals(1, toml.getKeyGroup("a.b").getLong("c").intValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_return_null_if_no_value_for_key() throws Exception {
|
||||
Toml toml = new Toml().parse("");
|
||||
|
||||
assertNull(toml.getString("a"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_return_null_when_no_value_for_multi_key() throws Exception {
|
||||
Toml toml = new Toml().parse("");
|
||||
|
||||
assertNull(toml.getString("group.key"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_load_from_file() throws Exception {
|
||||
Toml toml = new Toml().parse(new File(getClass().getResource("should_load_from_file.toml").getFile()));
|
||||
|
||||
assertEquals("value", toml.getString("key"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_support_numbers_in_key_names() throws Exception {
|
||||
Toml toml = new Toml().parse("a1 = 1");
|
||||
|
||||
assertEquals(1, toml.getLong("a1").intValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_support_numbers_in_key_group_names() throws Exception {
|
||||
Toml toml = new Toml().parse("[group1]\na = 1");
|
||||
|
||||
assertEquals(1, toml.getLong("group1.a").intValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_support_underscores_in_key_names() throws Exception {
|
||||
Toml toml = new Toml().parse("a_a = 1");
|
||||
|
||||
assertEquals(1, toml.getLong("a_a").intValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_support_question_marks_in_key_names() throws Exception {
|
||||
Toml toml = new Toml().parse("key?=true");
|
||||
|
||||
assertTrue(toml.getBoolean("key?"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_support_underscores_in_key_group_names() throws Exception {
|
||||
Toml toml = new Toml().parse("[group_a]\na = 1");
|
||||
|
||||
assertEquals(1, toml.getLong("group_a.a").intValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_support_sharp_sign_in_key_group_names() throws Exception {
|
||||
Toml toml = new Toml().parse("[group#]\nkey=1");
|
||||
|
||||
assertEquals(1, toml.getLong("group#.key").intValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_support_blank_lines() throws Exception {
|
||||
Toml toml = new Toml().parse(new File(getClass().getResource("should_support_blank_line.toml").getFile()));
|
||||
|
||||
assertEquals(1, toml.getLong("group.key").intValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_support_special_characters_in_strings() {
|
||||
Toml toml = new Toml().parse(new File(getClass().getResource("should_support_special_characters_in_strings.toml").getFile()));
|
||||
|
||||
assertEquals("\" \t \n \r \\ / \b \f", toml.getString("key"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_support_unicode_characters_in_strings() throws Exception {
|
||||
Toml toml = new Toml().parse("key=\"\\u00B1\"");
|
||||
|
||||
assertEquals("±", toml.getString("key"));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void should_fail_on_reserved_special_character_in_strings() throws Exception {
|
||||
new Toml().parse("key=\"\\m\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_ignore_comma_at_end_of_array() throws Exception {
|
||||
Toml toml = new Toml().parse("key=[1,2,3,]");
|
||||
|
||||
assertEquals(asList(1L, 2L, 3L), toml.getList("key", Long.class));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void should_fail_when_dot_in_key_name() throws Exception {
|
||||
new Toml().parse("a.a = 1");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void should_fail_on_invalid_date() throws Exception {
|
||||
new Toml().parse("d = 2012-13-01T15:00:00Z");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void should_fail_when_key_is_overwritten_by_key_group() {
|
||||
new Toml().parse("[a]\nb=1\n[a.b]\nc=2");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void should_fail_when_key_is_overwritten_by_another_key() {
|
||||
new Toml().parse("[fruit]\ntype=\"apple\"\ntype=\"orange\"");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void should_fail_when_key_group_defined_twice() throws Exception {
|
||||
new Toml().parse("[a]\nb=1\n[a]\nc=2");
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void should_fail_when_illegal_characters_after_key_group() throws Exception {
|
||||
new Toml().parse("[error] if you didn't catch this, your parser is broken");
|
||||
}
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
package com.moandjiezana.toml;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import com.moandjiezana.toml.testutils.KeyGroupAsMap;
|
||||
import com.moandjiezana.toml.testutils.TomlKeyGroups;
|
||||
import com.moandjiezana.toml.testutils.TomlPrimitives;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Calendar;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class TomlToClassTest {
|
||||
|
||||
@Test
|
||||
public void should_convert_primitive_values() throws Exception {
|
||||
Toml toml = new Toml().parse(file("should_convert_primitive_values.toml"));
|
||||
|
||||
TomlPrimitives values = toml.to(TomlPrimitives.class);
|
||||
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.set(1979, Calendar.MAY, 27, 7, 32, 00);
|
||||
calendar.set(Calendar.MILLISECOND, 0);
|
||||
calendar.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
|
||||
assertEquals("string", values.string);
|
||||
assertEquals((Long) 123L, values.number);
|
||||
assertEquals(2.1, values.decimal, 0);
|
||||
assertTrue(values.bool);
|
||||
assertEquals(calendar.getTime(), values.date);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_convert_key_groups() throws Exception {
|
||||
String fileName = "should_convert_key_groups.toml";
|
||||
Toml toml = new Toml().parse(file(fileName));
|
||||
|
||||
TomlKeyGroups tomlKeyGroups = toml.to(TomlKeyGroups.class);
|
||||
|
||||
assertEquals("value1", tomlKeyGroups.group1.string);
|
||||
assertEquals("value2", tomlKeyGroups.group2.string);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_use_defaults() throws Exception {
|
||||
Toml defaults = new Toml().parse(file("should_convert_key_groups.toml"));
|
||||
Toml toml = new Toml(defaults).parse("");
|
||||
|
||||
TomlKeyGroups tomlKeyGroups = toml.to(TomlKeyGroups.class);
|
||||
|
||||
assertEquals("value1", tomlKeyGroups.group1.string);
|
||||
assertEquals("value2", tomlKeyGroups.group2.string);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_ignore_keys_not_in_class() throws Exception {
|
||||
TomlPrimitives tomlPrimitives = new Toml().parse("a=1\nstring=\"s\"").to(TomlPrimitives.class);
|
||||
|
||||
assertEquals("s", tomlPrimitives.string);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_convert_key_group_as_map() throws Exception {
|
||||
KeyGroupAsMap keyGroupAsMap = new Toml().parse("[group]\nkey=\"value\"").to(KeyGroupAsMap.class);
|
||||
|
||||
assertEquals("value", keyGroupAsMap.group.get("key"));
|
||||
}
|
||||
|
||||
private File file(String fileName) {
|
||||
return new File(getClass().getResource(fileName).getFile());
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
package com.moandjiezana.toml.testutils;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class KeyGroupAsMap {
|
||||
|
||||
public Map<String, Object> group;
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
package com.moandjiezana.toml.testutils;
|
||||
|
||||
|
||||
public class TomlKeyGroups {
|
||||
|
||||
public TomlPrimitives group1;
|
||||
public TomlPrimitives group2;
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
package com.moandjiezana.toml.testutils;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public class TomlPrimitives {
|
||||
|
||||
public String string;
|
||||
public Long number;
|
||||
public Double decimal;
|
||||
public Boolean bool;
|
||||
public Date date;
|
||||
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
# This is a TOML document. Boom.
|
||||
|
||||
title = "TOML Example"
|
||||
|
||||
[owner]
|
||||
name = "Tom Preston-Werner"
|
||||
organization = "GitHub"
|
||||
bio = "GitHub Cofounder & CEO\nLikes tater tots and beer."
|
||||
dob = 1979-05-27T07:32:00Z # First class dates? Why not?
|
||||
|
||||
[database]
|
||||
server = "192.168.1.1"
|
||||
ports = [ 8001, 8001, 8002 ]
|
||||
connection_max = 5000
|
||||
enabled = true
|
||||
|
||||
[servers]
|
||||
|
||||
# You can indent as you please. Tabs or spaces. TOML don't care.
|
||||
[servers.alpha]
|
||||
ip = "10.0.0.1"
|
||||
dc = "eqdc10"
|
||||
|
||||
[servers.beta]
|
||||
ip = "10.0.0.2"
|
||||
dc = "eqdc10"
|
||||
|
||||
[clients]
|
||||
data = [ ["gamma", "delta"], [1, 2] ] # just an update to make sure parsers support it
|
||||
|
||||
# Line breaks are OK when inside arrays
|
||||
hosts = [
|
||||
"alpha",
|
||||
"omega"
|
||||
]
|
|
@ -1,33 +0,0 @@
|
|||
# Test file for TOML
|
||||
# Only this one tries to emulate a TOML file written by a user of the kind of parser writers probably hate
|
||||
# This part you'll really hate
|
||||
|
||||
[the]
|
||||
test_string = "You'll hate me after this - #" # " Annoying, isn't it?
|
||||
|
||||
[the.hard]
|
||||
test_array = [ "] ", " # "] # ] There you go, parse this!
|
||||
test_array2 = [ "Test #11 ]proved that", "Experiment #9 was a success" ]
|
||||
# You didn't think it'd as easy as chucking out the last #, did you?
|
||||
another_test_string = " Same thing, but with a string #"
|
||||
harder_test_string = " And when \"'s are in the string, along with # \"" # "and comments are there too"
|
||||
# Things will get harder
|
||||
|
||||
[the.hard.bit#]
|
||||
what? = "You don't think some user won't do that?"
|
||||
multi_line_array = [
|
||||
"]",
|
||||
# ] Oh yes I did
|
||||
]
|
||||
|
||||
# Each of the following keygroups/key value pairs should produce an error. Uncomment to them to test
|
||||
|
||||
#[error] if you didn't catch this, your parser is broken
|
||||
#string = "Anything other than tabs, spaces and newline after a keygroup or key value pair has ended should produce an error unless it is a comment" like this
|
||||
#array = [
|
||||
# "This might most likely happen in multiline arrays",
|
||||
# Like here,
|
||||
# "or here,
|
||||
# and here"
|
||||
# ] End of array comment, forgot the #
|
||||
#number = 3.14 pi <--again forgot the #
|
|
@ -1,11 +0,0 @@
|
|||
# Each of the following keygroups/key value pairs should produce an error. Uncomment to them to test
|
||||
|
||||
[error] if you didn't catch this, your parser is broken
|
||||
#string = "Anything other than tabs, spaces and newline after a keygroup or key value pair has ended should produce an error unless it is a comment" like this
|
||||
#array = [
|
||||
# "This might most likely happen in multiline arrays",
|
||||
# Like here,
|
||||
# "or here,
|
||||
# and here"
|
||||
# ] End of array comment, forgot the #
|
||||
#number = 3.14 pi <--again forgot the #
|
|
@ -1,4 +0,0 @@
|
|||
[siteInfo.local.sh]
|
||||
enable=true
|
||||
[siteInfo.localMobile.sh]
|
||||
enable=false
|
|
@ -1,5 +0,0 @@
|
|||
[group1]
|
||||
string = "value1"
|
||||
|
||||
[group2]
|
||||
string = "value2"
|
|
@ -1,5 +0,0 @@
|
|||
string = "string"
|
||||
number = 123
|
||||
decimal = 2.1
|
||||
bool = true
|
||||
date = 1979-05-27T07:32:00Z
|
|
@ -1 +0,0 @@
|
|||
key = "value"
|
|
@ -1,5 +0,0 @@
|
|||
# Comment
|
||||
|
||||
[group]
|
||||
|
||||
key = 1
|
|
@ -1 +0,0 @@
|
|||
key = "\" \t \n \r \\ \/ \b \f"
|
Loading…
Reference in a new issue