docs(java): written javadocs, removed unused class

This commit is contained in:
zaaarf 2024-09-17 23:16:39 +02:00
parent 54db029ecc
commit 27b56cbd03
No known key found for this signature in database
GPG key ID: 102E445F4C3F829B
20 changed files with 555 additions and 108 deletions

View file

@ -1,14 +1,18 @@
package mp.code; package mp.code;
import mp.code.data.Callback;
import mp.code.data.Cursor;
import mp.code.data.TextChange; import mp.code.data.TextChange;
import mp.code.exceptions.ControllerException; import mp.code.exceptions.ControllerException;
import java.util.Objects;
import java.util.Optional; 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.
* <p>
* It is generally safer to avoid storing this directly, see the api notes for {@link Workspace}.
*/
public final class BufferController {
private final long ptr; private final long ptr;
BufferController(long ptr) { BufferController(long ptr) {
@ -16,46 +20,97 @@ public class BufferController {
} }
private static native String get_name(long self); private static native String get_name(long self);
/**
* Gets the name (path) of the buffer.
* @return the path of the buffer
*/
public String getName() { public String getName() {
return get_name(this.ptr); return get_name(this.ptr);
} }
private static native String get_content(long self) throws ControllerException; 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 { public String getContent() throws ControllerException {
return get_content(this.ptr); return get_content(this.ptr);
} }
private static native TextChange try_recv(long self) throws ControllerException; 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<TextChange> tryRecv() throws ControllerException { public Optional<TextChange> tryRecv() throws ControllerException {
return Optional.ofNullable(try_recv(this.ptr)); return Optional.ofNullable(try_recv(this.ptr));
} }
private static native Cursor recv(long self) throws ControllerException; private static native TextChange recv(long self) throws ControllerException;
public Cursor recv() 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); return recv(this.ptr);
} }
private static native void send(long self, TextChange change) throws ControllerException; 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 { 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<BufferController> cb); private static native void callback(long self, Consumer<BufferController> cb);
public void callback(Callback<BufferController> cb) {
callback(this.ptr, Objects.requireNonNull(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<BufferController> cb) {
callback(this.ptr, cb);
} }
private static native void clear_callback(long self); private static native void clear_callback(long self);
/**
* Clears the registered callback.
* @see #callback(Consumer)
*/
public void clearCallback() { public void clearCallback() {
clear_callback(this.ptr); clear_callback(this.ptr);
} }
private static native void poll(long self); private static native void poll(long self) throws ControllerException;
public void poll() {
/**
* Blocks until a {@link TextChange} is available.
* @throws ControllerException if the controller was stopped
*/
public void poll() throws ControllerException {
poll(this.ptr); poll(this.ptr);
} }
private static native boolean stop(long self); 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() { public boolean stop() {
return stop(this.ptr); return stop(this.ptr);
} }

View file

@ -1,72 +1,150 @@
package mp.code; package mp.code;
import lombok.Getter;
import mp.code.data.Config; import mp.code.data.Config;
import mp.code.data.User; import mp.code.data.User;
import mp.code.exceptions.ConnectionException; import mp.code.exceptions.ConnectionException;
import mp.code.exceptions.ConnectionRemoteException; import mp.code.exceptions.ConnectionRemoteException;
import java.util.Objects;
import java.util.Optional; 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; private final long ptr;
public static native Client connect(Config config) throws ConnectionException;
Client(long ptr) { Client(long ptr) {
this.ptr = 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); 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() { public User getUser() {
return get_user(this.ptr); return get_user(this.ptr);
} }
private static native Workspace join_workspace(long self, String id) throws ConnectionException; private static native Workspace join_workspace(long self, String workspaceId) throws ConnectionException;
public Workspace joinWorkspace(String id) throws ConnectionException {
return join_workspace(this.ptr, Objects.requireNonNull(id)); /**
* 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; private static native void create_workspace(long self, String workspaceId) throws ConnectionRemoteException;
public void createWorkspace(String id) throws ConnectionRemoteException {
create_workspace(this.ptr, Objects.requireNonNull(id)); /**
* 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; private static native void delete_workspace(long self, String workspaceId) throws ConnectionRemoteException;
public void deleteWorkspace(String id) throws ConnectionRemoteException {
delete_workspace(this.ptr, Objects.requireNonNull(id)); /**
* 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; 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 { 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; 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 { public String[] listWorkspaces(boolean owned, boolean invited) throws ConnectionRemoteException {
return list_workspaces(this.ptr, owned, invited); return list_workspaces(this.ptr, owned, invited);
} }
private static native String[] active_workspaces(long self); 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() { public String[] activeWorkspaces() {
return active_workspaces(this.ptr); return active_workspaces(this.ptr);
} }
private static native boolean leave_workspace(long self, String id); private static native boolean leave_workspace(long self, String workspaceId);
public boolean leaveWorkspace(String id) {
return leave_workspace(this.ptr, Objects.requireNonNull(id)); /**
* 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); private static native Workspace get_workspace(long self, String workspace);
public Optional<Workspace> 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<Workspace> getWorkspace(String workspaceId) {
return Optional.ofNullable(get_workspace(this.ptr, workspaceId));
} }
private static native void refresh(long self) throws ConnectionRemoteException; 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 { public void refresh() throws ConnectionRemoteException {
refresh(this.ptr); refresh(this.ptr);
} }
private static native void free(long self); private static native void free(long self);
@Override @Override
protected void finalize() { protected void finalize() {

View file

@ -1,13 +1,17 @@
package mp.code; package mp.code;
import mp.code.data.Callback;
import mp.code.data.Cursor; import mp.code.data.Cursor;
import mp.code.exceptions.ControllerException; import mp.code.exceptions.ControllerException;
import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.function.Consumer;
public class CursorController { /**
* Allows interaction with the CodeMP cursor position tracking system.
* <p>
* It is generally safer to avoid storing this directly, see the api notes for {@link Workspace}.
*/
public final class CursorController {
private final long ptr; private final long ptr;
CursorController(long ptr) { CursorController(long ptr) {
@ -15,36 +19,75 @@ public class CursorController {
} }
private static native Cursor try_recv(long self) throws ControllerException; 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<Cursor> tryRecv() throws ControllerException { public Optional<Cursor> tryRecv() throws ControllerException {
return Optional.ofNullable(try_recv(this.ptr)); return Optional.ofNullable(try_recv(this.ptr));
} }
private static native Cursor recv(long self) throws ControllerException; 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 { public Cursor recv() throws ControllerException {
return recv(this.ptr); return recv(this.ptr);
} }
private static native void send(long self, Cursor cursor) throws ControllerException; 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 { 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<CursorController> cb); private static native void callback(long self, Consumer<CursorController> cb);
public void callback(Callback<CursorController> cb) {
callback(this.ptr, Objects.requireNonNull(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<CursorController> cb) {
callback(this.ptr, cb);
} }
private static native void clear_callback(long self); private static native void clear_callback(long self);
/**
* Clears the registered callback.
* @see #callback(Consumer)
*/
public void clearCallback() { public void clearCallback() {
clear_callback(this.ptr); clear_callback(this.ptr);
} }
private static native void poll(long self); private static native void poll(long self) throws ControllerException;
public void poll() {
/**
* Blocks until a {@link Cursor} update is available.
* @throws ControllerException if the controller was stopped
*/
public void poll() throws ControllerException {
poll(this.ptr); poll(this.ptr);
} }
private static native boolean stop(long self); 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() { public boolean stop() {
return stop(this.ptr); return stop(this.ptr);
} }

View file

@ -2,7 +2,41 @@ package mp.code;
import java.io.IOException; 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.
* <p>
* Passing false will have the native library manage threads, but it may fail to
* work with some more advanced features.
* <p>
* 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:
* <p><code>new Thread(() -> Extensions.drive(true)).start();</code></p>
* @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; private static boolean loaded = false;
static synchronized void loadLibraryIfNotPresent() { static synchronized void loadLibraryIfNotPresent() {
if(loaded) return; 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 { static {
Extensions.loadLibraryIfNotPresent(); Extensions.loadLibraryIfNotPresent();
} }

View file

@ -1,6 +1,5 @@
package mp.code; package mp.code;
import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
@ -9,7 +8,16 @@ import mp.code.exceptions.ConnectionException;
import mp.code.exceptions.ConnectionRemoteException; import mp.code.exceptions.ConnectionRemoteException;
import mp.code.exceptions.ControllerException; 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; private final long ptr;
Workspace(long ptr) { Workspace(long ptr) {
@ -17,66 +25,147 @@ public class Workspace {
} }
private static native String get_workspace_id(long self); private static native String get_workspace_id(long self);
/**
* Gets the unique identifier of the current workspace.
* @return the identifier
*/
public String getWorkspaceId() { public String getWorkspaceId() {
return get_workspace_id(this.ptr); return get_workspace_id(this.ptr);
} }
private static native CursorController get_cursor(long self); private static native CursorController get_cursor(long self);
/**
* Gets the {@link CursorController} for the current workspace.
* @return the {@link CursorController}
*/
public CursorController getCursor() { public CursorController getCursor() {
return get_cursor(this.ptr); return get_cursor(this.ptr);
} }
private static native BufferController get_buffer(long self, String path); 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<BufferController> getBuffer(String path) { public Optional<BufferController> 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); 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<String> filter, boolean strict) { public String[] getFileTree(Optional<String> filter, boolean strict) {
return get_file_tree(this.ptr, filter.orElse(null), strict); return get_file_tree(this.ptr, filter.orElse(null), strict);
} }
private static native String[] active_buffers(long self); 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() { public String[] activeBuffers() {
return active_buffers(this.ptr); return active_buffers(this.ptr);
} }
private static native void create_buffer(long self, String path) throws ConnectionRemoteException; 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 { 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; 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 { 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); 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) { 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; 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 { public void fetchBuffers() throws ConnectionRemoteException {
fetch_buffers(this.ptr); fetch_buffers(this.ptr);
} }
private static native void fetch_users(long self) throws ConnectionRemoteException; 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 { public void fetchUsers() throws ConnectionRemoteException {
fetch_buffers(this.ptr); fetch_buffers(this.ptr);
} }
private static native UUID[] list_buffer_users(long self, String path) throws ConnectionRemoteException; 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 { 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; 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 { 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; 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 { public Event event() throws ControllerException {
return event(this.ptr); return event(this.ptr);
} }
@ -90,8 +179,11 @@ public class Workspace {
static { static {
Extensions.loadLibraryIfNotPresent(); Extensions.loadLibraryIfNotPresent();
} }
public static class Event { /**
* Represents a workspace-wide event.
*/
public static final class Event {
private final Type type; private final Type type;
private final String argument; private final String argument;
@ -100,19 +192,31 @@ public class Workspace {
this.argument = argument; this.argument = argument;
} }
/**
* Gets the user who joined, if any did.
* @return the user who joined, if any did
*/
public Optional<String> getUserJoined() { public Optional<String> getUserJoined() {
if(this.type == Type.USER_JOIN) { if(this.type == Type.USER_JOIN) {
return Optional.of(this.argument); return Optional.of(this.argument);
} else return Optional.empty(); } else return Optional.empty();
} }
/**
* Gets the user who left, if any did.
* @return the user who left, if any did
*/
public Optional<String> getUserLeft() { public Optional<String> getUserLeft() {
if(this.type == Type.USER_LEAVE) { if(this.type == Type.USER_LEAVE) {
return Optional.of(this.argument); return Optional.of(this.argument);
} else return Optional.empty(); } else return Optional.empty();
} }
public Optional<String> getTargetBuffer() { /**
* Gets the path of buffer that changed, if any did.
* @return the path of buffer that changed, if any did
*/
public Optional<String> getChangedBuffer() {
if(this.type == Type.FILE_TREE_UPDATED) { if(this.type == Type.FILE_TREE_UPDATED) {
return Optional.of(this.argument); return Optional.of(this.argument);
} else return Optional.empty(); } else return Optional.empty();

View file

@ -1,6 +0,0 @@
package mp.code.data;
@FunctionalInterface
public interface Callback<T> {
void invoke(T controller);
}

View file

@ -8,17 +8,30 @@ import lombok.ToString;
import java.util.Optional; import java.util.Optional;
import java.util.OptionalInt; import java.util.OptionalInt;
/**
* A data class representing the connection configuration.
*/
@ToString @ToString
@EqualsAndHashCode @EqualsAndHashCode
@RequiredArgsConstructor(access = AccessLevel.PRIVATE) @RequiredArgsConstructor(access = AccessLevel.PRIVATE)
@SuppressWarnings("OptionalUsedAsFieldOrParameterType") @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
public class Config { public class Config {
private final String username; /** The username to connect with. */
private final String password; public final String username;
private final Optional<String> host; /** The password to connect with. */
private final OptionalInt port; public final String password;
private final Optional<Boolean> tls; /** The host to connect to, if custom. */
public final Optional<String> host;
/** The port to connect to, if custom. */
public final OptionalInt port;
/** Whether to use TLS, if custom. */
public final Optional<Boolean> 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) { public Config(String username, String password) {
this( this(
username, 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) { public Config(String username, String password, String host, int port, boolean tls) {
this( this(
username, username,

View file

@ -4,11 +4,44 @@ import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.ToString; import lombok.ToString;
/**
* A data class holding information about a cursor event.
*/
@ToString @ToString
@EqualsAndHashCode @EqualsAndHashCode
@RequiredArgsConstructor @RequiredArgsConstructor
public class Cursor { 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; public final String buffer;
/**
* The user who controls the cursor.
*/
public final String user; public final String user;
} }

View file

@ -1,7 +1,15 @@
package mp.code.data; package mp.code.data;
import mp.code.Workspace;
/**
* The result of a {@link Workspace#detachFromBuffer(String)} operation.
*/
public enum DetachResult { public enum DetachResult {
/** The user was not attached to this buffer. */
NOT_ATTACHED, NOT_ATTACHED,
/** The user detached from the buffer and stopped it. */
DETACHING, DETACHING,
/** The user was attached, but the buffer was already stopped. */
ALREADY_DETACHED ALREADY_DETACHED
} }

View file

@ -3,30 +3,86 @@ package mp.code.data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.ToString; import lombok.ToString;
import mp.code.Extensions;
import java.util.OptionalLong; import java.util.OptionalLong;
/**
* A data class holding information about a text change.
*/
@ToString @ToString
@EqualsAndHashCode @EqualsAndHashCode
@RequiredArgsConstructor @RequiredArgsConstructor
@SuppressWarnings("OptionalUsedAsFieldOrParameterType") @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
public class TextChange { public class TextChange {
/**
* The starting position of the change.
* If negative, it is clamped to 0.
*/
public final int start; public final int start;
/**
* The endomg position of the change.
* If negative, it is clamped to 0.
*/
public final int end; 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; 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 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() { 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() { public boolean isInsert() {
return !this.content.isEmpty(); return !this.content.isEmpty();
} }
/**
* Checks whether this change is a no-op.
* @return true if this change is a no-op
*/
public boolean isEmpty() { public boolean isEmpty() {
return !this.isDelete() && !this.isInsert(); 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;
}
} }

View file

@ -6,10 +6,20 @@ import lombok.ToString;
import java.util.UUID; import java.util.UUID;
/**
* A data class holding information about a user.
*/
@ToString @ToString
@EqualsAndHashCode @EqualsAndHashCode
@RequiredArgsConstructor @RequiredArgsConstructor
public class User { public class User {
/**
* The {@link UUID} of the user.
*/
public final UUID id; public final UUID id;
/**
* The human-readable name of the user.
*/
public final String name; public final String name;
} }

View file

@ -4,7 +4,12 @@ package mp.code.exceptions;
* An exception that may occur when processing network requests. * An exception that may occur when processing network requests.
*/ */
public abstract class ConnectionException extends Exception { 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);
} }
} }

View file

@ -4,7 +4,12 @@ package mp.code.exceptions;
* An exception returned by the server as a response. * An exception returned by the server as a response.
*/ */
public abstract class ConnectionRemoteException extends ConnectionException { 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);
} }
} }

View file

@ -4,7 +4,12 @@ package mp.code.exceptions;
* An exception that occurred from the underlying tonic layer. * An exception that occurred from the underlying tonic layer.
*/ */
public abstract class ConnectionTransportException extends Exception { 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);
} }
} }

View file

@ -3,11 +3,15 @@ package mp.code.exceptions;
/** /**
* An exception that may occur when a {@link mp.code.BufferController} or * An exception that may occur when a {@link mp.code.BufferController} or
* a {@link mp.code.CursorController} perform an illegal operation. * a {@link mp.code.CursorController} perform an illegal operation.
* It may also occur as a result of {@link mp.code.Workspace#event()} and * It may also occur as a result of {@link mp.code.Workspace#event()}.
* {@link mp.code.Workspace#selectBuffer(long)}.
*/ */
public abstract class ControllerException extends Exception { 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);
} }
} }

View file

@ -5,7 +5,12 @@ package mp.code.exceptions;
* the worker has already stopped. * the worker has already stopped.
*/ */
public class ControllerStoppedException extends ControllerException { 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);
} }
} }

View file

@ -5,7 +5,12 @@ package mp.code.exceptions;
* fulfilling the request, without rejecting it first. * fulfilling the request, without rejecting it first.
*/ */
public class ControllerUnfulfilledException extends ControllerException { 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);
} }
} }

