ConfigLib Java Library

repository·master·Indexed 42 days ago

https://github.com/exlll/configlib

A Java library for Minecraft developers to simplify saving, loading, updating, and commenting on YAML configuration files by mapping Java classes and records to YAML structures. It supports a wide range of data types, including Bukkit's ConfigurationSerializable, and provides tools like @Configuration and @Comment annotations, as well as YamlConfigurationStore for efficient configuration management.

Tokens
8.9K
Snippets
23
Records
30
Agent score
4%

What's inside ConfigLib

  1. Declare configuration types using @Configuration

    master

    To define a configuration type in ConfigLib, you can use either a Java record or a non-generic class annotated with @de.exlll.configlib.Configuration.

    Requirements for Classes:

    • Must have a no-args constructor (can be private).
    • Inner classes (non-static) are not supported because they lack the required implicit constructor.
    • It is highly recommended to initialize all fields with non-null default values.

    Requirements for Records:

    • Records are supported natively. To provide default values when using the update method (if the file does not exist), add a no-args constructor that calls the canonical constructor with your desired defaults.
    // Using a class
    @Configuration
    public final class MyConfig {
        private int value = 42;
        // No-args constructor required
        private MyConfig() {}
    }
    
    // Using a record
    @Configuration
    record User(String name, String email) {
        // No-args constructor for default values during 'update'
        User() {
            this("John Doe", "john@doe.com");
        }
    }
  2. Recursive type definitions are not supported

    master

    ConfigLib currently does not support direct or indirect recursive type definitions. Attempting to define a configuration type that references itself will not work.

    Unsupported patterns include:

    • Direct recursion: A class containing a field of its own type.
    • Indirect recursion: Two or more classes that reference each other in a cycle.
    // Direct recursion (NOT SUPPORTED)
    @Configuration
    static final class R {
        R r;
    }
    
    // Indirect recursion (NOT SUPPORTED)
    @Configuration
    static final class R1 {
        R2 r2;
    }
    
    @Configuration
    static final class R2 {
        R1 r1;
    }
  3. Understand the ConfigLib module structure

    master

    ConfigLib is organized into three main types of modules depending on your requirements:

    • configlib-core: Contains the core logic, including the object mapper (converting instances to/from maps), serializers, and comment extraction. It has no Minecraft dependencies.
    • configlib-yaml: Extends core with YAML serialization and deserialization capabilities. It also has no Minecraft dependencies.
    • Platform-specific modules (configlib-paper, configlib-velocity, configlib-waterfall): These are convenience plugins that shade configlib-core, configlib-yaml, and the YAML parser.
      • Use configlib-paper if you need support for Bukkit classes like ItemStack via the ConfigLib.BUKKIT_DEFAULT_PROPERTIES object.
      • These modules are designed to be used as standalone plugins. If you use them, add them as a dependency to your plugin.yml (Paper/Waterfall) or the dependencies array (Velocity).
  4. Subclassing configuration types

    master

    ConfigLib supports configuration inheritance. Subclasses of configuration types do not require the @Configuration annotation. When serializing, fields from parent classes are written before the fields of the child class (top-to-bottom). Parent classes can be declared as abstract.

    Restriction: Field shadowing (where a subclass defines a field with the same name as a field in a superclass) is currently not supported.

  5. Restrictions on overwriting values with environment variables

    master

    Environment variables are resolved early in the process before type information is fully available, leading to the following restrictions:

    • Simple values only: You can only overwrite simple values. Attempting to overwrite a collection or a map will cause an exception.
    • No insertions: Environment variables cannot be used to insert new values into existing collections or maps.
    • No null overwrites: You cannot overwrite null values because they lack type information.
    • Boolean format: To overwrite a boolean, the environment variable must be assigned the string true or false (case-insensitive).
    • Integer vs Floating Point: If a number is interpreted as an integer during the initial configuration file read (i.e., it lacks a decimal point), it cannot be overwritten by a floating-point number, even if the target configuration type expects a float.
  6. Understand type conversion and serializer selection

    master

    Type Conversion

    Before storage, types are converted into serializable types (e.g., types handled by the underlying YAML library) according to this mapping:

    Source typeTarget type
    Boolean typesBoolean
    Integer typesLong
    Floating point typesDouble
    Characters and stringsString
    Big numeric typesString
    Time related typesString
    Utility typesString
    EnumsString
    ConfigurationsMap<String, ?>
    Set<S>List<T>
    List<S>List<T>
    S[]List<T>
    Map<S1, S2>Map<T1, T2>
    ConfigurationSerializableString

    Serializer Selection Logic

    Serializers (implementing de.exlll.configlib.Serializer) are selected based on the compile-time type of the configuration element, not the runtime type of its value. This means if you have a field A a = new B(), only fields of A will be serialized unless you use @Polymorphic.

  7. Handle missing and null values in configuration

    master

    Missing Values

    When a value is missing from a configuration file (e.g., due to deletion or schema changes), fields in configuration classes retain their assigned default values, and record components are initialized with the default value of their corresponding type.

    Null Values

    Null values are supported but discouraged. You can control their behavior via ConfigurationProperties:

    • outputNulls(false): Null elements and collection elements are not written to the file. Associated comments are also omitted.
    • inputNulls(false): Null values in the file are treated as missing (retaining default values).
    • inputNulls(true): Null values in the file overwrite default values or set record components to null. Note: An exception is thrown if the target type is a primitive.

    By default, both outputNulls and inputNulls are false (forbidden).

    YamlConfigurationProperties.newBuilder()
            .outputNulls(false)
            .inputNulls(true)
            .build();
  8. Ignore and filter configuration fields

    master

    To prevent certain fields from being serialized or updated during deserialization, you can use the following methods:

    1. Keywords: Mark fields as final, static, transient, or annotate them with @Ignore.
    2. Custom Filtering: Provide an instance of FieldFilter to the configuration properties to filter out additional fields.

    Note: Record components cannot be filtered using a FieldFilter.

  9. Enable case-sensitive resolution for configuration paths

    master

    By default, configuration paths that differ only in case may be treated as the same. To target paths like alpha.beta and ALPHA.BETA individually, you must set caseSensitiveResolution to true. Once enabled, you can overwrite these specific values using environment variables following the pattern path_to_key (e.g., alpha_beta and ALPHA_BETA).

    # Example configuration requiring case-sensitive resolution
    alpha:
      beta: 1
    ALPHA:
      BETA: 2

    After setting caseSensitiveResolution: true, use:

    export alpha_beta=new_value

    export ALPHA_BETA=other_value

  10. Install ConfigLib via Maven Central

    master

    The recommended way to use ConfigLib is via Maven Central. Use the configlib-yaml artifact for standard YAML support, or configlib-paper if you require Bukkit class serialization (e.g., ItemStack).

    <!-- Maven -->
    <dependency>
        <groupId>de.exlll</groupId>
        <artifactId>configlib-yaml</artifactId>
        <version>4.8.1</version>
    </dependency>
    
    <!-- Gradle (Kotlin) -->
    repositories { mavenCentral() }
    
    dependencies { implementation("de.exlll:configlib-yaml:4.8.1") }
  11. Install ConfigLib via JitPack

    master

    You can use JitPack to import ConfigLib directly from GitHub.

    <!-- Maven -->
    <repository>
        <id>jitpack.io</id>
        <url>https://jitpack.io</url>
    </repository>
    
    <dependency>
        <groupId>com.github.Exlll.ConfigLib</groupId>
        <artifactId>configlib-yaml</artifactId>
        <version>v4.8.1</version>
    </dependency>
    
    <!-- Gradle (Groovy) -->
    repositories { maven { url 'https://jitpack.io' } }
    
    dependencies { implementation 'com.github.Exlll.ConfigLib:configlib-yaml:v4.8.1' }
    
    <!-- Gradle (Kotlin) -->
    repositories { maven { url = uri("https://jitpack.io") } }
    
    dependencies { implementation("com.github.Exlll.ConfigLib:configlib-yaml:v4.8.1") }
  12. Overwrite configuration values with environment variables

    master

    ConfigLib allows environment variables to overwrite values for booleans, numbers, and strings. To enable this, configure EnvVarResolutionConfiguration and attach it to your ConfigurationProperties instance.

    Naming Convention

    To target a specific field, use an environment variable name that matches the field name:

    • Simple fields: Use the field name (e.g., FIELD_NAME).
    • Nested fields: Join field names with underscores (e.g., PARENT_CHILD).
    • List elements: Use the index as the field name (e.g., LIST_0, LIST_1).
    • Lists of maps: Join all path segments (e.g., LIST_0_FIELD_NAME).

    Configuration Options

    • Prefixes: Use resolveEnvVarsWithPrefix(prefix, caseSensitiveResolution) to restrict which environment variables are processed. It is highly recommended to use a project-specific prefix.
    • Case Sensitivity: On UNIX, environment variables are case-sensitive. On Windows, they are case-insensitive. If you want to use uppercase environment variables to match lowercase field names on all platforms, set caseSensitiveResolution to false.
    // Enable environment variable resolution with a prefix
    final var envVarConf = ConfigurationProperties.EnvVarResolutionConfiguration
            .resolveEnvVarsWithPrefix("MY_PREFIX_", false);
    
    final var properties = ConfigurationProperties.newBuilder()
            .setEnvVarResolutionConfiguration(envVarConf)
            .build();
    
    /* 
    Example Mapping:
    YAML:
      a: 0
      b:
        c: 1
      d:
        - 2
    
    Environment Variables (with prefix MY_PREFIX_ and case-insensitive):
    MY_PREFIX_A -> 0
    MY_PREFIX_B_C -> 1
    MY_PREFIX_D_0 -> 2
    */