Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
No results found
Show changes
Commits on Source (51)
File added
# Tugas Besar Sistem Terpadu
## Peer2Peer Collaborative Editing
#### Petunjuk Penggunaan Program
1. Compile
```
cd src && javac Char.java Controller.java CRDT.java Identifier.java Main.java TextEditor.java
```
2. Jalankan program
```
java Main
```
#### Pembagian Tugas
- Rahmat Nur I. Santosa (13516009): 33% - CRDT, Controller, Identifier, Laporan
- Nira Rizki Ramadhani (13516018): 33% - Char, CRDT, Controller, Laporan
- Seldi Kurnia Trihardja (13516042): 33% - TextEditor, VersionVector, Messenger, Laporan
## Laporan
### Desain Program
Program terdiri dari beberapa struktur utama yaitu Controller, CRDT, Editor, Messenger, serta Version Vector. CRDT atau Conflict-Free Replicated Data Type berfungsi untuk menjaga konsistensi dari data program. CRDT terhubung dengan Version Vector, yaitu suatu struktur data yang mencatat versi dari suatu operasi agar kausalitasnya terjaga. Kemudian terdapat Editor yang merupakan komponen text editor yang dibuat menggunakan Java Swing. Messenger berfungsi untuk menghubungkan suatu peer dengan peer yang lainnya yang berfungsi sebagai server sekaligus client. Terakhir, terdapat controller berfungsi untuk mengintegrasi semua struktur utama dari program.
Struktur program digambarkan oleh bagan di bawah ini.
![Desain Program](img/desain.png)
### CRDT
Dalam program yang kami buat, CRDT adalah sebuah kelas yang memiliki struktur data:
dengan list of Character yang telah diberikan Identifier untuk posisinya, dimana Identifier terdiri dari coordinate dan site id. CRDT berfungsi menangani insert dan delete baik dengan cara local maupun remote serta memastikan bahwa operasi tersebut bersifat komutatif dan idempoten.
Struktur data dari CRDT pada program adalah sebagai berikut:
```
private int levelSize;
private String siteId;
private List<Char> struct;
private Controller controller;
```
Dengan deskripsi: LevelSize adalah jumlah node maksimum dari implementasi tree struktur pada penyimpanan karakter pada suatu level. SiteId menyimpan Id dari suatu peer. Struct menyimpan List of Character, atau keseluruhan karakter yang dimasukkan. Sedangkan Controller menghubungkan kelas dengan controller.
### Version Vector
Version Vector digunakan pada sebuah peer dan berfungsi untuk menampung counter dari semua peer yang berkomunikasi. Pada program yang kami buat, Version Vector berisi atribut site id sebagai identitas peer dan counter yang bersesuaian dengan site id peer.
Struktur data dari Version Vector adalah sebagai berikut:
```
private int count;
private String siteId;
```
Dengan deskripsi: count adalah jumlah operasi yang sudah dilakukan oleh peer tersebut. SiteId adalah ID dari peer yang menyimpan version vector.
### Deletion Buffer
DeletionBuffer digunakan sebagai tempat penyimpanan command delete yang dilakukan oleh peer lainnya. Command ini akan disimpan di dalam buffer sampai menunggu version vector yang diterima dari operasi peer lain sesuai dengan yang berada di dalam buffer. Proses ini dibuat untuk menjaga kausalitas operasi pada setiap peer.
Struktur data yang digunakan untuk deletion buffer berupa:
```
List<Char> deletionBuffer;
```
List of Character menyimpan objek Character yang harus di delete oleh setiap peer, dimana pada setiap objek karakter terdapat version vector yang memeriksa kausalitas program.
### Analisis Program
Program yang kami buat sudah mengikuti konsep CRDT, yang dapat memastikan perubahan pada teks bersifat komutatif dan idempoten.
Untuk mengembangkan program agar menjadi lebih baik lagi, dapat dibuat suatu server awal yang bertujuan untuk menghubungkan jaringan antara port-port. Hal ini dapat dibuat untuk memudahkan penghubungan awal peer. Selain itu, akan lebih efisien jika dapat diimplementasikan pada platform berbasis web dengan menggunakan library-library nya sehingga maintenance program lebih mudah.
### Test Case
#### Test Case 1. Insert Character
![tc1](img/tc1-insert.PNG)
#### Test Case 2. Delete Character
![tc2](img/tc2-delete.PNG)
#### Test Case 3. Insert Middle
![tc3](img/tc3-insertmiddle.PNG)
#### Test Case 4. Delete Middle
![tc4](img/tc4-deletemiddle.PNG)
img/desain.png

