Skip to content

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
    └── Managers

5.2 Constants ​

Single class with the three @token@-substituted values plus module-wide metadata:

FieldTypeNotes
VERSIONStringSubstituted from build.gradle version.
DESCRIPTIONStringSubstituted from build.gradle description.
AUTHORStringSubstituted from build.gradle ext.author.
MINECRAFT_VERSIONStringHardcoded (e.g. "1.21").
DATABASE_VERSIONintBumped by SchemaMigrator on each migration.
CURRENT_PUNISHMENT_SCHEMA_VERSIONint

5.3 Database package ​

Database (Database.java) ​

  • void setup(DatabaseConfig config) — builds a HikariDataSource with the tuned settings from §4.11; runs SchemaMigrator.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 on SQLState == "40001" (deadlock) with 1s backoff.
  • CompletableFuture<List<Map<String, Object>>> runQueryRaw(String sql, Object... params) — used by SchemaMigrator for INFORMATION_SCHEMA reads.
  • void shutdown() — dataSource.close().
  • DatabaseStatus getStatus() — CONNECTING / CONNECTED / FAILED / DISCONNECTED.

DatabaseConfig (@Data) ​

FieldType
hostString
portint
nameString
userString
passwordString
sslboolean
maxPoolSizeint
minIdleint

TypeTokens (TypeTokens.java) ​

Singleton Gson TypeTokens for JSON column deserialization:

TokenType
STRINGString
STRING_LISTList<String>
UUID_LISTList<UUID>
REPORT_LISTList<Report>
NOTE_LISTList<String>

SchemaMigrator (SchemaMigrator.java) ​

Constructed with the Database and the platform AppelToolsAdapter. On migrate():

  1. Runs CREATE TABLE IF NOT EXISTS for every table.
  2. Reads INFORMATION_SCHEMA.COLUMNS for each table.
  3. Compares against in-code spec; emits ALTER TABLE ... ADD COLUMN / MODIFY COLUMN / DROP COLUMN.
  4. Normalizes MySQL type strings so TINYINT(1)↔BOOLEAN, INT(11)↔INT, BIGINT(20)↔BIGINT, SMALLINT(5) UNSIGNED↔SMALLINT UNSIGNED, LONGTEXT DEFAULT '[]'↔JSON are equal.
  5. For ENUM modifications, widens only if no existing data contains values outside the new enum set.
  6. Bumps DATABASE_VERSION in appelSettings.

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 from PunishmentManager.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 ​

ClassNotes
Punishment (abstract)Interface + abstract base with all 11 columns + UUID + active flag.
PermPunishmentConcrete for punishments table.
TempPunishmentAdds expiresAt (Instant) and duration (long, ms).
UndonePunishmentConcrete for undonePunishments table.
PunishmentType enumBAN, TEMP_BAN, MUTE, TEMP_MUTE, KICK, IP_BAN, TEMP_IP_BAN, IP_MUTE, TEMP_IP_MUTE, IP_KICK, UNBAN, UNMUTE, IP_UNBAN, IP_UNMUTE.
PunishmentCheckerUtility: 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:

ChannelDirectionDirection
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 ​

MethodPurpose
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 ​

ClassNotes
ATPlayerDataMaps to playerData table (see §4.2).
ATPlayerServerDataMaps to playerServerData table (see §4.3).
ATServerDataMaps to servers table.
ATHomeMaps to homes table.
ATFriendRequestMaps to friendRequests table.
ATStaffInventoryMaps 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 ​

  • QueryKey enum mapping to columns: PUNISHED_UUID("PunishedUuid"), PUNISHED_IP("PunishedIP"), ACTIVE("Active").
  • QueryFilter interface with boolean 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 MessageKey carries the same, plus the locale in which it is read. A message may be a single string or a list of lines.
  • TypeTokens holds the types a key can have: STRING, STRING_LIST, INTEGER, BOOLEAN, DOUBLE, MAP, PATH and DURATION.

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 of messages.yml lets 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, a null is left out of the file, and data.yml gets 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:

MethodColorTitle
createLogEmbed(String title, String description)Greenvaries
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)variesvaries
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 4s style.
  • 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 moderator

SignedQueue ​

  • 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 ​

ClassPurpose
ConstantsBuild-token substituted values.
Database, DatabaseConfig, DatabaseStatusHikariCP wrapper.
TypeTokensGson tokens.
SchemaMigratorAuto-migrating schemas.
All DAOs in §5.4Data access.
All punishment classes§5.5.
All types in §5.6Per-system POJOs.
ConfigKey, MessageKey, TypeTokens, ModuleAdapter, ModulesAdapter, YamlStore, MessageMigrations, ConfigDefaultsConfig framework.
AppelToolsAdapter, ATUserAdapter, PlayerManager, KeyAdapter, MessageKeyAdapterPlatform abstractions.
AppelLogger, SLF4JLoggerLogger.
SchedulerAdapter, ScheduledTaskAdapterScheduler abstraction.
BaseDiscordBot, EmbedsDiscord.
CommonUtils, TextUtils, FileUtils, UUIDFetcherGeneric utilities.
PluginUpdaterPlugin updater (see 11-updater.md).
SignedQueue, QueuedData, SignedResultCross-server chat queue.

Built with VitePress.