├── .github └── workflows │ └── check.yml ├── .gitignore ├── LICENSE ├── README.md ├── build.gradle ├── gradle.properties.example ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src ├── main └── java │ └── net │ └── sandrohc │ └── schematic4j │ ├── SchematicFormat.java │ ├── SchematicLoader.java │ ├── SchematicUtil.java │ ├── exception │ ├── MissingFieldException.java │ ├── NoParserFoundException.java │ ├── ParsingException.java │ └── SchematicBuilderException.java │ ├── nbt │ ├── Deserializer.java │ ├── ExceptionBiFunction.java │ ├── ExceptionTriConsumer.java │ ├── MaxDepthIO.java │ ├── MaxDepthReachedException.java │ ├── Serializer.java │ ├── StringDeserializer.java │ ├── StringSerializer.java │ ├── io │ │ ├── LittleEndianNBTInputStream.java │ │ ├── LittleEndianNBTOutputStream.java │ │ ├── NBTDeserializer.java │ │ ├── NBTInput.java │ │ ├── NBTInputStream.java │ │ ├── NBTOutput.java │ │ ├── NBTOutputStream.java │ │ ├── NBTSerializer.java │ │ ├── NBTUtil.java │ │ ├── NamedTag.java │ │ ├── ParseException.java │ │ ├── SNBTDeserializer.java │ │ ├── SNBTParser.java │ │ ├── SNBTSerializer.java │ │ ├── SNBTUtil.java │ │ ├── SNBTWriter.java │ │ └── StringPointer.java │ └── tag │ │ ├── ArrayTag.java │ │ ├── ByteArrayTag.java │ │ ├── ByteTag.java │ │ ├── CompoundTag.java │ │ ├── DoubleTag.java │ │ ├── EndTag.java │ │ ├── FloatTag.java │ │ ├── IntArrayTag.java │ │ ├── IntTag.java │ │ ├── ListTag.java │ │ ├── LongArrayTag.java │ │ ├── LongTag.java │ │ ├── NonNullEntrySet.java │ │ ├── NumberTag.java │ │ ├── ShortTag.java │ │ ├── StringTag.java │ │ └── Tag.java │ ├── parser │ ├── LitematicaParser.java │ ├── Parser.java │ ├── SchematicaParser.java │ └── SpongeParser.java │ ├── schematic │ ├── LitematicaSchematic.java │ ├── Schematic.java │ ├── SchematicaSchematic.java │ ├── SpongeSchematic.java │ └── types │ │ ├── Pair.java │ │ ├── SchematicBiome.java │ │ ├── SchematicBlock.java │ │ ├── SchematicBlockEntity.java │ │ ├── SchematicBlockPos.java │ │ ├── SchematicEntity.java │ │ ├── SchematicEntityPos.java │ │ ├── SchematicItem.java │ │ └── SchematicNamed.java │ └── utils │ ├── DateUtils.java │ └── TagUtils.java └── test ├── java └── net │ └── sandrohc │ └── schematic4j │ ├── SchematicFormatTest.java │ ├── nbt │ └── io │ │ └── NBTUtilTest.java │ └── parser │ ├── LitematicaParserTest.java │ ├── SchematicaParserTest.java │ ├── SpongeParserTest.java │ ├── TestUtils.java │ └── __snapshots__ │ ├── LitematicaParserTest.snap │ ├── SchematicaParserTest.snap │ └── SpongeParserTest.snap └── resources ├── schematics ├── litematica │ ├── v5 │ │ ├── island.litematic │ │ ├── mansion.litematic │ │ ├── simple.litematic │ │ └── tower.litematic │ └── v6 │ │ └── demo.litematic ├── schematica │ ├── 12727.schematic │ └── 9383.schematic └── sponge │ ├── v1 │ └── sponge-v1.schem │ ├── v2 │ ├── green-cottage.schem │ ├── interieur-exterieur-chunk-project.schem │ └── issue-1.schem │ └── v3 │ └── sponge-v3.schem ├── simplelogger.properties └── snapshot.properties /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | name: Check 2 | on: [ pull_request, push ] 3 | jobs: 4 | check: 5 | name: Java ${{ matrix.java }} 6 | runs-on: ubuntu-latest 7 | strategy: 8 | matrix: 9 | java: [ 8, 21 ] 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v4 13 | - name: Validate Gradle wrapper 14 | uses: gradle/wrapper-validation-action@v1 15 | - name: Setup JDK ${{ matrix.java }} 16 | uses: actions/setup-java@v4 17 | with: 18 | java-version: ${{ matrix.java }} 19 | distribution: 'temurin' 20 | cache: 'gradle' 21 | - name: Test & Coverage 22 | run: ./gradlew test --no-daemon 23 | - name: Upload to Codecov 24 | if: ${{ matrix.java == '21' }} # Upload from a single matrix instance 25 | uses: codecov/codecov-action@v3 26 | env: 27 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 28 | with: 29 | files: ./build/reports/jacoco/test/jacocoTestReport.xml 30 | - name: Upload artifacts 31 | if: ${{ matrix.java == '21' }} # Upload from a single matrix instance 32 | uses: actions/upload-artifact@v3 33 | with: 34 | name: Artifacts 35 | path: build/libs/* 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /build/ 3 | /run/ 4 | 5 | # Ignore Gradle GUI config 6 | gradle-app.setting 7 | 8 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 9 | !gradle-wrapper.jar 10 | 11 | # Cache of project 12 | .gradletasknamecache 13 | 14 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 15 | # gradle/wrapper/gradle-wrapper.properties 16 | 17 | # Java Snapshot Testing 18 | /**/__snapshots__/*.debug 19 | 20 | /.idea/ 21 | /*.iml 22 | /out/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Sandro Marques 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Schematic4j 2 | 3 | [![Latest version](https://img.shields.io/maven-central/v/net.sandrohc/schematic4j?label=version)](https://central.sonatype.com/artifact/net.sandrohc/schematic4j) 4 | 5 | Java parser for the .schem/.schematic/.litematic Minecraft formats. 🗺 6 | 7 | ## Supported formats 8 | 9 | | Format | Extension | Links | 10 | |----------------------------|------------|--------------------------------------------------------------------------| 11 | | [Sponge Schematic][sponge] | .schem | Spec: [v1][sponge-spec-v1] • [v2][sponge-spec-v2] • [v3][sponge-spec-v3] | 12 | | [Litematica][litematica] | .litematic | [Spec][litematica-spec] • [Discussion][litematica-discussion] | 13 | | [Schematica][schematica] | .schematic | [Spec][schematica-spec] | 14 | 15 | [sponge]: https://github.com/SpongePowered/Schematic-Specification 16 | [sponge-spec-v1]: https://github.com/SpongePowered/Schematic-Specification/blob/master/versions/schematic-1.md 17 | [sponge-spec-v2]: https://github.com/SpongePowered/Schematic-Specification/blob/master/versions/schematic-2.md 18 | [sponge-spec-v3]: https://github.com/SpongePowered/Schematic-Specification/blob/master/versions/schematic-3.md 19 | [litematica]: https://github.com/maruohon/litematica 20 | [litematica-spec]: https://github.com/maruohon/litematica/blob/pre-rewrite/fabric/1.20.x/src/main/java/fi/dy/masa/litematica/schematic/LitematicaSchematic.java 21 | [litematica-discussion]: https://github.com/maruohon/litematica/issues/53#issuecomment-520279558 22 | [schematica]: https://curseforge.com/minecraft/mc-mods/schematica 23 | [schematica-spec]: https://minecraft.fandom.com/wiki/Schematic_file_format 24 | 25 | ## Installation 26 | 27 | Add the following dependency to your build file. 28 | 29 | If using Gradle (`build.gradle`): 30 | ```groovy 31 | repositories { 32 | mavenCentral() 33 | } 34 | 35 | dependencies { 36 | implementation 'net.sandrohc:schematic4j:1.1.0' 37 | } 38 | ``` 39 | 40 | If using Maven (`pom.xml`): 41 | ```xml 42 | 43 | net.sandrohc 44 | schematic4j 45 | 1.1.0 46 | 47 | ``` 48 | 49 | For development builds, please see: https://jitpack.io/#net.sandrohc/schematic4j 50 | 51 | ## Usage 52 | 53 | Here are some examples on how to use this library: 54 | 55 | ```java 56 | // Load schematic from a file. 57 | // Currently supported formats include .schematic, .schem and .litematic. 58 | Schematic schematic = SchematicLoader.load("/path/to/your.schematic"); 59 | 60 | schematic.name(); 61 | schematic.width(); 62 | schematic.height(); 63 | schematic.length(); 64 | schematic.block(0, 0, 0).name; 65 | schematic.blocks().collect(Collectors.toList()); 66 | schematic.blockEntities().collect(Collectors.toList()); 67 | schematic.entities().collect(Collectors.toList()); 68 | ``` -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java-library' 3 | id 'jacoco' 4 | id 'maven-publish' 5 | id 'signing' 6 | } 7 | 8 | group 'net.sandrohc' 9 | version '1.1.0' 10 | 11 | ext.isReleaseVersion = !version.endsWith('SNAPSHOT') 12 | 13 | repositories { 14 | mavenCentral() 15 | } 16 | 17 | dependencies { 18 | api 'org.slf4j:slf4j-api:1.7.36' 19 | implementation 'org.checkerframework:checker-qual:3.41.0' 20 | 21 | testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.1' 22 | testImplementation 'org.junit.jupiter:junit-jupiter-params:5.10.1' 23 | testImplementation 'org.assertj:assertj-core:3.24.2' 24 | testImplementation 'io.github.origin-energy:java-snapshot-testing-junit5:4.0.6' 25 | testImplementation 'io.github.origin-energy:java-snapshot-testing-plugin-jackson:4.0.6' 26 | testImplementation 'com.fasterxml.jackson.core:jackson-core:2.16.0' 27 | testImplementation 'com.fasterxml.jackson.core:jackson-databind:2.16.0' 28 | testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' 29 | testRuntimeOnly 'org.slf4j:slf4j-simple:1.7.36' 30 | testRuntimeOnly 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.16.0' 31 | testRuntimeOnly 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.16.0' 32 | } 33 | 34 | java { 35 | sourceCompatibility = JavaVersion.VERSION_1_8 36 | targetCompatibility = JavaVersion.VERSION_1_8 37 | 38 | withJavadocJar() 39 | withSourcesJar() 40 | } 41 | 42 | jar { 43 | manifest { 44 | attributes( 45 | 'Implementation-Title': 'schematic4j', 46 | 'Implementation-Version': project.version 47 | ) 48 | } 49 | } 50 | 51 | javadoc { 52 | if (JavaVersion.current().isJava9Compatible()) { 53 | options.addBooleanOption('html5', true) 54 | } 55 | } 56 | 57 | test { 58 | useJUnitPlatform() 59 | finalizedBy jacocoTestReport 60 | } 61 | 62 | jacocoTestReport { 63 | dependsOn test 64 | reports { 65 | xml.required = true 66 | } 67 | } 68 | 69 | tasks.withType(JavaCompile) { 70 | // force UTF-8 encoding on Windows machines 71 | options.encoding = 'UTF-8' 72 | } 73 | 74 | publishing { 75 | repositories { 76 | maven { 77 | def releasesRepoUrl = layout.buildDirectory.dir('repos/releases') 78 | def snapshotsRepoUrl = layout.buildDirectory.dir('repos/snapshots') 79 | url = isReleaseVersion ? releasesRepoUrl : snapshotsRepoUrl 80 | //credentials { 81 | // username = project.hasProperty('ossrhUsername') ? ossrhUsername : 'unk' 82 | // password = project.hasProperty('ossrhPassword') ? ossrhPassword : 'unk' 83 | //} 84 | } 85 | } 86 | 87 | publications { 88 | maven(MavenPublication) { 89 | from components.java 90 | pom { 91 | name = 'Schematic4J' 92 | description = 'Java parser for the .schem/.schematic/.litematic Minecraft formats. 🗺' 93 | url = 'https://github.com/SandroHc/schematic4j' 94 | licenses { 95 | license { 96 | name = 'MIT License' 97 | url = 'https://opensource.org/licenses/MIT' 98 | } 99 | } 100 | developers { 101 | developer { 102 | id = 'SandroHc' 103 | name = 'Sandro Marques' 104 | email = 'sandro123iv@gmail.com' 105 | } 106 | } 107 | scm { 108 | connection = 'scm:git:git://github.com/SandroHc/schematic4j.git' 109 | developerConnection = 'scm:git:ssh://github.com/SandroHc/schematic4j.git' 110 | url = 'https://github.com/SandroHc/schematic4j' 111 | } 112 | issueManagement { 113 | system = 'GitHub' 114 | url = 'https://github.com/SandroHc/schematic4j/issues' 115 | } 116 | } 117 | } 118 | } 119 | } 120 | 121 | signing { 122 | useGpgCmd() 123 | sign publishing.publications.maven 124 | } 125 | tasks.withType(Sign).configureEach { 126 | onlyIf { System.env['JITPACK'] == null } 127 | } 128 | -------------------------------------------------------------------------------- /gradle.properties.example: -------------------------------------------------------------------------------- 1 | signing.gnupg.executable=gpg 2 | signing.gnupg.keyName=12345678 3 | signing.gnupg.passphrase=PASS 4 | 5 | ossrhUsername=USER 6 | ossrhPassword=PASS -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandroHc/schematic4j/c581e2f605cea6820cad6c59937fe15eec961dc1/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 87 | APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit 88 | 89 | # Use the maximum available, or set MAX_FD != -1 to use that value. 90 | MAX_FD=maximum 91 | 92 | warn () { 93 | echo "$*" 94 | } >&2 95 | 96 | die () { 97 | echo 98 | echo "$*" 99 | echo 100 | exit 1 101 | } >&2 102 | 103 | # OS specific support (must be 'true' or 'false'). 104 | cygwin=false 105 | msys=false 106 | darwin=false 107 | nonstop=false 108 | case "$( uname )" in #( 109 | CYGWIN* ) cygwin=true ;; #( 110 | Darwin* ) darwin=true ;; #( 111 | MSYS* | MINGW* ) msys=true ;; #( 112 | NONSTOP* ) nonstop=true ;; 113 | esac 114 | 115 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 116 | 117 | 118 | # Determine the Java command to use to start the JVM. 119 | if [ -n "$JAVA_HOME" ] ; then 120 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 121 | # IBM's JDK on AIX uses strange locations for the executables 122 | JAVACMD=$JAVA_HOME/jre/sh/java 123 | else 124 | JAVACMD=$JAVA_HOME/bin/java 125 | fi 126 | if [ ! -x "$JAVACMD" ] ; then 127 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 128 | 129 | Please set the JAVA_HOME variable in your environment to match the 130 | location of your Java installation." 131 | fi 132 | else 133 | JAVACMD=java 134 | if ! command -v java >/dev/null 2>&1 135 | then 136 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | fi 142 | 143 | # Increase the maximum file descriptors if we can. 144 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 145 | case $MAX_FD in #( 146 | max*) 147 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 148 | # shellcheck disable=SC2039,SC3045 149 | MAX_FD=$( ulimit -H -n ) || 150 | warn "Could not query maximum file descriptor limit" 151 | esac 152 | case $MAX_FD in #( 153 | '' | soft) :;; #( 154 | *) 155 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 156 | # shellcheck disable=SC2039,SC3045 157 | ulimit -n "$MAX_FD" || 158 | warn "Could not set maximum file descriptor limit to $MAX_FD" 159 | esac 160 | fi 161 | 162 | # Collect all arguments for the java command, stacking in reverse order: 163 | # * args from the command line 164 | # * the main class name 165 | # * -classpath 166 | # * -D...appname settings 167 | # * --module-path (only if needed) 168 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 169 | 170 | # For Cygwin or MSYS, switch paths to Windows format before running java 171 | if "$cygwin" || "$msys" ; then 172 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 173 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 174 | 175 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 176 | 177 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 178 | for arg do 179 | if 180 | case $arg in #( 181 | -*) false ;; # don't mess with options #( 182 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 183 | [ -e "$t" ] ;; #( 184 | *) false ;; 185 | esac 186 | then 187 | arg=$( cygpath --path --ignore --mixed "$arg" ) 188 | fi 189 | # Roll the args list around exactly as many times as the number of 190 | # args, so each arg winds up back in the position where it started, but 191 | # possibly modified. 192 | # 193 | # NB: a `for` loop captures its iteration list before it begins, so 194 | # changing the positional parameters here affects neither the number of 195 | # iterations, nor the values presented in `arg`. 196 | shift # remove old arg 197 | set -- "$@" "$arg" # push replacement arg 198 | done 199 | fi 200 | 201 | 202 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 203 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 204 | 205 | # Collect all arguments for the java command: 206 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 207 | # and any embedded shellness will be escaped. 208 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 209 | # treated as '${Hostname}' itself on the command line. 210 | 211 | set -- \ 212 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 213 | -classpath "$CLASSPATH" \ 214 | org.gradle.wrapper.GradleWrapperMain \ 215 | "$@" 216 | 217 | # Stop when "xargs" is not available. 218 | if ! command -v xargs >/dev/null 2>&1 219 | then 220 | die "xargs is not available" 221 | fi 222 | 223 | # Use "xargs" to parse quoted args. 224 | # 225 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 226 | # 227 | # In Bash we could simply go: 228 | # 229 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 230 | # set -- "${ARGS[@]}" "$@" 231 | # 232 | # but POSIX shell has neither arrays nor command substitution, so instead we 233 | # post-process each arg (as a line of input to sed) to backslash-escape any 234 | # character that might be a shell metacharacter, then use eval to reverse 235 | # that process (while maintaining the separation between arguments), and wrap 236 | # the whole thing up as a single "set" statement. 237 | # 238 | # This will of course break if any of these variables contains a newline or 239 | # an unmatched quote. 240 | # 241 | 242 | eval "set -- $( 243 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 244 | xargs -n1 | 245 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 246 | tr '\n' ' ' 247 | )" '"$@"' 248 | 249 | exec "$JAVACMD" "$@" 250 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'schematic4j' 2 | 3 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/SchematicFormat.java: -------------------------------------------------------------------------------- 1 | package net.sandrohc.schematic4j; 2 | 3 | import java.util.HashMap; 4 | import java.util.HashSet; 5 | import java.util.Map; 6 | import java.util.Optional; 7 | import java.util.Set; 8 | import java.util.function.Supplier; 9 | 10 | import org.checkerframework.checker.nullness.qual.NonNull; 11 | import org.checkerframework.checker.nullness.qual.Nullable; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | 15 | import net.sandrohc.schematic4j.exception.NoParserFoundException; 16 | import net.sandrohc.schematic4j.nbt.tag.CompoundTag; 17 | import net.sandrohc.schematic4j.parser.LitematicaParser; 18 | import net.sandrohc.schematic4j.parser.Parser; 19 | import net.sandrohc.schematic4j.parser.SchematicaParser; 20 | import net.sandrohc.schematic4j.parser.SpongeParser; 21 | 22 | public enum SchematicFormat { 23 | SPONGE_V1("schem", SpongeParser::new), 24 | SPONGE_V2("schem", SpongeParser::new), 25 | SPONGE_V3("schem", SpongeParser::new), 26 | LITEMATICA("litematic", LitematicaParser::new), 27 | SCHEMATICA("schematic", SchematicaParser::new), 28 | UNKNOWN; 29 | 30 | private static final Logger log = LoggerFactory.getLogger(SchematicFormat.class); 31 | 32 | public final String fileExtension; 33 | private final Supplier parserGenerator; 34 | 35 | SchematicFormat(String fileExtension, Supplier parserGenerator) { 36 | this.fileExtension = fileExtension; 37 | this.parserGenerator = parserGenerator; 38 | } 39 | 40 | SchematicFormat(String fileExtension) { 41 | this(fileExtension, null); 42 | } 43 | 44 | SchematicFormat() { 45 | this(null, null); 46 | } 47 | 48 | public Parser createParser() throws NoParserFoundException { 49 | if (parserGenerator == null) 50 | throw new NoParserFoundException(this); 51 | 52 | return parserGenerator.get(); 53 | } 54 | 55 | /** 56 | * Tries to guess the schematic format of the input. 57 | * 58 | * @param nbt The NBT input to check 59 | * @return The format guesses from looking at the input, or {@link SchematicFormat#UNKNOWN} if no known format was found. 60 | */ 61 | public static @NonNull SchematicFormat guessFormat(@Nullable CompoundTag nbt) { 62 | if (nbt == null) { 63 | return SchematicFormat.UNKNOWN; 64 | } 65 | 66 | final Candidates candidates = new Candidates<>(); 67 | guessSpongeFormat(candidates, nbt); 68 | guessLitematicaFormat(candidates, nbt); 69 | guessSchematicaFormat(candidates, nbt); 70 | 71 | final SchematicFormat guess = candidates.best().orElse(SchematicFormat.UNKNOWN); 72 | log.debug("Guessed {} as the format", guess); 73 | return guess; 74 | } 75 | 76 | private static void guessSpongeFormat(Candidates candidates, @NonNull CompoundTag rootTag) { 77 | if (rootTag.containsKey(SpongeParser.NBT_VERSION)) { 78 | final int version = rootTag.getInt(SpongeParser.NBT_VERSION); 79 | switch (version) { 80 | case 1: 81 | candidates.increment(SchematicFormat.SPONGE_V1, 5); 82 | case 2: 83 | candidates.increment(SchematicFormat.SPONGE_V2, 5); 84 | case 3: 85 | candidates.increment(SchematicFormat.SPONGE_V3, 5); 86 | } 87 | } else { 88 | candidates.increment(SchematicFormat.SPONGE_V1, 1); 89 | candidates.exclude(SchematicFormat.SPONGE_V2); 90 | candidates.exclude(SchematicFormat.SPONGE_V3); 91 | } 92 | if (rootTag.containsKey(SpongeParser.NBT_DATA_VERSION)) { 93 | candidates.increment(SchematicFormat.SPONGE_V1, 1); 94 | candidates.increment(SchematicFormat.SPONGE_V2, 1); 95 | candidates.increment(SchematicFormat.SPONGE_V3, 1); 96 | } else { 97 | candidates.exclude(SchematicFormat.SPONGE_V2); 98 | candidates.exclude(SchematicFormat.SPONGE_V3); 99 | } 100 | if (rootTag.containsKey(SpongeParser.NBT_WIDTH)) { 101 | candidates.increment(SchematicFormat.SPONGE_V1, 1); 102 | candidates.increment(SchematicFormat.SPONGE_V2, 1); 103 | candidates.increment(SchematicFormat.SPONGE_V3, 1); 104 | } 105 | if (rootTag.containsKey(SpongeParser.NBT_HEIGHT)) { 106 | candidates.increment(SchematicFormat.SPONGE_V1, 1); 107 | candidates.increment(SchematicFormat.SPONGE_V2, 1); 108 | candidates.increment(SchematicFormat.SPONGE_V3, 1); 109 | } 110 | if (rootTag.containsKey(SpongeParser.NBT_LENGTH)) { 111 | candidates.increment(SchematicFormat.SPONGE_V1, 1); 112 | candidates.increment(SchematicFormat.SPONGE_V2, 1); 113 | candidates.increment(SchematicFormat.SPONGE_V3, 1); 114 | } 115 | if (rootTag.containsKey(SpongeParser.NBT_PALETTE)) { 116 | candidates.increment(SchematicFormat.SPONGE_V1, 1); 117 | candidates.increment(SchematicFormat.SPONGE_V2, 1); 118 | } 119 | if (rootTag.containsKey(SpongeParser.NBT_PALETTE_MAX)) { 120 | candidates.increment(SchematicFormat.SPONGE_V1, 1); 121 | candidates.increment(SchematicFormat.SPONGE_V2, 1); 122 | } 123 | if (rootTag.containsKey(SpongeParser.NBT_BLOCK_DATA)) { 124 | candidates.increment(SchematicFormat.SPONGE_V1, 1); 125 | candidates.increment(SchematicFormat.SPONGE_V2, 1); 126 | } 127 | if (rootTag.containsKey(SpongeParser.NBT_BIOME_DATA)) { 128 | candidates.increment(SchematicFormat.SPONGE_V2, 1); 129 | } 130 | if (rootTag.containsKey(SpongeParser.NBT_TILE_ENTITIES)) { 131 | candidates.increment(SchematicFormat.SPONGE_V1, 1); 132 | } 133 | if (rootTag.containsKey(SpongeParser.NBT_BLOCK_ENTITIES)) { 134 | candidates.increment(SchematicFormat.SPONGE_V2, 1); 135 | } 136 | if (rootTag.containsKey(SpongeParser.NBT_V3_BLOCKS)) { 137 | candidates.increment(SchematicFormat.SPONGE_V3, 1); 138 | } 139 | if (rootTag.containsKey(SpongeParser.NBT_V3_BIOMES)) { 140 | candidates.increment(SchematicFormat.SPONGE_V3, 1); 141 | } 142 | if (rootTag.containsKey(SpongeParser.NBT_METADATA)) { 143 | candidates.increment(SchematicFormat.SPONGE_V1, 1); 144 | candidates.increment(SchematicFormat.SPONGE_V2, 1); 145 | candidates.increment(SchematicFormat.SPONGE_V3, 1); 146 | } 147 | } 148 | 149 | private static void guessLitematicaFormat(Candidates candidates, @NonNull CompoundTag nbt) { 150 | if (nbt.containsKey(LitematicaParser.NBT_MINECRAFT_DATA_VERSION)) { 151 | candidates.increment(SchematicFormat.LITEMATICA, 1); 152 | } 153 | if (nbt.containsKey(LitematicaParser.NBT_VERSION)) { 154 | candidates.increment(SchematicFormat.LITEMATICA, 1); 155 | } 156 | if (nbt.containsKey(LitematicaParser.NBT_METADATA)) { 157 | candidates.increment(SchematicFormat.LITEMATICA, 1); 158 | } 159 | if (nbt.containsKey(LitematicaParser.NBT_REGIONS)) { 160 | candidates.increment(SchematicFormat.LITEMATICA, 2); 161 | } else { 162 | candidates.exclude(SchematicFormat.LITEMATICA); 163 | } 164 | } 165 | 166 | private static void guessSchematicaFormat(Candidates candidates, @NonNull CompoundTag nbt) { 167 | if (nbt.containsKey(SchematicaParser.NBT_MAPPING_SCHEMATICA)) { 168 | candidates.increment(SchematicFormat.SCHEMATICA, 10); 169 | } else { 170 | candidates.exclude(SchematicFormat.SCHEMATICA); 171 | } 172 | if (nbt.containsKey(SchematicaParser.NBT_WIDTH)) { 173 | candidates.increment(SchematicFormat.SCHEMATICA, 1); 174 | } else { 175 | candidates.exclude(SchematicFormat.SCHEMATICA); 176 | } 177 | if (nbt.containsKey(SchematicaParser.NBT_HEIGHT)) { 178 | candidates.increment(SchematicFormat.SCHEMATICA, 1); 179 | } else { 180 | candidates.exclude(SchematicFormat.SCHEMATICA); 181 | } 182 | if (nbt.containsKey(SchematicaParser.NBT_LENGTH)) { 183 | candidates.increment(SchematicFormat.SCHEMATICA, 1); 184 | } else { 185 | candidates.exclude(SchematicFormat.SCHEMATICA); 186 | } 187 | } 188 | 189 | protected static class Candidates { 190 | protected final Map candidates = new HashMap<>(); 191 | protected final Set excluded = new HashSet<>(); 192 | 193 | public void increment(T candidate, int weight) { 194 | if (!excluded.contains(candidate)) { 195 | final int currentWeight = candidates.getOrDefault(candidate, 0); 196 | candidates.put(candidate, currentWeight + weight); 197 | log.trace("Format candidate {} prioritized by {} (total: {})", candidate, weight, currentWeight + weight); 198 | } 199 | } 200 | 201 | public void exclude(T candidate) { 202 | log.trace("Excluded format: {}", candidate); 203 | excluded.add(candidate); 204 | candidates.remove(candidate); 205 | } 206 | 207 | public Optional best() { 208 | final Optional> best = candidates.entrySet().stream().reduce((a, b) -> { 209 | if (a.getValue() >= b.getValue()) { 210 | return a; 211 | } else { 212 | return b; 213 | } 214 | }); 215 | log.trace("Format candidates: {}", candidates); 216 | log.trace("Excluded formats: {}", excluded); 217 | log.trace("Best candidate: {}", best); 218 | return best.map(Map.Entry::getKey); 219 | } 220 | 221 | @Override 222 | public String toString() { 223 | return "Candidates[candidates=" + candidates + ", excluded=" + excluded + ']'; 224 | } 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/SchematicLoader.java: -------------------------------------------------------------------------------- 1 | package net.sandrohc.schematic4j; 2 | 3 | import java.io.BufferedInputStream; 4 | import java.io.File; 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import java.nio.file.Files; 8 | import java.nio.file.Path; 9 | import java.nio.file.Paths; 10 | 11 | import org.checkerframework.checker.nullness.qual.NonNull; 12 | import org.checkerframework.checker.nullness.qual.Nullable; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | 16 | import net.sandrohc.schematic4j.exception.ParsingException; 17 | import net.sandrohc.schematic4j.nbt.io.NBTUtil; 18 | import net.sandrohc.schematic4j.nbt.io.NamedTag; 19 | import net.sandrohc.schematic4j.nbt.tag.CompoundTag; 20 | import net.sandrohc.schematic4j.parser.Parser; 21 | import net.sandrohc.schematic4j.schematic.Schematic; 22 | 23 | /** 24 | * A collection of utility methods to load and parse schematics. 25 | */ 26 | public class SchematicLoader { 27 | 28 | private static final Logger log = LoggerFactory.getLogger(SchematicLoader.class); 29 | 30 | private SchematicLoader() {} 31 | 32 | /** 33 | * Load a schematic from an input stream. 34 | * 35 | * @param is The input stream to load the schematic from. 36 | * @return The loaded and parsed schematic 37 | * @throws ParsingException in case no supported parses was found or there was a parsing error 38 | * @throws IOException in case of I/O error 39 | * @see SchematicLoader#load(Path) 40 | * @see SchematicLoader#load(File) 41 | * @see SchematicLoader#load(String) 42 | */ 43 | public static @NonNull Schematic load(@NonNull InputStream is) throws ParsingException, IOException { 44 | final NamedTag rootTag = NBTUtil.Reader.read().from(is); 45 | return parse(rootTag); 46 | } 47 | 48 | /** 49 | * Load a schematic from a file. 50 | * 51 | * @param path The file to load the schematic from. 52 | * @return The loaded and parsed schematic 53 | * @throws ParsingException in case no supported parses was found or there was a parsing error 54 | * @throws IOException in case of I/O error 55 | * @see SchematicLoader#load(InputStream) 56 | * @see SchematicLoader#load(File) 57 | * @see SchematicLoader#load(String) 58 | */ 59 | public static @NonNull Schematic load(@NonNull Path path) throws ParsingException, IOException { 60 | try (InputStream is = new BufferedInputStream(Files.newInputStream(path))) { 61 | return load(is); 62 | } 63 | } 64 | 65 | /** 66 | * Load a schematic from a file. 67 | * 68 | * @param file The file to load the schematic from. 69 | * @return The loaded and parsed schematic 70 | * @throws ParsingException in case no supported parses was found or there was a parsing error 71 | * @throws IOException in case of I/O error 72 | * @see SchematicLoader#load(InputStream) 73 | * @see SchematicLoader#load(Path) 74 | * @see SchematicLoader#load(String) 75 | */ 76 | public static @NonNull Schematic load(@NonNull File file) throws ParsingException, IOException { 77 | return load(file.toPath()); 78 | } 79 | 80 | /** 81 | * Load a schematic from a file. 82 | * 83 | * @param filePath The file path to load the schematic from. 84 | * @return The loaded and parsed schematic 85 | * @throws ParsingException in case no supported parses was found or there was a parsing error 86 | * @throws IOException in case of I/O error 87 | * @see SchematicLoader#load(InputStream) 88 | * @see SchematicLoader#load(Path) 89 | * @see SchematicLoader#load(File) 90 | */ 91 | public static @NonNull Schematic load(@NonNull String filePath) throws ParsingException, IOException { 92 | return load(Paths.get(filePath)); 93 | } 94 | 95 | /** 96 | * Attempts to guess the schematic format and parse the input. 97 | *
98 | * If you already know the format, consider parsing the NBT tag directly - i.e. {@code SchematicFormat.SPONGE_V2.createParser().parse(nbt)}. 99 | * 100 | * @param nbt The NBT root tag to parse. 101 | * @return The parsed schematic 102 | * @throws ParsingException in case no supported parses was found or there was a parsing error 103 | */ 104 | public static @NonNull Schematic parse(@Nullable CompoundTag nbt) throws ParsingException { 105 | SchematicFormat format = SchematicFormat.guessFormat(nbt); 106 | log.info("Found format: {}", format); 107 | 108 | Parser parser = format.createParser(); 109 | log.debug("Found parser: {}", parser); 110 | 111 | return parser.parse(nbt); 112 | } 113 | 114 | /** 115 | * Attempts to guess the schematic format and parse the input. 116 | *
117 | * If you already know the format, consider parsing the NBT tag directly - i.e. {@code SchematicFormat.SPONGE_V2.createParser().parse(nbt)}. 118 | * 119 | * @param input The NBT root tag to parse. 120 | * @return The parsed schematic 121 | * @throws ParsingException in case no supported parses was found or there was a parsing error 122 | */ 123 | public static @NonNull Schematic parse(@Nullable NamedTag input) throws ParsingException { 124 | final CompoundTag nbt = input!=null&& input.getTag() instanceof CompoundTag? (CompoundTag) input.getTag() :null; 125 | return parse(nbt); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/SchematicUtil.java: -------------------------------------------------------------------------------- 1 | package net.sandrohc.schematic4j; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.nio.file.Path; 7 | 8 | import org.checkerframework.checker.nullness.qual.NonNull; 9 | import org.checkerframework.checker.nullness.qual.Nullable; 10 | 11 | import net.sandrohc.schematic4j.exception.ParsingException; 12 | import net.sandrohc.schematic4j.nbt.io.NamedTag; 13 | import net.sandrohc.schematic4j.nbt.tag.CompoundTag; 14 | import net.sandrohc.schematic4j.schematic.Schematic; 15 | 16 | /** 17 | * A collection of utility methods to work with schematics. 18 | * 19 | * @deprecated Use {@link SchematicLoader} instead. 20 | */ 21 | @Deprecated 22 | public class SchematicUtil { 23 | 24 | private SchematicUtil() { 25 | } 26 | 27 | /** 28 | * Load a schematic from an input stream. 29 | * 30 | * @param is The input stream to load the schematic from. 31 | * @return The loaded and parsed schematic 32 | * @throws ParsingException in case no supported parses was found or there was a parsing error 33 | * @throws IOException in case of I/O error 34 | * @see SchematicLoader#load(Path) 35 | * @see SchematicLoader#load(File) 36 | * @see SchematicLoader#load(String) 37 | * @deprecated Use {@link SchematicLoader#load(InputStream)} instead. 38 | */ 39 | @Deprecated 40 | public static @NonNull Schematic load(@NonNull InputStream is) throws ParsingException, IOException { 41 | return SchematicLoader.load(is); 42 | } 43 | 44 | /** 45 | * Load a schematic from a file. 46 | * 47 | * @param path The file to load the schematic from. 48 | * @return The loaded and parsed schematic 49 | * @throws ParsingException in case no supported parses was found or there was a parsing error 50 | * @throws IOException in case of I/O error 51 | * @see SchematicLoader#load(InputStream) 52 | * @see SchematicLoader#load(File) 53 | * @see SchematicLoader#load(String) 54 | * @deprecated Use {@link SchematicLoader#load(Path)} instead. 55 | */ 56 | @Deprecated 57 | public static @NonNull Schematic load(@NonNull Path path) throws ParsingException, IOException { 58 | return SchematicLoader.load(path); 59 | } 60 | 61 | /** 62 | * Load a schematic from a file. 63 | * 64 | * @param file The file to load the schematic from. 65 | * @return The loaded and parsed schematic 66 | * @throws ParsingException in case no supported parses was found or there was a parsing error 67 | * @throws IOException in case of I/O error 68 | * @see SchematicLoader#load(InputStream) 69 | * @see SchematicLoader#load(Path) 70 | * @see SchematicLoader#load(String) 71 | * @deprecated Use {@link SchematicLoader#load(File)} instead. 72 | */ 73 | @Deprecated 74 | public static @NonNull Schematic load(@NonNull File file) throws ParsingException, IOException { 75 | return SchematicLoader.load(file); 76 | } 77 | 78 | /** 79 | * Load a schematic from a file. 80 | * 81 | * @param filePath The file path to load the schematic from. 82 | * @return The loaded and parsed schematic 83 | * @throws ParsingException in case no supported parses was found or there was a parsing error 84 | * @throws IOException in case of I/O error 85 | * @see SchematicLoader#load(InputStream) 86 | * @see SchematicLoader#load(Path) 87 | * @see SchematicLoader#load(File) 88 | * @deprecated Use {@link SchematicLoader#load(String)} instead. 89 | */ 90 | @Deprecated 91 | public static @NonNull Schematic load(@NonNull String filePath) throws ParsingException, IOException { 92 | return SchematicLoader.load(filePath); 93 | } 94 | 95 | /** 96 | * Attempts to guess the schematic format and parse the input. 97 | *
98 | * If you already know the format, consider parsing the NBT tag directly - i.e. {@code SchematicFormat.SPONGE_V2.createParser().parse(nbt)}. 99 | * 100 | * @param input The NBT root tag to parse. 101 | * @return The parsed schematic 102 | * @throws ParsingException in case no supported parses was found or there was a parsing error 103 | * @deprecated Use {@link SchematicLoader#parse(NamedTag)} instead. 104 | */ 105 | @Deprecated 106 | public static @NonNull Schematic parse(@Nullable NamedTag input) throws ParsingException { 107 | return SchematicLoader.parse(input); 108 | } 109 | 110 | /** 111 | * Tries to guess the schematic format of the input. 112 | * 113 | * @param input The NBT input to check 114 | * @return The format guesses from looking at the input, or {@link SchematicFormat#UNKNOWN} if no known format was found. 115 | * @deprecated Use {@link SchematicFormat#guessFormat(CompoundTag)} instead 116 | */ 117 | @Deprecated 118 | @NonNull 119 | public static SchematicFormat detectFormat(@Nullable NamedTag input) { 120 | final CompoundTag nbt = input != null && input.getTag() instanceof CompoundTag ? (CompoundTag) input.getTag() : null; 121 | return SchematicFormat.guessFormat(nbt); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/exception/MissingFieldException.java: -------------------------------------------------------------------------------- 1 | package net.sandrohc.schematic4j.exception; 2 | 3 | import net.sandrohc.schematic4j.nbt.tag.Tag; 4 | 5 | public class MissingFieldException extends ParsingException { 6 | 7 | public final Tag tag; 8 | public final String field; 9 | public final Class fieldType; 10 | 11 | public MissingFieldException(Tag tag, String field, Class fieldType) { 12 | super("Tag is missing field '" + field + "' of type " + fieldType.getSimpleName()); 13 | this.tag = tag; 14 | this.field = field; 15 | this.fieldType = fieldType; 16 | } 17 | 18 | public MissingFieldException(Tag tag, String field, Class fieldType, Throwable cause) { 19 | super("Tag is missing field '" + field + "' of type " + fieldType.getSimpleName(), cause); 20 | this.tag = tag; 21 | this.field = field; 22 | this.fieldType = fieldType; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/exception/NoParserFoundException.java: -------------------------------------------------------------------------------- 1 | package net.sandrohc.schematic4j.exception; 2 | 3 | import net.sandrohc.schematic4j.SchematicFormat; 4 | 5 | public class NoParserFoundException extends ParsingException { 6 | 7 | public final SchematicFormat format; 8 | 9 | 10 | public NoParserFoundException() { 11 | super("No suitable parser found"); 12 | this.format = null; 13 | } 14 | 15 | public NoParserFoundException(SchematicFormat format) { 16 | super("No suitable parser found for format " + format); 17 | this.format = format; 18 | } 19 | 20 | public NoParserFoundException(SchematicFormat format, Throwable cause) { 21 | super("No suitable parser found for format " + format, cause); 22 | this.format = format; 23 | } 24 | 25 | public NoParserFoundException(Throwable cause) { 26 | super("No suitable parser found", cause); 27 | this.format = null; 28 | } 29 | 30 | public NoParserFoundException(SchematicFormat format, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 31 | super("No suitable parser found for format " + format, cause, enableSuppression, writableStackTrace); 32 | this.format = format; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/exception/ParsingException.java: -------------------------------------------------------------------------------- 1 | package net.sandrohc.schematic4j.exception; 2 | 3 | public class ParsingException extends Exception { 4 | 5 | public ParsingException() { 6 | } 7 | 8 | public ParsingException(String message) { 9 | super(message); 10 | } 11 | 12 | public ParsingException(String message, Throwable cause) { 13 | super(message, cause); 14 | } 15 | 16 | public ParsingException(Throwable cause) { 17 | super(cause); 18 | } 19 | 20 | public ParsingException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 21 | super(message, cause, enableSuppression, writableStackTrace); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/exception/SchematicBuilderException.java: -------------------------------------------------------------------------------- 1 | package net.sandrohc.schematic4j.exception; 2 | 3 | public class SchematicBuilderException extends IllegalArgumentException { 4 | 5 | public SchematicBuilderException() { 6 | } 7 | 8 | public SchematicBuilderException(String message) { 9 | super(message); 10 | } 11 | 12 | public SchematicBuilderException(String message, Throwable cause) { 13 | super(message, cause); 14 | } 15 | 16 | public SchematicBuilderException(Throwable cause) { 17 | super(cause); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/nbt/Deserializer.java: -------------------------------------------------------------------------------- 1 | /* Vendored version of Quertz NBT 6.1 - https://github.com/Querz/NBT */ 2 | package net.sandrohc.schematic4j.nbt; 3 | 4 | import java.io.BufferedInputStream; 5 | import java.io.ByteArrayInputStream; 6 | import java.io.File; 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.net.URL; 10 | import java.nio.file.Files; 11 | 12 | /** 13 | * A generic NBT deserializer. 14 | * 15 | * @param Type of the deserialized value 16 | */ 17 | public interface Deserializer { 18 | 19 | /** 20 | * Deserialize NBT from an input stream. 21 | * 22 | * @param stream The input stream containing the NBT data 23 | * @return The deserialized NBT 24 | * @throws IOException In case there's an error reading from the input stream 25 | */ 26 | T fromStream(InputStream stream) throws IOException; 27 | 28 | /** 29 | * Deserialize NBT from a file. 30 | * 31 | * @param file The file containing the NBT data 32 | * @return The deserialized NBT 33 | * @throws IOException In case there's an error reading from the file 34 | */ 35 | default T fromFile(File file) throws IOException { 36 | try (BufferedInputStream bis = new BufferedInputStream(Files.newInputStream(file.toPath()))) { 37 | return fromStream(bis); 38 | } 39 | } 40 | 41 | /** 42 | * Deserialize NBT from a byte array. 43 | * 44 | * @param data The byte array containing the NBT data 45 | * @return The deserialized NBT 46 | * @throws IOException In case there's an error reading from the byte array 47 | */ 48 | default T fromBytes(byte[] data) throws IOException { 49 | ByteArrayInputStream stream = new ByteArrayInputStream(data); 50 | return fromStream(stream); 51 | } 52 | 53 | /** 54 | * Deserialize NBT from a JAR resource. 55 | * 56 | * @param clazz The class belonging to the JAR that contains the resource 57 | * @param path The resource path containing the NBT data 58 | * @return The deserialized NBT 59 | * @throws IOException In case there's an error reading from the resource 60 | */ 61 | default T fromResource(Class clazz, String path) throws IOException { 62 | try (InputStream stream = clazz.getClassLoader().getResourceAsStream(path)) { 63 | if (stream == null) { 64 | throw new IOException("resource \"" + path + "\" not found"); 65 | } 66 | return fromStream(stream); 67 | } 68 | } 69 | 70 | /** 71 | * Deserialize NBT from a URL. 72 | * 73 | * @param url The URL containing the NBT data 74 | * @return The deserialized NBT 75 | * @throws IOException In case there's an error reading from the URL 76 | */ 77 | default T fromURL(URL url) throws IOException { 78 | try (InputStream stream = url.openStream()) { 79 | return fromStream(stream); 80 | } 81 | } 82 | 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/nbt/ExceptionBiFunction.java: -------------------------------------------------------------------------------- 1 | /* Vendored version of Quertz NBT 6.1 - https://github.com/Querz/NBT */ 2 | package net.sandrohc.schematic4j.nbt; 3 | 4 | /** 5 | * A bi-function that may throw an exception. 6 | * 7 | * @param First value type 8 | * @param Second value type 9 | * @param Return type 10 | * @param Exception type 11 | */ 12 | @FunctionalInterface 13 | public interface ExceptionBiFunction { 14 | 15 | /** 16 | * @param t The first value 17 | * @param u The second value 18 | * @return The return value 19 | * @throws E The exception 20 | */ 21 | R accept(T t, U u) throws E; 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/nbt/ExceptionTriConsumer.java: -------------------------------------------------------------------------------- 1 | /* Vendored version of Quertz NBT 6.1 - https://github.com/Querz/NBT */ 2 | package net.sandrohc.schematic4j.nbt; 3 | 4 | /** 5 | * A tri-consumer that may throw an exception. 6 | * 7 | * @param First value type 8 | * @param Second value type 9 | * @param Third value type 10 | * @param Exception type 11 | */ 12 | @FunctionalInterface 13 | public interface ExceptionTriConsumer { 14 | 15 | /** 16 | * @param t The first value 17 | * @param u The second value 18 | * @param v The third value 19 | * @throws E The exception 20 | */ 21 | void accept(T t, U u, V v) throws E; 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/nbt/MaxDepthIO.java: -------------------------------------------------------------------------------- 1 | /* Vendored version of Quertz NBT 6.1 - https://github.com/Querz/NBT */ 2 | package net.sandrohc.schematic4j.nbt; 3 | 4 | public interface MaxDepthIO { 5 | 6 | default int decrementMaxDepth(int maxDepth) { 7 | if (maxDepth < 0) { 8 | throw new IllegalArgumentException("negative maximum depth is not allowed"); 9 | } else if (maxDepth == 0) { 10 | throw new MaxDepthReachedException("reached maximum depth of NBT structure"); 11 | } 12 | return --maxDepth; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/nbt/MaxDepthReachedException.java: -------------------------------------------------------------------------------- 1 | /* Vendored version of Quertz NBT 6.1 - https://github.com/Querz/NBT */ 2 | package net.sandrohc.schematic4j.nbt; 3 | 4 | /** 5 | * Exception indicating that the maximum (de-)serialization depth has been reached. 6 | */ 7 | public class MaxDepthReachedException extends RuntimeException { 8 | 9 | public MaxDepthReachedException(String msg) { 10 | super(msg); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/nbt/Serializer.java: -------------------------------------------------------------------------------- 1 | /* Vendored version of Quertz NBT 6.1 - https://github.com/Querz/NBT */ 2 | package net.sandrohc.schematic4j.nbt; 3 | 4 | import java.io.BufferedOutputStream; 5 | import java.io.ByteArrayOutputStream; 6 | import java.io.File; 7 | import java.io.FileOutputStream; 8 | import java.io.IOException; 9 | import java.io.OutputStream; 10 | 11 | public interface Serializer { 12 | 13 | void toStream(T object, OutputStream out) throws IOException; 14 | 15 | default void toFile(T object, File file) throws IOException { 16 | try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file))) { 17 | toStream(object, bos); 18 | } 19 | } 20 | 21 | default byte[] toBytes(T object) throws IOException { 22 | ByteArrayOutputStream bos = new ByteArrayOutputStream(); 23 | toStream(object, bos); 24 | bos.close(); 25 | return bos.toByteArray(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/nbt/StringDeserializer.java: -------------------------------------------------------------------------------- 1 | /* Vendored version of Quertz NBT 6.1 - https://github.com/Querz/NBT */ 2 | package net.sandrohc.schematic4j.nbt; 3 | 4 | import java.io.File; 5 | import java.io.FileReader; 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.io.InputStreamReader; 9 | import java.io.Reader; 10 | import java.io.StringReader; 11 | 12 | public interface StringDeserializer extends Deserializer { 13 | 14 | T fromReader(Reader reader) throws IOException; 15 | 16 | default T fromString(String s) throws IOException { 17 | return fromReader(new StringReader(s)); 18 | } 19 | 20 | @Override 21 | default T fromStream(InputStream stream) throws IOException { 22 | try (Reader reader = new InputStreamReader(stream)) { 23 | return fromReader(reader); 24 | } 25 | } 26 | 27 | @Override 28 | default T fromFile(File file) throws IOException { 29 | try (Reader reader = new FileReader(file)) { 30 | return fromReader(reader); 31 | } 32 | } 33 | 34 | @Override 35 | default T fromBytes(byte[] data) throws IOException { 36 | return fromReader(new StringReader(new String(data))); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/nbt/StringSerializer.java: -------------------------------------------------------------------------------- 1 | /* Vendored version of Quertz NBT 6.1 - https://github.com/Querz/NBT */ 2 | package net.sandrohc.schematic4j.nbt; 3 | 4 | import java.io.File; 5 | import java.io.FileWriter; 6 | import java.io.IOException; 7 | import java.io.OutputStream; 8 | import java.io.OutputStreamWriter; 9 | import java.io.StringWriter; 10 | import java.io.Writer; 11 | 12 | public interface StringSerializer extends Serializer { 13 | 14 | void toWriter(T object, Writer writer) throws IOException; 15 | 16 | default String toString(T object) throws IOException { 17 | Writer writer = new StringWriter(); 18 | toWriter(object, writer); 19 | writer.flush(); 20 | return writer.toString(); 21 | } 22 | 23 | @Override 24 | default void toStream(T object, OutputStream stream) throws IOException { 25 | Writer writer = new OutputStreamWriter(stream); 26 | toWriter(object, writer); 27 | writer.flush(); 28 | } 29 | 30 | @Override 31 | default void toFile(T object, File file) throws IOException { 32 | try (Writer writer = new FileWriter(file)) { 33 | toWriter(object, writer); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/nbt/io/LittleEndianNBTInputStream.java: -------------------------------------------------------------------------------- 1 | /* Vendored version of Quertz NBT 6.1 - https://github.com/Querz/NBT */ 2 | package net.sandrohc.schematic4j.nbt.io; 3 | 4 | import java.io.Closeable; 5 | import java.io.DataInput; 6 | import java.io.DataInputStream; 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.nio.charset.StandardCharsets; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | import net.sandrohc.schematic4j.nbt.ExceptionBiFunction; 14 | import net.sandrohc.schematic4j.nbt.MaxDepthIO; 15 | import net.sandrohc.schematic4j.nbt.tag.ByteArrayTag; 16 | import net.sandrohc.schematic4j.nbt.tag.ByteTag; 17 | import net.sandrohc.schematic4j.nbt.tag.CompoundTag; 18 | import net.sandrohc.schematic4j.nbt.tag.DoubleTag; 19 | import net.sandrohc.schematic4j.nbt.tag.EndTag; 20 | import net.sandrohc.schematic4j.nbt.tag.FloatTag; 21 | import net.sandrohc.schematic4j.nbt.tag.IntArrayTag; 22 | import net.sandrohc.schematic4j.nbt.tag.IntTag; 23 | import net.sandrohc.schematic4j.nbt.tag.ListTag; 24 | import net.sandrohc.schematic4j.nbt.tag.LongArrayTag; 25 | import net.sandrohc.schematic4j.nbt.tag.LongTag; 26 | import net.sandrohc.schematic4j.nbt.tag.ShortTag; 27 | import net.sandrohc.schematic4j.nbt.tag.StringTag; 28 | import net.sandrohc.schematic4j.nbt.tag.Tag; 29 | 30 | public class LittleEndianNBTInputStream implements DataInput, NBTInput, MaxDepthIO, Closeable { 31 | 32 | private final DataInputStream input; 33 | 34 | private static final Map, IOException>> readers = new HashMap<>(); 35 | private static final Map> idClassMapping = new HashMap<>(); 36 | 37 | static { 38 | put(EndTag.ID, (i, d) -> EndTag.INSTANCE, EndTag.class); 39 | put(ByteTag.ID, (i, d) -> readByte(i), ByteTag.class); 40 | put(ShortTag.ID, (i, d) -> readShort(i), ShortTag.class); 41 | put(IntTag.ID, (i, d) -> readInt(i), IntTag.class); 42 | put(LongTag.ID, (i, d) -> readLong(i), LongTag.class); 43 | put(FloatTag.ID, (i, d) -> readFloat(i), FloatTag.class); 44 | put(DoubleTag.ID, (i, d) -> readDouble(i), DoubleTag.class); 45 | put(ByteArrayTag.ID, (i, d) -> readByteArray(i), ByteArrayTag.class); 46 | put(StringTag.ID, (i, d) -> readString(i), StringTag.class); 47 | put(ListTag.ID, LittleEndianNBTInputStream::readListTag, ListTag.class); 48 | put(CompoundTag.ID, LittleEndianNBTInputStream::readCompound, CompoundTag.class); 49 | put(IntArrayTag.ID, (i, d) -> readIntArray(i), IntArrayTag.class); 50 | put(LongArrayTag.ID, (i, d) -> readLongArray(i), LongArrayTag.class); 51 | } 52 | 53 | private static void put(byte id, ExceptionBiFunction, IOException> reader, Class clazz) { 54 | readers.put(id, reader); 55 | idClassMapping.put(id, clazz); 56 | } 57 | 58 | public LittleEndianNBTInputStream(InputStream in) { 59 | input = new DataInputStream(in); 60 | } 61 | 62 | public LittleEndianNBTInputStream(DataInputStream in) { 63 | input = in; 64 | } 65 | 66 | public NamedTag readTag(int maxDepth) throws IOException { 67 | byte id = readByte(); 68 | return new NamedTag(readUTF(), readTag(id, maxDepth)); 69 | } 70 | 71 | public Tag readRawTag(int maxDepth) throws IOException { 72 | byte id = readByte(); 73 | return readTag(id, maxDepth); 74 | } 75 | 76 | private Tag readTag(byte type, int maxDepth) throws IOException { 77 | ExceptionBiFunction, IOException> f; 78 | if ((f = readers.get(type)) == null) { 79 | throw new IOException("invalid tag id \"" + type + "\""); 80 | } 81 | return f.accept(this, maxDepth); 82 | } 83 | 84 | private static ByteTag readByte(LittleEndianNBTInputStream in) throws IOException { 85 | return new ByteTag(in.readByte()); 86 | } 87 | 88 | private static ShortTag readShort(LittleEndianNBTInputStream in) throws IOException { 89 | return new ShortTag(in.readShort()); 90 | } 91 | 92 | private static IntTag readInt(LittleEndianNBTInputStream in) throws IOException { 93 | return new IntTag(in.readInt()); 94 | } 95 | 96 | private static LongTag readLong(LittleEndianNBTInputStream in) throws IOException { 97 | return new LongTag(in.readLong()); 98 | } 99 | 100 | private static FloatTag readFloat(LittleEndianNBTInputStream in) throws IOException { 101 | return new FloatTag(in.readFloat()); 102 | } 103 | 104 | private static DoubleTag readDouble(LittleEndianNBTInputStream in) throws IOException { 105 | return new DoubleTag(in.readDouble()); 106 | } 107 | 108 | private static StringTag readString(LittleEndianNBTInputStream in) throws IOException { 109 | return new StringTag(in.readUTF()); 110 | } 111 | 112 | private static ByteArrayTag readByteArray(LittleEndianNBTInputStream in) throws IOException { 113 | ByteArrayTag bat = new ByteArrayTag(new byte[in.readInt()]); 114 | in.readFully(bat.getValue()); 115 | return bat; 116 | } 117 | 118 | private static IntArrayTag readIntArray(LittleEndianNBTInputStream in) throws IOException { 119 | int l = in.readInt(); 120 | int[] data = new int[l]; 121 | IntArrayTag iat = new IntArrayTag(data); 122 | for (int i = 0; i < l; i++) { 123 | data[i] = in.readInt(); 124 | } 125 | return iat; 126 | } 127 | 128 | private static LongArrayTag readLongArray(LittleEndianNBTInputStream in) throws IOException { 129 | int l = in.readInt(); 130 | long[] data = new long[l]; 131 | LongArrayTag iat = new LongArrayTag(data); 132 | for (int i = 0; i < l; i++) { 133 | data[i] = in.readLong(); 134 | } 135 | return iat; 136 | } 137 | 138 | private static ListTag readListTag(LittleEndianNBTInputStream in, int maxDepth) throws IOException { 139 | byte listType = in.readByte(); 140 | ListTag list = ListTag.createUnchecked(idClassMapping.get(listType)); 141 | int length = in.readInt(); 142 | if (length < 0) { 143 | length = 0; 144 | } 145 | for (int i = 0; i < length; i++) { 146 | list.addUnchecked(in.readTag(listType, in.decrementMaxDepth(maxDepth))); 147 | } 148 | return list; 149 | } 150 | 151 | private static CompoundTag readCompound(LittleEndianNBTInputStream in, int maxDepth) throws IOException { 152 | CompoundTag comp = new CompoundTag(); 153 | for (int id = in.readByte() & 0xFF; id != 0; id = in.readByte() & 0xFF) { 154 | String key = in.readUTF(); 155 | Tag element = in.readTag((byte) id, in.decrementMaxDepth(maxDepth)); 156 | comp.put(key, element); 157 | } 158 | return comp; 159 | } 160 | 161 | @Override 162 | public void readFully(byte[] b) throws IOException { 163 | input.readFully(b); 164 | } 165 | 166 | @Override 167 | public void readFully(byte[] b, int off, int len) throws IOException { 168 | input.readFully(b, off, len); 169 | } 170 | 171 | @Override 172 | public int skipBytes(int n) throws IOException { 173 | return input.skipBytes(n); 174 | } 175 | 176 | @Override 177 | public boolean readBoolean() throws IOException { 178 | return input.readBoolean(); 179 | } 180 | 181 | @Override 182 | public byte readByte() throws IOException { 183 | return input.readByte(); 184 | } 185 | 186 | @Override 187 | public int readUnsignedByte() throws IOException { 188 | return input.readUnsignedByte(); 189 | } 190 | 191 | @Override 192 | public short readShort() throws IOException { 193 | return Short.reverseBytes(input.readShort()); 194 | } 195 | 196 | public int readUnsignedShort() throws IOException { 197 | return Short.toUnsignedInt(Short.reverseBytes(input.readShort())); 198 | } 199 | 200 | @Override 201 | public char readChar() throws IOException { 202 | return Character.reverseBytes(input.readChar()); 203 | } 204 | 205 | @Override 206 | public int readInt() throws IOException { 207 | return Integer.reverseBytes(input.readInt()); 208 | } 209 | 210 | @Override 211 | public long readLong() throws IOException { 212 | return Long.reverseBytes(input.readLong()); 213 | } 214 | 215 | @Override 216 | public float readFloat() throws IOException { 217 | return Float.intBitsToFloat(Integer.reverseBytes(input.readInt())); 218 | } 219 | 220 | @Override 221 | public double readDouble() throws IOException { 222 | return Double.longBitsToDouble(Long.reverseBytes(input.readLong())); 223 | } 224 | 225 | @Override 226 | @Deprecated 227 | public String readLine() throws IOException { 228 | return input.readLine(); 229 | } 230 | 231 | @Override 232 | public void close() throws IOException { 233 | input.close(); 234 | } 235 | 236 | @Override 237 | public String readUTF() throws IOException { 238 | byte[] bytes = new byte[readUnsignedShort()]; 239 | readFully(bytes); 240 | return new String(bytes, StandardCharsets.UTF_8); 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/nbt/io/LittleEndianNBTOutputStream.java: -------------------------------------------------------------------------------- 1 | /* Vendored version of Quertz NBT 6.1 - https://github.com/Querz/NBT */ 2 | package net.sandrohc.schematic4j.nbt.io; 3 | 4 | import java.io.Closeable; 5 | import java.io.DataOutput; 6 | import java.io.DataOutputStream; 7 | import java.io.IOException; 8 | import java.io.OutputStream; 9 | import java.nio.charset.StandardCharsets; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | import net.sandrohc.schematic4j.nbt.ExceptionTriConsumer; 14 | import net.sandrohc.schematic4j.nbt.MaxDepthIO; 15 | import net.sandrohc.schematic4j.nbt.tag.ByteArrayTag; 16 | import net.sandrohc.schematic4j.nbt.tag.ByteTag; 17 | import net.sandrohc.schematic4j.nbt.tag.CompoundTag; 18 | import net.sandrohc.schematic4j.nbt.tag.DoubleTag; 19 | import net.sandrohc.schematic4j.nbt.tag.EndTag; 20 | import net.sandrohc.schematic4j.nbt.tag.FloatTag; 21 | import net.sandrohc.schematic4j.nbt.tag.IntArrayTag; 22 | import net.sandrohc.schematic4j.nbt.tag.IntTag; 23 | import net.sandrohc.schematic4j.nbt.tag.ListTag; 24 | import net.sandrohc.schematic4j.nbt.tag.LongArrayTag; 25 | import net.sandrohc.schematic4j.nbt.tag.LongTag; 26 | import net.sandrohc.schematic4j.nbt.tag.ShortTag; 27 | import net.sandrohc.schematic4j.nbt.tag.StringTag; 28 | import net.sandrohc.schematic4j.nbt.tag.Tag; 29 | 30 | public class LittleEndianNBTOutputStream implements DataOutput, NBTOutput, MaxDepthIO, Closeable { 31 | 32 | private final DataOutputStream output; 33 | 34 | private static Map, Integer, IOException>> writers = new HashMap<>(); 35 | private static Map, Byte> classIdMapping = new HashMap<>(); 36 | 37 | static { 38 | put(EndTag.ID, (o, t, d) -> {}, EndTag.class); 39 | put(ByteTag.ID, (o, t, d) -> writeByte(o, t), ByteTag.class); 40 | put(ShortTag.ID, (o, t, d) -> writeShort(o, t), ShortTag.class); 41 | put(IntTag.ID, (o, t, d) -> writeInt(o, t), IntTag.class); 42 | put(LongTag.ID, (o, t, d) -> writeLong(o, t), LongTag.class); 43 | put(FloatTag.ID, (o, t, d) -> writeFloat(o, t), FloatTag.class); 44 | put(DoubleTag.ID, (o, t, d) -> writeDouble(o, t), DoubleTag.class); 45 | put(ByteArrayTag.ID, (o, t, d) -> writeByteArray(o, t), ByteArrayTag.class); 46 | put(StringTag.ID, (o, t, d) -> writeString(o, t), StringTag.class); 47 | put(ListTag.ID, LittleEndianNBTOutputStream::writeList, ListTag.class); 48 | put(CompoundTag.ID, LittleEndianNBTOutputStream::writeCompound, CompoundTag.class); 49 | put(IntArrayTag.ID, (o, t, d) -> writeIntArray(o, t), IntArrayTag.class); 50 | put(LongArrayTag.ID, (o, t, d) -> writeLongArray(o, t), LongArrayTag.class); 51 | } 52 | 53 | private static void put(byte id, ExceptionTriConsumer, Integer, IOException> f, Class clazz) { 54 | writers.put(id, f); 55 | classIdMapping.put(clazz, id); 56 | } 57 | 58 | public LittleEndianNBTOutputStream(OutputStream out) { 59 | output = new DataOutputStream(out); 60 | } 61 | 62 | public LittleEndianNBTOutputStream(DataOutputStream out) { 63 | output = out; 64 | } 65 | 66 | public void writeTag(NamedTag tag, int maxDepth) throws IOException { 67 | writeByte(tag.getTag().getID()); 68 | if (tag.getTag().getID() != 0) { 69 | writeUTF(tag.getName() == null ? "" : tag.getName()); 70 | } 71 | writeRawTag(tag.getTag(), maxDepth); 72 | } 73 | 74 | public void writeTag(Tag tag, int maxDepth) throws IOException { 75 | writeByte(tag.getID()); 76 | if (tag.getID() != 0) { 77 | writeUTF(""); 78 | } 79 | writeRawTag(tag, maxDepth); 80 | } 81 | 82 | public void writeRawTag(Tag tag, int maxDepth) throws IOException { 83 | ExceptionTriConsumer, Integer, IOException> f; 84 | if ((f = writers.get(tag.getID())) == null) { 85 | throw new IOException("invalid tag \"" + tag.getID() + "\""); 86 | } 87 | f.accept(this, tag, maxDepth); 88 | } 89 | 90 | static byte idFromClass(Class clazz) { 91 | Byte id = classIdMapping.get(clazz); 92 | if (id == null) { 93 | throw new IllegalArgumentException("unknown Tag class " + clazz.getName()); 94 | } 95 | return id; 96 | } 97 | 98 | private static void writeByte(LittleEndianNBTOutputStream out, Tag tag) throws IOException { 99 | out.writeByte(((ByteTag) tag).asByte()); 100 | } 101 | 102 | private static void writeShort(LittleEndianNBTOutputStream out, Tag tag) throws IOException { 103 | out.writeShort(((ShortTag) tag).asShort()); 104 | } 105 | 106 | private static void writeInt(LittleEndianNBTOutputStream out, Tag tag) throws IOException { 107 | out.writeInt(((IntTag) tag).asInt()); 108 | } 109 | 110 | private static void writeLong(LittleEndianNBTOutputStream out, Tag tag) throws IOException { 111 | out.writeLong(((LongTag) tag).asLong()); 112 | } 113 | 114 | private static void writeFloat(LittleEndianNBTOutputStream out, Tag tag) throws IOException { 115 | out.writeFloat(((FloatTag) tag).asFloat()); 116 | } 117 | 118 | private static void writeDouble(LittleEndianNBTOutputStream out, Tag tag) throws IOException { 119 | out.writeDouble(((DoubleTag) tag).asDouble()); 120 | } 121 | 122 | private static void writeString(LittleEndianNBTOutputStream out, Tag tag) throws IOException { 123 | out.writeUTF(((StringTag) tag).getValue()); 124 | } 125 | 126 | private static void writeByteArray(LittleEndianNBTOutputStream out, Tag tag) throws IOException { 127 | out.writeInt(((ByteArrayTag) tag).length()); 128 | out.write(((ByteArrayTag) tag).getValue()); 129 | } 130 | 131 | private static void writeIntArray(LittleEndianNBTOutputStream out, Tag tag) throws IOException { 132 | out.writeInt(((IntArrayTag) tag).length()); 133 | for (int i : ((IntArrayTag) tag).getValue()) { 134 | out.writeInt(i); 135 | } 136 | } 137 | 138 | private static void writeLongArray(LittleEndianNBTOutputStream out, Tag tag) throws IOException { 139 | out.writeInt(((LongArrayTag) tag).length()); 140 | for (long l : ((LongArrayTag) tag).getValue()) { 141 | out.writeLong(l); 142 | } 143 | } 144 | 145 | private static void writeList(LittleEndianNBTOutputStream out, Tag tag, int maxDepth) throws IOException { 146 | out.writeByte(idFromClass(((ListTag) tag).getTypeClass())); 147 | out.writeInt(((ListTag) tag).size()); 148 | for (Tag t : ((ListTag) tag)) { 149 | out.writeRawTag(t, out.decrementMaxDepth(maxDepth)); 150 | } 151 | } 152 | 153 | private static void writeCompound(LittleEndianNBTOutputStream out, Tag tag, int maxDepth) throws IOException { 154 | for (Map.Entry> entry : (CompoundTag) tag) { 155 | if (entry.getValue().getID() == 0) { 156 | throw new IOException("end tag not allowed"); 157 | } 158 | out.writeByte(entry.getValue().getID()); 159 | out.writeUTF(entry.getKey()); 160 | out.writeRawTag(entry.getValue(), out.decrementMaxDepth(maxDepth)); 161 | } 162 | out.writeByte(0); 163 | } 164 | 165 | @Override 166 | public void close() throws IOException { 167 | output.close(); 168 | } 169 | 170 | @Override 171 | public void flush() throws IOException { 172 | output.flush(); 173 | } 174 | 175 | @Override 176 | public void write(int b) throws IOException { 177 | output.write(b); 178 | } 179 | 180 | @Override 181 | public void write(byte[] b) throws IOException { 182 | output.write(b); 183 | } 184 | 185 | @Override 186 | public void write(byte[] b, int off, int len) throws IOException { 187 | output.write(b, off, len); 188 | } 189 | 190 | @Override 191 | public void writeBoolean(boolean v) throws IOException { 192 | output.writeBoolean(v); 193 | } 194 | 195 | @Override 196 | public void writeByte(int v) throws IOException { 197 | output.writeByte(v); 198 | } 199 | 200 | @Override 201 | public void writeShort(int v) throws IOException { 202 | output.writeShort(Short.reverseBytes((short) v)); 203 | } 204 | 205 | @Override 206 | public void writeChar(int v) throws IOException { 207 | output.writeChar(Character.reverseBytes((char) v)); 208 | } 209 | 210 | @Override 211 | public void writeInt(int v) throws IOException { 212 | output.writeInt(Integer.reverseBytes(v)); 213 | } 214 | 215 | @Override 216 | public void writeLong(long v) throws IOException { 217 | output.writeLong(Long.reverseBytes(v)); 218 | } 219 | 220 | @Override 221 | public void writeFloat(float v) throws IOException { 222 | output.writeInt(Integer.reverseBytes(Float.floatToIntBits(v))); 223 | } 224 | 225 | @Override 226 | public void writeDouble(double v) throws IOException { 227 | output.writeLong(Long.reverseBytes(Double.doubleToLongBits(v))); 228 | } 229 | 230 | @Override 231 | public void writeBytes(String s) throws IOException { 232 | output.writeBytes(s); 233 | } 234 | 235 | @Override 236 | public void writeChars(String s) throws IOException { 237 | output.writeChars(s); 238 | } 239 | 240 | @Override 241 | public void writeUTF(String s) throws IOException { 242 | byte[] bytes = s.getBytes(StandardCharsets.UTF_8); 243 | writeShort(bytes.length); 244 | write(bytes); 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/nbt/io/NBTDeserializer.java: -------------------------------------------------------------------------------- 1 | /* Vendored version of Quertz NBT 6.1 - https://github.com/Querz/NBT */ 2 | package net.sandrohc.schematic4j.nbt.io; 3 | 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.util.zip.GZIPInputStream; 7 | 8 | import net.sandrohc.schematic4j.nbt.Deserializer; 9 | import net.sandrohc.schematic4j.nbt.tag.Tag; 10 | 11 | public class NBTDeserializer implements Deserializer { 12 | 13 | private boolean compressed, littleEndian; 14 | 15 | public NBTDeserializer() { 16 | this(true); 17 | } 18 | 19 | public NBTDeserializer(boolean compressed) { 20 | this.compressed = compressed; 21 | } 22 | 23 | public NBTDeserializer(boolean compressed, boolean littleEndian) { 24 | this.compressed = compressed; 25 | this.littleEndian = littleEndian; 26 | } 27 | 28 | @Override 29 | public NamedTag fromStream(InputStream stream) throws IOException { 30 | NBTInput nbtIn; 31 | InputStream input; 32 | if (compressed) { 33 | input = new GZIPInputStream(stream); 34 | } else { 35 | input = stream; 36 | } 37 | 38 | if (littleEndian) { 39 | nbtIn = new LittleEndianNBTInputStream(input); 40 | } else { 41 | nbtIn = new NBTInputStream(input); 42 | } 43 | return nbtIn.readTag(Tag.DEFAULT_MAX_DEPTH); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/nbt/io/NBTInput.java: -------------------------------------------------------------------------------- 1 | /* Vendored version of Quertz NBT 6.1 - https://github.com/Querz/NBT */ 2 | package net.sandrohc.schematic4j.nbt.io; 3 | 4 | import java.io.IOException; 5 | 6 | import net.sandrohc.schematic4j.nbt.tag.Tag; 7 | 8 | /** 9 | * A generic NBT input 10 | */ 11 | public interface NBTInput { 12 | 13 | /** 14 | * Read a named tag from the input. 15 | * 16 | * @param maxDepth Maximum depth before failing deserialization 17 | * @return The named tag read 18 | * @throws IOException In case of error reading from the input 19 | */ 20 | NamedTag readTag(int maxDepth) throws IOException; 21 | 22 | /** 23 | * Read a tag from the input. 24 | * 25 | * @param maxDepth Maximum depth before failing deserialization 26 | * @return The tag read 27 | * @throws IOException In case of error reading from the input 28 | */ 29 | Tag readRawTag(int maxDepth) throws IOException; 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/nbt/io/NBTInputStream.java: -------------------------------------------------------------------------------- 1 | /* Vendored version of Quertz NBT 6.1 - https://github.com/Querz/NBT */ 2 | package net.sandrohc.schematic4j.nbt.io; 3 | 4 | import java.io.DataInputStream; 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | import net.sandrohc.schematic4j.nbt.ExceptionBiFunction; 11 | import net.sandrohc.schematic4j.nbt.MaxDepthIO; 12 | import net.sandrohc.schematic4j.nbt.tag.ByteArrayTag; 13 | import net.sandrohc.schematic4j.nbt.tag.ByteTag; 14 | import net.sandrohc.schematic4j.nbt.tag.CompoundTag; 15 | import net.sandrohc.schematic4j.nbt.tag.DoubleTag; 16 | import net.sandrohc.schematic4j.nbt.tag.EndTag; 17 | import net.sandrohc.schematic4j.nbt.tag.FloatTag; 18 | import net.sandrohc.schematic4j.nbt.tag.IntArrayTag; 19 | import net.sandrohc.schematic4j.nbt.tag.IntTag; 20 | import net.sandrohc.schematic4j.nbt.tag.ListTag; 21 | import net.sandrohc.schematic4j.nbt.tag.LongArrayTag; 22 | import net.sandrohc.schematic4j.nbt.tag.LongTag; 23 | import net.sandrohc.schematic4j.nbt.tag.ShortTag; 24 | import net.sandrohc.schematic4j.nbt.tag.StringTag; 25 | import net.sandrohc.schematic4j.nbt.tag.Tag; 26 | 27 | public class NBTInputStream extends DataInputStream implements NBTInput, MaxDepthIO { 28 | 29 | private static Map, IOException>> readers = new HashMap<>(); 30 | private static Map> idClassMapping = new HashMap<>(); 31 | 32 | static { 33 | put(EndTag.ID, (i, d) -> EndTag.INSTANCE, EndTag.class); 34 | put(ByteTag.ID, (i, d) -> readByte(i), ByteTag.class); 35 | put(ShortTag.ID, (i, d) -> readShort(i), ShortTag.class); 36 | put(IntTag.ID, (i, d) -> readInt(i), IntTag.class); 37 | put(LongTag.ID, (i, d) -> readLong(i), LongTag.class); 38 | put(FloatTag.ID, (i, d) -> readFloat(i), FloatTag.class); 39 | put(DoubleTag.ID, (i, d) -> readDouble(i), DoubleTag.class); 40 | put(ByteArrayTag.ID, (i, d) -> readByteArray(i), ByteArrayTag.class); 41 | put(StringTag.ID, (i, d) -> readString(i), StringTag.class); 42 | put(ListTag.ID, NBTInputStream::readListTag, ListTag.class); 43 | put(CompoundTag.ID, NBTInputStream::readCompound, CompoundTag.class); 44 | put(IntArrayTag.ID, (i, d) -> readIntArray(i), IntArrayTag.class); 45 | put(LongArrayTag.ID, (i, d) -> readLongArray(i), LongArrayTag.class); 46 | } 47 | 48 | private static void put(byte id, ExceptionBiFunction, IOException> reader, Class clazz) { 49 | readers.put(id, reader); 50 | idClassMapping.put(id, clazz); 51 | } 52 | 53 | public NBTInputStream(InputStream in) { 54 | super(in); 55 | } 56 | 57 | public NamedTag readTag(int maxDepth) throws IOException { 58 | byte id = readByte(); 59 | return new NamedTag(readUTF(), readTag(id, maxDepth)); 60 | } 61 | 62 | public Tag readRawTag(int maxDepth) throws IOException { 63 | byte id = readByte(); 64 | return readTag(id, maxDepth); 65 | } 66 | 67 | private Tag readTag(byte type, int maxDepth) throws IOException { 68 | ExceptionBiFunction, IOException> f; 69 | if ((f = readers.get(type)) == null) { 70 | throw new IOException("invalid tag id \"" + type + "\""); 71 | } 72 | return f.accept(this, maxDepth); 73 | } 74 | 75 | private static ByteTag readByte(NBTInputStream in) throws IOException { 76 | return new ByteTag(in.readByte()); 77 | } 78 | 79 | private static ShortTag readShort(NBTInputStream in) throws IOException { 80 | return new ShortTag(in.readShort()); 81 | } 82 | 83 | private static IntTag readInt(NBTInputStream in) throws IOException { 84 | return new IntTag(in.readInt()); 85 | } 86 | 87 | private static LongTag readLong(NBTInputStream in) throws IOException { 88 | return new LongTag(in.readLong()); 89 | } 90 | 91 | private static FloatTag readFloat(NBTInputStream in) throws IOException { 92 | return new FloatTag(in.readFloat()); 93 | } 94 | 95 | private static DoubleTag readDouble(NBTInputStream in) throws IOException { 96 | return new DoubleTag(in.readDouble()); 97 | } 98 | 99 | private static StringTag readString(NBTInputStream in) throws IOException { 100 | return new StringTag(in.readUTF()); 101 | } 102 | 103 | private static ByteArrayTag readByteArray(NBTInputStream in) throws IOException { 104 | ByteArrayTag bat = new ByteArrayTag(new byte[in.readInt()]); 105 | in.readFully(bat.getValue()); 106 | return bat; 107 | } 108 | 109 | private static IntArrayTag readIntArray(NBTInputStream in) throws IOException { 110 | int l = in.readInt(); 111 | int[] data = new int[l]; 112 | IntArrayTag iat = new IntArrayTag(data); 113 | for (int i = 0; i < l; i++) { 114 | data[i] = in.readInt(); 115 | } 116 | return iat; 117 | } 118 | 119 | private static LongArrayTag readLongArray(NBTInputStream in) throws IOException { 120 | int l = in.readInt(); 121 | long[] data = new long[l]; 122 | LongArrayTag iat = new LongArrayTag(data); 123 | for (int i = 0; i < l; i++) { 124 | data[i] = in.readLong(); 125 | } 126 | return iat; 127 | } 128 | 129 | private static ListTag readListTag(NBTInputStream in, int maxDepth) throws IOException { 130 | byte listType = in.readByte(); 131 | ListTag list = ListTag.createUnchecked(idClassMapping.get(listType)); 132 | int length = in.readInt(); 133 | if (length < 0) { 134 | length = 0; 135 | } 136 | for (int i = 0; i < length; i++) { 137 | list.addUnchecked(in.readTag(listType, in.decrementMaxDepth(maxDepth))); 138 | } 139 | return list; 140 | } 141 | 142 | private static CompoundTag readCompound(NBTInputStream in, int maxDepth) throws IOException { 143 | CompoundTag comp = new CompoundTag(); 144 | for (int id = in.readByte() & 0xFF; id != 0; id = in.readByte() & 0xFF) { 145 | String key = in.readUTF(); 146 | Tag element = in.readTag((byte) id, in.decrementMaxDepth(maxDepth)); 147 | comp.put(key, element); 148 | } 149 | return comp; 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/nbt/io/NBTOutput.java: -------------------------------------------------------------------------------- 1 | /* Vendored version of Quertz NBT 6.1 - https://github.com/Querz/NBT */ 2 | package net.sandrohc.schematic4j.nbt.io; 3 | 4 | import java.io.IOException; 5 | 6 | import net.sandrohc.schematic4j.nbt.tag.Tag; 7 | 8 | public interface NBTOutput { 9 | 10 | void writeTag(NamedTag tag, int maxDepth) throws IOException; 11 | 12 | void writeTag(Tag tag, int maxDepth) throws IOException; 13 | 14 | void flush() throws IOException; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/nbt/io/NBTOutputStream.java: -------------------------------------------------------------------------------- 1 | /* Vendored version of Quertz NBT 6.1 - https://github.com/Querz/NBT */ 2 | package net.sandrohc.schematic4j.nbt.io; 3 | 4 | import java.io.DataOutputStream; 5 | import java.io.IOException; 6 | import java.io.OutputStream; 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | import net.sandrohc.schematic4j.nbt.ExceptionTriConsumer; 11 | import net.sandrohc.schematic4j.nbt.MaxDepthIO; 12 | import net.sandrohc.schematic4j.nbt.tag.ByteArrayTag; 13 | import net.sandrohc.schematic4j.nbt.tag.ByteTag; 14 | import net.sandrohc.schematic4j.nbt.tag.CompoundTag; 15 | import net.sandrohc.schematic4j.nbt.tag.DoubleTag; 16 | import net.sandrohc.schematic4j.nbt.tag.EndTag; 17 | import net.sandrohc.schematic4j.nbt.tag.FloatTag; 18 | import net.sandrohc.schematic4j.nbt.tag.IntArrayTag; 19 | import net.sandrohc.schematic4j.nbt.tag.IntTag; 20 | import net.sandrohc.schematic4j.nbt.tag.ListTag; 21 | import net.sandrohc.schematic4j.nbt.tag.LongArrayTag; 22 | import net.sandrohc.schematic4j.nbt.tag.LongTag; 23 | import net.sandrohc.schematic4j.nbt.tag.ShortTag; 24 | import net.sandrohc.schematic4j.nbt.tag.StringTag; 25 | import net.sandrohc.schematic4j.nbt.tag.Tag; 26 | 27 | public class NBTOutputStream extends DataOutputStream implements NBTOutput, MaxDepthIO { 28 | 29 | private static Map, Integer, IOException>> writers = new HashMap<>(); 30 | private static Map, Byte> classIdMapping = new HashMap<>(); 31 | 32 | static { 33 | put(EndTag.ID, (o, t, d) -> {}, EndTag.class); 34 | put(ByteTag.ID, (o, t, d) -> writeByte(o, t), ByteTag.class); 35 | put(ShortTag.ID, (o, t, d) -> writeShort(o, t), ShortTag.class); 36 | put(IntTag.ID, (o, t, d) -> writeInt(o, t), IntTag.class); 37 | put(LongTag.ID, (o, t, d) -> writeLong(o, t), LongTag.class); 38 | put(FloatTag.ID, (o, t, d) -> writeFloat(o, t), FloatTag.class); 39 | put(DoubleTag.ID, (o, t, d) -> writeDouble(o, t), DoubleTag.class); 40 | put(ByteArrayTag.ID, (o, t, d) -> writeByteArray(o, t), ByteArrayTag.class); 41 | put(StringTag.ID, (o, t, d) -> writeString(o, t), StringTag.class); 42 | put(ListTag.ID, NBTOutputStream::writeList, ListTag.class); 43 | put(CompoundTag.ID, NBTOutputStream::writeCompound, CompoundTag.class); 44 | put(IntArrayTag.ID, (o, t, d) -> writeIntArray(o, t), IntArrayTag.class); 45 | put(LongArrayTag.ID, (o, t, d) -> writeLongArray(o, t), LongArrayTag.class); 46 | } 47 | 48 | private static void put(byte id, ExceptionTriConsumer, Integer, IOException> f, Class clazz) { 49 | writers.put(id, f); 50 | classIdMapping.put(clazz, id); 51 | } 52 | 53 | public NBTOutputStream(OutputStream out) { 54 | super(out); 55 | } 56 | 57 | public void writeTag(NamedTag tag, int maxDepth) throws IOException { 58 | writeByte(tag.getTag().getID()); 59 | if (tag.getTag().getID() != 0) { 60 | writeUTF(tag.getName() == null ? "" : tag.getName()); 61 | } 62 | writeRawTag(tag.getTag(), maxDepth); 63 | } 64 | 65 | public void writeTag(Tag tag, int maxDepth) throws IOException { 66 | writeByte(tag.getID()); 67 | if (tag.getID() != 0) { 68 | writeUTF(""); 69 | } 70 | writeRawTag(tag, maxDepth); 71 | } 72 | 73 | public void writeRawTag(Tag tag, int maxDepth) throws IOException { 74 | ExceptionTriConsumer, Integer, IOException> f; 75 | if ((f = writers.get(tag.getID())) == null) { 76 | throw new IOException("invalid tag \"" + tag.getID() + "\""); 77 | } 78 | f.accept(this, tag, maxDepth); 79 | } 80 | 81 | static byte idFromClass(Class clazz) { 82 | Byte id = classIdMapping.get(clazz); 83 | if (id == null) { 84 | throw new IllegalArgumentException("unknown Tag class " + clazz.getName()); 85 | } 86 | return id; 87 | } 88 | 89 | private static void writeByte(NBTOutputStream out, Tag tag) throws IOException { 90 | out.writeByte(((ByteTag) tag).asByte()); 91 | } 92 | 93 | private static void writeShort(NBTOutputStream out, Tag tag) throws IOException { 94 | out.writeShort(((ShortTag) tag).asShort()); 95 | } 96 | 97 | private static void writeInt(NBTOutputStream out, Tag tag) throws IOException { 98 | out.writeInt(((IntTag) tag).asInt()); 99 | } 100 | 101 | private static void writeLong(NBTOutputStream out, Tag tag) throws IOException { 102 | out.writeLong(((LongTag) tag).asLong()); 103 | } 104 | 105 | private static void writeFloat(NBTOutputStream out, Tag tag) throws IOException { 106 | out.writeFloat(((FloatTag) tag).asFloat()); 107 | } 108 | 109 | private static void writeDouble(NBTOutputStream out, Tag tag) throws IOException { 110 | out.writeDouble(((DoubleTag) tag).asDouble()); 111 | } 112 | 113 | private static void writeString(NBTOutputStream out, Tag tag) throws IOException { 114 | out.writeUTF(((StringTag) tag).getValue()); 115 | } 116 | 117 | private static void writeByteArray(NBTOutputStream out, Tag tag) throws IOException { 118 | out.writeInt(((ByteArrayTag) tag).length()); 119 | out.write(((ByteArrayTag) tag).getValue()); 120 | } 121 | 122 | private static void writeIntArray(NBTOutputStream out, Tag tag) throws IOException { 123 | out.writeInt(((IntArrayTag) tag).length()); 124 | for (int i : ((IntArrayTag) tag).getValue()) { 125 | out.writeInt(i); 126 | } 127 | } 128 | 129 | private static void writeLongArray(NBTOutputStream out, Tag tag) throws IOException { 130 | out.writeInt(((LongArrayTag) tag).length()); 131 | for (long l : ((LongArrayTag) tag).getValue()) { 132 | out.writeLong(l); 133 | } 134 | } 135 | 136 | private static void writeList(NBTOutputStream out, Tag tag, int maxDepth) throws IOException { 137 | out.writeByte(idFromClass(((ListTag) tag).getTypeClass())); 138 | out.writeInt(((ListTag) tag).size()); 139 | for (Tag t : ((ListTag) tag)) { 140 | out.writeRawTag(t, out.decrementMaxDepth(maxDepth)); 141 | } 142 | } 143 | 144 | private static void writeCompound(NBTOutputStream out, Tag tag, int maxDepth) throws IOException { 145 | for (Map.Entry> entry : (CompoundTag) tag) { 146 | if (entry.getValue().getID() == 0) { 147 | throw new IOException("end tag not allowed"); 148 | } 149 | out.writeByte(entry.getValue().getID()); 150 | out.writeUTF(entry.getKey()); 151 | out.writeRawTag(entry.getValue(), out.decrementMaxDepth(maxDepth)); 152 | } 153 | out.writeByte(0); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/nbt/io/NBTSerializer.java: -------------------------------------------------------------------------------- 1 | /* Vendored version of Quertz NBT 6.1 - https://github.com/Querz/NBT */ 2 | package net.sandrohc.schematic4j.nbt.io; 3 | 4 | import java.io.IOException; 5 | import java.io.OutputStream; 6 | import java.util.zip.GZIPOutputStream; 7 | 8 | import net.sandrohc.schematic4j.nbt.Serializer; 9 | import net.sandrohc.schematic4j.nbt.tag.Tag; 10 | 11 | public class NBTSerializer implements Serializer { 12 | 13 | private boolean compressed, littleEndian; 14 | 15 | public NBTSerializer() { 16 | this(true); 17 | } 18 | 19 | public NBTSerializer(boolean compressed) { 20 | this.compressed = compressed; 21 | } 22 | 23 | public NBTSerializer(boolean compressed, boolean littleEndian) { 24 | this.compressed = compressed; 25 | this.littleEndian = littleEndian; 26 | } 27 | 28 | @Override 29 | public void toStream(NamedTag object, OutputStream out) throws IOException { 30 | NBTOutput nbtOut; 31 | OutputStream output; 32 | if (compressed) { 33 | output = new GZIPOutputStream(out, true); 34 | } else { 35 | output = out; 36 | } 37 | 38 | if (littleEndian) { 39 | nbtOut = new LittleEndianNBTOutputStream(output); 40 | } else { 41 | nbtOut = new NBTOutputStream(output); 42 | } 43 | nbtOut.writeTag(object, Tag.DEFAULT_MAX_DEPTH); 44 | nbtOut.flush(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/nbt/io/NBTUtil.java: -------------------------------------------------------------------------------- 1 | /* Vendored version of Quertz NBT 6.1 - https://github.com/Querz/NBT */ 2 | package net.sandrohc.schematic4j.nbt.io; 3 | 4 | import java.io.BufferedInputStream; 5 | import java.io.BufferedOutputStream; 6 | import java.io.ByteArrayInputStream; 7 | import java.io.File; 8 | import java.io.IOException; 9 | import java.io.InputStream; 10 | import java.io.OutputStream; 11 | import java.io.PushbackInputStream; 12 | import java.nio.file.Files; 13 | import java.nio.file.Path; 14 | import java.nio.file.Paths; 15 | import java.util.zip.GZIPInputStream; 16 | 17 | import net.sandrohc.schematic4j.nbt.tag.Tag; 18 | 19 | public final class NBTUtil { 20 | 21 | private NBTUtil() { 22 | } 23 | 24 | /** 25 | * Writer helper that follows the builder pattern. 26 | *

27 | * Usage example: 28 | *

{@code Writer.write(nbtTag)
 29 | 	 *     .littleEndian()
 30 | 	 *     .compressed(false)
 31 | 	 *     .to("file.schematic")}
32 | */ 33 | public static class Writer { 34 | 35 | public final NamedTag tag; 36 | private boolean compressed = true; 37 | private boolean littleEndian = false; 38 | 39 | private Writer(NamedTag tag) { 40 | this.tag = tag; 41 | } 42 | 43 | public static Writer write(NamedTag tag) { 44 | return new Writer(tag); 45 | } 46 | 47 | public static Writer write(Tag tag) { 48 | return new Writer(new NamedTag(null, tag)); 49 | } 50 | 51 | /** 52 | * Toggle compression for the output. GZIP compression is used. 53 | * 54 | * @param compressed Whether the output should be compressed or not 55 | * @return The writer builder 56 | */ 57 | public Writer compressed(boolean compressed) { 58 | this.compressed = compressed; 59 | return this; 60 | } 61 | 62 | /** 63 | * Write to the output as Little Endian. Usually reserved for network packets or some systems architectures. 64 | * 65 | * @return the writer builder 66 | */ 67 | public Writer littleEndian() { 68 | this.littleEndian = true; 69 | return this; 70 | } 71 | 72 | /** 73 | * Write to the output as Big Endian. This is the default. 74 | * 75 | * @return the writer builder 76 | */ 77 | public Writer bigEndian() { 78 | this.littleEndian = false; 79 | return this; 80 | } 81 | 82 | /** 83 | * Writes the NBT tag to an output stream. Terminal operator. 84 | * 85 | * @param os The output stream to write to 86 | * @throws IOException In case of error writing to the output stream 87 | */ 88 | public void to(OutputStream os) throws IOException { 89 | if (tag == null) 90 | throw new IllegalStateException("tag must be set"); 91 | if (os == null) 92 | throw new IllegalStateException("output must be set"); 93 | 94 | new NBTSerializer(compressed, littleEndian).toStream(tag, os); 95 | } 96 | 97 | /** 98 | * Writes the NBT tag to a file. Terminal operator. 99 | * 100 | * @param path The file path fo write to 101 | * @throws IOException In case of error writing to the file 102 | */ 103 | public void to(Path path) throws IOException { 104 | try (OutputStream os = new BufferedOutputStream(Files.newOutputStream(path))) { 105 | to(os); 106 | } 107 | } 108 | 109 | /** 110 | * Writes the NBT tag to a file. Terminal operator. 111 | * 112 | * @param file The file path fo write to 113 | * @throws IOException In case of error writing to the file 114 | */ 115 | public void to(File file) throws IOException { 116 | to(file.toPath()); 117 | } 118 | 119 | /** 120 | * Writes the NBT tag to a file. Terminal operator. 121 | * 122 | * @param file The file path fo write to 123 | * @throws IOException In case of error writing to the file 124 | */ 125 | public void to(String file) throws IOException { 126 | to(Paths.get(file)); 127 | } 128 | } 129 | 130 | /** 131 | * Reader helper that follows the builder pattern. 132 | *

133 | * Usage example: 134 | *

{@code Reader.read()
135 | 	 *     .littleEndian()
136 | 	 *     .from("file.schematic")}
137 | */ 138 | public static class Reader { 139 | 140 | private boolean littleEndian = false; 141 | 142 | public Reader() { 143 | } 144 | 145 | public static Reader read() { 146 | return new Reader(); 147 | } 148 | 149 | /** 150 | * Read from the source as Little Endian. Usually reserved for network packets or some systems architectures. 151 | * 152 | * @return the reader builder 153 | */ 154 | public Reader littleEndian() { 155 | this.littleEndian = true; 156 | return this; 157 | } 158 | 159 | /** 160 | * Read from the source as Big Endian. This is the default. 161 | * 162 | * @return the reader builder 163 | */ 164 | public Reader bigEndian() { 165 | this.littleEndian = false; 166 | return this; 167 | } 168 | 169 | /** 170 | * Reads the NBT tag from an input stream. Terminal operator. 171 | * 172 | * @param is The input stream to read from 173 | * @return The parsed NBT tag 174 | * @throws IOException In case of error reading from the input stream 175 | */ 176 | public NamedTag from(InputStream is) throws IOException { 177 | return new NBTDeserializer(false/* ignored, will autodetect compression */, littleEndian) 178 | .fromStream(detectDecompression(is)); 179 | } 180 | 181 | /** 182 | * Reads the NBT tag from a byte array. Terminal operator. 183 | * 184 | * @param bytes The byte array 185 | * @return The parsed NBT tag 186 | * @throws IOException In case of error reading from the input stream 187 | */ 188 | public NamedTag from(byte[] bytes) throws IOException { 189 | return from(new ByteArrayInputStream(bytes)); 190 | } 191 | 192 | /** 193 | * Reads the NBT tag from a file. Terminal operator. 194 | * 195 | * @param path The file path to read from 196 | * @return The parsed NBT tag 197 | * @throws IOException In case of error reading from the file 198 | */ 199 | public NamedTag from(Path path) throws IOException { 200 | try (InputStream is = new BufferedInputStream(Files.newInputStream(path))) { 201 | return from(is); 202 | } 203 | } 204 | 205 | /** 206 | * Reads the NBT tag from a file. Terminal operator. 207 | * 208 | * @param file The file path to read from 209 | * @return The parsed NBT tag 210 | * @throws IOException In case of error reading from the file 211 | */ 212 | public NamedTag from(File file) throws IOException { 213 | return from(file.toPath()); 214 | } 215 | 216 | /** 217 | * Reads the NBT tag from a file. Terminal operator. 218 | * 219 | * @param file The file path to read from 220 | * @return The parsed NBT tag 221 | * @throws IOException In case of error reading from the file 222 | */ 223 | public NamedTag from(String file) throws IOException { 224 | return from(Paths.get(file)); 225 | } 226 | 227 | private static InputStream detectDecompression(InputStream is) throws IOException { 228 | PushbackInputStream pbis = new PushbackInputStream(is, 2); 229 | int signature = (pbis.read() & 0xFF) + (pbis.read() << 8); 230 | pbis.unread(signature >> 8); 231 | pbis.unread(signature & 0xFF); 232 | return signature == GZIPInputStream.GZIP_MAGIC ? new GZIPInputStream(pbis) : pbis; 233 | } 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/nbt/io/NamedTag.java: -------------------------------------------------------------------------------- 1 | /* Vendored version of Quertz NBT 6.1 - https://github.com/Querz/NBT */ 2 | package net.sandrohc.schematic4j.nbt.io; 3 | 4 | import net.sandrohc.schematic4j.nbt.tag.Tag; 5 | 6 | /** 7 | * A named tag. 8 | */ 9 | public class NamedTag { 10 | 11 | /** 12 | * The name of the named tag. 13 | */ 14 | private String name; 15 | 16 | /** 17 | * The inner tag. 18 | */ 19 | private Tag tag; 20 | 21 | public NamedTag(String name, Tag tag) { 22 | this.name = name; 23 | this.tag = tag; 24 | } 25 | 26 | /** 27 | * Set a new name. 28 | * 29 | * @param name The new name 30 | */ 31 | public void setName(String name) { 32 | this.name = name; 33 | } 34 | 35 | /** 36 | * Set a new tag. 37 | * 38 | * @param tag The new tag 39 | */ 40 | public void setTag(Tag tag) { 41 | this.tag = tag; 42 | } 43 | 44 | /** 45 | * Get the named tag name. 46 | * 47 | * @return The named tag name 48 | */ 49 | public String getName() { 50 | return name; 51 | } 52 | 53 | /** 54 | * Get the named tag inner tag. 55 | * 56 | * @return The named tag inner tag 57 | */ 58 | public Tag getTag() { 59 | return tag; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/nbt/io/ParseException.java: -------------------------------------------------------------------------------- 1 | /* Vendored version of Quertz NBT 6.1 - https://github.com/Querz/NBT */ 2 | package net.sandrohc.schematic4j.nbt.io; 3 | 4 | import java.io.IOException; 5 | 6 | public class ParseException extends IOException { 7 | 8 | public ParseException(String msg) { 9 | super(msg); 10 | } 11 | 12 | public ParseException(String msg, String value, int index) { 13 | super(msg + " at: " + formatError(value, index)); 14 | } 15 | 16 | private static String formatError(String value, int index) { 17 | StringBuilder builder = new StringBuilder(); 18 | int i = Math.min(value.length(), index); 19 | if (i > 35) { 20 | builder.append("..."); 21 | } 22 | builder.append(value, Math.max(0, i - 35), i); 23 | builder.append("<--[HERE]"); 24 | return builder.toString(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/nbt/io/SNBTDeserializer.java: -------------------------------------------------------------------------------- 1 | /* Vendored version of Quertz NBT 6.1 - https://github.com/Querz/NBT */ 2 | package net.sandrohc.schematic4j.nbt.io; 3 | 4 | import java.io.BufferedReader; 5 | import java.io.IOException; 6 | import java.io.Reader; 7 | import java.util.stream.Collectors; 8 | 9 | import net.sandrohc.schematic4j.nbt.StringDeserializer; 10 | import net.sandrohc.schematic4j.nbt.tag.Tag; 11 | 12 | public class SNBTDeserializer implements StringDeserializer> { 13 | 14 | @Override 15 | public Tag fromReader(Reader reader) throws IOException { 16 | return fromReader(reader, Tag.DEFAULT_MAX_DEPTH); 17 | } 18 | 19 | public Tag fromReader(Reader reader, int maxDepth) throws IOException { 20 | BufferedReader bufferedReader; 21 | if (reader instanceof BufferedReader) { 22 | bufferedReader = (BufferedReader) reader; 23 | } else { 24 | bufferedReader = new BufferedReader(reader); 25 | } 26 | return SNBTParser.parse(bufferedReader.lines().collect(Collectors.joining()), maxDepth); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/nbt/io/SNBTParser.java: -------------------------------------------------------------------------------- 1 | /* Vendored version of Quertz NBT 6.1 - https://github.com/Querz/NBT */ 2 | package net.sandrohc.schematic4j.nbt.io; 3 | 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | import java.util.regex.Pattern; 7 | 8 | import net.sandrohc.schematic4j.nbt.MaxDepthIO; 9 | import net.sandrohc.schematic4j.nbt.tag.ArrayTag; 10 | import net.sandrohc.schematic4j.nbt.tag.ByteArrayTag; 11 | import net.sandrohc.schematic4j.nbt.tag.ByteTag; 12 | import net.sandrohc.schematic4j.nbt.tag.CompoundTag; 13 | import net.sandrohc.schematic4j.nbt.tag.DoubleTag; 14 | import net.sandrohc.schematic4j.nbt.tag.EndTag; 15 | import net.sandrohc.schematic4j.nbt.tag.FloatTag; 16 | import net.sandrohc.schematic4j.nbt.tag.IntArrayTag; 17 | import net.sandrohc.schematic4j.nbt.tag.IntTag; 18 | import net.sandrohc.schematic4j.nbt.tag.ListTag; 19 | import net.sandrohc.schematic4j.nbt.tag.LongArrayTag; 20 | import net.sandrohc.schematic4j.nbt.tag.LongTag; 21 | import net.sandrohc.schematic4j.nbt.tag.ShortTag; 22 | import net.sandrohc.schematic4j.nbt.tag.StringTag; 23 | import net.sandrohc.schematic4j.nbt.tag.Tag; 24 | 25 | public final class SNBTParser implements MaxDepthIO { 26 | 27 | private static final Pattern 28 | FLOAT_LITERAL_PATTERN = Pattern.compile("^[-+]?(?:\\d+\\.?|\\d*\\.\\d+)(?:e[-+]?\\d+)?f$", Pattern.CASE_INSENSITIVE), 29 | DOUBLE_LITERAL_PATTERN = Pattern.compile("^[-+]?(?:\\d+\\.?|\\d*\\.\\d+)(?:e[-+]?\\d+)?d$", Pattern.CASE_INSENSITIVE), 30 | DOUBLE_LITERAL_NO_SUFFIX_PATTERN = Pattern.compile("^[-+]?(?:\\d+\\.|\\d*\\.\\d+)(?:e[-+]?\\d+)?$", Pattern.CASE_INSENSITIVE), 31 | BYTE_LITERAL_PATTERN = Pattern.compile("^[-+]?\\d+b$", Pattern.CASE_INSENSITIVE), 32 | SHORT_LITERAL_PATTERN = Pattern.compile("^[-+]?\\d+s$", Pattern.CASE_INSENSITIVE), 33 | INT_LITERAL_PATTERN = Pattern.compile("^[-+]?\\d+$", Pattern.CASE_INSENSITIVE), 34 | LONG_LITERAL_PATTERN = Pattern.compile("^[-+]?\\d+l$", Pattern.CASE_INSENSITIVE), 35 | NUMBER_PATTERN = Pattern.compile("^[-+]?\\d+$"); 36 | 37 | private StringPointer ptr; 38 | 39 | private SNBTParser(String string) { 40 | this.ptr = new StringPointer(string); 41 | } 42 | 43 | public static Tag parse(String string, int maxDepth) throws ParseException { 44 | SNBTParser parser = new SNBTParser(string); 45 | Tag tag = parser.parseAnything(maxDepth); 46 | parser.ptr.skipWhitespace(); 47 | if (parser.ptr.hasNext()) { 48 | throw parser.ptr.parseException("invalid characters after end of snbt"); 49 | } 50 | return tag; 51 | } 52 | 53 | public static Tag parse(String string) throws ParseException { 54 | return parse(string, Tag.DEFAULT_MAX_DEPTH); 55 | } 56 | 57 | private Tag parseAnything(int maxDepth) throws ParseException { 58 | ptr.skipWhitespace(); 59 | switch (ptr.currentChar()) { 60 | case '{': 61 | return parseCompoundTag(maxDepth); 62 | case '[': 63 | if (ptr.hasCharsLeft(2) && ptr.lookAhead(1) != '"' && ptr.lookAhead(2) == ';') { 64 | return parseNumArray(); 65 | } 66 | return parseListTag(maxDepth); 67 | } 68 | return parseStringOrLiteral(); 69 | } 70 | 71 | private Tag parseStringOrLiteral() throws ParseException { 72 | ptr.skipWhitespace(); 73 | if (ptr.currentChar() == '"') { 74 | return new StringTag(ptr.parseQuotedString()); 75 | } 76 | String s = ptr.parseSimpleString(); 77 | if (s.isEmpty()) { 78 | throw new ParseException("expected non empty value"); 79 | } 80 | if (FLOAT_LITERAL_PATTERN.matcher(s).matches()) { 81 | return new FloatTag(Float.parseFloat(s.substring(0, s.length() - 1))); 82 | } else if (BYTE_LITERAL_PATTERN.matcher(s).matches()) { 83 | try { 84 | return new ByteTag(Byte.parseByte(s.substring(0, s.length() - 1))); 85 | } catch (NumberFormatException ex) { 86 | throw ptr.parseException("byte not in range: \"" + s.substring(0, s.length() - 1) + "\""); 87 | } 88 | } else if (SHORT_LITERAL_PATTERN.matcher(s).matches()) { 89 | try { 90 | return new ShortTag(Short.parseShort(s.substring(0, s.length() - 1))); 91 | } catch (NumberFormatException ex) { 92 | throw ptr.parseException("short not in range: \"" + s.substring(0, s.length() - 1) + "\""); 93 | } 94 | } else if (LONG_LITERAL_PATTERN.matcher(s).matches()) { 95 | try { 96 | return new LongTag(Long.parseLong(s.substring(0, s.length() - 1))); 97 | } catch (NumberFormatException ex) { 98 | throw ptr.parseException("long not in range: \"" + s.substring(0, s.length() - 1) + "\""); 99 | } 100 | } else if (INT_LITERAL_PATTERN.matcher(s).matches()) { 101 | try { 102 | return new IntTag(Integer.parseInt(s)); 103 | } catch (NumberFormatException ex) { 104 | throw ptr.parseException("int not in range: \"" + s.substring(0, s.length() - 1) + "\""); 105 | } 106 | } else if (DOUBLE_LITERAL_PATTERN.matcher(s).matches()) { 107 | return new DoubleTag(Double.parseDouble(s.substring(0, s.length() - 1))); 108 | } else if (DOUBLE_LITERAL_NO_SUFFIX_PATTERN.matcher(s).matches()) { 109 | return new DoubleTag(Double.parseDouble(s)); 110 | } else if ("true".equalsIgnoreCase(s)) { 111 | return new ByteTag(true); 112 | } else if ("false".equalsIgnoreCase(s)) { 113 | return new ByteTag(false); 114 | } 115 | return new StringTag(s); 116 | } 117 | 118 | private CompoundTag parseCompoundTag(int maxDepth) throws ParseException { 119 | ptr.expectChar('{'); 120 | 121 | CompoundTag compoundTag = new CompoundTag(); 122 | 123 | ptr.skipWhitespace(); 124 | while (ptr.hasNext() && ptr.currentChar() != '}') { 125 | ptr.skipWhitespace(); 126 | String key = ptr.currentChar() == '"' ? ptr.parseQuotedString() : ptr.parseSimpleString(); 127 | if (key.isEmpty()) { 128 | throw new ParseException("empty keys are not allowed"); 129 | } 130 | ptr.expectChar(':'); 131 | 132 | compoundTag.put(key, parseAnything(decrementMaxDepth(maxDepth))); 133 | 134 | if (!ptr.nextArrayElement()) { 135 | break; 136 | } 137 | } 138 | ptr.expectChar('}'); 139 | return compoundTag; 140 | } 141 | 142 | private ListTag parseListTag(int maxDepth) throws ParseException { 143 | ptr.expectChar('['); 144 | ptr.skipWhitespace(); 145 | ListTag list = ListTag.createUnchecked(EndTag.class); 146 | while (ptr.currentChar() != ']') { 147 | Tag element = parseAnything(decrementMaxDepth(maxDepth)); 148 | try { 149 | list.addUnchecked(element); 150 | } catch (IllegalArgumentException ex) { 151 | throw ptr.parseException(ex.getMessage()); 152 | } 153 | if (!ptr.nextArrayElement()) { 154 | break; 155 | } 156 | } 157 | ptr.expectChar(']'); 158 | return list; 159 | } 160 | 161 | private ArrayTag parseNumArray() throws ParseException { 162 | ptr.expectChar('['); 163 | char arrayType = ptr.next(); 164 | ptr.expectChar(';'); 165 | ptr.skipWhitespace(); 166 | switch (arrayType) { 167 | case 'B': 168 | return parseByteArrayTag(); 169 | case 'I': 170 | return parseIntArrayTag(); 171 | case 'L': 172 | return parseLongArrayTag(); 173 | } 174 | throw new ParseException("invalid array type '" + arrayType + "'"); 175 | } 176 | 177 | private ByteArrayTag parseByteArrayTag() throws ParseException { 178 | List byteList = new ArrayList<>(); 179 | while (ptr.currentChar() != ']') { 180 | String s = ptr.parseSimpleString(); 181 | ptr.skipWhitespace(); 182 | if (NUMBER_PATTERN.matcher(s).matches()) { 183 | try { 184 | byteList.add(Byte.parseByte(s)); 185 | } catch (NumberFormatException ex) { 186 | throw ptr.parseException("byte not in range: \"" + s + "\""); 187 | } 188 | } else { 189 | throw ptr.parseException("invalid byte in ByteArrayTag: \"" + s + "\""); 190 | } 191 | if (!ptr.nextArrayElement()) { 192 | break; 193 | } 194 | } 195 | ptr.expectChar(']'); 196 | byte[] bytes = new byte[byteList.size()]; 197 | for (int i = 0; i < byteList.size(); i++) { 198 | bytes[i] = byteList.get(i); 199 | } 200 | return new ByteArrayTag(bytes); 201 | } 202 | 203 | private IntArrayTag parseIntArrayTag() throws ParseException { 204 | List intList = new ArrayList<>(); 205 | while (ptr.currentChar() != ']') { 206 | String s = ptr.parseSimpleString(); 207 | ptr.skipWhitespace(); 208 | if (NUMBER_PATTERN.matcher(s).matches()) { 209 | try { 210 | intList.add(Integer.parseInt(s)); 211 | } catch (NumberFormatException ex) { 212 | throw ptr.parseException("int not in range: \"" + s + "\""); 213 | } 214 | } else { 215 | throw ptr.parseException("invalid int in IntArrayTag: \"" + s + "\""); 216 | } 217 | if (!ptr.nextArrayElement()) { 218 | break; 219 | } 220 | } 221 | ptr.expectChar(']'); 222 | return new IntArrayTag(intList.stream().mapToInt(i -> i).toArray()); 223 | } 224 | 225 | private LongArrayTag parseLongArrayTag() throws ParseException { 226 | List longList = new ArrayList<>(); 227 | while (ptr.currentChar() != ']') { 228 | String s = ptr.parseSimpleString(); 229 | ptr.skipWhitespace(); 230 | if (NUMBER_PATTERN.matcher(s).matches()) { 231 | try { 232 | longList.add(Long.parseLong(s)); 233 | } catch (NumberFormatException ex) { 234 | throw ptr.parseException("long not in range: \"" + s + "\""); 235 | } 236 | } else { 237 | throw ptr.parseException("invalid long in LongArrayTag: \"" + s + "\""); 238 | } 239 | if (!ptr.nextArrayElement()) { 240 | break; 241 | } 242 | } 243 | ptr.expectChar(']'); 244 | return new LongArrayTag(longList.stream().mapToLong(l -> l).toArray()); 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/nbt/io/SNBTSerializer.java: -------------------------------------------------------------------------------- 1 | /* Vendored version of Quertz NBT 6.1 - https://github.com/Querz/NBT */ 2 | package net.sandrohc.schematic4j.nbt.io; 3 | 4 | import java.io.IOException; 5 | import java.io.Writer; 6 | 7 | import net.sandrohc.schematic4j.nbt.StringSerializer; 8 | import net.sandrohc.schematic4j.nbt.tag.Tag; 9 | 10 | public class SNBTSerializer implements StringSerializer> { 11 | 12 | @Override 13 | public void toWriter(Tag tag, Writer writer) throws IOException { 14 | SNBTWriter.write(tag, writer); 15 | } 16 | 17 | public void toWriter(Tag tag, Writer writer, int maxDepth) throws IOException { 18 | SNBTWriter.write(tag, writer, maxDepth); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/nbt/io/SNBTUtil.java: -------------------------------------------------------------------------------- 1 | /* Vendored version of Quertz NBT 6.1 - https://github.com/Querz/NBT */ 2 | package net.sandrohc.schematic4j.nbt.io; 3 | 4 | import java.io.IOException; 5 | 6 | import net.sandrohc.schematic4j.nbt.tag.Tag; 7 | 8 | public class SNBTUtil { 9 | 10 | public static String toSNBT(Tag tag) throws IOException { 11 | return new SNBTSerializer().toString(tag); 12 | } 13 | 14 | public static Tag fromSNBT(String string) throws IOException { 15 | return new SNBTDeserializer().fromString(string); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/nbt/io/SNBTWriter.java: -------------------------------------------------------------------------------- 1 | /* Vendored version of Quertz NBT 6.1 - https://github.com/Querz/NBT */ 2 | package net.sandrohc.schematic4j.nbt.io; 3 | 4 | import java.io.IOException; 5 | import java.io.Writer; 6 | import java.lang.reflect.Array; 7 | import java.util.Map; 8 | import java.util.regex.Pattern; 9 | 10 | import net.sandrohc.schematic4j.nbt.MaxDepthIO; 11 | import net.sandrohc.schematic4j.nbt.tag.ByteArrayTag; 12 | import net.sandrohc.schematic4j.nbt.tag.ByteTag; 13 | import net.sandrohc.schematic4j.nbt.tag.CompoundTag; 14 | import net.sandrohc.schematic4j.nbt.tag.DoubleTag; 15 | import net.sandrohc.schematic4j.nbt.tag.EndTag; 16 | import net.sandrohc.schematic4j.nbt.tag.FloatTag; 17 | import net.sandrohc.schematic4j.nbt.tag.IntArrayTag; 18 | import net.sandrohc.schematic4j.nbt.tag.IntTag; 19 | import net.sandrohc.schematic4j.nbt.tag.ListTag; 20 | import net.sandrohc.schematic4j.nbt.tag.LongArrayTag; 21 | import net.sandrohc.schematic4j.nbt.tag.LongTag; 22 | import net.sandrohc.schematic4j.nbt.tag.ShortTag; 23 | import net.sandrohc.schematic4j.nbt.tag.StringTag; 24 | import net.sandrohc.schematic4j.nbt.tag.Tag; 25 | 26 | /** 27 | * SNBTWriter creates an SNBT String. 28 | * 29 | * */ 30 | public final class SNBTWriter implements MaxDepthIO { 31 | 32 | private static final Pattern NON_QUOTE_PATTERN = Pattern.compile("[a-zA-Z_.+\\-]+"); 33 | 34 | private Writer writer; 35 | 36 | private SNBTWriter(Writer writer) { 37 | this.writer = writer; 38 | } 39 | 40 | public static void write(Tag tag, Writer writer, int maxDepth) throws IOException { 41 | new SNBTWriter(writer).writeAnything(tag, maxDepth); 42 | } 43 | 44 | public static void write(Tag tag, Writer writer) throws IOException { 45 | write(tag, writer, Tag.DEFAULT_MAX_DEPTH); 46 | } 47 | 48 | private void writeAnything(Tag tag, int maxDepth) throws IOException { 49 | switch (tag.getID()) { 50 | case EndTag.ID: 51 | //do nothing 52 | break; 53 | case ByteTag.ID: 54 | writer.append(Byte.toString(((ByteTag) tag).asByte())).write('b'); 55 | break; 56 | case ShortTag.ID: 57 | writer.append(Short.toString(((ShortTag) tag).asShort())).write('s'); 58 | break; 59 | case IntTag.ID: 60 | writer.write(Integer.toString(((IntTag) tag).asInt())); 61 | break; 62 | case LongTag.ID: 63 | writer.append(Long.toString(((LongTag) tag).asLong())).write('l'); 64 | break; 65 | case FloatTag.ID: 66 | writer.append(Float.toString(((FloatTag) tag).asFloat())).write('f'); 67 | break; 68 | case DoubleTag.ID: 69 | writer.append(Double.toString(((DoubleTag) tag).asDouble())).write('d'); 70 | break; 71 | case ByteArrayTag.ID: 72 | writeArray(((ByteArrayTag) tag).getValue(), ((ByteArrayTag) tag).length(), "B"); 73 | break; 74 | case StringTag.ID: 75 | writer.write(escapeString(((StringTag) tag).getValue())); 76 | break; 77 | case ListTag.ID: 78 | writer.write('['); 79 | for (int i = 0; i < ((ListTag) tag).size(); i++) { 80 | writer.write(i == 0 ? "" : ","); 81 | writeAnything(((ListTag) tag).get(i), decrementMaxDepth(maxDepth)); 82 | } 83 | writer.write(']'); 84 | break; 85 | case CompoundTag.ID: 86 | writer.write('{'); 87 | boolean first = true; 88 | for (Map.Entry> entry : (CompoundTag) tag) { 89 | writer.write(first ? "" : ","); 90 | writer.append(escapeString(entry.getKey())).write(':'); 91 | writeAnything(entry.getValue(), decrementMaxDepth(maxDepth)); 92 | first = false; 93 | } 94 | writer.write('}'); 95 | break; 96 | case IntArrayTag.ID: 97 | writeArray(((IntArrayTag) tag).getValue(), ((IntArrayTag) tag).length(), "I"); 98 | break; 99 | case LongArrayTag.ID: 100 | writeArray(((LongArrayTag) tag).getValue(), ((LongArrayTag) tag).length(), "L"); 101 | break; 102 | default: 103 | throw new IOException("unknown tag with id \"" + tag.getID() + "\""); 104 | } 105 | } 106 | 107 | private void writeArray(Object array, int length, String prefix) throws IOException { 108 | writer.append('[').append(prefix).write(';'); 109 | for (int i = 0; i < length; i++) { 110 | writer.append(i == 0 ? "" : ",").write(Array.get(array, i).toString()); 111 | } 112 | writer.write(']'); 113 | } 114 | 115 | public static String escapeString(String s) { 116 | if (!NON_QUOTE_PATTERN.matcher(s).matches()) { 117 | StringBuilder sb = new StringBuilder(); 118 | sb.append('"'); 119 | for (int i = 0; i < s.length(); i++) { 120 | char c = s.charAt(i); 121 | if (c == '\\' || c == '"') { 122 | sb.append('\\'); 123 | } 124 | sb.append(c); 125 | } 126 | sb.append('"'); 127 | return sb.toString(); 128 | } 129 | return s; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/nbt/io/StringPointer.java: -------------------------------------------------------------------------------- 1 | /* Vendored version of Quertz NBT 6.1 - https://github.com/Querz/NBT */ 2 | package net.sandrohc.schematic4j.nbt.io; 3 | 4 | public class StringPointer { 5 | 6 | private String value; 7 | private int index; 8 | 9 | public StringPointer(String value) { 10 | this.value = value; 11 | } 12 | 13 | public String parseSimpleString() { 14 | int oldIndex = index; 15 | while (hasNext() && isSimpleChar(currentChar())) { 16 | index++; 17 | } 18 | return value.substring(oldIndex, index); 19 | } 20 | 21 | public String parseQuotedString() throws ParseException { 22 | int oldIndex = ++index; //ignore beginning quotes 23 | StringBuilder sb = null; 24 | boolean escape = false; 25 | while (hasNext()) { 26 | char c = next(); 27 | if (escape) { 28 | if (c != '\\' && c != '"') { 29 | throw parseException("invalid escape of '" + c + "'"); 30 | } 31 | escape = false; 32 | } else { 33 | if (c == '\\') { //escape 34 | escape = true; 35 | if (sb != null) { 36 | continue; 37 | } 38 | sb = new StringBuilder(value.substring(oldIndex, index - 1)); 39 | continue; 40 | } 41 | if (c == '"') { 42 | return sb == null ? value.substring(oldIndex, index - 1) : sb.toString(); 43 | } 44 | } 45 | if (sb != null) { 46 | sb.append(c); 47 | } 48 | } 49 | throw parseException("missing end quote"); 50 | } 51 | 52 | public boolean nextArrayElement() { 53 | skipWhitespace(); 54 | if (hasNext() && currentChar() == ',') { 55 | index++; 56 | skipWhitespace(); 57 | return true; 58 | } 59 | return false; 60 | } 61 | 62 | public void expectChar(char c) throws ParseException { 63 | skipWhitespace(); 64 | boolean hasNext = hasNext(); 65 | if (hasNext && currentChar() == c) { 66 | index++; 67 | return; 68 | } 69 | throw parseException("expected '" + c + "' but got " + (hasNext ? "'" + currentChar() + "'" : "EOF")); 70 | } 71 | 72 | public void skipWhitespace() { 73 | while (hasNext() && Character.isWhitespace(currentChar())) { 74 | index++; 75 | } 76 | } 77 | 78 | public boolean hasNext() { 79 | return index < value.length(); 80 | } 81 | 82 | public boolean hasCharsLeft(int num) { 83 | return this.index + num < value.length(); 84 | } 85 | 86 | public char currentChar() { 87 | return value.charAt(index); 88 | } 89 | 90 | public char next() { 91 | return value.charAt(index++); 92 | } 93 | 94 | public void skip(int offset) { 95 | index += offset; 96 | } 97 | 98 | public char lookAhead(int offset) { 99 | return value.charAt(index + offset); 100 | } 101 | 102 | private static boolean isSimpleChar(char c) { 103 | return c >= 'a' && c <= 'z' 104 | || c >= 'A' && c <= 'Z' 105 | || c >= '0' && c <= '9' 106 | || c == '-' 107 | || c == '+' 108 | || c == '.' 109 | || c == '_'; 110 | } 111 | 112 | public ParseException parseException(String msg) { 113 | return new ParseException(msg, value, index); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/nbt/tag/ArrayTag.java: -------------------------------------------------------------------------------- 1 | /* Vendored version of Quertz NBT 6.1 - https://github.com/Querz/NBT */ 2 | package net.sandrohc.schematic4j.nbt.tag; 3 | 4 | import java.lang.reflect.Array; 5 | 6 | /** 7 | * ArrayTag is an abstract representation of any NBT array tag. 8 | * For implementations see {@link ByteArrayTag}, {@link IntArrayTag}, {@link LongArrayTag}. 9 | * @param The array type. 10 | * */ 11 | public abstract class ArrayTag extends Tag { 12 | /** 13 | * An array tag. 14 | * @param value The inner array 15 | */ 16 | public ArrayTag(T value) { 17 | super(value); 18 | if (!value.getClass().isArray()) { 19 | throw new UnsupportedOperationException("type of array tag must be an array"); 20 | } 21 | } 22 | 23 | /** 24 | * Get ghe array length, or size. 25 | * @return The array length 26 | */ 27 | public int length() { 28 | return Array.getLength(getValue()); 29 | } 30 | 31 | @Override 32 | public T getValue() { 33 | return super.getValue(); 34 | } 35 | 36 | @Override 37 | public void setValue(T value) { 38 | super.setValue(value); 39 | } 40 | 41 | @Override 42 | public String valueToString(int maxDepth) { 43 | return arrayToString("", ""); 44 | } 45 | 46 | /** 47 | * @param prefix The item prefix 48 | * @param suffix The item suffix 49 | * @return The generated string 50 | */ 51 | protected String arrayToString(String prefix, String suffix) { 52 | StringBuilder sb = new StringBuilder("[").append(prefix).append("".equals(prefix) ? "" : ";"); 53 | for (int i = 0; i < length(); i++) { 54 | sb.append(i == 0 ? "" : ",").append(Array.get(getValue(), i)).append(suffix); 55 | } 56 | sb.append("]"); 57 | return sb.toString(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/nbt/tag/ByteArrayTag.java: -------------------------------------------------------------------------------- 1 | /* Vendored version of Quertz NBT 6.1 - https://github.com/Querz/NBT */ 2 | package net.sandrohc.schematic4j.nbt.tag; 3 | 4 | import java.util.Arrays; 5 | 6 | /** 7 | * A byte array NBT tag. 8 | */ 9 | public class ByteArrayTag extends ArrayTag implements Comparable { 10 | 11 | /** 12 | * The byte array tag discriminator. 13 | */ 14 | public static final byte ID = 7; 15 | 16 | /** 17 | * The default value. 18 | */ 19 | public static final byte[] ZERO_VALUE = new byte[0]; 20 | 21 | /** 22 | * An empty byte array tag. 23 | */ 24 | public ByteArrayTag() { 25 | super(ZERO_VALUE); 26 | } 27 | 28 | /** 29 | * A byte array tag. 30 | * @param value The inner array 31 | */ 32 | public ByteArrayTag(byte[] value) { 33 | super(value); 34 | } 35 | 36 | @Override 37 | public byte getID() { 38 | return ID; 39 | } 40 | 41 | @Override 42 | public boolean equals(Object other) { 43 | return super.equals(other) && Arrays.equals(getValue(), ((ByteArrayTag) other).getValue()); 44 | } 45 | 46 | @Override 47 | public int hashCode() { 48 | return Arrays.hashCode(getValue()); 49 | } 50 | 51 | @Override 52 | public int compareTo(ByteArrayTag other) { 53 | return Integer.compare(length(), other.length()); 54 | } 55 | 56 | @Override 57 | public ByteArrayTag clone() { 58 | return new ByteArrayTag(Arrays.copyOf(getValue(), length())); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/nbt/tag/ByteTag.java: -------------------------------------------------------------------------------- 1 | /* Vendored version of Quertz NBT 6.1 - https://github.com/Querz/NBT */ 2 | package net.sandrohc.schematic4j.nbt.tag; 3 | 4 | /** 5 | * A byte NBT tag. 6 | */ 7 | public class ByteTag extends NumberTag implements Comparable { 8 | 9 | /** 10 | * The byte tag discriminator. 11 | */ 12 | public static final byte ID = 1; 13 | 14 | /** 15 | * The default value. 16 | */ 17 | public static final byte ZERO_VALUE = 0; 18 | 19 | /** 20 | * A byte tag with the default value. 21 | */ 22 | public ByteTag() { 23 | super(ZERO_VALUE); 24 | } 25 | 26 | /** 27 | * A byte tag. 28 | * @param value The inner value 29 | */ 30 | public ByteTag(byte value) { 31 | super(value); 32 | } 33 | 34 | /** 35 | * A byte tag. 36 | * @param value The inner value 37 | */ 38 | public ByteTag(boolean value) { 39 | super((byte) (value ? 1 : 0)); 40 | } 41 | 42 | @Override 43 | public byte getID() { 44 | return ID; 45 | } 46 | 47 | /** 48 | * Convert this byte into a boolean value. Values greater than zero map to true. 49 | * 50 | * @return {@code true} if greater than 0, {@code false} otherwise 51 | */ 52 | public boolean asBoolean() { 53 | return getValue() > 0; 54 | } 55 | 56 | /** 57 | * Sets the inner byte value. 58 | * 59 | * @param value The new value 60 | */ 61 | public void setValue(byte value) { 62 | super.setValue(value); 63 | } 64 | 65 | @Override 66 | public boolean equals(Object other) { 67 | return super.equals(other) && asByte() == ((ByteTag) other).asByte(); 68 | } 69 | 70 | @Override 71 | public int compareTo(ByteTag other) { 72 | return getValue().compareTo(other.getValue()); 73 | } 74 | 75 | @Override 76 | public ByteTag clone() { 77 | return new ByteTag(getValue()); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/nbt/tag/DoubleTag.java: -------------------------------------------------------------------------------- 1 | /* Vendored version of Quertz NBT 6.1 - https://github.com/Querz/NBT */ 2 | package net.sandrohc.schematic4j.nbt.tag; 3 | 4 | /** 5 | * A double NBT tag. 6 | */ 7 | public class DoubleTag extends NumberTag implements Comparable { 8 | 9 | /** 10 | * The double tag discriminator. 11 | */ 12 | public static final byte ID = 6; 13 | 14 | /** 15 | * The default value. 16 | */ 17 | public static final double ZERO_VALUE = 0.0D; 18 | 19 | /** 20 | * A double tag with the default value. 21 | */ 22 | public DoubleTag() { 23 | super(ZERO_VALUE); 24 | } 25 | 26 | /** 27 | * A double tag. 28 | * 29 | * @param value The inner value 30 | */ 31 | public DoubleTag(double value) { 32 | super(value); 33 | } 34 | 35 | @Override 36 | public byte getID() { 37 | return ID; 38 | } 39 | 40 | /** 41 | * Set a new value. 42 | * 43 | * @param value The new value 44 | */ 45 | public void setValue(double value) { 46 | super.setValue(value); 47 | } 48 | 49 | @Override 50 | public boolean equals(Object other) { 51 | return super.equals(other) && getValue().equals(((DoubleTag) other).getValue()); 52 | } 53 | 54 | @Override 55 | public int compareTo(DoubleTag other) { 56 | return getValue().compareTo(other.getValue()); 57 | } 58 | 59 | @Override 60 | public DoubleTag clone() { 61 | return new DoubleTag(getValue()); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/nbt/tag/EndTag.java: -------------------------------------------------------------------------------- 1 | /* Vendored version of Quertz NBT 6.1 - https://github.com/Querz/NBT */ 2 | package net.sandrohc.schematic4j.nbt.tag; 3 | 4 | /** 5 | * An end NBT tag. Used to represent the lack of value, like a {@code null}. 6 | */ 7 | public final class EndTag extends Tag { 8 | 9 | /** 10 | * The end tag discriminator. 11 | */ 12 | public static final byte ID = 0; 13 | 14 | /** 15 | * The default value. 16 | */ 17 | public static final EndTag INSTANCE = new EndTag(); 18 | 19 | /** 20 | * An end tag. 21 | */ 22 | private EndTag() { 23 | super(null); 24 | } 25 | 26 | @Override 27 | public byte getID() { 28 | return ID; 29 | } 30 | 31 | @Override 32 | protected Void checkValue(Void value) { 33 | return value; 34 | } 35 | 36 | @Override 37 | public String valueToString(int maxDepth) { 38 | return "\"end\""; 39 | } 40 | 41 | @Override 42 | public EndTag clone() { 43 | return INSTANCE; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/nbt/tag/FloatTag.java: -------------------------------------------------------------------------------- 1 | /* Vendored version of Quertz NBT 6.1 - https://github.com/Querz/NBT */ 2 | package net.sandrohc.schematic4j.nbt.tag; 3 | 4 | /** 5 | * A float NBT tag. 6 | */ 7 | public class FloatTag extends NumberTag implements Comparable { 8 | 9 | /** 10 | * The float tag discriminator. 11 | */ 12 | public static final byte ID = 5; 13 | 14 | /** 15 | * The default value. 16 | */ 17 | public static final float ZERO_VALUE = 0.0F; 18 | 19 | /** 20 | * A float tag with the default value. 21 | */ 22 | public FloatTag() { 23 | super(ZERO_VALUE); 24 | } 25 | 26 | /** 27 | * A float tag. 28 | * @param value The inner value 29 | */ 30 | public FloatTag(float value) { 31 | super(value); 32 | } 33 | 34 | @Override 35 | public byte getID() { 36 | return ID; 37 | } 38 | 39 | /** 40 | * Set a new value. 41 | * 42 | * @param value The new value 43 | */ 44 | public void setValue(float value) { 45 | super.setValue(value); 46 | } 47 | 48 | @Override 49 | public boolean equals(Object other) { 50 | return super.equals(other) && getValue().equals(((FloatTag) other).getValue()); 51 | } 52 | 53 | @Override 54 | public int compareTo(FloatTag other) { 55 | return getValue().compareTo(other.getValue()); 56 | } 57 | 58 | @Override 59 | public FloatTag clone() { 60 | return new FloatTag(getValue()); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/nbt/tag/IntArrayTag.java: -------------------------------------------------------------------------------- 1 | /* Vendored version of Quertz NBT 6.1 - https://github.com/Querz/NBT */ 2 | package net.sandrohc.schematic4j.nbt.tag; 3 | 4 | import java.util.Arrays; 5 | 6 | /** 7 | * An int array NBT tag. 8 | */ 9 | public class IntArrayTag extends ArrayTag implements Comparable { 10 | 11 | /** 12 | * The int array tag discriminator. 13 | */ 14 | public static final byte ID = 11; 15 | 16 | /** 17 | * The default value. 18 | */ 19 | public static final int[] ZERO_VALUE = new int[0]; 20 | 21 | /** 22 | * An empty int array tag. 23 | */ 24 | public IntArrayTag() { 25 | super(ZERO_VALUE); 26 | } 27 | 28 | /** 29 | * An int array tag. 30 | * @param value The inner value 31 | */ 32 | public IntArrayTag(int[] value) { 33 | super(value); 34 | } 35 | 36 | @Override 37 | public byte getID() { 38 | return ID; 39 | } 40 | 41 | @Override 42 | public boolean equals(Object other) { 43 | return super.equals(other) && Arrays.equals(getValue(), ((IntArrayTag) other).getValue()); 44 | } 45 | 46 | @Override 47 | public int hashCode() { 48 | return Arrays.hashCode(getValue()); 49 | } 50 | 51 | @Override 52 | public int compareTo(IntArrayTag other) { 53 | return Integer.compare(length(), other.length()); 54 | } 55 | 56 | @Override 57 | public IntArrayTag clone() { 58 | return new IntArrayTag(Arrays.copyOf(getValue(), length())); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/nbt/tag/IntTag.java: -------------------------------------------------------------------------------- 1 | /* Vendored version of Quertz NBT 6.1 - https://github.com/Querz/NBT */ 2 | package net.sandrohc.schematic4j.nbt.tag; 3 | 4 | /** 5 | * An int NBT tag. 6 | */ 7 | public class IntTag extends NumberTag implements Comparable { 8 | 9 | /** 10 | * The int tag discriminator. 11 | */ 12 | public static final byte ID = 3; 13 | 14 | /** 15 | * The default value. 16 | */ 17 | public static final int ZERO_VALUE = 0; 18 | 19 | /** 20 | * An int tag with the default value. 21 | */ 22 | public IntTag() { 23 | super(ZERO_VALUE); 24 | } 25 | 26 | /** 27 | * An int tag. 28 | * @param value The inner value 29 | */ 30 | public IntTag(int value) { 31 | super(value); 32 | } 33 | 34 | @Override 35 | public byte getID() { 36 | return ID; 37 | } 38 | 39 | /** 40 | * Set a new value. 41 | * 42 | * @param value The new value 43 | */ 44 | public void setValue(int value) { 45 | super.setValue(value); 46 | } 47 | 48 | @Override 49 | public boolean equals(Object other) { 50 | return super.equals(other) && asInt() == ((IntTag) other).asInt(); 51 | } 52 | 53 | @Override 54 | public int compareTo(IntTag other) { 55 | return getValue().compareTo(other.getValue()); 56 | } 57 | 58 | @Override 59 | public IntTag clone() { 60 | return new IntTag(getValue()); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/nbt/tag/LongArrayTag.java: -------------------------------------------------------------------------------- 1 | /* Vendored version of Quertz NBT 6.1 - https://github.com/Querz/NBT */ 2 | package net.sandrohc.schematic4j.nbt.tag; 3 | 4 | import java.util.Arrays; 5 | 6 | /** 7 | * A long array NBT tag. 8 | */ 9 | public class LongArrayTag extends ArrayTag implements Comparable { 10 | 11 | /** 12 | * The long array tag discriminator. 13 | */ 14 | public static final byte ID = 12; 15 | 16 | /** 17 | * The default value. 18 | */ 19 | public static final long[] ZERO_VALUE = new long[0]; 20 | 21 | /** 22 | * An empty long array tag. 23 | */ 24 | public LongArrayTag() { 25 | super(ZERO_VALUE); 26 | } 27 | 28 | /** 29 | * A long array tag. 30 | * @param value The inner value 31 | */ 32 | public LongArrayTag(long[] value) { 33 | super(value); 34 | } 35 | 36 | @Override 37 | public byte getID() { 38 | return ID; 39 | } 40 | 41 | @Override 42 | public boolean equals(Object other) { 43 | return super.equals(other) && Arrays.equals(getValue(), ((LongArrayTag) other).getValue()); 44 | } 45 | 46 | @Override 47 | public int hashCode() { 48 | return Arrays.hashCode(getValue()); 49 | } 50 | 51 | @Override 52 | public int compareTo(LongArrayTag other) { 53 | return Integer.compare(length(), other.length()); 54 | } 55 | 56 | @Override 57 | public LongArrayTag clone() { 58 | return new LongArrayTag(Arrays.copyOf(getValue(), length())); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/nbt/tag/LongTag.java: -------------------------------------------------------------------------------- 1 | /* Vendored version of Quertz NBT 6.1 - https://github.com/Querz/NBT */ 2 | package net.sandrohc.schematic4j.nbt.tag; 3 | 4 | /** 5 | * A long NBT tag. 6 | */ 7 | public class LongTag extends NumberTag implements Comparable { 8 | 9 | /** 10 | * The long tag discriminator. 11 | */ 12 | public static final byte ID = 4; 13 | 14 | /** 15 | * The default value. 16 | */ 17 | public static final long ZERO_VALUE = 0L; 18 | 19 | /** 20 | * A long tag with the default value. 21 | */ 22 | public LongTag() { 23 | super(ZERO_VALUE); 24 | } 25 | 26 | /** 27 | * A long tag. 28 | * @param value The inner value 29 | */ 30 | public LongTag(long value) { 31 | super(value); 32 | } 33 | 34 | @Override 35 | public byte getID() { 36 | return ID; 37 | } 38 | 39 | /** 40 | * Set a new value. 41 | * 42 | * @param value The new value 43 | */ 44 | public void setValue(long value) { 45 | super.setValue(value); 46 | } 47 | 48 | @Override 49 | public boolean equals(Object other) { 50 | return super.equals(other) && asLong() == ((LongTag) other).asLong(); 51 | } 52 | 53 | @Override 54 | public int compareTo(LongTag other) { 55 | return getValue().compareTo(other.getValue()); 56 | } 57 | 58 | @Override 59 | public LongTag clone() { 60 | return new LongTag(getValue()); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/nbt/tag/NonNullEntrySet.java: -------------------------------------------------------------------------------- 1 | /* Vendored version of Quertz NBT 6.1 - https://github.com/Querz/NBT */ 2 | package net.sandrohc.schematic4j.nbt.tag; 3 | 4 | import java.util.Collection; 5 | import java.util.Iterator; 6 | import java.util.Map; 7 | import java.util.Set; 8 | 9 | import org.checkerframework.checker.nullness.qual.NonNull; 10 | 11 | /** 12 | * A decorator for the Set returned by CompoundTag#entrySet() 13 | * that disallows setting null values. 14 | * */ 15 | class NonNullEntrySet implements Set> { 16 | 17 | /** 18 | * The inner set. 19 | */ 20 | private final Set> set; 21 | 22 | NonNullEntrySet(Set> set) { 23 | this.set = set; 24 | } 25 | 26 | @Override 27 | public int size() { 28 | return set.size(); 29 | } 30 | 31 | @Override 32 | public boolean isEmpty() { 33 | return set.isEmpty(); 34 | } 35 | 36 | @Override 37 | public boolean contains(Object o) { 38 | return set.contains(o); 39 | } 40 | 41 | @Override 42 | public @NonNull Iterator> iterator() { 43 | return new NonNullEntrySetIterator(set.iterator()); 44 | } 45 | 46 | @Override 47 | public Object @NonNull [] toArray() { 48 | return set.toArray(); 49 | } 50 | 51 | @Override 52 | public T @NonNull [] toArray(T @NonNull [] a) { 53 | return set.toArray(a); 54 | } 55 | 56 | @Override 57 | public boolean add(Map.Entry kvEntry) { 58 | return set.add(kvEntry); 59 | } 60 | 61 | @Override 62 | public boolean remove(Object o) { 63 | return set.remove(o); 64 | } 65 | 66 | @Override 67 | public boolean containsAll(@NonNull Collection c) { 68 | return set.containsAll(c); 69 | } 70 | 71 | @Override 72 | public boolean addAll(@NonNull Collection> c) { 73 | return set.addAll(c); 74 | } 75 | 76 | @Override 77 | public boolean retainAll(@NonNull Collection c) { 78 | return set.retainAll(c); 79 | } 80 | 81 | @Override 82 | public boolean removeAll(@NonNull Collection c) { 83 | return set.removeAll(c); 84 | } 85 | 86 | @Override 87 | public void clear() { 88 | set.clear(); 89 | } 90 | 91 | class NonNullEntrySetIterator implements Iterator> { 92 | 93 | private final Iterator> iterator; 94 | 95 | NonNullEntrySetIterator(Iterator> iterator) { 96 | this.iterator = iterator; 97 | } 98 | 99 | @Override 100 | public boolean hasNext() { 101 | return iterator.hasNext(); 102 | } 103 | 104 | @Override 105 | public Map.Entry next() { 106 | return new NonNullEntry(iterator.next()); 107 | } 108 | } 109 | 110 | class NonNullEntry implements Map.Entry { 111 | 112 | private final Map.Entry entry; 113 | 114 | NonNullEntry(Map.Entry entry) { 115 | this.entry = entry; 116 | } 117 | 118 | @Override 119 | public K getKey() { 120 | return entry.getKey(); 121 | } 122 | 123 | @Override 124 | public V getValue() { 125 | return entry.getValue(); 126 | } 127 | 128 | @Override 129 | public V setValue(V value) { 130 | if (value == null) { 131 | throw new NullPointerException(getClass().getSimpleName() + " does not allow setting null"); 132 | } 133 | return entry.setValue(value); 134 | } 135 | 136 | @Override 137 | public boolean equals(Object o) { 138 | return entry.equals(o); 139 | } 140 | 141 | @Override 142 | public int hashCode() { 143 | return entry.hashCode(); 144 | } 145 | } 146 | } -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/nbt/tag/NumberTag.java: -------------------------------------------------------------------------------- 1 | /* Vendored version of Quertz NBT 6.1 - https://github.com/Querz/NBT */ 2 | package net.sandrohc.schematic4j.nbt.tag; 3 | 4 | /** 5 | * A generic numeric NBT tag. 6 | * 7 | * @see ByteTag 8 | * @see ShortTag 9 | * @see IntTag 10 | * @see LongTag 11 | * @see FloatTag 12 | * @see DoubleTag 13 | */ 14 | public abstract class NumberTag> extends Tag { 15 | 16 | /** 17 | * A number tag. 18 | * 19 | * @param value The inner value 20 | */ 21 | public NumberTag(T value) { 22 | super(value); 23 | } 24 | 25 | public byte asByte() { 26 | return getValue().byteValue(); 27 | } 28 | 29 | public short asShort() { 30 | return getValue().shortValue(); 31 | } 32 | 33 | public int asInt() { 34 | return getValue().intValue(); 35 | } 36 | 37 | public long asLong() { 38 | return getValue().longValue(); 39 | } 40 | 41 | public float asFloat() { 42 | return getValue().floatValue(); 43 | } 44 | 45 | public double asDouble() { 46 | return getValue().doubleValue(); 47 | } 48 | 49 | @Override 50 | public String valueToString(int maxDepth) { 51 | return getValue().toString(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/nbt/tag/ShortTag.java: -------------------------------------------------------------------------------- 1 | /* Vendored version of Quertz NBT 6.1 - https://github.com/Querz/NBT */ 2 | package net.sandrohc.schematic4j.nbt.tag; 3 | 4 | /** 5 | * A short NBT tag. 6 | */ 7 | public class ShortTag extends NumberTag implements Comparable { 8 | 9 | /** 10 | * The short tag discriminator. 11 | */ 12 | public static final byte ID = 2; 13 | 14 | /** 15 | * The default value. 16 | */ 17 | public static final short ZERO_VALUE = 0; 18 | 19 | /** 20 | * A short tag with the default value. 21 | */ 22 | public ShortTag() { 23 | super(ZERO_VALUE); 24 | } 25 | 26 | /** 27 | * A short tag. 28 | * @param value The inner value 29 | */ 30 | public ShortTag(short value) { 31 | super(value); 32 | } 33 | 34 | @Override 35 | public byte getID() { 36 | return ID; 37 | } 38 | 39 | /** 40 | * Set a new value. 41 | * 42 | * @param value The new value 43 | */ 44 | public void setValue(short value) { 45 | super.setValue(value); 46 | } 47 | 48 | @Override 49 | public boolean equals(Object other) { 50 | return super.equals(other) && asShort() == ((ShortTag) other).asShort(); 51 | } 52 | 53 | @Override 54 | public int compareTo(ShortTag other) { 55 | return getValue().compareTo(other.getValue()); 56 | } 57 | 58 | @Override 59 | public ShortTag clone() { 60 | return new ShortTag(getValue()); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/nbt/tag/StringTag.java: -------------------------------------------------------------------------------- 1 | /* Vendored version of Quertz NBT 6.1 - https://github.com/Querz/NBT */ 2 | package net.sandrohc.schematic4j.nbt.tag; 3 | 4 | /** 5 | * A string NBT tag. 6 | */ 7 | public class StringTag extends Tag implements Comparable { 8 | 9 | /** 10 | * The string tag discriminator. 11 | */ 12 | public static final byte ID = 8; 13 | 14 | /** 15 | * The default value. 16 | */ 17 | public static final String ZERO_VALUE = ""; 18 | 19 | /** 20 | * An empty string tag. 21 | */ 22 | public StringTag() { 23 | super(ZERO_VALUE); 24 | } 25 | 26 | /** 27 | * A string tag. 28 | * @param value The inner value 29 | */ 30 | public StringTag(String value) { 31 | super(value); 32 | } 33 | 34 | @Override 35 | public byte getID() { 36 | return ID; 37 | } 38 | 39 | @Override 40 | public String getValue() { 41 | return super.getValue(); 42 | } 43 | 44 | @Override 45 | public void setValue(String value) { 46 | super.setValue(value); 47 | } 48 | 49 | @Override 50 | public String valueToString(int maxDepth) { 51 | return escapeString(getValue(), false); 52 | } 53 | 54 | @Override 55 | public boolean equals(Object other) { 56 | return super.equals(other) && getValue().equals(((StringTag) other).getValue()); 57 | } 58 | 59 | @Override 60 | public int compareTo(StringTag o) { 61 | return getValue().compareTo(o.getValue()); 62 | } 63 | 64 | @Override 65 | public StringTag clone() { 66 | return new StringTag(getValue()); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/nbt/tag/Tag.java: -------------------------------------------------------------------------------- 1 | /* Vendored version of Quertz NBT 6.1 - https://github.com/Querz/NBT */ 2 | package net.sandrohc.schematic4j.nbt.tag; 3 | 4 | import java.util.Collections; 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | import java.util.Objects; 8 | import java.util.regex.Matcher; 9 | import java.util.regex.Pattern; 10 | 11 | import net.sandrohc.schematic4j.nbt.MaxDepthReachedException; 12 | 13 | /** 14 | * Base class for all NBT tags. 15 | * 16 | *

Nesting

17 | *

All methods serializing instances or deserializing data track the nesting levels to prevent 18 | * circular references or malicious data which could, when deserialized, result in thousands 19 | * of instances causing a denial of service.

20 | * 21 | *

These methods have a parameter for the maximum nesting depth they are allowed to traverse. A 22 | * value of {@code 0} means that only the object itself, but no nested objects may be processed. 23 | * If an instance is nested further than allowed, a {@link MaxDepthReachedException} will be thrown. 24 | * Providing a negative maximum nesting depth will cause an {@code IllegalArgumentException} 25 | * to be thrown.

26 | * 27 | *

Some methods do not provide a parameter to specify the maximum nesting depth, but instead use 28 | * {@link #DEFAULT_MAX_DEPTH}, which is also the maximum used by Minecraft. This is documented for 29 | * the respective methods.

30 | * 31 | *

If custom NBT tags contain objects other than NBT tags, which can be nested as well, then there 32 | * is no guarantee that {@code MaxDepthReachedException}s are thrown for them. The respective class 33 | * will document this behavior accordingly.

34 | * 35 | * @param The type of the contained value 36 | * */ 37 | public abstract class Tag implements Cloneable { 38 | 39 | /** 40 | * The default maximum depth of the NBT structure. 41 | * */ 42 | public static final int DEFAULT_MAX_DEPTH = 512; 43 | 44 | /** 45 | * Map of characters and their escaped counterparts. 46 | */ 47 | private static final Map ESCAPE_CHARACTERS; 48 | static { 49 | final Map temp = new HashMap<>(); 50 | temp.put("\\", "\\\\\\\\"); 51 | temp.put("\n", "\\\\n"); 52 | temp.put("\t", "\\\\t"); 53 | temp.put("\r", "\\\\r"); 54 | temp.put("\"", "\\\\\""); 55 | ESCAPE_CHARACTERS = Collections.unmodifiableMap(temp); 56 | } 57 | 58 | private static final Pattern ESCAPE_PATTERN = Pattern.compile("[\\\\\n\t\r\"]"); 59 | private static final Pattern NON_QUOTE_PATTERN = Pattern.compile("[a-zA-Z0-9_\\-+]+"); 60 | 61 | private T value; 62 | 63 | /** 64 | * Initializes this Tag with some value. If the value is {@code null}, it will 65 | * throw a {@code NullPointerException} 66 | * @param value The value to be set for this Tag. 67 | * */ 68 | public Tag(T value) { 69 | setValue(value); 70 | } 71 | 72 | /** 73 | * @return This Tag's ID, usually used for serialization and deserialization. 74 | * */ 75 | public abstract byte getID(); 76 | 77 | /** 78 | * @return The value of this Tag. 79 | * */ 80 | protected T getValue() { 81 | return value; 82 | } 83 | 84 | /** 85 | * Sets the value for this Tag directly. 86 | * @param value The value to be set. 87 | * @throws NullPointerException If the value is null 88 | * */ 89 | protected void setValue(T value) { 90 | this.value = checkValue(value); 91 | } 92 | 93 | /** 94 | * Checks if the value {@code value} is {@code null}. 95 | * @param value The value to check 96 | * @throws NullPointerException If {@code value} was {@code null} 97 | * @return The parameter {@code value} 98 | * */ 99 | protected T checkValue(T value) { 100 | return Objects.requireNonNull(value); 101 | } 102 | 103 | /** 104 | * Calls {@link Tag#toString(int)} with an initial depth of {@code 0}. 105 | * @see Tag#toString(int) 106 | * @throws MaxDepthReachedException If the maximum nesting depth is exceeded. 107 | * */ 108 | @Override 109 | public final String toString() { 110 | return toString(DEFAULT_MAX_DEPTH); 111 | } 112 | 113 | /** 114 | * Creates a string representation of this Tag in a valid JSON format. 115 | * @param maxDepth The maximum nesting depth. 116 | * @return The string representation of this Tag. 117 | * @throws MaxDepthReachedException If the maximum nesting depth is exceeded. 118 | * */ 119 | public String toString(int maxDepth) { 120 | return "{\"type\":\""+ getClass().getSimpleName() + "\"," + 121 | "\"value\":" + valueToString(maxDepth) + "}"; 122 | } 123 | 124 | /** 125 | * Calls {@link Tag#valueToString(int)} with {@link Tag#DEFAULT_MAX_DEPTH}. 126 | * @return The string representation of the value of this Tag. 127 | * @throws MaxDepthReachedException If the maximum nesting depth is exceeded. 128 | * */ 129 | public String valueToString() { 130 | return valueToString(DEFAULT_MAX_DEPTH); 131 | } 132 | 133 | /** 134 | * Returns a JSON representation of the value of this Tag. 135 | * @param maxDepth The maximum nesting depth. 136 | * @return The string representation of the value of this Tag. 137 | * @throws MaxDepthReachedException If the maximum nesting depth is exceeded. 138 | * */ 139 | public abstract String valueToString(int maxDepth); 140 | 141 | /** 142 | * Returns whether this Tag and some other Tag are equal. 143 | * They are equal if {@code other} is not {@code null} and they are of the same class. 144 | * Custom Tag implementations should overwrite this but check the result 145 | * of this {@code super}-method while comparing. 146 | * @param other The Tag to compare to. 147 | * @return {@code true} if they are equal based on the conditions mentioned above. 148 | * */ 149 | @Override 150 | public boolean equals(Object other) { 151 | return other != null && getClass() == other.getClass(); 152 | } 153 | 154 | /** 155 | * Calculates the hash code of this Tag. Tags which are equal according to {@link Tag#equals(Object)} 156 | * must return an equal hash code. 157 | * @return The hash code of this Tag. 158 | * */ 159 | @Override 160 | public int hashCode() { 161 | return value.hashCode(); 162 | } 163 | 164 | /** 165 | * Creates a clone of this Tag. 166 | * @return A clone of this Tag. 167 | * */ 168 | public abstract Tag clone(); 169 | 170 | /** 171 | * Escapes a string to fit into a JSON-like string representation for Minecraft 172 | * or to create the JSON string representation of a Tag returned from {@link Tag#toString()} 173 | * @param s The string to be escaped. 174 | * @param lenient {@code true} if it should force double quotes ({@code "}) at the start and 175 | * the end of the string. 176 | * @return The escaped string. 177 | * */ 178 | protected static String escapeString(String s, boolean lenient) { 179 | StringBuffer sb = new StringBuffer(); 180 | Matcher m = ESCAPE_PATTERN.matcher(s); 181 | while (m.find()) { 182 | m.appendReplacement(sb, ESCAPE_CHARACTERS.get(m.group())); 183 | } 184 | m.appendTail(sb); 185 | m = NON_QUOTE_PATTERN.matcher(s); 186 | if (!lenient || !m.matches()) { 187 | sb.insert(0, "\"").append("\""); 188 | } 189 | return sb.toString(); 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/parser/Parser.java: -------------------------------------------------------------------------------- 1 | package net.sandrohc.schematic4j.parser; 2 | 3 | import org.checkerframework.checker.nullness.qual.NonNull; 4 | import org.checkerframework.checker.nullness.qual.Nullable; 5 | 6 | import net.sandrohc.schematic4j.exception.ParsingException; 7 | import net.sandrohc.schematic4j.nbt.tag.CompoundTag; 8 | import net.sandrohc.schematic4j.schematic.Schematic; 9 | 10 | /** 11 | * A schematic parser. 12 | */ 13 | public interface Parser { 14 | 15 | /** 16 | * Parses the input NBT into a schematic. 17 | * 18 | * @param nbt The input NBT. 19 | * @return The parsed schematic. 20 | * @throws ParsingException In case there is a parsing error 21 | */ 22 | @NonNull 23 | Schematic parse(@Nullable CompoundTag nbt) throws ParsingException; 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/parser/SchematicaParser.java: -------------------------------------------------------------------------------- 1 | package net.sandrohc.schematic4j.parser; 2 | 3 | import java.util.Map.Entry; 4 | 5 | import org.checkerframework.checker.nullness.qual.NonNull; 6 | import org.checkerframework.checker.nullness.qual.Nullable; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import net.sandrohc.schematic4j.exception.ParsingException; 11 | import net.sandrohc.schematic4j.nbt.tag.CompoundTag; 12 | import net.sandrohc.schematic4j.nbt.tag.ListTag; 13 | import net.sandrohc.schematic4j.nbt.tag.NumberTag; 14 | import net.sandrohc.schematic4j.nbt.tag.Tag; 15 | import net.sandrohc.schematic4j.schematic.Schematic; 16 | import net.sandrohc.schematic4j.schematic.SchematicaSchematic; 17 | import net.sandrohc.schematic4j.schematic.types.SchematicBlockEntity; 18 | import net.sandrohc.schematic4j.schematic.types.SchematicEntity; 19 | import net.sandrohc.schematic4j.schematic.types.SchematicItem; 20 | 21 | import static net.sandrohc.schematic4j.utils.TagUtils.getByte; 22 | import static net.sandrohc.schematic4j.utils.TagUtils.getByteArrayOrThrow; 23 | import static net.sandrohc.schematic4j.utils.TagUtils.getCompound; 24 | import static net.sandrohc.schematic4j.utils.TagUtils.getCompoundList; 25 | import static net.sandrohc.schematic4j.utils.TagUtils.getCompoundOrThrow; 26 | import static net.sandrohc.schematic4j.utils.TagUtils.getShort; 27 | import static net.sandrohc.schematic4j.utils.TagUtils.getString; 28 | 29 | /** 30 | * Parses Schematica files (.schematic). 31 | *

32 | * Specification:
33 | * - https://minecraft.fandom.com/wiki/Schematic_file_format 34 | * - https://github.com/Lunatrius/Schematica/blob/master/src/main/java/com/github/lunatrius/schematica/world/schematic/SchematicAlpha.java 35 | */ 36 | public class SchematicaParser implements Parser { 37 | 38 | private static final Logger log = LoggerFactory.getLogger(SchematicaParser.class); 39 | 40 | public static final String NBT_MATERIALS = "Materials"; 41 | 42 | public static final String NBT_ICON = "Icon"; 43 | public static final String NBT_ICON_ID = "id"; 44 | public static final String NBT_ICON_COUNT = "Count"; 45 | public static final String NBT_ICON_DAMAGE = "Damage"; 46 | public static final String NBT_BLOCKS = "Blocks"; 47 | public static final String NBT_DATA = "Data"; 48 | public static final String NBT_ADD_BLOCKS = "AddBlocks"; 49 | public static final String NBT_ADD_BLOCKS_SCHEMATICA = "Add"; 50 | public static final String NBT_WIDTH = "Width"; 51 | public static final String NBT_LENGTH = "Length"; 52 | public static final String NBT_HEIGHT = "Height"; 53 | public static final String NBT_MAPPING_SCHEMATICA = "SchematicaMapping"; 54 | public static final String NBT_TILE_ENTITIES = "TileEntities"; 55 | public static final String NBT_ENTITIES = "Entities"; 56 | 57 | @Override 58 | public @NonNull Schematic parse(@Nullable CompoundTag nbt) throws ParsingException { 59 | log.debug("Parsing Schematica schematic"); 60 | 61 | final SchematicaSchematic schematic = new SchematicaSchematic(); 62 | if (nbt == null) { 63 | return schematic; 64 | } 65 | 66 | parseIcon(nbt, schematic); 67 | parseBlocks(nbt, schematic); 68 | parseBlockEntities(nbt, schematic); 69 | parseEntities(nbt, schematic); 70 | parseMaterials(nbt, schematic); 71 | 72 | return schematic; 73 | } 74 | 75 | private void parseIcon(CompoundTag root, SchematicaSchematic schematic) { 76 | log.trace("Parsing icon"); 77 | getCompound(root, NBT_ICON).ifPresent(iconTag -> { 78 | schematic.icon = new SchematicItem( 79 | getString(iconTag, NBT_ICON_ID).orElse("minecraft:dirt"), 80 | getByte(iconTag, NBT_ICON_COUNT).orElse((byte) 1), 81 | getShort(iconTag, NBT_ICON_DAMAGE).orElse((short) 0) 82 | ); 83 | }); 84 | } 85 | 86 | private void parseBlocks(CompoundTag root, SchematicaSchematic schematic) throws ParsingException { 87 | log.trace("Parsing blocks"); 88 | 89 | schematic.width = (int) getShort(root, NBT_WIDTH).orElse((short) 0); 90 | schematic.height = (int) getShort(root, NBT_HEIGHT).orElse((short) 0); 91 | schematic.length = (int) getShort(root, NBT_LENGTH).orElse((short) 0); 92 | 93 | /* Mappings */ 94 | final CompoundTag paletteTag = getCompoundOrThrow(root, NBT_MAPPING_SCHEMATICA); 95 | final int biggestId = paletteTag.values().stream().mapToInt(tag -> tag instanceof NumberTag ? ((NumberTag) tag).asInt() : 0).max().orElse(0) + 1; 96 | log.trace("Palette size: {}, biggest ID: {}", paletteTag.size(), biggestId); 97 | final String[] palette = new String[biggestId]; 98 | for (Entry> entry : paletteTag) { 99 | final String blockName = entry.getKey(); 100 | final int index = ((NumberTag) entry.getValue()).asInt(); 101 | palette[index] = blockName; 102 | } 103 | 104 | // Load the (optional) palette 105 | final byte[] blocksRaw = getByteArrayOrThrow(root, NBT_BLOCKS); 106 | final byte[] blockDataRaw = getByteArrayOrThrow(root, NBT_DATA); 107 | 108 | boolean extra = false; 109 | byte[] extraBlocks = null; 110 | if (root.containsKey(NBT_ADD_BLOCKS)) { 111 | extra = true; 112 | byte[] extraBlocksNibble = getByteArrayOrThrow(root, NBT_ADD_BLOCKS); 113 | extraBlocks = new byte[extraBlocksNibble.length * 2]; 114 | for (int i = 0; i < extraBlocksNibble.length; i++) { 115 | extraBlocks[i * 2] = (byte) ((extraBlocksNibble[i] >> 4) & 0xF); 116 | extraBlocks[i * 2 + 1] = (byte) (extraBlocksNibble[i] & 0xF); 117 | } 118 | } else if (root.containsKey(NBT_ADD_BLOCKS_SCHEMATICA)) { 119 | extra = true; 120 | extraBlocks = getByteArrayOrThrow(root, NBT_ADD_BLOCKS_SCHEMATICA); 121 | } 122 | 123 | int totalVolume = blocksRaw.length; 124 | int expectedTotalVolume = schematic.width * schematic.height * schematic.length; 125 | if (totalVolume != expectedTotalVolume) { 126 | log.warn("Number of blocks does not match expected. Expected {} blocks, but got {}", expectedTotalVolume, totalVolume); 127 | } 128 | 129 | int[] blocks = new int[totalVolume]; 130 | int[] blockMetadata = new int[totalVolume]; 131 | 132 | for (int index = 0; index < totalVolume; index++) { 133 | final int blockId = (blocksRaw[index] & 0xFF) | (extra ? ((extraBlocks[index] & 0xFF) << 8) : 0); 134 | final int metadata = blockDataRaw[index] & 0xFF; 135 | 136 | blocks[index] = blockId; 137 | blockMetadata[index] = metadata; 138 | } 139 | 140 | schematic.blockIds = blocks; 141 | schematic.blockMetadata = blockMetadata; 142 | schematic.blockPalette = palette; 143 | log.debug("Loaded {} blocks", blocks.length); 144 | } 145 | 146 | private void parseBlockEntities(CompoundTag root, SchematicaSchematic schematic) { 147 | final ListTag blockEntitiesTag = getCompoundList(root, NBT_TILE_ENTITIES).orElse(null); 148 | if (blockEntitiesTag == null) { 149 | log.trace("No block entities found"); 150 | return; 151 | } 152 | 153 | log.trace("Parsing block entities"); 154 | final SchematicBlockEntity[] blockEntities = new SchematicBlockEntity[blockEntitiesTag.size()]; 155 | 156 | int i = 0; 157 | for (CompoundTag blockEntityTag : blockEntitiesTag) { 158 | final SchematicBlockEntity blockEntity = SchematicBlockEntity.fromNbt(blockEntityTag); 159 | blockEntities[i++] = blockEntity; 160 | } 161 | 162 | schematic.blockEntities = blockEntities; 163 | log.debug("Loaded {} block entities", blockEntities.length); 164 | } 165 | 166 | private void parseEntities(CompoundTag root, SchematicaSchematic schematic) { 167 | final ListTag entitiesTag = getCompoundList(root, NBT_ENTITIES).orElse(null); 168 | if (entitiesTag == null) { 169 | log.trace("No entities found"); 170 | return; 171 | } 172 | 173 | log.trace("Parsing entities"); 174 | final SchematicEntity[] entities = new SchematicEntity[entitiesTag.size()]; 175 | 176 | int i = 0; 177 | for (final CompoundTag entityTag : entitiesTag) { 178 | final SchematicEntity entity = SchematicEntity.fromNbt(entityTag); 179 | entities[i++] = entity; 180 | } 181 | 182 | schematic.entities = entities; 183 | log.debug("Loaded {} entities", entities.length); 184 | } 185 | 186 | private void parseMaterials(CompoundTag root, SchematicaSchematic schematic) { 187 | log.trace("Parsing materials"); 188 | getString(root, NBT_MATERIALS).ifPresent(materials -> schematic.materials = materials); 189 | } 190 | 191 | @Override 192 | public String toString() { 193 | return "SchematicaParser"; 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/schematic/SchematicaSchematic.java: -------------------------------------------------------------------------------- 1 | package net.sandrohc.schematic4j.schematic; 2 | 3 | import java.util.Arrays; 4 | import java.util.Map; 5 | import java.util.TreeMap; 6 | import java.util.stream.Stream; 7 | 8 | import org.checkerframework.checker.nullness.qual.NonNull; 9 | import org.checkerframework.checker.nullness.qual.Nullable; 10 | 11 | import net.sandrohc.schematic4j.SchematicFormat; 12 | import net.sandrohc.schematic4j.schematic.types.SchematicBlock; 13 | import net.sandrohc.schematic4j.schematic.types.SchematicBlockEntity; 14 | import net.sandrohc.schematic4j.schematic.types.SchematicBlockPos; 15 | import net.sandrohc.schematic4j.schematic.types.SchematicEntity; 16 | import net.sandrohc.schematic4j.schematic.types.SchematicItem; 17 | 18 | import static net.sandrohc.schematic4j.schematic.types.SchematicBlock.AIR; 19 | 20 | /** 21 | * A Schematica schematic. Read more about it at https://minecraft.fandom.com/wiki/Schematic_file_format 22 | *
23 | *

Implementations

24 | * 32 | */ 33 | public class SchematicaSchematic implements Schematic { 34 | 35 | public static final String MATERIAL_CLASSIC = "Classic"; 36 | public static final String MATERIAL_ALPHA = "Alpha"; 37 | public static final String MATERIAL_STRUCTURE = "Structure"; 38 | 39 | /** 40 | * The schematic width, the X axis. 41 | */ 42 | public int width; 43 | 44 | /** 45 | * The schematic height, the Y axis. 46 | */ 47 | public int height; 48 | 49 | /** 50 | * The schematic length, the Z axis. 51 | */ 52 | public int length; 53 | 54 | /** 55 | * The unpacked list of block IDs. 56 | */ 57 | public int @NonNull [] blockIds = new int[0]; 58 | 59 | /** 60 | * The unpacked list of block metadata (used as discriminator before Minecraft's 1.7 block ID overhaul). 61 | */ 62 | public int @NonNull [] blockMetadata = new int[0]; 63 | 64 | /** 65 | * The unpacked list of blocks. 66 | */ 67 | public String @NonNull [] blockPalette = new String[0]; 68 | 69 | /** 70 | * The list of block/tile entities. 71 | */ 72 | public @NonNull SchematicBlockEntity @NonNull [] blockEntities = new SchematicBlockEntity[0]; 73 | 74 | /** 75 | * The list of entities. 76 | */ 77 | public @NonNull SchematicEntity @NonNull [] entities = new SchematicEntity[0]; 78 | 79 | /** 80 | * The schematic icon, if available. 81 | */ 82 | public @Nullable SchematicItem icon; 83 | 84 | /** 85 | * The schematic materials, if available. 86 | *

87 | * One of: 88 | *

    89 | *
  • {@link SchematicaSchematic#MATERIAL_CLASSIC MATERIAL_CLASSIC}
  • 90 | *
  • {@link SchematicaSchematic#MATERIAL_ALPHA MATERIAL_ALPHA}
  • 91 | *
  • {@link SchematicaSchematic#MATERIAL_STRUCTURE MATERIAL_STRUCTURE}
  • 92 | *
93 | */ 94 | public @Nullable String materials; 95 | 96 | public SchematicaSchematic() { 97 | } 98 | 99 | @Override 100 | public @NonNull SchematicFormat format() { 101 | return SchematicFormat.SCHEMATICA; 102 | } 103 | 104 | @Override 105 | public int width() { 106 | return width; 107 | } 108 | 109 | @Override 110 | public int height() { 111 | return height; 112 | } 113 | 114 | @Override 115 | public int length() { 116 | return length; 117 | } 118 | 119 | @Override 120 | public @NonNull SchematicBlockPos offset() { 121 | return SchematicBlockPos.ZERO; 122 | } 123 | 124 | @Override 125 | public @NonNull SchematicBlock block(int x, int y, int z) { 126 | final int blockIndex = posToIndex(x, y, z); 127 | if (blockIndex < 0 || blockIndex >= blockIds.length) { 128 | return AIR; // outside bounds 129 | } 130 | 131 | final int blockId = blockIds[blockIndex]; 132 | String blockName = blockPalette[blockId]; 133 | if (blockName == null) { 134 | blockName = "minecraft:legacy_id_" + blockId; 135 | } 136 | 137 | final int metadata = blockMetadata[blockIndex]; 138 | final Map states = new TreeMap<>(); 139 | if (metadata != 0) { 140 | states.put("metadata", String.valueOf(metadata)); 141 | } 142 | 143 | return new SchematicBlock(blockName, states); 144 | } 145 | 146 | /** 147 | * The raw block ID data. 148 | * 149 | * @return The raw block data 150 | */ 151 | public int @NonNull [] blockIdData() { 152 | return blockIds; 153 | } 154 | 155 | /** 156 | * The raw block metadata. 157 | * 158 | * @return The raw block data 159 | */ 160 | public int @NonNull [] blockMetadata() { 161 | return blockMetadata; 162 | } 163 | 164 | /** 165 | * The raw block palette. 166 | * 167 | * @return The raw block palette 168 | */ 169 | public String @NonNull [] blockPalette() { 170 | return blockPalette; 171 | } 172 | 173 | @Override 174 | public @NonNull Stream blockEntities() { 175 | return Arrays.stream(blockEntities); 176 | } 177 | 178 | /** 179 | * The raw block entity data. 180 | * 181 | * @return The raw block entity data 182 | */ 183 | public @NonNull SchematicBlockEntity[] blockEntityData() { 184 | return blockEntities; 185 | } 186 | 187 | @Override 188 | public @NonNull Stream entities() { 189 | return Arrays.stream(entities); 190 | } 191 | 192 | /** 193 | * The raw entity data. 194 | * 195 | * @return The raw entity data 196 | */ 197 | public @NonNull SchematicEntity[] entityData() { 198 | return entities; 199 | } 200 | 201 | @Override 202 | public @Nullable SchematicItem icon() { 203 | return icon; 204 | } 205 | 206 | /** 207 | * The schematic materials, if available. 208 | *

209 | * One of: 210 | *

    211 | *
  • {@link SchematicaSchematic#MATERIAL_CLASSIC MATERIAL_CLASSIC}
  • 212 | *
  • {@link SchematicaSchematic#MATERIAL_ALPHA MATERIAL_ALPHA}
  • 213 | *
  • {@link SchematicaSchematic#MATERIAL_STRUCTURE MATERIAL_STRUCTURE}
  • 214 | *
215 | * 216 | * @return The schematic materials, if available 217 | */ 218 | public @Nullable String materials() { 219 | return materials; 220 | } 221 | 222 | public int posToIndex(int x, int y, int z) { 223 | return x + (z * width) + (y * width * length); 224 | } 225 | 226 | public @NonNull SchematicBlockPos indexToPos(int index) { 227 | final int x = index % width; 228 | final int z = (index / width) % length; 229 | final int y = index / (width * length); 230 | return new SchematicBlockPos(x, y, z); 231 | } 232 | 233 | @Override 234 | public String toString() { 235 | return "SchematicSchematica[" + 236 | "name=" + name() + 237 | ", width=" + width + 238 | ", height=" + height + 239 | ", length=" + length + 240 | ']'; 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/schematic/types/Pair.java: -------------------------------------------------------------------------------- 1 | package net.sandrohc.schematic4j.schematic.types; 2 | 3 | import java.util.Objects; 4 | 5 | public class Pair { 6 | public L left; 7 | public R right; 8 | 9 | public Pair(L left, R right) { 10 | this.left = left; 11 | this.right = right; 12 | } 13 | 14 | public L left() { 15 | return left; 16 | } 17 | 18 | public R right() { 19 | return right; 20 | } 21 | 22 | @Override 23 | public boolean equals(Object o) { 24 | if (this == o) return true; 25 | if (o == null || getClass() != o.getClass()) return false; 26 | 27 | Pair pair = (Pair) o; 28 | 29 | if (!Objects.equals(left, pair.left)) return false; 30 | return Objects.equals(right, pair.right); 31 | } 32 | 33 | @Override 34 | public int hashCode() { 35 | int result = left != null ? left.hashCode() : 0; 36 | result = 31 * result + (right != null ? right.hashCode() : 0); 37 | return result; 38 | } 39 | 40 | @Override 41 | public String toString() { 42 | return "Pair[" + left + ", " + right + ']'; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/schematic/types/SchematicBiome.java: -------------------------------------------------------------------------------- 1 | package net.sandrohc.schematic4j.schematic.types; 2 | 3 | /** 4 | * Represents a biome as a resource ID, like "minecraft:plains". 5 | */ 6 | public class SchematicBiome extends SchematicBlock { 7 | 8 | public static final SchematicBiome AIR = new SchematicBiome("minecraft:air"); 9 | 10 | public SchematicBiome(String blockstate) { 11 | super(blockstate); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/schematic/types/SchematicBlock.java: -------------------------------------------------------------------------------- 1 | package net.sandrohc.schematic4j.schematic.types; 2 | 3 | import java.util.Collections; 4 | import java.util.Map; 5 | import java.util.TreeMap; 6 | 7 | import org.checkerframework.checker.nullness.qual.NonNull; 8 | 9 | /** 10 | * Represents a block as a resource identifier plus block states, like "minecraft:dirt" or "minecraft:chest[facing=south]". 11 | */ 12 | public class SchematicBlock extends SchematicNamed { 13 | 14 | public static final SchematicBlock AIR = new SchematicBlock("minecraft:air"); 15 | 16 | /** 17 | * The block name excluding block states. 18 | *
19 | * For example, the block state "minecraft:chest[facing=south]" would become just "minecraft:chest". 20 | */ 21 | public @NonNull String block; 22 | 23 | /** 24 | * The list of block states or properties. 25 | *
26 | * For example, chests have the "facing", "type" and "waterlogged" properties. 27 | */ 28 | public @NonNull Map states; 29 | 30 | public SchematicBlock(@NonNull String block, @NonNull Map states) { 31 | super(blockNameAndStatesToString(block, states)); 32 | this.block = block; 33 | this.states = states; 34 | } 35 | 36 | public SchematicBlock(String blockAndStates) { 37 | this(extractBlockName(blockAndStates), extractBlockStates(blockAndStates)); 38 | } 39 | 40 | /** 41 | * The block name excluding block states. 42 | *
43 | * For example, the block state "minecraft:chest[facing=south]" would become just "minecraft:chest". 44 | * 45 | * @return The block name 46 | */ 47 | public @NonNull String block() { 48 | return block; 49 | } 50 | 51 | /** 52 | * The list of block states or properties. 53 | *
54 | * For example, chests have the "facing", "type" and "waterlogged" properties. 55 | * 56 | * @return The block states 57 | */ 58 | public @NonNull Map states() { 59 | return states; 60 | } 61 | 62 | @Override 63 | public boolean equals(Object o) { 64 | if (this == o) return true; 65 | if (o == null || getClass() != o.getClass()) return false; 66 | if (!super.equals(o)) return false; 67 | 68 | SchematicBlock that = (SchematicBlock) o; 69 | 70 | if (!block.equals(that.block)) return false; 71 | return states.equals(that.states); 72 | } 73 | 74 | @Override 75 | public int hashCode() { 76 | int result = super.hashCode(); 77 | result = 31 * result + block.hashCode(); 78 | result = 31 * result + states.hashCode(); 79 | return result; 80 | } 81 | 82 | public static String extractBlockName(String blockAndStates) { 83 | int openingBracketPos = blockAndStates.indexOf('['); 84 | char lastChar = blockAndStates.charAt(blockAndStates.length() - 1); 85 | if (openingBracketPos != -1 && lastChar == ']') { 86 | return blockAndStates.substring(0, openingBracketPos); 87 | } else { 88 | return blockAndStates; 89 | } 90 | } 91 | 92 | public static Map extractBlockStates(String blockAndStates) { 93 | int openingBracketPos = blockAndStates.indexOf('['); 94 | char lastChar = blockAndStates.charAt(blockAndStates.length() - 1); 95 | if (openingBracketPos == -1 || lastChar != ']') { 96 | return Collections.emptyMap(); 97 | } 98 | 99 | final Map states = new TreeMap<>(); 100 | 101 | final String[] statesRaw = blockAndStates.substring(openingBracketPos + 1, blockAndStates.length() - 1).split(","); 102 | for (String state : statesRaw) { 103 | int separatorIndex = state.indexOf('='); 104 | if (separatorIndex != -1) { 105 | String name = state.substring(0, separatorIndex); 106 | String value = state.substring(separatorIndex + 1); 107 | states.put(name, value); 108 | } else { 109 | states.put(state, ""); 110 | } 111 | } 112 | 113 | return states; 114 | } 115 | 116 | public static String blockNameAndStatesToString(@NonNull String block, Map states) { 117 | if (states.isEmpty()) { 118 | return block; 119 | } 120 | 121 | final StringBuilder statesStr = new StringBuilder(); 122 | for (Map.Entry entry : states.entrySet()) { 123 | if (statesStr.length() > 0) { 124 | statesStr.append(','); 125 | } 126 | statesStr.append(entry.getKey()).append('=').append(entry.getValue()); 127 | } 128 | 129 | return block + '[' + statesStr + ']'; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/schematic/types/SchematicBlockEntity.java: -------------------------------------------------------------------------------- 1 | package net.sandrohc.schematic4j.schematic.types; 2 | 3 | import java.util.Map; 4 | import java.util.TreeMap; 5 | 6 | import org.checkerframework.checker.nullness.qual.Nullable; 7 | 8 | import net.sandrohc.schematic4j.nbt.tag.CompoundTag; 9 | import net.sandrohc.schematic4j.nbt.tag.Tag; 10 | 11 | import static java.util.stream.Collectors.toMap; 12 | import static net.sandrohc.schematic4j.utils.TagUtils.unwrap; 13 | 14 | /** 15 | * Represents a block/tile entity, like a chest or a furnace. 16 | */ 17 | public class SchematicBlockEntity extends SchematicNamed { 18 | 19 | /** 20 | * The position where they can be found in the schematic. 21 | */ 22 | public SchematicBlockPos pos; 23 | 24 | /** 25 | * Extra NBT data, like the items stored in a chest, if available. 26 | */ 27 | public Map data; 28 | 29 | public SchematicBlockEntity(String name, SchematicBlockPos pos, Map data) { 30 | super(name); 31 | this.pos = pos; 32 | this.data = data; 33 | } 34 | 35 | public static @Nullable SchematicBlockEntity fromNbt(Tag nbtTag) { 36 | if (!(nbtTag instanceof CompoundTag)) { 37 | return null; 38 | } 39 | final CompoundTag nbt = ((CompoundTag) nbtTag); 40 | 41 | final String id = nbt.getString("id"); 42 | final SchematicBlockPos pos = SchematicBlockPos.from(nbt); 43 | final Map extra = nbt.entrySet().stream() 44 | .filter(tag -> !tag.getKey().equals("id") && 45 | !tag.getKey().equals("x") && 46 | !tag.getKey().equals("y") && 47 | !tag.getKey().equals("z")) 48 | .collect(toMap(Map.Entry::getKey, e -> unwrap(e.getValue()), (a, b) -> b, TreeMap::new)); 49 | 50 | return new SchematicBlockEntity(id, pos, extra); 51 | } 52 | 53 | /** 54 | * The position where they can be found in the schematic. 55 | * 56 | * @return The block entity position 57 | */ 58 | public SchematicBlockPos pos() { 59 | return pos; 60 | } 61 | 62 | /** 63 | * Extra NBT data, like the items stored in a chest, if available. 64 | * 65 | * @return The NBT data 66 | */ 67 | public Map extra() { 68 | return data; 69 | } 70 | 71 | @Override 72 | public boolean equals(Object o) { 73 | if (this == o) return true; 74 | if (o == null || getClass() != o.getClass()) return false; 75 | if (!super.equals(o)) return false; 76 | 77 | SchematicBlockEntity that = (SchematicBlockEntity) o; 78 | 79 | if (!pos.equals(that.pos)) return false; 80 | return data.equals(that.data); 81 | } 82 | 83 | @Override 84 | public int hashCode() { 85 | int result = super.hashCode(); 86 | result = 31 * result + pos.hashCode(); 87 | result = 31 * result + data.hashCode(); 88 | return result; 89 | } 90 | 91 | @Override 92 | public String toString() { 93 | return getClass().getSimpleName() + "[name=" + name + ", pos=" + pos + ", data=" + data + ']'; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/schematic/types/SchematicBlockPos.java: -------------------------------------------------------------------------------- 1 | package net.sandrohc.schematic4j.schematic.types; 2 | 3 | import java.util.Comparator; 4 | 5 | import org.checkerframework.checker.nullness.qual.NonNull; 6 | import org.checkerframework.checker.nullness.qual.Nullable; 7 | 8 | import net.sandrohc.schematic4j.nbt.tag.CompoundTag; 9 | import net.sandrohc.schematic4j.nbt.tag.Tag; 10 | 11 | /** 12 | * Represents a block position. 13 | */ 14 | public class SchematicBlockPos implements Comparable { 15 | 16 | public static final SchematicBlockPos ZERO = new SchematicBlockPos(0, 0, 0); 17 | 18 | /** 19 | * The X coordinate. 20 | */ 21 | public final int x; 22 | 23 | /** 24 | * The Y coordinate. 25 | */ 26 | public final int y; 27 | 28 | /** 29 | * The Z coordinate. 30 | */ 31 | public final int z; 32 | 33 | public SchematicBlockPos(int x, int y, int z) { 34 | this.x = x; 35 | this.y = y; 36 | this.z = z; 37 | } 38 | 39 | public SchematicBlockPos(int[] pos) { 40 | this(pos[0], pos[1], pos[2]); 41 | } 42 | 43 | public SchematicBlockPos(SchematicBlockPos other) { 44 | this(other.x, other.y, other.z); 45 | } 46 | 47 | public static SchematicBlockPos from(int x, int y, int z) { 48 | return new SchematicBlockPos(x, y, z); 49 | } 50 | 51 | public static SchematicBlockPos from(int[] pos) { 52 | return from(pos[0], pos[1], pos[2]); 53 | } 54 | 55 | public static @Nullable SchematicBlockPos from(Tag nbtTag) { 56 | if (!(nbtTag instanceof CompoundTag)) { 57 | return null; 58 | } 59 | final CompoundTag nbt = ((CompoundTag) nbtTag); 60 | final int x = nbt.getInt("x"); 61 | final int y = nbt.getInt("y"); 62 | final int z = nbt.getInt("z"); 63 | return new SchematicBlockPos(x, y, z); 64 | } 65 | 66 | public static SchematicBlockPos from(SchematicBlockPos other) { 67 | return new SchematicBlockPos(other); 68 | } 69 | 70 | /** 71 | * The X coordinate. 72 | * 73 | * @return The X coordinate 74 | */ 75 | public int x() { 76 | return x; 77 | } 78 | 79 | /** 80 | * The Y coordinate. 81 | * 82 | * @return The Y coordinate 83 | */ 84 | public int y() { 85 | return y; 86 | } 87 | 88 | /** 89 | * The Z coordinate. 90 | * 91 | * @return The Z coordinate 92 | */ 93 | public int z() { 94 | return z; 95 | } 96 | 97 | @Override 98 | public boolean equals(Object o) { 99 | if (this == o) return true; 100 | if (o == null || getClass() != o.getClass()) return false; 101 | 102 | SchematicBlockPos that = (SchematicBlockPos) o; 103 | 104 | if (x != that.x) return false; 105 | if (y != that.y) return false; 106 | return z == that.z; 107 | } 108 | 109 | @Override 110 | public int hashCode() { 111 | int result = x; 112 | result = 31 * result + y; 113 | result = 31 * result + z; 114 | return result; 115 | } 116 | 117 | @Override 118 | public String toString() { 119 | return "(" + x + ", " + y + ", " + z + ')'; 120 | } 121 | 122 | @Override 123 | public int compareTo(@NonNull SchematicBlockPos o) { 124 | return Comparator.nullsLast( 125 | Comparator.comparingInt(obj -> obj.x) 126 | .thenComparingInt(obj -> obj.y) 127 | .thenComparingInt(obj -> obj.z) 128 | ).compare(this, o); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/schematic/types/SchematicEntity.java: -------------------------------------------------------------------------------- 1 | package net.sandrohc.schematic4j.schematic.types; 2 | 3 | import java.util.Map; 4 | import java.util.Objects; 5 | import java.util.TreeMap; 6 | 7 | import org.checkerframework.checker.nullness.qual.Nullable; 8 | 9 | import net.sandrohc.schematic4j.nbt.tag.CompoundTag; 10 | import net.sandrohc.schematic4j.nbt.tag.Tag; 11 | 12 | import static java.util.stream.Collectors.toMap; 13 | import static net.sandrohc.schematic4j.utils.TagUtils.getString; 14 | import static net.sandrohc.schematic4j.utils.TagUtils.unwrap; 15 | 16 | /** 17 | * Represents an entity, like a creeper. 18 | */ 19 | public class SchematicEntity extends SchematicNamed { 20 | 21 | /** 22 | * The position of the entity on the schematic. 23 | */ 24 | public SchematicEntityPos pos; 25 | 26 | /** 27 | * The extra NBT data the entity is holding, like a wolf's owner. 28 | */ 29 | public Map data; 30 | 31 | public SchematicEntity(String name, SchematicEntityPos pos, Map data) { 32 | super(name); 33 | this.pos = pos; 34 | this.data = data; 35 | } 36 | 37 | public static @Nullable SchematicEntity fromNbt(Tag nbtTag) { 38 | if (!(nbtTag instanceof CompoundTag)) { 39 | return null; 40 | } 41 | final CompoundTag nbt = ((CompoundTag) nbtTag); 42 | 43 | final String id = getString(nbt, "id").orElseGet(() -> nbt.getString("Id")); 44 | final SchematicEntityPos pos = SchematicEntityPos.from(nbt.get("Pos")); 45 | final Map extra = nbt.entrySet().stream() 46 | .filter(tag -> !tag.getKey().equals("id") && !tag.getKey().equals("Id") && !tag.getKey().equals("Pos")) 47 | .collect(toMap(Map.Entry::getKey, e -> unwrap(e.getValue()), (a, b) -> b, TreeMap::new)); 48 | 49 | return new SchematicEntity(id, pos, extra); 50 | } 51 | 52 | /** 53 | * The position of the entity on the schematic. 54 | * 55 | * @return The position of the entity 56 | */ 57 | public SchematicEntityPos pos() { 58 | return pos; 59 | } 60 | 61 | /** 62 | * The extra NBT data the entity is holding, like a wolf's owner. 63 | * 64 | * @return The extra NBT data 65 | */ 66 | public Map data() { 67 | return data; 68 | } 69 | 70 | @Override 71 | public boolean equals(Object o) { 72 | if (this == o) return true; 73 | if (o == null || getClass() != o.getClass()) return false; 74 | if (!super.equals(o)) return false; 75 | 76 | SchematicEntity that = (SchematicEntity) o; 77 | 78 | if (!Objects.equals(pos, that.pos)) return false; 79 | return Objects.equals(data, that.data); 80 | } 81 | 82 | @Override 83 | public int hashCode() { 84 | int result = super.hashCode(); 85 | result = 31 * result + (pos != null ? pos.hashCode() : 0); 86 | result = 31 * result + (data != null ? data.hashCode() : 0); 87 | return result; 88 | } 89 | 90 | @Override 91 | public String toString() { 92 | return "SchematicEntity[name=" + name + ", pos=" + pos + ", data=" + data + ']'; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/schematic/types/SchematicEntityPos.java: -------------------------------------------------------------------------------- 1 | package net.sandrohc.schematic4j.schematic.types; 2 | 3 | import java.util.Comparator; 4 | 5 | import org.checkerframework.checker.nullness.qual.NonNull; 6 | import org.checkerframework.checker.nullness.qual.Nullable; 7 | 8 | import net.sandrohc.schematic4j.nbt.tag.DoubleTag; 9 | import net.sandrohc.schematic4j.nbt.tag.ListTag; 10 | import net.sandrohc.schematic4j.nbt.tag.Tag; 11 | 12 | /** 13 | * Represents an entity position with decimal place precision. 14 | */ 15 | public class SchematicEntityPos implements Comparable { 16 | 17 | /** 18 | * The X coordinate. 19 | */ 20 | public double x; 21 | 22 | /** 23 | * The Y coordinate. 24 | */ 25 | public double y; 26 | 27 | /** 28 | * The Z coordinate. 29 | */ 30 | public double z; 31 | 32 | public SchematicEntityPos(double x, double y, double z) { 33 | this.x = x; 34 | this.y = y; 35 | this.z = z; 36 | } 37 | 38 | public SchematicEntityPos(double[] pos) { 39 | this(pos[0], pos[1], pos[2]); 40 | } 41 | 42 | public SchematicEntityPos(SchematicEntityPos other) { 43 | this(other.x, other.y, other.z); 44 | } 45 | 46 | public static SchematicEntityPos from(double x, double y, double z) { 47 | return new SchematicEntityPos(x, y, z); 48 | } 49 | 50 | public static SchematicEntityPos from(double[] pos) { 51 | return from(pos[0], pos[1], pos[2]); 52 | } 53 | 54 | @SuppressWarnings("unchecked") 55 | public static @Nullable SchematicEntityPos from(Tag nbtTag) { 56 | if (!(nbtTag instanceof ListTag)) { 57 | return null; 58 | } 59 | final ListTag nbt = ((ListTag) nbtTag); 60 | final double x = nbt.get(0).asDouble(); 61 | final double y = nbt.get(1).asDouble(); 62 | final double z = nbt.get(2).asDouble(); 63 | return new SchematicEntityPos(x, y, z); 64 | } 65 | 66 | public static SchematicEntityPos from(SchematicEntityPos other) { 67 | return new SchematicEntityPos(other); 68 | } 69 | 70 | /** 71 | * The X coordinate. 72 | * 73 | * @return The X coordinate 74 | */ 75 | public double x() { 76 | return x; 77 | } 78 | 79 | /** 80 | * The Y coordinate. 81 | * 82 | * @return The Y coordinate 83 | */ 84 | public double y() { 85 | return y; 86 | } 87 | 88 | /** 89 | * The Z coordinate. 90 | * 91 | * @return The Z coordinate 92 | */ 93 | public double z() { 94 | return z; 95 | } 96 | 97 | @Override 98 | public boolean equals(Object o) { 99 | if (this == o) return true; 100 | if (o == null || getClass() != o.getClass()) return false; 101 | 102 | SchematicEntityPos that = (SchematicEntityPos) o; 103 | 104 | if (Double.compare(x, that.x) != 0) return false; 105 | if (Double.compare(y, that.y) != 0) return false; 106 | return Double.compare(z, that.z) == 0; 107 | } 108 | 109 | @Override 110 | public int hashCode() { 111 | int result; 112 | long temp; 113 | temp = Double.doubleToLongBits(x); 114 | result = (int) (temp ^ (temp >>> 32)); 115 | temp = Double.doubleToLongBits(y); 116 | result = 31 * result + (int) (temp ^ (temp >>> 32)); 117 | temp = Double.doubleToLongBits(z); 118 | result = 31 * result + (int) (temp ^ (temp >>> 32)); 119 | return result; 120 | } 121 | 122 | @Override 123 | public String toString() { 124 | return "(" + x + ", " + y + ", " + z + ')'; 125 | } 126 | 127 | @Override 128 | public int compareTo(@NonNull SchematicEntityPos o) { 129 | return Comparator.nullsLast( 130 | Comparator.comparingDouble(obj -> obj.x) 131 | .thenComparingDouble(obj -> obj.y) 132 | .thenComparingDouble(obj -> obj.z) 133 | ).compare(this, o); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/schematic/types/SchematicItem.java: -------------------------------------------------------------------------------- 1 | package net.sandrohc.schematic4j.schematic.types; 2 | 3 | /** 4 | * Represents an item, like a stick. 5 | */ 6 | public class SchematicItem extends SchematicNamed { 7 | 8 | /** 9 | * The item count, like a stack of 64 sticks. 10 | */ 11 | public int count; 12 | 13 | /** 14 | * The damage amount. 15 | */ 16 | public int damage; 17 | 18 | public SchematicItem(String name, int count, int damage) { 19 | super(name); 20 | this.count = count; 21 | this.damage = damage; 22 | } 23 | 24 | /** 25 | * The item count, like a stack of 64 sticks. 26 | * 27 | * @return The item count 28 | */ 29 | public int count() { 30 | return count; 31 | } 32 | 33 | /** 34 | * The damage amount. 35 | * 36 | * @return The damage amount 37 | */ 38 | public int damage() { 39 | return damage; 40 | } 41 | 42 | @Override 43 | public boolean equals(Object o) { 44 | if (this == o) return true; 45 | if (o == null || getClass() != o.getClass()) return false; 46 | if (!super.equals(o)) return false; 47 | 48 | SchematicItem that = (SchematicItem) o; 49 | 50 | return count == that.count; 51 | } 52 | 53 | @Override 54 | public int hashCode() { 55 | int result = super.hashCode(); 56 | result = 31 * result + count; 57 | return result; 58 | } 59 | 60 | @Override 61 | public String toString() { 62 | return getClass().getSimpleName() + "[name=" + name + ", count=" + count + ", damage=" + damage + "]"; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/schematic/types/SchematicNamed.java: -------------------------------------------------------------------------------- 1 | package net.sandrohc.schematic4j.schematic.types; 2 | 3 | import java.util.Comparator; 4 | 5 | import org.checkerframework.checker.nullness.qual.NonNull; 6 | 7 | public abstract class SchematicNamed implements Comparable { 8 | 9 | /** 10 | * The resource name, usually as a resource identifier like "minecraft:dirt". 11 | */ 12 | public @NonNull String name; 13 | 14 | public SchematicNamed(@NonNull String name) { 15 | this.name = name; 16 | } 17 | 18 | /** 19 | * The resource name. 20 | * 21 | * @return The resource name 22 | */ 23 | public @NonNull String name() { 24 | return name; 25 | } 26 | 27 | @Override 28 | public boolean equals(Object o) { 29 | if (this == o) return true; 30 | if (o == null || getClass() != o.getClass()) return false; 31 | 32 | SchematicNamed that = (SchematicNamed) o; 33 | 34 | return name.equals(that.name); 35 | } 36 | 37 | @Override 38 | public int hashCode() { 39 | return name.hashCode(); 40 | } 41 | 42 | @Override 43 | public String toString() { 44 | return getClass().getSimpleName() + '[' + name + ']'; 45 | } 46 | 47 | @Override 48 | public int compareTo(@NonNull SchematicNamed o) { 49 | return Comparator.nullsLast(Comparator.comparing(obj -> obj.name)).compare(this, o); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/utils/DateUtils.java: -------------------------------------------------------------------------------- 1 | package net.sandrohc.schematic4j.utils; 2 | 3 | import java.time.Instant; 4 | import java.time.LocalDateTime; 5 | import java.time.ZoneId; 6 | 7 | /** 8 | * Collection of utility functions to work with dates. 9 | */ 10 | public class DateUtils { 11 | 12 | /** 13 | * Convert an epoch time into a {@linkplain LocalDateTime}. 14 | * 15 | * @param epoch The epoch time, in milliseconds 16 | * @return The {@linkplain LocalDateTime} 17 | */ 18 | public static LocalDateTime epochToDate(long epoch) { 19 | return LocalDateTime.ofInstant(Instant.ofEpochMilli(epoch), ZoneId.of("UTC")); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/net/sandrohc/schematic4j/utils/TagUtils.java: -------------------------------------------------------------------------------- 1 | package net.sandrohc.schematic4j.utils; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | import java.util.Map; 7 | import java.util.Optional; 8 | import java.util.TreeMap; 9 | 10 | import net.sandrohc.schematic4j.exception.MissingFieldException; 11 | import net.sandrohc.schematic4j.nbt.tag.ByteArrayTag; 12 | import net.sandrohc.schematic4j.nbt.tag.ByteTag; 13 | import net.sandrohc.schematic4j.nbt.tag.CompoundTag; 14 | import net.sandrohc.schematic4j.nbt.tag.DoubleTag; 15 | import net.sandrohc.schematic4j.nbt.tag.FloatTag; 16 | import net.sandrohc.schematic4j.nbt.tag.IntArrayTag; 17 | import net.sandrohc.schematic4j.nbt.tag.IntTag; 18 | import net.sandrohc.schematic4j.nbt.tag.ListTag; 19 | import net.sandrohc.schematic4j.nbt.tag.LongArrayTag; 20 | import net.sandrohc.schematic4j.nbt.tag.LongTag; 21 | import net.sandrohc.schematic4j.nbt.tag.NumberTag; 22 | import net.sandrohc.schematic4j.nbt.tag.ShortTag; 23 | import net.sandrohc.schematic4j.nbt.tag.StringTag; 24 | import net.sandrohc.schematic4j.nbt.tag.Tag; 25 | 26 | /** 27 | * Collection of utility functions to work with NBT tags. 28 | */ 29 | public class TagUtils { 30 | 31 | private TagUtils() {} 32 | 33 | public static Object unwrap(Tag value) { 34 | if (value instanceof StringTag) { 35 | return ((StringTag) value).getValue(); 36 | } else if (value instanceof LongTag) { 37 | return ((LongTag) value).asLong(); 38 | } else if (value instanceof IntTag) { 39 | return ((IntTag) value).asInt(); 40 | } else if (value instanceof ShortTag) { 41 | return ((ShortTag) value).asShort(); 42 | } else if (value instanceof ByteTag) { 43 | return ((ByteTag) value).asByte(); 44 | } else if (value instanceof FloatTag) { 45 | return ((FloatTag) value).asFloat(); 46 | } else if (value instanceof DoubleTag) { 47 | return ((DoubleTag) value).asDouble(); 48 | } else if (value instanceof IntArrayTag) { 49 | return ((IntArrayTag) value).getValue(); 50 | } else if (value instanceof ByteArrayTag) { 51 | return ((ByteArrayTag) value).getValue(); 52 | } else if (value instanceof LongArrayTag) { 53 | return ((LongArrayTag) value).getValue(); 54 | } else if (value instanceof CompoundTag) { 55 | final CompoundTag compoundTag = (CompoundTag) value; 56 | final Map map = new TreeMap<>(); 57 | for (final Map.Entry> entry : compoundTag) { 58 | map.put(entry.getKey(), unwrap(entry.getValue())); 59 | } 60 | return map; 61 | } else if (value instanceof ListTag) { 62 | final ListTag listTag = (ListTag) value; 63 | final List list = new ArrayList<>(listTag.size()); 64 | for (Tag tag : listTag) { 65 | list.add(unwrap(tag)); 66 | } 67 | return list; 68 | } else { 69 | return value; 70 | } 71 | } 72 | 73 | public static boolean containsAllTags(CompoundTag tag, String... requiredTags) { 74 | return Arrays.stream(requiredTags).allMatch(tag::containsKey); 75 | } 76 | 77 | public static boolean containsTag(CompoundTag tag, String... optionalTags) { 78 | return Arrays.stream(optionalTags).anyMatch(tag::containsKey); 79 | } 80 | 81 | 82 | public static Optional getInt(CompoundTag tag, String key) { 83 | return Optional.ofNullable(tag.getIntTag(key)).map(NumberTag::asInt); 84 | } 85 | 86 | public static Optional getShort(CompoundTag tag, String key) { 87 | return Optional.ofNullable(tag.getShortTag(key)).map(NumberTag::asShort); 88 | } 89 | 90 | public static Optional getByte(CompoundTag tag, String key) { 91 | return Optional.ofNullable(tag.getByteTag(key)).map(NumberTag::asByte); 92 | } 93 | 94 | public static Optional getLong(CompoundTag tag, String key) { 95 | return Optional.ofNullable(tag.getLongTag(key)).map(NumberTag::asLong); 96 | } 97 | 98 | public static Optional getFloat(CompoundTag tag, String key) { 99 | return Optional.ofNullable(tag.getFloatTag(key)).map(NumberTag::asFloat); 100 | } 101 | 102 | public static Optional getDouble(CompoundTag tag, String key) { 103 | return Optional.ofNullable(tag.getDoubleTag(key)).map(NumberTag::asDouble); 104 | } 105 | 106 | public static Optional getIntArray(CompoundTag tag, String key) { 107 | return Optional.ofNullable(tag.getIntArrayTag(key)).map(IntArrayTag::getValue); 108 | } 109 | 110 | public static Optional getByteArray(CompoundTag tag, String key) { 111 | return Optional.ofNullable(tag.getByteArrayTag(key)).map(ByteArrayTag::getValue); 112 | } 113 | 114 | public static Optional getLongArray(CompoundTag tag, String key) { 115 | return Optional.ofNullable(tag.getLongArrayTag(key)).map(LongArrayTag::getValue); 116 | } 117 | 118 | public static Optional> getFloatList(CompoundTag tag, String key) { 119 | return Optional.ofNullable(tag.getListTag(key)).map(ListTag::asFloatTagList); 120 | } 121 | 122 | public static Optional> getDoubleList(CompoundTag tag, String key) { 123 | return Optional.ofNullable(tag.getListTag(key)).map(ListTag::asDoubleTagList); 124 | } 125 | 126 | public static Optional> getCompoundList(CompoundTag tag, String key) { 127 | return Optional.ofNullable(tag.getListTag(key)).map(ListTag::asCompoundTagList); 128 | } 129 | 130 | public static Optional getString(CompoundTag tag, String key) { 131 | return Optional.ofNullable(tag.getStringTag(key)).map(StringTag::getValue); 132 | } 133 | 134 | public static Optional getCompound(CompoundTag tag, String key) { 135 | return Optional.ofNullable(tag.getCompoundTag(key)); 136 | } 137 | 138 | 139 | public static int getIntOrThrow(CompoundTag tag, String key) throws MissingFieldException { 140 | return getInt(tag, key).orElseThrow(() -> new MissingFieldException(tag, key, IntTag.class)); 141 | } 142 | 143 | public static short getShortOrThrow(CompoundTag tag, String key) throws MissingFieldException { 144 | return getShort(tag, key).orElseThrow(() -> new MissingFieldException(tag, key, ShortTag.class)); 145 | } 146 | 147 | public static byte getByteOrThrow(CompoundTag tag, String key) throws MissingFieldException { 148 | return getByte(tag, key).orElseThrow(() -> new MissingFieldException(tag, key, ByteTag.class)); 149 | } 150 | 151 | public static long getLongOrThrow(CompoundTag tag, String key) throws MissingFieldException { 152 | return getLong(tag, key).orElseThrow(() -> new MissingFieldException(tag, key, LongTag.class)); 153 | } 154 | 155 | public static float getFloatOrThrow(CompoundTag tag, String key) throws MissingFieldException { 156 | return getFloat(tag, key).orElseThrow(() -> new MissingFieldException(tag, key, FloatTag.class)); 157 | } 158 | 159 | public static double getDoubleOrThrow(CompoundTag tag, String key) throws MissingFieldException { 160 | return getDouble(tag, key).orElseThrow(() -> new MissingFieldException(tag, key, DoubleTag.class)); 161 | } 162 | 163 | public static int[] getIntArrayOrThrow(CompoundTag tag, String key) throws MissingFieldException { 164 | return getIntArray(tag, key).orElseThrow(() -> new MissingFieldException(tag, key, IntArrayTag.class)); 165 | } 166 | 167 | public static byte[] getByteArrayOrThrow(CompoundTag tag, String key) throws MissingFieldException { 168 | return getByteArray(tag, key).orElseThrow(() -> new MissingFieldException(tag, key, ByteArrayTag.class)); 169 | } 170 | 171 | public static long[] getLongArrayOrThrow(CompoundTag tag, String key) throws MissingFieldException { 172 | return getLongArray(tag, key).orElseThrow(() -> new MissingFieldException(tag, key, LongArrayTag.class)); 173 | } 174 | 175 | public static ListTag getFloatListOrThrow(CompoundTag tag, String key) throws MissingFieldException { 176 | return getFloatList(tag, key).orElseThrow(() -> new MissingFieldException(tag, key, ListTag.class)); 177 | } 178 | 179 | public static ListTag getDoubleListOrThrow(CompoundTag tag, String key) throws MissingFieldException { 180 | return getDoubleList(tag, key).orElseThrow(() -> new MissingFieldException(tag, key, ListTag.class)); 181 | } 182 | 183 | public static ListTag getCompoundListOrThrow(CompoundTag tag, String key) throws MissingFieldException { 184 | return getCompoundList(tag, key).orElseThrow(() -> new MissingFieldException(tag, key, ListTag.class)); 185 | } 186 | 187 | public static String getStringOrThrow(CompoundTag tag, String key) throws MissingFieldException { 188 | return getString(tag, key).orElseThrow(() -> new MissingFieldException(tag, key, StringTag.class)); 189 | } 190 | 191 | public static CompoundTag getCompoundOrThrow(CompoundTag tag, String key) throws MissingFieldException { 192 | return getCompound(tag, key).orElseThrow(() -> new MissingFieldException(tag, key, CompoundTag.class)); 193 | } 194 | 195 | } 196 | -------------------------------------------------------------------------------- /src/test/java/net/sandrohc/schematic4j/SchematicFormatTest.java: -------------------------------------------------------------------------------- 1 | package net.sandrohc.schematic4j; 2 | 3 | import java.util.stream.Stream; 4 | 5 | import org.junit.jupiter.params.ParameterizedTest; 6 | import org.junit.jupiter.params.provider.Arguments; 7 | import org.junit.jupiter.params.provider.MethodSource; 8 | 9 | import net.sandrohc.schematic4j.nbt.tag.CompoundTag; 10 | 11 | import static net.sandrohc.schematic4j.parser.TestUtils.nbtFromResource; 12 | import static org.assertj.core.api.Assertions.assertThat; 13 | 14 | public class SchematicFormatTest { 15 | 16 | @ParameterizedTest 17 | @MethodSource("guessFormatData") 18 | void guessFormat(SchematicFormat expected, String file) { 19 | final CompoundTag nbt = nbtFromResource(file); 20 | final SchematicFormat actual = SchematicFormat.guessFormat(nbt); 21 | assertThat(actual).isEqualTo(expected); 22 | } 23 | 24 | private static Stream guessFormatData() { 25 | return Stream.of( 26 | Arguments.of(SchematicFormat.SPONGE_V1, "/schematics/sponge/v1/sponge-v1.schem"), 27 | Arguments.of(SchematicFormat.SPONGE_V2, "/schematics/sponge/v2/issue-1.schem"), 28 | Arguments.of(SchematicFormat.SPONGE_V2, "/schematics/sponge/v2/green-cottage.schem"), 29 | Arguments.of(SchematicFormat.SPONGE_V2, "/schematics/sponge/v2/interieur-exterieur-chunk-project.schem"), 30 | Arguments.of(SchematicFormat.SPONGE_V3, "/schematics/sponge/v3/sponge-v3.schem"), 31 | Arguments.of(SchematicFormat.LITEMATICA, "/schematics/litematica/v6/demo.litematic"), 32 | Arguments.of(SchematicFormat.LITEMATICA, "/schematics/litematica/v5/island.litematic"), 33 | Arguments.of(SchematicFormat.LITEMATICA, "/schematics/litematica/v5/mansion.litematic"), 34 | Arguments.of(SchematicFormat.LITEMATICA, "/schematics/litematica/v5/simple.litematic"), 35 | Arguments.of(SchematicFormat.LITEMATICA, "/schematics/litematica/v5/tower.litematic"), 36 | Arguments.of(SchematicFormat.SCHEMATICA, "/schematics/schematica/9383.schematic"), 37 | Arguments.of(SchematicFormat.SCHEMATICA, "/schematics/schematica/12727.schematic") 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/test/java/net/sandrohc/schematic4j/nbt/io/NBTUtilTest.java: -------------------------------------------------------------------------------- 1 | package net.sandrohc.schematic4j.nbt.io; 2 | 3 | import java.io.ByteArrayInputStream; 4 | import java.io.ByteArrayOutputStream; 5 | import java.io.DataOutputStream; 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.io.OutputStream; 9 | 10 | import org.junit.jupiter.api.Test; 11 | 12 | import net.sandrohc.schematic4j.nbt.tag.StringTag; 13 | import net.sandrohc.schematic4j.nbt.tag.Tag; 14 | 15 | import static org.assertj.core.api.Assertions.assertThat; 16 | import static org.junit.jupiter.api.Assertions.fail; 17 | 18 | public class NBTUtilTest { 19 | 20 | public static final StringTag TAG = new StringTag("TEST"); 21 | 22 | public static final boolean DEFAULT_COMPRESSED = true; 23 | public static final boolean DEFAULT_LITTLE_ENDIAN = false; 24 | 25 | protected static byte[] serialize(Tag tag, boolean compressed, boolean littleEndian) { 26 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 27 | try (DataOutputStream dos = new DataOutputStream(baos)) { 28 | new NBTSerializer(compressed, littleEndian).toStream(new NamedTag(null, tag), dos); 29 | } catch (IOException ex) { 30 | ex.printStackTrace(); 31 | fail(ex.getMessage()); 32 | } 33 | return baos.toByteArray(); 34 | } 35 | 36 | @Test 37 | public void testWriter_invalidParams() throws IOException { 38 | try { 39 | NBTUtil.Writer.write((NamedTag) null).to(new ByteArrayOutputStream()); 40 | fail("did not check null tag"); 41 | } catch (IllegalStateException ignored) { 42 | } 43 | 44 | try { 45 | NBTUtil.Writer.write(TAG).to((OutputStream) null); 46 | fail("did not check null output stream"); 47 | } catch (IllegalStateException ignored) { 48 | } 49 | } 50 | 51 | @Test 52 | public void testWriter_compressionEnabled() throws IOException { 53 | final boolean compressed = true; 54 | final ByteArrayOutputStream os = new ByteArrayOutputStream(); 55 | 56 | NBTUtil.Writer.write(TAG).compressed(compressed).to(os); 57 | 58 | assertThat(os.toByteArray()).isEqualTo(serialize(TAG, compressed, DEFAULT_LITTLE_ENDIAN)); 59 | } 60 | 61 | @Test 62 | public void testWriter_compressionDisabled() throws IOException { 63 | final boolean compressed = false; 64 | final ByteArrayOutputStream os = new ByteArrayOutputStream(); 65 | 66 | NBTUtil.Writer.write(TAG).compressed(compressed).to(os); 67 | 68 | assertThat(os.toByteArray()).isEqualTo(serialize(TAG, compressed, DEFAULT_LITTLE_ENDIAN)); 69 | } 70 | 71 | @Test 72 | public void testReader_compressionEnabled() throws IOException { 73 | final boolean compressed = true; 74 | final InputStream is = new ByteArrayInputStream(serialize(TAG, compressed, DEFAULT_LITTLE_ENDIAN)); 75 | 76 | final NamedTag tag = NBTUtil.Reader.read().from(is); 77 | 78 | assertThat(tag.getTag()).isEqualTo(TAG); 79 | } 80 | 81 | @Test 82 | public void testReader_compressionDisabled() throws IOException { 83 | final boolean compressed = false; 84 | final InputStream is = new ByteArrayInputStream(serialize(TAG, compressed, DEFAULT_LITTLE_ENDIAN)); 85 | 86 | final NamedTag tag = NBTUtil.Reader.read().from(is); 87 | 88 | assertThat(tag.getTag()).isEqualTo(TAG); 89 | } 90 | 91 | 92 | @Test 93 | public void testWriter_littleEndian() throws IOException { 94 | final boolean littleEndian = true; 95 | final ByteArrayOutputStream os = new ByteArrayOutputStream(); 96 | 97 | NBTUtil.Writer.write(TAG).littleEndian().to(os); 98 | 99 | assertThat(os.toByteArray()).isEqualTo(serialize(TAG, DEFAULT_COMPRESSED, littleEndian)); 100 | } 101 | 102 | @Test 103 | public void testWriter_bigEndian() throws IOException { 104 | final boolean littleEndian = false; 105 | final ByteArrayOutputStream os = new ByteArrayOutputStream(); 106 | 107 | NBTUtil.Writer.write(TAG).bigEndian().to(os); 108 | 109 | assertThat(os.toByteArray()).isEqualTo(serialize(TAG, DEFAULT_COMPRESSED, littleEndian)); 110 | } 111 | 112 | @Test 113 | public void testReader_littleEndian() throws IOException { 114 | final boolean littleEndian = true; 115 | final InputStream is = new ByteArrayInputStream(serialize(TAG, DEFAULT_COMPRESSED, littleEndian)); 116 | 117 | final NamedTag tag = NBTUtil.Reader.read().littleEndian().from(is); 118 | 119 | assertThat(tag.getTag()).isEqualTo(TAG); 120 | } 121 | 122 | @Test 123 | public void testReader_bigEndian() throws IOException { 124 | final boolean littleEndian = false; 125 | final InputStream is = new ByteArrayInputStream(serialize(TAG, DEFAULT_COMPRESSED, littleEndian)); 126 | 127 | final NamedTag tag = NBTUtil.Reader.read().bigEndian().from(is); 128 | 129 | assertThat(tag.getTag()).isEqualTo(TAG); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/test/java/net/sandrohc/schematic4j/parser/LitematicaParserTest.java: -------------------------------------------------------------------------------- 1 | package net.sandrohc.schematic4j.parser; 2 | 3 | import java.time.LocalDateTime; 4 | 5 | import au.com.origin.snapshots.Expect; 6 | import au.com.origin.snapshots.junit5.SnapshotExtension; 7 | import org.assertj.core.api.SoftAssertions; 8 | import org.junit.jupiter.api.Test; 9 | import org.junit.jupiter.api.extension.ExtendWith; 10 | import org.junit.jupiter.params.ParameterizedTest; 11 | import org.junit.jupiter.params.provider.ValueSource; 12 | 13 | import net.sandrohc.schematic4j.SchematicFormat; 14 | import net.sandrohc.schematic4j.exception.ParsingException; 15 | import net.sandrohc.schematic4j.nbt.tag.CompoundTag; 16 | import net.sandrohc.schematic4j.schematic.Schematic; 17 | import net.sandrohc.schematic4j.schematic.LitematicaSchematic; 18 | import net.sandrohc.schematic4j.schematic.types.SchematicBlock; 19 | import net.sandrohc.schematic4j.schematic.types.SchematicBlockPos; 20 | 21 | import static net.sandrohc.schematic4j.parser.TestUtils.assertSchematic; 22 | import static net.sandrohc.schematic4j.parser.TestUtils.assertSchematicBlockIterator; 23 | import static net.sandrohc.schematic4j.parser.TestUtils.nbtFromResource; 24 | import static org.assertj.core.api.Assertions.assertThat; 25 | 26 | @ExtendWith(SnapshotExtension.class) 27 | public class LitematicaParserTest { 28 | 29 | private Expect expect; 30 | 31 | @Test 32 | public void parser() throws ParsingException { 33 | final CompoundTag nbt = nbtFromResource("/schematics/litematica/v6/demo.litematic"); 34 | final Schematic schem = new LitematicaParser().parse(nbt); 35 | assertThat(schem).isNotNull().isInstanceOf(LitematicaSchematic.class); 36 | 37 | SoftAssertions softly = new SoftAssertions(); 38 | softly.assertThat(schem.format()).isEqualTo(SchematicFormat.LITEMATICA); 39 | softly.assertThat(schem.width()).isEqualTo(4); 40 | softly.assertThat(schem.height()).isEqualTo(3); 41 | softly.assertThat(schem.length()).isEqualTo(4); 42 | softly.assertThat(schem.name()).isEqualTo("Demo Schematic"); 43 | softly.assertThat(schem.author()).isEqualTo("SandroHc"); 44 | softly.assertThat(schem.date()).isEqualTo(LocalDateTime.parse("2023-12-14T22:06:47.465")); 45 | softly.assertThat(((LitematicaSchematic) schem).regions()).hasSize(1); 46 | softly.assertThat(((LitematicaSchematic) schem).metadata()).extracting(o -> o.name).isEqualTo("Demo Schematic"); 47 | softly.assertThat(((LitematicaSchematic) schem).metadata()).extracting(o -> o.description).isEqualTo(""); 48 | softly.assertThat(((LitematicaSchematic) schem).metadata()).extracting(o -> o.author).isEqualTo("SandroHc"); 49 | softly.assertThat(((LitematicaSchematic) schem).metadata()).extracting(o -> o.timeCreated).isEqualTo(LocalDateTime.parse("2023-12-14T22:06:47.465")); 50 | softly.assertThat(((LitematicaSchematic) schem).metadata()).extracting(o -> o.timeModified).isEqualTo(LocalDateTime.parse("2023-12-14T22:06:47.465")); 51 | softly.assertThat(((LitematicaSchematic) schem).metadata()).extracting(o -> o.enclosingSize).isEqualTo(new SchematicBlockPos(4, 3, 4)); 52 | softly.assertThat(((LitematicaSchematic) schem).metadata()).extracting(o -> o.regionCount).isEqualTo(1); 53 | softly.assertThat(((LitematicaSchematic) schem).metadata()).extracting(o -> o.totalBlocks).isEqualTo(8L); 54 | softly.assertThat(((LitematicaSchematic) schem).metadata()).extracting(o -> o.totalVolume).isEqualTo(48L); 55 | softly.assertThat(((LitematicaSchematic) schem).metadata()).satisfies(o -> assertThat(o.extra).isEmpty()); 56 | softly.assertAll(); 57 | 58 | final LitematicaSchematic.Region region = ((LitematicaSchematic) schem).regions[0]; 59 | softly = new SoftAssertions(); 60 | softly.assertThat(region.name).isEqualTo("Demo Sub-region 1"); 61 | softly.assertThat(region.position).isEqualTo(new SchematicBlockPos(0, 0, 0)); 62 | softly.assertThat(region.size).isEqualTo(new SchematicBlockPos(4, 3, 4)); 63 | softly.assertThat(region.blockStates).hasSize(48); 64 | softly.assertThat(region.blockStatePalette).hasSize(6); 65 | softly.assertThat(region.blockEntities).hasSize(1); 66 | softly.assertThat(region.entities).hasSize(1); 67 | softly.assertThat(region.pendingBlockTicks).isEmpty(); 68 | softly.assertThat(region.pendingFluidTicks).isEmpty(); 69 | softly.assertAll(); 70 | 71 | softly = new SoftAssertions(); 72 | softly.assertThat(schem.block(0, 0, 0)).isEqualTo(new SchematicBlock("minecraft:white_wool")); 73 | softly.assertThat(schem.block(1, 0, 1)).isEqualTo(new SchematicBlock("minecraft:chest[facing=south,type=single,waterlogged=false]")); 74 | // X axis 75 | softly.assertThat(schem.block(1, 0, 0)).isEqualTo(new SchematicBlock("minecraft:red_wool")); 76 | softly.assertThat(schem.block(2, 0, 0)).isEqualTo(new SchematicBlock("minecraft:red_wool")); 77 | softly.assertThat(schem.block(3, 0, 0)).isEqualTo(new SchematicBlock("minecraft:air")); 78 | // Y axis 79 | softly.assertThat(schem.block(0, 1, 0)).isEqualTo(new SchematicBlock("minecraft:lime_wool")); 80 | softly.assertThat(schem.block(0, 2, 0)).isEqualTo(new SchematicBlock("minecraft:lime_wool")); 81 | softly.assertThat(schem.block(0, 3, 0)).isEqualTo(new SchematicBlock("minecraft:air")); 82 | // Z axis 83 | softly.assertThat(schem.block(0, 0, 1)).isEqualTo(new SchematicBlock("minecraft:light_blue_wool")); 84 | softly.assertThat(schem.block(0, 0, 2)).isEqualTo(new SchematicBlock("minecraft:light_blue_wool")); 85 | softly.assertThat(schem.block(0, 0, 3)).isEqualTo(new SchematicBlock("minecraft:air")); 86 | // Outside the boundaries of the schematic 87 | softly.assertThat(schem.block(-1, 0, 0)).isEqualTo(new SchematicBlock("minecraft:air")); 88 | softly.assertThat(schem.block(-1, 1, 1)).isEqualTo(new SchematicBlock("minecraft:air")); 89 | softly.assertThat(schem.block(0, -1, 0)).isEqualTo(new SchematicBlock("minecraft:air")); 90 | softly.assertThat(schem.block(1, -1, 1)).isEqualTo(new SchematicBlock("minecraft:air")); 91 | softly.assertThat(schem.block(0, 0, -1)).isEqualTo(new SchematicBlock("minecraft:air")); 92 | softly.assertThat(schem.block(1, 1, -1)).isEqualTo(new SchematicBlock("minecraft:air")); 93 | softly.assertAll(); 94 | } 95 | 96 | @Test 97 | public void blockIterator() throws ParsingException { 98 | assertSchematicBlockIterator(expect, "/schematics/litematica/v6/demo.litematic", new LitematicaParser()); 99 | } 100 | 101 | /** 102 | * Check if the schematics too big for snapshot still parse without errors. 103 | */ 104 | @ParameterizedTest 105 | @ValueSource(strings = {"/schematics/litematica/v5/island.litematic"}) 106 | public void parsesNoSnapshot(String file) throws ParsingException { 107 | final CompoundTag nbt = nbtFromResource(file); 108 | final Schematic schem = new LitematicaParser().parse(nbt); 109 | assertThat(schem).isNotNull().isInstanceOf(LitematicaSchematic.class); 110 | } 111 | 112 | @Test 113 | public void snapshot1() throws ParsingException { 114 | assertSchematic(expect, "/schematics/litematica/v6/demo.litematic", new LitematicaParser()); 115 | } 116 | 117 | @Test 118 | public void snapshot2() throws ParsingException { 119 | assertSchematic(expect, "/schematics/litematica/v5/simple.litematic", new LitematicaParser()); 120 | } 121 | 122 | @Test 123 | public void snapshot3() throws ParsingException { 124 | assertSchematic(expect, "/schematics/litematica/v5/mansion.litematic", new LitematicaParser()); 125 | } 126 | 127 | @Test 128 | public void snapshot4() throws ParsingException { 129 | assertSchematic(expect, "/schematics/litematica/v5/tower.litematic", new LitematicaParser()); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/test/java/net/sandrohc/schematic4j/parser/SchematicaParserTest.java: -------------------------------------------------------------------------------- 1 | package net.sandrohc.schematic4j.parser; 2 | 3 | import au.com.origin.snapshots.Expect; 4 | import au.com.origin.snapshots.junit5.SnapshotExtension; 5 | import org.assertj.core.api.SoftAssertions; 6 | import org.junit.jupiter.api.Test; 7 | import org.junit.jupiter.api.extension.ExtendWith; 8 | import org.junit.jupiter.params.ParameterizedTest; 9 | import org.junit.jupiter.params.provider.ValueSource; 10 | 11 | import net.sandrohc.schematic4j.SchematicFormat; 12 | import net.sandrohc.schematic4j.exception.ParsingException; 13 | import net.sandrohc.schematic4j.nbt.tag.CompoundTag; 14 | import net.sandrohc.schematic4j.schematic.Schematic; 15 | import net.sandrohc.schematic4j.schematic.SchematicaSchematic; 16 | 17 | import static net.sandrohc.schematic4j.parser.TestUtils.assertSchematic; 18 | import static net.sandrohc.schematic4j.parser.TestUtils.assertSchematicBlockIterator; 19 | import static net.sandrohc.schematic4j.parser.TestUtils.nbtFromResource; 20 | import static org.assertj.core.api.Assertions.assertThat; 21 | 22 | @ExtendWith(SnapshotExtension.class) 23 | public class SchematicaParserTest { 24 | 25 | private Expect expect; 26 | 27 | @Test 28 | public void parser() throws ParsingException { 29 | final CompoundTag nbt = nbtFromResource("/schematics/schematica/12727.schematic"); 30 | final Schematic schem = new SchematicaParser().parse(nbt); 31 | assertThat(schem).isNotNull().isInstanceOf(SchematicaSchematic.class); 32 | 33 | final SoftAssertions softly = new SoftAssertions(); 34 | softly.assertThat(schem.format()).isEqualTo(SchematicFormat.SCHEMATICA); 35 | softly.assertThat(schem.width()).isEqualTo(86); 36 | softly.assertThat(schem.height()).isEqualTo(82); 37 | softly.assertThat(schem.length()).isEqualTo(101); 38 | softly.assertThat(((SchematicaSchematic) schem).materials()).isEqualTo(SchematicaSchematic.MATERIAL_ALPHA); 39 | softly.assertAll(); 40 | } 41 | 42 | @Test 43 | public void blockIterator() throws ParsingException { 44 | assertSchematicBlockIterator(expect, "/schematics/schematica/9383.schematic", new SchematicaParser()); 45 | } 46 | 47 | /** 48 | * Check if the schematics too big for snapshot still parse without errors. 49 | */ 50 | @ParameterizedTest 51 | @ValueSource(strings = {"/schematics/schematica/12727.schematic"}) 52 | public void parsesNoSnapshot(String file) throws ParsingException { 53 | final CompoundTag nbt = nbtFromResource(file); 54 | final Schematic schem = new SchematicaParser().parse(nbt); 55 | assertThat(schem).isNotNull().isInstanceOf(SchematicaSchematic.class); 56 | } 57 | 58 | @Test 59 | public void snapshot1() throws ParsingException { 60 | assertSchematic(expect, "/schematics/schematica/9383.schematic", new SchematicaParser()); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/test/java/net/sandrohc/schematic4j/parser/SpongeParserTest.java: -------------------------------------------------------------------------------- 1 | package net.sandrohc.schematic4j.parser; 2 | 3 | import java.time.LocalDateTime; 4 | 5 | import au.com.origin.snapshots.Expect; 6 | import au.com.origin.snapshots.junit5.SnapshotExtension; 7 | import org.assertj.core.api.SoftAssertions; 8 | import org.junit.jupiter.api.Test; 9 | import org.junit.jupiter.api.extension.ExtendWith; 10 | import org.junit.jupiter.params.ParameterizedTest; 11 | import org.junit.jupiter.params.provider.ValueSource; 12 | 13 | import net.sandrohc.schematic4j.SchematicFormat; 14 | import net.sandrohc.schematic4j.exception.ParsingException; 15 | import net.sandrohc.schematic4j.nbt.tag.CompoundTag; 16 | import net.sandrohc.schematic4j.schematic.Schematic; 17 | import net.sandrohc.schematic4j.schematic.SpongeSchematic; 18 | import net.sandrohc.schematic4j.schematic.types.SchematicBlockPos; 19 | 20 | import static net.sandrohc.schematic4j.parser.TestUtils.assertSchematic; 21 | import static net.sandrohc.schematic4j.parser.TestUtils.assertSchematicBlockIterator; 22 | import static net.sandrohc.schematic4j.parser.TestUtils.nbtFromResource; 23 | import static org.assertj.core.api.Assertions.assertThat; 24 | 25 | @ExtendWith(SnapshotExtension.class) 26 | public class SpongeParserTest { 27 | 28 | private Expect expect; 29 | 30 | @Test 31 | public void parses() throws ParsingException { 32 | final CompoundTag nbt = nbtFromResource("/schematics/sponge/v2/issue-1.schem"); 33 | final Schematic schem = new SpongeParser().parse(nbt); 34 | assertThat(schem).isNotNull().isInstanceOf(SpongeSchematic.class); 35 | 36 | final SoftAssertions softly = new SoftAssertions(); 37 | softly.assertThat(schem.format()).isEqualTo(SchematicFormat.SPONGE_V2); 38 | softly.assertThat(schem.name()).isEqualTo("example_name"); 39 | softly.assertThat(schem.author()).isEqualTo("example_author"); 40 | softly.assertThat(schem.date()).isEqualTo(LocalDateTime.parse("2023-12-13T12:06:04.631")); 41 | softly.assertThat(schem.width()).isEqualTo(1); 42 | softly.assertThat(schem.height()).isEqualTo(41); 43 | softly.assertThat(schem.length()).isEqualTo(9); 44 | softly.assertThat(schem.offset()).isEqualTo(new SchematicBlockPos(22, -60, 13)); 45 | softly.assertThat(schem.block(0, 0, 0)).extracting(o -> o.name).isEqualTo("minecraft:stone"); 46 | softly.assertThat(schem.blocks()).hasSize(369); 47 | softly.assertThat(schem.blockEntities()).hasSize(1); 48 | softly.assertThat(schem.entities()).hasSize(1); 49 | softly.assertThat(schem.biomes()).isEmpty(); 50 | softly.assertThat(((SpongeSchematic) schem).dataVersion()).isEqualTo(2860); 51 | softly.assertThat(((SpongeSchematic) schem).metadata()).isNotNull(); 52 | softly.assertAll(); 53 | } 54 | 55 | @Test 56 | public void blockIterator() throws ParsingException { 57 | assertSchematicBlockIterator(expect, "/schematics/sponge/v2/issue-1.schem", new SpongeParser()); 58 | } 59 | 60 | /** 61 | * Check if the schematics too big for snapshot still parse without errors. 62 | */ 63 | @ParameterizedTest 64 | @ValueSource(strings = {"/schematics/sponge/v2/interieur-exterieur-chunk-project.schem"}) 65 | public void parsesNoSnapshot(String file) throws ParsingException { 66 | final CompoundTag nbt = nbtFromResource(file); 67 | final Schematic schem = new SpongeParser().parse(nbt); 68 | assertThat(schem).isNotNull().isInstanceOf(SpongeSchematic.class); 69 | } 70 | 71 | @Test 72 | public void snapshot1() throws ParsingException { 73 | assertSchematic(expect, "/schematics/sponge/v2/issue-1.schem", new SpongeParser()); 74 | } 75 | 76 | @Test 77 | public void snapshot2() throws ParsingException { 78 | assertSchematic(expect, "/schematics/sponge/v2/green-cottage.schem", new SpongeParser()); 79 | } 80 | 81 | @Test 82 | public void snapshot3() throws ParsingException { 83 | assertSchematic(expect, "/schematics/sponge/v3/sponge-v3.schem", new SpongeParser()); 84 | } 85 | 86 | @Test 87 | public void snapshot4() throws ParsingException { 88 | assertSchematic(expect, "/schematics/sponge/v1/sponge-v1.schem", new SpongeParser()); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/test/java/net/sandrohc/schematic4j/parser/TestUtils.java: -------------------------------------------------------------------------------- 1 | package net.sandrohc.schematic4j.parser; 2 | 3 | import java.io.InputStream; 4 | import java.util.Collection; 5 | import java.util.stream.Collectors; 6 | 7 | import au.com.origin.snapshots.Expect; 8 | 9 | import net.sandrohc.schematic4j.exception.ParsingException; 10 | import net.sandrohc.schematic4j.nbt.io.NBTUtil; 11 | import net.sandrohc.schematic4j.nbt.io.NamedTag; 12 | import net.sandrohc.schematic4j.nbt.tag.CompoundTag; 13 | import net.sandrohc.schematic4j.schematic.Schematic; 14 | import net.sandrohc.schematic4j.schematic.types.Pair; 15 | import net.sandrohc.schematic4j.schematic.types.SchematicBlock; 16 | import net.sandrohc.schematic4j.schematic.types.SchematicBlockPos; 17 | 18 | public class TestUtils { 19 | 20 | public static InputStream readResource(String file) { 21 | try { 22 | return TestUtils.class.getResourceAsStream(file); 23 | } catch (Exception e) { 24 | throw new RuntimeException("Failed to load resource: " + file, e); 25 | } 26 | } 27 | 28 | public static CompoundTag nbtFromResource(String file) { 29 | final InputStream resource = readResource(file); 30 | try { 31 | NamedTag nbt = NBTUtil.Reader.read().from(resource); 32 | return (CompoundTag) nbt.getTag(); 33 | } catch (Exception e) { 34 | throw new RuntimeException("Failed to load resource into NBT: " + file, e); 35 | } 36 | } 37 | 38 | public static void assertSchematic(Expect expect, String file, Parser parser) throws ParsingException { 39 | final CompoundTag nbt = nbtFromResource(file); 40 | final Schematic schem = parser.parse(nbt); 41 | expect.toMatchSnapshot(schem); 42 | } 43 | 44 | public static void assertSchematicBlockIterator(Expect expect, String file, Parser parser) throws ParsingException { 45 | final CompoundTag nbt = nbtFromResource(file); 46 | final Schematic schem = parser.parse(nbt); 47 | final Collection> blocks = schem.blocks().collect(Collectors.toList()); 48 | expect.toMatchSnapshot(blocks); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/test/resources/schematics/litematica/v5/island.litematic: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandroHc/schematic4j/c581e2f605cea6820cad6c59937fe15eec961dc1/src/test/resources/schematics/litematica/v5/island.litematic -------------------------------------------------------------------------------- /src/test/resources/schematics/litematica/v5/mansion.litematic: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandroHc/schematic4j/c581e2f605cea6820cad6c59937fe15eec961dc1/src/test/resources/schematics/litematica/v5/mansion.litematic -------------------------------------------------------------------------------- /src/test/resources/schematics/litematica/v5/simple.litematic: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandroHc/schematic4j/c581e2f605cea6820cad6c59937fe15eec961dc1/src/test/resources/schematics/litematica/v5/simple.litematic -------------------------------------------------------------------------------- /src/test/resources/schematics/litematica/v5/tower.litematic: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandroHc/schematic4j/c581e2f605cea6820cad6c59937fe15eec961dc1/src/test/resources/schematics/litematica/v5/tower.litematic -------------------------------------------------------------------------------- /src/test/resources/schematics/litematica/v6/demo.litematic: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandroHc/schematic4j/c581e2f605cea6820cad6c59937fe15eec961dc1/src/test/resources/schematics/litematica/v6/demo.litematic -------------------------------------------------------------------------------- /src/test/resources/schematics/schematica/12727.schematic: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandroHc/schematic4j/c581e2f605cea6820cad6c59937fe15eec961dc1/src/test/resources/schematics/schematica/12727.schematic -------------------------------------------------------------------------------- /src/test/resources/schematics/schematica/9383.schematic: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandroHc/schematic4j/c581e2f605cea6820cad6c59937fe15eec961dc1/src/test/resources/schematics/schematica/9383.schematic -------------------------------------------------------------------------------- /src/test/resources/schematics/sponge/v1/sponge-v1.schem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandroHc/schematic4j/c581e2f605cea6820cad6c59937fe15eec961dc1/src/test/resources/schematics/sponge/v1/sponge-v1.schem -------------------------------------------------------------------------------- /src/test/resources/schematics/sponge/v2/green-cottage.schem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandroHc/schematic4j/c581e2f605cea6820cad6c59937fe15eec961dc1/src/test/resources/schematics/sponge/v2/green-cottage.schem -------------------------------------------------------------------------------- /src/test/resources/schematics/sponge/v2/interieur-exterieur-chunk-project.schem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandroHc/schematic4j/c581e2f605cea6820cad6c59937fe15eec961dc1/src/test/resources/schematics/sponge/v2/interieur-exterieur-chunk-project.schem -------------------------------------------------------------------------------- /src/test/resources/schematics/sponge/v2/issue-1.schem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandroHc/schematic4j/c581e2f605cea6820cad6c59937fe15eec961dc1/src/test/resources/schematics/sponge/v2/issue-1.schem -------------------------------------------------------------------------------- /src/test/resources/schematics/sponge/v3/sponge-v3.schem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandroHc/schematic4j/c581e2f605cea6820cad6c59937fe15eec961dc1/src/test/resources/schematics/sponge/v3/sponge-v3.schem -------------------------------------------------------------------------------- /src/test/resources/simplelogger.properties: -------------------------------------------------------------------------------- 1 | org.slf4j.simpleLogger.defaultLogLevel=trace -------------------------------------------------------------------------------- /src/test/resources/snapshot.properties: -------------------------------------------------------------------------------- 1 | serializer=au.com.origin.snapshots.jackson.serializers.v1.DeterministicJacksonSnapshotSerializer 2 | comparator=au.com.origin.snapshots.comparators.v1.PlainTextEqualsComparator 3 | reporters=au.com.origin.snapshots.reporters.v1.PlainTextSnapshotReporter 4 | snapshot-dir=__snapshots__ 5 | output-dir=src/test/java 6 | ci-env-var=CI 7 | update-snapshot=none --------------------------------------------------------------------------------