Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/maven.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ jobs:
- name: Upload JAR artifact
uses: actions/upload-artifact@v4
with:
name: ConfigurationAPI
name: ConfigurationProvider
path: target/*.jar

# Optional: GitHub security / dependency tracking
Expand Down
335 changes: 245 additions & 90 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,40 +1,56 @@
[![Java CI with Maven](https://github.com/devspexx/ConfigurationAPI/actions/workflows/maven.yml/badge.svg)](https://github.com/devspexx/CentralDatabase/actions/workflows/maven.yml)
[![Java CI with Maven](https://github.com/devspexx/ConfigurationAPI/actions/workflows/maven.yml/badge.svg)](https://github.com/devspexx/ConfigurationAPI/actions/workflows/maven.yml)
[![Code Factor](https://img.shields.io/codefactor/grade/github/devspexx/ConfigurationAPI?label=Code%20Factor&style=flat)](https://www.codefactor.io/repository/github/devspexx/ConfigurationAPI)
![Latest Release](https://img.shields.io/github/v/release/devspexx/ConfigurationAPI?label=Latest%20Release&style=flat)

## 1. Overview

## ConfigurationAPI
ConfigurationAPI is a Paper-based plugin that handles configuration files for you.

This is a simple yet somewhat robust plugin I wrote in my free time.
It allows developers to not worry about config changes and implementing /reload commands in their java plugins.
It is a Paper based API, well, a Paper based plugin. It will only work on Paper servers. Don't try to install it on spigot
servers, it won't work. I might make a fork of it in the near future, to also support spigot.
<br><br>
This plugin automatically tracks any configs you configure it to track, and reloads them, when
they were manually edited (without code), and you can also write to them by code, of course. So it works
both ways.
It automatically watches, reloads, and synchronizes configs when they
change — no `/reload` commands needed. Designed to be fast, safe,
and developer-friendly.

## Features
> This plugin is Paper-only and will not work on Spigot.

- Simple API
- File watching (WatchService)
- checksum validation (no unnecessary reloads)
- Debounce protection
- Single watcher (no per-file threads)
- A cool sync event! (`ConfigReloadEvent`)
### 1.1 Features

## Installation
- No need for `/reload` commands — configs update instantly when files change.
- Efficiently monitors files at the system level with a single watcher thread.
- Reloads only when actual content changes — avoids unnecessary operations.
- Prevents duplicate reloads caused by rapid file system events.
- React to changes with built-in events:
- `ConfigReloadedEvent`
- `ConfigDeletedEvent`
- `ConfigRegisteredEvent`
- Access everything through `ConfigurationProvider` — no boilerplate.
- Create, register, and manage configs at runtime — not just on startup.
- Normalized paths ensure reliable lookups across environments.
- Designed for concurrent environments with minimal overhead.
- Direct access to Bukkit’s `YamlConfiguration` for full control when needed.

#### Use jar:
```xml
<dependency>
<groupId>dev.spexx</groupId>
<artifactId>ConfigurationAPI</artifactId>
<version>1.3.0</version>
</dependency>
```
### 1.2 Additional Features

- Load configs directly from your plugin JAR:
- `registerFromJar(File target, String resourcePath, JavaPlugin plugin)`

- Create custom config files:
- `register(File file)`

- Register configs with default values (only applied if missing):
- `registerWithDefaults(File file, Map<String, Object> defaults)`

- Retrieve configs safely:
- `get(File file)`
- `getByPath(String path)`
- `isRegistered(File file)`

#### or Jitpack

## 2. Installation

Add ConfigurationAPI to your project using one of the following methods:

### 2.1 JitPack (Recommended)
Add this to your `pom.xml`:
```xml
<repositories>
<repository>
Expand All @@ -46,100 +62,239 @@ both ways.
<dependency>
<groupId>com.github.devspexx</groupId>
<artifactId>ConfigurationAPI</artifactId>
<version>v1.3.0</version>
<version>v1.3.2</version>
</dependency>
```

### Usage
#### Full sample (with comments)
### 2.2 Manual JAR build
If you prefer building the project yourself:
```bash
git clone https://github.com/devspexx/ConfigurationAPI.git
cd ConfigurationAPI
mvn clean install
````
This installs the artifact into your local Maven repository.
You can then add it as a dependency using the version defined in the project's `pom.xml` (e.g. `1.3.2`).

#### 2.2.1 Runtime
ConfigurationAPI must be available at runtime. You can choose one of the following:
- Shade it into your plugin (<b>recommended</b> for standalone plugins)
- Install it as a plugin on the server and depend on it

> 💡 If you do not shade it, make sure the ConfigurationAPI plugin is present on the server.

### 3. API Usage
How do I integrate ConfigurationAPI into my plugin?

#### 3.1 Initialize the API
You should initialize the API inside your plugin's `onEnable()` method.

```java
import dev.spexx.configurationAPI.api.manager.ConfigManager;
import dev.spexx.configurationAPI.api.ConfigurationProvider;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;

// ConfigManager takes a plugin instance, should only
// be initialized once!
ConfigManager configManager = new ConfigManager(this);
public final class MyPlugin extends JavaPlugin {

// Sample 1 - registering files from the plugin jar itself
// Which means you should NOT use saveDefaultConfig() / saveConfig()
// anywhere in your plugin.
private final ConfigurationProvider configurationProvider;

// This is where you want the config from the plugin resources to be saved.
File myPluginConfigDestination = new File(getDataFolder(), "config.yml");
public @NotNull ConfigManager getConfigurationProvider() {
return configurationProvider.api();
}

// 2nd parameter, "config.yml" is the path inside the jar. Should really leave that as it is, only
// change your file name. 3rd parameter is your plugin instance - so we know from which plugin we're
// pulling the config.
configManager.
@Override
public void onEnable() {
setupConfigurationProvider();
}

registerFromJar(myPluginConfigDestination, "config.yml",this);
public void setupConfigurationProvider() {

// I recommend putting all of this in your onEnable, at the end don't forget to
// actually start the watch service. Note that you can also dynamically create files during
// runtime, you don't have to do everything in onEnable.
manager.
// Initialize ConfigurationProvider for this plugin.
// Passing "this" binds the provider to this plugin's lifecycle.
configurationProvider = new ConfigurationProvider(this);
configurationProvider.api().startFileWatcher();
}
}
```

start();
> 💡 You can register configs even after the file watcher has started.
> <b>Newly registered configs will be picked up and tracked automatically</b>.

// Sample 2 - registering custom yaml files, except not from plugin jar
// This is fairly simple!
YamlConfig myCustomConfig = manager.register(
new File(getDataFolder(), "data.yml")
);
#### 3.2 Working with Configs
Once the API is initialized, you can create, access, and modify configuration files.

##### 3.2.1 Register a custom config

```java
@NotNull YamlConfig dataConfig = getConfigurationProvider().register(
new File(getDataFolder(), "data.yml")
);
```
- Creates the file if it does not exist
- Loads and registers it
- Automatically tracked by the watcher

#### 3.2.2 Register with default values
```java
Map<String, Object> defaults = Map.of(
"settings.enabled", true,
"motd.line1", "Hello world"
);

getConfigurationProvider().registerWithDefaults(
new File(getDataFolder(), "config.yml"),
defaults
);
```
- Only missing values are added
- Existing values are never overwritten

// What you're doing here is creating a file, in your plugin folder - getDataFolder(),
// and you're naming it 'data.yml', that's about it.
// You can write to it:
myCustomConfig.
#### 3.2.3 Access a config

```java
import dev.spexx.configurationAPI.api.config.yaml.YamlConfig;

get().
YamlConfig config = getConfigurationProvider().get(
new File(getDataFolder(), "config.yml")
);

set("hello","world");
String value = config.get().getString("path.to.value");
```
> 💡 Prefer get(File) over getByPath(String) for better reliability.

// Read from it (get() - returns the latest internally cached config,
// basically, the latest config that matches the file on your disk)
// with get(), you get raw YamlConfiguration access, so you're able to do
// anything. If you do change anything in the file don't forget to
// save it in the end: myCustomConfig.save(), api will handle the rest.
@Nullable
String myValue = myCustomConfig.get().getString("something.here");
#### 3.2.4 Modify and save
```java
config.get().set("path.to.value", "newValue");

// ---
// If you find this confusing, there is javadoc at every method / public variable. So you won't be lost!
// Always save after modifying
config.save();
```

#### Listen for reload (If you need to)
#### 3.2.5 React to config changes

```java
import dev.spexx.configurationAPI.api.event.ConfigReloadedEvent;
import org.jetbrains.annotations.NotNull;

@EventHandler
public void onReload(@NotNull ConfigReloadEvent event) {
public void onReload(@NotNull ConfigReloadedEvent event) {
getLogger().info("Reloaded: " + event.getConfigName());
}
```
- Fired only when file content actually changes
- Runs on the main thread

#### What this event provides
##### 3.2.6 Recommended pattern (cache values)

```java
import dev.spexx.configurationAPI.api.event.ConfigReloadedEvent;
import org.bukkit.event.Listener;
import org.jetbrains.annotations.NotNull;

public class MyListener implements Listener {

private String cachedValue;

public MyListener(MyPlugin plugin) {
update(plugin);
}

public void update(@NotNull MyPlugin plugin) {
YamlConfig config = plugin.getConfigurationProvider().get(
new File(plugin.getDataFolder(), "config.yml")
);

cachedValue = config.get().getString("path.to.value");
}

@EventHandler
public void onReload(ConfigReloadedEvent event) {
update(plugin);
}
}
```
## ConfigReloadEvent

| Method | Description |
|--------------------|------------------------------------------|
| `getConfigName()` | Full name of the file (e.g. `config.yml`)|
| `getNewConfig()` | Updated configuration (latest state) |
| `getOldChecksum()` | Checksum before reload |
| `getNewChecksum()` | Checksum after reload |
- Avoid reading config on every event
- Cache values and refresh on reload

### 4 Events
ConfigurationAPI provides built-in events that allow you to react to configuration changes in real-time.
> 💡 All events are fired on the **main server thread**.

#### 4.1 ConfigReloadedEvent
Fired when a configuration file is modified and successfully reloaded.

| Method | Description |
|--------------------|----------------------------------------------|
| `getConfigName()` | Name of the config file (e.g. `config.yml`) |
| `getNewConfig()` | Updated `FileConfiguration` instance |
| `getOldChecksum()` | Checksum before reload (may be `null`) |
| `getNewChecksum()` | Checksum after reload (may be `null`) |

- Triggered only when file content actually changes (checksum-based).


#### 4.2 ConfigDeletedEvent
Fired when a tracked configuration file is deleted.

| Method | Description |
|--------------------|----------------------------------------------|
| `getConfigName()` | Name of the deleted config file |
| `getPath()` | Absolute path of the deleted file |

- Fired when the file is removed from disk
- Config is automatically untracked

#### 4.3 ConfigRegisteredEvent
Fired when a configuration is registered and loaded.

| Method | Description |
|-------------------|----------------------------------------------|
| `getConfigName()` | Name of the config file |
| `getConfig()` | Loaded `FileConfiguration` instance |
| `getChecksum()` | Initial checksum (may be `null`) |

- Fired after registration and initial load
- Useful for initialization logic

#### 4.4 Example usage of events

```java
import dev.spexx.configurationAPI.api.event.ConfigDeletedEvent;
import dev.spexx.configurationAPI.api.event.ConfigRegisteredEvent;
import dev.spexx.configurationAPI.api.event.ConfigReloadedEvent;
import org.jetbrains.annotations.NotNull;

public class ConfigListener implements Listener {

@EventHandler
public void onReload(@NotNull ConfigReloadedEvent event) {
Bukkit.getLogger().info("Reloaded: " + event.getConfigName());
}

@EventHandler
public void onDelete(@NotNull ConfigDeletedEvent event) {
Bukkit.getLogger().warning("Deleted: " + event.getConfigName());
}

@EventHandler
public void onRegister(@NotNull ConfigRegisteredEvent event) {
Bukkit.getLogger().info("Registered: " + event.getConfigName());
}
}
```
- Events are synchronous (main thread)
- Reload events are checksum-based (no duplicate triggers)
- Delete events automatically stop tracking the file
- Register events fire once per config registration

### 5. Contributing

Pull requests are welcome!

If you find a bug or have a feature request, feel free to open an issue.

#### Event behavior
When file changes:
- Change is detected internally in watcher
- Checksum is computed
- If checksum is different, reload event is fired, and new file is cached
- New file overrides the latest file (if any) in the cache
- If checksum isn't different, nothing happens, as there were no changes in the file
- Event is fired on main thread
## 6. Final Notes

---
ConfigurationAPI is designed to remove boilerplate and let you focus on building features.

### Thank you for reading!
If you find it useful, consider ⭐ starring the repository. I would really appreciate it!
Loading
Loading