89.4 KiB

img/tc1-insert.PNG

19.6 KiB

img/tc2-delete.PNG

19.1 KiB

img/tc3-insertmiddle.PNG

21.9 KiB

img/tc4-deletemiddle.PNG

22.4 KiB

build: src/Main.java
javac Char.java Controller.java CRDT.java Identifier.java Main.java TextEditor.java
\ No newline at end of file
File added
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class CRDT {
private int levelSize;
private String siteId;
private List<Char> struct;
private Controller controller;
public String getSiteId() {
return siteId;
}
public List<Char> getStruct() {
return struct;
}
public void setStruct(List<Char> struct) {
this.struct = struct;
}
public void setSiteId(String siteId) {
this.siteId = siteId;
}
public CRDT(String siteId) {
this.siteId = siteId;
this.struct = new ArrayList<Char>();
this.levelSize = 32;
}
public void localInsert(char value, int index) {
Char charObj = this.generateChar(value, index);
this.struct.add(index, charObj);
System.out.println("Insert (Position " + charObj.positionToString() + ")");
printString();
}
public void remoteInsert(Char _char) {
int idx = this.findInsertIndex(_char);
this.struct.add(idx, _char);
this.controller.textEditorInsert(_char.getValue(), idx);
}
public int findInsertIndex(Char _char) {
int idBefore = 0;
int idAfter = this.struct.size()-1;
if (_char.compareTo(this.struct.get(idAfter)) > 0) {
return this.struct.size();
} else if (this.struct.size() == 0 || _char.compareTo(this.struct.get(idBefore)) < 1) {
return idBefore;
}
while ((idBefore + 1) < idAfter) {
int middle = (int) Math.floor(idBefore + (idAfter-idBefore)/2);
int compareMid = _char.compareTo(this.struct.get(middle));
if (compareMid < 0) {
idAfter = middle;
} else
if (compareMid > 0) {
idBefore = middle;
} else {
return middle;
}
}
int compareBefore = _char.compareTo(this.struct.get(idBefore));
if (compareBefore == 0) {
return idBefore;
} else {
return idAfter;
}
}
public Char generateChar(char value, int index) {
List<Identifier> posBefore =
(index - 1 >= 0) && (index - 1 < this.struct.size()) ?
this.struct.get(index - 1).getPosition() :
new ArrayList<Identifier>();
List<Identifier> posAfter =
(index < this.struct.size()) && (index >= 0) ?
this.struct.get(index).getPosition() :
new ArrayList<Identifier>();
List<Identifier> newPos = new ArrayList<Identifier>();
this.generatePosBetween(posBefore, posAfter, newPos, 0);
return new Char(value, this.siteId, newPos);
}
public void generatePosBetween(List<Identifier> posBefore, List<Identifier> posAfter, List<Identifier> newPos, int level) {
int levelSize = (int) Math.pow(2, level)*this.levelSize;
Identifier idBefore =
(posBefore.size() > 0) ?
posBefore.get(0) :
new Identifier(0, this.siteId);
Identifier idAfter =
(posAfter.size() > 0) ?
posAfter.get(0) :
new Identifier(levelSize, this.siteId);
if (idAfter.getCoord() - idBefore.getCoord() > 1)
{
int newDigit = this.generateIdBetween(idBefore.getCoord(), idAfter.getCoord(), '+');
Identifier newIdentifier = new Identifier(newDigit, this.siteId);
newPos.add(newIdentifier);
}
else if (idAfter.getCoord() - idBefore.getCoord() == 1)
{
newPos.add(idBefore);
posBefore = (posBefore.size() > 0) ?
posBefore.subList(1, posBefore.size()) :
posBefore;
generatePosBetween(posBefore, new ArrayList<Identifier>(), newPos, level + 1);
}
else if (idBefore.getCoord() == idAfter.getCoord())
{
if (idBefore.getSiteId().compareTo(idAfter.getSiteId()) < 0) {
newPos.add(idBefore);
posBefore = (posBefore.size() > 0) ? posBefore.subList(1, posBefore.size()) : posBefore;
generatePosBetween(posBefore, new ArrayList<Identifier>(), newPos, level+1);
}
else if (idBefore.getSiteId().compareTo(idAfter.getSiteId()) == 0) {
newPos.add(idBefore);
posBefore = (posBefore.size() > 0) ? posBefore.subList(1, posBefore.size()) : posBefore;
posAfter = (posAfter.size() > 0) ? posAfter.subList(1, posAfter.size()) : posAfter;
generatePosBetween(posBefore, posAfter, newPos, level + 1);
}
else {
throw new Error("Fix Position Sorting");
}
}
}
public int generateIdBetween(int id1, int id2, char boundaryStrategy) {
int boundary = 10;
if ((id2 - id1) < boundary) {
id1 = id1 + 1;
} else {
id1 = (boundaryStrategy == '-') ? id2 - boundary : id1 + 1;
id2 = (boundaryStrategy == '-') ? id2 : id1 + boundary;
}
int id = (int) Math.floor(Math.random()*(id2 - id1)) + id1;
return id;
}
public void localDelete(int index) {
Char delChar = this.struct.remove(index - 1);
System.out.println("Delete (Position " + delChar.positionToString() + ")");
printString();
}
public void remoteDelete(Char _char) {
int pos = this.findPosition(_char);
if (pos == -1) {
System.out.println("No match index");
return;
}
this.struct.remove(pos);
this.controller.textEditorDelete(pos);
printString();
}
public int findPosition(Char _char) {
for (int i = 0; i<this.struct.size(); i++) {
if (_char.compareTo(this.struct.get(i)) == 0) {
return i;
}
}
return -1;
}
public void printString() {
for (Char c : this.struct) {
System.out.print(c.getValue());
}
System.out.println();
}
}
\ No newline at end of file
File added
import java.util.List;
public class Char implements Comparable<Char> {
private char value;
private String siteId;
private List<Identifier> position;
private int count;
public Char(char value, String siteId, List<Identifier> position) {
this.value = value;
this.position = position;
this.siteId = siteId;
this.count = 0;
}
public char getValue() {
return this.value;
}
public String getSiteID() {
return this.siteId;
}
public List<Identifier> getPosition() {
return this.position;
}
public void setValue(char value) {
this.value = value;
}
public void setSiteID(String siteId) {
this.siteId = siteId;
}
public void setPosition(List<Identifier> position) {
this.position = position;
}
public int getCount(){
return this.count;
}
public void setCount(int i){
this.count = i;
}
public String positionToString() {
String pos = "";
for (int i = 0; i < position.size(); i++) {
pos = pos + position.get(i).getCoord();
if (i < position.size() - 1) {
pos = pos + ".";
}
// System.out.print(position.get(i).getCoord() + ".");
}
return pos;
}
public int compareTo(Char other) {
List<Identifier> _position = this.position;
List<Identifier> _other = other.position;
int thisSize = _position.size();
int otherSize = _other.size();
int minSize = Math.min(thisSize, otherSize);
for (int i=0; i < minSize; i++) {
Identifier thisIdx = _position.get(i);
Identifier otherIdx = _other.get(i);
if (thisIdx.compareTo(otherIdx) != 0) {
return thisIdx.compareTo(otherIdx);
}
}
if (thisSize < otherSize) {
return -1;
} else if (thisSize > otherSize) {
return 1;
} else {
return 0;
}
}
}
// Java implementation for a client
// Save file as Client.java
import java.io.*;
import java.net.*;
import java.util.Scanner;
// Client class
public class Client
{
public static void main(String[] args) throws IOException
{
try
{
Scanner scn = new Scanner(System.in);
// getting localhost ip
InetAddress ip = InetAddress.getByName("localhost");
// establish the connection with server port 5056
System.out.println(" Enter port number to connect tos");
int PortToConnect = Integer.parseInt(scn.nextLine());
Socket s = new Socket(ip, PortToConnect);
// obtaining input and out streams
DataInputStream dis = new DataInputStream(s.getInputStream());
DataOutputStream dos = new DataOutputStream(s.getOutputStream());
// the following loop performs the exchange of
// information between client and client handler
while (true)
{
System.out.println(dis.readUTF());
String tosend = scn.nextLine();
dos.writeUTF(tosend);
// If client sends exit,close this connection
// and then break from the while loop
if(tosend.equals("Exit"))
{
System.out.println("Closing this connection : " + s);
s.close();
System.out.println("Connection closed");
break;
}
// printing date or time as requested by client
String received = dis.readUTF();
System.out.println(received);
}
// closing resources
scn.close();
dis.close();
dos.close();
}catch(Exception e){
e.printStackTrace();
}
}
}
\ No newline at end of file
File added
import java.util.ArrayList;
import java.util.List;
public class Controller {
TextEditor textEditor;
CRDT crdt;
List<VersionVector> versionList;
List<Char> deletionBuffer;
int count = 0;
public Controller() {
textEditor = new TextEditor(this);
crdt = new CRDT("test");
versionList = new ArrayList<VersionVector>();
deletionBuffer = new ArrayList<Char>();
}
public void onInsert(char value, int index) {
//text editor detected an insert op
crdt.localInsert(value, index);
for (VersionVector v : this.versionList) {
if(v.getSiteId()==crdt.getSiteId()){
v.increment();
}
}
//Broadcast insert
}
public void onDelete(int index) {
//text editor detected a delete op
crdt.localDelete(index);
for (VersionVector v : this.versionList) {
if(v.getSiteId()==crdt.getSiteId()){
v.increment();
}
}
//Broadcast delete op + (cdrt.siteId,counter)
}
public void textEditorInsert(char value, int index) {
textEditor.getTextArea().insert(String.valueOf(value), index);
int cursorPos = textEditor.getCursorPos();
if (index <= cursorPos) {
textEditor.getTextArea().setCaretPosition(cursorPos + 1);
}
}
public void textEditorDelete(int index) {
System.out.println("Deletion at index = " + index);
textEditor.getTextArea().replaceRange("", index, index + 1);
int cursorPos = textEditor.getCursorPos();
if (index <= cursorPos) {
System.out.println("cursorPos: " + cursorPos);
textEditor.getTextArea().setCaretPosition(0);
System.out.println("cursorPos after update: " + textEditor.getCursorPos());
}
}
public void handleRemoteInsert(Char _char) {
this.crdt.remoteInsert(_char);
for (VersionVector v : this.versionList) {
if (v.getSiteId()==_char.getSiteID()){
v.increment();
}
}
}
public void handleRemoteDelete(Char _char) {
this.crdt.remoteDelete(_char);
for (VersionVector v : this.versionList) {
if (v.getSiteId()==_char.getSiteID()){
v.increment();
}
}
}
//Get broadcast message
public void getInsertOp(Char op){
for (VersionVector v : this.versionList) {
if(v.getSiteId()==op.getSiteID()){
v.setCount(v.getCount()+1);
break;
}
}
}
public void getDeleteOp(Char op){
for (VersionVector v : this.versionList) {
if(v.getSiteId()==op.getSiteID()){
if(v.getCount()==op.getCount()){
v.setCount(v.getCount()+1);
break;
}
else{
//Put To deletionBuffer
break;
}
}
}
}
public void start() {
textEditor.show();
}
public boolean checkVersion(Char op){
for (VersionVector v : this.versionList) {
if(v.getSiteId()==op.getSiteID()){
if(v.getCount()==op.getCount()){
return true;
}
else{ return false; }
}
}
return false;
}
}
\ No newline at end of file
File added
public class Identifier implements Comparable<Identifier> {
private int coord;
private String siteId;
public int getCoord() {
return coord;
}
public String getSiteId() {
return siteId;
}
public void setSiteId(String siteId) {
this.siteId = siteId;
}
public void setCoord(int coord) {
this.coord = coord;
}
public Identifier(int _coord, String _siteId) {
coord = _coord;
siteId = _siteId;
}
public int compareTo(Identifier other) {
if (this.coord < other.coord) {
return -1;
} else if (this.coord > other.coord) {
return 1;
} else {
return this.siteId.compareTo(other.siteId);
}
}
}
\ No newline at end of file
File added
public class Main {
public static void main(String[] args) {
Controller controller = new Controller();
controller.start();
}
}
\ No newline at end of file
// Java implementation of Server side
// It contains two classes : Server and ClientHandler
// Save file as Server.java
import java.io.*;
import java.text.*;
import java.util.*;
import java.net.*;
// Server class
public class Server
{
public static void main(String[] args) throws IOException
{
Scanner scn = new Scanner(System.in);
// server is listening on port 5056
System.out.println(" Enter this server port number ");
int ServerPort = Integer.parseInt(scn.nextLine());
ServerSocket ss = new ServerSocket(ServerPort);
// running infinite loop for getting
// client request
while (true)
{
Socket s = null;
try
{
// socket object to receive incoming client requests
s = ss.accept();
System.out.println("A new client is connected : " + s);
// obtaining input and out streams
DataInputStream dis = new DataInputStream(s.getInputStream());
DataOutputStream dos = new DataOutputStream(s.getOutputStream());
System.out.println("Assigning new thread for this client");
// create a new thread object
Thread t = new ClientHandler(s, dis, dos);
// Invoking the start() method
t.start();
}
catch (Exception e){
s.close();
e.printStackTrace();
}
}
}
}
// ClientHandler class
class ClientHandler extends Thread
{
DateFormat fordate = new SimpleDateFormat("yyyy/MM/dd");
DateFormat fortime = new SimpleDateFormat("hh:mm:ss");
final DataInputStream dis;
final DataOutputStream dos;
final Socket s;
// Constructor
public ClientHandler(Socket s, DataInputStream dis, DataOutputStream dos)
{
this.s = s;
this.dis = dis;
this.dos = dos;
}
@Override
public void run()
{
String received;
String toreturn;
while (true)
{
try {
// Ask user what he wants
dos.writeUTF("What do you want?[Date | Time]..\n"+
"Type Exit to terminate connection.");
// receive the answer from client
received = dis.readUTF();
if(received.equals("Exit"))
{
System.out.println("Client " + this.s + " sends exit...");
System.out.println("Closing this connection.");
this.s.close();
System.out.println("Connection closed");
break;
}
// creating Date object
Date date = new Date();
System.out.println("Client asks for " + received);
// write on output stream based on the
// answer from the client
switch (received) {
case "Date" :
toreturn = fordate.format(date);
dos.writeUTF(toreturn);
break;
case "Time" :
toreturn = fortime.format(date);
dos.writeUTF(toreturn);
break;
default:
dos.writeUTF("Invalid input");
break;
}
} catch (IOException e) {
e.printStackTrace();
}
}
try
{
// closing resources
this.dis.close();
this.dos.close();
}catch(IOException e){
e.printStackTrace();
}
}
}
\ No newline at end of file