package mp.code; import java.util.Optional; import java.util.function.Consumer; import lombok.Getter; import mp.code.data.User; import mp.code.exceptions.ConnectionException; import mp.code.exceptions.ConnectionRemoteException; import mp.code.exceptions.ControllerException; /** * Represents a CodeMP workspace, which broadly speaking is a collection * of buffers across which edits and cursor movements are tracked. * 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) { this.ptr = ptr; Extensions.CLEANER.register(this, () -> free(ptr)); } private static native String id(long self); /** * Gets the unique identifier of the current workspace. * @return the identifier */ public String id() { return id(this.ptr); } private static native CursorController cursor(long self); /** * Gets the {@link CursorController} for the current workspace. * @return the {@link CursorController} */ public CursorController cursor() { return 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, path)); } private static native String[] search_buffers(long self, String filter); /** * Searches for buffers matching the filter in this workspace. * @param filter the filter to apply * @return an array containing file tree as flat paths */ @SuppressWarnings("OptionalUsedAsFieldOrParameterType") public String[] searchBuffers(Optional filter) { return search_buffers(this.ptr, filter.orElse(null)); } 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 User[] user_list(long self); /** * Returns the users currently in the workspace. * @return an array containing the users in the workspace */ public User[] userList() { return user_list(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, path); } private static native BufferController attach_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 attachBuffer(String path) throws ConnectionException { return attach_buffer(ptr, path); } private static native boolean detach_buffer(long self, String path); /** * Detaches from a given buffer. * @param path the path of the buffer to detach from * @return a boolean, true only if there are still dangling references preventing controller from stopping */ public boolean detachBuffer(String path) { return detach_buffer(this.ptr, path); } private static native String[] fetch_buffers(long self) throws ConnectionRemoteException; /** * Updates and fetches the local list of buffers. * @return the updated list * @throws ConnectionRemoteException if an error occurs in communicating with the server */ public String[] fetchBuffers() throws ConnectionRemoteException { return fetch_buffers(this.ptr); } private static native User[] fetch_users(long self) throws ConnectionRemoteException; /** * Updates and fetches the local list of users. * @return the updated list * @throws ConnectionRemoteException if an error occurs in communicating with the server */ public User[] fetchUsers() throws ConnectionRemoteException { return fetch_users(this.ptr); } private static native User[] fetch_buffer_users(long self, String path) throws ConnectionRemoteException; /** * Fetches the users 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 {@link User}s * @throws ConnectionRemoteException if an error occurs in communicating with the server, or the user wasn't attached */ public User[] fetchBufferUsers(String path) throws ConnectionRemoteException { return fetch_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, path); } private static native Event try_recv(long self) throws ControllerException; /** * Tries to get a {@link Event} from the queue if any were present, and returns * an empty optional otherwise. * @return the first workspace 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 Event recv(long self) throws ControllerException; /** * Blocks until a {@link Event} is available and returns it. * @return the workspace event that occurred * @throws ControllerException if the controller was stopped */ public Event recv() throws ControllerException { return recv(this.ptr); } private static native void callback(long self, Consumer cb); /** * Registers a callback to be invoked whenever a new {@link Event} is ready to be received. * This will not work unless a Java thread has been dedicated to the event loop. * @param cb a {@link Consumer} that receives the controller when the change occurs; * you should probably spawn a new thread in here, to avoid deadlocking * @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) throws ControllerException; /** * Blocks until a {@link Event} is available. * @throws ControllerException if the controller was stopped */ public void poll() throws ControllerException { poll(this.ptr); } private static native void free(long self); static { NativeUtils.loadLibraryIfNeeded(); } /** * Represents a workspace-wide event. */ public static final class Event { /** The type of the event. */ public final @Getter Type type; private final String argument; Event(Type type, String argument) { this.type = type; 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(); } /** * 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(); } /** * The type of workspace event. */ public enum Type { /** * Somebody joined a workspace. * @see #getUserJoined() to get the name */ USER_JOIN, /** * Somebody left a workspace * @see #getUserLeft() to get the name */ USER_LEAVE, /** * The filetree was updated. * @see #getChangedBuffer() to see the buffer that changed */ FILE_TREE_UPDATED } } }