/* * License: source-license.txt * If this code is used independently, copy the license here. */ package wombat.gui.text.sta; import java.awt.Color; import java.io.*; import java.net.*; import java.util.*; import javax.swing.*; import javax.swing.text.BadLocationException; import wombat.util.Base64; import wombat.util.errors.ErrorManager; import wombat.gui.text.LinedTextPane; import wombat.gui.text.SchemeTextArea; /** * Designed to share a text area between two or more users over a local network. */ public class SharedTextArea extends SchemeTextArea { private static final long serialVersionUID = 2220038488909999007L; public static final boolean NETWORK_DEBUG = true; public static int NEXT_PORT = 5309; boolean Running = true; String DocumentName; STAServer ST; STAClient CT; NetworkedDocumentListener NDL; /** * Create the shared text area. * @param host The address of the server * @param port The port to connect on * @param server We should host the server */ public SharedTextArea(InetAddress host, int port, boolean server) { super(true, true); DocumentName = encodeAddress(host, port); // Custom text pane that displays server information. code = new LinedTextPane(this); add(new JScrollPane(code)); // Distinguish it from other text areas. code.setBackground(new Color(240, 255, 240)); // Attach the document listener. NDL = new NetworkedDocumentListener(this); code.getDocument().addDocumentListener(NDL); // Create the server if requested, the client either way. if (server) { try { ST = new STAServer(this, port); } catch(Exception e) { ErrorManager.logError("Unable to create server: " + e); code.setText("Unable to create server: " + e); e.printStackTrace(); } try { CT = new STAClient(this, InetAddress.getLocalHost(), port); } catch(Exception e) { ErrorManager.logError("Unable to connect to server on localhost: " + e); code.setText("Unable to connect to server on localhost: " + e); e.printStackTrace(); } } else { try { CT = new STAClient(this, InetAddress.getLocalHost(), port); } catch(Exception e) { ErrorManager.logError("Unable to connect to server at " + host + ":" + port + ": " + e); code.setText("Unable to connect to server at " + host + ":" + port + ": " + e); e.printStackTrace(); } } } /** * Process a received line. * @param line The new line input. * @param onServer If we're doing something on the host, versus on a join'er. * @return Any response to be sent back. */ protected synchronized void processLocal(String line) { NDL.suppress(true); try { String[] parts = line.split(","); String lineMsgType = parts[0]; String[] args = Arrays.copyOfRange(parts, 1, parts.length); // Text has been inserted into the remote document. if ("insert".equals(lineMsgType)) { int off = Integer.parseInt(args[0]); String str = new String(Base64.decode(args[2]), "UTF-8"); code.getDocument().insertString(off, str, null); } // Text has been removed from the remote document. else if ("remove".equals(lineMsgType)) { int off = Integer.parseInt(args[0]); int len = Integer.parseInt(args[1]); code.getDocument().remove(off, len); } // Someone has decided to say hello, send them our document else if ("hello".equals(lineMsgType)) { String str = Base64.encodeBytes(code.getText().getBytes("UTF-8")); CT.send(makeMessage("sync", str)); } // We have a sync request, honor it else if ("sync".equals(lineMsgType)) { try { String str = new String(Base64.decode(args[0]), "UTF-8"); code.setText(str); } catch(Exception e) { ErrorManager.logError("Unable to sync documents: " + e); e.printStackTrace(); } } // Someone wants to check to see if we're in sync else if ("check-sync".equals(lineMsgType)) { try { int usHash = code.getText().hashCode(); int themHash = Integer.parseInt(args[0]); if (usHash != themHash) { String str = Base64.encodeBytes(code.getText().getBytes("UTF-8")); CT.send(makeMessage("sync", str)); } } catch(Exception e) { ErrorManager.logError("Unable to check sync status: " + e); e.printStackTrace(); } } } catch(BadLocationException ex) { ErrorManager.logError("Unable to process local line (BadLocation): " + ex); ex.printStackTrace(); } catch (UnsupportedEncodingException ex) { ErrorManager.logError("Unable to process local line (UnsupportedEncoding): " + ex); ex.printStackTrace(); } catch (IOException ex) { ErrorManager.logError("Unable to process local line (IO): " + ex); ex.printStackTrace(); } NDL.suppress(false); } /** * Encode an IP:port address as a Base64 string. * @param host The host to encode * @param port The port to encode * @return The string. */ public static String encodeAddress(InetAddress host, int port) { return Base64.encodeBytes(new byte[]{ host.getAddress()[0], host.getAddress()[1], host.getAddress()[2], host.getAddress()[3], (byte) ((port / 256) - 128), (byte) ((port % 256) - 128) }); } /** * Get the IP out of an encoded string. * @param str The encoded string. * @return The IP */ public static InetAddress decodeAddressHost(String str) { try { return Inet4Address.getByAddress(Arrays.copyOf(Base64.decode(str), 4)); } catch (Exception e) { ErrorManager.logError("Unable decode host from address: " + e); e.printStackTrace(); } return null; } /** * Get the port out of an encoded string. * @param str The encoded string. * @return The port */ public static int decodeAddressPort(String str) { try { byte[] bytes = Base64.decode(str); return ((((int) bytes[4]) + 128) * 256) + (((int) bytes[5]) + 128); } catch (Exception e) { ErrorManager.logError("Unable decode port from address: " + e); e.printStackTrace(); } return -1; } /** * Make a message to send over the network. * @param cmd The command to send. * @param args Any necessary arguments. * @return */ public String makeMessage(String cmd, Object ... args) { StringBuilder msg = new StringBuilder(); msg.append(cmd); for (Object o : args) { msg.append(','); msg.append(o.toString()); } return msg.toString(); } /** * Get the document name. * @return Duh. */ public String getDocumentName() { return DocumentName; } /** * */ public boolean isShared() { return Running && CT != null; } /** * */ public void disconnect() { Running = false; DocumentName = null; ST = null; CT = null; code.setBackground(Color.WHITE); } }