//lots of imports here import java.util.*; // name-IP address map import java.net.*; // several Socket classes and InetAddress import java.io.*; // IOException and a couple *Writers import java.awt.*; // GUI elements import java.awt.event.*; // GUI event handlers import java.util.regex.*; // regular expression library, used to prevent a minor exploit // changed the class/file name, class names should usually start with a capital // subclass java.awt.Frame (a window) so we can be displayed on-screen // implement WindowListener and ActionListener (from java.awt.event) so we can handle our own events // implement java.lang.Runnable so we can listen for incoming messages in a seperate thread // (I've reciently realized multi-threading wasn't actually necessary...) public class uChat extends Frame implements WindowListener, ActionListener, Runnable { // NOTE: within source code, I tend to use "us", "we", "our", etc. to refer to the class/program and it's actions. Don't ask why, I don't know. // network-related vars InetAddress addrs; // broadcast IP address, declared below int port = 12121; // communication port number DatagramSocket sock; // multi-purpose UDP socket // GUI elements TextField name; // top text field TextArea disp; // main text area TextField input; // bottom text field Button send; // obvious Button connect; // not really "connecting" but close enough MaplookupTable; // keeps track of which user(name) is at which IP address // constructor, throws any Exception because I was too lazy to list each one that could happen // NOT A BEST PRACTICE public uChat() throws Exception { super("uChat"); // call superclass constructor: Frame(String s); sets window title // retrieve broadcast IP address, method may (but shouldn't with a numerical address) throw UnknownHostException addrs = InetAddress.getByName("255.255.255.255"); // initialze Socket, and bind to the port sock = new DatagramSocket(port); // initialize the lookup table as a HashMap lookupTable = new HashMap (); // set up GUI elements disp = new TextArea("",0,0,TextArea.SCROLLBARS_VERTICAL_ONLY); // no starting text, default height and width, scrollbar display policy input = new TextField(); name = new TextField(); send = new Button("Send"); // buttons need connect = new Button("Join"); // labels // prevent editing the message display box disp.setEditable(false); //GUI layout design can be pretty hairy // a java.awt.Panel is a simple container // constructor accept java.awt.LayoutManager as an arg, BorderLayout is a subtype Panel pnl1 = new Panel(new BorderLayout()); // add an anonymous label, followed by our name field and connect button pnl1.add(new Label("Name:"), BorderLayout.WEST); pnl1.add(name, BorderLayout.CENTER); pnl1.add(connect, BorderLayout.EAST); // the lower Panel, with the input field and send button Panel pnl2 = new Panel(new BorderLayout()); pnl2.add(input,BorderLayout.CENTER); pnl2.add(send,BorderLayout.EAST); // set the our layout and add the panels and display area setLayout(new BorderLayout()); add(pnl1, BorderLayout.NORTH); add(disp, BorderLayout.CENTER); add(pnl2, BorderLayout.SOUTH); // set up a new thread and pass us as the Runnable Thread t = new Thread(this); // start the thread t.start(); // add us as ActionListener for the main GUI elements send.addActionListener(this); connect.addActionListener(this); input.addActionListener(this); name.addActionListener(this); // add us as a WindowListener for ourself addWindowListener(this); // display the main window setVisible(true); } // end of constructor // override Frame.setVisible(boolean b) to add some functionallity public void setVisible(boolean b) { super.setVisible(b); // call Frame.setVisible() to actually show/hide the window if(b) { // if we're being shown... pack(); // ... shrink/grow the window so that each element gets it's 'prefferred size" Dimension scrn = Toolkit.getDefaultToolkit().getScreenSize(); // get the screen's size (in pixels) and... // ... calculater a point exactly our size up and left from the bottom corner of the screen and move us there setLocation((scrn.width-this.getSize().width)/1,(scrn.height-this.getSize().height)/1); // the result of this is causing the window to appear in the lower-right corner of the screen } } // end of setVisible public static void main(String arg[]) { // this is actually a stub, // call the constructor (from inside a try block), but don't keep a reference to the created object // (it'll dislay itself, so we don't need to) try { new uChat(); } // catch any Exception that pops up and pass it to the handle(Excpetion) method (it's way below) catch(Exception e) { handle(e); } } // end of main // a beast of a method here, this is from the ActionListener interface // it's called any time an ActionEvent occurs, like clicking a button or pressing enter in a text field // it's pretty convoluted. I'll devote an entire post (or more) to explaining it later. public void actionPerformed(ActionEvent evt) { if(name.getText().trim().equals("")) {return;} byte[] data= null; try { if(evt.getSource() == input || evt.getSource() == send) { if(input.getText().trim().equals("")) {return;} if(!connect.getLabel().equals("Leave")) {return;} { // a little pre-processing to prevent old-skool hax (exploiting text wrapping) // replaces every instance of 3+ space chars with 2 space chars input.setText( Pattern.compile(" {3,}") .matcher(input.getText().trim()) .replaceAll(" ") ); //end of setText() } data = "SAY:".concat(input.getText()).getBytes("ISO-8859-1"); } else if(evt.getSource() == connect || evt.getSource() == name) { if(connect.getLabel() == "Join") { name.setEditable(false); data = "JOIN".concat(name.getText()).getBytes("ISO-8859-1"); connect.setLabel("Leave"); input.requestFocus(); } else if (connect.getLabel() == "Leave") { data = "LEAV".concat(name.getText()).getBytes("ISO-8859-1"); connect.setLabel("Join"); name.setEditable(true); } else { return; /* shouldn't happen*/ } } else { return; /* shouldn't happen*/ } DatagramPacket out = new DatagramPacket(data, data.length,addrs,port); sock.send(out); } catch(Exception e) { handle(e); } input.setText(""); } // a "flag" to tell the listener thread to stop boolean die = false; // the run() method is from the java.lang.Runnable interface // this code will run in a seperate thread // it listens for incoming UDP packets, interprets them, and updated the display text area accordingly // it's fairly complicated to, and will get it's own post later public void run() { while(!die) { byte[] data = new byte[1024]; DatagramPacket in = new DatagramPacket(data, data.length); try { sock.receive(in); String tmpIN = new String(data, "ISO-8859-1"); if(tmpIN.startsWith("SAY:")) { String who = lookupTable.get(in.getAddress()); disp.append(who+": "+new String(data, 4, in.getLength()-4, "ISO-8859-1")+"\n"); toFront(); Toolkit.getDefaultToolkit().beep(); } else if(tmpIN.startsWith("JOIN")) { InetAddress tmp = in.getAddress(); String tmpS = new String(data, 4,in.getLength()-4, "ISO-8859-1"); lookupTable.put(tmp, tmpS); disp.append(tmpS+'('+tmp.getHostAddress()+')'+" has joined\n"); byte[] send = "HERE".concat(name.getText()).getBytes("ISO-8859-1"); DatagramPacket outbound = new DatagramPacket(send, send.length, tmp, port); sock.send(outbound); } else if(tmpIN.startsWith("HERE")) { InetAddress tmp = in.getAddress(); String tmpS = new String(data, 4,in.getLength()-4, "ISO-8859-1"); lookupTable.put(tmp, tmpS); if(!tmpS.equals(name.getText())) { //needed to suppress "you are here" disp.append(tmpS+'('+tmp.getHostAddress()+')'+" is here\n"); } } else if(tmpIN.startsWith("LEAV")) { InetAddress tmp = in.getAddress(); String tmpS = new String(data, 4,in.getLength()-4, "ISO-8859-1"); lookupTable.remove(tmp); disp.append(tmpS+'('+tmp.getHostAddress()+')'+" has left\n"); } else { /* unknown, silently ignore */ } } catch(Exception e) { handle(e); } } try { sock.close(); } catch(Exception e) { handle(e); } } // from the WindowListener interface, this method is called when the user asks the window to close by clicking it's exit button // it does a little clean-up: announcing to other uChaters that we're leaving public void windowClosing(WindowEvent evt) { setVisible(false); // hide the window // change the flag, telling the listener thread to stop die = true; // if we're connected if(connect.getLabel() == "Join") { //only send LEAV if we're JOINed try { //build and send packet announcing that we're leaving byte[] data = "LEAV".concat(name.getText()).getBytes("ISO-8859-1"); DatagramPacket out = new DatagramPacket(data, data.length,addrs,port); sock.send(out); } catch(Exception e) { handle(e); } // if an exception occurs, pass it to handle(Exception) } // this frees all resources associated with this window, allowing the event-processing threads to stop dispose(); // this method explicitly tells the JMV to exit, but isn't needed as all threads should be ready to stop // System.exit(0); } // these are also part of the WindowListner interface, but aren't used by this program public void windowOpened(WindowEvent evt) { } public void windowClosed(WindowEvent evt) { } public void windowIconified(WindowEvent evt) { } public void windowDeiconified(WindowEvent evt) { } public void windowActivated(WindowEvent evt) { } public void windowDeactivated(WindowEvent evt) { } // all purpose Exception handler. // NOT REMOTELY A BEST PRACTICE but sufficient for this small program // static, so it can be accessed from main() public static void handle(Exception e) { // set up a pair of writers to convert the Excpetion's stack trace to a string StringWriter sb = new StringWriter(); PrintWriter out = new PrintWriter(sb); // dump the stack trace to the above writer and System.out (aka STDOUT, aka the terminal window) e.printStackTrace(out); e.printStackTrace(System.out); // make a new text area, default sizes, with the stack trace (via the writers) as its content TextArea disp = new TextArea(sb.getBuffer().toString()); // simple frame, set its layout Frame f0 = new Frame("Oops"); f0.setLayout(new BorderLayout()); // anonymous inner class as event listener for the frame, more in another post f0.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent evt) { ((Window)evt.getSource()).dispose(); } }); // add a label, then add the text area from above f0.add(new Label("Oops, something went wrong. uC may or may not still work."), BorderLayout.NORTH); f0.add(disp, BorderLayout.CENTER); // pack() and show the frame f0.pack(); f0.setVisible(true); // if you crash uChat (hint: start another instance while one's running), you'll see this method // in action. it's not a pretty error message, and would tell your average user absolutely nothing. // to be honest, this was 75% to shut the compiler up about "uncaught exceptions", 25% for debugging } }
First time here? Please, start at the beginning.
uChat Source
Same as in Part 2.1, except with the complicated parts still in (still no comments there, sorry):
Subscribe to:
Posts (Atom)
No comments:
Post a Comment