resources) {
+ if (resources.size() <= 0) {
+ System.out.println("Resource can not be download !");
+ return;
+ }
+ if (resources.size() > 1) {
+ System.out.println("There are more than 1 resource with identical name:");
+ System.out.println("File List: ");
+ for (Resource r : resources.values()) {
+ System.out.println(r);
+ }
+ System.out.println("Please specify the GUID of the resource: ");
+ UUID GUID = null;
+ try {
+ GUID = UUID.fromString(sc.nextLine());
+ } catch (IllegalArgumentException e) {
+ System.out.println("UUID Error");
+ return;
+ }
+ if (GUID == null) {
+ System.out.println("UUID Error");
+ return;
+ }
+ Resource resource = resources.get(GUID);
+ if (resource == null) {
+ System.out.println("Input GUID not in DHRT");
+ return;
+ }
+ Peer p = (Peer) resource.possessedBy.values().stream().sorted().toArray()[0];
+ P2P_download(p, resource);
+ } else {
+ Resource resource = (Resource) resources.values().toArray()[0];
+ if (resource == null)
+ return;
+ Peer p = (Peer) resource.possessedBy.values().stream().sorted().toArray()[0];
+ P2P_download(p, resource);
+ }
+ }
+
+ private static void P2P_download(Peer p, Resource resource) {
+ Integer port = p.getP2P_port();
+ String IP = p.getIP();
+ Registry p2pRegistry = null;
+ File file = null;
+ try {
+ p2pRegistry = LocateRegistry.getRegistry(IP, port);
+ System.out.println("Try to connect: "+ p.getGUID());
+ P2P_FileRegistry p2PFileRegistry = (P2P_FileRegistry) p2pRegistry.lookup("p2PFileRegistry");
+ System.out.println("Connected.");
+ System.out.println("Downloading FROM: " + p.getGUID());
+ file = p2PFileRegistry.download(resource.getGUID());
+ } catch (RemoteException e) {
+ System.out.println("RemoteException");
+ try {
+ DHRT = syncingRegistry.syncUHRT();
+ download(resource.getName(), download_retry_count);
+ return;
+ } catch (RemoteException ex) {
+ ex.printStackTrace();
+ }
+// e.printStackTrace();
+ } catch (NotBoundException e) {
+ e.printStackTrace();
+ }
+ if (file != null) {
+
+ System.out.println("File downloaded !");
+ System.out.println("File length : " + file.length());
+
+ try {
+ FileUtil.writeToStream(file, new FileOutputStream("download/" + peer.getGUID() + "_FROM_" + p.getGUID() + "_" + resource.getName()));
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ }
+
+ } else {
+ System.out.println("P2P_download File Error");
+ return;
+ }
+
+ }
+//
+// private static void P2P_download(Peer p, Resource resource, Integer retry_count) {
+// retry_count += 1;
+// Integer port = p.getP2P_port();
+// String IP = p.getIP();
+// Registry p2pRegistry = null;
+// File file = null;
+// try {
+// p2pRegistry = LocateRegistry.getRegistry(IP, port);
+// P2P_FileRegistry p2PFileRegistry = (P2P_FileRegistry) p2pRegistry.lookup("p2PFileRegistry");
+// file = p2PFileRegistry.download(resource.getGUID());
+// } catch (RemoteException e) {
+// System.out.println("Retry Failed !");
+// try {
+// DHRT = syncingRegistry.syncUHRT();
+// System.out.println("Retrying " + retry_count);
+// if (retry_count <= 5) {
+// try {
+// Thread.sleep(1000);
+// } catch (InterruptedException ex) {
+// ex.printStackTrace();
+// }
+// P2P_download(p, resource, retry_count);
+// }
+// return;
+// } catch (RemoteException ex) {
+// ex.printStackTrace();
+// }
+//// e.printStackTrace();
+// } catch (NotBoundException e) {
+// e.printStackTrace();
+// }
+// if (file != null) {
+// System.out.println("File downloaded !");
+// System.out.println("File length : " + file.length());
+//
+// try {
+// FileUtil.writeToStream(file, new FileOutputStream("download/" + peer.getGUID() + "_FROM_" + p.getGUID() + "_" + resource.getName()));
+// } catch (FileNotFoundException e) {
+// e.printStackTrace();
+// }
+//
+// } else {
+// System.out.println("P2P_download File Error");
+// return;
+// }
+//
+// }
+
+ public static void exit() {
+ System.exit(0);
+ }
+
+ public static void recover() {
+ init_peer();
+ }
+
+ public static void recover(int retry_count) {
+ retry_count += 1;
+ System.out.println("Try to recover......." + retry_count);
+ if (retry_count > 10) {
+ System.out.println();
+ System.out.println("++++++++++++++++++++++++++++++++++++++++++++++++");
+ System.out.println("================== CLIENT ====================");
+ System.out.println("================== SHUTDOWN ====================");
+ System.out.println("++++++++++++++++++++++++++++++++++++++++++++++++");
+ System.out.println();
+ exit();
+ }
+ service.interrupt();
+ service = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ recover();
+ }
+ });
+ service.start();
+ }
+}
diff --git a/src/main/java/com/echo/p2p_project/client/gui/ClientIndexController.java b/src/main/java/com/echo/p2p_project/client/gui/ClientIndexController.java
new file mode 100644
index 0000000..4c871c1
--- /dev/null
+++ b/src/main/java/com/echo/p2p_project/client/gui/ClientIndexController.java
@@ -0,0 +1,11 @@
+package com.echo.p2p_project.client.gui;
+
+/**
+ * @Author: WangYuyang
+ * @Date: 2021/10/23-17:11
+ * @Project: P2P_Project
+ * @Package: com.echo.p2p_project.client.gui
+ * @Description:
+ **/
+public class ClientIndexController {
+}
diff --git a/src/main/java/com/echo/p2p_project/client/gui/ClientIndexView.java b/src/main/java/com/echo/p2p_project/client/gui/ClientIndexView.java
new file mode 100644
index 0000000..b2e27f0
--- /dev/null
+++ b/src/main/java/com/echo/p2p_project/client/gui/ClientIndexView.java
@@ -0,0 +1,34 @@
+package com.echo.p2p_project.client.gui;
+
+import cn.hutool.Hutool;
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.io.resource.Resource;
+import cn.hutool.core.io.resource.ResourceUtil;
+import javafx.application.Application;
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Scene;
+import javafx.stage.Stage;
+
+import java.io.IOException;
+
+/**
+ * @Author: WangYuyang
+ * @Date: 2021/10/23-17:11
+ * @Project: P2P_Project
+ * @Package: com.echo.p2p_project.client.gui
+ * @Description:
+ **/
+public class ClientIndexView extends Application {
+ @Override
+ public void start(Stage stage) throws IOException {
+ FXMLLoader fxmlLoader = new FXMLLoader(ResourceUtil.getResource("gui/client_index.fxml"));
+ Scene scene = new Scene(fxmlLoader.load(), 320, 240);
+ stage.setTitle("Client!");
+ stage.setScene(scene);
+ stage.show();
+ }
+
+ public static void main(String[] args) {
+ launch();
+ }
+}
diff --git a/src/main/java/com/echo/p2p_project/client/gui/HelloApplication.java b/src/main/java/com/echo/p2p_project/client/gui/HelloApplication.java
new file mode 100644
index 0000000..494d9d3
--- /dev/null
+++ b/src/main/java/com/echo/p2p_project/client/gui/HelloApplication.java
@@ -0,0 +1,23 @@
+package com.echo.p2p_project.client.gui;
+
+import javafx.application.Application;
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Scene;
+import javafx.stage.Stage;
+
+import java.io.IOException;
+
+public class HelloApplication extends Application {
+ @Override
+ public void start(Stage stage) throws IOException {
+ FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("hello-view.fxml"));
+ Scene scene = new Scene(fxmlLoader.load(), 320, 240);
+ stage.setTitle("Hello!");
+ stage.setScene(scene);
+ stage.show();
+ }
+
+ public static void main(String[] args) {
+ launch();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/echo/p2p_project/client/gui/HelloController.java b/src/main/java/com/echo/p2p_project/client/gui/HelloController.java
new file mode 100644
index 0000000..e4428a7
--- /dev/null
+++ b/src/main/java/com/echo/p2p_project/client/gui/HelloController.java
@@ -0,0 +1,14 @@
+package com.echo.p2p_project.client.gui;
+
+import javafx.fxml.FXML;
+import javafx.scene.control.Label;
+
+public class HelloController {
+ @FXML
+ private Label welcomeText;
+
+ @FXML
+ protected void onHelloButtonClick() {
+ welcomeText.setText("Welcome to JavaFX Application!");
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/echo/p2p_project/client/interfaces/P2P_FileRegistry.java b/src/main/java/com/echo/p2p_project/client/interfaces/P2P_FileRegistry.java
new file mode 100644
index 0000000..3022ea3
--- /dev/null
+++ b/src/main/java/com/echo/p2p_project/client/interfaces/P2P_FileRegistry.java
@@ -0,0 +1,17 @@
+package com.echo.p2p_project.client.interfaces;
+
+import java.io.File;
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+import java.util.UUID;
+
+/**
+ * @Author: WangYuyang
+ * @Date: 2021/10/20-21:07
+ * @Project: P2P_Project
+ * @Package: com.echo.p2p_project.client.interfaces
+ * @Description:
+ **/
+public interface P2P_FileRegistry extends Remote {
+ File download(UUID resID) throws RemoteException;
+}
diff --git a/src/main/java/com/echo/p2p_project/client/model/CHeartBeat.java b/src/main/java/com/echo/p2p_project/client/model/CHeartBeat.java
new file mode 100644
index 0000000..4b27b26
--- /dev/null
+++ b/src/main/java/com/echo/p2p_project/client/model/CHeartBeat.java
@@ -0,0 +1,61 @@
+package com.echo.p2p_project.client.model;
+
+import com.echo.p2p_project.client.ClientMain;
+import com.echo.p2p_project.server.interfaces.HeartBeatRegistry;
+
+import java.rmi.RemoteException;
+import java.util.UUID;
+
+/**
+ * @Author: WangYuyang
+ * @Date: 2021/10/20-18:13
+ * @Project: P2P_Project
+ * @Package: com.echo.p2p_project.client.model
+ * @Description:
+ **/
+public class CHeartBeat {
+ public static Thread heart;
+ private static Boolean running = true;
+
+
+ public static void StartHeart(HeartBeatRegistry heartBeatRegistry, UUID GUID) {
+ running = true;
+ heart = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ System.out.println("Start Heart Beat");
+ while (running) {
+ try {
+ Boolean status = heartBeatRegistry.heartBeat(GUID);
+// System.out.println(status);
+ ClientMain.retry_times = 0;
+ } catch (RemoteException e) {
+ running = false;
+ System.out.println("Main Server DOWN !");
+ ClientMain.retry_times += 1;
+ ClientMain.recover(ClientMain.retry_times);
+// ClientMain.exit();
+// System.out.println("Trying to recover...");
+// downCount+=1;
+// System.out.println("DOWN COUNT: " + downCount);
+// if(downCount > 5) {
+// System.out.println("DOWN COUNT > 5: Exiting..");
+//
+// }
+
+ }
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ });
+ heart.start();
+ }
+
+ public static void endHeart() {
+ running = false;
+ }
+}
diff --git a/src/main/java/com/echo/p2p_project/client/model/P2PFileImpl.java b/src/main/java/com/echo/p2p_project/client/model/P2PFileImpl.java
new file mode 100644
index 0000000..33ac074
--- /dev/null
+++ b/src/main/java/com/echo/p2p_project/client/model/P2PFileImpl.java
@@ -0,0 +1,50 @@
+package com.echo.p2p_project.client.model;
+
+import com.echo.p2p_project.client.ClientMain;
+import com.echo.p2p_project.client.interfaces.P2P_FileRegistry;
+import com.echo.p2p_project.u_model.Resource;
+
+import java.io.File;
+import java.rmi.RemoteException;
+import java.rmi.server.RMISocketFactory;
+import java.rmi.server.UnicastRemoteObject;
+import java.util.UUID;
+
+/**
+ * @Author: WangYuyang
+ * @Date: 2021/10/20-21:08
+ * @Project: P2P_Project
+ * @Package: com.echo.p2p_project.client.model
+ * @Description:
+ **/
+public class P2PFileImpl extends UnicastRemoteObject implements P2P_FileRegistry {
+ /**
+ * Creates and exports a new UnicastRemoteObject object using an
+ * anonymous port.
+ *
+ * The object is exported with a server socket
+ * created using the {@link RMISocketFactory} class.
+ *
+ * @throws RemoteException if failed to export object
+ * @since JDK1.1
+ */
+ public P2PFileImpl() throws RemoteException {
+ super();
+ }
+
+
+ @Override
+ public File download(UUID resID) throws RemoteException{
+ Resource resource = ClientMain.DHRT.get(resID);
+ if(resource == null){
+ System.out.println("Resource Not in local DHRT.");
+ return null;
+ }
+ File file = new File("res/" + resource.getName());
+ if (file==null) {
+ System.out.println("Resource Not in File System.");
+ return null;
+ }
+ return file;
+ }
+}
diff --git a/src/main/java/com/echo/p2p_project/server/ServerMain.java b/src/main/java/com/echo/p2p_project/server/ServerMain.java
new file mode 100644
index 0000000..9c022b5
--- /dev/null
+++ b/src/main/java/com/echo/p2p_project/server/ServerMain.java
@@ -0,0 +1,108 @@
+package com.echo.p2p_project.server;
+
+import com.echo.p2p_project.Util;
+import com.echo.p2p_project.server.interfaces.ConstructRegistry;
+import com.echo.p2p_project.server.interfaces.HeartBeatRegistry;
+import com.echo.p2p_project.server.interfaces.HelloRegistryFacade;
+import com.echo.p2p_project.server.interfaces.SyncingRegistry;
+import com.echo.p2p_project.server.model.*;
+import com.echo.p2p_project.u_model.Peer;
+import com.echo.p2p_project.u_model.Resource;
+import com.sun.javafx.collections.ObservableMapWrapper;
+import javafx.collections.ObservableMap;
+
+import java.io.PrintStream;
+import java.rmi.RemoteException;
+import java.rmi.registry.LocateRegistry;
+import java.rmi.registry.Registry;
+import java.util.*;
+
+/**
+ * @Author: WangYuyang
+ * @Date: 2021/10/19-15:22
+ * @Project: P2P_Project
+ * @Package: com.echo.p2p_project.server
+ * @Description:
+ **/
+public class ServerMain{
+ public static ObservableMapWrapper UHPT = new ObservableMapWrapper<>(new LinkedHashMap<>());
+ public static ObservableMapWrapper UHRT = new ObservableMapWrapper<>(new LinkedHashMap<>());
+ private static Registry registry;
+ private static Boolean HasStarted = false;
+ private static Thread service;
+
+ public static void main(String[] args) {
+ service = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ init();
+ }
+ });
+ service.start();
+ Scanner sc = new Scanner(System.in);
+ System.out.println("");
+ System.out.print(">>> ");
+ while (sc.hasNextLine()) {
+ String line = sc.nextLine();
+ switch (line) {
+ case "\n":
+ System.out.print(">>> ");
+ break;
+ case "i":
+ System.out.println("UHPT: " + UHPT);
+ System.out.println("UHPT Size: " + UHPT.size());
+ System.out.println("UHRT: " + UHRT);
+ System.out.println("UHRT Size: " + UHRT.size());
+ System.out.println("registry: " + registry);
+ break;
+ }
+ System.out.print(">>> ");
+ }
+
+ }
+
+ public static void init() {
+ try {
+ // Start Registry, Port: 1099
+ registry = LocateRegistry.createRegistry(Util.RMI_PORT);
+ reg_services();
+
+
+ System.out.println("======= RMI Start Up! ============");
+ System.out.println(registry.toString());
+ System.out.println("Registered: " + Arrays.toString(registry.list()));
+ for (String s : registry.list()) {
+ System.out.println("Registered: " + s);
+ }
+ System.out.println("============ WatchDog ============");
+ if (!HasStarted)
+ WatchDog.startWatchDog();
+ System.out.println("======= Start Up Finished ========");
+ HasStarted = true;
+
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private static void reg_services() {
+ try {
+ HelloRegistryFacade hello = new HelloRegistryFacadeImpl();
+ ConstructRegistry constructRegistry = new ConstructImpl();
+ HeartBeatRegistry heartBeatRegistry = new HeartBeatImpl();
+ SyncingRegistry syncingRegistry = new SyncingImpl();
+
+ registry.rebind("HelloRegistry", hello);
+ registry.rebind("constructRegistry", constructRegistry);
+ registry.rebind("heatBeatRegistry", heartBeatRegistry);
+ registry.rebind("syncingRegistry", syncingRegistry);
+
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public static void exit(){
+ service.interrupt();
+ }
+}
diff --git a/src/main/java/com/echo/p2p_project/server/gui/ServerIndexApplication.java b/src/main/java/com/echo/p2p_project/server/gui/ServerIndexApplication.java
new file mode 100644
index 0000000..a222d3c
--- /dev/null
+++ b/src/main/java/com/echo/p2p_project/server/gui/ServerIndexApplication.java
@@ -0,0 +1,78 @@
+package com.echo.p2p_project.server.gui;
+
+import cn.hutool.core.io.resource.ResourceUtil;
+import com.echo.p2p_project.server.ServerMain;
+import javafx.application.Application;
+import javafx.collections.MapChangeListener;
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Scene;
+import javafx.scene.control.TableView;
+import javafx.scene.control.TextArea;
+import javafx.scene.control.TreeView;
+import javafx.stage.Stage;
+
+import java.io.IOException;
+
+/**
+ * @Author: WangYuyang
+ * @Date: 2021/10/23-17:20
+ * @Project: P2P_Project
+ * @Package: com.echo.p2p_project.server.gui
+ * @Description:
+ **/
+public class ServerIndexApplication extends Application {
+ private static Thread server_thread;
+ private static Thread log_thread;
+ private static Boolean is_started = false;
+// public static ObservableMap O_UHPT;
+// public static ObservableMap O_UHRT;
+
+ public static void main(String[] args) {
+ launch();
+ }
+
+
+ public static void init_server() {
+ if (is_started == false) {
+ is_started = true;
+ server_thread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ ServerMain.main(new String[]{});
+ }
+ });
+ server_thread.start();
+ } else {
+ System.out.println("CAN NOT START");
+ }
+ }
+
+ public static void stopServer() {
+ if (is_started) {
+ ServerMain.exit();
+ server_thread.interrupt();
+ is_started = false;
+ System.exit(0);
+ }
+ }
+
+ public static void start_log(TextArea logField, TableView uhpt_table, TableView uhrt_table, TreeView uhrt_tree) {
+// log_thread = new Thread(new Runnable() {
+// @Override
+// public void run() {
+//
+// }
+// });
+// log_thread.start();
+
+ }
+
+ @Override
+ public void start(Stage stage) throws IOException {
+ FXMLLoader fxmlLoader = new FXMLLoader(ResourceUtil.getResource("gui/server_index.fxml"));
+ Scene scene = new Scene(fxmlLoader.load());
+ stage.setTitle("Server");
+ stage.setScene(scene);
+ stage.show();
+ }
+}
diff --git a/src/main/java/com/echo/p2p_project/server/gui/ServerIndexController.java b/src/main/java/com/echo/p2p_project/server/gui/ServerIndexController.java
new file mode 100644
index 0000000..3919a81
--- /dev/null
+++ b/src/main/java/com/echo/p2p_project/server/gui/ServerIndexController.java
@@ -0,0 +1,152 @@
+package com.echo.p2p_project.server.gui;
+
+import com.echo.p2p_project.server.ServerMain;
+import com.echo.p2p_project.u_model.Peer;
+import com.echo.p2p_project.u_model.Resource;
+import com.sun.javafx.collections.ObservableListWrapper;
+import javafx.application.Platform;
+import javafx.beans.property.SimpleStringProperty;
+import javafx.beans.value.ObservableValue;
+import javafx.collections.MapChangeListener;
+import javafx.collections.ObservableList;
+import javafx.event.ActionEvent;
+import javafx.scene.control.*;
+import javafx.util.Callback;
+
+import java.util.ArrayList;
+
+/**
+ * @Author: WangYuyang
+ * @Date: 2021/10/23-17:21
+ * @Project: P2P_Project
+ * @Package: com.echo.p2p_project.server.gui
+ * @Description:
+ **/
+public class ServerIndexController {
+ public Button StartServerButton;
+ public TextArea LogField;
+ public Button StopServerButton;
+ public TableView uhpt_table;
+ public TableView uhrt_table;
+ public TreeView uhrt_tree;
+ public TableColumn UHPT_GUID;
+ public TableColumn UHPT_IP;
+ public TableColumn UHPT_PORT;
+ public TableColumn UHRT_GUID;
+ public TableColumn UHRT_NAME;
+
+
+ public void initialize() {
+ UHPT_GUID.setCellValueFactory(new Callback, ObservableValue>() {
+ @Override
+ public ObservableValue call(TableColumn.CellDataFeatures param) {
+ return new SimpleStringProperty(param.getValue().getGUID().toString());
+ }
+ });
+ UHPT_IP.setCellValueFactory(new Callback, ObservableValue>() {
+ @Override
+ public ObservableValue call(TableColumn.CellDataFeatures param) {
+ return new SimpleStringProperty(param.getValue().getIP().toString());
+ }
+ });
+ UHPT_PORT.setCellValueFactory(new Callback, ObservableValue>() {
+ @Override
+ public ObservableValue call(TableColumn.CellDataFeatures param) {
+ return new SimpleStringProperty(param.getValue().getP2P_port().toString());
+ }
+ });
+ UHRT_GUID.setCellValueFactory(new Callback, ObservableValue>() {
+ @Override
+ public ObservableValue call(TableColumn.CellDataFeatures param) {
+ return new SimpleStringProperty(param.getValue().getGUID().toString());
+ }
+ });
+ UHRT_NAME.setCellValueFactory(new Callback, ObservableValue>() {
+ @Override
+ public ObservableValue call(TableColumn.CellDataFeatures param) {
+ return new SimpleStringProperty(param.getValue().getName().toString());
+ }
+ });
+
+ ServerMain.UHPT.addListener(new MapChangeListener() {
+ @Override
+ public void onChanged(Change change) {
+ Platform.runLater(new Runnable() {
+ @Override
+ public void run() {
+
+
+ //update log
+ LogField.appendText(change.toString() + "\n\n");
+
+ //update table
+ ObservableList peers = new ObservableListWrapper(new ArrayList<>(ServerMain.UHPT.values()));
+ uhpt_table.setItems(peers);
+
+
+ //update tree
+ TreeItem rootItem = new TreeItem("Server");
+ rootItem.setExpanded(true);
+ for (Peer p : peers) {
+ TreeItem item = new TreeItem(p.getGUID().toString());
+ for (Resource r : p.possessing.values()) {
+ item.setExpanded(true);
+ TreeItem filename = new TreeItem(r.getName().toString());
+ item.getChildren().add(filename);
+ }
+ rootItem.getChildren().add(item);
+
+ }
+ uhrt_tree.setRoot(rootItem);
+ }
+ });
+
+
+ }
+ });
+ ServerMain.UHRT.addListener(new MapChangeListener() {
+ @Override
+ public void onChanged(Change change) {
+ Platform.runLater(new Runnable() {
+ @Override
+ public void run() {
+ //update log
+ LogField.appendText(change.toString() + "\n\n");
+
+ //update table
+ ObservableList resources = new ObservableListWrapper(new ArrayList<>(ServerMain.UHRT.values()));
+ uhrt_table.setItems(resources);
+
+ //update tree
+ ObservableList peers = new ObservableListWrapper(new ArrayList<>(ServerMain.UHPT.values()));
+ TreeItem rootItem = new TreeItem("Server");
+ rootItem.setExpanded(true);
+ for (Peer p : peers) {
+ TreeItem item = new TreeItem(p.getGUID().toString());
+ for (Resource r : p.possessing.values()) {
+ item.setExpanded(true);
+ TreeItem filename = new TreeItem(r.getName().toString());
+ item.getChildren().add(filename);
+ }
+ rootItem.getChildren().add(item);
+
+ }
+ uhrt_tree.setRoot(rootItem);
+
+ }
+ });
+ }
+ });
+
+
+ }
+
+ public void StartServerButtonPressed(ActionEvent actionEvent) {
+ ServerIndexApplication.init_server();
+
+ }
+
+ public void StopServerButtonPressed(ActionEvent actionEvent) {
+ ServerIndexApplication.stopServer();
+ }
+}
diff --git a/src/main/java/com/echo/p2p_project/server/interfaces/ConstructRegistry.java b/src/main/java/com/echo/p2p_project/server/interfaces/ConstructRegistry.java
new file mode 100644
index 0000000..18524c6
--- /dev/null
+++ b/src/main/java/com/echo/p2p_project/server/interfaces/ConstructRegistry.java
@@ -0,0 +1,20 @@
+package com.echo.p2p_project.server.interfaces;
+
+import com.echo.p2p_project.u_model.Peer;
+import com.echo.p2p_project.u_model.Resource;
+
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+import java.util.UUID;
+
+/**
+ * @Author: WangYuyang
+ * @Date: 2021/10/20-17:09
+ * @Project: P2P_Project
+ * @Package: com.echo.p2p_project.server.interfaces
+ * @Description:
+ **/
+public interface ConstructRegistry extends Remote {
+ Peer ConstructPeer(String name, String IP, Integer P2P_port) throws RemoteException;
+ Resource ConstructResource(UUID PeerGUID, String name) throws RemoteException;
+}
diff --git a/src/main/java/com/echo/p2p_project/server/interfaces/HeartBeatRegistry.java b/src/main/java/com/echo/p2p_project/server/interfaces/HeartBeatRegistry.java
new file mode 100644
index 0000000..27381b9
--- /dev/null
+++ b/src/main/java/com/echo/p2p_project/server/interfaces/HeartBeatRegistry.java
@@ -0,0 +1,16 @@
+package com.echo.p2p_project.server.interfaces;
+
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+import java.util.UUID;
+
+/**
+ * @Author: WangYuyang
+ * @Date: 2021/10/20-17:48
+ * @Project: P2P_Project
+ * @Package: com.echo.p2p_project.server.interfaces
+ * @Description:
+ **/
+public interface HeartBeatRegistry extends Remote {
+ Boolean heartBeat(UUID GUID) throws RemoteException;
+}
diff --git a/src/main/java/com/echo/p2p_project/server/interfaces/HelloRegistryFacade.java b/src/main/java/com/echo/p2p_project/server/interfaces/HelloRegistryFacade.java
new file mode 100644
index 0000000..c6e151b
--- /dev/null
+++ b/src/main/java/com/echo/p2p_project/server/interfaces/HelloRegistryFacade.java
@@ -0,0 +1,15 @@
+package com.echo.p2p_project.server.interfaces;
+
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+
+/**
+ * @Author: WangYuyang
+ * @Date: 2021/10/20-16:37
+ * @Project: P2P_Project
+ * @Package: com.echo.p2p_project.server.interfaces
+ * @Description:
+ **/
+public interface HelloRegistryFacade extends Remote {
+ String helloWorld(String name) throws RemoteException;
+}
diff --git a/src/main/java/com/echo/p2p_project/server/interfaces/SyncingRegistry.java b/src/main/java/com/echo/p2p_project/server/interfaces/SyncingRegistry.java
new file mode 100644
index 0000000..9f1ddf7
--- /dev/null
+++ b/src/main/java/com/echo/p2p_project/server/interfaces/SyncingRegistry.java
@@ -0,0 +1,20 @@
+package com.echo.p2p_project.server.interfaces;
+
+import com.echo.p2p_project.u_model.Peer;
+
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+import java.util.HashMap;
+import java.util.UUID;
+
+/**
+ * @Author: WangYuyang
+ * @Date: 2021/10/20-22:45
+ * @Project: P2P_Project
+ * @Package: com.echo.p2p_project.server.interfaces
+ * @Description:
+ **/
+public interface SyncingRegistry extends Remote {
+ Peer syncPeer(UUID GUID) throws RemoteException;
+ HashMap syncUHRT() throws RemoteException;
+}
diff --git a/src/main/java/com/echo/p2p_project/server/model/ConstructImpl.java b/src/main/java/com/echo/p2p_project/server/model/ConstructImpl.java
new file mode 100644
index 0000000..7f43693
--- /dev/null
+++ b/src/main/java/com/echo/p2p_project/server/model/ConstructImpl.java
@@ -0,0 +1,79 @@
+package com.echo.p2p_project.server.model;
+
+import com.echo.p2p_project.server.ServerMain;
+import com.echo.p2p_project.server.interfaces.ConstructRegistry;
+import com.echo.p2p_project.u_model.Peer;
+import com.echo.p2p_project.u_model.Resource;
+
+import java.rmi.RemoteException;
+import java.rmi.server.RMISocketFactory;
+import java.rmi.server.UnicastRemoteObject;
+import java.util.HashMap;
+import java.util.Random;
+import java.util.UUID;
+
+/**
+ * @Author: WangYuyang
+ * @Date: 2021/10/20-17:10
+ * @Project: P2P_Project
+ * @Package: com.echo.p2p_project.server.model
+ * @Description:
+ **/
+public class ConstructImpl extends UnicastRemoteObject implements ConstructRegistry {
+ /**
+ * Creates and exports a new UnicastRemoteObject object using an
+ * anonymous port.
+ *
+ * The object is exported with a server socket
+ * created using the {@link RMISocketFactory} class.
+ *
+ * @throws RemoteException if failed to export object
+ * @since JDK1.1
+ */
+ public ConstructImpl() throws RemoteException {
+ super();
+ }
+
+ @Override
+ public Peer ConstructPeer(String name, String IP_Address, Integer P2P_port) throws RemoteException {
+ UUID GUID = UUID.randomUUID();
+ if(ServerMain.UHPT.containsKey(GUID)) {
+ System.out.println("DUP GUID!!!!!!!");
+ return null;
+ }
+ String peerName = name;
+ String IP = IP_Address;
+ Integer port = P2P_port;
+ Random random = new Random();
+ Integer routingMetric = random.nextInt(100);
+ Peer peer = new Peer(GUID, peerName, IP, port, routingMetric);
+ ServerMain.UHPT.put(peer.getGUID(), peer);
+ System.out.println("Peer Reg: " + peer);
+ return peer;
+ }
+
+ @Override
+ public Resource ConstructResource(UUID PeerGUID, String name) throws RemoteException {
+ Peer peer = ServerMain.UHPT.get(PeerGUID);
+
+ //If peer not registered in center
+ if(peer == null)
+ return null; // Res register failed
+
+
+ UUID GUID = UUID.randomUUID();
+ //If duplicated GUID
+ if(ServerMain.UHPT.containsKey(GUID)) {
+ System.out.println("DUP GUID!!!!!!!");
+ return null; // Res register failed
+ }
+
+ String ResName = name;
+ Resource res = new Resource(GUID, ResName);
+ res.possessedBy.put(peer.getGUID(), peer);
+ ServerMain.UHRT.put(res.getGUID(), res);
+ ServerMain.UHPT.get(peer.getGUID()).possessing.put(res.getGUID(), res);
+ System.out.println("Resources Registered: " + res);
+ return res;
+ }
+}
diff --git a/src/main/java/com/echo/p2p_project/server/model/HeartBeatImpl.java b/src/main/java/com/echo/p2p_project/server/model/HeartBeatImpl.java
new file mode 100644
index 0000000..64b658b
--- /dev/null
+++ b/src/main/java/com/echo/p2p_project/server/model/HeartBeatImpl.java
@@ -0,0 +1,42 @@
+package com.echo.p2p_project.server.model;
+
+import com.echo.p2p_project.server.ServerMain;
+import com.echo.p2p_project.server.interfaces.HeartBeatRegistry;
+import com.echo.p2p_project.u_model.Peer;
+
+import java.rmi.RemoteException;
+import java.rmi.server.RMISocketFactory;
+import java.rmi.server.UnicastRemoteObject;
+import java.util.UUID;
+
+/**
+ * @Author: WangYuyang
+ * @Date: 2021/10/20-17:49
+ * @Project: P2P_Project
+ * @Package: com.echo.p2p_project.server.model
+ * @Description:
+ **/
+public class HeartBeatImpl extends UnicastRemoteObject implements HeartBeatRegistry {
+ /**
+ * Creates and exports a new UnicastRemoteObject object using an
+ * anonymous port.
+ *
+ *
The object is exported with a server socket
+ * created using the {@link RMISocketFactory} class.
+ *
+ * @throws RemoteException if failed to export object
+ * @since JDK1.1
+ */
+ public HeartBeatImpl() throws RemoteException {
+ super();
+ }
+
+ @Override
+ public Boolean heartBeat(UUID GUID) throws RemoteException {
+ Peer peer = ServerMain.UHPT.get(GUID);
+ if(peer == null)
+ return false;
+ peer.setMissedHartBeat(0);
+ return true;
+ }
+}
diff --git a/src/main/java/com/echo/p2p_project/server/model/HelloRegistryFacadeImpl.java b/src/main/java/com/echo/p2p_project/server/model/HelloRegistryFacadeImpl.java
new file mode 100644
index 0000000..2f7cb92
--- /dev/null
+++ b/src/main/java/com/echo/p2p_project/server/model/HelloRegistryFacadeImpl.java
@@ -0,0 +1,39 @@
+package com.echo.p2p_project.server.model;
+
+import com.echo.p2p_project.server.interfaces.HelloRegistryFacade;
+
+import java.rmi.RemoteException;
+import java.rmi.server.RMISocketFactory;
+import java.rmi.server.UnicastRemoteObject;
+
+/**
+ * @Author: WangYuyang
+ * @Date: 2021/10/20-16:38
+ * @Project: P2P_Project
+ * @Package: com.echo.p2p_project.server.model
+ * @Description:
+ **/
+public class HelloRegistryFacadeImpl extends UnicastRemoteObject implements HelloRegistryFacade {
+
+
+ /**
+ * Creates and exports a new UnicastRemoteObject object using an
+ * anonymous port.
+ *
+ *
The object is exported with a server socket
+ * created using the {@link RMISocketFactory} class.
+ *
+ * @throws RemoteException if failed to export object
+ * @since JDK1.1
+ */
+ public HelloRegistryFacadeImpl() throws RemoteException {
+ super();
+ System.out.println("HelloRegistryFacadeImpl");
+ }
+
+ @Override
+ public String helloWorld(String name) {
+ return "[Registry] Hi, " + name;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/echo/p2p_project/server/model/SyncingImpl.java b/src/main/java/com/echo/p2p_project/server/model/SyncingImpl.java
new file mode 100644
index 0000000..195ebae
--- /dev/null
+++ b/src/main/java/com/echo/p2p_project/server/model/SyncingImpl.java
@@ -0,0 +1,53 @@
+package com.echo.p2p_project.server.model;
+
+import com.echo.p2p_project.server.ServerMain;
+import com.echo.p2p_project.server.interfaces.SyncingRegistry;
+import com.echo.p2p_project.u_model.Peer;
+
+import java.rmi.RemoteException;
+import java.rmi.server.RMISocketFactory;
+import java.rmi.server.UnicastRemoteObject;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.UUID;
+
+/**
+ * @Author: WangYuyang
+ * @Date: 2021/10/20-22:45
+ * @Project: P2P_Project
+ * @Package: com.echo.p2p_project.server.model
+ * @Description:
+ **/
+public class SyncingImpl extends UnicastRemoteObject implements SyncingRegistry {
+ /**
+ * Creates and exports a new UnicastRemoteObject object using an
+ * anonymous port.
+ *
+ *
The object is exported with a server socket
+ * created using the {@link RMISocketFactory} class.
+ *
+ * @throws RemoteException if failed to export object
+ * @since JDK1.1
+ */
+ public SyncingImpl() throws RemoteException {
+ super();
+ }
+
+ @Override
+ public Peer syncPeer(UUID GUID) throws RemoteException{
+ Peer peer = ServerMain.UHPT.get(GUID);
+ if (peer==null)
+ return null;
+ return peer;
+ }
+
+ @Override
+ public HashMap syncUHRT() throws RemoteException {
+ HashMap hashMap = new LinkedHashMap();
+ for (UUID key: ServerMain.UHRT.keySet()) {
+ hashMap.put(key, ServerMain.UHRT.get(key));
+ }
+ HashMap map = hashMap;
+ return map;
+ }
+}
diff --git a/src/main/java/com/echo/p2p_project/server/model/WatchDog.java b/src/main/java/com/echo/p2p_project/server/model/WatchDog.java
new file mode 100644
index 0000000..cd29766
--- /dev/null
+++ b/src/main/java/com/echo/p2p_project/server/model/WatchDog.java
@@ -0,0 +1,63 @@
+package com.echo.p2p_project.server.model;
+
+import com.echo.p2p_project.server.ServerMain;
+import com.echo.p2p_project.u_model.Peer;
+import com.echo.p2p_project.u_model.Resource;
+
+import java.util.UUID;
+
+/**
+ * @Author: WangYuyang
+ * @Date: 2021/10/20-17:52
+ * @Project: P2P_Project
+ * @Package: com.echo.p2p_project.server.model
+ * @Description:
+ **/
+public class WatchDog {
+ private static Thread watch_thread;
+ private static Boolean running = true;
+
+ public static void startWatchDog() {
+ watch_thread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ while (running) {
+ for (int i = 0; i < ServerMain.UHPT.size(); i++) {
+ UUID uuid = (UUID) ServerMain.UHPT.keySet().toArray()[i];
+ Peer p = ServerMain.UHPT.get(uuid);
+// System.out.println(p);
+ if (p == null) {
+ System.out.println("REMOVED(peer is null): " + uuid);
+ ServerMain.UHPT.remove(uuid);
+ continue;
+ }
+ p.setMissedHartBeat(p.getMissedHartBeat() + 1);
+ if (p.getMissedHartBeat() > 5) {
+ System.out.println();
+ System.out.println("==================================== REMOVE =====================================");
+ System.out.println("PEER (peer MissedHartBeat > 5): " + uuid);
+ for (UUID guid : ServerMain.UHPT.get(uuid).possessing.keySet()) {
+ System.out.println("Resources: " + guid);
+ ServerMain.UHRT.remove(guid);
+ }
+ ServerMain.UHPT.remove(uuid);
+ System.out.println("=================================================================================");
+ }
+ }
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ System.out.println("WatchDog Interrupted");
+ }
+ }
+ }
+ });
+ watch_thread.start();
+ System.out.println("WatchDog Started !");
+ }
+
+ public static void stopWatchDog() {
+ running = false;
+// watch_thread.interrupt();
+ }
+}
diff --git a/src/main/java/com/echo/p2p_project/u_model/Peer.java b/src/main/java/com/echo/p2p_project/u_model/Peer.java
new file mode 100644
index 0000000..4836408
--- /dev/null
+++ b/src/main/java/com/echo/p2p_project/u_model/Peer.java
@@ -0,0 +1,151 @@
+package com.echo.p2p_project.u_model;
+
+import cn.hutool.Hutool;
+import com.sun.istack.internal.NotNull;
+
+import java.io.Serializable;
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.UUID;
+
+/**
+ * @Author: WangYuyang
+ * @Date: 2021/10/19-15:23
+ * @Project: P2P_Project
+ * @Package: com.echo.p2p_project.u_model
+ * @Description:
+ **/
+public class Peer implements Serializable,Comparable {
+ private UUID GUID;
+ private String name;
+ private String IP;
+ private Integer P2P_port;
+ private Integer routingMetric;
+ private Integer MissedHartBeat = 0;
+ //Only use this possessing in DHRT
+ public HashMap possessing = new LinkedHashMap();
+
+ public Peer(UUID GUID, String name, String IP, Integer p2P_port, Integer routingMetric) {
+ this.GUID = GUID;
+ this.name = name;
+ this.IP = IP;
+ P2P_port = p2P_port;
+ this.routingMetric = routingMetric;
+ }
+
+ public UUID getGUID() {
+ return GUID;
+ }
+
+ public void setGUID(UUID GUID) {
+ this.GUID = GUID;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getIP() {
+ return IP;
+ }
+
+ public void setIP(String IP) {
+ this.IP = IP;
+ }
+
+ public Integer getP2P_port() {
+ return P2P_port;
+ }
+
+ public void setP2P_port(Integer p2P_port) {
+ P2P_port = p2P_port;
+ }
+
+ public Integer getRoutingMetric() {
+ return routingMetric;
+ }
+
+ public void setRoutingMetric(Integer routingMetric) {
+ this.routingMetric = routingMetric;
+ }
+
+ public HashMap getPossessing() {
+ return possessing;
+ }
+
+ public void setPossessing(HashMap possessing) {
+ this.possessing = possessing;
+ }
+
+ public Integer getMissedHartBeat() {
+ return MissedHartBeat;
+ }
+
+ public void setMissedHartBeat(Integer missedHartBeat) {
+ MissedHartBeat = missedHartBeat;
+ }
+
+ @Override
+ public String toString() {
+ return "Peer{" +
+ "GUID=" + GUID +
+ ", name='" + name + '\'' +
+ ", IP='" + IP + '\'' +
+ ", P2P_port=" + P2P_port +
+ ", routingMetric=" + routingMetric +
+ ", MissedHartBeat=" + MissedHartBeat +
+ ", possessing=" + possessing.keySet() +
+ '}';
+ }
+
+ /**
+ * Compares this object with the specified object for order. Returns a
+ * negative integer, zero, or a positive integer as this object is less
+ * than, equal to, or greater than the specified object.
+ *
+ * The implementor must ensure sgn(x.compareTo(y)) ==
+ * -sgn(y.compareTo(x)) for all x and y . (This
+ * implies that x.compareTo(y) must throw an exception iff
+ * y.compareTo(x) throws an exception.)
+ *
+ *
The implementor must also ensure that the relation is transitive:
+ * (x.compareTo(y)>0 && y.compareTo(z)>0) implies
+ * x.compareTo(z)>0 .
+ *
+ *
Finally, the implementor must ensure that x.compareTo(y)==0
+ * implies that sgn(x.compareTo(z)) == sgn(y.compareTo(z)) , for
+ * all z .
+ *
+ *
It is strongly recommended, but not strictly required that
+ * (x.compareTo(y)==0) == (x.equals(y)) . Generally speaking, any
+ * class that implements the Comparable interface and violates
+ * this condition should clearly indicate this fact. The recommended
+ * language is "Note: this class has a natural ordering that is
+ * inconsistent with equals."
+ *
+ *
In the foregoing description, the notation
+ * sgn( expression ) designates the mathematical
+ * signum function, which is defined to return one of -1 ,
+ * 0 , or 1 according to whether the value of
+ * expression is negative, zero or positive.
+ *
+ * @param o the object to be compared.
+ * @return a negative integer, zero, or a positive integer as this object
+ * is less than, equal to, or greater than the specified object.
+ * @throws NullPointerException if the specified object is null
+ * @throws ClassCastException if the specified object's type prevents it
+ * from being compared to this object.
+ */
+ @Override
+ public int compareTo(@NotNull Object o) {
+ Peer p = (Peer) o;
+ return p.routingMetric - p.routingMetric;
+ }
+}
diff --git a/src/main/java/com/echo/p2p_project/u_model/Resource.java b/src/main/java/com/echo/p2p_project/u_model/Resource.java
new file mode 100644
index 0000000..9fbb097
--- /dev/null
+++ b/src/main/java/com/echo/p2p_project/u_model/Resource.java
@@ -0,0 +1,57 @@
+package com.echo.p2p_project.u_model;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.UUID;
+
+/**
+ * @Author: WangYuyang
+ * @Date: 2021/10/20-16:57
+ * @Project: P2P_Project
+ * @Package: com.echo.p2p_project.u_model
+ * @Description:
+ **/
+public class Resource implements Serializable {
+ private UUID GUID;
+ private String name;
+ public HashMap possessedBy = new LinkedHashMap<>();
+
+ public Resource(UUID GUID, String name) {
+ this.GUID = GUID;
+ this.name = name;
+ }
+
+ public UUID getGUID() {
+ return GUID;
+ }
+
+ public void setGUID(UUID GUID) {
+ this.GUID = GUID;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public HashMap getPossessedBy() {
+ return possessedBy;
+ }
+
+ public void setPossessedBy(HashMap possessedBy) {
+ this.possessedBy = possessedBy;
+ }
+
+ @Override
+ public String toString() {
+ return "Resource{" +
+ "GUID=" + GUID +
+ ", name='" + name + '\'' +
+ ", possessedBy=" + possessedBy.keySet() +
+ '}';
+ }
+}
diff --git a/src/main/resources/com/echo/p2p_project/client_index.fxml b/src/main/resources/com/echo/p2p_project/client_index.fxml
new file mode 100644
index 0000000..1f156ba
--- /dev/null
+++ b/src/main/resources/com/echo/p2p_project/client_index.fxml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/gui/client_index.fxml b/src/main/resources/gui/client_index.fxml
new file mode 100644
index 0000000..9defe98
--- /dev/null
+++ b/src/main/resources/gui/client_index.fxml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/gui/server_index.fxml b/src/main/resources/gui/server_index.fxml
new file mode 100644
index 0000000..0f13976
--- /dev/null
+++ b/src/main/resources/gui/server_index.fxml
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/target/classes/com/echo/p2p_project/client_index.fxml b/target/classes/com/echo/p2p_project/client_index.fxml
new file mode 100644
index 0000000..1f156ba
--- /dev/null
+++ b/target/classes/com/echo/p2p_project/client_index.fxml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/target/classes/gui/client_index.fxml b/target/classes/gui/client_index.fxml
new file mode 100644
index 0000000..9defe98
--- /dev/null
+++ b/target/classes/gui/client_index.fxml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/target/classes/gui/server_index.fxml b/target/classes/gui/server_index.fxml
new file mode 100644
index 0000000..0f13976
--- /dev/null
+++ b/target/classes/gui/server_index.fxml
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+