View file

@ -6,6 +6,11 @@ package mp.code.exceptions;
* Only catch this if you are aware of the implications. * Only catch this if you are aware of the implications.
*/ */
public class JNIException extends RuntimeException { public class JNIException extends RuntimeException {
/**
* Creates a new exception with the given message.
* @param message the message
*/
public JNIException(String message) { public JNIException(String message) {
super(message); super(message);
} }

View file

@ -103,11 +103,10 @@ pub extern "system" fn Java_mp_code_BufferController_callback<'local>(
if let Err(e) = env.with_local_frame(5, |env| { if let Err(e) = env.with_local_frame(5, |env| {
use crate::ffi::java::JObjectify; use crate::ffi::java::JObjectify;
let jcontroller = controller.jobjectify(env)?; let jcontroller = controller.jobjectify(env)?;
let sig = format!("(L{};)V", "java/lang/Object");
if let Err(e) = env.call_method( if let Err(e) = env.call_method(
&cb_ref, &cb_ref,
"invoke", "accept",
&sig, "(Ljava/lang/Object;)V",
&[jni::objects::JValueGen::Object(&jcontroller)] &[jni::objects::JValueGen::Object(&jcontroller)]
) { ) {
tracing::error!("error invoking callback: {e:?}"); tracing::error!("error invoking callback: {e:?}");

View file

@ -74,11 +74,10 @@ pub extern "system" fn Java_mp_code_CursorController_callback<'local>(
if let Err(e) = env.with_local_frame(5, |env| { if let Err(e) = env.with_local_frame(5, |env| {
use crate::ffi::java::JObjectify; use crate::ffi::java::JObjectify;
let jcontroller = controller.jobjectify(env)?; let jcontroller = controller.jobjectify(env)?;
let sig = format!("(L{};)V", "java/lang/Object");
if let Err(e) = env.call_method( if let Err(e) = env.call_method(
&cb_ref, &cb_ref,
"invoke", "accept",
&sig, "(Ljava/lang/Object;)V",
&[jni::objects::JValueGen::Object(&jcontroller)] &[jni::objects::JValueGen::Object(&jcontroller)]
) { ) {
tracing::error!("error invoking callback: {e:?}"); tracing::error!("error invoking callback: {e:?}");