diff --git a/dist/java/src/mp/code/BufferController.java b/dist/java/src/mp/code/BufferController.java index c5f6b6d..df08a8b 100644 --- a/dist/java/src/mp/code/BufferController.java +++ b/dist/java/src/mp/code/BufferController.java @@ -1,14 +1,18 @@ package mp.code; -import mp.code.data.Callback; -import mp.code.data.Cursor; import mp.code.data.TextChange; import mp.code.exceptions.ControllerException; -import java.util.Objects; import java.util.Optional; +import java.util.function.Consumer; -public class BufferController { +/** + * Allows interaction with a CodeMP buffer, which in simple terms is a document + * that multiple people can edit concurrently. + *

+ * It is generally safer to avoid storing this directly, see the api notes for {@link Workspace}. + */ +public final class BufferController { private final long ptr; BufferController(long ptr) { @@ -16,46 +20,97 @@ public class BufferController { } private static native String get_name(long self); + + /** + * Gets the name (path) of the buffer. + * @return the path of the buffer + */ public String getName() { return get_name(this.ptr); } private static native String get_content(long self) throws ControllerException; + + /** + * Gets the contents of the buffer as a flat string. + * This may return incomplete results if called immediately after attaching. + * @return the contents fo the buffer as a flat string + * @throws ControllerException if the controller was stopped + */ public String getContent() throws ControllerException { return get_content(this.ptr); } private static native TextChange try_recv(long self) throws ControllerException; + + /** + * Tries to get a {@link TextChange} from the queue if any were present, and returns + * an empty optional otherwise. + * @return the first text change in queue, if any are present + * @throws ControllerException if the controller was stopped + */ public Optional tryRecv() throws ControllerException { return Optional.ofNullable(try_recv(this.ptr)); } - private static native Cursor recv(long self) throws ControllerException; - public Cursor recv() throws ControllerException { + private static native TextChange recv(long self) throws ControllerException; + + /** + * Blocks until a {@link TextChange} is available and returns it. + * @return the text change update that occurred + * @throws ControllerException if the controller was stopped + */ + public TextChange recv() throws ControllerException { return recv(this.ptr); } private static native void send(long self, TextChange change) throws ControllerException; + + /** + * Tries to send a {@link TextChange} update. + * @throws ControllerException if the controller was stopped + */ public void send(TextChange change) throws ControllerException { - send(this.ptr, Objects.requireNonNull(change)); + send(this.ptr, change); } - private static native void callback(long self, Callback cb); - public void callback(Callback cb) { - callback(this.ptr, Objects.requireNonNull(cb)); + private static native void callback(long self, Consumer cb); + + /** + * Registers a callback to be invoked whenever a {@link TextChange} occurs. + * This will not work unless a Java thread has been dedicated to the event loop. + * @see Extensions#drive(boolean) + */ + public void callback(Consumer cb) { + callback(this.ptr, cb); } private static native void clear_callback(long self); + + /** + * Clears the registered callback. + * @see #callback(Consumer) + */ public void clearCallback() { clear_callback(this.ptr); } - private static native void poll(long self); - public void poll() { + private static native void poll(long self) throws ControllerException; + + /** + * Blocks until a {@link TextChange} is available. + * @throws ControllerException if the controller was stopped + */ + public void poll() throws ControllerException { poll(this.ptr); } private static native boolean stop(long self); + + /** + * Stops the controller. Any further calls to it will fail. + * @return true if it was stopped successfully + */ public boolean stop() { return stop(this.ptr); } diff --git a/dist/java/src/mp/code/Client.java b/dist/java/src/mp/code/Client.java index f10613a..b70292d 100644 --- a/dist/java/src/mp/code/Client.java +++ b/dist/java/src/mp/code/Client.java @@ -1,72 +1,150 @@ package mp.code; +import lombok.Getter; import mp.code.data.Config; import mp.code.data.User; import mp.code.exceptions.ConnectionException; import mp.code.exceptions.ConnectionRemoteException; -import java.util.Objects; import java.util.Optional; -public class Client { +/** + * The main entrypoint of the library. + * This is the only object you are expected to hold yourself; unlike all the others, + * there are no copies of it managed exclusively by the library. When this is garbage + * collected, it will free the underlying memory. + * A Client is used to join and manage workspaces, and to obtain information about + * the current session. + */ +@Getter +public final class Client { private final long ptr; - public static native Client connect(Config config) throws ConnectionException; - Client(long ptr) { this.ptr = ptr; } + /** + * Connects to a remote CodeMP server and creates a {@link Client} instance + * for interacting with it. + * @param config a {@link Config} object containing the connection settings + * @return a holder for the Client's pointer + * @throws ConnectionException if an error occurs in communicating with the server + */ + public static native Client connect(Config config) throws ConnectionException; + private static native User get_user(long self); + + /** + * Gets information about the current user. + * @return a {@link User} object representing the user + */ public User getUser() { return get_user(this.ptr); } - private static native Workspace join_workspace(long self, String id) throws ConnectionException; - public Workspace joinWorkspace(String id) throws ConnectionException { - return join_workspace(this.ptr, Objects.requireNonNull(id)); + private static native Workspace join_workspace(long self, String workspaceId) throws ConnectionException; + + /** + * Joins a {@link Workspace} and returns it. + * @param workspaceId the id of the workspace to connect to + * @return the relevant {@link Workspace} + * @throws ConnectionException if an error occurs in communicating with the server + */ + public Workspace joinWorkspace(String workspaceId) throws ConnectionException { + return join_workspace(this.ptr, workspaceId); } - private static native void create_workspace(long self, String id) throws ConnectionRemoteException; - public void createWorkspace(String id) throws ConnectionRemoteException { - create_workspace(this.ptr, Objects.requireNonNull(id)); + private static native void create_workspace(long self, String workspaceId) throws ConnectionRemoteException; + + /** + * Creates a workspace. You need to call {@link #joinWorkspace(String)} to actually join + * and interact with it. + * @param workspaceId the id of the new workspace + * @throws ConnectionRemoteException if an error occurs in communicating with the server + */ + public void createWorkspace(String workspaceId) throws ConnectionRemoteException { + create_workspace(this.ptr, workspaceId); } - private static native void delete_workspace(long self, String id) throws ConnectionRemoteException; - public void deleteWorkspace(String id) throws ConnectionRemoteException { - delete_workspace(this.ptr, Objects.requireNonNull(id)); + private static native void delete_workspace(long self, String workspaceId) throws ConnectionRemoteException; + + /** + * Deletes a workspace. + * @param workspaceId the id of the workspace to delete + * @throws ConnectionRemoteException if an error occurs in communicating with the server + */ + public void deleteWorkspace(String workspaceId) throws ConnectionRemoteException { + delete_workspace(this.ptr, workspaceId); } private static native void invite_to_workspace(long self, String workspaceId, String user) throws ConnectionRemoteException; + + /** + * Invites a user to a workspace. + * @param workspaceId the id of the new workspace + * @param user the name of the user to invite + * @throws ConnectionRemoteException if an error occurs in communicating with the server + */ public void inviteToWorkspace(String workspaceId, String user) throws ConnectionRemoteException { - invite_to_workspace(this.ptr, Objects.requireNonNull(workspaceId), Objects.requireNonNull(user)); + invite_to_workspace(this.ptr, workspaceId, user); } private static native String[] list_workspaces(long self, boolean owned, boolean invited) throws ConnectionRemoteException; + + /** + * Lists available workspaces according to certain filters. + * @param owned if owned workspaces should be included + * @param invited if workspaces the user is invited to should be included + * @return an array of workspace IDs + * @throws ConnectionRemoteException if an error occurs in communicating with the server + */ public String[] listWorkspaces(boolean owned, boolean invited) throws ConnectionRemoteException { return list_workspaces(this.ptr, owned, invited); } private static native String[] active_workspaces(long self); + + /** + * Lists the currently active workspaces (the ones the user has currently joined). + * @return an array of workspace IDs + */ public String[] activeWorkspaces() { return active_workspaces(this.ptr); } - private static native boolean leave_workspace(long self, String id); - public boolean leaveWorkspace(String id) { - return leave_workspace(this.ptr, Objects.requireNonNull(id)); + private static native boolean leave_workspace(long self, String workspaceId); + + /** + * Leaves a workspace. + * @param workspaceId the id of the workspaces to leave + * @return true if it succeeded (usually fails if the workspace wasn't active) + */ + public boolean leaveWorkspace(String workspaceId) { + return leave_workspace(this.ptr, workspaceId); } private static native Workspace get_workspace(long self, String workspace); - public Optional getWorkspace(String workspace) { - return Optional.ofNullable(get_workspace(this.ptr, Objects.requireNonNull(workspace))); + + /** + * Gets an active workspace. + * @param workspaceId the id of the workspaces to get + * @return a {@link Workspace} with that name, if it was present and active + */ + public Optional getWorkspace(String workspaceId) { + return Optional.ofNullable(get_workspace(this.ptr, workspaceId)); } private static native void refresh(long self) throws ConnectionRemoteException; + + /** + * Refreshes the current access token. + * @throws ConnectionRemoteException if an error occurs in communicating with the server + */ public void refresh() throws ConnectionRemoteException { refresh(this.ptr); } - + private static native void free(long self); @Override protected void finalize() { diff --git a/dist/java/src/mp/code/CursorController.java b/dist/java/src/mp/code/CursorController.java index 369b58b..2b1f36d 100644 --- a/dist/java/src/mp/code/CursorController.java +++ b/dist/java/src/mp/code/CursorController.java @@ -1,13 +1,17 @@ package mp.code; -import mp.code.data.Callback; import mp.code.data.Cursor; import mp.code.exceptions.ControllerException; -import java.util.Objects; import java.util.Optional; +import java.util.function.Consumer; -public class CursorController { +/** + * Allows interaction with the CodeMP cursor position tracking system. + *

+ * It is generally safer to avoid storing this directly, see the api notes for {@link Workspace}. + */ +public final class CursorController { private final long ptr; CursorController(long ptr) { @@ -15,36 +19,75 @@ public class CursorController { } private static native Cursor try_recv(long self) throws ControllerException; + + /** + * Tries to get a {@link Cursor} update from the queue if any were present, and returns + * an empty optional otherwise. + * @return the first cursor event in queue, if any are present + * @throws ControllerException if the controller was stopped + */ public Optional tryRecv() throws ControllerException { return Optional.ofNullable(try_recv(this.ptr)); } private static native Cursor recv(long self) throws ControllerException; + + /** + * Blocks until a {@link Cursor} update is available and returns it. + * @return the cursor update that occurred + * @throws ControllerException if the controller was stopped + */ public Cursor recv() throws ControllerException { return recv(this.ptr); } private static native void send(long self, Cursor cursor) throws ControllerException; + + /** + * Tries to send a {@link Cursor} update. + * @throws ControllerException if the controller was stopped + */ public void send(Cursor cursor) throws ControllerException { - send(this.ptr, Objects.requireNonNull(cursor)); + send(this.ptr, cursor); } - private static native void callback(long self, Callback cb); - public void callback(Callback cb) { - callback(this.ptr, Objects.requireNonNull(cb)); + private static native void callback(long self, Consumer cb); + + /** + * Registers a callback to be invoked whenever a {@link Cursor} update occurs. + * This will not work unless a Java thread has been dedicated to the event loop. + * @see Extensions#drive(boolean) + */ + public void callback(Consumer cb) { + callback(this.ptr, cb); } private static native void clear_callback(long self); + + /** + * Clears the registered callback. + * @see #callback(Consumer) + */ public void clearCallback() { clear_callback(this.ptr); } - private static native void poll(long self); - public void poll() { + private static native void poll(long self) throws ControllerException; + + /** + * Blocks until a {@link Cursor} update is available. + * @throws ControllerException if the controller was stopped + */ + public void poll() throws ControllerException { poll(this.ptr); } private static native boolean stop(long self); + + /** + * Stops the controller. Any further calls to it will fail. + * @return true if it was stopped successfully + */ public boolean stop() { return stop(this.ptr); } diff --git a/dist/java/src/mp/code/Extensions.java b/dist/java/src/mp/code/Extensions.java index 7dca2fd..c6cddfa 100644 --- a/dist/java/src/mp/code/Extensions.java +++ b/dist/java/src/mp/code/Extensions.java @@ -2,7 +2,41 @@ package mp.code; import java.io.IOException; -public class Extensions { +/** + * A class holding utility functions, as well as functions which are specific + * to this language's glue and don't necessarily have a counterpart in the + * broader CodeMP API. + */ +public final class Extensions { + /** + * Hashes the given {@link String} using CodeMP's hashing algorithm (xxh3). + * @param input the string to hash + * @return the hash + */ + public static native long hash(String input); + + /** + * Drive the underlying library's asynchronous event loop. In other words, tells + * it what thread to use. You usually want to call this during initialisation. + *

+ * Passing false will have the native library manage threads, but it may fail to + * work with some more advanced features. + *

+ * You may alternatively call this with true, in a separate and dedicated Java thread; + * it will remain active in the background and act as event loop. Assign it like this: + *

new Thread(() -> Extensions.drive(true)).start();

+ * @param block true if it should use the current thread + */ + public static native void drive(boolean block); + + /** + * Configures the tracing subscriber for the native logs. + * Do not call this unless you want to see the native library's logs. + * @param path where to output this, null to use stdout + * @param debug whether to run it in debug mode + */ + public static native void setupTracing(String path, boolean debug); + private static boolean loaded = false; static synchronized void loadLibraryIfNotPresent() { if(loaded) return; @@ -17,27 +51,6 @@ public class Extensions { } } - /** - * Hashes the given {@link String} using CodeMP's hashing algorithm (xxh3). - * @param input the string to hash - * @return the hash - */ - public static native long hash(String input); - - /** - * Drive the underlying library's asynchronous event loop. - * @param block true if it should use the current thread, false if it should - * spawn a separate one - */ - public static native void drive(boolean block); - - /** - * Configures the tracing subscriber for the native logs. - * @param path where to output this, null to use stdout - * @param debug whether to run it in debug mode - */ - public static native void setupTracing(String path, boolean debug); - static { Extensions.loadLibraryIfNotPresent(); } diff --git a/dist/java/src/mp/code/Workspace.java b/dist/java/src/mp/code/Workspace.java index 73df6ea..419a193 100644 --- a/dist/java/src/mp/code/Workspace.java +++ b/dist/java/src/mp/code/Workspace.java @@ -1,6 +1,5 @@ package mp.code; -import java.util.Objects; import java.util.Optional; import java.util.UUID; @@ -9,7 +8,16 @@ import mp.code.exceptions.ConnectionException; import mp.code.exceptions.ConnectionRemoteException; import mp.code.exceptions.ControllerException; -public class Workspace { +/** + * Represents a CodeMP workspace, which broadly speaking is a collection + * of buffers across which edits and cursor movements are tracked. + * @apiNote Generally, it is safer to avoid storing this directly. + * Instead, users should let the native library manage as + * much as possible for them. + * They should store the workspace ID and retrieve the object + * whenever needed with {@link Client#getWorkspace(String)}. + */ +public final class Workspace { private final long ptr; Workspace(long ptr) { @@ -17,66 +25,147 @@ public class Workspace { } private static native String get_workspace_id(long self); + + /** + * Gets the unique identifier of the current workspace. + * @return the identifier + */ public String getWorkspaceId() { return get_workspace_id(this.ptr); } private static native CursorController get_cursor(long self); + + /** + * Gets the {@link CursorController} for the current workspace. + * @return the {@link CursorController} + */ public CursorController getCursor() { return get_cursor(this.ptr); } private static native BufferController get_buffer(long self, String path); + + /** + * Looks for a {@link BufferController} with the given path within the + * current workspace and returns it if it exists. + * @param path the current path + * @return the {@link BufferController} with the given path, if it exists + */ public Optional getBuffer(String path) { - return Optional.ofNullable(get_buffer(this.ptr, Objects.requireNonNull(path))); + return Optional.ofNullable(get_buffer(this.ptr, path)); } private static native String[] get_file_tree(long self, String filter, boolean strict); + + /** + * Gets the file tree for this workspace, optionally filtering it. + * @param filter applies an optional filter to the outputs + * @param strict whether it should be a strict match (equals) or not (startsWith) + * @return an array containing file tree as flat paths + */ + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") public String[] getFileTree(Optional filter, boolean strict) { return get_file_tree(this.ptr, filter.orElse(null), strict); } private static native String[] active_buffers(long self); + + /** + * Returns the currently active buffers (the ones the user is currently + * attached to). + * @return an array containing the paths of the active buffers + */ public String[] activeBuffers() { return active_buffers(this.ptr); } private static native void create_buffer(long self, String path) throws ConnectionRemoteException; + + /** + * Creates a buffer with the given path. + * @param path the new buffer's path + * @throws ConnectionRemoteException if an error occurs in communicating with the server + */ public void createBuffer(String path) throws ConnectionRemoteException { - create_buffer(this.ptr, Objects.requireNonNull(path)); + create_buffer(this.ptr, path); } private static native BufferController attach_to_buffer(long self, String path) throws ConnectionException; + + /** + * Attaches to an existing buffer with the given path, if present. + * @param path the path of the buffer to attach to + * @return the {@link BufferController} associated with that path + * @throws ConnectionException if an error occurs in communicating with the server, or if the buffer did not exist + */ public BufferController attachToBuffer(String path) throws ConnectionException { - return attach_to_buffer(ptr, Objects.requireNonNull(path)); + return attach_to_buffer(ptr, path); } private static native DetachResult detach_from_buffer(long self, String path); + + /** + * Detaches from a given buffer. + * @param path the path of the buffer to detach from + * @return a {@link DetachResult} representing the outcome of the operation + */ public DetachResult detachFromBuffer(String path) { - return detach_from_buffer(this.ptr, Objects.requireNonNull(path)); + return detach_from_buffer(this.ptr, path); } private static native void fetch_buffers(long self) throws ConnectionRemoteException; + + /** + * Updates the local list of buffers. + * @throws ConnectionRemoteException if an error occurs in communicating with the server + */ public void fetchBuffers() throws ConnectionRemoteException { fetch_buffers(this.ptr); } private static native void fetch_users(long self) throws ConnectionRemoteException; + + /** + * Updates the local list of users. + * @throws ConnectionRemoteException if an error occurs in communicating with the server + */ public void fetchUsers() throws ConnectionRemoteException { fetch_buffers(this.ptr); } private static native UUID[] list_buffer_users(long self, String path) throws ConnectionRemoteException; + + /** + * Lists the user attached to a certain buffer. + * The user must be attached to the buffer to perform this operation. + * @param path the path of the buffer to search + * @return an array of user {@link UUID UUIDs} + * @throws ConnectionRemoteException if an error occurs in communicating with the server, or the user wasn't attached + */ public UUID[] listBufferUsers(String path) throws ConnectionRemoteException { - return list_buffer_users(this.ptr, Objects.requireNonNull(path)); + return list_buffer_users(this.ptr, path); } private static native void delete_buffer(long self, String path) throws ConnectionRemoteException; + + /** + * Deletes the buffer with the given path. + * @param path the path of the buffer to delete + * @throws ConnectionRemoteException if an error occurs in communicating with the server + */ public void deleteBuffer(String path) throws ConnectionRemoteException { - delete_buffer(this.ptr, Objects.requireNonNull(path)); + delete_buffer(this.ptr, path); } private static native Event event(long self) throws ControllerException; + + /** + * Blocks until a workspace event occurs. + * You shouldn't call this, unless it's on a dedicated thread. + * @return the {@link Event} that has occurred + * @throws ControllerException if the event arrived while the underlying controller was already closed + */ public Event event() throws ControllerException { return event(this.ptr); } @@ -90,8 +179,11 @@ public class Workspace { static { Extensions.loadLibraryIfNotPresent(); } - - public static class Event { + + /** + * Represents a workspace-wide event. + */ + public static final class Event { private final Type type; private final String argument; @@ -100,19 +192,31 @@ public class Workspace { this.argument = argument; } + /** + * Gets the user who joined, if any did. + * @return the user who joined, if any did + */ public Optional getUserJoined() { if(this.type == Type.USER_JOIN) { return Optional.of(this.argument); } else return Optional.empty(); } + /** + * Gets the user who left, if any did. + * @return the user who left, if any did + */ public Optional getUserLeft() { if(this.type == Type.USER_LEAVE) { return Optional.of(this.argument); } else return Optional.empty(); } - public Optional getTargetBuffer() { + /** + * Gets the path of buffer that changed, if any did. + * @return the path of buffer that changed, if any did + */ + public Optional getChangedBuffer() { if(this.type == Type.FILE_TREE_UPDATED) { return Optional.of(this.argument); } else return Optional.empty(); diff --git a/dist/java/src/mp/code/data/Callback.java b/dist/java/src/mp/code/data/Callback.java deleted file mode 100644 index c436f67..0000000 --- a/dist/java/src/mp/code/data/Callback.java +++ /dev/null @@ -1,6 +0,0 @@ -package mp.code.data; - -@FunctionalInterface -public interface Callback { - void invoke(T controller); -} diff --git a/dist/java/src/mp/code/data/Config.java b/dist/java/src/mp/code/data/Config.java index f823c55..49c7e6e 100644 --- a/dist/java/src/mp/code/data/Config.java +++ b/dist/java/src/mp/code/data/Config.java @@ -8,17 +8,30 @@ import lombok.ToString; import java.util.Optional; import java.util.OptionalInt; +/** + * A data class representing the connection configuration. + */ @ToString @EqualsAndHashCode @RequiredArgsConstructor(access = AccessLevel.PRIVATE) @SuppressWarnings("OptionalUsedAsFieldOrParameterType") public class Config { - private final String username; - private final String password; - private final Optional host; - private final OptionalInt port; - private final Optional tls; + /** The username to connect with. */ + public final String username; + /** The password to connect with. */ + public final String password; + /** The host to connect to, if custom. */ + public final Optional host; + /** The port to connect to, if custom. */ + public final OptionalInt port; + /** Whether to use TLS, if custom. */ + public final Optional tls; + /** + * Provides the given username and password on the default server. + * @param username the username + * @param password the password + */ public Config(String username, String password) { this( username, @@ -29,6 +42,14 @@ public class Config { ); } + /** + * Provides the given username and password as well as a custom server. + * @param username the username + * @param password the password + * @param host the host server + * @param port the port CodeMP is running on, must be between 0 and 65535 + * @param tls whether to use TLS + */ public Config(String username, String password, String host, int port, boolean tls) { this( username, diff --git a/dist/java/src/mp/code/data/Cursor.java b/dist/java/src/mp/code/data/Cursor.java index 770bae9..e132fc9 100644 --- a/dist/java/src/mp/code/data/Cursor.java +++ b/dist/java/src/mp/code/data/Cursor.java @@ -4,11 +4,44 @@ import lombok.EqualsAndHashCode; import lombok.RequiredArgsConstructor; import lombok.ToString; +/** + * A data class holding information about a cursor event. + */ @ToString @EqualsAndHashCode @RequiredArgsConstructor public class Cursor { - public final int startRow, startCol, endRow, endCol; + /** + * The starting row of the cursor position. + * If negative, it is clamped to 0. + */ + public final int startRow; + + /** + * The starting column of the cursor position. + * If negative, it is clamped to 0. + */ + public final int startCol; + + /** + * The ending row of the cursor position. + * If negative, it is clamped to 0. + */ + public final int endRow; + + /** + * The ending column of the cursor position. + * If negative, it is clamped to 0. + */ + public final int endCol; + + /** + * The buffer the cursor is located on. + */ public final String buffer; + + /** + * The user who controls the cursor. + */ public final String user; } diff --git a/dist/java/src/mp/code/data/DetachResult.java b/dist/java/src/mp/code/data/DetachResult.java index 3210eb9..c367354 100644 --- a/dist/java/src/mp/code/data/DetachResult.java +++ b/dist/java/src/mp/code/data/DetachResult.java @@ -1,7 +1,15 @@ package mp.code.data; +import mp.code.Workspace; + +/** + * The result of a {@link Workspace#detachFromBuffer(String)} operation. + */ public enum DetachResult { + /** The user was not attached to this buffer. */ NOT_ATTACHED, + /** The user detached from the buffer and stopped it. */ DETACHING, + /** The user was attached, but the buffer was already stopped. */ ALREADY_DETACHED } diff --git a/dist/java/src/mp/code/data/TextChange.java b/dist/java/src/mp/code/data/TextChange.java index 9ab21d3..e656b55 100644 --- a/dist/java/src/mp/code/data/TextChange.java +++ b/dist/java/src/mp/code/data/TextChange.java @@ -3,30 +3,86 @@ package mp.code.data; import lombok.EqualsAndHashCode; import lombok.RequiredArgsConstructor; import lombok.ToString; +import mp.code.Extensions; import java.util.OptionalLong; +/** + * A data class holding information about a text change. + */ @ToString @EqualsAndHashCode @RequiredArgsConstructor @SuppressWarnings("OptionalUsedAsFieldOrParameterType") public class TextChange { + /** + * The starting position of the change. + * If negative, it is clamped to 0. + */ public final int start; + + /** + * The endomg position of the change. + * If negative, it is clamped to 0. + */ public final int end; + + /** + * The content of the change. + * It should never be null; if you need to represent absence of content, use an empty string. + */ public final String content; + + /** + * The hash of the content after applying it (calculated with {@link Extensions#hash(String)}). + * It is generally meaningless to send, but when received it is an invitation to check the hash + * and forcefully re-sync if necessary. + */ public final OptionalLong hash; // xxh3 hash + /** + * Checks if the change represents a deletion. + * It does if the starting index is lower than the ending index. + * It is NOT mutually exclusive with {@link #isInsert()}. + * @return true if this change represents a deletion + */ public boolean isDelete() { - return this.start != this.end; + return this.start < this.end; } + /** + * Checks if the change represents an insertion. + * It does if the content is not empty + * It is NOT mutually exclusive with {@link #isDelete()}. + * @return true if this change represents an insertion + */ public boolean isInsert() { return !this.content.isEmpty(); } + /** + * Checks whether this change is a no-op. + * @return true if this change is a no-op + */ public boolean isEmpty() { return !this.isDelete() && !this.isInsert(); } - //TODO: apply() + /** + * Applies the change to an input string and returns the result. + * @param input the input string + * @return the mutated string + */ + public String apply(String input) { + int preIndex = Math.min(this.start, input.length()); + String pre = ""; + try { + pre = input.substring(0, preIndex); + } catch(IndexOutOfBoundsException ignored) {} + String post = ""; + try { + post = input.substring(this.end); + } catch(IndexOutOfBoundsException ignored) {} + return pre + this.content + post; + } } diff --git a/dist/java/src/mp/code/data/User.java b/dist/java/src/mp/code/data/User.java index 84ab01f..b5ef86b 100644 --- a/dist/java/src/mp/code/data/User.java +++ b/dist/java/src/mp/code/data/User.java @@ -6,10 +6,20 @@ import lombok.ToString; import java.util.UUID; +/** + * A data class holding information about a user. + */ @ToString @EqualsAndHashCode @RequiredArgsConstructor public class User { + /** + * The {@link UUID} of the user. + */ public final UUID id; + + /** + * The human-readable name of the user. + */ public final String name; } diff --git a/dist/java/src/mp/code/exceptions/ConnectionException.java b/dist/java/src/mp/code/exceptions/ConnectionException.java index 264e1cc..31d116b 100644 --- a/dist/java/src/mp/code/exceptions/ConnectionException.java +++ b/dist/java/src/mp/code/exceptions/ConnectionException.java @@ -4,7 +4,12 @@ package mp.code.exceptions; * An exception that may occur when processing network requests. */ public abstract class ConnectionException extends Exception { - protected ConnectionException(String msg) { - super(msg); + + /** + * Creates a new exception with the given message. + * @param message the message + */ + protected ConnectionException(String message) { + super(message); } } diff --git a/dist/java/src/mp/code/exceptions/ConnectionRemoteException.java b/dist/java/src/mp/code/exceptions/ConnectionRemoteException.java index a0fe300..a3cc97f 100644 --- a/dist/java/src/mp/code/exceptions/ConnectionRemoteException.java +++ b/dist/java/src/mp/code/exceptions/ConnectionRemoteException.java @@ -4,7 +4,12 @@ package mp.code.exceptions; * An exception returned by the server as a response. */ public abstract class ConnectionRemoteException extends ConnectionException { - protected ConnectionRemoteException(String msg) { - super(msg); + + /** + * Creates a new exception with the given message. + * @param message the message + */ + public ConnectionRemoteException(String message) { + super(message); } } diff --git a/dist/java/src/mp/code/exceptions/ConnectionTransportException.java b/dist/java/src/mp/code/exceptions/ConnectionTransportException.java index a4fe22e..3228ca1 100644 --- a/dist/java/src/mp/code/exceptions/ConnectionTransportException.java +++ b/dist/java/src/mp/code/exceptions/ConnectionTransportException.java @@ -4,7 +4,12 @@ package mp.code.exceptions; * An exception that occurred from the underlying tonic layer. */ public abstract class ConnectionTransportException extends Exception { - protected ConnectionTransportException(String msg) { - super(msg); + + /** + * Creates a new exception with the given message. + * @param message the message + */ + public ConnectionTransportException(String message) { + super(message); } } diff --git a/dist/java/src/mp/code/exceptions/ControllerException.java b/dist/java/src/mp/code/exceptions/ControllerException.java index 4a26f39..2061478 100644 --- a/dist/java/src/mp/code/exceptions/ControllerException.java +++ b/dist/java/src/mp/code/exceptions/ControllerException.java @@ -3,11 +3,15 @@ package mp.code.exceptions; /** * An exception that may occur when a {@link mp.code.BufferController} or * a {@link mp.code.CursorController} perform an illegal operation. - * It may also occur as a result of {@link mp.code.Workspace#event()} and - * {@link mp.code.Workspace#selectBuffer(long)}. + * It may also occur as a result of {@link mp.code.Workspace#event()}. */ public abstract class ControllerException extends Exception { - protected ControllerException(String msg) { - super(msg); + + /** + * Creates a new exception with the given message. + * @param message the message + */ + protected ControllerException(String message) { + super(message); } } diff --git a/dist/java/src/mp/code/exceptions/ControllerStoppedException.java b/dist/java/src/mp/code/exceptions/ControllerStoppedException.java index b8f8d52..ca507b6 100644 --- a/dist/java/src/mp/code/exceptions/ControllerStoppedException.java +++ b/dist/java/src/mp/code/exceptions/ControllerStoppedException.java @@ -5,7 +5,12 @@ package mp.code.exceptions; * the worker has already stopped. */ public class ControllerStoppedException extends ControllerException { - protected ControllerStoppedException(String msg) { - super(msg); + + /** + * Creates a new exception with the given message. + * @param message the message + */ + public ControllerStoppedException(String message) { + super(message); } } diff --git a/dist/java/src/mp/code/exceptions/ControllerUnfulfilledException.java b/dist/java/src/mp/code/exceptions/ControllerUnfulfilledException.java index 5c5a9cd..52b483e 100644 --- a/dist/java/src/mp/code/exceptions/ControllerUnfulfilledException.java +++ b/dist/java/src/mp/code/exceptions/ControllerUnfulfilledException.java @@ -5,7 +5,12 @@ package mp.code.exceptions; * fulfilling the request, without rejecting it first. */ public class ControllerUnfulfilledException extends ControllerException { - protected ControllerUnfulfilledException(String msg) { - super(msg); + + /** + * Creates a new exception with the given message. + * @param message the message + */ + public ControllerUnfulfilledException(String message) { + super(message); } } diff --git a/dist/java/src/mp/code/exceptions/JNIException.java b/dist/java/src/mp/code/exceptions/JNIException.java index 37f3966..dd599da 100644 --- a/dist/java/src/mp/code/exceptions/JNIException.java +++ b/dist/java/src/mp/code/exceptions/JNIException.java @@ -6,6 +6,11 @@ package mp.code.exceptions; * Only catch this if you are aware of the implications. */ public class JNIException extends RuntimeException { + + /** + * Creates a new exception with the given message. + * @param message the message + */ public JNIException(String message) { super(message); } diff --git a/src/ffi/java/buffer.rs b/src/ffi/java/buffer.rs index cfc4f3d..7731617 100644 --- a/src/ffi/java/buffer.rs +++ b/src/ffi/java/buffer.rs @@ -103,11 +103,10 @@ pub extern "system" fn Java_mp_code_BufferController_callback<'local>( if let Err(e) = env.with_local_frame(5, |env| { use crate::ffi::java::JObjectify; let jcontroller = controller.jobjectify(env)?; - let sig = format!("(L{};)V", "java/lang/Object"); if let Err(e) = env.call_method( &cb_ref, - "invoke", - &sig, + "accept", + "(Ljava/lang/Object;)V", &[jni::objects::JValueGen::Object(&jcontroller)] ) { tracing::error!("error invoking callback: {e:?}"); diff --git a/src/ffi/java/cursor.rs b/src/ffi/java/cursor.rs index adae888..73dc431 100644 --- a/src/ffi/java/cursor.rs +++ b/src/ffi/java/cursor.rs @@ -74,11 +74,10 @@ pub extern "system" fn Java_mp_code_CursorController_callback<'local>( if let Err(e) = env.with_local_frame(5, |env| { use crate::ffi::java::JObjectify; let jcontroller = controller.jobjectify(env)?; - let sig = format!("(L{};)V", "java/lang/Object"); if let Err(e) = env.call_method( &cb_ref, - "invoke", - &sig, + "accept", + "(Ljava/lang/Object;)V", &[jni::objects::JValueGen::Object(&jcontroller)] ) { tracing::error!("error invoking callback: {e:?}");