Appearance
5. Common Module Reference
The common module (AppelTools-Common.jar) is a pure-Java library. It contains platform-agnostic types, database access, configuration framework, plugin updater, chat filter, Discord embeds, and the AppelToolsAdapter interface that the Paper and Velocity modules implement.
The replaceTokens Gradle task substitutes @version@, @description@, @author@ from build.gradle into Constants.java at compile time (so a constant Constants.VERSION resolves to 2.0).
5.1 Package Tree
me.Maussee.AppelTools.common
├── Adapters
├── Config
├── Database
│ └── DAO
├── Discord
├── Logger
├── Punishments
├── Queue
├── Scheduler
├── Types
│ ├── Data
│ └── Query
└── Utils
├── Exceptions
└── Managers5.2 Constants
Single class with the three @token@-substituted values plus module-wide metadata:
| Field | Type | Notes |
|---|---|---|
VERSION | String | Substituted from build.gradle version. |
DESCRIPTION | String | Substituted from build.gradle description. |
AUTHOR | String | Substituted from build.gradle ext.author. |
MINECRAFT_VERSION | String | Hardcoded (e.g. "1.21"). |
DATABASE_VERSION | int | Bumped by SchemaMigrator on each migration. |
CURRENT_PUNISHMENT_SCHEMA_VERSION | int |
5.3 Database package
Database (Database.java)
void setup(DatabaseConfig config)— builds aHikariDataSourcewith the tuned settings from §4.11; runsSchemaMigrator.migrate().HikariDataSource getDataSource()— used by all DAOs.<T> CompletableFuture<T> runQuery(String sql, Function<ResultSet, T> mapper, Object... params)— async SELECT.<T> CompletableFuture<Integer> runUpdate(String sql, Function<ResultSet, List<T>> ignored, Object... params)— async INSERT/UPDATE/DELETE; retries up to 3 times onSQLState == "40001"(deadlock) with 1s backoff.CompletableFuture<List<Map<String, Object>>> runQueryRaw(String sql, Object... params)— used bySchemaMigratorforINFORMATION_SCHEMAreads.void shutdown()—dataSource.close().DatabaseStatus getStatus()—CONNECTING / CONNECTED / FAILED / DISCONNECTED.
DatabaseConfig (@Data)
| Field | Type |
|---|---|
host | String |
port | int |
name | String |
user | String |
password | String |
ssl | boolean |
maxPoolSize | int |
minIdle | int |
TypeTokens (TypeTokens.java)
Singleton Gson TypeTokens for JSON column deserialization:
| Token | Type |
|---|---|
STRING | String |
STRING_LIST | List<String> |
UUID_LIST | List<UUID> |
REPORT_LIST | List<Report> |
NOTE_LIST | List<String> |
SchemaMigrator (SchemaMigrator.java)
Constructed with the Database and the platform AppelToolsAdapter. On migrate():
- Runs
CREATE TABLE IF NOT EXISTSfor every table. - Reads
INFORMATION_SCHEMA.COLUMNSfor each table. - Compares against in-code spec; emits
ALTER TABLE ... ADD COLUMN/MODIFY COLUMN/DROP COLUMN. - Normalizes MySQL type strings so
TINYINT(1)↔BOOLEAN,INT(11)↔INT,BIGINT(20)↔BIGINT,SMALLINT(5) UNSIGNED↔SMALLINT UNSIGNED,LONGTEXT DEFAULT '[]'↔JSONare equal. - For
ENUMmodifications, widens only if no existing data contains values outside the new enum set. - Bumps
DATABASE_VERSIONinappelSettings.
5.4 Database/DAO package
Nine DAOs, each with @RequiredArgsConstructor + Lombok's @FieldDefaults(makeFinal=true):
AppelSettingsDB
CompletableFuture<String> get(String key)CompletableFuture<Void> update(String key, String value)
PlayerDataDB
CompletableFuture<ATPlayerData> getPlayerData(UUID uuid)CompletableFuture<ATPlayerData> getPlayerDataByName(String name)CompletableFuture<Void> updatePlayerData(ATPlayerData data)
JSON columns deserialized via TypeTokens (STRING_LIST, UUID_LIST, REPORT_LIST).
PlayerServerDataDB
CompletableFuture<List<ATPlayerServerData>> getTopBalances(UUID serverUuid, int limit)CompletableFuture<ATPlayerServerData> getPlayerServerData(UUID uuid, UUID serverUuid)CompletableFuture<Void> updatePlayerServerData(ATPlayerServerData data)
PunishmentDB
CompletableFuture<List<Punishment>> searchPunishments(List<QueryFilter> filters, Set<PunishmentType> types, Class<? extends Punishment> klass)CompletableFuture<List<PermPunishment>> getActivePermPunishments(MCPlayer player)CompletableFuture<List<TempPunishment>> getActiveIPTempPunishments(MCPlayer player, String ip)CompletableFuture<Void> deactivatePunishments(UUID id)(and overloads)CompletableFuture<Void> deactivateExpiredPunishments()(called fromPunishmentManager.checkAllTempPunishments)String generatePunishmentID()— see §4.13.
UndonePunishmentDB
CompletableFuture<UndonePunishment> getUndonePunishment(String id)CompletableFuture<List<UndonePunishment>> searchUndonePunishments(List<QueryFilter> filters)CompletableFuture<Void> updateUndonePunishment(UndonePunishment p)
ServerDataDB
CompletableFuture<ServerData> getServerData(int port)CompletableFuture<Void> updateServerData(String name, String ip, int port)
StaffInventoryDB
CompletableFuture<String> get(String server, UUID uuid)CompletableFuture<Void> set(String server, UUID uuid, String base64)CompletableFuture<Void> delete(String server, UUID uuid)
FriendRequestDB
CompletableFuture<Void> addFriendRequest(UUID sender, UUID receiver)—INSERT IGNORE.CompletableFuture<Void> removeFriendRequest(UUID sender, UUID receiver)CompletableFuture<List<FriendRequest>> getPendingRequests(UUID receiver)CompletableFuture<List<FriendRequest>> getSentRequests(UUID sender)CompletableFuture<Boolean> hasFriendRequest(UUID sender, UUID receiver)CompletableFuture<Void> removeAllRequests(UUID uuid)CompletableFuture<List<FriendRequest>> getAllFriendRequests()
HomeDB
CompletableFuture<List<Home>> getHomes(UUID serverUuid, UUID playerUuid)CompletableFuture<Home> getHome(UUID serverUuid, UUID playerUuid, String name)CompletableFuture<Void> setHome(Home home)—INSERT ... ON DUPLICATE KEY UPDATE.CompletableFuture<Void> deleteHome(UUID serverUuid, UUID playerUuid, String name)CompletableFuture<Void> renameHome(UUID serverUuid, UUID playerUuid, String oldName, String newName)
5.5 Punishments package
| Class | Notes |
|---|---|
Punishment (abstract) | Interface + abstract base with all 11 columns + UUID + active flag. |
PermPunishment | Concrete for punishments table. |
TempPunishment | Adds expiresAt (Instant) and duration (long, ms). |
UndonePunishment | Concrete for undonePunishments table. |
PunishmentType enum | BAN, TEMP_BAN, MUTE, TEMP_MUTE, KICK, IP_BAN, TEMP_IP_BAN, IP_MUTE, TEMP_IP_MUTE, IP_KICK, UNBAN, UNMUTE, IP_UNBAN, IP_UNMUTE. |
PunishmentChecker | Utility: enforces max-duration caps per rank, IP-targeted kick list construction, active ban lookup. |
PermPunishment and TempPunishment extend Punishment and add nothing but the type discriminant in their constructors. UndonePunishment shares the same column set as PermPunishment but adds a voidedAt timestamp internally (not in schema).
PunishmentType exposes helpers isBan(), isMute(), isKick(), isIp(), isTemp(), isUndone(), isPermanent(), and boolean isActive(Punishment).
5.6 Types/ package
ServerType enum
VELOCITY, PAPER.
Prefix
Lombok @Data class with a single String and a getString() returning it wrapped in MiniMessage brackets.
Permissions
See 09-permissions.md.
Party
@Data class Party { UUID id; UUID owner; Set<UUID> members; Set<UUID> invites; boolean allInvite; UUID mutedBy; }- Persisted in-memory only on the Velocity node.
PluginChannels
Class of constants used for plugin messaging:
| Channel | Direction | Direction |
|---|---|---|
CHAT | "appeltools:chat" | proxy → paper |
COMMAND | "appeltools:command" | paper → proxy |
STAFF_MODE | "appeltools:staffmode" | both |
STAFF_CHAT | "appeltools:staffchat" | both |
MAINTENANCE | "appeltools:maintenance" | proxy → paper |
FREEZE | "appeltools:freeze" | proxy → paper |
SERVER_SWITCH | "appeltools:switch" | paper → proxy |
DefaultOptions
@Data record of the per-player toggle defaults: paymentsToggled=true, friendNotificationsToggled=true, friendRequestsToggled=true, messagesToggled=true, staffToggled=true, staffChatToggled=false, staffModeToggled=false, staffSpyToggled=false.
RegexFilter — chat filter
| Method | Purpose |
|---|---|
boolean checkAdvertising(String message) | matches IP regex and URL regex; returns true if positive. |
boolean checkCurseWords(String message) | matches each curse word as a case-insensitive whole-word boundary regex. |
List<RegexFilter.Reason> checkAll(String message) | returns all reasons. |
The IP regex matches dotted-quad IPv4 with port optionally; the URL regex is a permissive (http(s)?://|www\.) pattern. Both are loaded from Config.ChatFilter config block.
CancellableDuration
@Data record { long durationMs; boolean cancelled; } — used by scheduled punishments and restart timers.
Data/ subpackage — POJOs
| Class | Notes |
|---|---|
ATPlayerData | Maps to playerData table (see §4.2). |
ATPlayerServerData | Maps to playerServerData table (see §4.3). |
ATServerData | Maps to servers table. |
ATHome | Maps to homes table. |
ATFriendRequest | Maps to friendRequests table. |
ATStaffInventory | Maps to staffInventory table. |
Report | { UUID reporter, UUID reported, String reason, Instant createdAt } — JSON list in playerData.Reports. |
PluginUpdateConfig | { String id; Provider provider; Map<String,Object> options; } (see 11-updater.md). |
Each @Data POJO has Gson @SerializedName per field for JSON columns plus a no-arg constructor and a full-arg constructor.
Query/ subpackage
QueryKeyenum mapping to columns:PUNISHED_UUID("PunishedUuid"),PUNISHED_IP("PunishedIP"),ACTIVE("Active").QueryFilterinterface withboolean isUseOr().SingleQueryFilter(@Data)—QueryKey key, Object value, boolean useOr.MultipleQueryFilter(@Data)—boolean useOr, List<SingleQueryFilter> filters. Varargs constructor.
Used by PunishmentDB.searchPunishments.
5.7 Config package
ConfigKey<T> (ConfigKey.java)
Lombok @Data:
java
public class ConfigKey<T> {
TypeToken<T> type;
String path; // e.g. "database.host"
T defaultValue;
}ConfigKey and MessageKey (ConfigKey.java, MessageKey.java)
- A key holds the path of its setting, its type and its default. The value is read from the file that was loaded and written back into the key, so that the rest of the plugin reads every setting from one place.
- A
MessageKeycarries the same, plus the locale in which it is read. A message may be a single string or a list of lines. TypeTokensholds the types a key can have:STRING,STRING_LIST,INTEGER,BOOLEAN,DOUBLE,MAP,PATHandDURATION.
ModuleAdapter (ModuleAdapter.java)
Abstract class for a feature module:
java
public abstract class ModuleAdapter {
public abstract String getId(); // e.g. "discord"
public abstract String getName(); // "Discord"
public abstract boolean isEnabledByDefault();
public abstract List<ConfigKey<?>> getKeys();
public abstract List<MessageKey<?>> getMessages();
public void onEnable() {}
public void onDisable() {}
}ModulesAdapter (ModulesAdapter.java)
@Data aggregator of List<ModuleAdapter>; provides getModule(String id).
Config (Config.java)
Static ConfigKey<T> fields grouped by module (Database, Discord, ChatFilter, Plugins, Other, etc.). All defaults are inline. Registered into the key registry of the platform by the static initializer of the class.
TypeTokens (Config/TypeTokens.java)
Same role as Database/TypeTokens but for config values (STRING, INTEGER, BOOLEAN, STRING_LIST, LOCALE).
Locale (Locale.java)
Enum with en_US, en_GB, nl_NL, de_DE, fr_FR, es_ES. Provides format(String) pattern.
MessageKey<T> (MessageKey.java)
Lombok @Data:
java
public class MessageKey<T> {
TypeToken<T> type;
String path; // e.g. "economy.balance-self"
T defaultValue;
List<Placeholder> placeholders;
MessageCategory category;
}MessageCategory enum: GENERAL, ECONOMY, TELEPORT, HOME, STAFF, FRIENDS, PARTY, MESSAGE, REPORT, PUNISHMENT, MAINTENANCE, ADMIN.
YamlStore (YamlStore.java)
static <T> QuotedYamlConfigurationStore<T> store(Class<T> type, Path file)— the store of a file. The store ofmessages.ymllets a message be written as a string or as a list of lines; every other file keeps its own types.PROPS/DATA_PROPS— the properties of the stores: the keys are written in lower kebab case, anullis left out of the file, anddata.ymlgets a header that says that the plugin manages it.
5.8 Adapters package
AppelToolsAdapter (AppelToolsAdapter.java)
See §3.5. The platform modules implement this.
ATUserAdapter (ATUserAdapter.java)
Platform-agnostic player surface:
java
public interface ATUserAdapter {
UUID getUniqueId();
String getName();
boolean hasPermission(String node);
void sendMessage(Component message);
void sendMessage(String legacy); // Legacy text → MiniMessage
boolean isOnline();
Optional<Server> getCurrentServer(); // Paper only
String getCurrentServerName();
void disconnect(Component reason);
void disconnect(String reason);
ATPlayerData getData(); // Cached
void saveData();
}KeyAdapter (KeyAdapter.java)
Static helper that registers the platform's ConfigKey set and MessageKey set.
MessageKeyAdapter (MessageKeyAdapter.java)
Per-platform companion to KeyAdapter — registers every MessageKey field declared inside paper/.../Messages.java or velocity/.../Messages.java.
PlayerManager<T extends ATUserAdapter> (PlayerManager.java)
Abstract generic manager:
java
public abstract class PlayerManager<T extends ATUserAdapter> {
protected final Map<UUID, T> players = new ConcurrentHashMap<>();
public abstract T getPlayer(UUID uuid);
public abstract T loadPlayer(UUID uuid) throws Exception;
public abstract void unloadPlayer(UUID uuid);
public abstract CompletableFuture<Void> savePlayer(T player);
public abstract CompletableFuture<Void> saveAllPlayers();
public abstract void startAutoSaveTask(); // every 5 minutes
public abstract void stopAutoSaveTask();
public abstract T getConsole();
public Collection<T> getOnlinePlayers();
}Subclasses: ATPPlayerManager (Paper), ATVPlayerManager (Velocity).
5.9 Logger package
AppelLogger (AppelLogger.java)
java
public interface AppelLogger {
void info(String msg);
void warn(String msg);
void error(String msg);
void error(String msg, Throwable t);
void debug(String msg);
}SLF4JLogger (SLF4JLogger.java)
Bridges to SLF4J. debug only emits if Boolean.getBoolean("appeltools.debug") is true.
5.10 Scheduler package
SchedulerAdapter (SchedulerAdapter.java)
java
public interface SchedulerAdapter {
ScheduledTaskAdapter runAsync(Runnable runnable);
ScheduledTaskAdapter runLater(Runnable runnable, Duration delay);
ScheduledTaskAdapter runRepeating(Runnable runnable, Duration initial, Duration period);
}ScheduledTaskAdapter (ScheduledTaskAdapter.java)
java
public interface ScheduledTaskAdapter {
void cancel();
boolean isCancelled();
}Implementations: BukkitSchedulerAdapter (Paper) and VelocitySchedulerAdapter (Velocity).
5.11 Discord package
BaseDiscordBot (BaseDiscordBot.java)
Abstract JDA wrapper:
void start(String token, List<SlashCommandData> slashCommands)void stop()void addEventListener(Object listener)boolean isConnected()JDA getJDA()(subclasses may restrict)
Subclasses: PaperDiscordBot and VelocityDiscordBot.
Embeds (Embeds.java)
Factory class:
| Method | Color | Title |
|---|---|---|
createLogEmbed(String title, String description) | Green | varies |
createMissingPluginsEmbed(List<String> missing) | Yellow | "Missing Plugins" |
createReportEmbed(Report report) | Red | "Report" |
createErrorEmbed(Throwable t) | Red | "Error" |
createWatchlistNoteEmbed(String player, String note) | Yellow | "Watch-list Note" |
createCurseEmbed(String player, String message) | Orange | "Curse Word" |
createStaffNotificationEmbed(StaffNotificationType type, String title, String description) | varies | varies |
createPunishmentEmbed(Punishment p) | Red | "Punishment" |
createStaffJoinEmbed(String name, String server) | Green | "Staff Join" |
createStaffLeaveEmbed(String name, String server) | Orange | "Staff Leave" |
createRestartEmbed(Duration remaining) | Orange | "Restart" |
createMaintenanceEmbed(boolean on) | Orange | "Maintenance" |
createHelpEmbed() | Blue | "Help" |
Each returns a MessageEmbed built with new EmbedBuilder().
5.12 Utils package
CommonUtils
static String colorize(String s)—&color code →§(legacy) for terminal output.static String randomString(int len)— alphanumeric random.static <T> T randomElement(List<T> list).static <T> List<T> immutable(List<T> list).
TextUtils
static String stripColor(String s).static String miniMessageToPlain(String mm).static String pluralize(int n, String singular, String plural).static String formatDuration(Duration d)—1d 2h 3m 4sstyle.static String formatRelative(Instant when).static List<String> splitWords(String s)— splits on space, respects quoted substrings.
FileUtils
static String readFile(Path path)— UTF-8.static void writeFile(Path path, String content)— creates parent dirs.static String readResource(String resourcePath).static byte[] serialize(Object obj)—ObjectOutputStream→ byte[].static Object deserialize(byte[] bytes)—ObjectInputStream.
UUIDFetcher
static UUID fromString(String s).static String toString(UUID u).
PluginUpdater (PluginUpdater.java)
See 11-updater.md for full provider details. The class has:
static CompletableFuture<PluginUpdateInfo> checkForUpdate(PluginUpdateConfig config)static CompletableFuture<Void> downloadAndReplace(PluginUpdateInfo info, Path pluginDir)- Six inner provider classes:
JenkinsProvider,SpigotMCProvider,ModrinthProvider,HangarProvider,GitHubProvider,GeyserProvider.
ModuleManagerAdapter (ModuleManagerAdapter.java)
Per-platform implementation that wires modules to their lifecycle hooks. See paper/.../Utils/Managers/ModuleManager.java and velocity/.../Utils/Managers/ModuleManager.java.
Utils/Exceptions/
StartupException— thrown when a hard requirement fails on startup.ModuleNotFoundException— thrown when a feature module lookup fails.
5.13 Queue package
A tiny pub/sub queue used to coordinate cross-server chat moderation.
SignedResult enum
ALLOWED, DENIED, PENDING.
QueuedData<T> (@Data)
java
String id; // random UUID
T payload; // chat message
CompletableFuture<SignedResult> future; // resolved by the moderatorSignedQueue
static String enqueue(MCUResult result, QueuedData<?> data)static <T> QueuedData<T> register(String id)static void resolve(String id, SignedResult result)static void resolveAll(SignedResult result)
Each enqueued entry auto-resolves to SignedResult.ALLOWED after 50 ms via a scheduled task, so the pipeline never stalls.
5.14 Build Output
./gradlew :common:build produces common/build/libs/AppelTools-Common.jar. Both the paper and velocity modules' shadowJar tasks dependsOn project(':common').tasks.shadowJar, so the common jar is bundled into both deployable JARs via project(':common') (the dependency is added via implementation project(':common')).
5.15 Class Index
| Class | Purpose |
|---|---|
Constants | Build-token substituted values. |
Database, DatabaseConfig, DatabaseStatus | HikariCP wrapper. |
TypeTokens | Gson tokens. |
SchemaMigrator | Auto-migrating schemas. |
| All DAOs in §5.4 | Data access. |
| All punishment classes | §5.5. |
| All types in §5.6 | Per-system POJOs. |
ConfigKey, MessageKey, TypeTokens, ModuleAdapter, ModulesAdapter, YamlStore, MessageMigrations, ConfigDefaults | Config framework. |
AppelToolsAdapter, ATUserAdapter, PlayerManager, KeyAdapter, MessageKeyAdapter | Platform abstractions. |
AppelLogger, SLF4JLogger | Logger. |
SchedulerAdapter, ScheduledTaskAdapter | Scheduler abstraction. |
BaseDiscordBot, Embeds | Discord. |
CommonUtils, TextUtils, FileUtils, UUIDFetcher | Generic utilities. |
PluginUpdater | Plugin updater (see 11-updater.md). |
SignedQueue, QueuedData, SignedResult | Cross-server chat queue. |