Allow multiple changes per ChangeMessage

This commit is contained in:
MrLetsplay 2024-06-22 18:51:29 +02:00
parent a64a685e75
commit 872e2842b9
Signed by: mr
SSH Key Fingerprint: SHA256:92jBH80vpXyaZHjaIl47pjRq+Yt7XGTArqQg1V7hSqg
5 changed files with 60 additions and 29 deletions

View File

@ -2,4 +2,4 @@ package me.mrletsplay.shareclientcore.connection;
import me.mrletsplay.shareclientcore.document.Char; import me.mrletsplay.shareclientcore.document.Char;
public record Change(String documentPath, ChangeType type, Char character) {} public record Change(ChangeType type, Char character) {}

View File

@ -3,12 +3,14 @@ package me.mrletsplay.shareclientcore.connection.message;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.io.DataOutputStream; import java.io.DataOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays;
import java.util.Objects;
import me.mrletsplay.shareclientcore.connection.Change; import me.mrletsplay.shareclientcore.connection.Change;
import me.mrletsplay.shareclientcore.connection.ChangeType; import me.mrletsplay.shareclientcore.connection.ChangeType;
import me.mrletsplay.shareclientcore.document.Char; import me.mrletsplay.shareclientcore.document.Char;
public record ChangeMessage(Change change) implements Message { public record ChangeMessage(String documentPath, Change[] changes) implements Message {
@Override @Override
public MessageType getType() { public MessageType getType() {
@ -17,14 +19,43 @@ public record ChangeMessage(Change change) implements Message {
@Override @Override
public void serialize(DataOutputStream out) throws IOException { public void serialize(DataOutputStream out) throws IOException {
out.writeUTF(change.documentPath()); out.writeUTF(documentPath);
out.writeUTF(change.type().name()); out.writeInt(changes.length);
change.character().serialize(out); for(int i = 0; i < changes.length; i++) {
out.writeUTF(changes[i].type().name());
changes[i].character().serialize(out);
}
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + Arrays.hashCode(changes);
result = prime * result + Objects.hash(documentPath);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ChangeMessage other = (ChangeMessage) obj;
return Arrays.equals(changes, other.changes) && Objects.equals(documentPath, other.documentPath);
} }
public static ChangeMessage deserialize(DataInputStream in) throws IOException { public static ChangeMessage deserialize(DataInputStream in) throws IOException {
try { try {
return new ChangeMessage(new Change(in.readUTF(), ChangeType.valueOf(in.readUTF()), Char.deserialize(in))); String documentPath = in.readUTF();
Change[] changes = new Change[in.readInt()];
for(int i = 0; i < changes.length; i++) {
changes[i] = new Change(ChangeType.valueOf(in.readUTF()), Char.deserialize(in));
}
return new ChangeMessage(documentPath, changes);
}catch(IllegalArgumentException e) { }catch(IllegalArgumentException e) {
throw new IOException("Invalid change type", e); throw new IOException("Invalid change type", e);
} }

View File

@ -57,7 +57,7 @@ public class SharedDocument implements MessageListener {
lamport++; lamport++;
Char ch = new Char(newPos, lamport, bytes[i]); Char ch = new Char(newPos, lamport, bytes[i]);
if(charBag.add(ch) == -1) throw new IllegalStateException("Couldn't insert newly created char"); if(charBag.add(ch) == -1) throw new IllegalStateException("Couldn't insert newly created char");
changes[i] = new Change(path, ChangeType.ADD, ch); changes[i] = new Change(ChangeType.ADD, ch);
charBefore = ch; charBefore = ch;
} }
@ -87,14 +87,12 @@ public class SharedDocument implements MessageListener {
public void localInsert(int index, String str) { public void localInsert(int index, String str) {
Change[] changes = insert(index, str, site); Change[] changes = insert(index, str, site);
for(Change c : changes) {
try { try {
connection.send(new ChangeMessage(c)); connection.send(new ChangeMessage(path, changes));
} catch (ConnectionException e) { } catch (ConnectionException e) {
e.printStackTrace(); // TODO: throw error e.printStackTrace(); // TODO: throw error
} }
} }
}
/** /**
* Deletes n characters from a document, starting at the specified index * Deletes n characters from a document, starting at the specified index
@ -109,18 +107,16 @@ public class SharedDocument implements MessageListener {
while(n-- > 0) { while(n-- > 0) {
// TODO: more efficient implementation (e.g. range delete in CharBag) // TODO: more efficient implementation (e.g. range delete in CharBag)
Char toRemove = charBag.get(index + 1); Char toRemove = charBag.get(index + 1);
changes[n] = new Change(path, ChangeType.REMOVE, toRemove); changes[n] = new Change(ChangeType.REMOVE, toRemove);
if(charBag.remove(toRemove) == -1) throw new IllegalStateException("Couldn't remove existing char"); if(charBag.remove(toRemove) == -1) throw new IllegalStateException("Couldn't remove existing char");
} }
for(Change c : changes) {
try { try {
connection.send(new ChangeMessage(c)); connection.send(new ChangeMessage(path, changes));
} catch (ConnectionException e) { } catch (ConnectionException e) {
e.printStackTrace(); // TODO: throw error e.printStackTrace(); // TODO: throw error
} }
} }
}
/** /**
* Inserts a remote change into the document and updates internal parameters accordingly * Inserts a remote change into the document and updates internal parameters accordingly
@ -191,14 +187,15 @@ public class SharedDocument implements MessageListener {
@Override @Override
public void onMessage(Message message) { public void onMessage(Message message) {
if(message instanceof ChangeMessage change) { if(message instanceof ChangeMessage change) {
Change c = change.change(); if(!change.documentPath().equals(path)) return;
if(!c.documentPath().equals(path)) return;
for(Change c : change.changes()) {
switch(c.type()) { switch(c.type()) {
case ADD -> remoteInsert(c.character()); case ADD -> remoteInsert(c.character());
case REMOVE -> remoteDelete(c.character()); case REMOVE -> remoteDelete(c.character());
} }
} }
} }
}
} }

View File

@ -9,7 +9,6 @@ import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.MethodSource;
import me.mrletsplay.shareclientcore.document.ArrayCharBag; import me.mrletsplay.shareclientcore.document.ArrayCharBag;
import me.mrletsplay.shareclientcore.document.BinaryTreeCharBag;
import me.mrletsplay.shareclientcore.document.Char; import me.mrletsplay.shareclientcore.document.Char;
import me.mrletsplay.shareclientcore.document.CharBag; import me.mrletsplay.shareclientcore.document.CharBag;
import me.mrletsplay.shareclientcore.document.Identifier; import me.mrletsplay.shareclientcore.document.Identifier;
@ -17,7 +16,7 @@ import me.mrletsplay.shareclientcore.document.Identifier;
public class CharBagTest { public class CharBagTest {
private static List<CharBag> getBags() { private static List<CharBag> getBags() {
return List.of(new ArrayCharBag(), new BinaryTreeCharBag()); return List.of(new ArrayCharBag()/*, new BinaryTreeCharBag() TODO implement BinaryTreeCharBag */);
} }
@ParameterizedTest @ParameterizedTest

View File

@ -42,8 +42,12 @@ public class MessageTest {
@Test @Test
public void testChangeMessage() throws IOException { public void testChangeMessage() throws IOException {
Change change = new Change("Project:src/test.txt", ChangeType.ADD, new Char(new Identifier[] {new Identifier(0, 1), new Identifier(1, 3)}, 42, (byte) 'e')); Change[] changes = {
ChangeMessage m = new ChangeMessage(change); new Change(ChangeType.ADD, new Char(new Identifier[] {new Identifier(0, 1), new Identifier(1, 3)}, 42, (byte) 'e')),
new Change(ChangeType.REMOVE, new Char(new Identifier[] {new Identifier(2, 1), new Identifier(1, 4)}, 314, (byte) 'q')),
};
ChangeMessage m = new ChangeMessage("Project:src/test.txt", changes);
assertEquals(deserialize(serialize(m)), m, "Deserialized message must equal message"); assertEquals(deserialize(serialize(m)), m, "Deserialized message must equal message");
} }