3.0.0 Release

- More general refactoring
- Add SNBT serializer
- Rebrand to ViaNBT, as the project has shifted greatly from the original

Package rename will follow at a (much) later date when ViaVersion is ready for another major version bump
This commit is contained in:
Nassim Jahnke 2023-10-08 16:16:44 +10:00
parent fe18bc74e7
commit 63c109efde
31 changed files with 1105 additions and 238 deletions

View File

@ -1,8 +1,11 @@
# OpenNBT
OpenNBT is a library for reading and writing NBT files, with some extra custom tags added to allow the storage of more data types.
# ViaNBT
ViaNBT is a library for dealing with [NBT](https://minecraft.wiki/w/NBT_format) and SNBT.
This project is derived from an earlier version of [OpenNBT](https://github.com/GeyserMC/OpenNBT/) and contains various fundamental improvements and changes to it, including:
This fork contains various improvements and changes specifically made for use in Via plugins, including but not limited to the following list:
* Most notably, move the tag name out the of tags themselves
* `SNBT` for string serialization
* Add primitive getter methods to number types
* Don't wrap values given in Tag#setValue / Tag constructors
* Abstract NumberTag class for easier number handling
@ -12,8 +15,43 @@ This fork contains various improvements and changes specifically made for use in
* Implement tag specific equals() methods
* Update to Java 8
## Building the Source
OpenNBT uses Maven to manage dependencies. Simply run 'mvn clean install' in the source's directory.
This project also includes code from [adventure](https://github.com/KyoriPowered/adventure) used for SNBT serialization.
## Dependency
**Maven:**
```xml
<repository>
<id>viaversion-repo</id>
<url>https://repo.viaversion.com</url>
</repository>
```
```xml
<dependency>
<groupId>com.viaversion</groupId>
<artifactId>nbt</artifactId>
<version>3.0.0</version>
</dependency>
```
**Gradle:**
```kotlin
repositories {
maven("https://repo.viaversion.com")
}
dependencies {
implementation("com.viaversion:nbt:3.0.0")
}
```
## Building
Run `mvn install` in the source's directory via Maven.
## License
OpenNBT is licensed under the **[MIT license](http://www.opensource.org/licenses/mit-license.html)**.
ViaNBT is licensed under the **[MIT license](http://www.opensource.org/licenses/mit-license.html)**.

13
pom.xml
View File

@ -4,13 +4,13 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.viaversion</groupId>
<artifactId>opennbt</artifactId>
<version>2.1.3</version>
<artifactId>nbt</artifactId>
<version>3.0.0</version>
<packaging>jar</packaging>
<name>OpenNBT</name>
<name>ViaNBT</name>
<description>A library for reading and writing NBT files, written in Java.</description>
<url>https://github.com/ViaVersion/OpenNBT</url>
<url>https://github.com/ViaVersion/ViaNBT</url>
<distributionManagement>
<repository>
@ -52,6 +52,7 @@
</issueManagement>
<dependencies>
<!-- Expected to be bundled -->
<dependency>
<groupId>it.unimi.dsi</groupId>
<artifactId>fastutil</artifactId>
@ -61,7 +62,7 @@
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<version>24.0.0</version>
<version>24.0.1</version>
<scope>provided</scope>
</dependency>
</dependencies>
@ -90,7 +91,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.3.0</version>
<version>3.2.1</version>
<executions>
<execution>
<id>attach-sources</id>

View File

@ -74,12 +74,7 @@ public final class NBTIO {
in = new GZIPInputStream(in);
}
CompoundTag tag = readTag(in, littleEndian);
if (!(tag instanceof CompoundTag)) {
throw new IOException("Root tag is not a CompoundTag!");
}
return tag;
return readTag(in, littleEndian);
} finally {
in.close();
}
@ -131,7 +126,7 @@ public final class NBTIO {
*/
public static void writeFile(CompoundTag tag, File file, boolean compressed, boolean littleEndian) throws IOException {
if (!file.exists()) {
if (file.getParentFile() != null && !file.getParentFile().exists()) {
if (file.getParentFile() != null) {
file.getParentFile().mkdirs();
}

View File

@ -1,24 +0,0 @@
package com.github.steveice10.opennbt.conversion;
/**
* An exception thrown when an error occurs while registering a converter.
*/
public class ConverterRegisterException extends RuntimeException {
private static final long serialVersionUID = -2022049594558041160L;
public ConverterRegisterException() {
super();
}
public ConverterRegisterException(String message) {
super(message);
}
public ConverterRegisterException(Throwable cause) {
super(cause);
}
public ConverterRegisterException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -1,17 +1,18 @@
package com.github.steveice10.opennbt.conversion;
import com.github.steveice10.opennbt.conversion.builtin.ByteArrayTagConverter;
import com.github.steveice10.opennbt.conversion.builtin.ByteTagConverter;
import com.github.steveice10.opennbt.conversion.builtin.CompoundTagConverter;
import com.github.steveice10.opennbt.conversion.builtin.DoubleTagConverter;
import com.github.steveice10.opennbt.conversion.builtin.FloatTagConverter;
import com.github.steveice10.opennbt.conversion.builtin.IntArrayTagConverter;
import com.github.steveice10.opennbt.conversion.builtin.IntTagConverter;
import com.github.steveice10.opennbt.conversion.builtin.ListTagConverter;
import com.github.steveice10.opennbt.conversion.builtin.LongArrayTagConverter;
import com.github.steveice10.opennbt.conversion.builtin.LongTagConverter;
import com.github.steveice10.opennbt.conversion.builtin.ShortTagConverter;
import com.github.steveice10.opennbt.conversion.builtin.StringTagConverter;
import com.github.steveice10.opennbt.conversion.converter.ByteArrayTagConverter;
import com.github.steveice10.opennbt.conversion.converter.ByteTagConverter;
import com.github.steveice10.opennbt.conversion.converter.CompoundTagConverter;
import com.github.steveice10.opennbt.conversion.converter.DoubleTagConverter;
import com.github.steveice10.opennbt.conversion.converter.FloatTagConverter;
import com.github.steveice10.opennbt.conversion.converter.IntArrayTagConverter;
import com.github.steveice10.opennbt.conversion.converter.IntTagConverter;
import com.github.steveice10.opennbt.conversion.converter.ListTagConverter;
import com.github.steveice10.opennbt.conversion.converter.LongArrayTagConverter;
import com.github.steveice10.opennbt.conversion.converter.LongTagConverter;
import com.github.steveice10.opennbt.conversion.converter.ShortTagConverter;
import com.github.steveice10.opennbt.conversion.converter.StringTagConverter;
import com.github.steveice10.opennbt.tag.TagRegistry;
import com.github.steveice10.opennbt.tag.builtin.ByteArrayTag;
import com.github.steveice10.opennbt.tag.builtin.ByteTag;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
@ -25,20 +26,19 @@ import com.github.steveice10.opennbt.tag.builtin.LongTag;
import com.github.steveice10.opennbt.tag.builtin.ShortTag;
import com.github.steveice10.opennbt.tag.builtin.StringTag;
import com.github.steveice10.opennbt.tag.builtin.Tag;
import java.io.Serializable;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jetbrains.annotations.Nullable;
/**
* A registry mapping tags and value types to converters.
*/
public class ConverterRegistry {
private static final Map<Class<? extends Tag>, TagConverter<? extends Tag, ?>> tagToConverter = new HashMap<Class<? extends Tag>, TagConverter<? extends Tag, ?>>();
private static final Map<Class<?>, TagConverter<? extends Tag, ?>> typeToConverter = new HashMap<Class<?>, TagConverter<? extends Tag, ?>>();
private static final Int2ObjectMap<TagConverter<? extends Tag, ?>> TAG_TO_CONVERTER = new Int2ObjectOpenHashMap<>();
private static final Map<Class<?>, TagConverter<? extends Tag, ?>> TYPE_TO_CONVERTER = new HashMap<>();
static {
register(ByteTag.class, Byte.class, new ByteTagConverter());
@ -63,19 +63,22 @@ public class ConverterRegistry {
* @param tag Tag type class to register the converter to.
* @param type Value type class to register the converter to.
* @param converter Converter to register.
* @throws ConverterRegisterException If an error occurs while registering the converter.
* @throws IllegalArgumentException if the tag or type are already registered
*/
public static <T extends Tag, V> void register(Class<T> tag, Class<V> type, TagConverter<T, V> converter) throws ConverterRegisterException {
if (tagToConverter.containsKey(tag)) {
throw new ConverterRegisterException("Type conversion to tag " + tag.getName() + " is already registered.");
public static <T extends Tag, V> void register(Class<T> tag, Class<? extends V> type, TagConverter<T, V> converter) {
int tagId = TagRegistry.getIdFor(tag);
if (tagId == -1) {
throw new IllegalArgumentException("Tag " + tag.getName() + " is not a registered tag.");
}
if (TAG_TO_CONVERTER.containsKey(tagId)) {
throw new IllegalArgumentException("Type conversion to tag " + tag.getName() + " is already registered.");
}
if (TYPE_TO_CONVERTER.containsKey(type)) {
throw new IllegalArgumentException("Tag conversion to type " + type.getName() + " is already registered.");
}
if (typeToConverter.containsKey(type)) {
throw new ConverterRegisterException("Tag conversion to type " + type.getName() + " is already registered.");
}
tagToConverter.put(tag, converter);
typeToConverter.put(type, converter);
TAG_TO_CONVERTER.put(tagId, converter);
TYPE_TO_CONVERTER.put(type, converter);
}
/**
@ -87,8 +90,8 @@ public class ConverterRegistry {
* @param type Value type class to unregister.
*/
public static <T extends Tag, V> void unregister(Class<T> tag, Class<V> type) {
tagToConverter.remove(tag);
typeToConverter.remove(type);
TAG_TO_CONVERTER.remove(TagRegistry.getIdFor(tag));
TYPE_TO_CONVERTER.remove(type);
}
/**
@ -100,17 +103,17 @@ public class ConverterRegistry {
* @return The converted value.
* @throws ConversionException If a suitable converter could not be found.
*/
public static <T extends Tag, V> V convertToValue(T tag) throws ConversionException {
public static <T extends Tag, V> @Nullable V convertToValue(@Nullable T tag) throws ConversionException {
if (tag == null || tag.getValue() == null) {
return null;
}
if (!tagToConverter.containsKey(tag.getClass())) {
TagConverter<T, ? extends V> converter = (TagConverter<T, ? extends V>) TAG_TO_CONVERTER.get(tag.getClass());
if (converter == null) {
throw new ConversionException("Tag type " + tag.getClass().getName() + " has no converter.");
}
TagConverter<T, ?> converter = (TagConverter<T, ?>) tagToConverter.get(tag.getClass());
return (V) converter.convert(tag);
return converter.convert(tag);
}
/**
@ -122,56 +125,19 @@ public class ConverterRegistry {
* @return The converted tag.
* @throws ConversionException If a suitable converter could not be found.
*/
public static <V, T extends Tag> T convertToTag(V value) throws ConversionException {
public static <V, T extends Tag> @Nullable T convertToTag(@Nullable V value) throws ConversionException {
if (value == null) {
return null;
}
TagConverter<T, V> converter = (TagConverter<T, V>) typeToConverter.get(value.getClass());
// No need to check super classes since registering custom tags is not allowed
// and all the given ones cannot be extended, super class can't be instantiated
Class<?> valueClass = value.getClass();
TagConverter<T, ? super V> converter = (TagConverter<T, ? super V>) TYPE_TO_CONVERTER.get(valueClass);
if (converter == null) {
for (Class<?> clazz : getAllClasses(value.getClass())) {
if (typeToConverter.containsKey(clazz)) {
try {
converter = (TagConverter<T, V>) typeToConverter.get(clazz);
break;
} catch (ClassCastException e) {
}
}
}
}
if (converter == null) {
throw new ConversionException("Value type " + value.getClass().getName() + " has no converter.");
throw new ConversionException("Value type " + valueClass.getName() + " has no converter.");
}
return converter.convert(value);
}
private static Set<Class<?>> getAllClasses(Class<?> clazz) {
Set<Class<?>> ret = new LinkedHashSet<Class<?>>();
Class<?> c = clazz;
while (c != null) {
ret.add(c);
ret.addAll(getAllSuperInterfaces(c));
c = c.getSuperclass();
}
// Make sure Serializable is at the end to avoid mix-ups.
if (ret.contains(Serializable.class)) {
ret.remove(Serializable.class);
ret.add(Serializable.class);
}
return ret;
}
private static Set<Class<?>> getAllSuperInterfaces(Class<?> clazz) {
Set<Class<?>> ret = new HashSet<Class<?>>();
for (Class<?> c : clazz.getInterfaces()) {
ret.add(c);
ret.addAll(getAllSuperInterfaces(c));
}
return ret;
}
}

View File

@ -15,7 +15,7 @@ public interface TagConverter<T extends Tag, V> {
* @param tag Tag to convert.
* @return The converted value.
*/
public V convert(T tag);
V convert(T tag);
/**
* Converts a value to a tag.
@ -23,5 +23,5 @@ public interface TagConverter<T extends Tag, V> {
* @param value Value to convert.
* @return The converted tag.
*/
public T convert(V value);
T convert(V value);
}

View File

@ -1,4 +1,4 @@
package com.github.steveice10.opennbt.conversion.builtin;
package com.github.steveice10.opennbt.conversion.converter;
import com.github.steveice10.opennbt.conversion.TagConverter;
import com.github.steveice10.opennbt.tag.builtin.ByteArrayTag;

View File

@ -1,4 +1,4 @@
package com.github.steveice10.opennbt.conversion.builtin;
package com.github.steveice10.opennbt.conversion.converter;
import com.github.steveice10.opennbt.conversion.TagConverter;
import com.github.steveice10.opennbt.tag.builtin.ByteTag;

View File

@ -1,4 +1,4 @@
package com.github.steveice10.opennbt.conversion.builtin;
package com.github.steveice10.opennbt.conversion.converter;
import com.github.steveice10.opennbt.conversion.ConverterRegistry;
import com.github.steveice10.opennbt.conversion.TagConverter;

View File

@ -1,4 +1,4 @@
package com.github.steveice10.opennbt.conversion.builtin;
package com.github.steveice10.opennbt.conversion.converter;
import com.github.steveice10.opennbt.conversion.TagConverter;
import com.github.steveice10.opennbt.tag.builtin.DoubleTag;

View File

@ -1,4 +1,4 @@
package com.github.steveice10.opennbt.conversion.builtin;
package com.github.steveice10.opennbt.conversion.converter;
import com.github.steveice10.opennbt.conversion.TagConverter;
import com.github.steveice10.opennbt.tag.builtin.FloatTag;

View File

@ -1,4 +1,4 @@
package com.github.steveice10.opennbt.conversion.builtin;
package com.github.steveice10.opennbt.conversion.converter;
import com.github.steveice10.opennbt.conversion.TagConverter;
import com.github.steveice10.opennbt.tag.builtin.IntArrayTag;

View File

@ -1,4 +1,4 @@
package com.github.steveice10.opennbt.conversion.builtin;
package com.github.steveice10.opennbt.conversion.converter;
import com.github.steveice10.opennbt.conversion.TagConverter;
import com.github.steveice10.opennbt.tag.builtin.IntTag;

View File

@ -1,4 +1,4 @@
package com.github.steveice10.opennbt.conversion.builtin;
package com.github.steveice10.opennbt.conversion.converter;
import com.github.steveice10.opennbt.conversion.ConverterRegistry;
import com.github.steveice10.opennbt.conversion.TagConverter;

View File

@ -1,4 +1,4 @@
package com.github.steveice10.opennbt.conversion.builtin;
package com.github.steveice10.opennbt.conversion.converter;
import com.github.steveice10.opennbt.conversion.TagConverter;
import com.github.steveice10.opennbt.tag.builtin.LongArrayTag;

View File

@ -1,4 +1,4 @@
package com.github.steveice10.opennbt.conversion.builtin;
package com.github.steveice10.opennbt.conversion.converter;
import com.github.steveice10.opennbt.conversion.TagConverter;
import com.github.steveice10.opennbt.tag.builtin.LongTag;

View File

@ -1,4 +1,4 @@
package com.github.steveice10.opennbt.conversion.builtin;
package com.github.steveice10.opennbt.conversion.converter;
import com.github.steveice10.opennbt.conversion.TagConverter;
import com.github.steveice10.opennbt.tag.builtin.ShortTag;

View File

@ -1,4 +1,4 @@
package com.github.steveice10.opennbt.conversion.builtin;
package com.github.steveice10.opennbt.conversion.converter;
import com.github.steveice10.opennbt.conversion.TagConverter;
import com.github.steveice10.opennbt.tag.builtin.StringTag;

View File

@ -0,0 +1,148 @@
/*
* This file is part of adventure, licensed under the MIT License.
*
* Copyright (c) 2017-2021 KyoriPowered
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.steveice10.opennbt.stringified;
/**
* A character buffer designed to be inspected by a parser.
*/
final class CharBuffer {
private final CharSequence sequence;
private int index;
CharBuffer(final CharSequence sequence) {
this.sequence = sequence;
}
/**
* Get the character at the current position.
*
* @return The current character
*/
public char peek() {
return this.sequence.charAt(this.index);
}
public char peek(final int offset) {
return this.sequence.charAt(this.index + offset);
}
/**
* Get the current character and advance.
*
* @return current character
*/
public char take() {
return this.sequence.charAt(this.index++);
}
public boolean advance() {
this.index++;
return this.hasMore();
}
public boolean hasMore() {
return this.index < this.sequence.length();
}
public boolean hasMore(final int offset) {
return this.index + offset < this.sequence.length();
}
/**
* Search for the provided token, and advance the reader index past the {@code until} character.
*
* @param until Case-insensitive token
* @return the string starting at the current position (inclusive) and going until the location of {@code until}, exclusive
*/
public CharSequence takeUntil(char until) throws StringifiedTagParseException {
until = Character.toLowerCase(until);
int endIdx = -1;
for (int idx = this.index; idx < this.sequence.length(); ++idx) {
if (this.sequence.charAt(idx) == Tokens.ESCAPE_MARKER) {
idx++;
} else if (Character.toLowerCase(this.sequence.charAt(idx)) == until) {
endIdx = idx;
break;
}
}
if (endIdx == -1) {
throw this.makeError("No occurrence of " + until + " was found");
}
final CharSequence result = this.sequence.subSequence(this.index, endIdx);
this.index = endIdx + 1;
return result;
}
/**
* Assert that the next non-whitespace character is the provided parameter.
*
* <p>If the assertion is successful, the token will be consumed.</p>
*
* @param expectedChar expected character
* @return this
* @throws StringifiedTagParseException if EOF or non-matching value is found
*/
public CharBuffer expect(final char expectedChar) throws StringifiedTagParseException {
this.skipWhitespace();
if (!this.hasMore()) {
throw this.makeError("Expected character '" + expectedChar + "' but got EOF");
}
if (this.peek() != expectedChar) {
throw this.makeError("Expected character '" + expectedChar + "' but got '" + this.peek() + "'");
}
this.take();
return this;
}
/**
* If the next non-whitespace character is {@code token}, advance past it.
*
* <p>This method always consumes whitespace.</p>
*
* @param token next non-whitespace character to query
* @return if the next non-whitespace character is {@code token}
*/
public boolean takeIf(final char token) {
this.skipWhitespace();
if (this.hasMore() && this.peek() == token) {
this.advance();
return true;
}
return false;
}
public int index() {
return this.index;
}
public CharBuffer skipWhitespace() {
while (this.hasMore() && Character.isWhitespace(this.peek())) this.advance();
return this;
}
public StringifiedTagParseException makeError(final String message) {
return new StringifiedTagParseException(message, this.index);
}
}

View File

@ -0,0 +1,54 @@
package com.github.steveice10.opennbt.stringified;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.Tag;
/**
* Serialization of stringifies tags.
*/
public final class SNBT {
private SNBT() {
}
/**
* Reads a compound tag from a {@link String}.
*
* @param snbt SNBT input
* @return compound tag from the given SNBT input
* @throws StringifiedTagParseException if an exception was encountered while reading a compound tag
*/
public static Tag deserialize(final String snbt) {
final CharBuffer buffer = new CharBuffer(snbt);
final TagStringReader parser = new TagStringReader(buffer);
final Tag tag = parser.tag();
if (buffer.skipWhitespace().hasMore()) {
throw new StringifiedTagParseException("Input has trailing content", buffer.index());
}
return tag;
}
public static CompoundTag deserializeCompoundTag(final String snbt) {
final CharBuffer buffer = new CharBuffer(snbt);
final TagStringReader reader = new TagStringReader(buffer);
final CompoundTag tag = reader.compound();
if (buffer.skipWhitespace().hasMore()) {
throw new StringifiedTagParseException("Input has trailing content", buffer.index());
}
return tag;
}
/**
* Serializes a tag to SNBT.
*
* @param tag the compound tag
* @return serialized SNBT
* @throws IllegalArgumentException if an unknown tag is provided
*/
public static String serialize(final Tag tag) {
final StringBuilder builder = new StringBuilder();
final TagStringWriter writer = new TagStringWriter(builder);
writer.writeTag(tag);
return builder.toString();
}
}

View File

@ -0,0 +1,52 @@
/*
* This file is part of adventure, licensed under the MIT License.
*
* Copyright (c) 2017-2020 KyoriPowered
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.steveice10.opennbt.stringified;
// Specific Via changes:
// - Remove buffer field
// - Make public
// - Make unchecked
// - Rename to Stringified
/**
* An exception thrown when parsing a stringified binary tag.
*/
public final class StringifiedTagParseException extends RuntimeException {
private static final long serialVersionUID = -3001637514903912905L;
private final int position;
public StringifiedTagParseException(final String message, final int position) {
super(message);
this.position = position;
}
@Override
public String getMessage() {
return super.getMessage() + "(at position " + this.position + ")";
}
public int getPosition() {
return position;
}
}

View File

@ -0,0 +1,358 @@
/*
* This file is part of adventure, licensed under the MIT License.
*
* Copyright (c) 2017-2021 KyoriPowered
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.steveice10.opennbt.stringified;
import com.github.steveice10.opennbt.tag.builtin.ByteArrayTag;
import com.github.steveice10.opennbt.tag.builtin.ByteTag;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.DoubleTag;
import com.github.steveice10.opennbt.tag.builtin.FloatTag;
import com.github.steveice10.opennbt.tag.builtin.IntArrayTag;
import com.github.steveice10.opennbt.tag.builtin.IntTag;
import com.github.steveice10.opennbt.tag.builtin.ListTag;
import com.github.steveice10.opennbt.tag.builtin.LongArrayTag;
import com.github.steveice10.opennbt.tag.builtin.LongTag;
import com.github.steveice10.opennbt.tag.builtin.NumberTag;
import com.github.steveice10.opennbt.tag.builtin.ShortTag;
import com.github.steveice10.opennbt.tag.builtin.StringTag;
import com.github.steveice10.opennbt.tag.builtin.Tag;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import java.util.stream.IntStream;
import java.util.stream.LongStream;
// Specific Via changes:
// - Use ViaNBT tags
// - Small byteArray() optimization
// - acceptLegacy = true by default
final class TagStringReader {
private static final int MAX_DEPTH = 512;
private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
private static final int[] EMPTY_INT_ARRAY = new int[0];
private static final long[] EMPTY_LONG_ARRAY = new long[0];
private final CharBuffer buffer;
private boolean acceptLegacy = true; // Via - always true
private int depth;
TagStringReader(final CharBuffer buffer) {
this.buffer = buffer;
}
public CompoundTag compound() throws StringifiedTagParseException {
this.buffer.expect(Tokens.COMPOUND_BEGIN);
final CompoundTag compoundTag = new CompoundTag();
if (this.buffer.takeIf(Tokens.COMPOUND_END)) {
return compoundTag;
}
while (this.buffer.hasMore()) {
compoundTag.put(this.key(), this.tag());
if (this.separatorOrCompleteWith(Tokens.COMPOUND_END)) {
return compoundTag;
}
}
throw this.buffer.makeError("Unterminated compound tag!");
}
public ListTag list() throws StringifiedTagParseException {
final ListTag listTag = new ListTag();
this.buffer.expect(Tokens.ARRAY_BEGIN);
final boolean prefixedIndex = this.acceptLegacy && this.buffer.peek() == '0' && this.buffer.peek(1) == ':';
if (!prefixedIndex && this.buffer.takeIf(Tokens.ARRAY_END)) {
return listTag;
}
while (this.buffer.hasMore()) {
if (prefixedIndex) {
this.buffer.takeUntil(':');
}
final Tag next = this.tag();
listTag.add(next);
if (this.separatorOrCompleteWith(Tokens.ARRAY_END)) {
return listTag;
}
}
throw this.buffer.makeError("Reached end of file without end of list tag!");
}
/**
* Similar to a list tag in syntax, but returning a single array tag rather than a list of tags.
*
* @return array-typed tag
*/
public Tag array(char elementType) throws StringifiedTagParseException {
this.buffer.expect(Tokens.ARRAY_BEGIN)
.expect(elementType)
.expect(Tokens.ARRAY_SIGNATURE_SEPARATOR);
elementType = Character.toLowerCase(elementType);
if (elementType == Tokens.TYPE_BYTE) {
return new ByteArrayTag(this.byteArray());
} else if (elementType == Tokens.TYPE_INT) {
return new IntArrayTag(this.intArray());
} else if (elementType == Tokens.TYPE_LONG) {
return new LongArrayTag(this.longArray());
} else {
throw this.buffer.makeError("Type " + elementType + " is not a valid element type in an array!");
}
}
private byte[] byteArray() throws StringifiedTagParseException {
if (this.buffer.takeIf(Tokens.ARRAY_END)) {
return EMPTY_BYTE_ARRAY;
}
final IntList bytes = new IntArrayList(); // Via - no boxing
while (this.buffer.hasMore()) {
final CharSequence value = this.buffer.skipWhitespace().takeUntil(Tokens.TYPE_BYTE);
try {
bytes.add(Byte.parseByte(value.toString())); // Via
} catch (final NumberFormatException ex) {
throw this.buffer.makeError("All elements of a byte array must be bytes!");
}
if (this.separatorOrCompleteWith(Tokens.ARRAY_END)) {
final byte[] result = new byte[bytes.size()];
for (int i = 0; i < bytes.size(); ++i) {
result[i] = (byte) bytes.getInt(i); // Via
}
return result;
}
}
throw this.buffer.makeError("Reached end of document without array close");
}
private int[] intArray() throws StringifiedTagParseException {
if (this.buffer.takeIf(Tokens.ARRAY_END)) {
return EMPTY_INT_ARRAY;
}
final IntStream.Builder builder = IntStream.builder();
while (this.buffer.hasMore()) {
final Tag value = this.tag();
if (!(value instanceof IntTag)) {
throw this.buffer.makeError("All elements of an int array must be ints!");
}
builder.add(((NumberTag) value).asInt());
if (this.separatorOrCompleteWith(Tokens.ARRAY_END)) {
return builder.build().toArray();
}
}
throw this.buffer.makeError("Reached end of document without array close");
}
private long[] longArray() throws StringifiedTagParseException {
if (this.buffer.takeIf(Tokens.ARRAY_END)) {
return EMPTY_LONG_ARRAY;
}
final LongStream.Builder longs = LongStream.builder();
while (this.buffer.hasMore()) {
final CharSequence value = this.buffer.skipWhitespace().takeUntil(Tokens.TYPE_LONG);
try {
longs.add(Long.parseLong(value.toString()));
} catch (final NumberFormatException ex) {
throw this.buffer.makeError("All elements of a long array must be longs!");
}
if (this.separatorOrCompleteWith(Tokens.ARRAY_END)) {
return longs.build().toArray();
}
}
throw this.buffer.makeError("Reached end of document without array close");
}
public String key() throws StringifiedTagParseException {
this.buffer.skipWhitespace();
final char starChar = this.buffer.peek();
try {
if (starChar == Tokens.SINGLE_QUOTE || starChar == Tokens.DOUBLE_QUOTE) {
return unescape(this.buffer.takeUntil(this.buffer.take()).toString());
}
final StringBuilder builder = new StringBuilder();
while (this.buffer.hasMore()) {
final char peek = this.buffer.peek();
if (!Tokens.id(peek)) {
if (this.acceptLegacy) {
// In legacy format, a key is any non-colon character, with escapes allowed
if (peek == Tokens.ESCAPE_MARKER) {
this.buffer.take(); // skip
continue;
} else if (peek != Tokens.COMPOUND_KEY_TERMINATOR) {
builder.append(this.buffer.take());
continue;
}
}
break;
}
builder.append(this.buffer.take());
}
return builder.toString();
} finally {
this.buffer.expect(Tokens.COMPOUND_KEY_TERMINATOR);
}
}
public Tag tag() throws StringifiedTagParseException {
if (this.depth++ > MAX_DEPTH) {
throw this.buffer.makeError("Exceeded maximum allowed depth of " + MAX_DEPTH + " when reading tag");
}
try {
final char startToken = this.buffer.skipWhitespace().peek();
switch (startToken) {
case Tokens.COMPOUND_BEGIN:
return this.compound();
case Tokens.ARRAY_BEGIN:
// Maybe add in a legacy-only mode to read those?
if (this.buffer.hasMore(2) && this.buffer.peek(2) == ';') { // we know we're an array tag
return this.array(this.buffer.peek(1));
} else {
return this.list();
}
case Tokens.SINGLE_QUOTE:
case Tokens.DOUBLE_QUOTE:
// definitely a string tag
this.buffer.advance();
return new StringTag(unescape(this.buffer.takeUntil(startToken).toString()));
default: // scalar
return this.scalar();
}
} finally {
this.depth--;
}
}
/**
* A tag that is definitely some sort of scalar.
*
* <p>Does not detect quoted strings, so those should have been parsed already.</p>
*
* @return a parsed tag
*/
private Tag scalar() {
final StringBuilder builder = new StringBuilder();
int noLongerNumericAt = -1;
while (this.buffer.hasMore()) {
char current = this.buffer.peek();
if (current == '\\') { // escape -- we are significantly more lenient than original format at the moment
this.buffer.advance();
current = this.buffer.take();
} else if (Tokens.id(current)) {
this.buffer.advance();
} else { // end of value
break;
}
builder.append(current);
if (noLongerNumericAt == -1 && !Tokens.numeric(current)) {
noLongerNumericAt = builder.length();
}
}
final int length = builder.length();
final String built = builder.toString();
if (noLongerNumericAt == length) {
final char last = built.charAt(length - 1);
try {
switch (Character.toLowerCase(last)) { // try to read and return as a number
case Tokens.TYPE_BYTE:
return new ByteTag(Byte.parseByte(built.substring(0, length - 1)));
case Tokens.TYPE_SHORT:
return new ShortTag(Short.parseShort(built.substring(0, length - 1)));
case Tokens.TYPE_INT:
return new IntTag(Integer.parseInt(built.substring(0, length - 1)));
case Tokens.TYPE_LONG:
return new LongTag(Long.parseLong(built.substring(0, length - 1)));
case Tokens.TYPE_FLOAT:
final float floatValue = Float.parseFloat(built.substring(0, length - 1));
if (Float.isFinite(floatValue)) { // don't accept NaN and Infinity
return new FloatTag(floatValue);
}
break;
case Tokens.TYPE_DOUBLE:
final double doubleValue = Double.parseDouble(built.substring(0, length - 1));
if (Double.isFinite(doubleValue)) { // don't accept NaN and Infinity
return new DoubleTag(doubleValue);
}
break;
}
} catch (final NumberFormatException ignored) {
}
} else if (noLongerNumericAt == -1) { // if we run out of content without an explicit value separator, then we're either an integer or string tag -- all others have a character at the end
try {
return new IntTag(Integer.parseInt(built));
} catch (final NumberFormatException ex) {
if (built.indexOf('.') != -1) {
try {
return new DoubleTag(Double.parseDouble(built));
} catch (final NumberFormatException ex2) {
// ignore
}
}
}
}
if (built.equalsIgnoreCase(Tokens.LITERAL_TRUE)) {
return new ByteTag((byte) 1);
} else if (built.equalsIgnoreCase(Tokens.LITERAL_FALSE)) {
return new ByteTag((byte) 0);
}
return new StringTag(built);
}
private boolean separatorOrCompleteWith(final char endCharacter) throws StringifiedTagParseException {
if (this.buffer.takeIf(endCharacter)) {
return true;
}
this.buffer.expect(Tokens.VALUE_SEPARATOR);
return this.buffer.takeIf(endCharacter);
}
/**
* Remove simple escape sequences from a string.
*
* @param withEscapes input string with escapes
* @return string with escapes processed
*/
private static String unescape(final String withEscapes) {
int escapeIdx = withEscapes.indexOf(Tokens.ESCAPE_MARKER);
if (escapeIdx == -1) { // nothing to unescape
return withEscapes;
}
int lastEscape = 0;
final StringBuilder output = new StringBuilder(withEscapes.length());
do {
output.append(withEscapes, lastEscape, escapeIdx);
lastEscape = escapeIdx + 1;
} while ((escapeIdx = withEscapes.indexOf(Tokens.ESCAPE_MARKER, lastEscape + 1)) != -1); // add one extra character to make sure we don't include escaped backslashes
output.append(withEscapes.substring(lastEscape));
return output.toString();
}
public void legacy(final boolean acceptLegacy) {
this.acceptLegacy = acceptLegacy;
}
}

View File

@ -0,0 +1,244 @@
/*
* This file is part of adventure, licensed under the MIT License.
*
* Copyright (c) 2017-2020 KyoriPowered
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.steveice10.opennbt.stringified;
import com.github.steveice10.opennbt.tag.builtin.ByteArrayTag;
import com.github.steveice10.opennbt.tag.builtin.ByteTag;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.DoubleTag;
import com.github.steveice10.opennbt.tag.builtin.FloatTag;
import com.github.steveice10.opennbt.tag.builtin.IntArrayTag;
import com.github.steveice10.opennbt.tag.builtin.IntTag;
import com.github.steveice10.opennbt.tag.builtin.ListTag;
import com.github.steveice10.opennbt.tag.builtin.LongArrayTag;
import com.github.steveice10.opennbt.tag.builtin.LongTag;
import com.github.steveice10.opennbt.tag.builtin.NumberTag;
import com.github.steveice10.opennbt.tag.builtin.ShortTag;
import com.github.steveice10.opennbt.tag.builtin.StringTag;
import com.github.steveice10.opennbt.tag.builtin.Tag;
import java.util.Map;
// Specific Via changes:
// - Use ViaNBT tags
// - Do not throw IOException for non-I/O operation, replace Appendable with explicit StringBuilder
/**
* An emitter for the SNBT format.
*
* <p>Details on the format are described in the package documentation.</p>
*/
final class TagStringWriter {
private final StringBuilder out;
/**
* Whether a {@link Tokens#VALUE_SEPARATOR} needs to be printed before the beginning of the next object.
*/
private boolean needsSeparator;
public TagStringWriter(final StringBuilder out) {
this.out = out;
}
// NBT-specific
public TagStringWriter writeTag(final Tag tag) {
if (tag instanceof CompoundTag) {
return this.writeCompound((CompoundTag) tag);
} else if (tag instanceof ListTag) {
return this.writeList((ListTag) tag);
} else if (tag instanceof ByteArrayTag) {
return this.writeByteArray((ByteArrayTag) tag);
} else if (tag instanceof IntArrayTag) {
return this.writeIntArray((IntArrayTag) tag);
} else if (tag instanceof LongArrayTag) {
return this.writeLongArray((LongArrayTag) tag);
} else if (tag instanceof StringTag) {
return this.value(((StringTag) tag).getValue(), Tokens.EOF);
} else if (tag instanceof ByteTag) {
return this.value(Byte.toString(((NumberTag) tag).asByte()), Tokens.TYPE_BYTE);
} else if (tag instanceof ShortTag) {
return this.value(Short.toString(((NumberTag) tag).asShort()), Tokens.TYPE_SHORT);
} else if (tag instanceof IntTag) {
return this.value(Integer.toString(((NumberTag) tag).asInt()), Tokens.TYPE_INT);
} else if (tag instanceof LongTag) {
return this.value(Long.toString(((NumberTag) tag).asLong()), Character.toUpperCase(Tokens.TYPE_LONG)); // special case
} else if (tag instanceof FloatTag) {
return this.value(Float.toString(((NumberTag) tag).asFloat()), Tokens.TYPE_FLOAT);
} else if (tag instanceof DoubleTag) {
return this.value(Double.toString(((NumberTag) tag).asDouble()), Tokens.TYPE_DOUBLE);
} else {
throw new IllegalArgumentException("Unknown tag type: " + tag.getClass().getSimpleName());
// unknown!
}
}
private TagStringWriter writeCompound(final CompoundTag tag) {
this.beginCompound();
for (final Map.Entry<String, Tag> entry : tag.entrySet()) {
this.key(entry.getKey());
this.writeTag(entry.getValue());
}
this.endCompound();
return this;
}
private TagStringWriter writeList(final ListTag tag) {
this.beginList();
for (final Tag el : tag) {
this.printAndResetSeparator();
this.writeTag(el);
}
this.endList();
return this;
}
private TagStringWriter writeByteArray(final ByteArrayTag tag) {
this.beginArray(Tokens.TYPE_BYTE);
final byte[] value = tag.getValue();
for (int i = 0, length = value.length; i < length; i++) {
this.printAndResetSeparator();
this.value(Byte.toString(value[i]), Tokens.TYPE_BYTE);
}
this.endArray();
return this;
}
private TagStringWriter writeIntArray(final IntArrayTag tag) {
this.beginArray(Tokens.TYPE_INT);
final int[] value = tag.getValue();
for (int i = 0, length = value.length; i < length; i++) {
this.printAndResetSeparator();
this.value(Integer.toString(value[i]), Tokens.TYPE_INT);
}
this.endArray();
return this;
}
private TagStringWriter writeLongArray(final LongArrayTag tag) {
this.beginArray(Tokens.TYPE_LONG);
final long[] value = tag.getValue();
for (int i = 0, length = value.length; i < length; i++) {
this.printAndResetSeparator();
this.value(Long.toString(value[i]), Tokens.TYPE_LONG);
}
this.endArray();
return this;
}
// Value types
public TagStringWriter beginCompound() {
this.printAndResetSeparator();
this.out.append(Tokens.COMPOUND_BEGIN);
return this;
}
public TagStringWriter endCompound() {
this.out.append(Tokens.COMPOUND_END);
this.needsSeparator = true;
return this;
}
public TagStringWriter key(final String key) {
this.printAndResetSeparator();
this.writeMaybeQuoted(key, false);
this.out.append(Tokens.COMPOUND_KEY_TERMINATOR);
return this;
}
public TagStringWriter value(final String value, final char valueType) {
if (valueType == Tokens.EOF) { // string doesn't have its type
this.writeMaybeQuoted(value, true);
} else {
this.out.append(value);
if (valueType != Tokens.TYPE_INT) {
this.out.append(valueType);
}
}
this.needsSeparator = true;
return this;
}
public TagStringWriter beginList() {
this.printAndResetSeparator();
this.out.append(Tokens.ARRAY_BEGIN);
return this;
}
public TagStringWriter endList() {
this.out.append(Tokens.ARRAY_END);
this.needsSeparator = true;
return this;
}
private TagStringWriter beginArray(final char type) {
this.beginList()
.out.append(type)
.append(Tokens.ARRAY_SIGNATURE_SEPARATOR);
return this;
}
private TagStringWriter endArray() {
return this.endList();
}
private void writeMaybeQuoted(final String content, boolean requireQuotes) {
if (!requireQuotes) {
for (int i = 0; i < content.length(); ++i) {
if (!Tokens.id(content.charAt(i))) {
requireQuotes = true;
break;
}
}
}
if (requireQuotes) {
this.out.append(Tokens.DOUBLE_QUOTE);
this.out.append(escape(content, Tokens.DOUBLE_QUOTE));
this.out.append(Tokens.DOUBLE_QUOTE);
} else {
this.out.append(content);
}
}
private static String escape(final String content, final char quoteChar) {
final StringBuilder output = new StringBuilder(content.length());
for (int i = 0; i < content.length(); ++i) {
final char c = content.charAt(i);
if (c == quoteChar || c == '\\') {
output.append(Tokens.ESCAPE_MARKER);
}
output.append(c);
}
return output.toString();
}
private void printAndResetSeparator() {
if (this.needsSeparator) {
this.out.append(Tokens.VALUE_SEPARATOR);
this.needsSeparator = false;
}
}
}

View File

@ -0,0 +1,89 @@
/*
* This file is part of adventure, licensed under the MIT License.
*
* Copyright (c) 2017-2021 KyoriPowered
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.steveice10.opennbt.stringified;
final class Tokens {
// Compounds
static final char COMPOUND_BEGIN = '{';
static final char COMPOUND_END = '}';
static final char COMPOUND_KEY_TERMINATOR = ':';
// Arrays
static final char ARRAY_BEGIN = '[';
static final char ARRAY_END = ']';
static final char ARRAY_SIGNATURE_SEPARATOR = ';';
static final char VALUE_SEPARATOR = ',';
static final char SINGLE_QUOTE = '\'';
static final char DOUBLE_QUOTE = '"';
static final char ESCAPE_MARKER = '\\';
static final char TYPE_BYTE = 'b';
static final char TYPE_SHORT = 's';
static final char TYPE_INT = 'i'; // array only
static final char TYPE_LONG = 'l';
static final char TYPE_FLOAT = 'f';
static final char TYPE_DOUBLE = 'd';
static final String LITERAL_TRUE = "true";
static final String LITERAL_FALSE = "false";
static final String NEWLINE = System.getProperty("line.separator", "\n");
static final char EOF = '\0';
private Tokens() {
}
/**
* Return if a character is a valid component in an identifier.
*
* <p>An identifier character must match the expression {@code [a-zA-Z0-9_+.-]}</p>
*
* @param c the character
* @return identifier
*/
static boolean id(final char c) {
return (c >= 'a' && c <= 'z')
|| (c >= 'A' && c <= 'Z')
|| (c >= '0' && c <= '9')
|| c == '-' || c == '_'
|| c == '.' || c == '+';
}
/**
* Return whether a character could be at some position in a number.
*
* <p>A string passing this check does not necessarily mean it is syntactically valid.</p>
*
* @param c character to check
* @return if possibly part of a number
*/
static boolean numeric(final char c) {
return (c >= '0' && c <= '9') // digit
|| c == '+' || c == '-' // positive or negative
|| c == 'e' || c == 'E' // exponent
|| c == '.'; // decimal
}
}

View File

@ -1,24 +0,0 @@
package com.github.steveice10.opennbt.tag;
/**
* An exception thrown when an error occurs while created a tag instance.
*/
public class TagCreateException extends Exception {
private static final long serialVersionUID = -2022049594558041160L;
public TagCreateException() {
super();
}
public TagCreateException(String message) {
super(message);
}
public TagCreateException(Throwable cause) {
super(cause);
}
public TagCreateException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -1,24 +0,0 @@
package com.github.steveice10.opennbt.tag;
/**
* An exception thrown when an error occurs while registering a tag.
*/
public class TagRegisterException extends RuntimeException {
private static final long serialVersionUID = -2022049594558041160L;
public TagRegisterException() {
super();
}
public TagRegisterException(String message) {
super(message);
}
public TagRegisterException(Throwable cause) {
super(cause);
}
public TagRegisterException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -23,12 +23,11 @@ import org.jetbrains.annotations.Nullable;
*/
public final class TagRegistry {
private static final int HIGHEST_ID = LongArrayTag.ID;
private static final Class<? extends Tag>[] idToTag = new Class[HIGHEST_ID + 1];
private static final Supplier<? extends Tag>[] instanceSuppliers = new Supplier[HIGHEST_ID + 1];
private static final Object2IntMap<Class<? extends Tag>> tagToId = new Object2IntOpenHashMap<>();
private static final RegisteredTagType[] TAGS = new RegisteredTagType[HIGHEST_ID + 1];
private static final Object2IntMap<Class<? extends Tag>> TAG_TO_ID = new Object2IntOpenHashMap<>();
static {
tagToId.defaultReturnValue(-1);
TAG_TO_ID.defaultReturnValue(-1);
register(ByteTag.ID, ByteTag.class, ByteTag::new);
register(ShortTag.ID, ShortTag.class, ShortTag::new);
@ -49,22 +48,21 @@ public final class TagRegistry {
*
* @param id ID of the tag.
* @param tag Tag class to register.
* @throws TagRegisterException If an error occurs while registering the tag.
* @throws IllegalArgumentException if the id is unexpectedly out of bounds, or if the id or tag have already been registered
*/
public static void register(int id, Class<? extends Tag> tag, Supplier<? extends Tag> supplier) throws TagRegisterException {
public static void register(int id, Class<? extends Tag> tag, Supplier<? extends Tag> supplier) {
if (id < 0 || id > HIGHEST_ID) {
throw new TagRegisterException("Tag ID must be between 0 and " + HIGHEST_ID);
throw new IllegalArgumentException("Tag ID must be between 0 and " + HIGHEST_ID);
}
if (idToTag[id] != null) {
throw new TagRegisterException("Tag ID \"" + id + "\" is already in use.");
if (TAGS[id] != null) {
throw new IllegalArgumentException("Tag ID \"" + id + "\" is already in use.");
}
if (tagToId.containsKey(tag)) {
throw new TagRegisterException("Tag \"" + tag.getSimpleName() + "\" is already registered.");
if (TAG_TO_ID.containsKey(tag)) {
throw new IllegalArgumentException("Tag \"" + tag.getSimpleName() + "\" is already registered.");
}
instanceSuppliers[id] = supplier;
idToTag[id] = tag;
tagToId.put(tag, id);
TAGS[id] = new RegisteredTagType(tag, supplier);
TAG_TO_ID.put(tag, id);
}
/**
@ -73,9 +71,8 @@ public final class TagRegistry {
* @param id ID of the tag to unregister.
*/
public static void unregister(int id) {
tagToId.removeInt(getClassFor(id));
idToTag[id] = null;
instanceSuppliers[id] = null;
TAG_TO_ID.removeInt(getClassFor(id));
TAGS[id] = null;
}
/**
@ -86,7 +83,7 @@ public final class TagRegistry {
*/
@Nullable
public static Class<? extends Tag> getClassFor(int id) {
return id >= 0 && id < idToTag.length ? idToTag[id] : null;
return id >= 0 && id < TAGS.length ? TAGS[id].type : null;
}
/**
@ -96,7 +93,7 @@ public final class TagRegistry {
* @return The id of the given tag class, or -1 if it cannot be found.
*/
public static int getIdFor(Class<? extends Tag> clazz) {
return tagToId.getInt(clazz);
return TAG_TO_ID.getInt(clazz);
}
/**
@ -104,14 +101,25 @@ public final class TagRegistry {
*
* @param id Id of the tag.
* @return The created tag.
* @throws TagCreateException If an error occurs while creating the tag.
* @throws IllegalArgumentException if no tags is registered over the provided id
*/
public static Tag createInstance(int id) throws TagCreateException {
Supplier<? extends Tag> supplier = id > 0 && id < instanceSuppliers.length ? instanceSuppliers[id] : null;
public static Tag createInstance(int id) {
Supplier<? extends Tag> supplier = id > 0 && id < TAGS.length ? TAGS[id].supplier : null;
if (supplier == null) {
throw new TagCreateException("Could not find tag with ID \"" + id + "\".");
throw new IllegalArgumentException("Could not find tag with ID \"" + id + "\".");
}
return supplier.get();
}
private static final class RegisteredTagType {
private final Class<? extends Tag> type;
private final Supplier<? extends Tag> supplier;
private RegisteredTagType(final Class<? extends Tag> type, final Supplier<? extends Tag> supplier) {
this.type = type;
this.supplier = supplier;
}
}
}

View File

@ -1,6 +1,5 @@
package com.github.steveice10.opennbt.tag.builtin;
import com.github.steveice10.opennbt.tag.TagCreateException;
import com.github.steveice10.opennbt.tag.TagRegistry;
import com.github.steveice10.opennbt.tag.limiter.TagLimiter;
import java.io.DataInput;
@ -204,8 +203,6 @@ public class CompoundTag extends Tag implements Iterable<Entry<String, Tag>> {
tag.read(in, tagLimiter, newNestingLevel);
this.value.put(name, tag);
}
} catch (TagCreateException e) {
throw new IOException("Failed to create tag.", e);
} catch (EOFException ignored) {
throw new IOException("Closing tag was not found!");
}

View File

@ -1,6 +1,5 @@
package com.github.steveice10.opennbt.tag.builtin;
import com.github.steveice10.opennbt.tag.TagCreateException;
import com.github.steveice10.opennbt.tag.TagRegistry;
import com.github.steveice10.opennbt.tag.limiter.TagLimiter;
import java.io.DataInput;
@ -160,7 +159,7 @@ public class ListTag extends Tag implements Iterable<Tag> {
Tag tag;
try {
tag = TagRegistry.createInstance(id);
} catch (TagCreateException e) {
} catch (IllegalArgumentException e) {
throw new IOException("Failed to create tag.", e);
}

View File

@ -1,10 +1,10 @@
package com.github.steveice10.opennbt.tag.builtin;
import com.github.steveice10.opennbt.stringified.SNBT;
import com.github.steveice10.opennbt.tag.limiter.TagLimiter;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.lang.reflect.Array;
/**
* Represents an NBT tag.
@ -20,6 +20,16 @@ public abstract class Tag implements Cloneable {
*/
public abstract Object getValue();
/**
* Returns the unchecked value of this tag.
*
* @return unchecked value of this tag
* @param <T> expected type
*/
public <T> T value() {
return (T) getValue();
}
/**
* Reads this tag from an input stream.
*
@ -71,26 +81,6 @@ public abstract class Tag implements Cloneable {
@Override
public String toString() {
//TODO cleanup/push down
String value = "";
if (this.getValue() != null) {
value = this.getValue().toString();
if (this.getValue().getClass().isArray()) {
StringBuilder build = new StringBuilder();
build.append("[");
for (int index = 0; index < Array.getLength(this.getValue()); index++) {
if (index > 0) {
build.append(", ");
}
build.append(Array.get(this.getValue(), index));
}
build.append("]");
value = build.toString();
}
}
return this.getClass().getSimpleName() + " { " + value + " }";
return SNBT.serialize(this);
}
}

View File

@ -39,27 +39,27 @@ public interface TagLimiter {
void checkLevel(int nestedLevel);
default void countByte() {
this.countBytes(1);
this.countBytes(Byte.BYTES);
}
default void countShort() {
this.countBytes(2);
this.countBytes(Short.BYTES);
}
default void countInt() {
this.countBytes(4);
this.countBytes(Integer.BYTES);
}
default void countFloat() {
this.countBytes(4);
this.countBytes(Double.BYTES);
}
default void countLong() {
this.countBytes(8);
this.countBytes(Long.BYTES);
}
default void countDouble() {
this.countBytes(8);
this.countBytes(Double.BYTES);
}
/**