First time here? Please, start at the beginning.

Monday, August 23

Part 2.1: uChat Source explained (mostly)

Here's the (mostly) annotated and explained source for uChat. The really rough parts are left out for now, they'll get their own posts a bit later.

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

 Map lookupTable; // 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