View on GitHub

Axion

Another Extensible Implementation of NBT

Axion is dependent on SLF4J for logging.

Looking for the JavaDoc?

Quick Start

Get an Axion Instance

Axion instances can be set up with different configurations. The two built-in configurations are for the original and extended specification.

// Get the built-in, original specification
Axion axion = Axion.getSpecInstance();

// Get the built-in, extended specification
Axion axion = Axion.getExtInstance();

Read NBT

NBT data can be read from a file or a stream and returned as a TagCompound. The data can also be read directly into any class that implements the AxionWritable<TagCompound> interface.

// Read a TagCompound from a file
TagCompound tag = axion.read(file);

// Read a TagCompound from an InputStream
TagCompound tag = axion.read(inputStream);

// Read a file into an AxionWritable
AxionWritable<TagCompound> result = axion.read(file, writable);

// Read an InputStream into an AxionWritable
AxionWritable<TagCompound> result = axion.read(inputStream, writable);

Write NBT

NBT data can be written to a file or a stream. The data can be written from a TagCompound or directly from any class that implements the AxionWritable<TagCompound> interface.

// Write a TagCompound to a file
axion.write(tag, file);

// Write a TagCompound to an OutputStream
axion.write(tag, outputStream);

// Write an AxionWritable to a file
axion.write(writable, file);

// Write an AxionWritable to an OutputStream
axion.write(writable, outputStream);

Customization

Custom Configurations

Axion configurations specify how an instance reads and writes the NBT data.

Configurations consist of: * a base tag adapter * tags * adapters * converters * mappers * a compression type * a character encoding type

Create

Axion allows custom configurations to be created either from scratch or by duplicating an existing configuration. The built-in configurations can’t be changed directly, but they can be duplicated and the duplicates can be altered.

// Create a new, empty configuration
Axion.createInstance("new-instance-name");

// Duplicate an existing configuration
Axion.createInstanceFrom(axionInstance, "new-instance-name");

// Duplicate an existing configuration by name
Axion.createInstanceFrom("existing-instnace-name", "new-instance-name");

Get

Axion instances are retrieved via the String id given during creation.

// Get a custom configuration
Axion.getInstance("instance-name");

Delete

Instances are deleted also via the String id given during creation. Attempting to delete a built-in instance will result in an exception.

// Delete a custom configuration
Axion.deleteInstance("instance-to-delete");

Custom Base Tag Adapter

The base tag adapter, registered using a different method than the other adapters, is used by Axion as the entry point for reading all tags. The default base tag adapter is responsible for handling most of the common logic involved in reading and writing tags.

Here is the default base tag adapter used by Axion:

public class BaseTagAdapter extends TagAdapter<Tag> {

  @Override
  public Tag read(Tag parent, AxionInputStream in) throws IOException {
    int id = in.readUnsignedByte();
    return (id == 0) ? null : getAdapterFor(id).read(parent, in);
  }

  @Override
  public void write(Tag tag, AxionOutputStream out) throws IOException {
    int id = getIdFor(tag.getClass());
    out.writeByte(id);
    if (!(tag.getParent() instanceof TagList)) {
      out.writeString(tag.getName());
    }
    getAdapterFor(id).write(tag, out);
  }

}

The base tag adapter is registered using:

axion.registerBaseTagAdapter(new BaseTagAdapter());

Custom Tags

Custom tags are created by extending the abstract Tag class and are registered with an Axion configuration instance along with an id, data type, adapter, and converter.

For example, this is how you might register the TagByte:

axion.registerTag(1, TagByte.class, Byte.class, new TagByteAdapter(), new TagByteConverter());

Adapters

Adapters tell Axion how to read and write the tags. An adapter can be any class that extends the abstract TagAdapter class. Due to the way Axion works internally, adapters should not contain state. Any adapters that contain state may not behave as expected in multi-threaded situations.

For example, this is the adapter registered for the TagByte:

public class TagByteAdapter extends TagAdapter<TagByte> {

  @Override
  public void write(TagByte tag, AxionOutputStream out) throws IOException {
    out.writeByte(tag.get());
  }

  @Override
  public TagByte read(final Tag parent, final AxionInputStream in) throws IOException {
    return convertToTag((parent instanceof TagList) ? null : in.readString(), in.readByte());
  }

}

This adapter simply writes the payload of the tag as a single byte. When reading a byte tag it reads a name only if the tag is not a child of a list, then reads a single byte.

Converters

Converters tell Axion how to convert data to and from tags. A converter can be any class that extends the abstract TagConverter class. Due to the way Axion works internally, converters should not contain state. Any converters that contain state may not behave as expected in multi-threaded situations.

For example, this is the converter registered for the TagByte:

public class TagByteConverter extends TagConverter<TagByte, Byte> {

  @Override
  public TagByte convert(final String name, final Byte value) {
    return new TagByte(name, value);
  }

  @Override
  public Byte convert(final TagByte tag) {
    return tag.get();
  }

}

The TagByteConverter class is fairly simple and self-explanatory.


Mappers

Mappers are simply utility classes that you can register with an Axion configuration instance to assist in converting objects to and from NBT tags. Mappers are useful when you don’t have access to an object’s source or otherwise can’t implement the AxionWritable interface.

