I can't stress enough the importance of copy-pasting this into your editor of choice, the word wrap is going to make this impossible to dechpher. I'll see what I can do about the site layout.
//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) { /* left out for now */ } // 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() { // this loop does the hard part while(!die) { /* left out for now */ } // we've been told to stop, but we have a little clean up to do try { sock.close(); // close the socket } 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 // 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) } // change the flag, telling the listener thread to stop die = true; // 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 } }
Hopefully, with what you already know and the comments to help with what you don't, most of that makes at least some sense.
Not sure what I'll cover next... there's 3 or 4 topics that need to be covered. I should probably talk about multi-threading; there's a fair bit of network communication going on (that'll be at least 2 posts); I could go into how the interface goes together; need to touch on inner classes, maybe exception handling.
Anyway, that should be easier to understand than what I'd posted before. I'll update the top-bar page in a couple minutes.
No comments:
Post a Comment