public class DistributedTextEditor extends JFrame { private static final long serialVersionUID = -1412971829037207445L; private static DistributedTextEditor editor; private JTextArea area1 = new JTextArea(20, 120); private JTextArea area2 = new JTextArea(20, 120); private JTextField ipaddress = new JTextField("localhost"); private JTextField portNumber = new JTextField("4242"); private EventReplayer er; private Thread ert; private JFileChooser dialog = new JFileChooser(System.getProperty("user.dir")); private String currentFile = "Untitled"; private boolean changed = false; private boolean connected = false; private boolean active = false; private boolean locked = false; private boolean listen = false; private DocumentEventCapturer dec; private ServerSocket serverSocket; private Socket clientSocket; private LamportClock lc; private int serverport; public DistributedTextEditor() { area1.setFont(new Font("Monospaced", Font.PLAIN, 12)); area2.setFont(new Font("Monospaced", Font.PLAIN, 12)); ((AbstractDocument) area1.getDocument()).setDocumentFilter(dec); area2.setEditable(false); Container content = getContentPane(); content.setLayout(new BoxLayout(content, BoxLayout.Y_AXIS)); JScrollPane scroll1 = new JScrollPane( area1, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); content.add(scroll1, BorderLayout.CENTER); JScrollPane scroll2 = new JScrollPane( area2, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); content.add(scroll2, BorderLayout.CENTER); content.add(ipaddress, BorderLayout.CENTER); content.add(portNumber, BorderLayout.CENTER); JMenuBar JMB = new JMenuBar(); setJMenuBar(JMB); JMenu file = new JMenu("File"); JMenu edit = new JMenu("Edit"); JMB.add(file); JMB.add(edit); file.add(Listen); file.add(Connect); file.add(Disconnect); file.addSeparator(); file.add(Save); file.add(SaveAs); file.add(Quit); edit.add(Copy); edit.add(Paste); edit.getItem(0).setText("Copy"); edit.getItem(1).setText("Paste"); Save.setEnabled(false); SaveAs.setEnabled(false); Disconnect.setEnabled(false); setDefaultCloseOperation(EXIT_ON_CLOSE); pack(); area1.addKeyListener(k1); area1.addMouseListener(m1); setTitle("Disconnected"); setVisible(true); area1.insert("Welcome to Hjortehandlerne's distributed text editor. \n", 0); this.addWindowListener(w1); } private WindowListener w1 = new WindowListener() { /** Kill all active threads */ @Override public void windowClosing(WindowEvent e) { disconnect(); } @Override public void windowActivated(WindowEvent e) {} @Override public void windowClosed(WindowEvent e) {} @Override public void windowDeactivated(WindowEvent e) {} @Override public void windowDeiconified(WindowEvent e) {} @Override public void windowIconified(WindowEvent e) {} @Override public void windowOpened(WindowEvent e) {} }; private KeyListener k1 = new KeyAdapter() { public void keyPressed(KeyEvent e) { changed = true; Save.setEnabled(true); SaveAs.setEnabled(true); } /** * The keyReleased event ensures that the caret-position is updated for both peers, when the * user moves the caret with the arrow-keys. */ public void keyReleased(KeyEvent e) { int left = e.VK_LEFT; int right = e.VK_RIGHT; int up = e.VK_UP; int down = e.VK_DOWN; int home = e.VK_HOME; int end = e.VK_END; int A = e.VK_A; int kc = e.getKeyCode(); if (kc == left || kc == right || kc == up || kc == down || kc == home || kc == end || (kc == A && e.isControlDown())) { if (dec == null) return; lc.increment(); TextEvent cu = new CaretUpdate(area1.getCaretPosition(), lc.getTimeStamp()); dec.sendObjectToAllPeers(cu); er.getEventHistoryLock().lock(); er.getEventHistory().add(cu); er.getEventHistoryLock().unlock(); } } }; /** * This mouselistener ensures that both peers have an updated caret-position for this user, when * he moves his caret by a mouseclick. */ private MouseListener m1 = new MouseAdapter() { public void mouseReleased(MouseEvent e) { if (e.getButton() == e.BUTTON1 && connected) { if (dec == null) return; lc.increment(); TextEvent cu = new CaretUpdate(area1.getCaretPosition(), lc.getTimeStamp()); dec.sendObjectToAllPeers(cu); er.getEventHistoryLock().lock(); er.getEventHistory().add(cu); er.getEventHistoryLock().unlock(); } } }; /** * This action is called when the Listen-button is fired. It creates a serversocket, and awaits a * connection. When the first connection is made, a ConnectionData object is sent to the other * peer, and this editor creates new data for the incomming peer. After the first connection is * made, waitForConnection is called, which waits for more connections. */ Action Listen = new AbstractAction("Listen") { public void actionPerformed(ActionEvent e) { saveOld(); final InetAddress local; active = true; try { local = InetAddress.getLocalHost(); Runnable server = new Runnable() { public void run() { serverport = Integer.parseInt(portNumber.getText()); int myID = getNewId(); registerOnPort(); editor.setTitleToListen(); clientSocket = waitForConnectionFromClient(); lc = new LamportClock(myID); area1.setText(""); resetArea2(); if (clientSocket != null) { listen = true; connected = true; dec = new DocumentEventCapturer(lc, editor); setDocumentFilter(dec); er = new EventReplayer(editor, dec, lc); er.updateCaretPos(myID, 0); ert = new Thread(er); ert.start(); try { ObjectOutputStream output = new ObjectOutputStream(clientSocket.getOutputStream()); ObjectInputStream input = new ObjectInputStream(clientSocket.getInputStream()); JoinNetworkRequest request = (JoinNetworkRequest) input.readObject(); int id = getNewId(); Peer peer = new Peer( editor, er, id, clientSocket, output, input, lc, clientSocket.getInetAddress().getHostAddress(), request.getPort()); ConnectionData cd = new ConnectionData( er.getEventHistory(), er.getAcknowledgements(), er.getCarets(), id, area1.getText(), lc.getTimeStamp(), lc.getID(), dec.getPeers(), serverSocket.getLocalPort()); dec.addPeer(peer); er.addCaretPos(id, 0); Thread t = new Thread(peer); t.start(); peer.writeObjectToStream(cd); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } } waitForConnection(); } }; Thread serverThread = new Thread(server); serverThread.start(); } catch (UnknownHostException e1) { e1.printStackTrace(); } changed = false; Disconnect.setEnabled(true); Listen.setEnabled(false); Connect.setEnabled(false); Save.setEnabled(false); SaveAs.setEnabled(false); } }; /** * waitForConnection waits for incomming connections. There are two cases. If we receive a * JoinNetworkRequest it means that a new peer tries to get into the network. We lock the entire * system, and sends a ConnectionData object to the new peer, from which he can connect to every * other peer. We add this new peer to our data. * * <p>If we receive a NewPeerDataRequest, it means that a new peer has received ConnectionData * from another peer in the network, and he is now trying to connect to everyone, including me. We * then update our data with the new peer. */ private void waitForConnection() { while (active) { Socket client = waitForConnectionFromClient(); if (client != null) { try { ObjectOutputStream output = new ObjectOutputStream(client.getOutputStream()); ObjectInputStream input = new ObjectInputStream(client.getInputStream()); Object o = input.readObject(); if (o instanceof JoinNetworkRequest) { JoinNetworkRequest request = (JoinNetworkRequest) o; dec.sendObjectToAllPeers(new LockRequest(lc.getTimeStamp())); waitForAllToLock(); setLocked(true); Thread.sleep(500); int id = getNewId(); Peer p = new Peer( editor, er, id, client, output, input, lc, client.getInetAddress().getHostAddress(), request.getPort()); ConnectionData cd = new ConnectionData( er.getEventHistory(), er.getAcknowledgements(), er.getCarets(), id, area1.getText(), lc.getTimeStamp(), lc.getID(), dec.getPeers(), serverSocket.getLocalPort()); p.writeObjectToStream(cd); dec.addPeer(p); Thread t = new Thread(p); t.start(); er.addCaretPos(id, 0); } else if (o instanceof NewPeerDataRequest) { NewPeerDataRequest request = (NewPeerDataRequest) o; Peer newPeer = new Peer( editor, er, request.getId(), client, output, input, lc, client.getInetAddress().getHostAddress(), request.getPort()); dec.addPeer(newPeer); er.addCaretPos(request.getId(), request.getCaretPos()); newPeer.writeObjectToStream(new NewPeerDataAcknowledgement(lc.getTimeStamp())); Thread t = new Thread(newPeer); t.start(); } } catch (IOException | ClassNotFoundException | InterruptedException e) { e.printStackTrace(); } } } } private void waitForAllToLock() { for (Peer p : dec.getPeers()) { while (!p.isLocked() && p.isConnected()) { try { Thread.sleep(100); } catch (InterruptedException e) { } } p.setLocked(false); } } /** * Will register this server on the port number portNumber. Will not start waiting for * connections. For this you should call waitForConnectionFromClient(). */ private void registerOnPort() { try { serverSocket = new ServerSocket(Integer.parseInt(portNumber.getText())); } catch (IOException e) { serverSocket = null; System.err.println("Cannot open server socket on port number" + portNumber.getText()); System.err.println(e); System.exit(-1); } } /** Closes the serversocket */ public void deregisterOnPort() { if (serverSocket != null) { try { serverSocket.close(); serverSocket = null; } catch (IOException e) { System.err.println(e); } } } /** * Waits for the next client to connect on port number portNumber or takes the next one in line in * case a client is already trying to connect. Returns the socket of the connection, null if there * were any failures. */ private Socket waitForConnectionFromClient() { Socket res = null; try { res = serverSocket.accept(); } catch (IOException e) { } return res; } /** * This action is called when the Connect-button is fired. It creates a ClientSocket with the * known peer, and opens a serversocket to listen for new peers. We send a JoinNetworkRequest to * the known peer, and receive ConnectionData from him. We then connect to all other peers in the * network, and update our data from the received ConnectionData. At last we call * waitForConnection() to wait for new connections from new peers, and finally we tell everyone to * unlock the system */ Action Connect = new AbstractAction("Connect") { public void actionPerformed(ActionEvent e) { saveOld(); area1.setText(""); resetArea2(); try { clientSocket = new Socket(ipaddress.getText(), Integer.parseInt(portNumber.getText())); Random r = new Random(); serverport = 10000 + r.nextInt(8999); // random port :D serverSocket = new ServerSocket(serverport); active = true; editor.setTitleToListen(); connected = true; ObjectOutputStream output = new ObjectOutputStream(clientSocket.getOutputStream()); ObjectInputStream input = new ObjectInputStream(clientSocket.getInputStream()); output.writeObject(new JoinNetworkRequest(serverport)); ConnectionData data = getConnectionData(clientSocket, input); lc = new LamportClock(data.getId()); lc.setMaxTime(data.getTs()); dec = new DocumentEventCapturer(lc, editor); er = new EventReplayer(editor, dec, lc); ert = new Thread(er); ert.start(); Peer peer = new Peer( editor, er, data.getHostId(), clientSocket, output, input, lc, clientSocket.getInetAddress().getHostAddress(), data.getPort()); dec.addPeer(peer); Thread thread = new Thread(peer); thread.start(); er.setAcknowledgements(data.getAcknowledgements()); er.setEventHistory(data.getEventHistory()); er.setCarets(data.getCarets()); er.addCaretPos(lc.getID(), 0); for (PeerWrapper p : data.getPeers()) { Socket socket; try { socket = connectToPeer(p.getIP(), p.getPort()); ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream()); ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream()); outputStream.writeObject( new NewPeerDataRequest(lc.getID(), serverSocket.getLocalPort(), 0)); Peer newPeer = new Peer( editor, er, p.getId(), socket, outputStream, inputStream, lc, p.getIP(), p.getPort()); dec.addPeer(newPeer); Thread t = new Thread(newPeer); t.start(); } catch (IOException ex) { continue; } } Thread t1 = new Thread( new Runnable() { @Override public void run() { waitForConnection(); } }); t1.start(); area1.setText(data.getTextField()); area1.setCaretPosition(0); setDocumentFilter(dec); dec.sendObjectToAllPeers(new UnlockRequest(lc.getTimeStamp())); changed = false; Connect.setEnabled(false); Disconnect.setEnabled(true); Listen.setEnabled(false); Save.setEnabled(false); SaveAs.setEnabled(false); } catch (NumberFormatException | IOException e1) { setTitle("Unable to connect"); } } private Socket connectToPeer(String ip, int port) { try { Socket peer = new Socket(ip, port); return peer; } catch (IOException e) { e.printStackTrace(); } return null; } private ConnectionData getConnectionData(Socket clientSocket, ObjectInputStream input) { ConnectionData res; try { Object o = input.readObject(); res = (ConnectionData) o; return res; } catch (IOException | ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } }; /** This action is called when the Disconnect-button is fired. */ Action Disconnect = new AbstractAction("Disconnect") { public void actionPerformed(ActionEvent e) { disconnect(); } }; /** * The disconnect method will stop the eventreplayer, and send null to the other peers, to stop * their reading from their streams. The GUI-menu is updated appropriately. */ public void disconnect() { setTitle("Disconnected"); active = false; if (connected == true) { er.stopStreamToQueue(); ert.interrupt(); setDocumentFilter(null); connected = false; setLocked(false); } deregisterOnPort(); Disconnect.setEnabled(false); Connect.setEnabled(true); Listen.setEnabled(true); Save.setEnabled(true); SaveAs.setEnabled(true); } Action Save = new AbstractAction("Save") { public void actionPerformed(ActionEvent e) { if (!currentFile.equals("Untitled")) saveFile(currentFile); else saveFileAs(); } }; Action SaveAs = new AbstractAction("Save as...") { public void actionPerformed(ActionEvent e) { saveFileAs(); } }; Action Quit = new AbstractAction("Quit") { public void actionPerformed(ActionEvent e) { saveOld(); System.exit(0); } }; ActionMap m = area1.getActionMap(); Action Copy = m.get(DefaultEditorKit.copyAction); Action Paste = m.get(DefaultEditorKit.pasteAction); private void saveFileAs() { if (dialog.showSaveDialog(null) == JFileChooser.APPROVE_OPTION) saveFile(dialog.getSelectedFile().getAbsolutePath()); } private void saveOld() { if (changed) { if (JOptionPane.showConfirmDialog( this, "Would you like to save " + currentFile + " ?", "Save", JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) saveFile(currentFile); } } private void saveFile(String fileName) { try { FileWriter w = new FileWriter(fileName); area1.write(w); w.close(); currentFile = fileName; changed = false; Save.setEnabled(false); } catch (IOException e) { } } public boolean getActive() { return active; } public DocumentFilter getDocumentFilter() { return dec; } public JTextArea getTextArea() { return area1; } public DocumentEventCapturer getDocumentEventCapturer() { return dec; } public void setDocumentFilter(DocumentFilter filter) { ((AbstractDocument) area1.getDocument()).setDocumentFilter(filter); } public void setErrorMessage(String s) { area2.setText("Error: " + s); } public void setTitleToListen() { try { String serverString = serverSocket.getInetAddress().getLocalHost().toString(); String serverIP = serverString.split("/")[1]; editor.setTitle("Listening on: " + serverIP + ":" + serverport); } catch (UnknownHostException e) { e.printStackTrace(); } } public void resetArea2() { area2.setText(""); } public static void main(String[] arg) { editor = new DistributedTextEditor(); } public void setLocked(boolean b) { locked = b; area1.setEnabled(!b); } public boolean getListen() { return listen; } public void setTextInArea2(String res) { area2.setText(res); } public EventReplayer getEventReplayer() { return er; } public int getNewId() { Random r = new Random(); return r.nextInt(1000000); } }
/** * A text editor created for my own use. Credit to * http://forum.codecall.net/topic/49721-simple-text-editor/ * * @author Corey Hickson * @version 0.1 */ public class CoreyTextEditor extends JFrame { // Variables to help initialize the text editor private JTextArea area = new JTextArea(20, 120); private JFileChooser dialog = new JFileChooser(System.getProperty("user.dir")); private String currentFile = "Untitled"; private boolean changed = false; /** Constructor to create the frame and its components */ public CoreyTextEditor() { // Create a scroll pane area.setFont(new Font("Monospaced", Font.PLAIN, 12)); JScrollPane scroll = new JScrollPane( area, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); add(scroll, BorderLayout.CENTER); // Adds the system default look and feel try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (ClassNotFoundException | InstantiationException | UnsupportedLookAndFeelException | IllegalAccessException e) { e.printStackTrace(); } // Create a menu bar JMenuBar JMB = new JMenuBar(); setJMenuBar(JMB); JMenu file = new JMenu("File"); JMenu edit = new JMenu("Edit"); JMB.add(file); JMB.add(edit); // Finishing our menu bar file.add(New); file.add(Open); file.add(Save); file.add(SaveAs); file.addSeparator(); file.add(Quit); edit.add(Cut); edit.add(Copy); edit.add(Paste); edit.getItem(0).setText("Cut"); edit.getItem(0).setIcon(new ImageIcon("cut.gif")); edit.getItem(1).setText("Copy"); edit.getItem(1).setIcon(new ImageIcon("copy.gif")); edit.getItem(2).setText("Paste"); edit.getItem(2).setIcon(new ImageIcon("paste.gif")); // Time to make a toolbar! JToolBar tool = new JToolBar(); add(tool, BorderLayout.NORTH); tool.add(New); tool.add(Open); tool.add(Save); tool.addSeparator(); JButton cut = tool.add(Cut); JButton cop = tool.add(Copy); JButton pas = tool.add(Paste); cut.setText(null); cut.setIcon(new ImageIcon("cut.gif")); cop.setText(null); cop.setIcon(new ImageIcon("copy.gif")); pas.setText(null); pas.setIcon(new ImageIcon("paste.gif")); Save.setEnabled(false); SaveAs.setEnabled(false); setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); pack(); /* KeyListener to change Save and SaveAs */ KeyListener k1 = new KeyAdapter() { public void keyPressed(KeyEvent e) { changed = true; Save.setEnabled(true); SaveAs.setEnabled(true); } }; area.addKeyListener(k1); setTitle(currentFile + " - CoreyTextEditor"); setVisible(true); } Action New = new AbstractAction("New", new ImageIcon("new.gif")) { public void actionPerformed(ActionEvent e) { saveOld(); area.setText(""); currentFile = "Untitled"; setTitle(currentFile + " - CoreyTextEditor"); changed = false; Save.setEnabled(false); SaveAs.setEnabled(false); } }; Action Open = new AbstractAction("Open", new ImageIcon("open.gif")) { public void actionPerformed(ActionEvent e) { saveOld(); if (dialog.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) { readInFile(dialog.getSelectedFile().getAbsolutePath()); } SaveAs.setEnabled(true); } }; Action Save = new AbstractAction("Save", new ImageIcon("save.gif")) { public void actionPerformed(ActionEvent e) { if (!currentFile.equals("Untitled")) { saveFile(currentFile); } else { saveFileAs(); } } }; Action SaveAs = new AbstractAction("Save as...") { public void actionPerformed(ActionEvent e) { saveFileAs(); } }; Action Quit = new AbstractAction("Quit") { public void actionPerformed(ActionEvent e) { saveOld(); System.exit(0); } }; ActionMap m = area.getActionMap(); Action Cut = m.get(DefaultEditorKit.cutAction); Action Copy = m.get(DefaultEditorKit.copyAction); Action Paste = m.get(DefaultEditorKit.pasteAction); private void saveFileAs() { if (dialog.showSaveDialog(null) == JFileChooser.APPROVE_OPTION) { saveFile(dialog.getSelectedFile().getAbsolutePath()); } } private void saveOld() { if (changed) { if (JOptionPane.showConfirmDialog( this, "Would you like to save " + currentFile + " ?", "Save", JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) { saveFile(currentFile); } } } private void readInFile(String fileName) { try { FileReader r = new FileReader(fileName); area.read(r, null); r.close(); currentFile = fileName; setTitle(currentFile + " - CoreyTextEditor"); changed = false; } catch (IOException e) { Toolkit.getDefaultToolkit().beep(); JOptionPane.showMessageDialog(this, "Editor can't find the file called " + fileName); } } private void saveFile(String fileName) { try { FileWriter w = new FileWriter(fileName); area.write(w); w.close(); currentFile = fileName; setTitle(currentFile + " - CoreyTextEditor"); changed = false; Save.setEnabled(false); } catch (IOException e) { // No handling done here } } public static void main(String[] args) { new CoreyTextEditor(); } }