For example, let’s say you had a 3rd-party class that looked like this:

public class Vector3f {

  public float x, y, z;

  public Vector3f() {
    //
  }

  public Vector3f(final float x, final float y, final float z) {
    this.x = x;
    this.y = y;
    this.z = z;
  }

}

You could write a mapper to convert a Vector3f object into a TagList of TagFloat like:

public class Vector3fMapper implements NBTObjectMapper<TagList, Vector3f> {

  @Override
  public Vector3f createObjectFrom(TagList tag, Axion axion) {
    Vector3f v = new Vector3f();
    v.x = ((TagFloat) tag.get(0)).get();
    v.y = ((TagFloat) tag.get(1)).get();
    v.z = ((TagFloat) tag.get(2)).get();
    return v;
  }

  @Override
  public TagList createTagFrom(String name, Vector3f value, Axion axion) {
    TagList list = new TagList(TagFloat.class, name);
    list.add(new TagFloat("", value.x));
    list.add(new TagFloat("", value.y));
    list.add(new TagFloat("", value.z));
    return list;
  }

}

Then register the mapper like so:

axion.registerNBTObjectMapper(Vector3f.class, new Vector3fMapper());

Finally, use the mapper:

Vector3f position = new Vector3f(16f, 2.5f, 65f);

// Convert the Vector3f into a TagList
TagList tagList = axion.createTagFrom("vec1", position);

// Convert the TagList back into a Vector3f
Vector3f result = axion.createObjectFrom(tagList, Vector3f.class);

Compression Type

Both built-in configurations use GZip as the default compression type. This is consistent with the original specification of NBT. If you wish to change this, however, Axion configurations can use GZip, Deflater, or no compression.

// GZip compression
axion.setCompressionType(CompressionType.GZip);

// Deflater compression
axion.setCompressionType(CompressionType.Deflater);

// No compression
axion.setCompressionType(CompressionType.None);

Character Encoding Type

Both built-in configurations use a modified UTF-8 character encoding for reading and writing all strings. This is consistent with the DataInputStream#readUTF() and DataOutputStream#writeUTF(String string) provided by Java. If you wish to change this, however, Axion supports the following character encoding types:

public static enum CharacterEncodingType {
  MODIFIED_UTF_8, US_ASCII, ISO_8859_1, UTF_8, UTF_16BE, UTF_16LE, UTF_16
}

This can be changed using:

axion.setCharacterEncodingType(CharacterEncodingType.US_ASCII);

AxionWritable Interface

The AxionWritable provides an interface for easily creating classes that can be read and written by Axion.

For example, extending the Vector3f example used above:

public class Vector3f implements AxionWritable<TagList> {

  public float x, y, z;

  public Vector3f() {
    //
  }

  public Vector3f(final float x, final float y, final float z) {
    this.x = x;
    this.y = y;
    this.z = z;
  }

  @Override
  public TagList write(final Axion axion) {
    TagList out = new TagList(TagFloat.class);
    list.add(new TagFloat("", x));
    list.add(new TagFloat("", y));
    list.add(new TagFloat("", z));
    return out;
  }

  @Override
  public void read(final TagList in, final Axion axion) {
    x = ((TagFloat) in.get(0)).get();
    y = ((TagFloat) in.get(1)).get();
    z = ((TagFloat) in.get(2)).get();
  }

}

Debugging

Axion uses SLF4J for logging. Setting the logging level to TRACE can help when debugging.

Another useful tool in debugging your NBT is the toString(Tag tag) method:

axion.toString(tagToPrint);

This will return a string in the following format:

TagCompound("Level"): 11 entries
{
  TagShort("shortTest"): 32767
  TagLong("longTest"): 9223372036854775807
  TagFloat("floatTest"): 0.49823147
  TagString("stringTest"): HELLO WORLD THIS IS A TEST STRING ÅÄÖ!
  TagInt("intTest"): 2147483647
  TagCompound("nested compound test"): 2 entries
  {
    TagCompound("ham"): 2 entries
    {
      TagString("name"): Hampus
      TagFloat("value"): 0.75
    }
    TagCompound("egg"): 2 entries
    {
      TagString("name"): Eggbert
      TagFloat("value"): 0.5
    }
  }
  TagList("listTest (long)"): 5 entries of type TagLong
  {
    TagLong: 11
    TagLong: 12
    TagLong: 13
    TagLong: 14
    TagLong: 15
  }
  TagByte("byteTest"): 127
  TagList("listTest (compound)"): 2 entries of type TagCompound
  {
    TagCompound: 2 entries
    {
      TagString("name"): Compound tag #0
      TagLong("created-on"): 1264099775885
    }
    TagCompound: 2 entries
    {
      TagString("name"): Compound tag #1
      TagLong("created-on"): 1264099775885
    }
  }
  TagByteArray("byteArrayTest (the first 1000 values of (n*n*255+n*7)%100, starting with n=0 (0, 62, 34, 16, 8, ...))"): [1000 bytes]
  TagDouble("doubleTest"): 0.4931287132182315
}

License

Copyright (C) 2014 Jason Taylor. Released as open-source under Apache License, Version 2.0.