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
29 changes: 29 additions & 0 deletions .github/workflows/javadoc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Publish Javadoc

on:
push:
branches: [ main, master, dev ]
pull_request:
branches: [ main, master, dev ]

jobs:
build-javadoc:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up JDK 21
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '21'

- name: Build Javadoc
run: ./gradlew.bat javadoc javadocJar --no-daemon

- name: Upload Javadoc artifact
uses: actions/upload-artifact@v4
with:
name: javadoc
path: build/libs/*-javadoc.jar
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ All notable changes to the Matchbox plugin will be documented in this file.
- Integration testing for complex game lifecycle scenarios

### Changed
- **API annotations and stability markers**: Added explicit nullability and API status annotations across the `com.ohacd.matchbox.api` module
- Introduced `@com.ohacd.matchbox.api.annotation.Internal` and `@com.ohacd.matchbox.api.annotation.Experimental` to mark implementation and unstable APIs
- Adopted JetBrains `@NotNull/@Nullable` consistently on public API surfaces and added `@since` Javadoc where appropriate
- Updated `GameConfig` nullability for optional settings and annotated event classes and listeners
- **API documentation**: Added focused API Javadoc generation and a `javadocJar` artifact for distribution; added missing `@since` tags to experimental methods and performed minor doc cleanups to improve clarity and usability
- **Default Configuration**: Updated default config with optimized phase durations
- Discussion phase duration set to 60 seconds by default (was 30 seconds)
- Voting phase duration set to 30 seconds by default (was 15 seconds)
Expand Down
26 changes: 26 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,29 @@ processResources {
expand props
}
}

// Configure Javadoc to generate public-facing API docs only (com.ohacd.matchbox.api)
tasks.named('javadoc', Javadoc) {
description = 'Generates Javadoc for the public API (com.ohacd.matchbox.api) only'
group = 'Documentation'
// Limit sources to API package to avoid internal implementation warnings
source = fileTree('src/main/java') { include 'com/ohacd/matchbox/api/**' }
// Make sure referenced internal types are resolvable by depending on compilation output
dependsOn tasks.named('classes')
classpath = sourceSets.main.output + sourceSets.main.compileClasspath
options.encoding = 'UTF-8'
// External links disabled to avoid network fetch errors during build
// If desired, add stable links manually after verifying package-list availability

}

// Javadoc JAR for distribution
tasks.register('javadocJar', Jar) {
dependsOn tasks.named('javadoc')
archiveClassifier.set('javadoc')
from tasks.named('javadoc').get().destinationDir
}

artifacts {
archives tasks.named('javadocJar')
}
2 changes: 2 additions & 0 deletions src/main/java/com/ohacd/matchbox/api/ApiGameSession.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import com.ohacd.matchbox.api.annotation.Internal;

import java.util.*;

Expand Down Expand Up @@ -390,6 +391,7 @@ public String getStatusDescription() {
* @return the wrapped GameSession
* @deprecated This method exposes internal implementation details. Use the provided API methods instead.
*/
@Internal
@Deprecated
public GameSession getInternalSession() {
return session;
Expand Down
22 changes: 22 additions & 0 deletions src/main/java/com/ohacd/matchbox/api/ChatMessage.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,15 @@
* Immutable representation of a chat message with all metadata needed for routing.
* Used throughout the chat pipeline system.
*
* @param originalMessage original message content (unmodified)
* @param formattedMessage formatted message used for display
* @param sender player who sent the message
* @param senderId UUID of the sender
* @param channel chat channel the message belongs to
* @param sessionName session name the message was sent in
* @param isAlivePlayer whether the sender is an alive player
* @param timestamp instant the message was recorded
*
* @since 0.9.5
* @author Matchbox Team
*/
Expand All @@ -26,6 +35,13 @@ public record ChatMessage(
) {
/**
* Creates a new ChatMessage with the current timestamp.
*
* @param originalMessage original message content (unmodified)
* @param formattedMessage formatted message used for display
* @param sender sender player
* @param channel channel of message
* @param sessionName session name
* @param isAlivePlayer whether sender is alive
*/
public ChatMessage(
@NotNull Component originalMessage,
Expand All @@ -41,6 +57,9 @@ public ChatMessage(
/**
* Creates a copy of this message with a modified formatted message.
* Useful for processors that want to modify message content.
*
* @param newFormattedMessage the updated formatted message component
* @return a new ChatMessage with the modified formatted message
*/
public ChatMessage withFormattedMessage(@NotNull Component newFormattedMessage) {
return new ChatMessage(originalMessage, newFormattedMessage, sender, senderId, channel, sessionName, isAlivePlayer, timestamp);
Expand All @@ -49,6 +68,9 @@ public ChatMessage withFormattedMessage(@NotNull Component newFormattedMessage)
/**
* Creates a copy of this message with a modified channel.
* Useful for processors that want to reroute messages.
*
* @param newChannel new chat channel for the message
* @return a new ChatMessage routed to the provided channel
*/
public ChatMessage withChannel(@NotNull ChatChannel newChannel) {
return new ChatMessage(originalMessage, formattedMessage, sender, senderId, newChannel, sessionName, isAlivePlayer, timestamp);
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/com/ohacd/matchbox/api/ChatProcessor.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,32 +45,47 @@ public interface ChatProcessor {

/**
* Result of chat processing with optional modified message.
*
* @param result the processing result enum
* @param message the (possibly modified) message
*/
record ChatProcessingResult(@NotNull ChatResult result, @NotNull ChatMessage message) {

/**
* Creates an ALLOW result with the original message.
*
* @param message original chat message
* @return a ChatProcessingResult indicating ALLOW with the provided message
*/
public static ChatProcessingResult allow(@NotNull ChatMessage message) {
return new ChatProcessingResult(ChatResult.ALLOW, message);
}

/**
* Creates an ALLOW result with a modified message.
*
* @param modifiedMessage modified chat message
* @return a ChatProcessingResult indicating ALLOW with the modified message
*/
public static ChatProcessingResult allowModified(@NotNull ChatMessage modifiedMessage) {
return new ChatProcessingResult(ChatResult.ALLOW, modifiedMessage);
}

/**
* Creates a DENY result.
*
* @param message original chat message
* @return a ChatProcessingResult indicating DENY
*/
public static ChatProcessingResult deny(@NotNull ChatMessage message) {
return new ChatProcessingResult(ChatResult.DENY, message);
}

/**
* Creates a CANCEL result.
*
* @param message original chat message
* @return a ChatProcessingResult indicating CANCEL
*/
public static ChatProcessingResult cancel(@NotNull ChatMessage message) {
return new ChatProcessingResult(ChatResult.CANCEL, message);
Expand Down
7 changes: 4 additions & 3 deletions src/main/java/com/ohacd/matchbox/api/GameConfig.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.ohacd.matchbox.api;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

/**
* Configuration class for game sessions.
Expand Down Expand Up @@ -85,17 +86,17 @@ public int getVotingDuration() {
*
* @return spark ability setting ("random", "hunter_vision", "spark_swap", "delusion")
*/
@NotNull
@Nullable
public String getSparkSecondaryAbility() {
return sparkSecondaryAbility;
}

/**
* Gets the Medic secondary ability setting.
*
* @return medic ability setting ("random", "healing_sight")
* @return medic ability setting ("random", "healing_sight") or null if unset
*/
@NotNull
@Nullable
public String getMedicSecondaryAbility() {
return medicSecondaryAbility;
}
Expand Down
13 changes: 13 additions & 0 deletions src/main/java/com/ohacd/matchbox/api/MatchboxAPI.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import com.ohacd.matchbox.api.annotation.Experimental;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
Expand Down Expand Up @@ -42,6 +43,7 @@ private MatchboxAPI() {
* @return a new SessionBuilder instance
* @throws IllegalArgumentException if name is null or empty
*/
@NotNull
public static SessionBuilder createSessionBuilder(@NotNull String name) {
if (name == null || name.trim().isEmpty()) {
throw new IllegalArgumentException("Session name cannot be null or empty");
Expand All @@ -55,6 +57,7 @@ public static SessionBuilder createSessionBuilder(@NotNull String name) {
* @param name the session name (case-insensitive)
* @return Optional containing the session if found, empty otherwise
*/
@NotNull
public static Optional<ApiGameSession> getSession(@Nullable String name) {
if (name == null || name.trim().isEmpty()) {
return Optional.empty();
Expand Down Expand Up @@ -170,6 +173,7 @@ public static int endAllSessions() {
* @param player the player to check
* @return Optional containing the session if the player is in one, empty otherwise
*/
@NotNull
public static Optional<ApiGameSession> getPlayerSession(@Nullable Player player) {
if (player == null) return Optional.empty();

Expand All @@ -195,6 +199,7 @@ public static Optional<ApiGameSession> getPlayerSession(@Nullable Player player)
* @param player the player to check
* @return Optional containing the player's role if in a game, empty otherwise
*/
@NotNull
public static Optional<Role> getPlayerRole(@Nullable Player player) {
if (player == null) return Optional.empty();

Expand All @@ -217,6 +222,7 @@ public static Optional<Role> getPlayerRole(@Nullable Player player) {
* @param sessionName the session name
* @return Optional containing the current phase if session exists, empty otherwise
*/
@NotNull
public static Optional<GamePhase> getCurrentPhase(@Nullable String sessionName) {
if (sessionName == null || sessionName.trim().isEmpty()) {
return Optional.empty();
Expand Down Expand Up @@ -275,7 +281,9 @@ public static Set<MatchboxEventListener> getListeners() {
* @param processor the chat processor to register
* @return true if the processor was registered, false if session not found
* @throws IllegalArgumentException if sessionName or processor is null
* @since 0.9.5
*/
@Experimental
public static boolean registerChatProcessor(@NotNull String sessionName, @NotNull ChatProcessor processor) {
if (sessionName == null || sessionName.trim().isEmpty()) {
throw new IllegalArgumentException("Session name cannot be null or empty");
Expand Down Expand Up @@ -314,7 +322,9 @@ public static boolean registerChatProcessor(@NotNull String sessionName, @NotNul
* @param processor the chat processor to unregister
* @return true if the processor was unregistered, false if not found
* @throws IllegalArgumentException if sessionName or processor is null
* @since 0.9.5
*/
@Experimental
public static boolean unregisterChatProcessor(@NotNull String sessionName, @NotNull ChatProcessor processor) {
if (sessionName == null || sessionName.trim().isEmpty()) {
throw new IllegalArgumentException("Session name cannot be null or empty");
Expand Down Expand Up @@ -350,7 +360,9 @@ public static boolean unregisterChatProcessor(@NotNull String sessionName, @NotN
* @param sessionName the session name to clear processors from
* @return true if processors were cleared, false if session not found
* @throws IllegalArgumentException if sessionName is null
* @since 0.9.5
*/
@Experimental
public static boolean clearChatProcessors(@NotNull String sessionName) {
if (sessionName == null || sessionName.trim().isEmpty()) {
throw new IllegalArgumentException("Session name cannot be null or empty");
Expand Down Expand Up @@ -383,6 +395,7 @@ public static boolean clearChatProcessors(@NotNull String sessionName) {
*
* @param event the event to fire
*/
@com.ohacd.matchbox.api.annotation.Internal
static void fireEvent(@NotNull MatchboxEvent event) {
if (event == null) return;

Expand Down
15 changes: 14 additions & 1 deletion src/main/java/com/ohacd/matchbox/api/SessionBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import com.ohacd.matchbox.api.annotation.Experimental;

import java.util.*;

Expand Down Expand Up @@ -78,6 +79,7 @@ public SessionBuilder(@NotNull String sessionName) {
* @param players the players to include in the session
* @return this builder instance for method chaining
*/
@NotNull
public SessionBuilder withPlayers(@Nullable Collection<Player> players) {
this.players = players != null ? new ArrayList<>(players) : new ArrayList<>();
return this;
Expand All @@ -89,6 +91,7 @@ public SessionBuilder withPlayers(@Nullable Collection<Player> players) {
* @param players the players to include in the session
* @return this builder instance for method chaining
*/
@NotNull
public SessionBuilder withPlayers(@Nullable Player... players) {
this.players = players != null ? new ArrayList<>(Arrays.asList(players)) : new ArrayList<>();
return this;
Expand All @@ -100,6 +103,7 @@ public SessionBuilder withPlayers(@Nullable Player... players) {
* @param spawnPoints list of spawn locations
* @return this builder instance for method chaining
*/
@NotNull
public SessionBuilder withSpawnPoints(@Nullable List<Location> spawnPoints) {
this.spawnPoints = spawnPoints != null ? new ArrayList<>(spawnPoints) : new ArrayList<>();
return this;
Expand All @@ -111,6 +115,7 @@ public SessionBuilder withSpawnPoints(@Nullable List<Location> spawnPoints) {
* @param spawnPoints array of spawn locations
* @return this builder instance for method chaining
*/
@NotNull
public SessionBuilder withSpawnPoints(@Nullable Location... spawnPoints) {
this.spawnPoints = spawnPoints != null ? new ArrayList<>(Arrays.asList(spawnPoints)) : new ArrayList<>();
return this;
Expand All @@ -122,6 +127,7 @@ public SessionBuilder withSpawnPoints(@Nullable Location... spawnPoints) {
* @param discussionLocation the location where discussions take place
* @return this builder instance for method chaining
*/
@NotNull
public SessionBuilder withDiscussionLocation(@Nullable Location discussionLocation) {
this.discussionLocation = discussionLocation;
return this;
Expand All @@ -133,6 +139,7 @@ public SessionBuilder withDiscussionLocation(@Nullable Location discussionLocati
* @param seatLocations map of seat numbers to locations
* @return this builder instance for method chaining
*/
@NotNull
public SessionBuilder withSeatLocations(@Nullable Map<Integer, Location> seatLocations) {
this.seatLocations = seatLocations != null ? new HashMap<>(seatLocations) : new HashMap<>();
return this;
Expand All @@ -144,6 +151,7 @@ public SessionBuilder withSeatLocations(@Nullable Map<Integer, Location> seatLoc
* @param gameConfig the game configuration to use
* @return this builder instance for method chaining
*/
@NotNull
public SessionBuilder withCustomConfig(@Nullable GameConfig gameConfig) {
this.gameConfig = gameConfig != null ? gameConfig : new GameConfig.Builder().build();
return this;
Expand All @@ -155,6 +163,7 @@ public SessionBuilder withCustomConfig(@Nullable GameConfig gameConfig) {
* @param gameConfig the game configuration to use
* @return this builder instance for method chaining
*/
@NotNull
public SessionBuilder withConfig(@Nullable GameConfig gameConfig) {
return withCustomConfig(gameConfig);
}
Expand Down Expand Up @@ -213,8 +222,9 @@ public Optional<String> validate() {
*
* @return Optional containing the created session, empty if creation failed
*/
@NotNull
public Optional<ApiGameSession> start() {
return startWithResult().toOptional();
return startWithResult().getSession();
}

/**
Expand All @@ -223,7 +233,10 @@ public Optional<ApiGameSession> start() {
* but don't want to trigger full game initialization.
*
* @return Optional containing the created session, empty if creation failed
* @since 0.9.5 (experimental)
*/
@NotNull
@Experimental
public Optional<ApiGameSession> createSessionOnly() {
// Validate configuration first
Optional<String> validationError = validate();
Expand Down
Loading
Loading