Skip to content

Latest commit

 

History

History
361 lines (262 loc) · 8.45 KB

File metadata and controls

361 lines (262 loc) · 8.45 KB

Advanced Usage

This guide covers advanced features and edge cases in YamlAnnotations.

Leniency Modes

Leniency controls how strictly type conversions are enforced during YAML loading.

Leniency Levels

Level Description
STRICT Exact type matching required; throws exceptions on mismatches
LENIENT Attempts automatic type coercion when possible
UNDEFINED Field-level only: inherits from @YamlFile; defaults to LENIENT when @YamlFile is absent

Setting Leniency

Class-level (applies to all fields):

@YamlFile(lenient = Leniency.LENIENT)
public class Config extends YamlFileInterface {
    // All fields use LENIENT mode by default
}

Field-level (overrides class setting):

@YamlFile(lenient = Leniency.STRICT)
public class Config extends YamlFileInterface {

    @YamlKey("strict-value")
    public int strictValue = 0;  // Uses STRICT

    @YamlKey(value = "lenient-value", lenient = Leniency.LENIENT)
    public char lenientValue = 'A';  // Uses LENIENT
}

Leniency Behavior Examples

Character Conversion

char-field: "Hello"
Mode Result
STRICT Throws IOException - string too long
LENIENT Takes first character: 'H'

Float Precision

float-field: 0.123456789
Mode Result
STRICT Throws IOException - precision loss
LENIENT Converts with precision loss: 0.12345679f

Single Value to Collection

list-field: "single-item"
Mode Result
STRICT Throws IOException - expected list
LENIENT Wraps in list: ["single-item"]

Custom Type Conversion

Classes with String Constructor

Any class with a public constructor that takes a single String parameter can be used:

public class Email {
    private final String address;

    public Email(String address) {
        if (!address.contains("@")) {
            throw new IllegalArgumentException("Invalid email");
        }
        this.address = address;
    }

    @Override
    public String toString() {
        return address;
    }
}

public class Config extends YamlFileInterface {
    @YamlKey("contact-email")
    public Email contactEmail = new Email("admin@example.com");
}
contact-email: admin@example.com

Static Field Resolution

For types with public static fields (common in game APIs):

// Given a class like:
public class Sound {
    public static final Sound CLICK = new Sound("click");
    public static final Sound DING = new Sound("ding");
    // ...
}

// You can use it directly:
public class Config extends YamlFileInterface {
    @YamlKey("notification-sound")
    public Sound notificationSound = Sound.DING;
}
notification-sound: DING

The library will:

  1. On save: Find the static field name that matches the value
  2. On load: Look up the static field by name

Inheritance

Configuration classes can extend other configuration classes.

Important: Loading traverses the full class hierarchy, but saving only writes fields declared in the concrete class. Fields inherited from a parent class are not written on save.

public abstract class BaseConfig extends YamlFileInterface {
    @YamlKey("version")
    public int version = 1;

    @YamlKey("debug")
    public boolean debug = false;
}

public class PluginConfig extends BaseConfig {
    @YamlKey("plugin.name")
    public String pluginName = "MyPlugin";

    @YamlKey("plugin.enabled")
    public boolean enabled = true;
}

Saving new PluginConfig() writes only the fields declared in PluginConfig:

plugin:
  name: MyPlugin
  enabled: true

Loading will populate version and debug from the YAML file if those keys are present (since load traverses all parent classes), but they will not appear in freshly saved files.


Working with Bukkit/Spigot/Paper

Using Keyed Objects

The library automatically handles Bukkit's Keyed interface (like Sound, Material, etc.):

public class Config extends YamlFileInterface {
    @YamlKey("break-sound")
    public Sound breakSound = Sound.BLOCK_STONE_BREAK;

    @YamlKey("item-type")
    public Material itemType = Material.DIAMOND_SWORD;
}
break-sound: BLOCK_STONE_BREAK
item-type: DIAMOND_SWORD

Plugin Data Folder Integration

Use the plugin instance for automatic path resolution:

@YamlFile(fileName = "settings.yml")
public class Settings extends YamlFileInterface {
    @YamlKey("setting")
    public String setting = "value";
}

// In your plugin:
public class MyPlugin extends JavaPlugin {
    private Settings settings;

    @Override
    public void onEnable() {
        // Automatically uses: plugins/MyPlugin/settings.yml
        settings = new Settings().load(this);
    }

    @Override
    public void onDisable() {
        settings.save(this);
    }
}

Error Handling

Common Exceptions

Exception Cause
IOException File read/write errors, type conversion failures
FinalAttribute Attempting to use @YamlKey on a final field
DuplicateKey Same key path used multiple times

Handling Missing Fields

If a YAML file doesn't contain a key, the field keeps its default value:

public class Config extends YamlFileInterface {
    @YamlKey("existing")
    public String existing = "default";  // Loaded from YAML if present

    @YamlKey("missing")
    public String missing = "default";   // Keeps "default" if not in YAML
}

Null Primitives

Primitive types cannot be null. This will throw an exception:

# Error: Cannot assign null to primitive
int-field: null

Use wrapper types if you need nullable values:

@YamlKey("nullable-int")
public Integer nullableInt = null;  // Works fine

Performance Considerations

File Operations

  • load() reads the entire file into memory
  • save() writes the entire file atomically
  • For large configurations, consider splitting into multiple files

Reflection

The library uses reflection for field access on every load() and save() call. For performance-critical applications:

  • Minimize the number of annotated fields
  • Load configuration once at startup
  • Cache the configuration instance

Collection Types

  • ListArrayList (preserves insertion order)
  • SetLinkedHashSet (preserves insertion order)
  • QueueArrayDeque
  • MapLinkedHashMap (preserves insertion order)

Thread Safety

YamlAnnotations is not thread-safe. If you need concurrent access:

public class ThreadSafeConfig {
    private final Config config;
    private final ReadWriteLock lock = new ReentrantReadWriteLock();

    public ThreadSafeConfig(String path) throws IOException {
        config = new Config().load(path);
    }

    public String getValue() {
        lock.readLock().lock();
        try {
            return config.value;
        } finally {
            lock.readLock().unlock();
        }
    }

    public void setValue(String value) {
        lock.writeLock().lock();
        try {
            config.value = value;
        } finally {
            lock.writeLock().unlock();
        }
    }
}

Migration from Other Libraries

From Bukkit's FileConfiguration

Before (Bukkit):

FileConfiguration config = YamlConfiguration.loadConfiguration(file);
String name = config.getString("player.name", "Unknown");
int level = config.getInt("player.level", 1);

After (YamlAnnotations):

public class Config extends YamlFileInterface {
    @YamlKey("player.name")
    public String name = "Unknown";

    @YamlKey("player.level")
    public int level = 1;
}

Config config = new Config().load(file);
// Access directly: config.name, config.level

Benefits of Migration

  • Type safety at compile time
  • IDE auto-completion
  • Refactoring support
  • Default values in one place
  • Self-documenting configuration structure